From ec43b90ddd5e063233f6c16189365e815d060fb3 Mon Sep 17 00:00:00 2001 From: ris Date: Sat, 21 Dec 2024 00:48:45 -0500 Subject: [PATCH] Initial commit --- .cargo/config.toml | 13 + .gitignore | 1 + .gitmodules | 9 + .stfolder/syncthing-folder-dcf9ee.txt | 5 + .stignore | 1 + .../examples/basic/Cargo~20241114-122315.toml | 18 + .../blinky-async/Cargo~20241114-122315.toml | 15 + .../embassy_in_the_wild~20241114-122314.adoc | 24 + .../pages/new_project~20241114-122315.adoc | 202 + .../CHANGELOG~20241114-122315.md | 87 + .../Cargo~20241114-122315.toml | 191 + .../build_common~20241114-122315.rs | 145 + .../embassy-executor/build~20241114-122314.rs | 110 + .../src/lib~20241114-122315.rs | 151 + .../src/raw/waker~20241114-122314.rs | 71 + .../src/chips/nrf5340_app~20241114-122314.rs | 580 ++ .../embassy-nrf/src/lib~20241114-122314.rs | 673 +++ .../embassy-rp/Cargo~20241114-122314.toml | 151 + .../embassy-rp/src/lib~20241114-122315.rs | 691 +++ .../src/uart/mod~20241114-122314.rs | 1499 ++++++ .../embassy-stm32/Cargo~20241114-122314.toml | 1627 ++++++ .../embassy-stm32/src/lib~20241114-122315.rs | 541 ++ .../src/rcc/h~20241114-122315.rs | 1007 ++++ .../embassy-sync/src/watch~20241114-122315.rs | 1109 ++++ .../embassy-time/Cargo~20241114-122314.toml | 434 ++ .../nrf/Cargo~20241114-122315.toml | 34 + .../application/rp/Cargo~20241114-122315.toml | 36 + .../stm32f3/Cargo~20241114-122314.toml | 31 + .../stm32f7/Cargo~20241114-122314.toml | 32 + .../stm32h7/Cargo~20241114-122315.toml | 32 + .../stm32l0/Cargo~20241114-122315.toml | 31 + .../stm32l1/Cargo~20241114-122315.toml | 31 + .../stm32l4/Cargo~20241114-122315.toml | 31 + .../stm32wb-dfu/Cargo~20241114-122314.toml | 32 + .../stm32wl/Cargo~20241114-122314.toml | 31 + .../lpc55s69/Cargo~20241114-122315.toml | 22 + .../nrf-rtos-trace/Cargo~20241114-122314.toml | 37 + .../examples/nrf51/Cargo~20241114-122314.toml | 20 + .../nrf52810/Cargo~20241114-122315.toml | 24 + .../nrf52840/Cargo~20241114-122314.toml | 39 + .../nrf5340/Cargo~20241114-122315.toml | 30 + .../nrf9151/ns/Cargo~20241114-122314.toml | 20 + .../nrf9151/s/Cargo~20241114-122315.toml | 20 + .../nrf9160/Cargo~20241114-122315.toml | 26 + .../examples/rp/Cargo~20241114-122315.toml | 82 + .../examples/rp23/Cargo~20241114-122314.toml | 80 + .../examples/std/Cargo~20241114-122314.toml | 29 + .../stm32c0/Cargo~20241114-122314.toml | 24 + .../stm32f0/Cargo~20241114-122314.toml | 22 + .../stm32f1/Cargo~20241114-122314.toml | 31 + .../stm32f2/Cargo~20241114-122315.toml | 25 + .../stm32f3/Cargo~20241114-122315.toml | 29 + .../stm32f334/Cargo~20241114-122315.toml | 25 + .../stm32f4/Cargo~20241114-122314.toml | 38 + .../stm32f469/Cargo~20241114-122314.toml | 22 + .../stm32f7/Cargo~20241114-122314.toml | 36 + .../stm32g0/Cargo~20241114-122315.toml | 29 + .../stm32g4/Cargo~20241114-122314.toml | 29 + .../stm32h5/Cargo~20241114-122315.toml | 72 + .../stm32h7/Cargo~20241114-122314.toml | 75 + .../stm32h735/Cargo~20241114-122315.toml | 61 + .../stm32h755cm4/Cargo~20241114-122314.toml | 75 + .../stm32h755cm7/Cargo~20241114-122314.toml | 75 + .../stm32h7b0/Cargo~20241114-122314.toml | 74 + .../stm32h7rs/Cargo~20241114-122314.toml | 73 + .../stm32l0/Cargo~20241114-122314.toml | 30 + .../stm32l1/Cargo~20241114-122315.toml | 26 + .../stm32l4/Cargo~20241114-122315.toml | 39 + .../stm32l5/Cargo~20241114-122315.toml | 35 + .../stm32u0/Cargo~20241114-122314.toml | 29 + .../stm32u5/Cargo~20241114-122315.toml | 34 + .../stm32wb/Cargo~20241114-122315.toml | 56 + .../stm32wba/Cargo~20241114-122315.toml | 25 + .../stm32wl/Cargo~20241114-122315.toml | 27 + .../examples/wasm/Cargo~20241114-122315.toml | 21 + .../tests/nrf/Cargo~20241114-122315.toml | 114 + .../tests/riscv32/Cargo~20241114-122315.toml | 46 + .../tests/rp/Cargo~20241114-122314.toml | 68 + .../tests/stm32/Cargo~20241114-122315.toml | 235 + .stversions/ledisa/Cargo~20241113-194335.lock | 189 + .stversions/ledisa/Cargo~20241113-194335.toml | 16 + .stversions/ledisa/src/lib~20241113-194335.rs | 32 + .stversions/ledisa/~20241113-194335.gitignore | 1 + .stversions/src/.main.rs~20241101-222904.swp | Bin 0 -> 4096 bytes .stversions/src/.main.rs~20241101-225559.swp | Bin 0 -> 16384 bytes .stversions/src/.main.rs~20241101-225609.swp | Bin 0 -> 16384 bytes .stversions/src/.main.rs~20241101-231156.swp | Bin 0 -> 16384 bytes .stversions/src/.main.rs~20241101-235650.swp | Bin 0 -> 20480 bytes .stversions/src/.main.rs~20241101-235710.swp | Bin 0 -> 20480 bytes .stversions/src/.main.rs~20241102-134736.swp | Bin 0 -> 16384 bytes .stversions/src/.main.rs~20241106-001754.swp | Bin 0 -> 16384 bytes .stversions/src/.main.rs~20241106-001814.swp | Bin 0 -> 20480 bytes .stversions/src/main~20241030-005119.rs | 400 ++ .stversions/src/main~20241101-191805.rs | 400 ++ .stversions/src/main~20241101-194024.rs | 400 ++ .stversions/src/main~20241101-221415.rs | 400 ++ .stversions/src/main~20241101-225353.rs | 400 ++ .stversions/src/main~20241101-225609.rs | 400 ++ .stversions/src/main~20241101-231155.rs | 400 ++ .stversions/src/main~20241101-235710.rs | 400 ++ .stversions/src/main~20241102-124639.rs | 400 ++ .stversions/src/main~20241102-133327.rs | 400 ++ .stversions/src/main~20241106-001814.rs | 400 ++ Cargo.lock | 2332 ++++++++ Cargo.toml | 145 + build.rs | 36 + embassy/.gitattributes | 43 + embassy/.github/ci/book.sh | 17 + embassy/.github/ci/build-nightly.sh | 29 + embassy/.github/ci/build-xtensa.sh | 37 + embassy/.github/ci/build.sh | 34 + embassy/.github/ci/crlf.sh | 17 + embassy/.github/ci/doc.sh | 64 + embassy/.github/ci/rustfmt.sh | 12 + embassy/.github/ci/test-nightly.sh | 17 + embassy/.github/ci/test.sh | 36 + embassy/.github/workflows/matrix-bot.yml | 44 + embassy/.gitignore | 11 + embassy/.vscode/.gitignore | 4 + embassy/.vscode/extensions.json | 11 + embassy/.vscode/settings.json | 57 + embassy/LICENSE-APACHE | 201 + embassy/LICENSE-CC-BY-SA | 428 ++ embassy/LICENSE-MIT | 25 + embassy/NOTICE.md | 16 + embassy/README.md | 159 + embassy/ci-nightly.sh | 25 + embassy/ci-xtensa.sh | 38 + embassy/ci.sh | 316 ++ embassy/cyw43-firmware/43439A0.bin | Bin 0 -> 231077 bytes embassy/cyw43-firmware/43439A0_btfw.bin | Bin 0 -> 6164 bytes embassy/cyw43-firmware/43439A0_clm.bin | Bin 0 -> 984 bytes .../LICENSE-permissive-binary-license-1.0.txt | 49 + embassy/cyw43-firmware/README.md | 14 + embassy/cyw43-pio/CHANGELOG.md | 17 + embassy/cyw43-pio/Cargo.toml | 29 + embassy/cyw43-pio/README.md | 3 + embassy/cyw43-pio/src/lib.rs | 237 + embassy/cyw43/CHANGELOG.md | 23 + embassy/cyw43/Cargo.toml | 48 + embassy/cyw43/README.md | 46 + embassy/cyw43/src/bluetooth.rs | 508 ++ embassy/cyw43/src/bus.rs | 388 ++ embassy/cyw43/src/consts.rs | 670 +++ embassy/cyw43/src/control.rs | 740 +++ embassy/cyw43/src/countries.rs | 481 ++ embassy/cyw43/src/events.rs | 400 ++ embassy/cyw43/src/fmt.rs | 270 + embassy/cyw43/src/ioctl.rs | 127 + embassy/cyw43/src/lib.rs | 299 ++ embassy/cyw43/src/nvram.rs | 48 + embassy/cyw43/src/runner.rs | 666 +++ embassy/cyw43/src/structs.rs | 555 ++ embassy/cyw43/src/util.rs | 20 + embassy/docs/Makefile | 8 + embassy/docs/README.md | 29 + .../docs/examples/basic/.cargo/config.toml | 9 + embassy/docs/examples/basic/Cargo.toml | 18 + embassy/docs/examples/basic/build.rs | 35 + embassy/docs/examples/basic/memory.x | 7 + embassy/docs/examples/basic/src/main.rs | 26 + embassy/docs/examples/examples | 1 + .../layer-by-layer/.cargo/config.toml | 14 + .../docs/examples/layer-by-layer/Cargo.toml | 21 + .../layer-by-layer/blinky-async/Cargo.toml | 15 + .../layer-by-layer/blinky-async/src/main.rs | 23 + .../layer-by-layer/blinky-hal/Cargo.toml | 14 + .../layer-by-layer/blinky-hal/src/main.rs | 21 + .../layer-by-layer/blinky-irq/Cargo.toml | 14 + .../layer-by-layer/blinky-irq/src/main.rs | 93 + .../layer-by-layer/blinky-pac/Cargo.toml | 14 + .../layer-by-layer/blinky-pac/src/main.rs | 53 + embassy/docs/images/bootloader_flash.png | Bin 0 -> 32147 bytes embassy/docs/images/embassy_executor.drawio | 1 + embassy/docs/images/embassy_executor.png | Bin 0 -> 121382 bytes embassy/docs/images/embassy_irq.drawio | 1 + embassy/docs/images/embassy_irq.png | Bin 0 -> 134158 bytes embassy/docs/index.adoc | 16 + embassy/docs/pages/basic_application.adoc | 72 + embassy/docs/pages/beginners.adoc | 11 + embassy/docs/pages/best_practices.adoc | 55 + embassy/docs/pages/bootloader.adoc | 97 + embassy/docs/pages/developer.adoc | 1 + embassy/docs/pages/developer_stm32.adoc | 79 + embassy/docs/pages/embassy_in_the_wild.adoc | 26 + embassy/docs/pages/examples.adoc | 11 + embassy/docs/pages/faq.adoc | 433 ++ embassy/docs/pages/getting_started.adoc | 146 + embassy/docs/pages/hal.adoc | 14 + embassy/docs/pages/layer_by_layer.adoc | 85 + embassy/docs/pages/new_project.adoc | 202 + embassy/docs/pages/nrf.adoc | 29 + embassy/docs/pages/overview.adoc | 83 + embassy/docs/pages/project_structure.adoc | 93 + embassy/docs/pages/runtime.adoc | 46 + embassy/docs/pages/sharing_peripherals.adoc | 134 + embassy/docs/pages/stm32.adoc | 24 + embassy/docs/pages/system.adoc | 13 + embassy/docs/pages/time_keeping.adoc | 62 + embassy/embassy-boot-nrf/Cargo.toml | 46 + embassy/embassy-boot-nrf/README.md | 30 + embassy/embassy-boot-nrf/src/fmt.rs | 270 + embassy/embassy-boot-nrf/src/lib.rs | 158 + embassy/embassy-boot-rp/Cargo.toml | 79 + embassy/embassy-boot-rp/README.md | 12 + embassy/embassy-boot-rp/build.rs | 9 + embassy/embassy-boot-rp/src/fmt.rs | 270 + embassy/embassy-boot-rp/src/lib.rs | 103 + embassy/embassy-boot-stm32/Cargo.toml | 69 + embassy/embassy-boot-stm32/README.md | 10 + embassy/embassy-boot-stm32/build.rs | 9 + embassy/embassy-boot-stm32/src/fmt.rs | 270 + embassy/embassy-boot-stm32/src/lib.rs | 57 + embassy/embassy-boot/Cargo.toml | 53 + embassy/embassy-boot/README.md | 35 + embassy/embassy-boot/src/boot_loader.rs | 451 ++ .../src/digest_adapters/ed25519_dalek.rs | 30 + .../embassy-boot/src/digest_adapters/mod.rs | 5 + .../embassy-boot/src/digest_adapters/salty.rs | 29 + .../src/firmware_updater/asynch.rs | 462 ++ .../src/firmware_updater/blocking.rs | 497 ++ .../embassy-boot/src/firmware_updater/mod.rs | 49 + embassy/embassy-boot/src/fmt.rs | 270 + embassy/embassy-boot/src/lib.rs | 351 ++ embassy/embassy-boot/src/mem_flash.rs | 170 + embassy/embassy-boot/src/test_flash/asynch.rs | 64 + .../embassy-boot/src/test_flash/blocking.rs | 68 + embassy/embassy-boot/src/test_flash/mod.rs | 5 + embassy/embassy-embedded-hal/CHANGELOG.md | 21 + embassy/embassy-embedded-hal/Cargo.toml | 46 + embassy/embassy-embedded-hal/README.md | 12 + .../src/adapter/blocking_async.rs | 149 + .../embassy-embedded-hal/src/adapter/mod.rs | 7 + .../src/adapter/yielding_async.rs | 169 + .../src/flash/concat_flash.rs | 225 + .../src/flash/mem_flash.rs | 125 + embassy/embassy-embedded-hal/src/flash/mod.rs | 8 + .../src/flash/partition/asynch.rs | 149 + .../src/flash/partition/blocking.rs | 159 + .../src/flash/partition/mod.rs | 28 + embassy/embassy-embedded-hal/src/lib.rs | 39 + .../src/shared_bus/asynch/i2c.rs | 166 + .../src/shared_bus/asynch/mod.rs | 3 + .../src/shared_bus/asynch/spi.rs | 193 + .../src/shared_bus/blocking/i2c.rs | 187 + .../src/shared_bus/blocking/mod.rs | 3 + .../src/shared_bus/blocking/spi.rs | 167 + .../src/shared_bus/mod.rs | 59 + embassy/embassy-executor-macros/Cargo.toml | 25 + embassy/embassy-executor-macros/README.md | 5 + embassy/embassy-executor-macros/src/lib.rs | 175 + .../src/macros/main.rs | 177 + .../embassy-executor-macros/src/macros/mod.rs | 2 + .../src/macros/task.rs | 220 + embassy/embassy-executor-macros/src/util.rs | 74 + embassy/embassy-executor/CHANGELOG.md | 97 + embassy/embassy-executor/Cargo.toml | 205 + embassy/embassy-executor/README.md | 37 + embassy/embassy-executor/build.rs | 99 + embassy/embassy-executor/build_common.rs | 126 + embassy/embassy-executor/gen_config.py | 88 + embassy/embassy-executor/src/arch/avr.rs | 72 + embassy/embassy-executor/src/arch/cortex_m.rs | 231 + embassy/embassy-executor/src/arch/riscv32.rs | 80 + embassy/embassy-executor/src/arch/spin.rs | 58 + embassy/embassy-executor/src/arch/std.rs | 94 + embassy/embassy-executor/src/arch/wasm.rs | 83 + embassy/embassy-executor/src/fmt.rs | 270 + embassy/embassy-executor/src/lib.rs | 150 + embassy/embassy-executor/src/raw/mod.rs | 571 ++ .../src/raw/run_queue_atomics.rs | 88 + .../src/raw/run_queue_critical_section.rs | 74 + .../embassy-executor/src/raw/state_atomics.rs | 58 + .../src/raw/state_atomics_arm.rs | 83 + .../src/raw/state_critical_section.rs | 73 + .../embassy-executor/src/raw/timer_queue.rs | 73 + embassy/embassy-executor/src/raw/trace.rs | 84 + embassy/embassy-executor/src/raw/util.rs | 57 + embassy/embassy-executor/src/raw/waker.rs | 42 + .../embassy-executor/src/raw/waker_turbo.rs | 34 + embassy/embassy-executor/src/spawner.rs | 210 + embassy/embassy-executor/tests/test.rs | 285 + embassy/embassy-executor/tests/ui.rs | 23 + embassy/embassy-executor/tests/ui/abi.rs | 8 + embassy/embassy-executor/tests/ui/abi.stderr | 5 + .../embassy-executor/tests/ui/bad_return.rs | 10 + .../tests/ui/bad_return.stderr | 5 + embassy/embassy-executor/tests/ui/generics.rs | 8 + .../embassy-executor/tests/ui/generics.stderr | 5 + .../embassy-executor/tests/ui/impl_trait.rs | 6 + .../tests/ui/impl_trait.stderr | 5 + .../tests/ui/impl_trait_nested.rs | 8 + .../tests/ui/impl_trait_nested.stderr | 5 + .../tests/ui/impl_trait_static.rs | 6 + .../tests/ui/impl_trait_static.stderr | 5 + .../tests/ui/nonstatic_ref_anon.rs | 6 + .../tests/ui/nonstatic_ref_anon.stderr | 5 + .../tests/ui/nonstatic_ref_anon_nested.rs | 6 + .../tests/ui/nonstatic_ref_anon_nested.stderr | 5 + .../tests/ui/nonstatic_ref_elided.rs | 6 + .../tests/ui/nonstatic_ref_elided.stderr | 5 + .../tests/ui/nonstatic_ref_generic.rs | 6 + .../tests/ui/nonstatic_ref_generic.stderr | 11 + .../tests/ui/nonstatic_struct_anon.rs | 8 + .../tests/ui/nonstatic_struct_anon.stderr | 5 + .../tests/ui/nonstatic_struct_elided.rs | 8 + .../tests/ui/nonstatic_struct_elided.stderr | 10 + .../tests/ui/nonstatic_struct_generic.rs | 8 + .../tests/ui/nonstatic_struct_generic.stderr | 11 + .../embassy-executor/tests/ui/not_async.rs | 8 + .../tests/ui/not_async.stderr | 5 + embassy/embassy-executor/tests/ui/self.rs | 8 + embassy/embassy-executor/tests/ui/self.stderr | 13 + embassy/embassy-executor/tests/ui/self_ref.rs | 8 + .../embassy-executor/tests/ui/self_ref.stderr | 13 + .../embassy-executor/tests/ui/where_clause.rs | 12 + .../tests/ui/where_clause.stderr | 7 + embassy/embassy-futures/Cargo.toml | 28 + embassy/embassy-futures/README.md | 13 + embassy/embassy-futures/src/block_on.rs | 45 + embassy/embassy-futures/src/fmt.rs | 270 + embassy/embassy-futures/src/join.rs | 322 ++ embassy/embassy-futures/src/lib.rs | 15 + embassy/embassy-futures/src/select.rs | 545 ++ embassy/embassy-futures/src/yield_now.rs | 49 + embassy/embassy-hal-internal/Cargo.toml | 37 + embassy/embassy-hal-internal/README.md | 6 + embassy/embassy-hal-internal/build.rs | 7 + embassy/embassy-hal-internal/build_common.rs | 94 + .../src/atomic_ring_buffer.rs | 604 +++ embassy/embassy-hal-internal/src/drop.rs | 56 + embassy/embassy-hal-internal/src/fmt.rs | 270 + embassy/embassy-hal-internal/src/interrupt.rs | 870 +++ embassy/embassy-hal-internal/src/lib.rs | 17 + embassy/embassy-hal-internal/src/macros.rs | 135 + .../embassy-hal-internal/src/peripheral.rs | 177 + embassy/embassy-hal-internal/src/ratio.rs | 130 + embassy/embassy-net-adin1110/Cargo.toml | 42 + embassy/embassy-net-adin1110/README.md | 78 + embassy/embassy-net-adin1110/src/crc32.rs | 365 ++ embassy/embassy-net-adin1110/src/crc8.rs | 53 + embassy/embassy-net-adin1110/src/fmt.rs | 270 + embassy/embassy-net-adin1110/src/lib.rs | 1330 +++++ embassy/embassy-net-adin1110/src/mdio.rs | 177 + embassy/embassy-net-adin1110/src/phy.rs | 143 + embassy/embassy-net-adin1110/src/regs.rs | 417 ++ .../embassy-net-driver-channel/CHANGELOG.md | 23 + embassy/embassy-net-driver-channel/Cargo.toml | 30 + embassy/embassy-net-driver-channel/README.md | 86 + embassy/embassy-net-driver-channel/src/fmt.rs | 270 + embassy/embassy-net-driver-channel/src/lib.rs | 410 ++ embassy/embassy-net-driver/CHANGELOG.md | 17 + embassy/embassy-net-driver/Cargo.toml | 25 + embassy/embassy-net-driver/README.md | 18 + embassy/embassy-net-driver/src/lib.rs | 219 + embassy/embassy-net-enc28j60/Cargo.toml | 29 + embassy/embassy-net-enc28j60/README.md | 9 + embassy/embassy-net-enc28j60/src/bank0.rs | 69 + embassy/embassy-net-enc28j60/src/bank1.rs | 84 + embassy/embassy-net-enc28j60/src/bank2.rs | 86 + embassy/embassy-net-enc28j60/src/bank3.rs | 53 + embassy/embassy-net-enc28j60/src/common.rs | 106 + embassy/embassy-net-enc28j60/src/fmt.rs | 270 + embassy/embassy-net-enc28j60/src/header.rs | 30 + embassy/embassy-net-enc28j60/src/lib.rs | 721 +++ embassy/embassy-net-enc28j60/src/macros.rs | 89 + embassy/embassy-net-enc28j60/src/phy.rs | 35 + embassy/embassy-net-enc28j60/src/traits.rs | 57 + embassy/embassy-net-esp-hosted/Cargo.toml | 38 + embassy/embassy-net-esp-hosted/README.md | 11 + embassy/embassy-net-esp-hosted/src/control.rs | 230 + .../src/esp_hosted_config.proto | 432 ++ embassy/embassy-net-esp-hosted/src/fmt.rs | 270 + embassy/embassy-net-esp-hosted/src/ioctl.rs | 123 + embassy/embassy-net-esp-hosted/src/lib.rs | 359 ++ embassy/embassy-net-esp-hosted/src/proto.rs | 656 +++ embassy/embassy-net-nrf91/Cargo.toml | 39 + embassy/embassy-net-nrf91/README.md | 9 + embassy/embassy-net-nrf91/src/context.rs | 362 ++ embassy/embassy-net-nrf91/src/fmt.rs | 274 + embassy/embassy-net-nrf91/src/lib.rs | 1070 ++++ embassy/embassy-net-ppp/Cargo.toml | 33 + embassy/embassy-net-ppp/README.md | 9 + embassy/embassy-net-ppp/src/fmt.rs | 270 + embassy/embassy-net-ppp/src/lib.rs | 174 + embassy/embassy-net-tuntap/Cargo.toml | 21 + embassy/embassy-net-tuntap/README.md | 7 + embassy/embassy-net-tuntap/src/lib.rs | 244 + embassy/embassy-net-wiznet/Cargo.toml | 27 + embassy/embassy-net-wiznet/README.md | 16 + embassy/embassy-net-wiznet/src/chip/mod.rs | 48 + embassy/embassy-net-wiznet/src/chip/w5100s.rs | 65 + embassy/embassy-net-wiznet/src/chip/w5500.rs | 76 + embassy/embassy-net-wiznet/src/device.rs | 255 + embassy/embassy-net-wiznet/src/lib.rs | 133 + embassy/embassy-net/CHANGELOG.md | 76 + embassy/embassy-net/Cargo.toml | 84 + embassy/embassy-net/README.md | 54 + embassy/embassy-net/src/dns.rs | 120 + embassy/embassy-net/src/driver_util.rs | 112 + embassy/embassy-net/src/fmt.rs | 270 + embassy/embassy-net/src/lib.rs | 890 ++++ embassy/embassy-net/src/raw.rs | 191 + embassy/embassy-net/src/tcp.rs | 923 ++++ embassy/embassy-net/src/time.rs | 20 + embassy/embassy-net/src/udp.rs | 374 ++ embassy/embassy-nrf/CHANGELOG.md | 53 + embassy/embassy-nrf/Cargo.toml | 164 + embassy/embassy-nrf/README.md | 76 + embassy/embassy-nrf/src/buffered_uarte.rs | 905 ++++ embassy/embassy-nrf/src/chips/nrf51.rs | 174 + embassy/embassy-nrf/src/chips/nrf52805.rs | 249 + embassy/embassy-nrf/src/chips/nrf52810.rs | 278 + embassy/embassy-nrf/src/chips/nrf52811.rs | 280 + embassy/embassy-nrf/src/chips/nrf52820.rs | 274 + embassy/embassy-nrf/src/chips/nrf52832.rs | 328 ++ embassy/embassy-nrf/src/chips/nrf52833.rs | 374 ++ embassy/embassy-nrf/src/chips/nrf52840.rs | 381 ++ embassy/embassy-nrf/src/chips/nrf5340_app.rs | 514 ++ embassy/embassy-nrf/src/chips/nrf5340_net.rs | 323 ++ embassy/embassy-nrf/src/chips/nrf54l15_app.rs | 346 ++ embassy/embassy-nrf/src/chips/nrf9120.rs | 377 ++ embassy/embassy-nrf/src/chips/nrf9160.rs | 377 ++ embassy/embassy-nrf/src/egu.rs | 115 + embassy/embassy-nrf/src/fmt.rs | 270 + embassy/embassy-nrf/src/gpio.rs | 793 +++ embassy/embassy-nrf/src/gpiote.rs | 594 +++ embassy/embassy-nrf/src/i2s.rs | 1171 +++++ embassy/embassy-nrf/src/lib.rs | 738 +++ embassy/embassy-nrf/src/nfct.rs | 428 ++ embassy/embassy-nrf/src/nvmc.rs | 187 + embassy/embassy-nrf/src/pdm.rs | 475 ++ embassy/embassy-nrf/src/power.rs | 14 + embassy/embassy-nrf/src/ppi/dppi.rs | 81 + embassy/embassy-nrf/src/ppi/mod.rs | 367 ++ embassy/embassy-nrf/src/ppi/ppi.rs | 90 + embassy/embassy-nrf/src/pwm.rs | 915 ++++ embassy/embassy-nrf/src/qdec.rs | 295 ++ embassy/embassy-nrf/src/qspi.rs | 688 +++ embassy/embassy-nrf/src/radio/ble.rs | 396 ++ embassy/embassy-nrf/src/radio/ieee802154.rs | 539 ++ embassy/embassy-nrf/src/radio/mod.rs | 105 + embassy/embassy-nrf/src/reset.rs | 82 + embassy/embassy-nrf/src/rng.rs | 273 + embassy/embassy-nrf/src/saadc.rs | 723 +++ embassy/embassy-nrf/src/spim.rs | 653 +++ embassy/embassy-nrf/src/spis.rs | 555 ++ embassy/embassy-nrf/src/temp.rs | 101 + embassy/embassy-nrf/src/time_driver.rs | 295 ++ embassy/embassy-nrf/src/timer.rs | 295 ++ embassy/embassy-nrf/src/twim.rs | 942 ++++ embassy/embassy-nrf/src/twis.rs | 814 +++ embassy/embassy-nrf/src/uarte.rs | 1088 ++++ embassy/embassy-nrf/src/usb/mod.rs | 867 +++ embassy/embassy-nrf/src/usb/vbus_detect.rs | 180 + embassy/embassy-nrf/src/util.rs | 23 + embassy/embassy-nrf/src/wdt.rs | 188 + embassy/embassy-nxp/Cargo.toml | 17 + embassy/embassy-nxp/src/gpio.rs | 361 ++ embassy/embassy-nxp/src/lib.rs | 95 + embassy/embassy-nxp/src/pac_utils.rs | 323 ++ embassy/embassy-nxp/src/pint.rs | 442 ++ embassy/embassy-rp/CHANGELOG.md | 32 + embassy/embassy-rp/Cargo.toml | 152 + embassy/embassy-rp/LICENSE-APACHE | 202 + embassy/embassy-rp/LICENSE-MIT | 26 + embassy/embassy-rp/README.md | 27 + embassy/embassy-rp/build.rs | 39 + embassy/embassy-rp/funcsel.txt | 30 + embassy/embassy-rp/link-rp.x.in | 8 + embassy/embassy-rp/src/adc.rs | 459 ++ embassy/embassy-rp/src/block.rs | 1079 ++++ embassy/embassy-rp/src/bootsel.rs | 83 + embassy/embassy-rp/src/clocks.rs | 1191 +++++ .../embassy-rp/src/critical_section_impl.rs | 137 + embassy/embassy-rp/src/dma.rs | 315 ++ embassy/embassy-rp/src/flash.rs | 989 ++++ embassy/embassy-rp/src/float/add_sub.rs | 92 + embassy/embassy-rp/src/float/cmp.rs | 201 + embassy/embassy-rp/src/float/conv.rs | 157 + embassy/embassy-rp/src/float/div.rs | 139 + embassy/embassy-rp/src/float/functions.rs | 239 + embassy/embassy-rp/src/float/mod.rs | 150 + embassy/embassy-rp/src/float/mul.rs | 70 + embassy/embassy-rp/src/fmt.rs | 270 + embassy/embassy-rp/src/gpio.rs | 1379 +++++ embassy/embassy-rp/src/i2c.rs | 924 ++++ embassy/embassy-rp/src/i2c_slave.rs | 398 ++ embassy/embassy-rp/src/intrinsics.rs | 477 ++ embassy/embassy-rp/src/lib.rs | 700 +++ embassy/embassy-rp/src/multicore.rs | 344 ++ embassy/embassy-rp/src/otp.rs | 175 + embassy/embassy-rp/src/pio/instr.rs | 101 + embassy/embassy-rp/src/pio/mod.rs | 1377 +++++ .../embassy-rp/src/pio_programs/hd44780.rs | 203 + embassy/embassy-rp/src/pio_programs/i2s.rs | 95 + embassy/embassy-rp/src/pio_programs/mod.rs | 10 + .../embassy-rp/src/pio_programs/onewire.rs | 109 + embassy/embassy-rp/src/pio_programs/pwm.rs | 121 + .../src/pio_programs/rotary_encoder.rs | 73 + .../embassy-rp/src/pio_programs/stepper.rs | 147 + embassy/embassy-rp/src/pio_programs/uart.rs | 185 + embassy/embassy-rp/src/pio_programs/ws2812.rs | 118 + embassy/embassy-rp/src/pwm.rs | 611 +++ embassy/embassy-rp/src/relocate.rs | 66 + embassy/embassy-rp/src/reset.rs | 15 + embassy/embassy-rp/src/rom_data/mod.rs | 33 + embassy/embassy-rp/src/rom_data/rp2040.rs | 756 +++ embassy/embassy-rp/src/rom_data/rp235x.rs | 752 +++ embassy/embassy-rp/src/rtc/datetime_chrono.rs | 62 + .../embassy-rp/src/rtc/datetime_no_deps.rs | 128 + embassy/embassy-rp/src/rtc/filter.rs | 100 + embassy/embassy-rp/src/rtc/mod.rs | 204 + embassy/embassy-rp/src/spi.rs | 728 +++ embassy/embassy-rp/src/time_driver.rs | 144 + embassy/embassy-rp/src/trng.rs | 405 ++ embassy/embassy-rp/src/uart/buffered.rs | 839 +++ embassy/embassy-rp/src/uart/mod.rs | 1509 ++++++ embassy/embassy-rp/src/usb.rs | 827 +++ embassy/embassy-rp/src/watchdog.rs | 143 + embassy/embassy-stm32-wpan/Cargo.toml | 66 + embassy/embassy-stm32-wpan/README.md | 13 + embassy/embassy-stm32-wpan/build.rs | 58 + embassy/embassy-stm32-wpan/src/channels.rs | 96 + embassy/embassy-stm32-wpan/src/cmd.rs | 104 + embassy/embassy-stm32-wpan/src/consts.rs | 94 + embassy/embassy-stm32-wpan/src/evt.rs | 151 + embassy/embassy-stm32-wpan/src/fmt.rs | 270 + embassy/embassy-stm32-wpan/src/lhci.rs | 112 + embassy/embassy-stm32-wpan/src/lib.rs | 155 + .../embassy-stm32-wpan/src/mac/commands.rs | 478 ++ embassy/embassy-stm32-wpan/src/mac/consts.rs | 4 + embassy/embassy-stm32-wpan/src/mac/control.rs | 95 + embassy/embassy-stm32-wpan/src/mac/driver.rs | 120 + embassy/embassy-stm32-wpan/src/mac/event.rs | 153 + .../embassy-stm32-wpan/src/mac/indications.rs | 265 + embassy/embassy-stm32-wpan/src/mac/macros.rs | 32 + embassy/embassy-stm32-wpan/src/mac/mod.rs | 21 + embassy/embassy-stm32-wpan/src/mac/opcodes.rs | 92 + .../embassy-stm32-wpan/src/mac/responses.rs | 273 + embassy/embassy-stm32-wpan/src/mac/runner.rs | 109 + .../embassy-stm32-wpan/src/mac/typedefs.rs | 381 ++ embassy/embassy-stm32-wpan/src/shci.rs | 375 ++ embassy/embassy-stm32-wpan/src/sub/ble.rs | 95 + embassy/embassy-stm32-wpan/src/sub/mac.rs | 124 + embassy/embassy-stm32-wpan/src/sub/mm.rs | 83 + embassy/embassy-stm32-wpan/src/sub/mod.rs | 6 + embassy/embassy-stm32-wpan/src/sub/sys.rs | 105 + embassy/embassy-stm32-wpan/src/tables.rs | 276 + .../src/unsafe_linked_list.rs | 257 + embassy/embassy-stm32-wpan/tl_mbox.x.in | 15 + .../tl_mbox_extended_wb1.x.in | 16 + .../tl_mbox_extended_wbx5.x.in | 16 + embassy/embassy-stm32/Cargo.toml | 1642 ++++++ embassy/embassy-stm32/README.md | 38 + embassy/embassy-stm32/build.rs | 1854 +++++++ embassy/embassy-stm32/build_common.rs | 94 + embassy/embassy-stm32/src/adc/f1.rs | 174 + embassy/embassy-stm32/src/adc/f3.rs | 193 + embassy/embassy-stm32/src/adc/f3_v1_1.rs | 408 ++ embassy/embassy-stm32/src/adc/g4.rs | 510 ++ embassy/embassy-stm32/src/adc/mod.rs | 224 + .../embassy-stm32/src/adc/ringbuffered_v2.rs | 438 ++ embassy/embassy-stm32/src/adc/v1.rs | 204 + embassy/embassy-stm32/src/adc/v2.rs | 214 + embassy/embassy-stm32/src/adc/v3.rs | 499 ++ embassy/embassy-stm32/src/adc/v4.rs | 486 ++ embassy/embassy-stm32/src/can/bxcan/filter.rs | 479 ++ embassy/embassy-stm32/src/can/bxcan/mod.rs | 1196 +++++ .../embassy-stm32/src/can/bxcan/registers.rs | 560 ++ embassy/embassy-stm32/src/can/common.rs | 52 + embassy/embassy-stm32/src/can/enums.rs | 70 + embassy/embassy-stm32/src/can/fd/config.rs | 475 ++ embassy/embassy-stm32/src/can/fd/filter.rs | 379 ++ .../src/can/fd/message_ram/common.rs | 134 + .../src/can/fd/message_ram/enums.rs | 233 + .../src/can/fd/message_ram/extended_filter.rs | 136 + .../src/can/fd/message_ram/generic.rs | 168 + .../src/can/fd/message_ram/mod.rs | 150 + .../src/can/fd/message_ram/rxfifo_element.rs | 122 + .../src/can/fd/message_ram/standard_filter.rs | 136 + .../can/fd/message_ram/txbuffer_element.rs | 433 ++ .../src/can/fd/message_ram/txevent_element.rs | 138 + embassy/embassy-stm32/src/can/fd/mod.rs | 6 + .../embassy-stm32/src/can/fd/peripheral.rs | 738 +++ embassy/embassy-stm32/src/can/fdcan.rs | 1073 ++++ embassy/embassy-stm32/src/can/frame.rs | 463 ++ embassy/embassy-stm32/src/can/mod.rs | 14 + embassy/embassy-stm32/src/can/util.rs | 117 + embassy/embassy-stm32/src/cordic/enums.rs | 71 + embassy/embassy-stm32/src/cordic/errors.rs | 144 + embassy/embassy-stm32/src/cordic/mod.rs | 730 +++ embassy/embassy-stm32/src/cordic/utils.rs | 62 + embassy/embassy-stm32/src/crc/mod.rs | 7 + embassy/embassy-stm32/src/crc/v1.rs | 63 + embassy/embassy-stm32/src/crc/v2v3.rs | 174 + embassy/embassy-stm32/src/cryp/mod.rs | 1909 +++++++ embassy/embassy-stm32/src/dac/mod.rs | 539 ++ embassy/embassy-stm32/src/dac/tsel.rs | 301 ++ embassy/embassy-stm32/src/dcmi.rs | 483 ++ embassy/embassy-stm32/src/dma/dma_bdma.rs | 1082 ++++ embassy/embassy-stm32/src/dma/dmamux.rs | 25 + embassy/embassy-stm32/src/dma/gpdma.rs | 341 ++ embassy/embassy-stm32/src/dma/mod.rs | 141 + .../embassy-stm32/src/dma/ringbuffer/mod.rs | 333 ++ .../src/dma/ringbuffer/tests/mod.rs | 90 + .../src/dma/ringbuffer/tests/prop_test/mod.rs | 50 + .../dma/ringbuffer/tests/prop_test/reader.rs | 123 + .../dma/ringbuffer/tests/prop_test/writer.rs | 122 + embassy/embassy-stm32/src/dma/util.rs | 61 + embassy/embassy-stm32/src/dma/word.rs | 88 + embassy/embassy-stm32/src/dsihost.rs | 429 ++ embassy/embassy-stm32/src/eth/generic_smi.rs | 119 + embassy/embassy-stm32/src/eth/mod.rs | 228 + embassy/embassy-stm32/src/eth/v1/mod.rs | 327 ++ embassy/embassy-stm32/src/eth/v1/rx_desc.rs | 232 + embassy/embassy-stm32/src/eth/v1/tx_desc.rs | 171 + .../embassy-stm32/src/eth/v2/descriptors.rs | 253 + embassy/embassy-stm32/src/eth/v2/mod.rs | 372 ++ embassy/embassy-stm32/src/exti.rs | 406 ++ embassy/embassy-stm32/src/flash/asynch.rs | 201 + embassy/embassy-stm32/src/flash/common.rs | 306 ++ embassy/embassy-stm32/src/flash/f0.rs | 101 + embassy/embassy-stm32/src/flash/f1f3.rs | 111 + embassy/embassy-stm32/src/flash/f2.rs | 142 + embassy/embassy-stm32/src/flash/f4.rs | 555 ++ embassy/embassy-stm32/src/flash/f7.rs | 171 + embassy/embassy-stm32/src/flash/g.rs | 96 + embassy/embassy-stm32/src/flash/h5.rs | 177 + embassy/embassy-stm32/src/flash/h50.rs | 166 + embassy/embassy-stm32/src/flash/h7.rs | 165 + embassy/embassy-stm32/src/flash/l.rs | 240 + embassy/embassy-stm32/src/flash/mod.rs | 144 + embassy/embassy-stm32/src/flash/other.rs | 33 + embassy/embassy-stm32/src/flash/u0.rs | 96 + embassy/embassy-stm32/src/flash/u5.rs | 153 + embassy/embassy-stm32/src/fmc.rs | 368 ++ embassy/embassy-stm32/src/fmt.rs | 292 ++ embassy/embassy-stm32/src/gpio.rs | 1147 ++++ embassy/embassy-stm32/src/hash/mod.rs | 588 +++ embassy/embassy-stm32/src/hrtim/mod.rs | 444 ++ embassy/embassy-stm32/src/hrtim/traits.rs | 170 + embassy/embassy-stm32/src/hsem/mod.rs | 187 + embassy/embassy-stm32/src/i2c/mod.rs | 554 ++ embassy/embassy-stm32/src/i2c/v1.rs | 822 +++ embassy/embassy-stm32/src/i2c/v2.rs | 818 +++ embassy/embassy-stm32/src/i2s.rs | 715 +++ embassy/embassy-stm32/src/ipcc.rs | 265 + embassy/embassy-stm32/src/lib.rs | 552 ++ embassy/embassy-stm32/src/low_power.rs | 269 + embassy/embassy-stm32/src/lptim/channel.rs | 18 + embassy/embassy-stm32/src/lptim/mod.rs | 48 + embassy/embassy-stm32/src/lptim/pwm.rs | 168 + .../src/lptim/timer/channel_direction.rs | 18 + embassy/embassy-stm32/src/lptim/timer/mod.rs | 133 + .../src/lptim/timer/prescaler.rs | 90 + embassy/embassy-stm32/src/ltdc.rs | 578 ++ embassy/embassy-stm32/src/macros.rs | 105 + embassy/embassy-stm32/src/opamp.rs | 356 ++ embassy/embassy-stm32/src/ospi/enums.rs | 386 ++ embassy/embassy-stm32/src/ospi/mod.rs | 1302 +++++ embassy/embassy-stm32/src/qspi/enums.rs | 333 ++ embassy/embassy-stm32/src/qspi/mod.rs | 473 ++ embassy/embassy-stm32/src/rcc/bd.rs | 286 + embassy/embassy-stm32/src/rcc/c0.rs | 195 + embassy/embassy-stm32/src/rcc/f013.rs | 478 ++ embassy/embassy-stm32/src/rcc/f247.rs | 503 ++ embassy/embassy-stm32/src/rcc/g0.rs | 317 ++ embassy/embassy-stm32/src/rcc/g4.rs | 358 ++ embassy/embassy-stm32/src/rcc/h.rs | 1007 ++++ embassy/embassy-stm32/src/rcc/hsi48.rs | 62 + embassy/embassy-stm32/src/rcc/l.rs | 673 +++ embassy/embassy-stm32/src/rcc/mco.rs | 109 + embassy/embassy-stm32/src/rcc/mod.rs | 371 ++ embassy/embassy-stm32/src/rcc/u5.rs | 514 ++ embassy/embassy-stm32/src/rcc/wba.rs | 176 + embassy/embassy-stm32/src/rng.rs | 248 + embassy/embassy-stm32/src/rtc/datetime.rs | 192 + embassy/embassy-stm32/src/rtc/low_power.rs | 243 + embassy/embassy-stm32/src/rtc/mod.rs | 311 ++ embassy/embassy-stm32/src/rtc/v2.rs | 159 + embassy/embassy-stm32/src/rtc/v3.rs | 170 + embassy/embassy-stm32/src/sai/mod.rs | 1126 ++++ embassy/embassy-stm32/src/sdmmc/mod.rs | 1554 ++++++ embassy/embassy-stm32/src/spdifrx/mod.rs | 336 ++ embassy/embassy-stm32/src/spi/mod.rs | 1297 +++++ embassy/embassy-stm32/src/time.rs | 125 + embassy/embassy-stm32/src/time_driver.rs | 523 ++ .../src/timer/complementary_pwm.rs | 321 ++ .../embassy-stm32/src/timer/input_capture.rs | 214 + embassy/embassy-stm32/src/timer/low_level.rs | 695 +++ embassy/embassy-stm32/src/timer/mod.rs | 381 ++ embassy/embassy-stm32/src/timer/pwm_input.rs | 114 + embassy/embassy-stm32/src/timer/qei.rs | 106 + embassy/embassy-stm32/src/timer/simple_pwm.rs | 519 ++ .../src/tsc/acquisition_banks.rs | 209 + embassy/embassy-stm32/src/tsc/config.rs | 175 + embassy/embassy-stm32/src/tsc/errors.rs | 21 + embassy/embassy-stm32/src/tsc/io_pin.rs | 200 + embassy/embassy-stm32/src/tsc/mod.rs | 166 + embassy/embassy-stm32/src/tsc/pin_groups.rs | 669 +++ embassy/embassy-stm32/src/tsc/tsc.rs | 456 ++ embassy/embassy-stm32/src/tsc/types.rs | 93 + embassy/embassy-stm32/src/ucpd.rs | 669 +++ embassy/embassy-stm32/src/uid.rs | 31 + embassy/embassy-stm32/src/usart/buffered.rs | 927 ++++ embassy/embassy-stm32/src/usart/mod.rs | 2054 ++++++++ .../embassy-stm32/src/usart/ringbuffered.rs | 289 + embassy/embassy-stm32/src/usb/mod.rs | 109 + embassy/embassy-stm32/src/usb/otg.rs | 584 +++ embassy/embassy-stm32/src/usb/usb.rs | 1226 +++++ embassy/embassy-stm32/src/wdg/mod.rs | 125 + embassy/embassy-sync/CHANGELOG.md | 60 + embassy/embassy-sync/Cargo.toml | 45 + embassy/embassy-sync/README.md | 21 + embassy/embassy-sync/build.rs | 7 + embassy/embassy-sync/build_common.rs | 94 + .../embassy-sync/src/blocking_mutex/mod.rs | 190 + .../embassy-sync/src/blocking_mutex/raw.rs | 149 + embassy/embassy-sync/src/channel.rs | 910 ++++ embassy/embassy-sync/src/fmt.rs | 270 + embassy/embassy-sync/src/lazy_lock.rs | 152 + embassy/embassy-sync/src/lib.rs | 25 + embassy/embassy-sync/src/mutex.rs | 391 ++ embassy/embassy-sync/src/once_lock.rs | 236 + embassy/embassy-sync/src/pipe.rs | 646 +++ embassy/embassy-sync/src/priority_channel.rs | 745 +++ embassy/embassy-sync/src/pubsub/mod.rs | 784 +++ embassy/embassy-sync/src/pubsub/publisher.rs | 314 ++ embassy/embassy-sync/src/pubsub/subscriber.rs | 192 + embassy/embassy-sync/src/ring_buffer.rs | 138 + embassy/embassy-sync/src/semaphore.rs | 772 +++ embassy/embassy-sync/src/signal.rs | 140 + .../src/waitqueue/atomic_waker.rs | 63 + .../src/waitqueue/atomic_waker_turbo.rs | 30 + embassy/embassy-sync/src/waitqueue/mod.rs | 11 + .../embassy-sync/src/waitqueue/multi_waker.rs | 58 + .../src/waitqueue/waker_registration.rs | 52 + embassy/embassy-sync/src/watch.rs | 1121 ++++ embassy/embassy-sync/src/zerocopy_channel.rs | 342 ++ embassy/embassy-time-driver/CHANGELOG.md | 15 + embassy/embassy-time-driver/Cargo.toml | 370 ++ embassy/embassy-time-driver/README.md | 19 + embassy/embassy-time-driver/build.rs | 1 + embassy/embassy-time-driver/gen_tick.py | 81 + embassy/embassy-time-driver/src/lib.rs | 168 + embassy/embassy-time-driver/src/tick.rs | 482 ++ .../embassy-time-queue-driver/CHANGELOG.md | 15 + embassy/embassy-time-queue-driver/Cargo.toml | 58 + embassy/embassy-time-queue-driver/README.md | 8 + embassy/embassy-time-queue-driver/build.rs | 1 + embassy/embassy-time-queue-driver/src/lib.rs | 21 + .../src/queue_generic.rs | 146 + .../src/queue_integrated.rs | 89 + embassy/embassy-time/CHANGELOG.md | 74 + embassy/embassy-time/Cargo.toml | 410 ++ embassy/embassy-time/README.md | 47 + embassy/embassy-time/src/delay.rs | 81 + embassy/embassy-time/src/driver_mock.rs | 145 + embassy/embassy-time/src/driver_std.rs | 104 + embassy/embassy-time/src/driver_wasm.rs | 103 + embassy/embassy-time/src/duration.rs | 219 + embassy/embassy-time/src/fmt.rs | 270 + embassy/embassy-time/src/instant.rs | 161 + embassy/embassy-time/src/lib.rs | 63 + embassy/embassy-time/src/timer.rs | 269 + embassy/embassy-usb-dfu/Cargo.toml | 45 + embassy/embassy-usb-dfu/README.md | 6 + embassy/embassy-usb-dfu/src/application.rs | 136 + embassy/embassy-usb-dfu/src/consts.rs | 100 + embassy/embassy-usb-dfu/src/dfu.rs | 210 + embassy/embassy-usb-dfu/src/fmt.rs | 270 + embassy/embassy-usb-dfu/src/lib.rs | 56 + embassy/embassy-usb-driver/Cargo.toml | 22 + embassy/embassy-usb-driver/README.md | 17 + embassy/embassy-usb-driver/src/lib.rs | 397 ++ embassy/embassy-usb-logger/CHANGELOG.md | 28 + embassy/embassy-usb-logger/Cargo.toml | 21 + embassy/embassy-usb-logger/README.md | 15 + embassy/embassy-usb-logger/src/lib.rs | 325 ++ embassy/embassy-usb-synopsys-otg/CHANGELOG.md | 25 + embassy/embassy-usb-synopsys-otg/Cargo.toml | 25 + embassy/embassy-usb-synopsys-otg/README.md | 16 + embassy/embassy-usb-synopsys-otg/src/fmt.rs | 270 + embassy/embassy-usb-synopsys-otg/src/lib.rs | 1380 +++++ .../embassy-usb-synopsys-otg/src/otg_v1.rs | 4665 +++++++++++++++++ embassy/embassy-usb/CHANGELOG.md | 23 + embassy/embassy-usb/Cargo.toml | 60 + embassy/embassy-usb/README.md | 50 + embassy/embassy-usb/build.rs | 96 + embassy/embassy-usb/gen_config.py | 74 + embassy/embassy-usb/src/builder.rs | 617 +++ embassy/embassy-usb/src/class/cdc_acm.rs | 547 ++ .../src/class/cdc_ncm/embassy_net.rs | 104 + embassy/embassy-usb/src/class/cdc_ncm/mod.rs | 529 ++ embassy/embassy-usb/src/class/hid.rs | 552 ++ embassy/embassy-usb/src/class/midi.rs | 227 + embassy/embassy-usb/src/class/mod.rs | 7 + .../embassy-usb/src/class/uac1/class_codes.rs | 151 + embassy/embassy-usb/src/class/uac1/mod.rs | 134 + embassy/embassy-usb/src/class/uac1/speaker.rs | 778 +++ .../src/class/uac1/terminal_type.rs | 50 + embassy/embassy-usb/src/class/web_usb.rs | 186 + embassy/embassy-usb/src/control.rs | 146 + embassy/embassy-usb/src/descriptor.rs | 434 ++ embassy/embassy-usb/src/descriptor_reader.rs | 111 + embassy/embassy-usb/src/fmt.rs | 270 + embassy/embassy-usb/src/lib.rs | 786 +++ embassy/embassy-usb/src/msos.rs | 728 +++ embassy/embassy-usb/src/types.rs | 37 + embassy/examples/.cargo/config.toml | 3 + embassy/examples/boot/.cargo/config.toml | 9 + .../boot/application/nrf/.cargo/config.toml | 9 + .../examples/boot/application/nrf/Cargo.toml | 34 + .../examples/boot/application/nrf/README.md | 38 + .../examples/boot/application/nrf/build.rs | 37 + .../boot/application/nrf/memory-bl-nrf91.x | 19 + .../examples/boot/application/nrf/memory-bl.x | 18 + .../boot/application/nrf/memory-nrf91.x | 16 + .../examples/boot/application/nrf/memory.x | 15 + .../boot/application/nrf/src/bin/a.rs | 83 + .../boot/application/nrf/src/bin/b.rs | 25 + .../boot/application/rp/.cargo/config.toml | 12 + .../examples/boot/application/rp/Cargo.toml | 36 + .../examples/boot/application/rp/README.md | 28 + embassy/examples/boot/application/rp/build.rs | 35 + embassy/examples/boot/application/rp/memory.x | 27 + .../examples/boot/application/rp/src/bin/a.rs | 67 + .../examples/boot/application/rp/src/bin/b.rs | 22 + .../application/stm32f3/.cargo/config.toml | 9 + .../boot/application/stm32f3/Cargo.toml | 31 + .../boot/application/stm32f3/README.md | 29 + .../boot/application/stm32f3/build.rs | 37 + .../boot/application/stm32f3/memory.x | 15 + .../boot/application/stm32f3/src/bin/a.rs | 45 + .../boot/application/stm32f3/src/bin/b.rs | 23 + .../application/stm32f7/.cargo/config.toml | 9 + .../boot/application/stm32f7/Cargo.toml | 32 + .../boot/application/stm32f7/README.md | 29 + .../boot/application/stm32f7/build.rs | 37 + .../boot/application/stm32f7/flash-boot.sh | 8 + .../boot/application/stm32f7/memory-bl.x | 18 + .../boot/application/stm32f7/memory.x | 15 + .../boot/application/stm32f7/src/bin/a.rs | 48 + .../boot/application/stm32f7/src/bin/b.rs | 25 + .../application/stm32h7/.cargo/config.toml | 9 + .../boot/application/stm32h7/Cargo.toml | 32 + .../boot/application/stm32h7/README.md | 29 + .../boot/application/stm32h7/build.rs | 37 + .../boot/application/stm32h7/flash-boot.sh | 9 + .../boot/application/stm32h7/memory-bl.x | 18 + .../boot/application/stm32h7/memory.x | 15 + .../boot/application/stm32h7/src/bin/a.rs | 48 + .../boot/application/stm32h7/src/bin/b.rs | 25 + .../application/stm32l0/.cargo/config.toml | 9 + .../boot/application/stm32l0/Cargo.toml | 31 + .../boot/application/stm32l0/README.md | 29 + .../boot/application/stm32l0/build.rs | 37 + .../boot/application/stm32l0/memory.x | 15 + .../boot/application/stm32l0/src/bin/a.rs | 49 + .../boot/application/stm32l0/src/bin/b.rs | 23 + .../application/stm32l1/.cargo/config.toml | 9 + .../boot/application/stm32l1/Cargo.toml | 31 + .../boot/application/stm32l1/README.md | 29 + .../boot/application/stm32l1/build.rs | 37 + .../boot/application/stm32l1/memory.x | 15 + .../boot/application/stm32l1/src/bin/a.rs | 49 + .../boot/application/stm32l1/src/bin/b.rs | 23 + .../application/stm32l4/.cargo/config.toml | 9 + .../boot/application/stm32l4/Cargo.toml | 31 + .../boot/application/stm32l4/README.md | 29 + .../boot/application/stm32l4/build.rs | 37 + .../boot/application/stm32l4/memory.x | 15 + .../boot/application/stm32l4/src/bin/a.rs | 45 + .../boot/application/stm32l4/src/bin/b.rs | 23 + .../stm32wb-dfu/.cargo/config.toml | 9 + .../boot/application/stm32wb-dfu/Cargo.toml | 32 + .../boot/application/stm32wb-dfu/README.md | 9 + .../boot/application/stm32wb-dfu/build.rs | 37 + .../boot/application/stm32wb-dfu/memory.x | 15 + .../boot/application/stm32wb-dfu/src/main.rs | 61 + .../application/stm32wl/.cargo/config.toml | 9 + .../boot/application/stm32wl/Cargo.toml | 31 + .../boot/application/stm32wl/README.md | 29 + .../boot/application/stm32wl/build.rs | 37 + .../boot/application/stm32wl/memory.x | 24 + .../boot/application/stm32wl/src/bin/a.rs | 54 + .../boot/application/stm32wl/src/bin/b.rs | 29 + .../boot/bootloader/nrf/.cargo/config.toml | 20 + .../examples/boot/bootloader/nrf/Cargo.toml | 59 + .../examples/boot/bootloader/nrf/README.md | 11 + embassy/examples/boot/bootloader/nrf/build.rs | 37 + .../examples/boot/bootloader/nrf/memory-bm.x | 18 + .../boot/bootloader/nrf/memory-s140.x | 31 + embassy/examples/boot/bootloader/nrf/memory.x | 18 + .../examples/boot/bootloader/nrf/src/main.rs | 58 + .../boot/bootloader/rp/.cargo/config.toml | 8 + .../examples/boot/bootloader/rp/Cargo.toml | 33 + embassy/examples/boot/bootloader/rp/README.md | 17 + embassy/examples/boot/bootloader/rp/build.rs | 28 + embassy/examples/boot/bootloader/rp/memory.x | 31 + .../examples/boot/bootloader/rp/src/main.rs | 54 + .../bootloader/stm32-dual-bank/Cargo.toml | 56 + .../boot/bootloader/stm32-dual-bank/README.md | 44 + .../boot/bootloader/stm32-dual-bank/build.rs | 27 + .../boot/bootloader/stm32-dual-bank/memory.x | 18 + .../bootloader/stm32-dual-bank/src/main.rs | 53 + .../examples/boot/bootloader/stm32/Cargo.toml | 58 + .../examples/boot/bootloader/stm32/README.md | 11 + .../examples/boot/bootloader/stm32/build.rs | 27 + .../examples/boot/bootloader/stm32/memory.x | 18 + .../boot/bootloader/stm32/src/main.rs | 52 + .../boot/bootloader/stm32wb-dfu/Cargo.toml | 63 + .../boot/bootloader/stm32wb-dfu/README.md | 37 + .../boot/bootloader/stm32wb-dfu/build.rs | 27 + .../boot/bootloader/stm32wb-dfu/memory.x | 18 + .../boot/bootloader/stm32wb-dfu/src/main.rs | 106 + embassy/examples/lpc55s69/.cargo/config.toml | 8 + embassy/examples/lpc55s69/Cargo.toml | 22 + embassy/examples/lpc55s69/build.rs | 35 + embassy/examples/lpc55s69/memory.x | 28 + .../examples/lpc55s69/src/bin/blinky_nop.rs | 33 + .../lpc55s69/src/bin/button_executor.rs | 25 + .../nrf-rtos-trace/.cargo/config.toml | 9 + embassy/examples/nrf-rtos-trace/Cargo.toml | 37 + embassy/examples/nrf-rtos-trace/build.rs | 36 + embassy/examples/nrf-rtos-trace/memory.x | 7 + .../nrf-rtos-trace/src/bin/rtos_trace.rs | 69 + embassy/examples/nrf51/.cargo/config.toml | 9 + embassy/examples/nrf51/Cargo.toml | 20 + embassy/examples/nrf51/build.rs | 35 + embassy/examples/nrf51/memory.x | 5 + embassy/examples/nrf51/src/bin/blinky.rs | 20 + embassy/examples/nrf52810/.cargo/config.toml | 9 + embassy/examples/nrf52810/Cargo.toml | 24 + embassy/examples/nrf52810/build.rs | 35 + embassy/examples/nrf52810/memory.x | 7 + embassy/examples/nrf52810/src/bin/blinky.rs | 20 + .../examples/nrf52840-rtic/.cargo/config.toml | 9 + embassy/examples/nrf52840-rtic/Cargo.toml | 24 + embassy/examples/nrf52840-rtic/build.rs | 35 + embassy/examples/nrf52840-rtic/memory.x | 12 + .../examples/nrf52840-rtic/src/bin/blinky.rs | 43 + embassy/examples/nrf52840/.cargo/config.toml | 9 + embassy/examples/nrf52840/Cargo.toml | 39 + embassy/examples/nrf52840/build.rs | 35 + embassy/examples/nrf52840/memory.x | 12 + embassy/examples/nrf52840/src/bin/blinky.rs | 20 + .../nrf52840/src/bin/buffered_uart.rs | 53 + embassy/examples/nrf52840/src/bin/channel.rs | 42 + .../src/bin/channel_sender_receiver.rs | 49 + embassy/examples/nrf52840/src/bin/egu.rs | 43 + .../nrf52840/src/bin/ethernet_enc28j60.rs | 117 + .../src/bin/executor_fairness_test.rs | 42 + .../nrf52840/src/bin/gpiote_channel.rs | 65 + .../examples/nrf52840/src/bin/gpiote_port.rs | 33 + .../examples/nrf52840/src/bin/i2s_effect.rs | 115 + .../examples/nrf52840/src/bin/i2s_monitor.rs | 117 + .../examples/nrf52840/src/bin/i2s_waveform.rs | 153 + .../src/bin/manually_create_executor.rs | 48 + .../examples/nrf52840/src/bin/multiprio.rs | 145 + embassy/examples/nrf52840/src/bin/mutex.rs | 41 + embassy/examples/nrf52840/src/bin/nfct.rs | 79 + embassy/examples/nrf52840/src/bin/nvmc.rs | 42 + embassy/examples/nrf52840/src/bin/pdm.rs | 56 + .../nrf52840/src/bin/pdm_continuous.rs | 80 + embassy/examples/nrf52840/src/bin/ppi.rs | 72 + embassy/examples/nrf52840/src/bin/pubsub.rs | 106 + embassy/examples/nrf52840/src/bin/pwm.rs | 88 + .../nrf52840/src/bin/pwm_double_sequence.rs | 40 + .../examples/nrf52840/src/bin/pwm_sequence.rs | 35 + .../nrf52840/src/bin/pwm_sequence_ppi.rs | 66 + .../nrf52840/src/bin/pwm_sequence_ws2812b.rs | 74 + .../examples/nrf52840/src/bin/pwm_servo.rs | 46 + embassy/examples/nrf52840/src/bin/qdec.rs | 26 + embassy/examples/nrf52840/src/bin/qspi.rs | 81 + .../nrf52840/src/bin/qspi_lowpower.rs | 83 + .../examples/nrf52840/src/bin/raw_spawn.rs | 52 + embassy/examples/nrf52840/src/bin/rng.rs | 33 + embassy/examples/nrf52840/src/bin/saadc.rs | 28 + .../nrf52840/src/bin/saadc_continuous.rs | 70 + .../examples/nrf52840/src/bin/self_spawn.rs | 25 + .../src/bin/self_spawn_current_executor.rs | 21 + embassy/examples/nrf52840/src/bin/spim.rs | 70 + embassy/examples/nrf52840/src/bin/spis.rs | 29 + embassy/examples/nrf52840/src/bin/temp.rs | 25 + embassy/examples/nrf52840/src/bin/timer.rs | 30 + embassy/examples/nrf52840/src/bin/twim.rs | 33 + .../nrf52840/src/bin/twim_lowpower.rs | 52 + embassy/examples/nrf52840/src/bin/twis.rs | 47 + embassy/examples/nrf52840/src/bin/uart.rs | 37 + .../examples/nrf52840/src/bin/uart_idle.rs | 38 + .../examples/nrf52840/src/bin/uart_split.rs | 62 + .../examples/nrf52840/src/bin/usb_ethernet.rs | 162 + .../nrf52840/src/bin/usb_hid_keyboard.rs | 224 + .../nrf52840/src/bin/usb_hid_mouse.rs | 123 + .../examples/nrf52840/src/bin/usb_serial.rs | 109 + .../nrf52840/src/bin/usb_serial_multitask.rs | 109 + .../nrf52840/src/bin/usb_serial_winusb.rs | 128 + embassy/examples/nrf52840/src/bin/wdt.rs | 40 + .../nrf52840/src/bin/wifi_esp_hosted.rs | 139 + embassy/examples/nrf5340/.cargo/config.toml | 9 + embassy/examples/nrf5340/Cargo.toml | 30 + embassy/examples/nrf5340/build.rs | 35 + embassy/examples/nrf5340/memory.x | 7 + embassy/examples/nrf5340/src/bin/blinky.rs | 20 + .../nrf5340/src/bin/gpiote_channel.rs | 65 + embassy/examples/nrf5340/src/bin/uart.rs | 38 + embassy/examples/nrf54l15/.cargo/config.toml | 9 + embassy/examples/nrf54l15/Cargo.toml | 20 + embassy/examples/nrf54l15/build.rs | 35 + embassy/examples/nrf54l15/memory.x | 5 + embassy/examples/nrf54l15/src/bin/blinky.rs | 23 + .../examples/nrf9151/ns/.cargo/config.toml | 9 + embassy/examples/nrf9151/ns/Cargo.toml | 20 + embassy/examples/nrf9151/ns/README.md | 4 + embassy/examples/nrf9151/ns/build.rs | 35 + embassy/examples/nrf9151/ns/flash_tfm.sh | 2 + embassy/examples/nrf9151/ns/memory.x | 7 + embassy/examples/nrf9151/ns/src/bin/blinky.rs | 22 + embassy/examples/nrf9151/ns/src/bin/uart.rs | 37 + embassy/examples/nrf9151/ns/tfm.hex | 1543 ++++++ embassy/examples/nrf9151/s/.cargo/config.toml | 8 + embassy/examples/nrf9151/s/Cargo.toml | 20 + embassy/examples/nrf9151/s/build.rs | 35 + embassy/examples/nrf9151/s/memory.x | 5 + embassy/examples/nrf9151/s/src/bin/blinky.rs | 22 + embassy/examples/nrf9160/.cargo/config.toml | 9 + embassy/examples/nrf9160/Cargo.toml | 26 + embassy/examples/nrf9160/build.rs | 35 + embassy/examples/nrf9160/memory.x | 9 + embassy/examples/nrf9160/src/bin/blinky.rs | 20 + .../nrf9160/src/bin/modem_tcp_client.rs | 197 + embassy/examples/rp/.cargo/config.toml | 8 + embassy/examples/rp/Cargo.toml | 83 + embassy/examples/rp/assets/ferris.raw | Bin 0 -> 11008 bytes embassy/examples/rp/build.rs | 36 + embassy/examples/rp/memory.x | 17 + embassy/examples/rp/src/bin/adc.rs | 48 + embassy/examples/rp/src/bin/adc_dma.rs | 54 + .../examples/rp/src/bin/assign_resources.rs | 79 + embassy/examples/rp/src/bin/blinky.rs | 29 + .../rp/src/bin/blinky_two_channels.rs | 50 + .../examples/rp/src/bin/blinky_two_tasks.rs | 49 + embassy/examples/rp/src/bin/bluetooth.rs | 150 + embassy/examples/rp/src/bin/button.rs | 28 + embassy/examples/rp/src/bin/debounce.rs | 80 + .../rp/src/bin/ethernet_w5500_multisocket.rs | 140 + .../rp/src/bin/ethernet_w5500_tcp_client.rs | 128 + .../rp/src/bin/ethernet_w5500_tcp_server.rs | 137 + .../examples/rp/src/bin/ethernet_w5500_udp.rs | 117 + embassy/examples/rp/src/bin/flash.rs | 134 + embassy/examples/rp/src/bin/gpio_async.rs | 40 + embassy/examples/rp/src/bin/gpout.rs | 37 + embassy/examples/rp/src/bin/i2c_async.rs | 110 + .../examples/rp/src/bin/i2c_async_embassy.rs | 85 + embassy/examples/rp/src/bin/i2c_blocking.rs | 74 + embassy/examples/rp/src/bin/i2c_slave.rs | 117 + embassy/examples/rp/src/bin/interrupt.rs | 94 + embassy/examples/rp/src/bin/multicore.rs | 66 + embassy/examples/rp/src/bin/multiprio.rs | 145 + .../examples/rp/src/bin/orchestrate_tasks.rs | 318 ++ embassy/examples/rp/src/bin/pio_async.rs | 130 + embassy/examples/rp/src/bin/pio_dma.rs | 83 + embassy/examples/rp/src/bin/pio_hd44780.rs | 87 + embassy/examples/rp/src/bin/pio_i2s.rs | 92 + embassy/examples/rp/src/bin/pio_onewire.rs | 83 + embassy/examples/rp/src/bin/pio_pwm.rs | 38 + .../examples/rp/src/bin/pio_rotary_encoder.rs | 55 + embassy/examples/rp/src/bin/pio_servo.rs | 128 + embassy/examples/rp/src/bin/pio_stepper.rs | 49 + embassy/examples/rp/src/bin/pio_uart.rs | 199 + embassy/examples/rp/src/bin/pio_ws2812.rs | 68 + embassy/examples/rp/src/bin/pwm.rs | 79 + embassy/examples/rp/src/bin/pwm_input.rs | 26 + embassy/examples/rp/src/bin/rosc.rs | 31 + embassy/examples/rp/src/bin/rtc.rs | 45 + embassy/examples/rp/src/bin/shared_bus.rs | 115 + embassy/examples/rp/src/bin/sharing.rs | 150 + embassy/examples/rp/src/bin/spi.rs | 46 + embassy/examples/rp/src/bin/spi_async.rs | 31 + embassy/examples/rp/src/bin/spi_display.rs | 177 + embassy/examples/rp/src/bin/spi_gc9a01.rs | 126 + embassy/examples/rp/src/bin/spi_sdmmc.rs | 82 + embassy/examples/rp/src/bin/uart.rs | 25 + .../rp/src/bin/uart_buffered_split.rs | 58 + embassy/examples/rp/src/bin/uart_r503.rs | 158 + embassy/examples/rp/src/bin/uart_unidir.rs | 50 + embassy/examples/rp/src/bin/usb_ethernet.rs | 158 + .../examples/rp/src/bin/usb_hid_keyboard.rs | 188 + embassy/examples/rp/src/bin/usb_hid_mouse.rs | 173 + embassy/examples/rp/src/bin/usb_logger.rs | 36 + embassy/examples/rp/src/bin/usb_midi.rs | 108 + embassy/examples/rp/src/bin/usb_raw.rs | 196 + embassy/examples/rp/src/bin/usb_raw_bulk.rs | 139 + embassy/examples/rp/src/bin/usb_serial.rs | 117 + .../rp/src/bin/usb_serial_with_handler.rs | 64 + .../rp/src/bin/usb_serial_with_logger.rs | 115 + embassy/examples/rp/src/bin/usb_webusb.rs | 155 + embassy/examples/rp/src/bin/watchdog.rs | 51 + .../examples/rp/src/bin/wifi_ap_tcp_server.rs | 135 + embassy/examples/rp/src/bin/wifi_blinky.rs | 66 + embassy/examples/rp/src/bin/wifi_scan.rs | 66 + .../examples/rp/src/bin/wifi_tcp_server.rs | 155 + .../examples/rp/src/bin/wifi_webrequest.rs | 190 + embassy/examples/rp/src/bin/zerocopy.rs | 94 + embassy/examples/rp23/.cargo/config.toml | 10 + embassy/examples/rp23/Cargo.toml | 80 + embassy/examples/rp23/assets/ferris.raw | Bin 0 -> 11008 bytes embassy/examples/rp23/build.rs | 35 + embassy/examples/rp23/memory.x | 75 + embassy/examples/rp23/src/bin/adc.rs | 53 + embassy/examples/rp23/src/bin/adc_dma.rs | 59 + .../examples/rp23/src/bin/assign_resources.rs | 84 + embassy/examples/rp23/src/bin/blinky.rs | 47 + .../rp23/src/bin/blinky_two_channels.rs | 55 + .../examples/rp23/src/bin/blinky_two_tasks.rs | 54 + embassy/examples/rp23/src/bin/button.rs | 33 + embassy/examples/rp23/src/bin/debounce.rs | 85 + embassy/examples/rp23/src/bin/flash.rs | 130 + embassy/examples/rp23/src/bin/gpio_async.rs | 45 + embassy/examples/rp23/src/bin/gpout.rs | 42 + embassy/examples/rp23/src/bin/i2c_async.rs | 115 + .../rp23/src/bin/i2c_async_embassy.rs | 90 + embassy/examples/rp23/src/bin/i2c_blocking.rs | 79 + embassy/examples/rp23/src/bin/i2c_slave.rs | 122 + embassy/examples/rp23/src/bin/interrupt.rs | 99 + embassy/examples/rp23/src/bin/multicore.rs | 71 + embassy/examples/rp23/src/bin/multiprio.rs | 150 + embassy/examples/rp23/src/bin/otp.rs | 36 + embassy/examples/rp23/src/bin/pio_async.rs | 135 + embassy/examples/rp23/src/bin/pio_dma.rs | 88 + embassy/examples/rp23/src/bin/pio_hd44780.rs | 92 + embassy/examples/rp23/src/bin/pio_i2s.rs | 100 + embassy/examples/rp23/src/bin/pio_onewire.rs | 88 + embassy/examples/rp23/src/bin/pio_pwm.rs | 43 + .../rp23/src/bin/pio_rotary_encoder.rs | 60 + embassy/examples/rp23/src/bin/pio_servo.rs | 133 + embassy/examples/rp23/src/bin/pio_stepper.rs | 54 + embassy/examples/rp23/src/bin/pio_uart.rs | 202 + embassy/examples/rp23/src/bin/pio_ws2812.rs | 73 + embassy/examples/rp23/src/bin/pwm.rs | 84 + embassy/examples/rp23/src/bin/pwm_input.rs | 31 + .../src/bin/pwm_tb6612fng_motor_driver.rs | 110 + embassy/examples/rp23/src/bin/rosc.rs | 36 + embassy/examples/rp23/src/bin/shared_bus.rs | 120 + embassy/examples/rp23/src/bin/sharing.rs | 155 + embassy/examples/rp23/src/bin/spi.rs | 51 + embassy/examples/rp23/src/bin/spi_async.rs | 36 + embassy/examples/rp23/src/bin/spi_display.rs | 182 + embassy/examples/rp23/src/bin/spi_sdmmc.rs | 88 + embassy/examples/rp23/src/bin/trng.rs | 54 + embassy/examples/rp23/src/bin/uart.rs | 30 + .../rp23/src/bin/uart_buffered_split.rs | 63 + embassy/examples/rp23/src/bin/uart_r503.rs | 163 + embassy/examples/rp23/src/bin/uart_unidir.rs | 55 + .../examples/rp23/src/bin/usb_hid_keyboard.rs | 193 + embassy/examples/rp23/src/bin/usb_webusb.rs | 160 + embassy/examples/rp23/src/bin/watchdog.rs | 56 + embassy/examples/rp23/src/bin/zerocopy.rs | 99 + embassy/examples/std/Cargo.toml | 29 + embassy/examples/std/README.md | 23 + embassy/examples/std/src/bin/net.rs | 96 + embassy/examples/std/src/bin/net_dns.rs | 83 + embassy/examples/std/src/bin/net_ppp.rs | 215 + embassy/examples/std/src/bin/net_udp.rs | 91 + embassy/examples/std/src/bin/serial.rs | 55 + embassy/examples/std/src/bin/tcp_accept.rs | 119 + embassy/examples/std/src/bin/tick.rs | 21 + embassy/examples/std/src/serial_port.rs | 66 + embassy/examples/stm32c0/.cargo/config.toml | 9 + embassy/examples/stm32c0/Cargo.toml | 24 + embassy/examples/stm32c0/build.rs | 5 + embassy/examples/stm32c0/src/bin/blinky.rs | 26 + embassy/examples/stm32c0/src/bin/button.rs | 24 + .../examples/stm32c0/src/bin/button_exti.rs | 25 + embassy/examples/stm32f0/.cargo/config.toml | 8 + embassy/examples/stm32f0/Cargo.toml | 22 + embassy/examples/stm32f0/build.rs | 5 + embassy/examples/stm32f0/src/bin/adc.rs | 40 + embassy/examples/stm32f0/src/bin/blinky.rs | 27 + .../src/bin/button_controlled_blink.rs | 62 + .../examples/stm32f0/src/bin/button_exti.rs | 25 + embassy/examples/stm32f0/src/bin/hello.rs | 16 + embassy/examples/stm32f0/src/bin/multiprio.rs | 149 + embassy/examples/stm32f0/src/bin/wdg.rs | 24 + embassy/examples/stm32f1/.cargo/config.toml | 9 + embassy/examples/stm32f1/Cargo.toml | 31 + embassy/examples/stm32f1/build.rs | 5 + embassy/examples/stm32f1/src/bin/adc.rs | 39 + embassy/examples/stm32f1/src/bin/blinky.rs | 26 + embassy/examples/stm32f1/src/bin/can.rs | 140 + embassy/examples/stm32f1/src/bin/hello.rs | 19 + .../examples/stm32f1/src/bin/input_capture.rs | 52 + embassy/examples/stm32f1/src/bin/pwm_input.rs | 54 + .../examples/stm32f1/src/bin/usb_serial.rs | 121 + embassy/examples/stm32f2/.cargo/config.toml | 9 + embassy/examples/stm32f2/Cargo.toml | 25 + embassy/examples/stm32f2/build.rs | 5 + embassy/examples/stm32f2/src/bin/blinky.rs | 26 + embassy/examples/stm32f2/src/bin/pll.rs | 52 + embassy/examples/stm32f3/.cargo/config.toml | 9 + embassy/examples/stm32f3/Cargo.toml | 29 + embassy/examples/stm32f3/README.md | 24 + embassy/examples/stm32f3/build.rs | 5 + embassy/examples/stm32f3/src/bin/blinky.rs | 26 + embassy/examples/stm32f3/src/bin/button.rs | 30 + .../examples/stm32f3/src/bin/button_events.rs | 154 + .../examples/stm32f3/src/bin/button_exti.rs | 25 + embassy/examples/stm32f3/src/bin/flash.rs | 39 + embassy/examples/stm32f3/src/bin/hello.rs | 19 + embassy/examples/stm32f3/src/bin/multiprio.rs | 150 + embassy/examples/stm32f3/src/bin/spi_dma.rs | 31 + .../examples/stm32f3/src/bin/tsc_blocking.rs | 138 + .../examples/stm32f3/src/bin/tsc_multipin.rs | 204 + embassy/examples/stm32f3/src/bin/usart_dma.rs | 32 + .../examples/stm32f3/src/bin/usb_serial.rs | 115 + embassy/examples/stm32f334/.cargo/config.toml | 9 + embassy/examples/stm32f334/Cargo.toml | 25 + embassy/examples/stm32f334/build.rs | 5 + embassy/examples/stm32f334/src/bin/adc.rs | 65 + embassy/examples/stm32f334/src/bin/button.rs | 26 + embassy/examples/stm32f334/src/bin/hello.rs | 19 + embassy/examples/stm32f334/src/bin/opamp.rs | 68 + embassy/examples/stm32f334/src/bin/pwm.rs | 84 + embassy/examples/stm32f4/.cargo/config.toml | 9 + embassy/examples/stm32f4/Cargo.toml | 39 + embassy/examples/stm32f4/build.rs | 5 + embassy/examples/stm32f4/src/bin/adc.rs | 67 + embassy/examples/stm32f4/src/bin/adc_dma.rs | 83 + embassy/examples/stm32f4/src/bin/blinky.rs | 26 + embassy/examples/stm32f4/src/bin/button.rs | 31 + .../examples/stm32f4/src/bin/button_exti.rs | 25 + embassy/examples/stm32f4/src/bin/can.rs | 68 + embassy/examples/stm32f4/src/bin/dac.rs | 36 + embassy/examples/stm32f4/src/bin/eth.rs | 130 + .../stm32f4/src/bin/eth_compliance_test.rs | 77 + embassy/examples/stm32f4/src/bin/eth_w5500.rs | 134 + embassy/examples/stm32f4/src/bin/flash.rs | 64 + .../examples/stm32f4/src/bin/flash_async.rs | 84 + embassy/examples/stm32f4/src/bin/hello.rs | 19 + embassy/examples/stm32f4/src/bin/i2c.rs | 27 + embassy/examples/stm32f4/src/bin/i2c_async.rs | 61 + .../stm32f4/src/bin/i2c_comparison.rs | 134 + embassy/examples/stm32f4/src/bin/i2s_dma.rs | 33 + .../examples/stm32f4/src/bin/input_capture.rs | 52 + embassy/examples/stm32f4/src/bin/mco.rs | 29 + embassy/examples/stm32f4/src/bin/multiprio.rs | 150 + embassy/examples/stm32f4/src/bin/pwm.rs | 35 + .../stm32f4/src/bin/pwm_complementary.rs | 53 + embassy/examples/stm32f4/src/bin/pwm_input.rs | 54 + embassy/examples/stm32f4/src/bin/rtc.rs | 35 + embassy/examples/stm32f4/src/bin/sdmmc.rs | 107 + embassy/examples/stm32f4/src/bin/spi.rs | 31 + embassy/examples/stm32f4/src/bin/spi_dma.rs | 31 + embassy/examples/stm32f4/src/bin/usart.rs | 31 + .../stm32f4/src/bin/usart_buffered.rs | 34 + embassy/examples/stm32f4/src/bin/usart_dma.rs | 32 + .../examples/stm32f4/src/bin/usb_ethernet.rs | 194 + .../stm32f4/src/bin/usb_hid_keyboard.rs | 232 + .../examples/stm32f4/src/bin/usb_hid_mouse.rs | 158 + embassy/examples/stm32f4/src/bin/usb_raw.rs | 231 + .../examples/stm32f4/src/bin/usb_serial.rs | 136 + .../stm32f4/src/bin/usb_uac_speaker.rs | 390 ++ embassy/examples/stm32f4/src/bin/wdt.rs | 41 + .../examples/stm32f4/src/bin/ws2812_pwm.rs | 102 + .../examples/stm32f4/src/bin/ws2812_spi.rs | 95 + embassy/examples/stm32f469/.cargo/config.toml | 9 + embassy/examples/stm32f469/Cargo.toml | 22 + embassy/examples/stm32f469/build.rs | 5 + embassy/examples/stm32f469/src/bin/dsi_bsp.rs | 694 +++ embassy/examples/stm32f469/src/bin/ferris.bin | 70 + embassy/examples/stm32f7/.cargo/config.toml | 9 + embassy/examples/stm32f7/Cargo.toml | 36 + embassy/examples/stm32f7/build.rs | 5 + embassy/examples/stm32f7/src/bin/adc.rs | 33 + embassy/examples/stm32f7/src/bin/blinky.rs | 26 + embassy/examples/stm32f7/src/bin/button.rs | 31 + .../examples/stm32f7/src/bin/button_exti.rs | 25 + embassy/examples/stm32f7/src/bin/can.rs | 73 + embassy/examples/stm32f7/src/bin/cryp.rs | 80 + embassy/examples/stm32f7/src/bin/eth.rs | 131 + embassy/examples/stm32f7/src/bin/flash.rs | 55 + embassy/examples/stm32f7/src/bin/hash.rs | 78 + embassy/examples/stm32f7/src/bin/hello.rs | 19 + embassy/examples/stm32f7/src/bin/qspi.rs | 301 ++ embassy/examples/stm32f7/src/bin/sdmmc.rs | 62 + embassy/examples/stm32f7/src/bin/usart_dma.rs | 31 + .../examples/stm32f7/src/bin/usb_serial.rs | 136 + embassy/examples/stm32g0/.cargo/config.toml | 9 + embassy/examples/stm32g0/Cargo.toml | 29 + embassy/examples/stm32g0/build.rs | 5 + embassy/examples/stm32g0/src/bin/adc.rs | 34 + embassy/examples/stm32g0/src/bin/adc_dma.rs | 44 + .../stm32g0/src/bin/adc_oversampling.rs | 43 + embassy/examples/stm32g0/src/bin/blinky.rs | 26 + embassy/examples/stm32g0/src/bin/button.rs | 24 + .../examples/stm32g0/src/bin/button_exti.rs | 25 + embassy/examples/stm32g0/src/bin/flash.rs | 43 + embassy/examples/stm32g0/src/bin/hf_timer.rs | 62 + embassy/examples/stm32g0/src/bin/i2c_async.rs | 48 + .../examples/stm32g0/src/bin/input_capture.rs | 67 + .../stm32g0/src/bin/pwm_complementary.rs | 57 + embassy/examples/stm32g0/src/bin/pwm_input.rs | 63 + embassy/examples/stm32g0/src/bin/rtc.rs | 31 + .../examples/stm32g0/src/bin/spi_neopixel.rs | 101 + embassy/examples/stm32g0/src/bin/usart.rs | 25 + .../stm32g0/src/bin/usart_buffered.rs | 34 + embassy/examples/stm32g0/src/bin/usart_dma.rs | 27 + .../examples/stm32g0/src/bin/usb_serial.rs | 97 + embassy/examples/stm32g4/.cargo/config.toml | 9 + embassy/examples/stm32g4/Cargo.toml | 29 + embassy/examples/stm32g4/build.rs | 5 + embassy/examples/stm32g4/src/bin/adc.rs | 39 + .../stm32g4/src/bin/adc_differential.rs | 47 + embassy/examples/stm32g4/src/bin/adc_dma.rs | 60 + .../stm32g4/src/bin/adc_oversampling.rs | 57 + embassy/examples/stm32g4/src/bin/blinky.rs | 26 + embassy/examples/stm32g4/src/bin/button.rs | 24 + .../examples/stm32g4/src/bin/button_exti.rs | 25 + embassy/examples/stm32g4/src/bin/can.rs | 234 + embassy/examples/stm32g4/src/bin/pll.rs | 34 + embassy/examples/stm32g4/src/bin/pwm.rs | 35 + embassy/examples/stm32g4/src/bin/usb_c_pd.rs | 86 + .../examples/stm32g4/src/bin/usb_serial.rs | 111 + embassy/examples/stm32h5/.cargo/config.toml | 8 + embassy/examples/stm32h5/Cargo.toml | 72 + embassy/examples/stm32h5/build.rs | 5 + embassy/examples/stm32h5/src/bin/adc.rs | 59 + embassy/examples/stm32h5/src/bin/blinky.rs | 26 + .../examples/stm32h5/src/bin/button_exti.rs | 25 + embassy/examples/stm32h5/src/bin/can.rs | 98 + embassy/examples/stm32h5/src/bin/cordic.rs | 78 + embassy/examples/stm32h5/src/bin/eth.rs | 133 + embassy/examples/stm32h5/src/bin/i2c.rs | 42 + embassy/examples/stm32h5/src/bin/rng.rs | 24 + embassy/examples/stm32h5/src/bin/stop.rs | 71 + embassy/examples/stm32h5/src/bin/usart.rs | 39 + embassy/examples/stm32h5/src/bin/usart_dma.rs | 47 + .../examples/stm32h5/src/bin/usart_split.rs | 47 + .../examples/stm32h5/src/bin/usb_serial.rs | 126 + .../stm32h5/src/bin/usb_uac_speaker.rs | 381 ++ embassy/examples/stm32h7/.cargo/config.toml | 8 + embassy/examples/stm32h7/Cargo.toml | 75 + embassy/examples/stm32h7/build.rs | 35 + embassy/examples/stm32h7/memory.x | 14 + embassy/examples/stm32h7/src/bin/adc.rs | 60 + embassy/examples/stm32h7/src/bin/adc_dma.rs | 76 + embassy/examples/stm32h7/src/bin/blinky.rs | 26 + .../examples/stm32h7/src/bin/button_exti.rs | 25 + embassy/examples/stm32h7/src/bin/camera.rs | 302 ++ embassy/examples/stm32h7/src/bin/can.rs | 98 + embassy/examples/stm32h7/src/bin/dac.rs | 68 + embassy/examples/stm32h7/src/bin/dac_dma.rs | 158 + embassy/examples/stm32h7/src/bin/eth.rs | 133 + .../examples/stm32h7/src/bin/eth_client.rs | 132 + .../stm32h7/src/bin/eth_client_mii.rs | 138 + embassy/examples/stm32h7/src/bin/flash.rs | 55 + embassy/examples/stm32h7/src/bin/fmc.rs | 216 + embassy/examples/stm32h7/src/bin/i2c.rs | 42 + .../examples/stm32h7/src/bin/i2c_shared.rs | 113 + .../stm32h7/src/bin/low_level_timer_api.rs | 138 + embassy/examples/stm32h7/src/bin/mco.rs | 29 + embassy/examples/stm32h7/src/bin/multiprio.rs | 150 + embassy/examples/stm32h7/src/bin/pwm.rs | 57 + embassy/examples/stm32h7/src/bin/rng.rs | 26 + embassy/examples/stm32h7/src/bin/rtc.rs | 36 + embassy/examples/stm32h7/src/bin/sai.rs | 187 + embassy/examples/stm32h7/src/bin/sdmmc.rs | 63 + embassy/examples/stm32h7/src/bin/signal.rs | 36 + embassy/examples/stm32h7/src/bin/spi.rs | 71 + embassy/examples/stm32h7/src/bin/spi_bdma.rs | 85 + embassy/examples/stm32h7/src/bin/spi_dma.rs | 68 + embassy/examples/stm32h7/src/bin/usart.rs | 39 + embassy/examples/stm32h7/src/bin/usart_dma.rs | 47 + .../examples/stm32h7/src/bin/usart_split.rs | 47 + .../examples/stm32h7/src/bin/usb_serial.rs | 137 + embassy/examples/stm32h7/src/bin/wdg.rs | 23 + embassy/examples/stm32h723/.cargo/config.toml | 8 + embassy/examples/stm32h723/Cargo.toml | 69 + embassy/examples/stm32h723/build.rs | 35 + embassy/examples/stm32h723/memory.x | 106 + embassy/examples/stm32h723/src/bin/spdifrx.rs | 165 + embassy/examples/stm32h735/.cargo/config.toml | 8 + embassy/examples/stm32h735/Cargo.toml | 61 + embassy/examples/stm32h735/build.rs | 35 + embassy/examples/stm32h735/memory.x | 5 + embassy/examples/stm32h735/src/bin/ferris.bmp | Bin 0 -> 6794 bytes embassy/examples/stm32h735/src/bin/ltdc.rs | 467 ++ .../examples/stm32h755cm4/.cargo/config.toml | 8 + embassy/examples/stm32h755cm4/Cargo.toml | 75 + embassy/examples/stm32h755cm4/build.rs | 35 + embassy/examples/stm32h755cm4/memory.x | 15 + .../examples/stm32h755cm4/src/bin/blinky.rs | 32 + .../examples/stm32h755cm7/.cargo/config.toml | 8 + embassy/examples/stm32h755cm7/Cargo.toml | 75 + embassy/examples/stm32h755cm7/build.rs | 35 + embassy/examples/stm32h755cm7/memory.x | 15 + .../examples/stm32h755cm7/src/bin/blinky.rs | 54 + embassy/examples/stm32h7b0/.cargo/config.toml | 8 + embassy/examples/stm32h7b0/Cargo.toml | 74 + embassy/examples/stm32h7b0/build.rs | 35 + embassy/examples/stm32h7b0/memory.x | 5 + .../stm32h7b0/src/bin/ospi_memory_mapped.rs | 433 ++ embassy/examples/stm32h7rs/.cargo/config.toml | 8 + embassy/examples/stm32h7rs/Cargo.toml | 73 + embassy/examples/stm32h7rs/build.rs | 5 + embassy/examples/stm32h7rs/src/bin/blinky.rs | 51 + .../examples/stm32h7rs/src/bin/button_exti.rs | 25 + embassy/examples/stm32h7rs/src/bin/can.rs | 98 + embassy/examples/stm32h7rs/src/bin/i2c.rs | 42 + embassy/examples/stm32h7rs/src/bin/mco.rs | 29 + .../examples/stm32h7rs/src/bin/multiprio.rs | 150 + embassy/examples/stm32h7rs/src/bin/rng.rs | 26 + embassy/examples/stm32h7rs/src/bin/rtc.rs | 36 + embassy/examples/stm32h7rs/src/bin/signal.rs | 36 + embassy/examples/stm32h7rs/src/bin/spi.rs | 49 + embassy/examples/stm32h7rs/src/bin/spi_dma.rs | 46 + embassy/examples/stm32h7rs/src/bin/usart.rs | 39 + .../examples/stm32h7rs/src/bin/usart_dma.rs | 47 + .../examples/stm32h7rs/src/bin/usart_split.rs | 47 + .../examples/stm32h7rs/src/bin/usb_serial.rs | 140 + embassy/examples/stm32l0/.cargo/config.toml | 9 + embassy/examples/stm32l0/Cargo.toml | 30 + embassy/examples/stm32l0/README.md | 24 + embassy/examples/stm32l0/build.rs | 5 + embassy/examples/stm32l0/src/bin/adc.rs | 40 + embassy/examples/stm32l0/src/bin/blinky.rs | 26 + embassy/examples/stm32l0/src/bin/button.rs | 29 + .../examples/stm32l0/src/bin/button_exti.rs | 26 + embassy/examples/stm32l0/src/bin/dds.rs | 116 + embassy/examples/stm32l0/src/bin/flash.rs | 39 + embassy/examples/stm32l0/src/bin/raw_spawn.rs | 52 + embassy/examples/stm32l0/src/bin/spi.rs | 30 + embassy/examples/stm32l0/src/bin/tsc_async.rs | 116 + .../examples/stm32l0/src/bin/tsc_blocking.rs | 142 + .../examples/stm32l0/src/bin/tsc_multipin.rs | 209 + embassy/examples/stm32l0/src/bin/usart_dma.rs | 27 + embassy/examples/stm32l0/src/bin/usart_irq.rs | 34 + embassy/examples/stm32l1/.cargo/config.toml | 9 + embassy/examples/stm32l1/Cargo.toml | 26 + embassy/examples/stm32l1/build.rs | 5 + embassy/examples/stm32l1/src/bin/blinky.rs | 26 + embassy/examples/stm32l1/src/bin/flash.rs | 39 + embassy/examples/stm32l1/src/bin/spi.rs | 30 + embassy/examples/stm32l1/src/bin/usart.rs | 37 + .../examples/stm32l1/src/bin/usb_serial.rs | 101 + embassy/examples/stm32l4/.cargo/config.toml | 12 + embassy/examples/stm32l4/Cargo.toml | 39 + embassy/examples/stm32l4/README.md | 24 + embassy/examples/stm32l4/build.rs | 5 + embassy/examples/stm32l4/src/bin/adc.rs | 29 + embassy/examples/stm32l4/src/bin/blinky.rs | 23 + embassy/examples/stm32l4/src/bin/button.rs | 23 + .../examples/stm32l4/src/bin/button_exti.rs | 25 + embassy/examples/stm32l4/src/bin/can.rs | 68 + embassy/examples/stm32l4/src/bin/dac.rs | 35 + embassy/examples/stm32l4/src/bin/dac_dma.rs | 129 + embassy/examples/stm32l4/src/bin/i2c.rs | 21 + .../stm32l4/src/bin/i2c_blocking_async.rs | 24 + embassy/examples/stm32l4/src/bin/i2c_dma.rs | 36 + embassy/examples/stm32l4/src/bin/mco.rs | 26 + embassy/examples/stm32l4/src/bin/rng.rs | 37 + embassy/examples/stm32l4/src/bin/rtc.rs | 52 + .../src/bin/spe_adin1110_http_server.rs | 441 ++ embassy/examples/stm32l4/src/bin/spi.rs | 30 + .../stm32l4/src/bin/spi_blocking_async.rs | 47 + embassy/examples/stm32l4/src/bin/spi_dma.rs | 43 + embassy/examples/stm32l4/src/bin/tsc_async.rs | 108 + .../examples/stm32l4/src/bin/tsc_blocking.rs | 147 + .../examples/stm32l4/src/bin/tsc_multipin.rs | 198 + embassy/examples/stm32l4/src/bin/usart.rs | 30 + embassy/examples/stm32l4/src/bin/usart_dma.rs | 34 + .../examples/stm32l4/src/bin/usb_serial.rs | 132 + embassy/examples/stm32l5/.cargo/config.toml | 9 + embassy/examples/stm32l5/Cargo.toml | 35 + embassy/examples/stm32l5/build.rs | 5 + .../examples/stm32l5/src/bin/button_exti.rs | 25 + embassy/examples/stm32l5/src/bin/rng.rs | 38 + embassy/examples/stm32l5/src/bin/stop.rs | 61 + .../examples/stm32l5/src/bin/usb_ethernet.rs | 171 + .../examples/stm32l5/src/bin/usb_hid_mouse.rs | 133 + .../examples/stm32l5/src/bin/usb_serial.rs | 108 + embassy/examples/stm32u0/.cargo/config.toml | 9 + embassy/examples/stm32u0/Cargo.toml | 29 + embassy/examples/stm32u0/build.rs | 5 + embassy/examples/stm32u0/src/bin/adc.rs | 30 + embassy/examples/stm32u0/src/bin/blinky.rs | 26 + embassy/examples/stm32u0/src/bin/button.rs | 24 + .../examples/stm32u0/src/bin/button_exti.rs | 25 + embassy/examples/stm32u0/src/bin/crc.rs | 31 + embassy/examples/stm32u0/src/bin/dac.rs | 35 + embassy/examples/stm32u0/src/bin/flash.rs | 43 + embassy/examples/stm32u0/src/bin/i2c.rs | 21 + embassy/examples/stm32u0/src/bin/rng.rs | 43 + embassy/examples/stm32u0/src/bin/rtc.rs | 49 + embassy/examples/stm32u0/src/bin/spi.rs | 30 + embassy/examples/stm32u0/src/bin/usart.rs | 25 + .../examples/stm32u0/src/bin/usb_serial.rs | 109 + embassy/examples/stm32u0/src/bin/wdt.rs | 41 + embassy/examples/stm32u5/.cargo/config.toml | 9 + embassy/examples/stm32u5/Cargo.toml | 34 + embassy/examples/stm32u5/build.rs | 5 + embassy/examples/stm32u5/src/bin/blinky.rs | 26 + embassy/examples/stm32u5/src/bin/boot.rs | 14 + embassy/examples/stm32u5/src/bin/ferris.bmp | Bin 0 -> 6794 bytes embassy/examples/stm32u5/src/bin/flash.rs | 55 + embassy/examples/stm32u5/src/bin/i2c.rs | 25 + embassy/examples/stm32u5/src/bin/ltdc.rs | 461 ++ embassy/examples/stm32u5/src/bin/rng.rs | 25 + embassy/examples/stm32u5/src/bin/tsc.rs | 84 + .../examples/stm32u5/src/bin/usb_hs_serial.rs | 129 + .../examples/stm32u5/src/bin/usb_serial.rs | 126 + embassy/examples/stm32wb/.cargo/config.toml | 10 + embassy/examples/stm32wb/Cargo.toml | 56 + embassy/examples/stm32wb/build.rs | 11 + embassy/examples/stm32wb/src/bin/blinky.rs | 26 + .../examples/stm32wb/src/bin/button_exti.rs | 25 + .../stm32wb/src/bin/eddystone_beacon.rs | 248 + .../examples/stm32wb/src/bin/gatt_server.rs | 398 ++ embassy/examples/stm32wb/src/bin/mac_ffd.rs | 186 + .../examples/stm32wb/src/bin/mac_ffd_net.rs | 177 + embassy/examples/stm32wb/src/bin/mac_rfd.rs | 183 + embassy/examples/stm32wb/src/bin/tl_mbox.rs | 77 + .../examples/stm32wb/src/bin/tl_mbox_ble.rs | 65 + .../examples/stm32wb/src/bin/tl_mbox_mac.rs | 77 + embassy/examples/stm32wba/.cargo/config.toml | 8 + embassy/examples/stm32wba/Cargo.toml | 25 + embassy/examples/stm32wba/build.rs | 10 + embassy/examples/stm32wba/src/bin/blinky.rs | 26 + .../examples/stm32wba/src/bin/button_exti.rs | 25 + embassy/examples/stm32wl/.cargo/config.toml | 9 + embassy/examples/stm32wl/Cargo.toml | 27 + embassy/examples/stm32wl/build.rs | 5 + embassy/examples/stm32wl/memory.x | 15 + embassy/examples/stm32wl/src/bin/blinky.rs | 32 + embassy/examples/stm32wl/src/bin/button.rs | 34 + .../examples/stm32wl/src/bin/button_exti.rs | 31 + embassy/examples/stm32wl/src/bin/flash.rs | 45 + embassy/examples/stm32wl/src/bin/random.rs | 51 + embassy/examples/stm32wl/src/bin/rtc.rs | 57 + .../examples/stm32wl/src/bin/uart_async.rs | 67 + embassy/examples/wasm/Cargo.toml | 21 + embassy/examples/wasm/README.md | 26 + embassy/examples/wasm/index.html | 25 + embassy/examples/wasm/src/lib.rs | 28 + embassy/release/bump-dependency.sh | 11 + embassy/rust-toolchain-nightly.toml | 12 + embassy/rust-toolchain.toml | 12 + embassy/rustfmt.toml | 3 + embassy/tests/link_ram_cortex_m.x | 280 + embassy/tests/nrf/.cargo/config.toml | 11 + embassy/tests/nrf/Cargo.toml | 114 + embassy/tests/nrf/build.rs | 37 + embassy/tests/nrf/gen_test.py | 44 + embassy/tests/nrf/memory-nrf51422.x | 5 + embassy/tests/nrf/memory-nrf52832.x | 5 + embassy/tests/nrf/memory-nrf52833.x | 5 + embassy/tests/nrf/memory-nrf52840.x | 5 + embassy/tests/nrf/memory-nrf5340.x | 5 + embassy/tests/nrf/memory-nrf9160.x | 5 + embassy/tests/nrf/src/bin/buffered_uart.rs | 82 + .../tests/nrf/src/bin/buffered_uart_full.rs | 70 + .../tests/nrf/src/bin/buffered_uart_halves.rs | 86 + .../tests/nrf/src/bin/buffered_uart_spam.rs | 86 + .../nrf/src/bin/ethernet_enc28j60_perf.rs | 85 + embassy/tests/nrf/src/bin/gpio.rs | 31 + embassy/tests/nrf/src/bin/gpiote.rs | 50 + embassy/tests/nrf/src/bin/spim.rs | 42 + embassy/tests/nrf/src/bin/timer.rs | 26 + embassy/tests/nrf/src/bin/uart_halves.rs | 41 + embassy/tests/nrf/src/bin/uart_split.rs | 49 + .../tests/nrf/src/bin/wifi_esp_hosted_perf.rs | 111 + embassy/tests/nrf/src/common.rs | 117 + embassy/tests/perf-client/Cargo.toml | 10 + embassy/tests/perf-client/src/lib.rs | 179 + embassy/tests/perf-server/Cargo.toml | 8 + embassy/tests/perf-server/deploy.sh | 11 + embassy/tests/perf-server/perf-server.service | 16 + embassy/tests/perf-server/src/main.rs | 90 + embassy/tests/riscv32/.cargo/config.toml | 5 + embassy/tests/riscv32/Cargo.toml | 46 + embassy/tests/riscv32/build.rs | 8 + embassy/tests/riscv32/link.x | 214 + embassy/tests/riscv32/memory.x | 14 + embassy/tests/riscv32/src/bin/empty.rs | 15 + embassy/tests/rp/.cargo/config.toml | 21 + embassy/tests/rp/Cargo.toml | 68 + embassy/tests/rp/build.rs | 17 + embassy/tests/rp/memory.x | 4 + embassy/tests/rp/src/bin/adc.rs | 154 + embassy/tests/rp/src/bin/bootsel.rs | 25 + embassy/tests/rp/src/bin/cyw43-perf.rs | 107 + embassy/tests/rp/src/bin/dma_copy_async.rs | 41 + .../tests/rp/src/bin/ethernet_w5100s_perf.rs | 93 + embassy/tests/rp/src/bin/flash.rs | 71 + embassy/tests/rp/src/bin/float.rs | 51 + embassy/tests/rp/src/bin/gpio.rs | 246 + embassy/tests/rp/src/bin/gpio_async.rs | 148 + embassy/tests/rp/src/bin/gpio_multicore.rs | 67 + embassy/tests/rp/src/bin/i2c.rs | 237 + embassy/tests/rp/src/bin/multicore.rs | 51 + embassy/tests/rp/src/bin/pio_irq.rs | 51 + embassy/tests/rp/src/bin/pio_multi_load.rs | 124 + embassy/tests/rp/src/bin/pwm.rs | 182 + embassy/tests/rp/src/bin/spi.rs | 28 + embassy/tests/rp/src/bin/spi_async.rs | 84 + embassy/tests/rp/src/bin/timer.rs | 25 + embassy/tests/rp/src/bin/uart.rs | 169 + embassy/tests/rp/src/bin/uart_buffered.rs | 254 + embassy/tests/rp/src/bin/uart_dma.rs | 250 + embassy/tests/rp/src/bin/uart_upgrade.rs | 58 + embassy/tests/stm32/.cargo/config.toml | 22 + embassy/tests/stm32/Cargo.toml | 235 + embassy/tests/stm32/build.rs | 35 + embassy/tests/stm32/gen_test.py | 44 + embassy/tests/stm32/src/bin/can.rs | 74 + embassy/tests/stm32/src/bin/can_common.rs | 109 + embassy/tests/stm32/src/bin/cordic.rs | 140 + embassy/tests/stm32/src/bin/cryp.rs | 79 + embassy/tests/stm32/src/bin/dac.rs | 76 + embassy/tests/stm32/src/bin/dac_l1.rs | 86 + embassy/tests/stm32/src/bin/eth.rs | 120 + embassy/tests/stm32/src/bin/fdcan.rs | 142 + embassy/tests/stm32/src/bin/gpio.rs | 227 + embassy/tests/stm32/src/bin/hash.rs | 102 + embassy/tests/stm32/src/bin/rng.rs | 60 + embassy/tests/stm32/src/bin/rtc.rs | 45 + embassy/tests/stm32/src/bin/sdmmc.rs | 141 + embassy/tests/stm32/src/bin/spi.rs | 139 + embassy/tests/stm32/src/bin/spi_dma.rs | 180 + embassy/tests/stm32/src/bin/stop.rs | 80 + embassy/tests/stm32/src/bin/timer.rs | 26 + embassy/tests/stm32/src/bin/ucpd.rs | 120 + embassy/tests/stm32/src/bin/usart.rs | 115 + embassy/tests/stm32/src/bin/usart_dma.rs | 73 + .../stm32/src/bin/usart_rx_ringbuffered.rs | 110 + embassy/tests/stm32/src/bin/wpan_ble.rs | 252 + embassy/tests/stm32/src/bin/wpan_mac.rs | 127 + embassy/tests/stm32/src/common.rs | 719 +++ embassy/tests/utils/Cargo.toml | 8 + .../tests/utils/src/bin/saturate_serial.rs | 53 + embedded-nal/.github/workflows/ci.yml | 59 + embedded-nal/.github/workflows/clippy.yml | 27 + embedded-nal/.github/workflows/rustfmt.yml | 29 + embedded-nal/.gitignore | 4 + embedded-nal/CHANGELOG.md | 99 + embedded-nal/Cargo.toml | 21 + embedded-nal/LICENSE-APACHE | 201 + embedded-nal/LICENSE-MIT | 27 + embedded-nal/README.md | 55 + embedded-nal/embedded-nal-async/CHANGELOG.md | 61 + embedded-nal/embedded-nal-async/Cargo.toml | 16 + embedded-nal/embedded-nal-async/README.md | 30 + embedded-nal/embedded-nal-async/src/dns.rs | 62 + embedded-nal/embedded-nal-async/src/lib.rs | 14 + .../embedded-nal-async/src/stack/mod.rs | 5 + .../embedded-nal-async/src/stack/tcp.rs | 35 + .../embedded-nal-async/src/stack/udp.rs | 199 + embedded-nal/rustfmt.toml | 2 + embedded-nal/src/dns.rs | 75 + embedded-nal/src/lib.rs | 15 + embedded-nal/src/stack/mod.rs | 7 + embedded-nal/src/stack/share.rs | 143 + embedded-nal/src/stack/tcp.rs | 134 + embedded-nal/src/stack/udp.rs | 110 + memory.x | 17 + rp-hal/.github/workflows/on_target_tests.yml | 70 + rp-hal/.github/workflows/rp2040_hal.yml | 99 + .../.github/workflows/rp2040_hal_examples.yml | 72 + rp-hal/.github/workflows/rp235x_hal_arm.yml | 99 + .../workflows/rp235x_hal_examples_arm.yml | 72 + .../workflows/rp235x_hal_examples_riscv.yml | 66 + rp-hal/.github/workflows/rp235x_hal_riscv.yml | 99 + rp-hal/.github/workflows/rp_binary_info.yml | 77 + rp-hal/.github/workflows/rp_hal_common.yml | 77 + rp-hal/.gitignore | 5 + rp-hal/CODE_OF_CONDUCT.md | 39 + rp-hal/LICENSE-APACHE | 202 + rp-hal/LICENSE-MIT | 19 + rp-hal/NOTICE | 3 + rp-hal/README.md | 312 ++ rp-hal/clippy.toml | 1 + rp-hal/format.bat | 10 + rp-hal/format.sh | 12 + rp-hal/on-target-tests/.cargo/config.toml | 37 + rp-hal/on-target-tests/.gitignore | 14 + rp-hal/on-target-tests/Cargo.toml | 78 + rp-hal/on-target-tests/LICENSE-APACHE | 202 + rp-hal/on-target-tests/LICENSE-MIT | 19 + rp-hal/on-target-tests/NOTICE | 3 + rp-hal/on-target-tests/README.md | 52 + rp-hal/on-target-tests/memory.x | 15 + rp-hal/on-target-tests/run_tests.bat | 6 + rp-hal/on-target-tests/run_tests.sh | 6 + rp-hal/on-target-tests/tests/dma_dyn.rs | 205 + rp-hal/on-target-tests/tests/dma_m2m_u16.rs | 99 + rp-hal/on-target-tests/tests/dma_m2m_u32.rs | 99 + rp-hal/on-target-tests/tests/dma_m2m_u8.rs | 99 + .../tests/dma_spi_loopback_u16.rs | 136 + .../tests/dma_spi_loopback_u8.rs | 137 + rp-hal/on-target-tests/tests/gpio.rs | 163 + rp-hal/on-target-tests/tests/i2c_loopback.rs | 128 + .../tests/i2c_loopback_async.rs | 82 + .../tests/i2c_tests/blocking.rs | 530 ++ rp-hal/on-target-tests/tests/i2c_tests/mod.rs | 127 + .../tests/i2c_tests/non_blocking.rs | 390 ++ .../tests/i2c_tests/test_executor.rs | 79 + rp-hal/rp-binary-info/Cargo.toml | 17 + rp-hal/rp-binary-info/LICENSE-APACHE | 202 + rp-hal/rp-binary-info/LICENSE-MIT | 19 + rp-hal/rp-binary-info/NOTICE | 3 + rp-hal/rp-binary-info/README.md | 15 + rp-hal/rp-binary-info/src/consts.rs | 31 + rp-hal/rp-binary-info/src/lib.rs | 251 + rp-hal/rp-binary-info/src/macros.rs | 169 + rp-hal/rp-binary-info/src/types.rs | 192 + rp-hal/rp-hal-common/Cargo.toml | 14 + rp-hal/rp-hal-common/LICENSE-APACHE | 202 + rp-hal/rp-hal-common/LICENSE-MIT | 19 + rp-hal/rp-hal-common/NOTICE | 3 + rp-hal/rp-hal-common/README.md | 15 + rp-hal/rp-hal-common/src/lib.rs | 11 + .../rp-hal-common/src/uart/common_configs.rs | 45 + rp-hal/rp-hal-common/src/uart/mod.rs | 6 + rp-hal/rp-hal-common/src/uart/utils.rs | 88 + rp-hal/rp2040-hal-examples/.cargo/config.toml | 37 + rp-hal/rp2040-hal-examples/Cargo.toml | 47 + rp-hal/rp2040-hal-examples/LICENSE-APACHE | 202 + rp-hal/rp2040-hal-examples/LICENSE-MIT | 19 + rp-hal/rp2040-hal-examples/NOTICE | 3 + rp-hal/rp2040-hal-examples/README.md | 149 + rp-hal/rp2040-hal-examples/build.rs | 14 + rp-hal/rp2040-hal-examples/memory.x | 83 + rp-hal/rp2040-hal-examples/src/bin/adc.rs | 127 + .../src/bin/adc_fifo_dma.rs | 187 + .../src/bin/adc_fifo_irq.rs | 185 + .../src/bin/adc_fifo_poll.rs | 194 + rp-hal/rp2040-hal-examples/src/bin/alloc.rs | 123 + .../src/bin/binary_info_demo.rs | 109 + rp-hal/rp2040-hal-examples/src/bin/blinky.rs | 89 + rp-hal/rp2040-hal-examples/src/bin/dht11.rs | 100 + .../src/bin/dormant_sleep.rs | 298 ++ .../src/bin/gpio_dyn_pin_array.rs | 133 + .../src/bin/gpio_in_out.rs | 93 + .../src/bin/gpio_irq_example.rs | 186 + rp-hal/rp2040-hal-examples/src/bin/i2c.rs | 106 + .../rp2040-hal-examples/src/bin/i2c_async.rs | 127 + .../src/bin/i2c_async_cancelled.rs | 151 + .../src/bin/lcd_display.rs | 118 + .../src/bin/mem_to_mem_dma.rs | 80 + .../src/bin/multicore_fifo_blink.rs | 175 + .../src/bin/multicore_polyblink.rs | 131 + .../rp2040-hal-examples/src/bin/pio_blink.rs | 79 + rp-hal/rp2040-hal-examples/src/bin/pio_dma.rs | 117 + .../src/bin/pio_proc_blink.rs | 67 + .../src/bin/pio_side_set.rs | 86 + .../src/bin/pio_synchronized.rs | 104 + .../rp2040-hal-examples/src/bin/pwm_blink.rs | 121 + .../src/bin/pwm_blink_embedded_hal_1.rs | 121 + .../src/bin/pwm_irq_input.rs | 219 + .../rp2040-hal-examples/src/bin/rom_funcs.rs | 192 + .../src/bin/rosc_as_system_clock.rs | 334 ++ .../src/bin/rtc_irq_example.rs | 181 + .../src/bin/rtc_sleep_example.rs | 167 + rp-hal/rp2040-hal-examples/src/bin/spi.rs | 127 + rp-hal/rp2040-hal-examples/src/bin/spi_dma.rs | 108 + .../rp2040-hal-examples/src/bin/spi_eh_bus.rs | 307 ++ rp-hal/rp2040-hal-examples/src/bin/uart.rs | 109 + .../rp2040-hal-examples/src/bin/uart_dma.rs | 146 + .../src/bin/uart_loopback.rs | 429 ++ .../src/bin/vector_table.rs | 182 + .../rp2040-hal-examples/src/bin/watchdog.rs | 111 + rp-hal/rp2040-hal-macros/.gitignore | 11 + rp-hal/rp2040-hal-macros/Cargo.toml | 17 + rp-hal/rp2040-hal-macros/LICENSE-APACHE | 202 + rp-hal/rp2040-hal-macros/LICENSE-MIT | 19 + rp-hal/rp2040-hal-macros/NOTICE | 3 + rp-hal/rp2040-hal-macros/README.md | 21 + rp-hal/rp2040-hal-macros/src/lib.rs | 59 + rp-hal/rp2040-hal/.gitignore | 11 + rp-hal/rp2040-hal/CHANGELOG.md | 422 ++ rp-hal/rp2040-hal/Cargo.toml | 100 + rp-hal/rp2040-hal/LICENSE-APACHE | 202 + rp-hal/rp2040-hal/LICENSE-MIT | 19 + rp-hal/rp2040-hal/NOTICE | 3 + rp-hal/rp2040-hal/README.md | 132 + rp-hal/rp2040-hal/src/adc.rs | 925 ++++ rp-hal/rp2040-hal/src/arch.rs | 67 + rp-hal/rp2040-hal/src/async_utils.rs | 143 + .../rp2040-hal/src/atomic_register_access.rs | 40 + rp-hal/rp2040-hal/src/clocks/clock_sources.rs | 89 + rp-hal/rp2040-hal/src/clocks/macros.rs | 431 ++ rp-hal/rp2040-hal/src/clocks/mod.rs | 608 +++ .../rp2040-hal/src/critical_section_impl.rs | 91 + rp-hal/rp2040-hal/src/dma/bidirectional.rs | 160 + rp-hal/rp2040-hal/src/dma/double_buffer.rs | 316 ++ rp-hal/rp2040-hal/src/dma/mod.rs | 327 ++ rp-hal/rp2040-hal/src/dma/single_buffer.rs | 149 + rp-hal/rp2040-hal/src/dma/single_channel.rs | 259 + rp-hal/rp2040-hal/src/float/add_sub.rs | 89 + rp-hal/rp2040-hal/src/float/cmp.rs | 198 + rp-hal/rp2040-hal/src/float/conv.rs | 154 + rp-hal/rp2040-hal/src/float/div.rs | 136 + rp-hal/rp2040-hal/src/float/functions.rs | 236 + rp-hal/rp2040-hal/src/float/mod.rs | 155 + rp-hal/rp2040-hal/src/float/mul.rs | 67 + rp-hal/rp2040-hal/src/gpio/func.rs | 212 + rp-hal/rp2040-hal/src/gpio/mod.rs | 1590 ++++++ rp-hal/rp2040-hal/src/gpio/pin.rs | 175 + rp-hal/rp2040-hal/src/gpio/pin/pin_sealed.rs | 219 + rp-hal/rp2040-hal/src/gpio/pin_group.rs | 209 + rp-hal/rp2040-hal/src/gpio/pull.rs | 64 + rp-hal/rp2040-hal/src/i2c.rs | 410 ++ rp-hal/rp2040-hal/src/i2c/controller.rs | 499 ++ .../src/i2c/controller/non_blocking.rs | 347 ++ rp-hal/rp2040-hal/src/i2c/peripheral.rs | 320 ++ rp-hal/rp2040-hal/src/intrinsics.rs | 274 + rp-hal/rp2040-hal/src/lib.rs | 169 + rp-hal/rp2040-hal/src/multicore.rs | 387 ++ rp-hal/rp2040-hal/src/pio.rs | 2298 ++++++++ rp-hal/rp2040-hal/src/pll.rs | 332 ++ rp-hal/rp2040-hal/src/prelude.rs | 3 + rp-hal/rp2040-hal/src/pwm/dyn_slice.rs | 30 + rp-hal/rp2040-hal/src/pwm/mod.rs | 1037 ++++ rp-hal/rp2040-hal/src/pwm/reg.rs | 111 + rp-hal/rp2040-hal/src/resets.rs | 53 + rp-hal/rp2040-hal/src/rom_data.rs | 747 +++ rp-hal/rp2040-hal/src/rosc.rs | 148 + rp-hal/rp2040-hal/src/rtc/datetime.rs | 207 + rp-hal/rp2040-hal/src/rtc/datetime_chrono.rs | 68 + rp-hal/rp2040-hal/src/rtc/filter.rs | 120 + rp-hal/rp2040-hal/src/rtc/mod.rs | 217 + rp-hal/rp2040-hal/src/sio.rs | 846 +++ rp-hal/rp2040-hal/src/spi.rs | 562 ++ rp-hal/rp2040-hal/src/spi/pins.rs | 140 + rp-hal/rp2040-hal/src/ssi.rs | 5 + rp-hal/rp2040-hal/src/timer.rs | 544 ++ rp-hal/rp2040-hal/src/typelevel.rs | 98 + rp-hal/rp2040-hal/src/uart/common_configs.rs | 2 + rp-hal/rp2040-hal/src/uart/mod.rs | 49 + rp-hal/rp2040-hal/src/uart/peripheral.rs | 522 ++ rp-hal/rp2040-hal/src/uart/pins.rs | 242 + rp-hal/rp2040-hal/src/uart/reader.rs | 332 ++ rp-hal/rp2040-hal/src/uart/utils.rs | 93 + rp-hal/rp2040-hal/src/uart/writer.rs | 295 ++ rp-hal/rp2040-hal/src/usb.rs | 749 +++ rp-hal/rp2040-hal/src/usb/errata5.rs | 148 + rp-hal/rp2040-hal/src/vector_table.rs | 94 + rp-hal/rp2040-hal/src/vreg.rs | 37 + rp-hal/rp2040-hal/src/watchdog.rs | 226 + rp-hal/rp2040-hal/src/xosc.rs | 223 + rp-hal/rp235x-hal-examples/.cargo/config.toml | 96 + rp-hal/rp235x-hal-examples/.gitignore | 11 + rp-hal/rp235x-hal-examples/Cargo.toml | 44 + rp-hal/rp235x-hal-examples/LICENSE-APACHE | 202 + rp-hal/rp235x-hal-examples/LICENSE-MIT | 19 + rp-hal/rp235x-hal-examples/NOTICE | 3 + rp-hal/rp235x-hal-examples/README.md | 315 ++ rp-hal/rp235x-hal-examples/build.rs | 27 + rp-hal/rp235x-hal-examples/memory.x | 77 + rp-hal/rp235x-hal-examples/riscv_examples.txt | 30 + rp-hal/rp235x-hal-examples/rp235x_riscv.x | 253 + rp-hal/rp235x-hal-examples/src/bin/adc.rs | 131 + .../src/bin/adc_fifo_dma.rs | 193 + .../src/bin/adc_fifo_irq.rs | 199 + .../src/bin/adc_fifo_poll.rs | 198 + rp-hal/rp235x-hal-examples/src/bin/alloc.rs | 124 + .../rp235x-hal-examples/src/bin/arch_flip.rs | 110 + .../src/bin/binary_info_demo.rs | 102 + rp-hal/rp235x-hal-examples/src/bin/blinky.rs | 93 + .../rp235x-hal-examples/src/bin/block_loop.rs | 120 + rp-hal/rp235x-hal-examples/src/bin/dht11.rs | 102 + .../rp235x-hal-examples/src/bin/float_test.rs | 324 ++ .../src/bin/gpio_dyn_pin_array.rs | 138 + .../src/bin/gpio_in_out.rs | 97 + .../src/bin/gpio_irq_example.rs | 190 + rp-hal/rp235x-hal-examples/src/bin/i2c.rs | 108 + .../rp235x-hal-examples/src/bin/i2c_async.rs | 134 + .../src/bin/i2c_async_cancelled.rs | 154 + .../src/bin/lcd_display.rs | 118 + .../src/bin/mem_to_mem_dma.rs | 95 + .../src/bin/multicore_fifo_blink.rs | 178 + .../src/bin/multicore_polyblink.rs | 136 + .../rp235x-hal-examples/src/bin/pio_blink.rs | 92 + rp-hal/rp235x-hal-examples/src/bin/pio_dma.rs | 134 + .../src/bin/pio_proc_blink.rs | 80 + .../src/bin/pio_side_set.rs | 83 + .../src/bin/pio_synchronized.rs | 117 + .../src/bin/powman_test.rs | 326 ++ .../rp235x-hal-examples/src/bin/pwm_blink.rs | 124 + .../src/bin/pwm_blink_embedded_hal_1.rs | 124 + .../src/bin/pwm_irq_input.rs | 223 + .../rp235x-hal-examples/src/bin/rom_funcs.rs | 456 ++ .../src/bin/rosc_as_system_clock.rs | 337 ++ rp-hal/rp235x-hal-examples/src/bin/spi.rs | 131 + rp-hal/rp235x-hal-examples/src/bin/spi_dma.rs | 123 + rp-hal/rp235x-hal-examples/src/bin/uart.rs | 163 + .../rp235x-hal-examples/src/bin/uart_dma.rs | 142 + .../src/bin/uart_loopback.rs | 428 ++ rp-hal/rp235x-hal-examples/src/bin/usb.rs | 153 + .../src/bin/vector_table.rs | 191 + .../rp235x-hal-examples/src/bin/watchdog.rs | 114 + rp-hal/rp235x-hal-macros/.gitignore | 11 + rp-hal/rp235x-hal-macros/Cargo.toml | 20 + rp-hal/rp235x-hal-macros/LICENSE-APACHE | 202 + rp-hal/rp235x-hal-macros/LICENSE-MIT | 19 + rp-hal/rp235x-hal-macros/NOTICE | 3 + rp-hal/rp235x-hal-macros/README.md | 22 + rp-hal/rp235x-hal-macros/src/lib.rs | 76 + rp-hal/rp235x-hal/.gitignore | 11 + rp-hal/rp235x-hal/CHANGELOG.md | 13 + rp-hal/rp235x-hal/Cargo.toml | 101 + rp-hal/rp235x-hal/README.md | 133 + rp-hal/rp235x-hal/src/adc.rs | 950 ++++ rp-hal/rp235x-hal/src/arch.rs | 138 + rp-hal/rp235x-hal/src/async_utils.rs | 143 + .../rp235x-hal/src/atomic_register_access.rs | 40 + rp-hal/rp235x-hal/src/block.rs | 1092 ++++ rp-hal/rp235x-hal/src/clocks/clock_sources.rs | 118 + rp-hal/rp235x-hal/src/clocks/macros.rs | 431 ++ rp-hal/rp235x-hal/src/clocks/mod.rs | 746 +++ .../rp235x-hal/src/critical_section_impl.rs | 91 + rp-hal/rp235x-hal/src/dcp.rs | 159 + rp-hal/rp235x-hal/src/dma/bidirectional.rs | 160 + rp-hal/rp235x-hal/src/dma/double_buffer.rs | 316 ++ rp-hal/rp235x-hal/src/dma/mod.rs | 327 ++ rp-hal/rp235x-hal/src/dma/single_buffer.rs | 149 + rp-hal/rp235x-hal/src/dma/single_channel.rs | 263 + rp-hal/rp235x-hal/src/gpio/func.rs | 234 + rp-hal/rp235x-hal/src/gpio/mod.rs | 1646 ++++++ rp-hal/rp235x-hal/src/gpio/pin.rs | 216 + rp-hal/rp235x-hal/src/gpio/pin/pin_sealed.rs | 279 + rp-hal/rp235x-hal/src/gpio/pin_group.rs | 190 + rp-hal/rp235x-hal/src/gpio/pull.rs | 64 + rp-hal/rp235x-hal/src/i2c.rs | 415 ++ rp-hal/rp235x-hal/src/i2c/controller.rs | 499 ++ .../src/i2c/controller/non_blocking.rs | 347 ++ rp-hal/rp235x-hal/src/i2c/peripheral.rs | 320 ++ rp-hal/rp235x-hal/src/lib.rs | 169 + rp-hal/rp235x-hal/src/lposc.rs | 31 + rp-hal/rp235x-hal/src/multicore.rs | 371 ++ rp-hal/rp235x-hal/src/otp.rs | 71 + rp-hal/rp235x-hal/src/pio.rs | 2331 ++++++++ rp-hal/rp235x-hal/src/pll.rs | 335 ++ rp-hal/rp235x-hal/src/powman.rs | 662 +++ rp-hal/rp235x-hal/src/prelude.rs | 3 + rp-hal/rp235x-hal/src/pwm/dyn_slice.rs | 30 + rp-hal/rp235x-hal/src/pwm/mod.rs | 1081 ++++ rp-hal/rp235x-hal/src/pwm/reg.rs | 111 + rp-hal/rp235x-hal/src/reboot.rs | 87 + rp-hal/rp235x-hal/src/resets.rs | 54 + rp-hal/rp235x-hal/src/rom_data.rs | 1349 +++++ rp-hal/rp235x-hal/src/rosc.rs | 152 + rp-hal/rp235x-hal/src/sio.rs | 616 +++ rp-hal/rp235x-hal/src/spi.rs | 588 +++ rp-hal/rp235x-hal/src/spi/pins.rs | 140 + rp-hal/rp235x-hal/src/timer.rs | 639 +++ rp-hal/rp235x-hal/src/typelevel.rs | 98 + rp-hal/rp235x-hal/src/uart/common_configs.rs | 2 + rp-hal/rp235x-hal/src/uart/mod.rs | 72 + rp-hal/rp235x-hal/src/uart/peripheral.rs | 522 ++ rp-hal/rp235x-hal/src/uart/pins.rs | 340 ++ rp-hal/rp235x-hal/src/uart/reader.rs | 335 ++ rp-hal/rp235x-hal/src/uart/utils.rs | 93 + rp-hal/rp235x-hal/src/uart/writer.rs | 296 ++ rp-hal/rp235x-hal/src/usb.rs | 623 +++ rp-hal/rp235x-hal/src/vector_table.rs | 102 + rp-hal/rp235x-hal/src/watchdog.rs | 230 + rp-hal/rp235x-hal/src/xosc.rs | 227 + rust-toolchain.toml | 12 + src/clock.rs | 92 + src/lib.rs | 1 + src/main.rs | 361 ++ src/main_final_working.rs | 424 ++ src/resources.rs | 0 1976 files changed, 308007 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .stfolder/syncthing-folder-dcf9ee.txt create mode 100644 .stignore create mode 100644 .stversions/embassy/docs/examples/basic/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/docs/examples/layer-by-layer/blinky-async/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/docs/pages/embassy_in_the_wild~20241114-122314.adoc create mode 100644 .stversions/embassy/docs/pages/new_project~20241114-122315.adoc create mode 100644 .stversions/embassy/embassy-executor/CHANGELOG~20241114-122315.md create mode 100644 .stversions/embassy/embassy-executor/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/embassy-executor/build_common~20241114-122315.rs create mode 100644 .stversions/embassy/embassy-executor/build~20241114-122314.rs create mode 100644 .stversions/embassy/embassy-executor/src/lib~20241114-122315.rs create mode 100644 .stversions/embassy/embassy-executor/src/raw/waker~20241114-122314.rs create mode 100644 .stversions/embassy/embassy-nrf/src/chips/nrf5340_app~20241114-122314.rs create mode 100644 .stversions/embassy/embassy-nrf/src/lib~20241114-122314.rs create mode 100644 .stversions/embassy/embassy-rp/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/embassy-rp/src/lib~20241114-122315.rs create mode 100644 .stversions/embassy/embassy-rp/src/uart/mod~20241114-122314.rs create mode 100644 .stversions/embassy/embassy-stm32/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/embassy-stm32/src/lib~20241114-122315.rs create mode 100644 .stversions/embassy/embassy-stm32/src/rcc/h~20241114-122315.rs create mode 100644 .stversions/embassy/embassy-sync/src/watch~20241114-122315.rs create mode 100644 .stversions/embassy/embassy-time/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/boot/application/nrf/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/boot/application/rp/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/boot/application/stm32f3/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/boot/application/stm32f7/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/boot/application/stm32h7/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/boot/application/stm32l0/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/boot/application/stm32l1/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/boot/application/stm32l4/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/boot/application/stm32wb-dfu/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/boot/application/stm32wl/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/lpc55s69/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/nrf-rtos-trace/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/nrf51/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/nrf52810/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/nrf52840/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/nrf5340/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/nrf9151/ns/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/nrf9151/s/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/nrf9160/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/rp/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/rp23/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/std/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32c0/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32f0/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32f1/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32f2/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32f3/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32f334/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32f4/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32f469/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32f7/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32g0/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32g4/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32h5/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32h7/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32h735/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32h755cm4/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32h755cm7/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32h7b0/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32h7rs/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32l0/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32l1/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32l4/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32l5/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32u0/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/examples/stm32u5/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32wb/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32wba/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/stm32wl/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/examples/wasm/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/tests/nrf/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/tests/riscv32/Cargo~20241114-122315.toml create mode 100644 .stversions/embassy/tests/rp/Cargo~20241114-122314.toml create mode 100644 .stversions/embassy/tests/stm32/Cargo~20241114-122315.toml create mode 100644 .stversions/ledisa/Cargo~20241113-194335.lock create mode 100644 .stversions/ledisa/Cargo~20241113-194335.toml create mode 100644 .stversions/ledisa/src/lib~20241113-194335.rs create mode 100644 .stversions/ledisa/~20241113-194335.gitignore create mode 100644 .stversions/src/.main.rs~20241101-222904.swp create mode 100644 .stversions/src/.main.rs~20241101-225559.swp create mode 100644 .stversions/src/.main.rs~20241101-225609.swp create mode 100644 .stversions/src/.main.rs~20241101-231156.swp create mode 100644 .stversions/src/.main.rs~20241101-235650.swp create mode 100644 .stversions/src/.main.rs~20241101-235710.swp create mode 100644 .stversions/src/.main.rs~20241102-134736.swp create mode 100644 .stversions/src/.main.rs~20241106-001754.swp create mode 100644 .stversions/src/.main.rs~20241106-001814.swp create mode 100644 .stversions/src/main~20241030-005119.rs create mode 100644 .stversions/src/main~20241101-191805.rs create mode 100644 .stversions/src/main~20241101-194024.rs create mode 100644 .stversions/src/main~20241101-221415.rs create mode 100644 .stversions/src/main~20241101-225353.rs create mode 100644 .stversions/src/main~20241101-225609.rs create mode 100644 .stversions/src/main~20241101-231155.rs create mode 100644 .stversions/src/main~20241101-235710.rs create mode 100644 .stversions/src/main~20241102-124639.rs create mode 100644 .stversions/src/main~20241102-133327.rs create mode 100644 .stversions/src/main~20241106-001814.rs create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 embassy/.gitattributes create mode 100755 embassy/.github/ci/book.sh create mode 100755 embassy/.github/ci/build-nightly.sh create mode 100755 embassy/.github/ci/build-xtensa.sh create mode 100755 embassy/.github/ci/build.sh create mode 100755 embassy/.github/ci/crlf.sh create mode 100755 embassy/.github/ci/doc.sh create mode 100755 embassy/.github/ci/rustfmt.sh create mode 100755 embassy/.github/ci/test-nightly.sh create mode 100755 embassy/.github/ci/test.sh create mode 100644 embassy/.github/workflows/matrix-bot.yml create mode 100644 embassy/.gitignore create mode 100644 embassy/.vscode/.gitignore create mode 100644 embassy/.vscode/extensions.json create mode 100644 embassy/.vscode/settings.json create mode 100644 embassy/LICENSE-APACHE create mode 100644 embassy/LICENSE-CC-BY-SA create mode 100644 embassy/LICENSE-MIT create mode 100644 embassy/NOTICE.md create mode 100644 embassy/README.md create mode 100755 embassy/ci-nightly.sh create mode 100755 embassy/ci-xtensa.sh create mode 100755 embassy/ci.sh create mode 100644 embassy/cyw43-firmware/43439A0.bin create mode 100644 embassy/cyw43-firmware/43439A0_btfw.bin create mode 100644 embassy/cyw43-firmware/43439A0_clm.bin create mode 100644 embassy/cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt create mode 100644 embassy/cyw43-firmware/README.md create mode 100644 embassy/cyw43-pio/CHANGELOG.md create mode 100644 embassy/cyw43-pio/Cargo.toml create mode 100644 embassy/cyw43-pio/README.md create mode 100644 embassy/cyw43-pio/src/lib.rs create mode 100644 embassy/cyw43/CHANGELOG.md create mode 100644 embassy/cyw43/Cargo.toml create mode 100644 embassy/cyw43/README.md create mode 100644 embassy/cyw43/src/bluetooth.rs create mode 100644 embassy/cyw43/src/bus.rs create mode 100644 embassy/cyw43/src/consts.rs create mode 100644 embassy/cyw43/src/control.rs create mode 100644 embassy/cyw43/src/countries.rs create mode 100644 embassy/cyw43/src/events.rs create mode 100644 embassy/cyw43/src/fmt.rs create mode 100644 embassy/cyw43/src/ioctl.rs create mode 100644 embassy/cyw43/src/lib.rs create mode 100644 embassy/cyw43/src/nvram.rs create mode 100644 embassy/cyw43/src/runner.rs create mode 100644 embassy/cyw43/src/structs.rs create mode 100644 embassy/cyw43/src/util.rs create mode 100644 embassy/docs/Makefile create mode 100644 embassy/docs/README.md create mode 100644 embassy/docs/examples/basic/.cargo/config.toml create mode 100644 embassy/docs/examples/basic/Cargo.toml create mode 100644 embassy/docs/examples/basic/build.rs create mode 100644 embassy/docs/examples/basic/memory.x create mode 100644 embassy/docs/examples/basic/src/main.rs create mode 120000 embassy/docs/examples/examples create mode 100644 embassy/docs/examples/layer-by-layer/.cargo/config.toml create mode 100644 embassy/docs/examples/layer-by-layer/Cargo.toml create mode 100644 embassy/docs/examples/layer-by-layer/blinky-async/Cargo.toml create mode 100644 embassy/docs/examples/layer-by-layer/blinky-async/src/main.rs create mode 100644 embassy/docs/examples/layer-by-layer/blinky-hal/Cargo.toml create mode 100644 embassy/docs/examples/layer-by-layer/blinky-hal/src/main.rs create mode 100644 embassy/docs/examples/layer-by-layer/blinky-irq/Cargo.toml create mode 100644 embassy/docs/examples/layer-by-layer/blinky-irq/src/main.rs create mode 100644 embassy/docs/examples/layer-by-layer/blinky-pac/Cargo.toml create mode 100644 embassy/docs/examples/layer-by-layer/blinky-pac/src/main.rs create mode 100644 embassy/docs/images/bootloader_flash.png create mode 100644 embassy/docs/images/embassy_executor.drawio create mode 100644 embassy/docs/images/embassy_executor.png create mode 100644 embassy/docs/images/embassy_irq.drawio create mode 100644 embassy/docs/images/embassy_irq.png create mode 100644 embassy/docs/index.adoc create mode 100644 embassy/docs/pages/basic_application.adoc create mode 100644 embassy/docs/pages/beginners.adoc create mode 100644 embassy/docs/pages/best_practices.adoc create mode 100644 embassy/docs/pages/bootloader.adoc create mode 100644 embassy/docs/pages/developer.adoc create mode 100644 embassy/docs/pages/developer_stm32.adoc create mode 100644 embassy/docs/pages/embassy_in_the_wild.adoc create mode 100644 embassy/docs/pages/examples.adoc create mode 100644 embassy/docs/pages/faq.adoc create mode 100644 embassy/docs/pages/getting_started.adoc create mode 100644 embassy/docs/pages/hal.adoc create mode 100644 embassy/docs/pages/layer_by_layer.adoc create mode 100644 embassy/docs/pages/new_project.adoc create mode 100644 embassy/docs/pages/nrf.adoc create mode 100644 embassy/docs/pages/overview.adoc create mode 100644 embassy/docs/pages/project_structure.adoc create mode 100644 embassy/docs/pages/runtime.adoc create mode 100644 embassy/docs/pages/sharing_peripherals.adoc create mode 100644 embassy/docs/pages/stm32.adoc create mode 100644 embassy/docs/pages/system.adoc create mode 100644 embassy/docs/pages/time_keeping.adoc create mode 100644 embassy/embassy-boot-nrf/Cargo.toml create mode 100644 embassy/embassy-boot-nrf/README.md create mode 100644 embassy/embassy-boot-nrf/src/fmt.rs create mode 100644 embassy/embassy-boot-nrf/src/lib.rs create mode 100644 embassy/embassy-boot-rp/Cargo.toml create mode 100644 embassy/embassy-boot-rp/README.md create mode 100644 embassy/embassy-boot-rp/build.rs create mode 100644 embassy/embassy-boot-rp/src/fmt.rs create mode 100644 embassy/embassy-boot-rp/src/lib.rs create mode 100644 embassy/embassy-boot-stm32/Cargo.toml create mode 100644 embassy/embassy-boot-stm32/README.md create mode 100644 embassy/embassy-boot-stm32/build.rs create mode 100644 embassy/embassy-boot-stm32/src/fmt.rs create mode 100644 embassy/embassy-boot-stm32/src/lib.rs create mode 100644 embassy/embassy-boot/Cargo.toml create mode 100644 embassy/embassy-boot/README.md create mode 100644 embassy/embassy-boot/src/boot_loader.rs create mode 100644 embassy/embassy-boot/src/digest_adapters/ed25519_dalek.rs create mode 100644 embassy/embassy-boot/src/digest_adapters/mod.rs create mode 100644 embassy/embassy-boot/src/digest_adapters/salty.rs create mode 100644 embassy/embassy-boot/src/firmware_updater/asynch.rs create mode 100644 embassy/embassy-boot/src/firmware_updater/blocking.rs create mode 100644 embassy/embassy-boot/src/firmware_updater/mod.rs create mode 100644 embassy/embassy-boot/src/fmt.rs create mode 100644 embassy/embassy-boot/src/lib.rs create mode 100644 embassy/embassy-boot/src/mem_flash.rs create mode 100644 embassy/embassy-boot/src/test_flash/asynch.rs create mode 100644 embassy/embassy-boot/src/test_flash/blocking.rs create mode 100644 embassy/embassy-boot/src/test_flash/mod.rs create mode 100644 embassy/embassy-embedded-hal/CHANGELOG.md create mode 100644 embassy/embassy-embedded-hal/Cargo.toml create mode 100644 embassy/embassy-embedded-hal/README.md create mode 100644 embassy/embassy-embedded-hal/src/adapter/blocking_async.rs create mode 100644 embassy/embassy-embedded-hal/src/adapter/mod.rs create mode 100644 embassy/embassy-embedded-hal/src/adapter/yielding_async.rs create mode 100644 embassy/embassy-embedded-hal/src/flash/concat_flash.rs create mode 100644 embassy/embassy-embedded-hal/src/flash/mem_flash.rs create mode 100644 embassy/embassy-embedded-hal/src/flash/mod.rs create mode 100644 embassy/embassy-embedded-hal/src/flash/partition/asynch.rs create mode 100644 embassy/embassy-embedded-hal/src/flash/partition/blocking.rs create mode 100644 embassy/embassy-embedded-hal/src/flash/partition/mod.rs create mode 100644 embassy/embassy-embedded-hal/src/lib.rs create mode 100644 embassy/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs create mode 100644 embassy/embassy-embedded-hal/src/shared_bus/asynch/mod.rs create mode 100644 embassy/embassy-embedded-hal/src/shared_bus/asynch/spi.rs create mode 100644 embassy/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs create mode 100644 embassy/embassy-embedded-hal/src/shared_bus/blocking/mod.rs create mode 100644 embassy/embassy-embedded-hal/src/shared_bus/blocking/spi.rs create mode 100644 embassy/embassy-embedded-hal/src/shared_bus/mod.rs create mode 100644 embassy/embassy-executor-macros/Cargo.toml create mode 100644 embassy/embassy-executor-macros/README.md create mode 100644 embassy/embassy-executor-macros/src/lib.rs create mode 100644 embassy/embassy-executor-macros/src/macros/main.rs create mode 100644 embassy/embassy-executor-macros/src/macros/mod.rs create mode 100644 embassy/embassy-executor-macros/src/macros/task.rs create mode 100644 embassy/embassy-executor-macros/src/util.rs create mode 100644 embassy/embassy-executor/CHANGELOG.md create mode 100644 embassy/embassy-executor/Cargo.toml create mode 100644 embassy/embassy-executor/README.md create mode 100644 embassy/embassy-executor/build.rs create mode 100644 embassy/embassy-executor/build_common.rs create mode 100644 embassy/embassy-executor/gen_config.py create mode 100644 embassy/embassy-executor/src/arch/avr.rs create mode 100644 embassy/embassy-executor/src/arch/cortex_m.rs create mode 100644 embassy/embassy-executor/src/arch/riscv32.rs create mode 100644 embassy/embassy-executor/src/arch/spin.rs create mode 100644 embassy/embassy-executor/src/arch/std.rs create mode 100644 embassy/embassy-executor/src/arch/wasm.rs create mode 100644 embassy/embassy-executor/src/fmt.rs create mode 100644 embassy/embassy-executor/src/lib.rs create mode 100644 embassy/embassy-executor/src/raw/mod.rs create mode 100644 embassy/embassy-executor/src/raw/run_queue_atomics.rs create mode 100644 embassy/embassy-executor/src/raw/run_queue_critical_section.rs create mode 100644 embassy/embassy-executor/src/raw/state_atomics.rs create mode 100644 embassy/embassy-executor/src/raw/state_atomics_arm.rs create mode 100644 embassy/embassy-executor/src/raw/state_critical_section.rs create mode 100644 embassy/embassy-executor/src/raw/timer_queue.rs create mode 100644 embassy/embassy-executor/src/raw/trace.rs create mode 100644 embassy/embassy-executor/src/raw/util.rs create mode 100644 embassy/embassy-executor/src/raw/waker.rs create mode 100644 embassy/embassy-executor/src/raw/waker_turbo.rs create mode 100644 embassy/embassy-executor/src/spawner.rs create mode 100644 embassy/embassy-executor/tests/test.rs create mode 100644 embassy/embassy-executor/tests/ui.rs create mode 100644 embassy/embassy-executor/tests/ui/abi.rs create mode 100644 embassy/embassy-executor/tests/ui/abi.stderr create mode 100644 embassy/embassy-executor/tests/ui/bad_return.rs create mode 100644 embassy/embassy-executor/tests/ui/bad_return.stderr create mode 100644 embassy/embassy-executor/tests/ui/generics.rs create mode 100644 embassy/embassy-executor/tests/ui/generics.stderr create mode 100644 embassy/embassy-executor/tests/ui/impl_trait.rs create mode 100644 embassy/embassy-executor/tests/ui/impl_trait.stderr create mode 100644 embassy/embassy-executor/tests/ui/impl_trait_nested.rs create mode 100644 embassy/embassy-executor/tests/ui/impl_trait_nested.stderr create mode 100644 embassy/embassy-executor/tests/ui/impl_trait_static.rs create mode 100644 embassy/embassy-executor/tests/ui/impl_trait_static.stderr create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_ref_anon.rs create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_ref_anon.stderr create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_ref_elided.rs create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_ref_elided.stderr create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_ref_generic.rs create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_ref_generic.stderr create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_struct_anon.rs create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_struct_anon.stderr create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_struct_elided.rs create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_struct_elided.stderr create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_struct_generic.rs create mode 100644 embassy/embassy-executor/tests/ui/nonstatic_struct_generic.stderr create mode 100644 embassy/embassy-executor/tests/ui/not_async.rs create mode 100644 embassy/embassy-executor/tests/ui/not_async.stderr create mode 100644 embassy/embassy-executor/tests/ui/self.rs create mode 100644 embassy/embassy-executor/tests/ui/self.stderr create mode 100644 embassy/embassy-executor/tests/ui/self_ref.rs create mode 100644 embassy/embassy-executor/tests/ui/self_ref.stderr create mode 100644 embassy/embassy-executor/tests/ui/where_clause.rs create mode 100644 embassy/embassy-executor/tests/ui/where_clause.stderr create mode 100644 embassy/embassy-futures/Cargo.toml create mode 100644 embassy/embassy-futures/README.md create mode 100644 embassy/embassy-futures/src/block_on.rs create mode 100644 embassy/embassy-futures/src/fmt.rs create mode 100644 embassy/embassy-futures/src/join.rs create mode 100644 embassy/embassy-futures/src/lib.rs create mode 100644 embassy/embassy-futures/src/select.rs create mode 100644 embassy/embassy-futures/src/yield_now.rs create mode 100644 embassy/embassy-hal-internal/Cargo.toml create mode 100644 embassy/embassy-hal-internal/README.md create mode 100644 embassy/embassy-hal-internal/build.rs create mode 100644 embassy/embassy-hal-internal/build_common.rs create mode 100644 embassy/embassy-hal-internal/src/atomic_ring_buffer.rs create mode 100644 embassy/embassy-hal-internal/src/drop.rs create mode 100644 embassy/embassy-hal-internal/src/fmt.rs create mode 100644 embassy/embassy-hal-internal/src/interrupt.rs create mode 100644 embassy/embassy-hal-internal/src/lib.rs create mode 100644 embassy/embassy-hal-internal/src/macros.rs create mode 100644 embassy/embassy-hal-internal/src/peripheral.rs create mode 100644 embassy/embassy-hal-internal/src/ratio.rs create mode 100644 embassy/embassy-net-adin1110/Cargo.toml create mode 100644 embassy/embassy-net-adin1110/README.md create mode 100644 embassy/embassy-net-adin1110/src/crc32.rs create mode 100644 embassy/embassy-net-adin1110/src/crc8.rs create mode 100644 embassy/embassy-net-adin1110/src/fmt.rs create mode 100644 embassy/embassy-net-adin1110/src/lib.rs create mode 100644 embassy/embassy-net-adin1110/src/mdio.rs create mode 100644 embassy/embassy-net-adin1110/src/phy.rs create mode 100644 embassy/embassy-net-adin1110/src/regs.rs create mode 100644 embassy/embassy-net-driver-channel/CHANGELOG.md create mode 100644 embassy/embassy-net-driver-channel/Cargo.toml create mode 100644 embassy/embassy-net-driver-channel/README.md create mode 100644 embassy/embassy-net-driver-channel/src/fmt.rs create mode 100644 embassy/embassy-net-driver-channel/src/lib.rs create mode 100644 embassy/embassy-net-driver/CHANGELOG.md create mode 100644 embassy/embassy-net-driver/Cargo.toml create mode 100644 embassy/embassy-net-driver/README.md create mode 100644 embassy/embassy-net-driver/src/lib.rs create mode 100644 embassy/embassy-net-enc28j60/Cargo.toml create mode 100644 embassy/embassy-net-enc28j60/README.md create mode 100644 embassy/embassy-net-enc28j60/src/bank0.rs create mode 100644 embassy/embassy-net-enc28j60/src/bank1.rs create mode 100644 embassy/embassy-net-enc28j60/src/bank2.rs create mode 100644 embassy/embassy-net-enc28j60/src/bank3.rs create mode 100644 embassy/embassy-net-enc28j60/src/common.rs create mode 100644 embassy/embassy-net-enc28j60/src/fmt.rs create mode 100644 embassy/embassy-net-enc28j60/src/header.rs create mode 100644 embassy/embassy-net-enc28j60/src/lib.rs create mode 100644 embassy/embassy-net-enc28j60/src/macros.rs create mode 100644 embassy/embassy-net-enc28j60/src/phy.rs create mode 100644 embassy/embassy-net-enc28j60/src/traits.rs create mode 100644 embassy/embassy-net-esp-hosted/Cargo.toml create mode 100644 embassy/embassy-net-esp-hosted/README.md create mode 100644 embassy/embassy-net-esp-hosted/src/control.rs create mode 100644 embassy/embassy-net-esp-hosted/src/esp_hosted_config.proto create mode 100644 embassy/embassy-net-esp-hosted/src/fmt.rs create mode 100644 embassy/embassy-net-esp-hosted/src/ioctl.rs create mode 100644 embassy/embassy-net-esp-hosted/src/lib.rs create mode 100644 embassy/embassy-net-esp-hosted/src/proto.rs create mode 100644 embassy/embassy-net-nrf91/Cargo.toml create mode 100644 embassy/embassy-net-nrf91/README.md create mode 100644 embassy/embassy-net-nrf91/src/context.rs create mode 100644 embassy/embassy-net-nrf91/src/fmt.rs create mode 100644 embassy/embassy-net-nrf91/src/lib.rs create mode 100644 embassy/embassy-net-ppp/Cargo.toml create mode 100644 embassy/embassy-net-ppp/README.md create mode 100644 embassy/embassy-net-ppp/src/fmt.rs create mode 100644 embassy/embassy-net-ppp/src/lib.rs create mode 100644 embassy/embassy-net-tuntap/Cargo.toml create mode 100644 embassy/embassy-net-tuntap/README.md create mode 100644 embassy/embassy-net-tuntap/src/lib.rs create mode 100644 embassy/embassy-net-wiznet/Cargo.toml create mode 100644 embassy/embassy-net-wiznet/README.md create mode 100644 embassy/embassy-net-wiznet/src/chip/mod.rs create mode 100644 embassy/embassy-net-wiznet/src/chip/w5100s.rs create mode 100644 embassy/embassy-net-wiznet/src/chip/w5500.rs create mode 100644 embassy/embassy-net-wiznet/src/device.rs create mode 100644 embassy/embassy-net-wiznet/src/lib.rs create mode 100644 embassy/embassy-net/CHANGELOG.md create mode 100644 embassy/embassy-net/Cargo.toml create mode 100644 embassy/embassy-net/README.md create mode 100644 embassy/embassy-net/src/dns.rs create mode 100644 embassy/embassy-net/src/driver_util.rs create mode 100644 embassy/embassy-net/src/fmt.rs create mode 100644 embassy/embassy-net/src/lib.rs create mode 100644 embassy/embassy-net/src/raw.rs create mode 100644 embassy/embassy-net/src/tcp.rs create mode 100644 embassy/embassy-net/src/time.rs create mode 100644 embassy/embassy-net/src/udp.rs create mode 100644 embassy/embassy-nrf/CHANGELOG.md create mode 100644 embassy/embassy-nrf/Cargo.toml create mode 100644 embassy/embassy-nrf/README.md create mode 100644 embassy/embassy-nrf/src/buffered_uarte.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf51.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf52805.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf52810.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf52811.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf52820.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf52832.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf52833.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf52840.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf5340_app.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf5340_net.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf54l15_app.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf9120.rs create mode 100644 embassy/embassy-nrf/src/chips/nrf9160.rs create mode 100644 embassy/embassy-nrf/src/egu.rs create mode 100644 embassy/embassy-nrf/src/fmt.rs create mode 100644 embassy/embassy-nrf/src/gpio.rs create mode 100644 embassy/embassy-nrf/src/gpiote.rs create mode 100644 embassy/embassy-nrf/src/i2s.rs create mode 100644 embassy/embassy-nrf/src/lib.rs create mode 100644 embassy/embassy-nrf/src/nfct.rs create mode 100644 embassy/embassy-nrf/src/nvmc.rs create mode 100644 embassy/embassy-nrf/src/pdm.rs create mode 100644 embassy/embassy-nrf/src/power.rs create mode 100644 embassy/embassy-nrf/src/ppi/dppi.rs create mode 100644 embassy/embassy-nrf/src/ppi/mod.rs create mode 100644 embassy/embassy-nrf/src/ppi/ppi.rs create mode 100644 embassy/embassy-nrf/src/pwm.rs create mode 100644 embassy/embassy-nrf/src/qdec.rs create mode 100755 embassy/embassy-nrf/src/qspi.rs create mode 100644 embassy/embassy-nrf/src/radio/ble.rs create mode 100644 embassy/embassy-nrf/src/radio/ieee802154.rs create mode 100644 embassy/embassy-nrf/src/radio/mod.rs create mode 100644 embassy/embassy-nrf/src/reset.rs create mode 100644 embassy/embassy-nrf/src/rng.rs create mode 100644 embassy/embassy-nrf/src/saadc.rs create mode 100644 embassy/embassy-nrf/src/spim.rs create mode 100644 embassy/embassy-nrf/src/spis.rs create mode 100644 embassy/embassy-nrf/src/temp.rs create mode 100644 embassy/embassy-nrf/src/time_driver.rs create mode 100644 embassy/embassy-nrf/src/timer.rs create mode 100644 embassy/embassy-nrf/src/twim.rs create mode 100644 embassy/embassy-nrf/src/twis.rs create mode 100644 embassy/embassy-nrf/src/uarte.rs create mode 100644 embassy/embassy-nrf/src/usb/mod.rs create mode 100644 embassy/embassy-nrf/src/usb/vbus_detect.rs create mode 100644 embassy/embassy-nrf/src/util.rs create mode 100644 embassy/embassy-nrf/src/wdt.rs create mode 100644 embassy/embassy-nxp/Cargo.toml create mode 100644 embassy/embassy-nxp/src/gpio.rs create mode 100644 embassy/embassy-nxp/src/lib.rs create mode 100644 embassy/embassy-nxp/src/pac_utils.rs create mode 100644 embassy/embassy-nxp/src/pint.rs create mode 100644 embassy/embassy-rp/CHANGELOG.md create mode 100644 embassy/embassy-rp/Cargo.toml create mode 100644 embassy/embassy-rp/LICENSE-APACHE create mode 100644 embassy/embassy-rp/LICENSE-MIT create mode 100644 embassy/embassy-rp/README.md create mode 100644 embassy/embassy-rp/build.rs create mode 100644 embassy/embassy-rp/funcsel.txt create mode 100644 embassy/embassy-rp/link-rp.x.in create mode 100644 embassy/embassy-rp/src/adc.rs create mode 100644 embassy/embassy-rp/src/block.rs create mode 100644 embassy/embassy-rp/src/bootsel.rs create mode 100644 embassy/embassy-rp/src/clocks.rs create mode 100644 embassy/embassy-rp/src/critical_section_impl.rs create mode 100644 embassy/embassy-rp/src/dma.rs create mode 100644 embassy/embassy-rp/src/flash.rs create mode 100644 embassy/embassy-rp/src/float/add_sub.rs create mode 100644 embassy/embassy-rp/src/float/cmp.rs create mode 100644 embassy/embassy-rp/src/float/conv.rs create mode 100644 embassy/embassy-rp/src/float/div.rs create mode 100644 embassy/embassy-rp/src/float/functions.rs create mode 100644 embassy/embassy-rp/src/float/mod.rs create mode 100644 embassy/embassy-rp/src/float/mul.rs create mode 100644 embassy/embassy-rp/src/fmt.rs create mode 100644 embassy/embassy-rp/src/gpio.rs create mode 100644 embassy/embassy-rp/src/i2c.rs create mode 100644 embassy/embassy-rp/src/i2c_slave.rs create mode 100644 embassy/embassy-rp/src/intrinsics.rs create mode 100644 embassy/embassy-rp/src/lib.rs create mode 100644 embassy/embassy-rp/src/multicore.rs create mode 100644 embassy/embassy-rp/src/otp.rs create mode 100644 embassy/embassy-rp/src/pio/instr.rs create mode 100644 embassy/embassy-rp/src/pio/mod.rs create mode 100644 embassy/embassy-rp/src/pio_programs/hd44780.rs create mode 100644 embassy/embassy-rp/src/pio_programs/i2s.rs create mode 100644 embassy/embassy-rp/src/pio_programs/mod.rs create mode 100644 embassy/embassy-rp/src/pio_programs/onewire.rs create mode 100644 embassy/embassy-rp/src/pio_programs/pwm.rs create mode 100644 embassy/embassy-rp/src/pio_programs/rotary_encoder.rs create mode 100644 embassy/embassy-rp/src/pio_programs/stepper.rs create mode 100644 embassy/embassy-rp/src/pio_programs/uart.rs create mode 100644 embassy/embassy-rp/src/pio_programs/ws2812.rs create mode 100644 embassy/embassy-rp/src/pwm.rs create mode 100644 embassy/embassy-rp/src/relocate.rs create mode 100644 embassy/embassy-rp/src/reset.rs create mode 100644 embassy/embassy-rp/src/rom_data/mod.rs create mode 100644 embassy/embassy-rp/src/rom_data/rp2040.rs create mode 100644 embassy/embassy-rp/src/rom_data/rp235x.rs create mode 100644 embassy/embassy-rp/src/rtc/datetime_chrono.rs create mode 100644 embassy/embassy-rp/src/rtc/datetime_no_deps.rs create mode 100644 embassy/embassy-rp/src/rtc/filter.rs create mode 100644 embassy/embassy-rp/src/rtc/mod.rs create mode 100644 embassy/embassy-rp/src/spi.rs create mode 100644 embassy/embassy-rp/src/time_driver.rs create mode 100644 embassy/embassy-rp/src/trng.rs create mode 100644 embassy/embassy-rp/src/uart/buffered.rs create mode 100644 embassy/embassy-rp/src/uart/mod.rs create mode 100644 embassy/embassy-rp/src/usb.rs create mode 100644 embassy/embassy-rp/src/watchdog.rs create mode 100644 embassy/embassy-stm32-wpan/Cargo.toml create mode 100644 embassy/embassy-stm32-wpan/README.md create mode 100644 embassy/embassy-stm32-wpan/build.rs create mode 100644 embassy/embassy-stm32-wpan/src/channels.rs create mode 100644 embassy/embassy-stm32-wpan/src/cmd.rs create mode 100644 embassy/embassy-stm32-wpan/src/consts.rs create mode 100644 embassy/embassy-stm32-wpan/src/evt.rs create mode 100644 embassy/embassy-stm32-wpan/src/fmt.rs create mode 100644 embassy/embassy-stm32-wpan/src/lhci.rs create mode 100644 embassy/embassy-stm32-wpan/src/lib.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/commands.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/consts.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/control.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/driver.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/event.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/indications.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/macros.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/mod.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/opcodes.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/responses.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/runner.rs create mode 100644 embassy/embassy-stm32-wpan/src/mac/typedefs.rs create mode 100644 embassy/embassy-stm32-wpan/src/shci.rs create mode 100644 embassy/embassy-stm32-wpan/src/sub/ble.rs create mode 100644 embassy/embassy-stm32-wpan/src/sub/mac.rs create mode 100644 embassy/embassy-stm32-wpan/src/sub/mm.rs create mode 100644 embassy/embassy-stm32-wpan/src/sub/mod.rs create mode 100644 embassy/embassy-stm32-wpan/src/sub/sys.rs create mode 100644 embassy/embassy-stm32-wpan/src/tables.rs create mode 100644 embassy/embassy-stm32-wpan/src/unsafe_linked_list.rs create mode 100644 embassy/embassy-stm32-wpan/tl_mbox.x.in create mode 100644 embassy/embassy-stm32-wpan/tl_mbox_extended_wb1.x.in create mode 100644 embassy/embassy-stm32-wpan/tl_mbox_extended_wbx5.x.in create mode 100644 embassy/embassy-stm32/Cargo.toml create mode 100644 embassy/embassy-stm32/README.md create mode 100644 embassy/embassy-stm32/build.rs create mode 100644 embassy/embassy-stm32/build_common.rs create mode 100644 embassy/embassy-stm32/src/adc/f1.rs create mode 100644 embassy/embassy-stm32/src/adc/f3.rs create mode 100644 embassy/embassy-stm32/src/adc/f3_v1_1.rs create mode 100644 embassy/embassy-stm32/src/adc/g4.rs create mode 100644 embassy/embassy-stm32/src/adc/mod.rs create mode 100644 embassy/embassy-stm32/src/adc/ringbuffered_v2.rs create mode 100644 embassy/embassy-stm32/src/adc/v1.rs create mode 100644 embassy/embassy-stm32/src/adc/v2.rs create mode 100644 embassy/embassy-stm32/src/adc/v3.rs create mode 100644 embassy/embassy-stm32/src/adc/v4.rs create mode 100644 embassy/embassy-stm32/src/can/bxcan/filter.rs create mode 100644 embassy/embassy-stm32/src/can/bxcan/mod.rs create mode 100644 embassy/embassy-stm32/src/can/bxcan/registers.rs create mode 100644 embassy/embassy-stm32/src/can/common.rs create mode 100644 embassy/embassy-stm32/src/can/enums.rs create mode 100644 embassy/embassy-stm32/src/can/fd/config.rs create mode 100644 embassy/embassy-stm32/src/can/fd/filter.rs create mode 100644 embassy/embassy-stm32/src/can/fd/message_ram/common.rs create mode 100644 embassy/embassy-stm32/src/can/fd/message_ram/enums.rs create mode 100644 embassy/embassy-stm32/src/can/fd/message_ram/extended_filter.rs create mode 100644 embassy/embassy-stm32/src/can/fd/message_ram/generic.rs create mode 100644 embassy/embassy-stm32/src/can/fd/message_ram/mod.rs create mode 100644 embassy/embassy-stm32/src/can/fd/message_ram/rxfifo_element.rs create mode 100644 embassy/embassy-stm32/src/can/fd/message_ram/standard_filter.rs create mode 100644 embassy/embassy-stm32/src/can/fd/message_ram/txbuffer_element.rs create mode 100644 embassy/embassy-stm32/src/can/fd/message_ram/txevent_element.rs create mode 100644 embassy/embassy-stm32/src/can/fd/mod.rs create mode 100644 embassy/embassy-stm32/src/can/fd/peripheral.rs create mode 100644 embassy/embassy-stm32/src/can/fdcan.rs create mode 100644 embassy/embassy-stm32/src/can/frame.rs create mode 100644 embassy/embassy-stm32/src/can/mod.rs create mode 100644 embassy/embassy-stm32/src/can/util.rs create mode 100644 embassy/embassy-stm32/src/cordic/enums.rs create mode 100644 embassy/embassy-stm32/src/cordic/errors.rs create mode 100644 embassy/embassy-stm32/src/cordic/mod.rs create mode 100644 embassy/embassy-stm32/src/cordic/utils.rs create mode 100644 embassy/embassy-stm32/src/crc/mod.rs create mode 100644 embassy/embassy-stm32/src/crc/v1.rs create mode 100644 embassy/embassy-stm32/src/crc/v2v3.rs create mode 100644 embassy/embassy-stm32/src/cryp/mod.rs create mode 100644 embassy/embassy-stm32/src/dac/mod.rs create mode 100644 embassy/embassy-stm32/src/dac/tsel.rs create mode 100644 embassy/embassy-stm32/src/dcmi.rs create mode 100644 embassy/embassy-stm32/src/dma/dma_bdma.rs create mode 100644 embassy/embassy-stm32/src/dma/dmamux.rs create mode 100644 embassy/embassy-stm32/src/dma/gpdma.rs create mode 100644 embassy/embassy-stm32/src/dma/mod.rs create mode 100644 embassy/embassy-stm32/src/dma/ringbuffer/mod.rs create mode 100644 embassy/embassy-stm32/src/dma/ringbuffer/tests/mod.rs create mode 100644 embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs create mode 100644 embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs create mode 100644 embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs create mode 100644 embassy/embassy-stm32/src/dma/util.rs create mode 100644 embassy/embassy-stm32/src/dma/word.rs create mode 100644 embassy/embassy-stm32/src/dsihost.rs create mode 100644 embassy/embassy-stm32/src/eth/generic_smi.rs create mode 100644 embassy/embassy-stm32/src/eth/mod.rs create mode 100644 embassy/embassy-stm32/src/eth/v1/mod.rs create mode 100644 embassy/embassy-stm32/src/eth/v1/rx_desc.rs create mode 100644 embassy/embassy-stm32/src/eth/v1/tx_desc.rs create mode 100644 embassy/embassy-stm32/src/eth/v2/descriptors.rs create mode 100644 embassy/embassy-stm32/src/eth/v2/mod.rs create mode 100644 embassy/embassy-stm32/src/exti.rs create mode 100644 embassy/embassy-stm32/src/flash/asynch.rs create mode 100644 embassy/embassy-stm32/src/flash/common.rs create mode 100644 embassy/embassy-stm32/src/flash/f0.rs create mode 100644 embassy/embassy-stm32/src/flash/f1f3.rs create mode 100644 embassy/embassy-stm32/src/flash/f2.rs create mode 100644 embassy/embassy-stm32/src/flash/f4.rs create mode 100644 embassy/embassy-stm32/src/flash/f7.rs create mode 100644 embassy/embassy-stm32/src/flash/g.rs create mode 100644 embassy/embassy-stm32/src/flash/h5.rs create mode 100644 embassy/embassy-stm32/src/flash/h50.rs create mode 100644 embassy/embassy-stm32/src/flash/h7.rs create mode 100644 embassy/embassy-stm32/src/flash/l.rs create mode 100644 embassy/embassy-stm32/src/flash/mod.rs create mode 100644 embassy/embassy-stm32/src/flash/other.rs create mode 100644 embassy/embassy-stm32/src/flash/u0.rs create mode 100644 embassy/embassy-stm32/src/flash/u5.rs create mode 100644 embassy/embassy-stm32/src/fmc.rs create mode 100644 embassy/embassy-stm32/src/fmt.rs create mode 100644 embassy/embassy-stm32/src/gpio.rs create mode 100644 embassy/embassy-stm32/src/hash/mod.rs create mode 100644 embassy/embassy-stm32/src/hrtim/mod.rs create mode 100644 embassy/embassy-stm32/src/hrtim/traits.rs create mode 100644 embassy/embassy-stm32/src/hsem/mod.rs create mode 100644 embassy/embassy-stm32/src/i2c/mod.rs create mode 100644 embassy/embassy-stm32/src/i2c/v1.rs create mode 100644 embassy/embassy-stm32/src/i2c/v2.rs create mode 100644 embassy/embassy-stm32/src/i2s.rs create mode 100644 embassy/embassy-stm32/src/ipcc.rs create mode 100644 embassy/embassy-stm32/src/lib.rs create mode 100644 embassy/embassy-stm32/src/low_power.rs create mode 100644 embassy/embassy-stm32/src/lptim/channel.rs create mode 100644 embassy/embassy-stm32/src/lptim/mod.rs create mode 100644 embassy/embassy-stm32/src/lptim/pwm.rs create mode 100644 embassy/embassy-stm32/src/lptim/timer/channel_direction.rs create mode 100644 embassy/embassy-stm32/src/lptim/timer/mod.rs create mode 100644 embassy/embassy-stm32/src/lptim/timer/prescaler.rs create mode 100644 embassy/embassy-stm32/src/ltdc.rs create mode 100644 embassy/embassy-stm32/src/macros.rs create mode 100644 embassy/embassy-stm32/src/opamp.rs create mode 100644 embassy/embassy-stm32/src/ospi/enums.rs create mode 100644 embassy/embassy-stm32/src/ospi/mod.rs create mode 100644 embassy/embassy-stm32/src/qspi/enums.rs create mode 100644 embassy/embassy-stm32/src/qspi/mod.rs create mode 100644 embassy/embassy-stm32/src/rcc/bd.rs create mode 100644 embassy/embassy-stm32/src/rcc/c0.rs create mode 100644 embassy/embassy-stm32/src/rcc/f013.rs create mode 100644 embassy/embassy-stm32/src/rcc/f247.rs create mode 100644 embassy/embassy-stm32/src/rcc/g0.rs create mode 100644 embassy/embassy-stm32/src/rcc/g4.rs create mode 100644 embassy/embassy-stm32/src/rcc/h.rs create mode 100644 embassy/embassy-stm32/src/rcc/hsi48.rs create mode 100644 embassy/embassy-stm32/src/rcc/l.rs create mode 100644 embassy/embassy-stm32/src/rcc/mco.rs create mode 100644 embassy/embassy-stm32/src/rcc/mod.rs create mode 100644 embassy/embassy-stm32/src/rcc/u5.rs create mode 100644 embassy/embassy-stm32/src/rcc/wba.rs create mode 100644 embassy/embassy-stm32/src/rng.rs create mode 100644 embassy/embassy-stm32/src/rtc/datetime.rs create mode 100644 embassy/embassy-stm32/src/rtc/low_power.rs create mode 100644 embassy/embassy-stm32/src/rtc/mod.rs create mode 100644 embassy/embassy-stm32/src/rtc/v2.rs create mode 100644 embassy/embassy-stm32/src/rtc/v3.rs create mode 100644 embassy/embassy-stm32/src/sai/mod.rs create mode 100644 embassy/embassy-stm32/src/sdmmc/mod.rs create mode 100644 embassy/embassy-stm32/src/spdifrx/mod.rs create mode 100644 embassy/embassy-stm32/src/spi/mod.rs create mode 100644 embassy/embassy-stm32/src/time.rs create mode 100644 embassy/embassy-stm32/src/time_driver.rs create mode 100644 embassy/embassy-stm32/src/timer/complementary_pwm.rs create mode 100644 embassy/embassy-stm32/src/timer/input_capture.rs create mode 100644 embassy/embassy-stm32/src/timer/low_level.rs create mode 100644 embassy/embassy-stm32/src/timer/mod.rs create mode 100644 embassy/embassy-stm32/src/timer/pwm_input.rs create mode 100644 embassy/embassy-stm32/src/timer/qei.rs create mode 100644 embassy/embassy-stm32/src/timer/simple_pwm.rs create mode 100644 embassy/embassy-stm32/src/tsc/acquisition_banks.rs create mode 100644 embassy/embassy-stm32/src/tsc/config.rs create mode 100644 embassy/embassy-stm32/src/tsc/errors.rs create mode 100644 embassy/embassy-stm32/src/tsc/io_pin.rs create mode 100644 embassy/embassy-stm32/src/tsc/mod.rs create mode 100644 embassy/embassy-stm32/src/tsc/pin_groups.rs create mode 100644 embassy/embassy-stm32/src/tsc/tsc.rs create mode 100644 embassy/embassy-stm32/src/tsc/types.rs create mode 100644 embassy/embassy-stm32/src/ucpd.rs create mode 100644 embassy/embassy-stm32/src/uid.rs create mode 100644 embassy/embassy-stm32/src/usart/buffered.rs create mode 100644 embassy/embassy-stm32/src/usart/mod.rs create mode 100644 embassy/embassy-stm32/src/usart/ringbuffered.rs create mode 100644 embassy/embassy-stm32/src/usb/mod.rs create mode 100644 embassy/embassy-stm32/src/usb/otg.rs create mode 100644 embassy/embassy-stm32/src/usb/usb.rs create mode 100644 embassy/embassy-stm32/src/wdg/mod.rs create mode 100644 embassy/embassy-sync/CHANGELOG.md create mode 100644 embassy/embassy-sync/Cargo.toml create mode 100644 embassy/embassy-sync/README.md create mode 100644 embassy/embassy-sync/build.rs create mode 100644 embassy/embassy-sync/build_common.rs create mode 100644 embassy/embassy-sync/src/blocking_mutex/mod.rs create mode 100644 embassy/embassy-sync/src/blocking_mutex/raw.rs create mode 100644 embassy/embassy-sync/src/channel.rs create mode 100644 embassy/embassy-sync/src/fmt.rs create mode 100644 embassy/embassy-sync/src/lazy_lock.rs create mode 100644 embassy/embassy-sync/src/lib.rs create mode 100644 embassy/embassy-sync/src/mutex.rs create mode 100644 embassy/embassy-sync/src/once_lock.rs create mode 100644 embassy/embassy-sync/src/pipe.rs create mode 100644 embassy/embassy-sync/src/priority_channel.rs create mode 100644 embassy/embassy-sync/src/pubsub/mod.rs create mode 100644 embassy/embassy-sync/src/pubsub/publisher.rs create mode 100644 embassy/embassy-sync/src/pubsub/subscriber.rs create mode 100644 embassy/embassy-sync/src/ring_buffer.rs create mode 100644 embassy/embassy-sync/src/semaphore.rs create mode 100644 embassy/embassy-sync/src/signal.rs create mode 100644 embassy/embassy-sync/src/waitqueue/atomic_waker.rs create mode 100644 embassy/embassy-sync/src/waitqueue/atomic_waker_turbo.rs create mode 100644 embassy/embassy-sync/src/waitqueue/mod.rs create mode 100644 embassy/embassy-sync/src/waitqueue/multi_waker.rs create mode 100644 embassy/embassy-sync/src/waitqueue/waker_registration.rs create mode 100644 embassy/embassy-sync/src/watch.rs create mode 100644 embassy/embassy-sync/src/zerocopy_channel.rs create mode 100644 embassy/embassy-time-driver/CHANGELOG.md create mode 100644 embassy/embassy-time-driver/Cargo.toml create mode 100644 embassy/embassy-time-driver/README.md create mode 100644 embassy/embassy-time-driver/build.rs create mode 100644 embassy/embassy-time-driver/gen_tick.py create mode 100644 embassy/embassy-time-driver/src/lib.rs create mode 100644 embassy/embassy-time-driver/src/tick.rs create mode 100644 embassy/embassy-time-queue-driver/CHANGELOG.md create mode 100644 embassy/embassy-time-queue-driver/Cargo.toml create mode 100644 embassy/embassy-time-queue-driver/README.md create mode 100644 embassy/embassy-time-queue-driver/build.rs create mode 100644 embassy/embassy-time-queue-driver/src/lib.rs create mode 100644 embassy/embassy-time-queue-driver/src/queue_generic.rs create mode 100644 embassy/embassy-time-queue-driver/src/queue_integrated.rs create mode 100644 embassy/embassy-time/CHANGELOG.md create mode 100644 embassy/embassy-time/Cargo.toml create mode 100644 embassy/embassy-time/README.md create mode 100644 embassy/embassy-time/src/delay.rs create mode 100644 embassy/embassy-time/src/driver_mock.rs create mode 100644 embassy/embassy-time/src/driver_std.rs create mode 100644 embassy/embassy-time/src/driver_wasm.rs create mode 100644 embassy/embassy-time/src/duration.rs create mode 100644 embassy/embassy-time/src/fmt.rs create mode 100644 embassy/embassy-time/src/instant.rs create mode 100644 embassy/embassy-time/src/lib.rs create mode 100644 embassy/embassy-time/src/timer.rs create mode 100644 embassy/embassy-usb-dfu/Cargo.toml create mode 100644 embassy/embassy-usb-dfu/README.md create mode 100644 embassy/embassy-usb-dfu/src/application.rs create mode 100644 embassy/embassy-usb-dfu/src/consts.rs create mode 100644 embassy/embassy-usb-dfu/src/dfu.rs create mode 100644 embassy/embassy-usb-dfu/src/fmt.rs create mode 100644 embassy/embassy-usb-dfu/src/lib.rs create mode 100644 embassy/embassy-usb-driver/Cargo.toml create mode 100644 embassy/embassy-usb-driver/README.md create mode 100644 embassy/embassy-usb-driver/src/lib.rs create mode 100644 embassy/embassy-usb-logger/CHANGELOG.md create mode 100644 embassy/embassy-usb-logger/Cargo.toml create mode 100644 embassy/embassy-usb-logger/README.md create mode 100644 embassy/embassy-usb-logger/src/lib.rs create mode 100644 embassy/embassy-usb-synopsys-otg/CHANGELOG.md create mode 100644 embassy/embassy-usb-synopsys-otg/Cargo.toml create mode 100644 embassy/embassy-usb-synopsys-otg/README.md create mode 100644 embassy/embassy-usb-synopsys-otg/src/fmt.rs create mode 100644 embassy/embassy-usb-synopsys-otg/src/lib.rs create mode 100644 embassy/embassy-usb-synopsys-otg/src/otg_v1.rs create mode 100644 embassy/embassy-usb/CHANGELOG.md create mode 100644 embassy/embassy-usb/Cargo.toml create mode 100644 embassy/embassy-usb/README.md create mode 100644 embassy/embassy-usb/build.rs create mode 100644 embassy/embassy-usb/gen_config.py create mode 100644 embassy/embassy-usb/src/builder.rs create mode 100644 embassy/embassy-usb/src/class/cdc_acm.rs create mode 100644 embassy/embassy-usb/src/class/cdc_ncm/embassy_net.rs create mode 100644 embassy/embassy-usb/src/class/cdc_ncm/mod.rs create mode 100644 embassy/embassy-usb/src/class/hid.rs create mode 100644 embassy/embassy-usb/src/class/midi.rs create mode 100644 embassy/embassy-usb/src/class/mod.rs create mode 100644 embassy/embassy-usb/src/class/uac1/class_codes.rs create mode 100644 embassy/embassy-usb/src/class/uac1/mod.rs create mode 100644 embassy/embassy-usb/src/class/uac1/speaker.rs create mode 100644 embassy/embassy-usb/src/class/uac1/terminal_type.rs create mode 100644 embassy/embassy-usb/src/class/web_usb.rs create mode 100644 embassy/embassy-usb/src/control.rs create mode 100644 embassy/embassy-usb/src/descriptor.rs create mode 100644 embassy/embassy-usb/src/descriptor_reader.rs create mode 100644 embassy/embassy-usb/src/fmt.rs create mode 100644 embassy/embassy-usb/src/lib.rs create mode 100644 embassy/embassy-usb/src/msos.rs create mode 100644 embassy/embassy-usb/src/types.rs create mode 100644 embassy/examples/.cargo/config.toml create mode 100644 embassy/examples/boot/.cargo/config.toml create mode 100644 embassy/examples/boot/application/nrf/.cargo/config.toml create mode 100644 embassy/examples/boot/application/nrf/Cargo.toml create mode 100644 embassy/examples/boot/application/nrf/README.md create mode 100644 embassy/examples/boot/application/nrf/build.rs create mode 100644 embassy/examples/boot/application/nrf/memory-bl-nrf91.x create mode 100644 embassy/examples/boot/application/nrf/memory-bl.x create mode 100644 embassy/examples/boot/application/nrf/memory-nrf91.x create mode 100644 embassy/examples/boot/application/nrf/memory.x create mode 100644 embassy/examples/boot/application/nrf/src/bin/a.rs create mode 100644 embassy/examples/boot/application/nrf/src/bin/b.rs create mode 100644 embassy/examples/boot/application/rp/.cargo/config.toml create mode 100644 embassy/examples/boot/application/rp/Cargo.toml create mode 100644 embassy/examples/boot/application/rp/README.md create mode 100644 embassy/examples/boot/application/rp/build.rs create mode 100644 embassy/examples/boot/application/rp/memory.x create mode 100644 embassy/examples/boot/application/rp/src/bin/a.rs create mode 100644 embassy/examples/boot/application/rp/src/bin/b.rs create mode 100644 embassy/examples/boot/application/stm32f3/.cargo/config.toml create mode 100644 embassy/examples/boot/application/stm32f3/Cargo.toml create mode 100644 embassy/examples/boot/application/stm32f3/README.md create mode 100644 embassy/examples/boot/application/stm32f3/build.rs create mode 100644 embassy/examples/boot/application/stm32f3/memory.x create mode 100644 embassy/examples/boot/application/stm32f3/src/bin/a.rs create mode 100644 embassy/examples/boot/application/stm32f3/src/bin/b.rs create mode 100644 embassy/examples/boot/application/stm32f7/.cargo/config.toml create mode 100644 embassy/examples/boot/application/stm32f7/Cargo.toml create mode 100644 embassy/examples/boot/application/stm32f7/README.md create mode 100644 embassy/examples/boot/application/stm32f7/build.rs create mode 100755 embassy/examples/boot/application/stm32f7/flash-boot.sh create mode 100644 embassy/examples/boot/application/stm32f7/memory-bl.x create mode 100644 embassy/examples/boot/application/stm32f7/memory.x create mode 100644 embassy/examples/boot/application/stm32f7/src/bin/a.rs create mode 100644 embassy/examples/boot/application/stm32f7/src/bin/b.rs create mode 100644 embassy/examples/boot/application/stm32h7/.cargo/config.toml create mode 100644 embassy/examples/boot/application/stm32h7/Cargo.toml create mode 100644 embassy/examples/boot/application/stm32h7/README.md create mode 100644 embassy/examples/boot/application/stm32h7/build.rs create mode 100755 embassy/examples/boot/application/stm32h7/flash-boot.sh create mode 100644 embassy/examples/boot/application/stm32h7/memory-bl.x create mode 100644 embassy/examples/boot/application/stm32h7/memory.x create mode 100644 embassy/examples/boot/application/stm32h7/src/bin/a.rs create mode 100644 embassy/examples/boot/application/stm32h7/src/bin/b.rs create mode 100644 embassy/examples/boot/application/stm32l0/.cargo/config.toml create mode 100644 embassy/examples/boot/application/stm32l0/Cargo.toml create mode 100644 embassy/examples/boot/application/stm32l0/README.md create mode 100644 embassy/examples/boot/application/stm32l0/build.rs create mode 100644 embassy/examples/boot/application/stm32l0/memory.x create mode 100644 embassy/examples/boot/application/stm32l0/src/bin/a.rs create mode 100644 embassy/examples/boot/application/stm32l0/src/bin/b.rs create mode 100644 embassy/examples/boot/application/stm32l1/.cargo/config.toml create mode 100644 embassy/examples/boot/application/stm32l1/Cargo.toml create mode 100644 embassy/examples/boot/application/stm32l1/README.md create mode 100644 embassy/examples/boot/application/stm32l1/build.rs create mode 100644 embassy/examples/boot/application/stm32l1/memory.x create mode 100644 embassy/examples/boot/application/stm32l1/src/bin/a.rs create mode 100644 embassy/examples/boot/application/stm32l1/src/bin/b.rs create mode 100644 embassy/examples/boot/application/stm32l4/.cargo/config.toml create mode 100644 embassy/examples/boot/application/stm32l4/Cargo.toml create mode 100644 embassy/examples/boot/application/stm32l4/README.md create mode 100644 embassy/examples/boot/application/stm32l4/build.rs create mode 100644 embassy/examples/boot/application/stm32l4/memory.x create mode 100644 embassy/examples/boot/application/stm32l4/src/bin/a.rs create mode 100644 embassy/examples/boot/application/stm32l4/src/bin/b.rs create mode 100644 embassy/examples/boot/application/stm32wb-dfu/.cargo/config.toml create mode 100644 embassy/examples/boot/application/stm32wb-dfu/Cargo.toml create mode 100644 embassy/examples/boot/application/stm32wb-dfu/README.md create mode 100644 embassy/examples/boot/application/stm32wb-dfu/build.rs create mode 100644 embassy/examples/boot/application/stm32wb-dfu/memory.x create mode 100644 embassy/examples/boot/application/stm32wb-dfu/src/main.rs create mode 100644 embassy/examples/boot/application/stm32wl/.cargo/config.toml create mode 100644 embassy/examples/boot/application/stm32wl/Cargo.toml create mode 100644 embassy/examples/boot/application/stm32wl/README.md create mode 100644 embassy/examples/boot/application/stm32wl/build.rs create mode 100644 embassy/examples/boot/application/stm32wl/memory.x create mode 100644 embassy/examples/boot/application/stm32wl/src/bin/a.rs create mode 100644 embassy/examples/boot/application/stm32wl/src/bin/b.rs create mode 100644 embassy/examples/boot/bootloader/nrf/.cargo/config.toml create mode 100644 embassy/examples/boot/bootloader/nrf/Cargo.toml create mode 100644 embassy/examples/boot/bootloader/nrf/README.md create mode 100644 embassy/examples/boot/bootloader/nrf/build.rs create mode 100644 embassy/examples/boot/bootloader/nrf/memory-bm.x create mode 100644 embassy/examples/boot/bootloader/nrf/memory-s140.x create mode 100644 embassy/examples/boot/bootloader/nrf/memory.x create mode 100644 embassy/examples/boot/bootloader/nrf/src/main.rs create mode 100644 embassy/examples/boot/bootloader/rp/.cargo/config.toml create mode 100644 embassy/examples/boot/bootloader/rp/Cargo.toml create mode 100644 embassy/examples/boot/bootloader/rp/README.md create mode 100644 embassy/examples/boot/bootloader/rp/build.rs create mode 100644 embassy/examples/boot/bootloader/rp/memory.x create mode 100644 embassy/examples/boot/bootloader/rp/src/main.rs create mode 100644 embassy/examples/boot/bootloader/stm32-dual-bank/Cargo.toml create mode 100644 embassy/examples/boot/bootloader/stm32-dual-bank/README.md create mode 100644 embassy/examples/boot/bootloader/stm32-dual-bank/build.rs create mode 100644 embassy/examples/boot/bootloader/stm32-dual-bank/memory.x create mode 100644 embassy/examples/boot/bootloader/stm32-dual-bank/src/main.rs create mode 100644 embassy/examples/boot/bootloader/stm32/Cargo.toml create mode 100644 embassy/examples/boot/bootloader/stm32/README.md create mode 100644 embassy/examples/boot/bootloader/stm32/build.rs create mode 100644 embassy/examples/boot/bootloader/stm32/memory.x create mode 100644 embassy/examples/boot/bootloader/stm32/src/main.rs create mode 100644 embassy/examples/boot/bootloader/stm32wb-dfu/Cargo.toml create mode 100644 embassy/examples/boot/bootloader/stm32wb-dfu/README.md create mode 100644 embassy/examples/boot/bootloader/stm32wb-dfu/build.rs create mode 100644 embassy/examples/boot/bootloader/stm32wb-dfu/memory.x create mode 100644 embassy/examples/boot/bootloader/stm32wb-dfu/src/main.rs create mode 100644 embassy/examples/lpc55s69/.cargo/config.toml create mode 100644 embassy/examples/lpc55s69/Cargo.toml create mode 100644 embassy/examples/lpc55s69/build.rs create mode 100644 embassy/examples/lpc55s69/memory.x create mode 100644 embassy/examples/lpc55s69/src/bin/blinky_nop.rs create mode 100644 embassy/examples/lpc55s69/src/bin/button_executor.rs create mode 100644 embassy/examples/nrf-rtos-trace/.cargo/config.toml create mode 100644 embassy/examples/nrf-rtos-trace/Cargo.toml create mode 100644 embassy/examples/nrf-rtos-trace/build.rs create mode 100644 embassy/examples/nrf-rtos-trace/memory.x create mode 100644 embassy/examples/nrf-rtos-trace/src/bin/rtos_trace.rs create mode 100644 embassy/examples/nrf51/.cargo/config.toml create mode 100644 embassy/examples/nrf51/Cargo.toml create mode 100644 embassy/examples/nrf51/build.rs create mode 100644 embassy/examples/nrf51/memory.x create mode 100644 embassy/examples/nrf51/src/bin/blinky.rs create mode 100644 embassy/examples/nrf52810/.cargo/config.toml create mode 100644 embassy/examples/nrf52810/Cargo.toml create mode 100644 embassy/examples/nrf52810/build.rs create mode 100644 embassy/examples/nrf52810/memory.x create mode 100644 embassy/examples/nrf52810/src/bin/blinky.rs create mode 100644 embassy/examples/nrf52840-rtic/.cargo/config.toml create mode 100644 embassy/examples/nrf52840-rtic/Cargo.toml create mode 100644 embassy/examples/nrf52840-rtic/build.rs create mode 100644 embassy/examples/nrf52840-rtic/memory.x create mode 100644 embassy/examples/nrf52840-rtic/src/bin/blinky.rs create mode 100644 embassy/examples/nrf52840/.cargo/config.toml create mode 100644 embassy/examples/nrf52840/Cargo.toml create mode 100644 embassy/examples/nrf52840/build.rs create mode 100644 embassy/examples/nrf52840/memory.x create mode 100644 embassy/examples/nrf52840/src/bin/blinky.rs create mode 100644 embassy/examples/nrf52840/src/bin/buffered_uart.rs create mode 100644 embassy/examples/nrf52840/src/bin/channel.rs create mode 100644 embassy/examples/nrf52840/src/bin/channel_sender_receiver.rs create mode 100644 embassy/examples/nrf52840/src/bin/egu.rs create mode 100644 embassy/examples/nrf52840/src/bin/ethernet_enc28j60.rs create mode 100644 embassy/examples/nrf52840/src/bin/executor_fairness_test.rs create mode 100644 embassy/examples/nrf52840/src/bin/gpiote_channel.rs create mode 100644 embassy/examples/nrf52840/src/bin/gpiote_port.rs create mode 100644 embassy/examples/nrf52840/src/bin/i2s_effect.rs create mode 100644 embassy/examples/nrf52840/src/bin/i2s_monitor.rs create mode 100644 embassy/examples/nrf52840/src/bin/i2s_waveform.rs create mode 100644 embassy/examples/nrf52840/src/bin/manually_create_executor.rs create mode 100644 embassy/examples/nrf52840/src/bin/multiprio.rs create mode 100644 embassy/examples/nrf52840/src/bin/mutex.rs create mode 100644 embassy/examples/nrf52840/src/bin/nfct.rs create mode 100644 embassy/examples/nrf52840/src/bin/nvmc.rs create mode 100644 embassy/examples/nrf52840/src/bin/pdm.rs create mode 100644 embassy/examples/nrf52840/src/bin/pdm_continuous.rs create mode 100644 embassy/examples/nrf52840/src/bin/ppi.rs create mode 100644 embassy/examples/nrf52840/src/bin/pubsub.rs create mode 100644 embassy/examples/nrf52840/src/bin/pwm.rs create mode 100644 embassy/examples/nrf52840/src/bin/pwm_double_sequence.rs create mode 100644 embassy/examples/nrf52840/src/bin/pwm_sequence.rs create mode 100644 embassy/examples/nrf52840/src/bin/pwm_sequence_ppi.rs create mode 100644 embassy/examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs create mode 100644 embassy/examples/nrf52840/src/bin/pwm_servo.rs create mode 100644 embassy/examples/nrf52840/src/bin/qdec.rs create mode 100644 embassy/examples/nrf52840/src/bin/qspi.rs create mode 100644 embassy/examples/nrf52840/src/bin/qspi_lowpower.rs create mode 100644 embassy/examples/nrf52840/src/bin/raw_spawn.rs create mode 100644 embassy/examples/nrf52840/src/bin/rng.rs create mode 100644 embassy/examples/nrf52840/src/bin/saadc.rs create mode 100644 embassy/examples/nrf52840/src/bin/saadc_continuous.rs create mode 100644 embassy/examples/nrf52840/src/bin/self_spawn.rs create mode 100644 embassy/examples/nrf52840/src/bin/self_spawn_current_executor.rs create mode 100644 embassy/examples/nrf52840/src/bin/spim.rs create mode 100644 embassy/examples/nrf52840/src/bin/spis.rs create mode 100644 embassy/examples/nrf52840/src/bin/temp.rs create mode 100644 embassy/examples/nrf52840/src/bin/timer.rs create mode 100644 embassy/examples/nrf52840/src/bin/twim.rs create mode 100644 embassy/examples/nrf52840/src/bin/twim_lowpower.rs create mode 100644 embassy/examples/nrf52840/src/bin/twis.rs create mode 100644 embassy/examples/nrf52840/src/bin/uart.rs create mode 100644 embassy/examples/nrf52840/src/bin/uart_idle.rs create mode 100644 embassy/examples/nrf52840/src/bin/uart_split.rs create mode 100644 embassy/examples/nrf52840/src/bin/usb_ethernet.rs create mode 100644 embassy/examples/nrf52840/src/bin/usb_hid_keyboard.rs create mode 100644 embassy/examples/nrf52840/src/bin/usb_hid_mouse.rs create mode 100644 embassy/examples/nrf52840/src/bin/usb_serial.rs create mode 100644 embassy/examples/nrf52840/src/bin/usb_serial_multitask.rs create mode 100644 embassy/examples/nrf52840/src/bin/usb_serial_winusb.rs create mode 100644 embassy/examples/nrf52840/src/bin/wdt.rs create mode 100644 embassy/examples/nrf52840/src/bin/wifi_esp_hosted.rs create mode 100644 embassy/examples/nrf5340/.cargo/config.toml create mode 100644 embassy/examples/nrf5340/Cargo.toml create mode 100644 embassy/examples/nrf5340/build.rs create mode 100644 embassy/examples/nrf5340/memory.x create mode 100644 embassy/examples/nrf5340/src/bin/blinky.rs create mode 100644 embassy/examples/nrf5340/src/bin/gpiote_channel.rs create mode 100644 embassy/examples/nrf5340/src/bin/uart.rs create mode 100644 embassy/examples/nrf54l15/.cargo/config.toml create mode 100644 embassy/examples/nrf54l15/Cargo.toml create mode 100644 embassy/examples/nrf54l15/build.rs create mode 100644 embassy/examples/nrf54l15/memory.x create mode 100644 embassy/examples/nrf54l15/src/bin/blinky.rs create mode 100644 embassy/examples/nrf9151/ns/.cargo/config.toml create mode 100644 embassy/examples/nrf9151/ns/Cargo.toml create mode 100644 embassy/examples/nrf9151/ns/README.md create mode 100644 embassy/examples/nrf9151/ns/build.rs create mode 100644 embassy/examples/nrf9151/ns/flash_tfm.sh create mode 100644 embassy/examples/nrf9151/ns/memory.x create mode 100644 embassy/examples/nrf9151/ns/src/bin/blinky.rs create mode 100644 embassy/examples/nrf9151/ns/src/bin/uart.rs create mode 100644 embassy/examples/nrf9151/ns/tfm.hex create mode 100644 embassy/examples/nrf9151/s/.cargo/config.toml create mode 100644 embassy/examples/nrf9151/s/Cargo.toml create mode 100644 embassy/examples/nrf9151/s/build.rs create mode 100644 embassy/examples/nrf9151/s/memory.x create mode 100644 embassy/examples/nrf9151/s/src/bin/blinky.rs create mode 100644 embassy/examples/nrf9160/.cargo/config.toml create mode 100644 embassy/examples/nrf9160/Cargo.toml create mode 100644 embassy/examples/nrf9160/build.rs create mode 100644 embassy/examples/nrf9160/memory.x create mode 100644 embassy/examples/nrf9160/src/bin/blinky.rs create mode 100644 embassy/examples/nrf9160/src/bin/modem_tcp_client.rs create mode 100644 embassy/examples/rp/.cargo/config.toml create mode 100644 embassy/examples/rp/Cargo.toml create mode 100644 embassy/examples/rp/assets/ferris.raw create mode 100644 embassy/examples/rp/build.rs create mode 100644 embassy/examples/rp/memory.x create mode 100644 embassy/examples/rp/src/bin/adc.rs create mode 100644 embassy/examples/rp/src/bin/adc_dma.rs create mode 100644 embassy/examples/rp/src/bin/assign_resources.rs create mode 100644 embassy/examples/rp/src/bin/blinky.rs create mode 100644 embassy/examples/rp/src/bin/blinky_two_channels.rs create mode 100644 embassy/examples/rp/src/bin/blinky_two_tasks.rs create mode 100644 embassy/examples/rp/src/bin/bluetooth.rs create mode 100644 embassy/examples/rp/src/bin/button.rs create mode 100644 embassy/examples/rp/src/bin/debounce.rs create mode 100644 embassy/examples/rp/src/bin/ethernet_w5500_multisocket.rs create mode 100644 embassy/examples/rp/src/bin/ethernet_w5500_tcp_client.rs create mode 100644 embassy/examples/rp/src/bin/ethernet_w5500_tcp_server.rs create mode 100644 embassy/examples/rp/src/bin/ethernet_w5500_udp.rs create mode 100644 embassy/examples/rp/src/bin/flash.rs create mode 100644 embassy/examples/rp/src/bin/gpio_async.rs create mode 100644 embassy/examples/rp/src/bin/gpout.rs create mode 100644 embassy/examples/rp/src/bin/i2c_async.rs create mode 100644 embassy/examples/rp/src/bin/i2c_async_embassy.rs create mode 100644 embassy/examples/rp/src/bin/i2c_blocking.rs create mode 100644 embassy/examples/rp/src/bin/i2c_slave.rs create mode 100644 embassy/examples/rp/src/bin/interrupt.rs create mode 100644 embassy/examples/rp/src/bin/multicore.rs create mode 100644 embassy/examples/rp/src/bin/multiprio.rs create mode 100644 embassy/examples/rp/src/bin/orchestrate_tasks.rs create mode 100644 embassy/examples/rp/src/bin/pio_async.rs create mode 100644 embassy/examples/rp/src/bin/pio_dma.rs create mode 100644 embassy/examples/rp/src/bin/pio_hd44780.rs create mode 100644 embassy/examples/rp/src/bin/pio_i2s.rs create mode 100644 embassy/examples/rp/src/bin/pio_onewire.rs create mode 100644 embassy/examples/rp/src/bin/pio_pwm.rs create mode 100644 embassy/examples/rp/src/bin/pio_rotary_encoder.rs create mode 100644 embassy/examples/rp/src/bin/pio_servo.rs create mode 100644 embassy/examples/rp/src/bin/pio_stepper.rs create mode 100644 embassy/examples/rp/src/bin/pio_uart.rs create mode 100644 embassy/examples/rp/src/bin/pio_ws2812.rs create mode 100644 embassy/examples/rp/src/bin/pwm.rs create mode 100644 embassy/examples/rp/src/bin/pwm_input.rs create mode 100644 embassy/examples/rp/src/bin/rosc.rs create mode 100644 embassy/examples/rp/src/bin/rtc.rs create mode 100644 embassy/examples/rp/src/bin/shared_bus.rs create mode 100644 embassy/examples/rp/src/bin/sharing.rs create mode 100644 embassy/examples/rp/src/bin/spi.rs create mode 100644 embassy/examples/rp/src/bin/spi_async.rs create mode 100644 embassy/examples/rp/src/bin/spi_display.rs create mode 100644 embassy/examples/rp/src/bin/spi_gc9a01.rs create mode 100644 embassy/examples/rp/src/bin/spi_sdmmc.rs create mode 100644 embassy/examples/rp/src/bin/uart.rs create mode 100644 embassy/examples/rp/src/bin/uart_buffered_split.rs create mode 100644 embassy/examples/rp/src/bin/uart_r503.rs create mode 100644 embassy/examples/rp/src/bin/uart_unidir.rs create mode 100644 embassy/examples/rp/src/bin/usb_ethernet.rs create mode 100644 embassy/examples/rp/src/bin/usb_hid_keyboard.rs create mode 100644 embassy/examples/rp/src/bin/usb_hid_mouse.rs create mode 100644 embassy/examples/rp/src/bin/usb_logger.rs create mode 100644 embassy/examples/rp/src/bin/usb_midi.rs create mode 100644 embassy/examples/rp/src/bin/usb_raw.rs create mode 100644 embassy/examples/rp/src/bin/usb_raw_bulk.rs create mode 100644 embassy/examples/rp/src/bin/usb_serial.rs create mode 100644 embassy/examples/rp/src/bin/usb_serial_with_handler.rs create mode 100644 embassy/examples/rp/src/bin/usb_serial_with_logger.rs create mode 100644 embassy/examples/rp/src/bin/usb_webusb.rs create mode 100644 embassy/examples/rp/src/bin/watchdog.rs create mode 100644 embassy/examples/rp/src/bin/wifi_ap_tcp_server.rs create mode 100644 embassy/examples/rp/src/bin/wifi_blinky.rs create mode 100644 embassy/examples/rp/src/bin/wifi_scan.rs create mode 100644 embassy/examples/rp/src/bin/wifi_tcp_server.rs create mode 100644 embassy/examples/rp/src/bin/wifi_webrequest.rs create mode 100644 embassy/examples/rp/src/bin/zerocopy.rs create mode 100644 embassy/examples/rp23/.cargo/config.toml create mode 100644 embassy/examples/rp23/Cargo.toml create mode 100644 embassy/examples/rp23/assets/ferris.raw create mode 100644 embassy/examples/rp23/build.rs create mode 100644 embassy/examples/rp23/memory.x create mode 100644 embassy/examples/rp23/src/bin/adc.rs create mode 100644 embassy/examples/rp23/src/bin/adc_dma.rs create mode 100644 embassy/examples/rp23/src/bin/assign_resources.rs create mode 100644 embassy/examples/rp23/src/bin/blinky.rs create mode 100644 embassy/examples/rp23/src/bin/blinky_two_channels.rs create mode 100644 embassy/examples/rp23/src/bin/blinky_two_tasks.rs create mode 100644 embassy/examples/rp23/src/bin/button.rs create mode 100644 embassy/examples/rp23/src/bin/debounce.rs create mode 100644 embassy/examples/rp23/src/bin/flash.rs create mode 100644 embassy/examples/rp23/src/bin/gpio_async.rs create mode 100644 embassy/examples/rp23/src/bin/gpout.rs create mode 100644 embassy/examples/rp23/src/bin/i2c_async.rs create mode 100644 embassy/examples/rp23/src/bin/i2c_async_embassy.rs create mode 100644 embassy/examples/rp23/src/bin/i2c_blocking.rs create mode 100644 embassy/examples/rp23/src/bin/i2c_slave.rs create mode 100644 embassy/examples/rp23/src/bin/interrupt.rs create mode 100644 embassy/examples/rp23/src/bin/multicore.rs create mode 100644 embassy/examples/rp23/src/bin/multiprio.rs create mode 100644 embassy/examples/rp23/src/bin/otp.rs create mode 100644 embassy/examples/rp23/src/bin/pio_async.rs create mode 100644 embassy/examples/rp23/src/bin/pio_dma.rs create mode 100644 embassy/examples/rp23/src/bin/pio_hd44780.rs create mode 100644 embassy/examples/rp23/src/bin/pio_i2s.rs create mode 100644 embassy/examples/rp23/src/bin/pio_onewire.rs create mode 100644 embassy/examples/rp23/src/bin/pio_pwm.rs create mode 100644 embassy/examples/rp23/src/bin/pio_rotary_encoder.rs create mode 100644 embassy/examples/rp23/src/bin/pio_servo.rs create mode 100644 embassy/examples/rp23/src/bin/pio_stepper.rs create mode 100644 embassy/examples/rp23/src/bin/pio_uart.rs create mode 100644 embassy/examples/rp23/src/bin/pio_ws2812.rs create mode 100644 embassy/examples/rp23/src/bin/pwm.rs create mode 100644 embassy/examples/rp23/src/bin/pwm_input.rs create mode 100644 embassy/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs create mode 100644 embassy/examples/rp23/src/bin/rosc.rs create mode 100644 embassy/examples/rp23/src/bin/shared_bus.rs create mode 100644 embassy/examples/rp23/src/bin/sharing.rs create mode 100644 embassy/examples/rp23/src/bin/spi.rs create mode 100644 embassy/examples/rp23/src/bin/spi_async.rs create mode 100644 embassy/examples/rp23/src/bin/spi_display.rs create mode 100644 embassy/examples/rp23/src/bin/spi_sdmmc.rs create mode 100644 embassy/examples/rp23/src/bin/trng.rs create mode 100644 embassy/examples/rp23/src/bin/uart.rs create mode 100644 embassy/examples/rp23/src/bin/uart_buffered_split.rs create mode 100644 embassy/examples/rp23/src/bin/uart_r503.rs create mode 100644 embassy/examples/rp23/src/bin/uart_unidir.rs create mode 100644 embassy/examples/rp23/src/bin/usb_hid_keyboard.rs create mode 100644 embassy/examples/rp23/src/bin/usb_webusb.rs create mode 100644 embassy/examples/rp23/src/bin/watchdog.rs create mode 100644 embassy/examples/rp23/src/bin/zerocopy.rs create mode 100644 embassy/examples/std/Cargo.toml create mode 100644 embassy/examples/std/README.md create mode 100644 embassy/examples/std/src/bin/net.rs create mode 100644 embassy/examples/std/src/bin/net_dns.rs create mode 100644 embassy/examples/std/src/bin/net_ppp.rs create mode 100644 embassy/examples/std/src/bin/net_udp.rs create mode 100644 embassy/examples/std/src/bin/serial.rs create mode 100644 embassy/examples/std/src/bin/tcp_accept.rs create mode 100644 embassy/examples/std/src/bin/tick.rs create mode 100644 embassy/examples/std/src/serial_port.rs create mode 100644 embassy/examples/stm32c0/.cargo/config.toml create mode 100644 embassy/examples/stm32c0/Cargo.toml create mode 100644 embassy/examples/stm32c0/build.rs create mode 100644 embassy/examples/stm32c0/src/bin/blinky.rs create mode 100644 embassy/examples/stm32c0/src/bin/button.rs create mode 100644 embassy/examples/stm32c0/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32f0/.cargo/config.toml create mode 100644 embassy/examples/stm32f0/Cargo.toml create mode 100644 embassy/examples/stm32f0/build.rs create mode 100644 embassy/examples/stm32f0/src/bin/adc.rs create mode 100644 embassy/examples/stm32f0/src/bin/blinky.rs create mode 100644 embassy/examples/stm32f0/src/bin/button_controlled_blink.rs create mode 100644 embassy/examples/stm32f0/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32f0/src/bin/hello.rs create mode 100644 embassy/examples/stm32f0/src/bin/multiprio.rs create mode 100644 embassy/examples/stm32f0/src/bin/wdg.rs create mode 100644 embassy/examples/stm32f1/.cargo/config.toml create mode 100644 embassy/examples/stm32f1/Cargo.toml create mode 100644 embassy/examples/stm32f1/build.rs create mode 100644 embassy/examples/stm32f1/src/bin/adc.rs create mode 100644 embassy/examples/stm32f1/src/bin/blinky.rs create mode 100644 embassy/examples/stm32f1/src/bin/can.rs create mode 100644 embassy/examples/stm32f1/src/bin/hello.rs create mode 100644 embassy/examples/stm32f1/src/bin/input_capture.rs create mode 100644 embassy/examples/stm32f1/src/bin/pwm_input.rs create mode 100644 embassy/examples/stm32f1/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32f2/.cargo/config.toml create mode 100644 embassy/examples/stm32f2/Cargo.toml create mode 100644 embassy/examples/stm32f2/build.rs create mode 100644 embassy/examples/stm32f2/src/bin/blinky.rs create mode 100644 embassy/examples/stm32f2/src/bin/pll.rs create mode 100644 embassy/examples/stm32f3/.cargo/config.toml create mode 100644 embassy/examples/stm32f3/Cargo.toml create mode 100644 embassy/examples/stm32f3/README.md create mode 100644 embassy/examples/stm32f3/build.rs create mode 100644 embassy/examples/stm32f3/src/bin/blinky.rs create mode 100644 embassy/examples/stm32f3/src/bin/button.rs create mode 100644 embassy/examples/stm32f3/src/bin/button_events.rs create mode 100644 embassy/examples/stm32f3/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32f3/src/bin/flash.rs create mode 100644 embassy/examples/stm32f3/src/bin/hello.rs create mode 100644 embassy/examples/stm32f3/src/bin/multiprio.rs create mode 100644 embassy/examples/stm32f3/src/bin/spi_dma.rs create mode 100644 embassy/examples/stm32f3/src/bin/tsc_blocking.rs create mode 100644 embassy/examples/stm32f3/src/bin/tsc_multipin.rs create mode 100644 embassy/examples/stm32f3/src/bin/usart_dma.rs create mode 100644 embassy/examples/stm32f3/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32f334/.cargo/config.toml create mode 100644 embassy/examples/stm32f334/Cargo.toml create mode 100644 embassy/examples/stm32f334/build.rs create mode 100644 embassy/examples/stm32f334/src/bin/adc.rs create mode 100644 embassy/examples/stm32f334/src/bin/button.rs create mode 100644 embassy/examples/stm32f334/src/bin/hello.rs create mode 100644 embassy/examples/stm32f334/src/bin/opamp.rs create mode 100644 embassy/examples/stm32f334/src/bin/pwm.rs create mode 100644 embassy/examples/stm32f4/.cargo/config.toml create mode 100644 embassy/examples/stm32f4/Cargo.toml create mode 100644 embassy/examples/stm32f4/build.rs create mode 100644 embassy/examples/stm32f4/src/bin/adc.rs create mode 100644 embassy/examples/stm32f4/src/bin/adc_dma.rs create mode 100644 embassy/examples/stm32f4/src/bin/blinky.rs create mode 100644 embassy/examples/stm32f4/src/bin/button.rs create mode 100644 embassy/examples/stm32f4/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32f4/src/bin/can.rs create mode 100644 embassy/examples/stm32f4/src/bin/dac.rs create mode 100644 embassy/examples/stm32f4/src/bin/eth.rs create mode 100644 embassy/examples/stm32f4/src/bin/eth_compliance_test.rs create mode 100644 embassy/examples/stm32f4/src/bin/eth_w5500.rs create mode 100644 embassy/examples/stm32f4/src/bin/flash.rs create mode 100644 embassy/examples/stm32f4/src/bin/flash_async.rs create mode 100644 embassy/examples/stm32f4/src/bin/hello.rs create mode 100644 embassy/examples/stm32f4/src/bin/i2c.rs create mode 100644 embassy/examples/stm32f4/src/bin/i2c_async.rs create mode 100644 embassy/examples/stm32f4/src/bin/i2c_comparison.rs create mode 100644 embassy/examples/stm32f4/src/bin/i2s_dma.rs create mode 100644 embassy/examples/stm32f4/src/bin/input_capture.rs create mode 100644 embassy/examples/stm32f4/src/bin/mco.rs create mode 100644 embassy/examples/stm32f4/src/bin/multiprio.rs create mode 100644 embassy/examples/stm32f4/src/bin/pwm.rs create mode 100644 embassy/examples/stm32f4/src/bin/pwm_complementary.rs create mode 100644 embassy/examples/stm32f4/src/bin/pwm_input.rs create mode 100644 embassy/examples/stm32f4/src/bin/rtc.rs create mode 100644 embassy/examples/stm32f4/src/bin/sdmmc.rs create mode 100644 embassy/examples/stm32f4/src/bin/spi.rs create mode 100644 embassy/examples/stm32f4/src/bin/spi_dma.rs create mode 100644 embassy/examples/stm32f4/src/bin/usart.rs create mode 100644 embassy/examples/stm32f4/src/bin/usart_buffered.rs create mode 100644 embassy/examples/stm32f4/src/bin/usart_dma.rs create mode 100644 embassy/examples/stm32f4/src/bin/usb_ethernet.rs create mode 100644 embassy/examples/stm32f4/src/bin/usb_hid_keyboard.rs create mode 100644 embassy/examples/stm32f4/src/bin/usb_hid_mouse.rs create mode 100644 embassy/examples/stm32f4/src/bin/usb_raw.rs create mode 100644 embassy/examples/stm32f4/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32f4/src/bin/usb_uac_speaker.rs create mode 100644 embassy/examples/stm32f4/src/bin/wdt.rs create mode 100644 embassy/examples/stm32f4/src/bin/ws2812_pwm.rs create mode 100644 embassy/examples/stm32f4/src/bin/ws2812_spi.rs create mode 100644 embassy/examples/stm32f469/.cargo/config.toml create mode 100644 embassy/examples/stm32f469/Cargo.toml create mode 100644 embassy/examples/stm32f469/build.rs create mode 100644 embassy/examples/stm32f469/src/bin/dsi_bsp.rs create mode 100644 embassy/examples/stm32f469/src/bin/ferris.bin create mode 100644 embassy/examples/stm32f7/.cargo/config.toml create mode 100644 embassy/examples/stm32f7/Cargo.toml create mode 100644 embassy/examples/stm32f7/build.rs create mode 100644 embassy/examples/stm32f7/src/bin/adc.rs create mode 100644 embassy/examples/stm32f7/src/bin/blinky.rs create mode 100644 embassy/examples/stm32f7/src/bin/button.rs create mode 100644 embassy/examples/stm32f7/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32f7/src/bin/can.rs create mode 100644 embassy/examples/stm32f7/src/bin/cryp.rs create mode 100644 embassy/examples/stm32f7/src/bin/eth.rs create mode 100644 embassy/examples/stm32f7/src/bin/flash.rs create mode 100644 embassy/examples/stm32f7/src/bin/hash.rs create mode 100644 embassy/examples/stm32f7/src/bin/hello.rs create mode 100644 embassy/examples/stm32f7/src/bin/qspi.rs create mode 100644 embassy/examples/stm32f7/src/bin/sdmmc.rs create mode 100644 embassy/examples/stm32f7/src/bin/usart_dma.rs create mode 100644 embassy/examples/stm32f7/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32g0/.cargo/config.toml create mode 100644 embassy/examples/stm32g0/Cargo.toml create mode 100644 embassy/examples/stm32g0/build.rs create mode 100644 embassy/examples/stm32g0/src/bin/adc.rs create mode 100644 embassy/examples/stm32g0/src/bin/adc_dma.rs create mode 100644 embassy/examples/stm32g0/src/bin/adc_oversampling.rs create mode 100644 embassy/examples/stm32g0/src/bin/blinky.rs create mode 100644 embassy/examples/stm32g0/src/bin/button.rs create mode 100644 embassy/examples/stm32g0/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32g0/src/bin/flash.rs create mode 100644 embassy/examples/stm32g0/src/bin/hf_timer.rs create mode 100644 embassy/examples/stm32g0/src/bin/i2c_async.rs create mode 100644 embassy/examples/stm32g0/src/bin/input_capture.rs create mode 100644 embassy/examples/stm32g0/src/bin/pwm_complementary.rs create mode 100644 embassy/examples/stm32g0/src/bin/pwm_input.rs create mode 100644 embassy/examples/stm32g0/src/bin/rtc.rs create mode 100644 embassy/examples/stm32g0/src/bin/spi_neopixel.rs create mode 100644 embassy/examples/stm32g0/src/bin/usart.rs create mode 100644 embassy/examples/stm32g0/src/bin/usart_buffered.rs create mode 100644 embassy/examples/stm32g0/src/bin/usart_dma.rs create mode 100644 embassy/examples/stm32g0/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32g4/.cargo/config.toml create mode 100644 embassy/examples/stm32g4/Cargo.toml create mode 100644 embassy/examples/stm32g4/build.rs create mode 100644 embassy/examples/stm32g4/src/bin/adc.rs create mode 100644 embassy/examples/stm32g4/src/bin/adc_differential.rs create mode 100644 embassy/examples/stm32g4/src/bin/adc_dma.rs create mode 100644 embassy/examples/stm32g4/src/bin/adc_oversampling.rs create mode 100644 embassy/examples/stm32g4/src/bin/blinky.rs create mode 100644 embassy/examples/stm32g4/src/bin/button.rs create mode 100644 embassy/examples/stm32g4/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32g4/src/bin/can.rs create mode 100644 embassy/examples/stm32g4/src/bin/pll.rs create mode 100644 embassy/examples/stm32g4/src/bin/pwm.rs create mode 100644 embassy/examples/stm32g4/src/bin/usb_c_pd.rs create mode 100644 embassy/examples/stm32g4/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32h5/.cargo/config.toml create mode 100644 embassy/examples/stm32h5/Cargo.toml create mode 100644 embassy/examples/stm32h5/build.rs create mode 100644 embassy/examples/stm32h5/src/bin/adc.rs create mode 100644 embassy/examples/stm32h5/src/bin/blinky.rs create mode 100644 embassy/examples/stm32h5/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32h5/src/bin/can.rs create mode 100644 embassy/examples/stm32h5/src/bin/cordic.rs create mode 100644 embassy/examples/stm32h5/src/bin/eth.rs create mode 100644 embassy/examples/stm32h5/src/bin/i2c.rs create mode 100644 embassy/examples/stm32h5/src/bin/rng.rs create mode 100644 embassy/examples/stm32h5/src/bin/stop.rs create mode 100644 embassy/examples/stm32h5/src/bin/usart.rs create mode 100644 embassy/examples/stm32h5/src/bin/usart_dma.rs create mode 100644 embassy/examples/stm32h5/src/bin/usart_split.rs create mode 100644 embassy/examples/stm32h5/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32h5/src/bin/usb_uac_speaker.rs create mode 100644 embassy/examples/stm32h7/.cargo/config.toml create mode 100644 embassy/examples/stm32h7/Cargo.toml create mode 100644 embassy/examples/stm32h7/build.rs create mode 100644 embassy/examples/stm32h7/memory.x create mode 100644 embassy/examples/stm32h7/src/bin/adc.rs create mode 100644 embassy/examples/stm32h7/src/bin/adc_dma.rs create mode 100644 embassy/examples/stm32h7/src/bin/blinky.rs create mode 100644 embassy/examples/stm32h7/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32h7/src/bin/camera.rs create mode 100644 embassy/examples/stm32h7/src/bin/can.rs create mode 100644 embassy/examples/stm32h7/src/bin/dac.rs create mode 100644 embassy/examples/stm32h7/src/bin/dac_dma.rs create mode 100644 embassy/examples/stm32h7/src/bin/eth.rs create mode 100644 embassy/examples/stm32h7/src/bin/eth_client.rs create mode 100644 embassy/examples/stm32h7/src/bin/eth_client_mii.rs create mode 100644 embassy/examples/stm32h7/src/bin/flash.rs create mode 100644 embassy/examples/stm32h7/src/bin/fmc.rs create mode 100644 embassy/examples/stm32h7/src/bin/i2c.rs create mode 100644 embassy/examples/stm32h7/src/bin/i2c_shared.rs create mode 100644 embassy/examples/stm32h7/src/bin/low_level_timer_api.rs create mode 100644 embassy/examples/stm32h7/src/bin/mco.rs create mode 100644 embassy/examples/stm32h7/src/bin/multiprio.rs create mode 100644 embassy/examples/stm32h7/src/bin/pwm.rs create mode 100644 embassy/examples/stm32h7/src/bin/rng.rs create mode 100644 embassy/examples/stm32h7/src/bin/rtc.rs create mode 100644 embassy/examples/stm32h7/src/bin/sai.rs create mode 100644 embassy/examples/stm32h7/src/bin/sdmmc.rs create mode 100644 embassy/examples/stm32h7/src/bin/signal.rs create mode 100644 embassy/examples/stm32h7/src/bin/spi.rs create mode 100644 embassy/examples/stm32h7/src/bin/spi_bdma.rs create mode 100644 embassy/examples/stm32h7/src/bin/spi_dma.rs create mode 100644 embassy/examples/stm32h7/src/bin/usart.rs create mode 100644 embassy/examples/stm32h7/src/bin/usart_dma.rs create mode 100644 embassy/examples/stm32h7/src/bin/usart_split.rs create mode 100644 embassy/examples/stm32h7/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32h7/src/bin/wdg.rs create mode 100644 embassy/examples/stm32h723/.cargo/config.toml create mode 100644 embassy/examples/stm32h723/Cargo.toml create mode 100644 embassy/examples/stm32h723/build.rs create mode 100644 embassy/examples/stm32h723/memory.x create mode 100644 embassy/examples/stm32h723/src/bin/spdifrx.rs create mode 100644 embassy/examples/stm32h735/.cargo/config.toml create mode 100644 embassy/examples/stm32h735/Cargo.toml create mode 100644 embassy/examples/stm32h735/build.rs create mode 100644 embassy/examples/stm32h735/memory.x create mode 100644 embassy/examples/stm32h735/src/bin/ferris.bmp create mode 100644 embassy/examples/stm32h735/src/bin/ltdc.rs create mode 100644 embassy/examples/stm32h755cm4/.cargo/config.toml create mode 100644 embassy/examples/stm32h755cm4/Cargo.toml create mode 100644 embassy/examples/stm32h755cm4/build.rs create mode 100644 embassy/examples/stm32h755cm4/memory.x create mode 100644 embassy/examples/stm32h755cm4/src/bin/blinky.rs create mode 100644 embassy/examples/stm32h755cm7/.cargo/config.toml create mode 100644 embassy/examples/stm32h755cm7/Cargo.toml create mode 100644 embassy/examples/stm32h755cm7/build.rs create mode 100644 embassy/examples/stm32h755cm7/memory.x create mode 100644 embassy/examples/stm32h755cm7/src/bin/blinky.rs create mode 100644 embassy/examples/stm32h7b0/.cargo/config.toml create mode 100644 embassy/examples/stm32h7b0/Cargo.toml create mode 100644 embassy/examples/stm32h7b0/build.rs create mode 100644 embassy/examples/stm32h7b0/memory.x create mode 100644 embassy/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs create mode 100644 embassy/examples/stm32h7rs/.cargo/config.toml create mode 100644 embassy/examples/stm32h7rs/Cargo.toml create mode 100644 embassy/examples/stm32h7rs/build.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/blinky.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/can.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/i2c.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/mco.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/multiprio.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/rng.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/rtc.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/signal.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/spi.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/spi_dma.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/usart.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/usart_dma.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/usart_split.rs create mode 100644 embassy/examples/stm32h7rs/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32l0/.cargo/config.toml create mode 100644 embassy/examples/stm32l0/Cargo.toml create mode 100644 embassy/examples/stm32l0/README.md create mode 100644 embassy/examples/stm32l0/build.rs create mode 100644 embassy/examples/stm32l0/src/bin/adc.rs create mode 100644 embassy/examples/stm32l0/src/bin/blinky.rs create mode 100644 embassy/examples/stm32l0/src/bin/button.rs create mode 100644 embassy/examples/stm32l0/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32l0/src/bin/dds.rs create mode 100644 embassy/examples/stm32l0/src/bin/flash.rs create mode 100644 embassy/examples/stm32l0/src/bin/raw_spawn.rs create mode 100644 embassy/examples/stm32l0/src/bin/spi.rs create mode 100644 embassy/examples/stm32l0/src/bin/tsc_async.rs create mode 100644 embassy/examples/stm32l0/src/bin/tsc_blocking.rs create mode 100644 embassy/examples/stm32l0/src/bin/tsc_multipin.rs create mode 100644 embassy/examples/stm32l0/src/bin/usart_dma.rs create mode 100644 embassy/examples/stm32l0/src/bin/usart_irq.rs create mode 100644 embassy/examples/stm32l1/.cargo/config.toml create mode 100644 embassy/examples/stm32l1/Cargo.toml create mode 100644 embassy/examples/stm32l1/build.rs create mode 100644 embassy/examples/stm32l1/src/bin/blinky.rs create mode 100644 embassy/examples/stm32l1/src/bin/flash.rs create mode 100644 embassy/examples/stm32l1/src/bin/spi.rs create mode 100644 embassy/examples/stm32l1/src/bin/usart.rs create mode 100644 embassy/examples/stm32l1/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32l4/.cargo/config.toml create mode 100644 embassy/examples/stm32l4/Cargo.toml create mode 100644 embassy/examples/stm32l4/README.md create mode 100644 embassy/examples/stm32l4/build.rs create mode 100644 embassy/examples/stm32l4/src/bin/adc.rs create mode 100644 embassy/examples/stm32l4/src/bin/blinky.rs create mode 100644 embassy/examples/stm32l4/src/bin/button.rs create mode 100644 embassy/examples/stm32l4/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32l4/src/bin/can.rs create mode 100644 embassy/examples/stm32l4/src/bin/dac.rs create mode 100644 embassy/examples/stm32l4/src/bin/dac_dma.rs create mode 100644 embassy/examples/stm32l4/src/bin/i2c.rs create mode 100644 embassy/examples/stm32l4/src/bin/i2c_blocking_async.rs create mode 100644 embassy/examples/stm32l4/src/bin/i2c_dma.rs create mode 100644 embassy/examples/stm32l4/src/bin/mco.rs create mode 100644 embassy/examples/stm32l4/src/bin/rng.rs create mode 100644 embassy/examples/stm32l4/src/bin/rtc.rs create mode 100644 embassy/examples/stm32l4/src/bin/spe_adin1110_http_server.rs create mode 100644 embassy/examples/stm32l4/src/bin/spi.rs create mode 100644 embassy/examples/stm32l4/src/bin/spi_blocking_async.rs create mode 100644 embassy/examples/stm32l4/src/bin/spi_dma.rs create mode 100644 embassy/examples/stm32l4/src/bin/tsc_async.rs create mode 100644 embassy/examples/stm32l4/src/bin/tsc_blocking.rs create mode 100644 embassy/examples/stm32l4/src/bin/tsc_multipin.rs create mode 100644 embassy/examples/stm32l4/src/bin/usart.rs create mode 100644 embassy/examples/stm32l4/src/bin/usart_dma.rs create mode 100644 embassy/examples/stm32l4/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32l5/.cargo/config.toml create mode 100644 embassy/examples/stm32l5/Cargo.toml create mode 100644 embassy/examples/stm32l5/build.rs create mode 100644 embassy/examples/stm32l5/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32l5/src/bin/rng.rs create mode 100644 embassy/examples/stm32l5/src/bin/stop.rs create mode 100644 embassy/examples/stm32l5/src/bin/usb_ethernet.rs create mode 100644 embassy/examples/stm32l5/src/bin/usb_hid_mouse.rs create mode 100644 embassy/examples/stm32l5/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32u0/.cargo/config.toml create mode 100644 embassy/examples/stm32u0/Cargo.toml create mode 100644 embassy/examples/stm32u0/build.rs create mode 100644 embassy/examples/stm32u0/src/bin/adc.rs create mode 100644 embassy/examples/stm32u0/src/bin/blinky.rs create mode 100644 embassy/examples/stm32u0/src/bin/button.rs create mode 100644 embassy/examples/stm32u0/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32u0/src/bin/crc.rs create mode 100644 embassy/examples/stm32u0/src/bin/dac.rs create mode 100644 embassy/examples/stm32u0/src/bin/flash.rs create mode 100644 embassy/examples/stm32u0/src/bin/i2c.rs create mode 100644 embassy/examples/stm32u0/src/bin/rng.rs create mode 100644 embassy/examples/stm32u0/src/bin/rtc.rs create mode 100644 embassy/examples/stm32u0/src/bin/spi.rs create mode 100644 embassy/examples/stm32u0/src/bin/usart.rs create mode 100644 embassy/examples/stm32u0/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32u0/src/bin/wdt.rs create mode 100644 embassy/examples/stm32u5/.cargo/config.toml create mode 100644 embassy/examples/stm32u5/Cargo.toml create mode 100644 embassy/examples/stm32u5/build.rs create mode 100644 embassy/examples/stm32u5/src/bin/blinky.rs create mode 100644 embassy/examples/stm32u5/src/bin/boot.rs create mode 100644 embassy/examples/stm32u5/src/bin/ferris.bmp create mode 100644 embassy/examples/stm32u5/src/bin/flash.rs create mode 100644 embassy/examples/stm32u5/src/bin/i2c.rs create mode 100644 embassy/examples/stm32u5/src/bin/ltdc.rs create mode 100644 embassy/examples/stm32u5/src/bin/rng.rs create mode 100644 embassy/examples/stm32u5/src/bin/tsc.rs create mode 100644 embassy/examples/stm32u5/src/bin/usb_hs_serial.rs create mode 100644 embassy/examples/stm32u5/src/bin/usb_serial.rs create mode 100644 embassy/examples/stm32wb/.cargo/config.toml create mode 100644 embassy/examples/stm32wb/Cargo.toml create mode 100644 embassy/examples/stm32wb/build.rs create mode 100644 embassy/examples/stm32wb/src/bin/blinky.rs create mode 100644 embassy/examples/stm32wb/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32wb/src/bin/eddystone_beacon.rs create mode 100644 embassy/examples/stm32wb/src/bin/gatt_server.rs create mode 100644 embassy/examples/stm32wb/src/bin/mac_ffd.rs create mode 100644 embassy/examples/stm32wb/src/bin/mac_ffd_net.rs create mode 100644 embassy/examples/stm32wb/src/bin/mac_rfd.rs create mode 100644 embassy/examples/stm32wb/src/bin/tl_mbox.rs create mode 100644 embassy/examples/stm32wb/src/bin/tl_mbox_ble.rs create mode 100644 embassy/examples/stm32wb/src/bin/tl_mbox_mac.rs create mode 100644 embassy/examples/stm32wba/.cargo/config.toml create mode 100644 embassy/examples/stm32wba/Cargo.toml create mode 100644 embassy/examples/stm32wba/build.rs create mode 100644 embassy/examples/stm32wba/src/bin/blinky.rs create mode 100644 embassy/examples/stm32wba/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32wl/.cargo/config.toml create mode 100644 embassy/examples/stm32wl/Cargo.toml create mode 100644 embassy/examples/stm32wl/build.rs create mode 100644 embassy/examples/stm32wl/memory.x create mode 100644 embassy/examples/stm32wl/src/bin/blinky.rs create mode 100644 embassy/examples/stm32wl/src/bin/button.rs create mode 100644 embassy/examples/stm32wl/src/bin/button_exti.rs create mode 100644 embassy/examples/stm32wl/src/bin/flash.rs create mode 100644 embassy/examples/stm32wl/src/bin/random.rs create mode 100644 embassy/examples/stm32wl/src/bin/rtc.rs create mode 100644 embassy/examples/stm32wl/src/bin/uart_async.rs create mode 100644 embassy/examples/wasm/Cargo.toml create mode 100644 embassy/examples/wasm/README.md create mode 100644 embassy/examples/wasm/index.html create mode 100644 embassy/examples/wasm/src/lib.rs create mode 100755 embassy/release/bump-dependency.sh create mode 100644 embassy/rust-toolchain-nightly.toml create mode 100644 embassy/rust-toolchain.toml create mode 100644 embassy/rustfmt.toml create mode 100644 embassy/tests/link_ram_cortex_m.x create mode 100644 embassy/tests/nrf/.cargo/config.toml create mode 100644 embassy/tests/nrf/Cargo.toml create mode 100644 embassy/tests/nrf/build.rs create mode 100644 embassy/tests/nrf/gen_test.py create mode 100644 embassy/tests/nrf/memory-nrf51422.x create mode 100644 embassy/tests/nrf/memory-nrf52832.x create mode 100644 embassy/tests/nrf/memory-nrf52833.x create mode 100644 embassy/tests/nrf/memory-nrf52840.x create mode 100644 embassy/tests/nrf/memory-nrf5340.x create mode 100644 embassy/tests/nrf/memory-nrf9160.x create mode 100644 embassy/tests/nrf/src/bin/buffered_uart.rs create mode 100644 embassy/tests/nrf/src/bin/buffered_uart_full.rs create mode 100644 embassy/tests/nrf/src/bin/buffered_uart_halves.rs create mode 100644 embassy/tests/nrf/src/bin/buffered_uart_spam.rs create mode 100644 embassy/tests/nrf/src/bin/ethernet_enc28j60_perf.rs create mode 100644 embassy/tests/nrf/src/bin/gpio.rs create mode 100644 embassy/tests/nrf/src/bin/gpiote.rs create mode 100644 embassy/tests/nrf/src/bin/spim.rs create mode 100644 embassy/tests/nrf/src/bin/timer.rs create mode 100644 embassy/tests/nrf/src/bin/uart_halves.rs create mode 100644 embassy/tests/nrf/src/bin/uart_split.rs create mode 100644 embassy/tests/nrf/src/bin/wifi_esp_hosted_perf.rs create mode 100644 embassy/tests/nrf/src/common.rs create mode 100644 embassy/tests/perf-client/Cargo.toml create mode 100644 embassy/tests/perf-client/src/lib.rs create mode 100644 embassy/tests/perf-server/Cargo.toml create mode 100755 embassy/tests/perf-server/deploy.sh create mode 100644 embassy/tests/perf-server/perf-server.service create mode 100644 embassy/tests/perf-server/src/main.rs create mode 100644 embassy/tests/riscv32/.cargo/config.toml create mode 100644 embassy/tests/riscv32/Cargo.toml create mode 100644 embassy/tests/riscv32/build.rs create mode 100644 embassy/tests/riscv32/link.x create mode 100644 embassy/tests/riscv32/memory.x create mode 100644 embassy/tests/riscv32/src/bin/empty.rs create mode 100644 embassy/tests/rp/.cargo/config.toml create mode 100644 embassy/tests/rp/Cargo.toml create mode 100644 embassy/tests/rp/build.rs create mode 100644 embassy/tests/rp/memory.x create mode 100644 embassy/tests/rp/src/bin/adc.rs create mode 100644 embassy/tests/rp/src/bin/bootsel.rs create mode 100644 embassy/tests/rp/src/bin/cyw43-perf.rs create mode 100644 embassy/tests/rp/src/bin/dma_copy_async.rs create mode 100644 embassy/tests/rp/src/bin/ethernet_w5100s_perf.rs create mode 100644 embassy/tests/rp/src/bin/flash.rs create mode 100644 embassy/tests/rp/src/bin/float.rs create mode 100644 embassy/tests/rp/src/bin/gpio.rs create mode 100644 embassy/tests/rp/src/bin/gpio_async.rs create mode 100644 embassy/tests/rp/src/bin/gpio_multicore.rs create mode 100644 embassy/tests/rp/src/bin/i2c.rs create mode 100644 embassy/tests/rp/src/bin/multicore.rs create mode 100644 embassy/tests/rp/src/bin/pio_irq.rs create mode 100644 embassy/tests/rp/src/bin/pio_multi_load.rs create mode 100644 embassy/tests/rp/src/bin/pwm.rs create mode 100644 embassy/tests/rp/src/bin/spi.rs create mode 100644 embassy/tests/rp/src/bin/spi_async.rs create mode 100644 embassy/tests/rp/src/bin/timer.rs create mode 100644 embassy/tests/rp/src/bin/uart.rs create mode 100644 embassy/tests/rp/src/bin/uart_buffered.rs create mode 100644 embassy/tests/rp/src/bin/uart_dma.rs create mode 100644 embassy/tests/rp/src/bin/uart_upgrade.rs create mode 100644 embassy/tests/stm32/.cargo/config.toml create mode 100644 embassy/tests/stm32/Cargo.toml create mode 100644 embassy/tests/stm32/build.rs create mode 100644 embassy/tests/stm32/gen_test.py create mode 100644 embassy/tests/stm32/src/bin/can.rs create mode 100644 embassy/tests/stm32/src/bin/can_common.rs create mode 100644 embassy/tests/stm32/src/bin/cordic.rs create mode 100644 embassy/tests/stm32/src/bin/cryp.rs create mode 100644 embassy/tests/stm32/src/bin/dac.rs create mode 100644 embassy/tests/stm32/src/bin/dac_l1.rs create mode 100644 embassy/tests/stm32/src/bin/eth.rs create mode 100644 embassy/tests/stm32/src/bin/fdcan.rs create mode 100644 embassy/tests/stm32/src/bin/gpio.rs create mode 100644 embassy/tests/stm32/src/bin/hash.rs create mode 100644 embassy/tests/stm32/src/bin/rng.rs create mode 100644 embassy/tests/stm32/src/bin/rtc.rs create mode 100644 embassy/tests/stm32/src/bin/sdmmc.rs create mode 100644 embassy/tests/stm32/src/bin/spi.rs create mode 100644 embassy/tests/stm32/src/bin/spi_dma.rs create mode 100644 embassy/tests/stm32/src/bin/stop.rs create mode 100644 embassy/tests/stm32/src/bin/timer.rs create mode 100644 embassy/tests/stm32/src/bin/ucpd.rs create mode 100644 embassy/tests/stm32/src/bin/usart.rs create mode 100644 embassy/tests/stm32/src/bin/usart_dma.rs create mode 100644 embassy/tests/stm32/src/bin/usart_rx_ringbuffered.rs create mode 100644 embassy/tests/stm32/src/bin/wpan_ble.rs create mode 100644 embassy/tests/stm32/src/bin/wpan_mac.rs create mode 100644 embassy/tests/stm32/src/common.rs create mode 100644 embassy/tests/utils/Cargo.toml create mode 100644 embassy/tests/utils/src/bin/saturate_serial.rs create mode 100644 embedded-nal/.github/workflows/ci.yml create mode 100644 embedded-nal/.github/workflows/clippy.yml create mode 100644 embedded-nal/.github/workflows/rustfmt.yml create mode 100644 embedded-nal/.gitignore create mode 100644 embedded-nal/CHANGELOG.md create mode 100644 embedded-nal/Cargo.toml create mode 100644 embedded-nal/LICENSE-APACHE create mode 100644 embedded-nal/LICENSE-MIT create mode 100644 embedded-nal/README.md create mode 100644 embedded-nal/embedded-nal-async/CHANGELOG.md create mode 100644 embedded-nal/embedded-nal-async/Cargo.toml create mode 100644 embedded-nal/embedded-nal-async/README.md create mode 100644 embedded-nal/embedded-nal-async/src/dns.rs create mode 100644 embedded-nal/embedded-nal-async/src/lib.rs create mode 100644 embedded-nal/embedded-nal-async/src/stack/mod.rs create mode 100644 embedded-nal/embedded-nal-async/src/stack/tcp.rs create mode 100644 embedded-nal/embedded-nal-async/src/stack/udp.rs create mode 100644 embedded-nal/rustfmt.toml create mode 100644 embedded-nal/src/dns.rs create mode 100644 embedded-nal/src/lib.rs create mode 100644 embedded-nal/src/stack/mod.rs create mode 100644 embedded-nal/src/stack/share.rs create mode 100644 embedded-nal/src/stack/tcp.rs create mode 100644 embedded-nal/src/stack/udp.rs create mode 100644 memory.x create mode 100644 rp-hal/.github/workflows/on_target_tests.yml create mode 100644 rp-hal/.github/workflows/rp2040_hal.yml create mode 100644 rp-hal/.github/workflows/rp2040_hal_examples.yml create mode 100644 rp-hal/.github/workflows/rp235x_hal_arm.yml create mode 100644 rp-hal/.github/workflows/rp235x_hal_examples_arm.yml create mode 100644 rp-hal/.github/workflows/rp235x_hal_examples_riscv.yml create mode 100644 rp-hal/.github/workflows/rp235x_hal_riscv.yml create mode 100644 rp-hal/.github/workflows/rp_binary_info.yml create mode 100644 rp-hal/.github/workflows/rp_hal_common.yml create mode 100644 rp-hal/.gitignore create mode 100644 rp-hal/CODE_OF_CONDUCT.md create mode 100644 rp-hal/LICENSE-APACHE create mode 100644 rp-hal/LICENSE-MIT create mode 100644 rp-hal/NOTICE create mode 100644 rp-hal/README.md create mode 100644 rp-hal/clippy.toml create mode 100644 rp-hal/format.bat create mode 100755 rp-hal/format.sh create mode 100644 rp-hal/on-target-tests/.cargo/config.toml create mode 100644 rp-hal/on-target-tests/.gitignore create mode 100644 rp-hal/on-target-tests/Cargo.toml create mode 100644 rp-hal/on-target-tests/LICENSE-APACHE create mode 100644 rp-hal/on-target-tests/LICENSE-MIT create mode 100644 rp-hal/on-target-tests/NOTICE create mode 100644 rp-hal/on-target-tests/README.md create mode 100644 rp-hal/on-target-tests/memory.x create mode 100755 rp-hal/on-target-tests/run_tests.bat create mode 100755 rp-hal/on-target-tests/run_tests.sh create mode 100644 rp-hal/on-target-tests/tests/dma_dyn.rs create mode 100644 rp-hal/on-target-tests/tests/dma_m2m_u16.rs create mode 100644 rp-hal/on-target-tests/tests/dma_m2m_u32.rs create mode 100644 rp-hal/on-target-tests/tests/dma_m2m_u8.rs create mode 100644 rp-hal/on-target-tests/tests/dma_spi_loopback_u16.rs create mode 100644 rp-hal/on-target-tests/tests/dma_spi_loopback_u8.rs create mode 100644 rp-hal/on-target-tests/tests/gpio.rs create mode 100644 rp-hal/on-target-tests/tests/i2c_loopback.rs create mode 100644 rp-hal/on-target-tests/tests/i2c_loopback_async.rs create mode 100644 rp-hal/on-target-tests/tests/i2c_tests/blocking.rs create mode 100644 rp-hal/on-target-tests/tests/i2c_tests/mod.rs create mode 100644 rp-hal/on-target-tests/tests/i2c_tests/non_blocking.rs create mode 100644 rp-hal/on-target-tests/tests/i2c_tests/test_executor.rs create mode 100644 rp-hal/rp-binary-info/Cargo.toml create mode 100644 rp-hal/rp-binary-info/LICENSE-APACHE create mode 100644 rp-hal/rp-binary-info/LICENSE-MIT create mode 100644 rp-hal/rp-binary-info/NOTICE create mode 100644 rp-hal/rp-binary-info/README.md create mode 100644 rp-hal/rp-binary-info/src/consts.rs create mode 100644 rp-hal/rp-binary-info/src/lib.rs create mode 100644 rp-hal/rp-binary-info/src/macros.rs create mode 100644 rp-hal/rp-binary-info/src/types.rs create mode 100644 rp-hal/rp-hal-common/Cargo.toml create mode 100644 rp-hal/rp-hal-common/LICENSE-APACHE create mode 100644 rp-hal/rp-hal-common/LICENSE-MIT create mode 100644 rp-hal/rp-hal-common/NOTICE create mode 100644 rp-hal/rp-hal-common/README.md create mode 100644 rp-hal/rp-hal-common/src/lib.rs create mode 100644 rp-hal/rp-hal-common/src/uart/common_configs.rs create mode 100644 rp-hal/rp-hal-common/src/uart/mod.rs create mode 100644 rp-hal/rp-hal-common/src/uart/utils.rs create mode 100644 rp-hal/rp2040-hal-examples/.cargo/config.toml create mode 100644 rp-hal/rp2040-hal-examples/Cargo.toml create mode 100644 rp-hal/rp2040-hal-examples/LICENSE-APACHE create mode 100644 rp-hal/rp2040-hal-examples/LICENSE-MIT create mode 100644 rp-hal/rp2040-hal-examples/NOTICE create mode 100644 rp-hal/rp2040-hal-examples/README.md create mode 100644 rp-hal/rp2040-hal-examples/build.rs create mode 100644 rp-hal/rp2040-hal-examples/memory.x create mode 100644 rp-hal/rp2040-hal-examples/src/bin/adc.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/adc_fifo_dma.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/adc_fifo_irq.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/adc_fifo_poll.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/alloc.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/binary_info_demo.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/blinky.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/dht11.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/dormant_sleep.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/gpio_dyn_pin_array.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/gpio_in_out.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/gpio_irq_example.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/i2c.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/i2c_async.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/i2c_async_cancelled.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/lcd_display.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/mem_to_mem_dma.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/multicore_fifo_blink.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/multicore_polyblink.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/pio_blink.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/pio_dma.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/pio_proc_blink.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/pio_side_set.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/pio_synchronized.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/pwm_blink.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/pwm_blink_embedded_hal_1.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/pwm_irq_input.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/rom_funcs.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/rosc_as_system_clock.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/rtc_irq_example.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/rtc_sleep_example.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/spi.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/spi_dma.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/spi_eh_bus.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/uart.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/uart_dma.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/uart_loopback.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/vector_table.rs create mode 100644 rp-hal/rp2040-hal-examples/src/bin/watchdog.rs create mode 100644 rp-hal/rp2040-hal-macros/.gitignore create mode 100644 rp-hal/rp2040-hal-macros/Cargo.toml create mode 100644 rp-hal/rp2040-hal-macros/LICENSE-APACHE create mode 100644 rp-hal/rp2040-hal-macros/LICENSE-MIT create mode 100644 rp-hal/rp2040-hal-macros/NOTICE create mode 100644 rp-hal/rp2040-hal-macros/README.md create mode 100644 rp-hal/rp2040-hal-macros/src/lib.rs create mode 100644 rp-hal/rp2040-hal/.gitignore create mode 100644 rp-hal/rp2040-hal/CHANGELOG.md create mode 100644 rp-hal/rp2040-hal/Cargo.toml create mode 100644 rp-hal/rp2040-hal/LICENSE-APACHE create mode 100644 rp-hal/rp2040-hal/LICENSE-MIT create mode 100644 rp-hal/rp2040-hal/NOTICE create mode 100644 rp-hal/rp2040-hal/README.md create mode 100644 rp-hal/rp2040-hal/src/adc.rs create mode 100644 rp-hal/rp2040-hal/src/arch.rs create mode 100644 rp-hal/rp2040-hal/src/async_utils.rs create mode 100644 rp-hal/rp2040-hal/src/atomic_register_access.rs create mode 100644 rp-hal/rp2040-hal/src/clocks/clock_sources.rs create mode 100644 rp-hal/rp2040-hal/src/clocks/macros.rs create mode 100644 rp-hal/rp2040-hal/src/clocks/mod.rs create mode 100644 rp-hal/rp2040-hal/src/critical_section_impl.rs create mode 100644 rp-hal/rp2040-hal/src/dma/bidirectional.rs create mode 100644 rp-hal/rp2040-hal/src/dma/double_buffer.rs create mode 100644 rp-hal/rp2040-hal/src/dma/mod.rs create mode 100644 rp-hal/rp2040-hal/src/dma/single_buffer.rs create mode 100644 rp-hal/rp2040-hal/src/dma/single_channel.rs create mode 100644 rp-hal/rp2040-hal/src/float/add_sub.rs create mode 100644 rp-hal/rp2040-hal/src/float/cmp.rs create mode 100644 rp-hal/rp2040-hal/src/float/conv.rs create mode 100644 rp-hal/rp2040-hal/src/float/div.rs create mode 100644 rp-hal/rp2040-hal/src/float/functions.rs create mode 100644 rp-hal/rp2040-hal/src/float/mod.rs create mode 100644 rp-hal/rp2040-hal/src/float/mul.rs create mode 100644 rp-hal/rp2040-hal/src/gpio/func.rs create mode 100644 rp-hal/rp2040-hal/src/gpio/mod.rs create mode 100644 rp-hal/rp2040-hal/src/gpio/pin.rs create mode 100644 rp-hal/rp2040-hal/src/gpio/pin/pin_sealed.rs create mode 100644 rp-hal/rp2040-hal/src/gpio/pin_group.rs create mode 100644 rp-hal/rp2040-hal/src/gpio/pull.rs create mode 100644 rp-hal/rp2040-hal/src/i2c.rs create mode 100644 rp-hal/rp2040-hal/src/i2c/controller.rs create mode 100644 rp-hal/rp2040-hal/src/i2c/controller/non_blocking.rs create mode 100644 rp-hal/rp2040-hal/src/i2c/peripheral.rs create mode 100644 rp-hal/rp2040-hal/src/intrinsics.rs create mode 100644 rp-hal/rp2040-hal/src/lib.rs create mode 100644 rp-hal/rp2040-hal/src/multicore.rs create mode 100644 rp-hal/rp2040-hal/src/pio.rs create mode 100644 rp-hal/rp2040-hal/src/pll.rs create mode 100644 rp-hal/rp2040-hal/src/prelude.rs create mode 100644 rp-hal/rp2040-hal/src/pwm/dyn_slice.rs create mode 100644 rp-hal/rp2040-hal/src/pwm/mod.rs create mode 100644 rp-hal/rp2040-hal/src/pwm/reg.rs create mode 100644 rp-hal/rp2040-hal/src/resets.rs create mode 100644 rp-hal/rp2040-hal/src/rom_data.rs create mode 100644 rp-hal/rp2040-hal/src/rosc.rs create mode 100644 rp-hal/rp2040-hal/src/rtc/datetime.rs create mode 100644 rp-hal/rp2040-hal/src/rtc/datetime_chrono.rs create mode 100644 rp-hal/rp2040-hal/src/rtc/filter.rs create mode 100644 rp-hal/rp2040-hal/src/rtc/mod.rs create mode 100644 rp-hal/rp2040-hal/src/sio.rs create mode 100644 rp-hal/rp2040-hal/src/spi.rs create mode 100644 rp-hal/rp2040-hal/src/spi/pins.rs create mode 100644 rp-hal/rp2040-hal/src/ssi.rs create mode 100644 rp-hal/rp2040-hal/src/timer.rs create mode 100644 rp-hal/rp2040-hal/src/typelevel.rs create mode 100644 rp-hal/rp2040-hal/src/uart/common_configs.rs create mode 100644 rp-hal/rp2040-hal/src/uart/mod.rs create mode 100644 rp-hal/rp2040-hal/src/uart/peripheral.rs create mode 100644 rp-hal/rp2040-hal/src/uart/pins.rs create mode 100644 rp-hal/rp2040-hal/src/uart/reader.rs create mode 100644 rp-hal/rp2040-hal/src/uart/utils.rs create mode 100644 rp-hal/rp2040-hal/src/uart/writer.rs create mode 100644 rp-hal/rp2040-hal/src/usb.rs create mode 100644 rp-hal/rp2040-hal/src/usb/errata5.rs create mode 100644 rp-hal/rp2040-hal/src/vector_table.rs create mode 100644 rp-hal/rp2040-hal/src/vreg.rs create mode 100644 rp-hal/rp2040-hal/src/watchdog.rs create mode 100644 rp-hal/rp2040-hal/src/xosc.rs create mode 100644 rp-hal/rp235x-hal-examples/.cargo/config.toml create mode 100644 rp-hal/rp235x-hal-examples/.gitignore create mode 100644 rp-hal/rp235x-hal-examples/Cargo.toml create mode 100644 rp-hal/rp235x-hal-examples/LICENSE-APACHE create mode 100644 rp-hal/rp235x-hal-examples/LICENSE-MIT create mode 100644 rp-hal/rp235x-hal-examples/NOTICE create mode 100644 rp-hal/rp235x-hal-examples/README.md create mode 100644 rp-hal/rp235x-hal-examples/build.rs create mode 100644 rp-hal/rp235x-hal-examples/memory.x create mode 100644 rp-hal/rp235x-hal-examples/riscv_examples.txt create mode 100644 rp-hal/rp235x-hal-examples/rp235x_riscv.x create mode 100644 rp-hal/rp235x-hal-examples/src/bin/adc.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/adc_fifo_dma.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/adc_fifo_irq.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/adc_fifo_poll.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/alloc.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/arch_flip.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/binary_info_demo.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/blinky.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/block_loop.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/dht11.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/float_test.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/gpio_dyn_pin_array.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/gpio_in_out.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/gpio_irq_example.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/i2c.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/i2c_async.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/i2c_async_cancelled.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/lcd_display.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/mem_to_mem_dma.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/multicore_fifo_blink.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/multicore_polyblink.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/pio_blink.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/pio_dma.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/pio_proc_blink.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/pio_side_set.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/pio_synchronized.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/powman_test.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/pwm_blink.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/pwm_blink_embedded_hal_1.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/pwm_irq_input.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/rom_funcs.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/rosc_as_system_clock.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/spi.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/spi_dma.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/uart.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/uart_dma.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/uart_loopback.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/usb.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/vector_table.rs create mode 100644 rp-hal/rp235x-hal-examples/src/bin/watchdog.rs create mode 100644 rp-hal/rp235x-hal-macros/.gitignore create mode 100644 rp-hal/rp235x-hal-macros/Cargo.toml create mode 100644 rp-hal/rp235x-hal-macros/LICENSE-APACHE create mode 100644 rp-hal/rp235x-hal-macros/LICENSE-MIT create mode 100644 rp-hal/rp235x-hal-macros/NOTICE create mode 100644 rp-hal/rp235x-hal-macros/README.md create mode 100644 rp-hal/rp235x-hal-macros/src/lib.rs create mode 100644 rp-hal/rp235x-hal/.gitignore create mode 100644 rp-hal/rp235x-hal/CHANGELOG.md create mode 100644 rp-hal/rp235x-hal/Cargo.toml create mode 100644 rp-hal/rp235x-hal/README.md create mode 100644 rp-hal/rp235x-hal/src/adc.rs create mode 100644 rp-hal/rp235x-hal/src/arch.rs create mode 100644 rp-hal/rp235x-hal/src/async_utils.rs create mode 100644 rp-hal/rp235x-hal/src/atomic_register_access.rs create mode 100644 rp-hal/rp235x-hal/src/block.rs create mode 100644 rp-hal/rp235x-hal/src/clocks/clock_sources.rs create mode 100644 rp-hal/rp235x-hal/src/clocks/macros.rs create mode 100644 rp-hal/rp235x-hal/src/clocks/mod.rs create mode 100644 rp-hal/rp235x-hal/src/critical_section_impl.rs create mode 100644 rp-hal/rp235x-hal/src/dcp.rs create mode 100644 rp-hal/rp235x-hal/src/dma/bidirectional.rs create mode 100644 rp-hal/rp235x-hal/src/dma/double_buffer.rs create mode 100644 rp-hal/rp235x-hal/src/dma/mod.rs create mode 100644 rp-hal/rp235x-hal/src/dma/single_buffer.rs create mode 100644 rp-hal/rp235x-hal/src/dma/single_channel.rs create mode 100644 rp-hal/rp235x-hal/src/gpio/func.rs create mode 100644 rp-hal/rp235x-hal/src/gpio/mod.rs create mode 100644 rp-hal/rp235x-hal/src/gpio/pin.rs create mode 100644 rp-hal/rp235x-hal/src/gpio/pin/pin_sealed.rs create mode 100644 rp-hal/rp235x-hal/src/gpio/pin_group.rs create mode 100644 rp-hal/rp235x-hal/src/gpio/pull.rs create mode 100644 rp-hal/rp235x-hal/src/i2c.rs create mode 100644 rp-hal/rp235x-hal/src/i2c/controller.rs create mode 100644 rp-hal/rp235x-hal/src/i2c/controller/non_blocking.rs create mode 100644 rp-hal/rp235x-hal/src/i2c/peripheral.rs create mode 100644 rp-hal/rp235x-hal/src/lib.rs create mode 100644 rp-hal/rp235x-hal/src/lposc.rs create mode 100644 rp-hal/rp235x-hal/src/multicore.rs create mode 100644 rp-hal/rp235x-hal/src/otp.rs create mode 100644 rp-hal/rp235x-hal/src/pio.rs create mode 100644 rp-hal/rp235x-hal/src/pll.rs create mode 100644 rp-hal/rp235x-hal/src/powman.rs create mode 100644 rp-hal/rp235x-hal/src/prelude.rs create mode 100644 rp-hal/rp235x-hal/src/pwm/dyn_slice.rs create mode 100644 rp-hal/rp235x-hal/src/pwm/mod.rs create mode 100644 rp-hal/rp235x-hal/src/pwm/reg.rs create mode 100644 rp-hal/rp235x-hal/src/reboot.rs create mode 100644 rp-hal/rp235x-hal/src/resets.rs create mode 100644 rp-hal/rp235x-hal/src/rom_data.rs create mode 100644 rp-hal/rp235x-hal/src/rosc.rs create mode 100644 rp-hal/rp235x-hal/src/sio.rs create mode 100644 rp-hal/rp235x-hal/src/spi.rs create mode 100644 rp-hal/rp235x-hal/src/spi/pins.rs create mode 100644 rp-hal/rp235x-hal/src/timer.rs create mode 100644 rp-hal/rp235x-hal/src/typelevel.rs create mode 100644 rp-hal/rp235x-hal/src/uart/common_configs.rs create mode 100644 rp-hal/rp235x-hal/src/uart/mod.rs create mode 100644 rp-hal/rp235x-hal/src/uart/peripheral.rs create mode 100644 rp-hal/rp235x-hal/src/uart/pins.rs create mode 100644 rp-hal/rp235x-hal/src/uart/reader.rs create mode 100644 rp-hal/rp235x-hal/src/uart/utils.rs create mode 100644 rp-hal/rp235x-hal/src/uart/writer.rs create mode 100644 rp-hal/rp235x-hal/src/usb.rs create mode 100644 rp-hal/rp235x-hal/src/vector_table.rs create mode 100644 rp-hal/rp235x-hal/src/watchdog.rs create mode 100644 rp-hal/rp235x-hal/src/xosc.rs create mode 100644 rust-toolchain.toml create mode 100644 src/clock.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/main_final_working.rs create mode 100644 src/resources.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..73e501f --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,13 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +#runner = "probe-rs run --chip RP2040" +runner = "probe-rs run --chip esp32c6" + +[build] +target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ + +[env] +SSID = "SSID" +SSID_PASSWORD = "PSK" +CLIENT_ID = "495f6297-b962-4266-9c00-74138ae5a1f1" +DEFMT_LOG = "debug" +LED_SIZE = "100" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fa78f36 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "embassy"] + path = embassy + url = https://github.com/embassy-rs/embassy.git +[submodule "embedded-nal"] + path = embedded-nal + url = https://github.com/rust-embedded-community/embedded-nal.git +[submodule "rp-hal"] + path = rp-hal + url = https://github.com/rp-rs/rp-hal.git diff --git a/.stfolder/syncthing-folder-dcf9ee.txt b/.stfolder/syncthing-folder-dcf9ee.txt new file mode 100644 index 0000000..ab29889 --- /dev/null +++ b/.stfolder/syncthing-folder-dcf9ee.txt @@ -0,0 +1,5 @@ +# This directory is a Syncthing folder marker. +# Do not delete. + +folderID: ggvqx-3ckoc +created: 2024-10-27T17:32:22-04:00 diff --git a/.stignore b/.stignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.stignore @@ -0,0 +1 @@ +target/ diff --git a/.stversions/embassy/docs/examples/basic/Cargo~20241114-122315.toml b/.stversions/embassy/docs/examples/basic/Cargo~20241114-122315.toml new file mode 100644 index 0000000..5d391ad --- /dev/null +++ b/.stversions/embassy/docs/examples/basic/Cargo~20241114-122315.toml @@ -0,0 +1,18 @@ +[package] +authors = ["Dario Nieuwenhuis "] +edition = "2018" +name = "embassy-basic-example" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["defmt", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt"] } +embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] } + +defmt = "0.3" +defmt-rtt = "0.3" + +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"] } diff --git a/.stversions/embassy/docs/examples/layer-by-layer/blinky-async/Cargo~20241114-122315.toml b/.stversions/embassy/docs/examples/layer-by-layer/blinky-async/Cargo~20241114-122315.toml new file mode 100644 index 0000000..7f8d8af --- /dev/null +++ b/.stversions/embassy/docs/examples/layer-by-layer/blinky-async/Cargo~20241114-122315.toml @@ -0,0 +1,15 @@ +[package] +name = "blinky-async" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"] } +embassy-executor = { version = "0.6.0", features = ["arch-cortex-m", "executor-thread"] } + +defmt = "0.3.0" +defmt-rtt = "0.3.0" +panic-probe = { version = "0.3.0", features = ["print-defmt"] } diff --git a/.stversions/embassy/docs/pages/embassy_in_the_wild~20241114-122314.adoc b/.stversions/embassy/docs/pages/embassy_in_the_wild~20241114-122314.adoc new file mode 100644 index 0000000..bb457a8 --- /dev/null +++ b/.stversions/embassy/docs/pages/embassy_in_the_wild~20241114-122314.adoc @@ -0,0 +1,24 @@ += Embassy in the wild! + +Here are known examples of real-world projects which make use of Embassy. Feel free to link:https://github.com/embassy-rs/embassy/blob/main/docs/pages/embassy_in_the_wild.adoc[add more]! + +* link:https://github.com/1-rafael-1/pi-pico-alarmclock-rust[A Raspberry Pi Pico W Alarmclock] +** A hobbyist project building an alarm clock around a Pi Pico W complete with code, components list and enclosure design files. +* link:https://github.com/haobogu/rmk/[RMK: A feature-rich Rust keyboard firmware] +** RMK has built-in layer support, wireless(BLE) support, real-time key editing support using vial, and more! +** Targets STM32, RP2040, nRF52 and ESP32 MCUs +* link:https://github.com/cbruiz/printhor/[Printhor: The highly reliable but not necessarily functional 3D printer firmware] +** Targets some STM32 MCUs +* link:https://github.com/card-io-ecg/card-io-fw[Card/IO firmware] - firmware for an open source ECG device +** Targets the ESP32-S3 or ESP32-C6 MCU +* The link:https://github.com/lora-rs/lora-rs[lora-rs] project includes link:https://github.com/lora-rs/lora-rs/tree/main/examples/stm32l0/src/bin[various standalone examples] for NRF52840, RP2040, STM32L0 and STM32WL +* link:https://github.com/matoushybl/air-force-one[Air force one: A simple air quality monitoring system] +** Targets nRF52 and uses nrf-softdevice + +* link:https://github.com/schmettow/ylab-edge-go[YLab Edge Go] and link:https://github.com/schmettow/ylab-edge-pro[YLab Edge Pro] projects develop +firmware (RP2040, STM32) for capturing physiological data in behavioural science research. Included so far are: +** biopotentials (analog ports) +** motion capture (6-axis accelerometers) +** air quality (CO2, Temp, Humidity) +** comes with an app for capturing and visualizing data [link:https://github.com/schmettow/ystudio-zero[Ystudio]] + diff --git a/.stversions/embassy/docs/pages/new_project~20241114-122315.adoc b/.stversions/embassy/docs/pages/new_project~20241114-122315.adoc new file mode 100644 index 0000000..38cea04 --- /dev/null +++ b/.stversions/embassy/docs/pages/new_project~20241114-122315.adoc @@ -0,0 +1,202 @@ += Starting a new project + +Once you’ve successfully xref:#_getting_started[run some example projects], the next step is to make a standalone Embassy project. + +== Tools for generating Embassy projects + +=== CLI +- link:https://github.com/adinack/cargo-embassy[cargo-embassy] (STM32 and NRF) + +=== cargo-generate +- link:https://github.com/lulf/embassy-template[embassy-template] (STM32, NRF, and RP) +- link:https://github.com/bentwire/embassy-rp2040-template[embassy-rp2040-template] (RP) + + +== Starting a project from scratch + +As an example, let’s create a new embassy project from scratch for a STM32G474. The same instructions are applicable for any supported chip with some minor changes. + +Run: + +[source,bash] +---- +cargo new stm32g474-example +cd stm32g474-example +---- + +to create an empty rust project: + +[source] +---- +stm32g474-example +├── Cargo.toml +└── src + └── main.rs +---- + +Looking in link:https://github.com/embassy-rs/embassy/tree/main/examples[the Embassy examples], we can see there’s a `stm32g4` folder. Find `src/blinky.rs` and copy its contents into our `src/main.rs`. + +=== The .cargo/config.toml + +Currently, we’d need to provide cargo with a target triple every time we run `cargo build` or `cargo run`. Let’s spare ourselves that work by copying `.cargo/config.toml` from `examples/stm32g4` into our project. + +[source] +---- +stm32g474-example +├── .cargo +│   └── config.toml +├── Cargo.toml +└── src + └── main.rs +---- + +In addition to a target triple, `.cargo/config.toml` contains a `runner` key which allows us to conveniently run our project on hardware with `cargo run` via probe-rs. In order for this to work, we need to provide the correct chip ID. We can do this by checking `probe-rs chip list`: + +[source,bash] +---- +$ probe-rs chip list | grep -i stm32g474re + STM32G474RETx +---- + +and copying `STM32G474RETx` into `.cargo/config.toml` as so: + +[source,toml] +---- +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32G071C8Rx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32G474RETx" +---- + +=== Cargo.toml + +Now that cargo knows what target to compile for (and probe-rs knows what chip to run it on), we’re ready to add some dependencies. + +Looking in `examples/stm32g4/Cargo.toml`, we can see that the examples require a number of embassy crates. For blinky, we’ll only need three of them: `embassy-stm32`, `embassy-executor` and `embassy-time`. + + +At the time of writing, embassy is already published to crates.io. Therefore, dependencies can easily added via Cargo.toml. + +[source,toml] +---- +[dependencies] +embassy-stm32 = { version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"] } +embassy-executor = { version = "0.6.1", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +---- + +Prior, embassy needed to be installed straight from the git repository. Installing from git is still useful, if you want to checkout a specic revision of an embassy crate which is not yet published. +The recommended way of doing so is as follows: + +* Copy the required `embassy-*` lines from the example `Cargo.toml` +* Make any necessary changes to `features`, e.g. requiring the `stm32g474re` feature of `embassy-stm32` +* Remove the `path = ""` keys in the `embassy-*` entries +* Create a `[patch.crates-io]` section, with entries for each embassy crate we need. These should all contain identical values: a link to the git repository, and a reference to the commit we’re checking out. Assuming you want the latest commit, you can find it by running `git ls-remote https://github.com/embassy-rs/embassy.git HEAD` + +NOTE: When using this method, it’s necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crate’s `Cargo.toml` in the specificed `rev` under `[patch.crates.io]`. This means that when updating, you have to a pick a new revision, change everything in `[patch.crates.io]` to match it, and then correct any versions under `[dependencies]` which have changed. + +An example Cargo.toml file might look as follows: + +[source,toml] +---- +[dependencies] +embassy-stm32 = {version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"]} +embassy-executor = { version = "0.3.3", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } + +[patch.crates-io] +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" } +embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" } +---- + +There are a few other dependencies we need to build the project, but fortunately they’re much simpler to install. Copy their lines from the example `Cargo.toml` to the the `[dependencies]` section in the new `Cargo.toml`: + +[source,toml] +---- +defmt = "0.3.5" +defmt-rtt = "0.4.0" +cortex-m = {version = "0.7.7", features = ["critical-section-single-core"]} +cortex-m-rt = "0.7.3" +panic-probe = "0.3.1" +---- + +These are the bare minimum dependencies required to run `blinky.rs`, but it’s worth taking a look at the other dependencies specified in the example `Cargo.toml`, and noting what features are required for use with embassy – for example `futures = { version = "0.3.17", default-features = false, features = ["async-await"] }`. + +Finally, copy the `[profile.release]` section from the example `Cargo.toml` into ours. + +[source,toml] +---- +[profile.release] +debug = 2 +---- + +=== rust-toolchain.toml + +Before we can build our project, we need to add an additional file to tell cargo to use the nightly toolchain. Copy the `rust-toolchain.toml` from the embassy repo to ours, and trim the list of targets down to only the target triple relevent for our project — in this case, `thumbv7em-none-eabi`: + +[source] +---- +stm32g474-example +├── .cargo +│   └── config.toml +├── Cargo.toml +├── rust-toolchain.toml +└── src + └── main.rs +---- + +[source,toml] +---- +# Before upgrading check that everything is available on all tier1 targets here: +# https://rust-lang.github.io/rustup-components-history +[toolchain] +channel = "nightly-2023-11-01" +components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ] +targets = ["thumbv7em-none-eabi"] +---- + +=== build.rs + +In order to produce a working binary for our target, cargo requires a custom build script. Copy `build.rs` from the example to our project: + +[source] +---- +stm32g474-example +├── build.rs +├── .cargo +│ └── config.toml +├── Cargo.toml +├── rust-toolchain.toml +└── src + └── main.rs +---- + +=== Building and running + +At this point, we‘re finally ready to build and run our project! Connect your board via a debug probe and run: + +[source,bash] +---- +cargo run --release +---- + +should result in a blinking LED (if there’s one attached to the pin in `src/main.rs` – change it if not!) and the following output: + +[source] +---- + Compiling stm32g474-example v0.1.0 (/home/you/stm32g474-example) + Finished release [optimized + debuginfo] target(s) in 0.22s + Running `probe-rs run --chip STM32G474RETx target/thumbv7em-none-eabi/release/stm32g474-example` + Erasing sectors ✔ [00:00:00] [#########################################################] 18.00 KiB/18.00 KiB @ 54.09 KiB/s (eta 0s ) + Programming pages ✔ [00:00:00] [#########################################################] 17.00 KiB/17.00 KiB @ 35.91 KiB/s (eta 0s ) Finished in 0.817s +0.000000 TRACE BDCR configured: 00008200 +└─ embassy_stm32::rcc::bd::{impl#3}::init::{closure#4} @ /home/you/.cargo/git/checkouts/embassy-9312dcb0ed774b29/7703f47/embassy-stm32/src/fmt.rs:117 +0.000000 DEBUG rcc: Clocks { sys: Hertz(16000000), pclk1: Hertz(16000000), pclk1_tim: Hertz(16000000), pclk2: Hertz(16000000), pclk2_tim: Hertz(16000000), hclk1: Hertz(16000000), hclk2: Hertz(16000000), pll1_p: None, adc: None, adc34: None, rtc: Some(Hertz(32000)) } +└─ embassy_stm32::rcc::set_freqs @ /home/you/.cargo/git/checkouts/embassy-9312dcb0ed774b29/7703f47/embassy-stm32/src/fmt.rs:130 +0.000000 INFO Hello World! +└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:14 +0.000091 INFO high +└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:19 +0.300201 INFO low +└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:23 +---- diff --git a/.stversions/embassy/embassy-executor/CHANGELOG~20241114-122315.md b/.stversions/embassy/embassy-executor/CHANGELOG~20241114-122315.md new file mode 100644 index 0000000..b342ccc --- /dev/null +++ b/.stversions/embassy/embassy-executor/CHANGELOG~20241114-122315.md @@ -0,0 +1,87 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.6.2 - 2024-11-06 + +- The `nightly` feature no longer requires `nightly-2024-09-06` or newer. + +## 0.6.1 - 2024-10-21 + +- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, + and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types + for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. +- Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature. +- Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires `nightly-2024-09-06` or newer. +- Improve macro error messages. + +## 0.6.0 - 2024-08-05 + +- Add collapse_debuginfo to fmt.rs macros. +- initial support for AVR +- use nightly waker_getters APIs + +## 0.5.1 - 2024-10-21 + +- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, + and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types + for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. + +## 0.5.0 - 2024-01-11 + +- Updated to `embassy-time-driver 0.1`, `embassy-time-queue-driver 0.1`, compatible with `embassy-time v0.3` and higher. + +## 0.4.0 - 2023-12-05 + +- Removed `arch-xtensa`. Use the executor provided by the HAL crate (`esp-hal`, `esp32s3-hal`, etc...) instead. +- Added an arena allocator for tasks, allowing using the `main` and `task` macros on Rust 1.75 stable. (it is only used if the `nightly` feature is not enabled. When `nightly` is enabled, `type_alias_impl_trait` is used to statically allocate tasks, as before). + +## 0.3.3 - 2023-11-15 + +- Add `main` macro reexport for Xtensa arch. +- Remove use of `atomic-polyfill`. The executor now has multiple implementations of its internal data structures for cases where the target supports atomics or doesn't. + +## 0.3.2 - 2023-11-06 + +- Use `atomic-polyfill` for `riscv32` +- Removed unused dependencies (static_cell, futures-util) + +## 0.3.1 - 2023-11-01 + +- Fix spurious "Found waker not created by the Embassy executor" error in recent nightlies. + +## 0.3.0 - 2023-08-25 + +- Replaced Pender. Implementations now must define an extern function called `__pender`. +- Made `raw::AvailableTask` public +- Made `SpawnToken::new_failed` public +- You can now use arbitrary expressions to specify `#[task(pool_size = X)]` + +## 0.2.1 - 2023-08-10 + +- Avoid calling `pend()` when waking expired timers +- Properly reset finished task state with `integrated-timers` enabled +- Introduce `InterruptExecutor::spawner()` +- Fix incorrect critical section in Xtensa executor + +## 0.2.0 - 2023-04-27 + +- Replace unnecessary atomics in runqueue +- add Pender, rework Cargo features. +- add support for turbo-wakers. +- Allow TaskStorage to auto-implement `Sync` +- Use AtomicPtr for signal_ctx, removes 1 unsafe. +- Replace unsound critical sections with atomics + +## 0.1.1 - 2022-11-23 + +- Fix features for documentation + +## 0.1.0 - 2022-11-23 + +- First release diff --git a/.stversions/embassy/embassy-executor/Cargo~20241114-122315.toml b/.stversions/embassy/embassy-executor/Cargo~20241114-122315.toml new file mode 100644 index 0000000..2450ae6 --- /dev/null +++ b/.stversions/embassy/embassy-executor/Cargo~20241114-122315.toml @@ -0,0 +1,191 @@ +[package] +name = "embassy-executor" +version = "0.6.2" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "async/await executor designed for embedded usage" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-executor" +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/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/" +features = ["defmt"] +flavors = [ + { name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] }, + { name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] }, + { name = "cortex-m", target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] }, + { name = "riscv32", target = "riscv32imac-unknown-none-elf", features = ["arch-riscv32", "executor-thread"] }, +] + +[package.metadata.docs.rs] +default-target = "thumbv7em-none-eabi" +targets = ["thumbv7em-none-eabi"] +features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +rtos-trace = { version = "0.1.2", optional = true } + +embassy-executor-macros = { version = "0.6.2", path = "../embassy-executor-macros" } +embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver", optional = true } +embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver", optional = true } +critical-section = "1.1" + +document-features = "0.2.7" + +# needed for AVR +portable-atomic = { version = "1.5", optional = true } + +# arch-cortex-m dependencies +cortex-m = { version = "0.7.6", optional = true } + +# arch-wasm dependencies +wasm-bindgen = { version = "0.2.82", optional = true } +js-sys = { version = "0.3", optional = true } + +# arch-avr dependencies +avr-device = { version = "0.5.3", optional = true } + +[dev-dependencies] +critical-section = { version = "1.1", features = ["std"] } +trybuild = "1.0" + +[build-dependencies] +rustc_version = "0.4.1" + +[features] + +## Enable nightly-only features +nightly = ["embassy-executor-macros/nightly"] + +# Enables turbo wakers, which requires patching core. Not surfaced in the docs by default due to +# being an complicated advanced and undocumented feature. +# See: https://github.com/embassy-rs/embassy/pull/1263 +turbowakers = [] + +## Use the executor-integrated `embassy-time` timer queue. +integrated-timers = ["dep:embassy-time-driver", "dep:embassy-time-queue-driver"] + +#! ### Architecture +_arch = [] # some arch was picked +## std +arch-std = ["_arch", "critical-section/std"] +## Cortex-M +arch-cortex-m = ["_arch", "dep:cortex-m"] +## RISC-V 32 +arch-riscv32 = ["_arch"] +## WASM +arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"] +## AVR +arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] +## spin (architecture agnostic; never sleeps) +arch-spin = ["_arch"] + +#! ### Executor + +## Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs) +executor-thread = [] +## Enable the interrupt-mode executor (available in Cortex-M only) +executor-interrupt = [] + +#! ### Task Arena Size +#! Sets the [task arena](#task-arena) size. Necessary if you’re not using `nightly`. +#! +#!
+#! Preconfigured Task Arena Sizes: +#! +#! + +# BEGIN AUTOGENERATED CONFIG FEATURES +# Generated by gen_config.py. DO NOT EDIT. +## 64 +task-arena-size-64 = [] +## 128 +task-arena-size-128 = [] +## 192 +task-arena-size-192 = [] +## 256 +task-arena-size-256 = [] +## 320 +task-arena-size-320 = [] +## 384 +task-arena-size-384 = [] +## 512 +task-arena-size-512 = [] +## 640 +task-arena-size-640 = [] +## 768 +task-arena-size-768 = [] +## 1024 +task-arena-size-1024 = [] +## 1280 +task-arena-size-1280 = [] +## 1536 +task-arena-size-1536 = [] +## 2048 +task-arena-size-2048 = [] +## 2560 +task-arena-size-2560 = [] +## 3072 +task-arena-size-3072 = [] +## 4096 (default) +task-arena-size-4096 = [] # Default +## 5120 +task-arena-size-5120 = [] +## 6144 +task-arena-size-6144 = [] +## 8192 +task-arena-size-8192 = [] +## 10240 +task-arena-size-10240 = [] +## 12288 +task-arena-size-12288 = [] +## 16384 +task-arena-size-16384 = [] +## 20480 +task-arena-size-20480 = [] +## 24576 +task-arena-size-24576 = [] +## 32768 +task-arena-size-32768 = [] +## 40960 +task-arena-size-40960 = [] +## 49152 +task-arena-size-49152 = [] +## 65536 +task-arena-size-65536 = [] +## 81920 +task-arena-size-81920 = [] +## 98304 +task-arena-size-98304 = [] +## 131072 +task-arena-size-131072 = [] +## 163840 +task-arena-size-163840 = [] +## 196608 +task-arena-size-196608 = [] +## 262144 +task-arena-size-262144 = [] +## 327680 +task-arena-size-327680 = [] +## 393216 +task-arena-size-393216 = [] +## 524288 +task-arena-size-524288 = [] +## 655360 +task-arena-size-655360 = [] +## 786432 +task-arena-size-786432 = [] +## 1048576 +task-arena-size-1048576 = [] + +# END AUTOGENERATED CONFIG FEATURES + +#!
diff --git a/.stversions/embassy/embassy-executor/build_common~20241114-122315.rs b/.stversions/embassy/embassy-executor/build_common~20241114-122315.rs new file mode 100644 index 0000000..af6bb06 --- /dev/null +++ b/.stversions/embassy/embassy-executor/build_common~20241114-122315.rs @@ -0,0 +1,145 @@ +// NOTE: this file is copy-pasted between several Embassy crates, because there is no +// straightforward way to share this code: +// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path = +// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate +// reside in the crate's directory, +// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because +// symlinks don't work on Windows. + +use std::collections::HashSet; +use std::env; + +/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring +/// them (`cargo:rust-check-cfg=cfg(X)`). +#[derive(Debug)] +pub struct CfgSet { + enabled: HashSet, + declared: HashSet, +} + +impl CfgSet { + pub fn new() -> Self { + Self { + enabled: HashSet::new(), + declared: HashSet::new(), + } + } + + /// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation. + /// + /// All configs that can potentially be enabled should be unconditionally declared using + /// [`Self::declare()`]. + pub fn enable(&mut self, cfg: impl AsRef) { + if self.enabled.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-cfg={}", cfg.as_ref()); + } + } + + pub fn enable_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.enable(cfg.as_ref()); + } + } + + /// Declare a valid config for conditional compilation, without enabling it. + /// + /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. + pub fn declare(&mut self, cfg: impl AsRef) { + if self.declared.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); + } + } + + pub fn declare_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.declare(cfg.as_ref()); + } + } + + pub fn set(&mut self, cfg: impl Into, enable: bool) { + let cfg = cfg.into(); + if enable { + self.enable(cfg.clone()); + } + self.declare(cfg); + } +} + +/// Sets configs that describe the target platform. +pub fn set_target_cfgs(cfgs: &mut CfgSet) { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + cfgs.enable_all(&["cortex_m", "armv6m"]); + } else if target.starts_with("thumbv7m-") { + cfgs.enable_all(&["cortex_m", "armv7m"]); + } else if target.starts_with("thumbv7em-") { + cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]); + } else if target.starts_with("thumbv8m.base") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]); + } else if target.starts_with("thumbv8m.main") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]); + } + cfgs.declare_all(&[ + "cortex_m", + "armv6m", + "armv7m", + "armv7em", + "armv8m", + "armv8m_base", + "armv8m_main", + ]); + + cfgs.set("has_fpu", target.ends_with("-eabihf")); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CompilerDate { + year: u16, + month: u8, + day: u8, +} + +impl CompilerDate { + fn parse(date: &str) -> Option { + let mut parts = date.split('-'); + let year = parts.next()?.parse().ok()?; + let month = parts.next()?.parse().ok()?; + let day = parts.next()?.parse().ok()?; + Some(Self { year, month, day }) + } +} + +impl PartialEq<&str> for CompilerDate { + fn eq(&self, other: &&str) -> bool { + let Some(other) = Self::parse(other) else { + return false; + }; + self.eq(&other) + } +} + +impl PartialOrd<&str> for CompilerDate { + fn partial_cmp(&self, other: &&str) -> Option { + Self::parse(other).map(|other| self.cmp(&other)) + } +} + +pub struct CompilerInfo { + #[allow(unused)] + pub version: rustc_version::Version, + pub channel: rustc_version::Channel, + pub commit_date: Option, +} + +pub fn compiler_info() -> Option { + let Ok(meta) = rustc_version::version_meta() else { + return None; + }; + + Some(CompilerInfo { + version: meta.semver, + channel: meta.channel, + commit_date: meta.commit_date.as_deref().and_then(CompilerDate::parse), + }) +} diff --git a/.stversions/embassy/embassy-executor/build~20241114-122314.rs b/.stversions/embassy/embassy-executor/build~20241114-122314.rs new file mode 100644 index 0000000..c4c86e1 --- /dev/null +++ b/.stversions/embassy/embassy-executor/build~20241114-122314.rs @@ -0,0 +1,110 @@ +use std::collections::HashMap; +use std::fmt::Write; +use std::path::PathBuf; +use std::{env, fs}; + +#[path = "./build_common.rs"] +mod common; + +static CONFIGS: &[(&str, usize)] = &[ + // BEGIN AUTOGENERATED CONFIG FEATURES + // Generated by gen_config.py. DO NOT EDIT. + ("TASK_ARENA_SIZE", 4096), + // END AUTOGENERATED CONFIG FEATURES +]; + +struct ConfigState { + value: usize, + seen_feature: bool, + seen_env: bool, +} + +fn main() { + let crate_name = env::var("CARGO_PKG_NAME") + .unwrap() + .to_ascii_uppercase() + .replace('-', "_"); + + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any + // other file changed. + println!("cargo:rerun-if-changed=build.rs"); + + // Rebuild if config envvar changed. + for (name, _) in CONFIGS { + println!("cargo:rerun-if-env-changed={crate_name}_{name}"); + } + + let mut configs = HashMap::new(); + for (name, default) in CONFIGS { + configs.insert( + *name, + ConfigState { + value: *default, + seen_env: false, + seen_feature: false, + }, + ); + } + + let prefix = format!("{crate_name}_"); + for (var, value) in env::vars() { + if let Some(name) = var.strip_prefix(&prefix) { + let Some(cfg) = configs.get_mut(name) else { + panic!("Unknown env var {name}") + }; + + let Ok(value) = value.parse::() else { + panic!("Invalid value for env var {name}: {value}") + }; + + cfg.value = value; + cfg.seen_env = true; + } + + if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") { + if let Some(i) = feature.rfind('_') { + let name = &feature[..i]; + let value = &feature[i + 1..]; + if let Some(cfg) = configs.get_mut(name) { + let Ok(value) = value.parse::() else { + panic!("Invalid value for feature {name}: {value}") + }; + + // envvars take priority. + if !cfg.seen_env { + if cfg.seen_feature { + panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value); + } + + cfg.value = value; + cfg.seen_feature = true; + } + } + } + } + } + + let mut data = String::new(); + + for (name, cfg) in &configs { + writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap(); + } + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("config.rs").to_string_lossy().to_string(); + fs::write(out_file, data).unwrap(); + + let mut rustc_cfgs = common::CfgSet::new(); + common::set_target_cfgs(&mut rustc_cfgs); + + // Waker API changed on 2024-09-06 + rustc_cfgs.declare("at_least_2024_09_06"); + let Some(compiler) = common::compiler_info() else { + return; + }; + if compiler.channel == rustc_version::Channel::Nightly + && compiler.commit_date.map(|d| d >= "2024-09-06").unwrap_or(false) + { + rustc_cfgs.enable("at_least_2024_09_06"); + } +} diff --git a/.stversions/embassy/embassy-executor/src/lib~20241114-122315.rs b/.stversions/embassy/embassy-executor/src/lib~20241114-122315.rs new file mode 100644 index 0000000..8e07a8b --- /dev/null +++ b/.stversions/embassy/embassy-executor/src/lib~20241114-122315.rs @@ -0,0 +1,151 @@ +#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)] +#![cfg_attr(all(feature = "nightly", not(at_least_2024_09_06)), feature(waker_getters))] +#![allow(clippy::new_without_default)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +pub use embassy_executor_macros::task; + +macro_rules! check_at_most_one { + (@amo [$($feats:literal)*] [] [$($res:tt)*]) => { + #[cfg(any($($res)*))] + compile_error!(concat!("At most one of these features can be enabled at the same time:", $(" `", $feats, "`",)*)); + }; + (@amo $feats:tt [$curr:literal $($rest:literal)*] [$($res:tt)*]) => { + check_at_most_one!(@amo $feats [$($rest)*] [$($res)* $(all(feature=$curr, feature=$rest),)*]); + }; + ($($f:literal),*$(,)?) => { + check_at_most_one!(@amo [$($f)*] [$($f)*] []); + }; +} +check_at_most_one!( + "arch-avr", + "arch-cortex-m", + "arch-riscv32", + "arch-std", + "arch-wasm", + "arch-spin", +); + +#[cfg(feature = "_arch")] +#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")] +#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")] +#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")] +#[cfg_attr(feature = "arch-std", path = "arch/std.rs")] +#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")] +#[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")] +mod arch; + +#[cfg(feature = "_arch")] +#[allow(unused_imports)] // don't warn if the module is empty. +pub use arch::*; + +pub mod raw; + +mod spawner; +pub use spawner::*; + +mod config { + #![allow(unused)] + include!(concat!(env!("OUT_DIR"), "/config.rs")); +} + +/// Implementation details for embassy macros. +/// Do not use. Used for macros and HALs only. Not covered by semver guarantees. +#[doc(hidden)] +#[cfg(not(feature = "nightly"))] +pub mod _export { + use core::alloc::Layout; + use core::cell::{Cell, UnsafeCell}; + use core::future::Future; + use core::mem::MaybeUninit; + use core::ptr::null_mut; + + use critical_section::{CriticalSection, Mutex}; + + use crate::raw::TaskPool; + + struct Arena { + buf: UnsafeCell>, + ptr: Mutex>, + } + + unsafe impl Sync for Arena {} + unsafe impl Send for Arena {} + + impl Arena { + const fn new() -> Self { + Self { + buf: UnsafeCell::new(MaybeUninit::uninit()), + ptr: Mutex::new(Cell::new(null_mut())), + } + } + + fn alloc(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit { + let layout = Layout::new::(); + + let start = self.buf.get().cast::(); + let end = unsafe { start.add(N) }; + + let mut ptr = self.ptr.borrow(cs).get(); + if ptr.is_null() { + ptr = self.buf.get().cast::(); + } + + let bytes_left = (end as usize) - (ptr as usize); + let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize); + + if align_offset + layout.size() > bytes_left { + panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/"); + } + + let res = unsafe { ptr.add(align_offset) }; + let ptr = unsafe { ptr.add(align_offset + layout.size()) }; + + self.ptr.borrow(cs).set(ptr); + + unsafe { &mut *(res as *mut MaybeUninit) } + } + } + + static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new(); + + pub struct TaskPoolRef { + // type-erased `&'static mut TaskPool` + // Needed because statics can't have generics. + ptr: Mutex>, + } + unsafe impl Sync for TaskPoolRef {} + unsafe impl Send for TaskPoolRef {} + + impl TaskPoolRef { + pub const fn new() -> Self { + Self { + ptr: Mutex::new(Cell::new(null_mut())), + } + } + + /// Get the pool for this ref, allocating it from the arena the first time. + /// + /// safety: for a given TaskPoolRef instance, must always call with the exact + /// same generic params. + pub unsafe fn get(&'static self) -> &'static TaskPool { + critical_section::with(|cs| { + let ptr = self.ptr.borrow(cs); + if ptr.get().is_null() { + let pool = ARENA.alloc::>(cs); + pool.write(TaskPool::new()); + ptr.set(pool as *mut _ as _); + } + + unsafe { &*(ptr.get() as *const _) } + }) + } + } +} diff --git a/.stversions/embassy/embassy-executor/src/raw/waker~20241114-122314.rs b/.stversions/embassy/embassy-executor/src/raw/waker~20241114-122314.rs new file mode 100644 index 0000000..30b8cdd --- /dev/null +++ b/.stversions/embassy/embassy-executor/src/raw/waker~20241114-122314.rs @@ -0,0 +1,71 @@ +use core::task::{RawWaker, RawWakerVTable, Waker}; + +use super::{wake_task, TaskHeader, TaskRef}; + +static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop); + +unsafe fn clone(p: *const ()) -> RawWaker { + RawWaker::new(p, &VTABLE) +} + +unsafe fn wake(p: *const ()) { + wake_task(TaskRef::from_ptr(p as *const TaskHeader)) +} + +unsafe fn drop(_: *const ()) { + // nop +} + +pub(crate) unsafe fn from_task(p: TaskRef) -> Waker { + Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE)) +} + +/// Get a task pointer from a waker. +/// +/// This can be used as an optimization in wait queues to store task pointers +/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps +/// avoid dynamic dispatch. +/// +/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task). +/// +/// # Panics +/// +/// Panics if the waker is not created by the Embassy executor. +pub fn task_from_waker(waker: &Waker) -> TaskRef { + let (vtable, data) = { + #[cfg(not(feature = "nightly"))] + { + struct WakerHack { + data: *const (), + vtable: &'static RawWakerVTable, + } + + // safety: OK because WakerHack has the same layout as Waker. + // This is not really guaranteed because the structs are `repr(Rust)`, it is + // indeed the case in the current implementation. + // TODO use waker_getters when stable. https://github.com/rust-lang/rust/issues/96992 + let hack: &WakerHack = unsafe { core::mem::transmute(waker) }; + (hack.vtable, hack.data) + } + + #[cfg(feature = "nightly")] + { + #[cfg(not(at_least_2024_09_06))] + { + let raw_waker = waker.as_raw(); + (raw_waker.vtable(), raw_waker.data()) + } + + #[cfg(at_least_2024_09_06)] + { + (waker.vtable(), waker.data()) + } + } + }; + + if vtable != &VTABLE { + panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.") + } + // safety: our wakers are always created with `TaskRef::as_ptr` + unsafe { TaskRef::from_ptr(data as *const TaskHeader) } +} diff --git a/.stversions/embassy/embassy-nrf/src/chips/nrf5340_app~20241114-122314.rs b/.stversions/embassy/embassy-nrf/src/chips/nrf5340_app~20241114-122314.rs new file mode 100644 index 0000000..43588ee --- /dev/null +++ b/.stversions/embassy/embassy-nrf/src/chips/nrf5340_app~20241114-122314.rs @@ -0,0 +1,580 @@ +/// Peripheral Access Crate +#[allow(unused_imports)] +#[rustfmt::skip] +pub mod pac { + // The nRF5340 has a secure and non-secure (NS) mode. + // To avoid cfg spam, we remove _ns or _s suffixes here. + + #[cfg(feature="rt")] + pub use nrf_pac::NVIC_PRIO_BITS; + pub use nrf_pac::{common, shared}; + + #[cfg(feature="rt")] + #[doc(no_inline)] + pub use nrf_pac::interrupt; + + #[doc(no_inline)] + pub use nrf_pac::{ + Interrupt, + + cache_s as cache, + cachedata_s as cachedata, + cacheinfo_s as cacheinfo, + clock_ns as clock, + comp_ns as comp, + cryptocell_s as cryptocell, + cti_s as cti, + ctrlap_ns as ctrlap, + dcnf_ns as dcnf, + dppic_ns as dppic, + egu_ns as egu, + ficr_s as ficr, + fpu_ns as fpu, + gpiote_s as gpiote, + i2s_ns as i2s, + ipc_ns as ipc, + kmu_ns as kmu, + lpcomp_ns as lpcomp, + mutex_ns as mutex, + nfct_ns as nfct, + nvmc_ns as nvmc, + oscillators_ns as oscillators, + gpio_ns as gpio, + pdm_ns as pdm, + power_ns as power, + pwm_ns as pwm, + qdec_ns as qdec, + qspi_ns as qspi, + regulators_ns as regulators, + reset_ns as reset, + rtc_ns as rtc, + saadc_ns as saadc, + spim_ns as spim, + spis_ns as spis, + spu_s as spu, + tad_s as tad, + timer_ns as timer, + twim_ns as twim, + twis_ns as twis, + uarte_ns as uarte, + uicr_s as uicr, + usbd_ns as usbd, + usbregulator_ns as usbregulator, + vmc_ns as vmc, + wdt_ns as wdt, + }; + + /// Non-Secure mode (NS) peripherals + pub mod ns { + #[cfg(feature = "nrf5340-app-ns")] + #[doc(no_inline)] + pub use nrf_pac::{ + CLOCK_NS as CLOCK, + COMP_NS as COMP, + CTRLAP_NS as CTRLAP, + DCNF_NS as DCNF, + DPPIC_NS as DPPIC, + EGU0_NS as EGU0, + EGU1_NS as EGU1, + EGU2_NS as EGU2, + EGU3_NS as EGU3, + EGU4_NS as EGU4, + EGU5_NS as EGU5, + FPU_NS as FPU, + GPIOTE1_NS as GPIOTE1, + I2S0_NS as I2S0, + IPC_NS as IPC, + KMU_NS as KMU, + LPCOMP_NS as LPCOMP, + MUTEX_NS as MUTEX, + NFCT_NS as NFCT, + NVMC_NS as NVMC, + OSCILLATORS_NS as OSCILLATORS, + P0_NS as P0, + P1_NS as P1, + PDM0_NS as PDM0, + POWER_NS as POWER, + PWM0_NS as PWM0, + PWM1_NS as PWM1, + PWM2_NS as PWM2, + PWM3_NS as PWM3, + QDEC0_NS as QDEC0, + QDEC1_NS as QDEC1, + QSPI_NS as QSPI, + REGULATORS_NS as REGULATORS, + RESET_NS as RESET, + RTC0_NS as RTC0, + RTC1_NS as RTC1, + SAADC_NS as SAADC, + SPIM0_NS as SPIM0, + SPIM1_NS as SPIM1, + SPIM2_NS as SPIM2, + SPIM3_NS as SPIM3, + SPIM4_NS as SPIM4, + SPIS0_NS as SPIS0, + SPIS1_NS as SPIS1, + SPIS2_NS as SPIS2, + SPIS3_NS as SPIS3, + TIMER0_NS as TIMER0, + TIMER1_NS as TIMER1, + TIMER2_NS as TIMER2, + TWIM0_NS as TWIM0, + TWIM1_NS as TWIM1, + TWIM2_NS as TWIM2, + TWIM3_NS as TWIM3, + TWIS0_NS as TWIS0, + TWIS1_NS as TWIS1, + TWIS2_NS as TWIS2, + TWIS3_NS as TWIS3, + UARTE0_NS as UARTE0, + UARTE1_NS as UARTE1, + UARTE2_NS as UARTE2, + UARTE3_NS as UARTE3, + USBD_NS as USBD, + USBREGULATOR_NS as USBREGULATOR, + VMC_NS as VMC, + WDT0_NS as WDT0, + WDT1_NS as WDT1, + }; + } + + /// Secure mode (S) peripherals + pub mod s { + #[cfg(feature = "nrf5340-app-s")] + #[doc(no_inline)] + pub use nrf_pac::{ + CACHEDATA_S as CACHEDATA, + CACHEINFO_S as CACHEINFO, + CACHE_S as CACHE, + CLOCK_S as CLOCK, + COMP_S as COMP, + CRYPTOCELL_S as CRYPTOCELL, + CTI_S as CTI, + CTRLAP_S as CTRLAP, + DCNF_S as DCNF, + DPPIC_S as DPPIC, + EGU0_S as EGU0, + EGU1_S as EGU1, + EGU2_S as EGU2, + EGU3_S as EGU3, + EGU4_S as EGU4, + EGU5_S as EGU5, + FICR_S as FICR, + FPU_S as FPU, + GPIOTE0_S as GPIOTE0, + I2S0_S as I2S0, + IPC_S as IPC, + KMU_S as KMU, + LPCOMP_S as LPCOMP, + MUTEX_S as MUTEX, + NFCT_S as NFCT, + NVMC_S as NVMC, + OSCILLATORS_S as OSCILLATORS, + P0_S as P0, + P1_S as P1, + PDM0_S as PDM0, + POWER_S as POWER, + PWM0_S as PWM0, + PWM1_S as PWM1, + PWM2_S as PWM2, + PWM3_S as PWM3, + QDEC0_S as QDEC0, + QDEC1_S as QDEC1, + QSPI_S as QSPI, + REGULATORS_S as REGULATORS, + RESET_S as RESET, + RTC0_S as RTC0, + RTC1_S as RTC1, + SAADC_S as SAADC, + SPIM0_S as SPIM0, + SPIM1_S as SPIM1, + SPIM2_S as SPIM2, + SPIM3_S as SPIM3, + SPIM4_S as SPIM4, + SPIS0_S as SPIS0, + SPIS1_S as SPIS1, + SPIS2_S as SPIS2, + SPIS3_S as SPIS3, + SPU_S as SPU, + TAD_S as TAD, + TIMER0_S as TIMER0, + TIMER1_S as TIMER1, + TIMER2_S as TIMER2, + TWIM0_S as TWIM0, + TWIM1_S as TWIM1, + TWIM2_S as TWIM2, + TWIM3_S as TWIM3, + TWIS0_S as TWIS0, + TWIS1_S as TWIS1, + TWIS2_S as TWIS2, + TWIS3_S as TWIS3, + UARTE0_S as UARTE0, + UARTE1_S as UARTE1, + UARTE2_S as UARTE2, + UARTE3_S as UARTE3, + UICR_S as UICR, + USBD_S as USBD, + USBREGULATOR_S as USBREGULATOR, + VMC_S as VMC, + WDT0_S as WDT0, + WDT1_S as WDT1, + }; + } + + #[cfg(feature = "_ns")] + pub use ns::*; + #[cfg(feature = "_s")] + pub use s::*; +} + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; +pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; + +pub const FLASH_SIZE: usize = 1024 * 1024; + +embassy_hal_internal::peripherals! { + // USB + USBD, + + // RTC + RTC0, + RTC1, + + // WDT + WDT, + + // NVMC + NVMC, + + // UARTE, TWI & SPI + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, + + // SAADC + SAADC, + + // PWM + PWM0, + PWM1, + PWM2, + PWM3, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + + // QSPI + QSPI, + + // PDM + PDM0, + + // QDEC + QDEC0, + QDEC1, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + PPI_CH16, + PPI_CH17, + PPI_CH18, + PPI_CH19, + PPI_CH20, + PPI_CH21, + PPI_CH22, + PPI_CH23, + PPI_CH24, + PPI_CH25, + PPI_CH26, + PPI_CH27, + PPI_CH28, + PPI_CH29, + PPI_CH30, + PPI_CH31, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + #[cfg(feature = "nfc-pins-as-gpio")] + P0_02, + #[cfg(feature = "nfc-pins-as-gpio")] + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + P0_09, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // GPIO port 1 + P1_00, + P1_01, + P1_02, + P1_03, + P1_04, + P1_05, + P1_06, + P1_07, + P1_08, + P1_09, + P1_10, + P1_11, + P1_12, + P1_13, + P1_14, + P1_15, + + // EGU + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, +} + +impl_usb!(USBD, USBD, USBD); + +impl_uarte!(SERIAL0, UARTE0, SERIAL0); +impl_uarte!(SERIAL1, UARTE1, SERIAL1); +impl_uarte!(SERIAL2, UARTE2, SERIAL2); +impl_uarte!(SERIAL3, UARTE3, SERIAL3); + +impl_spim!(SERIAL0, SPIM0, SERIAL0); +impl_spim!(SERIAL1, SPIM1, SERIAL1); +impl_spim!(SERIAL2, SPIM2, SERIAL2); +impl_spim!(SERIAL3, SPIM3, SERIAL3); + +impl_spis!(SERIAL0, SPIS0, SERIAL0); +impl_spis!(SERIAL1, SPIS1, SERIAL1); +impl_spis!(SERIAL2, SPIS2, SERIAL2); +impl_spis!(SERIAL3, SPIS3, SERIAL3); + +impl_twim!(SERIAL0, TWIM0, SERIAL0); +impl_twim!(SERIAL1, TWIM1, SERIAL1); +impl_twim!(SERIAL2, TWIM2, SERIAL2); +impl_twim!(SERIAL3, TWIM3, SERIAL3); + +impl_twis!(SERIAL0, TWIS0, SERIAL0); +impl_twis!(SERIAL1, TWIS1, SERIAL1); +impl_twis!(SERIAL2, TWIS2, SERIAL2); +impl_twis!(SERIAL3, TWIS3, SERIAL3); + +impl_pwm!(PWM0, PWM0, PWM0); +impl_pwm!(PWM1, PWM1, PWM1); +impl_pwm!(PWM2, PWM2, PWM2); +impl_pwm!(PWM3, PWM3, PWM3); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); + +impl_qspi!(QSPI, QSPI, QSPI); + +impl_pdm!(PDM0, PDM0, PDM0); + +impl_qdec!(QDEC0, QDEC0, QDEC0); +impl_qdec!(QDEC1, QDEC1, QDEC1); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +#[cfg(feature = "nfc-pins-as-gpio")] +impl_pin!(P0_02, 0, 2); +#[cfg(feature = "nfc-pins-as-gpio")] +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +impl_pin!(P0_09, 0, 9); +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_pin!(P1_00, 1, 0); +impl_pin!(P1_01, 1, 1); +impl_pin!(P1_02, 1, 2); +impl_pin!(P1_03, 1, 3); +impl_pin!(P1_04, 1, 4); +impl_pin!(P1_05, 1, 5); +impl_pin!(P1_06, 1, 6); +impl_pin!(P1_07, 1, 7); +impl_pin!(P1_08, 1, 8); +impl_pin!(P1_09, 1, 9); +impl_pin!(P1_10, 1, 10); +impl_pin!(P1_11, 1, 11); +impl_pin!(P1_12, 1, 12); +impl_pin!(P1_13, 1, 13); +impl_pin!(P1_14, 1, 14); +impl_pin!(P1_15, 1, 15); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH10, 10 => configurable); +impl_ppi_channel!(PPI_CH11, 11 => configurable); +impl_ppi_channel!(PPI_CH12, 12 => configurable); +impl_ppi_channel!(PPI_CH13, 13 => configurable); +impl_ppi_channel!(PPI_CH14, 14 => configurable); +impl_ppi_channel!(PPI_CH15, 15 => configurable); +impl_ppi_channel!(PPI_CH16, 16 => configurable); +impl_ppi_channel!(PPI_CH17, 17 => configurable); +impl_ppi_channel!(PPI_CH18, 18 => configurable); +impl_ppi_channel!(PPI_CH19, 19 => configurable); +impl_ppi_channel!(PPI_CH20, 20 => configurable); +impl_ppi_channel!(PPI_CH21, 21 => configurable); +impl_ppi_channel!(PPI_CH22, 22 => configurable); +impl_ppi_channel!(PPI_CH23, 23 => configurable); +impl_ppi_channel!(PPI_CH24, 24 => configurable); +impl_ppi_channel!(PPI_CH25, 25 => configurable); +impl_ppi_channel!(PPI_CH26, 26 => configurable); +impl_ppi_channel!(PPI_CH27, 27 => configurable); +impl_ppi_channel!(PPI_CH28, 28 => configurable); +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, 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); + +impl_egu!(EGU0, EGU0, EGU0); +impl_egu!(EGU1, EGU1, EGU1); +impl_egu!(EGU2, EGU2, EGU2); +impl_egu!(EGU3, EGU3, EGU3); +impl_egu!(EGU4, EGU4, EGU4); +impl_egu!(EGU5, EGU5, EGU5); + +embassy_hal_internal::interrupt_mod!( + FPU, + CACHE, + SPU, + CLOCK_POWER, + SERIAL0, + SERIAL1, + SPIM4, + SERIAL2, + SERIAL3, + GPIOTE0, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + RTC1, + WDT0, + WDT1, + COMP_LPCOMP, + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, + PWM0, + PWM1, + PWM2, + PWM3, + PDM0, + I2S0, + IPC, + QSPI, + NFCT, + GPIOTE1, + QDEC0, + QDEC1, + USBD, + USBREGULATOR, + KMU, + CRYPTOCELL, +); diff --git a/.stversions/embassy/embassy-nrf/src/lib~20241114-122314.rs b/.stversions/embassy/embassy-nrf/src/lib~20241114-122314.rs new file mode 100644 index 0000000..03d3ca5 --- /dev/null +++ b/.stversions/embassy/embassy-nrf/src/lib~20241114-122314.rs @@ -0,0 +1,673 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![cfg_attr( + docsrs, + doc = "

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

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

\n\n" +)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +#[cfg(not(any( + feature = "_nrf51", + feature = "nrf52805", + feature = "nrf52810", + feature = "nrf52811", + feature = "nrf52820", + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", + feature = "nrf5340-app-s", + feature = "nrf5340-app-ns", + feature = "nrf5340-net", + feature = "nrf9160-s", + feature = "nrf9160-ns", + feature = "nrf9120-s", + feature = "nrf9120-ns", + feature = "nrf9151-s", + feature = "nrf9151-ns", + feature = "nrf9161-s", + feature = "nrf9161-ns", +)))] +compile_error!( + "No chip feature activated. You must activate exactly one of the following features: + nrf51, + nrf52805, + nrf52810, + nrf52811, + nrf52820, + nrf52832, + nrf52833, + nrf52840, + nrf5340-app-s, + nrf5340-app-ns, + nrf5340-net, + nrf9160-s, + nrf9160-ns, + nrf9120-s, + nrf9120-ns, + nrf9151-s, + nrf9151-ns, + nrf9161-s, + nrf9161-ns, + " +); + +#[cfg(all(feature = "reset-pin-as-gpio", not(feature = "_nrf52")))] +compile_error!("feature `reset-pin-as-gpio` is only valid for nRF52 series chips."); + +#[cfg(all(feature = "nfc-pins-as-gpio", not(any(feature = "_nrf52", feature = "_nrf5340-app"))))] +compile_error!("feature `nfc-pins-as-gpio` is only valid for nRF52, or nRF53's application core."); + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; +pub(crate) mod util; + +#[cfg(feature = "_time-driver")] +mod time_driver; + +#[cfg(not(feature = "_nrf51"))] +pub mod buffered_uarte; +pub mod gpio; +#[cfg(feature = "gpiote")] +pub mod gpiote; + +// TODO: tested on other chips +#[cfg(not(any(feature = "_nrf91", feature = "_nrf5340-app")))] +pub mod radio; + +#[cfg(not(feature = "_nrf51"))] +pub mod egu; +#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] +pub mod i2s; +pub mod nvmc; +#[cfg(any( + feature = "nrf52810", + feature = "nrf52811", + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-app", + feature = "_nrf91", +))] +pub mod pdm; +pub mod ppi; +#[cfg(not(any( + feature = "_nrf51", + feature = "nrf52805", + feature = "nrf52820", + feature = "_nrf5340-net" +)))] +pub mod pwm; +#[cfg(not(any(feature = "_nrf51", feature = "_nrf91", feature = "_nrf5340-net")))] +pub mod qdec; +#[cfg(any(feature = "nrf52840", feature = "_nrf5340-app"))] +pub mod qspi; +#[cfg(not(any(feature = "_nrf5340-app", feature = "_nrf91")))] +pub mod rng; +#[cfg(not(any(feature = "_nrf51", feature = "nrf52820", feature = "_nrf5340-net")))] +pub mod saadc; +#[cfg(not(feature = "_nrf51"))] +pub mod spim; +#[cfg(not(feature = "_nrf51"))] +pub mod spis; +#[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] +pub mod temp; +pub mod timer; +#[cfg(not(feature = "_nrf51"))] +pub mod twim; +#[cfg(not(feature = "_nrf51"))] +pub mod twis; +#[cfg(not(feature = "_nrf51"))] +pub mod uarte; +#[cfg(any( + feature = "_nrf5340-app", + feature = "nrf52820", + feature = "nrf52833", + feature = "nrf52840" +))] +pub mod usb; +#[cfg(not(feature = "_nrf5340"))] +pub mod wdt; + +// This mod MUST go last, so that it sees all the `impl_foo!` macros +#[cfg_attr(feature = "_nrf51", path = "chips/nrf51.rs")] +#[cfg_attr(feature = "nrf52805", path = "chips/nrf52805.rs")] +#[cfg_attr(feature = "nrf52810", path = "chips/nrf52810.rs")] +#[cfg_attr(feature = "nrf52811", path = "chips/nrf52811.rs")] +#[cfg_attr(feature = "nrf52820", path = "chips/nrf52820.rs")] +#[cfg_attr(feature = "nrf52832", path = "chips/nrf52832.rs")] +#[cfg_attr(feature = "nrf52833", path = "chips/nrf52833.rs")] +#[cfg_attr(feature = "nrf52840", path = "chips/nrf52840.rs")] +#[cfg_attr(feature = "_nrf5340-app", path = "chips/nrf5340_app.rs")] +#[cfg_attr(feature = "_nrf5340-net", path = "chips/nrf5340_net.rs")] +#[cfg_attr(feature = "_nrf9160", path = "chips/nrf9160.rs")] +#[cfg_attr(feature = "_nrf9120", path = "chips/nrf9120.rs")] +mod chip; + +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +/// +/// Example of how to bind one interrupt: +/// +/// ```rust,ignore +/// use embassy_nrf::{bind_interrupts, spim, peripherals}; +/// +/// bind_interrupts!(struct Irqs { +/// SPIM3 => spim::InterruptHandler; +/// }); +/// ``` +/// +/// Example of how to bind multiple interrupts in a single macro invocation: +/// +/// ```rust,ignore +/// use embassy_nrf::{bind_interrupts, spim, twim, peripherals}; +/// +/// bind_interrupts!(struct Irqs { +/// SPIM3 => spim::InterruptHandler; +/// SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => twim::InterruptHandler; +/// }); +/// ``` + +// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + } + )* + }; + } + +// Reexports + +#[cfg(feature = "unstable-pac")] +pub use chip::pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use chip::pac; +pub use chip::{peripherals, Peripherals, EASY_DMA_SIZE}; +pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; + +pub use crate::chip::interrupt; +#[cfg(feature = "rt")] +pub use crate::pac::NVIC_PRIO_BITS; + +pub mod config { + //! Configuration options used when initializing the HAL. + + /// High frequency clock source. + pub enum HfclkSource { + /// Internal source + Internal, + /// External source from xtal. + ExternalXtal, + } + + /// Low frequency clock source + pub enum LfclkSource { + /// Internal RC oscillator + InternalRC, + /// Synthesized from the high frequency clock source. + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + Synthesized, + /// External source from xtal. + ExternalXtal, + /// External source from xtal with low swing applied. + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + ExternalLowSwing, + /// External source from xtal with full swing applied. + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + ExternalFullSwing, + } + + /// SWD access port protection setting. + #[non_exhaustive] + pub enum Debug { + /// Debugging is allowed (APPROTECT is disabled). Default. + Allowed, + /// Debugging is not allowed (APPROTECT is enabled). + Disallowed, + /// APPROTECT is not configured (neither to enable it or disable it). + /// This can be useful if you're already doing it by other means and + /// you don't want embassy-nrf to touch UICR. + NotConfigured, + } + + /// Settings for enabling the built in DCDC converters. + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + pub struct DcdcConfig { + /// Config for the first stage DCDC (VDDH -> VDD), if disabled LDO will be used. + #[cfg(feature = "nrf52840")] + pub reg0: bool, + /// Configure the voltage of the first stage DCDC. It is stored in non-volatile memory (UICR.REGOUT0 register); pass None to not touch it. + #[cfg(feature = "nrf52840")] + pub reg0_voltage: Option, + /// Config for the second stage DCDC (VDD -> DEC4), if disabled LDO will be used. + pub reg1: bool, + } + + /// Output voltage setting for REG0 regulator stage. + #[cfg(feature = "nrf52840")] + pub enum Reg0Voltage { + /// 1.8 V + _1V8 = 0, + /// 2.1 V + _2V1 = 1, + /// 2.4 V + _2V4 = 2, + /// 2.7 V + _2V7 = 3, + /// 3.0 V + _3V0 = 4, + /// 3.3 V + _3v3 = 5, + //ERASED = 7, means 1.8V + } + + /// Settings for enabling the built in DCDC converters. + #[cfg(feature = "_nrf5340-app")] + pub struct DcdcConfig { + /// Config for the high voltage stage, if disabled LDO will be used. + pub regh: bool, + /// Config for the main rail, if disabled LDO will be used. + pub regmain: bool, + /// Config for the radio rail, if disabled LDO will be used. + pub regradio: bool, + } + + /// Settings for enabling the built in DCDC converter. + #[cfg(feature = "_nrf91")] + pub struct DcdcConfig { + /// Config for the main rail, if disabled LDO will be used. + pub regmain: bool, + } + + /// Configuration for peripherals. Default configuration should work on any nRF chip. + #[non_exhaustive] + pub struct Config { + /// High frequency clock source. + pub hfclk_source: HfclkSource, + /// Low frequency clock source. + pub lfclk_source: LfclkSource, + #[cfg(not(feature = "_nrf5340-net"))] + /// DCDC configuration. + pub dcdc: DcdcConfig, + /// GPIOTE interrupt priority. Should be lower priority than softdevice if used. + #[cfg(feature = "gpiote")] + pub gpiote_interrupt_priority: crate::interrupt::Priority, + /// Time driver interrupt priority. Should be lower priority than softdevice if used. + #[cfg(feature = "_time-driver")] + pub time_interrupt_priority: crate::interrupt::Priority, + /// Enable or disable the debug port. + pub debug: Debug, + } + + impl Default for Config { + fn default() -> Self { + Self { + // There are hobby nrf52 boards out there without external XTALs... + // Default everything to internal so it Just Works. User can enable external + // xtals if they know they have them. + hfclk_source: HfclkSource::Internal, + lfclk_source: LfclkSource::InternalRC, + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + dcdc: DcdcConfig { + #[cfg(feature = "nrf52840")] + reg0: false, + #[cfg(feature = "nrf52840")] + reg0_voltage: None, + reg1: false, + }, + #[cfg(feature = "_nrf5340-app")] + dcdc: DcdcConfig { + regh: false, + regmain: false, + regradio: false, + }, + #[cfg(feature = "_nrf91")] + dcdc: DcdcConfig { regmain: false }, + #[cfg(feature = "gpiote")] + gpiote_interrupt_priority: crate::interrupt::Priority::P0, + #[cfg(feature = "_time-driver")] + time_interrupt_priority: crate::interrupt::Priority::P0, + + // In NS mode, default to NotConfigured, assuming the S firmware will do it. + #[cfg(feature = "_ns")] + debug: Debug::NotConfigured, + #[cfg(not(feature = "_ns"))] + debug: Debug::Allowed, + } + } + } +} + +#[cfg(feature = "_nrf91")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32; + pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF802C as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; +} + +#[cfg(feature = "_nrf5340-app")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32; + pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF801C as *mut u32; + pub const UICR_NFCPINS: *mut u32 = 0x00FF8028 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x50FA50FA; +} + +#[cfg(feature = "_nrf5340-net")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x01FF8000 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x50FA50FA; +} + +#[cfg(feature = "_nrf52")] +#[allow(unused)] +mod consts { + pub const UICR_PSELRESET1: *mut u32 = 0x10001200 as *mut u32; + pub const UICR_PSELRESET2: *mut u32 = 0x10001204 as *mut u32; + pub const UICR_NFCPINS: *mut u32 = 0x1000120C as *mut u32; + pub const UICR_APPROTECT: *mut u32 = 0x10001208 as *mut u32; + pub const UICR_REGOUT0: *mut u32 = 0x10001304 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x0000_005a; +} + +#[cfg(not(feature = "_nrf51"))] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum WriteResult { + /// Word was written successfully, needs reset. + Written, + /// Word was already set to the value we wanted to write, nothing was done. + Noop, + /// Word is already set to something else, we couldn't write the desired value. + Failed, +} + +#[cfg(not(feature = "_nrf51"))] +unsafe fn uicr_write(address: *mut u32, value: u32) -> WriteResult { + uicr_write_masked(address, value, 0xFFFF_FFFF) +} + +#[cfg(not(feature = "_nrf51"))] +unsafe fn uicr_write_masked(address: *mut u32, value: u32, mask: u32) -> WriteResult { + let curr_val = address.read_volatile(); + if curr_val & mask == value & mask { + return WriteResult::Noop; + } + + // We can only change `1` bits to `0` bits. + if curr_val & value & mask != value & mask { + return WriteResult::Failed; + } + + let nvmc = pac::NVMC; + nvmc.config().write(|w| w.set_wen(pac::nvmc::vals::Wen::WEN)); + while !nvmc.ready().read().ready() {} + address.write_volatile(value | !mask); + while !nvmc.ready().read().ready() {} + nvmc.config().write(|_| {}); + while !nvmc.ready().read().ready() {} + + WriteResult::Written +} + +/// Initialize the `embassy-nrf` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once at startup, otherwise it panics. +pub fn init(config: config::Config) -> Peripherals { + // Do this first, so that it panics if user is calling `init` a second time + // before doing anything important. + let peripherals = Peripherals::take(); + + #[allow(unused_mut)] + let mut needs_reset = false; + + // Setup debug protection. + #[cfg(not(feature = "_nrf51"))] + match config.debug { + config::Debug::Allowed => { + #[cfg(feature = "_nrf52")] + unsafe { + let variant = (0x1000_0104 as *mut u32).read_volatile(); + // Get the letter for the build code (b'A' .. b'F') + let build_code = (variant >> 8) as u8; + + if build_code >= chip::APPROTECT_MIN_BUILD_CODE { + // Chips with a certain chip type-specific build code or higher have an + // improved APPROTECT ("hardware and software controlled access port protection") + // which needs explicit action by the firmware to keep it unlocked + // See https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/working-with-the-nrf52-series-improved-approtect + + // UICR.APPROTECT = SwDisabled + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; + // APPROTECT.DISABLE = SwDisabled + (0x4000_0558 as *mut u32).write_volatile(consts::APPROTECT_DISABLED); + } else { + // nothing to do on older chips, debug is allowed by default. + } + } + + #[cfg(feature = "_nrf5340")] + unsafe { + let p = pac::CTRLAP; + + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; + p.approtect().disable().write_value(consts::APPROTECT_DISABLED); + + #[cfg(feature = "_nrf5340-app")] + { + let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; + p.secureapprotect().disable().write_value(consts::APPROTECT_DISABLED); + } + } + + // nothing to do on the nrf9160, debug is allowed by default. + } + config::Debug::Disallowed => unsafe { + // UICR.APPROTECT = Enabled + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_ENABLED); + needs_reset |= res == WriteResult::Written; + #[cfg(any(feature = "_nrf5340-app", feature = "_nrf91"))] + { + let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_ENABLED); + needs_reset |= res == WriteResult::Written; + } + }, + config::Debug::NotConfigured => {} + } + + #[cfg(feature = "_nrf52")] + unsafe { + let value = if cfg!(feature = "reset-pin-as-gpio") { + !0 + } else { + chip::RESET_PIN + }; + let res1 = uicr_write(consts::UICR_PSELRESET1, value); + let res2 = uicr_write(consts::UICR_PSELRESET2, value); + needs_reset |= res1 == WriteResult::Written || res2 == WriteResult::Written; + if res1 == WriteResult::Failed || res2 == WriteResult::Failed { + #[cfg(not(feature = "reset-pin-as-gpio"))] + warn!( + "You have requested enabling chip reset functionality on the reset pin, by not enabling the Cargo feature `reset-pin-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + #[cfg(feature = "reset-pin-as-gpio")] + warn!( + "You have requested using the reset pin as GPIO, by enabling the Cargo feature `reset-pin-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + } + } + + #[cfg(any(feature = "_nrf52", feature = "_nrf5340-app"))] + unsafe { + let value = if cfg!(feature = "nfc-pins-as-gpio") { 0 } else { 1 }; + let res = uicr_write_masked(consts::UICR_NFCPINS, value, 1); + needs_reset |= res == WriteResult::Written; + if res == WriteResult::Failed { + // with nfc-pins-as-gpio, this can never fail because we're writing all zero bits. + #[cfg(not(feature = "nfc-pins-as-gpio"))] + warn!( + "You have requested to use P0.09 and P0.10 pins for NFC, by not enabling the Cargo feature `nfc-pins-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + } + } + + #[cfg(feature = "nrf52840")] + unsafe { + if let Some(value) = config.dcdc.reg0_voltage { + let value = value as u32; + let res = uicr_write_masked(consts::UICR_REGOUT0, value, 0b00000000_00000000_00000000_00000111); + needs_reset |= res == WriteResult::Written; + if res == WriteResult::Failed { + warn!( + "Failed to set regulator voltage, as UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + } + } + } + + if needs_reset { + cortex_m::peripheral::SCB::sys_reset(); + } + + let r = pac::CLOCK; + + // Start HFCLK. + match config.hfclk_source { + config::HfclkSource::Internal => {} + config::HfclkSource::ExternalXtal => { + // Datasheet says this is likely to take 0.36ms + r.events_hfclkstarted().write_value(0); + r.tasks_hfclkstart().write_value(1); + while r.events_hfclkstarted().read() == 0 {} + } + } + + // Configure LFCLK. + #[cfg(not(any(feature = "_nrf51", feature = "_nrf5340", feature = "_nrf91")))] + match config.lfclk_source { + config::LfclkSource::InternalRC => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::RC)), + config::LfclkSource::Synthesized => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::SYNTH)), + config::LfclkSource::ExternalXtal => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::XTAL)), + config::LfclkSource::ExternalLowSwing => r.lfclksrc().write(|w| { + w.set_src(pac::clock::vals::Lfclksrc::XTAL); + w.set_external(true); + w.set_bypass(false); + }), + config::LfclkSource::ExternalFullSwing => r.lfclksrc().write(|w| { + w.set_src(pac::clock::vals::Lfclksrc::XTAL); + w.set_external(true); + w.set_bypass(true); + }), + } + #[cfg(feature = "_nrf91")] + match config.lfclk_source { + config::LfclkSource::InternalRC => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFRC)), + config::LfclkSource::ExternalXtal => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFXO)), + } + + // Start LFCLK. + // Datasheet says this could take 100us from synth source + // 600us from rc source, 0.25s from an external source. + r.events_lfclkstarted().write_value(0); + r.tasks_lfclkstart().write_value(1); + while r.events_lfclkstarted().read() == 0 {} + + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + { + // Setup DCDCs. + #[cfg(feature = "nrf52840")] + if config.dcdc.reg0 { + pac::POWER.dcdcen0().write(|w| w.set_dcdcen(true)); + } + if config.dcdc.reg1 { + pac::POWER.dcdcen().write(|w| w.set_dcdcen(true)); + } + } + #[cfg(feature = "_nrf91")] + { + // Setup DCDC. + if config.dcdc.regmain { + pac::REGULATORS.dcdcen().write(|w| w.set_dcdcen(true)); + } + } + #[cfg(feature = "_nrf5340-app")] + { + // Setup DCDC. + let reg = pac::REGULATORS; + if config.dcdc.regh { + reg.vregh().dcdcen().write(|w| w.set_dcdcen(true)); + } + if config.dcdc.regmain { + reg.vregmain().dcdcen().write(|w| w.set_dcdcen(true)); + } + if config.dcdc.regradio { + reg.vregradio().dcdcen().write(|w| w.set_dcdcen(true)); + } + } + + // Init GPIOTE + #[cfg(feature = "gpiote")] + gpiote::init(config.gpiote_interrupt_priority); + + // init RTC time driver + #[cfg(feature = "_time-driver")] + time_driver::init(config.time_interrupt_priority); + + // Disable UARTE (enabled by default for some reason) + #[cfg(feature = "_nrf91")] + { + use pac::uarte::vals::Enable; + pac::UARTE0.enable().write(|w| w.set_enable(Enable::DISABLED)); + pac::UARTE1.enable().write(|w| w.set_enable(Enable::DISABLED)); + } + + peripherals +} diff --git a/.stversions/embassy/embassy-rp/Cargo~20241114-122314.toml b/.stversions/embassy/embassy-rp/Cargo~20241114-122314.toml new file mode 100644 index 0000000..4616116 --- /dev/null +++ b/.stversions/embassy/embassy-rp/Cargo~20241114-122314.toml @@ -0,0 +1,151 @@ +[package] +name = "embassy-rp" +version = "0.2.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Embassy Hardware Abstraction Layer (HAL) for the Raspberry Pi RP2040 microcontroller" +keywords = ["embedded", "async", "raspberry-pi", "rp2040", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-rp" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-rp-v$VERSION/embassy-rp/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-rp/src/" +features = ["defmt", "unstable-pac", "time-driver"] +flavors = [ + { name = "rp2040", target = "thumbv6m-none-eabi", features = ["rp2040"] }, + { name = "rp235xa", target = "thumbv8m.main-none-eabihf", features = ["rp235xa"] }, + { name = "rp235xb", target = "thumbv8m.main-none-eabihf", features = ["rp235xb"] }, +] + +[package.metadata.docs.rs] +features = ["defmt", "unstable-pac", "time-driver"] + +[features] +default = [ "rt" ] +## Enable the rt feature of [`rp-pac`](https://docs.rs/rp-pac). This brings in the [`cortex-m-rt`](https://docs.rs/cortex-m-rt) crate, which adds startup code and minimal runtime initialization. +rt = [ "rp-pac/rt" ] + +## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers. +defmt = ["dep:defmt", "embassy-usb-driver/defmt", "embassy-hal-internal/defmt"] + +## Configure the [`critical-section`](https://docs.rs/critical-section) crate to use an implementation that is safe for multicore use on rp2040. +critical-section-impl = ["critical-section/restore-state-u8"] + +## Reexport the PAC for the currently enabled chip at `embassy_rp::pac`. +## This is unstable because semver-minor (non-breaking) releases of `embassy-rp` may major-bump (breaking) the PAC version. +## If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC. +## There are no plans to make this stable. +unstable-pac = [] + +## Enable the timer for use with `embassy-time` with a 1MHz tick rate. +time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000_000"] + +## Enable ROM function cache. This will store the address of a ROM function when first used, improving performance of subsequent calls. +rom-func-cache = [] +## Enable implementations of some compiler intrinsics using functions in the rp2040 Mask ROM. +## These should be as fast or faster than the implementations in compiler-builtins. They also save code space and reduce memory contention. +## Compiler intrinsics are used automatically, you do not need to change your code to get performance improvements from this feature. +intrinsics = [] +## Enable intrinsics based on extra ROM functions added in the v2 version of the rp2040 Mask ROM. +## This version added a lot more floating point operations - many f64 functions and a few f32 functions were added in ROM v2. +rom-v2-intrinsics = [] + +## Allow using QSPI pins as GPIO pins. This is mostly not what you want (because your flash is attached via QSPI pins) +## and adds code and memory overhead when this feature is enabled. +qspi-as-gpio = [] + +## Indicate code is running from RAM. +## Set this if all code is in RAM, and the cores never access memory-mapped flash memory through XIP. +## This allows the flash driver to not force pausing execution on both cores when doing flash operations. +run-from-ram = [] + +#! ### boot2 flash chip support +#! RP2040's internal bootloader is only able to run code from the first 256 bytes of flash. +#! A 2nd stage bootloader (boot2) is required to run larger programs from external flash. +#! Select from existing boot2 implementations via the following features. If none are selected, +#! boot2-w25q080 will be used (w25q080 is the flash chip used on the pico). +#! Each implementation uses flash commands and timings specific to a QSPI flash chip family for better performance. +## Use boot2 with support for Renesas/Dialog AT25SF128a SPI flash. +boot2-at25sf128a = [] +## Use boot2 with support for Gigadevice GD25Q64C SPI flash. +boot2-gd25q64cs = [] +## Use boot2 that only uses generic flash commands - these are supported by all SPI flash, but are slower. +boot2-generic-03h = [] +## Use boot2 with support for ISSI IS25LP080 SPI flash. +boot2-is25lp080 = [] +## Use boot2 that copies the entire program to RAM before booting. This uses generic flash commands to perform the copy. +boot2-ram-memcpy = [] +## Use boot2 with support for Winbond W25Q080 SPI flash. +boot2-w25q080 = [] +## Use boot2 with support for Winbond W25X10CL SPI flash. +boot2-w25x10cl = [] +## Have embassy not provide the boot2 so you can use your own. +## Place your own in the ".boot2" section like: +## ``` +## #[link_section = ".boot2"] +## #[used] +## static BOOT2: [u8; 256] = [0; 256]; // Provide your own with e.g. include_bytes! +## ``` +boot2-none = [] + +## Configure the hal for use with the rp2040 +rp2040 = ["rp-pac/rp2040"] +_rp235x = ["rp-pac/rp235x"] +## Configure the hal for use with the rp235xA +rp235xa = ["_rp235x"] +## Configure the hal for use with the rp235xB +rp235xb = ["_rp235x"] + +# hack around cortex-m peripherals being wrong when running tests. +_test = [] + +## Add a binary-info header block containing picotool-compatible metadata. +## +## Takes up a little flash space, but picotool can then report the name of your +## program and other details. +binary-info = ["rt", "dep:rp-binary-info", "rp-binary-info?/binary-info"] + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../embassy-sync" } +embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } +embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } +embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" } +embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } +atomic-polyfill = "1.0.1" +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +nb = "1.1.0" +cfg-if = "1.0.0" +cortex-m-rt = ">=0.6.15,<0.8" +cortex-m = "0.7.6" +critical-section = "1.2.0" +chrono = { version = "0.4", default-features = false, optional = true } +embedded-io = { version = "0.6.1" } +embedded-io-async = { version = "0.6.1" } +embedded-storage = { version = "0.3" } +embedded-storage-async = { version = "0.4.1" } +rand_core = "0.6.4" +fixed = "1.28.0" + +rp-pac = { git = "https://github.com/embassy-rs/rp-pac.git", rev = "a7f42d25517f7124ad3b4ed492dec8b0f50a0e6c", feature = ["rt"] } + +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-nb = { version = "1.0" } + +pio-proc = {version= "0.2" } +pio = {version= "0.2.1" } +rp2040-boot2 = "0.3" +document-features = "0.2.10" +sha2-const-stable = "0.1" +rp-binary-info = { version = "0.1.0", optional = true } +smart-leds = "0.4.0" + +[dev-dependencies] +embassy-executor = { version = "0.6.2", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } +static_cell = { version = "2" } diff --git a/.stversions/embassy/embassy-rp/src/lib~20241114-122315.rs b/.stversions/embassy/embassy-rp/src/lib~20241114-122315.rs new file mode 100644 index 0000000..51f6ed7 --- /dev/null +++ b/.stversions/embassy/embassy-rp/src/lib~20241114-122315.rs @@ -0,0 +1,691 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +#[cfg(feature = "binary-info")] +pub use rp_binary_info as binary_info; + +#[cfg(feature = "critical-section-impl")] +mod critical_section_impl; + +#[cfg(feature = "rp2040")] +mod intrinsics; + +pub mod adc; +#[cfg(feature = "_rp235x")] +pub mod block; +#[cfg(feature = "rp2040")] +pub mod bootsel; +pub mod clocks; +pub mod dma; +pub mod flash; +#[cfg(feature = "rp2040")] +mod float; +pub mod gpio; +pub mod i2c; +pub mod i2c_slave; +pub mod multicore; +#[cfg(feature = "_rp235x")] +pub mod otp; +pub mod pio_programs; +pub mod pwm; +mod reset; +pub mod rom_data; +#[cfg(feature = "rp2040")] +pub mod rtc; +pub mod spi; +#[cfg(feature = "time-driver")] +pub mod time_driver; +#[cfg(feature = "_rp235x")] +pub mod trng; +pub mod uart; +pub mod usb; +pub mod watchdog; + +// PIO +pub mod pio; +pub(crate) mod relocate; + +// Reexports +pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +#[cfg(feature = "unstable-pac")] +pub use rp_pac as pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use rp_pac as pac; + +#[cfg(feature = "rt")] +pub use crate::pac::NVIC_PRIO_BITS; + +#[cfg(feature = "rp2040")] +embassy_hal_internal::interrupt_mod!( + TIMER_IRQ_0, + TIMER_IRQ_1, + TIMER_IRQ_2, + TIMER_IRQ_3, + PWM_IRQ_WRAP, + USBCTRL_IRQ, + XIP_IRQ, + PIO0_IRQ_0, + PIO0_IRQ_1, + PIO1_IRQ_0, + PIO1_IRQ_1, + DMA_IRQ_0, + DMA_IRQ_1, + IO_IRQ_BANK0, + IO_IRQ_QSPI, + SIO_IRQ_PROC0, + SIO_IRQ_PROC1, + CLOCKS_IRQ, + SPI0_IRQ, + SPI1_IRQ, + UART0_IRQ, + UART1_IRQ, + ADC_IRQ_FIFO, + I2C0_IRQ, + I2C1_IRQ, + RTC_IRQ, + SWI_IRQ_0, + SWI_IRQ_1, + SWI_IRQ_2, + SWI_IRQ_3, + SWI_IRQ_4, + SWI_IRQ_5, +); + +#[cfg(feature = "_rp235x")] +embassy_hal_internal::interrupt_mod!( + TIMER0_IRQ_0, + TIMER0_IRQ_1, + TIMER0_IRQ_2, + TIMER0_IRQ_3, + TIMER1_IRQ_0, + TIMER1_IRQ_1, + TIMER1_IRQ_2, + TIMER1_IRQ_3, + PWM_IRQ_WRAP_0, + PWM_IRQ_WRAP_1, + DMA_IRQ_0, + DMA_IRQ_1, + USBCTRL_IRQ, + PIO0_IRQ_0, + PIO0_IRQ_1, + PIO1_IRQ_0, + PIO1_IRQ_1, + PIO2_IRQ_0, + PIO2_IRQ_1, + IO_IRQ_BANK0, + IO_IRQ_BANK0_NS, + IO_IRQ_QSPI, + IO_IRQ_QSPI_NS, + SIO_IRQ_FIFO, + SIO_IRQ_BELL, + SIO_IRQ_FIFO_NS, + SIO_IRQ_BELL_NS, + CLOCKS_IRQ, + SPI0_IRQ, + SPI1_IRQ, + UART0_IRQ, + UART1_IRQ, + ADC_IRQ_FIFO, + I2C0_IRQ, + I2C1_IRQ, + TRNG_IRQ, + PLL_SYS_IRQ, + PLL_USB_IRQ, + SWI_IRQ_0, + SWI_IRQ_1, + SWI_IRQ_2, + SWI_IRQ_3, + SWI_IRQ_4, + SWI_IRQ_5, +); + +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +/// +/// Example of how to bind one interrupt: +/// +/// ```rust,ignore +/// use embassy_rp::{bind_interrupts, usb, peripherals}; +/// +/// bind_interrupts!(struct Irqs { +/// USBCTRL_IRQ => usb::InterruptHandler; +/// }); +/// ``` +/// +// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + } + )* + }; +} + +#[cfg(feature = "rp2040")] +embassy_hal_internal::peripherals! { + PIN_0, + PIN_1, + PIN_2, + PIN_3, + PIN_4, + PIN_5, + PIN_6, + PIN_7, + PIN_8, + PIN_9, + PIN_10, + PIN_11, + PIN_12, + PIN_13, + PIN_14, + PIN_15, + PIN_16, + PIN_17, + PIN_18, + PIN_19, + PIN_20, + PIN_21, + PIN_22, + PIN_23, + PIN_24, + PIN_25, + PIN_26, + PIN_27, + PIN_28, + PIN_29, + PIN_QSPI_SCLK, + PIN_QSPI_SS, + PIN_QSPI_SD0, + PIN_QSPI_SD1, + PIN_QSPI_SD2, + PIN_QSPI_SD3, + + UART0, + UART1, + + SPI0, + SPI1, + + I2C0, + I2C1, + + DMA_CH0, + DMA_CH1, + DMA_CH2, + DMA_CH3, + DMA_CH4, + DMA_CH5, + DMA_CH6, + DMA_CH7, + DMA_CH8, + DMA_CH9, + DMA_CH10, + DMA_CH11, + + PWM_SLICE0, + PWM_SLICE1, + PWM_SLICE2, + PWM_SLICE3, + PWM_SLICE4, + PWM_SLICE5, + PWM_SLICE6, + PWM_SLICE7, + + USB, + + RTC, + + FLASH, + + ADC, + ADC_TEMP_SENSOR, + + CORE1, + + PIO0, + PIO1, + + WATCHDOG, + BOOTSEL, +} + +#[cfg(feature = "_rp235x")] +embassy_hal_internal::peripherals! { + PIN_0, + PIN_1, + PIN_2, + PIN_3, + PIN_4, + PIN_5, + PIN_6, + PIN_7, + PIN_8, + PIN_9, + PIN_10, + PIN_11, + PIN_12, + PIN_13, + PIN_14, + PIN_15, + PIN_16, + PIN_17, + PIN_18, + PIN_19, + PIN_20, + PIN_21, + PIN_22, + PIN_23, + PIN_24, + PIN_25, + PIN_26, + PIN_27, + PIN_28, + PIN_29, + #[cfg(feature = "rp235xb")] + PIN_30, + #[cfg(feature = "rp235xb")] + PIN_31, + #[cfg(feature = "rp235xb")] + PIN_32, + #[cfg(feature = "rp235xb")] + PIN_33, + #[cfg(feature = "rp235xb")] + PIN_34, + #[cfg(feature = "rp235xb")] + PIN_35, + #[cfg(feature = "rp235xb")] + PIN_36, + #[cfg(feature = "rp235xb")] + PIN_37, + #[cfg(feature = "rp235xb")] + PIN_38, + #[cfg(feature = "rp235xb")] + PIN_39, + #[cfg(feature = "rp235xb")] + PIN_40, + #[cfg(feature = "rp235xb")] + PIN_41, + #[cfg(feature = "rp235xb")] + PIN_42, + #[cfg(feature = "rp235xb")] + PIN_43, + #[cfg(feature = "rp235xb")] + PIN_44, + #[cfg(feature = "rp235xb")] + PIN_45, + #[cfg(feature = "rp235xb")] + PIN_46, + #[cfg(feature = "rp235xb")] + PIN_47, + PIN_QSPI_SCLK, + PIN_QSPI_SS, + PIN_QSPI_SD0, + PIN_QSPI_SD1, + PIN_QSPI_SD2, + PIN_QSPI_SD3, + + UART0, + UART1, + + SPI0, + SPI1, + + I2C0, + I2C1, + + DMA_CH0, + DMA_CH1, + DMA_CH2, + DMA_CH3, + DMA_CH4, + DMA_CH5, + DMA_CH6, + DMA_CH7, + DMA_CH8, + DMA_CH9, + DMA_CH10, + DMA_CH11, + DMA_CH12, + DMA_CH13, + DMA_CH14, + DMA_CH15, + + PWM_SLICE0, + PWM_SLICE1, + PWM_SLICE2, + PWM_SLICE3, + PWM_SLICE4, + PWM_SLICE5, + PWM_SLICE6, + PWM_SLICE7, + PWM_SLICE8, + PWM_SLICE9, + PWM_SLICE10, + PWM_SLICE11, + + USB, + + RTC, + + FLASH, + + ADC, + ADC_TEMP_SENSOR, + + CORE1, + + PIO0, + PIO1, + PIO2, + + WATCHDOG, + BOOTSEL, + + TRNG +} + +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] +macro_rules! select_bootloader { + ( $( $feature:literal => $loader:ident, )+ default => $default:ident ) => { + $( + #[cfg(feature = $feature)] + #[link_section = ".boot2"] + #[used] + static BOOT2: [u8; 256] = rp2040_boot2::$loader; + )* + + #[cfg(not(any( $( feature = $feature),* )))] + #[link_section = ".boot2"] + #[used] + static BOOT2: [u8; 256] = rp2040_boot2::$default; + } +} + +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] +select_bootloader! { + "boot2-at25sf128a" => BOOT_LOADER_AT25SF128A, + "boot2-gd25q64cs" => BOOT_LOADER_GD25Q64CS, + "boot2-generic-03h" => BOOT_LOADER_GENERIC_03H, + "boot2-is25lp080" => BOOT_LOADER_IS25LP080, + "boot2-ram-memcpy" => BOOT_LOADER_RAM_MEMCPY, + "boot2-w25q080" => BOOT_LOADER_W25Q080, + "boot2-w25x10cl" => BOOT_LOADER_W25X10CL, + default => BOOT_LOADER_W25Q080 +} + +/// Installs a stack guard for the CORE0 stack in MPU region 0. +/// Will fail if the MPU is already configured. This function requires +/// a `_stack_end` symbol to be defined by the linker script, and expects +/// `_stack_end` to be located at the lowest address (largest depth) of +/// the stack. +/// +/// This method can *only* set up stack guards on the currently +/// executing core. Stack guards for CORE1 are set up automatically, +/// only CORE0 should ever use this. +/// +/// # Usage +/// +/// ```no_run +/// use embassy_rp::install_core0_stack_guard; +/// use embassy_executor::{Executor, Spawner}; +/// +/// #[embassy_executor::main] +/// async fn main(_spawner: Spawner) { +/// // set up by the linker as follows: +/// // +/// // MEMORY { +/// // STACK0: ORIGIN = 0x20040000, LENGTH = 4K +/// // } +/// // +/// // _stack_end = ORIGIN(STACK0); +/// // _stack_start = _stack_end + LENGTH(STACK0); +/// // +/// install_core0_stack_guard().expect("MPU already configured"); +/// let p = embassy_rp::init(Default::default()); +/// +/// // ... +/// } +/// ``` +pub fn install_core0_stack_guard() -> Result<(), ()> { + extern "C" { + static mut _stack_end: usize; + } + unsafe { install_stack_guard(core::ptr::addr_of_mut!(_stack_end)) } +} + +#[cfg(all(feature = "rp2040", not(feature = "_test")))] +#[inline(always)] +unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { + let core = unsafe { cortex_m::Peripherals::steal() }; + + // Fail if MPU is already configured + if core.MPU.ctrl.read() != 0 { + return Err(()); + } + + // 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) | (1 << 4)); // set address and update RNR + 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 + ); + } + Ok(()) +} + +#[cfg(all(feature = "_rp235x", not(feature = "_test")))] +#[inline(always)] +unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { + let core = unsafe { cortex_m::Peripherals::steal() }; + + // Fail if MPU is already configured + if core.MPU.ctrl.read() != 0 { + return Err(()); + } + + unsafe { + core.MPU.ctrl.write(5); // enable mpu with background default map + core.MPU.rbar.write(stack_bottom as u32 & !0xff); // set address + core.MPU.rlar.write(1); // enable region + } + Ok(()) +} + +// This is to hack around cortex_m defaulting to ARMv7 when building tests, +// so the compile fails when we try to use ARMv8 peripherals. +#[cfg(feature = "_test")] +#[inline(always)] +unsafe fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> { + Ok(()) +} + +/// HAL configuration for RP. +pub mod config { + use crate::clocks::ClockConfig; + + /// HAL configuration passed when initializing. + #[non_exhaustive] + pub struct Config { + /// Clock configuration. + pub clocks: ClockConfig, + } + + impl Default for Config { + fn default() -> Self { + Self { + clocks: ClockConfig::crystal(12_000_000), + } + } + } + + impl Config { + /// Create a new configuration with the provided clock config. + pub fn new(clocks: ClockConfig) -> Self { + Self { clocks } + } + } +} + +/// Initialize the `embassy-rp` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once at startup, otherwise it panics. +pub fn init(config: config::Config) -> Peripherals { + // Do this first, so that it panics if user is calling `init` a second time + // before doing anything important. + let peripherals = Peripherals::take(); + + unsafe { + clocks::init(config.clocks); + #[cfg(feature = "time-driver")] + time_driver::init(); + dma::init(); + gpio::init(); + } + + peripherals +} + +#[cfg(all(feature = "rt", feature = "rp2040"))] +#[cortex_m_rt::pre_init] +unsafe fn pre_init() { + // SIO does not get reset when core0 is reset with either `scb::sys_reset()` or with SWD. + // Since we're using SIO spinlock 31 for the critical-section impl, this causes random + // hangs if we reset in the middle of a CS, because the next boot sees the spinlock + // as locked and waits forever. + // + // See https://github.com/embassy-rs/embassy/issues/1736 + // and https://github.com/rp-rs/rp-hal/issues/292 + // and https://matrix.to/#/!vhKMWjizPZBgKeknOo:matrix.org/$VfOkQgyf1PjmaXZbtycFzrCje1RorAXd8BQFHTl4d5M + // + // According to Raspberry Pi, this is considered Working As Intended, and not an errata, + // even though this behavior is different from every other ARM chip (sys_reset usually resets + // the *system* as its name implies, not just the current core). + // + // To fix this, reset SIO on boot. We must do this in pre_init because it's unsound to do it + // in `embassy_rp::init`, since the user could've acquired a CS by then. pre_init is guaranteed + // to run before any user code. + // + // A similar thing could happen with PROC1. It is unclear whether it's possible for PROC1 + // to stay unreset through a PROC0 reset, so we reset it anyway just in case. + // + // Important info from PSM logic (from Luke Wren in above Matrix thread) + // + // The logic is, each PSM stage is reset if either of the following is true: + // - The previous stage is in reset and FRCE_ON is false + // - FRCE_OFF is true + // + // The PSM order is SIO -> PROC0 -> PROC1. + // So, we have to force-on PROC0 to prevent it from getting reset when resetting SIO. + pac::PSM.frce_on().write_and_wait(|w| { + w.set_proc0(true); + }); + // Then reset SIO and PROC1. + pac::PSM.frce_off().write_and_wait(|w| { + w.set_sio(true); + w.set_proc1(true); + }); + // clear force_off first, force_on second. The other way around would reset PROC0. + pac::PSM.frce_off().write_and_wait(|_| {}); + pac::PSM.frce_on().write_and_wait(|_| {}); +} + +/// Extension trait for PAC regs, adding atomic xor/bitset/bitclear writes. +#[allow(unused)] +trait RegExt { + #[allow(unused)] + fn write_xor(&self, f: impl FnOnce(&mut T) -> R) -> R; + fn write_set(&self, f: impl FnOnce(&mut T) -> R) -> R; + fn write_clear(&self, f: impl FnOnce(&mut T) -> R) -> R; + fn write_and_wait(&self, f: impl FnOnce(&mut T) -> R) -> R + where + T: PartialEq; +} + +impl RegExt for pac::common::Reg { + fn write_xor(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x1000) as *mut T; + ptr.write_volatile(val); + } + res + } + + fn write_set(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x2000) as *mut T; + ptr.write_volatile(val); + } + res + } + + fn write_clear(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x3000) as *mut T; + ptr.write_volatile(val); + } + res + } + + fn write_and_wait(&self, f: impl FnOnce(&mut T) -> R) -> R + where + T: PartialEq, + { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + self.as_ptr().write_volatile(val); + while self.as_ptr().read_volatile() != val {} + } + res + } +} diff --git a/.stversions/embassy/embassy-rp/src/uart/mod~20241114-122314.rs b/.stversions/embassy/embassy-rp/src/uart/mod~20241114-122314.rs new file mode 100644 index 0000000..08f2092 --- /dev/null +++ b/.stversions/embassy/embassy-rp/src/uart/mod~20241114-122314.rs @@ -0,0 +1,1499 @@ +//! UART driver. +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use atomic_polyfill::{AtomicU16, Ordering}; +use embassy_futures::select::{select, Either}; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_time::{Delay, Timer}; +use pac::uart::regs::Uartris; + +use crate::clocks::clk_peri_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::gpio::{AnyPin, SealedPin}; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::pac::io::vals::{Inover, Outover}; +use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; + +mod buffered; +pub use buffered::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, BufferedUartTx}; + +/// Word length. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum DataBits { + /// 5 bits. + DataBits5, + /// 6 bits. + DataBits6, + /// 7 bits. + DataBits7, + /// 8 bits. + DataBits8, +} + +impl DataBits { + fn bits(&self) -> u8 { + match self { + Self::DataBits5 => 0b00, + Self::DataBits6 => 0b01, + Self::DataBits7 => 0b10, + Self::DataBits8 => 0b11, + } + } +} + +/// Parity bit. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Parity { + /// No parity. + ParityNone, + /// Even parity. + ParityEven, + /// Odd parity. + ParityOdd, +} + +/// Stop bits. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum StopBits { + #[doc = "1 stop bit"] + STOP1, + #[doc = "2 stop bits"] + STOP2, +} + +/// UART config. +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Config { + /// Baud rate. + pub baudrate: u32, + /// Word length. + pub data_bits: DataBits, + /// Stop bits. + pub stop_bits: StopBits, + /// Parity bit. + pub parity: Parity, + /// Invert the tx pin output + pub invert_tx: bool, + /// Invert the rx pin input + pub invert_rx: bool, + /// Invert the rts pin + pub invert_rts: bool, + /// Invert the cts pin + pub invert_cts: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + baudrate: 115200, + data_bits: DataBits::DataBits8, + stop_bits: StopBits::STOP1, + parity: Parity::ParityNone, + invert_rx: false, + invert_tx: false, + invert_rts: false, + invert_cts: false, + } + } +} + +/// Serial error +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Triggered when the FIFO (or shift-register) is overflowed. + Overrun, + /// Triggered when a break is received + Break, + /// Triggered when there is a parity mismatch between what's received and + /// our settings. + Parity, + /// Triggered when the received character didn't have a valid stop bit. + Framing, +} + +/// Read To Break error +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ReadToBreakError { + /// Read this many bytes, but never received a line break. + MissingBreak(usize), + /// Other, standard issue with the serial request + Other(Error), +} + +/// Internal DMA state of UART RX. +pub struct DmaState { + rx_err_waker: AtomicWaker, + rx_errs: AtomicU16, +} + +/// UART driver. +pub struct Uart<'d, T: Instance, M: Mode> { + tx: UartTx<'d, T, M>, + rx: UartRx<'d, T, M>, +} + +/// UART TX driver. +pub struct UartTx<'d, T: Instance, M: Mode> { + tx_dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +/// UART RX driver. +pub struct UartRx<'d, T: Instance, M: Mode> { + rx_dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { + /// Create a new DMA-enabled UART which can only send data + pub fn new( + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + 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, + } + } + + /// Transmit the provided buffer blocking execution until done. + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let r = T::regs(); + for &b in buffer { + while r.uartfr().read().txff() {} + r.uartdr().write(|w| w.set_data(b)); + } + Ok(()) + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + let r = T::regs(); + while !r.uartfr().read().txfe() {} + Ok(()) + } + + /// Check if UART is busy transmitting. + pub fn busy(&self) -> bool { + T::regs().uartfr().read().busy() + } + + /// Assert a break condition after waiting for the transmit buffers to empty, + /// for the specified number of bit times. This condition must be asserted + /// for at least two frame times to be effective, `bits` will adjusted + /// according to frame size, parity, and stop bit settings to ensure this. + /// + /// This method may block for a long amount of time since it has to wait + /// for the transmit fifo to empty, which may take a while on slow links. + pub async fn send_break(&mut self, bits: u32) { + let regs = T::regs(); + let bits = bits.max({ + let lcr = regs.uartlcr_h().read(); + let width = lcr.wlen() as u32 + 5; + let parity = lcr.pen() as u32; + let stops = 1 + lcr.stp2() as u32; + 2 * (1 + width + parity + stops) + }); + let divx64 = (((regs.uartibrd().read().baud_divint() as u32) << 6) + + regs.uartfbrd().read().baud_divfrac() as u32) as u64; + let div_clk = clk_peri_freq() as u64 * 64; + let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk; + + self.blocking_flush().unwrap(); + while self.busy() {} + regs.uartlcr_h().write_set(|w| w.set_brk(true)); + Timer::after_micros(wait_usecs).await; + regs.uartlcr_h().write_clear(|w| w.set_brk(true)); + } +} + +impl<'d, T: Instance> UartTx<'d, T, Blocking> { + /// Create a new UART TX instance for blocking mode operations. + pub fn new_blocking( + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(tx); + Uart::::init(Some(tx.map_into()), None, None, None, config); + Self::new_inner(None) + } + + /// Convert this uart TX instance into a buffered uart using the provided + /// irq and transmit buffer. + pub fn into_buffered( + self, + irq: impl Binding>, + tx_buffer: &'d mut [u8], + ) -> BufferedUartTx<'d, T> { + buffered::init_buffers::(irq, Some(tx_buffer), None); + + BufferedUartTx { phantom: PhantomData } + } +} + +impl<'d, T: Instance> UartTx<'d, T, Async> { + /// Write to UART TX from the provided buffer using DMA. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let ch = self.tx_dma.as_mut().unwrap(); + let transfer = unsafe { + T::regs().uartdmacr().write_set(|reg| { + reg.set_txdmae(true); + }); + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::write(ch, buffer, T::regs().uartdr().as_ptr() as *mut _, T::TX_DREQ.into()) + }; + transfer.await; + Ok(()) + } +} + +impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { + /// Create a new DMA-enabled UART which can only receive data + pub fn new( + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + _irq: impl Binding>, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(rx, rx_dma); + Uart::::init(None, Some(rx.map_into()), None, None, config); + Self::new_inner(true, Some(rx_dma.map_into())) + } + + fn new_inner(has_irq: bool, rx_dma: Option>) -> Self { + debug_assert_eq!(has_irq, rx_dma.is_some()); + if has_irq { + // disable all error interrupts initially + T::regs().uartimsc().write(|w| w.0 = 0); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + } + Self { + rx_dma, + phantom: PhantomData, + } + } + + /// Read from UART RX blocking execution until done. + pub fn blocking_read(&mut self, mut buffer: &mut [u8]) -> Result<(), Error> { + while !buffer.is_empty() { + let received = self.drain_fifo(buffer).map_err(|(_i, e)| e)?; + buffer = &mut buffer[received..]; + } + Ok(()) + } + + /// Returns Ok(len) if no errors occurred. Returns Err((len, err)) if an error was + /// encountered. in both cases, `len` is the number of *good* bytes copied into + /// `buffer`. + fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result { + let r = T::regs(); + for (i, b) in buffer.iter_mut().enumerate() { + if r.uartfr().read().rxfe() { + return Ok(i); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + return Err((i, Error::Overrun)); + } else if dr.be() { + return Err((i, Error::Break)); + } else if dr.pe() { + return Err((i, Error::Parity)); + } else if dr.fe() { + return Err((i, Error::Framing)); + } else { + *b = dr.data(); + } + } + Ok(buffer.len()) + } +} + +impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> { + fn drop(&mut self) { + if self.rx_dma.is_some() { + T::Interrupt::disable(); + // clear dma flags. irq handlers use these to disambiguate among themselves. + T::regs().uartdmacr().write_clear(|reg| { + reg.set_rxdmae(true); + reg.set_txdmae(true); + reg.set_dmaonerr(true); + }); + } + } +} + +impl<'d, T: Instance> UartRx<'d, T, Blocking> { + /// Create a new UART RX instance for blocking mode operations. + pub fn new_blocking( + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(rx); + Uart::::init(None, Some(rx.map_into()), None, None, config); + Self::new_inner(false, None) + } + + /// Convert this uart RX instance into a buffered uart using the provided + /// irq and receive buffer. + pub fn into_buffered( + self, + irq: impl Binding>, + rx_buffer: &'d mut [u8], + ) -> BufferedUartRx<'d, T> { + buffered::init_buffers::(irq, None, Some(rx_buffer)); + + BufferedUartRx { phantom: PhantomData } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let uart = T::regs(); + if !uart.uartdmacr().read().rxdmae() { + return; + } + + let state = T::dma_state(); + let errs = uart.uartris().read(); + state.rx_errs.store(errs.0 as u16, Ordering::Relaxed); + state.rx_err_waker.wake(); + // disable the error interrupts instead of clearing the flags. clearing the + // flags would allow the dma transfer to continue, potentially signaling + // completion before we can check for errors that happened *during* the transfer. + uart.uartimsc().write_clear(|w| w.0 = errs.0); + } +} + +impl<'d, T: Instance> UartRx<'d, T, Async> { + /// Read from UART RX into the provided buffer. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + // clear error flags before we drain the fifo. errors that have accumulated + // in the flags will also be present in the fifo. + T::dma_state().rx_errs.store(0, Ordering::Relaxed); + T::regs().uarticr().write(|w| { + w.set_oeic(true); + w.set_beic(true); + w.set_peic(true); + w.set_feic(true); + }); + + // then drain the fifo. we need to read at most 32 bytes. errors that apply + // to fifo bytes will be reported directly. + let buffer = match { + let limit = buffer.len().min(32); + self.drain_fifo(&mut buffer[0..limit]) + } { + Ok(len) if len < buffer.len() => &mut buffer[len..], + Ok(_) => return Ok(()), + Err((_i, e)) => return Err(e), + }; + + // start a dma transfer. if errors have happened in the interim some error + // interrupt flags will have been raised, and those will be picked up immediately + // by the interrupt handler. + let ch = self.rx_dma.as_mut().unwrap(); + T::regs().uartimsc().write_set(|w| { + w.set_oeim(true); + w.set_beim(true); + w.set_peim(true); + w.set_feim(true); + }); + T::regs().uartdmacr().write_set(|reg| { + reg.set_rxdmae(true); + reg.set_dmaonerr(true); + }); + let transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read(ch, T::regs().uartdr().as_ptr() as *const _, buffer, T::RX_DREQ.into()) + }; + + // wait for either the transfer to complete or an error to happen. + let transfer_result = select( + transfer, + poll_fn(|cx| { + T::dma_state().rx_err_waker.register(cx.waker()); + match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { + 0 => Poll::Pending, + e => Poll::Ready(Uartris(e as u32)), + } + }), + ) + .await; + + let errors = match transfer_result { + Either::First(()) => { + // We're here because the DMA finished, BUT if an error occurred on the LAST + // byte, then we may still need to grab the error state! + Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) + } + Either::Second(e) => { + // We're here because we errored, which means this is the error that + // was problematic. + e + } + }; + + // If we got no error, just return at this point + if errors.0 == 0 { + return Ok(()); + } + + // If we DID get an error, we need to figure out which one it was. + if errors.oeris() { + return Err(Error::Overrun); + } else if errors.beris() { + return Err(Error::Break); + } else if errors.peris() { + return Err(Error::Parity); + } else if errors.feris() { + return Err(Error::Framing); + } + unreachable!("unrecognized rx error"); + } + + /// Read from the UART, waiting for a line break. + /// + /// We read until one of the following occurs: + /// + /// * We read `buffer.len()` bytes without a line break + /// * returns `Err(ReadToBreakError::MissingBreak(buffer.len()))` + /// * We read `n` bytes then a line break occurs + /// * returns `Ok(n)` + /// * We encounter some error OTHER than a line break + /// * returns `Err(ReadToBreakError::Other(error))` + /// + /// **NOTE**: you MUST provide a buffer one byte larger than your largest expected + /// message to reliably detect the framing on one single call to `read_to_break()`. + /// + /// * If you expect a message of 20 bytes + line break, and provide a 20-byte buffer: + /// * The first call to `read_to_break()` will return `Err(ReadToBreakError::MissingBreak(20))` + /// * The next call to `read_to_break()` will immediately return `Ok(0)`, from the "stale" line break + /// * If you expect a message of 20 bytes + line break, and provide a 21-byte buffer: + /// * The first call to `read_to_break()` will return `Ok(20)`. + /// * The next call to `read_to_break()` will work as expected + pub async fn read_to_break(&mut self, buffer: &mut [u8]) -> Result { + self.read_to_break_with_count(buffer, 0).await + } + + /// Read from the UART, waiting for a line break as soon as at least `min_count` bytes have been read. + /// + /// We read until one of the following occurs: + /// + /// * We read `buffer.len()` bytes without a line break + /// * returns `Err(ReadToBreakError::MissingBreak(buffer.len()))` + /// * We read `n > min_count` bytes then a line break occurs + /// * returns `Ok(n)` + /// * We encounter some error OTHER than a line break + /// * returns `Err(ReadToBreakError::Other(error))` + /// + /// If a line break occurs before `min_count` bytes have been read, the break will be ignored and the read will continue + /// + /// **NOTE**: you MUST provide a buffer one byte larger than your largest expected + /// message to reliably detect the framing on one single call to `read_to_break()`. + /// + /// * If you expect a message of 20 bytes + line break, and provide a 20-byte buffer: + /// * The first call to `read_to_break()` will return `Err(ReadToBreakError::MissingBreak(20))` + /// * The next call to `read_to_break()` will immediately return `Ok(0)`, from the "stale" line break + /// * If you expect a message of 20 bytes + line break, and provide a 21-byte buffer: + /// * The first call to `read_to_break()` will return `Ok(20)`. + /// * The next call to `read_to_break()` will work as expected + pub async fn read_to_break_with_count( + &mut self, + buffer: &mut [u8], + min_count: usize, + ) -> Result { + // clear error flags before we drain the fifo. errors that have accumulated + // in the flags will also be present in the fifo. + T::dma_state().rx_errs.store(0, Ordering::Relaxed); + T::regs().uarticr().write(|w| { + w.set_oeic(true); + w.set_beic(true); + w.set_peic(true); + w.set_feic(true); + }); + + // then drain the fifo. we need to read at most 32 bytes. errors that apply + // to fifo bytes will be reported directly. + let mut sbuffer = match { + let limit = buffer.len().min(32); + self.drain_fifo(&mut buffer[0..limit]) + } { + // Drained fifo, still some room left! + Ok(len) if len < buffer.len() => &mut buffer[len..], + // Drained (some/all of the fifo), no room left + Ok(len) => return Err(ReadToBreakError::MissingBreak(len)), + // We got a break WHILE draining the FIFO, return what we did get before the break + Err((len, Error::Break)) => { + if len < min_count && len < buffer.len() { + &mut buffer[len..] + } else { + return Ok(len); + } + } + // Some other error, just return the error + Err((_i, e)) => return Err(ReadToBreakError::Other(e)), + }; + + // start a dma transfer. if errors have happened in the interim some error + // interrupt flags will have been raised, and those will be picked up immediately + // by the interrupt handler. + let mut ch = self.rx_dma.as_mut().unwrap(); + T::regs().uartimsc().write_set(|w| { + w.set_oeim(true); + w.set_beim(true); + w.set_peim(true); + w.set_feim(true); + }); + T::regs().uartdmacr().write_set(|reg| { + reg.set_rxdmae(true); + reg.set_dmaonerr(true); + }); + + loop { + let transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read( + &mut ch, + T::regs().uartdr().as_ptr() as *const _, + sbuffer, + T::RX_DREQ.into(), + ) + }; + + // wait for either the transfer to complete or an error to happen. + let transfer_result = select( + transfer, + poll_fn(|cx| { + T::dma_state().rx_err_waker.register(cx.waker()); + match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { + 0 => Poll::Pending, + e => Poll::Ready(Uartris(e as u32)), + } + }), + ) + .await; + + // Figure out our error state + let errors = match transfer_result { + Either::First(()) => { + // We're here because the DMA finished, BUT if an error occurred on the LAST + // byte, then we may still need to grab the error state! + Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) + } + Either::Second(e) => { + // We're here because we errored, which means this is the error that + // was problematic. + e + } + }; + + if errors.0 == 0 { + // No errors? That means we filled the buffer without a line break. + // For THIS function, that's a problem. + return Err(ReadToBreakError::MissingBreak(buffer.len())); + } else if errors.beris() { + // We got a Line Break! By this point, we've finished/aborted the DMA + // transaction, which means that we need to figure out where it left off + // by looking at the write_addr. + // + // First, we do a sanity check to make sure the write value is within the + // range of DMA we just did. + let sval = buffer.as_ptr() as usize; + let eval = sval + buffer.len(); + + // This is the address where the DMA would write to next + let next_addr = ch.regs().write_addr().read() as usize; + + // If we DON'T end up inside the range, something has gone really wrong. + // Note that it's okay that `eval` is one past the end of the slice, as + // this is where the write pointer will end up at the end of a full + // transfer. + if (next_addr < sval) || (next_addr > eval) { + unreachable!("UART DMA reported invalid `write_addr`"); + } + + if (next_addr - sval) < min_count { + sbuffer = &mut buffer[(next_addr - sval)..]; + continue; + } + + let regs = T::regs(); + let all_full = next_addr == eval; + + // NOTE: This is off label usage of RSR! See the issue below for + // why I am not checking if there is an "extra" FIFO byte, and why + // I am checking RSR directly (it seems to report the status of the LAST + // POPPED value, rather than the NEXT TO POP value like the datasheet + // suggests!) + // + // issue: https://github.com/raspberrypi/pico-feedback/issues/367 + let last_was_break = regs.uartrsr().read().be(); + + return match (all_full, last_was_break) { + (true, true) | (false, _) => { + // We got less than the full amount + a break, or the full amount + // and the last byte was a break. Subtract the break off by adding one to sval. + Ok(next_addr.saturating_sub(1 + sval)) + } + (true, false) => { + // We finished the whole DMA, and the last DMA'd byte was NOT a break + // character. This is an error. + // + // NOTE: we COULD potentially return Ok(buffer.len()) here, since we + // know a line break occured at SOME POINT after the DMA completed. + // + // However, we have no way of knowing if there was extra data BEFORE + // that line break, so instead return an Err to signal to the caller + // that there are "leftovers", and they'll catch the actual line break + // on the next call. + // + // Doing it like this also avoids racyness: now whether you finished + // the full read BEFORE the line break occurred or AFTER the line break + // occurs, you still get `MissingBreak(buffer.len())` instead of sometimes + // getting `Ok(buffer.len())` if you were "late enough" to observe the + // line break. + Err(ReadToBreakError::MissingBreak(buffer.len())) + } + }; + } else if errors.oeris() { + return Err(ReadToBreakError::Other(Error::Overrun)); + } else if errors.peris() { + return Err(ReadToBreakError::Other(Error::Parity)); + } else if errors.feris() { + return Err(ReadToBreakError::Other(Error::Framing)); + } + unreachable!("unrecognized rx error"); + } + } +} + +impl<'d, T: Instance> Uart<'d, T, Blocking> { + /// Create a new UART without hardware flow control + pub fn new_blocking( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

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

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

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

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + _irq: impl Binding>, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

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

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + _irq: impl Binding>, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

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

+ 'd, + mut tx: PeripheralRef<'d, AnyPin>, + mut rx: PeripheralRef<'d, AnyPin>, + mut rts: Option>, + mut cts: Option>, + has_irq: bool, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + 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(has_irq, rx_dma), + } + } + + fn init( + tx: Option>, + rx: Option>, + rts: Option>, + cts: Option>, + config: Config, + ) { + let r = T::regs(); + if let Some(pin) = &tx { + let funcsel = { + let pin_number = ((pin.gpio().as_ptr() as u32) & 0x1FF) / 8; + if (pin_number % 4) == 0 { + 2 + } else { + 11 + } + }; + pin.gpio().ctrl().write(|w| { + w.set_funcsel(funcsel); + w.set_outover(if config.invert_tx { + Outover::INVERT + } else { + Outover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + if let Some(pin) = &rx { + let funcsel = { + let pin_number = ((pin.gpio().as_ptr() as u32) & 0x1FF) / 8; + if ((pin_number - 1) % 4) == 0 { + 2 + } else { + 11 + } + }; + pin.gpio().ctrl().write(|w| { + w.set_funcsel(funcsel); + w.set_inover(if config.invert_rx { + Inover::INVERT + } else { + Inover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + if let Some(pin) = &cts { + pin.gpio().ctrl().write(|w| { + w.set_funcsel(2); + w.set_inover(if config.invert_cts { + Inover::INVERT + } else { + Inover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + if let Some(pin) = &rts { + pin.gpio().ctrl().write(|w| { + w.set_funcsel(2); + w.set_outover(if config.invert_rts { + Outover::INVERT + } else { + Outover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + + Self::set_baudrate_inner(config.baudrate); + + let (pen, eps) = match config.parity { + Parity::ParityNone => (false, false), + Parity::ParityOdd => (true, false), + Parity::ParityEven => (true, true), + }; + + r.uartlcr_h().write(|w| { + w.set_wlen(config.data_bits.bits()); + w.set_stp2(config.stop_bits == StopBits::STOP2); + w.set_pen(pen); + w.set_eps(eps); + w.set_fen(true); + }); + + r.uartifls().write(|w| { + w.set_rxiflsel(0b000); + w.set_txiflsel(0b000); + }); + + r.uartcr().write(|w| { + w.set_uarten(true); + w.set_rxe(true); + w.set_txe(true); + w.set_ctsen(cts.is_some()); + w.set_rtsen(rts.is_some()); + }); + } + + fn lcr_modify(f: impl FnOnce(&mut crate::pac::uart::regs::UartlcrH) -> R) -> R { + let r = T::regs(); + + // Notes from PL011 reference manual: + // + // - Before writing the LCR, if the UART is enabled it needs to be + // disabled and any current TX + RX activity has to be completed + // + // - There is a BUSY flag which waits for the current TX char, but this is + // OR'd with TX FIFO !FULL, so not usable when FIFOs are enabled and + // potentially nonempty + // + // - FIFOs can't be set to disabled whilst a character is in progress + // (else "FIFO integrity is not guaranteed") + // + // Combination of these means there is no general way to halt and poll for + // end of TX character, if FIFOs may be enabled. Either way, there is no + // way to poll for end of RX character. + // + // So, insert a 15 Baud period delay before changing the settings. + // 15 Baud is comfortably higher than start + max data + parity + stop. + // Anything else would require API changes to permit a non-enabled UART + // state after init() where settings can be changed safely. + let clk_base = crate::clocks::clk_peri_freq(); + + let cr = r.uartcr().read(); + if cr.uarten() { + r.uartcr().modify(|w| { + w.set_uarten(false); + w.set_txe(false); + w.set_rxe(false); + }); + + // Note: Maximise precision here. Show working, the compiler will mop this up. + // Create a 16.6 fixed-point fractional division ratio; then scale to 32-bits. + let mut brdiv_ratio = 64 * r.uartibrd().read().0 + r.uartfbrd().read().0; + brdiv_ratio <<= 10; + // 3662 is ~(15 * 244.14) where 244.14 is 16e6 / 2^16 + let scaled_freq = clk_base / 3662; + let wait_time_us = brdiv_ratio / scaled_freq; + embedded_hal_1::delay::DelayNs::delay_us(&mut Delay, wait_time_us); + } + + let res = r.uartlcr_h().modify(f); + + r.uartcr().write_value(cr); + + res + } + + /// sets baudrate on runtime + pub fn set_baudrate(&mut self, baudrate: u32) { + Self::set_baudrate_inner(baudrate); + } + + fn set_baudrate_inner(baudrate: u32) { + let r = T::regs(); + + let clk_base = crate::clocks::clk_peri_freq(); + + let baud_rate_div = (8 * clk_base) / baudrate; + let mut baud_ibrd = baud_rate_div >> 7; + let mut baud_fbrd = ((baud_rate_div & 0x7f) + 1) / 2; + + if baud_ibrd == 0 { + baud_ibrd = 1; + baud_fbrd = 0; + } else if baud_ibrd >= 65535 { + baud_ibrd = 65535; + baud_fbrd = 0; + } + + // Load PL011's baud divisor registers + r.uartibrd().write_value(pac::uart::regs::Uartibrd(baud_ibrd)); + r.uartfbrd().write_value(pac::uart::regs::Uartfbrd(baud_fbrd)); + + Self::lcr_modify(|_| {}); + } +} + +impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { + /// Transmit the provided buffer blocking execution until done. + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.blocking_write(buffer) + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + self.tx.blocking_flush() + } + + /// Read from UART RX blocking execution until done. + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.blocking_read(buffer) + } + + /// Check if UART is busy transmitting. + pub fn busy(&self) -> bool { + self.tx.busy() + } + + /// Wait until TX is empty and send break condition. + pub async fn send_break(&mut self, bits: u32) { + self.tx.send_break(bits).await + } + + /// Split the Uart into a transmitter and receiver, which is particularly + /// useful when having two tasks correlating to transmitting and receiving. + pub fn split(self) -> (UartTx<'d, T, M>, UartRx<'d, T, M>) { + (self.tx, self.rx) + } + + /// Split the Uart into a transmitter and receiver by mutable reference, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split_ref(&mut self) -> (&mut UartTx<'d, T, M>, &mut UartRx<'d, T, M>) { + (&mut self.tx, &mut self.rx) + } +} + +impl<'d, T: Instance> Uart<'d, T, Async> { + /// Write to UART TX from the provided buffer. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.write(buffer).await + } + + /// Read from UART RX into the provided buffer. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.read(buffer).await + } + + /// Read until the buffer is full or a line break occurs. + /// + /// See [`UartRx::read_to_break()`] for more details + pub async fn read_to_break<'a>(&mut self, buf: &'a mut [u8]) -> Result { + self.rx.read_to_break(buf).await + } + + /// Read until the buffer is full or a line break occurs after at least `min_count` bytes have been read. + /// + /// See [`UartRx::read_to_break_with_count()`] for more details + pub async fn read_to_break_with_count<'a>( + &mut self, + buf: &'a mut [u8], + min_count: usize, + ) -> Result { + self.rx.read_to_break_with_count(buf, min_count).await + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, T, M> { + type Error = Error; + fn read(&mut self) -> Result> { + let r = T::regs(); + if r.uartfr().read().rxfe() { + return Err(nb::Error::WouldBlock); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + Err(nb::Error::Other(Error::Overrun)) + } else if dr.be() { + Err(nb::Error::Other(Error::Break)) + } else if dr.pe() { + Err(nb::Error::Other(Error::Parity)) + } else if dr.fe() { + Err(nb::Error::Other(Error::Framing)) + } else { + Ok(dr.data()) + } + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, T, M> { + type Error = Error; + + fn write(&mut self, word: u8) -> Result<(), nb::Error> { + let r = T::regs(); + if r.uartfr().read().txff() { + return Err(nb::Error::WouldBlock); + } + + r.uartdr().write(|w| w.set_data(word)); + Ok(()) + } + + fn flush(&mut self) -> Result<(), nb::Error> { + let r = T::regs(); + if !r.uartfr().read().txfe() { + return Err(nb::Error::WouldBlock); + } + Ok(()) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, T, M> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for Uart<'d, T, M> { + type Error = Error; + + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for Uart<'d, T, M> { + type Error = Error; + + fn write(&mut self, word: u8) -> Result<(), nb::Error> { + embedded_hal_02::serial::Write::write(&mut self.tx, word) + } + + fn flush(&mut self) -> Result<(), nb::Error> { + embedded_hal_02::serial::Write::flush(&mut self.tx) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, T, M> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { + match *self { + Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, + Self::Break => embedded_hal_nb::serial::ErrorKind::Other, + Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, + Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, + } + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, T, M> { + fn read(&mut self) -> nb::Result { + let r = T::regs(); + if r.uartfr().read().rxfe() { + return Err(nb::Error::WouldBlock); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + Err(nb::Error::Other(Error::Overrun)) + } else if dr.be() { + Err(nb::Error::Other(Error::Break)) + } else if dr.pe() { + Err(nb::Error::Other(Error::Parity)) + } else if dr.fe() { + Err(nb::Error::Other(Error::Framing)) + } else { + Ok(dr.data()) + } + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, T, M> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +impl<'d, T: Instance> embedded_io::ErrorType for UartTx<'d, T, Blocking> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io::Write for UartTx<'d, T, Blocking> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf).map(|_| buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, T, M> { + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, T, M> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +impl<'d, T: Instance> embedded_io::ErrorType for Uart<'d, T, Blocking> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io::Write for Uart<'d, T, Blocking> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf).map(|_| buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +trait SealedMode {} + +trait SealedInstance { + const TX_DREQ: u8; + const RX_DREQ: u8; + + fn regs() -> pac::uart::Uart; + + fn buffered_state() -> &'static buffered::State; + + fn dma_state() -> &'static DmaState; +} + +/// UART mode. +#[allow(private_bounds)] +pub trait Mode: SealedMode {} + +macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; +} + +/// Blocking mode. +pub struct Blocking; +/// Async mode. +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); + +/// UART instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance { + /// Interrupt for this instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_instance { + ($inst:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { + impl SealedInstance for peripherals::$inst { + const TX_DREQ: u8 = $tx_dreq; + const RX_DREQ: u8 = $rx_dreq; + + fn regs() -> pac::uart::Uart { + pac::$inst + } + + fn buffered_state() -> &'static buffered::State { + static STATE: buffered::State = buffered::State::new(); + &STATE + } + + fn dma_state() -> &'static DmaState { + static STATE: DmaState = DmaState { + rx_err_waker: AtomicWaker::new(), + rx_errs: AtomicU16::new(0), + }; + &STATE + } + } + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +impl_instance!(UART0, UART0_IRQ, 20, 21); +impl_instance!(UART1, UART1_IRQ, 22, 23); + +/// Trait for TX pins. +pub trait TxPin: crate::gpio::Pin {} +/// Trait for RX pins. +pub trait RxPin: crate::gpio::Pin {} +/// Trait for Clear To Send (CTS) pins. +pub trait CtsPin: crate::gpio::Pin {} +/// Trait for Request To Send (RTS) pins. +pub trait RtsPin: crate::gpio::Pin {} + +macro_rules! impl_pin { + ($pin:ident, $instance:ident, $function:ident) => { + impl $function for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, UART0, TxPin); +impl_pin!(PIN_1, UART0, RxPin); +impl_pin!(PIN_2, UART0, CtsPin); +impl_pin!(PIN_3, UART0, RtsPin); +impl_pin!(PIN_4, UART1, TxPin); +impl_pin!(PIN_5, UART1, RxPin); +impl_pin!(PIN_6, UART1, CtsPin); +impl_pin!(PIN_7, UART1, RtsPin); +impl_pin!(PIN_8, UART1, TxPin); +impl_pin!(PIN_9, UART1, RxPin); +impl_pin!(PIN_10, UART1, CtsPin); +impl_pin!(PIN_11, UART1, RtsPin); +impl_pin!(PIN_12, UART0, TxPin); +impl_pin!(PIN_13, UART0, RxPin); +impl_pin!(PIN_14, UART0, CtsPin); +impl_pin!(PIN_15, UART0, RtsPin); +impl_pin!(PIN_16, UART0, TxPin); +impl_pin!(PIN_17, UART0, RxPin); +impl_pin!(PIN_18, UART0, CtsPin); +impl_pin!(PIN_19, UART0, RtsPin); +impl_pin!(PIN_20, UART1, TxPin); +impl_pin!(PIN_21, UART1, RxPin); +impl_pin!(PIN_22, UART1, CtsPin); +impl_pin!(PIN_23, UART1, RtsPin); +impl_pin!(PIN_24, UART1, TxPin); +impl_pin!(PIN_25, UART1, RxPin); +impl_pin!(PIN_26, UART1, CtsPin); +impl_pin!(PIN_27, UART1, RtsPin); +impl_pin!(PIN_28, UART0, TxPin); +impl_pin!(PIN_29, UART0, RxPin); + +// Additional functions added by all 2350s +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_2, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_3, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_6, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_7, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_10, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_11, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_14, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_15, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_18, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_19, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_22, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_23, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_26, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_27, UART1, RxPin); + +// Additional pins added by larger 2350 packages. +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, UART0, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, UART0, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, UART1, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, UART1, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, UART1, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, UART1, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, UART0, RtsPin); + +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, UART0, RxPin); diff --git a/.stversions/embassy/embassy-stm32/Cargo~20241114-122314.toml b/.stversions/embassy/embassy-stm32/Cargo~20241114-122314.toml new file mode 100644 index 0000000..e2ba4fd --- /dev/null +++ b/.stversions/embassy/embassy-stm32/Cargo~20241114-122314.toml @@ -0,0 +1,1627 @@ +[package] +name = "embassy-stm32" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Embassy Hardware Abstraction Layer (HAL) for ST STM32 series microcontrollers" +keywords = ["embedded", "async", "stm32", "hal", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-stm32" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-stm32-v$VERSION/embassy-stm32/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-stm32/src/" + +features = ["defmt", "unstable-pac", "exti", "time-driver-any", "time"] +flavors = [ + { regex_feature = "stm32f0.*", target = "thumbv6m-none-eabi" }, + { regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" }, + { regex_feature = "stm32f2.*", target = "thumbv7m-none-eabi" }, + { regex_feature = "stm32f3.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi", features = ["low-power"] }, + { regex_feature = "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", features = ["low-power"] }, + { regex_feature = "stm32h5.*", target = "thumbv8m.main-none-eabihf", features = ["low-power"] }, + { regex_feature = "stm32h7.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32l0.*", target = "thumbv6m-none-eabi", features = ["low-power"] }, + { regex_feature = "stm32l1.*", target = "thumbv7m-none-eabi" }, + { regex_feature = "stm32l4.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32l5.*", target = "thumbv8m.main-none-eabihf", features = ["low-power"] }, + { regex_feature = "stm32u0.*", target = "thumbv6m-none-eabi" }, + { regex_feature = "stm32u5.*", target = "thumbv8m.main-none-eabihf" }, + { regex_feature = "stm32wb.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32wba.*", target = "thumbv8m.main-none-eabihf" }, + { regex_feature = "stm32wl.*", target = "thumbv7em-none-eabi" }, +] + +[package.metadata.docs.rs] +features = ["defmt", "unstable-pac", "exti", "time-driver-any", "time", "stm32h755zi-cm7"] +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../embassy-sync" } +embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } +embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] } +embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal", default-features = false } +embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } +embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } +embassy-usb-synopsys-otg = {version = "0.1.0", path = "../embassy-usb-synopsys-otg" } +embassy-executor = { version = "0.6.2", path = "../embassy-executor", optional = true } + +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-nb = { version = "1.0" } +embedded-can = "0.4" + +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } + + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +cortex-m-rt = ">=0.6.15,<0.8" +cortex-m = "0.7.6" +futures-util = { version = "0.3.30", default-features = false } +rand_core = "0.6.3" +sdio-host = "0.5.0" +critical-section = "1.1" +#stm32-metapac = { version = "15" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-7bb5f235587c3a6886a7be1c8f58fdf22c5257f3" } + +vcell = "0.1.3" +nb = "1.0.0" +stm32-fmc = "0.3.0" +cfg-if = "1.0.0" +embedded-io = { version = "0.6.0" } +embedded-io-async = { version = "0.6.1" } +chrono = { version = "^0.4", default-features = false, optional = true } +bit_field = "0.10.2" +document-features = "0.2.7" + +static_assertions = { version = "1.1" } +volatile-register = { version = "0.2.1" } +bitflags = "2.4.2" + +block-device-driver = { version = "0.2" } +aligned = "0.4.1" + +[dev-dependencies] +critical-section = { version = "1.1", features = ["std"] } +proptest = "1.5.0" +proptest-state-machine = "0.3.0" + +[build-dependencies] +proc-macro2 = "1.0.36" +quote = "1.0.15" + +#stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-7bb5f235587c3a6886a7be1c8f58fdf22c5257f3", default-features = false, features = ["metadata"] } + +[features] +default = ["rt"] + +## Enable `stm32-metapac`'s `rt` feature +rt = ["stm32-metapac/rt"] + +## Use [`defmt`](https://docs.rs/defmt/latest/defmt/) for logging +defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-internal/defmt", "embedded-io-async/defmt-03", "embassy-usb-driver/defmt", "embassy-net-driver/defmt", "embassy-time?/defmt"] + +exti = [] +low-power = [ "dep:embassy-executor", "embassy-executor?/arch-cortex-m", "time" ] +low-power-debug-with-sleep = [] + +## Automatically generate `memory.x` file using [`stm32-metapac`](https://docs.rs/stm32-metapac/) +memory-x = ["stm32-metapac/memory-x"] + +## Use secure registers when TrustZone is enabled +trustzone-secure = [] + +## Re-export stm32-metapac at `embassy_stm32::pac`. +## This is unstable because semver-minor (non-breaking) releases of embassy-stm32 may major-bump (breaking) the stm32-metapac version. +## If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC. +## There are no plans to make this stable. +unstable-pac = [] + +#! ## Time + +## Enables additional driver features that depend on embassy-time +time = ["dep:embassy-time", "embassy-embedded-hal/time"] + +# Features starting with `_` are for internal use only. They're not intended +# to be enabled by other crates, and are not covered by semver guarantees. +_time-driver = ["dep:embassy-time-driver", "time"] + +## Use any time driver +time-driver-any = ["_time-driver"] +## Use TIM1 as time driver +time-driver-tim1 = ["_time-driver"] +## Use TIM2 as time driver +time-driver-tim2 = ["_time-driver"] +## Use TIM3 as time driver +time-driver-tim3 = ["_time-driver"] +## Use TIM4 as time driver +time-driver-tim4 = ["_time-driver"] +## Use TIM5 as time driver +time-driver-tim5 = ["_time-driver"] +## Use TIM8 as time driver +time-driver-tim8 = ["_time-driver"] +## Use TIM9 as time driver +time-driver-tim9 = ["_time-driver"] +## Use TIM12 as time driver +time-driver-tim12 = ["_time-driver"] +## Use TIM15 as time driver +time-driver-tim15 = ["_time-driver"] +## Use TIM20 as time driver +time-driver-tim20 = ["_time-driver"] +## Use TIM21 as time driver +time-driver-tim21 = ["_time-driver"] +## Use TIM22 as time driver +time-driver-tim22 = ["_time-driver"] +## Use TIM23 as time driver +time-driver-tim23 = ["_time-driver"] +## Use TIM24 as time driver +time-driver-tim24 = ["_time-driver"] + + +#! ## Analog Switch Pins (Pxy_C) on STM32H7 series +#! Get `PXY` and `PXY_C` singletons. Digital impls are on `PXY`, Analog impls are on `PXY_C` +#! If disabled, you get only the `PXY` singleton. It has both digital and analog impls. + +## Split PA0 +split-pa0 = ["_split-pins-enabled"] +## Split PA1 +split-pa1 = ["_split-pins-enabled"] +## Split PC2 +split-pc2 = ["_split-pins-enabled"] +## Split PC3 +split-pc3 = ["_split-pins-enabled"] + +## internal use only +_split-pins-enabled = [] + +## internal use only +_dual-core = [] + +#! ## Chip-selection features +#! Select your chip by specifying the model as a feature, e.g. `stm32c011d6`. +#! Check the `Cargo.toml` for the latest list of supported chips. +#! +#! **Important:** Do not forget to adapt the target chip in your toolchain, +#! e.g. in `.cargo/config.toml`. + +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" ] +stm32f030f4 = [ "stm32-metapac/stm32f030f4" ] +stm32f030k6 = [ "stm32-metapac/stm32f030k6" ] +stm32f030r8 = [ "stm32-metapac/stm32f030r8" ] +stm32f030rc = [ "stm32-metapac/stm32f030rc" ] +stm32f031c4 = [ "stm32-metapac/stm32f031c4" ] +stm32f031c6 = [ "stm32-metapac/stm32f031c6" ] +stm32f031e6 = [ "stm32-metapac/stm32f031e6" ] +stm32f031f4 = [ "stm32-metapac/stm32f031f4" ] +stm32f031f6 = [ "stm32-metapac/stm32f031f6" ] +stm32f031g4 = [ "stm32-metapac/stm32f031g4" ] +stm32f031g6 = [ "stm32-metapac/stm32f031g6" ] +stm32f031k4 = [ "stm32-metapac/stm32f031k4" ] +stm32f031k6 = [ "stm32-metapac/stm32f031k6" ] +stm32f038c6 = [ "stm32-metapac/stm32f038c6" ] +stm32f038e6 = [ "stm32-metapac/stm32f038e6" ] +stm32f038f6 = [ "stm32-metapac/stm32f038f6" ] +stm32f038g6 = [ "stm32-metapac/stm32f038g6" ] +stm32f038k6 = [ "stm32-metapac/stm32f038k6" ] +stm32f042c4 = [ "stm32-metapac/stm32f042c4" ] +stm32f042c6 = [ "stm32-metapac/stm32f042c6" ] +stm32f042f4 = [ "stm32-metapac/stm32f042f4" ] +stm32f042f6 = [ "stm32-metapac/stm32f042f6" ] +stm32f042g4 = [ "stm32-metapac/stm32f042g4" ] +stm32f042g6 = [ "stm32-metapac/stm32f042g6" ] +stm32f042k4 = [ "stm32-metapac/stm32f042k4" ] +stm32f042k6 = [ "stm32-metapac/stm32f042k6" ] +stm32f042t6 = [ "stm32-metapac/stm32f042t6" ] +stm32f048c6 = [ "stm32-metapac/stm32f048c6" ] +stm32f048g6 = [ "stm32-metapac/stm32f048g6" ] +stm32f048t6 = [ "stm32-metapac/stm32f048t6" ] +stm32f051c4 = [ "stm32-metapac/stm32f051c4" ] +stm32f051c6 = [ "stm32-metapac/stm32f051c6" ] +stm32f051c8 = [ "stm32-metapac/stm32f051c8" ] +stm32f051k4 = [ "stm32-metapac/stm32f051k4" ] +stm32f051k6 = [ "stm32-metapac/stm32f051k6" ] +stm32f051k8 = [ "stm32-metapac/stm32f051k8" ] +stm32f051r4 = [ "stm32-metapac/stm32f051r4" ] +stm32f051r6 = [ "stm32-metapac/stm32f051r6" ] +stm32f051r8 = [ "stm32-metapac/stm32f051r8" ] +stm32f051t8 = [ "stm32-metapac/stm32f051t8" ] +stm32f058c8 = [ "stm32-metapac/stm32f058c8" ] +stm32f058r8 = [ "stm32-metapac/stm32f058r8" ] +stm32f058t8 = [ "stm32-metapac/stm32f058t8" ] +stm32f070c6 = [ "stm32-metapac/stm32f070c6" ] +stm32f070cb = [ "stm32-metapac/stm32f070cb" ] +stm32f070f6 = [ "stm32-metapac/stm32f070f6" ] +stm32f070rb = [ "stm32-metapac/stm32f070rb" ] +stm32f071c8 = [ "stm32-metapac/stm32f071c8" ] +stm32f071cb = [ "stm32-metapac/stm32f071cb" ] +stm32f071rb = [ "stm32-metapac/stm32f071rb" ] +stm32f071v8 = [ "stm32-metapac/stm32f071v8" ] +stm32f071vb = [ "stm32-metapac/stm32f071vb" ] +stm32f072c8 = [ "stm32-metapac/stm32f072c8" ] +stm32f072cb = [ "stm32-metapac/stm32f072cb" ] +stm32f072r8 = [ "stm32-metapac/stm32f072r8" ] +stm32f072rb = [ "stm32-metapac/stm32f072rb" ] +stm32f072v8 = [ "stm32-metapac/stm32f072v8" ] +stm32f072vb = [ "stm32-metapac/stm32f072vb" ] +stm32f078cb = [ "stm32-metapac/stm32f078cb" ] +stm32f078rb = [ "stm32-metapac/stm32f078rb" ] +stm32f078vb = [ "stm32-metapac/stm32f078vb" ] +stm32f091cb = [ "stm32-metapac/stm32f091cb" ] +stm32f091cc = [ "stm32-metapac/stm32f091cc" ] +stm32f091rb = [ "stm32-metapac/stm32f091rb" ] +stm32f091rc = [ "stm32-metapac/stm32f091rc" ] +stm32f091vb = [ "stm32-metapac/stm32f091vb" ] +stm32f091vc = [ "stm32-metapac/stm32f091vc" ] +stm32f098cc = [ "stm32-metapac/stm32f098cc" ] +stm32f098rc = [ "stm32-metapac/stm32f098rc" ] +stm32f098vc = [ "stm32-metapac/stm32f098vc" ] +stm32f100c4 = [ "stm32-metapac/stm32f100c4" ] +stm32f100c6 = [ "stm32-metapac/stm32f100c6" ] +stm32f100c8 = [ "stm32-metapac/stm32f100c8" ] +stm32f100cb = [ "stm32-metapac/stm32f100cb" ] +stm32f100r4 = [ "stm32-metapac/stm32f100r4" ] +stm32f100r6 = [ "stm32-metapac/stm32f100r6" ] +stm32f100r8 = [ "stm32-metapac/stm32f100r8" ] +stm32f100rb = [ "stm32-metapac/stm32f100rb" ] +stm32f100rc = [ "stm32-metapac/stm32f100rc" ] +stm32f100rd = [ "stm32-metapac/stm32f100rd" ] +stm32f100re = [ "stm32-metapac/stm32f100re" ] +stm32f100v8 = [ "stm32-metapac/stm32f100v8" ] +stm32f100vb = [ "stm32-metapac/stm32f100vb" ] +stm32f100vc = [ "stm32-metapac/stm32f100vc" ] +stm32f100vd = [ "stm32-metapac/stm32f100vd" ] +stm32f100ve = [ "stm32-metapac/stm32f100ve" ] +stm32f100zc = [ "stm32-metapac/stm32f100zc" ] +stm32f100zd = [ "stm32-metapac/stm32f100zd" ] +stm32f100ze = [ "stm32-metapac/stm32f100ze" ] +stm32f101c4 = [ "stm32-metapac/stm32f101c4" ] +stm32f101c6 = [ "stm32-metapac/stm32f101c6" ] +stm32f101c8 = [ "stm32-metapac/stm32f101c8" ] +stm32f101cb = [ "stm32-metapac/stm32f101cb" ] +stm32f101r4 = [ "stm32-metapac/stm32f101r4" ] +stm32f101r6 = [ "stm32-metapac/stm32f101r6" ] +stm32f101r8 = [ "stm32-metapac/stm32f101r8" ] +stm32f101rb = [ "stm32-metapac/stm32f101rb" ] +stm32f101rc = [ "stm32-metapac/stm32f101rc" ] +stm32f101rd = [ "stm32-metapac/stm32f101rd" ] +stm32f101re = [ "stm32-metapac/stm32f101re" ] +stm32f101rf = [ "stm32-metapac/stm32f101rf" ] +stm32f101rg = [ "stm32-metapac/stm32f101rg" ] +stm32f101t4 = [ "stm32-metapac/stm32f101t4" ] +stm32f101t6 = [ "stm32-metapac/stm32f101t6" ] +stm32f101t8 = [ "stm32-metapac/stm32f101t8" ] +stm32f101tb = [ "stm32-metapac/stm32f101tb" ] +stm32f101v8 = [ "stm32-metapac/stm32f101v8" ] +stm32f101vb = [ "stm32-metapac/stm32f101vb" ] +stm32f101vc = [ "stm32-metapac/stm32f101vc" ] +stm32f101vd = [ "stm32-metapac/stm32f101vd" ] +stm32f101ve = [ "stm32-metapac/stm32f101ve" ] +stm32f101vf = [ "stm32-metapac/stm32f101vf" ] +stm32f101vg = [ "stm32-metapac/stm32f101vg" ] +stm32f101zc = [ "stm32-metapac/stm32f101zc" ] +stm32f101zd = [ "stm32-metapac/stm32f101zd" ] +stm32f101ze = [ "stm32-metapac/stm32f101ze" ] +stm32f101zf = [ "stm32-metapac/stm32f101zf" ] +stm32f101zg = [ "stm32-metapac/stm32f101zg" ] +stm32f102c4 = [ "stm32-metapac/stm32f102c4" ] +stm32f102c6 = [ "stm32-metapac/stm32f102c6" ] +stm32f102c8 = [ "stm32-metapac/stm32f102c8" ] +stm32f102cb = [ "stm32-metapac/stm32f102cb" ] +stm32f102r4 = [ "stm32-metapac/stm32f102r4" ] +stm32f102r6 = [ "stm32-metapac/stm32f102r6" ] +stm32f102r8 = [ "stm32-metapac/stm32f102r8" ] +stm32f102rb = [ "stm32-metapac/stm32f102rb" ] +stm32f103c4 = [ "stm32-metapac/stm32f103c4" ] +stm32f103c6 = [ "stm32-metapac/stm32f103c6" ] +stm32f103c8 = [ "stm32-metapac/stm32f103c8" ] +stm32f103cb = [ "stm32-metapac/stm32f103cb" ] +stm32f103r4 = [ "stm32-metapac/stm32f103r4" ] +stm32f103r6 = [ "stm32-metapac/stm32f103r6" ] +stm32f103r8 = [ "stm32-metapac/stm32f103r8" ] +stm32f103rb = [ "stm32-metapac/stm32f103rb" ] +stm32f103rc = [ "stm32-metapac/stm32f103rc" ] +stm32f103rd = [ "stm32-metapac/stm32f103rd" ] +stm32f103re = [ "stm32-metapac/stm32f103re" ] +stm32f103rf = [ "stm32-metapac/stm32f103rf" ] +stm32f103rg = [ "stm32-metapac/stm32f103rg" ] +stm32f103t4 = [ "stm32-metapac/stm32f103t4" ] +stm32f103t6 = [ "stm32-metapac/stm32f103t6" ] +stm32f103t8 = [ "stm32-metapac/stm32f103t8" ] +stm32f103tb = [ "stm32-metapac/stm32f103tb" ] +stm32f103v8 = [ "stm32-metapac/stm32f103v8" ] +stm32f103vb = [ "stm32-metapac/stm32f103vb" ] +stm32f103vc = [ "stm32-metapac/stm32f103vc" ] +stm32f103vd = [ "stm32-metapac/stm32f103vd" ] +stm32f103ve = [ "stm32-metapac/stm32f103ve" ] +stm32f103vf = [ "stm32-metapac/stm32f103vf" ] +stm32f103vg = [ "stm32-metapac/stm32f103vg" ] +stm32f103zc = [ "stm32-metapac/stm32f103zc" ] +stm32f103zd = [ "stm32-metapac/stm32f103zd" ] +stm32f103ze = [ "stm32-metapac/stm32f103ze" ] +stm32f103zf = [ "stm32-metapac/stm32f103zf" ] +stm32f103zg = [ "stm32-metapac/stm32f103zg" ] +stm32f105r8 = [ "stm32-metapac/stm32f105r8" ] +stm32f105rb = [ "stm32-metapac/stm32f105rb" ] +stm32f105rc = [ "stm32-metapac/stm32f105rc" ] +stm32f105v8 = [ "stm32-metapac/stm32f105v8" ] +stm32f105vb = [ "stm32-metapac/stm32f105vb" ] +stm32f105vc = [ "stm32-metapac/stm32f105vc" ] +stm32f107rb = [ "stm32-metapac/stm32f107rb" ] +stm32f107rc = [ "stm32-metapac/stm32f107rc" ] +stm32f107vb = [ "stm32-metapac/stm32f107vb" ] +stm32f107vc = [ "stm32-metapac/stm32f107vc" ] +stm32f205rb = [ "stm32-metapac/stm32f205rb" ] +stm32f205rc = [ "stm32-metapac/stm32f205rc" ] +stm32f205re = [ "stm32-metapac/stm32f205re" ] +stm32f205rf = [ "stm32-metapac/stm32f205rf" ] +stm32f205rg = [ "stm32-metapac/stm32f205rg" ] +stm32f205vb = [ "stm32-metapac/stm32f205vb" ] +stm32f205vc = [ "stm32-metapac/stm32f205vc" ] +stm32f205ve = [ "stm32-metapac/stm32f205ve" ] +stm32f205vf = [ "stm32-metapac/stm32f205vf" ] +stm32f205vg = [ "stm32-metapac/stm32f205vg" ] +stm32f205zc = [ "stm32-metapac/stm32f205zc" ] +stm32f205ze = [ "stm32-metapac/stm32f205ze" ] +stm32f205zf = [ "stm32-metapac/stm32f205zf" ] +stm32f205zg = [ "stm32-metapac/stm32f205zg" ] +stm32f207ic = [ "stm32-metapac/stm32f207ic" ] +stm32f207ie = [ "stm32-metapac/stm32f207ie" ] +stm32f207if = [ "stm32-metapac/stm32f207if" ] +stm32f207ig = [ "stm32-metapac/stm32f207ig" ] +stm32f207vc = [ "stm32-metapac/stm32f207vc" ] +stm32f207ve = [ "stm32-metapac/stm32f207ve" ] +stm32f207vf = [ "stm32-metapac/stm32f207vf" ] +stm32f207vg = [ "stm32-metapac/stm32f207vg" ] +stm32f207zc = [ "stm32-metapac/stm32f207zc" ] +stm32f207ze = [ "stm32-metapac/stm32f207ze" ] +stm32f207zf = [ "stm32-metapac/stm32f207zf" ] +stm32f207zg = [ "stm32-metapac/stm32f207zg" ] +stm32f215re = [ "stm32-metapac/stm32f215re" ] +stm32f215rg = [ "stm32-metapac/stm32f215rg" ] +stm32f215ve = [ "stm32-metapac/stm32f215ve" ] +stm32f215vg = [ "stm32-metapac/stm32f215vg" ] +stm32f215ze = [ "stm32-metapac/stm32f215ze" ] +stm32f215zg = [ "stm32-metapac/stm32f215zg" ] +stm32f217ie = [ "stm32-metapac/stm32f217ie" ] +stm32f217ig = [ "stm32-metapac/stm32f217ig" ] +stm32f217ve = [ "stm32-metapac/stm32f217ve" ] +stm32f217vg = [ "stm32-metapac/stm32f217vg" ] +stm32f217ze = [ "stm32-metapac/stm32f217ze" ] +stm32f217zg = [ "stm32-metapac/stm32f217zg" ] +stm32f301c6 = [ "stm32-metapac/stm32f301c6" ] +stm32f301c8 = [ "stm32-metapac/stm32f301c8" ] +stm32f301k6 = [ "stm32-metapac/stm32f301k6" ] +stm32f301k8 = [ "stm32-metapac/stm32f301k8" ] +stm32f301r6 = [ "stm32-metapac/stm32f301r6" ] +stm32f301r8 = [ "stm32-metapac/stm32f301r8" ] +stm32f302c6 = [ "stm32-metapac/stm32f302c6" ] +stm32f302c8 = [ "stm32-metapac/stm32f302c8" ] +stm32f302cb = [ "stm32-metapac/stm32f302cb" ] +stm32f302cc = [ "stm32-metapac/stm32f302cc" ] +stm32f302k6 = [ "stm32-metapac/stm32f302k6" ] +stm32f302k8 = [ "stm32-metapac/stm32f302k8" ] +stm32f302r6 = [ "stm32-metapac/stm32f302r6" ] +stm32f302r8 = [ "stm32-metapac/stm32f302r8" ] +stm32f302rb = [ "stm32-metapac/stm32f302rb" ] +stm32f302rc = [ "stm32-metapac/stm32f302rc" ] +stm32f302rd = [ "stm32-metapac/stm32f302rd" ] +stm32f302re = [ "stm32-metapac/stm32f302re" ] +stm32f302vb = [ "stm32-metapac/stm32f302vb" ] +stm32f302vc = [ "stm32-metapac/stm32f302vc" ] +stm32f302vd = [ "stm32-metapac/stm32f302vd" ] +stm32f302ve = [ "stm32-metapac/stm32f302ve" ] +stm32f302zd = [ "stm32-metapac/stm32f302zd" ] +stm32f302ze = [ "stm32-metapac/stm32f302ze" ] +stm32f303c6 = [ "stm32-metapac/stm32f303c6" ] +stm32f303c8 = [ "stm32-metapac/stm32f303c8" ] +stm32f303cb = [ "stm32-metapac/stm32f303cb" ] +stm32f303cc = [ "stm32-metapac/stm32f303cc" ] +stm32f303k6 = [ "stm32-metapac/stm32f303k6" ] +stm32f303k8 = [ "stm32-metapac/stm32f303k8" ] +stm32f303r6 = [ "stm32-metapac/stm32f303r6" ] +stm32f303r8 = [ "stm32-metapac/stm32f303r8" ] +stm32f303rb = [ "stm32-metapac/stm32f303rb" ] +stm32f303rc = [ "stm32-metapac/stm32f303rc" ] +stm32f303rd = [ "stm32-metapac/stm32f303rd" ] +stm32f303re = [ "stm32-metapac/stm32f303re" ] +stm32f303vb = [ "stm32-metapac/stm32f303vb" ] +stm32f303vc = [ "stm32-metapac/stm32f303vc" ] +stm32f303vd = [ "stm32-metapac/stm32f303vd" ] +stm32f303ve = [ "stm32-metapac/stm32f303ve" ] +stm32f303zd = [ "stm32-metapac/stm32f303zd" ] +stm32f303ze = [ "stm32-metapac/stm32f303ze" ] +stm32f318c8 = [ "stm32-metapac/stm32f318c8" ] +stm32f318k8 = [ "stm32-metapac/stm32f318k8" ] +stm32f328c8 = [ "stm32-metapac/stm32f328c8" ] +stm32f334c4 = [ "stm32-metapac/stm32f334c4" ] +stm32f334c6 = [ "stm32-metapac/stm32f334c6" ] +stm32f334c8 = [ "stm32-metapac/stm32f334c8" ] +stm32f334k4 = [ "stm32-metapac/stm32f334k4" ] +stm32f334k6 = [ "stm32-metapac/stm32f334k6" ] +stm32f334k8 = [ "stm32-metapac/stm32f334k8" ] +stm32f334r6 = [ "stm32-metapac/stm32f334r6" ] +stm32f334r8 = [ "stm32-metapac/stm32f334r8" ] +stm32f358cc = [ "stm32-metapac/stm32f358cc" ] +stm32f358rc = [ "stm32-metapac/stm32f358rc" ] +stm32f358vc = [ "stm32-metapac/stm32f358vc" ] +stm32f373c8 = [ "stm32-metapac/stm32f373c8" ] +stm32f373cb = [ "stm32-metapac/stm32f373cb" ] +stm32f373cc = [ "stm32-metapac/stm32f373cc" ] +stm32f373r8 = [ "stm32-metapac/stm32f373r8" ] +stm32f373rb = [ "stm32-metapac/stm32f373rb" ] +stm32f373rc = [ "stm32-metapac/stm32f373rc" ] +stm32f373v8 = [ "stm32-metapac/stm32f373v8" ] +stm32f373vb = [ "stm32-metapac/stm32f373vb" ] +stm32f373vc = [ "stm32-metapac/stm32f373vc" ] +stm32f378cc = [ "stm32-metapac/stm32f378cc" ] +stm32f378rc = [ "stm32-metapac/stm32f378rc" ] +stm32f378vc = [ "stm32-metapac/stm32f378vc" ] +stm32f398ve = [ "stm32-metapac/stm32f398ve" ] +stm32f401cb = [ "stm32-metapac/stm32f401cb" ] +stm32f401cc = [ "stm32-metapac/stm32f401cc" ] +stm32f401cd = [ "stm32-metapac/stm32f401cd" ] +stm32f401ce = [ "stm32-metapac/stm32f401ce" ] +stm32f401rb = [ "stm32-metapac/stm32f401rb" ] +stm32f401rc = [ "stm32-metapac/stm32f401rc" ] +stm32f401rd = [ "stm32-metapac/stm32f401rd" ] +stm32f401re = [ "stm32-metapac/stm32f401re" ] +stm32f401vb = [ "stm32-metapac/stm32f401vb" ] +stm32f401vc = [ "stm32-metapac/stm32f401vc" ] +stm32f401vd = [ "stm32-metapac/stm32f401vd" ] +stm32f401ve = [ "stm32-metapac/stm32f401ve" ] +stm32f405oe = [ "stm32-metapac/stm32f405oe" ] +stm32f405og = [ "stm32-metapac/stm32f405og" ] +stm32f405rg = [ "stm32-metapac/stm32f405rg" ] +stm32f405vg = [ "stm32-metapac/stm32f405vg" ] +stm32f405zg = [ "stm32-metapac/stm32f405zg" ] +stm32f407ie = [ "stm32-metapac/stm32f407ie" ] +stm32f407ig = [ "stm32-metapac/stm32f407ig" ] +stm32f407ve = [ "stm32-metapac/stm32f407ve" ] +stm32f407vg = [ "stm32-metapac/stm32f407vg" ] +stm32f407ze = [ "stm32-metapac/stm32f407ze" ] +stm32f407zg = [ "stm32-metapac/stm32f407zg" ] +stm32f410c8 = [ "stm32-metapac/stm32f410c8" ] +stm32f410cb = [ "stm32-metapac/stm32f410cb" ] +stm32f410r8 = [ "stm32-metapac/stm32f410r8" ] +stm32f410rb = [ "stm32-metapac/stm32f410rb" ] +stm32f410t8 = [ "stm32-metapac/stm32f410t8" ] +stm32f410tb = [ "stm32-metapac/stm32f410tb" ] +stm32f411cc = [ "stm32-metapac/stm32f411cc" ] +stm32f411ce = [ "stm32-metapac/stm32f411ce" ] +stm32f411rc = [ "stm32-metapac/stm32f411rc" ] +stm32f411re = [ "stm32-metapac/stm32f411re" ] +stm32f411vc = [ "stm32-metapac/stm32f411vc" ] +stm32f411ve = [ "stm32-metapac/stm32f411ve" ] +stm32f412ce = [ "stm32-metapac/stm32f412ce" ] +stm32f412cg = [ "stm32-metapac/stm32f412cg" ] +stm32f412re = [ "stm32-metapac/stm32f412re" ] +stm32f412rg = [ "stm32-metapac/stm32f412rg" ] +stm32f412ve = [ "stm32-metapac/stm32f412ve" ] +stm32f412vg = [ "stm32-metapac/stm32f412vg" ] +stm32f412ze = [ "stm32-metapac/stm32f412ze" ] +stm32f412zg = [ "stm32-metapac/stm32f412zg" ] +stm32f413cg = [ "stm32-metapac/stm32f413cg" ] +stm32f413ch = [ "stm32-metapac/stm32f413ch" ] +stm32f413mg = [ "stm32-metapac/stm32f413mg" ] +stm32f413mh = [ "stm32-metapac/stm32f413mh" ] +stm32f413rg = [ "stm32-metapac/stm32f413rg" ] +stm32f413rh = [ "stm32-metapac/stm32f413rh" ] +stm32f413vg = [ "stm32-metapac/stm32f413vg" ] +stm32f413vh = [ "stm32-metapac/stm32f413vh" ] +stm32f413zg = [ "stm32-metapac/stm32f413zg" ] +stm32f413zh = [ "stm32-metapac/stm32f413zh" ] +stm32f415og = [ "stm32-metapac/stm32f415og" ] +stm32f415rg = [ "stm32-metapac/stm32f415rg" ] +stm32f415vg = [ "stm32-metapac/stm32f415vg" ] +stm32f415zg = [ "stm32-metapac/stm32f415zg" ] +stm32f417ie = [ "stm32-metapac/stm32f417ie" ] +stm32f417ig = [ "stm32-metapac/stm32f417ig" ] +stm32f417ve = [ "stm32-metapac/stm32f417ve" ] +stm32f417vg = [ "stm32-metapac/stm32f417vg" ] +stm32f417ze = [ "stm32-metapac/stm32f417ze" ] +stm32f417zg = [ "stm32-metapac/stm32f417zg" ] +stm32f423ch = [ "stm32-metapac/stm32f423ch" ] +stm32f423mh = [ "stm32-metapac/stm32f423mh" ] +stm32f423rh = [ "stm32-metapac/stm32f423rh" ] +stm32f423vh = [ "stm32-metapac/stm32f423vh" ] +stm32f423zh = [ "stm32-metapac/stm32f423zh" ] +stm32f427ag = [ "stm32-metapac/stm32f427ag" ] +stm32f427ai = [ "stm32-metapac/stm32f427ai" ] +stm32f427ig = [ "stm32-metapac/stm32f427ig" ] +stm32f427ii = [ "stm32-metapac/stm32f427ii" ] +stm32f427vg = [ "stm32-metapac/stm32f427vg" ] +stm32f427vi = [ "stm32-metapac/stm32f427vi" ] +stm32f427zg = [ "stm32-metapac/stm32f427zg" ] +stm32f427zi = [ "stm32-metapac/stm32f427zi" ] +stm32f429ag = [ "stm32-metapac/stm32f429ag" ] +stm32f429ai = [ "stm32-metapac/stm32f429ai" ] +stm32f429be = [ "stm32-metapac/stm32f429be" ] +stm32f429bg = [ "stm32-metapac/stm32f429bg" ] +stm32f429bi = [ "stm32-metapac/stm32f429bi" ] +stm32f429ie = [ "stm32-metapac/stm32f429ie" ] +stm32f429ig = [ "stm32-metapac/stm32f429ig" ] +stm32f429ii = [ "stm32-metapac/stm32f429ii" ] +stm32f429ne = [ "stm32-metapac/stm32f429ne" ] +stm32f429ng = [ "stm32-metapac/stm32f429ng" ] +stm32f429ni = [ "stm32-metapac/stm32f429ni" ] +stm32f429ve = [ "stm32-metapac/stm32f429ve" ] +stm32f429vg = [ "stm32-metapac/stm32f429vg" ] +stm32f429vi = [ "stm32-metapac/stm32f429vi" ] +stm32f429ze = [ "stm32-metapac/stm32f429ze" ] +stm32f429zg = [ "stm32-metapac/stm32f429zg" ] +stm32f429zi = [ "stm32-metapac/stm32f429zi" ] +stm32f437ai = [ "stm32-metapac/stm32f437ai" ] +stm32f437ig = [ "stm32-metapac/stm32f437ig" ] +stm32f437ii = [ "stm32-metapac/stm32f437ii" ] +stm32f437vg = [ "stm32-metapac/stm32f437vg" ] +stm32f437vi = [ "stm32-metapac/stm32f437vi" ] +stm32f437zg = [ "stm32-metapac/stm32f437zg" ] +stm32f437zi = [ "stm32-metapac/stm32f437zi" ] +stm32f439ai = [ "stm32-metapac/stm32f439ai" ] +stm32f439bg = [ "stm32-metapac/stm32f439bg" ] +stm32f439bi = [ "stm32-metapac/stm32f439bi" ] +stm32f439ig = [ "stm32-metapac/stm32f439ig" ] +stm32f439ii = [ "stm32-metapac/stm32f439ii" ] +stm32f439ng = [ "stm32-metapac/stm32f439ng" ] +stm32f439ni = [ "stm32-metapac/stm32f439ni" ] +stm32f439vg = [ "stm32-metapac/stm32f439vg" ] +stm32f439vi = [ "stm32-metapac/stm32f439vi" ] +stm32f439zg = [ "stm32-metapac/stm32f439zg" ] +stm32f439zi = [ "stm32-metapac/stm32f439zi" ] +stm32f446mc = [ "stm32-metapac/stm32f446mc" ] +stm32f446me = [ "stm32-metapac/stm32f446me" ] +stm32f446rc = [ "stm32-metapac/stm32f446rc" ] +stm32f446re = [ "stm32-metapac/stm32f446re" ] +stm32f446vc = [ "stm32-metapac/stm32f446vc" ] +stm32f446ve = [ "stm32-metapac/stm32f446ve" ] +stm32f446zc = [ "stm32-metapac/stm32f446zc" ] +stm32f446ze = [ "stm32-metapac/stm32f446ze" ] +stm32f469ae = [ "stm32-metapac/stm32f469ae" ] +stm32f469ag = [ "stm32-metapac/stm32f469ag" ] +stm32f469ai = [ "stm32-metapac/stm32f469ai" ] +stm32f469be = [ "stm32-metapac/stm32f469be" ] +stm32f469bg = [ "stm32-metapac/stm32f469bg" ] +stm32f469bi = [ "stm32-metapac/stm32f469bi" ] +stm32f469ie = [ "stm32-metapac/stm32f469ie" ] +stm32f469ig = [ "stm32-metapac/stm32f469ig" ] +stm32f469ii = [ "stm32-metapac/stm32f469ii" ] +stm32f469ne = [ "stm32-metapac/stm32f469ne" ] +stm32f469ng = [ "stm32-metapac/stm32f469ng" ] +stm32f469ni = [ "stm32-metapac/stm32f469ni" ] +stm32f469ve = [ "stm32-metapac/stm32f469ve" ] +stm32f469vg = [ "stm32-metapac/stm32f469vg" ] +stm32f469vi = [ "stm32-metapac/stm32f469vi" ] +stm32f469ze = [ "stm32-metapac/stm32f469ze" ] +stm32f469zg = [ "stm32-metapac/stm32f469zg" ] +stm32f469zi = [ "stm32-metapac/stm32f469zi" ] +stm32f479ag = [ "stm32-metapac/stm32f479ag" ] +stm32f479ai = [ "stm32-metapac/stm32f479ai" ] +stm32f479bg = [ "stm32-metapac/stm32f479bg" ] +stm32f479bi = [ "stm32-metapac/stm32f479bi" ] +stm32f479ig = [ "stm32-metapac/stm32f479ig" ] +stm32f479ii = [ "stm32-metapac/stm32f479ii" ] +stm32f479ng = [ "stm32-metapac/stm32f479ng" ] +stm32f479ni = [ "stm32-metapac/stm32f479ni" ] +stm32f479vg = [ "stm32-metapac/stm32f479vg" ] +stm32f479vi = [ "stm32-metapac/stm32f479vi" ] +stm32f479zg = [ "stm32-metapac/stm32f479zg" ] +stm32f479zi = [ "stm32-metapac/stm32f479zi" ] +stm32f722ic = [ "stm32-metapac/stm32f722ic" ] +stm32f722ie = [ "stm32-metapac/stm32f722ie" ] +stm32f722rc = [ "stm32-metapac/stm32f722rc" ] +stm32f722re = [ "stm32-metapac/stm32f722re" ] +stm32f722vc = [ "stm32-metapac/stm32f722vc" ] +stm32f722ve = [ "stm32-metapac/stm32f722ve" ] +stm32f722zc = [ "stm32-metapac/stm32f722zc" ] +stm32f722ze = [ "stm32-metapac/stm32f722ze" ] +stm32f723ic = [ "stm32-metapac/stm32f723ic" ] +stm32f723ie = [ "stm32-metapac/stm32f723ie" ] +stm32f723vc = [ "stm32-metapac/stm32f723vc" ] +stm32f723ve = [ "stm32-metapac/stm32f723ve" ] +stm32f723zc = [ "stm32-metapac/stm32f723zc" ] +stm32f723ze = [ "stm32-metapac/stm32f723ze" ] +stm32f730i8 = [ "stm32-metapac/stm32f730i8" ] +stm32f730r8 = [ "stm32-metapac/stm32f730r8" ] +stm32f730v8 = [ "stm32-metapac/stm32f730v8" ] +stm32f730z8 = [ "stm32-metapac/stm32f730z8" ] +stm32f732ie = [ "stm32-metapac/stm32f732ie" ] +stm32f732re = [ "stm32-metapac/stm32f732re" ] +stm32f732ve = [ "stm32-metapac/stm32f732ve" ] +stm32f732ze = [ "stm32-metapac/stm32f732ze" ] +stm32f733ie = [ "stm32-metapac/stm32f733ie" ] +stm32f733ve = [ "stm32-metapac/stm32f733ve" ] +stm32f733ze = [ "stm32-metapac/stm32f733ze" ] +stm32f745ie = [ "stm32-metapac/stm32f745ie" ] +stm32f745ig = [ "stm32-metapac/stm32f745ig" ] +stm32f745ve = [ "stm32-metapac/stm32f745ve" ] +stm32f745vg = [ "stm32-metapac/stm32f745vg" ] +stm32f745ze = [ "stm32-metapac/stm32f745ze" ] +stm32f745zg = [ "stm32-metapac/stm32f745zg" ] +stm32f746be = [ "stm32-metapac/stm32f746be" ] +stm32f746bg = [ "stm32-metapac/stm32f746bg" ] +stm32f746ie = [ "stm32-metapac/stm32f746ie" ] +stm32f746ig = [ "stm32-metapac/stm32f746ig" ] +stm32f746ne = [ "stm32-metapac/stm32f746ne" ] +stm32f746ng = [ "stm32-metapac/stm32f746ng" ] +stm32f746ve = [ "stm32-metapac/stm32f746ve" ] +stm32f746vg = [ "stm32-metapac/stm32f746vg" ] +stm32f746ze = [ "stm32-metapac/stm32f746ze" ] +stm32f746zg = [ "stm32-metapac/stm32f746zg" ] +stm32f750n8 = [ "stm32-metapac/stm32f750n8" ] +stm32f750v8 = [ "stm32-metapac/stm32f750v8" ] +stm32f750z8 = [ "stm32-metapac/stm32f750z8" ] +stm32f756bg = [ "stm32-metapac/stm32f756bg" ] +stm32f756ig = [ "stm32-metapac/stm32f756ig" ] +stm32f756ng = [ "stm32-metapac/stm32f756ng" ] +stm32f756vg = [ "stm32-metapac/stm32f756vg" ] +stm32f756zg = [ "stm32-metapac/stm32f756zg" ] +stm32f765bg = [ "stm32-metapac/stm32f765bg" ] +stm32f765bi = [ "stm32-metapac/stm32f765bi" ] +stm32f765ig = [ "stm32-metapac/stm32f765ig" ] +stm32f765ii = [ "stm32-metapac/stm32f765ii" ] +stm32f765ng = [ "stm32-metapac/stm32f765ng" ] +stm32f765ni = [ "stm32-metapac/stm32f765ni" ] +stm32f765vg = [ "stm32-metapac/stm32f765vg" ] +stm32f765vi = [ "stm32-metapac/stm32f765vi" ] +stm32f765zg = [ "stm32-metapac/stm32f765zg" ] +stm32f765zi = [ "stm32-metapac/stm32f765zi" ] +stm32f767bg = [ "stm32-metapac/stm32f767bg" ] +stm32f767bi = [ "stm32-metapac/stm32f767bi" ] +stm32f767ig = [ "stm32-metapac/stm32f767ig" ] +stm32f767ii = [ "stm32-metapac/stm32f767ii" ] +stm32f767ng = [ "stm32-metapac/stm32f767ng" ] +stm32f767ni = [ "stm32-metapac/stm32f767ni" ] +stm32f767vg = [ "stm32-metapac/stm32f767vg" ] +stm32f767vi = [ "stm32-metapac/stm32f767vi" ] +stm32f767zg = [ "stm32-metapac/stm32f767zg" ] +stm32f767zi = [ "stm32-metapac/stm32f767zi" ] +stm32f768ai = [ "stm32-metapac/stm32f768ai" ] +stm32f769ag = [ "stm32-metapac/stm32f769ag" ] +stm32f769ai = [ "stm32-metapac/stm32f769ai" ] +stm32f769bg = [ "stm32-metapac/stm32f769bg" ] +stm32f769bi = [ "stm32-metapac/stm32f769bi" ] +stm32f769ig = [ "stm32-metapac/stm32f769ig" ] +stm32f769ii = [ "stm32-metapac/stm32f769ii" ] +stm32f769ng = [ "stm32-metapac/stm32f769ng" ] +stm32f769ni = [ "stm32-metapac/stm32f769ni" ] +stm32f777bi = [ "stm32-metapac/stm32f777bi" ] +stm32f777ii = [ "stm32-metapac/stm32f777ii" ] +stm32f777ni = [ "stm32-metapac/stm32f777ni" ] +stm32f777vi = [ "stm32-metapac/stm32f777vi" ] +stm32f777zi = [ "stm32-metapac/stm32f777zi" ] +stm32f778ai = [ "stm32-metapac/stm32f778ai" ] +stm32f779ai = [ "stm32-metapac/stm32f779ai" ] +stm32f779bi = [ "stm32-metapac/stm32f779bi" ] +stm32f779ii = [ "stm32-metapac/stm32f779ii" ] +stm32f779ni = [ "stm32-metapac/stm32f779ni" ] +stm32g030c6 = [ "stm32-metapac/stm32g030c6" ] +stm32g030c8 = [ "stm32-metapac/stm32g030c8" ] +stm32g030f6 = [ "stm32-metapac/stm32g030f6" ] +stm32g030j6 = [ "stm32-metapac/stm32g030j6" ] +stm32g030k6 = [ "stm32-metapac/stm32g030k6" ] +stm32g030k8 = [ "stm32-metapac/stm32g030k8" ] +stm32g031c4 = [ "stm32-metapac/stm32g031c4" ] +stm32g031c6 = [ "stm32-metapac/stm32g031c6" ] +stm32g031c8 = [ "stm32-metapac/stm32g031c8" ] +stm32g031f4 = [ "stm32-metapac/stm32g031f4" ] +stm32g031f6 = [ "stm32-metapac/stm32g031f6" ] +stm32g031f8 = [ "stm32-metapac/stm32g031f8" ] +stm32g031g4 = [ "stm32-metapac/stm32g031g4" ] +stm32g031g6 = [ "stm32-metapac/stm32g031g6" ] +stm32g031g8 = [ "stm32-metapac/stm32g031g8" ] +stm32g031j4 = [ "stm32-metapac/stm32g031j4" ] +stm32g031j6 = [ "stm32-metapac/stm32g031j6" ] +stm32g031k4 = [ "stm32-metapac/stm32g031k4" ] +stm32g031k6 = [ "stm32-metapac/stm32g031k6" ] +stm32g031k8 = [ "stm32-metapac/stm32g031k8" ] +stm32g031y8 = [ "stm32-metapac/stm32g031y8" ] +stm32g041c6 = [ "stm32-metapac/stm32g041c6" ] +stm32g041c8 = [ "stm32-metapac/stm32g041c8" ] +stm32g041f6 = [ "stm32-metapac/stm32g041f6" ] +stm32g041f8 = [ "stm32-metapac/stm32g041f8" ] +stm32g041g6 = [ "stm32-metapac/stm32g041g6" ] +stm32g041g8 = [ "stm32-metapac/stm32g041g8" ] +stm32g041j6 = [ "stm32-metapac/stm32g041j6" ] +stm32g041k6 = [ "stm32-metapac/stm32g041k6" ] +stm32g041k8 = [ "stm32-metapac/stm32g041k8" ] +stm32g041y8 = [ "stm32-metapac/stm32g041y8" ] +stm32g050c6 = [ "stm32-metapac/stm32g050c6" ] +stm32g050c8 = [ "stm32-metapac/stm32g050c8" ] +stm32g050f6 = [ "stm32-metapac/stm32g050f6" ] +stm32g050k6 = [ "stm32-metapac/stm32g050k6" ] +stm32g050k8 = [ "stm32-metapac/stm32g050k8" ] +stm32g051c6 = [ "stm32-metapac/stm32g051c6" ] +stm32g051c8 = [ "stm32-metapac/stm32g051c8" ] +stm32g051f6 = [ "stm32-metapac/stm32g051f6" ] +stm32g051f8 = [ "stm32-metapac/stm32g051f8" ] +stm32g051g6 = [ "stm32-metapac/stm32g051g6" ] +stm32g051g8 = [ "stm32-metapac/stm32g051g8" ] +stm32g051k6 = [ "stm32-metapac/stm32g051k6" ] +stm32g051k8 = [ "stm32-metapac/stm32g051k8" ] +stm32g061c6 = [ "stm32-metapac/stm32g061c6" ] +stm32g061c8 = [ "stm32-metapac/stm32g061c8" ] +stm32g061f6 = [ "stm32-metapac/stm32g061f6" ] +stm32g061f8 = [ "stm32-metapac/stm32g061f8" ] +stm32g061g6 = [ "stm32-metapac/stm32g061g6" ] +stm32g061g8 = [ "stm32-metapac/stm32g061g8" ] +stm32g061k6 = [ "stm32-metapac/stm32g061k6" ] +stm32g061k8 = [ "stm32-metapac/stm32g061k8" ] +stm32g070cb = [ "stm32-metapac/stm32g070cb" ] +stm32g070kb = [ "stm32-metapac/stm32g070kb" ] +stm32g070rb = [ "stm32-metapac/stm32g070rb" ] +stm32g071c6 = [ "stm32-metapac/stm32g071c6" ] +stm32g071c8 = [ "stm32-metapac/stm32g071c8" ] +stm32g071cb = [ "stm32-metapac/stm32g071cb" ] +stm32g071eb = [ "stm32-metapac/stm32g071eb" ] +stm32g071g6 = [ "stm32-metapac/stm32g071g6" ] +stm32g071g8 = [ "stm32-metapac/stm32g071g8" ] +stm32g071gb = [ "stm32-metapac/stm32g071gb" ] +stm32g071k6 = [ "stm32-metapac/stm32g071k6" ] +stm32g071k8 = [ "stm32-metapac/stm32g071k8" ] +stm32g071kb = [ "stm32-metapac/stm32g071kb" ] +stm32g071r6 = [ "stm32-metapac/stm32g071r6" ] +stm32g071r8 = [ "stm32-metapac/stm32g071r8" ] +stm32g071rb = [ "stm32-metapac/stm32g071rb" ] +stm32g081cb = [ "stm32-metapac/stm32g081cb" ] +stm32g081eb = [ "stm32-metapac/stm32g081eb" ] +stm32g081gb = [ "stm32-metapac/stm32g081gb" ] +stm32g081kb = [ "stm32-metapac/stm32g081kb" ] +stm32g081rb = [ "stm32-metapac/stm32g081rb" ] +stm32g0b0ce = [ "stm32-metapac/stm32g0b0ce" ] +stm32g0b0ke = [ "stm32-metapac/stm32g0b0ke" ] +stm32g0b0re = [ "stm32-metapac/stm32g0b0re" ] +stm32g0b0ve = [ "stm32-metapac/stm32g0b0ve" ] +stm32g0b1cb = [ "stm32-metapac/stm32g0b1cb" ] +stm32g0b1cc = [ "stm32-metapac/stm32g0b1cc" ] +stm32g0b1ce = [ "stm32-metapac/stm32g0b1ce" ] +stm32g0b1kb = [ "stm32-metapac/stm32g0b1kb" ] +stm32g0b1kc = [ "stm32-metapac/stm32g0b1kc" ] +stm32g0b1ke = [ "stm32-metapac/stm32g0b1ke" ] +stm32g0b1mb = [ "stm32-metapac/stm32g0b1mb" ] +stm32g0b1mc = [ "stm32-metapac/stm32g0b1mc" ] +stm32g0b1me = [ "stm32-metapac/stm32g0b1me" ] +stm32g0b1ne = [ "stm32-metapac/stm32g0b1ne" ] +stm32g0b1rb = [ "stm32-metapac/stm32g0b1rb" ] +stm32g0b1rc = [ "stm32-metapac/stm32g0b1rc" ] +stm32g0b1re = [ "stm32-metapac/stm32g0b1re" ] +stm32g0b1vb = [ "stm32-metapac/stm32g0b1vb" ] +stm32g0b1vc = [ "stm32-metapac/stm32g0b1vc" ] +stm32g0b1ve = [ "stm32-metapac/stm32g0b1ve" ] +stm32g0c1cc = [ "stm32-metapac/stm32g0c1cc" ] +stm32g0c1ce = [ "stm32-metapac/stm32g0c1ce" ] +stm32g0c1kc = [ "stm32-metapac/stm32g0c1kc" ] +stm32g0c1ke = [ "stm32-metapac/stm32g0c1ke" ] +stm32g0c1mc = [ "stm32-metapac/stm32g0c1mc" ] +stm32g0c1me = [ "stm32-metapac/stm32g0c1me" ] +stm32g0c1ne = [ "stm32-metapac/stm32g0c1ne" ] +stm32g0c1rc = [ "stm32-metapac/stm32g0c1rc" ] +stm32g0c1re = [ "stm32-metapac/stm32g0c1re" ] +stm32g0c1vc = [ "stm32-metapac/stm32g0c1vc" ] +stm32g0c1ve = [ "stm32-metapac/stm32g0c1ve" ] +stm32g431c6 = [ "stm32-metapac/stm32g431c6" ] +stm32g431c8 = [ "stm32-metapac/stm32g431c8" ] +stm32g431cb = [ "stm32-metapac/stm32g431cb" ] +stm32g431k6 = [ "stm32-metapac/stm32g431k6" ] +stm32g431k8 = [ "stm32-metapac/stm32g431k8" ] +stm32g431kb = [ "stm32-metapac/stm32g431kb" ] +stm32g431m6 = [ "stm32-metapac/stm32g431m6" ] +stm32g431m8 = [ "stm32-metapac/stm32g431m8" ] +stm32g431mb = [ "stm32-metapac/stm32g431mb" ] +stm32g431r6 = [ "stm32-metapac/stm32g431r6" ] +stm32g431r8 = [ "stm32-metapac/stm32g431r8" ] +stm32g431rb = [ "stm32-metapac/stm32g431rb" ] +stm32g431v6 = [ "stm32-metapac/stm32g431v6" ] +stm32g431v8 = [ "stm32-metapac/stm32g431v8" ] +stm32g431vb = [ "stm32-metapac/stm32g431vb" ] +stm32g441cb = [ "stm32-metapac/stm32g441cb" ] +stm32g441kb = [ "stm32-metapac/stm32g441kb" ] +stm32g441mb = [ "stm32-metapac/stm32g441mb" ] +stm32g441rb = [ "stm32-metapac/stm32g441rb" ] +stm32g441vb = [ "stm32-metapac/stm32g441vb" ] +stm32g471cc = [ "stm32-metapac/stm32g471cc" ] +stm32g471ce = [ "stm32-metapac/stm32g471ce" ] +stm32g471mc = [ "stm32-metapac/stm32g471mc" ] +stm32g471me = [ "stm32-metapac/stm32g471me" ] +stm32g471qc = [ "stm32-metapac/stm32g471qc" ] +stm32g471qe = [ "stm32-metapac/stm32g471qe" ] +stm32g471rc = [ "stm32-metapac/stm32g471rc" ] +stm32g471re = [ "stm32-metapac/stm32g471re" ] +stm32g471vc = [ "stm32-metapac/stm32g471vc" ] +stm32g471ve = [ "stm32-metapac/stm32g471ve" ] +stm32g473cb = [ "stm32-metapac/stm32g473cb" ] +stm32g473cc = [ "stm32-metapac/stm32g473cc" ] +stm32g473ce = [ "stm32-metapac/stm32g473ce" ] +stm32g473mb = [ "stm32-metapac/stm32g473mb" ] +stm32g473mc = [ "stm32-metapac/stm32g473mc" ] +stm32g473me = [ "stm32-metapac/stm32g473me" ] +stm32g473pb = [ "stm32-metapac/stm32g473pb" ] +stm32g473pc = [ "stm32-metapac/stm32g473pc" ] +stm32g473pe = [ "stm32-metapac/stm32g473pe" ] +stm32g473qb = [ "stm32-metapac/stm32g473qb" ] +stm32g473qc = [ "stm32-metapac/stm32g473qc" ] +stm32g473qe = [ "stm32-metapac/stm32g473qe" ] +stm32g473rb = [ "stm32-metapac/stm32g473rb" ] +stm32g473rc = [ "stm32-metapac/stm32g473rc" ] +stm32g473re = [ "stm32-metapac/stm32g473re" ] +stm32g473vb = [ "stm32-metapac/stm32g473vb" ] +stm32g473vc = [ "stm32-metapac/stm32g473vc" ] +stm32g473ve = [ "stm32-metapac/stm32g473ve" ] +stm32g474cb = [ "stm32-metapac/stm32g474cb" ] +stm32g474cc = [ "stm32-metapac/stm32g474cc" ] +stm32g474ce = [ "stm32-metapac/stm32g474ce" ] +stm32g474mb = [ "stm32-metapac/stm32g474mb" ] +stm32g474mc = [ "stm32-metapac/stm32g474mc" ] +stm32g474me = [ "stm32-metapac/stm32g474me" ] +stm32g474pb = [ "stm32-metapac/stm32g474pb" ] +stm32g474pc = [ "stm32-metapac/stm32g474pc" ] +stm32g474pe = [ "stm32-metapac/stm32g474pe" ] +stm32g474qb = [ "stm32-metapac/stm32g474qb" ] +stm32g474qc = [ "stm32-metapac/stm32g474qc" ] +stm32g474qe = [ "stm32-metapac/stm32g474qe" ] +stm32g474rb = [ "stm32-metapac/stm32g474rb" ] +stm32g474rc = [ "stm32-metapac/stm32g474rc" ] +stm32g474re = [ "stm32-metapac/stm32g474re" ] +stm32g474vb = [ "stm32-metapac/stm32g474vb" ] +stm32g474vc = [ "stm32-metapac/stm32g474vc" ] +stm32g474ve = [ "stm32-metapac/stm32g474ve" ] +stm32g483ce = [ "stm32-metapac/stm32g483ce" ] +stm32g483me = [ "stm32-metapac/stm32g483me" ] +stm32g483pe = [ "stm32-metapac/stm32g483pe" ] +stm32g483qe = [ "stm32-metapac/stm32g483qe" ] +stm32g483re = [ "stm32-metapac/stm32g483re" ] +stm32g483ve = [ "stm32-metapac/stm32g483ve" ] +stm32g484ce = [ "stm32-metapac/stm32g484ce" ] +stm32g484me = [ "stm32-metapac/stm32g484me" ] +stm32g484pe = [ "stm32-metapac/stm32g484pe" ] +stm32g484qe = [ "stm32-metapac/stm32g484qe" ] +stm32g484re = [ "stm32-metapac/stm32g484re" ] +stm32g484ve = [ "stm32-metapac/stm32g484ve" ] +stm32g491cc = [ "stm32-metapac/stm32g491cc" ] +stm32g491ce = [ "stm32-metapac/stm32g491ce" ] +stm32g491kc = [ "stm32-metapac/stm32g491kc" ] +stm32g491ke = [ "stm32-metapac/stm32g491ke" ] +stm32g491mc = [ "stm32-metapac/stm32g491mc" ] +stm32g491me = [ "stm32-metapac/stm32g491me" ] +stm32g491rc = [ "stm32-metapac/stm32g491rc" ] +stm32g491re = [ "stm32-metapac/stm32g491re" ] +stm32g491vc = [ "stm32-metapac/stm32g491vc" ] +stm32g491ve = [ "stm32-metapac/stm32g491ve" ] +stm32g4a1ce = [ "stm32-metapac/stm32g4a1ce" ] +stm32g4a1ke = [ "stm32-metapac/stm32g4a1ke" ] +stm32g4a1me = [ "stm32-metapac/stm32g4a1me" ] +stm32g4a1re = [ "stm32-metapac/stm32g4a1re" ] +stm32g4a1ve = [ "stm32-metapac/stm32g4a1ve" ] +stm32h503cb = [ "stm32-metapac/stm32h503cb" ] +stm32h503eb = [ "stm32-metapac/stm32h503eb" ] +stm32h503kb = [ "stm32-metapac/stm32h503kb" ] +stm32h503rb = [ "stm32-metapac/stm32h503rb" ] +stm32h523cc = [ "stm32-metapac/stm32h523cc" ] +stm32h523ce = [ "stm32-metapac/stm32h523ce" ] +stm32h523he = [ "stm32-metapac/stm32h523he" ] +stm32h523rc = [ "stm32-metapac/stm32h523rc" ] +stm32h523re = [ "stm32-metapac/stm32h523re" ] +stm32h523vc = [ "stm32-metapac/stm32h523vc" ] +stm32h523ve = [ "stm32-metapac/stm32h523ve" ] +stm32h523zc = [ "stm32-metapac/stm32h523zc" ] +stm32h523ze = [ "stm32-metapac/stm32h523ze" ] +stm32h533ce = [ "stm32-metapac/stm32h533ce" ] +stm32h533he = [ "stm32-metapac/stm32h533he" ] +stm32h533re = [ "stm32-metapac/stm32h533re" ] +stm32h533ve = [ "stm32-metapac/stm32h533ve" ] +stm32h533ze = [ "stm32-metapac/stm32h533ze" ] +stm32h562ag = [ "stm32-metapac/stm32h562ag" ] +stm32h562ai = [ "stm32-metapac/stm32h562ai" ] +stm32h562ig = [ "stm32-metapac/stm32h562ig" ] +stm32h562ii = [ "stm32-metapac/stm32h562ii" ] +stm32h562rg = [ "stm32-metapac/stm32h562rg" ] +stm32h562ri = [ "stm32-metapac/stm32h562ri" ] +stm32h562vg = [ "stm32-metapac/stm32h562vg" ] +stm32h562vi = [ "stm32-metapac/stm32h562vi" ] +stm32h562zg = [ "stm32-metapac/stm32h562zg" ] +stm32h562zi = [ "stm32-metapac/stm32h562zi" ] +stm32h563ag = [ "stm32-metapac/stm32h563ag" ] +stm32h563ai = [ "stm32-metapac/stm32h563ai" ] +stm32h563ig = [ "stm32-metapac/stm32h563ig" ] +stm32h563ii = [ "stm32-metapac/stm32h563ii" ] +stm32h563mi = [ "stm32-metapac/stm32h563mi" ] +stm32h563rg = [ "stm32-metapac/stm32h563rg" ] +stm32h563ri = [ "stm32-metapac/stm32h563ri" ] +stm32h563vg = [ "stm32-metapac/stm32h563vg" ] +stm32h563vi = [ "stm32-metapac/stm32h563vi" ] +stm32h563zg = [ "stm32-metapac/stm32h563zg" ] +stm32h563zi = [ "stm32-metapac/stm32h563zi" ] +stm32h573ai = [ "stm32-metapac/stm32h573ai" ] +stm32h573ii = [ "stm32-metapac/stm32h573ii" ] +stm32h573mi = [ "stm32-metapac/stm32h573mi" ] +stm32h573ri = [ "stm32-metapac/stm32h573ri" ] +stm32h573vi = [ "stm32-metapac/stm32h573vi" ] +stm32h573zi = [ "stm32-metapac/stm32h573zi" ] +stm32h723ve = [ "stm32-metapac/stm32h723ve" ] +stm32h723vg = [ "stm32-metapac/stm32h723vg" ] +stm32h723ze = [ "stm32-metapac/stm32h723ze" ] +stm32h723zg = [ "stm32-metapac/stm32h723zg" ] +stm32h725ae = [ "stm32-metapac/stm32h725ae" ] +stm32h725ag = [ "stm32-metapac/stm32h725ag" ] +stm32h725ie = [ "stm32-metapac/stm32h725ie" ] +stm32h725ig = [ "stm32-metapac/stm32h725ig" ] +stm32h725re = [ "stm32-metapac/stm32h725re" ] +stm32h725rg = [ "stm32-metapac/stm32h725rg" ] +stm32h725ve = [ "stm32-metapac/stm32h725ve" ] +stm32h725vg = [ "stm32-metapac/stm32h725vg" ] +stm32h725ze = [ "stm32-metapac/stm32h725ze" ] +stm32h725zg = [ "stm32-metapac/stm32h725zg" ] +stm32h730ab = [ "stm32-metapac/stm32h730ab" ] +stm32h730ib = [ "stm32-metapac/stm32h730ib" ] +stm32h730vb = [ "stm32-metapac/stm32h730vb" ] +stm32h730zb = [ "stm32-metapac/stm32h730zb" ] +stm32h733vg = [ "stm32-metapac/stm32h733vg" ] +stm32h733zg = [ "stm32-metapac/stm32h733zg" ] +stm32h735ag = [ "stm32-metapac/stm32h735ag" ] +stm32h735ig = [ "stm32-metapac/stm32h735ig" ] +stm32h735rg = [ "stm32-metapac/stm32h735rg" ] +stm32h735vg = [ "stm32-metapac/stm32h735vg" ] +stm32h735zg = [ "stm32-metapac/stm32h735zg" ] +stm32h742ag = [ "stm32-metapac/stm32h742ag" ] +stm32h742ai = [ "stm32-metapac/stm32h742ai" ] +stm32h742bg = [ "stm32-metapac/stm32h742bg" ] +stm32h742bi = [ "stm32-metapac/stm32h742bi" ] +stm32h742ig = [ "stm32-metapac/stm32h742ig" ] +stm32h742ii = [ "stm32-metapac/stm32h742ii" ] +stm32h742vg = [ "stm32-metapac/stm32h742vg" ] +stm32h742vi = [ "stm32-metapac/stm32h742vi" ] +stm32h742xg = [ "stm32-metapac/stm32h742xg" ] +stm32h742xi = [ "stm32-metapac/stm32h742xi" ] +stm32h742zg = [ "stm32-metapac/stm32h742zg" ] +stm32h742zi = [ "stm32-metapac/stm32h742zi" ] +stm32h743ag = [ "stm32-metapac/stm32h743ag" ] +stm32h743ai = [ "stm32-metapac/stm32h743ai" ] +stm32h743bg = [ "stm32-metapac/stm32h743bg" ] +stm32h743bi = [ "stm32-metapac/stm32h743bi" ] +stm32h743ig = [ "stm32-metapac/stm32h743ig" ] +stm32h743ii = [ "stm32-metapac/stm32h743ii" ] +stm32h743vg = [ "stm32-metapac/stm32h743vg" ] +stm32h743vi = [ "stm32-metapac/stm32h743vi" ] +stm32h743xg = [ "stm32-metapac/stm32h743xg" ] +stm32h743xi = [ "stm32-metapac/stm32h743xi" ] +stm32h743zg = [ "stm32-metapac/stm32h743zg" ] +stm32h743zi = [ "stm32-metapac/stm32h743zi" ] +stm32h745bg-cm7 = [ "stm32-metapac/stm32h745bg-cm7", "_dual-core" ] +stm32h745bg-cm4 = [ "stm32-metapac/stm32h745bg-cm4", "_dual-core" ] +stm32h745bi-cm7 = [ "stm32-metapac/stm32h745bi-cm7", "_dual-core" ] +stm32h745bi-cm4 = [ "stm32-metapac/stm32h745bi-cm4", "_dual-core" ] +stm32h745ig-cm7 = [ "stm32-metapac/stm32h745ig-cm7", "_dual-core" ] +stm32h745ig-cm4 = [ "stm32-metapac/stm32h745ig-cm4", "_dual-core" ] +stm32h745ii-cm7 = [ "stm32-metapac/stm32h745ii-cm7", "_dual-core" ] +stm32h745ii-cm4 = [ "stm32-metapac/stm32h745ii-cm4", "_dual-core" ] +stm32h745xg-cm7 = [ "stm32-metapac/stm32h745xg-cm7", "_dual-core" ] +stm32h745xg-cm4 = [ "stm32-metapac/stm32h745xg-cm4", "_dual-core" ] +stm32h745xi-cm7 = [ "stm32-metapac/stm32h745xi-cm7", "_dual-core" ] +stm32h745xi-cm4 = [ "stm32-metapac/stm32h745xi-cm4", "_dual-core" ] +stm32h745zg-cm7 = [ "stm32-metapac/stm32h745zg-cm7", "_dual-core" ] +stm32h745zg-cm4 = [ "stm32-metapac/stm32h745zg-cm4", "_dual-core" ] +stm32h745zi-cm7 = [ "stm32-metapac/stm32h745zi-cm7", "_dual-core" ] +stm32h745zi-cm4 = [ "stm32-metapac/stm32h745zi-cm4", "_dual-core" ] +stm32h747ag-cm7 = [ "stm32-metapac/stm32h747ag-cm7", "_dual-core" ] +stm32h747ag-cm4 = [ "stm32-metapac/stm32h747ag-cm4", "_dual-core" ] +stm32h747ai-cm7 = [ "stm32-metapac/stm32h747ai-cm7", "_dual-core" ] +stm32h747ai-cm4 = [ "stm32-metapac/stm32h747ai-cm4", "_dual-core" ] +stm32h747bg-cm7 = [ "stm32-metapac/stm32h747bg-cm7", "_dual-core" ] +stm32h747bg-cm4 = [ "stm32-metapac/stm32h747bg-cm4", "_dual-core" ] +stm32h747bi-cm7 = [ "stm32-metapac/stm32h747bi-cm7", "_dual-core" ] +stm32h747bi-cm4 = [ "stm32-metapac/stm32h747bi-cm4", "_dual-core" ] +stm32h747ig-cm7 = [ "stm32-metapac/stm32h747ig-cm7", "_dual-core" ] +stm32h747ig-cm4 = [ "stm32-metapac/stm32h747ig-cm4", "_dual-core" ] +stm32h747ii-cm7 = [ "stm32-metapac/stm32h747ii-cm7", "_dual-core" ] +stm32h747ii-cm4 = [ "stm32-metapac/stm32h747ii-cm4", "_dual-core" ] +stm32h747xg-cm7 = [ "stm32-metapac/stm32h747xg-cm7", "_dual-core" ] +stm32h747xg-cm4 = [ "stm32-metapac/stm32h747xg-cm4", "_dual-core" ] +stm32h747xi-cm7 = [ "stm32-metapac/stm32h747xi-cm7", "_dual-core" ] +stm32h747xi-cm4 = [ "stm32-metapac/stm32h747xi-cm4", "_dual-core" ] +stm32h747zi-cm7 = [ "stm32-metapac/stm32h747zi-cm7", "_dual-core" ] +stm32h747zi-cm4 = [ "stm32-metapac/stm32h747zi-cm4", "_dual-core" ] +stm32h750ib = [ "stm32-metapac/stm32h750ib" ] +stm32h750vb = [ "stm32-metapac/stm32h750vb" ] +stm32h750xb = [ "stm32-metapac/stm32h750xb" ] +stm32h750zb = [ "stm32-metapac/stm32h750zb" ] +stm32h753ai = [ "stm32-metapac/stm32h753ai" ] +stm32h753bi = [ "stm32-metapac/stm32h753bi" ] +stm32h753ii = [ "stm32-metapac/stm32h753ii" ] +stm32h753vi = [ "stm32-metapac/stm32h753vi" ] +stm32h753xi = [ "stm32-metapac/stm32h753xi" ] +stm32h753zi = [ "stm32-metapac/stm32h753zi" ] +stm32h755bi-cm7 = [ "stm32-metapac/stm32h755bi-cm7", "_dual-core" ] +stm32h755bi-cm4 = [ "stm32-metapac/stm32h755bi-cm4", "_dual-core" ] +stm32h755ii-cm7 = [ "stm32-metapac/stm32h755ii-cm7", "_dual-core" ] +stm32h755ii-cm4 = [ "stm32-metapac/stm32h755ii-cm4", "_dual-core" ] +stm32h755xi-cm7 = [ "stm32-metapac/stm32h755xi-cm7", "_dual-core" ] +stm32h755xi-cm4 = [ "stm32-metapac/stm32h755xi-cm4", "_dual-core" ] +stm32h755zi-cm7 = [ "stm32-metapac/stm32h755zi-cm7", "_dual-core" ] +stm32h755zi-cm4 = [ "stm32-metapac/stm32h755zi-cm4", "_dual-core" ] +stm32h757ai-cm7 = [ "stm32-metapac/stm32h757ai-cm7", "_dual-core" ] +stm32h757ai-cm4 = [ "stm32-metapac/stm32h757ai-cm4", "_dual-core" ] +stm32h757bi-cm7 = [ "stm32-metapac/stm32h757bi-cm7", "_dual-core" ] +stm32h757bi-cm4 = [ "stm32-metapac/stm32h757bi-cm4", "_dual-core" ] +stm32h757ii-cm7 = [ "stm32-metapac/stm32h757ii-cm7", "_dual-core" ] +stm32h757ii-cm4 = [ "stm32-metapac/stm32h757ii-cm4", "_dual-core" ] +stm32h757xi-cm7 = [ "stm32-metapac/stm32h757xi-cm7", "_dual-core" ] +stm32h757xi-cm4 = [ "stm32-metapac/stm32h757xi-cm4", "_dual-core" ] +stm32h757zi-cm7 = [ "stm32-metapac/stm32h757zi-cm7", "_dual-core" ] +stm32h757zi-cm4 = [ "stm32-metapac/stm32h757zi-cm4", "_dual-core" ] +stm32h7a3ag = [ "stm32-metapac/stm32h7a3ag" ] +stm32h7a3ai = [ "stm32-metapac/stm32h7a3ai" ] +stm32h7a3ig = [ "stm32-metapac/stm32h7a3ig" ] +stm32h7a3ii = [ "stm32-metapac/stm32h7a3ii" ] +stm32h7a3lg = [ "stm32-metapac/stm32h7a3lg" ] +stm32h7a3li = [ "stm32-metapac/stm32h7a3li" ] +stm32h7a3ng = [ "stm32-metapac/stm32h7a3ng" ] +stm32h7a3ni = [ "stm32-metapac/stm32h7a3ni" ] +stm32h7a3qi = [ "stm32-metapac/stm32h7a3qi" ] +stm32h7a3rg = [ "stm32-metapac/stm32h7a3rg" ] +stm32h7a3ri = [ "stm32-metapac/stm32h7a3ri" ] +stm32h7a3vg = [ "stm32-metapac/stm32h7a3vg" ] +stm32h7a3vi = [ "stm32-metapac/stm32h7a3vi" ] +stm32h7a3zg = [ "stm32-metapac/stm32h7a3zg" ] +stm32h7a3zi = [ "stm32-metapac/stm32h7a3zi" ] +stm32h7b0ab = [ "stm32-metapac/stm32h7b0ab" ] +stm32h7b0ib = [ "stm32-metapac/stm32h7b0ib" ] +stm32h7b0rb = [ "stm32-metapac/stm32h7b0rb" ] +stm32h7b0vb = [ "stm32-metapac/stm32h7b0vb" ] +stm32h7b0zb = [ "stm32-metapac/stm32h7b0zb" ] +stm32h7b3ai = [ "stm32-metapac/stm32h7b3ai" ] +stm32h7b3ii = [ "stm32-metapac/stm32h7b3ii" ] +stm32h7b3li = [ "stm32-metapac/stm32h7b3li" ] +stm32h7b3ni = [ "stm32-metapac/stm32h7b3ni" ] +stm32h7b3qi = [ "stm32-metapac/stm32h7b3qi" ] +stm32h7b3ri = [ "stm32-metapac/stm32h7b3ri" ] +stm32h7b3vi = [ "stm32-metapac/stm32h7b3vi" ] +stm32h7b3zi = [ "stm32-metapac/stm32h7b3zi" ] +stm32h7r3a8 = [ "stm32-metapac/stm32h7r3a8" ] +stm32h7r3i8 = [ "stm32-metapac/stm32h7r3i8" ] +stm32h7r3l8 = [ "stm32-metapac/stm32h7r3l8" ] +stm32h7r3r8 = [ "stm32-metapac/stm32h7r3r8" ] +stm32h7r3v8 = [ "stm32-metapac/stm32h7r3v8" ] +stm32h7r3z8 = [ "stm32-metapac/stm32h7r3z8" ] +stm32h7r7a8 = [ "stm32-metapac/stm32h7r7a8" ] +stm32h7r7i8 = [ "stm32-metapac/stm32h7r7i8" ] +stm32h7r7l8 = [ "stm32-metapac/stm32h7r7l8" ] +stm32h7r7z8 = [ "stm32-metapac/stm32h7r7z8" ] +stm32h7s3a8 = [ "stm32-metapac/stm32h7s3a8" ] +stm32h7s3i8 = [ "stm32-metapac/stm32h7s3i8" ] +stm32h7s3l8 = [ "stm32-metapac/stm32h7s3l8" ] +stm32h7s3r8 = [ "stm32-metapac/stm32h7s3r8" ] +stm32h7s3v8 = [ "stm32-metapac/stm32h7s3v8" ] +stm32h7s3z8 = [ "stm32-metapac/stm32h7s3z8" ] +stm32h7s7a8 = [ "stm32-metapac/stm32h7s7a8" ] +stm32h7s7i8 = [ "stm32-metapac/stm32h7s7i8" ] +stm32h7s7l8 = [ "stm32-metapac/stm32h7s7l8" ] +stm32h7s7z8 = [ "stm32-metapac/stm32h7s7z8" ] +stm32l010c6 = [ "stm32-metapac/stm32l010c6" ] +stm32l010f4 = [ "stm32-metapac/stm32l010f4" ] +stm32l010k4 = [ "stm32-metapac/stm32l010k4" ] +stm32l010k8 = [ "stm32-metapac/stm32l010k8" ] +stm32l010r8 = [ "stm32-metapac/stm32l010r8" ] +stm32l010rb = [ "stm32-metapac/stm32l010rb" ] +stm32l011d3 = [ "stm32-metapac/stm32l011d3" ] +stm32l011d4 = [ "stm32-metapac/stm32l011d4" ] +stm32l011e3 = [ "stm32-metapac/stm32l011e3" ] +stm32l011e4 = [ "stm32-metapac/stm32l011e4" ] +stm32l011f3 = [ "stm32-metapac/stm32l011f3" ] +stm32l011f4 = [ "stm32-metapac/stm32l011f4" ] +stm32l011g3 = [ "stm32-metapac/stm32l011g3" ] +stm32l011g4 = [ "stm32-metapac/stm32l011g4" ] +stm32l011k3 = [ "stm32-metapac/stm32l011k3" ] +stm32l011k4 = [ "stm32-metapac/stm32l011k4" ] +stm32l021d4 = [ "stm32-metapac/stm32l021d4" ] +stm32l021f4 = [ "stm32-metapac/stm32l021f4" ] +stm32l021g4 = [ "stm32-metapac/stm32l021g4" ] +stm32l021k4 = [ "stm32-metapac/stm32l021k4" ] +stm32l031c4 = [ "stm32-metapac/stm32l031c4" ] +stm32l031c6 = [ "stm32-metapac/stm32l031c6" ] +stm32l031e4 = [ "stm32-metapac/stm32l031e4" ] +stm32l031e6 = [ "stm32-metapac/stm32l031e6" ] +stm32l031f4 = [ "stm32-metapac/stm32l031f4" ] +stm32l031f6 = [ "stm32-metapac/stm32l031f6" ] +stm32l031g4 = [ "stm32-metapac/stm32l031g4" ] +stm32l031g6 = [ "stm32-metapac/stm32l031g6" ] +stm32l031k4 = [ "stm32-metapac/stm32l031k4" ] +stm32l031k6 = [ "stm32-metapac/stm32l031k6" ] +stm32l041c4 = [ "stm32-metapac/stm32l041c4" ] +stm32l041c6 = [ "stm32-metapac/stm32l041c6" ] +stm32l041e6 = [ "stm32-metapac/stm32l041e6" ] +stm32l041f6 = [ "stm32-metapac/stm32l041f6" ] +stm32l041g6 = [ "stm32-metapac/stm32l041g6" ] +stm32l041k6 = [ "stm32-metapac/stm32l041k6" ] +stm32l051c6 = [ "stm32-metapac/stm32l051c6" ] +stm32l051c8 = [ "stm32-metapac/stm32l051c8" ] +stm32l051k6 = [ "stm32-metapac/stm32l051k6" ] +stm32l051k8 = [ "stm32-metapac/stm32l051k8" ] +stm32l051r6 = [ "stm32-metapac/stm32l051r6" ] +stm32l051r8 = [ "stm32-metapac/stm32l051r8" ] +stm32l051t6 = [ "stm32-metapac/stm32l051t6" ] +stm32l051t8 = [ "stm32-metapac/stm32l051t8" ] +stm32l052c6 = [ "stm32-metapac/stm32l052c6" ] +stm32l052c8 = [ "stm32-metapac/stm32l052c8" ] +stm32l052k6 = [ "stm32-metapac/stm32l052k6" ] +stm32l052k8 = [ "stm32-metapac/stm32l052k8" ] +stm32l052r6 = [ "stm32-metapac/stm32l052r6" ] +stm32l052r8 = [ "stm32-metapac/stm32l052r8" ] +stm32l052t6 = [ "stm32-metapac/stm32l052t6" ] +stm32l052t8 = [ "stm32-metapac/stm32l052t8" ] +stm32l053c6 = [ "stm32-metapac/stm32l053c6" ] +stm32l053c8 = [ "stm32-metapac/stm32l053c8" ] +stm32l053r6 = [ "stm32-metapac/stm32l053r6" ] +stm32l053r8 = [ "stm32-metapac/stm32l053r8" ] +stm32l062c8 = [ "stm32-metapac/stm32l062c8" ] +stm32l062k8 = [ "stm32-metapac/stm32l062k8" ] +stm32l063c8 = [ "stm32-metapac/stm32l063c8" ] +stm32l063r8 = [ "stm32-metapac/stm32l063r8" ] +stm32l071c8 = [ "stm32-metapac/stm32l071c8" ] +stm32l071cb = [ "stm32-metapac/stm32l071cb" ] +stm32l071cz = [ "stm32-metapac/stm32l071cz" ] +stm32l071k8 = [ "stm32-metapac/stm32l071k8" ] +stm32l071kb = [ "stm32-metapac/stm32l071kb" ] +stm32l071kz = [ "stm32-metapac/stm32l071kz" ] +stm32l071rb = [ "stm32-metapac/stm32l071rb" ] +stm32l071rz = [ "stm32-metapac/stm32l071rz" ] +stm32l071v8 = [ "stm32-metapac/stm32l071v8" ] +stm32l071vb = [ "stm32-metapac/stm32l071vb" ] +stm32l071vz = [ "stm32-metapac/stm32l071vz" ] +stm32l072cb = [ "stm32-metapac/stm32l072cb" ] +stm32l072cz = [ "stm32-metapac/stm32l072cz" ] +stm32l072kb = [ "stm32-metapac/stm32l072kb" ] +stm32l072kz = [ "stm32-metapac/stm32l072kz" ] +stm32l072rb = [ "stm32-metapac/stm32l072rb" ] +stm32l072rz = [ "stm32-metapac/stm32l072rz" ] +stm32l072v8 = [ "stm32-metapac/stm32l072v8" ] +stm32l072vb = [ "stm32-metapac/stm32l072vb" ] +stm32l072vz = [ "stm32-metapac/stm32l072vz" ] +stm32l073cb = [ "stm32-metapac/stm32l073cb" ] +stm32l073cz = [ "stm32-metapac/stm32l073cz" ] +stm32l073rb = [ "stm32-metapac/stm32l073rb" ] +stm32l073rz = [ "stm32-metapac/stm32l073rz" ] +stm32l073v8 = [ "stm32-metapac/stm32l073v8" ] +stm32l073vb = [ "stm32-metapac/stm32l073vb" ] +stm32l073vz = [ "stm32-metapac/stm32l073vz" ] +stm32l081cb = [ "stm32-metapac/stm32l081cb" ] +stm32l081cz = [ "stm32-metapac/stm32l081cz" ] +stm32l081kz = [ "stm32-metapac/stm32l081kz" ] +stm32l082cz = [ "stm32-metapac/stm32l082cz" ] +stm32l082kb = [ "stm32-metapac/stm32l082kb" ] +stm32l082kz = [ "stm32-metapac/stm32l082kz" ] +stm32l083cb = [ "stm32-metapac/stm32l083cb" ] +stm32l083cz = [ "stm32-metapac/stm32l083cz" ] +stm32l083rb = [ "stm32-metapac/stm32l083rb" ] +stm32l083rz = [ "stm32-metapac/stm32l083rz" ] +stm32l083v8 = [ "stm32-metapac/stm32l083v8" ] +stm32l083vb = [ "stm32-metapac/stm32l083vb" ] +stm32l083vz = [ "stm32-metapac/stm32l083vz" ] +stm32l100c6 = [ "stm32-metapac/stm32l100c6" ] +stm32l100c6-a = [ "stm32-metapac/stm32l100c6-a" ] +stm32l100r8 = [ "stm32-metapac/stm32l100r8" ] +stm32l100r8-a = [ "stm32-metapac/stm32l100r8-a" ] +stm32l100rb = [ "stm32-metapac/stm32l100rb" ] +stm32l100rb-a = [ "stm32-metapac/stm32l100rb-a" ] +stm32l100rc = [ "stm32-metapac/stm32l100rc" ] +stm32l151c6 = [ "stm32-metapac/stm32l151c6" ] +stm32l151c6-a = [ "stm32-metapac/stm32l151c6-a" ] +stm32l151c8 = [ "stm32-metapac/stm32l151c8" ] +stm32l151c8-a = [ "stm32-metapac/stm32l151c8-a" ] +stm32l151cb = [ "stm32-metapac/stm32l151cb" ] +stm32l151cb-a = [ "stm32-metapac/stm32l151cb-a" ] +stm32l151cc = [ "stm32-metapac/stm32l151cc" ] +stm32l151qc = [ "stm32-metapac/stm32l151qc" ] +stm32l151qd = [ "stm32-metapac/stm32l151qd" ] +stm32l151qe = [ "stm32-metapac/stm32l151qe" ] +stm32l151r6 = [ "stm32-metapac/stm32l151r6" ] +stm32l151r6-a = [ "stm32-metapac/stm32l151r6-a" ] +stm32l151r8 = [ "stm32-metapac/stm32l151r8" ] +stm32l151r8-a = [ "stm32-metapac/stm32l151r8-a" ] +stm32l151rb = [ "stm32-metapac/stm32l151rb" ] +stm32l151rb-a = [ "stm32-metapac/stm32l151rb-a" ] +stm32l151rc = [ "stm32-metapac/stm32l151rc" ] +stm32l151rc-a = [ "stm32-metapac/stm32l151rc-a" ] +stm32l151rd = [ "stm32-metapac/stm32l151rd" ] +stm32l151re = [ "stm32-metapac/stm32l151re" ] +stm32l151uc = [ "stm32-metapac/stm32l151uc" ] +stm32l151v8 = [ "stm32-metapac/stm32l151v8" ] +stm32l151v8-a = [ "stm32-metapac/stm32l151v8-a" ] +stm32l151vb = [ "stm32-metapac/stm32l151vb" ] +stm32l151vb-a = [ "stm32-metapac/stm32l151vb-a" ] +stm32l151vc = [ "stm32-metapac/stm32l151vc" ] +stm32l151vc-a = [ "stm32-metapac/stm32l151vc-a" ] +stm32l151vd = [ "stm32-metapac/stm32l151vd" ] +stm32l151vd-x = [ "stm32-metapac/stm32l151vd-x" ] +stm32l151ve = [ "stm32-metapac/stm32l151ve" ] +stm32l151zc = [ "stm32-metapac/stm32l151zc" ] +stm32l151zd = [ "stm32-metapac/stm32l151zd" ] +stm32l151ze = [ "stm32-metapac/stm32l151ze" ] +stm32l152c6 = [ "stm32-metapac/stm32l152c6" ] +stm32l152c6-a = [ "stm32-metapac/stm32l152c6-a" ] +stm32l152c8 = [ "stm32-metapac/stm32l152c8" ] +stm32l152c8-a = [ "stm32-metapac/stm32l152c8-a" ] +stm32l152cb = [ "stm32-metapac/stm32l152cb" ] +stm32l152cb-a = [ "stm32-metapac/stm32l152cb-a" ] +stm32l152cc = [ "stm32-metapac/stm32l152cc" ] +stm32l152qc = [ "stm32-metapac/stm32l152qc" ] +stm32l152qd = [ "stm32-metapac/stm32l152qd" ] +stm32l152qe = [ "stm32-metapac/stm32l152qe" ] +stm32l152r6 = [ "stm32-metapac/stm32l152r6" ] +stm32l152r6-a = [ "stm32-metapac/stm32l152r6-a" ] +stm32l152r8 = [ "stm32-metapac/stm32l152r8" ] +stm32l152r8-a = [ "stm32-metapac/stm32l152r8-a" ] +stm32l152rb = [ "stm32-metapac/stm32l152rb" ] +stm32l152rb-a = [ "stm32-metapac/stm32l152rb-a" ] +stm32l152rc = [ "stm32-metapac/stm32l152rc" ] +stm32l152rc-a = [ "stm32-metapac/stm32l152rc-a" ] +stm32l152rd = [ "stm32-metapac/stm32l152rd" ] +stm32l152re = [ "stm32-metapac/stm32l152re" ] +stm32l152uc = [ "stm32-metapac/stm32l152uc" ] +stm32l152v8 = [ "stm32-metapac/stm32l152v8" ] +stm32l152v8-a = [ "stm32-metapac/stm32l152v8-a" ] +stm32l152vb = [ "stm32-metapac/stm32l152vb" ] +stm32l152vb-a = [ "stm32-metapac/stm32l152vb-a" ] +stm32l152vc = [ "stm32-metapac/stm32l152vc" ] +stm32l152vc-a = [ "stm32-metapac/stm32l152vc-a" ] +stm32l152vd = [ "stm32-metapac/stm32l152vd" ] +stm32l152vd-x = [ "stm32-metapac/stm32l152vd-x" ] +stm32l152ve = [ "stm32-metapac/stm32l152ve" ] +stm32l152zc = [ "stm32-metapac/stm32l152zc" ] +stm32l152zd = [ "stm32-metapac/stm32l152zd" ] +stm32l152ze = [ "stm32-metapac/stm32l152ze" ] +stm32l162qc = [ "stm32-metapac/stm32l162qc" ] +stm32l162qd = [ "stm32-metapac/stm32l162qd" ] +stm32l162rc = [ "stm32-metapac/stm32l162rc" ] +stm32l162rc-a = [ "stm32-metapac/stm32l162rc-a" ] +stm32l162rd = [ "stm32-metapac/stm32l162rd" ] +stm32l162re = [ "stm32-metapac/stm32l162re" ] +stm32l162vc = [ "stm32-metapac/stm32l162vc" ] +stm32l162vc-a = [ "stm32-metapac/stm32l162vc-a" ] +stm32l162vd = [ "stm32-metapac/stm32l162vd" ] +stm32l162vd-x = [ "stm32-metapac/stm32l162vd-x" ] +stm32l162ve = [ "stm32-metapac/stm32l162ve" ] +stm32l162zc = [ "stm32-metapac/stm32l162zc" ] +stm32l162zd = [ "stm32-metapac/stm32l162zd" ] +stm32l162ze = [ "stm32-metapac/stm32l162ze" ] +stm32l412c8 = [ "stm32-metapac/stm32l412c8" ] +stm32l412cb = [ "stm32-metapac/stm32l412cb" ] +stm32l412k8 = [ "stm32-metapac/stm32l412k8" ] +stm32l412kb = [ "stm32-metapac/stm32l412kb" ] +stm32l412r8 = [ "stm32-metapac/stm32l412r8" ] +stm32l412rb = [ "stm32-metapac/stm32l412rb" ] +stm32l412t8 = [ "stm32-metapac/stm32l412t8" ] +stm32l412tb = [ "stm32-metapac/stm32l412tb" ] +stm32l422cb = [ "stm32-metapac/stm32l422cb" ] +stm32l422kb = [ "stm32-metapac/stm32l422kb" ] +stm32l422rb = [ "stm32-metapac/stm32l422rb" ] +stm32l422tb = [ "stm32-metapac/stm32l422tb" ] +stm32l431cb = [ "stm32-metapac/stm32l431cb" ] +stm32l431cc = [ "stm32-metapac/stm32l431cc" ] +stm32l431kb = [ "stm32-metapac/stm32l431kb" ] +stm32l431kc = [ "stm32-metapac/stm32l431kc" ] +stm32l431rb = [ "stm32-metapac/stm32l431rb" ] +stm32l431rc = [ "stm32-metapac/stm32l431rc" ] +stm32l431vc = [ "stm32-metapac/stm32l431vc" ] +stm32l432kb = [ "stm32-metapac/stm32l432kb" ] +stm32l432kc = [ "stm32-metapac/stm32l432kc" ] +stm32l433cb = [ "stm32-metapac/stm32l433cb" ] +stm32l433cc = [ "stm32-metapac/stm32l433cc" ] +stm32l433rb = [ "stm32-metapac/stm32l433rb" ] +stm32l433rc = [ "stm32-metapac/stm32l433rc" ] +stm32l433vc = [ "stm32-metapac/stm32l433vc" ] +stm32l442kc = [ "stm32-metapac/stm32l442kc" ] +stm32l443cc = [ "stm32-metapac/stm32l443cc" ] +stm32l443rc = [ "stm32-metapac/stm32l443rc" ] +stm32l443vc = [ "stm32-metapac/stm32l443vc" ] +stm32l451cc = [ "stm32-metapac/stm32l451cc" ] +stm32l451ce = [ "stm32-metapac/stm32l451ce" ] +stm32l451rc = [ "stm32-metapac/stm32l451rc" ] +stm32l451re = [ "stm32-metapac/stm32l451re" ] +stm32l451vc = [ "stm32-metapac/stm32l451vc" ] +stm32l451ve = [ "stm32-metapac/stm32l451ve" ] +stm32l452cc = [ "stm32-metapac/stm32l452cc" ] +stm32l452ce = [ "stm32-metapac/stm32l452ce" ] +stm32l452rc = [ "stm32-metapac/stm32l452rc" ] +stm32l452re = [ "stm32-metapac/stm32l452re" ] +stm32l452vc = [ "stm32-metapac/stm32l452vc" ] +stm32l452ve = [ "stm32-metapac/stm32l452ve" ] +stm32l462ce = [ "stm32-metapac/stm32l462ce" ] +stm32l462re = [ "stm32-metapac/stm32l462re" ] +stm32l462ve = [ "stm32-metapac/stm32l462ve" ] +stm32l471qe = [ "stm32-metapac/stm32l471qe" ] +stm32l471qg = [ "stm32-metapac/stm32l471qg" ] +stm32l471re = [ "stm32-metapac/stm32l471re" ] +stm32l471rg = [ "stm32-metapac/stm32l471rg" ] +stm32l471ve = [ "stm32-metapac/stm32l471ve" ] +stm32l471vg = [ "stm32-metapac/stm32l471vg" ] +stm32l471ze = [ "stm32-metapac/stm32l471ze" ] +stm32l471zg = [ "stm32-metapac/stm32l471zg" ] +stm32l475rc = [ "stm32-metapac/stm32l475rc" ] +stm32l475re = [ "stm32-metapac/stm32l475re" ] +stm32l475rg = [ "stm32-metapac/stm32l475rg" ] +stm32l475vc = [ "stm32-metapac/stm32l475vc" ] +stm32l475ve = [ "stm32-metapac/stm32l475ve" ] +stm32l475vg = [ "stm32-metapac/stm32l475vg" ] +stm32l476je = [ "stm32-metapac/stm32l476je" ] +stm32l476jg = [ "stm32-metapac/stm32l476jg" ] +stm32l476me = [ "stm32-metapac/stm32l476me" ] +stm32l476mg = [ "stm32-metapac/stm32l476mg" ] +stm32l476qe = [ "stm32-metapac/stm32l476qe" ] +stm32l476qg = [ "stm32-metapac/stm32l476qg" ] +stm32l476rc = [ "stm32-metapac/stm32l476rc" ] +stm32l476re = [ "stm32-metapac/stm32l476re" ] +stm32l476rg = [ "stm32-metapac/stm32l476rg" ] +stm32l476vc = [ "stm32-metapac/stm32l476vc" ] +stm32l476ve = [ "stm32-metapac/stm32l476ve" ] +stm32l476vg = [ "stm32-metapac/stm32l476vg" ] +stm32l476ze = [ "stm32-metapac/stm32l476ze" ] +stm32l476zg = [ "stm32-metapac/stm32l476zg" ] +stm32l486jg = [ "stm32-metapac/stm32l486jg" ] +stm32l486qg = [ "stm32-metapac/stm32l486qg" ] +stm32l486rg = [ "stm32-metapac/stm32l486rg" ] +stm32l486vg = [ "stm32-metapac/stm32l486vg" ] +stm32l486zg = [ "stm32-metapac/stm32l486zg" ] +stm32l496ae = [ "stm32-metapac/stm32l496ae" ] +stm32l496ag = [ "stm32-metapac/stm32l496ag" ] +stm32l496qe = [ "stm32-metapac/stm32l496qe" ] +stm32l496qg = [ "stm32-metapac/stm32l496qg" ] +stm32l496re = [ "stm32-metapac/stm32l496re" ] +stm32l496rg = [ "stm32-metapac/stm32l496rg" ] +stm32l496ve = [ "stm32-metapac/stm32l496ve" ] +stm32l496vg = [ "stm32-metapac/stm32l496vg" ] +stm32l496wg = [ "stm32-metapac/stm32l496wg" ] +stm32l496ze = [ "stm32-metapac/stm32l496ze" ] +stm32l496zg = [ "stm32-metapac/stm32l496zg" ] +stm32l4a6ag = [ "stm32-metapac/stm32l4a6ag" ] +stm32l4a6qg = [ "stm32-metapac/stm32l4a6qg" ] +stm32l4a6rg = [ "stm32-metapac/stm32l4a6rg" ] +stm32l4a6vg = [ "stm32-metapac/stm32l4a6vg" ] +stm32l4a6zg = [ "stm32-metapac/stm32l4a6zg" ] +stm32l4p5ae = [ "stm32-metapac/stm32l4p5ae" ] +stm32l4p5ag = [ "stm32-metapac/stm32l4p5ag" ] +stm32l4p5ce = [ "stm32-metapac/stm32l4p5ce" ] +stm32l4p5cg = [ "stm32-metapac/stm32l4p5cg" ] +stm32l4p5qe = [ "stm32-metapac/stm32l4p5qe" ] +stm32l4p5qg = [ "stm32-metapac/stm32l4p5qg" ] +stm32l4p5re = [ "stm32-metapac/stm32l4p5re" ] +stm32l4p5rg = [ "stm32-metapac/stm32l4p5rg" ] +stm32l4p5ve = [ "stm32-metapac/stm32l4p5ve" ] +stm32l4p5vg = [ "stm32-metapac/stm32l4p5vg" ] +stm32l4p5ze = [ "stm32-metapac/stm32l4p5ze" ] +stm32l4p5zg = [ "stm32-metapac/stm32l4p5zg" ] +stm32l4q5ag = [ "stm32-metapac/stm32l4q5ag" ] +stm32l4q5cg = [ "stm32-metapac/stm32l4q5cg" ] +stm32l4q5qg = [ "stm32-metapac/stm32l4q5qg" ] +stm32l4q5rg = [ "stm32-metapac/stm32l4q5rg" ] +stm32l4q5vg = [ "stm32-metapac/stm32l4q5vg" ] +stm32l4q5zg = [ "stm32-metapac/stm32l4q5zg" ] +stm32l4r5ag = [ "stm32-metapac/stm32l4r5ag" ] +stm32l4r5ai = [ "stm32-metapac/stm32l4r5ai" ] +stm32l4r5qg = [ "stm32-metapac/stm32l4r5qg" ] +stm32l4r5qi = [ "stm32-metapac/stm32l4r5qi" ] +stm32l4r5vg = [ "stm32-metapac/stm32l4r5vg" ] +stm32l4r5vi = [ "stm32-metapac/stm32l4r5vi" ] +stm32l4r5zg = [ "stm32-metapac/stm32l4r5zg" ] +stm32l4r5zi = [ "stm32-metapac/stm32l4r5zi" ] +stm32l4r7ai = [ "stm32-metapac/stm32l4r7ai" ] +stm32l4r7vi = [ "stm32-metapac/stm32l4r7vi" ] +stm32l4r7zi = [ "stm32-metapac/stm32l4r7zi" ] +stm32l4r9ag = [ "stm32-metapac/stm32l4r9ag" ] +stm32l4r9ai = [ "stm32-metapac/stm32l4r9ai" ] +stm32l4r9vg = [ "stm32-metapac/stm32l4r9vg" ] +stm32l4r9vi = [ "stm32-metapac/stm32l4r9vi" ] +stm32l4r9zg = [ "stm32-metapac/stm32l4r9zg" ] +stm32l4r9zi = [ "stm32-metapac/stm32l4r9zi" ] +stm32l4s5ai = [ "stm32-metapac/stm32l4s5ai" ] +stm32l4s5qi = [ "stm32-metapac/stm32l4s5qi" ] +stm32l4s5vi = [ "stm32-metapac/stm32l4s5vi" ] +stm32l4s5zi = [ "stm32-metapac/stm32l4s5zi" ] +stm32l4s7ai = [ "stm32-metapac/stm32l4s7ai" ] +stm32l4s7vi = [ "stm32-metapac/stm32l4s7vi" ] +stm32l4s7zi = [ "stm32-metapac/stm32l4s7zi" ] +stm32l4s9ai = [ "stm32-metapac/stm32l4s9ai" ] +stm32l4s9vi = [ "stm32-metapac/stm32l4s9vi" ] +stm32l4s9zi = [ "stm32-metapac/stm32l4s9zi" ] +stm32l552cc = [ "stm32-metapac/stm32l552cc" ] +stm32l552ce = [ "stm32-metapac/stm32l552ce" ] +stm32l552me = [ "stm32-metapac/stm32l552me" ] +stm32l552qc = [ "stm32-metapac/stm32l552qc" ] +stm32l552qe = [ "stm32-metapac/stm32l552qe" ] +stm32l552rc = [ "stm32-metapac/stm32l552rc" ] +stm32l552re = [ "stm32-metapac/stm32l552re" ] +stm32l552vc = [ "stm32-metapac/stm32l552vc" ] +stm32l552ve = [ "stm32-metapac/stm32l552ve" ] +stm32l552zc = [ "stm32-metapac/stm32l552zc" ] +stm32l552ze = [ "stm32-metapac/stm32l552ze" ] +stm32l562ce = [ "stm32-metapac/stm32l562ce" ] +stm32l562me = [ "stm32-metapac/stm32l562me" ] +stm32l562qe = [ "stm32-metapac/stm32l562qe" ] +stm32l562re = [ "stm32-metapac/stm32l562re" ] +stm32l562ve = [ "stm32-metapac/stm32l562ve" ] +stm32l562ze = [ "stm32-metapac/stm32l562ze" ] +stm32u031c6 = [ "stm32-metapac/stm32u031c6" ] +stm32u031c8 = [ "stm32-metapac/stm32u031c8" ] +stm32u031f4 = [ "stm32-metapac/stm32u031f4" ] +stm32u031f6 = [ "stm32-metapac/stm32u031f6" ] +stm32u031f8 = [ "stm32-metapac/stm32u031f8" ] +stm32u031g6 = [ "stm32-metapac/stm32u031g6" ] +stm32u031g8 = [ "stm32-metapac/stm32u031g8" ] +stm32u031k4 = [ "stm32-metapac/stm32u031k4" ] +stm32u031k6 = [ "stm32-metapac/stm32u031k6" ] +stm32u031k8 = [ "stm32-metapac/stm32u031k8" ] +stm32u031r6 = [ "stm32-metapac/stm32u031r6" ] +stm32u031r8 = [ "stm32-metapac/stm32u031r8" ] +stm32u073c8 = [ "stm32-metapac/stm32u073c8" ] +stm32u073cb = [ "stm32-metapac/stm32u073cb" ] +stm32u073cc = [ "stm32-metapac/stm32u073cc" ] +stm32u073h8 = [ "stm32-metapac/stm32u073h8" ] +stm32u073hb = [ "stm32-metapac/stm32u073hb" ] +stm32u073hc = [ "stm32-metapac/stm32u073hc" ] +stm32u073k8 = [ "stm32-metapac/stm32u073k8" ] +stm32u073kb = [ "stm32-metapac/stm32u073kb" ] +stm32u073kc = [ "stm32-metapac/stm32u073kc" ] +stm32u073m8 = [ "stm32-metapac/stm32u073m8" ] +stm32u073mb = [ "stm32-metapac/stm32u073mb" ] +stm32u073mc = [ "stm32-metapac/stm32u073mc" ] +stm32u073r8 = [ "stm32-metapac/stm32u073r8" ] +stm32u073rb = [ "stm32-metapac/stm32u073rb" ] +stm32u073rc = [ "stm32-metapac/stm32u073rc" ] +stm32u083cc = [ "stm32-metapac/stm32u083cc" ] +stm32u083hc = [ "stm32-metapac/stm32u083hc" ] +stm32u083kc = [ "stm32-metapac/stm32u083kc" ] +stm32u083mc = [ "stm32-metapac/stm32u083mc" ] +stm32u083rc = [ "stm32-metapac/stm32u083rc" ] +stm32u535cb = [ "stm32-metapac/stm32u535cb" ] +stm32u535cc = [ "stm32-metapac/stm32u535cc" ] +stm32u535ce = [ "stm32-metapac/stm32u535ce" ] +stm32u535je = [ "stm32-metapac/stm32u535je" ] +stm32u535nc = [ "stm32-metapac/stm32u535nc" ] +stm32u535ne = [ "stm32-metapac/stm32u535ne" ] +stm32u535rb = [ "stm32-metapac/stm32u535rb" ] +stm32u535rc = [ "stm32-metapac/stm32u535rc" ] +stm32u535re = [ "stm32-metapac/stm32u535re" ] +stm32u535vc = [ "stm32-metapac/stm32u535vc" ] +stm32u535ve = [ "stm32-metapac/stm32u535ve" ] +stm32u545ce = [ "stm32-metapac/stm32u545ce" ] +stm32u545je = [ "stm32-metapac/stm32u545je" ] +stm32u545ne = [ "stm32-metapac/stm32u545ne" ] +stm32u545re = [ "stm32-metapac/stm32u545re" ] +stm32u545ve = [ "stm32-metapac/stm32u545ve" ] +stm32u575ag = [ "stm32-metapac/stm32u575ag" ] +stm32u575ai = [ "stm32-metapac/stm32u575ai" ] +stm32u575cg = [ "stm32-metapac/stm32u575cg" ] +stm32u575ci = [ "stm32-metapac/stm32u575ci" ] +stm32u575og = [ "stm32-metapac/stm32u575og" ] +stm32u575oi = [ "stm32-metapac/stm32u575oi" ] +stm32u575qg = [ "stm32-metapac/stm32u575qg" ] +stm32u575qi = [ "stm32-metapac/stm32u575qi" ] +stm32u575rg = [ "stm32-metapac/stm32u575rg" ] +stm32u575ri = [ "stm32-metapac/stm32u575ri" ] +stm32u575vg = [ "stm32-metapac/stm32u575vg" ] +stm32u575vi = [ "stm32-metapac/stm32u575vi" ] +stm32u575zg = [ "stm32-metapac/stm32u575zg" ] +stm32u575zi = [ "stm32-metapac/stm32u575zi" ] +stm32u585ai = [ "stm32-metapac/stm32u585ai" ] +stm32u585ci = [ "stm32-metapac/stm32u585ci" ] +stm32u585oi = [ "stm32-metapac/stm32u585oi" ] +stm32u585qi = [ "stm32-metapac/stm32u585qi" ] +stm32u585ri = [ "stm32-metapac/stm32u585ri" ] +stm32u585vi = [ "stm32-metapac/stm32u585vi" ] +stm32u585zi = [ "stm32-metapac/stm32u585zi" ] +stm32u595ai = [ "stm32-metapac/stm32u595ai" ] +stm32u595aj = [ "stm32-metapac/stm32u595aj" ] +stm32u595qi = [ "stm32-metapac/stm32u595qi" ] +stm32u595qj = [ "stm32-metapac/stm32u595qj" ] +stm32u595ri = [ "stm32-metapac/stm32u595ri" ] +stm32u595rj = [ "stm32-metapac/stm32u595rj" ] +stm32u595vi = [ "stm32-metapac/stm32u595vi" ] +stm32u595vj = [ "stm32-metapac/stm32u595vj" ] +stm32u595zi = [ "stm32-metapac/stm32u595zi" ] +stm32u595zj = [ "stm32-metapac/stm32u595zj" ] +stm32u599bj = [ "stm32-metapac/stm32u599bj" ] +stm32u599ni = [ "stm32-metapac/stm32u599ni" ] +stm32u599nj = [ "stm32-metapac/stm32u599nj" ] +stm32u599vi = [ "stm32-metapac/stm32u599vi" ] +stm32u599vj = [ "stm32-metapac/stm32u599vj" ] +stm32u599zi = [ "stm32-metapac/stm32u599zi" ] +stm32u599zj = [ "stm32-metapac/stm32u599zj" ] +stm32u5a5aj = [ "stm32-metapac/stm32u5a5aj" ] +stm32u5a5qi = [ "stm32-metapac/stm32u5a5qi" ] +stm32u5a5qj = [ "stm32-metapac/stm32u5a5qj" ] +stm32u5a5rj = [ "stm32-metapac/stm32u5a5rj" ] +stm32u5a5vj = [ "stm32-metapac/stm32u5a5vj" ] +stm32u5a5zj = [ "stm32-metapac/stm32u5a5zj" ] +stm32u5a9bj = [ "stm32-metapac/stm32u5a9bj" ] +stm32u5a9nj = [ "stm32-metapac/stm32u5a9nj" ] +stm32u5a9vj = [ "stm32-metapac/stm32u5a9vj" ] +stm32u5a9zj = [ "stm32-metapac/stm32u5a9zj" ] +stm32u5f7vi = [ "stm32-metapac/stm32u5f7vi" ] +stm32u5f7vj = [ "stm32-metapac/stm32u5f7vj" ] +stm32u5f9bj = [ "stm32-metapac/stm32u5f9bj" ] +stm32u5f9nj = [ "stm32-metapac/stm32u5f9nj" ] +stm32u5f9vi = [ "stm32-metapac/stm32u5f9vi" ] +stm32u5f9vj = [ "stm32-metapac/stm32u5f9vj" ] +stm32u5f9zi = [ "stm32-metapac/stm32u5f9zi" ] +stm32u5f9zj = [ "stm32-metapac/stm32u5f9zj" ] +stm32u5g7vj = [ "stm32-metapac/stm32u5g7vj" ] +stm32u5g9bj = [ "stm32-metapac/stm32u5g9bj" ] +stm32u5g9nj = [ "stm32-metapac/stm32u5g9nj" ] +stm32u5g9vj = [ "stm32-metapac/stm32u5g9vj" ] +stm32u5g9zj = [ "stm32-metapac/stm32u5g9zj" ] +stm32wb10cc = [ "stm32-metapac/stm32wb10cc" ] +stm32wb15cc = [ "stm32-metapac/stm32wb15cc" ] +stm32wb30ce = [ "stm32-metapac/stm32wb30ce" ] +stm32wb35cc = [ "stm32-metapac/stm32wb35cc" ] +stm32wb35ce = [ "stm32-metapac/stm32wb35ce" ] +stm32wb50cg = [ "stm32-metapac/stm32wb50cg" ] +stm32wb55cc = [ "stm32-metapac/stm32wb55cc" ] +stm32wb55ce = [ "stm32-metapac/stm32wb55ce" ] +stm32wb55cg = [ "stm32-metapac/stm32wb55cg" ] +stm32wb55rc = [ "stm32-metapac/stm32wb55rc" ] +stm32wb55re = [ "stm32-metapac/stm32wb55re" ] +stm32wb55rg = [ "stm32-metapac/stm32wb55rg" ] +stm32wb55vc = [ "stm32-metapac/stm32wb55vc" ] +stm32wb55ve = [ "stm32-metapac/stm32wb55ve" ] +stm32wb55vg = [ "stm32-metapac/stm32wb55vg" ] +stm32wb55vy = [ "stm32-metapac/stm32wb55vy" ] +stm32wba50ke = [ "stm32-metapac/stm32wba50ke" ] +stm32wba50kg = [ "stm32-metapac/stm32wba50kg" ] +stm32wba52ce = [ "stm32-metapac/stm32wba52ce" ] +stm32wba52cg = [ "stm32-metapac/stm32wba52cg" ] +stm32wba52ke = [ "stm32-metapac/stm32wba52ke" ] +stm32wba52kg = [ "stm32-metapac/stm32wba52kg" ] +stm32wba54ce = [ "stm32-metapac/stm32wba54ce" ] +stm32wba54cg = [ "stm32-metapac/stm32wba54cg" ] +stm32wba54ke = [ "stm32-metapac/stm32wba54ke" ] +stm32wba54kg = [ "stm32-metapac/stm32wba54kg" ] +stm32wba55ce = [ "stm32-metapac/stm32wba55ce" ] +stm32wba55cg = [ "stm32-metapac/stm32wba55cg" ] +stm32wba55he = [ "stm32-metapac/stm32wba55he" ] +stm32wba55hg = [ "stm32-metapac/stm32wba55hg" ] +stm32wba55ue = [ "stm32-metapac/stm32wba55ue" ] +stm32wba55ug = [ "stm32-metapac/stm32wba55ug" ] +stm32wl54cc-cm4 = [ "stm32-metapac/stm32wl54cc-cm4", "_dual-core" ] +stm32wl54cc-cm0p = [ "stm32-metapac/stm32wl54cc-cm0p", "_dual-core" ] +stm32wl54jc-cm4 = [ "stm32-metapac/stm32wl54jc-cm4", "_dual-core" ] +stm32wl54jc-cm0p = [ "stm32-metapac/stm32wl54jc-cm0p", "_dual-core" ] +stm32wl55cc-cm4 = [ "stm32-metapac/stm32wl55cc-cm4", "_dual-core" ] +stm32wl55cc-cm0p = [ "stm32-metapac/stm32wl55cc-cm0p", "_dual-core" ] +stm32wl55jc-cm4 = [ "stm32-metapac/stm32wl55jc-cm4", "_dual-core" ] +stm32wl55jc-cm0p = [ "stm32-metapac/stm32wl55jc-cm0p", "_dual-core" ] +stm32wle4c8 = [ "stm32-metapac/stm32wle4c8" ] +stm32wle4cb = [ "stm32-metapac/stm32wle4cb" ] +stm32wle4cc = [ "stm32-metapac/stm32wle4cc" ] +stm32wle4j8 = [ "stm32-metapac/stm32wle4j8" ] +stm32wle4jb = [ "stm32-metapac/stm32wle4jb" ] +stm32wle4jc = [ "stm32-metapac/stm32wle4jc" ] +stm32wle5c8 = [ "stm32-metapac/stm32wle5c8" ] +stm32wle5cb = [ "stm32-metapac/stm32wle5cb" ] +stm32wle5cc = [ "stm32-metapac/stm32wle5cc" ] +stm32wle5j8 = [ "stm32-metapac/stm32wle5j8" ] +stm32wle5jb = [ "stm32-metapac/stm32wle5jb" ] +stm32wle5jc = [ "stm32-metapac/stm32wle5jc" ] diff --git a/.stversions/embassy/embassy-stm32/src/lib~20241114-122315.rs b/.stversions/embassy/embassy-stm32/src/lib~20241114-122315.rs new file mode 100644 index 0000000..1e6185b --- /dev/null +++ b/.stversions/embassy/embassy-stm32/src/lib~20241114-122315.rs @@ -0,0 +1,541 @@ +#![cfg_attr(not(test), no_std)] +#![allow(async_fn_in_trait)] +#![cfg_attr( + docsrs, + doc = "

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

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

\n\n" +)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +// This must go FIRST so that all the other modules see its macros. +mod fmt; +include!(concat!(env!("OUT_DIR"), "/_macros.rs")); + +// Utilities +mod macros; +pub mod time; +/// Operating modes for peripherals. +pub mod mode { + trait SealedMode {} + + /// Operating mode for a peripheral. + #[allow(private_bounds)] + pub trait Mode: SealedMode {} + + macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; + } + + /// Blocking mode. + pub struct Blocking; + /// Async mode. + pub struct Async; + + impl_mode!(Blocking); + impl_mode!(Async); +} + +// Always-present hardware +pub mod dma; +pub mod gpio; +pub mod rcc; +#[cfg(feature = "_time-driver")] +mod time_driver; +pub mod timer; + +// Sometimes-present hardware + +#[cfg(adc)] +pub mod adc; +#[cfg(can)] +pub mod can; +// FIXME: Cordic driver cause stm32u5a5zj crash +#[cfg(all(cordic, not(any(stm32u5a5, stm32u5a9))))] +pub mod cordic; +#[cfg(crc)] +pub mod crc; +#[cfg(cryp)] +pub mod cryp; +#[cfg(dac)] +pub mod dac; +#[cfg(dcmi)] +pub mod dcmi; +#[cfg(dsihost)] +pub mod dsihost; +#[cfg(eth)] +pub mod eth; +#[cfg(feature = "exti")] +pub mod exti; +pub mod flash; +#[cfg(fmc)] +pub mod fmc; +#[cfg(hash)] +pub mod hash; +#[cfg(hrtim)] +pub mod hrtim; +#[cfg(hsem)] +pub mod hsem; +#[cfg(i2c)] +pub mod i2c; +#[cfg(any(all(spi_v1, rcc_f4), spi_v3))] +pub mod i2s; +#[cfg(stm32wb)] +pub mod ipcc; +#[cfg(feature = "low-power")] +pub mod low_power; +#[cfg(lptim)] +pub mod lptim; +#[cfg(ltdc)] +pub mod ltdc; +#[cfg(opamp)] +pub mod opamp; +#[cfg(octospi)] +pub mod ospi; +#[cfg(quadspi)] +pub mod qspi; +#[cfg(rng)] +pub mod rng; +#[cfg(all(rtc, not(rtc_v1)))] +pub mod rtc; +#[cfg(sai)] +pub mod sai; +#[cfg(sdmmc)] +pub mod sdmmc; +#[cfg(spi)] +pub mod spi; +#[cfg(tsc)] +pub mod tsc; +#[cfg(ucpd)] +pub mod ucpd; +#[cfg(uid)] +pub mod uid; +#[cfg(usart)] +pub mod usart; +#[cfg(any(usb, otg))] +pub mod usb; +#[cfg(iwdg)] +pub mod wdg; + +// This must go last, so that it sees all the impl_foo! macros defined earlier. +pub(crate) mod _generated { + #![allow(dead_code)] + #![allow(unused_imports)] + #![allow(non_snake_case)] + #![allow(missing_docs)] + + include!(concat!(env!("OUT_DIR"), "/_generated.rs")); +} + +pub use crate::_generated::interrupt; + +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +/// +/// Example of how to bind one interrupt: +/// +/// ```rust,ignore +/// use embassy_stm32::{bind_interrupts, usb, peripherals}; +/// +/// bind_interrupts!(struct Irqs { +/// OTG_FS => usb::InterruptHandler; +/// }); +/// ``` +/// +/// Example of how to bind multiple interrupts, and multiple handlers to each interrupt, in a single macro invocation: +/// +/// ```rust,ignore +/// use embassy_stm32::{bind_interrupts, i2c, peripherals}; +/// +/// bind_interrupts!(struct Irqs { +/// I2C1 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; +/// I2C2_3 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler, +/// i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; +/// }); +/// ``` + +// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + } + )* + }; +} + +// Reexports +pub use _generated::{peripherals, Peripherals}; +pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +#[cfg(feature = "unstable-pac")] +pub use stm32_metapac as pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use stm32_metapac as pac; + +use crate::interrupt::Priority; +#[cfg(feature = "rt")] +pub use crate::pac::NVIC_PRIO_BITS; + +/// `embassy-stm32` global configuration. +#[non_exhaustive] +#[derive(Clone, Copy)] +pub struct Config { + /// RCC config. + pub rcc: rcc::Config, + + /// Enable debug during sleep and stop. + /// + /// May increase power consumption. Defaults to true. + #[cfg(dbgmcu)] + pub enable_debug_during_sleep: bool, + + /// On low-power boards (eg. `stm32l4`, `stm32l5` and `stm32u5`), + /// some GPIO pins are powered by an auxiliary, independent power supply (`VDDIO2`), + /// which needs to be enabled before these pins can be used. + /// + /// May increase power consumption. Defaults to true. + #[cfg(any(stm32l4, stm32l5, stm32u5))] + pub enable_independent_io_supply: bool, + + /// BDMA interrupt priority. + /// + /// Defaults to P0 (highest). + #[cfg(bdma)] + pub bdma_interrupt_priority: Priority, + + /// DMA interrupt priority. + /// + /// Defaults to P0 (highest). + #[cfg(dma)] + pub dma_interrupt_priority: Priority, + + /// GPDMA interrupt priority. + /// + /// Defaults to P0 (highest). + #[cfg(gpdma)] + pub gpdma_interrupt_priority: Priority, + + /// Enables UCPD1 dead battery functionality. + /// + /// Defaults to false (disabled). + #[cfg(peri_ucpd1)] + pub enable_ucpd1_dead_battery: bool, + + /// Enables UCPD2 dead battery functionality. + /// + /// Defaults to false (disabled). + #[cfg(peri_ucpd2)] + pub enable_ucpd2_dead_battery: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + rcc: Default::default(), + #[cfg(dbgmcu)] + enable_debug_during_sleep: true, + #[cfg(any(stm32l4, stm32l5, stm32u5))] + enable_independent_io_supply: true, + #[cfg(bdma)] + bdma_interrupt_priority: Priority::P0, + #[cfg(dma)] + dma_interrupt_priority: Priority::P0, + #[cfg(gpdma)] + gpdma_interrupt_priority: Priority::P0, + #[cfg(peri_ucpd1)] + enable_ucpd1_dead_battery: false, + #[cfg(peri_ucpd2)] + enable_ucpd2_dead_battery: false, + } + } +} + +/// Initialize the `embassy-stm32` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once at startup, otherwise it panics. +#[cfg(not(feature = "_dual-core"))] +pub fn init(config: Config) -> Peripherals { + init_hw(config) +} + +#[cfg(feature = "_dual-core")] +mod dual_core { + use core::cell::UnsafeCell; + use core::mem::MaybeUninit; + use core::sync::atomic::{AtomicUsize, Ordering}; + + use rcc::Clocks; + + use super::*; + + /// Object containing data that embassy needs to share between cores. + /// + /// It cannot be initialized by the user. The intended use is: + /// + /// ``` + /// use core::mem::MaybeUninit; + /// use embassy_stm32::{init_secondary, SharedData}; + /// + /// #[link_section = ".ram_d3"] + /// static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + /// + /// init_secondary(&SHARED_DATA); + /// ``` + /// + /// This static must be placed in the same position for both cores. How and where this is done is left to the user. + pub struct SharedData { + init_flag: AtomicUsize, + clocks: UnsafeCell>, + config: UnsafeCell>, + } + + unsafe impl Sync for SharedData {} + + const INIT_DONE_FLAG: usize = 0xca11ab1e; + + /// Initialize the `embassy-stm32` HAL with the provided configuration. + /// This function does the actual initialization of the hardware, in contrast to [init_secondary] or [try_init_secondary]. + /// Any core can do the init, but it's important only one core does it. + /// + /// This returns the peripheral singletons that can be used for creating drivers. + /// + /// This should only be called once at startup, otherwise it panics. + /// + /// The `shared_data` is used to coordinate the init with the second core. Read the [SharedData] docs + /// for more information on its requirements. + pub fn init_primary(config: Config, shared_data: &'static MaybeUninit) -> Peripherals { + let shared_data = unsafe { shared_data.assume_init_ref() }; + + rcc::set_freqs_ptr(shared_data.clocks.get()); + let p = init_hw(config); + + unsafe { *shared_data.config.get() }.write(config); + + shared_data.init_flag.store(INIT_DONE_FLAG, Ordering::SeqCst); + + p + } + + /// Try to initialize the `embassy-stm32` HAL based on the init done by the other core using [init_primary]. + /// + /// This returns the peripheral singletons that can be used for creating drivers if the other core is done with its init. + /// If the other core is not done yet, this will return `None`. + /// + /// This should only be called once at startup, otherwise it may panic. + /// + /// The `shared_data` is used to coordinate the init with the second core. Read the [SharedData] docs + /// for more information on its requirements. + pub fn try_init_secondary(shared_data: &'static MaybeUninit) -> Option { + let shared_data = unsafe { shared_data.assume_init_ref() }; + + if shared_data.init_flag.load(Ordering::SeqCst) != INIT_DONE_FLAG { + return None; + } + + // Separate load and store to support the CM0 of the STM32WL + shared_data.init_flag.store(0, Ordering::SeqCst); + + Some(init_secondary_hw(shared_data)) + } + + /// Initialize the `embassy-stm32` HAL based on the init done by the other core using [init_primary]. + /// + /// This returns the peripheral singletons that can be used for creating drivers when the other core is done with its init. + /// If the other core is not done yet, this will spinloop wait on it. + /// + /// This should only be called once at startup, otherwise it may panic. + /// + /// The `shared_data` is used to coordinate the init with the second core. Read the [SharedData] docs + /// for more information on its requirements. + pub fn init_secondary(shared_data: &'static MaybeUninit) -> Peripherals { + loop { + if let Some(p) = try_init_secondary(shared_data) { + return p; + } + } + } + + fn init_secondary_hw(shared_data: &'static SharedData) -> Peripherals { + rcc::set_freqs_ptr(shared_data.clocks.get()); + + let config = unsafe { (*shared_data.config.get()).assume_init() }; + + // We use different timers on the different cores, so we have to still initialize one here + critical_section::with(|cs| { + unsafe { + dma::init( + cs, + #[cfg(bdma)] + config.bdma_interrupt_priority, + #[cfg(dma)] + config.dma_interrupt_priority, + #[cfg(gpdma)] + config.gpdma_interrupt_priority, + ) + } + + #[cfg(feature = "_time-driver")] + // must be after rcc init + time_driver::init(cs); + }); + + Peripherals::take() + } +} + +#[cfg(feature = "_dual-core")] +pub use dual_core::*; + +fn init_hw(config: Config) -> Peripherals { + critical_section::with(|cs| { + let p = Peripherals::take_with_cs(cs); + + #[cfg(dbgmcu)] + crate::pac::DBGMCU.cr().modify(|cr| { + #[cfg(dbgmcu_h5)] + { + cr.set_stop(config.enable_debug_during_sleep); + cr.set_standby(config.enable_debug_during_sleep); + } + #[cfg(any(dbgmcu_f0, dbgmcu_c0, dbgmcu_g0, dbgmcu_u5, dbgmcu_wba, dbgmcu_l5))] + { + cr.set_dbg_stop(config.enable_debug_during_sleep); + cr.set_dbg_standby(config.enable_debug_during_sleep); + } + #[cfg(any( + dbgmcu_f1, dbgmcu_f2, dbgmcu_f3, dbgmcu_f4, dbgmcu_f7, dbgmcu_g4, dbgmcu_f7, dbgmcu_l0, dbgmcu_l1, + dbgmcu_l4, dbgmcu_wb, dbgmcu_wl + ))] + { + cr.set_dbg_sleep(config.enable_debug_during_sleep); + cr.set_dbg_stop(config.enable_debug_during_sleep); + cr.set_dbg_standby(config.enable_debug_during_sleep); + } + #[cfg(dbgmcu_h7)] + { + cr.set_d1dbgcken(config.enable_debug_during_sleep); + cr.set_d3dbgcken(config.enable_debug_during_sleep); + cr.set_dbgsleep_d1(config.enable_debug_during_sleep); + cr.set_dbgstby_d1(config.enable_debug_during_sleep); + cr.set_dbgstop_d1(config.enable_debug_during_sleep); + } + }); + + #[cfg(not(any(stm32f1, stm32wb, stm32wl)))] + rcc::enable_and_reset_with_cs::(cs); + #[cfg(not(any(stm32h5, stm32h7, stm32h7rs, stm32wb, stm32wl)))] + rcc::enable_and_reset_with_cs::(cs); + #[cfg(not(any(stm32f2, stm32f4, stm32f7, stm32l0, stm32h5, stm32h7, stm32h7rs)))] + rcc::enable_and_reset_with_cs::(cs); + + // Enable the VDDIO2 power supply on chips that have it. + // Note that this requires the PWR peripheral to be enabled first. + #[cfg(any(stm32l4, stm32l5))] + { + crate::pac::PWR.cr2().modify(|w| { + // The official documentation states that we should ideally enable VDDIO2 + // through the PVME2 bit, but it looks like this isn't required, + // and CubeMX itself skips this step. + w.set_iosv(config.enable_independent_io_supply); + }); + } + #[cfg(stm32u5)] + { + crate::pac::PWR.svmcr().modify(|w| { + w.set_io2sv(config.enable_independent_io_supply); + }); + } + + // dead battery functionality is still present on these + // chips despite them not having UCPD- disable it + #[cfg(any(stm32g070, stm32g0b0))] + { + crate::pac::SYSCFG.cfgr1().modify(|w| { + w.set_ucpd1_strobe(true); + w.set_ucpd2_strobe(true); + }); + } + + unsafe { + #[cfg(ucpd)] + ucpd::init( + cs, + #[cfg(peri_ucpd1)] + config.enable_ucpd1_dead_battery, + #[cfg(peri_ucpd2)] + config.enable_ucpd2_dead_battery, + ); + + #[cfg(feature = "_split-pins-enabled")] + crate::pac::SYSCFG.pmcr().modify(|pmcr| { + #[cfg(feature = "split-pa0")] + pmcr.set_pa0so(true); + #[cfg(feature = "split-pa1")] + pmcr.set_pa1so(true); + #[cfg(feature = "split-pc2")] + pmcr.set_pc2so(true); + #[cfg(feature = "split-pc3")] + pmcr.set_pc3so(true); + }); + + gpio::init(cs); + dma::init( + cs, + #[cfg(bdma)] + config.bdma_interrupt_priority, + #[cfg(dma)] + config.dma_interrupt_priority, + #[cfg(gpdma)] + config.gpdma_interrupt_priority, + ); + #[cfg(feature = "exti")] + exti::init(cs); + + rcc::init(config.rcc); + + // must be after rcc init + #[cfg(feature = "_time-driver")] + time_driver::init(cs); + + #[cfg(feature = "low-power")] + { + crate::rcc::REFCOUNT_STOP2 = 0; + crate::rcc::REFCOUNT_STOP1 = 0; + } + } + + p + }) +} diff --git a/.stversions/embassy/embassy-stm32/src/rcc/h~20241114-122315.rs b/.stversions/embassy/embassy-stm32/src/rcc/h~20241114-122315.rs new file mode 100644 index 0000000..55fe8ca --- /dev/null +++ b/.stversions/embassy/embassy-stm32/src/rcc/h~20241114-122315.rs @@ -0,0 +1,1007 @@ +use core::ops::RangeInclusive; + +use crate::pac; +pub use crate::pac::rcc::vals::{ + Hsidiv as HSIPrescaler, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, Pllsrc as PllSource, Sw as Sysclk, +}; +use crate::pac::rcc::vals::{Pllrge, Pllvcosel, Timpre}; +use crate::pac::{FLASH, PWR, RCC}; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(64_000_000); + +/// CSI speed +pub const CSI_FREQ: Hertz = Hertz(4_000_000); + +const VCO_RANGE: RangeInclusive = Hertz(150_000_000)..=Hertz(420_000_000); +#[cfg(any(stm32h5, pwr_h7rm0455))] +const VCO_WIDE_RANGE: RangeInclusive = Hertz(128_000_000)..=Hertz(560_000_000); +#[cfg(pwr_h7rm0468)] +const VCO_WIDE_RANGE: RangeInclusive = Hertz(192_000_000)..=Hertz(836_000_000); +#[cfg(any(pwr_h7rm0399, pwr_h7rm0433))] +const VCO_WIDE_RANGE: RangeInclusive = Hertz(192_000_000)..=Hertz(960_000_000); +#[cfg(any(stm32h7rs))] +const VCO_WIDE_RANGE: RangeInclusive = Hertz(384_000_000)..=Hertz(1672_000_000); + +pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Ppre as APBPrescaler}; + +#[cfg(any(stm32h5, stm32h7))] +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum VoltageScale { + Scale0, + Scale1, + Scale2, + Scale3, +} +#[cfg(stm32h7rs)] +pub use crate::pac::pwr::vals::Vos as VoltageScale; +#[cfg(all(stm32h7rs, peri_usb_otg_hs))] +pub use crate::pac::rcc::vals::{Usbphycsel, Usbrefcksel}; + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1, HSEEXT=0) + Bypass, + /// external digital clock (full swing) (HSEBYP=1, HSEEXT=1) + #[cfg(any(rcc_h5, rcc_h50, rcc_h7rs))] + BypassDigital, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, +} + +#[derive(Clone, Copy)] +pub struct Pll { + /// Source clock selection. + pub source: PllSource, + + /// PLL pre-divider (DIVM). + pub prediv: PllPreDiv, + + /// PLL multiplication factor. + pub mul: PllMul, + + /// PLL P division factor. If None, PLL P output is disabled. + /// On PLL1, it must be even for most series (in particular, + /// it cannot be 1 in series other than stm32h7, stm32h7rs23/733, + /// stm32h7, stm32h7rs25/735 and stm32h7, stm32h7rs30.) + pub divp: Option, + /// PLL Q division factor. If None, PLL Q output is disabled. + pub divq: Option, + /// PLL R division factor. If None, PLL R output is disabled. + pub divr: Option, +} + +fn apb_div_tim(apb: &APBPrescaler, clk: Hertz, tim: TimerPrescaler) -> Hertz { + match (tim, apb) { + (TimerPrescaler::DefaultX2, APBPrescaler::DIV1) => clk, + (TimerPrescaler::DefaultX2, APBPrescaler::DIV2) => clk, + (TimerPrescaler::DefaultX2, APBPrescaler::DIV4) => clk / 2u32, + (TimerPrescaler::DefaultX2, APBPrescaler::DIV8) => clk / 4u32, + (TimerPrescaler::DefaultX2, APBPrescaler::DIV16) => clk / 8u32, + + (TimerPrescaler::DefaultX4, APBPrescaler::DIV1) => clk, + (TimerPrescaler::DefaultX4, APBPrescaler::DIV2) => clk, + (TimerPrescaler::DefaultX4, APBPrescaler::DIV4) => clk, + (TimerPrescaler::DefaultX4, APBPrescaler::DIV8) => clk / 2u32, + (TimerPrescaler::DefaultX4, APBPrescaler::DIV16) => clk / 4u32, + + _ => unreachable!(), + } +} + +/// Timer prescaler +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum TimerPrescaler { + /// The timers kernel clock is equal to hclk if PPREx corresponds to a + /// division by 1 or 2, else it is equal to 2*pclk + DefaultX2, + + /// The timers kernel clock is equal to hclk if PPREx corresponds to a + /// division by 1, 2 or 4, else it is equal to 4*pclk + DefaultX4, +} + +impl From for Timpre { + fn from(value: TimerPrescaler) -> Self { + match value { + TimerPrescaler::DefaultX2 => Timpre::DEFAULTX2, + TimerPrescaler::DefaultX4 => Timpre::DEFAULTX4, + } + } +} + +/// Power supply configuration +/// See RM0433 Rev 4 7.4 +#[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] +#[derive(Clone, Copy, PartialEq)] +pub enum SupplyConfig { + /// Default power supply configuration. + /// V CORE Power Domains are supplied from the LDO according to VOS. + /// SMPS step-down converter enabled at 1.2V, may be used to supply the LDO. + Default, + + /// Power supply configuration using the LDO. + /// V CORE Power Domains are supplied from the LDO according to VOS. + /// LDO power mode (Main, LP, Off) will follow system low-power modes. + /// SMPS step-down converter disabled. + LDO, + + /// Power supply configuration directly from the SMPS step-down converter. + /// V CORE Power Domains are supplied from SMPS step-down converter according to VOS. + /// LDO bypassed. + /// SMPS step-down converter power mode (MR, LP, Off) will follow system low-power modes. + DirectSMPS, + + /// Power supply configuration from the SMPS step-down converter, that supplies the LDO. + /// V CORE Power Domains are supplied from the LDO according to VOS + /// LDO power mode (Main, LP, Off) will follow system low-power modes. + /// SMPS step-down converter enabled according to SDLEVEL, and supplies the LDO. + /// SMPS step-down converter power mode (MR, LP, Off) will follow system low-power modes. + SMPSLDO(SMPSSupplyVoltage), + + /// Power supply configuration from SMPS supplying external circuits and potentially the LDO. + /// V CORE Power Domains are supplied from voltage regulator according to VOS + /// LDO power mode (Main, LP, Off) will follow system low-power modes. + /// SMPS step-down converter enabled according to SDLEVEL used to supply external circuits and may supply the LDO. + /// SMPS step-down converter forced ON in MR mode. + SMPSExternalLDO(SMPSSupplyVoltage), + + /// Power supply configuration from SMPS supplying external circuits and bypassing the LDO. + /// V CORE supplied from external source + /// SMPS step-down converter enabled according to SDLEVEL used to supply external circuits and may supply the external source for V CORE . + /// SMPS step-down converter forced ON in MR mode. + SMPSExternalLDOBypass(SMPSSupplyVoltage), + + /// Power supply configuration from an external source, SMPS disabled and the LDO bypassed. + /// V CORE supplied from external source + /// SMPS step-down converter disabled and LDO bypassed, voltage monitoring still active. + SMPSDisabledLDOBypass, +} + +/// SMPS step-down converter voltage output level. +/// This is only used in certain power supply configurations: +/// SMPSLDO, SMPSExternalLDO, SMPSExternalLDOBypass. +#[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum SMPSSupplyVoltage { + /// 1.8v + V1_8, + /// 2.5v + #[cfg(not(pwr_h7rs))] + V2_5, +} + +/// Configuration of the core clocks +#[non_exhaustive] +#[derive(Clone, Copy)] +pub struct Config { + pub hsi: Option, + pub hse: Option, + pub csi: bool, + pub hsi48: Option, + pub sys: Sysclk, + + pub pll1: Option, + pub pll2: Option, + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + pub pll3: Option, + + #[cfg(any(stm32h7, stm32h7rs))] + pub d1c_pre: AHBPrescaler, + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + pub apb2_pre: APBPrescaler, + #[cfg(not(stm32h7rs))] + pub apb3_pre: APBPrescaler, + #[cfg(any(stm32h7, stm32h7rs))] + pub apb4_pre: APBPrescaler, + #[cfg(stm32h7rs)] + pub apb5_pre: APBPrescaler, + + pub timer_prescaler: TimerPrescaler, + pub voltage_scale: VoltageScale, + pub ls: super::LsConfig, + + #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] + pub supply_config: SupplyConfig, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, +} + +impl Default for Config { + fn default() -> Self { + Self { + hsi: Some(HSIPrescaler::DIV1), + hse: None, + csi: false, + hsi48: Some(Default::default()), + sys: Sysclk::HSI, + pll1: None, + pll2: None, + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + pll3: None, + + #[cfg(any(stm32h7, stm32h7rs))] + d1c_pre: AHBPrescaler::DIV1, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + apb2_pre: APBPrescaler::DIV1, + #[cfg(not(stm32h7rs))] + apb3_pre: APBPrescaler::DIV1, + #[cfg(any(stm32h7, stm32h7rs))] + apb4_pre: APBPrescaler::DIV1, + #[cfg(stm32h7rs)] + apb5_pre: APBPrescaler::DIV1, + + timer_prescaler: TimerPrescaler::DefaultX2, + #[cfg(not(rcc_h7rs))] + voltage_scale: VoltageScale::Scale0, + #[cfg(rcc_h7rs)] + voltage_scale: VoltageScale::HIGH, + ls: Default::default(), + + #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] + supply_config: SupplyConfig::LDO, + + mux: Default::default(), + } + } +} + +pub(crate) unsafe fn init(config: Config) { + #[cfg(any(stm32h7))] + let pwr_reg = PWR.cr3(); + #[cfg(any(stm32h7rs))] + let pwr_reg = PWR.csr2(); + + // NB. The lower bytes of CR3 can only be written once after + // POR, and must be written with a valid combination. Refer to + // RM0433 Rev 7 6.8.4. This is partially enforced by dropping + // `self` at the end of this method, but of course we cannot + // know what happened between the previous POR and here. + #[cfg(pwr_h7rm0433)] + pwr_reg.modify(|w| { + w.set_scuen(true); + w.set_ldoen(true); + w.set_bypass(false); + }); + + #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] + { + use pac::pwr::vals::Sdlevel; + match config.supply_config { + SupplyConfig::Default => { + pwr_reg.modify(|w| { + w.set_sdlevel(Sdlevel::RESET); + w.set_sdexthp(false); + w.set_sden(true); + w.set_ldoen(true); + w.set_bypass(false); + }); + } + SupplyConfig::LDO => { + pwr_reg.modify(|w| { + w.set_sden(false); + w.set_ldoen(true); + w.set_bypass(false); + }); + } + SupplyConfig::DirectSMPS => { + pwr_reg.modify(|w| { + w.set_sdexthp(false); + w.set_sden(true); + w.set_ldoen(false); + w.set_bypass(false); + }); + } + SupplyConfig::SMPSLDO(sdlevel) + | SupplyConfig::SMPSExternalLDO(sdlevel) + | SupplyConfig::SMPSExternalLDOBypass(sdlevel) => { + let sdlevel = match sdlevel { + SMPSSupplyVoltage::V1_8 => Sdlevel::V1_8, + #[cfg(not(pwr_h7rs))] + SMPSSupplyVoltage::V2_5 => Sdlevel::V2_5, + }; + pwr_reg.modify(|w| { + w.set_sdlevel(sdlevel); + w.set_sdexthp(matches!( + config.supply_config, + SupplyConfig::SMPSExternalLDO(_) | SupplyConfig::SMPSExternalLDOBypass(_) + )); + w.set_sden(true); + w.set_ldoen(matches!( + config.supply_config, + SupplyConfig::SMPSLDO(_) | SupplyConfig::SMPSExternalLDO(_) + )); + w.set_bypass(matches!(config.supply_config, SupplyConfig::SMPSExternalLDOBypass(_))); + }); + } + SupplyConfig::SMPSDisabledLDOBypass => { + pwr_reg.modify(|w| { + w.set_sden(false); + w.set_ldoen(false); + w.set_bypass(true); + }); + } + } + } + + // Validate the supply configuration. If you are stuck here, it is + // because the voltages on your board do not match those specified + // in the D3CR.VOS and CR3.SDLEVEL fields. By default after reset + // VOS = Scale 3, so check that the voltage on the VCAP pins = + // 1.0V. + #[cfg(any(stm32h7))] + while !PWR.csr1().read().actvosrdy() {} + #[cfg(any(stm32h7rs))] + while !PWR.sr1().read().actvosrdy() {} + + // Configure voltage scale. + #[cfg(any(pwr_h5, pwr_h50))] + { + PWR.voscr().modify(|w| { + w.set_vos(match config.voltage_scale { + VoltageScale::Scale0 => crate::pac::pwr::vals::Vos::SCALE0, + VoltageScale::Scale1 => crate::pac::pwr::vals::Vos::SCALE1, + VoltageScale::Scale2 => crate::pac::pwr::vals::Vos::SCALE2, + VoltageScale::Scale3 => crate::pac::pwr::vals::Vos::SCALE3, + }) + }); + while !PWR.vossr().read().vosrdy() {} + } + #[cfg(syscfg_h7)] + { + // in chips without the overdrive bit, we can go from any scale to any scale directly. + PWR.d3cr().modify(|w| { + w.set_vos(match config.voltage_scale { + VoltageScale::Scale0 => crate::pac::pwr::vals::Vos::SCALE0, + VoltageScale::Scale1 => crate::pac::pwr::vals::Vos::SCALE1, + VoltageScale::Scale2 => crate::pac::pwr::vals::Vos::SCALE2, + VoltageScale::Scale3 => crate::pac::pwr::vals::Vos::SCALE3, + }) + }); + while !PWR.d3cr().read().vosrdy() {} + } + #[cfg(pwr_h7rs)] + { + PWR.csr4().modify(|w| w.set_vos(config.voltage_scale)); + while !PWR.csr4().read().vosrdy() {} + } + + #[cfg(syscfg_h7od)] + { + match config.voltage_scale { + VoltageScale::Scale0 => { + // to go to scale0, we must go to Scale1 first... + PWR.d3cr().modify(|w| w.set_vos(crate::pac::pwr::vals::Vos::SCALE1)); + while !PWR.d3cr().read().vosrdy() {} + + // Then enable overdrive. + critical_section::with(|_| pac::SYSCFG.pwrcr().modify(|w| w.set_oden(1))); + while !PWR.d3cr().read().vosrdy() {} + } + _ => { + // for all other scales, we can go directly. + PWR.d3cr().modify(|w| { + w.set_vos(match config.voltage_scale { + VoltageScale::Scale0 => unreachable!(), + VoltageScale::Scale1 => crate::pac::pwr::vals::Vos::SCALE1, + VoltageScale::Scale2 => crate::pac::pwr::vals::Vos::SCALE2, + VoltageScale::Scale3 => crate::pac::pwr::vals::Vos::SCALE3, + }) + }); + while !PWR.d3cr().read().vosrdy() {} + } + } + } + + // Turn on the HSI + match config.hsi { + None => RCC.cr().modify(|w| w.set_hsion(true)), + Some(hsidiv) => RCC.cr().modify(|w| { + w.set_hsidiv(hsidiv); + w.set_hsion(true); + }), + } + while !RCC.cr().read().hsirdy() {} + + // Use the HSI clock as system clock during the actual clock setup + RCC.cfgr().modify(|w| w.set_sw(Sysclk::HSI)); + while RCC.cfgr().read().sws() != Sysclk::HSI {} + + // Configure HSI + let hsi = match config.hsi { + None => None, + Some(hsidiv) => Some(HSI_FREQ / hsidiv), + }; + + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + RCC.cr().modify(|w| { + w.set_hsebyp(hse.mode != HseMode::Oscillator); + #[cfg(any(rcc_h5, rcc_h50, rcc_h7rs))] + w.set_hseext(match hse.mode { + HseMode::Oscillator | HseMode::Bypass => pac::rcc::vals::Hseext::ANALOG, + HseMode::BypassDigital => pac::rcc::vals::Hseext::DIGITAL, + }); + }); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + // Configure HSI48. + let hsi48 = config.hsi48.map(super::init_hsi48); + + // Configure CSI. + RCC.cr().modify(|w| w.set_csion(config.csi)); + let csi = match config.csi { + false => None, + true => { + while !RCC.cr().read().csirdy() {} + Some(CSI_FREQ) + } + }; + + // H7 has shared PLLSRC, check it's equal in all PLLs. + #[cfg(any(stm32h7, stm32h7rs))] + { + let plls = [&config.pll1, &config.pll2, &config.pll3]; + if !super::util::all_equal(plls.into_iter().flatten().map(|p| p.source)) { + panic!("Source must be equal across all enabled PLLs.") + }; + } + + // Configure PLLs. + let pll_input = PllInput { csi, hse, hsi }; + let pll1 = init_pll(0, config.pll1, &pll_input); + let pll2 = init_pll(1, config.pll2, &pll_input); + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + let pll3 = init_pll(2, config.pll3, &pll_input); + + // Configure sysclk + let sys = match config.sys { + Sysclk::HSI => unwrap!(hsi), + Sysclk::HSE => unwrap!(hse), + Sysclk::CSI => unwrap!(csi), + Sysclk::PLL1_P => unwrap!(pll1.p), + _ => unreachable!(), + }; + + // Check limits. + #[cfg(stm32h5)] + let (hclk_max, pclk_max) = match config.voltage_scale { + VoltageScale::Scale0 => (Hertz(250_000_000), Hertz(250_000_000)), + VoltageScale::Scale1 => (Hertz(200_000_000), Hertz(200_000_000)), + VoltageScale::Scale2 => (Hertz(150_000_000), Hertz(150_000_000)), + VoltageScale::Scale3 => (Hertz(100_000_000), Hertz(100_000_000)), + }; + #[cfg(pwr_h7rm0455)] + let (d1cpre_clk_max, hclk_max, pclk_max) = match config.voltage_scale { + VoltageScale::Scale0 => (Hertz(280_000_000), Hertz(280_000_000), Hertz(140_000_000)), + VoltageScale::Scale1 => (Hertz(225_000_000), Hertz(225_000_000), Hertz(112_500_000)), + VoltageScale::Scale2 => (Hertz(160_000_000), Hertz(160_000_000), Hertz(80_000_000)), + VoltageScale::Scale3 => (Hertz(88_000_000), Hertz(88_000_000), Hertz(44_000_000)), + }; + #[cfg(pwr_h7rm0468)] + let (d1cpre_clk_max, hclk_max, pclk_max) = match config.voltage_scale { + VoltageScale::Scale0 => { + let d1cpre_clk_max = if pac::SYSCFG.ur18().read().cpu_freq_boost() { + 550_000_000 + } else { + 520_000_000 + }; + (Hertz(d1cpre_clk_max), Hertz(275_000_000), Hertz(137_500_000)) + } + VoltageScale::Scale1 => (Hertz(400_000_000), Hertz(200_000_000), Hertz(100_000_000)), + VoltageScale::Scale2 => (Hertz(300_000_000), Hertz(150_000_000), Hertz(75_000_000)), + VoltageScale::Scale3 => (Hertz(170_000_000), Hertz(85_000_000), Hertz(42_500_000)), + }; + #[cfg(all(stm32h7, not(any(pwr_h7rm0455, pwr_h7rm0468))))] + let (d1cpre_clk_max, hclk_max, pclk_max) = match config.voltage_scale { + VoltageScale::Scale0 => (Hertz(480_000_000), Hertz(240_000_000), Hertz(120_000_000)), + VoltageScale::Scale1 => (Hertz(400_000_000), Hertz(200_000_000), Hertz(100_000_000)), + VoltageScale::Scale2 => (Hertz(300_000_000), Hertz(150_000_000), Hertz(75_000_000)), + VoltageScale::Scale3 => (Hertz(200_000_000), Hertz(100_000_000), Hertz(50_000_000)), + }; + #[cfg(stm32h7rs)] + let (d1cpre_clk_max, hclk_max, pclk_max) = match config.voltage_scale { + VoltageScale::HIGH => (Hertz(600_000_000), Hertz(300_000_000), Hertz(150_000_000)), + VoltageScale::LOW => (Hertz(400_000_000), Hertz(200_000_000), Hertz(100_000_000)), + }; + + #[cfg(any(stm32h7, stm32h7rs))] + let hclk = { + let d1cpre_clk = sys / config.d1c_pre; + assert!(d1cpre_clk <= d1cpre_clk_max); + sys / config.ahb_pre + }; + #[cfg(stm32h5)] + let hclk = sys / config.ahb_pre; + assert!(hclk <= hclk_max); + + let apb1 = hclk / config.apb1_pre; + let apb1_tim = apb_div_tim(&config.apb1_pre, hclk, config.timer_prescaler); + assert!(apb1 <= pclk_max); + let apb2 = hclk / config.apb2_pre; + let apb2_tim = apb_div_tim(&config.apb2_pre, hclk, config.timer_prescaler); + assert!(apb2 <= pclk_max); + #[cfg(not(stm32h7rs))] + let apb3 = hclk / config.apb3_pre; + #[cfg(not(stm32h7rs))] + assert!(apb3 <= pclk_max); + #[cfg(any(stm32h7, stm32h7rs))] + let apb4 = hclk / config.apb4_pre; + #[cfg(any(stm32h7, stm32h7rs))] + assert!(apb4 <= pclk_max); + #[cfg(stm32h7rs)] + let apb5 = hclk / config.apb5_pre; + #[cfg(stm32h7rs)] + assert!(apb5 <= pclk_max); + + flash_setup(hclk, config.voltage_scale); + + let rtc = config.ls.init(); + + #[cfg(all(stm32h7rs, peri_usb_otg_hs))] + let usb_refck = match config.mux.usbphycsel { + Usbphycsel::HSE => hse, + Usbphycsel::HSE_DIV_2 => hse.map(|hse_val| hse_val / 2u8), + Usbphycsel::PLL3_Q => pll3.q, + _ => None, + }; + #[cfg(all(stm32h7rs, peri_usb_otg_hs))] + let usb_refck_sel = match usb_refck { + Some(clk_val) => match clk_val { + Hertz(16_000_000) => Usbrefcksel::MHZ16, + Hertz(19_200_000) => Usbrefcksel::MHZ19_2, + Hertz(20_000_000) => Usbrefcksel::MHZ20, + Hertz(24_000_000) => Usbrefcksel::MHZ24, + Hertz(26_000_000) => Usbrefcksel::MHZ26, + Hertz(32_000_000) => Usbrefcksel::MHZ32, + _ => panic!("cannot select USBPHYC reference clock with source frequency of {} Hz, must be one of 16, 19.2, 20, 24, 26, 32 MHz", clk_val), + }, + None => Usbrefcksel::MHZ24, + }; + + #[cfg(stm32h7)] + { + RCC.d1cfgr().modify(|w| { + w.set_d1cpre(config.d1c_pre); + w.set_d1ppre(config.apb3_pre); + w.set_hpre(config.ahb_pre); + }); + // Ensure core prescaler value is valid before future lower core voltage + while RCC.d1cfgr().read().d1cpre() != config.d1c_pre {} + + RCC.d2cfgr().modify(|w| { + w.set_d2ppre1(config.apb1_pre); + w.set_d2ppre2(config.apb2_pre); + }); + RCC.d3cfgr().modify(|w| { + w.set_d3ppre(config.apb4_pre); + }); + } + #[cfg(stm32h7rs)] + { + RCC.cdcfgr().write(|w| { + w.set_cpre(config.d1c_pre); + }); + while RCC.cdcfgr().read().cpre() != config.d1c_pre {} + + RCC.bmcfgr().write(|w| { + w.set_bmpre(config.ahb_pre); + }); + while RCC.bmcfgr().read().bmpre() != config.ahb_pre {} + + RCC.apbcfgr().modify(|w| { + w.set_ppre1(config.apb1_pre); + w.set_ppre2(config.apb2_pre); + w.set_ppre4(config.apb4_pre); + w.set_ppre5(config.apb5_pre); + }); + + #[cfg(peri_usb_otg_hs)] + RCC.ahbperckselr().modify(|w| { + w.set_usbrefcksel(usb_refck_sel); + }); + } + #[cfg(stm32h5)] + { + // Set hpre + RCC.cfgr2().modify(|w| w.set_hpre(config.ahb_pre)); + while RCC.cfgr2().read().hpre() != config.ahb_pre {} + + // set ppre + RCC.cfgr2().modify(|w| { + w.set_ppre1(config.apb1_pre); + w.set_ppre2(config.apb2_pre); + w.set_ppre3(config.apb3_pre); + }); + } + + RCC.cfgr().modify(|w| w.set_timpre(config.timer_prescaler.into())); + + RCC.cfgr().modify(|w| w.set_sw(config.sys)); + while RCC.cfgr().read().sws() != config.sys {} + + // Disable HSI if not used + if config.hsi.is_none() { + RCC.cr().modify(|w| w.set_hsion(false)); + } + + // IO compensation cell - Requires CSI clock and SYSCFG + #[cfg(any(stm32h7))] // TODO h5, h7rs + if csi.is_some() { + // Enable the compensation cell, using back-bias voltage code + // provide by the cell. + critical_section::with(|_| { + pac::SYSCFG.cccsr().modify(|w| { + w.set_en(true); + w.set_cs(false); + w.set_hslv(false); + }) + }); + while !pac::SYSCFG.cccsr().read().rdy() {} + } + + config.mux.init(); + + set_clocks!( + sys: Some(sys), + hclk1: Some(hclk), + hclk2: Some(hclk), + hclk3: Some(hclk), + hclk4: Some(hclk), + #[cfg(stm32h7rs)] + hclk5: Some(hclk), + pclk1: Some(apb1), + pclk2: Some(apb2), + #[cfg(not(stm32h7rs))] + pclk3: Some(apb3), + #[cfg(any(stm32h7, stm32h7rs))] + pclk4: Some(apb4), + #[cfg(stm32h7rs)] + pclk5: Some(apb5), + + pclk1_tim: Some(apb1_tim), + pclk2_tim: Some(apb2_tim), + rtc: rtc, + + hsi: hsi, + hsi48: hsi48, + csi: csi, + csi_div_122: csi.map(|c| c / 122u32), + hse: hse, + + lse: None, + lsi: None, + + pll1_q: pll1.q, + pll2_p: pll2.p, + pll2_q: pll2.q, + pll2_r: pll2.r, + #[cfg(stm32h7rs)] + pll2_s: None, // TODO + #[cfg(stm32h7rs)] + pll2_t: None, // TODO + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + pll3_p: pll3.p, + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + pll3_q: pll3.q, + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + pll3_r: pll3.r, + + #[cfg(rcc_h50)] + pll3_p: None, + #[cfg(rcc_h50)] + pll3_q: None, + #[cfg(rcc_h50)] + pll3_r: None, + + #[cfg(dsihost)] + dsi_phy: None, // DSI PLL clock not supported, don't call `RccPeripheral::frequency()` in the drivers + + #[cfg(stm32h5)] + audioclk: None, + i2s_ckin: None, + #[cfg(stm32h7rs)] + spdifrx_symb: None, // TODO + #[cfg(stm32h7rs)] + clk48mohci: None, // TODO + #[cfg(stm32h7rs)] + hse_div_2: hse.map(|clk| clk / 2u32), + #[cfg(stm32h7rs)] + usb: Some(Hertz(48_000_000)), + ); +} + +struct PllInput { + hsi: Option, + hse: Option, + csi: Option, +} + +struct PllOutput { + p: Option, + #[allow(dead_code)] + q: Option, + #[allow(dead_code)] + r: Option, +} + +fn init_pll(num: usize, config: Option, input: &PllInput) -> PllOutput { + let Some(config) = config else { + // Stop PLL + RCC.cr().modify(|w| w.set_pllon(num, false)); + while RCC.cr().read().pllrdy(num) {} + + // "To save power when PLL1 is not used, the value of PLL1M must be set to 0."" + #[cfg(any(stm32h7, stm32h7rs))] + RCC.pllckselr().write(|w| w.set_divm(num, PllPreDiv::from_bits(0))); + #[cfg(stm32h5)] + RCC.pllcfgr(num).write(|w| w.set_divm(PllPreDiv::from_bits(0))); + + return PllOutput { + p: None, + q: None, + r: None, + }; + }; + + let in_clk = match config.source { + PllSource::DISABLE => panic!("must not set PllSource::Disable"), + PllSource::HSI => unwrap!(input.hsi), + PllSource::HSE => unwrap!(input.hse), + PllSource::CSI => unwrap!(input.csi), + }; + + let ref_clk = in_clk / config.prediv as u32; + + let ref_range = match ref_clk.0 { + ..=1_999_999 => Pllrge::RANGE1, + ..=3_999_999 => Pllrge::RANGE2, + ..=7_999_999 => Pllrge::RANGE4, + ..=16_000_000 => Pllrge::RANGE8, + x => panic!("pll ref_clk out of range: {} mhz", x), + }; + + // The smaller range (150 to 420 MHz) must + // be chosen when the reference clock frequency is lower than 2 MHz. + let wide_allowed = ref_range != Pllrge::RANGE1; + + let vco_clk = ref_clk * config.mul; + let vco_range = if VCO_RANGE.contains(&vco_clk) { + Pllvcosel::MEDIUMVCO + } else if wide_allowed && VCO_WIDE_RANGE.contains(&vco_clk) { + Pllvcosel::WIDEVCO + } else { + panic!("pll vco_clk out of range: {} hz", vco_clk.0) + }; + + let p = config.divp.map(|div| { + if num == 0 { + // on PLL1, DIVP must be even for most series. + // The enum value is 1 less than the divider, so check it's odd. + #[cfg(not(any(pwr_h7rm0468, stm32h7rs)))] + assert!(div.to_bits() % 2 == 1); + #[cfg(pwr_h7rm0468)] + assert!(div.to_bits() % 2 == 1 || div.to_bits() == 0); + } + + vco_clk / div + }); + let q = config.divq.map(|div| vco_clk / div); + let r = config.divr.map(|div| vco_clk / div); + + #[cfg(stm32h5)] + RCC.pllcfgr(num).write(|w| { + w.set_pllsrc(config.source); + w.set_divm(config.prediv); + w.set_pllvcosel(vco_range); + w.set_pllrge(ref_range); + w.set_pllfracen(false); + w.set_pllpen(p.is_some()); + w.set_pllqen(q.is_some()); + w.set_pllren(r.is_some()); + }); + + #[cfg(any(stm32h7, stm32h7rs))] + { + RCC.pllckselr().modify(|w| { + w.set_divm(num, config.prediv); + w.set_pllsrc(config.source); + }); + RCC.pllcfgr().modify(|w| { + w.set_pllvcosel(num, vco_range); + w.set_pllrge(num, ref_range); + w.set_pllfracen(num, false); + w.set_divpen(num, p.is_some()); + w.set_divqen(num, q.is_some()); + w.set_divren(num, r.is_some()); + }); + } + + RCC.plldivr(num).write(|w| { + w.set_plln(config.mul); + w.set_pllp(config.divp.unwrap_or(PllDiv::DIV2)); + w.set_pllq(config.divq.unwrap_or(PllDiv::DIV2)); + w.set_pllr(config.divr.unwrap_or(PllDiv::DIV2)); + }); + + RCC.cr().modify(|w| w.set_pllon(num, true)); + while !RCC.cr().read().pllrdy(num) {} + + PllOutput { p, q, r } +} + +fn flash_setup(clk: Hertz, vos: VoltageScale) { + // RM0481 Rev 1, table 37 + // LATENCY WRHIGHFREQ VOS3 VOS2 VOS1 VOS0 + // 0 0 0 to 20 MHz 0 to 30 MHz 0 to 34 MHz 0 to 42 MHz + // 1 0 20 to 40 MHz 30 to 60 MHz 34 to 68 MHz 42 to 84 MHz + // 2 1 40 to 60 MHz 60 to 90 MHz 68 to 102 MHz 84 to 126 MHz + // 3 1 60 to 80 MHz 90 to 120 MHz 102 to 136 MHz 126 to 168 MHz + // 4 2 80 to 100 MHz 120 to 150 MHz 136 to 170 MHz 168 to 210 MHz + // 5 2 170 to 200 MHz 210 to 250 MHz + #[cfg(stm32h5)] + let (latency, wrhighfreq) = match (vos, clk.0) { + (VoltageScale::Scale0, ..=42_000_000) => (0, 0), + (VoltageScale::Scale0, ..=84_000_000) => (1, 0), + (VoltageScale::Scale0, ..=126_000_000) => (2, 1), + (VoltageScale::Scale0, ..=168_000_000) => (3, 1), + (VoltageScale::Scale0, ..=210_000_000) => (4, 2), + (VoltageScale::Scale0, ..=250_000_000) => (5, 2), + + (VoltageScale::Scale1, ..=34_000_000) => (0, 0), + (VoltageScale::Scale1, ..=68_000_000) => (1, 0), + (VoltageScale::Scale1, ..=102_000_000) => (2, 1), + (VoltageScale::Scale1, ..=136_000_000) => (3, 1), + (VoltageScale::Scale1, ..=170_000_000) => (4, 2), + (VoltageScale::Scale1, ..=200_000_000) => (5, 2), + + (VoltageScale::Scale2, ..=30_000_000) => (0, 0), + (VoltageScale::Scale2, ..=60_000_000) => (1, 0), + (VoltageScale::Scale2, ..=90_000_000) => (2, 1), + (VoltageScale::Scale2, ..=120_000_000) => (3, 1), + (VoltageScale::Scale2, ..=150_000_000) => (4, 2), + + (VoltageScale::Scale3, ..=20_000_000) => (0, 0), + (VoltageScale::Scale3, ..=40_000_000) => (1, 0), + (VoltageScale::Scale3, ..=60_000_000) => (2, 1), + (VoltageScale::Scale3, ..=80_000_000) => (3, 1), + (VoltageScale::Scale3, ..=100_000_000) => (4, 2), + + _ => unreachable!(), + }; + + #[cfg(all(flash_h7, not(pwr_h7rm0468)))] + let (latency, wrhighfreq) = match (vos, clk.0) { + // VOS 0 range VCORE 1.26V - 1.40V + (VoltageScale::Scale0, ..=70_000_000) => (0, 0), + (VoltageScale::Scale0, ..=140_000_000) => (1, 1), + (VoltageScale::Scale0, ..=185_000_000) => (2, 1), + (VoltageScale::Scale0, ..=210_000_000) => (2, 2), + (VoltageScale::Scale0, ..=225_000_000) => (3, 2), + (VoltageScale::Scale0, ..=240_000_000) => (4, 2), + // VOS 1 range VCORE 1.15V - 1.26V + (VoltageScale::Scale1, ..=70_000_000) => (0, 0), + (VoltageScale::Scale1, ..=140_000_000) => (1, 1), + (VoltageScale::Scale1, ..=185_000_000) => (2, 1), + (VoltageScale::Scale1, ..=210_000_000) => (2, 2), + (VoltageScale::Scale1, ..=225_000_000) => (3, 2), + // VOS 2 range VCORE 1.05V - 1.15V + (VoltageScale::Scale2, ..=55_000_000) => (0, 0), + (VoltageScale::Scale2, ..=110_000_000) => (1, 1), + (VoltageScale::Scale2, ..=165_000_000) => (2, 1), + (VoltageScale::Scale2, ..=224_000_000) => (3, 2), + // VOS 3 range VCORE 0.95V - 1.05V + (VoltageScale::Scale3, ..=45_000_000) => (0, 0), + (VoltageScale::Scale3, ..=90_000_000) => (1, 1), + (VoltageScale::Scale3, ..=135_000_000) => (2, 1), + (VoltageScale::Scale3, ..=180_000_000) => (3, 2), + (VoltageScale::Scale3, ..=224_000_000) => (4, 2), + _ => unreachable!(), + }; + + // See RM0468 Rev 3 Table 16. FLASH recommended number of wait + // states and programming delay + #[cfg(all(flash_h7, pwr_h7rm0468))] + let (latency, wrhighfreq) = match (vos, clk.0) { + // VOS 0 range VCORE 1.26V - 1.40V + (VoltageScale::Scale0, ..=70_000_000) => (0, 0), + (VoltageScale::Scale0, ..=140_000_000) => (1, 1), + (VoltageScale::Scale0, ..=210_000_000) => (2, 2), + (VoltageScale::Scale0, ..=275_000_000) => (3, 3), + // VOS 1 range VCORE 1.15V - 1.26V + (VoltageScale::Scale1, ..=67_000_000) => (0, 0), + (VoltageScale::Scale1, ..=133_000_000) => (1, 1), + (VoltageScale::Scale1, ..=200_000_000) => (2, 2), + // VOS 2 range VCORE 1.05V - 1.15V + (VoltageScale::Scale2, ..=50_000_000) => (0, 0), + (VoltageScale::Scale2, ..=100_000_000) => (1, 1), + (VoltageScale::Scale2, ..=150_000_000) => (2, 2), + // VOS 3 range VCORE 0.95V - 1.05V + (VoltageScale::Scale3, ..=35_000_000) => (0, 0), + (VoltageScale::Scale3, ..=70_000_000) => (1, 1), + (VoltageScale::Scale3, ..=85_000_000) => (2, 2), + _ => unreachable!(), + }; + + // See RM0455 Rev 10 Table 16. FLASH recommended number of wait + // states and programming delay + #[cfg(flash_h7ab)] + let (latency, wrhighfreq) = match (vos, clk.0) { + // VOS 0 range VCORE 1.25V - 1.35V + (VoltageScale::Scale0, ..=42_000_000) => (0, 0), + (VoltageScale::Scale0, ..=84_000_000) => (1, 0), + (VoltageScale::Scale0, ..=126_000_000) => (2, 1), + (VoltageScale::Scale0, ..=168_000_000) => (3, 1), + (VoltageScale::Scale0, ..=210_000_000) => (4, 2), + (VoltageScale::Scale0, ..=252_000_000) => (5, 2), + (VoltageScale::Scale0, ..=280_000_000) => (6, 3), + // VOS 1 range VCORE 1.15V - 1.25V + (VoltageScale::Scale1, ..=38_000_000) => (0, 0), + (VoltageScale::Scale1, ..=76_000_000) => (1, 0), + (VoltageScale::Scale1, ..=114_000_000) => (2, 1), + (VoltageScale::Scale1, ..=152_000_000) => (3, 1), + (VoltageScale::Scale1, ..=190_000_000) => (4, 2), + (VoltageScale::Scale1, ..=225_000_000) => (5, 2), + // VOS 2 range VCORE 1.05V - 1.15V + (VoltageScale::Scale2, ..=34) => (0, 0), + (VoltageScale::Scale2, ..=68) => (1, 0), + (VoltageScale::Scale2, ..=102) => (2, 1), + (VoltageScale::Scale2, ..=136) => (3, 1), + (VoltageScale::Scale2, ..=160) => (4, 2), + // VOS 3 range VCORE 0.95V - 1.05V + (VoltageScale::Scale3, ..=22) => (0, 0), + (VoltageScale::Scale3, ..=44) => (1, 0), + (VoltageScale::Scale3, ..=66) => (2, 1), + (VoltageScale::Scale3, ..=88) => (3, 1), + _ => unreachable!(), + }; + #[cfg(flash_h7rs)] + let (latency, wrhighfreq) = match (vos, clk.0) { + // VOS high range VCORE 1.30V - 1.40V + (VoltageScale::HIGH, ..=40_000_000) => (0, 0), + (VoltageScale::HIGH, ..=80_000_000) => (1, 0), + (VoltageScale::HIGH, ..=120_000_000) => (2, 1), + (VoltageScale::HIGH, ..=160_000_000) => (3, 1), + (VoltageScale::HIGH, ..=200_000_000) => (4, 2), + (VoltageScale::HIGH, ..=240_000_000) => (5, 2), + (VoltageScale::HIGH, ..=280_000_000) => (6, 3), + (VoltageScale::HIGH, ..=320_000_000) => (7, 3), + // VOS low range VCORE 1.15V - 1.26V + (VoltageScale::LOW, ..=36_000_000) => (0, 0), + (VoltageScale::LOW, ..=72_000_000) => (1, 0), + (VoltageScale::LOW, ..=108_000_000) => (2, 1), + (VoltageScale::LOW, ..=144_000_000) => (3, 1), + (VoltageScale::LOW, ..=180_000_000) => (4, 2), + (VoltageScale::LOW, ..=216_000_000) => (5, 2), + _ => unreachable!(), + }; + + debug!("flash: latency={} wrhighfreq={}", latency, wrhighfreq); + + FLASH.acr().write(|w| { + w.set_wrhighfreq(wrhighfreq); + w.set_latency(latency); + }); + while FLASH.acr().read().latency() != latency {} +} diff --git a/.stversions/embassy/embassy-sync/src/watch~20241114-122315.rs b/.stversions/embassy/embassy-sync/src/watch~20241114-122315.rs new file mode 100644 index 0000000..59798d0 --- /dev/null +++ b/.stversions/embassy/embassy-sync/src/watch~20241114-122315.rs @@ -0,0 +1,1109 @@ +//! A synchronization primitive for passing the latest value to **multiple** receivers. + +use core::cell::RefCell; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::task::{Context, Poll}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::MultiWakerRegistration; + +/// The `Watch` is a single-slot signaling primitive that allows multiple receivers to concurrently await +/// changes to the value. Unlike a [`Signal`](crate::signal::Signal), `Watch` supports multiple receivers, +/// and unlike a [`PubSubChannel`](crate::pubsub::PubSubChannel), `Watch` immediately overwrites the previous +/// value when a new one is sent, without waiting for all receivers to read the previous value. +/// +/// This makes `Watch` particularly useful when a single task updates a value or "state", and multiple other tasks +/// need to be notified about changes to this value asynchronously. Receivers may "lose" stale values, as they are +/// always provided with the latest value. +/// +/// Typically, `Watch` instances are declared as `static`, and a [`Sender`] and [`Receiver`] +/// (or [`DynSender`] and/or [`DynReceiver`]) are obtained where relevant. An [`AnonReceiver`] +/// and [`DynAnonReceiver`] are also available, which do not increase the receiver count for the +/// channel, and unwrapping is therefore not required, but it is not possible to `.await` the channel. +/// ``` +/// +/// use futures_executor::block_on; +/// use embassy_sync::watch::Watch; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// +/// let f = async { +/// +/// static WATCH: Watch = Watch::new(); +/// +/// // Obtain receivers and sender +/// let mut rcv0 = WATCH.receiver().unwrap(); +/// let mut rcv1 = WATCH.dyn_receiver().unwrap(); +/// let mut snd = WATCH.sender(); +/// +/// // No more receivers, and no update +/// assert!(WATCH.receiver().is_none()); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// snd.send(10); +/// +/// // Receive the new value (async or try) +/// assert_eq!(rcv0.changed().await, 10); +/// assert_eq!(rcv1.try_changed(), Some(10)); +/// +/// // No update +/// assert_eq!(rcv0.try_changed(), None); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// snd.send(20); +/// +/// // Using `get` marks the value as seen +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// // But `get` also returns when unchanged +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.get().await, 20); +/// +/// }; +/// block_on(f); +/// ``` +pub struct Watch { + mutex: Mutex>>, +} + +struct WatchState { + data: Option, + current_id: u64, + wakers: MultiWakerRegistration, + receiver_count: usize, +} + +trait SealedWatchBehavior { + /// Poll the `Watch` for the current value, making it as seen. + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Poll the `Watch` for the value if it matches the predicate function + /// `f`, making it as seen. + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Poll the `Watch` for a changed value, marking it as seen, if an id is given. + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. + fn try_changed(&self, id: &mut u64) -> Option; + + /// Poll the `Watch` for a changed value that matches the predicate function + /// `f`, marking it as seen. + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed and matches the + /// predicate function `f`, marking it as seen. + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; + + /// Used when a receiver is dropped to decrement the receiver count. + /// + /// ## This method should not be called by the user. + fn drop_receiver(&self); + + /// Clears the value of the `Watch`. + fn clear(&self); + + /// Sends a new value to the `Watch`. + fn send(&self, val: T); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_modify(&self, f: &mut dyn Fn(&mut Option)); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool); +} + +/// A trait representing the 'inner' behavior of the `Watch`. +#[allow(private_bounds)] +pub trait WatchBehavior: SealedWatchBehavior { + /// Tries to get the value of the `Watch`, marking it as seen, if an id is given. + fn try_get(&self, id: Option<&mut u64>) -> Option; + + /// Tries to get the value of the `Watch` if it matches the predicate function + /// `f`, marking it as seen. + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option; + + /// Checks if the `Watch` is been initialized with a value. + fn contains_value(&self) -> bool; +} + +impl SealedWatchBehavior for Watch { + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match &s.data { + Some(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + None => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.data { + Some(ref data) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed(&self, id: &mut u64) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.current_id > *id { + true => { + *id = s.current_id; + s.data.clone() + } + false => None, + } + }) + } + + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + s.data.clone() + } + _ => None, + } + }) + } + + fn drop_receiver(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.receiver_count -= 1; + }) + } + + fn clear(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = None; + }) + } + + fn send(&self, val: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = Some(val); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn send_modify(&self, f: &mut dyn Fn(&mut Option)) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + f(&mut s.data); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if f(&mut s.data) { + s.current_id += 1; + s.wakers.wake(); + } + }) + } +} + +impl WatchBehavior for Watch { + fn try_get(&self, id: Option<&mut u64>) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + if let Some(id) = id { + *id = s.current_id; + } + s.data.clone() + }) + } + + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.data { + Some(ref data) if f(data) => { + if let Some(id) = id { + *id = s.current_id; + } + Some(data.clone()) + } + _ => None, + } + }) + } + + fn contains_value(&self) -> bool { + self.mutex.lock(|state| state.borrow().data.is_some()) + } +} + +impl Watch { + /// Create a new `Watch` channel. + pub const fn new() -> Self { + Self { + mutex: Mutex::new(RefCell::new(WatchState { + data: None, + current_id: 0, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + } + } + + /// Create a new [`Sender`] for the `Watch`. + pub fn sender(&self) -> Sender<'_, M, T, N> { + Sender(Snd::new(self)) + } + + /// Create a new [`DynSender`] for the `Watch`. + pub fn dyn_sender(&self) -> DynSender<'_, T> { + DynSender(Snd::new(self)) + } + + /// Try to create a new [`Receiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn receiver(&self) -> Option> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Some(Receiver(Rcv::new(self, 0))) + } else { + None + } + }) + } + + /// Try to create a new [`DynReceiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn dyn_receiver(&self) -> Option> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Some(DynReceiver(Rcv::new(self, 0))) + } else { + None + } + }) + } + + /// Try to create a new [`AnonReceiver`] for the `Watch`. + pub fn anon_receiver(&self) -> AnonReceiver<'_, M, T, N> { + AnonReceiver(AnonRcv::new(self, 0)) + } + + /// Try to create a new [`DynAnonReceiver`] for the `Watch`. + pub fn dyn_anon_receiver(&self) -> DynAnonReceiver<'_, T> { + DynAnonReceiver(AnonRcv::new(self, 0)) + } + + /// Returns the message ID of the latest message sent to the `Watch`. + /// + /// This counter is monotonic, and is incremented every time a new message is sent. + pub fn get_msg_id(&self) -> u64 { + self.mutex.lock(|state| state.borrow().current_id) + } + + /// Tries to get the value of the `Watch`. + pub fn try_get(&self) -> Option { + WatchBehavior::try_get(self, None) + } + + /// Tries to get the value of the `Watch` if it matches the predicate function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + WatchBehavior::try_get_and(self, None, &mut f) + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Snd<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Clone for Snd<'a, T, W> { + fn clone(&self) -> Self { + Self { + watch: self.watch, + _phantom: PhantomData, + } + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W) -> Self { + Self { + watch, + _phantom: PhantomData, + } + } + + /// Sends a new value to the `Watch`. + pub fn send(&self, val: T) { + self.watch.send(val) + } + + /// Clears the value of the `Watch`. + /// This will cause calls to [`Rcv::get`] to be pending. + pub fn clear(&self) { + self.watch.clear() + } + + /// Tries to retrieve the value of the `Watch`. + pub fn try_get(&self) -> Option { + self.watch.try_get(None) + } + + /// Tries to peek the current value of the `Watch` if it matches the predicate + /// function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(None, &mut f) + } + + /// Returns true if the `Watch` contains a value. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } + + /// Modify the value of the `Watch` using a closure. + pub fn send_modify(&self, mut f: F) + where + F: Fn(&mut Option), + { + self.watch.send_modify(&mut f) + } + + /// Modify the value of the `Watch` using a closure. The closure must return + /// `true` if the value was modified, which notifies all receivers. + pub fn send_if_modified(&self, mut f: F) + where + F: Fn(&mut Option) -> bool, + { + self.watch.send_if_modified(&mut f) + } +} + +/// A sender of a `Watch` channel. +/// +/// For a simpler type definition, consider [`DynSender`] at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct Sender<'a, M: RawMutex, T: Clone, const N: usize>(Snd<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Clone for Sender<'a, M, T, N> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Sender<'a, M, T, N> { + /// Converts the `Sender` into a [`DynSender`]. + pub fn as_dyn(self) -> DynSender<'a, T> { + DynSender(Snd::new(self.watch)) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Sender<'a, M, T, N> { + fn into(self) -> DynSender<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Sender<'a, M, T, N> { + type Target = Snd<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Sender<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A sender which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Sender`] with a simpler type definition, +pub struct DynSender<'a, T: Clone>(Snd<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Clone for DynSender<'a, T> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, T: Clone> Deref for DynSender<'a, T> { + type Target = Snd<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynSender<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Rcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Returns the current value of the `Watch` once it is initialized, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn get(&mut self) -> T { + poll_fn(|cx| self.watch.poll_get(&mut self.at_id, cx)).await + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Returns the value of the `Watch` if it matches the predicate function `f`, + /// or waits for it to match, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn get_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_get_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Waits for the `Watch` to change and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed(&mut self) -> T { + poll_fn(|cx| self.watch.poll_changed(&mut self.at_id, cx)).await + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Waits for the `Watch` to change to a value which satisfies the predicate + /// function `f` and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_changed_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Drop for Rcv<'a, T, W> { + fn drop(&mut self) { + self.watch.drop_receiver(); + } +} + +/// A anonymous receiver can NOT `.await` a change in the `Watch` value. +pub struct AnonRcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> AnonRcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + +/// A receiver of a `Watch` channel. +pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Receiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynReceiver<'a, T> { + let rcv = DynReceiver(Rcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Receiver<'a, M, T, N> { + fn into(self) -> DynReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { + type Target = Rcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Receiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynReceiver<'a, T: Clone>(Rcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynReceiver<'a, T> { + type Target = Rcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver of a `Watch` channel that cannot `.await` values. +pub struct AnonReceiver<'a, M: RawMutex, T: Clone, const N: usize>(AnonRcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> AnonReceiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynAnonReceiver<'a, T> { + let rcv = DynAnonReceiver(AnonRcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for AnonReceiver<'a, M, T, N> { + fn into(self) -> DynAnonReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for AnonReceiver<'a, M, T, N> { + type Target = AnonRcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for AnonReceiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver that cannot `.await` value, which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`AnonReceiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynAnonReceiver<'a, T: Clone>(AnonRcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynAnonReceiver<'a, T> { + type Target = AnonRcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynAnonReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(test)] +mod tests { + use futures_executor::block_on; + + use super::Watch; + use crate::blocking_mutex::raw::CriticalSectionRawMutex; + + #[test] + fn multiple_sends() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.changed().await, 10); + + // Receive another value + snd.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + + // No update + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn all_try_get() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(WATCH.try_get(), None); + assert_eq!(rcv.try_get(), None); + assert_eq!(snd.try_get(), None); + + // Receive the new value + snd.send(10); + assert_eq!(WATCH.try_get(), Some(10)); + assert_eq!(rcv.try_get(), Some(10)); + assert_eq!(snd.try_get(), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x > &5), Some(10)); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(10)); + assert_eq!(snd.try_get_and(|x| x > &5), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x < &5), None); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert_eq!(snd.try_get_and(|x| x < &5), None); + }; + block_on(f); + } + + #[test] + fn once_lock_like() { + let f = async { + static CONFIG0: u8 = 10; + static CONFIG1: u8 = 20; + + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(&CONFIG0); + let rcv0 = rcv.changed().await; + assert_eq!(rcv0, &10); + + // Receive another value + snd.send(&CONFIG1); + let rcv1 = rcv.try_changed(); + assert_eq!(rcv1, Some(&20)); + + // No update + assert_eq!(rcv.try_changed(), None); + + // Ensure similarity with original static + assert_eq!(rcv0, &CONFIG0); + assert_eq!(rcv1, Some(&CONFIG1)); + }; + block_on(f); + } + + #[test] + fn sender_modify() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Modify the value inplace + snd.send_modify(|opt| { + if let Some(inner) = opt { + *inner += 5; + } + }); + + // Get the modified value + assert_eq!(rcv.try_changed(), Some(15)); + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn predicate_fn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + snd.send(15); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(15)); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert!(rcv.try_changed().is_none()); + + snd.send(20); + assert_eq!(rcv.try_changed_and(|x| x > &5), Some(20)); + assert_eq!(rcv.try_changed_and(|x| x > &5), None); + + snd.send(25); + assert_eq!(rcv.try_changed_and(|x| x < &5), None); + assert_eq!(rcv.try_changed(), Some(25)); + + snd.send(30); + assert_eq!(rcv.changed_and(|x| x > &5).await, 30); + assert_eq!(rcv.get_and(|x| x > &5).await, 30); + }; + block_on(f); + } + + #[test] + fn receive_after_create() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain sender and send value + let snd = WATCH.sender(); + snd.send(10); + + // Obtain receiver and receive value + let mut rcv = WATCH.receiver().unwrap(); + assert_eq!(rcv.try_changed(), Some(10)); + }; + block_on(f); + } + + #[test] + fn max_receivers_drop() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Try to create 3 receivers (only 2 can exist at once) + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); + + // Drop the first receiver + drop(rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + assert!(rcv3.is_some()); + }; + block_on(f); + } + + #[test] + fn multiple_receivers() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receivers and sender + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.anon_receiver(); + let snd = WATCH.sender(); + + // No update for both + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + // Send a new value + snd.send(0); + + // Both receivers receive the new value + assert_eq!(rcv0.try_changed(), Some(0)); + assert_eq!(rcv1.try_changed(), Some(0)); + }; + block_on(f); + } + + #[test] + fn clone_senders() { + let f = async { + // Obtain different ways to send + static WATCH: Watch = Watch::new(); + let snd0 = WATCH.sender(); + let snd1 = snd0.clone(); + + // Obtain Receiver + let mut rcv = WATCH.receiver().unwrap().as_dyn(); + + // Send a value from first sender + snd0.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Send a value from second sender + snd1.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + }; + block_on(f); + } + + #[test] + fn use_dynamics() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut anon_rcv = WATCH.dyn_anon_receiver(); + let mut dyn_rcv = WATCH.dyn_receiver().unwrap(); + let dyn_snd = WATCH.dyn_sender(); + + // Send a value + dyn_snd.send(10); + + // Ensure the dynamic receiver receives the value + assert_eq!(anon_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn convert_to_dyn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let anon_rcv = WATCH.anon_receiver(); + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Convert to dynamic + let mut dyn_anon_rcv = anon_rcv.as_dyn(); + let mut dyn_rcv = rcv.as_dyn(); + let dyn_snd = snd.as_dyn(); + + // Send a value + dyn_snd.send(10); + + // Ensure the dynamic receiver receives the value + assert_eq!(dyn_anon_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn dynamic_receiver_count() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); + + // Convert to dynamic + let dyn_rcv0 = rcv0.unwrap().as_dyn(); + + // Drop the (now dynamic) receiver + drop(dyn_rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + let rcv4 = WATCH.receiver(); + assert!(rcv3.is_some()); + assert!(rcv4.is_none()); + }; + block_on(f); + } + + #[test] + fn contains_value() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), false); + assert_eq!(snd.contains_value(), false); + + // Send a value + snd.send(10); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), true); + assert_eq!(snd.contains_value(), true); + }; + block_on(f); + } +} diff --git a/.stversions/embassy/embassy-time/Cargo~20241114-122314.toml b/.stversions/embassy/embassy-time/Cargo~20241114-122314.toml new file mode 100644 index 0000000..0e61434 --- /dev/null +++ b/.stversions/embassy/embassy-time/Cargo~20241114-122314.toml @@ -0,0 +1,434 @@ +[package] +name = "embassy-time" +version = "0.3.2" +edition = "2021" +description = "Instant and Duration for embedded no-std systems, with async timer support" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-time" +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/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time/src/" +features = ["defmt", "std"] +target = "x86_64-unknown-linux-gnu" + +[package.metadata.docs.rs] +features = ["defmt", "std"] + +[features] +std = ["tick-hz-1_000_000", "critical-section/std"] +wasm = ["dep:wasm-bindgen", "dep:js-sys", "dep:wasm-timer", "tick-hz-1_000_000"] + +## Display the time since startup next to defmt log messages. +## At most 1 `defmt-timestamp-uptime-*` feature can be used. +## `defmt-timestamp-uptime` is provided for backwards compatibility (provides the same format as `uptime-us`). +## To use this you must have a time driver provided. +defmt-timestamp-uptime = ["defmt"] +defmt-timestamp-uptime-s = ["defmt"] +defmt-timestamp-uptime-ms = ["defmt"] +defmt-timestamp-uptime-us = ["defmt"] +defmt-timestamp-uptime-ts = ["defmt"] +defmt-timestamp-uptime-tms = ["defmt"] +defmt-timestamp-uptime-tus = ["defmt"] + +## Create a `MockDriver` that can be manually advanced for testing purposes. +mock-driver = ["tick-hz-1_000_000"] + +#! ### Generic Queue + +## Create a global, generic queue that can be used with any executor. +## To use this you must have a time driver provided. +generic-queue = [] + +#! The following features set how many timers are used for the generic queue. At most one +#! `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used. +#! +#! When using embassy-time from libraries, you should *not* enable any `generic-queue-*` feature, to allow the +#! end user to pick. + +## Generic Queue with 8 timers +generic-queue-8 = ["generic-queue"] +## Generic Queue with 16 timers +generic-queue-16 = ["generic-queue"] +## Generic Queue with 32 timers +generic-queue-32 = ["generic-queue"] +## Generic Queue with 64 timers +generic-queue-64 = ["generic-queue"] +## Generic Queue with 128 timers +generic-queue-128 = ["generic-queue"] + +#! ### Tick Rate +#! +#! At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used. +#! +#! If the time driver in use supports using arbitrary tick rates, you can enable one `tick-*` +#! feature from your binary crate to set the tick rate. The driver will use configured tick rate. +#! If the time driver supports a fixed tick rate, it will enable one feature itself, so you should +#! not enable one. Check the time driver documentation for details. +#! +#! When using embassy-time from libraries, you should *not* enable any `tick-*` feature, to allow the +#! end user or the driver to pick. +#!
+#! Available tick rates: +#! +#! + +# BEGIN TICKS +# Generated by gen_tick.py. DO NOT EDIT. +## 1Hz Tick Rate +tick-hz-1 = ["embassy-time-driver/tick-hz-1"] +## 2Hz Tick Rate +tick-hz-2 = ["embassy-time-driver/tick-hz-2"] +## 4Hz Tick Rate +tick-hz-4 = ["embassy-time-driver/tick-hz-4"] +## 8Hz Tick Rate +tick-hz-8 = ["embassy-time-driver/tick-hz-8"] +## 10Hz Tick Rate +tick-hz-10 = ["embassy-time-driver/tick-hz-10"] +## 16Hz Tick Rate +tick-hz-16 = ["embassy-time-driver/tick-hz-16"] +## 32Hz Tick Rate +tick-hz-32 = ["embassy-time-driver/tick-hz-32"] +## 64Hz Tick Rate +tick-hz-64 = ["embassy-time-driver/tick-hz-64"] +## 100Hz Tick Rate +tick-hz-100 = ["embassy-time-driver/tick-hz-100"] +## 128Hz Tick Rate +tick-hz-128 = ["embassy-time-driver/tick-hz-128"] +## 256Hz Tick Rate +tick-hz-256 = ["embassy-time-driver/tick-hz-256"] +## 512Hz Tick Rate +tick-hz-512 = ["embassy-time-driver/tick-hz-512"] +## 1.0kHz Tick Rate +tick-hz-1_000 = ["embassy-time-driver/tick-hz-1_000"] +## 1.024kHz Tick Rate +tick-hz-1_024 = ["embassy-time-driver/tick-hz-1_024"] +## 2.0kHz Tick Rate +tick-hz-2_000 = ["embassy-time-driver/tick-hz-2_000"] +## 2.048kHz Tick Rate +tick-hz-2_048 = ["embassy-time-driver/tick-hz-2_048"] +## 4.0kHz Tick Rate +tick-hz-4_000 = ["embassy-time-driver/tick-hz-4_000"] +## 4.096kHz Tick Rate +tick-hz-4_096 = ["embassy-time-driver/tick-hz-4_096"] +## 8.0kHz Tick Rate +tick-hz-8_000 = ["embassy-time-driver/tick-hz-8_000"] +## 8.192kHz Tick Rate +tick-hz-8_192 = ["embassy-time-driver/tick-hz-8_192"] +## 10.0kHz Tick Rate +tick-hz-10_000 = ["embassy-time-driver/tick-hz-10_000"] +## 16.0kHz Tick Rate +tick-hz-16_000 = ["embassy-time-driver/tick-hz-16_000"] +## 16.384kHz Tick Rate +tick-hz-16_384 = ["embassy-time-driver/tick-hz-16_384"] +## 20.0kHz Tick Rate +tick-hz-20_000 = ["embassy-time-driver/tick-hz-20_000"] +## 32.0kHz Tick Rate +tick-hz-32_000 = ["embassy-time-driver/tick-hz-32_000"] +## 32.768kHz Tick Rate +tick-hz-32_768 = ["embassy-time-driver/tick-hz-32_768"] +## 40.0kHz Tick Rate +tick-hz-40_000 = ["embassy-time-driver/tick-hz-40_000"] +## 64.0kHz Tick Rate +tick-hz-64_000 = ["embassy-time-driver/tick-hz-64_000"] +## 65.536kHz Tick Rate +tick-hz-65_536 = ["embassy-time-driver/tick-hz-65_536"] +## 80.0kHz Tick Rate +tick-hz-80_000 = ["embassy-time-driver/tick-hz-80_000"] +## 100.0kHz Tick Rate +tick-hz-100_000 = ["embassy-time-driver/tick-hz-100_000"] +## 128.0kHz Tick Rate +tick-hz-128_000 = ["embassy-time-driver/tick-hz-128_000"] +## 131.072kHz Tick Rate +tick-hz-131_072 = ["embassy-time-driver/tick-hz-131_072"] +## 160.0kHz Tick Rate +tick-hz-160_000 = ["embassy-time-driver/tick-hz-160_000"] +## 256.0kHz Tick Rate +tick-hz-256_000 = ["embassy-time-driver/tick-hz-256_000"] +## 262.144kHz Tick Rate +tick-hz-262_144 = ["embassy-time-driver/tick-hz-262_144"] +## 320.0kHz Tick Rate +tick-hz-320_000 = ["embassy-time-driver/tick-hz-320_000"] +## 512.0kHz Tick Rate +tick-hz-512_000 = ["embassy-time-driver/tick-hz-512_000"] +## 524.288kHz Tick Rate +tick-hz-524_288 = ["embassy-time-driver/tick-hz-524_288"] +## 640.0kHz Tick Rate +tick-hz-640_000 = ["embassy-time-driver/tick-hz-640_000"] +## 1.0MHz Tick Rate +tick-hz-1_000_000 = ["embassy-time-driver/tick-hz-1_000_000"] +## 1.024MHz Tick Rate +tick-hz-1_024_000 = ["embassy-time-driver/tick-hz-1_024_000"] +## 1.048576MHz Tick Rate +tick-hz-1_048_576 = ["embassy-time-driver/tick-hz-1_048_576"] +## 1.28MHz Tick Rate +tick-hz-1_280_000 = ["embassy-time-driver/tick-hz-1_280_000"] +## 2.0MHz Tick Rate +tick-hz-2_000_000 = ["embassy-time-driver/tick-hz-2_000_000"] +## 2.048MHz Tick Rate +tick-hz-2_048_000 = ["embassy-time-driver/tick-hz-2_048_000"] +## 2.097152MHz Tick Rate +tick-hz-2_097_152 = ["embassy-time-driver/tick-hz-2_097_152"] +## 2.56MHz Tick Rate +tick-hz-2_560_000 = ["embassy-time-driver/tick-hz-2_560_000"] +## 3.0MHz Tick Rate +tick-hz-3_000_000 = ["embassy-time-driver/tick-hz-3_000_000"] +## 4.0MHz Tick Rate +tick-hz-4_000_000 = ["embassy-time-driver/tick-hz-4_000_000"] +## 4.096MHz Tick Rate +tick-hz-4_096_000 = ["embassy-time-driver/tick-hz-4_096_000"] +## 4.194304MHz Tick Rate +tick-hz-4_194_304 = ["embassy-time-driver/tick-hz-4_194_304"] +## 5.12MHz Tick Rate +tick-hz-5_120_000 = ["embassy-time-driver/tick-hz-5_120_000"] +## 6.0MHz Tick Rate +tick-hz-6_000_000 = ["embassy-time-driver/tick-hz-6_000_000"] +## 8.0MHz Tick Rate +tick-hz-8_000_000 = ["embassy-time-driver/tick-hz-8_000_000"] +## 8.192MHz Tick Rate +tick-hz-8_192_000 = ["embassy-time-driver/tick-hz-8_192_000"] +## 8.388608MHz Tick Rate +tick-hz-8_388_608 = ["embassy-time-driver/tick-hz-8_388_608"] +## 9.0MHz Tick Rate +tick-hz-9_000_000 = ["embassy-time-driver/tick-hz-9_000_000"] +## 10.0MHz Tick Rate +tick-hz-10_000_000 = ["embassy-time-driver/tick-hz-10_000_000"] +## 10.24MHz Tick Rate +tick-hz-10_240_000 = ["embassy-time-driver/tick-hz-10_240_000"] +## 12.0MHz Tick Rate +tick-hz-12_000_000 = ["embassy-time-driver/tick-hz-12_000_000"] +## 16.0MHz Tick Rate +tick-hz-16_000_000 = ["embassy-time-driver/tick-hz-16_000_000"] +## 16.384MHz Tick Rate +tick-hz-16_384_000 = ["embassy-time-driver/tick-hz-16_384_000"] +## 16.777216MHz Tick Rate +tick-hz-16_777_216 = ["embassy-time-driver/tick-hz-16_777_216"] +## 18.0MHz Tick Rate +tick-hz-18_000_000 = ["embassy-time-driver/tick-hz-18_000_000"] +## 20.0MHz Tick Rate +tick-hz-20_000_000 = ["embassy-time-driver/tick-hz-20_000_000"] +## 20.48MHz Tick Rate +tick-hz-20_480_000 = ["embassy-time-driver/tick-hz-20_480_000"] +## 24.0MHz Tick Rate +tick-hz-24_000_000 = ["embassy-time-driver/tick-hz-24_000_000"] +## 30.0MHz Tick Rate +tick-hz-30_000_000 = ["embassy-time-driver/tick-hz-30_000_000"] +## 32.0MHz Tick Rate +tick-hz-32_000_000 = ["embassy-time-driver/tick-hz-32_000_000"] +## 32.768MHz Tick Rate +tick-hz-32_768_000 = ["embassy-time-driver/tick-hz-32_768_000"] +## 36.0MHz Tick Rate +tick-hz-36_000_000 = ["embassy-time-driver/tick-hz-36_000_000"] +## 40.0MHz Tick Rate +tick-hz-40_000_000 = ["embassy-time-driver/tick-hz-40_000_000"] +## 40.96MHz Tick Rate +tick-hz-40_960_000 = ["embassy-time-driver/tick-hz-40_960_000"] +## 48.0MHz Tick Rate +tick-hz-48_000_000 = ["embassy-time-driver/tick-hz-48_000_000"] +## 50.0MHz Tick Rate +tick-hz-50_000_000 = ["embassy-time-driver/tick-hz-50_000_000"] +## 60.0MHz Tick Rate +tick-hz-60_000_000 = ["embassy-time-driver/tick-hz-60_000_000"] +## 64.0MHz Tick Rate +tick-hz-64_000_000 = ["embassy-time-driver/tick-hz-64_000_000"] +## 65.536MHz Tick Rate +tick-hz-65_536_000 = ["embassy-time-driver/tick-hz-65_536_000"] +## 70.0MHz Tick Rate +tick-hz-70_000_000 = ["embassy-time-driver/tick-hz-70_000_000"] +## 72.0MHz Tick Rate +tick-hz-72_000_000 = ["embassy-time-driver/tick-hz-72_000_000"] +## 80.0MHz Tick Rate +tick-hz-80_000_000 = ["embassy-time-driver/tick-hz-80_000_000"] +## 81.92MHz Tick Rate +tick-hz-81_920_000 = ["embassy-time-driver/tick-hz-81_920_000"] +## 90.0MHz Tick Rate +tick-hz-90_000_000 = ["embassy-time-driver/tick-hz-90_000_000"] +## 96.0MHz Tick Rate +tick-hz-96_000_000 = ["embassy-time-driver/tick-hz-96_000_000"] +## 100.0MHz Tick Rate +tick-hz-100_000_000 = ["embassy-time-driver/tick-hz-100_000_000"] +## 110.0MHz Tick Rate +tick-hz-110_000_000 = ["embassy-time-driver/tick-hz-110_000_000"] +## 120.0MHz Tick Rate +tick-hz-120_000_000 = ["embassy-time-driver/tick-hz-120_000_000"] +## 128.0MHz Tick Rate +tick-hz-128_000_000 = ["embassy-time-driver/tick-hz-128_000_000"] +## 130.0MHz Tick Rate +tick-hz-130_000_000 = ["embassy-time-driver/tick-hz-130_000_000"] +## 131.072MHz Tick Rate +tick-hz-131_072_000 = ["embassy-time-driver/tick-hz-131_072_000"] +## 140.0MHz Tick Rate +tick-hz-140_000_000 = ["embassy-time-driver/tick-hz-140_000_000"] +## 144.0MHz Tick Rate +tick-hz-144_000_000 = ["embassy-time-driver/tick-hz-144_000_000"] +## 150.0MHz Tick Rate +tick-hz-150_000_000 = ["embassy-time-driver/tick-hz-150_000_000"] +## 160.0MHz Tick Rate +tick-hz-160_000_000 = ["embassy-time-driver/tick-hz-160_000_000"] +## 163.84MHz Tick Rate +tick-hz-163_840_000 = ["embassy-time-driver/tick-hz-163_840_000"] +## 170.0MHz Tick Rate +tick-hz-170_000_000 = ["embassy-time-driver/tick-hz-170_000_000"] +## 180.0MHz Tick Rate +tick-hz-180_000_000 = ["embassy-time-driver/tick-hz-180_000_000"] +## 190.0MHz Tick Rate +tick-hz-190_000_000 = ["embassy-time-driver/tick-hz-190_000_000"] +## 192.0MHz Tick Rate +tick-hz-192_000_000 = ["embassy-time-driver/tick-hz-192_000_000"] +## 200.0MHz Tick Rate +tick-hz-200_000_000 = ["embassy-time-driver/tick-hz-200_000_000"] +## 210.0MHz Tick Rate +tick-hz-210_000_000 = ["embassy-time-driver/tick-hz-210_000_000"] +## 220.0MHz Tick Rate +tick-hz-220_000_000 = ["embassy-time-driver/tick-hz-220_000_000"] +## 230.0MHz Tick Rate +tick-hz-230_000_000 = ["embassy-time-driver/tick-hz-230_000_000"] +## 240.0MHz Tick Rate +tick-hz-240_000_000 = ["embassy-time-driver/tick-hz-240_000_000"] +## 250.0MHz Tick Rate +tick-hz-250_000_000 = ["embassy-time-driver/tick-hz-250_000_000"] +## 256.0MHz Tick Rate +tick-hz-256_000_000 = ["embassy-time-driver/tick-hz-256_000_000"] +## 260.0MHz Tick Rate +tick-hz-260_000_000 = ["embassy-time-driver/tick-hz-260_000_000"] +## 262.144MHz Tick Rate +tick-hz-262_144_000 = ["embassy-time-driver/tick-hz-262_144_000"] +## 270.0MHz Tick Rate +tick-hz-270_000_000 = ["embassy-time-driver/tick-hz-270_000_000"] +## 280.0MHz Tick Rate +tick-hz-280_000_000 = ["embassy-time-driver/tick-hz-280_000_000"] +## 288.0MHz Tick Rate +tick-hz-288_000_000 = ["embassy-time-driver/tick-hz-288_000_000"] +## 290.0MHz Tick Rate +tick-hz-290_000_000 = ["embassy-time-driver/tick-hz-290_000_000"] +## 300.0MHz Tick Rate +tick-hz-300_000_000 = ["embassy-time-driver/tick-hz-300_000_000"] +## 320.0MHz Tick Rate +tick-hz-320_000_000 = ["embassy-time-driver/tick-hz-320_000_000"] +## 327.68MHz Tick Rate +tick-hz-327_680_000 = ["embassy-time-driver/tick-hz-327_680_000"] +## 340.0MHz Tick Rate +tick-hz-340_000_000 = ["embassy-time-driver/tick-hz-340_000_000"] +## 360.0MHz Tick Rate +tick-hz-360_000_000 = ["embassy-time-driver/tick-hz-360_000_000"] +## 380.0MHz Tick Rate +tick-hz-380_000_000 = ["embassy-time-driver/tick-hz-380_000_000"] +## 384.0MHz Tick Rate +tick-hz-384_000_000 = ["embassy-time-driver/tick-hz-384_000_000"] +## 400.0MHz Tick Rate +tick-hz-400_000_000 = ["embassy-time-driver/tick-hz-400_000_000"] +## 420.0MHz Tick Rate +tick-hz-420_000_000 = ["embassy-time-driver/tick-hz-420_000_000"] +## 440.0MHz Tick Rate +tick-hz-440_000_000 = ["embassy-time-driver/tick-hz-440_000_000"] +## 460.0MHz Tick Rate +tick-hz-460_000_000 = ["embassy-time-driver/tick-hz-460_000_000"] +## 480.0MHz Tick Rate +tick-hz-480_000_000 = ["embassy-time-driver/tick-hz-480_000_000"] +## 500.0MHz Tick Rate +tick-hz-500_000_000 = ["embassy-time-driver/tick-hz-500_000_000"] +## 512.0MHz Tick Rate +tick-hz-512_000_000 = ["embassy-time-driver/tick-hz-512_000_000"] +## 520.0MHz Tick Rate +tick-hz-520_000_000 = ["embassy-time-driver/tick-hz-520_000_000"] +## 524.288MHz Tick Rate +tick-hz-524_288_000 = ["embassy-time-driver/tick-hz-524_288_000"] +## 540.0MHz Tick Rate +tick-hz-540_000_000 = ["embassy-time-driver/tick-hz-540_000_000"] +## 560.0MHz Tick Rate +tick-hz-560_000_000 = ["embassy-time-driver/tick-hz-560_000_000"] +## 576.0MHz Tick Rate +tick-hz-576_000_000 = ["embassy-time-driver/tick-hz-576_000_000"] +## 580.0MHz Tick Rate +tick-hz-580_000_000 = ["embassy-time-driver/tick-hz-580_000_000"] +## 600.0MHz Tick Rate +tick-hz-600_000_000 = ["embassy-time-driver/tick-hz-600_000_000"] +## 620.0MHz Tick Rate +tick-hz-620_000_000 = ["embassy-time-driver/tick-hz-620_000_000"] +## 640.0MHz Tick Rate +tick-hz-640_000_000 = ["embassy-time-driver/tick-hz-640_000_000"] +## 655.36MHz Tick Rate +tick-hz-655_360_000 = ["embassy-time-driver/tick-hz-655_360_000"] +## 660.0MHz Tick Rate +tick-hz-660_000_000 = ["embassy-time-driver/tick-hz-660_000_000"] +## 680.0MHz Tick Rate +tick-hz-680_000_000 = ["embassy-time-driver/tick-hz-680_000_000"] +## 700.0MHz Tick Rate +tick-hz-700_000_000 = ["embassy-time-driver/tick-hz-700_000_000"] +## 720.0MHz Tick Rate +tick-hz-720_000_000 = ["embassy-time-driver/tick-hz-720_000_000"] +## 740.0MHz Tick Rate +tick-hz-740_000_000 = ["embassy-time-driver/tick-hz-740_000_000"] +## 760.0MHz Tick Rate +tick-hz-760_000_000 = ["embassy-time-driver/tick-hz-760_000_000"] +## 768.0MHz Tick Rate +tick-hz-768_000_000 = ["embassy-time-driver/tick-hz-768_000_000"] +## 780.0MHz Tick Rate +tick-hz-780_000_000 = ["embassy-time-driver/tick-hz-780_000_000"] +## 800.0MHz Tick Rate +tick-hz-800_000_000 = ["embassy-time-driver/tick-hz-800_000_000"] +## 820.0MHz Tick Rate +tick-hz-820_000_000 = ["embassy-time-driver/tick-hz-820_000_000"] +## 840.0MHz Tick Rate +tick-hz-840_000_000 = ["embassy-time-driver/tick-hz-840_000_000"] +## 860.0MHz Tick Rate +tick-hz-860_000_000 = ["embassy-time-driver/tick-hz-860_000_000"] +## 880.0MHz Tick Rate +tick-hz-880_000_000 = ["embassy-time-driver/tick-hz-880_000_000"] +## 900.0MHz Tick Rate +tick-hz-900_000_000 = ["embassy-time-driver/tick-hz-900_000_000"] +## 920.0MHz Tick Rate +tick-hz-920_000_000 = ["embassy-time-driver/tick-hz-920_000_000"] +## 940.0MHz Tick Rate +tick-hz-940_000_000 = ["embassy-time-driver/tick-hz-940_000_000"] +## 960.0MHz Tick Rate +tick-hz-960_000_000 = ["embassy-time-driver/tick-hz-960_000_000"] +## 980.0MHz Tick Rate +tick-hz-980_000_000 = ["embassy-time-driver/tick-hz-980_000_000"] +## 1.0GHz Tick Rate +tick-hz-1_000_000_000 = ["embassy-time-driver/tick-hz-1_000_000_000"] +## 1.31072GHz Tick Rate +tick-hz-1_310_720_000 = ["embassy-time-driver/tick-hz-1_310_720_000"] +## 2.62144GHz Tick Rate +tick-hz-2_621_440_000 = ["embassy-time-driver/tick-hz-2_621_440_000"] +## 5.24288GHz Tick Rate +tick-hz-5_242_880_000 = ["embassy-time-driver/tick-hz-5_242_880_000"] +# END TICKS + +#!
+ +[dependencies] +embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver" } +embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver" } + +defmt = { version = "0.3", optional = true } +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" } +embedded-hal-async = { version = "1.0" } + +futures-util = { version = "0.3.17", default-features = false } +critical-section = "1.1" +cfg-if = "1.0.0" +heapless = "0.8" + +document-features = "0.2.7" + +# 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"] } +embassy-executor = { version = "0.6.2", path = "../embassy-executor" } diff --git a/.stversions/embassy/examples/boot/application/nrf/Cargo~20241114-122315.toml b/.stversions/embassy/examples/boot/application/nrf/Cargo~20241114-122315.toml new file mode 100644 index 0000000..e2ae240 --- /dev/null +++ b/.stversions/embassy/examples/boot/application/nrf/Cargo~20241114-122315.toml @@ -0,0 +1,34 @@ +[package] +edition = "2021" +name = "embassy-boot-nrf-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } +embassy-nrf = { version = "0.2.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", ] } +embassy-boot = { version = "0.3.0", path = "../../../../embassy-boot", features = [] } +embassy-boot-nrf = { version = "0.3.0", path = "../../../../embassy-boot-nrf", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +panic-reset = { version = "0.1.1" } +embedded-hal = { version = "0.2.6" } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +ed25519-dalek = ["embassy-boot/ed25519-dalek"] +ed25519-salty = ["embassy-boot/ed25519-salty"] +skip-include = [] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-nrf/defmt", + "embassy-boot-nrf/defmt", + "embassy-sync/defmt", +] diff --git a/.stversions/embassy/examples/boot/application/rp/Cargo~20241114-122315.toml b/.stversions/embassy/examples/boot/application/rp/Cargo~20241114-122315.toml new file mode 100644 index 0000000..f61ac3f --- /dev/null +++ b/.stversions/embassy/examples/boot/application/rp/Cargo~20241114-122315.toml @@ -0,0 +1,36 @@ +[package] +edition = "2021" +name = "embassy-boot-rp-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } +embassy-rp = { version = "0.2.0", path = "../../../../embassy-rp", features = ["time-driver", "rp2040"] } +embassy-boot-rp = { version = "0.3.0", path = "../../../../embassy-boot-rp", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = "0.3" +defmt-rtt = "0.4" +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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-storage = "0.3.1" + +[features] +default = ["panic-reset"] +debug = [ + "embassy-rp/defmt", + "embassy-boot-rp/defmt", + "embassy-sync/defmt", + "panic-probe" +] +skip-include = [] + +[profile.release] +debug = true diff --git a/.stversions/embassy/examples/boot/application/stm32f3/Cargo~20241114-122314.toml b/.stversions/embassy/examples/boot/application/stm32f3/Cargo~20241114-122314.toml new file mode 100644 index 0000000..dd20d2e --- /dev/null +++ b/.stversions/embassy/examples/boot/application/stm32f3/Cargo~20241114-122314.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32f3-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f303re", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32" } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { 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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/.stversions/embassy/examples/boot/application/stm32f7/Cargo~20241114-122314.toml b/.stversions/embassy/examples/boot/application/stm32f7/Cargo~20241114-122314.toml new file mode 100644 index 0000000..ce38e9a --- /dev/null +++ b/.stversions/embassy/examples/boot/application/stm32f7/Cargo~20241114-122314.toml @@ -0,0 +1,32 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32f7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +panic-reset = { version = "0.1.1" } +embedded-hal = { version = "0.2.6" } +embedded-storage = "0.3.1" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/.stversions/embassy/examples/boot/application/stm32h7/Cargo~20241114-122315.toml b/.stversions/embassy/examples/boot/application/stm32h7/Cargo~20241114-122315.toml new file mode 100644 index 0000000..8410756 --- /dev/null +++ b/.stversions/embassy/examples/boot/application/stm32h7/Cargo~20241114-122315.toml @@ -0,0 +1,32 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32h7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32h743zi", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +panic-reset = { version = "0.1.1" } +embedded-hal = { version = "0.2.6" } +embedded-storage = "0.3.1" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/.stversions/embassy/examples/boot/application/stm32l0/Cargo~20241114-122315.toml b/.stversions/embassy/examples/boot/application/stm32l0/Cargo~20241114-122315.toml new file mode 100644 index 0000000..1a92517 --- /dev/null +++ b/.stversions/embassy/examples/boot/application/stm32l0/Cargo~20241114-122315.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32l0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l072cz", "time-driver-any", "exti", "memory-x"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { 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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/.stversions/embassy/examples/boot/application/stm32l1/Cargo~20241114-122315.toml b/.stversions/embassy/examples/boot/application/stm32l1/Cargo~20241114-122315.toml new file mode 100644 index 0000000..57eeb07 --- /dev/null +++ b/.stversions/embassy/examples/boot/application/stm32l1/Cargo~20241114-122315.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32l1-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l151cb-a", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { 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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/.stversions/embassy/examples/boot/application/stm32l4/Cargo~20241114-122315.toml b/.stversions/embassy/examples/boot/application/stm32l4/Cargo~20241114-122315.toml new file mode 100644 index 0000000..f51ca29 --- /dev/null +++ b/.stversions/embassy/examples/boot/application/stm32l4/Cargo~20241114-122315.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32l4-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { 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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/.stversions/embassy/examples/boot/application/stm32wb-dfu/Cargo~20241114-122314.toml b/.stversions/embassy/examples/boot/application/stm32wb-dfu/Cargo~20241114-122314.toml new file mode 100644 index 0000000..112de92 --- /dev/null +++ b/.stversions/embassy/examples/boot/application/stm32wb-dfu/Cargo~20241114-122314.toml @@ -0,0 +1,32 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32wb-dfu-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-usb = { version = "0.3.0", path = "../../../../embassy-usb" } +embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application", "cortex-m"] } + +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +panic-reset = { version = "0.1.1" } +embedded-hal = { version = "0.2.6" } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] diff --git a/.stversions/embassy/examples/boot/application/stm32wl/Cargo~20241114-122314.toml b/.stversions/embassy/examples/boot/application/stm32wl/Cargo~20241114-122314.toml new file mode 100644 index 0000000..e2c42ce --- /dev/null +++ b/.stversions/embassy/examples/boot/application/stm32wl/Cargo~20241114-122314.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32wl-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wl55jc-cm4", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { 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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/.stversions/embassy/examples/lpc55s69/Cargo~20241114-122315.toml b/.stversions/embassy/examples/lpc55s69/Cargo~20241114-122315.toml new file mode 100644 index 0000000..a18b29f --- /dev/null +++ b/.stversions/embassy/examples/lpc55s69/Cargo~20241114-122315.toml @@ -0,0 +1,22 @@ +[package] +edition = "2021" +name = "embassy-nxp-lpc55s69-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["rt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt"] } +panic-halt = "0.2.0" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = {version = "0.7.0"} +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/nrf-rtos-trace/Cargo~20241114-122314.toml b/.stversions/embassy/examples/nrf-rtos-trace/Cargo~20241114-122314.toml new file mode 100644 index 0000000..fbdd274 --- /dev/null +++ b/.stversions/embassy/examples/nrf-rtos-trace/Cargo~20241114-122314.toml @@ -0,0 +1,37 @@ +[package] +edition = "2021" +name = "embassy-nrf-rtos-trace-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[features] +default = ["log"] +log = [ + "dep:log", + "embassy-sync/log", + "embassy-executor/log", + "embassy-time/log", + "embassy-nrf/log", +] + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time" } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3" } +rand = { version = "0.8.4", default-features = false } +serde = { version = "1.0.136", default-features = false } +rtos-trace = "0.1.3" +systemview-target = { version = "0.1.2", features = ["callbacks-app", "callbacks-os", "log", "cortex-m"] } +log = { version = "0.4.17", optional = true } + +[[bin]] +name = "rtos_trace" + + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/nrf51/Cargo~20241114-122314.toml b/.stversions/embassy/examples/nrf51/Cargo~20241114-122314.toml new file mode 100644 index 0000000..3799a87 --- /dev/null +++ b/.stversions/embassy/examples/nrf51/Cargo~20241114-122314.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "embassy-nrf51-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf51", "gpiote", "time-driver-rtc1", "unstable-pac", "time", "rt"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/nrf52810/Cargo~20241114-122315.toml b/.stversions/embassy/examples/nrf52810/Cargo~20241114-122315.toml new file mode 100644 index 0000000..e3045bd --- /dev/null +++ b/.stversions/embassy/examples/nrf52810/Cargo~20241114-122315.toml @@ -0,0 +1,24 @@ +[package] +edition = "2021" +name = "embassy-nrf52810-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52810", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +fixed = "1.10.0" +static_cell = { version = "2" } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/nrf52840/Cargo~20241114-122314.toml b/.stversions/embassy/examples/nrf52840/Cargo~20241114-122314.toml new file mode 100644 index 0000000..e0a27c6 --- /dev/null +++ b/.stversions/embassy/examples/nrf52840/Cargo~20241114-122314.toml @@ -0,0 +1,39 @@ +[package] +edition = "2021" +name = "embassy-nrf52840-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embedded-io = { version = "0.6.0", features = ["defmt-03"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } +embassy-net-enc28j60 = { version = "0.1.0", path = "../../embassy-net-enc28j60", features = ["defmt"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +fixed = "1.10.0" +static_cell = { version = "2" } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +rand = { version = "0.8.4", default-features = false } +embedded-storage = "0.3.1" +usbd-hid = "0.8.1" +serde = { version = "1.0.136", default-features = false } +embedded-hal = { version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } +num-integer = { version = "0.1.45", default-features = false } +microfft = "0.5.0" + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/nrf5340/Cargo~20241114-122315.toml b/.stversions/embassy/examples/nrf5340/Cargo~20241114-122315.toml new file mode 100644 index 0000000..2a4e78b --- /dev/null +++ b/.stversions/embassy/examples/nrf5340/Cargo~20241114-122315.toml @@ -0,0 +1,30 @@ +[package] +edition = "2021" +name = "embassy-nrf5340-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf5340-app-s", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embedded-io-async = { version = "0.6.1" } + +defmt = "0.3" +defmt-rtt = "0.4" + +static_cell = "2" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +rand = { version = "0.8.4", default-features = false } +embedded-storage = "0.3.1" +usbd-hid = "0.8.1" +serde = { version = "1.0.136", default-features = false } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/nrf9151/ns/Cargo~20241114-122314.toml b/.stversions/embassy/examples/nrf9151/ns/Cargo~20241114-122314.toml new file mode 100644 index 0000000..6793317 --- /dev/null +++ b/.stversions/embassy/examples/nrf9151/ns/Cargo~20241114-122314.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "embassy-nrf9151-non-secure-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.6.2", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-ns", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/nrf9151/s/Cargo~20241114-122315.toml b/.stversions/embassy/examples/nrf9151/s/Cargo~20241114-122315.toml new file mode 100644 index 0000000..6311447 --- /dev/null +++ b/.stversions/embassy/examples/nrf9151/s/Cargo~20241114-122315.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "embassy-nrf9151-secure-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.6.2", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/nrf9160/Cargo~20241114-122315.toml b/.stversions/embassy/examples/nrf9160/Cargo~20241114-122315.toml new file mode 100644 index 0000000..d5b0f0a --- /dev/null +++ b/.stversions/embassy/examples/nrf9160/Cargo~20241114-122315.toml @@ -0,0 +1,26 @@ +[package] +edition = "2021" +name = "embassy-nrf9160-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-net-nrf91 = { version = "0.1.0", path = "../../embassy-net-nrf91", features = ["defmt"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "proto-ipv4", "medium-ip"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +heapless = "0.8" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +static_cell = { version = "2" } +embedded-io = "0.6.1" +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/rp/Cargo~20241114-122315.toml b/.stversions/embassy/examples/rp/Cargo~20241114-122315.toml new file mode 100644 index 0000000..b45f504 --- /dev/null +++ b/.stversions/embassy/examples/rp/Cargo~20241114-122315.toml @@ -0,0 +1,82 @@ +[package] +edition = "2021" +name = "embassy-rp-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns", "proto-ipv4", "proto-ipv6", "multicast"] } +embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-usb-logger = { version = "0.2.0", path = "../../embassy-usb-logger" } +cyw43 = { version = "0.2.0", path = "../../cyw43", features = ["defmt", "firmware-logs", "bluetooth"] } +cyw43-pio = { version = "0.2.0", path = "../../cyw43-pio", features = ["defmt"] } + +defmt = "0.3" +defmt-rtt = "0.4" +fixed = "1.23.1" +fixed-macro = "1.2" + +# for web request example +reqwless = { version = "0.13.0", features = ["defmt"] } +serde = { version = "1.0.203", default-features = false, features = ["derive"] } +serde-json-core = "0.5.1" + +# for assign resources example +assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } + +#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm"] } +cortex-m-rt = "0.7.0" +critical-section = "1.1" +panic-probe = { version = "0.3", features = ["print-defmt"] } +display-interface-spi = "0.5.0" +embedded-graphics = "0.8.1" +mipidsi = "0.8.0" +display-interface = "0.5.0" +byte-slice-cast = { version = "1.2.0", default-features = false } +smart-leds = "0.4.0" +heapless = "0.8" +usbd-hid = "0.8.1" + +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0" +embedded-hal-bus = { version = "0.1", features = ["async"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-storage = { version = "0.3" } +static_cell = "2.1" +portable-atomic = { version = "1.5", features = ["critical-section"] } +log = "0.4" +pio-proc = "0.2" +pio = "0.2.1" +rand = { version = "0.8.5", default-features = false } +embedded-sdmmc = "0.7.0" + +bt-hci = { version = "0.1.0", default-features = false, features = ["defmt"] } +trouble-host = { version = "0.1.0", features = ["defmt", "gatt"] } + +[profile.release] +debug = 2 +lto = true +opt-level = 'z' + +[profile.dev] +debug = 2 +lto = true +opt-level = "z" + +[patch.crates-io] +trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" } +embassy-executor = { path = "../../embassy-executor" } +embassy-sync = { path = "../../embassy-sync" } +embassy-futures = { path = "../../embassy-futures" } +embassy-time = { path = "../../embassy-time" } +embassy-time-driver = { path = "../../embassy-time-driver" } +embassy-embedded-hal = { path = "../../embassy-embedded-hal" } diff --git a/.stversions/embassy/examples/rp23/Cargo~20241114-122314.toml b/.stversions/embassy/examples/rp23/Cargo~20241114-122314.toml new file mode 100644 index 0000000..99a75a6 --- /dev/null +++ b/.stversions/embassy/examples/rp23/Cargo~20241114-122314.toml @@ -0,0 +1,80 @@ +[package] +edition = "2021" +name = "embassy-rp2350-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns"] } +embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-usb-logger = { version = "0.2.0", path = "../../embassy-usb-logger" } +cyw43 = { version = "0.2.0", path = "../../cyw43", features = ["defmt", "firmware-logs", "bluetooth"] } +cyw43-pio = { version = "0.2.0", path = "../../cyw43-pio", features = ["defmt"] } + +defmt = "0.3" +defmt-rtt = "0.4" +fixed = "1.23.1" +fixed-macro = "1.2" + +serde = { version = "1.0.203", default-features = false, features = ["derive"] } +serde-json-core = "0.5.1" + +# for assign resources example +assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } + +# for TB6612FNG example +tb6612fng = "1.0.0" + +#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm"] } +cortex-m-rt = "0.7.0" +critical-section = "1.1" +panic-probe = { version = "0.3", features = ["print-defmt"] } +display-interface-spi = "0.5.0" +embedded-graphics = "0.8.1" +mipidsi = "0.8.0" +display-interface = "0.5.0" +byte-slice-cast = { version = "1.2.0", default-features = false } +smart-leds = "0.3.0" +heapless = "0.8" +usbd-hid = "0.8.1" + +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0" +embedded-hal-bus = { version = "0.1", features = ["async"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-storage = { version = "0.3" } +static_cell = "2.1" +portable-atomic = { version = "1.5", features = ["critical-section"] } +log = "0.4" +pio-proc = "0.2" +pio = "0.2.1" +rand = { version = "0.8.5", default-features = false } +embedded-sdmmc = "0.7.0" + +bt-hci = { version = "0.1.0", default-features = false, features = ["defmt"] } +trouble-host = { version = "0.1.0", features = ["defmt", "gatt"] } + +[profile.release] +debug = 2 + +[profile.dev] +lto = true +opt-level = "z" + +[patch.crates-io] +trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" } +embassy-executor = { path = "../../embassy-executor" } +embassy-sync = { path = "../../embassy-sync" } +embassy-futures = { path = "../../embassy-futures" } +embassy-time = { path = "../../embassy-time" } +embassy-time-driver = { path = "../../embassy-time-driver" } +embassy-embedded-hal = { path = "../../embassy-embedded-hal" } diff --git a/.stversions/embassy/examples/std/Cargo~20241114-122314.toml b/.stversions/embassy/examples/std/Cargo~20241114-122314.toml new file mode 100644 index 0000000..fac180c --- /dev/null +++ b/.stversions/embassy/examples/std/Cargo~20241114-122314.toml @@ -0,0 +1,29 @@ +[package] +edition = "2021" +name = "embassy-std-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["log"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-std", "executor-thread", "log", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "std", ] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features=[ "std", "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } +embassy-net-tuntap = { version = "0.1.0", path = "../../embassy-net-tuntap" } +embassy-net-ppp = { version = "0.1.0", path = "../../embassy-net-ppp", features = ["log"]} +embedded-io-async = { version = "0.6.1" } +embedded-io-adapters = { version = "0.6.1", features = ["futures-03"] } +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.26.2" +clap = { version = "3.0.0-beta.5", features = ["derive"] } +rand_core = { version = "0.6.3", features = ["std"] } +heapless = { version = "0.8", default-features = false } +static_cell = "2" + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32c0/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32c0/Cargo~20241114-122314.toml new file mode 100644 index 0000000..f8a8450 --- /dev/null +++ b/.stversions/embassy/examples/stm32c0/Cargo~20241114-122314.toml @@ -0,0 +1,24 @@ +[package] +edition = "2021" +name = "embassy-stm32c0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32c031c6 to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32c031c6", "memory-x", "unstable-pac", "exti"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +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"] } +heapless = { version = "0.8", default-features = false } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32f0/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32f0/Cargo~20241114-122314.toml new file mode 100644 index 0000000..6154fdc --- /dev/null +++ b/.stversions/embassy/examples/stm32f0/Cargo~20241114-122314.toml @@ -0,0 +1,22 @@ +[package] +name = "embassy-stm32f0-examples" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f091rc to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "memory-x", "stm32f091rc", "time-driver-any", "exti", "unstable-pac"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3", features = ["print-defmt"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +static_cell = "2" +portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32f1/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32f1/Cargo~20241114-122314.toml new file mode 100644 index 0000000..c483685 --- /dev/null +++ b/.stversions/embassy/examples/stm32f1/Cargo~20241114-122314.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-stm32f1-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f103c8 to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any" ] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +nb = "1.0.0" +static_cell = "2.0.0" + +[profile.dev] +opt-level = "s" + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32f2/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32f2/Cargo~20241114-122315.toml new file mode 100644 index 0000000..0e4827e --- /dev/null +++ b/.stversions/embassy/examples/stm32f2/Cargo~20241114-122315.toml @@ -0,0 +1,25 @@ +[package] +edition = "2021" +name = "embassy-stm32f2-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f207zg to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f207zg", "unstable-pac", "memory-x", "time-driver-any", "exti"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +nb = "1.0.0" + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32f3/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32f3/Cargo~20241114-122315.toml new file mode 100644 index 0000000..08a9216 --- /dev/null +++ b/.stversions/embassy/examples/stm32f3/Cargo~20241114-122315.toml @@ -0,0 +1,29 @@ +[package] +edition = "2021" +name = "embassy-stm32f3-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f303ze to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-any", "exti"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +nb = "1.0.0" +embedded-storage = "0.3.1" +static_cell = "2" + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32f334/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32f334/Cargo~20241114-122315.toml new file mode 100644 index 0000000..cba83bf --- /dev/null +++ b/.stversions/embassy/examples/stm32f334/Cargo~20241114-122315.toml @@ -0,0 +1,25 @@ +[package] +edition = "2021" +name = "embassy-stm32f334-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f334r8", "unstable-pac", "memory-x", "time-driver-any", "exti"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +nb = "1.0.0" +embedded-storage = "0.3.1" +static_cell = "2" diff --git a/.stversions/embassy/examples/stm32f4/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32f4/Cargo~20241114-122314.toml new file mode 100644 index 0000000..32fb845 --- /dev/null +++ b/.stversions/embassy/examples/stm32f4/Cargo~20241114-122314.toml @@ -0,0 +1,38 @@ +[package] +edition = "2021" +name = "embassy-stm32f4-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f429zi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt" ] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } +embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-bus = { version = "0.2", features = ["async"] } +embedded-io = { version = "0.6.0" } +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +futures-util = { version = "0.3.30", default-features = false } +heapless = { version = "0.8", default-features = false } +nb = "1.0.0" +embedded-storage = "0.3.1" +micromath = "2.0.0" +usbd-hid = "0.8.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false} + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32f469/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32f469/Cargo~20241114-122314.toml new file mode 100644 index 0000000..139b4b9 --- /dev/null +++ b/.stversions/embassy/examples/stm32f469/Cargo~20241114-122314.toml @@ -0,0 +1,22 @@ +[package] +edition = "2021" +name = "embassy-stm32f469-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Specific examples only for stm32f469 +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f469ni", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "1.0.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32f7/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32f7/Cargo~20241114-122314.toml new file mode 100644 index 0000000..2c55da0 --- /dev/null +++ b/.stversions/embassy/examples/stm32f7/Cargo~20241114-122314.toml @@ -0,0 +1,36 @@ +[package] +edition = "2021" +name = "embassy-stm32f7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f777zi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f777zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embedded-io-async = { version = "0.6.1" } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +nb = "1.0.0" +rand_core = "0.6.3" +critical-section = "1.1" +embedded-storage = "0.3.1" +static_cell = "2" +sha2 = { version = "0.10.8", default-features = false } +hmac = "0.12.1" +aes-gcm = {version = "0.10.3", default-features = false, features = ["aes", "heapless"] } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32g0/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32g0/Cargo~20241114-122315.toml new file mode 100644 index 0000000..bf258a7 --- /dev/null +++ b/.stversions/embassy/examples/stm32g0/Cargo~20241114-122315.toml @@ -0,0 +1,29 @@ +[package] +edition = "2021" +name = "embassy-stm32g0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32g0b1re to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g0b1re", "memory-x", "unstable-pac", "exti"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } + +embedded-io-async = { version = "0.6.1" } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32g4/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32g4/Cargo~20241114-122314.toml new file mode 100644 index 0000000..cc3b44b --- /dev/null +++ b/.stversions/embassy/examples/stm32g4/Cargo~20241114-122314.toml @@ -0,0 +1,29 @@ +[package] +edition = "2021" +name = "embassy-stm32g4-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32g491re to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g491re", "memory-x", "unstable-pac", "exti"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +usbd-hid = "0.8.1" + +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" +embedded-can = { version = "0.4" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +static_cell = "2.0.0" + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32h5/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32h5/Cargo~20241114-122315.toml new file mode 100644 index 0000000..d1a865e --- /dev/null +++ b/.stversions/embassy/examples/stm32h5/Cargo~20241114-122315.toml @@ -0,0 +1,72 @@ +[package] +edition = "2021" +name = "embassy-stm32h5-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h563zi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h563zi", "memory-x", "time-driver-any", "exti", "unstable-pac", "low-power"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-io-async = { version = "0.6.1" } +embedded-nal-async = "0.8.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/.stversions/embassy/examples/stm32h7/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32h7/Cargo~20241114-122314.toml new file mode 100644 index 0000000..1ba792f --- /dev/null +++ b/.stversions/embassy/examples/stm32h7/Cargo~20241114-122314.toml @@ -0,0 +1,75 @@ +[package] +edition = "2021" +name = "embassy-stm32h7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h743bi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h743bi", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/.stversions/embassy/examples/stm32h735/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32h735/Cargo~20241114-122315.toml new file mode 100644 index 0000000..6a74d83 --- /dev/null +++ b/.stversions/embassy/examples/stm32h735/Cargo~20241114-122315.toml @@ -0,0 +1,61 @@ +[package] +edition = "2021" +name = "embassy-stm32h735-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h735ig", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +embedded-graphics = { version = "0.8.1" } +tinybmp = { version = "0.5" } + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/.stversions/embassy/examples/stm32h755cm4/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32h755cm4/Cargo~20241114-122314.toml new file mode 100644 index 0000000..ee96e64 --- /dev/null +++ b/.stversions/embassy/examples/stm32h755cm4/Cargo~20241114-122314.toml @@ -0,0 +1,75 @@ +[package] +edition = "2021" +name = "embassy-stm32h755cm4-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h755zi-cm4 to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm4", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/.stversions/embassy/examples/stm32h755cm7/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32h755cm7/Cargo~20241114-122314.toml new file mode 100644 index 0000000..61c4257 --- /dev/null +++ b/.stversions/embassy/examples/stm32h755cm7/Cargo~20241114-122314.toml @@ -0,0 +1,75 @@ +[package] +edition = "2021" +name = "embassy-stm32h755cm7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h743bi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm7", "time-driver-tim3", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/.stversions/embassy/examples/stm32h7b0/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32h7b0/Cargo~20241114-122314.toml new file mode 100644 index 0000000..255ce93 --- /dev/null +++ b/.stversions/embassy/examples/stm32h7b0/Cargo~20241114-122314.toml @@ -0,0 +1,74 @@ +[package] +edition = "2021" +name = "embassy-stm32h7b0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7b0vb", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/.stversions/embassy/examples/stm32h7rs/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32h7rs/Cargo~20241114-122314.toml new file mode 100644 index 0000000..055be6a --- /dev/null +++ b/.stversions/embassy/examples/stm32h7rs/Cargo~20241114-122314.toml @@ -0,0 +1,73 @@ +[package] +edition = "2021" +name = "embassy-stm32h7rs-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h743bi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7s3l8", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/.stversions/embassy/examples/stm32l0/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32l0/Cargo~20241114-122314.toml new file mode 100644 index 0000000..4e2bb8b --- /dev/null +++ b/.stversions/embassy/examples/stm32l0/Cargo~20241114-122314.toml @@ -0,0 +1,30 @@ +[package] +edition = "2021" +name = "embassy-stm32l0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32l072cz to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "unstable-pac", "time-driver-any", "exti", "memory-x"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +embedded-storage = "0.3.1" +embedded-io = { version = "0.6.0" } +embedded-io-async = { version = "0.6.1" } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +embedded-hal = "0.2.6" +static_cell = { version = "2" } +portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32l1/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32l1/Cargo~20241114-122315.toml new file mode 100644 index 0000000..865cad8 --- /dev/null +++ b/.stversions/embassy/examples/stm32l1/Cargo~20241114-122315.toml @@ -0,0 +1,26 @@ +[package] +edition = "2021" +name = "embassy-stm32l1-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32l151cb-a", "time-driver-any", "memory-x"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +embedded-storage = "0.3.1" + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32l4/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32l4/Cargo~20241114-122315.toml new file mode 100644 index 0000000..7f963fc --- /dev/null +++ b/.stversions/embassy/examples/stm32l4/Cargo~20241114-122315.toml @@ -0,0 +1,39 @@ +[package] +edition = "2021" +name = "embassy-stm32l4-examples" +version = "0.1.1" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32l4s5vi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4s5qi", "memory-x", "time-driver-any", "exti", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net-adin1110 = { version = "0.2.0", path = "../../embassy-net-adin1110" } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-io = { version = "0.6.0", features = ["defmt-03"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +chrono = { version = "^0.4", default-features = false } +rand = { version = "0.8.5", default-features = false } +static_cell = "2" + +micromath = "2.0.0" + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32l5/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32l5/Cargo~20241114-122315.toml new file mode 100644 index 0000000..0604625 --- /dev/null +++ b/.stversions/embassy/examples/stm32l5/Cargo~20241114-122315.toml @@ -0,0 +1,35 @@ +[package] +edition = "2021" +name = "embassy-stm32l5-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32l552ze to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "memory-x", "low-power"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +usbd-hid = "0.8.1" + +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +heapless = { version = "0.8", default-features = false } +rand_core = { version = "0.6.3", default-features = false } +embedded-io-async = { version = "0.6.1" } +static_cell = "2" + +[profile.release] +debug = 2 + +[[bin]] +name = "stop" +default-features = ["embassy-stm32/low-power"] diff --git a/.stversions/embassy/examples/stm32u0/Cargo~20241114-122314.toml b/.stversions/embassy/examples/stm32u0/Cargo~20241114-122314.toml new file mode 100644 index 0000000..97ef0b7 --- /dev/null +++ b/.stversions/embassy/examples/stm32u0/Cargo~20241114-122314.toml @@ -0,0 +1,29 @@ +[package] +edition = "2021" +name = "embassy-stm32u0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32u083rc to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32u083rc", "memory-x", "unstable-pac", "exti", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +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"] } +heapless = { version = "0.8", default-features = false } + +micromath = "2.0.0" +chrono = { version = "0.4.38", default-features = false } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32u5/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32u5/Cargo~20241114-122315.toml new file mode 100644 index 0000000..f474e6d --- /dev/null +++ b/.stversions/embassy/examples/stm32u5/Cargo~20241114-122315.toml @@ -0,0 +1,34 @@ +[package] +edition = "2021" +name = "embassy-stm32u5-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32u5g9zj to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u5g9zj", "time-driver-any", "memory-x" ] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +embedded-graphics = { version = "0.8.1" } +tinybmp = { version = "0.6.0" } + +micromath = "2.0.0" + +[features] +## Use secure registers when TrustZone is enabled +trustzone-secure = ["embassy-stm32/trustzone-secure"] + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32wb/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32wb/Cargo~20241114-122315.toml new file mode 100644 index 0000000..26de811 --- /dev/null +++ b/.stversions/embassy/examples/stm32wb/Cargo~20241114-122315.toml @@ -0,0 +1,56 @@ +[package] +edition = "2021" +name = "embassy-stm32wb-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32wb55rg to your chip name in both dependencies, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] } +embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", features = ["defmt", "stm32wb55rg"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +static_cell = "2" + +[features] +default = ["ble", "mac"] +mac = ["embassy-stm32-wpan/mac", "dep:embassy-net"] +ble = ["embassy-stm32-wpan/ble"] + +[[bin]] +name = "tl_mbox_ble" +required-features = ["ble"] + +[[bin]] +name = "tl_mbox_mac" +required-features = ["mac"] + +[[bin]] +name = "mac_ffd" +required-features = ["mac"] + +[[bin]] +name = "mac_ffd_net" +required-features = ["mac"] + +[[bin]] +name = "eddystone_beacon" +required-features = ["ble"] + +[[bin]] +name = "gatt_server" +required-features = ["ble"] + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32wba/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32wba/Cargo~20241114-122315.toml new file mode 100644 index 0000000..807f809 --- /dev/null +++ b/.stversions/embassy/examples/stm32wba/Cargo~20241114-122315.toml @@ -0,0 +1,25 @@ +[package] +edition = "2021" +name = "embassy-stm32wba-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wba52cg", "time-driver-any", "memory-x", "exti"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +static_cell = "2" + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/stm32wl/Cargo~20241114-122315.toml b/.stversions/embassy/examples/stm32wl/Cargo~20241114-122315.toml new file mode 100644 index 0000000..3b42239 --- /dev/null +++ b/.stversions/embassy/examples/stm32wl/Cargo~20241114-122315.toml @@ -0,0 +1,27 @@ +[package] +edition = "2021" +name = "embassy-stm32wl-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32wl55jc-cm4 to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-storage = "0.3.1" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +chrono = { version = "^0.4", default-features = false } + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/examples/wasm/Cargo~20241114-122315.toml b/.stversions/embassy/examples/wasm/Cargo~20241114-122315.toml new file mode 100644 index 0000000..ea66d9b --- /dev/null +++ b/.stversions/embassy/examples/wasm/Cargo~20241114-122315.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "embassy-wasm-example" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["log"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "wasm", ] } + +wasm-logger = "0.2.0" +wasm-bindgen = "0.2" +web-sys = { version = "0.3", features = ["Document", "Element", "HtmlElement", "Node", "Window" ] } +log = "0.4.11" + +[profile.release] +debug = 2 diff --git a/.stversions/embassy/tests/nrf/Cargo~20241114-122315.toml b/.stversions/embassy/tests/nrf/Cargo~20241114-122315.toml new file mode 100644 index 0000000..c1d386b --- /dev/null +++ b/.stversions/embassy/tests/nrf/Cargo~20241114-122315.toml @@ -0,0 +1,114 @@ +[package] +edition = "2021" +name = "embassy-nrf-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +teleprobe-meta = "1" + +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt", ] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "task-arena-size-16384", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } +embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } +embassy-net-enc28j60 = { version = "0.1.0", path = "../../embassy-net-enc28j60", features = ["defmt"] } +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } +static_cell = "2" +perf-client = { path = "../perf-client" } + +defmt = "0.3" +defmt-rtt = "0.4" + +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"] } +portable-atomic = { version = "1.6.0" } + +[features] +nrf51422 = ["embassy-nrf/nrf51", "portable-atomic/unsafe-assume-single-core"] +nrf52832 = ["embassy-nrf/nrf52832", "easydma"] +nrf52833 = ["embassy-nrf/nrf52833", "easydma", "two-uarts"] +nrf52840 = ["embassy-nrf/nrf52840", "easydma", "two-uarts"] +nrf5340 = ["embassy-nrf/nrf5340-app-s", "easydma", "two-uarts"] +nrf9160 = ["embassy-nrf/nrf9160-s", "easydma", "two-uarts"] + +easydma = [] +two-uarts = [] + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 's' +overflow-checks = false + +# BEGIN TESTS +# Generated by gen_test.py. DO NOT EDIT. +[[bin]] +name = "buffered_uart" +path = "src/bin/buffered_uart.rs" +required-features = [ "easydma",] + +[[bin]] +name = "buffered_uart_full" +path = "src/bin/buffered_uart_full.rs" +required-features = [ "easydma",] + +[[bin]] +name = "buffered_uart_halves" +path = "src/bin/buffered_uart_halves.rs" +required-features = [ "two-uarts",] + +[[bin]] +name = "buffered_uart_spam" +path = "src/bin/buffered_uart_spam.rs" +required-features = [ "two-uarts",] + +[[bin]] +name = "ethernet_enc28j60_perf" +path = "src/bin/ethernet_enc28j60_perf.rs" +required-features = [ "nrf52840",] + +[[bin]] +name = "gpio" +path = "src/bin/gpio.rs" +required-features = [] + +[[bin]] +name = "gpiote" +path = "src/bin/gpiote.rs" +required-features = [] + +[[bin]] +name = "spim" +path = "src/bin/spim.rs" +required-features = [ "easydma",] + +[[bin]] +name = "timer" +path = "src/bin/timer.rs" +required-features = [] + +[[bin]] +name = "uart_halves" +path = "src/bin/uart_halves.rs" +required-features = [ "two-uarts",] + +[[bin]] +name = "uart_split" +path = "src/bin/uart_split.rs" +required-features = [ "easydma",] + +[[bin]] +name = "wifi_esp_hosted_perf" +path = "src/bin/wifi_esp_hosted_perf.rs" +required-features = [ "nrf52840",] + +# END TESTS diff --git a/.stversions/embassy/tests/riscv32/Cargo~20241114-122315.toml b/.stversions/embassy/tests/riscv32/Cargo~20241114-122315.toml new file mode 100644 index 0000000..35dd283 --- /dev/null +++ b/.stversions/embassy/tests/riscv32/Cargo~20241114-122315.toml @@ -0,0 +1,46 @@ +[package] +edition = "2021" +name = "embassy-riscv-tests" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +critical-section = { version = "1.1.1", features = ["restore-state-bool"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync" } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-riscv32", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +riscv-rt = "0.12.2" +riscv = { version = "0.11.1", features = ["critical-section-single-hart"] } + + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 's' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 's' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/.stversions/embassy/tests/rp/Cargo~20241114-122314.toml b/.stversions/embassy/tests/rp/Cargo~20241114-122314.toml new file mode 100644 index 0000000..72f7ceb --- /dev/null +++ b/.stversions/embassy/tests/rp/Cargo~20241114-122314.toml @@ -0,0 +1,68 @@ +[package] +edition = "2021" +name = "embassy-rp-tests" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +teleprobe-meta = "1.1" + +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", ] } +embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = [ "defmt", "unstable-pac", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram", "rp2040"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet"] } +embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal/"} +cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } +cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } +perf-client = { path = "../perf-client" } + +defmt = "0.3.0" +defmt-rtt = "0.4" + +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" } +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } +panic-probe = { version = "0.3.0", features = ["print-defmt"] } +embedded-io-async = { version = "0.6.1" } +embedded-storage = { version = "0.3" } +static_cell = "2" +portable-atomic = { version = "1.5", features = ["critical-section"] } +pio = "0.2" +pio-proc = "0.2" +rand = { version = "0.8.5", default-features = false } + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 's' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 's' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/.stversions/embassy/tests/stm32/Cargo~20241114-122315.toml b/.stversions/embassy/tests/stm32/Cargo~20241114-122315.toml new file mode 100644 index 0000000..f30217f --- /dev/null +++ b/.stversions/embassy/tests/stm32/Cargo~20241114-122315.toml @@ -0,0 +1,235 @@ +[package] +edition = "2021" +name = "embassy-stm32-tests" +version = "0.1.0" +license = "MIT OR Apache-2.0" +autobins = false + +[features] +stm32c031c6 = ["embassy-stm32/stm32c031c6", "cm0", "not-gpdma"] +stm32f103c8 = ["embassy-stm32/stm32f103c8", "spi-v1", "not-gpdma"] +stm32f207zg = ["embassy-stm32/stm32f207zg", "spi-v1", "chrono", "not-gpdma", "eth", "rng"] +stm32f303ze = ["embassy-stm32/stm32f303ze", "chrono", "not-gpdma"] +stm32f429zi = ["embassy-stm32/stm32f429zi", "spi-v1", "chrono", "eth", "stop", "can", "not-gpdma", "dac", "rng"] +stm32f446re = ["embassy-stm32/stm32f446re", "spi-v1", "chrono", "stop", "can", "not-gpdma", "dac", "sdmmc"] +stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng"] +stm32g071rb = ["embassy-stm32/stm32g071rb", "cm0", "not-gpdma", "dac", "ucpd"] +stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng", "fdcan", "cordic"] +stm32h563zi = ["embassy-stm32/stm32h563zi", "spi-v345", "chrono", "eth", "rng", "fdcan", "hash", "cordic", "stop"] +stm32h753zi = ["embassy-stm32/stm32h753zi", "spi-v345", "chrono", "not-gpdma", "eth", "rng", "fdcan", "hash", "cryp"] +stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "spi-v345", "chrono", "not-gpdma", "eth", "dac", "rng", "fdcan", "hash", "cryp"] +stm32h7a3zi = ["embassy-stm32/stm32h7a3zi", "spi-v345", "not-gpdma", "rng", "fdcan"] +stm32l073rz = ["embassy-stm32/stm32l073rz", "cm0", "not-gpdma", "rng"] +stm32l152re = ["embassy-stm32/stm32l152re", "spi-v1", "chrono", "not-gpdma"] +stm32l496zg = ["embassy-stm32/stm32l496zg", "not-gpdma", "rng"] +stm32l4a6zg = ["embassy-stm32/stm32l4a6zg", "chrono", "not-gpdma", "rng", "hash"] +stm32l4r5zi = ["embassy-stm32/stm32l4r5zi", "chrono", "not-gpdma", "rng"] +stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash"] +stm32u585ai = ["embassy-stm32/stm32u585ai", "spi-v345", "chrono", "rng", "hash", "cordic"] +stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "spi-v345", "chrono", "rng", "hash"] # FIXME: cordic test cause it crash +stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng"] +stm32wba52cg = ["embassy-stm32/stm32wba52cg", "spi-v345", "chrono", "rng", "hash"] +stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono"] +stm32f091rc = ["embassy-stm32/stm32f091rc", "cm0", "not-gpdma", "chrono"] +stm32h503rb = ["embassy-stm32/stm32h503rb", "spi-v345", "rng", "stop"] +stm32h7s3l8 = ["embassy-stm32/stm32h7s3l8", "spi-v345", "rng", "cordic", "hash"] # TODO: fdcan crashes, cryp dma hangs. +stm32u083rc = ["embassy-stm32/stm32u083rc", "cm0", "rng", "chrono"] + +spi-v1 = [] +spi-v345 = [] +cryp = [] +hash = [] +eth = ["embassy-executor/task-arena-size-16384"] +rng = [] +sdmmc = [] +stop = ["embassy-stm32/low-power", "embassy-stm32/low-power-debug-with-sleep"] +chrono = ["embassy-stm32/chrono", "dep:chrono"] +can = [] +fdcan = [] +ble = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/ble"] +mac = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/mac"] +embassy-stm32-wpan = [] +not-gpdma = [] +dac = [] +ucpd = [] +cordic = ["dep:num-traits"] + +cm0 = ["portable-atomic/unsafe-assume-single-core"] + +[dependencies] +teleprobe-meta = "1" + +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "tick-hz-131_072", "defmt-timestamp-uptime"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "memory-x", "time-driver-any"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", optional = true, features = ["defmt", "stm32wb55rg", "ble"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet"] } +perf-client = { path = "../perf-client" } + +defmt = "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" } +embedded-hal-async = { version = "1.0" } +embedded-can = { version = "0.4" } +micromath = "2.0.0" +panic-probe = { version = "0.3.0", features = ["print-defmt"] } +rand_core = { version = "0.6", default-features = false } +rand_chacha = { version = "0.3", default-features = false } +static_cell = "2" +portable-atomic = { version = "1.5", features = [] } + +chrono = { version = "^0.4", default-features = false, optional = true} +sha2 = { version = "0.10.8", default-features = false } +hmac = "0.12.1" +aes-gcm = {version = "0.10.3", default-features = false, features = ["aes", "heapless"] } +num-traits = {version="0.2", default-features = false,features = ["libm"], optional = true} + +# BEGIN TESTS +# Generated by gen_test.py. DO NOT EDIT. +[[bin]] +name = "can" +path = "src/bin/can.rs" +required-features = [ "can",] + +[[bin]] +name = "cordic" +path = "src/bin/cordic.rs" +required-features = [ "rng", "cordic",] + +[[bin]] +name = "cryp" +path = "src/bin/cryp.rs" +required-features = [ "cryp",] + +[[bin]] +name = "dac" +path = "src/bin/dac.rs" +required-features = [ "dac",] + +[[bin]] +name = "dac_l1" +path = "src/bin/dac_l1.rs" +required-features = [ "stm32l152re",] + +[[bin]] +name = "eth" +path = "src/bin/eth.rs" +required-features = [ "eth",] + +[[bin]] +name = "fdcan" +path = "src/bin/fdcan.rs" +required-features = [ "fdcan",] + +[[bin]] +name = "gpio" +path = "src/bin/gpio.rs" +required-features = [] + +[[bin]] +name = "hash" +path = "src/bin/hash.rs" +required-features = [ "hash",] + +[[bin]] +name = "rng" +path = "src/bin/rng.rs" +required-features = [ "rng",] + +[[bin]] +name = "rtc" +path = "src/bin/rtc.rs" +required-features = [ "chrono",] + +[[bin]] +name = "sdmmc" +path = "src/bin/sdmmc.rs" +required-features = [ "sdmmc",] + +[[bin]] +name = "spi" +path = "src/bin/spi.rs" +required-features = [] + +[[bin]] +name = "spi_dma" +path = "src/bin/spi_dma.rs" +required-features = [] + +[[bin]] +name = "stop" +path = "src/bin/stop.rs" +required-features = [ "stop", "chrono",] + +[[bin]] +name = "timer" +path = "src/bin/timer.rs" +required-features = [] + +[[bin]] +name = "ucpd" +path = "src/bin/ucpd.rs" +required-features = [ "ucpd",] + +[[bin]] +name = "usart" +path = "src/bin/usart.rs" +required-features = [] + +[[bin]] +name = "usart_dma" +path = "src/bin/usart_dma.rs" +required-features = [] + +[[bin]] +name = "usart_rx_ringbuffered" +path = "src/bin/usart_rx_ringbuffered.rs" +required-features = [ "not-gpdma",] + +[[bin]] +name = "wpan_ble" +path = "src/bin/wpan_ble.rs" +required-features = [ "ble",] + +[[bin]] +name = "wpan_mac" +path = "src/bin/wpan_mac.rs" +required-features = [ "mac",] + +# END TESTS + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 's' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 's' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/.stversions/ledisa/Cargo~20241113-194335.lock b/.stversions/ledisa/Cargo~20241113-194335.lock new file mode 100644 index 0000000..7745270 --- /dev/null +++ b/.stversions/ledisa/Cargo~20241113-194335.lock @@ -0,0 +1,189 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bincode" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" +dependencies = [ + "bincode_derive", + "serde", +] + +[[package]] +name = "bincode_derive" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c" +dependencies = [ + "virtue", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "defmt" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9f309eff1f79b3ebdf252954d90ae440599c26c2c553fe87a2d17195f2dcb" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "defmt-parser" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f" +dependencies = [ + "thiserror", +] + +[[package]] +name = "ledisa" +version = "0.1.0" +dependencies = [ + "bincode", + "defmt", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtue" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" diff --git a/.stversions/ledisa/Cargo~20241113-194335.toml b/.stversions/ledisa/Cargo~20241113-194335.toml new file mode 100644 index 0000000..e4972bb --- /dev/null +++ b/.stversions/ledisa/Cargo~20241113-194335.toml @@ -0,0 +1,16 @@ +[package] +name = "ledisa" +version = "0.1.0" +edition = "2021" + +[dependencies] +bincode = { version = "2.0.0-rc.3", default-features = false, features = [ + "alloc", + "derive", +] } +defmt = "0.3" + +[features] +default = ["std"] +std = [] +alloc = [] diff --git a/.stversions/ledisa/src/lib~20241113-194335.rs b/.stversions/ledisa/src/lib~20241113-194335.rs new file mode 100644 index 0000000..a0655ed --- /dev/null +++ b/.stversions/ledisa/src/lib~20241113-194335.rs @@ -0,0 +1,32 @@ +#![no_std] + +extern crate alloc; + +use alloc::vec; +use alloc::vec::Vec; +use bincode::{config, Decode, Encode}; + +#[derive(Copy, Clone, Encode, Decode, PartialEq, Debug)] +pub struct Led { + pub r: u8, + pub g: u8, + pub b: u8, +} + +#[derive(Clone, Encode, Decode, PartialEq, Debug)] +pub struct LedStrip { + pub leds: Vec, +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(size: usize, r: u8, g: u8, b: u8) -> LedStrip { + let led_vec = vec![Led { r, g, b }; size]; + + // for i in 0..99 { + // led_vec[i] = Led{ r: i as u8 + 2, g: i as u8 + 13, b: i as u8 + 33 }; + // } + + LedStrip { leds: led_vec } + } +} diff --git a/.stversions/ledisa/~20241113-194335.gitignore b/.stversions/ledisa/~20241113-194335.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.stversions/ledisa/~20241113-194335.gitignore @@ -0,0 +1 @@ +/target diff --git a/.stversions/src/.main.rs~20241101-222904.swp b/.stversions/src/.main.rs~20241101-222904.swp new file mode 100644 index 0000000000000000000000000000000000000000..00265d71e378b32ca99d32578c741e51bec1da46 GIT binary patch literal 4096 zcmYc?2=nw+u+%eP00IF92ID@}^!!iE3~$947z#2$f`ma)VsSxIYEe-o0S$Epnfe8p z$@%d)sVT+!#YM^bxrv#1dPT*Atr%4?8UmvsKye80G8h{f8Yn9%DhLaOQf%6&0iz)> d8UmvsFd71*Aut*OqaiRF0;3@?8UjNr1OSaP7ViK6 literal 0 HcmV?d00001 diff --git a/.stversions/src/.main.rs~20241101-225559.swp b/.stversions/src/.main.rs~20241101-225559.swp new file mode 100644 index 0000000000000000000000000000000000000000..3d390e5cddcdcdf4a046d93ac09312261628c80d GIT binary patch literal 16384 zcmeHNO>87b6)s4~CM4k}2t*K(%3g(x!+1R7_3qleyH?D2lAXkVvAuXDvugG9)Xdbn zr@Obhd&kCE#0A0yAwh^Bgn$IffeQ#BabW`j4hRVWhaf~i2<5<^f)WG=4kUc9`e)id z>;~X5dbCY_e88Iy%DNb=SEY zuC1t`V4z^2V4z^2V4z^2V4z^2V4z^&jm1Fd-^^Zv%5MfP18++38mO{u8#n`u0x#Xk*!O_10GEI| z@E+iWI~e;R@HB7{cn~-S+ygM+*S9nFE#RBLGk^(91IK~)0e1thyp^$+0S|ZtI0C$Q z8)IJwz6N{-XaF^!3LFJ)2mW#^`UCtC_zCa~@D%Vxzya#Ot4A371Mp+uM}QABfO+74 z;N8I0TNwKe@MYjjz*E4-fl=Ug;5Oh^;2&>c>=(dyfeSz#xDVjKEx=1RGxj|2ec)N( z(?AIHfi2)7umX$&cLDDNet#2V&jD9~PXUhuE5I@^3A`8h9mesuz*m8f0Gq&r0F7@I zxZ#WEF27P=UTxIMEKLi1Yv7u;r$nuUz9v@?TA{Rg<9wrjZhd*RX7DwgQWuVMy0I1UKa)!~3|9n(VF9Q`MOhNd@*+R(4pJ)Do(q)hnsU zHT##su%uswDsxpC$c`Kvt3>WVS$@9Vi3uM0Z7UQ!>o6i_!Uj$hla7X(KMT@kD9egAVSku(o-_r zDXkJ=fXM$B6ot5YT*WrI{#$ zpI4c_P}1)SWjO|45bc%h6@7vg$gZ0+6*Am-bYRakQOK;vD%QY~;c(DhSYl`eTP2!= zv7nI|6*3KDRhK?}Up(Q<+};^X9W#t25zZ@lS*SE;c6N-9&+?J~VlUK4W-o_mlocih zQbm%s_N%ZQg?!r9sKu*&q+6y1_pXgybqld+@y8Xqs7HXf~GC&F1`2 zfPSVoJ~eZ^b7FGlS zx9z~BD0}u4Ye~0lW)2kI4?T_SkT=z(fM@2Nx#F|Uht5_!cUYn{#v(VcI)dC==6y@C zOUvqoRf=smX_woI)&vVQ+NW4DsB;So%k?>|aPhEGo?*<<&B6kgEmCAD`|`3S>*!|J z@sMq52`Yvrlj<|&v5x#NODLR;;|+Mah|r9I=Sdy=cpt`fpb6r~7?PbXSg@UJ0MfKf zceK-!-5wU3WR;3r@?12QENl!iRtGQKYXw4GmTs2^USAA)RtQhn_jhj`PDm0=%kVav8?^U&j18?{Ve_e48_HE z;5Mm6%y=(yLv(D_?Zyj>?MUImJMN|ri`abBbs}7(zGKVec4QZY^=n`peQ|{yo&ulL)?rd;pLT7bjg86cE0LE< zYSmktQ1%7tz<-q~r88o6T9}Gk7#b5RnApb1Sp!HPaIt%f(sqd!SYvmkX9WT)hUZwc z8!KbqBNGZ!?@z+mi$FM=CyArdE6-ul*~$!4+D*4Y+Us3g`k;MDcGSC4^z!Bg%=zA5QIKdYH(*N^7aTN>{3=|9$3=|9$3=|9$3=|B!@fb)# zKkUW)z0xuwFK`AJPJj6J=`hN@m`IVel z9L}I{2ZCP|s5BuSP3t)8ns7P@ppg~`-nbUxxQOS-&~hklph7;7VUI_y?0BlrZNwNN z#BN-UkcU(hTR^0e$-eL8I*~@34r8zcN6qL6lUtWnzRY zcvc9_;|T4FHa^(Ir7GT5{-YI@Og`aIX^OIeYW)d^QC@o4ky02Z9o^9^pW(`m>!#{h zDo6Ld9i)^*rA>LE(t@F(B#ozjjSY%~(kmz2-7Adqe=3yTbHWs^&eg2;3d82Oq*!LP zQc0ws1ioEhO!w59u(4c6ApG1K?|Ssj1(r^{#5PLbx6<}yhaaG3g!IT)yRXU}T=_cm zXE__%C!zj|61E`o%?6rUb3Bi1TenOl^rw}0s487b74DFb1vU$ah=?EpWm^kl7>{SXUa#HWH8Eo+I~)J4$BS1oR;#C{W~SCX z-M!V_9#0%2aR5#T2@(WR6b>Ljkq`*3fBh;u$AFV92nL<&+^>$5Lc>Hv8|L=Ed6ZdPHCn)jOmo$B^ z#-;4uqQJ*JG42V=HEdq@$9-vyw@u*`q<`xsGKq|VjDg!>APgE~)2FVSnJ5)UhKJa_ z@3?Y1wPj^91~LXR1~LXR1~LXR1~LXR1~LX-hYSSXUD_+q`whS@a7T<+a0Pho#P9#X z`yQVE62Jc&?*%+tC_rVhD`OyIAY&k7AY&k7AY&k7AY&k7AY&k7AY&k7;QyBav#x3D zP;ZU`0Qmk-F@S%(P1BwS{tWyOcnbJ5-~v~HOTazAuivU^Ujse~)PW1YFz~lin)W^5 z%fNM@4BQ7idyl657Nz2fRn%rZ$f{7KLI}lo(7%-z65lEHQ*%h;tBK%_!V#f ztOJ*UbHFfg<8Dp+5%3J~4dCm*XMl0wUBG?7JApfZKfF=Xega$r)__T147eBg*Ik17l2>71aaSY3qyp+>=seu^?i!EMVEMM7JT`%dZs#40_ zw$Cr_aVrd5Ima&aCF{DwJGs%>!8+Puz*L(?bz{E#h^oojIX_vPnTjj0cG9xF!lV|L z1*Kk2hK|v`9t1h{%4MF(LPs>j=x8ByI@0v|+Z`Wgp;tEp&Vm+a>l2s_px~XavPJo}(gkGTQ)y;}gS-)(Q3O)}E zPYPFxpqpEZA9Q_fA}K|!Ni^eowg?Q#eK(XA_lI&`Odkn&5a8{a>HA$ndb+OESl|sa zw9#5UddQ6?bqc594&{cI@mn4ATXM5LG>kmc?>ZK1IE;S8lQZIeV-O;KM?t{IR?TT1oS~^=;o;S)Di@glX-Z4Dk=xnXBVoc2RGU~$Z%b4tCc*yYE zRA9Bg9=}ieY@c*T_GO%jGROpUwu%4i35Vzp!_}v9uxA|By3hFAt}lAkmipQ|)SuF1 zp24bumhc(=u7!TUOjZxu?Jh&7^UBa1?s{U6+bs0C&s@jus$^<{gp^vOns8v(7zk)y zU8}^(;I~$W$EEOET$;AdmUw+FeMO&O1)}NnnF<+hG&-4lFS+ z{hb_5!bnh0jS88%zOG82|A4Qicb-|@&g25-T{ zO%YwUS|*6Jb-kQC#QrRzhLtbQudFOC_4qr!*!c}L`Ig-}UpG6;VZitDG@R7{qnl?_ zlW=+n$h!_~XywYXv9!2Q)nPN@QH}{;Di&ws%#GExN?B*`_XBbj!!3ATcR0x`FI5&- zHjK(bPk?^2H#RwQwlOs^b8f6QGc_?bIWaXgHe(fwW9KGIXQoYl)+{wjX(gMLhbqR} ze6_l{y53h&>9pH;u-+EVVZoKf4amWNry3z&JAjWNE@@1Z`zx{wbF7lM#fzp>w_!z; zJvXb>gi|+Cp9zPD&V^RMD)PF|Qj1Q#@5Rc)7YnY_E0G)3LdQ27oIG0UaC4CtOY4Op z@;&%vhgp)A0Tawwvsyfk^Gi#s<#{Y>(MVFBuFuoW#G00_OJp1adFd)OvemR*WE)C? zilNE4`c!!=9skR!3IAfd9hR&cG)drW5??;nhCLl=g80#UWG8DB%q1OwBrVk)?X*O* zh4m#~n4*?k2aUyRnFblNffwf1d@gr|(`3He=AD)q!1=YkF01jDxr-H!xsAilkEU0i zRal){qR#6qa2b|ECF5HyUJqdn3`W6R+2R;|B9DYfr&J|PgAZl7rP{V6i zpGtQ8iRsdWp%!WsF1xlXS7n_xIh!gA{UEu>z6=X~0Go|;Hx_5MO&O?P?82pkhbUEo zXuLbkGPZY763m*{F))Th<{P=N4a-Ac9Bu`(;FH=qP73YQMlGfxfaL!vcR-(PE)KF1>A+9qwl^4RK# zn8L*2lQ6a)5KiYw?5NaYbDVU#7{iqIliiQJ*EbWh5@}@cYO;cbhMrUXMR{fZ7yKU zG-2YdrF*o2#+0Zst7Pi!ljX!yFMTAF=L1E18`k~A(ZTs7eyo=i?{?$T%CG492P2K6 z4TIt&+GzA8>-r^EIICVDuwF;$=(Po3tm_M$?gNRbhfp04;cQSck{R5>>H1&_+}X&` zmg5@H?mK$+gZj2c=l>fxLw^BhYdZfIaQh|B_H^F=6tE9m1}1;55a0z%2(1B^-9B?=APn`Q-1fHYwKk!@NH^4K%<3Jba z0J}f~$N?vSA3)#l0~9CtB0%~-0c2OkK*m7EK*m7EK*m7EK*m7E!0V5JI6AJP3>0Zd zqI_(hA)f2&%#@NH^ggOkR3Aw?+k)h58vWZw(1KD>40#A^Hr4+c{kqfk*E8DJvWB3BEg*X64hb5d? zp^Gr>#p|Ppm__`dpW$>AgF(lWN7c2w)@z`@?tx0W)*KcJew&ginwAr)8FWkc|1OzS`EHcnm0F@ F{{ntmNvHq- literal 0 HcmV?d00001 diff --git a/.stversions/src/.main.rs~20241101-231156.swp b/.stversions/src/.main.rs~20241101-231156.swp new file mode 100644 index 0000000000000000000000000000000000000000..1bf5bbcda4fd298e0a1f8f0e50d22f9570bc5a33 GIT binary patch literal 16384 zcmeHNO>87b6>dn#5;h5eAf$~HQMN@Gug2pU+v~O4yOzw@$xhaP*j~KItXe(YH8U0W zboW+w@7Q?F0SN)8Z~!4tlt_f12qEAE93lk)n}9$<+>j8b1ce~}rR4-!zE}M-?H{s1 zxq#@_d^6ouuU@@+_3G8D*VC+ixUs~hD^(5G{hIdt=icA;{&t7<@+nR81r^@@l4jsH zxsD4z`fe|SgkrVIKUow_oX+@ ztzb}SpwK{}fkFd?1_})n8YnbSXrR!*|CRsd0-Iuxru`6j5_lYV1Q-V%0yN-PCpGOmz_)>?00Wo;&HxVrr+~k{L(~2Oc)&&A z1n|<`n)Vgo8Q=+E0jL30UOW#H4mY2X3iUBLaoKi-D%0M7#)xCo2_CEz~b=AD}M8{kFY zTfpam>p%nqfCMfBXMhKRQ@|^CXxacw~ zzNwNmEO$k2V755hV-I(uke#+X$#vZirLLQ{Ee)?_pnRZIUs+qI8S5K!^^3!7zpUjr zFu$T|`-OpT_FRKl)k=^nxq{FPg~b*wFVrt>tgO{^wysL*+;Pq=>~JdzJvqzH=ZbaR z<-OAI%wZlRYEB}>$=W_QQJtPl7+Bj`+kRsbi_3}P@1Xz;{Gw zNFI2Rw0JO3@)PwDfe!-SZJI%_Ye-+$l^FB9WkwEqv*Vl6aLQA-EpMPSxP-syVBC_M z_P`+O%wX5GSj%Pf|DBSNj2n{>>vtFm7}`p^Nj0GBM14im_SGPBCS-R_!|Ld4ere9A zU#RxY$a1y~U${D3Z7dsO)BOrtc)5y+eua;Upi2!_@_d3E^_d)XPv$Dd;|ekXoo(WK zE!82$!*Jz94tI=$QrG%iyIvsrY-_px4veQZQD(5Jup2?UYfJz&J6xKM&K9}7nmuAn zumaI`bEZOu8_y2xnHCC}^>D@PnIh~@x(iDT&0xDki!dhCGowPKuCJ-q=ROcG_%gG0 zdlTcjzRbgUDXwvu*38ZhvyoXg@NeveI?3$i7>TmRq=8hBq_4vwtVE$7g;~dCwko`J zU!((Tka28F^va(tYs&Xk{qeo(-P5jt7Afuj;`0!o5Y_bzOa5_Zh3iO zv9G`5o1NQGi*L!Z`I^~VibB3qrs-S{F}r0pIRU2!U+=lFq2)_U#^S>Kx(=HWALCf? zwQ6-HsoYpuZPa!4a1fHK80^6FdV^_YeX+5yykRuv`vmlNy^)FOGp)(7>9Zrv>B+H? ziLuGak!h=19XUHu8=o@y8MD@^WlT03k2H+cx%KtUm9-or%M9_Ujg8~`Ano$P21qaV z`-~~HeK;CYC{2v=fmL>XmNin}c)@gS2c|^Tvol&#xVDiwPIx_xFtS3{kXHkkS7O$g z3%<~J^g_jR`z=btTCx$stz}-f6uYvFFRYR8z&X3jlC&3?P@grUC6hU~xVTcE!_F2@ zC)Mft9Q~Ns*s{%uEaXsKwp9&nwH*)DhN7Tm=rUoS36K5bKiN9rWE`)@(#?Zb3Or8g z&_}v3sv}*He)PVw(~Sx?lTAQcmdTEOI-=ddCX?(=aZjF$&XNsH1C80j1M`{zm)C^b zW`Wn`y^a~e19tsg*5n=Y8n!v+wGMhfT4QC_U^cge&21KX411!Y39JscBUl52Q7}(- zIA))eM?@r3CK3Xy0I?1>x-Ry}5FR&RMw$7hD=ciUUK4YQl6~yNqoaP*WZ|xl?f%gZ zPGRWyITuaA*icQD8HK_LbX~e_qs`$isYk4IFLFZ+Y|U-QdxGT%?!qhXr8i3mI_jzr zu2KCWRB~IQjmGM+uAZ>nkgyUPgNjqY;f;$=EqlS(RBg;qyEPiuJ;#$P(xymfQ&nLc zL>JqaVZj+7;>bd>q_S(uQ2k>UejR#kKE~p5 zE1ZEosjuUx&_6|Mp@o3tjkU;AC0tcV6N)Z^*A0BjOej?mi_^qX+{V;UM9X(Db7l|J zM_lW{rW7X8#;WhFcFcfdr|=w;qOdaJ9g(Oo_4-7N7zD)GI!PRr+HsDf&UR#&(q0+@ zDX;hX)JLW4B8Y>(rucnN#bj{=4mnA|$8gKmWnSKeoe;7nOZfE~9TlJ*4}Fne<4&2y zTkbI&ESd(4cFhYcn9xx^hn)FA0u^Dvm}$WzL(2lSL!Bv7Wmf6Z+at?KmR@#CCeIHw z?MJP9siTAQN&Q&AC<%F!*2=Hw`srBWIBHNjiKC5Nv93Sn33tU01vc%t9HX}2i*
Z+bCjbj*0}`%eNd;hg_n;4{ESfn{I`p!5F#@EXqhKL&mT>;aSq*aig9 z0nP%af%gIL0qzD)0CxeDC%6H819%qrI`9SHBfu2!XUO^|;1|G80h0X%ptuSR6dEWr zP-vjgK%s#`1BC_(4g8;IAjzq#v;?Il(%c|k>mUmp=**Oo?e|~B(7ON>>1+#>Gg;2? z3bGtjf@Y{gGPJF}oA_q?IkGtaLHQA6%P6C1fION~3E6e-w2*NlDUj`PO(cAg01~0; zP@+MGtS7<_i(Jw2WS3b;N<>KZxC}WGAt@Pwl%^)SzLOh7n!k(3a!BT~MuH^yJ_x@~ z93a8a7E&GPy;OHR7w7hQ-i&Z!j89iM;zAZ-Ity2akz$MWhjB(zanc3@PjA)G%38mJ zJnutHldkHXkMzJXJ;eQxZu)nLrsyZRR00F1ILefj=99CYuD7JuHM+uaL{O^YEtSg4 z<7k&QqhHsr^pKq3CE0EonBfZUC35&UbGzKeo1M5-#oNxm-J+JsyB=yyX*sA`z3b7a zF1@TsO3af=2PrL|XVQv!6FV)Xqu+xAq>@CWEpesNgrT7&&8K>d4T^};D@WWuNQ{I* zN|gTQh$*j~<17yn!{(TvNhodxNWcLEj3El%c}O*Hl9cplld>X}-ocQEl(V==1) U-NDsNMRAH2%b7mI2iEbw0H6s?-2eap literal 0 HcmV?d00001 diff --git a/.stversions/src/.main.rs~20241101-235650.swp b/.stversions/src/.main.rs~20241101-235650.swp new file mode 100644 index 0000000000000000000000000000000000000000..0f6c88afd37c3c578722e65c82d806a645e7bcad GIT binary patch literal 20480 zcmeHNYm6kMG1OW*c-`NGHX4TU(Jv%#|sofoE($nkh?7TcZv%6?*m8z?6 zcbBK@vGtgqVRrdpKqNAfksyG<1c@XhSP=<7cqFoT#t8_dDB_P1;bI-}uvSkwGp{Ndi=zulvK;QKW# za47NaPiRF!n}^}ST`K%o;7kXO?VAJMiKe5_o^~zAtA^3tyC_SP94I;P?QkH8yHg7f z?VO&i&73?j!QT0foo}bLvTn(Nk^?0NN)D79C^=AapyWWwfszCN2RRT2_iDF6_jdwA z;5!uV#cvGktMAuu-@Z-n|ERvdiSH@AcTj=qluyZlk^?0NN)D79C^=AapyWWwfsz9y z2TBf<94I;Pzn246N7L>DdH+tAc=-Rx25{@UH0`UvKLCFSya@ah-~(5I_W^GOzW6px z`*q+afevsUI01b9Ax(Q3_*LLKPzSyT`0KZ7+8+Zi0?z;!fYZPufCjw!pr(Ba_$2Tl zzyub6Gr+rncLD$U7ESvXzy~e?_XA&kv!;Cn_%QH1&;V+{3~&;75P0nYj0gA%@Tb6s zfL{V$0EWON;6C81_iNf;0Dlhb18cx4Fbg~ayaHyu415&$Fz{=@`+-xyJAt3%d`lQ>Sm8;$$m()-$R2Z(n4PlykQ+u2hlXKw zI-%)zO{7og^^L7Y&1`Ki*Dp=7n-#6l0t>1lH&2WLYv`HOR80q6>WN}2c5K$T+Nker zZ*0{J))FaoJ{X*9>~lMb{cwq$FC-g=$A|jl+3_}r)B;7KC42YW+|1&gJV+ zEFM+`d3u<5rh7e(b@9o=3Ja@4r|V2kRugX+T0ybj>1mb(9V_N6?sK-aT^EgVMRXNy z={Hn6H`t*Z^MJ=JErC&`<2F|}u2db*iS=~Mic$3Cg#LO-#FFdd9L_MGUoD( z02z@>6*h|PM1|643Z;i(A>*{nAQ3Rw75v{yd5G~aJOz^DeRGuRTHdPbM^4^stu{OcKS+Vx7cw4;Thr5rXD%KXCT>fF%)+nC}e^L^5?jg#=kdn&UyP zF%h7=wb@j}Ak4}Pc<2Ou9$Eu~t@6%h_K7h;3Y?x-5ET+!IXjSNS|}velT~YIIdMMe z9wafgqFtR9p)6=*LWNAj*b=qRy+*U*jZfByo_$S;rN&>G-X{NfYo*62tJPq;P4ek`GxlDhGY6WGhOE zj#TVvi5Q06x4_7OVbs$XrO%WutY2JSUvI4D{5!tdxoxrd*8C3NvW9C(%=araovj$N zTVeBaPZIIK$2_v1Y0pFIrw_ z08ygsrL$Vw@j7OvIHC10!o-eQGrS%_yp&jHD)?gaiHlX=%WLS9n%W4V)-tWDOI4P& z3u)y0P|hB+L)r^0u+KiLsmWYkUEQcJV`r1oNqL5`Og|Pjwrq1E2^q`FwyKlYdIKNX zrYJ$x(4}fVBOd$5e`o82k{S3zmTn%jQlN2Cg+Ap%sE%|&{4sK7ryCVyCYyjXEu$U% z^qpQGn~d6>WKX__&eVpcp^VkV2lLwz5BD6e$0Fb5!@dQhYxBOfhi#7e-BArl zYplYWtix@m!#gbY8TLd`CbIjylRz36go62DpJVokc~ppK%1FWiE5ca^8=Z?iGKR*D zm|0K-iE?l9|NLATo^5>zO?c zbxA#9rTd8&V_;iePwol!z~LUW;$ix-hC@ePBtkWcUmTUZuG2$h@mUvN*lvi}5F3Ly zr=W#zoqVb}jAj>Vv!>XsQMv99{BR@e&`IZt$ig_NoRlxqhBCm3BRi6*OxFry5wQ!s z4nD}#5RAsR*Q?`$zjHV!1;T8&~U z)v7qs#Ey%@>o)#}8BroblGDag+{M(;iI(qU=Byz~A5pENRq2>S8>?}+*|#E&ox&ej zbPB8ByyGYqrdpp0;|zkr**Z~*O6)ktmCkl#h|*zt1QJ^B&ZUo1>EaLveNE^0LvbdP zCveb73_hl{Y+V-VZS2RORW0E=RXR#QPY!*R@9{x}$u0Mo7HpaZjCRY9Y>3d&cE*(X zXaRA;fH2d7QA5iPYGa*=qC%|FrFTe@qn2LwN+Qq4s^+KG!&K2h`J{R*Pf|x-Ra@v4 z!#E`=mZt{66M5PwBpb&2e8<}eVh5YHOvk8g=wicI;q*I}m}v+x@C3>RHD#Ica~y7N zV<$VPvy&6oJl~W@aQPm^ou@Ur|KG+P`t!J3)BS%6Kfi~&{qF*w0G;Suf1N4E%fm6Vvz;^?02JQ#$1ISMB z3h)x}ao}UXF91IRECBxuTK@_70`O-5(f&E0d`b?K94I+Za-ifu$$^psB?n3l{6FD< zvZ)KR1eqpM+aTOJumVR0v%-+w%v~|$3qX>=uA$`F%x3sBtQ?erYRH2zv?uGmNA+u8WE!GeIUL?Er5cP*I?>E?|OaUd$_l+ z@OFY5qr6?=iVIpCYtXnp2@{*-AI6z1NTUq~p1z8qRkXZ=V%uYls;=sO0Q0~xJ;;8D zH~E|56#b}27%*^)BTJdSqV9Tz(G7jqbe+M#iS!wGsf1k~SG%+t{dz%l2;&6T!(Q9O z3|H|U!iJAKx5qp1?8sVGf4At}qMFLrL$%2)2UZJTk4AavV<%yVc@pLz!R7NTv}N1W zoR*fO-_Ze5iX!Qn^K`WhK|@WNPw^=QiVCGqp>S`sFboE1q4c*S2HC^6D^iA{Y{RnW4izU z5R~Kt@DJ1dpJM)B!~Om@faie<@Cfh@;4Q!d!2Q72amW8H;OBu40vo`+z&*g%aEJdi z@P6PaU>VSX?*-ljd;@p*zX$#j_#@ymz^8!&;39AfcllR<&jKF}8FYqWpF#vxByb8Pw{0i_Q@GP(hJOq4${QJOXfKLD)1wI1o0SWL7unjx_+=8$F zYrq$P-v(l!3p@o}26liS0R98M{#Sud0WSeR4Lk>QfeXM70^bL`4xj%wfiD7|1bzcD#d^7%gs{66OI1z-s50h_=n zfadi+;4Y64WNBimy4JoKz+ebBpK>70kpqc}K%uKbhz&rYC>r`(NR6so0N&I7nRXq!0ClG?mstSw)a-4*&$c5gN<&BoDw!{a8LRztQ5Js zf^lD;oxwAi`+W<2rRGLOwImS(gW>-&4ffl@&Y!6ZGV?+&gWp!#OB8>cS+XC3#uVtX6PACaNce3r&AW{-^utMw8c1tM&#+24LR!(*FD(0#ICw?n0CWm4nLNVd+QNcz#Z$w@ zMpCyzGuWG3MqN3|+Tv`5)#exQk6tDbTM_%ZniwLAx&fz-YpDfGG>`MPa>WX>u_>ny zRWnnu>`^!@D(o>TgZG##_EKkAnvvHM2Ig}$5H~XmuRr~#D3z>LkuRH0Wln7t$gy`R zJRk|gZcH3OkM?ereDca|WF`nzNlyb*BHhYSgtl_p^h70chrz>@1l4MlwjQlec-8U7 zJIZ(P;(;VRft-Qx7dsvHodIVPk~$|c2WU`GhV81*57C_xMMaLMX60gilZNfVaakDp zohgE!`;HXJ)pZw|sAt6>^12*h)j~wmf$P}J4*4&hQ z&1ucwsKwDQN>S7G3>*TiYk`X$c}AuNlj!{6%@Y-dby=0)O4g_%Jy?}GGAGG7E*UM; z8Qa*^O_AJ&O4!Zy)MzO=g<4vPeyc=@E*$yFIbUG~Z-o@p5kG&`ip=&w%p)8yrG}t( zT=@L^q^;u6A83?PJiW*mD>&*$g0kSUk#QC>o&(JtRl*VHoqRZ3jn>A_R=v@ZF;y*E z9hpN&EvkZ+mO_wMrw%)ul)g-(HH_c^G=g|A5yt`^W>Fo&qeK@TF&vr@1hi!ojCkco z2|0IW=?;-?kg_JONRTU`Y2|9obrB61M8MHp^$>qxCi8RY=^ZtnkiKVlis6*?U3kTL zN7kL5PJ<|FT$m=|yy(;Qv33&W}m$jeuDh>NL+A1o%R$iY0xj`DZ7YO79o zy%0G_DUn-95J9q(YWuG1d$KO^5)8qxrJq-hQcy@)bVoxKnJx3eDiRQ>2%j@nD5x=wZq5Pc6^ zGzBm8An9NRVNwE=igTvPGYT;7|HYpRvf_ z<5wYebkmN*fsDsc`x_ZQ3gMa9>Bf4bF48cBnwEB${t}n7Tren&4y(yUmqsXzF1WO^ M>*Ka{g94TR3k&?Wpr)zdRQJ3F4KogHcD>Gd|Am#1fT4Qs1ZU46T| zJYA2i$Mg)d%MT|}AdZ9}31BerBPLEPB%B|4fDj4^#NkJRQ2v-llpjcdO~^~K5+dQv zckaFQ=$`Q|34ao$O7l%u-RHUIo_p@S=bZNR#pW7Ys7`Bm?$$K#L%-5H{I^@Q_qO{)-9HWt zfge-26~7U%ufAWoe*HSV|D*c;0lp{k-a!SbQ#=I+3Jw$;C^%4Xpx{8kfr0}C2MP`p z94I(YaG>D8|6UH59ZkCfJ2mY&;8VcI z00Wo@&H%p%`~vW=uhX=D0X*PA;CA4fuhq1tf!_n(3p9WlFb$jp?gU=KAio2A8~AJB zW59=jCxHWC4Y&jN?(LfPx4_>3?*ul0bzl~_7kK_QP5T`14DboyY2Y`33h*<)8-RO& zS6-uOUjsf5^nf*>2Al%!2L2remVW@g1Uv_P5O^0b1Y#fp-T~YXya{+C@Zv3?9e5si zKkz=_9l+axMc@SRZ5VRC1w0FM0S;^eX91eymxZy0jXbd%nqAHg*?mqNu~U{8a9#JK zK-bMqCosIOf%J(|ePgRpGg{lr^#{xBT1CsZ;a5d&mKgfx&^4&3T1iWbhLIWB7Hd4* zsPAlVY}Itu5-D{)7%VpSxfMrVu*A;gl6BqX!&3R|cpF4&jv~>LwYxYwy)dU5uy)hB zS!E)N2US5{DTrOexe`Ss@yUY<3#voAYnRK_*c}F@pYL~Sip74%j5v$>oNaB_MWb91 zT}4}^tE!!=>`;z*z$2EFz^IaO8><_asR{XfH#-w2kY|PmuEn}8qX?dop~j6#ko+x! z0Vgjf-6R~)bt?U+rd<<*%!yFPF%7G)vz4`FqkdsJ6C-P|YxuUSv(4tZF|&|mbZjq| zF`H%h$Os*(u#s;k%#}WqD?JQy8K-3iiGa>7;r~{`LyU*v$&noG8>3X$vQ`~0w6kVw zx&B7RQ=6?YNLAFgLx#V{;xJ++>%@+8z%b~F5Hy#2zP--}EDm|dJa=#)lBo+SB*-Gt zY!`Bki2&uT&88v-fmVjk1KaQOz#Qmom3KDNPmBptVE5ddsF2{w*?~OMLLsp(SIwbm zN77P;Un%sLp(p4Rns9<2oS9uJb7rKK{PTw)Wi zA}`cQDlf-SlvXAVB!sBG#!1+SBR`JPjw@`__FBH3B;cDQhT%_1;nGqi8?tCr4*FQh zR+JJQiP+N;(RHhDf{_DVuO}}`pDA5fzp%W%-dN4}cYL+QZL#>)ybj+ohih@f_bW7= ztq8MQVRN%kdYWc=E@WtZXU$k`th98p)yd1f zfro5El%Q(pQZ=6vkKNEIRP9*+1~sc?=yti}5^!4cR1_abyodT@T!z z(c@5-)Falp7rPM#w&nKZj$jRJ?m{OXCNFC^a@0j46r=dXLCNjfJyaH-b@7GGhKLQY zEr?SJTKM6~r<%iXX1+FKh`kz>>)yZ%Hi8bFb1sQ2jDyNa`7$i10-QI}1DVQn%pejG zyU^<3gG>#;Xnec9I!<^p31ZDVm>9z)@s(O^+ww6Mms`W7_>7}nE@_ejnc4j?H6djaLqQ|l%b>LOC zgpov1QA2jM{=O*7X%mzhjB1h7bdfp=?l7mKi_A z;odfOu7f%|IdR$b40!;T?_tz=RHOUTdNjkfXl4nzk;iIr}PztIc52nzb_}|8V zb}d&-+CRuX0&5xBGYu4v=Y=`8#Rpwj+=vQTdt4LdUKoJv$Q+QNA&A(}j`}Qi?XDL% z%z{xOhOx(Gu#wmS86#lO)NIEeI2Wk zy?uqZW84_!?Fv_1&|;f|#+5QmY?6N%XFM;BHW+yFDu!0kvJUcXk2R{gs(U`n1IP3r z`yt+BZ;Dg&qaIsN;`PViFD zYa5v1D&7NF@NwsMc?W(SS*z;p=6zdKQ+ax*HkswXYT@b8C@*=eI0!IL!W<;He2xW{ zY@3?Xl5+GrIzUQMBwe;2t+pX(s7doFKBYiWq2$RG?u{0P!5}G={N@UiRXf*mFj^Qg z$80iwPFJg{6o`O}19Ljks^Z2*9rp5_7VCNBnt`MfU!;tZ8!YMH?y@7SnIJmy&5kvB zfG6LE@ZMRVUU>wn0k=4bZ#vP`+raZkwnfiWLwI21RAVs<1KsFqrX*>i#d4;<^2j=- z`~NRPN!|hfFx~$t=Km$!@Ba{ZFHiyA4BP{}4!8rj9rzyZ_7Vren1 zeEz2Zn%CQan><30rHQHPnEQqggCU%J%6&9T?jtG!g{}%AG603baOee{QsV4UD`K=1 z?6ZMuI=r;gYHY19uQd`yp<3GI+=sh^wuy5ruFdKC+VaD)yIfz&PHuNfsgFwamMcV* zhjBZ!0=vzPD_#f>e`-9JL(=uVGceJ{Z79*c8m6v46oY{1V58e2r-TI?+(TXqD@Cp| z)e$iuXWTE%OyenMUf*0_iMdfxElI?{VEDhtgAkTGd_1(hy{BBm$x4<5NeATo0CM$e z)cw>Hl|(5rsiPH?JdaIb+uJ3Z^CB#o*!;NZEXg`qK{W)-3_WAf4N0nF`Ub)dN>`1m zY>S(r=hlTi5?)(O^KK&uyr8U614#|(7gi}pNXy&$g=Jq72cM`MK#9Uj%13xyo1a&X zcxpJ`Na9v#`g^m>s4GWVTbQY^+T1+;(MuVT6_Hm`6GKE%H{jH9Ewx~Y=26yGrdVM% zHsti7YHBK$Jqm|Kh22MG@E&o+Ug|7O)3aK_z`R%kanm#K`O|-jQAt}B`LgLm=G10^ z9BY??17ct7#>5fyXzf>MvOp}(O$B zS8aE^qihGyA4rlD$QcNKzSCjf9&k1xsk1|4fCdF+*sgN@5Zwt;ROEPSRxZ>xY1l3t zm${+em?C(&Z%C0`T{oeLdRFWqsv+r@K(XK5VW|^Gh zlF>4qv5no5A(Gor3A>q|8Z9NKP)jP&Z~-5{()SEcF`TlV z1Ftym$huQgNf3op*gdGKp?Lr@;d3t~S`?Bj*_0{Lxdl?BifgiN#|;f~*<<_C*+q`X z1`)?1nI?;no~6bZwOqFo{d|*tX37;dGn@9Mb`d!D#@Sw1K5`qa3$<%orNr+l<^>$b zbjV^CXVq!|X*;Qp8;esNVAQ1wYcGrt)JD+;w7jL1$~{(+ko4N=Tj;m+>XC9J-+6r~ zzD3NQH$-HXW4dOKJGecSRDjYZrLU#Q6xxGmK1ytvdt5FfNNc)y)VmaOYv821P7r|7*=(@r1}LOP=`bpc^&`rTtR2;@nD5x^{XB z5Pc6EGzBm8AntIf&k_1rtxl;`HYKKIvTFy<&1F{>Bf4bE|M^WnwE5!{1TVbTren!4y(yUmqaLx QF1Vz!d%C*Brb}ZQj*n@Y&Cj%M+`LzN^D#~H1l_#zAx+<_ zb18T3P~bUFjC#Uy4VyRoQD0i49aA_Z>EF4DJV&m9Tmv6i11Gezyfm{Q*267GL8pt(}Yv8|E17<_hh9KU9 z6ac{ce~JP8Z^fs^3f2Q=;P;Md?+pbAcdKOEJx zAA#q<9uVMRa11;E-v5ZE{RzAdUIVX!Z-Q+Q03SRBPJ`dyuW2{HOW<2z6+8mYfaBo3 z`_M1&CGc6W2d;xUI0D}Ju%`V5ybXQ>UIt$Qp8%)9QE(4<>xibk0bT*$0ABzucpB8e z5?BP|;3RnWUi2Nj3ce0r1kVBi#z7f81pbL}{0aCmcpl7xi=Y5#j8B0(94vEqrAlpe zpq?Ay-ZVoStu>;P!f;ZjaGLHw zVQ>k5z(KzyHyZui2$&Zt}(>m^wBmf;CUXDiiZV|==o&=77WVWOAdA;IrZfm*g6Ka=}hCU;k6 z63#{mWCA+d!1rpRL-dE?$cP+l8~svejKAahqE~G>(_UYH$`eHfs|wn}XZSoD`T;Xp zBkXi`7&={4hUReB6WiQoq0fEhI`)pbO-)cHB^HS$9N0Am0+O$-RAXWA2P(tkQh04H zO5Cbvv#@@XLWs<2lG-~<1$H^ogHQ)vuxns*b6n1+RGslrG-fYi6C)X`$edQ zffoj8!$r0t+%-=mH*ifB!|;i%aCWxX>$0je>hz(O9ZO1bBxX-jMAxmh2}RnvUP(@3 ze-`_~%B8vG<%Pwb{thp8VO>qWCAYy>&F)ed@a-ZE=URZ#Ewaf8I6Y0XTn9F^e0j-O zT$o?eVKd@09234gHZ~I{uGdzo6`h^&19BCEZFpXHFiET|Ru`7njp}?a1N{+iWMcYU zb8>w8{78Lza(rZBd~$MR+8P@hIX_W8J7w~7X1Q5TE7_<%UNu(c*48#^tC@-{Gr*@j zeiq;Td6yT~krggH5h}`|?ZMHILTO->4;*6`XIVAzjh9TPVZ)Rtd3Hvt3#VbEjuT!F zJq)dYRpnJ5<`tWD>Vhv-pSVEc8dvM()Ns)jaOwu@v#<)C6{GOj)q9_z<{ zvUI}9*lw34iwDgVc$~zck91&E2bv)L=sjg83l(f89f0IsDm&U~i&h(pOuRZpEx8UF zix)HvdCVp*m|OR`ye6C$^W6^bw#@(@u;cBpI&YiTu*@;H+3x{qjulyzHMk`jyukvO zVNFzde5=hHA*_MHD3~kT9HURlqf8`IDw3wbk+NMZbRDdb0X(kHj3V<)M_5>1-8#k; zclNLnkB)j_odr7{mis3jn?l#|b0M69u_2i(GYo|7>$-GWMvKE;Qj3`BZs-K)*s9Zt z78J`C+<{lzO-`1u>8PliaEAXKAOY>y0t;J`yDO2+bvJFtdDO-y5FL(o@GYa> z^!w~OChQgl(7^h}w=uqE7r75O;QpetnWE)a-(6{&KF50E+9vJQir5v2SkXy4lo>k} zWKL&O?9SA>bXe|m-G;&KCL1N?8*iWcpp+eK_2AVdRIsadZqYs+a?&6iQd>HEv+^!% z2aq+M=(j6$P=Hp{^+kS-+eH>F%!kxq(J-L5tFCXs&<@tK&*k@LQ2P*=JWZ>(Yw31w zUt=n(GQDJi?vi!I6Er=giNW_3?d@%M6L$y~m3X_}t$1S`msZ|K*B^}}j`kKxC()iH zb6eM+bcIv%0)eGHx{qF4@YuRO&*^vH&D4*mj)!nbC>iD1zuVLG{deH>h6Xkr*N8Ui z(YYTqu4wf9e+$phzrgb~J^zp3=LdMUe+7IGJP#~T2eW{l`;UP?;yM5O;05q$und*} zJ^v4Yn|R)T9ozuBfZ_mKK!7$l4;}>%gNMLTa6h;YP@Ldr;Je^E;M?HK;8S1<{0*}H z6}$m{2}t&zgZ#)fkZT~&%pr z?e$*R&>I8X(%B|*&ZLpWD~N^A9TY6@DxY!w8DJ+ ztf%Wu>2{2cux;TN#_%pnMep%wmlUI4$18OaoZto7sv8*L63!(e{dnefcmwZ$qEaPy zEBg+NO2%)5s5AxdplbC-NTammutF&@PAX`mw0x0CE2^8S(~^Aj+uuRD6N|Jdu9WI9 zG?b+ARF{!KQKsa`WN!6mM!+D+l>BBgQ}jDiv+U0dn`44Pq+_L0oC`wWRS3qkuhv)_ zwF=_um)BU!rB^txbka*?qx8BgXY-%|S_Wi<9}L n15K^ju1mJ9TBZ`}O-(e^n9M4S*FT%-Russ^bf(X6-#q>o-mXY2 literal 0 HcmV?d00001 diff --git a/.stversions/src/.main.rs~20241106-001754.swp b/.stversions/src/.main.rs~20241106-001754.swp new file mode 100644 index 0000000000000000000000000000000000000000..e76b086fb40999dfa57453878ef0e15de5ed11e7 GIT binary patch literal 16384 zcmeI2U2Ggz6~_k}T1cCwDiXYvYey|>v|f81H?=2;ky*RRrv8fUkb()L+1b0hH<_K8 z+_{ro*L8(d5CWbm;H8a9%L5V*yi^DTDqj@>slY>p!~=bTst6t^;-e~5YRmuJ`PlJC zlb}inq#5mRcV_O_x#ymH?wvE+82@;Ekxf^|4IFnE#+QC`Py6EQHyMxFh7kz5dHqA$ zVbI`GZeFLrBY_wTgyUN-Z-!%`bjG^2@G3IAeiKEGLIZ^c-mwM_8Am6o<41;v*!%B1 z_l|2TDkwBiXrRzQp@BjJg$4=@6dEWrP-x))O9Lu6V7vv9-wk@;T?udDxQz3Da{VT* zui*So$@NY6od6aJP?_Q=G*D=u&_JPqLIZ^c3Jnw*C^S%LpwK{}fkFd?2L5+7U^flp zE{JzK1px5=pJD*79>#M%_#^l+xC%ZGeDEMR18xH^+-?}(0G|U*a1soIKOZuTXTjIN zWl#ej059HV7|($xK_A=?7&ru8yVWp$3!VXA1YZDsFb(bmhrvG%8phwjFTnGl4(FR6 zTfpCLHjKx?W8fjs2R(2By!9UF4EzDS3|;`=0$%|pVBi3Fv%HS|~7327Q@I9~%roj|A0%(j6fww- zZJHkMl}3*5uA>_%rrWgE)@N%E=$f2MCnm?Ik0lj2m$I^h!Xy@#6)j##MxNEZtW-%~ za#?1w(i1H)GE#}Wp0tB}yJKT43Yxa!Om#S0U9ag%c_MTLb(J1Y>Uo%L#eH_UVrdTa zD($zmuzbEEJfTWypA$*ZORGE=3AwTYDSRo^W@#lkG0l~U zv=p@_(M%Y)LRpfBek2_p4wZsLeUu?U2HtJhVYq3@z%;cObG&6oE?R5GC#8|JPT{ru zq0;ao{(FOdOKvxZhLL86o1Vj39-}|yl&qxR7=&2ABT&GRjkK9m1Exv2FTmDxC$|UI z((Kxn(=plH;;dCWJw8Zq+)Gv@|m#m5DK|)jbxrE6-ooIoaEvh(;}O?8O=FdR9NRo~hvb-O%yWgk&4~3g&GXH2TN3Tc&S8_FwSUZMukMv zT-Bvdd^n!)W#(-5CXbrt5?6ClT;VcJnVA`3qcd#iZR~{_$?RnxiL%0^fmD#Bt=%Fl zM=FR^)^M4v2!AaQ=?z?y#V~vlE1a1r54x-?jXS-sWhau79I4sU6fsSwV?&XyY1Yz{ z#GfU;uy%TOX=#38puhc#omkhCZ_#h^RlBzsDc&#BaIPtgZkZjMgwrz&$M;}EOXn7? zh55NP6E-6r;+XK&@$utH;`;JRy=Jm|LPf4(xC77Y52uN>h5G!`x>cVWWT5}xjVd0h z(aGt1TgN7*?;CAQADbARoH%xDblMpoAH8p~dUVR>_uAD~HLK}-{rq9pqN3puR9KyyD+s^#(>H4YD72g|_D2*7A7uqdOzAbaTrP!Zk^}-~1A0FCc zj-&;_h7O(MMlzza3k%D&SuAhym{OW)&eD&K)h%10$V_&nW$V?EjkfC}+0r?v7@AC~ z&xFS!@}I1q@HDR9W9bS)(*Wn!Ag@X zQE^MYhsKgs%|ITzg$w34LN2ceugyZg%X=ML!3}nUP1fKY`wG@N=C^jbL7He~R%cD_ zh$e3`BSzvp@ z!5Zr~Fs8V(jpcZ3EQlIRZ3bBLPkn3(UB}OfXbQ%LWU|ai2{$xN>9wskhtH%IG2Q*h zQ|Q>L*N#^e#}(XzYurjt7P0fF>6`G6`WO2puO-?jtS@W&3TqAtE3rE0%?fI`aq+2S zFPxaFPFQ-$M&X+8`f^z|X}fb?C!rsdFSaksfk(i$BiolHiCtSN{a;)-cjzI$E1_sy zd+i!FddHzFst1P6n;06)BLj}z*}4;;Hy(G?wQ@lM+O#PL`;r?gkF1Ma6e*+OUBVWk+}Yv{gWF1XOWHSHJNI5GyV&o+wM(dAOK;!e%{t_yLD;9Z zZ1(2mo$o8inoRU-71}F6JMQ`dzrx)zi&y7;YH(;6(A!l%bYN(E>)GY-vxS`2xDcSbmRSL!Oam!>I$ay@A`CUMOg6^Og(h$sS>+c4>*>)~l z9J-*;1_E6ak+hIKn$iK)HSV?$EF&opsPSwBY!SN=%62J6AQkHg)nSn*TE6Tu2l0dm zaT<>yydfmT5fG9zL^p7Aok&A(abI@HTsBCMBvc3C*Qf?aaAX5f3bbCTxxR;o={eqr z@E8|A^5D4>vIyIqzdVBISgb$vGn$IyE$Dc9s=HP;1`Xuv-c@PRRLu|Y%)UNu zgey3gh{5AQ*yBySnTbnP{7d<_Q&ci}S3{*Kq6Ss#cQpp3rH2zqiE+}A9IfS(OgeGh zRGpFLqu-q!q&ta78{$Hx0YgJc8c%&08x&tRBM7+MONCrXt6!286r9I)$Xct z6Gy%d{r=5?^hr#=BDurKe6xY3)?D8w+tw{p3H=Tw9%@Wx9b4Nuo9R{@UBz^!&&Zy6 cJT{hD4c<`N*ESj5a;^mL25R#LifkMI2B3Xw#Q*>R literal 0 HcmV?d00001 diff --git a/.stversions/src/.main.rs~20241106-001814.swp b/.stversions/src/.main.rs~20241106-001814.swp new file mode 100644 index 0000000000000000000000000000000000000000..cceacc556369590a840b974dcefb52d2cc8212ed GIT binary patch literal 20480 zcmeHOeQadcRev;T3Te``i3I8&q}RJ@I#Z3u9^32MZ@sR}coI))ue}~`cGG2fdh_Pa z%!}XGz7LOGuS20!hz}tuNdHKql7dtUQvZpnRP?)1OQk@BDn29{1cE9R6{3ibQmB+J zzjN<>Z{Cc(yMamwr1!Kx&wKat+;h)8_ug~Pwx=I!Zm`Abw1(%+n)WMy@lfxH@86+) z$kepJro`Jnp&12j9)<_EsqkcAPX)H+8xHS8Q&DJ5xu)$^!|3*16eS7{6dd?rIFQ8M z$%T6_&d$`PPoJ7#58Qw8hiR>-TX3M@K*5260|f^P4ip?HI8bn);K2Vw4#dHo+6~bC zI$!|&h{B!ty$0;7?;qT_abpkf-&Wsu;CBWvP=V?cPr-qL0|f^P4ip?HI8bn);6TBF zf&&Ey3Jw$;C^+zcmjh-;(;ftQZz2N#{QqPF`0jnU=L3HadVL`+$GDThsm(_#@y&pb5Mk_}d@Tw9f-i0!M%iybX9Ga5wPkk80X0z*m45 zfG-2T4eSFk5CIo}w*xP~LDRkfd<=LN*Z@ue6TlmQf4NK3o(4Vyybst12EbL|#_Pc| z;9I~ez}JCK0>26900Zs>UWIYxo4|{}bHFpe2Y@Gl3&5km8t@1(3EU0*6AUij0X_$O z1b7ZxEV6 zzTc@SmINI$<}B`Ww!KpqjdDeF6>XKSt9GulLpf%L$1E*@QKjQH);BLzZO@KN>6jIz z7^Y~|_F{y{Btfj}txHX#xqZQ?RU;l7L1_D-9Uqjo)Qhff&7`%cw-DV#fn&!;$Rj@q zEgnsj0>wTm5TF2e+h!CU7-696f{az(H4_KDb>vH_oVICuU4NorKRJVhSk^E>c)yuUz;9g zSkA5y*q+X|n&*v~#bHLr_H!9?!;AnKkxLae^X){r(r0p|2VpK_R%Vb0=$??81N_9^7T|csi&DL`Ljf|%@S7DHiV{*z4Q*b;492J7|hM; z`gtC&hV~v0)12kyGMijx6E~3;>Linw6DZ0m69-a4RA1vHY$kD##97A`wq^URz)lnJ zO%lWKPf6kOa%DJV(Wo5siIS}-B|1{ErzN86R^J389bK=dFG`;&U07dRIe)&fKIGrY z)s}X|;@j{$eA^ssBr)Hw&~&z9%x;Cv&q3*Fn&o?tq4O6vjP=H9ONY$Z@8ekTwdv`z zDsyLZt6A6CLs3ks;#41+*FTkJ*4LYj^E*a!by$F6@Frs(#glW3XS(w9$PumZUz*+J#8+eQ0QpSs`r*CU|I_)zplxtgmm@ zSFpXwIi);ZU!flpyIZzHk(i9-W&73XD?P_YwjoMTHFT+(&xpq+^55A%p=lg{z|tLr zmJ8HQs?{f5NY=3~h(G#}+39Wt!O12dP0MIUKYhE`$4;ZRDA|+mp)<9sX((fM@xlCd z#KS$?>#@jp`JivcP=ju8z}mcT?qRQEes@$2(n71SChKs^?(hzaeTI!ul!>f9?<9~0 z2BBbn*oV|&e$WUC5ltCMng(sk4zb(0*eGMD-G~_#7MPxGVUP9Om{X)2VLP6h3X(R9 z4+8A@kNngEhK`@5WC6m4Y?90*ww*}VL$7D_ICLiUh~@4lUW|cldp)_USdPs-sK&$e zWdkRVx=4h66u&qxd0o4Q%Hp#wzOd&Iu_1N`aachMubq6VIf!N!YBPq|vQfG2JASws zcIdcsNn~LhR8Gp5VL>C{*pZ#fRHkc&v53Wmat9w|Y6wQ-+w0YF&^rrWQ9BT9-oexu z9tp5iXFFDa(Rkd7Z&eBu(4j51a4vamHS(oiOW&%DIQ_)7%h3=1hH^Bcio1;kyNd}l zu*va#%&$2>>0?@Wv?`sbXb;v8xB6zpv8niuNe8wHjz+d(blMLU#&HFOv(=>3nb@38 zDxGcK5V*tiv?O%ntxF%L(#3fXsyze?4#n|J9@ar8O~MJSWve$&Z(~0Ot!km)s?u=+ zdUELNe2+U7CU@r(TCivuFxqWDvLI;3+Zj{&qXooa1R_t%N)0VL(~Wf|iVD$77w92L zms+6ND=iE@R&{v5JxtXhR8*?%hDqvltZEDGqwDXK6w3pK;E6n#z9CQR@;!<>S2eo-zaMw# zpT_-~?*HV||32>a{|S5z_(R|+U;|hOR)C)X{sZ^??*V@bd<=LN*azMNJP7a z{toyw@I3Gvz~jJW;LX5YfCl^k_y1RcKL)-CdD8{}u<7OpERqI_ zCkw)$+TuLo!}ZLuROSSf)?9! z8rRCOj!FJuoXLW;w_xDus~B2E8+MRyd#q8_RoxG8XFs6_*$?q%_@+2TKk5-y1zfGj z$W>ZZ7cgD#hQ4dKw&U1QX&PQ8VOPg}E3HPqUQivtV!%sbuWew4t9TD#gU3bK;~jWr zWUZ>doA*vpP35bh+GJ4!tA(#dqrCL7k}$+P2}_RP@;Me-vTbTkOUu#k=m052k#xnr zT5UtnP?P3Ud`f|$Lg|w$+#4;7TYOq5{mm67yL7JQaI`RFj@e|loUT??DG&iC1?F_5 zRmF|XIxOB7Tde1ka|M!4e33Frj;yqQyUUKTW`gL*H#^qk0iJvt!vAK0dS%nEhTP&L zzS%@mZ;tPiY>S?$hVY@tsm5X!wzbjKOi9wZiselI%E#96)Kn%l@KBn!78%iVt_F95 z_~12EITH8(2cQuDfr9vP|EHM$&*OgoyFeG9JO8(FpZ^B%C&1T$uL55Iejo6F7O)N6 z1AH0x`7Z&V0QP_e@K%8C_^$we22c#Z^T6)_zYaVJ41hgg9(X-KaR5IL{2V}c`pdur zz#YKrfR|vicp6v(ZotR?SHQEthk>Vn1K_>D`|I@%zfD811Ca?ya2JQe}hR^@cfzJY; z0e%f|fD6DDa2G(a0PhEW7I+)*An-Tv=f4DyzyE_k1Ncecui?Xg0r)IHzW&F7IpBWa zEdb5sw?_X{21zMBQ2x@8R5d#t1E$l^GhJS~*lKK_U)gA+>Oz&-qSHXgL?1JCIdXcLwx7>5q`O?fZp7PKqoa-yK1S+Z}i5M6x_mA-~ zf>8}07j|~`l#4fA&$1wCf}9~=u3=4bmP(=&S;Wx_NGJ;q7>3p*9;7V50tJO*V#5VBj2kFQz5*s13%ntgrOgnHEJNOA^p25nQlYQcR}N)7AMvOl)%M%;=@v-8+?6%*6R z8ny_cGdr&&RCpvR>>(FQQM-kfcX>$~n$m0bkRuWXJRobGoT;X-w z8}Dei)8`K^=}F}bmPfwRLEm;bn~>Dmk>Q|0K^eBSTt7s2N)#12nVQwL`W6k_gKsi7 z^jlK|fA%dYlI!m_G*QorJw!Fu!X;2Fct}|lkQC# zna|xj_4}mH;?Q?AO39ycWGoeY@gu=kSp71dLdI=~WhtuWh8=O<84hQ=(b~MYU2n8x z>{N?ZN9N5@iz=tbr4S6OQ->W++k#A^^^f=fG=jJ<5w8LsX3-l$Inl*P&_ol0z_pBm z5y$%|AxmeLZxO);DQn^?1-TNsSgzKg>ZAdK2>6n#9zqO^WPUC^<)h{!(!C64F#N8* z3&%I_$huQgX`F;q={=~bfq4KjNiU{47LqL4R9l3Ot<5hWaAXlLWyLjFx8p?ydF8Re z>FhC%-~|z}BAF(OkcOwm7`0rtQ?-7ZerBM`XXdiL)II~x+&J6o$_;L#b)j}`E7R>_ zUchlohb;EkoLUVa?PBJ}#^O|m7j^l<+KXa@uTek&EpIWcBP67~cKR0jE!{jO4&u%7 z`cQm}=sbUb7%bQH%pP}f%POf*q%BI{$dW0D2XT9p*fRIHTt<-AZ1JdfDdv{rX1Y!g zee!b31t{ef6p<$6JSY6(#-X~%OTHqOCY!_^_w8U1RuD~SS6FwD(U3MN%3$UYe!0_7 zq{=8SnjNvdBW_bIW-5&JBg&4f9o4C*a+75v2Axw1VO0j+4Oe!E3#^C}%qOV`!C{gW z4d3Bst=i$WT!bK{L~btOA-b4Rs^z<`@5#EvOE3h(mQGr^HUx!~rK-qmnHN^k1|kyS z5Jn7=8itU&2*sq^J1uF6C`F3vf=^u6*ATLjtAN=!Rs(4iXqZ)5_Xnzm+G@ydgFU8e zXQu$sx8tHIxR9Ns!=*k)kY}|zrA|;&Vp=AOzR*ZUPA&i`BeY@aae}VX7`knsc&6pSd7U z`b?j(2;Y-eA$D}bibF>RQ>guoj3l`*Ozd=HJyI8G3_(pxJ4}Cx%ULcMlm>v+; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "getlitfam"; +const WIFI_PASSWORD: &str = "getitlitmafam"; +const CLIENT_ID: &str = "pico-495f6297-b962-4266-9c00-74138ae5a1f1"; +const TOPIC: &str = "hello"; +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let tcp_client = TcpClient::new(stack, &tcp_state); + + let addr = embedded_nal_async::SocketAddr::new( + embedded_nal_async::Ipv4Addr::new(10, 42, 0, 1).into(), + 1883, + ); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + } + //Timer::after(Duration::from_secs(2)).await; + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/.stversions/src/main~20241101-191805.rs b/.stversions/src/main~20241101-191805.rs new file mode 100644 index 0000000..e4f16a1 --- /dev/null +++ b/.stversions/src/main~20241101-191805.rs @@ -0,0 +1,400 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::Timer; +//use embedded_nal_async::TcpConnect; +//use embedded_nal_async::stack::tcp::TcpConnect; +use embedded_nal_async::TcpConnect; +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +use rust_mqtt::{ + client::{client::MqttClient, client_config::ClientConfig}, + utils::rng_generator::CountingRng, +}; +//pub type RGB8 = RGB; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "cat"; +const WIFI_PASSWORD: &str = "catcat123cat"; +const CLIENT_ID: &str = "pico-495f6297-b962-4266-9c00-74138ae5a1f1"; +const TOPIC: &str = "hello"; +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let tcp_client = TcpClient::new(stack, &tcp_state); + + let addr = embedded_nal_async::SocketAddr::new( + embedded_nal_async::Ipv4Addr::new(192, 168, 0, 100).into(), + 1883, + ); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + } + //Timer::after(Duration::from_secs(2)).await; + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/.stversions/src/main~20241101-194024.rs b/.stversions/src/main~20241101-194024.rs new file mode 100644 index 0000000..d2b1790 --- /dev/null +++ b/.stversions/src/main~20241101-194024.rs @@ -0,0 +1,400 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::Timer; +//use embedded_nal_async::TcpConnect; +//use embedded_nal_async::stack::tcp::TcpConnect; +use embedded_nal_async::TcpConnect; +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +use rust_mqtt::{ + client::{client::MqttClient, client_config::ClientConfig}, + utils::rng_generator::CountingRng, +}; +//pub type RGB8 = RGB; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "cat"; +const WIFI_PASSWORD: &str = "catcat123cat"; +const CLIENT_ID: &str = "pico-495f6297-b962-4266-9c00-74138ae5a1f1"; +const TOPIC: &str = "hello"; +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let tcp_client = TcpClient::new(stack, &tcp_state); + + let addr = embedded_nal_async::SocketAddr::new( + embedded_nal_async::Ipv4Addr::new(192, 168, 0, 100).into(), + 1883, + ); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + } + Timer::after(Duration::from_millis(2)).await; + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/.stversions/src/main~20241101-221415.rs b/.stversions/src/main~20241101-221415.rs new file mode 100644 index 0000000..b0e02c3 --- /dev/null +++ b/.stversions/src/main~20241101-221415.rs @@ -0,0 +1,400 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::Timer; +//use embedded_nal_async::TcpConnect; +//use embedded_nal_async::stack::tcp::TcpConnect; +use embedded_nal_async::TcpConnect; +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +use rust_mqtt::{ + client::{client::MqttClient, client_config::ClientConfig}, + utils::rng_generator::CountingRng, +}; +//pub type RGB8 = RGB; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "hey can I uhhh"; +const WIFI_PASSWORD: &str = "1%of@dmin"; +const CLIENT_ID: &str = "pico-495f6297-b962-4266-9c00-74138ae5a1f1"; +const TOPIC: &str = "hello"; +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let tcp_client = TcpClient::new(stack, &tcp_state); + + let addr = embedded_nal_async::SocketAddr::new( + embedded_nal_async::Ipv4Addr::new(10, 0, 0, 73).into(), + 1883, + ); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + } + //Timer::after(Duration::from_secs(2)).await; + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/.stversions/src/main~20241101-225353.rs b/.stversions/src/main~20241101-225353.rs new file mode 100644 index 0000000..eeb0cf0 --- /dev/null +++ b/.stversions/src/main~20241101-225353.rs @@ -0,0 +1,400 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::Timer; +//use embedded_nal_async::TcpConnect; +//use embedded_nal_async::stack::tcp::TcpConnect; +use embedded_nal_async::TcpConnect; +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +use rust_mqtt::{ + client::{client::MqttClient, client_config::ClientConfig}, + utils::rng_generator::CountingRng, +}; +//pub type RGB8 = RGB; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "hey can I uhhh"; +const WIFI_PASSWORD: &str = "1%of@dmin"; +const CLIENT_ID: &str = "pico-495f6297-b962-4266-9c00-74138ae5a1f1"; +const TOPIC: &str = "hello"; +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let tcp_client = TcpClient::new(stack, &tcp_state); + + let addr = embedded_nal_async::SocketAddr::new( + embedded_nal_async::Ipv4Addr::new(10, 0, 0, 67).into(), + 1883, + ); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + } + //Timer::after(Duration::from_secs(2)).await; + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/.stversions/src/main~20241101-225609.rs b/.stversions/src/main~20241101-225609.rs new file mode 100644 index 0000000..9cc8568 --- /dev/null +++ b/.stversions/src/main~20241101-225609.rs @@ -0,0 +1,400 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::Timer; +//use embedded_nal_async::TcpConnect; +//use embedded_nal_async::stack::tcp::TcpConnect; +use embedded_nal_async::TcpConnect; +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +use rust_mqtt::{ + client::{client::MqttClient, client_config::ClientConfig}, + utils::rng_generator::CountingRng, +}; +//pub type RGB8 = RGB; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "hey can I uhhh"; +const WIFI_PASSWORD: &str = "1%of@dmin"; +const CLIENT_ID: &str = "pico-495f6297-b962-4266-9c00-74138ae5a1f1"; +const TOPIC: &str = "hello"; +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let tcp_client = TcpClient::new(stack, &tcp_state); + + let addr = embedded_nal_async::SocketAddr::new( + embedded_nal_async::Ipv4Addr::new(192, 168, 0, 103).into(), + 1883, + ); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + } + //Timer::after(Duration::from_secs(2)).await; + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/.stversions/src/main~20241101-231155.rs b/.stversions/src/main~20241101-231155.rs new file mode 100644 index 0000000..467827e --- /dev/null +++ b/.stversions/src/main~20241101-231155.rs @@ -0,0 +1,400 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::Timer; +//use embedded_nal_async::TcpConnect; +//use embedded_nal_async::stack::tcp::TcpConnect; +use embedded_nal_async::TcpConnect; +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +use rust_mqtt::{ + client::{client::MqttClient, client_config::ClientConfig}, + utils::rng_generator::CountingRng, +}; +//pub type RGB8 = RGB; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "cat"; +const WIFI_PASSWORD: &str = "catcat123cat"; +const CLIENT_ID: &str = "pico-495f6297-b962-4266-9c00-74138ae5a1f1"; +const TOPIC: &str = "hello"; +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let tcp_client = TcpClient::new(stack, &tcp_state); + + let addr = embedded_nal_async::SocketAddr::new( + embedded_nal_async::Ipv4Addr::new(192, 168, 0, 103).into(), + 1883, + ); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + } + //Timer::after(Duration::from_secs(2)).await; + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/.stversions/src/main~20241101-235710.rs b/.stversions/src/main~20241101-235710.rs new file mode 100644 index 0000000..567e960 --- /dev/null +++ b/.stversions/src/main~20241101-235710.rs @@ -0,0 +1,400 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::Timer; +//use embedded_nal_async::TcpConnect; +//use embedded_nal_async::stack::tcp::TcpConnect; +use embedded_nal_async::TcpConnect; +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +use rust_mqtt::{ + client::{client::MqttClient, client_config::ClientConfig}, + utils::rng_generator::CountingRng, +}; +//pub type RGB8 = RGB; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "cat"; +const WIFI_PASSWORD: &str = "catcat123cat"; +const CLIENT_ID: &str = "pico-495f6297-b962-4266-9c00-74138ae5a1f1"; +const TOPIC: &str = "hello"; +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let tcp_client = TcpClient::new(stack, &tcp_state); + + let addr = embedded_nal_async::SocketAddr::new( + embedded_nal_async::Ipv4Addr::new(192, 168, 0, 104).into(), + 1883, + ); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + } + //Timer::after(Duration::from_secs(2)).await; + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/.stversions/src/main~20241102-124639.rs b/.stversions/src/main~20241102-124639.rs new file mode 100644 index 0000000..cb55ede --- /dev/null +++ b/.stversions/src/main~20241102-124639.rs @@ -0,0 +1,400 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::Timer; +//use embedded_nal_async::TcpConnect; +//use embedded_nal_async::stack::tcp::TcpConnect; +use embedded_nal_async::TcpConnect; +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +use rust_mqtt::{ + client::{client::MqttClient, client_config::ClientConfig}, + utils::rng_generator::CountingRng, +}; +//pub type RGB8 = RGB; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "MyAccessPoint"; +const WIFI_PASSWORD: &str = "12345678"; +const CLIENT_ID: &str = "pico-495f6297-b962-4266-9c00-74138ae5a1f1"; +const TOPIC: &str = "hello"; +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let tcp_client = TcpClient::new(stack, &tcp_state); + + let addr = embedded_nal_async::SocketAddr::new( + embedded_nal_async::Ipv4Addr::new(192, 168, 12, 1).into(), + 1883, + ); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + } + //Timer::after(Duration::from_secs(2)).await; + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/.stversions/src/main~20241102-133327.rs b/.stversions/src/main~20241102-133327.rs new file mode 100644 index 0000000..8c7f751 --- /dev/null +++ b/.stversions/src/main~20241102-133327.rs @@ -0,0 +1,400 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::Timer; +//use embedded_nal_async::TcpConnect; +//use embedded_nal_async::stack::tcp::TcpConnect; +use embedded_nal_async::TcpConnect; +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +use rust_mqtt::{ + client::{client::MqttClient, client_config::ClientConfig}, + utils::rng_generator::CountingRng, +}; +//pub type RGB8 = RGB; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "octopus"; +const WIFI_PASSWORD: &str = "12345678"; +const CLIENT_ID: &str = "pico-495f6297-b962-4266-9c00-74138ae5a1f1"; +const TOPIC: &str = "hello"; +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let tcp_client = TcpClient::new(stack, &tcp_state); + + let addr = embedded_nal_async::SocketAddr::new( + embedded_nal_async::Ipv4Addr::new(192, 168, 12, 1).into(), + 1883, + ); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + } + //Timer::after(Duration::from_secs(2)).await; + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/.stversions/src/main~20241106-001814.rs b/.stversions/src/main~20241106-001814.rs new file mode 100644 index 0000000..c09320d --- /dev/null +++ b/.stversions/src/main~20241106-001814.rs @@ -0,0 +1,400 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::Timer; +use embedded_nal_async::TcpConnect; +//use embedded_nal_async::TcpConnect; +//use embedded_nal_async::stack::tcp::TcpConnect; +//use embedded_nal_async::TcpConnect; +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +use rust_mqtt::{ + client::{client::MqttClient, client_config::ClientConfig}, + utils::rng_generator::CountingRng, +}; +//pub type RGB8 = RGB; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "cat"; +const WIFI_PASSWORD: &str = "catcat123cat"; +const CLIENT_ID: &str = "pico-test-495f6297-b962-4266-9c00-74138ae5a1f1"; +const TOPIC: &str = "hello"; +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let mut tcp_client = TcpClient::new(stack, &tcp_state); + tcp_client.set_timeout(Some(embassy_time::Duration::from_secs(360))); + + let addr = core::net::SocketAddr::new(core::net::Ipv4Addr::new(192, 168, 0, 102).into(), 1883); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + } + //Timer::after(Duration::from_secs(2)).await; + let _ = client.send_ping().await; + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..46a912b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2332 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "assign-resources" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c0fc5119624f5c171d084b6a1bd3b6447dfadab980bb0d84717906239b77f85" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version 0.2.3", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bincode" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" +dependencies = [ + "bincode_derive", + "serde", +] + +[[package]] +name = "bincode_derive" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c" +dependencies = [ + "virtue", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bt-hci" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a6508c63d7d137e8188833d9ed3ca97e40d676cf5217874c8c1c24851b012d" +dependencies = [ + "defmt", + "embassy-sync", + "embedded-io", + "embedded-io-async", + "futures-intrusive", + "heapless 0.8.0", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "bytemuck" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "crc-any" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "cyw43" +version = "0.2.0" +dependencies = [ + "bt-hci", + "cortex-m", + "cortex-m-rt", + "defmt", + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync", + "embassy-time", + "embedded-hal 1.0.0", + "embedded-io-async", + "futures", + "heapless 0.8.0", + "num_enum", +] + +[[package]] +name = "cyw43-pio" +version = "0.2.0" +dependencies = [ + "cyw43", + "defmt", + "embassy-rp", + "fixed", + "pio", + "pio-proc", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.77", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + +[[package]] +name = "defmt" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9f309eff1f79b3ebdf252954d90ae440599c26c2c553fe87a2d17195f2dcb" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "defmt-parser" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab697b3dbbc1750b7c8b821aa6f6e7f2480b47a99bc057a2ed7b170ebef0c51" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "display-interface" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7517c040926d7b02b111884aa089177db80878533127f7c1b480d852c5fb4112" + +[[package]] +name = "display-interface" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba2aab1ef3793e6f7804162debb5ac5edb93b3d650fbcc5aeb72fcd0e6c03a0" + +[[package]] +name = "display-interface-spi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9ec30048b1955da2038fcc3c017f419ab21bb0001879d16c0a3749dc6b7a" +dependencies = [ + "byte-slice-cast", + "display-interface 0.5.0", + "embedded-hal 1.0.0", + "embedded-hal-async", +] + +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + +[[package]] +name = "ector" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26aaaf5921e8e919d9ff876f40a09df8109063b3a27de5fb6ad4b346573ddd7f" +dependencies = [ + "ector-macros", + "embassy-executor", + "embassy-sync", + "futures", + "portable-atomic", + "static_cell", +] + +[[package]] +name = "ector-macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a39444881d223c9d51d936d2a4631df71d4c00e8edbc167303bfb89d6353d418" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "emballoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f1cdcc6ee777565b5b04209526974a213a8f97879bdb314572265db86e3c63f" +dependencies = [ + "spin", +] + +[[package]] +name = "embassy-embedded-hal" +version = "0.2.0" +dependencies = [ + "defmt", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-storage", + "embedded-storage-async", + "nb 1.1.0", +] + +[[package]] +name = "embassy-executor" +version = "0.6.3" +dependencies = [ + "cortex-m", + "critical-section", + "defmt", + "document-features", + "embassy-executor-macros", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.6.2" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "embassy-futures" +version = "0.1.1" + +[[package]] +name = "embassy-hal-internal" +version = "0.2.0" +dependencies = [ + "cortex-m", + "critical-section", + "defmt", + "num-traits", +] + +[[package]] +name = "embassy-net" +version = "0.5.0" +dependencies = [ + "defmt", + "document-features", + "embassy-net-driver", + "embassy-sync", + "embassy-time", + "embedded-io-async", + "embedded-nal-async", + "heapless 0.8.0", + "managed", + "smoltcp", +] + +[[package]] +name = "embassy-net-driver" +version = "0.2.0" +dependencies = [ + "defmt", +] + +[[package]] +name = "embassy-net-driver-channel" +version = "0.3.0" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync", +] + +[[package]] +name = "embassy-net-wiznet" +version = "0.1.0" +dependencies = [ + "defmt", + "embassy-futures", + "embassy-net-driver-channel", + "embassy-time", + "embedded-hal 1.0.0", + "embedded-hal-async", +] + +[[package]] +name = "embassy-rp" +version = "0.2.0" +dependencies = [ + "atomic-polyfill", + "cfg-if", + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt", + "document-features", + "embassy-embedded-hal", + "embassy-futures", + "embassy-hal-internal", + "embassy-sync", + "embassy-time", + "embassy-time-driver", + "embassy-time-queue-driver", + "embassy-usb-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "embedded-io-async", + "embedded-storage", + "embedded-storage-async", + "fixed", + "nb 1.1.0", + "pio", + "pio-proc", + "rand_core", + "rp-pac", + "rp2040-boot2", + "sha2-const-stable", + "smart-leds", +] + +[[package]] +name = "embassy-sync" +version = "0.6.1" +dependencies = [ + "cfg-if", + "critical-section", + "defmt", + "embedded-io-async", + "futures-sink", + "futures-util", + "heapless 0.8.0", +] + +[[package]] +name = "embassy-time" +version = "0.3.2" +dependencies = [ + "cfg-if", + "critical-section", + "defmt", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-util", +] + +[[package]] +name = "embassy-time-driver" +version = "0.1.0" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-driver" +version = "0.1.0" +dependencies = [ + "embassy-executor", + "heapless 0.8.0", +] + +[[package]] +name = "embassy-usb" +version = "0.3.0" +dependencies = [ + "defmt", + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync", + "embassy-usb-driver", + "heapless 0.8.0", + "ssmarshal", + "usbd-hid", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.1.0" +dependencies = [ + "defmt", +] + +[[package]] +name = "embassy-usb-logger" +version = "0.2.0" +dependencies = [ + "embassy-futures", + "embassy-sync", + "embassy-usb", + "log", +] + +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-graphics" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0649998afacf6d575d126d83e68b78c0ab0e00ca2ac7e9b3db11b4cbe8274ef0" +dependencies = [ + "az", + "byteorder", + "embedded-graphics-core 0.4.0", + "float-cmp", + "micromath", +] + +[[package]] +name = "embedded-graphics-core" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b1239db5f3eeb7e33e35bd10bd014e7b2537b17e071f726a09351431337cfa" +dependencies = [ + "az", + "byteorder", +] + +[[package]] +name = "embedded-graphics-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba9ecd261f991856250d2207f6d8376946cd9f412a2165d3b75bc87a0bc7a044" +dependencies = [ + "az", + "byteorder", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-bus" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d3980bf28e8577db59fe2bdb3df868a419469d2cecb363644eea2b6f7797669" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", + "embedded-hal-async", + "portable-atomic", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +dependencies = [ + "defmt", +] + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "defmt", + "embedded-io", +] + +[[package]] +name = "embedded-nal" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56a28be191a992f28f178ec338a0bf02f63d7803244add736d026a471e6ed77" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "embedded-nal-async" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76959917cd2b86f40a98c28dd5624eddd1fa69d746241c8257eac428d83cb211" +dependencies = [ + "embedded-io-async", + "embedded-nal", +] + +[[package]] +name = "embedded-sdmmc" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "150f320125310e179b9e73b081173b349e63c5c7d4ca44db4e5b9121b10387ec" +dependencies = [ + "byteorder", + "embedded-hal 1.0.0", + "heapless 0.8.0", + "log", +] + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "embedded-storage-async" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" +dependencies = [ + "embedded-storage", +] + +[[package]] +name = "embedded-time" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a4b4d10ac48d08bfe3db7688c402baadb244721f30a77ce360bd24c3dffe58" +dependencies = [ + "num", +] + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fixed" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c6e0b89bf864acd20590dbdbad56f69aeb898abfc9443008fd7bd48b2cc85a" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + +[[package]] +name = "fixed-macro" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0c48af8cb14e02868f449f8a2187bd78af7a08da201fdc78d518ecb1675bc" +dependencies = [ + "fixed", + "fixed-macro-impl", + "fixed-macro-types", +] + +[[package]] +name = "fixed-macro-impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c93086f471c0a1b9c5e300ea92f5cd990ac6d3f8edf27616ef624b8fa6402d4b" +dependencies = [ + "fixed", + "paste", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "fixed-macro-types" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "044a61b034a2264a7f65aa0c3cd112a01b4d4ee58baace51fead3f21b993c7e4" +dependencies = [ + "fixed", + "fixed-macro-impl", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "frunk" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874b6a17738fc273ec753618bac60ddaeac48cb1d7684c3e7bd472e57a28b817" +dependencies = [ + "frunk_core", + "frunk_derives", +] + +[[package]] +name = "frunk_core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3529a07095650187788833d585c219761114005d5976185760cf794d265b6a5c" + +[[package]] +name = "frunk_derives" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e99b8b3c28ae0e84b604c75f721c21dc77afb3706076af5e8216d15fd1deaae3" +dependencies = [ + "frunk_proc_macro_helpers", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "frunk_proc_macro_helpers" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a956ef36c377977e512e227dcad20f68c2786ac7a54dacece3746046fea5ce" +dependencies = [ + "frunk_core", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "fugit" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7" +dependencies = [ + "gcd", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version 0.4.1", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "defmt", + "hash32 0.3.1", + "serde", + "stable_deref_trait", +] + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "lalrpop" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" +dependencies = [ + "ascii-canvas", + "bit-set", + "diff", + "ena", + "is-terminal", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.6.29", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" +dependencies = [ + "regex", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "micromath" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "num" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "panic-probe" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4047d9235d1423d66cc97da7d07eddb54d4f154d6c13805c6d0793956f4f25b0" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico_leds" +version = "0.1.0" +dependencies = [ + "assign-resources", + "bincode", + "byte-slice-cast", + "cortex-m", + "cortex-m-rt", + "cyw43", + "cyw43-pio", + "defmt", + "defmt-rtt", + "display-interface 0.5.0", + "display-interface-spi", + "ector", + "emballoc", + "embassy-embedded-hal", + "embassy-executor", + "embassy-futures", + "embassy-net", + "embassy-net-wiznet", + "embassy-rp", + "embassy-sync", + "embassy-time", + "embassy-usb", + "embassy-usb-logger", + "embedded-alloc", + "embedded-graphics", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-bus", + "embedded-io-async", + "embedded-nal", + "embedded-nal-async", + "embedded-sdmmc", + "embedded-storage", + "embedded-time", + "fixed", + "fixed-macro", + "fugit", + "heapless 0.8.0", + "log", + "panic-probe", + "pio", + "pio-proc", + "portable-atomic", + "rand", + "rgb", + "rp2040-hal", + "serde", + "serde-json-core", + "smart-leds", + "smart-leds-trait", + "st7789", + "static_cell", + "usbd-hid", + "ws2812-async", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + +[[package]] +name = "pio-parser" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77532c2b8279aef98dfc7207ef15298a5a3d6b6cc76ccc8b65913d69f3a8dd6b" +dependencies = [ + "lalrpop", + "lalrpop-util", + "pio", + "regex-syntax 0.6.29", +] + +[[package]] +name = "pio-proc" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b04dc870fb3a4fd8b3e4ca8c61b53bc8ac4eb78b66805d2b3c2e5c4829e0d7a" +dependencies = [ + "codespan-reporting", + "lalrpop-util", + "pio", + "pio-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "regex-syntax 0.6.29", + "syn 1.0.109", +] + +[[package]] +name = "portable-atomic" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d30538d42559de6b034bc76fd6dd4c38961b1ee5c6c56e3808c50128fdbc22ce" +dependencies = [ + "critical-section", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "redox_syscall" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "rlsf" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222fb240c3286247ecdee6fa5341e7cdad0ffdf8e7e401d9937f2d58482a20bf" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "svgbobdoc", +] + +[[package]] +name = "rp-binary-info" +version = "0.1.0" +source = "git+https://github.com/rp-rs/rp-hal.git#f23f87709dadb045a55e68937f0cda28a3bc32da" + +[[package]] +name = "rp-hal-common" +version = "0.1.0" +source = "git+https://github.com/rp-rs/rp-hal.git#f23f87709dadb045a55e68937f0cda28a3bc32da" +dependencies = [ + "fugit", +] + +[[package]] +name = "rp-pac" +version = "6.0.0" +source = "git+https://github.com/embassy-rs/rp-pac.git?rev=a7f42d25517f7124ad3b4ed492dec8b0f50a0e6c#a7f42d25517f7124ad3b4ed492dec8b0f50a0e6c" +dependencies = [ + "cortex-m", + "cortex-m-rt", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rp2040-hal" +version = "0.10.0" +source = "git+https://github.com/rp-rs/rp-hal.git#f23f87709dadb045a55e68937f0cda28a3bc32da" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "itertools", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "rp-binary-info", + "rp-hal-common", + "rp2040-hal-macros", + "rp2040-pac", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp2040-hal-macros" +version = "0.1.0" +source = "git+https://github.com/rp-rs/rp-hal.git#f23f87709dadb045a55e68937f0cda28a3bc32da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "rp2040-pac" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cbcd3f7a0ca7bbe61dc4eb7e202842bee4e27b769a7bf3a4a72fa399d6e404" +dependencies = [ + "cortex-m", + "critical-section", + "vcell", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.23", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b81787e655bd59cecadc91f7b6b8651330b2be6c33246039a65e5cd6f4e0828" +dependencies = [ + "heapless 0.8.0", + "ryu", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smart-leds" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66df34e571fa9993fa6f99131a374d58ca3d694b75f9baac93458fe0d6057bf0" +dependencies = [ + "smart-leds-trait", +] + +[[package]] +name = "smart-leds-trait" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bc64ee02bbbf469603016df746c0ed224f263280b6ebb49b7ebadbff375c572" +dependencies = [ + "rgb", +] + +[[package]] +name = "smoltcp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "defmt", + "heapless 0.8.0", + "managed", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", + "portable-atomic", +] + +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + +[[package]] +name = "st7789" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e89eaa897f8688aa897a7a8a62847e00e3f49f804d918bdebd14d4a1beb5ab" +dependencies = [ + "display-interface 0.4.1", + "embedded-graphics-core 0.3.3", + "embedded-hal 0.2.7", + "heapless 0.7.17", + "nb 1.1.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_cell" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89b0684884a883431282db1e4343f34afc2ff6996fe1f4a1664519b66e14c1e" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless 0.8.0", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield 0.14.0", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown 0.13.2", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtue" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "ws2812-async" +version = "0.2.0" +source = "git+https://github.com/kalkyl/ws2812-async#7ebce834c96bd568899ac50cb3c5f91985d6a5c0" +dependencies = [ + "embedded-hal-async", + "smart-leds", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a8ccedf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,145 @@ +[package] +name = "pico_leds" +version = "0.1.0" +edition = "2021" + +[dependencies] +embassy-embedded-hal = { path = "embassy/embassy-embedded-hal", features = [ + "defmt", +] } +embassy-sync = { path = "embassy/embassy-sync", features = ["defmt"] } +embassy-executor = { path = "embassy/embassy-executor", features = [ + "task-arena-size-98304", + "arch-cortex-m", + "executor-thread", + "executor-interrupt", + "defmt", +] } +embassy-time = { path = "embassy/embassy-time", features = [ + "defmt", + "defmt-timestamp-uptime", +] } +embassy-rp = { path = "embassy/embassy-rp", features = [ + "defmt", + "unstable-pac", + "time-driver", + "critical-section-impl", + "rp2040", +] } +embassy-usb = { path = "embassy/embassy-usb", features = ["defmt"] } +embassy-net = { path = "embassy/embassy-net", features = [ + "defmt", + "tcp", + "udp", + "raw", + "dhcpv4", + "medium-ethernet", + "dns", +] } +embassy-net-wiznet = { path = "embassy/embassy-net-wiznet", features = [ + "defmt", +] } +embassy-futures = { path = "embassy/embassy-futures" } +embassy-usb-logger = { path = "embassy/embassy-usb-logger" } +cyw43 = { path = "embassy/cyw43", features = [ + "defmt", + "firmware-logs", + "bluetooth", +] } +cyw43-pio = { path = "embassy/cyw43-pio", features = ["defmt"] } + +defmt = "0.3" +defmt-rtt = "0.4" +fixed = "1.23.1" +fixed-macro = "1.2" + +#coap-lite = { path = "coap-lite", features = ["udp"] , default-features = false} +embedded-alloc = "0.6" + +# for web request example +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde-json-core = "0.6" + +#cortex-m = { path = "cortex-m/cortex-m", features = ["critical-section-single-core"] } +#cortex-m = { path = "cortex-m", features = ["critical-section-single-core"] } + +cortex-m = { version = "0.7", features = ["inline-asm"] } +#cortex-m-rt = { path = "cortex-m/cortex-m-rt" } +cortex-m-rt = "*" + +panic-probe = { version = "0.3", features = ["print-defmt"] } +display-interface-spi = "0.5" +embedded-graphics = "0.8" +st7789 = "0.7" +display-interface = "0.5" +byte-slice-cast = { version = "1.2.0", default-features = false } +smart-leds = "0.4" +heapless = "0.8" +usbd-hid = "0.8.1" + +#embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0" +embedded-hal-bus = { version = "0.2", features = ["async"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-storage = { version = "0.3" } +static_cell = "2.1" +portable-atomic = { version = "1.5", features = ["critical-section"] } +log = "0.4" +pio-proc = "0.2" +pio = "0.2.1" +rand = { version = "0.8.5", default-features = false } +embedded-sdmmc = "0.8.0" +embedded-nal = "0.9" + +#trouble-host = { version = "0.1.0", features = ["defmt", "gatt"] } + +embedded-time = "0.12" +bincode = { version = "2.0.0-rc.3", default-features = false, features = [ + "alloc", + "derive", +] } +ector = { version = "0.6.0", default-features = false } +embedded-nal-async = { version = "0.8" } + +rp2040-hal = { git = "https://github.com/rp-rs/rp-hal.git" } +#rp2040-boot2 = "0.3" +#ws2812-spi = "0.5.0" +fugit = "0.3" +#ws2811-spi = { path = "ws2811-spi-rs", features = ["mosi_idle_high"] } +# embassy-nrf = { version = "0.1.0", default-features = false, features = ["time-driver-rtc1", "gpiote"]} +smart-leds-trait = "*" + +ws2812-async = { git = "https://github.com/kalkyl/ws2812-async" } +rgb = "0.8.43" + +#native-tls = "*" +#dns-lookup = "*" +# embedded-tls = { version = "0.12.0", default-features = false, features = ["async", "defmt"]} +assign-resources = "0.4.1" +embedded-hal = "1.0" +emballoc = { version = "0.3.0", features = ["portable_atomic"] } + +[profile.release] +codegen-units = 1 +debug = 0 +debug-assertions = false +incremental = true +lto = 'fat' +opt-level = 's' +overflow-checks = false + +[profile.dev] +debug = 2 +lto = true +#opt-level = "z" +opt-level = 3 + + +[patch.crates-io] +#trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" } +embassy-executor = { path = "embassy/embassy-executor" } +embassy-sync = { path = "embassy/embassy-sync" } +embassy-futures = { path = "embassy/embassy-futures" } +embassy-time = { path = "embassy/embassy-time" } +embassy-time-driver = { path = "embassy/embassy-time-driver" } +embassy-embedded-hal = { path = "embassy/embassy-embedded-hal" } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..3f915f9 --- /dev/null +++ b/build.rs @@ -0,0 +1,36 @@ +//! 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=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/embassy/.gitattributes b/embassy/.gitattributes new file mode 100644 index 0000000..a51376f --- /dev/null +++ b/embassy/.gitattributes @@ -0,0 +1,43 @@ +* text=auto + +*.adoc text +*.html text +*.in text +*.json text +*.md text +*.proto text +*.py text +*.rs text +*.service text +*.sh text +*.toml text +*.txt text +*.x text +*.yml text + +.vscode/*.json linguist-language=JSON-with-Comments + +*.raw binary +*.bin binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.mov binary +*.mp4 binary +*.mp3 binary +*.flv binary +*.fla binary +*.swf binary +*.gz binary +*.zip binary +*.7z binary +*.ttf binary +*.eot binary +*.woff binary +*.pyc binary +*.pdf binary +*.ez binary +*.bz2 binary +*.swp binary diff --git a/embassy/.github/ci/book.sh b/embassy/.github/ci/book.sh new file mode 100755 index 0000000..285cdc8 --- /dev/null +++ b/embassy/.github/ci/book.sh @@ -0,0 +1,17 @@ +#!/bin/bash +## on push branch=main + +set -euxo pipefail + +make -C docs + +export KUBECONFIG=/ci/secrets/kubeconfig.yml +POD=$(kubectl -n embassy get po -l app=website -o jsonpath={.items[0].metadata.name}) + +mkdir -p build +mv docs/book build/book +tar -C build -cf book.tar book +kubectl exec $POD -- mkdir -p /usr/share/nginx/html +kubectl cp book.tar $POD:/usr/share/nginx/html/ +kubectl exec $POD -- find /usr/share/nginx/html +kubectl exec $POD -- tar -C /usr/share/nginx/html -xvf /usr/share/nginx/html/book.tar diff --git a/embassy/.github/ci/build-nightly.sh b/embassy/.github/ci/build-nightly.sh new file mode 100755 index 0000000..95cb410 --- /dev/null +++ b/embassy/.github/ci/build-nightly.sh @@ -0,0 +1,29 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target +mv rust-toolchain-nightly.toml rust-toolchain.toml + +# needed for "dumb HTTP" transport support +# used when pointing stm32-metapac to a CI-built one. +export CARGO_NET_GIT_FETCH_WITH_CLI=true + +# Restore lockfiles +if [ -f /ci/cache/lockfiles.tar ]; then + echo Restoring lockfiles... + tar xf /ci/cache/lockfiles.tar +fi + +hashtime restore /ci/cache/filetime.json || true +hashtime save /ci/cache/filetime.json + +./ci-nightly.sh + +# Save lockfiles +echo Saving lockfiles... +find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+ diff --git a/embassy/.github/ci/build-xtensa.sh b/embassy/.github/ci/build-xtensa.sh new file mode 100755 index 0000000..88357a0 --- /dev/null +++ b/embassy/.github/ci/build-xtensa.sh @@ -0,0 +1,37 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target + +# needed for "dumb HTTP" transport support +# used when pointing stm32-metapac to a CI-built one. +export CARGO_NET_GIT_FETCH_WITH_CLI=true + +cargo install espup +/ci/cache/cargo/bin/espup install --toolchain-version 1.83.0.1 + +# Restore lockfiles +if [ -f /ci/cache/lockfiles.tar ]; then + echo Restoring lockfiles... + tar xf /ci/cache/lockfiles.tar +fi + +hashtime restore /ci/cache/filetime.json || true +hashtime save /ci/cache/filetime.json + +mkdir .cargo +cat > .cargo/config.toml<< EOF +[unstable] +build-std = ["alloc", "core"] +EOF + +./ci-xtensa.sh + +# Save lockfiles +echo Saving lockfiles... +find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+ diff --git a/embassy/.github/ci/build.sh b/embassy/.github/ci/build.sh new file mode 100755 index 0000000..68a7c0c --- /dev/null +++ b/embassy/.github/ci/build.sh @@ -0,0 +1,34 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target +if [ -f /ci/secrets/teleprobe-token.txt ]; then + echo Got teleprobe token! + export TELEPROBE_HOST=https://teleprobe.embassy.dev + export TELEPROBE_TOKEN=$(cat /ci/secrets/teleprobe-token.txt) + export TELEPROBE_CACHE=/ci/cache/teleprobe_cache.json +fi + +# needed for "dumb HTTP" transport support +# used when pointing stm32-metapac to a CI-built one. +export CARGO_NET_GIT_FETCH_WITH_CLI=true + +# Restore lockfiles +if [ -f /ci/cache/lockfiles.tar ]; then + echo Restoring lockfiles... + tar xf /ci/cache/lockfiles.tar +fi + +hashtime restore /ci/cache/filetime.json || true +hashtime save /ci/cache/filetime.json + +./ci.sh + +# Save lockfiles +echo Saving lockfiles... +find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+ diff --git a/embassy/.github/ci/crlf.sh b/embassy/.github/ci/crlf.sh new file mode 100755 index 0000000..69838ce --- /dev/null +++ b/embassy/.github/ci/crlf.sh @@ -0,0 +1,17 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +FILES_WITH_CRLF=$(find ! -path "./.git/*" -not -type d | xargs file -N | (grep " CRLF " || true)) + +if [ -z "$FILES_WITH_CRLF" ]; then + echo -e "No files with CRLF endings found." + exit 0 +else + NR_FILES=$(echo "$FILES_WITH_CRLF" | wc -l) + echo -e "ERROR: Found ${NR_FILES} files with CRLF endings." + echo "$FILES_WITH_CRLF" + exit "$NR_FILES" +fi diff --git a/embassy/.github/ci/doc.sh b/embassy/.github/ci/doc.sh new file mode 100755 index 0000000..47babe8 --- /dev/null +++ b/embassy/.github/ci/doc.sh @@ -0,0 +1,64 @@ +#!/bin/bash +## on push branch=main + +set -euxo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target +export BUILDER_THREADS=4 +export BUILDER_COMPRESS=true +mv rust-toolchain-nightly.toml rust-toolchain.toml + +# force rustup to download the toolchain before starting building. +# Otherwise, the docs builder is running multiple instances of cargo rustdoc concurrently. +# They all see the toolchain is not installed and try to install it in parallel +# which makes rustup very sad +rustc --version > /dev/null + +docserver-builder -i ./embassy-boot -o webroot/crates/embassy-boot/git.zup +docserver-builder -i ./embassy-boot-nrf -o webroot/crates/embassy-boot-nrf/git.zup +docserver-builder -i ./embassy-boot-rp -o webroot/crates/embassy-boot-rp/git.zup +docserver-builder -i ./embassy-boot-stm32 -o webroot/crates/embassy-boot-stm32/git.zup +docserver-builder -i ./embassy-embedded-hal -o webroot/crates/embassy-embedded-hal/git.zup +docserver-builder -i ./embassy-executor -o webroot/crates/embassy-executor/git.zup +docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup +docserver-builder -i ./embassy-nrf -o webroot/crates/embassy-nrf/git.zup +docserver-builder -i ./embassy-rp -o webroot/crates/embassy-rp/git.zup +docserver-builder -i ./embassy-sync -o webroot/crates/embassy-sync/git.zup +docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup +docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup +docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/git.zup --output-static webroot/static + +docserver-builder -i ./embassy-time -o webroot/crates/embassy-time/git.zup +docserver-builder -i ./embassy-time-driver -o webroot/crates/embassy-time-driver/git.zup +docserver-builder -i ./embassy-time-queue-driver -o webroot/crates/embassy-time-queue-driver/git.zup + +docserver-builder -i ./embassy-usb -o webroot/crates/embassy-usb/git.zup +docserver-builder -i ./embassy-usb-dfu -o webroot/crates/embassy-usb-dfu/git.zup +docserver-builder -i ./embassy-usb-driver -o webroot/crates/embassy-usb-driver/git.zup +docserver-builder -i ./embassy-usb-logger -o webroot/crates/embassy-usb-logger/git.zup +docserver-builder -i ./embassy-usb-synopsys-otg -o webroot/crates/embassy-usb-synopsys-otg/git.zup + +docserver-builder -i ./embassy-net -o webroot/crates/embassy-net/git.zup +docserver-builder -i ./embassy-net-driver -o webroot/crates/embassy-net-driver/git.zup +docserver-builder -i ./embassy-net-driver-channel -o webroot/crates/embassy-net-driver-channel/git.zup +docserver-builder -i ./embassy-net-wiznet -o webroot/crates/embassy-net-wiznet/git.zup +docserver-builder -i ./embassy-net-ppp -o webroot/crates/embassy-net-ppp/git.zup +docserver-builder -i ./embassy-net-tuntap -o webroot/crates/embassy-net-tuntap/git.zup +docserver-builder -i ./embassy-net-enc28j60 -o webroot/crates/embassy-net-enc28j60/git.zup +docserver-builder -i ./embassy-net-esp-hosted -o webroot/crates/embassy-net-esp-hosted/git.zup +docserver-builder -i ./embassy-net-adin1110 -o webroot/crates/embassy-net-adin1110/git.zup + +export KUBECONFIG=/ci/secrets/kubeconfig.yml +POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) +kubectl cp webroot/crates $POD:/data +kubectl cp webroot/static $POD:/data + +# build and upload stm32 last +# so that it doesn't prevent other crates from getting docs updates when it breaks. + +rm -rf webroot +docserver-builder -i ./embassy-stm32 -o webroot/crates/embassy-stm32/git.zup +POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) +kubectl cp webroot/crates $POD:/data diff --git a/embassy/.github/ci/rustfmt.sh b/embassy/.github/ci/rustfmt.sh new file mode 100755 index 0000000..369239c --- /dev/null +++ b/embassy/.github/ci/rustfmt.sh @@ -0,0 +1,12 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target +mv rust-toolchain-nightly.toml rust-toolchain.toml + +find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check --skip-children --unstable-features --edition 2021 diff --git a/embassy/.github/ci/test-nightly.sh b/embassy/.github/ci/test-nightly.sh new file mode 100755 index 0000000..a03b55e --- /dev/null +++ b/embassy/.github/ci/test-nightly.sh @@ -0,0 +1,17 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target +mv rust-toolchain-nightly.toml rust-toolchain.toml + +cargo test --manifest-path ./embassy-executor/Cargo.toml +cargo test --manifest-path ./embassy-executor/Cargo.toml --features nightly + +MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml +MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml --features nightly +MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-sync/Cargo.toml diff --git a/embassy/.github/ci/test.sh b/embassy/.github/ci/test.sh new file mode 100755 index 0000000..0fd6820 --- /dev/null +++ b/embassy/.github/ci/test.sh @@ -0,0 +1,36 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target + +# needed for "dumb HTTP" transport support +# used when pointing stm32-metapac to a CI-built one. +export CARGO_NET_GIT_FETCH_WITH_CLI=true + +cargo test --manifest-path ./embassy-executor/Cargo.toml +cargo test --manifest-path ./embassy-futures/Cargo.toml +cargo test --manifest-path ./embassy-sync/Cargo.toml +cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml +cargo test --manifest-path ./embassy-hal-internal/Cargo.toml +cargo test --manifest-path ./embassy-time/Cargo.toml --features mock-driver,embassy-time-queue-driver/generic-queue-8 +cargo test --manifest-path ./embassy-time-driver/Cargo.toml + +cargo test --manifest-path ./embassy-boot/Cargo.toml +cargo test --manifest-path ./embassy-boot/Cargo.toml --features ed25519-dalek +cargo test --manifest-path ./embassy-boot/Cargo.toml --features ed25519-salty + +cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nrf52840,time-driver-rtc1,gpiote + +cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver,rp2040,_test +cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver,rp235xa,_test + +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f429vg,exti,time-driver-any,exti +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f732ze,exti,time-driver-any,exti +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f769ni,exti,time-driver-any,exti + +cargo test --manifest-path ./embassy-net-adin1110/Cargo.toml diff --git a/embassy/.github/workflows/matrix-bot.yml b/embassy/.github/workflows/matrix-bot.yml new file mode 100644 index 0000000..3e2a865 --- /dev/null +++ b/embassy/.github/workflows/matrix-bot.yml @@ -0,0 +1,44 @@ +name: Matrix bot +on: + pull_request_target: + types: [opened, closed] + +jobs: + new-pr: + if: github.event.action == 'opened' && github.repository == 'embassy-rs/embassy' + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: send message + uses: s3krit/matrix-message-action@v0.0.3 + with: + room_id: ${{ secrets.MATRIX_ROOM_ID }} + access_token: ${{ secrets.MATRIX_ACCESS_TOKEN }} + message: "New PR: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})" + server: "matrix.org" + + merged-pr: + if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.repository == 'embassy-rs/embassy' + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: send message + uses: s3krit/matrix-message-action@v0.0.3 + with: + room_id: ${{ secrets.MATRIX_ROOM_ID }} + access_token: ${{ secrets.MATRIX_ACCESS_TOKEN }} + message: "PR merged: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})" + server: "matrix.org" + + abandoned-pr: + if: github.event.action == 'closed' && github.event.pull_request.merged == false && github.repository == 'embassy-rs/embassy' + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: send message + uses: s3krit/matrix-message-action@v0.0.3 + with: + room_id: ${{ secrets.MATRIX_ROOM_ID }} + access_token: ${{ secrets.MATRIX_ACCESS_TOKEN }} + message: "PR closed without merging: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})" + server: "matrix.org" diff --git a/embassy/.gitignore b/embassy/.gitignore new file mode 100644 index 0000000..352c1f1 --- /dev/null +++ b/embassy/.gitignore @@ -0,0 +1,11 @@ +target +target_ci +target_ci_stable +Cargo.lock +third_party +/Cargo.toml +out/ +# editor artifacts +.zed +.neoconf.json +*.vim diff --git a/embassy/.vscode/.gitignore b/embassy/.vscode/.gitignore new file mode 100644 index 0000000..8c3dd8a --- /dev/null +++ b/embassy/.vscode/.gitignore @@ -0,0 +1,4 @@ +*.cortex-debug.*.json +launch.json +tasks.json +*.cfg diff --git a/embassy/.vscode/extensions.json b/embassy/.vscode/extensions.json new file mode 100644 index 0000000..a8bb78a --- /dev/null +++ b/embassy/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [] +} \ No newline at end of file diff --git a/embassy/.vscode/settings.json b/embassy/.vscode/settings.json new file mode 100644 index 0000000..48d0957 --- /dev/null +++ b/embassy/.vscode/settings.json @@ -0,0 +1,57 @@ +{ + "[toml]": { + "editor.formatOnSave": false + }, + "[markdown]": { + "editor.formatOnSave": false + }, + "rust-analyzer.check.allTargets": false, + "rust-analyzer.check.noDefaultFeatures": true, + "rust-analyzer.cargo.noDefaultFeatures": true, + "rust-analyzer.showUnlinkedFileNotification": false, + // Uncomment the target of your chip. + //"rust-analyzer.cargo.target": "thumbv6m-none-eabi", + //"rust-analyzer.cargo.target": "thumbv7m-none-eabi", + "rust-analyzer.cargo.target": "thumbv7em-none-eabi", + //"rust-analyzer.cargo.target": "thumbv7em-none-eabihf", + //"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.cargo.features": [ + // Comment out these features when working on the examples. Most example crates do not have any cargo features. + "stm32f446re", + "time-driver-any", + "unstable-pac", + "exti", + "rt", + ], + "rust-analyzer.linkedProjects": [ + "embassy-stm32/Cargo.toml", + // To work on the examples, comment the line above and all of the cargo.features lines, + // then uncomment ONE line below to select the chip you want to work on. + // This makes rust-analyzer work on the example crate and all its dependencies. + // "examples/nrf52840-rtic/Cargo.toml", + // "examples/nrf5340/Cargo.toml", + // "examples/nrf-rtos-trace/Cargo.toml", + // "examples/rp/Cargo.toml", + // "examples/std/Cargo.toml", + // "examples/stm32c0/Cargo.toml", + // "examples/stm32f0/Cargo.toml", + // "examples/stm32f1/Cargo.toml", + // "examples/stm32f2/Cargo.toml", + // "examples/stm32f3/Cargo.toml", + // "examples/stm32f334/Cargo.toml", + // "examples/stm32f4/Cargo.toml", + // "examples/stm32f7/Cargo.toml", + // "examples/stm32g0/Cargo.toml", + // "examples/stm32g4/Cargo.toml", + // "examples/stm32h5/Cargo.toml", + // "examples/stm32h7/Cargo.toml", + // "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/stm32wl/Cargo.toml", + // "examples/wasm/Cargo.toml", + ], +} diff --git a/embassy/LICENSE-APACHE b/embassy/LICENSE-APACHE new file mode 100644 index 0000000..8f7956e --- /dev/null +++ b/embassy/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) Embassy project contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/embassy/LICENSE-CC-BY-SA b/embassy/LICENSE-CC-BY-SA new file mode 100644 index 0000000..a73481c --- /dev/null +++ b/embassy/LICENSE-CC-BY-SA @@ -0,0 +1,428 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/embassy/LICENSE-MIT b/embassy/LICENSE-MIT new file mode 100644 index 0000000..1fe5730 --- /dev/null +++ b/embassy/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) Embassy project contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/embassy/NOTICE.md b/embassy/NOTICE.md new file mode 100644 index 0000000..a50b394 --- /dev/null +++ b/embassy/NOTICE.md @@ -0,0 +1,16 @@ +# Notices for Embassy + +This content is produced and maintained by the Embassy project contributors. + +## Copyright + +All content is the property of the respective authors or their employers. +For more information regarding authorship of content, please consult the +listed source code repository logs. + +## Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Apache Software License 2.0 which is available at +https://www.apache.org/licenses/LICENSE-2.0, or the MIT license which is +available at https://opensource.org/licenses/MIT diff --git a/embassy/README.md b/embassy/README.md new file mode 100644 index 0000000..8952b03 --- /dev/null +++ b/embassy/README.md @@ -0,0 +1,159 @@ +# Embassy + +Embassy is the next-generation framework for embedded applications. Write safe, correct and energy-efficient embedded code faster, using the Rust programming language, its async facilities, and the Embassy libraries. + +## Documentation - API reference - Website - Chat +## Rust + async ❤️ embedded + +The Rust programming language is blazingly fast and memory-efficient, with no runtime, garbage collector or OS. It catches a wide variety of bugs at compile time, thanks to its full memory- and thread-safety, and expressive type system. + +Rust's async/await allows for unprecedentedly easy and efficient multitasking in embedded systems. Tasks get transformed at compile time into state machines that get run cooperatively. It requires no dynamic memory allocation, and runs on a single stack, so no per-task stack size tuning is required. It obsoletes the need for a traditional RTOS with kernel context switching, and is faster and smaller than one! + +## Batteries included + +- **Hardware Abstraction Layers** - HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy. + - embassy-stm32, for all STM32 microcontroller families. + - embassy-nrf, for the Nordic Semiconductor nRF52, nRF53, nRF91 series. + - embassy-rp, for the Raspberry Pi RP2040 microcontroller. + - esp-rs, for the Espressif Systems ESP32 series of chips. + - Embassy HAL support for Espressif chips is being developed in the [esp-rs/esp-hal](https://github.com/esp-rs/esp-hal) repository. + - Async WiFi, Bluetooth and ESP-NOW is being developed in the [esp-rs/esp-wifi](https://github.com/esp-rs/esp-wifi) repository. + - ch32-hal, for the WCH 32-bit RISC-V(CH32V) series of chips. + +- **Time that Just Works** - +No more messing with hardware timers. embassy_time provides Instant, Duration and Timer types that are globally available and never overflow. + +- **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. + +- **Low-power ready** - +Easily build devices with years of battery life. The async executor automatically puts the core to sleep when there's no work to do. Tasks are woken by interrupts, there is no busy-loop polling while waiting. + +- **Networking** - +The embassy-net network stack implements extensive networking functionality, including Ethernet, IP, TCP, UDP, ICMP and DHCP. Async drastically simplifies managing timeouts and serving multiple connections concurrently. + +- **Bluetooth** - +The nrf-softdevice crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers. +The embassy-stm32-wpan crate provides Bluetooth Low Energy 5.x support for stm32wb microcontrollers. + +- **LoRa** - The lora-rs project provides an async LoRa and LoRaWAN stack that works well on Embassy. + +- **USB** - +embassy-usb implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own. + +- **Bootloader and DFU** - +embassy-boot is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks. + + +## Sneak peek + +```rust,ignore +use defmt::info; +use embassy_executor::Spawner; +use embassy_time::{Duration, Timer}; +use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull}; +use embassy_nrf::Peripherals; + +// Declare async tasks +#[embassy_executor::task] +async fn blink(pin: AnyPin) { + let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); + + loop { + // Timekeeping is globally available, no need to mess with hardware timers. + led.set_high(); + Timer::after_millis(150).await; + led.set_low(); + Timer::after_millis(150).await; + } +} + +// Main is itself an async task as well. +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + // Spawned tasks run in the background, concurrently. + spawner.spawn(blink(p.P0_13.degrade())).unwrap(); + + let mut button = Input::new(p.P0_11, Pull::Up); + loop { + // Asynchronously wait for GPIO events, allowing other tasks + // to run, or the core to sleep. + button.wait_for_low().await; + info!("Button pressed!"); + button.wait_for_high().await; + info!("Button released!"); + } +} +``` + +## Examples + +Examples are found in the `examples/` folder separated by the chip manufacturer they are designed to run on. For example: + +* `examples/nrf52840` run on the `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards. +* `examples/nrf5340` run on the `nrf5340-dk` board (PCA10095). +* `examples/stm32xx` for the various STM32 families. +* `examples/rp` are for the RP2040 chip. +* `examples/std` are designed to run locally on your PC. + +### Running examples + +- Install `probe-rs` following the instructions at . +- Change directory to the sample's base directory. For example: + +```bash +cd examples/nrf52840 +``` + +- Ensure `Cargo.toml` sets the right feature for the name of the chip you are programming. + If this name is incorrect, the example may fail to run or immediately crash + after being programmed. + +- Ensure `.cargo/config.toml` contains the name of the chip you are programming. + +- Run the example + +For example: + +```bash +cargo run --release --bin blinky +``` + +For more help getting started, see [Getting Started][1] and [Running the Examples][2]. + +## Developing Embassy with Rust Analyzer based editors + +The [Rust Analyzer](https://rust-analyzer.github.io/) is used by [Visual Studio Code](https://code.visualstudio.com/) +and others. Given the multiple targets that Embassy serves, there is no Cargo workspace file. Instead, the Rust Analyzer +must be told of the target project to work with. In the case of Visual Studio Code, +please refer to the `.vscode/settings.json` file's `rust-analyzer.linkedProjects`setting. + +## Minimum supported Rust version (MSRV) + +Embassy is guaranteed to compile on stable Rust 1.75 and up. It *might* +compile with older versions but that may change in any new patch release. + +## Why the name? + +EMBedded ASYnc! :) + +## License + +Embassy 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. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[1]: https://github.com/embassy-rs/embassy/wiki/Getting-Started +[2]: https://github.com/embassy-rs/embassy/wiki/Running-the-Examples diff --git a/embassy/ci-nightly.sh b/embassy/ci-nightly.sh new file mode 100755 index 0000000..1b69cc1 --- /dev/null +++ b/embassy/ci-nightly.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -eo pipefail + +export RUSTFLAGS=-Dwarnings +export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info +if [[ -z "${CARGO_TARGET_DIR}" ]]; then + export CARGO_TARGET_DIR=target_ci +fi + +cargo batch \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,arch-cortex-m,executor-thread,executor-interrupt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32 \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread \ + --- build --release --manifest-path examples/nrf52840-rtic/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840-rtic \ + +cargo build --release --manifest-path embassy-executor/Cargo.toml --target avr-unknown-gnu-atmega328 -Z build-std=core,alloc --features nightly,arch-avr,avr-device/atmega328p diff --git a/embassy/ci-xtensa.sh b/embassy/ci-xtensa.sh new file mode 100755 index 0000000..056e85d --- /dev/null +++ b/embassy/ci-xtensa.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +set -eo pipefail + +export RUSTFLAGS=-Dwarnings +export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info +export RUSTUP_TOOLCHAIN=esp +if [[ -z "${CARGO_TARGET_DIR}" ]]; then + export CARGO_TARGET_DIR=target_ci +fi + +cargo batch \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features log \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features defmt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features defmt,arch-spin,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt,arch-spin,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32s3-none-elf --features defmt,arch-spin,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin,rtos-trace \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin,executor-thread \ + --- build --release --manifest-path embassy-sync/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt \ + --- build --release --manifest-path embassy-time/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt,defmt-timestamp-uptime,mock-driver \ + --- build --release --manifest-path embassy-time-queue-driver/Cargo.toml --target xtensa-esp32s2-none-elf \ + --- build --release --manifest-path embassy-time-queue-driver/Cargo.toml --target xtensa-esp32s2-none-elf --features generic-queue-8 \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ieee802154 \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,medium-ieee802154 \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154 \ diff --git a/embassy/ci.sh b/embassy/ci.sh new file mode 100755 index 0000000..6b52319 --- /dev/null +++ b/embassy/ci.sh @@ -0,0 +1,316 @@ +#!/bin/bash + +set -eo pipefail + +if ! command -v cargo-batch &> /dev/null; then + echo "cargo-batch could not be found. Install it with the following command:" + echo "" + echo " cargo install --git https://github.com/embassy-rs/cargo-batch cargo --bin cargo-batch --locked" + echo "" + exit 1 +fi + +export RUSTFLAGS=-Dwarnings +export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info +if [[ -z "${CARGO_TARGET_DIR}" ]]; then + export CARGO_TARGET_DIR=target_ci +fi + +TARGET=$(rustc -vV | sed -n 's|host: ||p') + +BUILD_EXTRA="" +if [ $TARGET = "x86_64-unknown-linux-gnu" ]; then + BUILD_EXTRA="--- build --release --manifest-path examples/std/Cargo.toml --target $TARGET --out-dir out/examples/std" +fi + +# CI intentionally does not use -eabihf on thumbv7em to minimize dep compile time. +cargo batch \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt,arch-cortex-m,executor-thread,executor-interrupt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,rtos-trace \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,executor-interrupt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32 \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread \ + --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \ + --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,mock-driver \ + --- build --release --manifest-path embassy-time-queue-driver/Cargo.toml --target thumbv6m-none-eabi \ + --- build --release --manifest-path embassy-time-queue-driver/Cargo.toml --target thumbv6m-none-eabi --features generic-queue-8 \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ieee802154 \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,medium-ieee802154 \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52820,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52832,gpiote,time,time-driver-rtc1,reset-pin-as-gpio \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52833,gpiote,time,time-driver-rtc1,nfc-pins-as-gpio \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160-s,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160-ns,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-s,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-ns,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-net,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf54l15-app-s,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf54l15-app-ns,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,log,time \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,log,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,log,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,defmt,time \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,defmt,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,defmt,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,defmt,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,defmt,time \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,time \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,defmt,rp2040 \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,log,rp2040 \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,intrinsics,rp2040 \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,qspi-as-gpio,rp2040 \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,defmt,rp235xa \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,log,rp235xa \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,rp235xa,binary-info \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f038f6,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f030c6,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f058t8,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f030r8,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f031k6,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f030rc,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f070f6,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f078vb,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f042g4,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f072c8,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f401ve,defmt,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f405zg,defmt,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f407zg,defmt,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f401ve,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f405zg,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f407zg,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f410tb,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f412zg,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f413vh,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f415zg,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f417zg,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f423zh,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f427zi,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f437zi,log,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f439zi,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f446ze,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f469zi,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f479zi,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f730i8,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h735zg,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h725re,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7b3ai,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7b3ai,defmt,exti,time-driver-tim1,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7r3z8,defmt,exti,time-driver-tim1,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7r7a8,defmt,exti,time-driver-tim1,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7s3a8,defmt,exti,time-driver-tim1,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7s7z8,defmt,exti,time-driver-tim1,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l431cb,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l422cb,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb15cc,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l041f6,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l051k8,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073cz,defmt,exti,time-driver-any,low-power,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303c8,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f398ve,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f378cc,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g0c1ve,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,low-power,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32wl54jc-cm0p,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wle5jb,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g474pe,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f107vc,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103re,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f100c4,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h503rb,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h523cc,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h562ag,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba50ke,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba55ug,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5f9zj,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5g9nj,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb35ce,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u031r8,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u073mb,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv8m.main-none-eabihf \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features ''\ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs,bluetooth' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs,bluetooth' \ + --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040' \ + --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040,overclock' \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns \ + --- build --release --manifest-path embassy-boot-rp/Cargo.toml --target thumbv6m-none-eabi --features embassy-rp/rp2040 \ + --- build --release --manifest-path embassy-boot-stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \ + --- build --release --manifest-path docs/examples/basic/Cargo.toml --target thumbv7em-none-eabi \ + --- build --release --manifest-path docs/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \ + --- build --release --manifest-path docs/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \ + --- build --release --manifest-path docs/examples/layer-by-layer/blinky-irq/Cargo.toml --target thumbv7em-none-eabi \ + --- build --release --manifest-path docs/examples/layer-by-layer/blinky-async/Cargo.toml --target thumbv7em-none-eabi \ + --- build --release --manifest-path examples/nrf52810/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52810 \ + --- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840 \ + --- build --release --manifest-path examples/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf5340 \ + --- build --release --manifest-path examples/nrf54l15/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf54l15 \ + --- build --release --manifest-path examples/nrf9160/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9160 \ + --- build --release --manifest-path examples/nrf9151/s/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9151/s \ + --- build --release --manifest-path examples/nrf9151/ns/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9151/ns \ + --- build --release --manifest-path examples/nrf51/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/nrf51 \ + --- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/rp \ + --- build --release --manifest-path examples/rp23/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/rp23 \ + --- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32f0 \ + --- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \ + --- build --release --manifest-path examples/stm32f2/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f2 \ + --- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f3 \ + --- build --release --manifest-path examples/stm32f334/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f334 \ + --- build --release --manifest-path examples/stm32f4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f4 \ + --- build --release --manifest-path examples/stm32f469/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f469 \ + --- build --release --manifest-path examples/stm32f7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f7 \ + --- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32c0 \ + --- build --release --manifest-path examples/stm32g0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32g0 \ + --- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \ + --- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32h5 \ + --- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \ + --- build --release --manifest-path examples/stm32h7b0/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7b0 \ + --- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \ + --- build --release --manifest-path examples/stm32h755cm4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm4 \ + --- build --release --manifest-path examples/stm32h755cm7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm7 \ + --- build --release --manifest-path examples/stm32h7rs/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7rs \ + --- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32l0 \ + --- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32l1 \ + --- build --release --manifest-path examples/stm32l4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32l4 \ + --- build --release --manifest-path examples/stm32l5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32l5 \ + --- build --release --manifest-path examples/stm32u0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32u0 \ + --- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32u5 \ + --- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wb \ + --- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32wba \ + --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wl \ + --- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/lpc55s69 \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf52840 \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf9160 \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns,skip-include --out-dir out/examples/boot/nrf9120 \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns,skip-include --out-dir out/examples/boot/nrf9151 \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns,skip-include --out-dir out/examples/boot/nrf9161 \ + --- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/rp \ + --- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f3 \ + --- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f7 \ + --- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32h7 \ + --- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l0 \ + --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \ + --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \ + --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32wl \ + --- build --release --manifest-path examples/boot/application/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32wb-dfu \ + --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ + --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ + --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \ + --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns \ + --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns \ + --- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \ + --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \ + --- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wb55rg \ + --- build --release --manifest-path examples/boot/bootloader/stm32-dual-bank/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32h743zi \ + --- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f446re --out-dir out/tests/stm32f446re \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/stm32g491re \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/stm32g071rb \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/stm32c031c6 \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/stm32h755zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi --out-dir out/tests/stm32h753zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7a3zi --out-dir out/tests/stm32h7a3zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/stm32wb55rg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h563zi --out-dir out/tests/stm32h563zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u585ai --out-dir out/tests/stm32u585ai \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5a5zj --out-dir out/tests/stm32u5a5zj \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba52cg --out-dir out/tests/stm32wba52cg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073rz --out-dir out/tests/stm32l073rz \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l152re --out-dir out/tests/stm32l152re \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4a6zg --out-dir out/tests/stm32l4a6zg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r5zi --out-dir out/tests/stm32l4r5zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze --out-dir out/tests/stm32l552ze \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f767zi --out-dir out/tests/stm32f767zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f207zg --out-dir out/tests/stm32f207zg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303ze --out-dir out/tests/stm32f303ze \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l496zg --out-dir out/tests/stm32l496zg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55jc --out-dir out/tests/stm32wl55jc \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7s3l8 --out-dir out/tests/stm32h7s3l8 \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f091rc --out-dir out/tests/stm32f091rc \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h503rb --out-dir out/tests/stm32h503rb \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc --out-dir out/tests/stm32u083rc \ + --- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51422 --out-dir out/tests/nrf51422-dk \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52832 --out-dir out/tests/nrf52832-dk \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52833 --out-dir out/tests/nrf52833-dk \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840 --out-dir out/tests/nrf52840-dk \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340 --out-dir out/tests/nrf5340-dk \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160 --out-dir out/tests/nrf9160-dk \ + --- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \ + $BUILD_EXTRA + + +# temporarily disabled, these boards are dead. +rm -rf out/tests/stm32f103c8 +rm -rf out/tests/nrf52840-dk + +rm out/tests/stm32wb55rg/wpan_mac +rm out/tests/stm32wb55rg/wpan_ble + +# unstable, I think it's running out of RAM? +rm out/tests/stm32f207zg/eth + +# temporarily disabled, hard faults for unknown reasons +rm out/tests/stm32f207zg/usart_rx_ringbuffered + +# doesn't work, gives "noise error", no idea why. usart_dma does pass. +rm out/tests/stm32u5a5zj/usart + +if [[ -z "${TELEPROBE_TOKEN-}" ]]; then + echo No teleprobe token found, skipping running HIL tests + exit +fi + +teleprobe client run -r out/tests diff --git a/embassy/cyw43-firmware/43439A0.bin b/embassy/cyw43-firmware/43439A0.bin new file mode 100644 index 0000000000000000000000000000000000000000..a05482fe957fbbf27d334e9b5c4fa6c53022f0bf GIT binary patch literal 231077 zcmeFZdwkTz`7b`7%U-id77}1{Bb!|UNtVDS;SvOOvkM;*f`*G)Kw1|ES6EA1G10G|C71mGn*F`!vjM56J?Hn= zIp>ciubFvf=9y=ndFJ-aGxJ$O2)RF*kUdHNcN{4t3u*oTr~c;Kmn@m|D08*CU>YI+ z&ix%S`5Z2b%Ev~?oOy&?o=J!o@b+TJ4e?&U3%_BA_j*E3%_8K+*@Tpp5K<2J_FO{l zLH+{d|GPT=cj=z+zvu6NSJpf}R_~J&zOVl=z^c*oE9IA$GGz8m42j&#kaD=U0befv zz6{0lR{V4PO4zPsGH)YR1@;?f1&*E2)XGPA;;lb&Y~W;)8_~|8b%#(<_IDFON3k; zA*3~#AwLDb7p)8#9i?g`!5>Qqk08nsf@yb_?HqGY;Km*2ah$z!sAUej7Az>j)i~NL&Jecs*i@}BhT-p;XfiH^)&o@#J8J< z?Gd|-7l<@9(D)~jWnLP-7kO8v^Iqf)8SlM_QZDyiBp~O19$75sw?&?i>9<9Ua(ZiI zSPtKbd?Dw*7TF}1I~G|j*V7xRm($;lurgkABqXOdN2*7|VVRGn2$ADm5m64CBIji~ zU6FLT9q&gr$?3m|9F@aYBmXVado_|E;~j~tm*WQ`Z8HC_MUv(8{gJV9{jWw|lk0gU zG9c%_68V)J-y0F-_}<8aa``=x4RX2NkwQ6tcjSni|7s*zZuhGZhaCDMf0pUK68VG7 zpD&Uqr*}k}@6zR`9qk@0s$ej?-Vima34FGSv!%e@e}TQ0XN@~hGOWpcf{ zBBIQfC(?BC=Vo@A=3a znf~*U$7MctMApi9bS(Vm?>`dwj|BcBf&WP0{~Zamr@7^G1r7}4O;Ze31Kwng!Fs?S zOo_n;z}B1?Yy{jfVY6H|2k?3278$kzPEU@(#ej2)W3UbIhC8>)IHiE!I1+=G09LEF z$>}Qqcf1>eoq*d~W3UUb{zMF}2E6Ix7`zFvZgGsh8}PTCvGf|i+s?({TEK>{VsJgh zPkBnNYahjFds>DM0G@wmEWHu1{?!=GV}QxrSb7`9|7{HZ2=Mt)H~{$5w_@oXfTfRO za0qbYyD>WF0l(26OBVs>d=N{Q0EZf4yb%o{$1cZk)PRf6#dy;L-q06=4S;j9W3UnM zraNMA4q$P#EmpwuPQ=oS0b5VSU>jh=#TZ-)c-^HKyae!k%`-9i1J)bWtv8gTNW7`zGazRnmOH{carF}McsF=q^?7O)`{ORuN+)v@$_fZzCAOb!PCulObg zHv;}!I0hdB+;$-bw*hVs#>##~;r19D0Q}CMV{ixH!Oj>Q0^HUWgU50K2U}+SV z0N3=!(utOk$Nw6G)qp=e7lZYH2mc;}4S;JW*T}Lm0)8znHimNmKb{bSt$@`jF}N7; zk`*z08(`zg7+eateQgY00(e_h3|;}ar#=Qd0lS2)RV7t%N4ct1!crmO%|$|qpnUU$ zYm2MIHS#9sdcW$M0d8}`n_CbnHY?xcN*Nc)9SOLEnuW=YKHOU#7N^ z94^3>X#SY~9Y2ck{W%H6@_0F~&B5AeF3oczk1f-A_P`+v2Xv5UsI^iFj;#i#t-R{EndV$v>QT(>RK+HR{zwB2Y|2A2rL&IWAobAuya ze4ACWLhDL2C)pA$;Q(V{0(`;n7wUrY@+4dN;{CuU;(L-?AmSgT&BB*oEJWHrf^nH? zE@83RW|(x&wi|hk(iZqF(pI74i;}V|lYPnTMYgi-E28EUdrDxb>xS|a`?$au*NpNM z+o|>X(0+SDpiR()<^U(ft_~E_(B`#Y3eGAYQHc%$qXy=3V=1*4WQ3n$$DF0+d z`XsZBfI}kINj70xnc7(@d7{4Mo$!6gt4VV*)eEh8gepdSp$%F%wsl0c$~gydn7Nq|Fg2*x2E`B+_u0ilk< zdW=`tjcDKA0=EKgHQZu2{)Y7DOC7SpG)~3k27-Sad zZ4m*3RpMt7sbvF^G}UxX=Lh{-)g22IC+)&a;XeP{ z!ZsmJPzxoPP3^(CE$NF4!Wb7*FvdAOM01v%sdJUn zB&4{ur>b(fj#aKtCEmz~RxNoYtY%Z4w;Ob&#FXM%DX!aH%M9wm1xTrMaxT=tZZW)SSIIaH26YkV ztaLe?BsAYijK#;V)_Ig<*-X5k5a;*k%gs)Gc_rL_xN~skHHgFQhdT#nUW+)~ezc*bR3P2tE*!R<10PL%Fgu^N4}*6=r9}o6OZQKw6oDA zL?cbn+)X5Rc2Hff**z&$88Csz3*GAaEI}i>dQHNlvPq>1F|lV-IhmAR&6q9Krkgb4 zjvfmnQxr(uMDnbGz1x4XT^F*rG8T1i*SoZ0bq_Ox5v+bgxxu9r4|nfRdE5R?_xSQ$ zm%+uDl;S_S$A^+{F4*L4(bmUZt@U@^TDjKzUUg}F8RetBo1i|%TvV+Qm-UQk6pw$4 ze)L_gAqC4BVnjdAM}OABO@-Tp__B%H34>a(KDJVs5M%Yi{4d3dJ&oD}(a5}LG$KZM zw0a<_ZHV(FdQYY%G{o;Y(a&hcc=ZkU-uy|b&U;oOTi)we#Chfyx;iAHXNSnLWvPqi zpERtRpPVwupxJd+`r9k(7O$%?xw<5>q_Xsg;X0Sv^I<=$`%Fr2Q;3_o)eV}VC+=rF z+|UybsEQi2s)D$oCpK=^stQ?;+FYk4J2}rvpL~RS?u--o&^Xte(xi;1>|8KaNIUGa51%S> zr5+)kS)~>swN4@GyXU%6eP5;)m7Yq~3dExjRp2?*w=!*dX~SsBNLPM&nyb7yb<=gt zE4TM3CdiY$Inaa@UO=~dRo`zOe$6D-0 zYe{XLHWhpZyHd+DTpIDQULIp|V{fj&2_|7Yaz5zFXgq#i{3U6nbDGXII@i}>Y&-zh z1Q&s$bN@l)7v6&Vn37wT--PgfxC3yX!O<}^fY5j=LwLZY2-AU2*9!H~`vnd){j!TO z9zUL%v8lTEKyle{UxziwLVPbxPG%O%s2FSl#OKXv8^StwO( zQ;LgxU5fGTI^^zmX4PoKKlM<0#kOeVr^i*JsjuIy5#L98G;;fQQ-wc$QG9&$V+{G` z-6LnDqnS4|rg*{XB3YcMzeuvwCKY0wsJ+PMGlD*->LYyepnIJ%V0C2;y4MgBCw?nU z6)y1;PtO;5J}sok2i722*B)m0;#M;0ZN-?=R#(X=?mwjYqLEjJX0}*exq`xU{Jdo2 z#O`Bcl2MS3Q)M!@g)1K;7+fk){vXG%5+E)A%jiNn@6A!O>w~qNg$pjkDv>CD*=cs| z59`^)JiddcV`-sA{J4R$BnEAZ&TTM>4C}FLf@?P@i#%&8H&lpG$XCXD=BFE?kw1Pn z`{v3G;i?n;L|f>ZiE;8+=l%^@F5;=&U=kS5epf*$>pABlQ#jAL4GN5bb5(p;&k&D! z{nS#%Q@OsP&!=TQ``5P!oG0O9g?M}C{=50`RA8LDdlE3r8;%LAoO5$$zbmVhcwRW4 zKosJ_&i*QeOev$3mbXkL70O1aO@ce3HNj^-hErlMnf=^V_p{7y}n z(eSfd!ikCMFr#KV0t@~YEOHT}W6)js^mzlhA_3TzA+uncn>>y&{wl`%F}SbcjJGkQ z0`49-y8rAz$hq`KSqm?40X1YAC;sjX9T@^7%~})z4r#~F>uqsj|I669K=;b}+y>gk zM-_tFs1^T52QM7(R}3y$q7^^xa5`)KBnz=+3h|8&#%!)W?_#qmhNBxRU94C(LU-2t z#8h&{r~NyyFjL@#cwvu};|xesw-7TM`f~LcKU3s^&iUnPl4}z`ci-$^nXW(zxp2nK z9H=tzCSHu3RfKs>sqkQ#LS#CZw2)~;>~XhC?-VdTX08vopSYVsrNTYP4|k9pBEIM@ z74Al?Pmb+G%!Sywj$dVr%@*G0 z{uQXMM#)0r)MzcLBZ)@SA`u0p9}n*MRQ;ybSPxj#8Q9XTb3X z;Miw>pkuso6)2Y@*9*+Wz$^po0bB}rA>ii$&j-8!@H2pIfE|E0cWe>%wcP11DR-`C zyr-lOtn~{?&P@TP;Wps|X;R^$P>#S4Y3`?TyZvkeYAHskJ3(P?uwEioVwoKv(`E)c zB;IOq^5(hDf)HaI8)A+65N}KfDU7<1(x?rojOvius0>YjPG>N(p%fztr5Y#unT9*9 z^ChEI6C&X|8NPorgZfZc_E}1cOxA|(6B(rne!P;0pRQ!!k5^jhn)eK6JoANB`1}jb zc=n6&#@;VT>y*OnWColKZZRAo?)R1>Tn$$VcQ4$daGT+tgWCnS2kuokKb-!*YRHEe zx3{(!2N!kz}{uxp9yNSb<>pZ)xt1$L z2NH%zPCNKzFoNK+qLBxp1%g7b3ux`YD+5f{Sd_wwG_ZY${q^}K?LitRFGeF9qLnU6 zwNSv7ig-(BDqa6Ki$_%%4KfGCG6&ItCkK>5cC*o}J;Y>DIzJt<3bdXZqj?!hVUAD? zF8?z4eLc4hu}8!!^}G;`6pz-EC)Z=_OoJXvTY3y9Eu~NcitC5gyLf>u^pGW%iViw= zxS|G9YbooPbTB%QIS2`0wyt+6!fK|DOg_MidwPLoGzwJ$R|pvl^1vtxHbl{D_>;1)$7!fl&spN>5)@jU`lMx+Q5H_L}(SfQ#lA{22 zmT;Xw%+biB(F{LouoF?)X+>&u;MqYcWvsrkq|`@q{LumZ(3KHG#~qy|Z#2dWpL#hh~|S7myU*?j!<0musJ&L;2<-Zgp`7%VK?k3mIKj& zTL-PeRJqmk6n#?S_o*Ja9XWMzavRie48g^&7` zYS5b!AG%IZxo(m8kBRwY%6%&wVZwcJS!cFMd~T*-TkuVgspsbuE#Yq{TW~f=pr?-f z5Z zlBL1f*+l+y{--sa60aw@&VaIhWZ>r!*xGVd1(__nplVQn z%kyDH*r??p-#<#L8Q>DW|0(VS;ArIAZ`;C|q^${-7H=0#8TiA6_k>TQyX?ck=T}~c4%{1##SYg} zx!*M!dqa*n(QA*T?64iFedQ3%T@gN1dl+*1_3-3J_^?{R+%Vj9t2CKlhr-J3ahC~) zd}iSdV4NB<3x~ajd`AF(Hbimo^5R>Pr4T9xCvN1x#kOvRdA&VL@Ev$w(pg6aOan^= z?!d^vwhLy2Z+MURqLIYt9%;$_s&KS8^HPnJc&TQ9SSq@?1K1(Cm9CY*UNe-LBXn>F znSM8SO6nOy>$@Dr99EO;{Lm3EV}4%RQ0O~A`+<(5*&}aHOl#r}^chYVZcp(@d_jT0 z%<|g5>?ZwNKy#;`m{)?M4TU2EMWAgMrG3Opc7cAD??4>Z$m9{``UMLrM56pl(05>^ zaKv}G0cFk8m0nYz*F0h@HcjrQ=SE}G<=9iBF{>PVLS(b% z3I(+axd$H>^J}Ncee%ye*d_7Rv0mKaUMl3H7pDVD9GvE*uoZCMV5}F9U4D|Wqu(Y& z`lT0`8=26r!ba7*O)KOx^zO?fXE;dL;2WFb!fNfo{<|iulBS^dS6`l$IyYqXGTF2* zCPT)%mr!}#`(3Uu9=idp*NE| z8YuN?-_e%*B|IIyGdXO9Zw^m`Zw%+d&j=U5Perd4U7caAa-U_4&aWlmdtK1XCjFL> zDbGt0tJXZvLwD{X!7a>wY3@tUOT)I2fwv=BLVhhxwLwdH6R~MmV@D!v*7U2fUm?$W zHMSSAf~&E5#0sy*b|O}EHTEoGGiu|*!>m~-f}|P;vta#*AT|TBgNR+J{UKn^yjt#F z#AaQM-Gx~3)tD2pIagyV5xcH--l5oda1UDREdon*zew(7&YLIX*GD7oU$zKVj2Sw{ zf&l(VQh@9HLZ8L<(mgqUm$RUhokgz9sICOV5cJ2eS_qfiuz= zjig*Eg6_3S?!WfoJb@8r)0q@0L)&J@+BUFapiG#Jy6D_l29B?^`A3(Q33KYFc_|l5 zFH!kWuA>9C0fgS!zGA@F4HQFKl%f$7i$^I=2gTx1in-TNoCb>5jZ)0HR3y`*QdkG+ zQVlT-T-rNxpmTll9i{r!r7U5yTVds#bObLSHk#+wQVGrm=DtX*R54&*BtD$1y{6>8Xyh?arIxLXLn8yvMxe>e zx#6&Peu}hh9w%}ftU!VJrM&n`$1TvXY``nR95D^g#SF?7is)F1{PyCsdNwOM@DwCA z*E`LJ-6(R$ca);h%%RphBH4h@E|gpu0cBl^v@V@sUt(~2ibnD;=3=C%a2lc1f@ldX zNwMu1)wJw1P?;Ce_A3=qQ>@igi=7C}W#Fe2J){~agKYB`MXyzME7b9wR!H&5FYnOE%5=i<@T1l z(~Z@yR99AK`^N~O^gMwRN6v6F*QFSR;^S=tZX2xw*3&^-VUmNh>*TW}y*~)-yhXN8 z(K~};e_Xl6R@5?Xu$DKp8YZzJoVY^zNjb2qRh}{WIph+Hp;ZqnWHIh%J_y3lMP-O~ z%i#uuI8)Oz`arZ~6T68qC*d@mbcwy7b!^_W!)EW}&<>m~9CRn*g+!d8yut#3F>{yH z14(_FL`$Hu$|#sD+TL?jtCXtKw&5uZF(rt%b}~4*;9Lfa*#~Rm#jnq{NyPmX1KlpLbzGgM_e9klN~PRG3F1@##4qikbhOmG9=1YDW!3oJSdaeXnig)j zDw&GG4}s60Rk?1|wJKWUlS47=Yvp+B53s*J18i-yi7_9x(YuH;tUmOF8yryxjA`s8 z<$wjUA$75wXyn6@>^`DkW@S$M26AE#a)+=7M%e~3IlGV3&cp~jY)k6XCaPhBp|tv& zqLHo9r|re9N`aY~ng8FxW9jacm{nZ1;JnPkN|p(U*Nl*y>VUpasV03Y)zp7Ee&fZV zv)_+{bhP&QGnG|J^t(}@BmMi)8!v`_fVt@m=W_I^xgsZ-e(mh|UY7CV@2{4r7(4;$ zKlvx>-^(#x^#1^}4Ae6nvz@92AE#)iwjr%iExNjxknKY6EvKbWK?BFMGxG*uM-w+k zj!wilpyM?Wt&hV9Sfx~t&e3>r>sj&=Z6z$hFUjLpj22Z4{&8)B`1`KPDw9Pgw^SqJ zBi|G+u0Km&94+x8En&NG%UX+&ApW{*w)pA{%{$*Xa zU(>D$|FW)6QP--gb*)rh+pdu_KWx`Jxh^~Ea#ST9rL!nrmY(*i^!|3H0yh*4ZXS)e zt)wu8VzWO%%VNaqkPY5RfuFC5ZD(E3)~9_h3UdHf%Zte+#@j_Vm}kieHUkDtt%X=@&cjX(0qAlXn})bRd;F4 zM=uWy9cZG~;v4OW|7Kl|7ss5TvWczBwhPIaYX^ENs}!y)Gx^%NcDa}0(mgAN>FMEI zd+f>oW`+3M53nEaQMhuj&lVp|7cyMX(xRMTcG}XY1v%m%*|FWTp56u3GSH@e6=iC_ z--E7QQGR!oa>d~L;JBivCCnre7ZX&sB!rf<9P3|@se!$e?t{$XXyo_Nd_(6KM7A}@ zHQd8wIO(oPM<1E|RHIuyn{h8P+1zhzSSyzaj^TP(B^jIyoABmv!6t(tCXtD^Jn&5i<`qv*_h>~b#d3254(4y#ao9QUVAs-NzI z&MO@)t>A_0TqMsXZg)G-*HrhoeN^{|7Z=L)UpptyU|s%xPF^eNf~&m7=H$m+Kb(`- zwk-7r*iBsum;HjiS|$DkJM0zNT{}jYAC&Nd6Xn&_;o4dGRri%K660hWIC=S>R)=f( zKK%pid%Bdahtk$fe#mjXv)Cmar*fiJd(1y!C-jndvF6NHjBcCQ>BdV9v7&3Gum$ps z7cZTKY=nG^E*Ovotu0}a$Og6Px2r4J;EWpJF-%)K= zC1#$%DF0Dxgr{61`)eiYxJt)%L5FeM-O0I%?DimY1)8$lSkvk#awqo}4B#ldrVm+Ofj_fX?n~TCwIk+}lmIUC5shYr(Nz zOBFZhUf$fVypahoHx5E4g@sn$0prEoy)yJm@#uE!^YMc6kV>Aj@nYUt*yp%i%C5^5!|n&>+Rkb)<2j@Fq@8$R9X?ou)8X~K ziUXXN_ZiF&J2>&tUPVj3Q{e+1v<~PV%PHPl2G8Z80ZyFK%gVSqGbjGE*C;5xDqp~{hV~|s(c z?Ii2o8ob7I-dn$LGjwX)aEErvt*HWUvALXlhCS#OxI>gL^+k3snasTy@y`WT?&x`_ z4BQrD*0Ip!=ni>h#1?{!!&lGAv61iSU>i6m_wtCJOewlT1H1YZ(6E9AH%jA5o38_n z?N@2UcCM@r4m4=(l&8N#!&?fhw%&91-tKRgJ~i_FTiBCQyWVu~PCJXXl?n9=kDre| zqd!VaIzc5aJ-e?(i#K*V{HdEB^>=OGW!H5WFkUyGHTo6hvA19PNAL3j8VH>Z3Z0P>r~}$L%NCxKoDxgL9>|xX&*k z2D<;?CWz0!5XAkzwfcC>#~Tb#qW=W%f#@l)O)?fZT*TBSO)A*xYLjvbPQag9z*V(L z)&d9o83kM67Z;p>Kd*oc)jqKN=JcwwRWGbOB_+509QTZGPI#toaLO}%58m~4%HFCe zeY}eA)A5hnrlve^7$dQ5-)!Ow7xwAydO>2^j%)(7aABV!dtFNJnqHSJFMEEk({p~G z{+T|VTHU8m>-to>nm(PXqVK5gxxRl<9`z2~{UU6W>j^YHkm`~1SweL-f8VVR+GO{Z(Sp%dIJBis6vI$a;5n{0coPsvyG z`IS%i-9HQ1OV=2-8^BA(+xB;ZoXhT7X>hD(oTm&9=N8x2^`{J5o$tF&AUxq@tGK&Q z8Mrm}DhEQxnk`jZ5pG@ce$@$tC)UvOC~0lH7jGY7HG5H?D3EMDy^k$taPyqJVXH*i z5H8*dxgngo6|(H9=8KNnzv`ac_m2n0iX2N{gJ=UwC42#pMa(lUF+(5T!W60Jc_JpNZ9|+t8IT$an4&iL?a~`rhYdGcv>mS+*rDH6Q!SDe zZ|FsO!H-k2Lx1(}GZYB#hixoDE-Mxw=U9O;gCF|s{q+ke)(Pa<=Kj|3;}m76K#1km zFSO0FU4LW#a1ZcCX(so6Aic4)`Z(T~<;q6tHu13QXl2-FV#V)z zh!ObA2T~*4n+$zZ{n-M1V{{)w6b`&8M%yG%Aw!3L77zc~hjBY1B%@@g$69q~*+y51 z!Rji?WctN5qRZIF>Ka{o^e7=31MaueU7KEjj0tb+(>Y&|I7qpP1x#Lmr1_bEj3tJ> zcIz67yU{sOXg|ddZF$IARUuPeF7cX(i_&g%P3dD*dfO(O#xVPCv5L~!Wc%HQjm}NB z+4pR+RrHC~)-`Hcr;fkju)Vi~55K8Q6~e*vYTH7~dp*nVy+OPO%@ zX;5h-KexTKVTtqSwh8zA98`uL8n@=A6uZQ)=C5nEp9Ph5I!YzKOrH^>^4>?3%F}%+ zg~B%5_Eeurxjp4{)%d$9t%qzcg4RQ}@%KFR16q{}dO#~#nNgKqwgUWQxX#Ml>~W?@=FBiO=;CqZ+45y5~eA&yR4z-~6!aIoCQF(;*w<(%Q9HCz2Uk z6^k!dxNr{Bus+ouAHW;xu%1*pvz_~#4cO1{W=?*g2pcBeDUOG)K1$y_b-UcI`i1Jz zSXxyq#uO`ae64J`;Wla^hV2+*P#$|OI-{bIZ=xh;QGm`Qch-KxR%yeS<>?3P$66Ia zu?1teU`*#e1D!o2>;3SJEMsNGu|3z$B4Vakv^>d*VZI+OW?3=X-7Ym|wPVJfB=)}L z8@mZ+Ll$O3f!f3movh}EI_{s65smbIhnG;8kCbNpLMzU~d9Vdb&}IV1rQ@m`EiAzA zJGR>R_6kv4PD5D8XiMPSaM%qN>V(F|TG(aisZt22(W-6H&GJ5YHo9biQc#86ilnYA zyhovS+7g_ODYPz{BNz`Z0(58e<^^xIHVJ>k-J)Af%$krv;*49wS&sC^A&K*JGOW~okJ1vor1N0!SwBlOHo z+x|JADp}L+xeA>bS%S0$kVzi&308ceOE2>;9!;dzr1^;@q+S&3} z1ABme9dOLo(h!aOBFY|k+xrgQI~<8Z^Fk^4U0Q{wapK<1!VbLRNEb5qkm;@f$#(@T zego>+*-da)KtlCGDL8v|gtlpQz%j8O`mi_dift@*~m= zBblmhO&8J|7*m30>|st~^7S4LcMQaQM4Cj7Nb`)0hxHt=x3}`O{0l2VeVGtJ{f8~1FHZ|Cbr@9wa}=F$O}4gZtob{ zIyu!Kr|PgjmRw+yuB6VlQ(T=mxpx3B94iKYvIg-&o!Hbv5Vs9WOJngvqw&9u#$WAG z%lTi8#(kst=SSmpJ>4?BACJ;|wnt}*QM?8GZ|%t`Ct1`2Ya4!NS&W0fX2FdlxoK8b2n-$%{JiXiqt{q+#rS zpRN-RL6Q}Nt7R^W1QtD5ZPSTGyYW59n6XL8Mt(M)2OP+C=;8W>}I*>ub=PX@AB5nyw*!EGisw8=z5 zkDDmR6n_@(0?>Y|Y=6efN1d2>hGc2+&P0Bt82KO?i&5)MG;%shObYo#nuOS&qV3Wq z_qb4pq_%GL)7N(-uS3!qb*->$3Z#!EV*`b@TD(7`-yqd5?2zlQSE5*LvKTGWe29>g%xqdjv z$!VcpdLNQMj^?aX!LoM!05~M5A+vjrq_U=gGV$uf#b+Ov&%;V_%~>jq_oEEvj(C*X z>*Iw9sC~*=`nGP)LSCReR1EIkNXO^RurrgLbgb;&7$+>GeO5%rD#h?^ zyb-xH?=T!@vSTwtBwXfjV8E#Vt2QP|lw(3Wi+O%08v zPU-xxY5Wf`Gk|H!jthO=bcMsv#;92*j_Jfp=@65>M@p0>#zJDvXU^SwHm$UH5lj|YJ|jS39cU{c zAwBdlhESiEeNOhZvOgX^UFq)=Gvv4uz7g@+c*S0;GM|2Ky{`Ecm#M1KrBR$1AdWGQ z&Tn;yEZ5yM#(z>$GfW8Yf|(~x&!5-f4QR2I56@$ggong&UGxnKwOd(jkBg7K%t7O% zHwN1q$2ZhKyO`i5CS{$fPT|=sjD0z2Z}RS5>6XUD_{Q?sMWrzy>0cpviiU|@<3uL$ z7~YW{lhnqzKpEjY^o0fMzuhdFIi)!yPnb4pLH)LUHUe&P=3?L5h{RPJFnF;uIo9 zEvG2O-Ir*}G^9j3jQDDt6)$(3mKHNV6+i5yIeAFA&|z_Tq`B5K#HS$M*D+b3!8 zv?3{O(@r4~sqPs~t~6K5&J!!twP~9)b>loqHBP+X=0qLWpk^)V);MpHC(fs9eQ%&G ziI}^aQa$XT?ScJ6wukPvJNgw|fspQ*y%(>mI%hTT)`Z%v{C9~?c zKBBTHI%atjd^;~DT2C4h>Tn7vAeQ{DSq)rs+M}!`Gq_7?G+k&>*K_wIiE1uAILrI& zBU!GXbWH!OeZomDZ(L_wUQ$O>RV~rC7x|dH#9%@LV^kL8{`CG}JP%@iy=1?PNoGld6uG3+vOEh9+2{S&cWz@yQnOg>+5BK3CVX%{$D4 zPgI)4OwQ3aHmP}QviL{`gEg;thhvZ|Z{9kip3hMSje^=cQ_7o~EDrY^k?KiGsL)$f z&w%2a(yF48iG}reM>Mv^Uf0A1nWEQ_Hp9D&SvVYeAj!sn-y{c0S-sLWwad%vOuk^T^ zfcsL5aW512$PyPU5!Cg>lqat5r4-ya>p!U?W_75!s?dwuo66=j!7#(&%O>-2X}9qt zB->PFA0*3~)|gy%(&xsvu+vJ)GU2#XZ&Y|57Z-IZ8phVfqlbR{U`{n~s-ACPf1uvJr+v2`ot_ zgJ^p)(1dSRE~o;Td2FXKPZhZJI#nLunI76#_UvsW&(KBn!*Sg?gKk64pluUMY}5qa zLK+vOy~qXi{!--ep|^3zt!@RpX#%KTYWg9?w9pQj;)E_TeO&jHLHC3ygSLl(`#;fJ zh`cNG06+Yuv|w`)ON?zpkXj%$_!G@Cj{hna!IGDwC5V@{ObBGk30&^LY=kj z7gtK?=r28y84Tr=_OWV1>!LnZYg(^vT{P&vZs`X4J1Hb@d5{rp_xmB0n*)^oE-Br_2berI zSUO15&~{sL5T=giD3HSi7Y!0E5jF49yH@4Xd2)<>7wyu2#92;b{OUEzDW{pjY`MjW zl;aDphJVB`_-%_y^wzpOMc}7-T;NW;GlT49>J;nQe~Sf;F z3O{a|} zWI|(n6Y9+CSN^=jpV(03pWaflh|)ZD8?mUm>3&vWtyfJ6%x)f6Z)~Q!P(o)SG$)K< z(Wo@uD!W#J`$6B-j-l7h~S(JhCcsEMFK+uRygT&vTv=Q*3MeuFq8CF&&8<)Nw8+bUMwaz#AA<3te2&K|M;D0 zL9JYRLdR;9PDbhVDE%x->#mf}8XJxl(p5%}-W%VbNBQyf`r0Hf#nrBBe9`;v?Wu~lBsA(K3Xy@K-Yb`vRi`kzRA|)|Vs`PpXz1dx(YS4H|j;_^UnrLzi?sKR-J64%RCzr;j=^Ubw+p&x>;Z>Gdhc>B0 ziC*na_&#l&B9z#`VrSRZl91Mu;l&AVXM(Rph-)y4Y=`ML^hPbg7uP^u%EVp1()4=R zM&>}@Rd$Z|t!;_PMBOm~GEtmPXi!M==A~&B(%gB?Rf*nJim|76`e*q}{;`kpc}yTK zFFlBz7yHJ9`dv~NS`KMpbc_jYZc$zx3EHl4k`k}U6S-s$IBuo$fUx=;q)tt)i)(nz zk}ON4TcvN;dN;Q;R~dyn6eR+CdV=^;Hy4Wc&1}dNpE#@WEpaD@v<=JsC9oC5`)tE` zNtxoZUX9n?#9B+;alY5`uxr*kYOzgbKfhV5quN{6!ZTbS`Pc z-oXV`^|nEGwW?N!T2(&m)OJ;!sv%qY*}Pfx`etZ|g-LI#z1n-mifT41$U)0gj%t~w z{8YzG?wIBCJYsU4l#Ura_QaD-RWFgm_F47pV^nXWS`rtU-w8m=z;cYv$(j#&-9N6fBM(lM*gKJH|5m6wcdPlx<9UM6f{*^s7z3r}QI z>xrK&gsuVmgTXtiVVd~gT^g?vzYq0#cnL|`NyOb}p{ZE2rM!9V(tJkkbwY2LDOu;a zn;d<_oDGQbnFIc&z)gnBhMNRuhBLus!5QH)=S@IQybV2p34R`G!0S`0OXZ0lcHmrE zyHti-JCqIT+9YUq29GkR^o}e4s3{FAA=i~0Qr15xW^}Q|bbeI(udGfCRyZm@w}Z-$ zG3%h48|rnnF5Xgn z4EM~L;@^9F5l;HeTDp!vzox54ckdj5t{2qMNa=`v;Z?-gEH>N>fpWr1Prk34lge_X_TQ29GLarS(ClktxCUBoPms3avNPfcO z8Xp_hG6^_cmtRSHCkg#b#5c}p8eR`45Dxh?{@R)3dV_Cs)3O$2{oX|{nWA&LR zuRctOLTPM@BY9_L2)u#!iQql{>`L5ofpfsBv$Q=l5B9CI3d#PCLMnOZGs)QanPh;Q z3O66l+SpvRN|AUv(Oc!uIZRAuiOr`P_zZ@={!VC^De6$#p`)|$!FkE`+0r`QcrVqZ z;sOPut1R2`FjiT-jyAH`SMa-cMx_rc+34vYE>P6MichlR^Vp7i%68gFJ7aDjI2nc1 zX0_MgfNmzAA}Ag+idQ*`m&WV`o^gtKy~;t?j0Zc~nilqvDcRE6#}j>2&(#GA<(+A~ zh3&XC#szft#0E;AHOJQva>nW8~SE7*Kj+n3KiJSfO1yHFh+bGFcoz{7zk>Z;?M9>!=dv zz7p(gnc~-7*;27?e0`~3@8xmIjO}=t;^#=8rK5ZI3Z*UlYd3yfJdcQn+~2Rd#PV36 zxoRceH|%v6BG)E1xDznmOR(EyV*bW8Xlf7pm0q3Ch+m}m-5E8$v>zkG{xfl2Ki*Bh zJ%6TTe>_dr_2?d_^}W?XOKU+TQ%vvCG%OWJKNqL>QM#GpK<84}!S$e_#U7{Y`9lgD z>Xjxn#_i3#wBtgyR6R9YTJ-q$9BS%M7#dO=no{``*r+Mx@!~^gqB)9AoNXu7;(G%Y zuZyAMZ=B(+!}rc5T(h)#hR4q0)@_3L)fw75^KmAf7NGiurK4SPGUL3f1al~slhix| zzwDFQky;w({ljWlmi#)O(mP%(ITP2gs%DEp7ov40fY%p0<9!NnV8n`$DgLJ0oR*w9U`1%vJf7dvlzgqiky>m>XZ(t^Q#KfTGh*m=t)#nkDY%BftCF>RGz zGglu^el%I+6BoOZTjz`Jb(7#0JCh&fMKyor*O7qdfW24bxg994oSN7<_R*7gfzG-d za$23F%L=IY({-xVtuZY6MuOUdjyC#)8ygftoMk)S``obCw%c~$A8y@T9;b}~+`xQf zQs7k+5&L@zux3zbZO^4g-+r`AaP+Zw@5ZrA-fPlY>$w_r>o(l+(UWE}&S&n8cv0p! z=gtvs$D45HQ;$A6_fb_?7e`B}esk_Al9gkM56@TQmP0(=lmBZPw*mfLk8GFE(wVHn z5$R7>BL2E3KKv(cx`%I|wCO7Dgl1i>eg1qYu8~RBGU0Y1|E15QWA1cuaVLGz`LtWn zU>D9b;a6kA#oQc>LILygPgeTdK_2^rz^aLLO=t2R>VVd|xV1Tm`Lwix0e8$& zvE|H)mebO@f`*y~g|dB3=z)n1O=m3N#(8@OrSmdwBK}%q7g_eNZrpj|-m*;CCap2% z2+Nvo$GaAav+t>Yrk>=ed>ZyAll1@*7xZM7$A>qlt)7XvL!<33@MQm{@C70k_t3Jq zi7*ul&r2JvX}gFxt%q+oE$vdWxNlk}SPy?DHM&2OcGW&Fz2Nn~csX4^U@ZwuLM*&_G<>#zmFZvDy**q|vd ziUalNg~9H`12jg&Z}2`~LlJ(dW$5;5b%@TP>DC8Y%Pu-a67k*c zT~f)+&!p#SyI`}=yXP+|b^FUI^=ZpJ9CmMZE?lE8Hf2dS;rBgms!EmrC$1xW5CV z4gEpHC%WH~rdbcb@^ttSGroDN!D|8h8b@f9ejy*ADK%1@mB-F83LEq8v0a#*lw@=G zUyxQ)nu2nv1n)f^T7d)GHOIV>reb4kYOC?~*po7%e;KuDnwA zp!Ie?YPYWBRqcl#xtjj~%{RdgrVi^k27B=?B{ApX1q1uiZQ>`rZ-?Em@tl@wt?9m1 zYo;}vz}G3+v6U%)yWH0|yMEa?LHPYZn_VXaCG|eG4);x2{DB-3-cE{_y3AF-A0XwE z#i#K*WRnN`#^;G!xjUWv@m&=WTitUvy(BSid})>QnnX6gy=j}o76>>SRywI2y~Nn$ z_egeYgI^fjH@O|}{qA&nq#s)!Yk?KuNe+7PBx@(W8KSo!v+$dJTqp+lyObsh<%{jT zue!#HWUUFOURav-jAfTJLUzG6p$u>qO~BE@n6^nHMkWN`x=o5&ISLs88G&itir?P^ zw)1E7mtrPk#7w4diVcG{)Q(@{6y1(WrvbMoTwp(qar+T7j>Z@{W*d#Mi0#KOSaMqW zg|Ke{rDqWJkS^#9x|2X>&|MLuN*<_mel%cH)At|60maY_)tt3V7>{xFr%q~#;{skt z?V|y=8lg267Dt&El=4cfm0GqF0-Ud8SkGV<7TAW>_S=8KVLfUdvQ;Fv#^Js4>rzi) zoGdM+DPJtXD_knUaa}hE$AlEvlrN^ohqIyCy@ui94mG`Wn1$V2bpXp&q9b@^VJonv zCH}qO?zAQ+lViX(G-3~(lM=|tJFP-i;aqX7=M=`z2@Au4a7-pI^ct$=(ZOsvQT7}W zRuwmt7KA7^x)!TRr=VN1kf`A^Uq~MAZmZpbRU*yUiT9SWdBKa8jWfhi>ygUNu7xE% z2Ojn*;PPY4hgWYGw5Bfw0&PzYex^-=wut)ehm(`7_^r!W(8NR^repLj@B=g=HJev^ zrXvQD*0+c+;lICLcB4JYC{JE~-DP$ze11m2Q2i)*y*md_m`rBVHM7_qndIlj$htUq z_oR3quY{o!nK}2^0)0@~D=A3s;R;gw-il^$tVI(}jC@(34YIxa3Z#)+1q%56G^x8w zIqU(tnd$wsf=V=i#3%B?`>!=s_Lb@k;4gEG$w+O${ zu_mSk)KsQvz(hQV7>Q{e&!PX(dQZhFvS};_DKLFaKGER0o+GaTg?CnHPa8)52yfYN z@YxkbINyRyXc>eG5+A)&_i9)|l`5=ztWu8e!AcHzCAtn~D($=&r&>pOJC(0eloaK2qkJ5b z?&_jrlC(ixbV!mWR4m3jmGR2$6jeed>&Zwj^?-{1>QaH~v5=SWESY39yFrX(A2^bXJLUm!P-@wlxN5%VaxX)}W)8aBQmb4RyXaG(}Y8(k&-R$gn>H)VV71e1q~%U0XS=223JQbEJN+L=XgMo;4Aspy#wI_U33=35PO5s?NIB}knyY?6wHqU93q7}n;<$|9ZiL)t zM8t(Cp@V6KYzWgHx)HWg)|CE>?ZRIQ*P{GVz0Qv@KEsU^{WICRjN+lWa-RNZ{#2~&A z7T2yPd@nl!@e=5x7n4~tt;w&@g0HfhguFQ}@+^=;&yfYdNp2cz;N3S;bVJCK*jGRf z_Rw!PzH{+AuJ?iy{78?U!5jhPKCw$qVavQ6{+Q}zz2|8uuW)nDkI@E`tf~HX;n8`L$PluxSH_!{ zqm9iJ*TO|J)rSRTGnl6!>;6PCKDCr(xdT3u!CI z%#6BgijP6IMbgad%BZd6rS93lBbH$uS=Cj7)&a{6`fLy_r6>RDe^h+R8jQjALOXN3 z^g}_<$-52J9q2iZupQXaOpdY~KGBOAv1l!N>P#G&8$QMOQhGYh3Qnx>+M+K6o$&+A zmIgkD{OTMX$LJ?S-}Ml$jey^>sBB8lax>ocY116A`?Nj;3}&=gNqfN&>z>ko_AsMp zKc6coM68r7Xva7m)Me-M$d~7&_Z!C02=`om5DD}>gSLS$z+6DgeXwks5WBXPdN$I5 z@p*S?J&qQT!F}fCIF>@~49CzrHj*9Si=BMAh6OIknqC8Mbf~9h%H_f$cd0oz<+206 znZfDTK1^|!>hUW#xe>KCb`SM>UZ!S#@`Ig%+kDB8~B)NP-cBLvJkxX5t~#0p15gvqCA(%!h^3Xs@|1 zvc~7ge2jp**WbQ_m7?Y||L=sx`hUd&6d0os1+J=?$^{_E(0(LUKpSIeS5caZC<2o`1(#9rXLh(I}%!%oXnwUrN zaqVoqr*roSw_OQt=mV<~pygJsO6E^=Y~xdQN%^6qxGx|KUUjk~eOG|;Vl^jt%K404 zYY7QY-h;8Aeo0{VLIY3?+GXF9)U16_ze~5LPjIwrcMW>~tx;guYfBViQHtX;G zdz9RFZYcaFTwYRFd#2R1ZQ9*f?v-R8lcPLg;gj7epYntayn#$av-1~2Kf0vRQ2#X7 zw`Sek%!O0;_dLL>_B`lU?-_&ssPZIxA0u;cFQ#C9C}qzd6!vI$13W$!hJ4vz;60fO z)=ek-y9&WC$&k+*xQ%qh{iK0gPtW+M0siK~%2!yT>eB9csnky1Z7AGBqq~QY|AY1T zp8l&9+&GAAlUgjMD5%-!sWG?_21Q~MJl+tJHu>x^%h zgtkz?-duRFmaIYvI{#W{SYbG;+%w9*Ze1XA6m*ZufMU;+{y^rsb)!gPw+`Otxc8D; z8>kNxEcWeB_-TtMLTjOIvD{h*J;vPgZ+`A?tL+I`)@u$^>3QKRSi!Iw?Ynw%&Rigf zB%GpE>1N6Juy)V;8RcLHVi{F)Vnn$^u|u^(jb~TxQtVRglEK&34gP7RTj5r@kBCq3 zLXg@fPWiQafNzo}juEXprHl!@SCc32d2@Y=lpwv< zHE=B~L-bnZo}p!luyWF~*>rt-`u?aDT-Lb|vgFJ4fxBw1;|? zb1V8~`yqPXvuF17eu?{W(VFv|gj%;xfQ$;;^f&xR7w1JDzPcRvhTkzJ5OZ{IorVxWf8C36?u#T=FTrhFZ`gI?~ToP3a zwbf}tx;@<;$FRETdAXZG?BdZLA<3! z&_=Amy!G@<1D_0rvJvE8-FI^kKSI8#gB2Nb3+>a3Z@v;6L`*$FfBb(wahyww&>AG) zyF*&Pc8}b5w3H#A)zO|tWgSkGd+g+cy2*Q3bIB^Rvs2K&9{1=E4*nK%&HetNXbEOl zA+Lr-SRJu!oEuo{?BW!i>U;YqKsOr2URQt2n-!C4~MWTPHW3J-EVQm(V zR@bL@TwVWytPAq=njFkp7UF}vWxYQGxj4#$8F-dI#!jXrd4NnLIpJ1V8CnHammw|t zmNkQ&n<7V7Y06p=EhB*qm#r#tw+eEcBm3bTsc!6kdWJ&Bj8UFgHj18wNV*4#z(ZVj zt(Y(J+B`3iE9dB4Pe{_8$I_ciHC+qDU&R*>rZ09-Z)7fbh8C0RE;=49{uDg_A7d_~ zh3NXv!qTenRFK)-E<7>ao(20m^`|T&zv@x!If|GIDz&QdzEgXq^9jCqdFbP=pAjW& z8{b*I(#p=WKy=bbTDz}`Z0oH6q6zjEtOtLL*;A?AqNih$e+?q8N{Lj$Mojo#<1j6s z^W1blwVrL1=JG#|{}XX}%IZ_7gGzDy=fc-AFy2>_Gv_pW?gk*C7VR$8LWdt%A0CkQ ziOiiNe`_$&*#g|Z#vf;ioap~HOVHVY&Jr#Yog+rK#Vwx$GX%4f&JU}5z335??Hj9p znA$M%Hm--=LT_vlRv09Cst{}mMJ#xskXIqvD?zjc8Pc7!hoY%iXv7krG1efig&{96 zk{sYya)4jSkq7ri2Wxwy3}e3p+;ap>&CQTrn1CSHxipey)_@ET&7K>XkWp1?rbUX# zmL0dTs$ms-|F6u*;9m!9(*ymT>NneN46?r7PMKUb^TT#z?^x^4AgjA8A-NtCG7Z#M zcSbm!yjg!rIBi7Us&|j!$sEWsq2Oc|ZL``9&i+kR)oRaN!N}0{?!!%90X*}0;5H`h zn^q$(rKg6*ju%6wP9Dts8uuED$;<-SD1J4jYc0csB)v zMC+2D(0i%ej)!B5rty~+B_B+1>wSrCedDk;O*7R{v)hJ{(V|ak7JAl@VDkP9GO4Q= zPY|#Dnf!>{4-njakIVR9cuI9>%q>&i60C;Y@p@r<>^fVpE4u1WB&Co95JBH5nTfumC7 zC5IsYTU)t=vve#~!IqbUzB`l6_EeGIb?N-st<%WM{mCNsax6QSr+4MZj+yOp;8jRuOsx09<4YH*l84ZZJQ7cj3M=n}@R${^igyj92D zL>Sn_f0O%-^iA$JqDLCzwL5Lh@pIJ~i1+NS zVT-z}*wHe)g_q~C4;&i$A$}F`zdn4ghG&GZczyVXYZl;|1y!sW7%7vAblt#}V>kTJ zZ;ps(A2AN|S^OFCdo0I2meR9WOB&{~fqESk&pi4=x#=i3y-MZLnou@5bYqxrp??gX zHAn1mIf#%xBa9Wd$C`%uQvL9_EcY(W7G>)}_1;=0&rZ_nhi+tY8T`^VVn=vaH}gs7 z2VJRuqzun9aOZ&*`Oj8R_%w$HdjJ3a%!~i`x{1E+a9zA+-F3TbnF2fMg(c0m+`TmX zHSjk%D$GvW(7@ev;#28N`@!zTj%?%mzE*lb_CY1+K6KFy04d}IxFa-?RR|mcu@_0WpVmC@= za|egAeQv+6w!uHlx7MHj+!qSzSvR~OK4>fJ>gfzK2h86SiJ@yoMETmo5v5n2*B(^l z%?@Q_95dvJdO9}uF!tz@`q%D_(50lQe_(_zC4cLWkI>_NbmTMS;reW!uXdq-8}70j zF$-Iqm2zn(3tc>wpGYQz>1;AzhIo}LuC*yCUs8we`==?TklV_^n0WEf3Ho7SexJd54G{sS*qzQ z@~gU@d$no$nmI8rM(qnQefM^w0v)P9*ubrMvXUitn z8KFIyEj)#9s#Cg!tYp<;NFXUqwf_jT2+NEn*eF&S)&2%yB|RezF@0C-S8K_;H|hD* zhMWTn&8D$?(SxnY)OyL7btEx7tkr=r9~7)bl%K&`RQ7dHm?&;fEPCK;I_D@&O0wtf zk8jP3*~Pn*838Ol@^@if^Q4(Fe3Bj94(X0e$VRq^*~yHcZ>AgQneHVs|IUtRMRYB! zuY^&u_-zz9^~@1=)F8f@p9IG7ENo}N?mmh4@k3zYVn^-AcG2JC;}cn8c;NT>23{3W z?EXKIaeO=f9k!rfD#!3VAD@*I|6^8G49v>Ql}3HFf<$j)Y>q2~j9EO+g7r?8M^Pds zQU_ltlk}6|F!YfbSd-JqGkv?URyJU5yz zxWmGIrc@Qhd!}f($PwYGv9Q?=XW;qZTHX_8LiT~LtVr4tUjgTU!&%v0ON#p9EA{`p zWmNndyF$cSD= zYYy0A=;{XlD;UlcyW#N@eSPf6-oFi;mkg`?{WibAqnvR1TCQ%L280f&BrdCUiLyjZokg_jJXzN%tV^|QgpY1A^hdvH z8y$mpT##WER7bS(>yWF+g{Y{(&_8;ur_v8!Eq_6JhBbXC^fHPT>MK*YFDx(dZx<$7 z&3=U^*>7IK+RV$g3yZ9W{ZyVv4ceUEiEV zra~7eCGM^_Fz?g^j|-!9@II_C*+XgBqAwsVM9UuwEHXQaZY7z@M13416c!XM zK02U?YI;%cRftJ)mK@&)^f2uD@JQ}kjlNartvw@LLo@jJ9D&D z(p`sbO=PGON4xScT8S07s}!kYV0$id8W8a1gvq<3V683;yv2303S z^Hus21~a%S-fqPF%D@Iy4!aB5aY;h-sQUYu>N4F&--i@iT2lqK?nZ4j2DE_^+OXUk zTMism^O#*}qz4!xI}uz*vCfATgF21NXFTWjt5<6G&nwilfb%>`wQ%^l?`h%F`K4qn z?B0Xi%E%z-3shf$S4N^~8$ne4vE>c^93YDHrtf1PM`Oziw&%EQHM#C5XISvb!zbUJ z)0X`-*TRqh_$HP~XScHi`t&pXfpK z6xDV0Cn!dN6SP38%DqZo(XM$U1Vo}7m~&2TYm=Z>rO;~6b6YPmG7}lqgRF#@%uncfIG^o%K3^50`f3+iypV4PW5qo3m!1dp3g2Y(@ltxBNlLu79t`jqc%r_pp9z{PfzxwWmE^Je|khT%^-4zAsYR!-m zXfhJxxbueUc|?P#@x93PdAxhnd7w~$k1&IKJ-eD(^uRP{++90(V+Q$aFGr&;&Bgrc zjy&9dBkXxE-`D0hgYk{Z=fh_YB6e9q2odDqH8wA&et&&+E_`20$+3R0W?-G7Ud6b) zoI&30)niTCG`2y=g?y$VTkn`fD`#mBi_te9*zoxOE8_o3a*|qeZaiS!0GV|-0<{^_ zv&40d$|k3l#P>W)xf+q>W;>WRX27mEh>Sd!1DWNoK!~spJQ$+1Pe(-xhz#`VD^g(&SdmBR8*bygeYwG0iOOnv}(P6gedN zY|nSPir)h*61pCYtV?Fp(X8}e+M$neqc1%l8^A)Dg0(twx50Q zBmAaB+F&QJtn?@nI(GP6wrRnq$mqr)K}Meff)DL{_g zFTwaQk)Ppy<9LmTj_?g#_yam(1Vu-TE546v9!Cs2O8-v}7!N9@;tfx%C_pRUeq>Nw zub9n@q;;HoPmZ$#wJ3*8abm~=*^{vpzgoL#%F7rVC zv|z?s1P-2m4yDg}Box1PIju2T1=pG;1jIX`UskZJaS>UHyh@vJZ!r#gY|HY)!fV!l z?^SA21AW+8WQKO5hqj!uE}iyi2?HjdjP;)dgl`%u-TvK>>%P+s@;3p!r->wAVStPI zgp;2gy52~ygM68>F2gS^GUR=OUcVQaXp=}%mjV9zvtq=x6q+S?j-amDCGvr1 z^LyGE^BZWpzEG#mH^h_5v>WW&s=A zRKSJQo}^rR>`vX`^)w1o>sF(;%5il$Info5D^FnEeu8ry-E(hH{yoPta%-23THH%;NLYg?}zk2dyJ~$Ey(-5VocGU*n?M` z0n5e`IuGDlon#%^i!1bg3pgRB!XkjIgQ@`J+DK?Bn832!Kr$jqs+|0*E2%jPQMZga zN%V6kZ9_J)qKp82|M>>>;UAw)64H3nk8=@MfgxB?Ff6Eq7H=p2YS4IQA4hGO?_*al zQ~Sx^8?t;F>=}$b-(8Lb`Yvd_69O7fZh+3^-lt*Fc=F5zL=NXAhCT!q@@#=Rh#KSU zuW`MdeB5B$*ri~iA2gVp#}Pr;Xi$6Pw&RX19ibJ+>)683GI><4$q-k1V44mr;O7DZ zHS3%e^N45(!5zAveB(j})g)n++_IG3Yhq}7ffVmag7;)6+Z&EI9i_Ra<$tJ4e2qbS z9dnHIgI!RPR&va7yzb2cy!Vi%+6P^iAS_jX)$~%EL&#k#&a5f8*9n?Oy zvY}Zh6+Io!hE2G`cHE%~XHq}G<2^sUK;LXC#S8cl9YAqNEwtq}xjMGEI}Qms2CaL3 zAj?^Bl+sJ5fxY}bR(RPJpe;>x!aK2%u}VM9Fi7)1F<|lC zF6@0&xckl0^ z9Ugb?M|9?~IyyqRs9we7{SlbDk=^^FnM$Px7&}!qG6m9o|An(dfeMU9SHa*=j%|Nb z3wqAU(D@W@jFIaK#8D!59Auw6vEu8r2LrELPkZf_`V`N}0x+v2E4LkQ;(|exv9};O z1Z^3(19&avS)L5U6#k5S0S%2IYwN$l{b=omL{#2ZevUM?Q(=+AZ$gyV;OAERa)v zi?xdC8UKE*3@l~z>ZW^FPl7Z?&tFLI4s3_irKkD8p|j!tUUnyyj^~RQ%q66}Zw;P6 zgO;xrF~rG*8?Z(Wy1xoiNhSLeavMHHTH3&~q>|3wg&5tm{2xnq13xfRjO$!NF2Jk5 z3`{%>;p^hFPHCM+e0P)T{@;4t3>U#(%>Nzq>=}-_v8Y={ zU6$Cxv+BIH0fDJla2&FwXC&+e8LiFmRd^L?of6}-Boxl@(SAUqxi#oLSsvP+$ehsv zjR7s8bv;Nf^ks@=U3X=*BCBno&#CcGBI|mxS~=tHNQOsUP5rYMT?Sx}%kGu$Q>1sM zp#0ZqEHg@^B?G6vS$j^Deoc8>@o zh^_H^R|22m!_(|7!3q{%>*#!0(3jCnXMB2puD7i=qm{AD3n4Rlkk-Hvjz4jiLUOju z=|!py7JDBFAB-k&z7CZ|5gwTB1aXuK#zfOuAt=uU>hx^?K*~%ze!I!w zn4b}0a;62j>b6ooMBhx0qO_BN|44j3l#l-RZ|`ChdTNH}S@1HBK?`w7Bm1&Evkz-( z%MJryewePjy@hm!A4h~hhEMq#OM5U-b98)WiQ`K`{w5?tf{l+hYL$`fW&D{8@7R~{>QcI*B6f0ein0C7@#VtCX>FQ1tT}#uwIM`)`oTnpX!z_ zpN-6LL51vWG5`7&iF+_&d?dai=bV}k{24DC#`$@oJe>oJB4aEDTDmm+m1vbycD}JC z1-wSm(3cq5<9P>ppx^xR0!TA84g0o0pD(MbDhW)u7sGbRzY8&JXE3krtFrQD%no_t z%vVb`c2(8b19Uc+)j%U6sl9$#Vfh6!G#Q8Z&rwKQqAZ|{R&aPqWOgs(yp63cSdvWQ zk;7HhsHedPR8zW*iRf*CtIX4O@>Ja$PDP&m^76X$^(iVTG}6cGywxek=ooa=E!@aL zGFen7A*;#`M;)^m@@U-`9d~h-rlI0%dLPWaDjhs9cCybUyVNA+R<8A@^A)@V$iYaz z#3#GdDEUCxWK~9|OFn^CGF32Hso#eo*18MMrPZ7pXK)Lic)h03FT0w#N@h(f)ZqCAK#8G)|MfGUZfkdTODQwMi~9+O<ZgQX=tWY z7Pz*gdW&;9;&)#tGhcCVAH>om-gFwr-RgAdTOF-U_37Ta#wNzpT2}#0VRiqPLSzsp zKH+>lI1Ral>4nbM%Ri}ZaxT=wyeH*U z@QTDoQ#vG<^e5t;)a3de=Yo}xWFFU?rw^IdkyW82)1nG^%J=>Q!-oV!Dj_pFO&g?kvF#5r} z!48{%{n(dhnBZTE*bb9RQnb|ZhEp=(4M+OAh3mi@V&2zKRkP5)uV#kdeIwDZ#JO3& z)PXJQ8_qN7h#st|0_U9+5wccDEVX1=SQ^m)HA3~pzjx7^*~y1>ORL?;(v#YGT=ZPH zbj7^Ba0NMy>@B`(JtFvb)O`tl!(85kwy(a~_O;pB+St{22;7c`gcO~=X@1ioAr0Gl zU29Vpw$rhlsnc(szxfdIY910yy4KC~$tb(>WdG1Dm0NsUAm`(M9**W)_iaH0;0xRp zX00?NJ+S0SvtQXJizuPuG6-}X@1h}wDPAevC0(kQn;$x)70o! zELWCJIgMjp$3Cpi=bso9$?N|Ymn z0}GN>oOjId)3JS2vK@@$HXN5AvY58wI`V%1F63eK`7b=>Yf4wsx8RI$nDT z9()Fp9jnxf^M5fs8qa_Wt^wabAM0pOIMuhLnp(ma6BhYUOTh?C*UuH?{&Qan!TU0t zw<;OSxL^iwpGB~lu7IybYmK+d!(zLP3hfg93$fd~gljg=xdoJO2{b63 zwSteIzoc3jO_BII^2k4NtRPrbgFDC>ky&ay?z{nc5t*Esp$$MbjQPLrRV3bPc<9IS zKkITJ9{Rs4{SRH%Kzw}1H4}4WY0bK7u)BeSYBD6`Qu5F6X+%|SFYLivK;Om6^5fec zBHl?AqANr+%(7&WwSN>@e&^+M3rzoDp@)s9X@Y-?QrH^Da z&fjAGu5sLn{resK@pKnoSs0Mfgfm%E<>)SKxL&9_e zWs5r`%(QM59?@+T9yPopNF&HrmiULLBnimb4km}~?twL&F^4-)D~8EUJCS2HnI(RS zje+G>*Xn9&EtP-q?>gf17d(hA=U^>D-K{DxFy;cc#vL+Nux>dkD}Eh@mKGdzSFwd~kS%Wo&0+7LX)S zwYM$r#gOi&C~`ZwR!{4k;-~v29EU|p)F(fwr$?oj z)#0xrul1fhUjY0SwW+*Y4}XL@EkbLO5umo2!}YMX;WtD}vRVT4N`~yO_fj?!^s(K- zqp+LO7MlUNE>T4Ad+K#QZ#9iK^e$0&UYic?PVS3EyQiVYSvN0Ic+y%}qLZW~qt_X< z$SaRtU!-l(`MR1~n_$75qd#$2sDPEk=d9O%mY#?=^+kFypU@(Q4Px9ixliLgm7e5w zKNotAV#5y!2d%VLV?z1xpHcP})?IMGf~c6k!8>S29Kkc^3q;-L;d+f1@26-X{|oZY zx%_5&bS3r`psnM3e*+v!ezO(UJJAw`wY>X@Lik;N0j&IE=jpqqZ(15E@KJ2mgpeF9 z^$1!j{=W#e)B;buq?h{hoArUz^ZCAv>H_yLScAOF^L^f>1;DH6j0!Q%zz7D2M+}z( z@i#`_Ur?qy>#3z%$^MW`-KmRYYy?N`8X7MPHp#UN@)HlBL%onJypNy%I{p@q_s~dj zGt+>K@{N!f8*1A`i997ZpWksK%V5KsR`upWWB+iZ0oxDtASWb`Gns=R$XXu=?umNrpKCz}w?g8fpvQ?95{qeWrTAUBf zG3`VtI5$oBEO2Nd1M}hZnh`DO8`A?lE5iz=bA-D9cCvi|ZX>Mc=;s^6KCK1Ts6mkL zr4jF_HLyxtkj8Wxz@nTtJHcYmP#rD z1+WfGMO3uLBQ4~CZKC}wsSZzOReJXenO2PtbEqL>W4;Gg?j+S~7)J^P{GaonnY@qh z=s%W%$0HerC~|VU|>}jhK5G8Ip$zW&ZD7`5o^vw9MNMn z9-i})kTv4Su?JV=ObM3fJcOwHFNI;IZF|vJY)=ulrxcCG zcDcA+UX+9Fnd0`$A``YN#O;crOl;2+x91fN#&)H+U0I~V_9Ah6QIQ(k4sqL2B*peJ zaT~rZJZpx(pjC4~j~EF0&vKkTH+^arTxYs_@d6Z)t3bSfjK&MF@yIS3Ex?%m(YWJA zCSuyz=KYN-HH{f4Xysa@&9HM5*X?`{`ZlLQ->!s2KxZjgh{nNc4i=F$y*5}7GzY)G ze)aBX*bEjxJH~m2g8`jq(0@n+Ddn3M74zD$+@%ktRnE5{4OBrIXph;4?sXLNI*K(V zr^4Du2Y>(H;-kU-!h=^{JcJd-^1_w(554=hF`}d}Fdj6{PnCPtqjCHIfG1MXa z!D=RUk-_o}Y&WfIl2dkgJ4gRfj(g#qZNMP(R&&k-J{?>(OP7ppen+ ze=4khdl4aPfc}|wf-^A4L&V`jv)OFBsFcW@z10@*e!GRGtItBB97VNx)M5Tj1>Yqs zVub6z5E}h2`H8d9UkV;$8ERqPL~B8F**KQcv%wBXSb@QQhGtL|yRSx(A!$#?B@*e^d&_raeTHnVo0<3Kb`akqmRGd+mxdwUN=2dn;gNv)P$Qgd#BJIO^Q8s9qV*D3o} zn?LK@p+nIpBAQEzREbUI)HAL^f(qj2p#_ zN{P_3X^bAT>pn849}#?`lo9PHd8A;JG?G0E5*ZLr=p5!O zfn>OJ@*fOiz}~Rk9=?S5BD>p<0DN#wg>?uEjD@E#d5J6 zEl*h&#Igz(-Ye@(VKTBN#LId^P(TLEMy4i$(gNS__3!rY@`DAy$nL)t8*`1_FEhe# zb(uBotG;4j58RGTiFLunE$TShwhd7`1h^%O7+PTe<(G4HD24_L}^WF z$*`wgrlp}p9vZ?+*3%4u6gw6|o2dPaWqI(z(B12qf+vHBRD>;Ylz}6e$TQ#u(_boY zTW#gXkk5PQG01ImC_Xy{`9fn3x(B=#_@7|=rSL!@GU7(>Qx^c&^Qe$lF*Pvj%H&lH z`E?)ogmT6Qd00GXq$T#QxfOfys>Ezr#hBj)rsAK7f?%x3>WfI&YB1ZB!xy~)SdI%D zTT63J=)u(^9UFN@7TJ#9RR4u%@L&4I#tR#nmhHkALpgG&bP88=6Zk^dpMXdxN{2L= z{y5F;oC8*(+Z3xb>&5myq1Yuw%v2J%eUjmSDdj!90kM%`jDVqyR0E+gW?3P4&J4rN zRL)yaCb>FzO`D!Z(Tx)%C%YRD3zA5^zNKT$)NYroOB=mA@z0mEiEE~GySl4&>ER{2 z{2QE=TvBI^^D%*Ik2TTQwEJue1@m6IUUES%o>PJ>3^|y?5IbfbLuB2I;b##KBA=)Q zp#*V6{~7b71V8$1_H-^Ho^pSTc$!5tO5gJ1OOqqWq*cs98*LOA(l4@I4U}GnxAtWh zYZw!`01aT3F{HTIc}94qFcs|>8U-ykFM*aVIh}{H^N<+=NUD%RU#&Ds$>2MTIUQa; zEdzN@Nh4fo)GTa#F6sdqO{7 zGAMoN4k{xKW+uHSyz@adkm>Ml4C6zUQk%n)IOQhZVKVNv?b^qFX85h@9DXjmX0+}J z(+=Od7P}SObWMu-(%KHm=Mb%`#M$dnTYdgFxK@ULYf)1Hxqh7Y>N!wHDy8V&$>cd; z4rT-zp>^xBxX33mT_-qd86z?ve1_~8bXyW>1b?$G>-8)-o}nz}f=(_0YwfMr12HH3 zU!1soe~fYwQrUqq=)^p+1K)A}$)CR& z4j~ia2Iqk-!05HtIAz-?H|Q7oHym#?0gc@ZTWcLN@+iM7vTXFS;6CShSauT?)bpV~ z0hE;Cgko=#r%4ukh`;qz`U;H9WeX!I`hM^oCaGIG9{QFY#kIV?xf^S3;{9_3qv1qH zx_`p-8=1%s5Sf4{fX_y}wp)x^dLXj=Ak(tqAlo8sm3i`z|8F_iABsX3ntW14C+>&1 zfqhMP(}Bxc#YFzA&ctIC2QEfWN_#If?LhxBM-Bl=Jp{s?grqJ6r7fy_}YurO1H&7 z!3Vy3jnt+F*S^v_i|90J9B(R`Et6*C3!3M$>mOPNm~56{j#eISLdLoOuVf1 zB}DCNE?TTeh&r_sfT5uraj=)^n7yZvuW2hdf{gHoQ@+pb z^3l6yBlofttQ!mv)yUfIrv8O7ZR#S4TY*`IGKik%R4v&gxpxz<4j%57UOzRZjPYJa<&){?fuY|`6HpOVVE9v8E{6;aF= zoIr{L&)DtE;cfg9U;y{_(dReA`Ykd3oLIUVP)m8q824dkt4!OuQ}k*(FaAl}6ckM10&C@) zh{nnAn{hourq>-4nD&e-t{IO9)loG^>zs~=jh_>FA2=>>?fAN&5X9=tkg;`OY-rw% z?5nfB^$exFDX#*1m+Sbu-&TFbZ(D!LKbhxD3o+hW$in@!g^H><>#f)pQdqrkKUb(c z2>)Gp1+rgCndEHxuL5@HOw~Nt*xmm0>J_b%`81mXvd_r?Yepo3oAxhhvk_xph)M1n zN+R7~2|))XnAP)=-&>d5vzC^zlQYneHf90t(m#}zKdGSo(!>hcJe3JJHkQMfPD67 z8x5CfOO>5@kcO?W>r#J<83^_= z2bCp;lVUMfD;K1isU?&WIdWs3b^DDehGN7eWdsHXFb2BW$TQ?kc$LwHRVr2>;&9?u3!kV0(w8?Pgp4^$jz+6l@k+(_*}v zax!*C!qt`yi`_Pn`Js|`xR>m}JV@j|WJp~y_qUYq!C;z>`E2;M8M`3=6WX?N@V&aT z5fdQJ3r55>EQX%s^v@vsF*{lGZ)G#LQ@V2~Y*}(x zZRcjPVvKT&w_A)c5bLAnr6OviY` zQZAA>vg;dmTpj<*>dz`ef|lL(Y65J|eS*C{ z#jR~-^1(=6&ln5$uQ1(=oxHvqcBurE_PG$;`?>I8a}m%vXI=Mgho!%I zDhc+zDg24S83x2$6*9&{!e0$D!CR|y-;BL>J)srj$iTrfnh)(kT%njU5jooh|HngC zcZb?NgH3c(4%(z`RA=CzQ}1(nz=Qr^J&hh47}1Z_+wP5M%StasG{s`m5q%pg7ah^h zhp4mv3mBO_qQu`?N8!Md=^HpZNMc zcskAs5pWa=)?)wUb&|r{vHe$qQOLHyhc{R4c+sR7H%g>ns|!XmKL zwTbiGuMnF_F;({H<~l3?=mE#UnQcSKM$xPAV%<(56VWeI3Mp>u`8xWJJfiRKD{(EO zv-tglMbIdKPTO)LQLsxKmCvO_8LjA5((3&wfyuDbFL5k<&gNHp%4OVHaNdGFuF9c? zZN?x;ZHco3OR-1iYpsE28eCQWs%UJ01jLR5t+0PG3J}slW);?`?i1fesCOE zZ)Dz#HD8?#R^O<8o4}@0W?!Yf5v}_dl&!PG!0;l`rubf+dXjwX`gWzspDW^kH1>ga zS5;&7zg|Q6tu=Nt-e)SA3~SR-m%8<&T;Dlxwl6SE&UR`%wU9UxUK^P2;46{iMb7`wJC6I$AeL~u@Fld}@NZ0H?1;mC_A1UotX*T3JW>ku!p+#(D_ZXd(Dhk(Oi3Z0g9Veg>AC%f zgv*6DV;^0i(SWp08xNWhh5A+Z@W`-H+DO7Eb!0FsQz@`arNS~b6qc!ESf;e23fpKj z7(G7$>qGu(D#cPns03WK!*5cVB^OL}g>5$SNOuBeT~QZ4L1$g-J5y?A`s-@OWA>hi z*gHguj!=&{P|AJ57?T}C9oF|oWDUYEjxu_`e7t(>P)C|0-6@@rj$;fXnE~cSW-f3f zs~&Q_fIO9$+dpdz!soW7-6xkp$Cf&QC0wm+0qXActWHjxFDo%}+33@Z)VP7zyPJXX zZV}-ZbXj6{{bCJEDhB2w&s<=<_TmGVw>#5-mvx+a8`)Sc965< ztYgi2@SYpUzk2WHAZCw@1_H0Sh;mxS85?OP&k|^zln<(s{8B6@v%!pLtHt1_w@(Gr zinzV_-0fIGv;nbnw_~bk1JHf({d3PL!3)M9^H6K){kB|8~hycZFfG>W{{MbA}+-hmWB`B8H7qOJ9P>Z zZSx`fo*aj~WAJbqNggeKZx5dpePC6=e|RRN248i=hz@G0JmZH`e3*FmAkMXf&`VUQ~vWtGKZ zRH5j5R>m4r!HSmv57z%2_t(<_E`N=}eOF}G(IXm!T4*YvBQc1fUP;~pZg|t!O33@H z$+vDLSq%)&XxOuq2Hd@w;_@jT9EksxVdVXDa_=#r2yun~e+;yDjmWdpj9%}G^UzlE z8MR)4#Jm`>J!=9n?^bv(`@5l`9TQeGYi;lt zsD{E0he!+e9OU?t`!@0M|5W3fL$Z;N2pXS>!%R=xZ7HvgK(2-M6>Id#{c(RI<&4gax117v zijkL)J$+U`2MLQQ{76{T?Bz2sKH_DzhxhuC>k+sSMiswC0u5PY(`+sw4ShL?lw-Ex zs`gJ|pRWgFJ5$KPHv+~7aa>+nxtug212C1}m_qNp;|3geC}pJUb_GTseO?*)>b61L z1`8T---_SA6q@!Lq6an^7Byr1&6beht0?Pr@PmQ@L6VdeqSn@Qlo+20W|5zqYsI)# zvbSRIkX4u=D4M&7w#T+w=nw_&Vvqe|rm zN7M%9mJ07$nY`30H_0)+e;Jkgtgv_QT+;??0`1Ke25J}5K;GCBef8K2We1nCTCf}PMv&h+Z>bc}HXIfqOGdW--^FEY|Y3Op;vU5y$3 zq180k290C*9b(FJDFPiaNAmka0>MdB4QXiQ*M z#|nA-dPp!k|ERxIrr^2k+W-*gG9Ubz)ZxJI8p zF-PPEwKInDZyBYTsKJh0{?b(B3S=YA9Vzqo}fP)(e`j7QVjmh;O2!`DW}0p`)4O9ex#bunu7LsV>Vv6NA^w zsD*Y=9!u;M zJEGAU8UvyO(<){1J?zw4bV2%lsP4W1-mmehXWf&bwPZtU36eQ8CiB_uInY`R?gVHpa<_5E&De*8##|*t7pW!#`&Miy7qynL z?Kfk;hbFA_4uf_oft3K|Hd9NO5z%hhZ51S?7i&KlO5YQTG1`VDC5KXcIecuH5zlxr ze0jxwXQ6XgQ5pJy5A*d<^h37Cf!TvviR7Y{Xg+K_N=SVyvc54C+@w%N)0x_>FvUtR9wgh4lVzK1 z8@G#U;wja0$+RA`sA+f3+r%tc67Q35!78P7!ybg(@Y=$J7L}X!NA$r$$^ndLYnJkw zZ8E2_r5Njq+(a!+h;k6r?i;a{h+!BVqNP(EG}uGgfJY(+v)f7eg47m6XpOTJnRV7U zs10Q)axWYgZ8xyhdZ|q#ZoAPS3T-vH+uIUR!fUPc0jC>LL&8vcrFl}Z|6y%2y2L&w z57Jp0h%zw0l=06Z$CeiC12>@mm!N+b-6iNBCU+A02eLr!xDh*YUFikFv6FEoi}2?M)Vx=leuX>q*f6zF6v${X*U_sOYTBTe+YeKCN#wfA<83tKWZh( z8yiwXqADTRyJF51M7yT38Ge1W$b13Tg#=^|pG7!i2xJnsUSunzEi%M3*}(mJ>$>ZE zBQHl^FDCEw(|jh4(xhoe#JT8ar1)GiBJcJ)lbtrO#{1T2$WObKk)fIKC&oC(GnbII zzIZO4m3V)v@wqp*OnwP7-*e8R&e?*@rR8tL9wS)4f!nYKtL8JtO6OnE{|c@P&UrX~ z1NNiSf|Ai4rpH>H7jZ26de!{gX=gC*Y|bM7G$g%NC!I4t>OL(v8K=K$zWLr!6CXcv z6h}_}e~i5ed=yo-2V7OXW+M#=5Rw4Bu!MxAn*at->~x^m)CANK62vTk)d@lX7dE8> zC}B|}0SpOhB+kg9PCCd8QJG`}XB>6rbpy`J3avP!1LIho>ZB_K>F3N|@v=T3RFYxeu?I?oJn~gebAH4f&dDgz8lkb5U+KWcJ6r z&>wS7ZZ;Mzm4(AXuaqKE>gxQWrAvhZ_ZftYR6=s|*NTaZatfxLh25c=9{$}<=CzN) z{r3%6?Cj;^m9t=VB}q6hZ{H!PtMi0`{KX~n_(x%*lzc){nRIfB_@~Z%_p(OK5u;fR z--StsdnIF!(fqY5Tig{L!%6 z7@wAp9vPMUHPux|buIlZ>I!WGXgDyw$H2pQ7*`7CsYj^VJ2&w9BXdQ)PUTalaG<_< zrm2DWf6`=l$lV>>eajASn#AoC$_eh%@+F|P8u@q1sSVV_-fD2A0d>~SkvE7*qrF4H zPg*>`0V)z{s_VU0einQ;(eK~dSSuq&L0+~FJ!PvlSNyK+H}Y2NS@~(>nDlcAgK)}K z)1dKu?ALQO3FLK=v8TT9(??*nOg0?O(?nN3TolMw$J3?`U1+JfAqyQy8ZnraNf3Rj$0TTKq>S z-+dC=^3cXDJBBxIG33mC%!)QJ_DaD3wSX%v{O0K}K(&2o0(Q#N9KG$0)_*TUjk)qL zykn*dW@vY8)vgneQk5$fnQT1iXDyyqfXDy)%j*&b~f0^GnjoYNhWH?XdRbsk^zvp&BCx}87u`N?SM$1K#NsIAe@Im?qvq2(~Jp4wav zgxj3Hq}Knt|7i7<0RyBuFR^O>z38vY1A0)mKxKn^B!Fj(T7NF+b+9gZ8f|)ifLg=a zZ(~5;rf$nPva%oR_y{?yafO$qn4w;F01RCjvY)r5cVXP;gU_p?*3oy5&VKW1JSIPwU!0N%X0 z0NxkO=yTx_{&asEC}aCSTHqVV4mL&|7s9=-SL%>v%G^CTwNb25=nZpu>Mvin{ChvR zJxa{z2f5-KN^kn#+GQqxk2nIYLf21@}BvYX(Bn2hTmPUXaUuZR1lnCY((66i3blV(6l>I5wEm%NF5f3TE$x^C+eX+WHj z(6QlE_s(s0@m}Z@-mNm4!M*xNvTATpzOD}hbq#d#)<$?!g+U6J^fC4*9%BP)5$DzW z=W)z_!YFVG&=x?;a{G_&(ZTz#YX8@e3np`Y(idH3pHo0sbUyzz>}1K3JifVCdc8|s zL%xXV{kgLLJ*RyBr+khDhQW%<;7Q*T?u9iC%hK#!O)e`B?GlvSC;b?pygFPzrC5#> z@3{`iYYm6wQlsMuq>K5od*n#FP;>pWzW2WCJ|Jj-b-)qf=D)`as>s*38VV`uh zJB8Q5fA&mZ?)=zS>5B-opoLv%;g?+W4rBv%yUuk=owvLGGp{c@&D4%s%ibL52+d}k zSli#>w!p3ct$%0!=DY@rB|iaPDTjDtaF@0R=U10fFuGPku5*w00>;>bo^Y+Ilv=5k zS~Xy&Pr0a-xrg;RjWCtO|fs4{xSVQzh;Hv)43@gr7@V13U9VV=9Nr2Pr%o6$|GfD3+G zT`JDk4iB4Z)hBr4=o+iQVZWFf_D`VJiTir3r#3iQaQ5iM40DRHJdbahe&?GG?2JSi zbwVCS<(nm}$gyN)#F~JC<5QWi>X|M0%8`96d_U_Ui-8pm?P0>$T|jX7Djqp{~9J;8b0TJg=+Sf8cw zc1(oFvZoq8M-j)U{fR8`82=KDmOfWsxiM|3SJ?wc0Qr%@ejf73AlD;c?C*motnx(sW5~vBpHE z%Y&?cY4{vm13QzyrW&IWRT{MChT-sz?kFM;>9w+bYOQQsSu2l!nC|I0Je9*vo*2K9MA)mENVe#8Xs#Js=30UU~On%vz+WAnXxT?(}*qJ2Zg@#I;vTFS~z!Ey3+uhLA5Qj1FJp43% zDI0BUTC#_`$fa4mX5T^iJ0oi}!0sJ>Rc-qlLf``o(D+Qu+TQ@ZNpV{XQdn7YwVjZ> z_BX_%XPuT)O%8EAYC%Yj(8Hlz`qNOYS1K`#*#Z28gYYHjXuNM)cF(PKupzTUcirv~ zAF6^b9OID>7!0TN@J4yBxaqPA{(G;P;>`1O)>g=S95KAsGu@pj?rq5+ zTrq3LS<Do$=OO<+WiYe>rgT;F!-TQn2$JV3W39qHlzrm67b#>{M9WxQ|Kgg3-+#KsAZ|z5CEsFDsbvo-zq1jx zsq9-{F{pyFAuTI#wU$IG_(1W?nYo5V9Dl z*Wt>SZN%>bWu=JADa-w8D{6IL*|VtC!)5yr#+McOd~2(|t7ZP+93rZ<10WxmdPCLq zS6yp^>JpnCn-=sA!|XE?8fN)z!dG=n_^MIC zJxMNOOp*O5rUT!@N3b~l_`J8gy zHNRIH(qjaQLL|80cO@%0nNWIq{y)HPCEKq07#r>5cgo2FFJrI2U=AbsTqGNqCH~Mk z$ETZQ?`m-|y+Bm%B!47Nw8DA7)KU88r zbVb0*$^(ayRHbJ-8Xj~cL7uRpl@v&o-f1m6ZsQ(8ylz(+BxOD z_H}CcTG+tQs{2Ko4)3lKjU9$lbj^TAz=THB&7>0dbfycwwM^v(K|hW3L4dkZnTd0y za$}t!OJiZ306RG`G>sYa1fCd&64rIY7;i%;htp)g{RMzWlE76Ymwg>RL z!nV+7;|=gQD@*tFXkLq+ngUyluX@y&D;IdkPnay(do1NZ{fHt9mmj6S^-x)}eLqUR zp1V=|N;zTLICzk(FB;=}Umjtz_;xq2<0iGJEN{sp1{$%080I#bImOQ;XEMU>8$N{~ zg)!oL47{@V+l9$oS`xc@AmwaCo}{=oA+54kh*4y)t%5>zsra@rKy#9Rc*04N0~Gy?4lLtdrI#Vbyo^7*W3%g zdW1$8{UdjrsBiEFF6c1=m*sM#*~l+5Wr_@Pyd)b3&Js6Vd3b@5e|WxK+}a7QJL?m< z+=lChPvm{jly`wnp=)|cAq$z8U3{z)<3N1+@>}vC_LuT8?GS{%5BtbW@vk@qYBm9j z9`_#He~kMzxc?0I2`07wEe?J9Rl7KBji^c(a9}`+F5?9vpOZfzZL)!dS``Od1j_~v-~EUYF!Z{Nz!y0wtC?!|kq|H77uK3cR@1s}BV zM*K6ssCKar?fM=NXGdPi4zv`J7s^rWd9;retvnQHeIoRr)oMoVTEa3o`L;8R)=qv! zO0kxy`EZFOH7IlKSi5xZG@D(#aoO^OmT%EL^JNjvyq>&tb#1kCKf`_5=JWs~vEA9K zV=v|E1ju`9n3XsYE4Sh-mlx<>f4Gd_jPd9KmrluNlv+2V z)@R0)!uo9QCJi6W&xB^dOJ*k31f-ysaW#5bbK$y5_icT@t2jf9g3LW%4@}~a)m;RC zvth}xO1$=C?WP=@Gnuf$X4tdDl-Dqdu#Sd*vz$?loF%JZ2d?p!9R=r{aX8-^vf*68 z4uS4AZ`hHqLY!)$s^FY}vD|-;u-Oyb6aI&Dv9PV(np> z>>P68Y4fe8nb9`vtPPA^9DLcGOjd7nAA7k(&rMaI_`ZR=zC$(l8e>;a(@w~eqocp| zM*821T%@F9-%!2Iy=E8X&PBYd|H-b-g*~D5oIRW(QydR$*f$*9h4!qLVT?~sEGm9o zp$>!cC)O3erck+{t|it9V+*Sss|tTvsJf`az97R2jYo!i?2ZIf>QqrsZ;Q@#Rt1+F5YuW-eO?J zZNPOCG&|R#w`OL&(%03cO?7sjlGj<;w8uS!+_cI!xpan-BWu|%ug2+RIB&*xPa|VV zS4yUn($z*fTduXij|Tig>EKgshrG&2Av@&9bFlkD+HgH71)*~woey?P{dt^|1s6%W zJxU&{jH!+7@V6Wq8Rw-?4t~QYisxHd%$wtQ&OA~4U28b+RXMlIz!hFOeqKik*)Bg} zr5v=QfBh{t`rU8!qfdTIv(XFn(Xbn1OsZp%<`Gz_jnY}1&1Q)P)w{SJfIea!os?qd zX|%QI2Eel0F3!5bVNL`uixiB>V77}JaNd8fc2M)->9KU_a@!gAWRiQKB-%ytcIBSMm1r?wY=m=d#>L)AiOm#vG0HEB`WMBu*fb zq19pA=|ytJOn7_7>Mu-&_VRw5O`27&sP_-!tIQqgL&U`$x_SmPqZ;dX?T-gAhE?Ef zXf<#A5#I|K=M=`yRHdzNx$+%kSaH?`EHwW^L2rnip!Z%aE_AX$bG=$vc#%`)0LGHz zb-GZl6@1+Y~a7#dx;60=NE$=6C*MMdK;G8VF|r8y_#tXg?yH$8)Kq{67~5a^)xwOkw`+B^&|IWK>9=deFhOPg1ahyUh5Zh8be6bV(UU*oGCF68SAc-6<=fT(5pg}uJFmEs;4hsKUvwP+;&qyvyhnepAOY-j z(HP1tS-jD!i@wyk=~4821B^P{AhUn0FJFJeHE;Sw%Uu6*|b4kLMtX;(+Qe%=%wT|_B~POzKxr< z1v=OHz)g$tQR+OT+GifVFwRagErWOF)(!5dS?@Mb>`Rkp_UG{|^rE0WfqD&xod&qa z0JlC3-eE@o7iy|w81_Zrpv~STyWM*>ZElR*T(<0Ljo$5gxBMrJ6w9>e?0L^`u}Mdx0#rspaig zcFI^9aMuW*7dGW?0Fa#Lz)~TX$%6%p9d}mDESb34aCK0Om3)!p=}cdKTav;b=(QOR z^jeJvdULI3=90DVyg52P4%#Hsk?(}dDNgPQNJ_a#?*ndjlGgtg%X;%ItS=w4BkWlq zk7La^DIXLHetV1Zt%8Q=uSzJlpYpDsdjKc!T7Cz7iNYpl{$%um=Pv6TcgmCV>0Own zwe`E14X4S*lHMj?S&2xeWVteHFE`Szf_mQq50f$vx`0X0iJ9#s3l^H2XNxmiOG@(K zy-EfB`A{%7G!46nEb(jDbk^G1;2DPyI=yoIzzFAj$14tP=lT(B>WJ3m8|X}XfUkP< z=Kn~cYg11g;GuZ6DN8(o-P6)7HuT;s@hv4!#+-{d%9S<4j_j6FKNFL>$YWM4itYzD z^&;fQFUmUO1EhpXC-g>ijH|d7Wd!dh;BW z4OvhekMa!@#6s*lkk-J#8`lKr)rDKr;nqOH2-SQV z`XcmIn3w9w?v+W&@Wept4Re`3?290X^MBw7n4^!8-xZv`xPriDazI1iOSxG)9p^Dq zDsn_u8~S{vi;wla_??|))77wnpxA71T+t^r2Xe&hww%UM0zX?_!;Jr@S5?g#YZ5+# z{1s#0I6#p)asmr`(ABmJa*I~g{fG6c?!Ro%h}S;=yFagVu4g?9ebWxpE(v--@n-^~6FvF|TMtIn?6fX--VKDx6zAq9+ zZ>o?jb~b%{mW!^|N=KkQ_UGaDpTbqXQC77rD4l^dX2x0UNE!MagW^M0r`$1;9wz;j}b#Hd(&)S z3);o;W#bgzfhlFxGHc+*jr7()_bk~<7Nw-srZoqp;XSpD@Kwb?ehO+xkAM|y9QuUb zv|ilQX2mEP2l)_uFhjy&1V0C_M9?(J@O=QEoO?f{-TBaCL1}-_)`cN&6d{I`{>md<`Jlt`?&qOY`&P9gtts+TlsDiYV zPjxeosl0Ybs!}8u9}a1LY^;|?CHWbR2Vb^?#mS*zo_Mf@!RmNe{u`_+^k0%D0Ebl# z^KE*~#%g#Qpj@8sf_B`4H4Xh5ar0<|&by}4TfiAEgN~+sPO>KxbJY^y$3FqyaTI*o zlIExxdU{mm|EAqMLi`vu8%e;hBP{U@b|mb!FU}c-nU|!rxk4Irht83&jAE*DPT1QB zv{m)6Lnj1YoKoEJIkL@6y2cFFixP1i=CbMd_N^U|O5jW;1wA1O+FyO`Ni@=5K7QtT zCgy|ZnW)|3>GLnEL{Y0AHZ5X`JS4~Qa=m=FkDZ45(l3CB^}_63^^1oMxcmPrcN^G)F5yA~S7C&Hj;f;8 zsjl!jH5K{71L7as|NrFp{QpOel+)G@4eCbd6ZP^S*RDN(*t2t!YxW<9y)Z1$I2xL) zv8riuoPkZpuW^rKfG2w4;>3huu5zk1Q?b864^Qh?mkj7**F7LU)X7fOPl=gu)f?u8 zhcJXS@#F@UQEd#RvGA;2IyY-$oZGdL&L%J$mzK_b1>Y%R_hO+Qcmab1RjS?tM0%|i zxB@HEmuxh+IhtRMrE|6TR|#0Jah@^ip-Xade3hVwzxpx0e>tN*gr}1!8vg+9HHW?l zvYHh}!GtfqwT0vDzyKj1gpmw|L8AoyfV@F15pv*%{(x+R*N~m`>tko0ESSmj;g|Ma{P?O2yuAJc{%wZq0SYk8ZO!KG2hIS(b%oH@x2`1ynUh3e?C;nFBL5q}k;1 z$nlwdQfIIQHnv)xM$<@Wt&_+59gd}5vTz`;Yowjyv{~GC$S)6qKfKRVx0*?N(DT*m z^@-Jv?Dhk&EKhQqoq52zoCj()IM%VwAKanD7Ww?Nwc;PSR~=`XhVpUlH*g)vzYiQz zLM=#~H<=GgQvx({h#Olhz60OcdYgGq?(@Xs#hrTGsXG-)e7|>wb?miopJCF}Z3E_g zwVIw97N92>^J3AbJoDC`BhS;0l%8*U@#^Y}30GItBr$29HvN4Cg}?U+a6j=6slwcH zNJ?q#(Fwm02a|@+#I>Fx;bB=-k+wMwG9c*OW7bsLF26XY@xOu3e)+}Yazei9q_Rss z@rbw!D9zkqRrN;k;}))FJFl)()u@kYDmBL+M2$9zPqe0O{tcG|>#`{Lny7&<5v-!e z5bLH`zLn?)=jbj_7;NHwiICK9gdY_xtZ#HD$MI^|0z==S<`J=_B?XoPnVtU)Pv*MU zh6x-$Au+<8Cw!l%$9Xs;$jl?+sP+Vls!uOpgT}euZ)f*6JR&Bx_e5m($m=O&20Ph4 z!k6K^AAM27w=^b<(e~+kXx5v-rUD^Ojk!{b=ji@c+wFXb3SutR`e!LI{x-eBsTfW( z#zCT8olweo2<2z62UgOh8s(nzu*QT^!`b;jWr%~Oy|wWYxHhuHGhppDoW;4>S&!@* zM0`W0dq+uvbCsCa32QwK(qP9C!`GFxW4^v0YXAqnmleOg37tlg1ZD3iiQ$*7R`d3* z={dWgTgsTr952d4jVj2?_U4p1^xpU7#YSN0IgWPQGZ#1<*I6#XsoK9voYk2Ee)Jm) z?NVeFYZAnT?J13QB?ybF5(M$d)&ueZ>yAc>MIHv$ETB!!%ipN13c4b3;E#$)ZTwec zC;d%2rKu}n1Nn>GSm&w)jpxno=EaK~#GOK)1^M3tuTsQSOe~wK@B&n`bmVcd6?YZ$ zAO%YfMg;O7sVn(ye-3YVH$S)(IjH(`NN%kw(eQ@GD)^ro>IKi=SaLuj~8M z>04cV4&KFvQw{`1dRP7pV>W@0W7#Gx$*6CY>-ZX`%CQy}k}3R|rYj8sZ0kb}kKJ(f)|Kp(pDW!dZ>!jW z84cgRAMMHFHiwusjnCqc(9{yE{Jb}gAadu23i(^xLfhq^Gf|r`4D!KE(LXx zP)Zi@kHe?&0eL%ieo<}}Jl!4Wea`T>SP8VJ1HIdjdm`j#->AkR-D_9sl>QxRuliZ( z9}p%1AL*r3Zk4HLOYFZS*Zuk(4f)G zj^jMsxanS4xU8xxK}thn@d3G&DO{EC;Q^A`rcUrCIA1d1+qB!6T@BjNkXahZ?xC(k z=YLY0;I#3pL~Fa=&EC`H#aJ_kL((x*vEpB44ic+3&GZe8{Ne^WArA$&;LI<9k+D1B z@N2*fvrKGk<4hkngipys!S|Z1r@7Uim+zOy@pm`wELq`<9ItA`-@*-iU< zM~taSV9cLCv*(#8#4UWrdiI=CJyV~iRitSNnh0HlCQ1{-5N|OQ|7!f}@LwU%Qn4v| zkBZJS13OBy1AD=xu!srY?I?*momdR-OOq1|D_{ji7P#-Y>g7+(sql|1#=Qwozi-Mj z&=y}HmW6em|4=Wrh5G>IaSWXrn>kvIb#1tP($OEn5BWXK+OS z{B+B3)+K+R2p*NOp4MPB+*O|f2_3U8vuo6#$HW-L>PHvRc%YEIil7 zz%tRwYfgsSe9L5}ayfAO82BWCr{9&p$pg=S$}zs_b^bTJ=K5c_{j#cgusqAhray4) zzUBL6u6glB+|7F6nq!bFO|w5!Rzp7e;&%~@lW#_gsPHv>SbZpuL~!z&>mPH5O^Y_! zIs3ItyLS_t4m@{`o}7y(uY0ot2E30`dG36h1z+)P_+$U*VdLuM;`l4!U7Kz-ur8Il zXY}`#PtBT2ZKV!%4_qcrWs2v1j4;h{(gs^5{_BeB;9Wd3+TeRtPM^l4$)5LnQ)Xx@ z-|tPGp~LU^8JbG!J*vv<-)fG-3iVG#;?wQyRM!O5DXQD6L$5RPMvD`CF%Uiut`MJT zqyF`+X}S3IndI+X}^sa2{rtogJ<@IuBV1lDxLj z9~&!qU-!)iXRutX?rOlmX2fyhTT39}`@HyCS;l zsw)Y_U5E%ZaDi=(1m}#b?7*CLtHcQK6w5`q-C)-6b?8sjyE{Ad&}XQF{ze}9ulmlt zw}ZWN|9J#Xsem4_HdLv9c*F$j&-?11k%q_C`HFnm?O%F$ME?F~l`2 zhYkqx$1HYu> zE%qLrEl+F(m0{NRQx}x96oWdi&l5ibZ8aMZ8-=vz+U-g?Vd^8q(Oq^xr1`7}ee0(X z2fq4)dEyD|!f#H;dnkC~t_LwsJZSHUwvkLaPq96`O(6%Q->>8lqr_(iawN(%O3l+lU%| zC+%RQ9RiB#Ft|V80iablL$LP@L97l`v{GN97*OGFLR2rfpk}kZKV=sv%4Z1jv7?2r zf$pdJztL9+f$k5|48_xJprXxmkMXx5PrLxy+E0t1KLg!-C-pf{N?PQd9oU9;{otn% zK-3^u~hBu33eLF%EyF*nK|WVf$C|Bbn!6Tb3u@jj~^ZU(!cm zHw3F)NP+U<%MI2Q+kjiyN-IrnfemL#_O8DcP>W;zZ`1eZ(S~uF+?HZ-KsP^yAq~mt ze39tv9Bfe?(o`~IjI;(H*Hx+yY7Z5ONk9spjZ-G_Uq2Tvo9Cqss?SYP9z zE!DxK;%z@^aVau`{4$hhdQ@z`40JMh!ky4SmMq0t6j}%#l^spUWkqgw>h}#vN?b1D z#=*by{x)_(B=%gLZqo5&u^NycKSJoxW48|)dN|OeR6?SVJ{kR4gH_^%^)dd*s(f)& z8#8($f1J;dHHLAg)W#^tF(a6WhL{sL4cI`u+>R^2^1`e?*_u4#LgH_G6Nbb!9CpNZ z3^=ZZ1WFr6PqNc2a(uEy9yHz}8=`;H0PC9w=qydR=;bV0U@Ge?WBk+Lm5>?DqOH}( zHI=%91(3ZPP+AJJejx|VbG$3$1k*Tn>N4@&Ry%wdGo$GpQz6-ex4nbh!9wwDTO;x=9Pw~ngqx4nsDruPoIyV?Q&Jw~nX zx4p?@=$?h<5@&oAwtmoo!Kzd!X138@`%KekIN!*=wkI+h*ar5lb(ZQmk)5}Bk`vuW ziW}i4_Jp5{6ul@}4 z@%$D%)wlQB^$m5`uWzVw;Ivl>f2f5sL8^lk*)3%Iw4JBiBHNg>_9k#ceNldUM1(&y zGs=H&RD|!}L)oY(e`XY=r?%Bq7Qk=qS@b%{3udcx@;-IcPnd@Q+|t1=h=0V8dc1PV`hZ%I$)@)QEja2~uPSaC(P((x12YOkuZ~(~*9>62lf?F3G}d z?7;ZHlfyJN&W-T(jL(ke<}1(fY%JE{Joq|CIYpEcgbe0ixGNB*?N1TjI~a=ezI52f z^IUdg@|fxd0;Nk@uX4i#qXT=A=@W~&tHKViE!^i!R!Vb+V?Vh9DatIrmvW$d&tCbxf^IZ8JqvQ3o$}yxMycyB2U2Zv z#TW4|k3t7PSC*6waZS+^B&IbajpNJ z)m-CL3YUrZb-uwg)s7y6JyiC>SHNF#Cscl;HbGb_4CYmz=+jR*Qu&}aL8vKD$v^30 zApsaOqpsv7_#{ZFzKGp3DXLXx+xBs<_(Mw zN7{%Q0!o8^k)_1Ng3=;xfD)%~``xf<>In%?4~z63fNU$fXJa8@J1}WB4@xhd2#xL+ zHoOZz-r8>0(~RZmNCp=Fn1w21P7HwTKl*s|p=IJH?Pq&4j7bY?62oJ-`LfUf^W2UZOeRH=F~*zihsD=tZ9ycvM^A=&1*Azv?McWoZVwAG{AH14UDB5 z=DLzIN~`Z|h4i*ZJC^dRMSiz}Z-*>vN7}bRzJrs}V%BtCz62@FzOqNfc<6}ivmKN7 zF_5#A>L;+C?|>7hGLatma(98G4IL<)QjpmOPP5>!96nN zm_BU-(v#F_yNof=brU9=uCrG2}50NY^f2Ok{oSSv3il zT+Nz~NyFJkryaLXJ%Xn>NUW&!)xcQ)OM?a8o5ggJ{v?)CiyZ||L#w{8t!OoU zWBS7Qz8ZKkP~Ncx^NvCZS7;BECnWJm4EmvIMX~#UF%->yOK^J$@{)4RZ=-d6+LF0S$j9X{3RvS*A zlJ}2-3=DeaYUohzA4OWI_O6+Wa>eXU2K{H4X_=UMrEoQ4ISNes23ee90s2>xIw|@n z?&%bXEX}mg-GK+}MeqkICPC_+U~Av2YE1bs4SFwTuVu69RLMrEu|&Aq2+7P83w~GF zN`Q--2Q)y!Y@CidmI9G+14xoe_N3Onfp$s1~wnQVhY>e{SRloz>L+2mH4WpCH7?~1(x&ol!k(T z4dJcDsE)DQA1{2n@Y}+%J4i;J0~xZN%YeT`P0QV*)O{>lzT{lO-W-FXdmW7vQu0n< zgHK*N{C!RE+zkyRHa)>SvN#>NUqS9BJ{h~EP_Q-hI|othg;C~s-*2wZjb$&Sm}7Cb z3wPtqk+}OK?yP1F?sOyB3sW%nWZ+pEmC!A&5Nq*n;*AZR`Obfs`u4=G_Ip#hRvQs!14gYT4&+U zL0cUPHifK><8gwOBv|qZV=rWNHK^+i=o8WWVRw{J8mcEa6?)Z$j5I{0KRnc zujUu!htE@PNV)G*KasQNmV{5!Yk)JAQVT@Z3T9He$K80M(dv7)@oD7@H42u~y+SVD zVjJFwe5=wMZIW{EMoEo$gO|^MlRN`m|6e=4@xJXYDw)o|;n?ETHx(6qD#u$b@OK*u z)`t#2*ON3Ttk$BEC}>w&t@JF(Z8W}x&Z44lx+9^25@uYF7x4zFV`%OO?9Nq{6-W<_ zwX|~BB>-!+0=t=OK5n3>poj|!*r z8JKMsU>6b!jtFh%QyNt@neYw}kgQz~`$kwF^d^7<`&inJQM1rvbOxjge(3oz^_X*R zNWYOjlcSjgynR5L(iIIHvK!Jn2vajTN?2-F8{%(B?@92}E1i^5eMKc{z`CZlJTFhF z)j>}eZ=X&ykoe{JT1rpeC!m>F+b#XQOI0(UAMxZ8Ar)%`qfx!674!pM^6l@8RSExF_iM!BXOoi95YIbH^smiKH-`CTe zGFL6s)&m2|rR6zetgxH!3SPX0ogk!hM$2|Szj)+95m;nldU!>Cu_(RUPmNHhU-eUo z3iXC)#J)C_Pg1BVG5)S`d~(HNJ{GpBUu(M2f3@Ze>>InK*DW zalf~_&Y4_Q;7qC(gHvuMRwY-nycjIRT@voz!sE6cgU@Jz#eopTn z#$h*$eL&hRedEZQI(OpXRv^8MtO`hZUBi!$I01?YNcZ>?kBO4tPr~m?e=>dx{YFJ< zS$cYoupgW?GbTk~j~mScfuZfYOfABm00IUhO80c92oZ=GCSJNMfPceI!Xip)abN{= ziZw-?AS$i9#Cb_R!w_c1K)gdmA0!gSfro*ohth|1k$i$hkNwJ-cX*uh((;Bg=VZ9A$@e>kc-+_KljNVMNpF$%w%MB;&mhAxwQF?hn7+!13_2e;G}4)9$q29l&IEkDA?R$VPl zXe2F&tA!E1Eai&#C;ME6=2w>43f^oNbp0XC$LfAVIwNA7rfl?(hbQ0qZs~wHCVyo< zV+kx0ir<2*67?LTIfp}j^J*D+dnsh;DT5f@tki z9^>Uv69Uq>pb4nXDg0WG-7a4>Xn~>=jk!^XVE1eN6&TGVrw&RFcVpHXXrHHfrCZX8<`XJN?TmV6zxLOf z!!s5TVxii_y&AS_7}g!eK7>UZD@w+$CH!h}9CrET>~ZH0(tGg5H?0i_$~K&#)iagj z7b0dLTbY8jqrwEF0-Ge==uUxl&pun+hf3%`Noo!H+{C}^v(1O5RjEmfSt|g&cRa(U z9#1}!@aKV%UjW>42FmVe`uvWt_YJ0;@Tu&5NI&>|O{?LrDRFa9^ z7TEG@$WhJOV*%;=9-0diara#ho?=fHS2?DD&t{Di6j@~9-*+=lT_bssdda+&YLiFiPI zv}XqT+vJ5=s$N)y@84|$dIsgK>9Ha2_j)ERWGk4; zMZkzfzQ%CQY~M_bic~%mIsaMA?9cf`Xtt8`93|&WCFe7Jvk%Xyz7IK1?Y{5u0;P=i zC0#XrO?Ll+!{luw{6+uO%O9V6VgXXV5O5s(L2?JO@cUk1HhxbA=HT~OU><&d8JLgX zqX9d9-w0&l_efyjF$p+w_u+RzU;%#T2C|htcOHBD%lL0DX0RTV&NaDr2%j&hls{sj z69mn4S|{g1hQU}!4?6>R^+)}JMpYRK{ykLT&VcnoDEPOK;HF)N2|JDqU|i*Mq2Syb zs@GU(hQ8)zfMW4WzfomOi)ncuYshPk2(Q||8mCDC=?kD!#W!h;3|3}M|H>Sm8>{#; z^}sf%oL0FFp3S0p>w7A{VF1Zxrz&#U4RaMy{(o(GjDWq|U4`V`bd$Zaj=?iSEr7!YohBrDqGafE#Y`Vup(TEaJ-R)sVM zj>ml$-8bXj_?{$HA!Z8nvnAAvI40IKrRoRx!lSt+1#?Y9z-YSb zP&xvB`3&;@$XMc>TJ=NlA3qu`I3qFPUOFNvs3Wi&3P^^46*=6~Z5(YU1ocDkB>dhu z-g+W~$GAHblooe^dxd=weajo*gfp1v<8&X8XiW``()!e+)IQZH&c}|z>D8#I=!?IQ zrlJ4ol=>wg_rd_pk80dK8Zaunc|ckcfYt%^&$3&8Q*`08hx0px-Ss~#$r4=r=Sv0v z-!_4N1v17#)p0fGi?>uMScSX{Jlu0Bz#B^$;#JY>KbAv378rS?$UTSTW)z-J=N3CSm!`HJiwrL%~ zF1B`8eYLCwpBv3r!j=FYLo2dQRlw6roSU8L!WH_5whW-bpikHq;S@O+=w+ec?2s0# z;rpxrG#|v60@8~T(Y=yeA|34aZQ%E8_ULgb{;K#s{Adqa|&jQ(jP0_DFV$KYkw5n#2MWRUWfE7 z6s(X*o;VruRgw?#O>~M|f-_TNBiVy5u~HxAjP#3|W$mgT44-LQ`0b2_42is%;`=ny z7g_49Dc4Qd*L0&iWR7w_m$uZ)n4W_^^hX0#UgAwYvZ;MZjYUFxag}c2mvN~>JSu1# z-jAK+04ChOJJ726O8=&|4fk({(yE{IPWBy=k1;w}_8*duSJ)w~c>#WxNM4lU6(&Ts zs(o}nNVzv+G?$2`&X&dj2T2C)0Gd<`aQ93IeW-mN>_@}*541=497y8`f1RvQFq3+M zk1;A=3!bd8(}O?VI=^0rH*{f^EhTxc>J^%=L%~1Xti@bR>&GWIH!ADLWR!$fM%$)@ z2>n3I)1D)tF|IMzM^9t*FdoH9Qqy3B#xkuTHhjO|`N2UcvccOsJ2AWByJlv0y>5S8 zwM%L1YP9vd`WV3ut>koJH+q-`c3H?l#Xy&n(9{Fch%Uy2QVhDoglMiCx3B2~Sn7sM z_oZrU?)GB5MXmGsjIDI@#iL z&;^S&V1#3SFjVM)wTbZnWNf{iv>a$=BK!G_|CpI_m6_=~dA{#{ctw04d!4x~;Xz^kiO1BpuA~xv_1M^0?-~Dn(53%i)8fVx&XAGW|0r;pR&(tQ zVsLXW^)uQ#hy4-DS>dt>A4gyDm@r~blCV_fT&gsn;N--il!yCjvG=3d@S(nq?lbZ` zOuieI``-H|vYwNh(i&Bm9bj{h+2Qst&Dh~w8|w+fb-6GSxx!y)+7i?rsED;MI=ymo zyc6*VTmG0cVE1SLCL~x}TfGd1(cRa^D9` z)p@9ozK?uL(%94WT^4q?f_rX!<#x^PqQu z!}%24l1AZp{v~Y-{=&yidxVgd(;gGOM=w1@HXOM80Qw94E zcH9`?JUs_n-w!vHG}4S;e8Y&ZYQvrLhOzN9zN#Mfn53JNZRdGk~cUWrx}c? zOOT|`gX->48_vVBSKU2+!{~h2b=pc5nRN`jYo-ZjWn+HC5uLERp6eoVU{B+>3k| z(Vjm^uCb@(=PHJYu@S6yY~wE9SfDks5o#X`|5`MkWYejaS>0_eRd-v~JsIAQPp+Dy z@!}MEvXltB3}6himU|o1-RI@<_2=b%>)(=JSiekJb@U#_h)@op96~v)mAg=04#Ku0 ztPo*~=h6DQOY42P{I`w+P0UypXI<=A88$WIu8**C#d#K%sIGs^J_wz!syW(=I&XP- zJ*6DOjsfpE248xNsHFD7pWb4&Iw8(kH7EHZ=dIjx%8}Gk35uSo*jwMfBga$9T2|`T zirci3-m^?A>a^BO+9gq`EUoGNzdtYgoeK@$(kM;_VncrYW5Pw@-R^F%V>QNV6_lr|6vwUrl@s78*v_@KS7eHa?Yvs5C}=4<4-SVzcYRjjv+ecvaTAvaUs@ZrPN5O+OFz>M1ifk=1(NrnlGN zDhw4_9j%+h=j1|CL~9NBR!A7mgC}M%6Ong9pj!gNo{)Mk${i*aHYXh>yqD1oo8|Y- zM89taI;#@%9X!A$z}9~Xta0fKv`Fj%R?7HNW)dULzLQI{@>H|g>6?jqvUlp)ce|cK zxSm_6wkc^_z7UqL5sz4OWE#`;7uS{V;>4*;6f>1s&3y5*zxNnDYLghyyVJHVgr)}V7%JwdTVg$x z@;+nkoG8zmC1&71+jSXyye)Ea7JTM>8TfW3>syh2b>*8SYX1Ty=IbXOM9lu~4BlWe z_zDD-c_(&qw*s(aJf;F4kqA$@lGLNn!<;Ic?nE!-4|OH^LM1Gz-xQh?{**{zzx|Ie zoFz;SNZP>cg-4xLj<`K)FQH_SCIt(rwLulP{aIg;aAo6A(Wo2giFD387H_+N6Uf0h zvoHyT&UySA@wZo)v9Ot{U|t=Dc#H70@K33=XGgNe8*VFjj^3W@W35ZPx4+o!?|xAZ zDBn4t^jET63jy)xr#}0I@@2m;_xM=gj(;tTvRQm*VNNEo}hBiAeWm;nK!BJG5%*GU5gUe zg1Ae{-6gZ|_X6jh-CyS)kS7?c8|m#eRr?!cX=b3v*|xhv&3$F7EA%zm2{U8j z)%-}G1}BtS^fZ?L=?OFF^YRMpMG9+^Fdwo&xr^}Dt!dS1FVP&kR{V7v>l+H5k{z4D z$6)5hEDu{0DKp{?*aRO|L;G9*dgN==nzO`$R@J01VxSFN&SptBiB4&3Kn#?m4bn0H zefl$gO>8PoYV`1mH6+%OM85sb%8U{EM8+XLqM&7D9je@g!w(rF`@;`eqkb0tIyeSh zf6VLFwEW<={hTN;^LCCRJ&gu=KlC&*=Uh@G$_?^@oCf(&MT2}Gzd^2QHn=~%&gxj- z@2~514RVDQUN6#~@_YoI_uYncUgu>}7J1SdV4p8l_W32=_YwS?rFm@)XWX&S(mm`@ zdDYN!sAnc%E>bncr=G;NZWw9xof|(M(*m^>E85)49eZV7w2u7Ng2o?(l@SQuS1O;FO@}nv1ZZ)JVBmT zX^*FlPHIq6bl|z}la6Ce!+DEvR6dY{^vx3*#zA8WI)KlWhA~mF6JyLoeRM^OT>|At zIwU{w+hN`tEjT3(`d$@AHDByj6$9A`TAWKoZ-)z<-q~I)Z0$qAg`o)F3KMH76%Ssa zQdYq3s#JXWN-}f{RKS~QVKzalV8O;}*!Xi+4krzdcHKU~B~G)sL4Q`ylYT*H?qNR5 z>49F&cA%oFarVqPbLE7n0pQ5LT*8VE-!M6i!eV$9Ja&b(kT0S%;4X3SV5e%aINP82 zR6&|I0%@K+>Am=!i(|zlH|7B`lsvl@6}OZ#R`6WlU)ZormC)j|&yf_@pP)Nj4}GcP zJvj9=IcLBk?+Nj#E9@+;LOYS^$|$??-4Id7Ok+R7e>?s|%)bO?A_Kf3@;(t4a4n4X zSnwo9r&a|?ani;l_dLjHc3{lZ@I^pHD74i9o5I8V5DeZZcU!PN=Id_-|Lf)w+;fM+ z)EDpycv|joalR@%=K|Wew%I6j%sxMxe6PS0i;C)21N0bm^HZ>!>co{S02%Q+SG51J zXb*N=lrsBTvL)+U^!Nz>Al-4<&`iE>GB6f1x)ucfANJlnE~+DoAFX?PZxEVA!=||n zq9AG-P(jVo0=6s~+%hq%jes^r1pyN@nMtE))R+V%3t|?cCc&7+c1gs)p8*!VsT2ZkM~*`YTr3)lP4T zL|=)X?m60DqJ@`h2b3uPvf0*#n|bEgW<9_DJLq;u4I`a6r*N9P)JeRXCpS8uvR=deLq(ShX~ zIv{kX^de>UzYV<$p^XDVw?X4YA7>^Hh^H0tRP3WX5MRA{(CH{2l!tZB+zSyg;IIK4 zuD_XXBUz{TOa})|QKSP!zPl4=({huc5t6lQqajVA(K*7Tva6sIMGl=PqWGhO*5w9M zxU>h8A+A7!HH=w7V=&=77iH@dUHDRmInJ)RUKhv@YX<59nV(dH^?A!0 zX#LyYHW5ja5lKMwJDqCM7je<0 zjz`W2MYN~oqE|S!o%-c$ZiltREgpBQ29Nt_3eU8fYR?IRTl~tg2JSP#_Y)4UulihW zM%G_Joq2;`IkaJI5ehISDd5MK(B6_PVsfI5;9)0Q0^CbzD83^U#~EK zxVKm0^@m%0Q;OpahoaUk?sQN{S*tfa;oS4?!S(xoPi3SXMu+&KOV=2N9mxpj&m0u! z*CK(la1{arT7&GM!Jdv{;iH$IB`i?FWYLSylB+y=;aOgf+l6Sw z7Tho34LN7WImBjAYM5ih>V%Dox|%eZ6h;v}^Q=m`2mQh-i%BpeH->wm`E`M$ZHA3X zX_+S*{~boQtR(E5Buy>`IW#gYN1i!-r>GTq^u^>B$Y{d&>rH*%+5E-&qReDggPUp% z?p%{N=-;MY(Q>>cTy3&9<}cpiR#rdjR*&0ftB15Cd8phGd9uo_-WgyISNeaKx7gl1L&0I6+t%N@=i-aQN5Xd2R(BScfrx>eaO zX1IiCfn=^YCPQK%$|Ki)7md;5YmCagW#hcyieQY&K_@5M^+npHI)(b%kd7_4C|X-_ z;#cEdW2jRGf0w_Q?rvm~hMkk^&n9xv9==J&*X-VyjaeZXJBFKGk#^AFD?HMx(=K-6 z9*j9GtEudL;b!8#pfB9nL>X>WlF>VPr?G4D0^*hW0?%_XrH%*wrnbpre)l7}4MJNE z`dj*0WnFe7UYJ1xB{V0IEYX#8nn3eB(|G&Eg03m;N0Uj?XKu$&WwHxuo;1fZOjv;aD5TZP;5KRhbrbx)}TXZ4sFn$HFL$pWAC~`l3{P%~$HE zKCQ9uUO#85vb6zsrpvk6Ce$lp^Y-yHX*|WZt(_G%YKybL)kbHg)Xy~tz5I0FSTGhN zwoICL^Y@{)H2R`!tQ?*^xdz`o;Y`dm&cpWg&BFfOEzm*9(9>cp_u$-;W@cr#)iw+A zla{j%XnbljLGSuga)0L(;n=&TMVbmm; z<&X^Kt=SPjj@pP^_$53kSe`mBSL=%u&?~e8GIhK)x)NV#jBAle)Od0Aqc`PFF~^x! zVKphi-3zPNtW-FcXk^ehwbuQWL5*9BOz`H*&ga};35u<48}Vg`Gna=(Ykcvb6fj2M z3w_xZ0pIFE2lEMUtv!aX$L2V$`cl2Uv)QIb-={n>aPR9=F-7-68xds4)vVMjW7=IW zU5T+o)2arw{mncU9Z@3Ml?dUmCs-8F@^r}>vskh9-i;d}jp!R`%%0Hct$A(qTXx6} zga#%vG%%fjR10a0!Kg0lti;GMMH<)rXk6D(8zfG9N0wFxBmgv0d+K#zaf=k@jsjQyaI1f_Gnz6^%7m@F6SbK$47vs%QB|>iR ztTI_aK{KWcdKHzmF7eHN+L55uHUDUuXIE}R8F&>mj?%1wooBUbo2HgR(d}o4xY|_- z?cy$RlN7^-P6}rRUe)=Udq_Qv2a49d{9Qh2u&e9T4udQ*NP(|1c!yEOZO)wl*&*nk zDrisTpI_lfkw#{>b&fa{H=w?GjDpzBp)}v0jKRv_f+-XI<&TmPbm53K1p=s?R z>)gHZgu7*gx=kj}bVEK{5#rRIGq+h5h zT1TSkCyZ2QI?kg8+B?i}OM%;YxP8}w@5ij?EeKZ~o)wLeRqpuxaneW1;$mCQTL$~w z%8r}!&)d#hf_?ZykbEVV*V`afb}?6zzqkl@{(Q|-xSe*2u`Zm8>cxnDGyfe>3%Q~^1p-r z7_lv^8Q+M-OsRK2nCQSa{n}Nu6B~uQ>G){ORNN@s+?rW+T3i!v;_M_>sMQ_D$twzM zY!&>2!feN$%1&o`&i>_7oPLcvK1hLAo`8Dz>c4(Un=>DB6?00`|P(c%OSlzSkUk!mK4RqG-cRK|7;(|2SC`~VQ&)oBpdmgme zDmT+iu)C7RT#`085jsI=UfhneK+?{XV8i=N$StDRbW0PTg?^05wmnz@7=mPuh%@9G zm-w{aP&CQ$=;?HJXEI5V2saX5hSx~*5-Uxf1a-@^lIEpgXl!B^WEyB3?ta$&u3>jt zNDFSXwG4D4e~^Mk37c*(0#kkSC72`bKqK1a93lTRkp~r>_h(7TjVc*O)2aq*(^vA0!_ER5sUJI9W zubJ*RJ+41rh0Z)s{Ul&*cLJ|M_R4GMOaxzkC*xb3YJAISfX3%qE;_h}iS#wi*0eF- zeCOo+QfbwIH^&YB^0w(PQ`sK(z1)XxZF6&*zkeWhlJq^^rVAHWz1eng)#|BlHKA4M ztH7iHeBXDT`Smd8hN+qs+C|FSD!ELHH7wK2*=1W;w8A;pPvIQRai#3?)7@9E@iYq?f;i*A4S5w?mkGfTz;Ev zfA`+z$M=5R=Fh3xKIWA6{oVV0_%U`YQvQk!ubVa;sPU{8SFzaPFuX|Uha%TY!)!&QvX_n|MDy)zBQRo(6(TIE_~5G7Gnq|WS7xri22C2QSTi8vj_9?PE*FDE<3oupJr(>a%v zf#1XDJ(W7V46DkZktxO|?_9Us<67wPjx0Jzo+dwqiF{@Ii2t~yMM-gj*SQgtvpjH< z#t5pDN3r6ilHS+&EJn&STLVU_ad>0hQ%WxeWsYqxlBODOjO1gr$Q4OVQ`bfsA2qJbTFYXVKgh?Miu!VhZAdI zqxl=ev$3ECtu`2Uqwflwy#}}6*=BOl;x|*DU6^gzSco;Ape?|9P{_vbt@@7EIpa)pR-*QWfR>M2+Bw*C2(TA=w*3UW&oryKtyf&uo@Ob&4y3B1cUI%u7nDDgrR_jeg=hM~~ijPoQB-*!-JnuMU! z(Eq}wHny+gMk!miI~B5fA){8t<1Cr9isSo`mz~YF3UH*}+9lSx>dotI6HVLKJH?)^ z61dvo`V78Qx0?;NMAKQg|JFsf^(wr=JAD4?Gh7lUOS+9!PfuoVz9~aHTECxh033ce zHnSaIW;&MF2Vf9{pl+#-kE}_nNg{hpqf1b zY$FT@oP%f-f<~oIGfZ={X1FF+GeRS1^qP^HI8D4JS`(u&Xc9F^nqW;JMAL8@B?N1# zHM~Zq(P*?9KaE_Y(D-WxX#zAsnnHdYU&YGV?-<#jJ`Mje-#?FZh&v2wNe2EgmNItY z_^}fbg}A&0`32*X6NU79;#p~7{*t_+r9xR@etA*xG9k8Xk}$ild`0Q9!U7?-Kv=M{ zys%6tFBZxzg@UCpuSCmc6fP|;T`8<6%Ue`9Ny{{I3oFVe39(BG;$U*(M++Hw<$2dR zq?IAo0R(f(^YR~pOF<5TPbn>2mWLF|^OlfP99$8HskE?g0GhQ7=?!$qyn9B!HzM)+ z3kgE;!-b{DWPCA1+xSh+Zf)NM5ChUXbJk$zCwd3&wlF1OO>WiWg7yf{9*`*cXv6rbLPg z*%AbytRw-RhSZ81ekd~dQOJMcsb%ns^@hskHF>)G5_*0W99)-&Ua>)90y z5y$J+Galis2xovfjj(@5*ery78)3I1Y&pU2@PsZmyMWK)Rg>dkJCJ zBdh~qcOvX5gst|a8wYzyUTnc_=sU3$tfXLo)iS`oq~E?MzqH>TYe`Vn?PUdkjE!W_5+m^^PQn=*?Slp%90#bCmoS3= z_~=^+D!@*{IS=n4x-i7#rL+6!GXIH={FyF{Bgo5`j$oLxT_pGco>NjhslIpsBz)ct zc%Ofuz*JIv>{%%tkW~|2zT)9WyzbuoFqD*Vnq??{3o4WFWmU-%JauTM1o19&H@P4B z;m{9KcwLy2AkM(9%cpKYek3}~nFP@^0tlwfPM<+={@kf^2+o~9M}k=yoSg90RZl)o zP{wgeg4Cv^eEH(l%Hw!)KXu3|#Y;0J;go-Z;NQQ@SIfJ7m(5g#y=;0qaotRB!QLe2x=Kdvy=00?zw5+wiaz)>8w7qA!ZZ~FWR(%(+NzeF(h zmJk2X2VV#LD@fi0^dR1M2qL@^km4gqfBt~f{`>Gua)&}19#8=Pc!iga5j1*nsRRRX z3`=OL*ZdLuI=mUE75x0kxV66|<`QXbw zSnq=`0y>bs5s+CimI5jRui=t=rPrMX+tKi+^>3fM1Xp|ghsAmQQ7o@N+$H}tUjO*S zYq;cI<#kWG!{-iMa$oCpH%_^R2P?hxGZyeqG_C+j`~;L+8G9a(_+1D16X1G4`g;+O zY!Z%+ejP5{fE%vI{lJHp5nhGxI+WWFa3<=XyOXimfcJq9KLJ+5T@JV(a4zUP=(ksa z)8B)@8PcWk86%2sd}fTsX`&}M4gUmzNzkA%O7w$#^i%@!=UB4@F;4sFy*NTictBq~ zAfWUH`@$R9t!i)lDv3^u@Z=xSm!D=YKGcWnMtbp^`}|pyKZRE^-AD=-+wYzv#S_<$ zPuF_Wxq~B!b}c7pk`Gf6|Hg&*5b&#Z)R;kBiTn%JNkE!$uCI$-}>nMZkjE7oM@3rS{>Gc_H;Q;v+%gcWr-tTu+DYD$x-ny88c;uHnC=YxdEl3{M$O`5{Q@ z9P+hSj;VNZSMe&T9%x)4|1&;+f&EeHHyA~vd}8_H>$f_IuT{QrfU!`?|1W6M6d#(8 z1m9JAaZpP*4~CGxj(sV`%d=Z09J8eqFOQx={&N}cOGhpF{}~J-cUs&Nd2_Ql(9 z9Ulyg5BJ9FrT@yuhrW2<>gR(uUp&?UMBj`RFToOD`ja>>9>-m zAN^$?&ae_8f1Yi8iD0j<<@m zUHljiB>X7W3lwgBh!>8&>5P%$UD6+Jw68zZ2aZQBizP_N8~Ha3|qU_uI^@ZQt@7~96Z@M4& z+#B*rZlZLK`0$T>;Zl9;44_gzKJ(2F`M!0H%r~Cj?DMA{PWkbm;Zb~w8~l|%yl>u6 z`EY7+M6d1(k99iX8Xw-5zV-&(?*`oe27J&Bc)$&K;0?IWhxe5y=m!7b8}N`D@WD6W zLk8fqN20%VSdX`8FyaE_Bzhw z5Z_ZUeHay!nynaO#=RoCh;7X#n^_Ijx%E_PNLGV%6s!o#%`5!9Qp}*49mO& z^5rKp*79H)tDmCeIBYbswhU)1N5yeTM8nv;8ETG016AGp7{-5%WGDCL2eceV4Gn({ zd43!>g0Y1Q7qaR?e~v>GNAA4H3@Zk4oIn(e8P^1GTohx&v$n9XErC3jjC0o8e}|2_ zj_*s$_+k)GY0;ncgJ8Zde<Hd|#YiU+-^O$%@ka;$;hq7F~0-fzP|&)H1*S1D``D z+x{a>;vk0kZ`{A72_DdHD0NB~rTmxmLO%3I_3K%G{6EtmU5Dq+e@R0v>O-{swNn{$ zW}vPz8LNOBoLD#N*8{_)Q_V7Ff%#w>WADRkhFSEpX7Sp07JX1EF1vM7XYOl{&kmTi z$)eb{`^1`?!~bNzulAOWX`3@rmj9)<`M_%@5Bz%HC%>EM407JHbM+ltzI!lgckP?1 zi+j|0DY)qi;L z=wHXYSyi5WYS*6oe^}rB@a$Q)*Dn1$vEk@Q*`6;Rzo$v`vwuEq^BdpI%KuWiC_X-P zt@izqsrtyVmzR#75i%%s&fGF>;Ro8q505?eN#)wnrCVM--?TkvVMIg8lR2O4n6>1K z@bg<*hby;!zjk~;M@ju+-H?xWhO3vP44eK)=Ewsd?ishwKWbw3o5%0W{qn{8#Nx4* z-&Q|UUY!40MQFv*DmDMbrXJ5n)on{=?sVNg{9?X0xPe8FCxZ`Dbin%KZb%ymePtb ziDjU8WEqlPfa{*pyk$U_E~K31fxM_7Z$&w>R{l^isFvijMI}YW#Vg8LX>s1t`Q^9+ znqO`yEi9vU)>^`0W@c73?6(zfU}piTO`d@J5KfTJ%YgR(ffj#Y0gMG^1)Dohn2|n5 z5GFCfdvh2Y8!z2?3G_D!)UQ6V0r6O+c%|UJpV|E(^GZs}Sl;|cO7gtFMU1YHEyZQ! z^Kq}cK)OgpN%I#j!o5~LnfV39W%KE#YegA*bvefZ5T|NjG{$3mq$A1^%zoVjD9v%=kHO?`(_(VHJ z6HS~rX&eS)>E_J$8?%gXk7Gde>Crd);_e>p_k~h8y zDgCka$K^{sWuiCzyYr;5cg+}2c%l@*laFD)p1xX;u75o-Pa`tP$dSl@S9 z**!4FU_T7A0cIs__V9NX-2476J&AN_6HIkp*w{w4y> zc%5TIcXF(9$G^_cC7=7yVNrh8Lz_7E3CG$Iwi1SxY0Ym>;>qoz&n+78ktO&U@b}e! zDC=F9k^a+_?AHyIEdCjam9Ybxyzt6XUN|5sv{d>AidvNSTc`jw2;&a@^Voy?V=Pg_ zh5N7UT2T6dlEMzOYuH4Lbrh!pzSr#6XmFqZZ^DMMCUo7wIB!&-r~cow4@_&IePBET z-3QvAXMba7{zbY2X|A{D{mU}?v-emyavuafUQa*J{omQI@k%ow9yLoDkk+-Z1MBsF z_WyCsRLHd!p#bpkrSK-A#w;0~VqAQ^h zHzaF)n9;JQ(LZ`l`j=sUo_Uw~6Ue3{isp_J?%@Xi8N<@YLPBA;c(4PKYGRMUO%7f1 z{z-}idn=12U2xrayAdZz;9*TiS90tpoGqsw2%gULnUJOPn>gR4%jROL@al=h24vlg zw+8-@sXf|JibJ{}9Ux{$K7M8A@>MxPeoTQXr!fCyQDMOjb1L*S3}3~ujHzkU zqUK^RKKp05-nKmOT-n1I>THoyrnJ$>wzT69I@y40ye|6P&2j*H$sph~t zEw*g9{3gEIg?I4!e~1&?CtL9H-8|k*vJGcKPP)QzuJ?0D&pZM{Zmh|TJe-G-BOS+q zu-vtK0`;k&YwZb)lU3ht8s1tZP%2BigOP5Fjp?h9!;&uOjERxInFhI}q!~9AvXr40 zJRkA*a0bXPXi3svaJ_a_VgJ>2ygSxxK#ZUFD4_R-_?!V_>$Dd`?;7N~3^plSWkqZ= zUTAI}X3;_?)F9MU7;0)CWY1V_F?it*&81t0K?`1jY0tD&n=+?9F9Pofq8CJ&=@k>n zfO-sdi2XB@gRj*7b%H;5u^GMKS65&+Ud-$Zz0+WEi@$Vfwy8`pjRwdi&PFS)>@Xmu z-#NILHAZz^oB2(%U-oBixi!c#81)!$4zd_5e(51*YVj*Oyz-@>TZBoy4e8wqqk{=S zJ<36|sN<%bF!N10th2-%X3j5)P6$4Iu4tYz@H=DINWAu;UghT?nGD_Htd0Pw&dHvG zxX~N(c#}4VRdbNW_@XDy#tEc7PKUOz+{WXL75W9zsz({e0se~iT6Q2MCuKTUlY*E8 z#6HwAqUblVzLSfIF(>DY$cwa`%bV(!PiKzI%WrpHoptL(t!Zo~KixY=0)m<63vs<2nep#}2>!x3~XBeZL;(051Ys*2);QA*$=XenoxisUJ45=fY7B zI1`()-^xnv!hZQZD;v5OJc6CxxRo5ZX~t;F=nwV?dbA3;JI<`zr#J0EdsbtVwVL&& zv6k~Lw`Y$z&a%!jtnpXZD)(6PAjnFvF8Bn#28?SWMnq*cv|E~$*>mzr4pZCf``wRx z%tXE@za zE8g%i=p52TYY|)bWwZ;luleoN_HsMwXI*rC(~GeNSRKX%8Uc4Tv8KO&XqDn19gu@e zvurv+6t{NB(Ut|9^}K;m?_PtE`5@8>$>AaAj9HLQN6C~^!!&;K(dy%{o0&Giz{Q00 zptYsz4yzLUkCP_S^bTuzXkO?NE?Mq)T+WRvkg;&i&SJt$Po_Cqb%*Z{#i@YBXbB68 zaj0bKa@gx>@)Ionww4Kz&qXpwszNx`7i}_s z$hx%$yH!>OY0q5Jf~oG-)nQDK*@wDW$xU-PLjywJ7d|`umk1`v?f5{bnTO#F2h-fG zY;4$Rw)=r_7IWeLFXwX5FWl35EbON+7X2w{uCmwPzkV*4R0mhNU1yR>V-?aCAaj}| z$WM#P89JSfIMOClN9E*RXi!69pL?1q-X4Q@yXt!6P{U#)+zMf^TZ1>hdtwX)16{7;u5t2SS&S+(3p1)?8stc0-kOIxr8FolN@p$l;FJ!Qn0g>BADSKV z3neYfqL}N5d59%&C%M~Pt*M9CEKYZ@^fe2hmvnKA&*na`4$}X*;ejTlBT(sawR=K) zlwEHv_{Rb^Tw#B8FW#_2nqRAj8@E5rbrMpTX}*N){1viqT zkNz*|bw@d;w2O=O144IFA3uqgROZjnFVnzNSME^Dlq}-fOCDqPl$>3bZz7D@+jRG) zb6b|9Ch_){)Aex9jnD?)E?T>Ya@N&}IrUDtUxa0=DPRm=~W$Ok#=!b*BSR?^{-l@ zEu?wk%Qid`MSnS_3rKjriND~-LrCm}E&K*>lj`^lj6Ot1G+(x-_R1KW|9udDp?-L`Z5k7mc#$Vx!{}NZ~%j!69zW&PypoT52f{$?w4%{J7hY zzV&SDdDpYOlPt&Dm~wB=YO&53e)HbmuJ+U%4)Z6KZ##O<_{=YjocUv?z=ia14r|y2 z%>5HB@|ZyTfu6PP@Aa&6aFbR;x(9E1-uRqxTiAQO&UUUhc;Q{)gB`})i!CFt-W-u* zZ1*G$HODMw3Bz-aHWfP_Pm7S&AN~?=G~Y3SRv^4-m~VZtq*q}VOc7Z^G=nDHzjres zEbBM1*A+G+&Rik2nMKg`KE~n{7dZ~}IvmLDimB;AIj?p06b{AAadl1SFuXm%SbRrX zATz@3$2yYMCA3Cqfc-6)oiOzjr$3WIf?KEjgbfGl5gge4j}pd)DQ zj6IXJ40A?uh*_1RijFX+yX8^$Snf5;bD(X@Trw^660={18IHbzhYa=-X&vbn^UxdI zVwY2a+5JAZv1=4cOD@m2Ru`ogjj*LTSQ?ElGJ7;^G`_6Pi#Mg`dG#l|#SQP%%293_ z2C1-v5N@Ir-=CdRAoZShP;PNCTB=*TLsTM#Q3FzV5OV4{b){*oP2HpHTvL>ML80JV zji&RiCwjOEoUq4u-c{an47xZ2>?Kj4=~82kJnyRLA(}f&Vpo8w0p~rh{d;(Dd^5}apcj}-C_*;)MM})Llj_D42e~FLdU@~Cl!pyX#50j;- zAen~QLrnKZ8Id+8)Ed2OsPnFKm*vRy50{7BIXrAc$5qn^hexUGWTu^w!&J&7hBJ8P0nK{{Z(t0`LB z&^pgs8*Xt0+M%&40IO%WxT}j^->0@vJrs6wrZ7m>)587_$RoG~5<{5PEnacR;~=5^ zcx!yb3A(|Cu_%7mF{-J80OH>w;C)g78ZJ9md_>@9if6aHhBEeT6x9mf29oyKC; zqO+NDIN_l@cFQHmnLM9Jva3G>Jnu^MTx(N>UANCrn&QmoT_dD2OwRlb>w3!7M~;c$ zdIDC-X$~t|G(jFM94i91UB94c9smOGTFr|+*` z%eA(v1@a(O(9>CKB?_~&?@7O)kv*E1i8G5YAf=Zi6}m-P7fP@=4H79L7EpWixcH{s z=)0cBIsJ!N1HSM(jvWDf4{j=bvwLTd5x)(PG~&o>b{`E21NK#rTF*r{yIX>kdM&MD z;kF0nRM64rD8SmpX7_%*5TgT)J;)y}^>FzRrWxi4@NWTs3X&o0dA%ICL(f2a9Ih9G znjz=A8S);t)u5h~f`o)~|}bBz9xB8x=!RAESb0 zZ_<8O?kMhs)*pi!ay%$59nsI0cA^s?j|+NeX$|7lPhAU{V!x(%YxuaBUlg&FjgtSs z-O&Z9nzVjU)|oOU@Oz!s$4w4qlC_Sipmy_K*swx`o8S%iA;Lv2WeKvA2_=%If=cf8 z_Ev)wURv*@Ke=Zp>BOmiHAiiN{FLe-NFj#4D`;Y6j^xVR4*!yNaW_t}2p?TZ`au0l zFQGrY21!k!)wGI;^3>!EgA9?d1;cC;D%b=_93CNe#;J`aUOxqUq{qN7uhenUZJcci z*`{=i?X{aJ#w%8rnB)@7c8d!-I~EnK&VJOgI*W87tbzF!t7VoD26-e^?p2A#u6Wst zQ_W_qZ|!F-s!moWYt`9Qo$5z8yQ)+E2zvo~%s8$Z5|%g~zehP)HH`~$*pG0EYPF!O zW`e4^1k%&)ziftlHRd=B`5|G!3v(N7eigfIe|7IM)J`ja^fb8gzlkHE;W3QQ11@)X zdd5NqG&H7wGe*a&R$i|){ndTYU|w81jU<|!wLm+v6sZTz$UFiamDn7%>ukS?DGs#_vcNfLyx^u~G2u6HW{1pw zC-hP7bU)4EBRi}m)3W`rvQ$F;NHvF<8NK%Q4i0iLtK8AN07)NDLMF)g7VLszaYBeR zN6k(J&FBunR^?XAo{)J7Iu)5P9&XVc&3m76k6_U2>rL&?U z%(A2(P+Jb+9<#i2?eeW=$m6lzi&^u$>z&JS;AXp6-N~R2P7Y2<@}AbPruCTL>73_J z7$45S=-yb*X8mGie}dWUu(A%g4Xfwb5SU9pTiLI`zk|5~^D)eGaQhHrO!rwUJBo28 z^#F^No2>4Yvse*kPUs2h6x$%5Cf?){{{elY&vJ1#r+5}M{VeAc)zD;9ExDY4OEp|P zU3Q#7{Q)wKqLN~4krkxH6&iuBeCr;alx&-4X&1*LmeJ_TqcGcerQpRL&>g5Ms>k_6 zvxh=wU7;BiOMJL8{WzRyMzKrcg$`Lxy(NFa@CDGd(?t}Uad(fs>9mnEwnBr8(xgfV zG;J{%PbnM`SFEO)80mk89Ay?asf9sP-X&?=--%vWg}v;^yd!RZjLjI&res4q2c&_^ zJ+6SO^*F0wf);w5Lvo%vZ(R%C9iX>x@E`3^OWI6fEo zeYykMYxXhyT;#U2V{!$HUgyq$mW0VAT+Dv+y7L0}x_i3tx_c(foiMY7eFbuaDUJ-)SM%i}+kU`&_R26PsLyyiZ$=e%p1d)l4TW+YqW)ry(~3uG+MXYG~N zjNFW@RLgJTmUb>~RZD{9j5}(xtcG?qY`C)Kj9XV312^5Psv6R~cgC%@scU|KEj3$H za}w~c7`3!D<8y*7;THcbew)W-8HQ>svbsT=^B^x%-OSZo5-p+b> zed%UGTvmLxyf(@lYW6o{+)7G}xYc~j&5WbXko#!-Bg}7NMZ01vsMD*{P9vuHR#}~L z>nNhq=`B&&Um)cmv#L(LHQKDHLns}^OLdRXx(IV%mj5P>+pdpq}%_{zSkzg+!A!k(eER0G!OJ7JzjJ=1<{E)4C#P9J*vgA71AUz-VN zgqaRQYrRM4Tp%~nvU!hOl2u=x6NZ{;_gr-yxk9I%Zs_Z!G1Dzx#F~2^dX!sy#gTv= zh~fvbZ*^ebnx3%WyekO1I+ysq7_du?c5u3r*5%*2jeGpIxWo!^H87W04tc!%x_V5re$Vl zD>~Drvd%Q4wlghT)0vi~?rf+~b*4>IcBYvWoee9v&a^51ooUlqXG6)s-gfta-u6!a z&W6YK^tL;+oeirroee8hoehsDJHflohUK>2cIO$l+kU9Gy@&4&+xmWQyNmA~ypZqJ zeF90*_A_p~eQeI&-mC46y;r;6>%H2g-mKX&Dk~v7A(PcWM;kOl>Y!;Sv|9Os>iJ8U zD@J4*y9RMsu_Rq}J#snK77aY!hZkQK8gI@Pa)!>p%F#}DE(*s@S2NnfAYal`TAc=| zeWnPkii5eZ%&V?>mxpArxUeRspJAI~3(kqd`P_Y$+GzRJ5kGL~908EZ~r8Km-+D4d@YWHd{x^+2kl3{vax6v5BV z;pEg?o;eQYz&A)(N4TwA%CxD@0r_AfujOPC^dPbqeil!<1Y4tNB+dXi4YOC8j95<% z%sGp`miM2anJq(DG>2cYS=`*wSzt^Ru|zrM7Y7FaE;DtZ1Q=pyN~j-a6iNyNQjw+1~tbLQcZ1 z<mBKxX}?xIO@9{F^43Rz*>M zSd8DUpT$uwbse>;y0k%P8{

hHheL08geeuEG0?Pu%4pM}WI+f!o>|y&MQ{CCRFa z8`4&7dnN4%xKDEP(K0aCLCd|tZ<_{k=sg`9-l4T%i^1EINP1&Q42x0P0Yh>-rk8*0 zW?4AxHq@kb;(VL-v23IqH(fZ>GaWQ(pmEbXBSJ7)3f;%;F+CifJ5_}Cdb*c$uPHe& z2xpYXZLPKd%jdRz8~)f>wvnVpW1rvZC{o+n#lJerHVQ*y9CBIN>O|8;aYlR02CNn* z$5h6dOQd~0{RT4HiMVq8U*R?nF_G5HaeEHCQ>$qYeYeE>xh*Vyb_*LdvxVe{&uH03 z^J79*3vAJYWYABC+itlmfAu}^jfb8mitpTV|5mJf>almSzMdy{3Tz@y zTA-5((l0rCn7(P6HMbo^o-K2p`I>|R(&Qpe*RR>&5mHR}XN|2>{Q8@WWK-lmQ3R&D&vPjUliw% zYonrcQ8zImCVjOrX3c7AjLvR^ea&jzE|zKSW}ka*M{}9XuJd8C)pUZvP1MGROR*@` zTK|ABU07IH2s6g`Bb3%|jZxd{G4?da$myoC45tu?BYm}iRMu=v+c+mZGRPYbX_z8y zlyZ||qt((S=DC<&X{yJb-M@3YWe)DD6{kO4vQ4pVkSh74ddFHFPJ&#d5ALa@?0f{D zS^`fr?6fW%?;gUedrJH z@{Dg>d8Q2awQoZU_}kFwx&~UE?kgH(8i~62V^O3ca!_QDe6rFW2@T1)9a|RE8Ts%0 zW7aGPun$k*T1F;7x8BCb6BNh&QTsQ+B@AE4gvRVx&{ibtu*YDAzayXpdbZMc=Xv|b zn?-EY*lchAxEuZBu9k$N?TdHh$&#opvD5U#L2Dn=$c!QBaD`qW7L9Y!$E(0wUea7W z*06&#YVq>q9cyARzMorju8P}*?{BUihSq|^D985^@9iC8JGUmkYJ1g=qLT{kcNF*b z#W<0^$~4x!%w}J6?n!3f@nlTZI~a>St^-$h6tT9J5~I#sRjbf^w|y~%;F6$K8X?S8 z^;MibX48(U#|~4!rH8!k1L;Ky(_oZ%NW!hB4=T@FuOnEn}`^fi`RD4ZfFflhbDsBY0wG=8}5c+L(PuBZRhjnU8*b7qp`Zg zeEBuB+zn1hFjLO?6X#u5FI&tHS-!Eog|mN`=y9Hx;1^EpdXO@w=iYSTPU9hb{SsMJ zJ56Dl0M|qlxwbi(eJJws7|rqNTTOK9ABejPuEr(4fgRbk@ZUQr?w6bu=&>&G1?N1e zOnW>y>9v)Vkx%$dEj(%cAcbRhbw z@}+>&bTjw+4y&mIl4GM$!qcL+jgL0dXo!0ybDU)J&bN-rfc@fuO0;t>e(DMJZlgt2 z$JJaEpY2SKQkrBfJ1qR<+BE(&6Y9+q>}Jbt`itUw9mai&EU}g^BK5e1o`f`B5cf2; zJ#bq>2oVy~HTQ!(5!KsO2qBh9Mu*qZ&ip{H-jZy@kSLh#6Z`>-;R=gr(L%9}~X!pp%2aH6fV2gSHHT zj*Nzn!nbnKv)uBBSWNilo3@13+_W{z)_(u|DBTur(${;hj0!y7`M@htgY3@xvxgC` z+blb7jUHsuZDIDCUYZ|;^2I(HPFhV?xBp?CZkv!0*P=K+7&l0f32Ba!bp>|a*68^0 zErxHb@o5grxu-c(m!qtds_iYZ%r97sc?7o~Yokzrkg1 zn8y|B;TIh#Th50%xrQtZfKhb#3Rcx<_b^NfqymST}MPr61H4 zw3SOta|Evw!|PU_TuC_%06sj$PTyu}5LH!t8+i6u2FLISu24dHj2hR~UFtP7)>@M;HB)rc&1h;o#K85-FL5}S@bj@!D z9yUAQ!uueYR+ujibL=F{KVW1ZdFg_X_Duug9E?2wr5SGUuR)xz!kh*^_0QMh?Mr(q z!WDyVE6hol@FtGk4f7n#H!wj67Yjr5mjwkh1QO37@s%mvFaGg=5a%$Bor$&x)3CS2cXdGCFaX@i}N}BWlg;*nn zxO|y+jQ_AQM;VQixAuq<;@VczaCu02f8WgBtkiEEB3OrQ@=mSJGc+|G0N7IhN7$m6&_a;Qg(mf zlm;V_5FKeB0EphpbJEK}Y?)X%S<(re+GB!XT6z|CTz%S>5y01=WK>xAv z9iC;r$+L&w;Mp?Rx1-Iz`YQH#a0`JM_bA>X-pw%=OfuT`CD5gSCKKki*Le0RaB2-U zDVNVX$o{TWf@;UWd=A}3tvsc{JMMqkdv0a)Dqj!4d;q`xR`Ovl)W#Crg~%B;{jzKPNXzdCcg?pU6%|3%<3VvyWewt7OVC^5)I)^4@c+q966; zc$JjnRefB~@6|fh64%w^&bYtY?B)95ey%@#XjzW`b-n3!sW%lM{WkHki+GlgytRp4 zl06UkZWA4@f&7zl)6c(A*X8t<>v9_PA95NX@z&qR+YiB8MuI~~MQmgkQDU?5f$x?4$E@byqR-j^ zmt8$FC?XiXnns*P? zvQ4Ef&#H9~k@ELxcPZ?HCHvv-l!jtpp@PTtKAy&;G%U^f;W3ZP*1gNL9jD(+9cy~C z;-Lx)@;ODyr`oAedt9$}^WzUn`J5q9MXx%y(ma614>Zr9EC-!>l!Xnia|Z}@?m>om zpwS?eufzP1>pyhsM{AwD_8-XgzJouaQag0HsaM`&rT#>{@*nVxJII={ z8+i5$%)6Km9>iMb7xaTKu-<9KT+oAl(u?!KAK-Qc_?y802>cNG%RT5jGq4`|zsP&{ zxTwnQe|+z`gPaoTdp$EC`ssYn@2~Ic_j>*Q*fV>r zXFbne&w6faJ(u-Ve4^lv{8quuM)-HYAHlfOfu|rc?zx2OS5oP29qf&e2^tXaMdV84 z%)y|S5T-@geg>V=EeEUw{JzYK=j(j&bNuludg3oa{H+XH=#RhIA3w_a!V3^{wM@O(e7voY6RPK>UWO!cK(QC}PcwQepxuDxFlYgw?R89J(IiyQ z_DlY8U^lZgA4N)OEKRCGGiBqN%ghc-Wtzn_(7l4R%q*=*fK~vS!JzvA+3Ki7Nn1-| z^ijY)U$;$G*sahfbkP;;e(eWRGj24Y&0`<1jJy z!qlMs0_kuDYu<~JEJ2#H6Cz(TO% zZ8xlIW}ynCxYU?|Rn3Iz`$5ryp@-fy8yAHX}0GThBdV1E~KrQ78%!@Z2J-et%c zw=udMETnfC{+GXWH~He+Rfey#GA!t#GW>KSm0>YBU)@DzxB+h~x~L2b@s`s?Ww;)1 z3w>YcRfZho@iIc5@qM9J8B*ykM@YIa&Ru0l<8MOsiwK=cp*}g@RgPp4q5N6ge^-VJ zKoLn4zgmWK5E|T*`uq2kA=T2Z&h8Svw+v?@^cMP>B-y>`c>TDJIn!)IE zMq#WYS^DJRYu!sxGmiMbkcuxf%>3zM_uNDPZqLMB*8#ONj?B>93{X)YMez-Jf4-?w zhRS`QUHMX>_O{=j%1D;V4di8ke;y*CdLNTPIArid53Y*g`T;kq2e+Hy)WAL1gR5Y; z_6}a0@C9p6O!j-xQ@citX61Q0jM@#AOIH3y zmhL~0uFfxWYDZKHPIS;n(^=F#3KlTR&p|02`85t+X0i6e;-BuiT-416z6Czmx7I9Y zDeOcFr;dClf3cN%it3q#uD=%bPA}E$%ModQI;^R8dF&l3Xounb0&utY4~@Kd5V7q& zv48&`VrR10!H6BoVsAt2Sv|40{D;`dJ+TL}*e@e?P*3dT{~@;Vp4d+z_L>XDk8kg^DQU_9O3E)qHX9% z2gI`t?PwXE-tD9Y;m7bCMcX-p=Wl3d>1bCOc>fq}=oO6dA7M;bfbm`he0Lf4{^r}M zIM0f2zK3r%eV$Og4cbzdrvgyp=Vf@OIcplx-)g7XueVYMpr`HflrSiv`c0-UqWs&H zs8feGTt;m#L2sAWu5D5445(Kd0lnm>F4GkO%JD;mOn>b1WVO?1@cx=E&vW?d4$N2F zt&jP%vELx(qwPAk-KsEDV#HkycqU+OmBR2W;FkbT11tlM0FN|QOl;SQeU%7D4we8O z*RC)?M^nXd`V#ZGh&c?DDkYo-?e16_$#4&Ne+}9s?Ac z!?wv9r*#ciwkJO;KR`iLo5NPk;L8%KIm1Js{A(Na5nNdo`gFeR{k2n46}aEG%Tw33 zry#4qmqLiMc`a9_Uw4_oeA%9LS?ef7QKorRkjv~{i+2O=m(~juq60JnkdZ+FfJO@2M}}tP;;i)Zq=F`2_D#=>Kox|6|nGvltJ4#Mm$o^_a$y ziHP?F;wb>5mSEn5zR5Ew?uTrqwe7*|3GwNZF=W7`+FOdeVGk&y+i2wE%19^S%M5GZ zffRX2F|tixaCxmSg}zQJq}VN0zDJ&l9(gKzw^D)ap5w`8ys#w`{d~~+KwnU{_SmOEjMtw)GZJ$E_txdxsMk6? z(^208s12acUW7K$(^D*uP`x{2Z{AWvgyP*9R!C#jjJJ{g33(gNn>1sVyswX9jh_Pj z?y4E3p!gINLC)s<9*X>BzBEwpSIu}06njBI{Y4MOT0aH)msK-JOD_Y(KxcD74@H5^ zmj*P)RWnFiF9b!Hvw2+)#X3L5I-;PSI2RN{oz_=DF@7nl#nb~Y1UAyy%&^NCmbCQw z^4|5?uyPWrEvZNcW7Fd@2mAj{J6JWt2K#(IXlKZG%Gd^bEunfYu#@FGW$eQ~4(xdO zP8r(_%RozZq)}!%G%I5t)(mWz%rr4zZ3b4#X6k!`Wu{a2!cGBJfn4|{IDXZPrQj?+ zwbSGAG94SVOZ_R_5A2U#gWoRc#*P8@lJ|YTP05unHE3JHbi|zzA!oca>iVon3R`B> zwhYDCGYny0AuK@f*^(4Sy6s@#KlgUqAKlpgz<%Q0yB=vdBNR3!>0sbK@SPk1MGwQ=IZhMB ze`_x~9&~BmthL6qemc}rKi%5bd(kOCHwpPz@kbx8h2Z5O@H*Dd>pDMgBmBIs@xzgR zULWe=^&!UVN~HY|1!v>^E6`Z+{wUt-yArC?(Uq#?-p)I+BizTp zwRYZ-9pQcp+)d!RWkF+$*$s8WcOvKPj+QJvOC;KvU{tS z>^=bv$*#mNyWasO*=_X8?!8W0!|pA+9i5!`k1zh6ENJ9h2~LZU@9ug;xaGjD=_L!o z_M<;0zW&toy+$9S|FImj}6G4%>XF_rn`FN%!Xb;KyJQgpS)`II{5oee>q8U4owBoMlpgQH zP5@T!@age1-pyvTwnu^;AHoXMaa?GtGmOjzTN$ot`}%=bHarTd+VMiAK8_ ziS@ezynluEl8-T=81KGRw)sTFsHJ1)uY}tDp^tZL z_H(?>|E*aag?`K$Kjxv1JU`|YKW0+L3O{D0A2Y6FQS&yLFMZ+0&Hzr#ETUd%&l?|(%z!Z|5MEC7YqMY>5ax0|sFZ;zin3?1 z&O|e0ou2mgAk2`RJ|Ejm7GhRjr^nSEgLhir!1&)D)ojSWgs=3+`Z#^He@fOGq|$e9 zUSJCSW4uBMUt%eP2HyllK7H3vaA_?tc(T^gH>bnS>hv6IH@tdjopBvP)@_qRDZO

w>?fhn1I6lG&!cVPT)KYe*G<|KS;7b>wI z+M-XZjzS%N0~W8Sj6f^)>C^&ng|Lcq*|_1;s3U;;4Xpa1snLe!KAo`t_g~gyrN(Dh zQ5zynAQLM9KC6+e+Yn|oqW!mTyL=V;?~~}8m*6SFvlM-DA)Xz0KEhLg{yGu$?;7gm z7TAp+>^Hv!2>Z=%QEPi_ zKV6UQhjs|LcjtiY-nIT5w6(?muiXCM<6^#_i_hA`OaJ#|;hR^WcC$Gwux!-~(ho^{ zSl!k=yWrFRVNd(#7+C%D&oL+eSb+LA9>QAev#{{}!urum6MfXh(-F6SB73{at8&9dg{^M|H1uMybJ7v7qQ z_O7V!lX5Aa#)bgZ^c!5JB|rH_kbzIB%1^n`*9eTB+j?oOP~Z<;i;%U1<+olc5Z3vz z1;7>%R_`>|7h}9lg$N{czAOK5kIipvs7yZzQjCKlx$*U5`g9v75qx zFF0h{i)?fxR12x2pdWlF53USke(Z*&-jGVA#M;oEqid`_ zs2p0sl@Hh%R+(7V=~*VL%mXTZmXxjRtQGb#PhqESd41^CTAX?R2JIjeeX$j`;&1T0 zf#*Fu2k|7K-;PDUt%Hs|AMJ+r0MSY8Ej8*gwRy8yQw^V?h6;0^t=FZG-1E{S^SH4| z;$Hib#m1PRi;;$CoXew=W|FkJm5bJ#<3}n+Im~LOY7{?`cLt48j@*M&1Cr!{$B81d zqCpWI+Nj39`p3=JaYj3d>x1_W&mOPJoapHAlzacAR^kK)z4a;WQ^7?)*6_2mk$Es! zj#GntKP7$@{URFsIQvwh{go?9)w`l`j*r`_1cz@*K~9{_{^$uEJQ0nV&{Da~Qp^5&BB;mu*Xa^t4&!mox0ysdN&N~aV4{K&z(X5+-G=EFGn zQRwsxmS@)v#)-J0Hk|hl;YFbn|3aVip_YL-74fq)-mQVh+R)s=m7IVx_gt98ch)eN zmD@z@&sq&TXg9`A)1h#)vQo7>#qo%PiyCH$vM9G5y=H<3qp@{iW)plHnU3=Cus9T_ z%_yFHC@4c?o@#s3!AFs7IsI7k_nx2*!9iYU+A&~fY$p#BPR*4kcYKQzv=%yT=A%J#lpuM z3m=<#^*kpSH(3i~=;X-j#6A7XIC%-bu8`u|#U?Y(Y#7&Ow&zYZU(Wj(eDM=MKJcQA zyI*WHZW7GTMi>jNo1TkUScrYH-rwbp%1wl1Lk=A)P{!yQb`Rrj9#`@4nU?o0$5!8w zDV-|qFAmp`_Z!X4$A{1kw~QN`;A=P7%!|Wy@D(c2iP$Od-H~RlJ8)gP;d;^jf&F{? zK6nT<;0%t>$75+G&i}?8f#=|-9N<1q(WaBv4#O#A<$T+Q8#={&%Mi@vki@j&a87yi z=^>-+NUL6T<=q%1KyGTiI4 zc3D!EsGUVi(*01;k_uZx3%sV(vGao{Ri&8|&<~pG5BNNZ^|7V6lj8eWbhbV=&Yfg3 zGN`IGDRvfoC-GeF!@5J$mju)nF3~&eIJtGz%L_Q!WY*!Vk@)aMS+EZ;33w;OyvaNe zr+_A#=`{FHQod({`5Nj}Xq(mppCePwhaYVBNH3&V?>iseK~;zS%jI;gHjHVZZKQsK24Re6m(!!&^BN2|;q%Y#k9 zW_!+boWdO4sQPdC1q$JIs9F|i_#Hdp86m@MkA>H)$5kz}HNh=|l?t4B-ADd@8jd37 z|IznwDLfG*gn}}QdK&w)Vb;3!d_PJ0dIK?d$~3OAF|~ zf8z?Otd}^`N7nLmr9Q^O88*r$?u0y{iq1b>5p@PN&Q$X|uS>Zu&bUz?W#TG-%=#$n zNQ?KOfpyQCPvR8z$$lku$8H7$oPg)Aqmb|ivN`tzM!f+|0dx0P}+ zGaL3V3AHt8RcDP@R|vHLvV63JYWFVGN=b@!CEc2;tVkZvcr8bg9&o9n0~+XUqDvR8 zVs9o_P_(kKJI+T-;OAC7Ab6-_zAYJh@pvanbma;8qyWFdZs&lqppEh&_HI!;5xi0U zT!^qK#iUgceK*QSOmu4h(S{sw^lcAEfGkV=9MPArwe;qwrKOvrA6vRPy4pem!Hp-v%;_z@U4r@(WI9<$-IROv9C*l`e`@`3^@W>aHdMtdmlV8puU3ll{?MmlH`BV6d3O0K0{{Uqk)Z`r> zfD@!@?9<W1b9~CMMWXFR@(M)Q7sXK+ z&BzN~X;-~tB;FNx-`rK|pgcY6*bY8YYCl?bu*IICYdVY8L2t??9vogYDZ$}Q>3a_s zMZN&L-s0-$RJtVPwUV!_K)mrS$5dKqA-wp{HZD4-P7snX4hndvKT?@T<^+7G8oww9 zV-a_Y0wybn;MqoyH#WK1m)=KDDRh zC~j&7Dc$nCj2q#j=gO+7{DXaJxl!*q1#`v2)IYpy_gv)-pC`?gL#7T*=EO4l9bS5K zeY%CT2p`wS!OId+YZ-JqXsp4(j|p$!<6dr@;m>aX^Kz+C}&sfp;z9t(APV zBK(71U+P^C{=xjt`|3eGczn^Lws9lXBCqb|v7}eb-qJsCPs}vLgKM0Z(hY{ZFDt&TiGWB5r4XLg$<4$-}b+qot2&x-ygth1# z1Fd_iQLXnPXWx4+$f#=yUk&@-^PYU4@RM{5p0zaSWjF<;8Rg`Y@0S^a6c*@3IOPHUjx5nSPB4s(w3qDGV# z{UKQ4)QI9MuLR%CC;Y@RK7Wwz@L6ydpS{2SQqR{j+<(Q_1McGUPAzvy3wkJ%Tnj+y zZoQXJ;o#kT@)vLQWjsFhO5biC?_;ve^7FW@7ms7^;<5LahxdFr!~GY0`LovEvg|GO zz#hsZ*Bns3&v-nP{AD9a@_z8g&B}e4?&!nlhB2X^d1DPHl%@{C=x|*cEm?3jf3kTH zJm}u^YMoxsH7|K*u4!*=<($wRh1XNBOM@ly2MM2&uY+kY#g+7ZU_9B;ckkV9a?Cp z2@Q98wC>!+A)U%wcj^?i#Q0W)t&YpMRkSKJNh=m8)Gh0^-T?lg(<~ zEgael1DrvVxbdqS;!C&~sWI0{BfE}`bQl{AE-8t9k;mvtqdW2aPq)H$Fi!!F58f>6 z_jJR=HB#OZYhoH(7yWiNw{w*kwNHpl-2QR@#uF>O1LxO z!|PI43%t-zx^>`>$>Qn9oVt&=@&I^eBVQ89X6;Acdq&8-aWwqLQoSohy(6z9Wu3+x zK4$F2I}+-hw^jG#tq(kCXnK0H-nHiWdnj*7O4zw}YrI8Q3F(vvRKlZ0Iryl&;z{h( zqO6G%@_(M*#YI;)`n;D?KL#0Oa8a+Lf9-2lZC7u1Nq><>n~T7;B)#0SCv^j6(5_1* zEwfPm$T z)=J&CsHX$be(Nw!3`Dzb2W$d-i`sKcj9(_C)6diRbb6Qcw|i1sc-MD(m&@UMC}+D= z_-<%Qj73%Xk>gIO_vZE8o;VrqW8ifX;wUZZyXbpM{k0x?U*7IR-jtSD^Ao7sS3H+H zGu&;KAEjyV!#@u)tMzEvTrByP8iaP~l5nLhyW0%Dtv#{uBv1OL*YjQ{)tfO$VN#pl zpMH`{+Vxo?bQ5@;i5b_Jf)OF`;;~+~9r}O9%U%6HM*3eblQBJf`$mdCwZOi>?&lcX zkTdpTcj~=!+W&v0UVT^Uy(Qn?V>|j%FGuPaRaolq28P+Z#6#p04tGSg(_BB@e8)Ppe4ok$948DeCPx5pxD9N(+CF+Gbt+lj^6Sds{s9MIrZ4SBuM(m@T%DpIXf<@<@wYHd?a+KyUkN!%wZ;X1Ymvf_(gz+L?&^y)2$rkTCXZlKTi{c}t4cN4 zXa*(`_QvMJrSSc_GnsrL$r(Y2>2ObX!Ap(pN9i^A?ks?hNInQ&s+@dWctEqy+$-|78K|Vi5b(_Z;X6l zlruu+-_pWsb`a9cn(1leZz^<~7HDrOF!M7lKr1Us@r^zR)$%Ryyb6B8WJOgF9zHG& zw>{;DQHolbuYY64(~%lye_2t3G=lmdR`ZO5Grv5|$15iGk2&0kxq=M%J^Bp)Z{fc5 zLw(_8Uj@mGaGN00cHB*2b9`mdK4vneSmJH3Ta*@`e*L_6BGy)9c;p<)$w#+AuDU~- z^a$IOT6KE3P47@+H6{?+{5M_>v+NYU?-ic;Y?cp&q-)KQw)@OqIGSyhj^Ibf*mfoN zYjjCBBqvJZ^exU9W6?Sj%~9|}>BQR1kJ6uSjWu&)aess{-a>0hlpd{Htva-ExyroE z(IB5Raaf5sEuRsT%dzIxn6$hap2aXTfU&GanhQ_=iVvsDx@j8oVzf+tf3SGA>z!*F zF2n5=u1Y_(Qvd3bTHMXcpM|IUlY;8z2E{4)Oo(CmR+Nur`Dn~g6)+!wKTCgv?Ud2* z!SZE9X`&Sn`mT|E*K*f)!{H~8{06(E<8B_F2|4s36hFgl?;i;K&+y@vgno?PKE!O4 z{UzV~TgWwZNpHV&UjLP>nwa74SoM{xo9GLD7ol{s)2{y`w{Kbh{X%1Ww@HV?-8OuUEGk}t`Y6mwJs{PYMQ;i2 z{;3u#y~Vlvr54L`F>`wj{{Iy`e-<7+ohiKXtOAk7MC)itK0BIb#eFH%jyEpRAP#cq_UX~v?$MSohPTm&!q z#-c^X7AarMeb~OZUaGf{*ErwortY19_u4!Q*}Zfw=^xD7cZ+1a6&U?>U6VZ4WXb%x zZf~|Bx=B`<2=jYkFEE!g&vB7w4bcPH3}yebR5$28qZTQf+0rg)ztkYBCUJUe7S*7= z@Xd!5CRwwvU&Ca?_~W$hwP39xzS+{>Xw2Nhd?^fZP6zE4)Q}eG6l!&g^j+(Hj&J3G zvF|1;8@|Pg&3nmw;|b`av|}xxMHN84fHD6%^YZD@Xm7a^-Nqpoc@A-gP+B2G@7(Mu zfj78lgVs#*L)_LD$Ct3!HVm}o^utNnQX)S}?xhrUm z4D0BClUsOJ-(Au)7Kd<&z)eBQwC>XEc>wzUhR$2DolK@RK3L8&UV z43F1-prrPUZ0B^Sb%B!oz-$JoY222m{@JmXtHauL$k8pRE0uUs7He_8YQRM<7HgE) zX)^*NGjD8>(y^Zb-!k<*s>P@z8~U&uCbiOBL*Jt`R(P>GXtI})ui?^u82ha&Y0QFG z&vy=EuK;-u;dEEgrl}NgLk8|2Sx^e!-0%XdveOz@l6b;ScIyN1N$B+?z%Ij`lJ*R- z{>h=X(0&ScuHI-VKY)M5fZ(ibYN-K1<;l%feRg0^d)48#y}hktQpUq?y`qVFhe2J| zrGlC6S;XCfMzYx+F(J*zA27Fh-o8zxN^`%P?eNq55%wvp+j4CdM=~agDTrSMJ_=AS z@PW|@pViyRq9t#|dzaDq1uiRPK})66Flc{_RbB&`Wg+>N8r(< zE7}}_5Q8fZJO$(JHk*;gonW0B+! Mw%74+XFL}sCCRg@9XeigZBR{>d7oT<#-;) zGyG7$M-*mb-jUjnS|$9vVy{Vh_%P%Q_n9inm0JU5&2WXhL?wxNtFCC=<4X5=(GCLT zEb@n3upaBq1-KU%X;)#)qPnD|8kAcCzpz*%eYfvBKT5N({{FQisB$v1Qo$y!HqH&%yiH^F8op+K<`*bG?-(Ut_CM|=nfbC zezswCqF1hWsfT5&MD+(d84x_CCx8aV2 z_V6`!Z{R-3mPnlyJ5YW(Px7mFaoC6A^AF8Mxv*a;bygwwp}CMNI9F(= z_cbVO{roxnr85dV20KURjP|GQX@zc*bq)T?f0Wj{_(_oyiNl}q1)3@UQF`J{=5n7Q>r;#e%4j#mlpWQ76$Jei<@2Syo~e3M#aGFCH(RxHw7wfcdOk zq4>qKtBqE2|J8;1&6y5Hn{lgIC+axI;T`^`x|}aIIjpwu)FRoL8hiZp< zN3?Aas|_nVXG0I+#5nmlNX?c4pt)g3J<#X=Nj-l zn!1VI2eAijzH>_# zPjg(J1K7mE!#fBz0iM9%FK-h(0dOLNBiad0jD@Boo^3lLKLV^qJixek1XQyC>%^6v zL^TWW3>H3yr7{EX0u~;^=obJU8yo5%+Oc@IAWS9xp3z$H1)aDLqo-PY8@yNughzc;CouJfzey= zy$cA}iZaX31%!X0(LsJ|I&pg&ds~gSD)6inWfrFjJd22<5kF@nuUZGcBG87yKobOh3iX6gTy(MJO=fy^{w72~-CGP|ax z_Fn<#XTOy6R@&vP?`72rNUXciJO{wRodQ~gk3C6 z+~ic0XZBTTaqDm8QjXeTwqdWF#^~ZfSLmlR`RV3@F0F^|5k@x#bj$p7qx^K!LH9@x zT^Xa(gKnOm?mj==IM9_bI-GcvcQCpz(9QJI4f4~4gKh_-Q-SVXM%NE?v3|NBKb;zM z@AiCmn?R{n2)Rc`eG2^_j}p(eL-2Kle*P>T51T(N$CcvAkz*T-wsN^Z2ppqm>X2l2 zl=HOQGTK zC)VI@TX_B+ck2M%X~;Qsf-$E^?%-g_)C~d;5qR{(j}7HBBKHt|NohR&5$W%M&qtG0aS}@-5xd*FkVelDDm;_*O zoe?mD{Fw2;%vL%v^7Qv(qJWvBt^lU59}@|TTRj`SYn3e!I9Un>=D3zK!E(g@EUxaQ zX&bob_zm34zZ7!AIt#hKC2Zh6_(vgh0xwz`*suw_*k<^!sER zpxlAlC>7pw@V<25K6$(bcx$mNWbKp3E-IEMWEIOsJbFB?>|(hF|DzV_ZDi*b%1@eR zS@rO~UnoyE>1~B_GW%_`(0dAgi{-wc@0SHhEyC%37TU^w-R$=r_B$b#=oho!=uCRg zWWPC?2HOtskR4wvFN`mfGeS0L!|FE3PneWOK8iCG%NJm2T)}?US*!NR6DAhRvls4@ zABzHhu1b8ZNe$@-tnS&Y;8kaKTxh4Fx=d3@~hGAAe(eMN8f6ysL zo9*E$K(pL5>ny$w?^FT0mP8%(EuWfp&TKsIyM04^z58FrJ&?Ew-Zs{(bAIFdZuk$< zr|!gwi4NNHPVsgpw@^G4?W8BJH=Fz8u9~f8m5uI1R^F#R2Q6?UD20jUDd7HVL!yu@ z8|~oKz_lxdlqGz8iq*F#kGR*gB=YY)R}nt^NUC`_xcxJDKOPsf^WAV=QfnS#ciVrDe}Y+<;@d{;L0a0tB~F*V84z_k)!OCPkv%w^Y-Kha5;!0 z7+g-*+JdmERe{?`Phy!vnD@YLYqS#DTTcIxNB@k?{^L#- za*nYZxZC45aAzOqxY20y3R}pbQ&xmaz=iJ$2{`m31ZxNFK(w(qt6Um9BxY&i!W%+#+((eap)nQ zS}kDZ-SYH7S!ch`7AVRWKWi0UP`4xo=o&t2t$#s_U;7IK>jFm&9HnqpjpCfqqd2|S zNPAIe{7{rC-;P+5Y*v-ef;~A;%(+N4Im5I|t8BPgk|GMqn|8K_S+t=h6!sGG;3OH&Tl+3BoxLZwydxk-yz$C7q7vFHZi$mGpZ`cX##_vXE2BGL@;Ia9WMvdGhF<4({WH$01f$GI&;CC~L4q@q3v=hIf zER74`Z#Ih)%-{}IW&wET#HrvimxU<-E5$P8JCDJgcFJoY(#1p__>+Aw_*>0j2{0#q zPID0q{t2*B91FOF!B-jmVbE`5@Fhl{gYUh`;PWgz2jROJd={`xEdJJlSqYtJ!gsTo zO#f{6G0sl`ze;g8zE{QaL3`40 z|1U~g1YJWmW7og;-*=NRE(XRI*O z87HKGrZpr9H0`<5oD+p9&ZEL)=VT$lIaQeGd{Br-_%gxdd`KAYOcBO9bA&O@hlOZo zsxZ6EEzqJR8n=a5y1oG$-gLXxBrj6No6>+0- zY8UrMDZC)Qah{7hnh9GuZr(h?n8cEcs)@q5R_G?r@JjI`^dMcH1X%xJuoQVQmRY}H z6A8N#>njQ|xy@*MEgX6`W=<_O+*<*d=3uaniTR@@E*c#46@9oS0ao)9bmrn0+f7L+ zPBU}kuPi|(+4Bc*nQ4RPXPI+q%qHkPe?#-dxR|lEh8wZf z9P#cuvQ5yIUX=4{1arXF3bU^4qHM1U+7`U6@3u>x3*K2+(T&_StdizHgTz?$*_dfH zzS7*~8QK+${d;|JyJ~M5h4|mMasPBJZwx9|nM<03D`q+Nr~V*$TcaJc7w!jXhcvv+ zetUQ&EV}aP=AixQ=yjLkR4kSFrXsbi?=CZLFg_t)5Xfrt>QBgLaPp|hv_ZCnBpA4?}>mS^@Y=M8o_d1Qm^G}O!c99C%v{+$NhuIc8DvR<&u|Ao&}ZzV)1eyiA++#pURT@C z*l{@@XC}8?E8*6p=clU5^V9Rw)a6(Ocw70l*YnRdog>6}aC0%mr9 zJjX>(YYeQ^qL$RePB!=3Pqpry?`YrcQF$uvLRE;nzSG>_g1z%et4_$*Ok<2EaBF=W ze!nnHGG@5fWu1_Zo042-p8P=mi|~P5Z_02#x025P{2})PoLwrGwIL(<*ee1C92}z+kQJd#Ui20ruc2xk`Z=IY1RR9rxhU8*Uy5dE+IVYm5^T9CFFdzVp2PXzqn%js1w5Cd-49yv>g||Rnm_pg`;X3|%_O;&KVF`r zHNSz?^Z`p?HRCkY{KiIDy4Pa{piLIzqNyS|744{a0<_Aw`Ptck^BGL@zb0Xx^I+?y z_5GU;wwA1qYifQ>#O*fnN3De?pi(N=Wdl;9#%+*gnH4-rPOMt*y+wQ{`zzaOj@7+-Nc zd=N*e=iH&m+aRaXzdsk$pZpE$8kE1i@_vD2vj#J+ z#S;uU;dvp8&*eQLeu=i7Hwivf1B$SIdTFt+SQV=j|AGCnp!+mz@gw5b7yc?IajMoU zYlX%0%;Us4=QM`sVe`!2OZrw{9ourgcOAnGtXW;2Os}ps%=llI^f~>ENiJ{P9+`{X zBP;ZLi>%~Z+zQf(A~2hAT6PP^WD(Cb;j}E{|3^CCHbz%hBo8Xp;@lsd+*RTf!$#<} z{_^h#adQ@pj2;(wCw@6SZt{zaRf*4C#Ed`sZ=p0$e6)!(tTzU<%*GW} zd}*jTpe2%=*A9F7LmNXAqqFtrXzjJ%6@QipJl+z*0x4qas z;m%)WyhN?#N3`H)_>kHp*Y)_Jz+g`t)`?=xq2h`r!rqMUcL`dj8ak#r7WFbzv;v>u zUb{+piO1~carH)=x}%h4c7!;RTtCHooEn^hv)~+LmT5cAMDn<8G06Z7+E*54)k;i( zhN1Qh_nejU?Psx9fFF6>aG!m?o#wdj6Aw4(95grZseCCe#a;hoig=%LAKra3i|P0h zySJ(AdBd*N^zSvCTMb%6hRch6^1zHB>Qj0A7|iduIjd0&QbwpEf(Yv!iIT(IfD<5QPbrd`lUP>;p?oMpFFLP z+A5wnHyQczg@^QnZ^zupUYr3;Hof5(B3f_?SSt#AV++q~#UuqreEq2w9(53J2J9s8 z`s(rMbG~}awFGMB$Rl&l$hHBXNtf7dRH1K z+>oZYp~-70?9r4^=SNs0r~8LLS{N+a*nWVQ=K7U~SHps`$uHJ0G=zUAa&!0UOfooG}IL6zJHqT-x$P+q=Uq)ix;;J3Q#^3}+f zc+Li$FR-eF?c>aKs9`@uLw8spM`Jb1fV!WE5uf(y-H=WlOoTpVKpd>G<@^_J3rv?PYg^sw~~*K9CxdcG!n1pW42P*7b|jH-HD7MuJ}?3 zNBpeD|2n|57qvSNbmv2N9=>5~;*^J6mWPpuW5OQA*K2~1gZV564?&(d$)Km6LP(1? z3+cEy1Sdc(mbmlxrJse>y5(9Jr`GEYk+80Av?m^k=!G3&2m|)Jc4zI7bnJYBX9{Jh zs#IM%DAl(LN_P$9)`q4>+h~?#K$;RVnCy+N9b@B-do3H}4{ z%Xq$=Tg0uy|JJ99xOedXDBybJm-0}J|3=_^dEug$HRzBZ7h9FKvHT2V`I(6POhkVE zahvk<{Q3BF=>0EIewG+0XD0Ao2#f9U#{1LNIPcPp9}E1;z|U-qL5xxsV=0RfjSw3` z5*tVMgsfm8Mua?zkeEh8dLT<{Il~VJJ_GpSjofznW}tq#Ap*Ge&YIexcgZZ;)?IJr zb|ux~bU%9e-$SEUp$3y=1oI8-P3J~7hwuG_@GRb5WOG zt0CEs_wNX(wZDM&Kr*BHWMUFnfZYBaH3~Bn_W3<^hiKe}rx8}q!gkx|_JjrM-GD$BM8GzXQWtYPwNR&>rIAP2&q7<)s5y}c(-8&@NWWdYfSEiuQDV7 zAB2<^G*a163RBa$ZDdb#(NFksZbKr&8GxJR$5k07Fx?)k zho=rl&w|}JY^On&C#f@x^?T8*{m`2IJZllQ=oNAQ^C`z=)^J#t;J9Ugr{UR|g|N&b ztUGbsQS4v44)`U&AL7{w+F^jFEkd2bbNv&JivVsT;uYel%|<_SxrC!~7mP`IJ7(nS z<(iN$v%;{qN6ad;^4a{tVS1Wr-C*ex6=L{up;{j};!l>DnHw{cvs6*_@?n8rWH*5N zCCn5qQe_^`+?d6W2#CrM2L>fu`;XEY3KwOFqjXArJ!Y9zSu++L&rVsm@x^-CRR1UI z>kGeHqTgEEc_l%vf#IPcD-DJ=j$XzlnJoYR4ewcP-(0){vxj`PQq;cspk*$Vf$G`*zf5PAifWx#VhY|O` z-;h3FxG=299#@0`*9W*t*x6sh^(XYDC!&Fi0B$SB!mpozEvwK0+6;<0N}|YqA`I{b zgwA>Lj69HiXDv%}Aii^kQLhrYC@U=Ms<_j#6T5tFNI9ZvV#qi*H0dfvk%jb%v7&oJ zdXB{{o`6{Z2G3>i1;Ej;+iysZGB|o-BK9)`nHwEnFQ)G&1KSMizXW?+pcgQj5)<|< zFgTvUZ$j_$^)NBmhn5RDWw;OL4n~hQM8t>)O`#aJQh)JUW}bs?f&P0Jv_4>T;t8Z1 z?KqA;MuB&viP4irj6ZtGmjPQfnT|V$M=EWo2U)Z_p~Cq5ObYzQcpyW}1kPnEsh@Io z?z-dz?rQ~UEv@m}FjvFD-_AF{g=xuZ-fo9XvjpL~bP~91&}Fz^U1fKE4a_lM<|rwq zEB-f#c^ENkfh$%K?soh#r(IZ-a~n8Nu=7)3HUo2p@p>!%LBz{OT6W;76xiL!^K z@i+(RUSK(CkJsT_uK||^9LgQk-gxFEs4x*+Jp^o?hBz;@N>^!ju2`6OC>Xo+X^))O zQ{X>wxTdQrN9VXM#e1wd&pIk{1iX*KJ7?IHWr3VVVa~;x1zQYz1+Gg1n}Nxjq{m); z+F$h#>}4=od>YtwssCq*SZ9)^Yp^4EHCKXFoZ&bd*mg^0ro7yU4{?q}3wBOKtDLB- zoNlg{BNHCLEREOmmoC8)2AZ45!8m7LKRO)dC{A=GqSw6xb6s>3IMv&H)(_*B z>%ti-j#JwD7|t&~CVwH!&)k*iJ3}Jlbii!1JJgF?*g1jusOy*`puXZ7=A$~$hObLI z8Qc+X^zUx$^0dIxqV{uLdgBivN0+bL#M$NgP}*}Y9(o-bsn_#ooKP;bT$dI@DpZ%x z*lEvYVOG+}D^lvMC`++?$@&HIHV^X=v)TOk=b-q)a7B9H7Id+d)>V5~HLu#cdYgPH z9=AKh{II&Bu6eZ=dUv#`kI}F?Yqeq3<&|peqJ}jTPd3b`kYac=obHOAT}->mH%8k^ z<=veAWgfGySeG{AMthFOyhyzg?acE2%NDBvC#cjy!^;Yx;WtLqAbajU`HE8E6ofta z=i~V}MXWC7r$%lhKMEG8EBf(b`F?P2${j3zc~JrTcB6c5Ww^+HTP!zORTw4X(&Ey& z9sD+4yx38bjMa73W9JD!K5ab1tHu7_nq-S5<$Q+wL6r2lR>&E-pVAPE9_Yc9SEM7Y zA&|-$;IWG?`u+yJPghOXr}E|cbVHhwW~Th3?WWsu?S1LR@|=~lRzfQ!26F_?^Jw*5 zp8shhSzU~e!Af?yJUwKnIJW6)li81-JB5nc3+(G+`I4bjcjJ*nTn1iz^;OqVBVU&N}bDMno z@m{Oxo7$K59olr_r3=C70yOU4R!KmrhS?;eZqxolujjm%))O`8jeTk8|53yZ`g;-g zNVCr-YQVb8Akh64^C^kE@1z(fjy+7eBNsiQVfy05j#8{ZM>v$&>3U0=DH?5=i|Ot6 z=<%(YRoIn!U7FD9#29g1GGV7`bLO4;yb1fKef8ORDCFoXuTg!D4x#$&(hT!UY=pMDt1`LhweY_%8(~duvJeFm_88zX;4$JEfhP*jNIauV z#+4yP>q>a(H1&(AsI#t&$>BzZE&j~{Xi{fQFo)UOF&rF zyizd#c4dV4(|1(2Mx~AN*SWpzHOYc?GOKaMv=ROq*VIWhZv6Rb*trI*Qr^OtXvs7k z%y55>8o1N#%;XMI?fV?HufQF~>RVWP80uSCdN}G^IChh&@J@50U7idXy&kLqs#VD$ z5#n>-70aV7TBlFG#crxag>I@vr2TPq>hGZQ{|og8|I_ijbh(JTi2s@27jY#h+tHVb zIFk2l*a6{(IS#AdtCMcETaOj``lnJyq&Vn;#ij%Aot{Hp=W;5?*=}L^b?H%j%~xu( zKBaoIykwK#2Fpc>jAb4RC!j=fQ6l}^F}5g+)e?q~;cj7(06#`yz$@L^zzlXf7d2sz zYBn%!Qopp2bVXT6nyn_vZ?Mle+S>SFI_>i#8yB-6uyPcl3TvxRH$IRa z2hZbS9EDCcCju|y+yfV#h7`7GQq5%ND8zkOpPuf=@XXG^d^vCleq0>0a};7}S1$HD zP`rK2&QXZL-e^C@#Oxe}_-dCEdmP9X8fP%D)L-tRT@c$erNG?BFn{luUE72?qR7x!?zXm= zYYi>tJ^=1p{BOtq)&a%bpYhDavj(_eJRc4O4IbOZO_)zurc$sC_4>rdl6%G$I7f8X&rdX(x+HP%gVCd|76v;IFgUxBAf zoS2DmX@{R(oyw02%nKf)ZBuk9FPsV=(uTX&p|5dCf63$Z{o53t;4u&1=&ODB2DFb0 zPlN&EV&Tb(6r?e>Mr%B4MH*>&Ps*wvSvnner?a_NIzhjhPV$Yu3QFrGq&1uPS@ZAG zN?UV>r~h+WaY$<#(%SrQ)B4N*>$LbuA@Do3@RKp{>4%jg+h`gUxAI#RTUA@tTRT0! z?aV#G>#2PNnrYT%U*{~;P_!24HUi+?ox+jfJ*~yAS7YW&A>O?0tc_rMBxu~noF?{2 zM4(3e75eUA)Q;!HH_zu%na>S&t`L=6AbwSx7Qbpvg^P5O z!JJHRoUac3?S9nbNvN}^Q=PHJTm!~?s$aQ>?lZr!X%KE5eo9fE9AwrtCA;S0ZQQA3 z*KGWcZqzxGT^76zZFtPpnp~bNG!AwCbra2}uMnr8zV*W?$`#^FybZ*eRptM~-kS$T zRb+j`b$iP~fUp|E)}1C02xupP5D+lX(2_)DQ53hKJ8Op|q)Eay>exUw5JxtZMaD=_ zgU-y8fH| z)u~gb&N+oMRk!7&uJ`e1r{?Zxdk;KI=D7k9dJ&p@{Is6lW2SaUb%YZd*k7%HW8{R)QN3{}j#}LWC%+0HG12 zt8!8DV)Y{0!UKAu2iJJPtsQL*&bS(rZH^pr_gC^HgV)>tdZn5YsHSx9pp0*FTIQu! z3HK9Uis-rvBGC}zu_lG0?nlJqDCMGQK(ekeQ0w(J$QJNmCK)c9`XeO-DW`ymJ)8$a zD}Arz-jKo)Irp&F=}^8>_e_DM?oFX7pite`rL&Mi~aKQV$Q@)aG zXOkTwvcF)oRo#p82b@BXuTgz1&MGJldVjG$Ovhp%CD9mfUfD>n0=!QGHLq4v(D$2SxQfu^earB3mC7 zd_oId#GokX`56VH&JzahH357|W(>hu1D>P=D5^c87oEWff!!&*U|NmHAvr}25y#Zy9irjkP;YwXpaz|*wh&JhiO07kH<>s zW_O_#t*||9t=rEGmdlXsKO#S2U{OhR&OcbWsQuF;0AM0)(Ta_!Ysf)M+6_cpixd#A9zyICK6?UQEY$(&t7@t%mkO!0Uj z!sQNkF^|PnnLmG_9TE*%_iFbiZtY5q*}76^UcHhe7fCB$ui8f{2HZEmk?e%tdDI#0 ze4+YWwjxJaZtGSRWZKN@JrDKnyPj-RCyJrK7UiK6okBBP?8ygasR-KmjjXF~^}cdb zuvIzRyi37zWuks%kf}JiTNT$dTi*cQ!v*9n4<)TvOYWFD(6Xp7JsxR}yND-@>| z8=#X`eSlE3)>%FZpmw(6(F4H5M|i z&vk3bzvU$_tD9(>qL%wuzFm{u$JKrgmyA*-e=zHRTBt%=_c%^Auk1cG;H*=IuEoZ+r0N-p9KxV8<;1CFyN0r zH)D=wR_2F+7j{{Cv$--K7R*y=-cbBi9)FCVGsi)~D%!z3BhUsJc#C*E;FgSop0`>1 z7V((NUF^kCV#+ylis8>T*pZ+oMrcb@>bi3}Gwhi_!i8cJ&gr)6w{4p?s(t|IAsr-X zbY>;r@>}vns`>iY)7zkL-lDfN&oT3-^17IBWMjoZ_~qH5Tox8}`zYu;OL&0{)N9FgMMH%nv=aAq@uZMICEP465nzI$Sp=q@~r(zNDX zkOt7;_bp2Res@`P_n^O{z>X3LGqHhZI|ty{C`QV=8AM0Gwyba?PXxeC@HEY2^P>UN{2*9YqeTz z8<9F#nhDA$7^&}wigkc3)4~n~*k)s--|&vT9(W+ceCooQq+Rm#ie0c5Z9&d+O{GaB z$x7jqP2f%o%80yIJ;NW)<(()ElAo3!@oqIe*fI}%5u+_SwH>ZUKF|9^Q$s#!0S(B3hKaBNbnfp)8`?BBX+L6b=3kTsG$kyp%n$#aFiNP?Muaal z=WR9UjtiJI!M14edq^0$*+{fvWRsDvm%|ZmZ(Ior1M~#_ucn)jE;Yg;i<;g`O&^5x zON|7PDARj;Bl0%tb;oHX6zT$M?C-OPXvkMh#2We;+#K%Ws)3v$sk@z{ASDtB38Qr^@W24`8pQ| zM;TA$Xg77+7@qqlIf{jBd@NT})qNwCP1@u=Hf~!O^iogcf4~^JF+8W&MzmpLYF?j> zk2y~}12zV3(u%twvAiDN2sMv9KOAy;o^eNTY5==Cz)A@*%ADG9BD9eOg&o2=9+ZUj zXSDd^X@Wo20ZYgsO@si+RNeY;d-TG-fR09udGe3TgVz5`%9KvXPsDsCgS93y>+g~q zGR;|)xgjROr!`ydJ`4#=TG2*{ube=6>uL3vYC9=2V;msxo9my+!%(|k=2N8a5rcUv zPB}y0J-=xFLanH~PGN0h1TOf|HVzWBjYBk8KjO6FMaa2rcAvo8TJcA@9MGYh$7Q3N z-fKWgUrJGPoTc2BV{#Zf_1ySYP2*<)!q>gc6|gNZe_^0w%X7&q16PKxo4;=D%3~{g zM&Jaa@Np8uqGPeUGNL`-pR(AdC(-;^BbxJS_nZnyup5u3 zv7y-i^quHC|YX9@)@5kezXVc z6n0aACIP?9Gzzk0p8PA)OfU6Dz$v!blRqkHR5COByt&Xk$BiH2*^`8uy0-=%=x4s$ zqibw4#DJFGdX2DAlo)n^e2W-Z)89NAWwv`6H2h!76~t55qn)R+=Po=VPmdHPmdMj0 zzmm7Gsv7O`?Xct#fw%sGx!4O-#}@GWuS56W4muVont|qi zoBD0JL!~=HAkyEP=GC{wtRLKyCyUuZIHE&nzr&)U>a@R39cmfLEyZ#^i-jGvE8YN`J&oG&o0U_+H!xIPz&U=1`FVMO zWh;KeEpOm=pd}E$Lo9#6?@-GH{2DFKB_D=wvOF(`*`JpO+e4jmoI!7EKk2=uBq`io zk}}0rnO1_6z{lJTX=yosSd9A?0GF5+`qn1*(~!n?v&@_Z*dBJdI%7AQt6eKPp0zZK zu4Nsue>Yne7j?ugH@g-Wbi~d#i;MF+VyBv`7w7R<1I{gru`Vv`c=koJrNG${JJReb zaOCLC!7=GLduaDo-=tX2n9@BC9vY-Im~$sv0&6cxxk*YG6H(aM#Ju4_){&xmJ8D3 zE%uzC-9sQDd_kJmlA7~&{+BD5DI8esein$df8Y!qR3lFQ9<@O45G$dZjl#}g0k|e} z>nJt?v9W;P0yY=)ly1L;(c>osTYIGqAhb~3?S{-BXVfWB`K}txEu-%s+M}_d+nw-o)EX5 zVNsW0;Y$mvPScTR4kQ((t9f)PuBpNv_mVWdg|v;H-0MFFoOSID*ClBLmqAzpx&ZNl z3$P_WDJ2q8vUjIUN)~V`4ZBJFj4#SLA!!0%CB6lZ^~w6^g(L|NzTGn<1K;0lF@v*Y zjtHA>sfHdxpQ4}YtDJY@e2kf|NgJR8Z$zD@)zRtDjHbf5hvh*ryW~kGX0}C8y?4n| zz{7q1aul0&ZS7)`r_+kTI13{U#1W^5c98?Dju@Q6#K3mXfk9nT_e5aFEZ(?TjunsO zHmjl2%OGj6>v|$mY7Nz6 zHLrxd_+$yYl3c>-pD1B-;J^4_2`fR~t#CveSZZR$HK1r{Rfl~AoRU-OFkrRcHM1I> zJrww#Qam+Qm0#ZGC8}bmf=*el#%6|A8_XKd4E6|tb=HknC?DdYy^QX4seAtcM@L0iu zeOtTt&pb9sAZeIOQhf7t+k2j8ad(EUaXM%eL$eZgAVc#n&jZLePYR0C+6*V| zOA4qGuC5j+|J%*lh=AST@8c`4H^(_`SQSnHH{T;cGX~rR+BaQZ&ha^D#H^1F zs@FsX)CW{ePSWB1tmeQ2T62TFGxr8)oxmCdEJ+05Rz%7(ui1E@!Ks4<3BzqU!|J!e zj?uV2+ottF?omAVlxAv66b~mrdn=%7I1k6-OMUP@i~Iq1G>;W$pF+JcayQS01}oev za20U7W*5uXuztsi@1GoyVu2iJ6bs`L=6>5vZQe;T-Cu`3nbVGY-pOWKAjcH!Q0=n< zI}CRWIOTKn_0)Z9*xwP}d<8Pp@c)i9=hbqy9C0^XF z<_Fx5|0-vRg+#N`h*m7R`Mk@x1U`-aUhl9&^jtaDp}Mqj+4ZzK$lebZ*3(%{Try6o zaSz!^oUGgJk8+s|OL7c9SJjF=Ad^vHf8cbKZ44-S+|UGB;Cmt6O*5NLze%n*G9n5s zS%FjWiS;9Hc`_*ivK`O~W>I6fT(92r3Qh_c?A65spO|@?+Z2g&LbjQSt{Zh6W4KL| z2!kLc>qfaMACL1Ll|uGQ$oss%;qX!HZC#SKOA37ER03DPJ^E$|n*sMXgumNe!W!ZC zd#i*E*j2(}b^?FEHNpkGUc%1fdDKtkZ1w>tI^m#x0kR7rC&X#@MQ?P3QriS&94l8#-Nkq|8K0R8^?GlV*kw#m* zm<_m+g!*gMC2Tg{r!wf=!+;a<1Y=KFhS&mpFo^eA0#=0t%%S$CZ*cOKXbQ*4n^D{B z9qhxIImYlMDI5@p#2u5s=k7P{IGI^L+|dUX_h932y`o)UhSWySWFPDiYHjud9!QEr z;MRb0SO>0fBpxtkla&;o_Ys`TUXr9HnhlDqK;NTl*uUN>VeP?4A z*vccA7w?y_eAG)B?_q@VRr&fW*b0OJdiJJh=*L#t-*#r$8|*nwoyqLbnJZUC+45lH zt}ko%&cljowAVI$0ST0Bd<{HFq$G-m5zplDR385T@ifG>&?%?q@34wv75&i!D=Y)Wj= z3-OTcXW|h&d#1iqvOlp!Cya+=KNI)ki3@Y;1B1mh9p@6X<_19yK9i@#o2Dk;!)H}H zMj^@Ve_$AInvzU%`%G*`8p-X~8YY+~CQsyPSCOX3?bn*(BjS15B(!x(y~V^=b{k{} z@$P2m0Vs0&i7mmXB_s%uI^xD`E~igv<0BwbfV9Ck7*Dg9CPWP9DSdD3$DF4clYG7) zPM4SSc)V#4k57^lczit0p6U5m=tB_|eVJ2rH1}V7uY^(mQ{U4V82178#~7zH4xWZT z0MB>9U%Vf5!qF1;I`Vx)`QV$7_YBfLJ6yu_AH%N&?tmNjQ3+r37uUG%9glr(0!GpT zZ~;?>Jn@nu@2d`=j%XU!=3*}39AAu&SAbiiTGr=4RAE~ z%4@VM%&_=(Nos){qIt1j=FsPz1wN57+pa)TfZip_r~x)W2sS#v=5xLao>w;pYg}T} zJ-An1SU-lZap-q37xU%b`lybvJnPzvizkyzlehp+Ce{zTIj~?<);e7mD^C@cv$wb}3Q?B|Hy0g|a8Mb3+)LroTVKkdyhLi;=-MO99 zol_1T`4Z~{;Lz(UzzBYK_-EiRM*2SZQ|n8ZfUpVf8H5R571%wvaT4{IgP`#QI0sue zTpkf21BjFdML*OY0Mv+UK(hPqy1L^mp0N6OF@Q^9H^mL{=eH6DuD z9SP_&aV50X!@iVjjeni3iwldE>s|NGx^^tBR!)z1Lh{=gkJGWjS~)jfgP1G6RxXMs zSyF0Wr=F9~`vC4dpb_xBbonnt`woCf4M7wII*abnOK?`pq3$5(n(EK8H{0sv+R%Va zb0M)-LvP3Q!7`nkx$ge-mky{w2JoDt(^#f#K{rMRvqg z(`MCO8Zch&T@9Q3a_`wYr_Y#So*w??k*k4$dRSiyzN#0d*@CYIF>tJF4V%tO4|@&U zLtu9{$fEJ-V>G_Nrt{K$-ZS)cJ8nOT2=D}4wK0pV@$_gEdKlpgF`C%!Bc+qQw|;(A zkL_cht-)8jF<}Gon7hSn+!FvR1A(c{1f`g1v?!7NDSQ5NyF zT7pgKC|EBTT@iqLKgdcF8}@LkPC6YBeB2}?0Aq>5DCplmf0-HDy&3JWOE9+kXh>>9 z{=bc^E6rL{9k5XY2!^?PJ72MVnXR*ZDIdn&vf&l$XRLni)8}TNTD5|Wnq7~Q4Dr1_ z9k6FlCyZ7BM%w=oMu%H|4x{&4{@=hTVC%*`8WmD$kiG2bGe5^+w3?)}`f=#=SD^!) zgZ~CDr+yJGPyG*Yv2=k8GgZs61ef1``uqPKToQf(E_eQa0+-dT=HGL;l>QQ2#97cf z;Qejv(yn9Z?+9wwx=+{L$NT%f|DnGJ{NkJlsnuale9^4iKI?~L8tv83aCh?&;XL+% zXJkD44>KZEoe_F z@<5=gVF9GDS=pU`0IG1J?L79SiI9_y!|i5m1*fxz+tHrTH{C*`m1z?7KEbqel@LQ^ z)0Ym1URX6KcH^F7gF?enSJNA6p4FXsSg#AKJMxrv$y3^y=UC{4(vCdEN*>MCj|3rR zCt}LCz6iZg+>xi`mOO|ld33!llu(|}2c?#MNpM7qE6cQ3KZ0HeSZdv}6{9mezg zR3Da7&GmHYj>!IazZLtt{->+?osqQSwtJsL?(V01L2g0=i^-v&V@Qrv%jHOY-c>3M z_eC2~C?o+vxU~E666S#W6Wn*rjE!%B4ki4pUt`aP^#=D+TM5_Rif2#UhEE_nw4Uxv z>xyS@t|mO|pLbDR=G_;VccW;^%+-0Edu7f8 z+x*- zzQ~k1dGo`VrJy8hPp>WD zAhdhmZ!^9RJrS(FVGQ=GdR2iFiEp$W!+8|h&wiFui0#wr2Ej-K1~SnzrDJ6d!PN%E9YYKdFSBgg+VnNAPc@ z!CSrSR67FSOT#(O<6YoHe%qSyd#M|^90`z&3W+!moBbhX+|J;pUFjKc0uv5K)vR`& zDiP^%189V)uV#Y1v;*d+Hm<_-04N|(K<`2wVMVy`@!z!$K_w)ZkPbuwrM?V zkDm&vd0^1Hd%D*U&NeiT(1H&5t@elD;1ILcXQ%`nuw01K{ty@#*cha}N>svA+8?@g z>jpcpLM)zwX3#bof`+!PxjxgT+4Q|C|8D`$k+Ay_QL|mU)m-c=--Uh3u!z7$L!c06 zvcGBH1zFskt9D_;>;l(umpr68b5+J_95C0Mfow}ivVt3qf*uGyoyXIN7uxQf4c?Oe z6vHhp#Lf9L=jJFaEosJn8}1<7v-^T^F9q8OJE z;t|675X|o26t?k(_W{(?ZVx;S?945(1SIRst==M)fAE#cKNvi`l#OmIXEE^UTeZ%x zn(w8#;G;C)yopjO@P!1?RpYT&*jeLxjn)<#w`rPH4xQ8rE3&Y#V4*vXNZy;yr$|15 zPFo?3>YOav&KPZzMf2&j>^ShgnCTv|`x$$7yg2DxB~KBMqB~Q8l(=(do}xjD?^`2M zMxATOr?<37Y5LZaA1|69r+AOJ5mpViuvGHLa9gJLW&jtTuipee6{z z?5&`%t0O$pE^jc-WUUs`PMB#S8Pi*JP9yDy@z$HrC)mQk->jn+fAB4MYT`X7qPhKY zvMp{DpJfAbD#{?gXba?LOZxgL;+-dIn~M{j7Qr(GvgDq!h8JL2Ong?`P&P#zb^_Ka zVk0u{gv1lQ4SRR2A)RfEPS$M_HjaY*JPm%MoZ5{eAr)mtJL9l|P6j9RvOZpPoV7z& zHXf(m6npG!g~Pp92|sk!=$O5i#%^dkN&V3CJsO7@aE(hqmtx@1L=^u-os!Ima;mmBWZ90J;g8Br|2Pd8C&`Xph6X zK1oe^r1j#=_bEIG;k9BI4`Z2-ip9&INk~?zZ;B2lL48y7>$P-$P!ioB)asqz>I7^P z#f_)vEJQFRiXWXa9*8wprVfX-1Vs`6_cW*+(HUu_>;~FK?b{7EC!>^Ihd-&QoKd?I zvpv#?YWl?;Ncc7JUw3sa!UOs5Z>!ltkJOL<7FAO}*kKDR0Z9!2}C>o+DzE98*ZmE z*k{;-(UK@;;gW3rv({E`>2(_0-$_TC{dZH{|GQGQg;3v6blE?7#1wkB%@+uq?k#`g?k=~61S6+MdsOCXA9=w6M&bc)) z;12Xal8oeW=Y$*}%3x8W?Yd23mvj zRor}*<1{3HYu~2oIaY8u5O@B*0~&VV1ELuY!}5H$Q?RIi^&{P|6+5Q|o|-Mkgfin) z*kmBOpdaW0<#&dd{s)>X;JdVYidhJ3EigJmt&_z#N?%>`l^hjl0UUZ?!;LnuN%Dyt zg}X&W?RU7_n2Tay9g4#kke-JNC+))p9X@hh+UB zYZB`Bfh82bwHDYZvNQ3O_R1X1n2qRYk z*nH4g@&FD2wmvgGlK$E~VWHp(CZJ~7utl)H<{jjm0IoxKoS%ANpTLtl67=tR=o&=v zJog;%+tMEUp^0~OBuKvcRPZ^&s?s1arfUL<(@I(i4Ve= z$kEsU8{HasG?yg6sWu-=t85Xd&4<#e>A{Vm!9!cuw$8@ajNCfHU3GuKJ9AK{@MJ4; zXGjTL$L;g>0Nm+oLHN8>(wJB?bB5sz#fV;C+_=idJUe-vdrB`rf_o}b3mWGmR}gYd zMJ^4s;okGo<26d&*-A>8za6**0Ap-RJN0KU$HX4#`nyK-C()-YqPs1S-{E<+y>?HO zxoTRmoT)9AkH>s*2qD}Va$HC_cm?nfA`$%Pcq#Rp*ler~b+bxg7R&lsgni%~Dqczga3ops%z z-bGrwr&zwM6%uOq+=$YgEJR6{<2J(?VUg!b9IYkap{>R853uXqGfGoWH=O)HIn0~k ze-&OVUyUQ#$sf`BpW=EU^e@b`m+`GuIPBkWr(y{7riP&YKf)3cNk2~n)g+GlaFqe*Tg2l z=1NYx_l0&#k|j$CufYtd&hpQZQ<-(>r_HN8tUjS^=d|TD$kHb`5TE8K-4t zg7r|e`?dD80m3-_7+C(*O5hPwdN|T|^Yk7_e?)t&o+#g}I0MS*i)ZI<(j74w+TN(o z>!{B+H+vtkU65!|jFZ_MV1)4ZjMm z7Vc>{!d+1{xa(ysv}}fbC+)y)gz!}Hb0KbFT`xCm9><2e*URIKn(cxy+)bL&W1+8I zyU=y->YS~xD5X%&Q|gZ736qaxurU*`^Z5-ZVA7m~3^;DOtIP?@X5#N*Ki@r<0Uj^T zG1_K`-=5r=)5lpkO;cZ)+Xwi3Tkg(jgX?#sLrQRBH>`YH?xpu;!KT62^2I*JnyE;e zA(oxic?O`wEza5E5$wOdlcqN^P=$i(Fg9=(473b!f>`Sewk@@7g@w1T8{IY*5OREk z^O2hOb3T>tTOXEv9oBP)o%ZDkalVXtoB`j3RVjq9(*fE$#@Ox^x1TuXAq;$1sz0V|x?S~Yxtkz83Y-n;(J-@S z%$t~r`uZ?Y=sD=ntZVE+U+Vig*I`$Cme_FWsocG?XZ6t5ayjstD{Ww~&#|DjT)G=M zPB>-nlr~@XoAOaj5Om3OW}oBkg`shGrwaJqFw3kT!s6~uquVZD!47^2RuA`sZ$i%a z{-$9X(qYt$2|Lus_9pHbN`Q6?=+qktH3_z{N#Do`?so66o5y4)qqpC|+Yi?%xG?)B zrTq0q0(PwRplg!raF-8KKIVGI)_MweL!KF8%Q>9Mnr4W*PSBe+ars%oAfL)#$9^iG zfcqBC19uVbGTe`FVr+sf06UUF5$B{2yhgNt$0<8{WQ%hS^wvL>Wvw6jHu2kYA-M4z zY6s%fGX&qZ2?34wX??hh6LY(a*XgEmf6qXSYmyP68x@vfM2rOgp|hO*=O*x8+=6w~ zAVxbYA*<8?sU@@Hsx!?Vot@^0URPP1v#y~yXJKXP+Y1{~uP!vFMdv`MJ|`{x?VQT= zt2qtn(YfY~oZPgGw{uCxHz&I?MFmgjC&Bx~~Mpy=9 z#CJ&vzU{8ofMl)nx_1|#+m-7AyGaeD%z3Sx9RW=zBx#Um{QDG!64$;#wgAuH!^Sy3 z4LdS>iBlN+j6E=J^R(uAk*^`TT%t9G#jlt19t=teO2$aAGpytNa9b*LC&3FC1{-&{ zi3yncL_5uul20M){W-zyjwJM3tS*-;*&Hc=(?iJ=3j4^7G!BQ;nJ zGAMORTX$%M&eX?ZO(b4*H^g)yus5Qi_?otN3EL$bCZYX@CZlhhA8XiSaBm;g zu;KV_!oCq47dNzFy(1eAX2N&b+G6>yaCulIj2Nw7w*^MQuK0-|z_8%$-f-_>kW6i3 z1Azr$f9FOxYRUu(A8HFrg5(}T@3p!2B>teEfqSOs^zlGq4{x?1jo#~p80ERv*0bZy zowyrBhcZI&y;s_bVPk~$ms?Qh0Mv8ahZ?pM?lS5AuE^862NFXQNYvX2daU7oMofE;n@M(jqQ!R`Bf((7`tj&jdz+g~G~w79W%*``5{+Gs07?U%H@ zJ)m~aYI(SBpBy|^h_4NMQx4H-#&^H-$NKIbchWTerT2Uv? zTW5HH=N{*C@6isznrCJrW`dkN?w*JeIYoaje>&Wkj@W(r89X-BHytHE(b3X7d`D!v zUf_4%mB{J($tnc9`D*t-nnTNil)Do8#+S%<7{=r2a6SquePdChFoO^=jmNk9X2bS; zxZzI7FH(Cp`AD;TkRh6X^#2(;4mFbv`3v zztb9R_d|!|8;sE@7@KdyEkt+&#_C%5wQxSTD2(G=xSklt&*Irmq2e-WGm&xhjKBM7I%k3>t`z0-Z>F14H7Q9BKu#p4dq zU9wv{h{iUo|5I9+FH{-N@h>7RdT&1!nnqu5lruV5nK1)=JrRovRv^{e7lPQRU}db$ zY7IttF@`7(#hJJhv_y_I5Cuh=YV6Q@)bAbC-wuBT>Y1>wN0Nf?cD9CqVjF27%=JoZ zJE*Kt2IvhU7TFqzCl>zX1?cOJHW1cihl)^66=0Bpwtonps2_dqtZP}Wi_*WY9g&R& zbM9C8t*(IuTT8d(O|Ul;j9&x)4XzJL(s+WB7#!XyI44cG#>?ep*Zygu#{J$pfk6Z2 z?T5c;Ei7#|nIbBW&;kyI*=H7LD5 z`})wNVtKuWC~0~+^LnW~&cHl+tWU0k^HR!KujDdT*hgRoepAMtA6LdqNZWw$-+nA* zBh$;+V8qufC}aJumx5mdeq2Hsb0p!psf_LGC9rwRG^{zej9rIogu4OPy&K*^-#i5u zhcUG}XOlb{_loKwmX-LuPBb?_QiutYh-apDZe!1btPuMV8MS>hCaXFH9m?R!HH z`t!}KA({O|P@;{hr1(YYIXr*j8u1XRuA!)_i&_WkT%oAzLsFb`IIJ!fI$30;U1ze` zdL-jK7k9k1dGBkjf@E&@(;=!3Nw=hK#G!>H8r_Tf7iDjFyTY^oxP!kuvIzYL_YB1L!*Ul3Qg*T(z`t`Nf~QH9e2YgT;#3^;&nJO zKMHk-hfPU3laRf=d5sfLn?LR$JoZn#yB=df?11HFF5mxf-Cew1sFkYKGCCRd<6mm? zF1ij&qjno=R=G07Y+e)j(NY;)Yl7o!}gGw`mlYMZVz;ZCig}AMR4pivv!$2V#{rDmFw0IyKOh60p+^ zw#^Y=JE3qxS96Fc_Ft%7Pp&CrNpNSLD`U-YV?);PwuZnDLj3x=GIj;eua}pxz?9H^ z1ItN9_mlbl`S3J9zoDM%&*cM7pv;#6D@6{S#@$UWhd#Y-cuFAp5bF)bImy?U(TnDk zHtz}O3K;EUPip`xeME@)!b%^_+$i~4eV|a~rRNq5d*nOZgnK|O4ILXwQh&G!eK6plf!`6;j!wylM zV>SY}!Cl!<#*k_mr%G?VC44&pNm;!5GdbRb+-5qV-N6~t_JqG;oAsBmY-h6pJB#P+7 z$*(;Vj+Tb=mYQx{KA_m2HQDY+7BEV3ZdBmL691_SjglzH7Txgv#C0AChEZH@=7#r1 zt1=^$b}MuCwH?6Sa8%#d;a)*|sotC6uRwgqt7U8k{29oj;ICKEmj1jgW9tm|fxt`* z2qYYPHmYG)?D2V>cwYyNDi7+pVC=|@W5e-Yt1!<1d!pfzyTK=n` za#^vw6`a+ILVH0)LB#4}d7Gseyfp>KTonn&D7JD3{JD&=L2FpaKR~0&#i6)Az=)dy zEPmPXPMi~?>JDK2$5}h&=bV?l)*L;o_Fuu?jchWUgnf3L|G|G6ekeODEf;l-!w`3R zl`}Qj8-kK9N>$hk)0tYTiSWU1zvp&l!B;DVzSKqOd9H`>Y!~gesmW7;2YlXz+?sU( zKNFdfJcXZ$F!5DjhYEYvX<@`AdxC9Z(!}H(=NV}jxctzNsWZSz;BiRDoRu8n*d#5i zn8(aKDRuYSVR`QUx&S;iW}k(e{{$QO;*MCGec?&zZ1V(I8?1296PwtOP3JO{{$;bq zq_Np~`b5!`0_$OcpwHa=ZVKQV-KHTaLzDEI{I^ud*k7IKqE%aMnq~{fc$zM|4-DuS zXVs=BA+dGCn{_=3_oP*(M&rK8)WyoYN>S$(@f7=gS;k(3(r?C4f!*%`El^j+^!1ou zUzM>6%&X=h0t-Wb5m*v(~R16*$t!JE7YT3UvVxl-bXI2lK+V=8fS83rvj*}JQ?5;dVU z_d{P4O1~T5Ak5Vu#Y3Wk9isAxhPKqq7ys*|au(VkMUnmm?z=ZQ^)@McXr5;LJDQ!~hNR{E#tuSo;!>687DVX!|EnO&RB zxc!l=Y_dNhjEi)T4U+ffkPVVg>>j7z{>XXQAHfK)h+OU_45Q{xH~}YWC=4TI15V7z z_BCiCTcsWP5xHKTnZIemz5409eKDDQ3iPqD0M}WtJerorYK~JSY zdZ~rZ`KO=_g9nzg>)}cp5^8FpSGZKwWNDCAqy9wOP#tpkDLiRdQY{{62|=2pg-%?@ zAT0!G(^0cb2i1-CS(9&6*!QDeo78$mF@khY)N3i~<*^5yen<>D6Ov?fgd|ruN$NBA zMv$k1*D)XKbOXv`!;~9}h$i|Q>bV>4BU3rs2e*5OQs>z?w2^yzn*S!>uai%(?cs43d2{!=2*i* z+N0cOe{JCt;@Q*Z5qm;B1zLKf?Qzj_wsPTW`Tp4r{FiQEyW}M%n6daV@YGCPOy7}f2Z^cU$ePAQAWhA$T;e!1ty zDB+|KLFbl~@|cU65gJw>)*JG6`skH7*=!-`KP2GH{;agpTg7vp>hdmG{zcnNr}8c6Gh>%IAvL}ZSmt`(wTSU`F$1q@VK~b$DdHweQ-24PoOoV$LsUPbl5>U zBWb+mXU(_~R?(O3(N>{S)YgnL;BC!^pqL&%{9^nTBEH ze+ql_w`&b)7d4{X29GuWy}P7az9pjP&5U{!sP1 zGVoDxy3W=eT4*QOM;KL}rc!wjtTmlrA0l?62~bT>sG<0Kh}YxkWv20H%4KoXG&F5Rni;ss4gk5HocCN z(OptrYI+SRcXmm69$&b^$M`1sim~?r4MzGEBbB-Z7VhXvPdB|GALUi(_e zob&+RzR1h|r+g7_7|r0~+8Lg`cR?ZfJ~598Yd@a@C=X%U{@g}UboG+H;wxu)I+ z^r(BTiLCDUp)`cxIra@%WSitcrtGBE@?hAoqx^$StGPWp;F1+|XB(x4|8})JgPy0) zYS~%%hWvx&4f#j7pZFKgN#4dcNWZ>a5@^!LV6NbAk$QsW~?N#Xr7RfYctja0Xz;Bl>%NAkG|i!Y{d zz-oKJCkRnnG&NetTHFU4eX~s@b@7miU=RZseT4IQ3g4-g_G05vSZSn`XguAcK8;eJ zmg4Ds{OK?}tyG_y)Thhvbha=QGmDukIR5>&^k&N-@OshJwhAZ5Ko9*}+R<_Z_C@4_ zDRmn53EU>QrEot0Z*+%06mB2fLBvbpPk}pw@Q?7@;8(zpf%_Z6XW^&AJp}i6xPkWr z$HFOlcanP}dB*_AT`g-<ROgZ>yzEUQYO?C0#<>>YH zRqt~2dPTl3Cc0`ZugCfhTt};X5$7M+>PB?2&SB)5%d{iz3Cxy&dkMSl>&y{D@kE0s z(e;Bno+O&eaXVp~x4%!(aCe)GxZ#n3d!%pMkDVdynfrI_nMqo+FEE}LQkRKcZg~{U zUaw8%n7rHt2bz|avnz$bfAG5(flgRb&WT;rSn1?H=m<^o@gWV}IxY}RhpZPsrFeQgU$jz-&lk305=T6#~t zbwWHy-#fVscj3W}TwczXC`OF`J{h0)Dd^50k+%rPMu#I4W1Y6ghoX&e} zFrFxuNc5)ZYM*`9=GQ8p4(Uni#oD%V^b>IddZQN=TYbIIi^Qw*;+&5xwGahDwB5td zy!T#Ou@Zd)cg78wHF5~+;pQPcxdi8>tIFBm;Cs+tThIpzovrXKz1|nqeRG3!R}E~r zQ@>)~lvY~+r@kCcBtd1t`s@|M!8;_IBb^pUqMZG?^#HH;3&;(64z!yqmk8#DCDBfW zs>1%-Ys7tVv7kwMHdi3MJEw5YO1a*dT;Aubq;2*rp<6@j;@T5LPgCo5ufzIlT*Dsr ztznva4GV$e`}rtion8FviOPkA*hMT5UpNyCE0<23R5wWffP`^_^o3}CM!4I&B*V$Z zG{Rz*VynjMHQ~;A+V$AQN46v!q;i+#rY%U#bEGZEUX+=SpX?&`>))>s_!R=bLf}^j z{0f0zA@C~%eucoV5cm}Wze3c3xUm@`S0|=yNIMVYRixw2+vjRt6VNvRe^z1^` zCAH9%y}-3(LB=9SDtmBx(yZw-9%flcOkb8?kj`dX@3$sRrL@BI1^M}@`HON_P%z7t zzodxW`{3*(<8;bsygxrZ!$^hXW~2uzInvV}H8aLRmu4(V&sMAD$nsY(KO=8)q$PIw|KvEx z`~WM;$aCdqW)@}?-O^ySxxDU~s00U}%Yo6Bx(t0w@$~$nm>5SE!QWYsx-8bx*$df4 zMSz7xDI|5dLn%Ooguk?LS*gp$T7KU6PT2iF2(i4hLi+Z&tXzMKm_KbvZkJS^Z|tr4 z0MsQ#dD#onigJ~zW-KpqIjD#IV0SDkDqM!g{au#G1Iu}HyFHkyL87vtE1)bx9IkGH_##fi5)Me>QT>i>0%Fiy$$X$w> zE2C<3WV-U75wQHZTn++W!v7k0w~eZfQHT2T*#x$uBRw6xjizFp6fSVCC@jiAE3*sv zXi1});L7(8zSMk2K{_Ab>M-q0h*Js#9!Yijv)?+IvJ3N-CNl?(7iBgB-2O366;L2d zpH6dTy9zRvQZP4zkHMdff-dQ|jD#-f(hs$w0zk^& zLHrXLiwZLq6z1l;FeZVsFuIh{(}9CJ=Mmw+>>|u+KHFUmiyt#u{CLrl%CRDiF#jhz zU;n=fDbp|kQ?9eAz+0&R!MH3Hhj5hOSgOnybt-qFkuEd2Gf|z$oe7K;WMt7;;6o;@ zV>0`vk}}l*89v&Pw@^V$|96e)csYiD=l||yAbEdJqWhHAA}u{TD>FN{sAz@Kb6qid zW=0+@O4OkpUsgxJZJ%bYbOnFMViCym_ZhvF;aapLPn}4#^mVRl1M)PHHeyJ&VH zxnnUvln*Uh>BePs%=03UNVG)LbOIWrR|->OE!m64(ggQ^5M)}WAB7Q={HU$KmFDPJ zR#cLervbC8K;WtVG_`uSq^TgdB~66@Pvi4@K`w73g=4yeW7V)yWh^C&(qrO(hMCgS z{q5kRgpX7X3%)RB7o|D7j3F9r075}}uD`*{iyXO`1sRJeS;0$AKUL5b%9!v233XqTU#L=d4jSwVPFhk}l%KcM(LqMuN;gm$om9h;C1@KKT$Gib zQJA&>w6!ZA2s=Y5s}tkryNa^&7f~f#4p(}b!=(1X-7PIhI~ zbx^#R+ntL#p(6|!Kfnl|DL_&ma6&=tZ@w7uZ4aR-PUg|_#38S)#$6Czm*u!is=6L` zNr8?`L4Mw?)Y(`|7b4e>3e=`{na*9|+er{A`E&hU=Z~tdXDB4Q+N5Q!LK;3azB4)CcuISr=k3qScQu}iUL5TQZ7PFelRK&_gKsSp62d=YX^aO3tT%=JK)-pLW7#7 zQe;|WnnGLwV|S&lk{0oyde^)~g|wq6D9T7T5(+jVc}WJg)~p~edl5^^P0wGDx}>16 zh`Dlek(#>*DKQM_2{|Iui<%8+ zv4Pwd*dR3>9?F3I6CTGMJ!)L1#`UUiQ2ik8>sdE7uGGWAdZ_W9svoNQy;Q$9d@cU< zQR978zn|*&SN$;fxK)h}Q2lV#AFMtfqWb>t7GT&RDTM{B&tM^88(Gg>7Y7^9ho3Hg z1S|TZw$dN9k^ZR7^hdDk`bTY`KY}g&8Tl_`k5HrVm&kt^yF)F|t;Qeme?ay9<)dM@ z{;3t{c&=6;z+W-70v+WUmDj%^pwV9<|7G5T9~1Z)bMs&3ovg+?TB@WIWa-a4xmL-q zLZCChQi0C6QUThz z74S~}tNJ~q0*KQvpua@=@lI}?LOvEUOdCFrT+8zKW|Vkb9W)XnK0$VR=R7K0maeeT z7?H+GM!r#IsYPkampKa5NXG^`PRRo#3br1o9hSFbIp^jr%e5f34AZs??@eK#C0Jtb zjEONmk`2aGMq#1Rn!l*1AU`)ZqX2xiV7`L~j{<06L0&$@i1G^-6lLdS5EBDfAvNe3mssR@Qpt+S|hDmtI0ad8fG0}4Y&5P_ObT1_O}^C+15GMxz-1*k63r?UaEh{y1;smb(Zxm>saeJYrJ*5HNiT;+W+2} z_l~&NYPDN!)_biptufYEYn=5h>wN2@*2k>7t@Er8Thpv=>q_e?Yq~YVnrSVvW?3t& zuUU6m|7`s)Yqj-l>mRLuvNl*RTEDYiu+~}YtzTN-v%YWrz*=M7W8G`5wC=LLZvBgO zzx9B%*7}k4W9wJeudT%E4^@{Ze>s4!n^`BK) zRnDqccAu!)vAYl2Sy~lieRKEes`{#Lt9I?4`_|Fjnzv%!ihT>+L?ngEWl~2)Ow6JM zVEqzH?HPzo)K5f&P*ugVfy)PYTELwv}{@^nV-S}{Q0Qj zdq|}#{Ex6e}TT12_8i*AM6;KX^us_ z3hFy)e)2embp&Ji7dgWbWG9dOkMjljJLb;V+vko-jwrSA*JMF@My6v)ZqWj?l69RB z3y2Ah_NK1@Y}Gs+&Bw$>Yq7BMxzjakUg5F@1^LS?{#Vs_%x!VrFkW=wqC$G1(4QeK zKLcI7ET2~hJ8euV%uvo#^k-4a1aeTSr^Y*q0Q0SAK{^$jo3T`ZH+?F#h^QZxc}KXZ z$e*MZpH@^Di~h;%`bh`yM$yk&>CDb@cKt#@rUN^aC5w>r)|zGmDg3M=x%ta}A+@7p zyMmy70kR z22QQgl}4h_0!*}m<*B*(X*t=8vizKL3sF|NL1URt=+4ZRS)i+?{tlEu@Ja0mMs(ax z5RbV;K(al7BbElTd*{hZ5HE68;{TI^yRg1fnkj*$e-ngco=TUk3ho_?JrvVw?X%37dHd z-vlmA{lyaO!EEb)C}Arv;agCSzg$8$_J{va!vA6K-Q%OKj{o5~=d-tDH`(0~5(FfR z0Rjm^0A;DykZ@74C4dr(c8Odhhy)N7tNrTc9xg%xmRM2bDkxT?qCv&VMN7R@qqgFu zO&76()oPU0wBNSodC#2BhG6~D=leXb-|PA3`D^x^&$-T-nKLtI&e@I9Ld>)L{_ZVY zDD>biy|~mzTmM4~Tcw3RdwQJz{Vk->&i~NDPHEv2&;9Rye+#L!`#-d>TUv;JhoVl- zV(cx`7tWt^=@bYx-fR{pTr&igzi}qS2Q6lS#BxGZ3Y42a=T-=do36bP?o_aSR}M=s zq!+_&dTl;DmMkq;f^?nwiU=}lEGtI~v7w7>m_)az9yBsP;}qF25E7vgqc^g_XL(6jBIdQDV5*f0?UTew_s#LlJ!F0?{9%+!|7WvvLV@8((?T+ zuxvPe3r05htn8`XTj<|~2bK+|Z^6ih6zlgQT@si)*#gUk)3;z`gWsyF`}Z*02--3}1XIM{)oIbglgTWU(29^z{Z^6ihGp&d^bqt9XnCa&( zUTRLPz}JS;!Q!@Ctc$t3=>)S_gk5NtAADB5^P{K81NIR$cgcf8a5eF$6S_ii*FMdzb4^H2Lkq00Dk9c5taQYUE zJoxN;@sN-Qr*FZ?gRj1WhpDm_+ycvk)3;#c!Qa1whprYx9-O`fBM&;igNKtXusk?@ z3q~F|eW%icX(1sGPTzu&2VUQ)^l-8TmItSA!N`L&xInv8)X5fD9-O`fBMM~1Hqs`ajAAl#HoQ};EG$XbBO{FQBFY0x8oSTLF@`7`-Q*F?S-KPh?CN^f{A-ue z4YSKf=FgOv1B`3tO|wMwU@qk{l^eQ;mxz!vI*l>m%m7)@VJEX#QcK9?zSvp;0mYDA zHw>dTyxoS&YZ#whbZ2rJZYRIKze|HoU@yQ*1cdhCj67BpY60!wEJV zXTw4p7TEA&8(wI`^KE#Z4aeAUv<=U-;W;)OX~PjVgyo0vUt|pzc!3Si?}BGb{&`jc z4z<&HHXI`G2bO_jtih5V1GACa9b*l$AuEcU9w0E>1+(q+SvKr1@LVeaLy|tnO28~h zkF*RNWwAHevGBC@A{r-O_c=`Vb5B`fgXL0|45gY#4hF989_!QrT zKLIbZ^C#L+_!9YJ?KGcWaC;ZnQ1}q(u$>nELwb~*7XCw8_zrNGpRA9M;I`p@eW ze)IpJQ~1pvw&Cbb;XD86&RnT?Zl~~_|J+XDJO3yfj<8{Vr|_YFxDC&?;jm8ONB>Y8 z=GkzF4F`7$pZW*c=>eU>$NsZy*uM*!cFyV)KK6%fnAHV^ul-q_!q0yERgR3iZ&x~G zr-h&KUHIAGyVIn5+vzj9pzyiBS7!nWpZhcHbVjF1XLbtT`ZGI)Ujc;=QO>}gHcaoz z=R;PPzh|d`nVo!2=R$b{d)hF)E6sJboo7Y{UkfzW5eXG@1~x+>$|Dvw%c{v<(ysL*{$mG zCw20v9k*|wyVK-5y2|tEp3869?KnD3TG`{#cD}OP)pk8)=j*QWW<07ZooMI(1cZxr zE+3jKz3DpEV`eTYp9@6^VaB)GURv8>YzO6$9n_aFf68yPUB$*o!tv3vxp3YmJhDs6 zZ-j$6QP|i@3$Ia)|nqS z-Z~SJJ@aSdzJW9{d+rVJe0R?|X}6gYr_6#+tl&qs>&{*v`W8G#C#Q15qIn~)y=9(V zL{7NucD~ayJB|3H2Qy+1HM`^I%xA@ugSbz|j-333QG$x_{%g_v4E($7@H~E3Nv-w# zaa5kGwFlnwjGcHnUgg}7;>J6F{I)6nC+%z3B&(p0ua6E*7*(ns%EkXNIRB>Z;Wy5R zI<3FqO%i@X5O43c7krb3{{nI2wYiT@c=5K`M<>ehw%iW@)4tJ@MwL#E|7Lbx>EE6o ze5Xa;3HufWC-lZ!B70s4Jf-kI8T%H+PF((VY|%$2a=sz_`%(BQ8Zdu?dKmAW-L3Gi zsvmdWf80eD-VHgcy%7JnanbYqP6gLG7{3H>RB#FBgfV!vqqE~3{2SO9d6f?D?%_@A zvp?w^>&E{wJZ-;s*mmM-dGBx}Ufe+Ljqjbfup@Z`{^env^3-9c9c0AP?Tl7^& z^zSr~V%~uvyv8x6gJSxZrPcLpm~JOa=E-cpMKKeAqE)9tC(=;(gz84gJW7FjzZ zx}DOj2L}rN!8LJNktC=sJ*j0v)T*n zmZaN)<=s|%ZQk+7x13<5drib0y5$Pm^zbqIxcY3sssxH+X9WThJlOL3lX;Zas zLy-^{xiS<|l`$?Nb8sEh>XF+C2sl1&?zPJ{qu!mK%q_GqQX4NANIQq#uDt!IEN?PV zu8wjmySbGarMp#i%=PuMUD89(r?n%BISkbm42G#_w?Vv54S7fD@D_^YwZ%)e(!z1q z$4f_2bKYS}ZdY|u5PDI_p4*O zs4P;)nPqtxE`K|Qk{JA_MAymSOE#;TQrFjqA4LVM-!6(pYOd(u>YTzijc*tkNb#qr zw}AmBedXcJ6wBS9O14lrm%NcF1SM5Psha(2KQpnv81SG0e-`RN_40@BjfZZfRtaQvcuK2zxYN2A~e zecH*mUd?TKPG(jUdT-FehHLQ+_`FY749(#-O@G?O#J8S~kAHba#}?CA6H?2OGK6_A z9goz;OEzP+H6^Hb!;5pLm+pov;Q1LQdC$~CCO5X-w7FIIy<;<#jsJB#be}1*{bXh> zG7Z`6GL7xhiiay~I6bN&Af=EQ0Wp{X zW+XA+rZn7O{g}nf)7HBTTC8{50QP{7swg;wn(1~5=3#&q1+%R*6!A4%O#mrHRx5yY zx7CUYt_!W-1NdfHjmUJ}W3A`ZPjvnMrKGa07m?`+Sce##WBmc&GK#H-IknBIMas8X z*X;|FZ?jc}%;aM2VhU;1y7?FY-H`uzcBmC(s_KUR02Fy=R>u|wGNuCf|Y-5rt& z%B@`rbaqrzKI$EXM499YA;nC2R9BDBW)S5zk5c*gOdwOpI}oLvWAPs!m&jD;4>B?O zqs)lfD5m~Ip=Bx>bDG{Q@|`D3-0Lg@M(b4Ww%zp5t`+y*j(SHabSrZe*Nd9L`znek z^FfSk1C{<&dcXWUvv6NXX2BPdIW*COc7by+G0koMLq~Zk|1cf>!^Sl_`jkSqB?ee& z`eTvzZ{t)v_iusEud+kmstOsBw$QY4BlDZ={%%zz)dgqBEQ?HXjCC@gu}&GMa;wTx zS5n{-8E0_3bpQ*Hk}Q}=n_~Trm*W|0D*$QhHxb?L{D*dc6jeYgEPx7NC(&OwJt7rc zVXa<5W*W=##P*W)0bWqjDw720J z`DQihx`NrHa@6WrYCOM}Nev;*CCQ}ns5!Ed0%xE|GK$#WfS%bDxE-Jm1#VN1l-?0v ze6y|_dzjS7|Eb8Yx{8EPEn<2wt>oLST<|{?`BgkoWbDZzmX5lxqq|A{P`zKO+cTru z6G7@)UdDcz*byQ+oy*akQ}ft0oSD=nJ6>5^9gV#Pt)Q{Z#BG-FkZK$)4C<_X zE~$QrJ(l@MxpiWkJZeM++jEdn_aall6Qt%D+&eX21u(lCLR{gsM)^9(P4{YGS@4g( zfj@JKiUZVKP(+C0Z7!Hdfn#<(I$kh}nl>ZTOPSDZfP!n6QA1=6g|`|k-HH_E06AaU z#}Vcst&StrKDNnh?5MCx*X?g26;)#}GT`$924!IT2bdAoWAH@^)pw3PE1rLebVOQv#%7UP2YKea*6R_jN*NB0+eLv|YE~fj6_OcP z&KPuLuEn?WDR`!8Fr2!Xdw`M=>k=Lfg@Wtg+15t>_PByF`&HB3)C|#_xdt>3(4c7? zx_y|n85TMfSn0q-)!ydEG_?atxB8`OuReqnZ5sDfA&8Uv&P%N~A>W9c6taABgEirR{$GRIOF_zGQH7zy9pVIDPFX<#sS*=g0)k1FC!I@e+S{d-Z!Za8wJWQ=m(WYW=kdf6^ z7t|hA8|(U3jK;WvM@lweMyL=x(tTn&Sth1Y3iE>5T3i(vRc%y=-{&~v0S z4B2d<8^+DA-_jE{*ZlfTX<*;w&X`?t7^v=7pgLh+-CI}iRt};QU^QUjV43n3sfWrk z2JaAqQRj*3r?W-()_L-+XSJO&D~-8BZ`FfDFGqzah&EAju;{HiS38Ghlg^b2I?t{r zjisFlWg&}n5tS95AU&KG0?aDvPCbxrGP1Ch&I24zPpb&5hM1%^AkY!t_ zzSPBbV|`>L{v`nfCXz37V0C=fg>*gSSz*`=qDVMEFQNvTt<+qK)-=hEFsCwQ^K}LV z|7}Vxf~;fVVUJom3ihOCbthCY(IE5{+QOElFJnH^`bXA49_JNyipnacb;i<6W5wW= z9^e(bX^}R18Ytu*?}2GrL2CF1+QJIFg3POEe&hj~b)&wga1H6JOuZZJl4cGwbuWDL z4>Y6xgFSe4x3lhKN1A$qkjijZ2K2Ikn$u-NDQl~K;;b^HHV986RmdqAVobEvPs}RQ z8`H6SEfDV43vEWX2cO6UI zj6NlO8`aO~!JEeMao4eJSl(9cF1$@TazBARAdRK>-K7wfy_9E$pE4^Ta9tHwB&%~c z6_rV=qC}g`)3bRFP%Abyq0LA&#VrHCE7&m8djPm8JsrSH={+%9Hx;g>^=7KSFGKj|UxfZc;V2<$R-l-|!E>GAX?eKp#p+y(@a)8#Nch8-u^8 zryW$eK{c4I&gd`FX2zM|{ya(-m7^C%y>S1kklx71(inU!Qf)1hsWb=Al#Z-0$n6m6 z$F&c8fPUQO6w;of&|B0h zs2srHC0Glj#9rA7N?t=}?NzH{l$>d_lEN3TJEbLhu(u_3B7v;t;!G$6!uFQeOtZ~Q z`j05`Dw7RwHW^Q9s~DJg6|>D|&1z?{j-NwjIX=`57(e>AkSG)OMbog-J-P_xhgclH zEDopuC^z3!-HZ&Vwnn+Mdl)1{bd#m)pttHj7>#o)GFP&+0e0g6kT$GAuohdq-0mIS zURE7VQ?6p}L9b#A9+CY|f2b$TFz9jUC@e6`ffZv5lVa)^g?@^9MV^L_1xxCBZsm-?N79&4ZPnpQ z^;Vl_S$m9{@pqB|-g=;^LKX980rOHaY|}h4of{}HhmK#>d+?Xzk%GN4hpr!=v9A)M z9_tWj7K6A*NxdmhlrZYRbI_M8E1^WCiLQawbnzVAx(+S{q$-ed{y}RdsbuW|5h1e% z$a0=wwr4B_7=y;>Ey`5qaViRwL)=H98a>*Mu{6UN@$^8q*{;JCtX(L?smEX=fb?a6 z(5ym}!Nmq=*}$%!f*Gb;Fl_PFoT@~(R<^E~c#y2VP)88E-Je1e4_#c9O`$^{(7}a< z|FW&^m^4a85B-U(g}NRzXkBcJH6c?gUx*f`QH9%M5oC z#OMYpnk1RR57iLq>OBaZKoy7~Y_&9k119OJJ7r_Ars_H-hN*@$rdek(T1!Pou_9d3 zQB=Z!ib_lm5s>#66k@yB(v9Px5*wNyH!sl0PUk1Ibf!V-o*IQgh^Vx# zWQ{ zpj|2HI;i>-)H05cgU)U zKHm>L*nWL82SWZIikp#KqxR0SsG||Rdnr_Cs@KbKLxo@< zaDUXSN<)If*~tZ~04UH^Yit`x>LJr+`?8m~h-G|vcBWm#V6e$xunFRc4My>GS@&v! z;%VK>)*#EQBPmY|K6(Si?rH%$VNtsdB^n&;Tgzv6(JU}*3>A!58kU>2M@DABG6mUW z{+?5=>^;y%pTsv&JgtYc<0W8XkD2Ymn#M2UdbVHqce^884h2ld!%Ydt{=9x2Su zewUy4{#zEmp&s&jzfBa?b< z6;G+V>N9Z^EUuP%?KNslkJ)%9-!_FzV|*Tvo!+{eVm(S|!0%;M9P1Px4mCZ3Z#c`U z7ECw>n+j}*VVYczEiiy=hg83s2E3D~UV&4LmS6JnJGORe3kMd~mlPZ3g}^oI{uS3# z&>1#G?)*0J8FqWL`Bwlp1wIEr$u9u>6lmu;^lLYB$hVO!gHWw92uJbBLFmUQv@0=) zz1Q;K&2VuV0jV*-ZBks_>n;Ay98A3)Lc-(vQFH!03RLTwgKNm)g(8td;pOUmfWYU* z&Gfq67)4|Kt6&hv=2xaPOPg$qCh(%(rqE5Q8Y*}&CN6yEUf~DlUKU7vN>gQMuz74Y z(=O2JYFVx7LlXknH-)Dk5VC=5oX;>(4@G!l7JE6MyQRKC4s4$QNHst_m28oN6~{ms zU>HSk{^u}qFuX<8MppD)e3Y&~xQ-kn=y*{BwSJ^|dvOeh`0c}LQwXVX!>~b%O%f|w z8_)lU=1T=>Gw_jVa}>pj8n4bbYE0l1(g<{+02JxMf2POOs}#CXb`r-r&1NouB>-)@ z9UTt}k2%I7<%f2mD|twqg4gIlv&U`6;xy5maX<(Tsvd+$xGw(M1wbUrEU>SDsu+tk zn+nkF8x&eqe35rkZf%}0DYOre{_Rpz)&CI$?$=h-n0`z`i|EvhE6g6*7W%oWJ5 zfaN`n95bMpE#tidc7Vg?=@L%WrIZ-E%5gO}h7>HR9CF;oB~=HE7^uVz_>((m{>F2{ z?vw_I1Vf6-;EZfPiet988ytA+tF4a4Efg@|dbKBVR%=F7MI$R(M^~7MErop~nbqAh zO@$>=VRVI5m|oev0y4S6^h#A<3TN?7!-ma<4IjtZ#8z_L0&%gG8u%NSlD~l|A6GcW zgDtw5SK{AX>#@;jf^ zE}Za#kKp~m5rs636C-A|6yu6OWaDNonUnB&us8do2e8%gZXw+|b3|itY-aX_taVI5u~e)(2AJ@yvq@tdgFI?& z{B>qgS>y?JGglj&9RkN4qR^7=72uOYbyP4#t`LpW@fOAO4hlV_EGSBt<{mIfc(baD z)CO#w$Pu&hm{4Mx=ma_5hoc~yrmzD#dGM4%QC}UHkui*7S7p;^50&A_fHTLYA>Ilnu##v1ao7+(X4vHD!_?uaWf0%Xn7f#$qu>r6L%0fObho}yFaz~>T zh{GP__y;oKqDibY8guo{$BN`M)? zF9Wd)cAZ1JV;6c)41T`w4kHUjOYfOG{+(01`KV2|roT(OJYTHa{MiUq@Cu93V^p?j z5;-rhw^J=44<3zaHx&}J?(&V{yagKsVnyWTT#2RXOvO%q$lxx*0O z&8HybLEVorakKEzV#Ykz>n`Fsl{LEl*hcrERqk!!j!4JbJ$XmfE0J{iCLthv?F5Pa2OfGdH zWtvXdGU&DswQParu>#)`^~@%0N`ei?RZ;p-2`q3vFPWJL?1@ZrZvQ1V8z7`bVQXbI zt(#^Xwu|h={;-uoJGq{tWwlw`y_#I%7_Z5-)&eyS%U_Yui1AN2YSB#{62h0B|g`ZH#0O&SW{0cilT1#Eulx(5U zjNb9_#>#TG%2W`cQOtuknXU`9lXaU9cr1gKjFKL3MA%T84A+P0{m<|V=deSHwFG#S z-0Z#U=8$4%BZkY)I5WUcVZ+Yk3dweb?|87XO$G7Sv8v*1H+yQk1SB}y)tD>LLsT^s z=5EekA$o}O?~Hk8%UbEd>VpBYZ^j_&kES}@%9JH2h_H%d#_~t5hl75*9<0`xG~lqr zqrd~w+yk<8t~%qMI-XGyZ^B#D@{;{aE#}hM!f@za_TLk<*{LFIUMhr%!8cj z3t=TiUZm!!Xd2cMS9l4j;n)~V>eom?UH;gl4j}~v`It%l2B}O6fXBGr%Sgd#_=!oq zf)rK^(S7{wRZMdt1wJ*Y*N~b-0pkT||1DCJ#mMdf^=_8TZ*b7?1J1D^AmGn}2czIe1*x-~i9X z9xO4THZfinf2U2heO;1;D8Z@7k`YttHr-H`Fw?`(-LR1)Hgt*9dIraUF!$Z;odfjus=; zou4}!nLNpox2N=87nr|EidJIL- z(Qx%~6Hv!)J8Y5XJJ3734RRlnA>oXjwL>_BdA7kzbtUAd>j|EiY#J~XJblFp>)MJK z&n$9ng_gjfZDM}75V0$xPFNbULBlFg(7Lv0-aA~dcGUu=`7&&@u1Be1d>*^FqiwYu zZ(1IDHs!%bvB$NLXBZs`J)1pvIK|p(vo9Zy%%|gnhlxmcVatNsJU9xT*~%eBrSQzc zD}62dO|7QwQ1Y7;!JK2%?c>#SJ=mhE#dFCihwEAKJ-JNGvo~2>P_E~t$H}%nz8dc+zizh!CYf2n=zbVGU%|9bzLp;dOWYU8J|G!!XIm30-O- zw$IuQK{6lb$!-Yn29B+GgJugFC^D4oq6H0Ho;Epi= zGa#cAjp1N=m7x#>upF33u1`;u6H*~P-_R0vAa2vJjDpI~!_84Pqc;~;v4hW;LKsUO zUH35#jt=ouq7l|`*}CTAgy!NGD1DIW_V0Fep!@*>c59)6yVFIxaD5BmKMFcKK$u(E zD6QCoP)uM5sd7fKR8F`v&!+%TmydmAHHsXj0MJwXHs!AZcuu~t=Fi_Y;2U@v#_K9r z42Q@aL=Qz$Wa`CIWGWiFOZ7J0-qTj+z#8p|)HXJ+VnSgtS|Opx20iB(aIVmcJ5$|P zsuT3$?jxeheFhGS52d7!u<^;&#WyM?!DC%AD}9(87$U}#`4;TUx|_Sv+8PnCMN&*s zOsyP|>~3UA=KW|2e;cYf6xlrlB0<9CVtN6E?p0CYVrdiiY!#Or4u+lV%Hd5BTh>l% z8#gw>+Rxwa76x*kr>ZedILb{Ah*`gwmw#dI=kbt+ih4VRHcItm#CV{3&tKYv0-yMk z`n?jAd0U05^Pev6BxA0WlQep z64b2gqv>u%kis}wl|GVkU`Wwo2oIcLnus&ExeXEQnc&pebsRS;!-Y+W)#Q3*k@I`) zxDrHrzS}+Bo`cY!8y6p-th;~AMI1Xy5y+QG;TqZ$$!Kqa*FAE+E2q>HnW^idFOYjC zR+H6w9fj57UJNFIe!UgS-b%{cCX&{D9hHx5%Aa?g>VlX~uj=KV4@|71 z&^EJF3n2N1#p@QZfM?+mi;SZ1DvWbvd|o-4X2&4*fMM}@KQk3T>=9^{+#meAc7L~+ z{%%oyO@IANf70}=Cz}pa_%1X(IzI3AuBOrZyj3=RT5mFG++pw%6#?k3NR;H|hx@c_ zEavcp`BpEY(p@hn((ZaRJ;}MlSOpn(+G2)L?#fw-Nk2fL9Wn>kL9;l`pmL#WS@1?( zg`G0vPP>R1%UWwYhU7XZnUsd*e|AVwIhOwgvKGNfi3PY1yA!YrqOC}d=kSGzt}Qv| zK6^_(X){l9<~)E!t)rYx-<=q*w6S(GS83w4HLlgnyA0;-6ufB|r%sW%>y+-Y0P2CD z=W&)jOx`>(?966kICXwNNE3y&+b9qo^Q1x7xE+*-RF8C?k-9A&Vl}3RFB(apfY$*b zlw*Jw2oLr(124zNT~1&iL?tQIyS}YjxerJxSSL*f>(%zyS@}VzjF1IE4r>5Fs1noz zRAB!u_IDQdLAEVmPdlwWXxjav-7Qyew6IlV8khr>4b=arUxlBDp`5L9i?EiX8E0T>)$2h`}`s0Qh2?WZ&%7 z0Kw|dsoCQ4nu?LZt@Lhy(Zok#(BzOg*e#>>D9-F<5SiL=)J)}DDap~_685cEt6k(#`krxa@;;@b z6Bv9aWQ^m_4r8S;_gSCGO}Gy z*;iboI#eUa*xBScQ`L#SB#i|xWuUyvzjLZyU8=o-t3k%H?Xn5>?fJKxEnKcfs@eL4;w`C75z>p9PRIhp#oKKlh`J)J~o&-5ClfsB~ zK*}>onntQw6@g*92f+=VY=Yo6tC!oVojILcGO+CDsxhWHgtE2(D7X?mJVzmOAL9~NSk7MpJyG!a(K)QJRijGzEO0^GQP%HbsU%U#%F-fSURi?;Wbly~iE zc~k_@$=}K&{0-}#J($m@CGuUt;)7--tpswZtY8-n-Kd_6Os58iwjw3u*)O3do?iix z{VuRi=MaI?jA;fxRri$kgS%EDP(1rh<`)P=c^!c$BIG^k zqfcUFS%KC$02Z}i>p|u*EXUYdsFRZVhQx=o%m^KSmZp8n$n%aGg1e>=ItwE!9l!Y< zw%wJEcZeHvwsXYb>V1LP$~PVHQcr?j>Qot=sn}uN>wum*^_%xlWF6(tF(`l8YK~w? ze}YNM>`P_+`g9(b-O1TL7bWj*Z=H*HT98HzMcC}5P{DA|PP3U>JO?N)|5WucKe_+K z)6~hAsUcILN3K&z3?9ZHCR;oIg3N5rd?+%R!s{SE<)%w)Y@KSyY&n8qGYtk|mVK#k z8X-3jaO}Srg9xpo-waSLTsFs~H(?tDLNGDMC7r4^CMEY^eqrbntcGsV$?s#hf5(Qx z-*h#TB4TPdv(wtev;7zL90Xf-f(jg+JN{)ijfmD2Sa(^Yr14TRDN{k7<_8@fJX;^*H)V0`2yL4sG~7$!|R=nVO)@ zBq44)va<7&o>0XixZ0%EjES7t+;1{v{sO%-%1y~4YXsLT!d~zKB^9Y?OyL@m`Gzaq zZIc~IXTlUZETqx4{K}*eTJIW8Nf+s=Jb;kMiKIMPG1v%WSQg-Z4j0Km^_esz2YTxo z*7fHD%)iLYC=X;alP(=%QWxoJZtNmdSR()zppk0MTPNM(a`dJ67THCYva4*eYq^OZ zOgaxmHgJ(scJbBUmzyEHGEcb}NrDIKg0dyLR(Z z9njHn@11_F^!TYbIQ?(6oOllrcrES9Zfj` z`H99%s_}?0!Yh|C3Y^QQn~@zbB=uvnVh5mBoS>u#iZpe8ay1yeiQtz)xK8IM5y)$h z#sTf-l|F#}aFcEeHK3#XxnN4U1BY&vr?-fiBx8N1O+mLKtV?GJxa>02^PXA4yn;4w zR{j}o_~#{fC+TK0h?}jUjF1Bu#5@$iAm&liF95EK=g(2OIPV7y5+aB)w@YRVf15|) zU#On^?Lk8mHOO3lvVxgdHOxSLBK!-}QMJl=mn)b-Y*Ri?@yOQ3heWweo%svX*m9N9 z?b`wV<~aE+Al;(g26}n9ssK-;75Gv1fut(hKuZS@>4b;&(WXRItB2d>bk_Y-DqOl% zyDLy+JqX(JC9T&ry!`9+J@CyWt%v!@vH<pU$gKwUVOm`~A0`ZIMpK^-=DZWJ^;-a?oQXsi)MeF^afQb$>JBR3H!?nCyMUn^u|^x{Qn+Mi^-G*#zWfAK)UGqkq({-FoMEy|#;K)j zQs&fhggLGRd2=iC@e~6v_|Oa3!1yfk`)K|zC<3;BHnG*MZHw@!CA4&|Ak}}xB9=4n zDa~NO7_9rJjdHV^hcH|TdX*u4gd)TO*ZAN8FwW03Wpz*z&iF#BPh66X-F_Vf@H|Io zwPR;CUB79Kdiiyzj4oCg;k4kmr+PdP*&s?{uVp{C*ms}w0VX!t^-#=+H8@P68f8}C zoU4VB-p4mLCB4V}sbxkD4Vyr)r1vGWBr2-{T~}mCtZSEnT6p={s9xu|{A`&~>bcl7 zRPASBDsQf$+`gzV*^%7W8U+eT?#th*9~nro{3mJXP-`tlmfRPqbu@GUJc(6Itqb!( z^Pfs%$-Pm*i3%7*j;E>1wt!`04b2tZ+m~morCzjf-zewH|DfZg&#I;ep~7uYU`o~i zlfK~;m28J@(?Y({6m9;%w`z1E)tvtU1q$rvRnSo%S1a$gP&th9rEWwk>*!(YWJe>a zb9-?S*X5(lGInV9{w7LxnM=9PnZdiwORCLO71pnzJdY{JBSd_jT_A3zdR(LoZ_)JyIh1_8f=Y#GQ0R&}K0T_P z`;fVieF+>Q3QA5noM1z-?K3JQv*XmvN>umq9F8Mk4LkD@+@UIg@HZz^_dsg2Ak}XN z&3lOE_uHXX@BpDFKcu6Ob;h;1Gk_fATfDG#$F7Ypo`cv)kQ?FR z`+qmSVh{dT_Hc!~Gp@(RmyJ2t1GM;dLh@nl=Bd1Hue+&m7$}%Y$-mRR;cQHPOIaXc zxD>!gfmU35k`%&*bL06dP>)H&pzbg>#ffkiLCJ^8VHP#dDJb_QiJF$oFR<*Q%l-(8 zoJOI$buD`LhQ*|TQC|%WH~B;B5nkKBnz8&%6v*Vifi$oU{#Ch91WiX*zoD8icfKTq zXCljiuRi0bngJsRFl6J zt0(zuGqSJ0+jLB$Y4h#BG3tvcw8o6=UuXO#X5pRWGBL4z`l{{!J8-OYSif3roQ+;M-^JO8)Pl}ek;+>@x$ldK;e|D*XH)1NyG=Q= z^JbYdeVnSdt#RgMX+D= z&~hF%CdNS7Q&|~41-o-8^!}acKwWeqI;spd{8`nQ==Hh6W5ntmN32{&DxVv+Z&vwr_bV$b8R;_o18Cjk{p|08wl%Fr# zuJ=6aDUh4@e4uU`97~YNqquj^10AARgAErH;Z2eejwToYkgeV#Q6Smng(94a%$6P^ z)f)a}#)~_SLl6R6XuYood2!JK*OMo5Ci99`+)5?hc!9eC!Fp}g%s@swB{rfO9@6Ff zdIGDx?nA8Cf&aLqR^08C$~pw2$z;N)Xb#D!SEI;|e_6R){U_2`s*Tob%xte=4Hvti zth;BMP*2zHi?tPRg&S^VG+iSj1444h{SCkZa{mor5xM^kkVkF~9{BEd2Jyt19j9z( zAe7_Sz{20ZkMT`%HL-)rvPO~jXKGEUOXYkcsl1<3bC#^1xW98~Q^B0SLpbh<)PD;j zhL4-Qx6!7tV~_&T)|u)W)xUo#+)66;Bbe%kcM5tC2_%_yYNx3DvN##jX=gzXDt1T@ z?hpvNjzV^SyqS6_R4F1ar3X4>)41Dlg5yMN zlb3}~mzsvDFe1H(_{601p29RIQ)r#(;VH8Rw1`*C`>d`S3sG5YzC9}fFuAz90L1HL zbvX6%?xV7|z(D&9ncBKyW#Tp`KcwS*Q4S1K&xJE}8o_pw%k+jA@?KV?m^aOq1yKU+ zY0@)hEU(IbT%bH z=sl+gjIA=fYqSgDhovwy?QbYqn%R%Pwc*<_!Juyl-Y4R_81i-z+&AX!A|>x3VT*(W zTV`Hqe6`g)t^|WWvYsv0ra4;>SWCQ0z{JdG`#K?+nJ74IH4-FG6L*D?&Ct! zte@q5i@{qW9NSjx^W{JR;kS|i*y#&KufyvQILAXt@HBwW0+vt#FD|ijg`JzV@ypG# z2|i2LKp11;PNLk|g>oGQ75I1%I6Zz4yZxQi!jV<$;^WPSsy^Y=$3N$46ID11`yCRBMf+H{U>v zWu5bpK`b>S1Qy0C&4kwo--aYOfK^oYpQJGug)zN`LN*aSBWA7-@4pm`vR0ifVC6F+ zZBotkI@Dv?;|rtSC6N9Xuuq zsfaTMRDd=kha$3Dupc_FSC})QZY45N zVI?`c%tmxfE2JLYY=VAxRq##* zxC)#z+C;ASmPxFU?`OIZfoHzkZ8>kM-`;)jcqxs~_t7Xseam0N0 zJ*QICJ9(wiS8MT=bstWar46xPrz*9Q+3~yTAqt~URUKGBYNJvANjmkS-$5GA99F% zd|m1RcwhPGG(LC)pBKA^?xoNXokS+$Q{ugGjD+V-j^b~060L|<35BN-%R$e}!=DT! zB{RY+ai=~Hz-ttoC`(m}a~ZXie20=xKb5S=L&bg5xAVMFQ22c`7dH+%t^3I%?`DLmDOR?bgupQ&m~yI$5y zUxpMPaLFJ%!!$=hkuj(cZpn3y|+RdFQ( zZNLw(i*e7}<^ZHLQnC*2kOa4+j6kMHFb<}{t>NZE3xgNS^z3u2Vjgbzl;=S1h7|4= z4C(OqTIUxwihJY>whXP6L~3|797@);o4 zO(`Q4r}zgcWvRYk#sdJRzok|Uqvv@c8vuhP*>92JM+qVMcn=ShVmnLj7D0=ZekY?D z>yE=3TumCuSNDrE&WB6!w61#``#&@W8ypW2ytSn5xdvKrrJQivaLaq`g(GiB;ypk$3l zH<+?mbTQ77pOUepY_j@efGL|$ZxMwy*u8TFY}ZVYDj$M{Z*LdME&3EVBLBm+do;!u z?U#(#mC%|~KBZ<_%EAMRL^HsV$ZhfPMm%1+j56;Bz_1DzcqW1d{>jZbHzhfhLJpE$gcKH2odiI)2T z9zD^7*w;-DpIEkm2jAEMO%4(TXP8IYZdn`b4RGVy5q&U+^0ia=n5;JYY~%67+&`Im z(TT&G>65n#Pqb_Sm~f&AkKn&`*@7W9ToIR+lrSDRRXMj z`B)VlZ(I9{yV~gbP-9B7+65ll83&KZP7z-?|J}PQaoWh`NF{ngajljEwj@HAwZLls_TWFaAy)*mnbh zUe!4`Y!g2UtD!a94f#ve+dn~_&hMui!xk$RMc=t|9&4OO{05b_QtltA4;7ZOs9+_Va>M4uVRF{Lm@?W5# z_Ssn$QqR%QVX`W|fdJ4kZGqH7>b;gN0;1Og;%DfxXW-8)Q zzR$VIQls%}?hl?z@H9I&?N)FU&8E6|bW=wnHHoLf`|av;PwgFWqTn(h8Ol2HY>&YWW*S7r{@oDRMqFZNVn>qxd8kcOvqn9_%r}OR+46cdwnvgLe$O z0_cH{lMiGNRsE^10C2wo`MHZi4@=6sPW)i0GsF*;I^#QjFb`KqnAI})z&rycC@7an z`M7DeKZ{a%qrEt*owr@=aX6ka?v45Tn<({KWP+gYmT#Fj!9kJCM-2F-0Y=VeB8-Ar zl&xfoLPpWG=}hI|_;4Q4VAYqw5Ghg&?Ux(dVZcrU9x|Y-2RLszV%N3*D5WmL{I^vn`Z)4=Mwz;}RK9u6 z0yjdQ)z~|UEP(GItVr$_cXO}#_Ru#dnbohs%@tIG2cZ%!j%lmKc823MCqU||M2h$R z)YZcFm^+^p3|nh|qw;;}i$4E3S`j17=(JXUP)O>%6boGzAI#C2AZT+iow@~&AReOB z2k@K=7#7i|xaJXFsqTvmOXv)#TNFPXaPr$m`DUxjP)C5Co#G1gKn?;~k0Qi3R7ZpR z+eHKggCP~++s1Hl$jVm_?kUJ)jqG~3=7yQItxr_Ym5AH+yP?0V!NZE;S@0kdk8dDs z!5*+8yJRyX%e~mpl8E#ytCa~XD_A@m!r!9QZ#m^}o&`;AMZ9pdoN%P+yPN@fxa&zj2_fzA;euWfX5ZFcyXnGm^d{F%! zeMKfVnrQ;p1d1e;vm7_qy`I$Hnv}V{o%%b@OlL4O$CKL1DM#w>L?Or9VfD0u-h?ow z4Qk3@kT-K|lROMQ=t0dE%}DhZSflRL7ub$v}@@Y3HOq(ApsQ{f|-=A$2R zyv@JWlaixB!l#UyH_X&;rQYY?B9_rhsV6Y$J~X-($b$2ak<~ALOC9QE1zWq&+B^e` zif>ow_|1qV_#H6+B}!(|=sJ}Tl=mbY&u9$7NHCmt@TJGD#&CO$2PnP&4Y=+XANH~7 z{U2tW+SL1}NinN+$2F97)i4(bktkC3lr}(T2iYK!3qiKDdXK*6%oF5iro<`!T97_2 zhR+6=hKu3d=@X<_rdE=_A2k3h+rkzRJ6_7+RhisRc4mLT?7qf>H_xK8t10Jukqn-rcLO*gO90eSphNZ zE7Tgmo(hT#gaCko{um%qXX+?Iiv4f~KqL_H&J5ehI5gnlIr=R>yUlN0!=f!aa00+M zP>|Y}DVe+ADK*wE{=9;+kP!i|9Um&<8|90}9PYYh0TlK~TvMM3~ z!G?_TbuPx>yTi%<1E>IAZj((YEXP93eR5dWp-mpd#z_FW0;dZ8%WzW6{%wsSSFc)TNngEORQw5bL=7M=u1WDLAAd$k%H5e z8vu$@`%9O_M_h?l6H8DX;A%3}Q?YQSx_t8mLa% zsj2wPk0B*S5nu;_x+$0l{`1h$FJtmCONGXLXZw#ZY1w&;R*GS+99 zvXj1b`wxE_olkBgbFFPO=Sh@bh>Rg0G>;th4L=7J{0s805P=(p+N%~(W)xEaCK}WjJB91X{&nCnjLbx_B~np@n(5Xsgm~oh zloogdq4xTlL`GsP$B{Z&fgPXzdm-dNksHj&46eglv6-%8Z#G*Kuse8~;QoYs5x#{) zsnhY@YYWR@gDGkd-1e*N3{8gNPzmPEgDvpC6 zeaD0?dK$?Pyh>6*$z(XFuDnt_<&+ruV)YT zn;!lt6Z81#6BKF0f1#mB5HbY(w;19A+}Li!MR$ImWMsiaTNe1ie?}HyEW>-lBxP}t z(I_Gz1AE6fFJ-I)@KXlZ9&LV!9vIj%DGxend=+{eY~;0se$1UU`~& zL^0YdVsBa*u;j<42wM7|%=jbY=6riYZ;HJ*U%l2xuoNiJ8dwUr-~n-Y+h@w8n-Ir| zNkc%?M>f!sbJQJe_jW8f7Xt+KjKbryMO-0Ts&j+Ic1)>hC6)asXvMl0gc(SY#_B+k zXv~u;I!e?(tvbNH|RoGoCP7w0>h%uSHMM3kIO z-Txw~Yrz9y`ef|8w}jzi+5d-IN;Y8AxR`s7w%r&H-;epVQ07LY%Ht?@cRnI<)!2mOu#|8J8Sig6;an z)Oe!>L6(g_pn^=!roiQRYA}cYj*5?i0+;g!2E5ofqfhd$m&TgQxiS6=#K3j?Kwqj6Yf<6;b&W03qEuK*hED|}$0+5ddAdFdNMWr$K za^X~HEH{k1u05~_x1Vw-VB#+PSL22#)VrVB!qJE>`#hk)OywptHYcgXC|@XP#tC^n zTsl&@@ew$vLMjLc?f-o2VVa(4z;y;(10Exm?0h+M)iHE5&s=)~@!3ac$N29fB;*g# z?a(Le06+{PwI!;?xUgwd$P2M}c;W&{nWlf(r4JIgqgHuB&}}O0zXpi6M3Mhd-0R0>EF8(Tpup zQb4ks0uO_rvF>7s;Zn6JB8;w^Hk+{w!d_er_nYZ~HKY?1cuZ@|9Uv;eyvDnQj$e_+ z;kZVbl?%dwC#97%6ElQ|0L`PPi6X??#7k7R>k9_4$W{vP#6G>r*$Ut2Fj-9z=@0@1 z5CTl5TRYniV4GjQqzU^U?VyJJv~W`$o_R3?JOK36;ki8=4oMNIZsbHJVw=DSMD*?q z3&yM3GsDGNI-6yl(Ezxb76$O*E--+fJn%hD??ImPC^kJUe#!X|hbVcD8WUMbo^!FQ zeUVVNFf#rpjKExUyS5dIODy7w!<21jtz@f!Wt=H?4vNG%1;x5}E}GWypW}GO*oNg` z!{)cSm3@b|B>rG47J5H^2?iMYfb}!}*3s=-Tl|tRy&L)yg}16R-%%mg#+v6$*TY18zJl}+sUwrTn( zq)?=er^3U<#B3re!#@4yC}g^I@F)m#15Fu3$8k9~Y-ZKN&(zaN5dJf+AFF+T{M5el(5 zDA~0Rv_mzS2Sr2hlsW*>(9$du6 zS$!;(Q+$CP8+0?}Q2>gyG%ZNR|4UZNn*oVdq(2;OpaKZ`|Gw5uaF8)Vanm&Cvbh5^+}|TkDG5n zX0f-h5&uWjwvML2**I>Vg@B+2#`S7>wCGtDH59Rm%CtK_rF^AovSobqy$zn~;IxL!;)v<+G4_UUE^$<3Npc)MV z+e0-P)B@EAav}&zm`yJ{L^kgI_=wQ}ZSg5#G|fI19-y)ZSX|UK2;9sF?$FhEup;<4 ziuipH>9MgtUy?sp(=dG7g%m7h-Z1?sSle=h%Z+%9j?BCxapJ88+OyM z7+_sjAjlmGB(Ip;A$(+?WJABk16XaC`)goIzpPMj}!3VTlg=ZjQG<|n*T0D&|5NF)MJ5^xgEnz zi$CoGme%RO97dL$#$&0%cz+jz5A(mj)KO!L)U#u8)Iu9elBsSmU?G4zS+E4JE%>N# z4dZ1~#8Mq*@Ty!4I1mH78-gE8y-X7)5U!w*^V#+hIFUv?=9`~V%=skszX1#IbbJ4W zuqII93!JvN+Jj%iA&eqln@P8Wyg3Dg)UlM}ja^-b`9Wr3Z^YVm4G@PQGR@;=e)BQ0 zAa8DKNI?=A*<=6z5myh6NADX*%>rm5b*)+H8UQzCRslfQuQrRi%B)OT#7Ep?Q>-j0$r9-GzVdOT#5gTzt=cifs#K zZ8K^3EMK5B2wX@8`BV5ODeWvgNDRlzKtVE$GybpWv|L?{n;B^#02F~8hwJ4G@`h6y z4mNRRor~}sxCr_S$sE9Vw)lnIMGPc7W~g_LcuoKmS3gG?SBov+kzx^aY6^cz4@RGY zb5Wj=S^jCzTMtoMp+ttKT>x2t-c3+;+J#6hLdtY>vAhkFh6{>#%=ZZaE*p$2#*VJr`ZivbzcLDd=9|$P^VnGAmrergvT>&rLK;GP|UZHD+~&-|M6Im$zxe#{<(cKkX2JU zb&aZx@iOWvyq=f#b2E52MftDZ&E}nUccMahq~?kzp1_U(n#XxuUK8d1*&qZjvJag| zY4{s>fV`m1co;4?0RDo1d+Z?%_{3umX}n@Jb4G6Idh8)@eSGRRMk{V6LKjAL?&y$C z{!%vmUosvJF|BA2ZF(6{@F|1l+(VT7 ztY8=G2TtXVYMCORrLyL$DeY+-O3dUGHulQHaDcryzp~>wI+FTf{NNsnt^T-U_A@Xv zF|q^N#Y~B}BJ!}BaD(Q-$bOE>^K{2eWp)uW>O_AAb*-S?4$qA*G3{RY3i@Nz9kNk} z**-wIFjVrkikb##gO^bGfsC}@8l1fjoGqU-67Qc4t>PjJ^ESpuwwiVLv#j(JZ<}tv z5FI7?1ZS#|hb7aC|EuHSA~p1O5!AS^--+4p`aPCFK=5RD2j{|tUMvz*Tw~D+%gM@@6Bmoj438cD~Z9>RHC;^QU zS82OYt91e16-9|kyG+H4+-fTiZ$oKUjNa=KpQIAWW!2jI+k4M<&iSI&bgg^-$Ykc5 z*?Z5PJ$v@d?3wYK@lbMCd#Q)&LQ_jTWocL#F;;URE zD}%nNH0L)4t+Jho3|igcOk~jNE@$Gaka8Xy6An&R)-=SZzeB)CYZ%vHGsFEWptUjz zkDBR0WVsrB3}yvgEAZoy#-p$`dQo>Hml7Y9Z`f4I98ozrEN)%1c>AJ0PwIfg%n>7J zHh){11--OW)yqzrC5iRe_(S5ETrQV`7>pAu4{{cV9S_q5`Lu2zDCU_t1jRgjgH2#A zbe(mBLBVJOO>@>vW!(tf6IXIDPSt{liwjgoNnBws_Lc@dzH{lfg}_DXJV2wTVh5b+ z7byPt&iX}c-S<9)rKDrZS_Bmog|avTrTBu;bsI1kgrVxtEy}{9xC0*-b;%ib#J&W& z=7U){~dsZz^aalxvN__khvF zU4{`^56Pf$%7AA$jTQ+QmaJ|A&t|a{P)GO)%lyTkn5`tc`zYqJkcirLkxwa=6DTiG zqRT=D3IZ(*d1ayhp?*J9?mnsQA-h~g(2vB(_>P`x9pL({KStVH-74DKI;5WD7=PP= zZ#2f8fH`thSvays_!y-c3JOXu#U7h3Sel3b*PCizHhnphS>(&+Zq%or-n4s>3sAEbFB`(21*2>Bj#$@wY^<1L!MOM;~m)W1s&PE^xO!%G*3j#IKd0oRc6~E4w{;9b$Aulo{n;W&WtS0^bH5!C1|Ds zVRM@VG-0F|eZ$+h=&*>j(6=nJRf6r3b30|hHwkFo=*ee$9zipDswbb!UY_)l*w`jx z#3~tRUH0q(HqSl(f;vQA@q`sCF=D|tyy7WhQ)Ml4uRlvUI09NDxR;tmCp<*&W zn#0p^v|kG~Tm|x={^BfrgU*#a%>`3y}(LjetnNp-6@7+ND- z;uShGtp-XC>sfHnS%}7YNLp+=fi}}t%QsvIw{Qc#Nx4qpZpT+~cc+89e7QWD%IGLb z3Eir%9zy8c;Vk0?M-HAJ|2O+On8 zD)({p@O}*ClI4Q*a3o@K;C_LGeK{uXJ(*A}SU1i5U^w$dlRM=z(n*d1sQJ*E%1}z1(vfcV=E1;Dy~*wkJgRo)Q9&YoV1ooX|Z-C$i9Tah`f{ja~<@$%xWW zj9u(vA;3;K8uhepDzlk-_~d-VUP<)MB)v=aAibmF^_vP~2Lfs;Zz|V4FDY90 z$g+=@ePe8m>75wM3GdZo7ch~6s|8+M zEO-R^n@J5&fIS;vxQVAN$k~I}grHGs53V21vvY!oeaibCp=FU;Kk)9d&&nw%S`N^e z?*W7P;FOEX`)}<0=)FT&e!Jm8B4(fTa$o-wFfsrI3#LIVKFE%rmzNaGLQF=;N$eO& z@_0OBB~J&F#-ta7t1>Sr?@Ek76r(Jz?rki5-d*etfxGErp9lU#fc*|@+ZR;kQQk&s zd_rh}*#u4`!)>b6<@)GKq4QoI4ZSS@Jw5g)aq$~CO3YJ?yc#o(SXYAv_#KR3@VYbO zA&fw@fsQQ1o-JsHKO!b_wwV`9y;}rBJujFdOLZ}rMHhy?y8@3P)9R!b^G2Aq2;NRP zm(g+rYe8=*7w@m&%(AYgu7f5QQ=wbnfx_S5Y(mB}{88jFEaDl~s6p92IyS0@R+g6z z9vHkGT5Lo#o-&%mZf$ybxAJ1IfJl;sLBF_LdA|kexivoTL9>-45-wH2Fx#yMJ{iv2 zN6le`v7ZyXo)hsRN#0(w8D@odFMn$kpCRn$@aE+Lg|O~LJEzX#_{KXG-ls8LCELa>P!+61SA*85Se1DOCin*B#WgT;1(i09uoOtM zRwgNeK|rH9qK9&SNeoeLZ6O^shT)v-c}S6=JDQ(a$;F)ckzxi#;{ZQX5bNn6pCpgI14M@7$Z&6@9C_mP zKcczB*Y~O`z>uZNw*qFUiJN-i0rtSf#H;8Q-;0TIA^652m^`ijh+&?5mGIFG;#n1H5Qt6+Nq(8Y z`Ry^z(9f&y!J*D>Vo$r#`y8rydT7PdXpVE2_TYNLv*u9#2la3tt>4t7h|I#C&`8BA(>j3*vE;ihjKCb#ogy;loBp zCYb=n^O<1XcDyZTDPJp|uw=`zXYnss9Twr+E40Lh_p;0mJn8D|1Cw*8oV*`LV*tIS zCC!NDsArgXlN>PPPeOIIB9XP}$&&cKZaC<`d8hw5!%b~O;{E&K@N;0nL~=&-fswEo zs?LIzKiF@Kk>>Ngp*oD~4PTSg=uK9KTTkPx*Y$(ZMD&&Mb!%0}G8b~ra)w#>PXh+M;)t<%aQLK&fefMail;M1M z+CyyeT~Joc`I-Tj>HlY2XYIlB$N6oz6JaVj81F%l-wxTGG`yls5M3pSZGyes)8{>ys9yam=64C~Di7X?sW0)CBlQm&=p2uCT)vPP1Lm2`#>HPPkrT zscb0R47-&AN&GI_2rR%3&drrcg^%D;CV1~(Pvz30VXhT`3 zu^anKelT1Z!3RVM-5L?!6q8mM0S3z|ibzvNP~5S!m@!#sjvm&)mOE*oa*0vUJ4(K; z+-hS-Ns1F@yj_PY;XY&gq6 zPnRQ$iUr_(73ZPS*fa#G$ z0zQZ0r`yS_l*UN${RNdbesnV>dVrRBhDXT;W4vqW+xXtdxAs`iTmBX;2X~KX#iBf` z7rQkEgW6xP4u=MSXzFk`jEv%49kKj$;){Vf27J3bGT12OiM6*GNq3XcdlK2Lmguy%4>lxGiPKkwq8p`KID=cJY(i1&=Y1aVZ#RGb6?E&895t z?}EmWy+@rS{lCC8#|U#<3B9Xla~GW?k$6pG74>ju2VyHof5Tn~b&{%{4^?^)^Bi}y z%YgHfNhjMmvp$G^<3vC0emD`u^o}Sk3!=+*C~$jP>i^iwVI&$Vo|^a{y8)2OrOMi& zJU>nsw9&)Ia1oHOr~eefzFW#Mq`-~fXKVpv@pe126d3zTe1J(>*a6H+ZKN@PW<=5ebj-?1 zjgW-d;IzT$-A3`9E{|)?aBI!Bhe`g&W4L}5No53 z`7N;(ckK3LK^gxH!5ehdSx?p(exEX{pRPKi$+>$HH1-&NfgkjZA=lSu8B_TH$`_;= z45i6us3!c!A$nsI*347^!b$(61|^QBq&Uvy=+NY_RDZYAON~K`%4bFLN&XBSSEjb3G%@;A|C}_OP4D(nmE5((_^P6j$;V_`*m!D~gBnD@77ikY>{{I<)yUI<)cFp-H>;`iuqjZT;TMRG0mx62b% z&(D~@ox-o;1IiZ=+|l@fhtsJPzE8<;Q(UFnA*JG%DuK$7c2pkwJL1q|%9EH%Uc2y2U*acsf0%Tp#kMCni%d zH4fK7)9-Ws`xSpK752ZFu;vDJg?gz>t(t!8!WmcHf+u$9cX#4n?y-zt%}z8_Zcc8_ z+|sPj=1nylud6Sw-V~|~)ot3eF;q9ZE>vA!zlHvmZfU3r)oo~4SG}>JVsmIiMMYid z#_FvVq0&tip^bHAq58)1>aC&r&Ff0*8$#>0lx}Vat!*g#eyDa+*~Xgc+OiOWHTWmA zZf!%TW?fyVzO*9LxT$et-MUc2hE1W`t>q{{=mRS%HrH2gs+|`IUpqJN+WhRmHKl9I z%JXuv0}HmJCL0xiX3YuDRdL46DRmrpN%&;65@i3;XqAok`W6$^yav4v^Vgqs!!)AOw`ki)?# z*+{fxG!VoqMHyQT0hq4^D1jnyphPLV6}c=UWE2z;8^|bA9(zBpM*fk0J_z}eem)fW zrTzRsc|ndwncN^h5YftS<@!x-ZkmYv14v+i}klX`ta;~rq3*xoHgN>ZH@}N?SXl7@-5=e z+7|K0H$GSX*nd5?V0J;?&)bH`%Hr|ATs&XrCHT)?!2j3c`MTQSKV$)^rV7;mwr!^z zV>a+zJNJt%;}2+mgYsVhBW2 zxQ#+Y7z~&Jp+yi93c(!c!yzyqbVU#l588OpaG)6o>IhH`0M$U?2l%dkiHe2+9|fEW zI4y9AKslfTfd&9g0(vLVOdu3U1dvD|89)XDi3PF&$d5oW3E4o%r-X!1l1E7uC3`6O z6tjww5jjnw;$x+R>o8aQA06!t{I)lSC`I8rFl8!5r5OT5s|^Az<|g2Jc#o<}?biIf z9>SSud3eFfmPr;#65iWjP;fYpC7yL&zM-GtPe-M7kT0f;C zA3O7}lH9G??blMluVuZPeU&k8_C19*-Ap4&D*3g(8l4`epaV^y9{r_-B%}apoXXrI zuL@Ao07Js239c{;jS96J-Bnou!8(Z~1WGg^K;n?{NL5Jnt^&MX=7P?V2Yz|$zCm+N>s25HACH5shd!;3GgsTJ$3i#b}1^t)voU5hmIeoA*DZEOcp z9iWF&fsjv-wpDSo5#>=)DiRu`2j_mEf5v)4kBWYx;70*Hi!_y(F5&RXJ;hxxyh7Fa z_OWd*8m;1{j=O?7tJl0cWo+m2cix-jl3ZgEdImKs&}FsXUaLzF4&EMgx0sO_o$9Tx zDEqFfwe$QpzD=)(`MOU)NE*UVW1J}QIqrUGTYb}#87s`4uDs+`A3a$4_0b9Omz*af z+pjx91|39bJI+zjym{H2?FqZ*HIzP|*}3=sLMN=e{!H2t-5V(*$~H7k+OzI0gm&?kz^B#ln8{aRxYFiTV zM}!|k_v5`6?)~POqCqp?sYqN~ZVmlm&VslnK56wF8}@8{+2ooglCc`0r!Y_z%|qLc zk6afqJ@Tx{a%9DTvByp{T25>qu<1aur7JUL=h24|dQQ^^JsErS@P~UZJ$p|V>>cs$ zSHdOO`#{Qws}pUurpDvx?t6mFo#RpuWv#F)SH8KJ@O7Rt&njxH9VG z?xaxM*=beXqX)l#;^hnDCqGcT`O@5q@MG}MxR8}6O1>|9XEZ{KvilbI z$@F_>EC^1UPM%DB{(IOw>OtQTNdGFM30^vyJ%z8H`AXc*D{8_U(P;w+3Ba#{O>b^H zcQIk~yB8l_Y|CBJx-0jyrzdg~o|^L5`|E-~ZHoWyPcIz$eO~3+>IF-`@YWQVCvE!b zgDWRPq~5Jd|F-9;;uAr*)p^kSkW676vLJ%{YtMGJ=m0?T1w$6N>lzKFfHQmt-um6& zb_>oe9v2Yf#C}>y(Gu(flS<;I!TT?>Z3kaw zKVUh3@W3nqs&jR%+v3{c3-5ET3S{tV)c!S z*q<7069q_ff;$)GHJ*H)X)}zd33|o5;&w3M;cvF}Z?uxJsu!23y6Xc^lh#iuWZ5y9bZ3{4xWg*7wW!1~F zudXo%TZt>!5^O~+Ldj%9gWjUII+-8`TZ1fG+qjMod-hz-gFtK#wCV(HiJ{qlh{p+F zi4~v}7b-1702`i+MHn^v@$@QE39x0n07H`mFz<&UK{i#=?O&>sVkB)+w3L|?*%~r6 zDp4&(Cu*g-YmQ}W?+j_xxQ5#Gwj#UQwG-*LcIp~hjP>HFwjets(^hlStE}jdxv69_ zfCa}&Z{Wq2Vu;JGfQqH8$^%B&^i46u>+D2~6qmB4H`1F&{LP)M7FRO2*T-GQAXq7F z@r;KS&684=1?&zQ-Ntv!g)N@>28dIM_3Sl|Y~my^*0VBCkV}!9P}3x@*=Z6TY`xdu zY^jhpI8o`llIeXa2$t*{Y|b?Hkw+s&c+Q2Cv$4K(F^&DjV=Av>bv`rNAoZBV z?JUJh1lu!43zVCjd0nF6aB;TVQE+&kAU@7mZ;WN)RRaOLHfI=)@eQOq&0-}>=uUTn zv67wb*0?~ZW2RotB^Y@jlg;S`qgkwDT^)J#m8)*wIST9x)Pw$I0RKsUy$ zi*q$!aA()C^n*yNdqxTHIu=Wc0Lf;IJuHTXkg0;blBIg6DO6Ipksi(zB!OVGTjurI zZ1mf##M?6V@Owu4no4bogN^rIZU->?b!4!5d9Vf1;b1@Fsf9u4?<1ZqT(l(`<56o1 zh|RkCwxG4SinF>P%QeqcEVv!{eUyM}UZ^$H5-?b6dz}UL)Ut6mma!Y&ZERBS<1FpQ zX-^iH=N>pq>!ZihnVb0jqz#D zK@D5ktrt)`M7a7^P#qs=ypKKm9*!lP)I>|UxAdOm1zhbK*43@7Xn=o3iJK5hmIJ*$ zTkO+N!vY&`dbP$=Y`-~+4Zgf~Wu=Dtt-@$PtCX10r_`{>_Y4giJu?kcG^8y+!<`0q zsRsl@GrM9~fz>%&t13a;dWVy` z202v;q%~hdBqErb0ZR#*plneaIJz*`y~jIS)f{f#Z%xZn4dG`sOEv!O0SL%0}ZPGQ@;d0oDMGawoy56|UwwUEP}AOeD| z)PP=6i7+IcUG{?0F=XNoD!dISnUx(-cK%+EJ`4B0PF;DlGD76tB;ZIVBo&DHVqO<4 z;kS&+iz{o>XivLTq|K$_r39zKkpyx!Ot{-+ zR+H1Jk>qTsU~%yy$;r7E;=11ggPj^`KNc*~k0~d5I@{%K?0^tKQuuY=qx%kGEgaA29FcmV!-$rx5;+%I=F^=5wC>ue z0#U)9>)vyf5rs?j&h|@Ey_{>DpPTFbu@nS*sj{Q-8>b^c1(o6AVJ(%+LVjsUb2^*t zb(LNyuSJv%>x!45BZ!QNOSU*!v^U9Wsd(&%{IEnzZu$I9en4)C);`Td&YA_GL}wpr z8J?j{)SvwN0(JA+;Yp)X$KFBhIbATFx~M%)p*C$rmU>j1_SqHeb(x1mjOa@CNH>^< z;z@AMkiCQ$$&xF0cL8Izt=gvKpIT;lU(w>o`MO=v;>h_NDb}a|)nE4oo&d-6Q;N)O zpr0x-q#w5%n(g?1@H)IloE<8Fk_QdAYiOK|d9{Rfc-O|QDmzl9j8MXije%3b?9|P1 zq0U&NP9&}>qZ}7&g0o4^H96!gmqbo&M2fp>vT}pM@9bwV?=%duq_XfE4Qw^eU`T5u zW@Io4QYt&z!;1s4mcgRB4NlC~5^-g)X+GtYFl42&gH0q3;zPw?i^%q)G?ne^No8;L zL^^d=f;kgUi#P?D=ay4LMIKSx@y@q3M}W763DAi5vZcs>h%_-U=olCYA{I9C#HM_N X^C0l)rvY5Xo3-pBxXe^|a|7~sadC2< literal 0 HcmV?d00001 diff --git a/embassy/cyw43-firmware/43439A0_clm.bin b/embassy/cyw43-firmware/43439A0_clm.bin new file mode 100644 index 0000000000000000000000000000000000000000..dc4ee02523209e1cc843272ead9d95fd285f2bbf GIT binary patch literal 984 zcmbVKJ#Q015S_KdmtzAVp^YrjAfa5&E87WHn)BJQ51*Zr?@UZOKuEEa#Fi5wMT&&z zC@Cpvsfg0j@B=9M0SJknn&x)*qvH!!Fv%H12%4k>XHMhE^%SE?Zt5j<4Cb$$s zNvc+{v_%=XGKX^xZ=zNeI8c{{y?Stjdr#Bf;w2DJ=~wXv7Sx*reG7cgsHNY;M~zvq z9}@Hkb_n(?Xh0`b$ZB+~r%B~zY(3j7(W+Zdi=H;K+H5Rf(SSt*>I6K+lgR3HSgS+9 zFtAj};E>AUQ%iM8b%*pkv!K@^U>iLixyK_%EE@5mA_hlPju{hEFlJ*hkDM?)Ve`qZ ztv-m&JL0{DL&>HYT<@M|DOJGZw87fR{2S~B_@lXe;r5-oOZS#n?&tBDvJ2O)-?(`z Nw`jEfwa=eK{sQ=&t>FLw literal 0 HcmV?d00001 diff --git a/embassy/cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt b/embassy/cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt new file mode 100644 index 0000000..cbb51f9 --- /dev/null +++ b/embassy/cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt @@ -0,0 +1,49 @@ +Permissive Binary License + +Version 1.0, July 2019 + +Redistribution. Redistribution and use in binary form, without +modification, are permitted provided that the following conditions are +met: + +1) Redistributions must reproduce the above copyright notice and the + following disclaimer in the documentation and/or other materials + provided with the distribution. + +2) Unless to the extent explicitly permitted by law, no reverse + engineering, decompilation, or disassembly of this software is + permitted. + +3) Redistribution as part of a software development kit must include the + accompanying file named �DEPENDENCIES� and any dependencies listed in + that file. + +4) Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +Limited patent license. The copyright holders (and contributors) grant a +worldwide, non-exclusive, no-charge, royalty-free patent license to +make, have made, use, offer to sell, sell, import, and otherwise +transfer this software, where such license applies only to those patent +claims licensable by the copyright holders (and contributors) that are +necessarily infringed by this software. This patent license shall not +apply to any combinations that include this software. No hardware is +licensed hereunder. + +If you institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the software +itself infringes your patent(s), then your rights granted under this +license shall terminate as of the date such litigation is filed. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/embassy/cyw43-firmware/README.md b/embassy/cyw43-firmware/README.md new file mode 100644 index 0000000..10a6b5d --- /dev/null +++ b/embassy/cyw43-firmware/README.md @@ -0,0 +1,14 @@ +# WiFi + Bluetooth firmware blobs + +Firmware obtained from https://github.com/georgerobotics/cyw43-driver/tree/main/firmware + +Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt) + +## Changelog + +* 2023-08-21: synced with `a1dc885` - Update 43439 fw + clm to come from `wb43439A0_7_95_49_00_combined.h` + add Bluetooth firmware +* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 to 7.95.62 + +## Notes + +If you update these files, please update the lengths in the `tests/rp/src/bin/cyw43_perf.rs` test (which relies on these files running from RAM). diff --git a/embassy/cyw43-pio/CHANGELOG.md b/embassy/cyw43-pio/CHANGELOG.md new file mode 100644 index 0000000..913f351 --- /dev/null +++ b/embassy/cyw43-pio/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-08-05 + +- Update to cyw43 0.2.0 +- Update to embassy-rp 0.2.0 + +## 0.1.0 - 2024-01-11 + +- First release diff --git a/embassy/cyw43-pio/Cargo.toml b/embassy/cyw43-pio/Cargo.toml new file mode 100644 index 0000000..4e21c25 --- /dev/null +++ b/embassy/cyw43-pio/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "cyw43-pio" +version = "0.2.0" +edition = "2021" +description = "RP2040 PIO SPI implementation for cyw43" +keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/cyw43-pio" + +[features] +# If disabled, SPI runs at 31.25MHz +# If enabled, SPI runs at 62.5MHz, which is 25% higher than 50Mhz which is the maximum according to the CYW43439 datasheet. +overclock = [] + +[dependencies] +cyw43 = { version = "0.2.0", path = "../cyw43" } +embassy-rp = { version = "0.2.0", path = "../embassy-rp" } +pio-proc = "0.2" +pio = "0.2.1" +fixed = "1.23.1" +defmt = { version = "0.3", optional = true } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43-pio/src/" +target = "thumbv6m-none-eabi" +features = ["embassy-rp/rp2040"] diff --git a/embassy/cyw43-pio/README.md b/embassy/cyw43-pio/README.md new file mode 100644 index 0000000..4a2b2aa --- /dev/null +++ b/embassy/cyw43-pio/README.md @@ -0,0 +1,3 @@ +# cyw43-pio + +RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. The PIO driver offloads SPI communication with the WiFi chip and improves throughput. diff --git a/embassy/cyw43-pio/src/lib.rs b/embassy/cyw43-pio/src/lib.rs new file mode 100644 index 0000000..40cf63a --- /dev/null +++ b/embassy/cyw43-pio/src/lib.rs @@ -0,0 +1,237 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +use core::slice; + +use cyw43::SpiBusCyw43; +use embassy_rp::dma::Channel; +use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate}; +use embassy_rp::pio::{instr, Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::{Peripheral, PeripheralRef}; +use fixed::FixedU32; +use pio_proc::pio_asm; + +/// SPI comms driven by PIO. +pub struct PioSpi<'d, PIO: Instance, const SM: usize, DMA> { + cs: Output<'d>, + sm: StateMachine<'d, PIO, SM>, + irq: Irq<'d, PIO, 0>, + dma: PeripheralRef<'d, DMA>, + wrap_target: u8, +} + +impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA> +where + DMA: Channel, + PIO: Instance, +{ + /// Create a new instance of PioSpi. + pub fn new( + common: &mut Common<'d, PIO>, + mut sm: StateMachine<'d, PIO, SM>, + irq: Irq<'d, PIO, 0>, + cs: Output<'d>, + dio: DIO, + clk: CLK, + dma: impl Peripheral

+ 'd, + ) -> Self + where + DIO: PioPin, + CLK: PioPin, + { + #[cfg(feature = "overclock")] + let program = pio_asm!( + ".side_set 1" + + ".wrap_target" + // write out x-1 bits + "lp:" + "out pins, 1 side 0" + "jmp x-- lp side 1" + // switch directions + "set pindirs, 0 side 0" + "nop side 1" // necessary for clkdiv=1. + "nop side 0" + // read in y-1 bits + "lp2:" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" + + // wait for event and irq host + "wait 1 pin 0 side 0" + "irq 0 side 0" + + ".wrap" + ); + #[cfg(not(feature = "overclock"))] + let program = pio_asm!( + ".side_set 1" + + ".wrap_target" + // write out x-1 bits + "lp:" + "out pins, 1 side 0" + "jmp x-- lp side 1" + // switch directions + "set pindirs, 0 side 0" + "nop side 0" + // read in y-1 bits + "lp2:" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" + + // wait for event and irq host + "wait 1 pin 0 side 0" + "irq 0 side 0" + + ".wrap" + ); + + let mut pin_io: embassy_rp::pio::Pin = common.make_pio_pin(dio); + pin_io.set_pull(Pull::None); + pin_io.set_schmitt(true); + pin_io.set_input_sync_bypass(true); + pin_io.set_drive_strength(Drive::_12mA); + pin_io.set_slew_rate(SlewRate::Fast); + + let mut pin_clk = common.make_pio_pin(clk); + pin_clk.set_drive_strength(Drive::_12mA); + pin_clk.set_slew_rate(SlewRate::Fast); + + let mut cfg = Config::default(); + let loaded_program = common.load_program(&program.program); + cfg.use_program(&loaded_program, &[&pin_clk]); + cfg.set_out_pins(&[&pin_io]); + cfg.set_in_pins(&[&pin_io]); + cfg.set_set_pins(&[&pin_io]); + cfg.shift_out.direction = ShiftDirection::Left; + cfg.shift_out.auto_fill = true; + //cfg.shift_out.threshold = 32; + cfg.shift_in.direction = ShiftDirection::Left; + cfg.shift_in.auto_fill = true; + //cfg.shift_in.threshold = 32; + + #[cfg(feature = "overclock")] + { + // 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to + // data sheet, but seems to work fine. + cfg.clock_divider = FixedU32::from_bits(0x0100); + } + + #[cfg(not(feature = "overclock"))] + { + // same speed as pico-sdk, 62.5Mhz + // This is actually the fastest we can go without overclocking. + // According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq. + // However, the PIO uses a fractional divider, which works by introducing jitter when + // the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz + // so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles + // violate the maximum from the data sheet. + cfg.clock_divider = FixedU32::from_bits(0x0200); + } + + sm.set_config(&cfg); + + sm.set_pin_dirs(Direction::Out, &[&pin_clk, &pin_io]); + sm.set_pins(Level::Low, &[&pin_clk, &pin_io]); + + Self { + cs, + sm, + irq, + dma: dma.into_ref(), + wrap_target: loaded_program.wrap.target, + } + } + + /// Write data to peripheral and return status. + pub async fn write(&mut self, write: &[u32]) -> u32 { + self.sm.set_enable(false); + let write_bits = write.len() * 32 - 1; + let read_bits = 31; + + #[cfg(feature = "defmt")] + defmt::trace!("write={} read={}", write_bits, read_bits); + + unsafe { + instr::set_x(&mut self.sm, write_bits as u32); + instr::set_y(&mut self.sm, read_bits as u32); + instr::set_pindir(&mut self.sm, 0b1); + instr::exec_jmp(&mut self.sm, self.wrap_target); + } + + self.sm.set_enable(true); + + self.sm.tx().dma_push(self.dma.reborrow(), write).await; + + let mut status = 0; + self.sm + .rx() + .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status)) + .await; + status + } + + /// Send command and read response into buffer. + pub async fn cmd_read(&mut self, cmd: u32, read: &mut [u32]) -> u32 { + self.sm.set_enable(false); + let write_bits = 31; + let read_bits = read.len() * 32 + 32 - 1; + + #[cfg(feature = "defmt")] + defmt::trace!("cmd_read write={} read={}", write_bits, read_bits); + + #[cfg(feature = "defmt")] + defmt::trace!("cmd_read cmd = {:02x} len = {}", cmd, read.len()); + + unsafe { + instr::set_y(&mut self.sm, read_bits as u32); + instr::set_x(&mut self.sm, write_bits as u32); + instr::set_pindir(&mut self.sm, 0b1); + instr::exec_jmp(&mut self.sm, self.wrap_target); + } + + // self.cs.set_low(); + self.sm.set_enable(true); + + self.sm.tx().dma_push(self.dma.reborrow(), slice::from_ref(&cmd)).await; + self.sm.rx().dma_pull(self.dma.reborrow(), read).await; + + let mut status = 0; + self.sm + .rx() + .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status)) + .await; + + #[cfg(feature = "defmt")] + defmt::trace!("cmd_read cmd = {:02x} len = {} read = {:08x}", cmd, read.len(), read); + + status + } +} + +impl<'d, PIO, const SM: usize, DMA> SpiBusCyw43 for PioSpi<'d, PIO, SM, DMA> +where + PIO: Instance, + DMA: Channel, +{ + async fn cmd_write(&mut self, write: &[u32]) -> u32 { + self.cs.set_low(); + let status = self.write(write).await; + self.cs.set_high(); + status + } + + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32 { + self.cs.set_low(); + let status = self.cmd_read(write, read).await; + self.cs.set_high(); + status + } + + async fn wait_for_event(&mut self) { + self.irq.wait().await; + } +} diff --git a/embassy/cyw43/CHANGELOG.md b/embassy/cyw43/CHANGELOG.md new file mode 100644 index 0000000..1859f73 --- /dev/null +++ b/embassy/cyw43/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-08-05 + +- Update to new versions of embassy-{time,sync} +- Add more fields to the BssInfo packet struct #2461 +- Extend the Scan API #2282 +- Reuse buf to reduce stack usage #2580 +- Add MAC address getter to cyw43 controller #2818 +- Add function to join WPA2 network with precomputed PSK. #2885 +- Add function to close soft AP. #3042 +- Fixing missing re-export #3211 + +## 0.1.0 - 2024-01-11 + +- First release diff --git a/embassy/cyw43/Cargo.toml b/embassy/cyw43/Cargo.toml new file mode 100644 index 0000000..7c02608 --- /dev/null +++ b/embassy/cyw43/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "cyw43" +version = "0.2.0" +edition = "2021" +description = "Rust driver for the CYW43439 WiFi chip, used in the Raspberry Pi Pico W." +keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/cyw43" + +[features] +defmt = ["dep:defmt", "heapless/defmt-03", "embassy-time/defmt", "bt-hci?/defmt", "embedded-io-async?/defmt-03"] +log = ["dep:log"] +bluetooth = ["dep:bt-hci", "dep:embedded-io-async"] + +# Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`. +firmware-logs = [] + +[dependencies] +embassy-time = { version = "0.3.2", path = "../embassy-time"} +embassy-sync = { version = "0.6.1", path = "../embassy-sync"} +embassy-futures = { version = "0.1.0", path = "../embassy-futures"} +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"} + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.17", optional = true } + +cortex-m = "0.7.6" +cortex-m-rt = "0.7.0" +futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } + +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +num_enum = { version = "0.5.7", default-features = false } +heapless = "0.8.0" + +# Bluetooth deps +embedded-io-async = { version = "0.6.0", optional = true } +bt-hci = { version = "0.1.0", optional = true } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/" +target = "thumbv6m-none-eabi" +features = ["defmt", "firmware-logs"] + +[package.metadata.docs.rs] +features = ["defmt", "firmware-logs"] diff --git a/embassy/cyw43/README.md b/embassy/cyw43/README.md new file mode 100644 index 0000000..5b4a5d7 --- /dev/null +++ b/embassy/cyw43/README.md @@ -0,0 +1,46 @@ +# cyw43 + +Rust driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver). + +## Current status + +Working: + +- Station mode (joining an AP). +- AP mode (creating an AP) +- Scanning +- Sending and receiving Ethernet frames. +- Using the default MAC address. +- [`embassy-net`](https://embassy.dev) integration. +- RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. +- Using IRQ for device events +- GPIO support (for LED on the Pico W) + +TODO: + +- Setting a custom MAC address. +- Bus sleep (for power consumption optimization) + +## Running the examples + +- Install `probe-rs` following the instructions at . +- `cd examples/rp` +### Example 1: Scan the wifi stations +- `cargo run --release --bin wifi_scan` +### Example 2: Create an access point (IP and credentials in the code) +- `cargo run --release --bin wifi_ap_tcp_server` +### Example 3: Connect to an existing network and create a server +- `cargo run --release --bin wifi_tcp_server` + +After a few seconds, you should see that DHCP picks up an IP address like this +``` +11.944489 DEBUG Acquired IP configuration: +11.944517 DEBUG IP address: 192.168.0.250/24 +11.944620 DEBUG Default gateway: 192.168.0.33 +11.944722 DEBUG DNS server 0: 192.168.0.33 +``` +This example implements a TCP echo server on port 1234. You can try connecting to it with: +``` +nc 192.168.0.250 1234 +``` +Send it some data, you should see it echoed back and printed in the firmware's logs. diff --git a/embassy/cyw43/src/bluetooth.rs b/embassy/cyw43/src/bluetooth.rs new file mode 100644 index 0000000..f617a8c --- /dev/null +++ b/embassy/cyw43/src/bluetooth.rs @@ -0,0 +1,508 @@ +use core::cell::RefCell; +use core::future::Future; +use core::mem::MaybeUninit; + +use bt_hci::transport::WithIndicator; +use bt_hci::{ControllerToHostPacket, FromHciBytes, HostToControllerPacket, PacketKind, WriteHci}; +use embassy_futures::yield_now; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::zerocopy_channel; +use embassy_time::{Duration, Timer}; +use embedded_hal_1::digital::OutputPin; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +use crate::consts::*; +use crate::util::round_up; +use crate::{util, CHIP}; + +pub(crate) struct BtState { + rx: [BtPacketBuf; 4], + tx: [BtPacketBuf; 4], + inner: MaybeUninit>, +} + +impl BtState { + pub const fn new() -> Self { + Self { + rx: [const { BtPacketBuf::new() }; 4], + tx: [const { BtPacketBuf::new() }; 4], + inner: MaybeUninit::uninit(), + } + } +} + +struct BtStateInnre<'d> { + rx: zerocopy_channel::Channel<'d, NoopRawMutex, BtPacketBuf>, + tx: zerocopy_channel::Channel<'d, NoopRawMutex, BtPacketBuf>, +} + +/// Bluetooth driver. +pub struct BtDriver<'d> { + rx: RefCell>, + tx: RefCell>, +} + +pub(crate) struct BtRunner<'d> { + pub(crate) tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, BtPacketBuf>, + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, BtPacketBuf>, + + // Bluetooth circular buffers + addr: u32, + h2b_write_pointer: u32, + b2h_read_pointer: u32, +} + +const BT_HCI_MTU: usize = 1024; + +/// Represents a packet of size MTU. +pub(crate) struct BtPacketBuf { + pub(crate) len: usize, + pub(crate) buf: [u8; BT_HCI_MTU], +} + +impl BtPacketBuf { + /// Create a new packet buffer. + pub const fn new() -> Self { + Self { + len: 0, + buf: [0; BT_HCI_MTU], + } + } +} + +pub(crate) fn new<'d>(state: &'d mut BtState) -> (BtRunner<'d>, BtDriver<'d>) { + // safety: this is a self-referential struct, however: + // - it can't move while the `'d` borrow is active. + // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. + let state_uninit: *mut MaybeUninit> = + (&mut state.inner as *mut MaybeUninit>).cast(); + let state = unsafe { &mut *state_uninit }.write(BtStateInnre { + rx: zerocopy_channel::Channel::new(&mut state.rx[..]), + tx: zerocopy_channel::Channel::new(&mut state.tx[..]), + }); + + let (rx_sender, rx_receiver) = state.rx.split(); + let (tx_sender, tx_receiver) = state.tx.split(); + + ( + BtRunner { + tx_chan: tx_receiver, + rx_chan: rx_sender, + + addr: 0, + h2b_write_pointer: 0, + b2h_read_pointer: 0, + }, + BtDriver { + rx: RefCell::new(rx_receiver), + tx: RefCell::new(tx_sender), + }, + ) +} + +pub(crate) struct CybtFwCb<'a> { + pub p_next_line_start: &'a [u8], +} + +pub(crate) struct HexFileData<'a> { + pub addr_mode: i32, + pub hi_addr: u16, + pub dest_addr: u32, + pub p_ds: &'a mut [u8], +} + +pub(crate) fn read_firmware_patch_line(p_btfw_cb: &mut CybtFwCb, hfd: &mut HexFileData) -> u32 { + let mut abs_base_addr32 = 0; + + loop { + let num_bytes = p_btfw_cb.p_next_line_start[0]; + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[1..]; + + let addr = (p_btfw_cb.p_next_line_start[0] as u16) << 8 | p_btfw_cb.p_next_line_start[1] as u16; + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[2..]; + + let line_type = p_btfw_cb.p_next_line_start[0]; + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[1..]; + + if num_bytes == 0 { + break; + } + + hfd.p_ds[..num_bytes as usize].copy_from_slice(&p_btfw_cb.p_next_line_start[..num_bytes as usize]); + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[num_bytes as usize..]; + + match line_type { + BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS => { + hfd.hi_addr = (hfd.p_ds[0] as u16) << 8 | hfd.p_ds[1] as u16; + hfd.addr_mode = BTFW_ADDR_MODE_EXTENDED; + } + BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS => { + hfd.hi_addr = (hfd.p_ds[0] as u16) << 8 | hfd.p_ds[1] as u16; + hfd.addr_mode = BTFW_ADDR_MODE_SEGMENT; + } + BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS => { + abs_base_addr32 = (hfd.p_ds[0] as u32) << 24 + | (hfd.p_ds[1] as u32) << 16 + | (hfd.p_ds[2] as u32) << 8 + | hfd.p_ds[3] as u32; + hfd.addr_mode = BTFW_ADDR_MODE_LINEAR32; + } + BTFW_HEX_LINE_TYPE_DATA => { + hfd.dest_addr = addr as u32; + match hfd.addr_mode { + BTFW_ADDR_MODE_EXTENDED => hfd.dest_addr += (hfd.hi_addr as u32) << 16, + BTFW_ADDR_MODE_SEGMENT => hfd.dest_addr += (hfd.hi_addr as u32) << 4, + BTFW_ADDR_MODE_LINEAR32 => hfd.dest_addr += abs_base_addr32, + _ => {} + } + return num_bytes as u32; + } + _ => {} + } + } + 0 +} + +impl<'a> BtRunner<'a> { + pub(crate) async fn init_bluetooth(&mut self, bus: &mut Bus, firmware: &[u8]) { + trace!("init_bluetooth"); + bus.bp_write32(CHIP.bluetooth_base_address + BT2WLAN_PWRUP_ADDR, BT2WLAN_PWRUP_WAKE) + .await; + Timer::after(Duration::from_millis(2)).await; + self.upload_bluetooth_firmware(bus, firmware).await; + self.wait_bt_ready(bus).await; + self.init_bt_buffers(bus).await; + self.wait_bt_awake(bus).await; + self.bt_set_host_ready(bus).await; + self.bt_toggle_intr(bus).await; + } + + pub(crate) async fn upload_bluetooth_firmware( + &mut self, + bus: &mut Bus, + firmware: &[u8], + ) { + // read version + let version_length = firmware[0]; + let _version = &firmware[1..=version_length as usize]; + // skip version + 1 extra byte as per cybt_shared_bus_driver.c + let firmware = &firmware[version_length as usize + 2..]; + // buffers + let mut data_buffer: [u8; 0x100] = [0; 0x100]; + let mut aligned_data_buffer: [u8; 0x100] = [0; 0x100]; + // structs + let mut btfw_cb = CybtFwCb { + p_next_line_start: firmware, + }; + let mut hfd = HexFileData { + addr_mode: BTFW_ADDR_MODE_EXTENDED, + hi_addr: 0, + dest_addr: 0, + p_ds: &mut data_buffer, + }; + loop { + let num_fw_bytes = read_firmware_patch_line(&mut btfw_cb, &mut hfd); + if num_fw_bytes == 0 { + break; + } + let fw_bytes = &hfd.p_ds[0..num_fw_bytes as usize]; + let mut dest_start_addr = hfd.dest_addr + CHIP.bluetooth_base_address; + let mut aligned_data_buffer_index: usize = 0; + // pad start + if !util::is_aligned(dest_start_addr, 4) { + let num_pad_bytes = dest_start_addr % 4; + let padded_dest_start_addr = util::round_down(dest_start_addr, 4); + let memory_value = bus.bp_read32(padded_dest_start_addr).await; + let memory_value_bytes = memory_value.to_le_bytes(); + // Copy the previous memory value's bytes to the start + for i in 0..num_pad_bytes as usize { + aligned_data_buffer[aligned_data_buffer_index] = memory_value_bytes[i]; + aligned_data_buffer_index += 1; + } + // Copy the firmware bytes after the padding bytes + for i in 0..num_fw_bytes as usize { + aligned_data_buffer[aligned_data_buffer_index] = fw_bytes[i]; + aligned_data_buffer_index += 1; + } + dest_start_addr = padded_dest_start_addr; + } else { + // Directly copy fw_bytes into aligned_data_buffer if no start padding is required + for i in 0..num_fw_bytes as usize { + aligned_data_buffer[aligned_data_buffer_index] = fw_bytes[i]; + aligned_data_buffer_index += 1; + } + } + // pad end + let mut dest_end_addr = dest_start_addr + aligned_data_buffer_index as u32; + if !util::is_aligned(dest_end_addr, 4) { + let offset = dest_end_addr % 4; + let num_pad_bytes_end = 4 - offset; + let padded_dest_end_addr = util::round_down(dest_end_addr, 4); + let memory_value = bus.bp_read32(padded_dest_end_addr).await; + let memory_value_bytes = memory_value.to_le_bytes(); + // Append the necessary memory bytes to pad the end of aligned_data_buffer + for i in offset..4 { + aligned_data_buffer[aligned_data_buffer_index] = memory_value_bytes[i as usize]; + aligned_data_buffer_index += 1; + } + dest_end_addr += num_pad_bytes_end; + } else { + // pad end alignment not needed + } + let buffer_to_write = &aligned_data_buffer[0..aligned_data_buffer_index as usize]; + assert!(dest_start_addr % 4 == 0); + assert!(dest_end_addr % 4 == 0); + assert!(aligned_data_buffer_index % 4 == 0); + bus.bp_write(dest_start_addr, buffer_to_write).await; + } + } + + pub(crate) async fn wait_bt_ready(&mut self, bus: &mut Bus) { + trace!("wait_bt_ready"); + let mut success = false; + for _ in 0..300 { + let val = bus.bp_read32(BT_CTRL_REG_ADDR).await; + trace!("BT_CTRL_REG_ADDR = {:08x}", val); + if val & BTSDIO_REG_FW_RDY_BITMASK != 0 { + success = true; + break; + } + Timer::after(Duration::from_millis(1)).await; + } + assert!(success == true); + } + + pub(crate) async fn wait_bt_awake(&mut self, bus: &mut Bus) { + trace!("wait_bt_awake"); + let mut success = false; + for _ in 0..300 { + let val = bus.bp_read32(BT_CTRL_REG_ADDR).await; + trace!("BT_CTRL_REG_ADDR = {:08x}", val); + if val & BTSDIO_REG_BT_AWAKE_BITMASK != 0 { + success = true; + break; + } + Timer::after(Duration::from_millis(1)).await; + } + assert!(success == true); + } + + pub(crate) async fn bt_set_host_ready(&mut self, bus: &mut Bus) { + trace!("bt_set_host_ready"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + // TODO: do we need to swap endianness on this read? + let new_val = old_val | BTSDIO_REG_SW_RDY_BITMASK; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + // TODO: use this + #[allow(dead_code)] + pub(crate) async fn bt_set_awake(&mut self, bus: &mut Bus, awake: bool) { + trace!("bt_set_awake"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + // TODO: do we need to swap endianness on this read? + let new_val = if awake { + old_val | BTSDIO_REG_WAKE_BT_BITMASK + } else { + old_val & !BTSDIO_REG_WAKE_BT_BITMASK + }; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + pub(crate) async fn bt_toggle_intr(&mut self, bus: &mut Bus) { + trace!("bt_toggle_intr"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + // TODO: do we need to swap endianness on this read? + let new_val = old_val ^ BTSDIO_REG_DATA_VALID_BITMASK; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + // TODO: use this + #[allow(dead_code)] + pub(crate) async fn bt_set_intr(&mut self, bus: &mut Bus) { + trace!("bt_set_intr"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + let new_val = old_val | BTSDIO_REG_DATA_VALID_BITMASK; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + pub(crate) async fn init_bt_buffers(&mut self, bus: &mut Bus) { + trace!("init_bt_buffers"); + self.addr = bus.bp_read32(WLAN_RAM_BASE_REG_ADDR).await; + assert!(self.addr != 0); + trace!("wlan_ram_base_addr = {:08x}", self.addr); + bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_IN, 0).await; + bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_OUT, 0).await; + bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_IN, 0).await; + bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_OUT, 0).await; + } + + async fn bt_bus_request(&mut self, bus: &mut Bus) { + // TODO: CYW43_THREAD_ENTER mutex? + self.bt_set_awake(bus, true).await; + self.wait_bt_awake(bus).await; + } + + pub(crate) async fn hci_write(&mut self, bus: &mut Bus) { + self.bt_bus_request(bus).await; + + // NOTE(unwrap): we only call this when we do have a packet in the queue. + let buf = self.tx_chan.try_receive().unwrap(); + debug!("HCI tx: {:02x}", crate::fmt::Bytes(&buf.buf[..buf.len])); + + let len = buf.len as u32 - 1; // len doesn't include hci type byte + let rounded_len = round_up(len, 4); + let total_len = 4 + rounded_len; + + let read_pointer = bus.bp_read32(self.addr + BTSDIO_OFFSET_HOST2BT_OUT).await; + let available = read_pointer.wrapping_sub(self.h2b_write_pointer + 4) % BTSDIO_FWBUF_SIZE; + if available < total_len { + warn!( + "bluetooth tx queue full, retrying. len {} available {}", + total_len, available + ); + yield_now().await; + return; + } + + // Build header + let mut header = [0u8; 4]; + header[0] = len as u8; + header[1] = (len >> 8) as u8; + header[2] = (len >> 16) as u8; + header[3] = buf.buf[0]; // HCI type byte + + // Write header + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer; + bus.bp_write(addr, &header).await; + self.h2b_write_pointer = (self.h2b_write_pointer + 4) % BTSDIO_FWBUF_SIZE; + + // Write payload. + let payload = &buf.buf[1..][..rounded_len as usize]; + if self.h2b_write_pointer as usize + payload.len() > BTSDIO_FWBUF_SIZE as usize { + // wraparound + let n = BTSDIO_FWBUF_SIZE - self.h2b_write_pointer; + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer; + bus.bp_write(addr, &payload[..n as usize]).await; + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF; + bus.bp_write(addr, &payload[n as usize..]).await; + } else { + // no wraparound + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer; + bus.bp_write(addr, payload).await; + } + self.h2b_write_pointer = (self.h2b_write_pointer + payload.len() as u32) % BTSDIO_FWBUF_SIZE; + + // Update pointer. + bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_IN, self.h2b_write_pointer) + .await; + + self.bt_toggle_intr(bus).await; + + self.tx_chan.receive_done(); + } + + async fn bt_has_work(&mut self, bus: &mut Bus) -> bool { + let int_status = bus.bp_read32(CHIP.sdiod_core_base_address + SDIO_INT_STATUS).await; + if int_status & I_HMB_FC_CHANGE != 0 { + bus.bp_write32( + CHIP.sdiod_core_base_address + SDIO_INT_STATUS, + int_status & I_HMB_FC_CHANGE, + ) + .await; + return true; + } + return false; + } + + pub(crate) async fn handle_irq(&mut self, bus: &mut Bus) { + if self.bt_has_work(bus).await { + loop { + // Check if we have data. + let write_pointer = bus.bp_read32(self.addr + BTSDIO_OFFSET_BT2HOST_IN).await; + let available = write_pointer.wrapping_sub(self.b2h_read_pointer) % BTSDIO_FWBUF_SIZE; + if available == 0 { + break; + } + + // read header + let mut header = [0u8; 4]; + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer; + bus.bp_read(addr, &mut header).await; + + // calc length + let len = header[0] as u32 | ((header[1]) as u32) << 8 | ((header[2]) as u32) << 16; + let rounded_len = round_up(len, 4); + if available < 4 + rounded_len { + warn!("ringbuf data not enough for a full packet?"); + break; + } + self.b2h_read_pointer = (self.b2h_read_pointer + 4) % BTSDIO_FWBUF_SIZE; + + // Obtain a buf from the channel. + let buf = self.rx_chan.send().await; + + buf.buf[0] = header[3]; // hci packet type + let payload = &mut buf.buf[1..][..rounded_len as usize]; + if self.b2h_read_pointer as usize + payload.len() > BTSDIO_FWBUF_SIZE as usize { + // wraparound + let n = BTSDIO_FWBUF_SIZE - self.b2h_read_pointer; + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer; + bus.bp_read(addr, &mut payload[..n as usize]).await; + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF; + bus.bp_read(addr, &mut payload[n as usize..]).await; + } else { + // no wraparound + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer; + bus.bp_read(addr, payload).await; + } + self.b2h_read_pointer = (self.b2h_read_pointer + payload.len() as u32) % BTSDIO_FWBUF_SIZE; + bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_OUT, self.b2h_read_pointer) + .await; + + buf.len = 1 + len as usize; + debug!("HCI rx: {:02x}", crate::fmt::Bytes(&buf.buf[..buf.len])); + + self.rx_chan.send_done(); + + self.bt_toggle_intr(bus).await; + } + } + } +} + +impl<'d> embedded_io_async::ErrorType for BtDriver<'d> { + type Error = core::convert::Infallible; +} + +impl<'d> bt_hci::transport::Transport for BtDriver<'d> { + fn read<'a>(&self, rx: &'a mut [u8]) -> impl Future, Self::Error>> { + async { + let ch = &mut *self.rx.borrow_mut(); + let buf = ch.receive().await; + let n = buf.len; + assert!(n < rx.len()); + rx[..n].copy_from_slice(&buf.buf[..n]); + ch.receive_done(); + + let kind = PacketKind::from_hci_bytes_complete(&rx[..1]).unwrap(); + let (res, _) = ControllerToHostPacket::from_hci_bytes_with_kind(kind, &rx[1..n]).unwrap(); + Ok(res) + } + } + + /// Write a complete HCI packet from the tx buffer + fn write(&self, val: &T) -> impl Future> { + async { + let ch = &mut *self.tx.borrow_mut(); + let buf = ch.send().await; + let buf_len = buf.buf.len(); + let mut slice = &mut buf.buf[..]; + WithIndicator::new(val).write_hci(&mut slice).unwrap(); + buf.len = buf_len - slice.len(); + ch.send_done(); + Ok(()) + } + } +} diff --git a/embassy/cyw43/src/bus.rs b/embassy/cyw43/src/bus.rs new file mode 100644 index 0000000..8a53484 --- /dev/null +++ b/embassy/cyw43/src/bus.rs @@ -0,0 +1,388 @@ +use embassy_futures::yield_now; +use embassy_time::Timer; +use embedded_hal_1::digital::OutputPin; +use futures::FutureExt; + +use crate::consts::*; +use crate::util::slice8_mut; + +/// Custom Spi Trait that _only_ supports the bus operation of the cyw43 +/// Implementors are expected to hold the CS pin low during an operation. +pub trait SpiBusCyw43 { + /// Issues a write command on the bus + /// First 32 bits of `word` are expected to be a cmd word + async fn cmd_write(&mut self, write: &[u32]) -> u32; + + /// Issues a read command on the bus + /// `write` is expected to be a 32 bit cmd word + /// `read` will contain the response of the device + /// Backplane reads have a response delay that produces one extra unspecified word at the beginning of `read`. + /// Callers that want to read `n` word from the backplane, have to provide a slice that is `n+1` words long. + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32; + + /// Wait for events from the Device. A typical implementation would wait for the IRQ pin to be high. + /// The default implementation always reports ready, resulting in active polling of the device. + async fn wait_for_event(&mut self) { + yield_now().await; + } +} + +pub(crate) struct Bus { + backplane_window: u32, + pwr: PWR, + spi: SPI, + status: u32, +} + +impl Bus +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + pub(crate) fn new(pwr: PWR, spi: SPI) -> Self { + Self { + backplane_window: 0xAAAA_AAAA, + pwr, + spi, + status: 0, + } + } + + pub async fn init(&mut self, bluetooth_enabled: bool) { + // Reset + trace!("WL_REG off/on"); + self.pwr.set_low().unwrap(); + Timer::after_millis(20).await; + self.pwr.set_high().unwrap(); + Timer::after_millis(250).await; + + trace!("read REG_BUS_TEST_RO"); + while self + .read32_swapped(FUNC_BUS, REG_BUS_TEST_RO) + .inspect(|v| trace!("{:#x}", v)) + .await + != FEEDBEAD + {} + + trace!("write REG_BUS_TEST_RW"); + self.write32_swapped(FUNC_BUS, REG_BUS_TEST_RW, TEST_PATTERN).await; + let val = self.read32_swapped(FUNC_BUS, REG_BUS_TEST_RW).await; + trace!("{:#x}", val); + assert_eq!(val, TEST_PATTERN); + + trace!("read REG_BUS_CTRL"); + let val = self.read32_swapped(FUNC_BUS, REG_BUS_CTRL).await; + trace!("{:#010b}", (val & 0xff)); + + // 32-bit word length, little endian (which is the default endianess). + // TODO: C library is uint32_t val = WORD_LENGTH_32 | HIGH_SPEED_MODE| ENDIAN_BIG | INTERRUPT_POLARITY_HIGH | WAKE_UP | 0x4 << (8 * SPI_RESPONSE_DELAY) | INTR_WITH_STATUS << (8 * SPI_STATUS_ENABLE); + trace!("write REG_BUS_CTRL"); + self.write32_swapped( + FUNC_BUS, + REG_BUS_CTRL, + WORD_LENGTH_32 + | HIGH_SPEED + | INTERRUPT_POLARITY_HIGH + | WAKE_UP + | 0x4 << (8 * REG_BUS_RESPONSE_DELAY) + | STATUS_ENABLE << (8 * REG_BUS_STATUS_ENABLE) + | INTR_WITH_STATUS << (8 * REG_BUS_STATUS_ENABLE), + ) + .await; + + trace!("read REG_BUS_CTRL"); + let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await; + trace!("{:#b}", val); + + // TODO: C doesn't do this? i doubt it messes anything up + trace!("read REG_BUS_TEST_RO"); + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; + trace!("{:#x}", val); + assert_eq!(val, FEEDBEAD); + + // TODO: C doesn't do this? i doubt it messes anything up + trace!("read REG_BUS_TEST_RW"); + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; + trace!("{:#x}", val); + assert_eq!(val, TEST_PATTERN); + + trace!("write SPI_RESP_DELAY_F1 CYW43_BACKPLANE_READ_PAD_LEN_BYTES"); + self.write8(FUNC_BUS, SPI_RESP_DELAY_F1, WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE) + .await; + + // TODO: Make sure error interrupt bits are clear? + // cyw43_write_reg_u8(self, BUS_FUNCTION, SPI_INTERRUPT_REGISTER, DATA_UNAVAILABLE | COMMAND_ERROR | DATA_ERROR | F1_OVERFLOW) != 0) + trace!("Make sure error interrupt bits are clear"); + self.write8( + FUNC_BUS, + REG_BUS_INTERRUPT, + (IRQ_DATA_UNAVAILABLE | IRQ_COMMAND_ERROR | IRQ_DATA_ERROR | IRQ_F1_OVERFLOW) as u8, + ) + .await; + + // Enable a selection of interrupts + // TODO: why not all of these F2_F3_FIFO_RD_UNDERFLOW | F2_F3_FIFO_WR_OVERFLOW | COMMAND_ERROR | DATA_ERROR | F2_PACKET_AVAILABLE | F1_OVERFLOW | F1_INTR + trace!("enable a selection of interrupts"); + let mut val = IRQ_F2_F3_FIFO_RD_UNDERFLOW + | IRQ_F2_F3_FIFO_WR_OVERFLOW + | IRQ_COMMAND_ERROR + | IRQ_DATA_ERROR + | IRQ_F2_PACKET_AVAILABLE + | IRQ_F1_OVERFLOW; + if bluetooth_enabled { + val = val | IRQ_F1_INTR; + } + self.write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, val).await; + } + + pub async fn wlan_read(&mut self, buf: &mut [u32], len_in_u8: u32) { + let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len_in_u8); + let len_in_u32 = (len_in_u8 as usize + 3) / 4; + + self.status = self.spi.cmd_read(cmd, &mut buf[..len_in_u32]).await; + } + + pub async fn wlan_write(&mut self, buf: &[u32]) { + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, buf.len() as u32 * 4); + //TODO try to remove copy? + let mut cmd_buf = [0_u32; 513]; + cmd_buf[0] = cmd; + cmd_buf[1..][..buf.len()].copy_from_slice(buf); + + self.status = self.spi.cmd_write(&cmd_buf[..buf.len() + 1]).await; + } + + #[allow(unused)] + pub async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { + trace!("bp_read addr = {:08x}", addr); + + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + assert!(addr % 4 == 0); + + // Backplane read buffer has one extra word for the response delay. + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; + + while !data.is_empty() { + // Ensure transfer doesn't cross a window boundary. + let window_offs = addr & BACKPLANE_ADDRESS_MASK; + let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; + + let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); + + self.backplane_set_window(addr).await; + + let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); + + // round `buf` to word boundary, add one extra word for the response delay + self.status = self.spi.cmd_read(cmd, &mut buf[..(len + 3) / 4 + 1]).await; + + // when writing out the data, we skip the response-delay byte + data[..len].copy_from_slice(&slice8_mut(&mut buf[1..])[..len]); + + // Advance ptr. + addr += len as u32; + data = &mut data[len..]; + } + } + + pub async fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) { + trace!("bp_write addr = {:08x}", addr); + + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + assert!(addr % 4 == 0); + + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; + + while !data.is_empty() { + // Ensure transfer doesn't cross a window boundary. + let window_offs = addr & BACKPLANE_ADDRESS_MASK; + let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; + + let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); + slice8_mut(&mut buf[1..])[..len].copy_from_slice(&data[..len]); + + self.backplane_set_window(addr).await; + + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); + buf[0] = cmd; + + self.status = self.spi.cmd_write(&buf[..(len + 3) / 4 + 1]).await; + + // Advance ptr. + addr += len as u32; + data = &data[len..]; + } + } + + pub async fn bp_read8(&mut self, addr: u32) -> u8 { + self.backplane_readn(addr, 1).await as u8 + } + + pub async fn bp_write8(&mut self, addr: u32, val: u8) { + self.backplane_writen(addr, val as u32, 1).await + } + + pub async fn bp_read16(&mut self, addr: u32) -> u16 { + self.backplane_readn(addr, 2).await as u16 + } + + #[allow(unused)] + pub async fn bp_write16(&mut self, addr: u32, val: u16) { + self.backplane_writen(addr, val as u32, 2).await + } + + #[allow(unused)] + pub async fn bp_read32(&mut self, addr: u32) -> u32 { + self.backplane_readn(addr, 4).await + } + + pub async fn bp_write32(&mut self, addr: u32, val: u32) { + self.backplane_writen(addr, val, 4).await + } + + async fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 { + trace!("backplane_readn addr = {:08x} len = {}", addr, len); + + self.backplane_set_window(addr).await; + + let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; + if len == 4 { + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG; + } + + let val = self.readn(FUNC_BACKPLANE, bus_addr, len).await; + + trace!("backplane_readn addr = {:08x} len = {} val = {:08x}", addr, len, val); + + return val; + } + + async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) { + trace!("backplane_writen addr = {:08x} len = {} val = {:08x}", addr, len, val); + + self.backplane_set_window(addr).await; + + let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; + if len == 4 { + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG; + } + self.writen(FUNC_BACKPLANE, bus_addr, val, len).await; + } + + async fn backplane_set_window(&mut self, addr: u32) { + let new_window = addr & !BACKPLANE_ADDRESS_MASK; + + if (new_window >> 24) as u8 != (self.backplane_window >> 24) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH, + (new_window >> 24) as u8, + ) + .await; + } + if (new_window >> 16) as u8 != (self.backplane_window >> 16) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_MID, + (new_window >> 16) as u8, + ) + .await; + } + if (new_window >> 8) as u8 != (self.backplane_window >> 8) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_LOW, + (new_window >> 8) as u8, + ) + .await; + } + self.backplane_window = new_window; + } + + pub async fn read8(&mut self, func: u32, addr: u32) -> u8 { + self.readn(func, addr, 1).await as u8 + } + + pub async fn write8(&mut self, func: u32, addr: u32, val: u8) { + self.writen(func, addr, val as u32, 1).await + } + + pub async fn read16(&mut self, func: u32, addr: u32) -> u16 { + self.readn(func, addr, 2).await as u16 + } + + #[allow(unused)] + pub async fn write16(&mut self, func: u32, addr: u32, val: u16) { + self.writen(func, addr, val as u32, 2).await + } + + pub async fn read32(&mut self, func: u32, addr: u32) -> u32 { + self.readn(func, addr, 4).await + } + + #[allow(unused)] + pub async fn write32(&mut self, func: u32, addr: u32, val: u32) { + self.writen(func, addr, val, 4).await + } + + async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { + let cmd = cmd_word(READ, INC_ADDR, func, addr, len); + let mut buf = [0; 2]; + // if we are reading from the backplane, we need an extra word for the response delay + let len = if func == FUNC_BACKPLANE { 2 } else { 1 }; + + self.status = self.spi.cmd_read(cmd, &mut buf[..len]).await; + + // if we read from the backplane, the result is in the second word, after the response delay + if func == FUNC_BACKPLANE { + buf[1] + } else { + buf[0] + } + } + + async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { + let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); + + self.status = self.spi.cmd_write(&[cmd, val]).await; + } + + async fn read32_swapped(&mut self, func: u32, addr: u32) -> u32 { + let cmd = cmd_word(READ, INC_ADDR, func, addr, 4); + let cmd = swap16(cmd); + let mut buf = [0; 1]; + + self.status = self.spi.cmd_read(cmd, &mut buf).await; + + swap16(buf[0]) + } + + async fn write32_swapped(&mut self, func: u32, addr: u32, val: u32) { + let cmd = cmd_word(WRITE, INC_ADDR, func, addr, 4); + let buf = [swap16(cmd), swap16(val)]; + + self.status = self.spi.cmd_write(&buf).await; + } + + pub async fn wait_for_event(&mut self) { + self.spi.wait_for_event().await; + } + + pub fn status(&self) -> u32 { + self.status + } +} + +fn swap16(x: u32) -> u32 { + x.rotate_left(16) +} + +fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 { + (write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF) +} diff --git a/embassy/cyw43/src/consts.rs b/embassy/cyw43/src/consts.rs new file mode 100644 index 0000000..c3f0dbf --- /dev/null +++ b/embassy/cyw43/src/consts.rs @@ -0,0 +1,670 @@ +#![allow(unused)] + +pub(crate) const FUNC_BUS: u32 = 0; +pub(crate) const FUNC_BACKPLANE: u32 = 1; +pub(crate) const FUNC_WLAN: u32 = 2; +pub(crate) const FUNC_BT: u32 = 3; + +// Register addresses +pub(crate) const REG_BUS_CTRL: u32 = 0x0; +pub(crate) const REG_BUS_RESPONSE_DELAY: u32 = 0x1; +pub(crate) const REG_BUS_STATUS_ENABLE: u32 = 0x2; +pub(crate) const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status +pub(crate) const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask +pub(crate) const REG_BUS_STATUS: u32 = 0x8; +pub(crate) const REG_BUS_TEST_RO: u32 = 0x14; +pub(crate) const REG_BUS_TEST_RW: u32 = 0x18; +pub(crate) const REG_BUS_RESP_DELAY: u32 = 0x1c; + +// SPI_BUS_CONTROL Bits +pub(crate) const WORD_LENGTH_32: u32 = 0x1; +pub(crate) const ENDIAN_BIG: u32 = 0x2; +pub(crate) const CLOCK_PHASE: u32 = 0x4; +pub(crate) const CLOCK_POLARITY: u32 = 0x8; +pub(crate) const HIGH_SPEED: u32 = 0x10; +pub(crate) const INTERRUPT_POLARITY_HIGH: u32 = 0x20; +pub(crate) const WAKE_UP: u32 = 0x80; + +// SPI_STATUS_ENABLE bits +pub(crate) const STATUS_ENABLE: u32 = 0x01; +pub(crate) const INTR_WITH_STATUS: u32 = 0x02; +pub(crate) const RESP_DELAY_ALL: u32 = 0x04; +pub(crate) const DWORD_PKT_LEN_EN: u32 = 0x08; +pub(crate) const CMD_ERR_CHK_EN: u32 = 0x20; +pub(crate) const DATA_ERR_CHK_EN: u32 = 0x40; + +// SPI_STATUS_REGISTER bits +pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; +pub(crate) const STATUS_UNDERFLOW: u32 = 0x00000002; +pub(crate) const STATUS_OVERFLOW: u32 = 0x00000004; +pub(crate) const STATUS_F2_INTR: u32 = 0x00000008; +pub(crate) const STATUS_F3_INTR: u32 = 0x00000010; +pub(crate) const STATUS_F2_RX_READY: u32 = 0x00000020; +pub(crate) const STATUS_F3_RX_READY: u32 = 0x00000040; +pub(crate) const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080; +pub(crate) const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100; +pub(crate) const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00; +pub(crate) const STATUS_F2_PKT_LEN_SHIFT: u32 = 9; +pub(crate) const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000; +pub(crate) const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000; +pub(crate) const STATUS_F3_PKT_LEN_SHIFT: u32 = 21; + +pub(crate) const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005; +pub(crate) const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006; +pub(crate) const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007; +pub(crate) const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008; +pub(crate) const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C; +pub(crate) const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D; +pub(crate) const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E; +pub(crate) const REG_BACKPLANE_PULL_UP: u32 = 0x1000F; +pub(crate) const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B; +pub(crate) const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C; +pub(crate) const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E; +pub(crate) const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F; + +pub(crate) const I_HMB_SW_MASK: u32 = 0x000000f0; +pub(crate) const I_HMB_FC_CHANGE: u32 = 1 << 5; +pub(crate) const SDIO_INT_STATUS: u32 = 0x20; +pub(crate) const SDIO_INT_HOST_MASK: u32 = 0x24; + +pub(crate) const SPI_F2_WATERMARK: u8 = 0x20; + +pub(crate) const BACKPLANE_WINDOW_SIZE: usize = 0x8000; +pub(crate) const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; +pub(crate) const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; +pub(crate) const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; +// Active Low Power (ALP) clock constants +pub(crate) const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08; +pub(crate) const BACKPLANE_ALP_AVAIL: u8 = 0x40; + +// Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect +// (AI) pub (crate) constants +pub(crate) const AI_IOCTRL_OFFSET: u32 = 0x408; +pub(crate) const AI_IOCTRL_BIT_FGC: u8 = 0x0002; +pub(crate) const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001; +pub(crate) const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020; + +pub(crate) const AI_RESETCTRL_OFFSET: u32 = 0x800; +pub(crate) const AI_RESETCTRL_BIT_RESET: u8 = 1; + +pub(crate) const AI_RESETSTATUS_OFFSET: u32 = 0x804; + +pub(crate) const TEST_PATTERN: u32 = 0x12345678; +pub(crate) const FEEDBEAD: u32 = 0xFEEDBEAD; + +// SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits +pub(crate) const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1" +pub(crate) const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002; +pub(crate) const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004; +pub(crate) const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1 +pub(crate) const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1 +pub(crate) const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020; +pub(crate) const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040; +pub(crate) const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests +pub(crate) const IRQ_MISC_INTR0: u16 = 0x0100; +pub(crate) const IRQ_MISC_INTR1: u16 = 0x0200; +pub(crate) const IRQ_MISC_INTR2: u16 = 0x0400; +pub(crate) const IRQ_MISC_INTR3: u16 = 0x0800; +pub(crate) const IRQ_MISC_INTR4: u16 = 0x1000; +pub(crate) const IRQ_F1_INTR: u16 = 0x2000; +pub(crate) const IRQ_F2_INTR: u16 = 0x4000; +pub(crate) const IRQ_F3_INTR: u16 = 0x8000; + +pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0; +pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1; +pub(crate) const CHANNEL_TYPE_DATA: u8 = 2; + +// CYW_SPID command structure constants. +pub(crate) const WRITE: bool = true; +pub(crate) const READ: bool = false; +pub(crate) const INC_ADDR: bool = true; +pub(crate) const FIXED_ADDR: bool = false; + +pub(crate) const AES_ENABLED: u32 = 0x0004; +pub(crate) const WPA2_SECURITY: u32 = 0x00400000; + +pub(crate) const MIN_PSK_LEN: usize = 8; +pub(crate) const MAX_PSK_LEN: usize = 64; + +// Bluetooth firmware extraction constants. +pub(crate) const BTFW_ADDR_MODE_UNKNOWN: i32 = 0; +pub(crate) const BTFW_ADDR_MODE_EXTENDED: i32 = 1; +pub(crate) const BTFW_ADDR_MODE_SEGMENT: i32 = 2; +pub(crate) const BTFW_ADDR_MODE_LINEAR32: i32 = 3; + +pub(crate) const BTFW_HEX_LINE_TYPE_DATA: u8 = 0; +pub(crate) const BTFW_HEX_LINE_TYPE_END_OF_DATA: u8 = 1; +pub(crate) const BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS: u8 = 2; +pub(crate) const BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS: u8 = 4; +pub(crate) const BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS: u8 = 5; + +// Bluetooth constants. +pub(crate) const SPI_RESP_DELAY_F1: u32 = 0x001d; +pub(crate) const WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE: u8 = 4; + +pub(crate) const BT2WLAN_PWRUP_WAKE: u32 = 3; +pub(crate) const BT2WLAN_PWRUP_ADDR: u32 = 0x640894; + +pub(crate) const BT_CTRL_REG_ADDR: u32 = 0x18000c7c; +pub(crate) const HOST_CTRL_REG_ADDR: u32 = 0x18000d6c; +pub(crate) const WLAN_RAM_BASE_REG_ADDR: u32 = 0x18000d68; + +pub(crate) const BTSDIO_REG_DATA_VALID_BITMASK: u32 = 1 << 1; +pub(crate) const BTSDIO_REG_BT_AWAKE_BITMASK: u32 = 1 << 8; +pub(crate) const BTSDIO_REG_WAKE_BT_BITMASK: u32 = 1 << 17; +pub(crate) const BTSDIO_REG_SW_RDY_BITMASK: u32 = 1 << 24; +pub(crate) const BTSDIO_REG_FW_RDY_BITMASK: u32 = 1 << 24; + +pub(crate) const BTSDIO_FWBUF_SIZE: u32 = 0x1000; +pub(crate) const BTSDIO_OFFSET_HOST_WRITE_BUF: u32 = 0; +pub(crate) const BTSDIO_OFFSET_HOST_READ_BUF: u32 = BTSDIO_FWBUF_SIZE; + +pub(crate) const BTSDIO_OFFSET_HOST2BT_IN: u32 = 0x00002000; +pub(crate) const BTSDIO_OFFSET_HOST2BT_OUT: u32 = 0x00002004; +pub(crate) const BTSDIO_OFFSET_BT2HOST_IN: u32 = 0x00002008; +pub(crate) const BTSDIO_OFFSET_BT2HOST_OUT: u32 = 0x0000200C; + +// Security type (authentication and encryption types are combined using bit mask) +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, PartialEq)] +#[repr(u32)] +pub(crate) enum Security { + OPEN = 0, + WPA2_AES_PSK = WPA2_SECURITY | AES_ENABLED, +} + +#[allow(non_camel_case_types)] +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum EStatus { + /// operation was successful + SUCCESS = 0, + /// operation failed + FAIL = 1, + /// operation timed out + TIMEOUT = 2, + /// failed due to no matching network found + NO_NETWORKS = 3, + /// operation was aborted + ABORT = 4, + /// protocol failure: packet not ack'd + NO_ACK = 5, + /// AUTH or ASSOC packet was unsolicited + UNSOLICITED = 6, + /// attempt to assoc to an auto auth configuration + ATTEMPT = 7, + /// scan results are incomplete + PARTIAL = 8, + /// scan aborted by another scan + NEWSCAN = 9, + /// scan aborted due to assoc in progress + NEWASSOC = 10, + /// 802.11h quiet period started + _11HQUIET = 11, + /// user disabled scanning (WLC_SET_SCANSUPPRESS) + SUPPRESS = 12, + /// no allowable channels to scan + NOCHANS = 13, + /// scan aborted due to CCX fast roam + CCXFASTRM = 14, + /// abort channel select + CS_ABORT = 15, +} + +impl PartialEq for u32 { + fn eq(&self, other: &EStatus) -> bool { + *self == *other as Self + } +} + +#[allow(dead_code)] +pub(crate) struct FormatStatus(pub u32); + +#[cfg(feature = "defmt")] +impl defmt::Format for FormatStatus { + fn format(&self, fmt: defmt::Formatter) { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + defmt::write!(fmt, " | {}", &stringify!($name)[7..]); + } + )* + }; + } + + implm!( + STATUS_DATA_NOT_AVAILABLE, + STATUS_UNDERFLOW, + STATUS_OVERFLOW, + STATUS_F2_INTR, + STATUS_F3_INTR, + STATUS_F2_RX_READY, + STATUS_F3_RX_READY, + STATUS_HOST_CMD_DATA_ERR, + STATUS_F2_PKT_AVAILABLE, + STATUS_F3_PKT_AVAILABLE + ); + } +} + +#[cfg(feature = "log")] +impl core::fmt::Debug for FormatStatus { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + core::write!(fmt, " | {}", &stringify!($name)[7..])?; + } + )* + }; + } + + implm!( + STATUS_DATA_NOT_AVAILABLE, + STATUS_UNDERFLOW, + STATUS_OVERFLOW, + STATUS_F2_INTR, + STATUS_F3_INTR, + STATUS_F2_RX_READY, + STATUS_F3_RX_READY, + STATUS_HOST_CMD_DATA_ERR, + STATUS_F2_PKT_AVAILABLE, + STATUS_F3_PKT_AVAILABLE + ); + Ok(()) + } +} + +#[cfg(feature = "log")] +impl core::fmt::Display for FormatStatus { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(self, f) + } +} + +#[allow(dead_code)] +pub(crate) struct FormatInterrupt(pub u16); + +#[cfg(feature = "defmt")] +impl defmt::Format for FormatInterrupt { + fn format(&self, fmt: defmt::Formatter) { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + defmt::write!(fmt, " | {}", &stringify!($name)[4..]); + } + )* + }; + } + + implm!( + IRQ_DATA_UNAVAILABLE, + IRQ_F2_F3_FIFO_RD_UNDERFLOW, + IRQ_F2_F3_FIFO_WR_OVERFLOW, + IRQ_COMMAND_ERROR, + IRQ_DATA_ERROR, + IRQ_F2_PACKET_AVAILABLE, + IRQ_F3_PACKET_AVAILABLE, + IRQ_F1_OVERFLOW, + IRQ_MISC_INTR0, + IRQ_MISC_INTR1, + IRQ_MISC_INTR2, + IRQ_MISC_INTR3, + IRQ_MISC_INTR4, + IRQ_F1_INTR, + IRQ_F2_INTR, + IRQ_F3_INTR + ); + } +} + +#[cfg(feature = "log")] +impl core::fmt::Debug for FormatInterrupt { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + core::write!(fmt, " | {}", &stringify!($name)[7..])?; + } + )* + }; + } + + implm!( + IRQ_DATA_UNAVAILABLE, + IRQ_F2_F3_FIFO_RD_UNDERFLOW, + IRQ_F2_F3_FIFO_WR_OVERFLOW, + IRQ_COMMAND_ERROR, + IRQ_DATA_ERROR, + IRQ_F2_PACKET_AVAILABLE, + IRQ_F3_PACKET_AVAILABLE, + IRQ_F1_OVERFLOW, + IRQ_MISC_INTR0, + IRQ_MISC_INTR1, + IRQ_MISC_INTR2, + IRQ_MISC_INTR3, + IRQ_MISC_INTR4, + IRQ_F1_INTR, + IRQ_F2_INTR, + IRQ_F3_INTR + ); + Ok(()) + } +} + +#[cfg(feature = "log")] +impl core::fmt::Display for FormatInterrupt { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(self, f) + } +} + +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u32)] +pub(crate) enum Ioctl { + GetMagic = 0, + GetVersion = 1, + Up = 2, + Down = 3, + GetLoop = 4, + SetLoop = 5, + Dump = 6, + GetMsglevel = 7, + SetMsglevel = 8, + GetPromisc = 9, + SetPromisc = 10, + GetRate = 12, + GetInstance = 14, + GetInfra = 19, + SetInfra = 20, + GetAuth = 21, + SetAuth = 22, + GetBssid = 23, + SetBssid = 24, + GetSsid = 25, + SetSsid = 26, + Restart = 27, + GetChannel = 29, + SetChannel = 30, + GetSrl = 31, + SetSrl = 32, + GetLrl = 33, + SetLrl = 34, + GetPlcphdr = 35, + SetPlcphdr = 36, + GetRadio = 37, + SetRadio = 38, + GetPhytype = 39, + DumpRate = 40, + SetRateParams = 41, + GetKey = 44, + SetKey = 45, + GetRegulatory = 46, + SetRegulatory = 47, + GetPassiveScan = 48, + SetPassiveScan = 49, + Scan = 50, + ScanResults = 51, + Disassoc = 52, + Reassoc = 53, + GetRoamTrigger = 54, + SetRoamTrigger = 55, + GetRoamDelta = 56, + SetRoamDelta = 57, + GetRoamScanPeriod = 58, + SetRoamScanPeriod = 59, + Evm = 60, + GetTxant = 61, + SetTxant = 62, + GetAntdiv = 63, + SetAntdiv = 64, + GetClosed = 67, + SetClosed = 68, + GetMaclist = 69, + SetMaclist = 70, + GetRateset = 71, + SetRateset = 72, + Longtrain = 74, + GetBcnprd = 75, + SetBcnprd = 76, + GetDtimprd = 77, + SetDtimprd = 78, + GetSrom = 79, + SetSrom = 80, + GetWepRestrict = 81, + SetWepRestrict = 82, + GetCountry = 83, + SetCountry = 84, + GetPm = 85, + SetPm = 86, + GetWake = 87, + SetWake = 88, + GetForcelink = 90, + SetForcelink = 91, + FreqAccuracy = 92, + CarrierSuppress = 93, + GetPhyreg = 94, + SetPhyreg = 95, + GetRadioreg = 96, + SetRadioreg = 97, + GetRevinfo = 98, + GetUcantdiv = 99, + SetUcantdiv = 100, + RReg = 101, + WReg = 102, + GetMacmode = 105, + SetMacmode = 106, + GetMonitor = 107, + SetMonitor = 108, + GetGmode = 109, + SetGmode = 110, + GetLegacyErp = 111, + SetLegacyErp = 112, + GetRxAnt = 113, + GetCurrRateset = 114, + GetScansuppress = 115, + SetScansuppress = 116, + GetAp = 117, + SetAp = 118, + GetEapRestrict = 119, + SetEapRestrict = 120, + ScbAuthorize = 121, + ScbDeauthorize = 122, + GetWdslist = 123, + SetWdslist = 124, + GetAtim = 125, + SetAtim = 126, + GetRssi = 127, + GetPhyantdiv = 128, + SetPhyantdiv = 129, + ApRxOnly = 130, + GetTxPathPwr = 131, + SetTxPathPwr = 132, + GetWsec = 133, + SetWsec = 134, + GetPhyNoise = 135, + GetBssInfo = 136, + GetPktcnts = 137, + GetLazywds = 138, + SetLazywds = 139, + GetBandlist = 140, + GetBand = 141, + SetBand = 142, + ScbDeauthenticate = 143, + GetShortslot = 144, + GetShortslotOverride = 145, + SetShortslotOverride = 146, + GetShortslotRestrict = 147, + SetShortslotRestrict = 148, + GetGmodeProtection = 149, + GetGmodeProtectionOverride = 150, + SetGmodeProtectionOverride = 151, + Upgrade = 152, + GetIgnoreBcns = 155, + SetIgnoreBcns = 156, + GetScbTimeout = 157, + SetScbTimeout = 158, + GetAssoclist = 159, + GetClk = 160, + SetClk = 161, + GetUp = 162, + Out = 163, + GetWpaAuth = 164, + SetWpaAuth = 165, + GetUcflags = 166, + SetUcflags = 167, + GetPwridx = 168, + SetPwridx = 169, + GetTssi = 170, + GetSupRatesetOverride = 171, + SetSupRatesetOverride = 172, + GetProtectionControl = 178, + SetProtectionControl = 179, + GetPhylist = 180, + EncryptStrength = 181, + DecryptStatus = 182, + GetKeySeq = 183, + GetScanChannelTime = 184, + SetScanChannelTime = 185, + GetScanUnassocTime = 186, + SetScanUnassocTime = 187, + GetScanHomeTime = 188, + SetScanHomeTime = 189, + GetScanNprobes = 190, + SetScanNprobes = 191, + GetPrbRespTimeout = 192, + SetPrbRespTimeout = 193, + GetAtten = 194, + SetAtten = 195, + GetShmem = 196, + SetShmem = 197, + SetWsecTest = 200, + ScbDeauthenticateForReason = 201, + TkipCountermeasures = 202, + GetPiomode = 203, + SetPiomode = 204, + SetAssocPrefer = 205, + GetAssocPrefer = 206, + SetRoamPrefer = 207, + GetRoamPrefer = 208, + SetLed = 209, + GetLed = 210, + GetInterferenceMode = 211, + SetInterferenceMode = 212, + GetChannelQa = 213, + StartChannelQa = 214, + GetChannelSel = 215, + StartChannelSel = 216, + GetValidChannels = 217, + GetFakefrag = 218, + SetFakefrag = 219, + GetPwroutPercentage = 220, + SetPwroutPercentage = 221, + SetBadFramePreempt = 222, + GetBadFramePreempt = 223, + SetLeapList = 224, + GetLeapList = 225, + GetCwmin = 226, + SetCwmin = 227, + GetCwmax = 228, + SetCwmax = 229, + GetWet = 230, + SetWet = 231, + GetPub = 232, + GetKeyPrimary = 235, + SetKeyPrimary = 236, + GetAciArgs = 238, + SetAciArgs = 239, + UnsetCallback = 240, + SetCallback = 241, + GetRadar = 242, + SetRadar = 243, + SetSpectManagment = 244, + GetSpectManagment = 245, + WdsGetRemoteHwaddr = 246, + WdsGetWpaSup = 247, + SetCsScanTimer = 248, + GetCsScanTimer = 249, + MeasureRequest = 250, + Init = 251, + SendQuiet = 252, + Keepalive = 253, + SendPwrConstraint = 254, + UpgradeStatus = 255, + CurrentPwr = 256, + GetScanPassiveTime = 257, + SetScanPassiveTime = 258, + LegacyLinkBehavior = 259, + GetChannelsInCountry = 260, + GetCountryList = 261, + GetVar = 262, + SetVar = 263, + NvramGet = 264, + NvramSet = 265, + NvramDump = 266, + Reboot = 267, + SetWsecPmk = 268, + GetAuthMode = 269, + SetAuthMode = 270, + GetWakeentry = 271, + SetWakeentry = 272, + NdconfigItem = 273, + Nvotpw = 274, + Otpw = 275, + IovBlockGet = 276, + IovModulesGet = 277, + SoftReset = 278, + GetAllowMode = 279, + SetAllowMode = 280, + GetDesiredBssid = 281, + SetDesiredBssid = 282, + DisassocMyap = 283, + GetNbands = 284, + GetBandstates = 285, + GetWlcBssInfo = 286, + GetAssocInfo = 287, + GetOidPhy = 288, + SetOidPhy = 289, + SetAssocTime = 290, + GetDesiredSsid = 291, + GetChanspec = 292, + GetAssocState = 293, + SetPhyState = 294, + GetScanPending = 295, + GetScanreqPending = 296, + GetPrevRoamReason = 297, + SetPrevRoamReason = 298, + GetBandstatesPi = 299, + GetPhyState = 300, + GetBssWpaRsn = 301, + GetBssWpa2Rsn = 302, + GetBssBcnTs = 303, + GetIntDisassoc = 304, + SetNumPeers = 305, + GetNumBss = 306, + GetWsecPmk = 318, + GetRandomBytes = 319, +} + +pub(crate) const WSEC_TKIP: u32 = 0x02; +pub(crate) const WSEC_AES: u32 = 0x04; + +pub(crate) const AUTH_OPEN: u32 = 0x00; +pub(crate) const AUTH_SAE: u32 = 0x03; + +pub(crate) const MFP_NONE: u32 = 0; +pub(crate) const MFP_CAPABLE: u32 = 1; +pub(crate) const MFP_REQUIRED: u32 = 2; + +pub(crate) const WPA_AUTH_DISABLED: u32 = 0x0000; +pub(crate) const WPA_AUTH_WPA_PSK: u32 = 0x0004; +pub(crate) const WPA_AUTH_WPA2_PSK: u32 = 0x0080; +pub(crate) const WPA_AUTH_WPA3_SAE_PSK: u32 = 0x40000; diff --git a/embassy/cyw43/src/control.rs b/embassy/cyw43/src/control.rs new file mode 100644 index 0000000..071ba88 --- /dev/null +++ b/embassy/cyw43/src/control.rs @@ -0,0 +1,740 @@ +use core::cmp::{max, min}; +use core::iter::zip; + +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::{HardwareAddress, LinkState}; +use embassy_time::{Duration, Timer}; + +use crate::consts::*; +use crate::events::{Event, EventSubscriber, Events}; +use crate::fmt::Bytes; +use crate::ioctl::{IoctlState, IoctlType}; +use crate::structs::*; +use crate::{countries, events, PowerManagementMode}; + +/// Control errors. +#[derive(Debug)] +pub struct Error { + /// Status code. + pub status: u32, +} + +/// Multicast errors. +#[derive(Debug)] +pub enum AddMulticastAddressError { + /// Not a multicast address. + NotMulticast, + /// No free address slots. + NoFreeSlots, +} + +/// Control driver. +pub struct Control<'a> { + state_ch: ch::StateRunner<'a>, + events: &'a Events, + ioctl_state: &'a IoctlState, +} + +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ScanType { + Active, + Passive, +} + +/// Scan options. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct ScanOptions { + /// SSID to scan for. + pub ssid: Option>, + /// If set to `None`, all APs will be returned. If set to `Some`, only APs + /// with the specified BSSID will be returned. + pub bssid: Option<[u8; 6]>, + /// Number of probes to send on each channel. + pub nprobes: Option, + /// Time to spend waiting on the home channel. + pub home_time: Option, + /// Scan type: active or passive. + pub scan_type: ScanType, + /// Period of time to wait on each channel when passive scanning. + pub dwell_time: Option, +} + +impl Default for ScanOptions { + fn default() -> Self { + Self { + ssid: None, + bssid: None, + nprobes: None, + home_time: None, + scan_type: ScanType::Passive, + dwell_time: None, + } + } +} + +/// Authentication type, used in [`JoinOptions::auth`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum JoinAuth { + /// Open network + Open, + /// WPA only + Wpa, + /// WPA2 only + Wpa2, + /// WPA3 only + Wpa3, + /// WPA2 + WPA3 + Wpa2Wpa3, +} + +/// Options for [`Control::join`]. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct JoinOptions<'a> { + /// Authentication type. Default `Wpa2Wpa3`. + pub auth: JoinAuth, + /// Enable TKIP encryption. Default false. + pub cipher_tkip: bool, + /// Enable AES encryption. Default true. + pub cipher_aes: bool, + /// Passphrase. Default empty. + pub passphrase: &'a [u8], + /// If false, `passphrase` is the human-readable passphrase string. + /// If true, `passphrase` is the result of applying the PBKDF2 hash to the + /// passphrase string. This makes it possible to avoid storing unhashed passwords. + /// + /// This is not compatible with WPA3. + /// Default false. + pub passphrase_is_prehashed: bool, +} + +impl<'a> JoinOptions<'a> { + /// Create a new `JoinOptions` for joining open networks. + pub fn new_open() -> Self { + Self { + auth: JoinAuth::Open, + cipher_tkip: false, + cipher_aes: false, + passphrase: &[], + passphrase_is_prehashed: false, + } + } + + /// Create a new `JoinOptions` for joining encrypted networks. + /// + /// Defaults to supporting WPA2+WPA3 with AES only, you may edit + /// the returned options to change this. + pub fn new(passphrase: &'a [u8]) -> Self { + let mut this = Self::default(); + this.passphrase = passphrase; + this + } +} + +impl<'a> Default for JoinOptions<'a> { + fn default() -> Self { + Self { + auth: JoinAuth::Wpa2Wpa3, + cipher_tkip: false, + cipher_aes: true, + passphrase: &[], + passphrase_is_prehashed: false, + } + } +} + +impl<'a> Control<'a> { + pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { + Self { + state_ch, + events: event_sub, + ioctl_state, + } + } + + async fn load_clm(&mut self, clm: &[u8]) { + const CHUNK_SIZE: usize = 1024; + + debug!("Downloading CLM..."); + + let mut offs = 0; + for chunk in clm.chunks(CHUNK_SIZE) { + let mut flag = DOWNLOAD_FLAG_HANDLER_VER; + if offs == 0 { + flag |= DOWNLOAD_FLAG_BEGIN; + } + offs += chunk.len(); + if offs == clm.len() { + flag |= DOWNLOAD_FLAG_END; + } + + let header = DownloadHeader { + flag, + dload_type: DOWNLOAD_TYPE_CLM, + len: chunk.len() as _, + crc: 0, + }; + let mut buf = [0; 8 + 12 + CHUNK_SIZE]; + buf[0..8].copy_from_slice(b"clmload\x00"); + buf[8..20].copy_from_slice(&header.to_bytes()); + buf[20..][..chunk.len()].copy_from_slice(&chunk); + self.ioctl(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..8 + 12 + chunk.len()]) + .await; + } + + // check clmload ok + assert_eq!(self.get_iovar_u32("clmload_status").await, 0); + } + + /// Initialize WiFi controller. + pub async fn init(&mut self, clm: &[u8]) { + self.load_clm(&clm).await; + + debug!("Configuring misc stuff..."); + + // Disable tx gloming which transfers multiple packets in one request. + // 'glom' is short for "conglomerate" which means "gather together into + // a compact mass". + self.set_iovar_u32("bus:txglom", 0).await; + self.set_iovar_u32("apsta", 1).await; + + // read MAC addr. + let mac_addr = self.address().await; + debug!("mac addr: {:02x}", Bytes(&mac_addr)); + + let country = countries::WORLD_WIDE_XX; + let country_info = CountryInfo { + country_abbrev: [country.code[0], country.code[1], 0, 0], + country_code: [country.code[0], country.code[1], 0, 0], + rev: if country.rev == 0 { -1 } else { country.rev as _ }, + }; + self.set_iovar("country", &country_info.to_bytes()).await; + + // set country takes some time, next ioctls fail if we don't wait. + Timer::after_millis(100).await; + + // Set antenna to chip antenna + self.ioctl_set_u32(Ioctl::SetAntdiv, 0, 0).await; + + self.set_iovar_u32("bus:txglom", 0).await; + Timer::after_millis(100).await; + //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? + //Timer::after_millis(100).await; + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + Timer::after_millis(100).await; + self.set_iovar_u32("ampdu_mpdu", 4).await; + Timer::after_millis(100).await; + //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes + + //Timer::after_millis(100).await; + + // evts + let mut evts = EventMask { + iface: 0, + events: [0xFF; 24], + }; + + // Disable spammy uninteresting events. + evts.unset(Event::RADIO); + evts.unset(Event::IF); + evts.unset(Event::PROBREQ_MSG); + evts.unset(Event::PROBREQ_MSG_RX); + evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::ROAM); + + self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; + + Timer::after_millis(100).await; + + // set wifi up + self.up().await; + + Timer::after_millis(100).await; + + self.ioctl_set_u32(Ioctl::SetGmode, 0, 1).await; // SET_GMODE = auto + self.ioctl_set_u32(Ioctl::SetBand, 0, 0).await; // SET_BAND = any + + Timer::after_millis(100).await; + + self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr)); + + debug!("cyw43 control init done"); + } + + /// Set the WiFi interface up. + async fn up(&mut self) { + self.ioctl(IoctlType::Set, Ioctl::Up, 0, &mut []).await; + } + + /// Set the interface down. + async fn down(&mut self) { + self.ioctl(IoctlType::Set, Ioctl::Down, 0, &mut []).await; + } + + /// Set power management mode. + pub async fn set_power_management(&mut self, mode: PowerManagementMode) { + // power save mode + let mode_num = mode.mode(); + if mode_num == 2 { + self.set_iovar_u32("pm2_sleep_ret", mode.sleep_ret_ms() as u32).await; + self.set_iovar_u32("bcn_li_bcn", mode.beacon_period() as u32).await; + self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; + self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; + } + self.ioctl_set_u32(Ioctl::SetPm, 0, mode_num).await; + } + + /// Join an unprotected network with the provided ssid. + pub async fn join(&mut self, ssid: &str, options: JoinOptions<'_>) -> Result<(), Error> { + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + + if options.auth == JoinAuth::Open { + self.ioctl_set_u32(Ioctl::SetWsec, 0, 0).await; + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await; + self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, WPA_AUTH_DISABLED).await; + } else { + let mut wsec = 0; + if options.cipher_aes { + wsec |= WSEC_AES; + } + if options.cipher_tkip { + wsec |= WSEC_TKIP; + } + self.ioctl_set_u32(Ioctl::SetWsec, 0, wsec).await; + + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; + self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; + self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; + + Timer::after_millis(100).await; + + let (wpa12, wpa3, auth, mfp, wpa_auth) = match options.auth { + JoinAuth::Open => unreachable!(), + JoinAuth::Wpa => (true, false, AUTH_OPEN, MFP_NONE, WPA_AUTH_WPA_PSK), + JoinAuth::Wpa2 => (true, false, AUTH_OPEN, MFP_CAPABLE, WPA_AUTH_WPA2_PSK), + JoinAuth::Wpa3 => (false, true, AUTH_SAE, MFP_REQUIRED, WPA_AUTH_WPA3_SAE_PSK), + JoinAuth::Wpa2Wpa3 => (true, true, AUTH_SAE, MFP_CAPABLE, WPA_AUTH_WPA3_SAE_PSK), + }; + + if wpa12 { + let mut flags = 0; + if !options.passphrase_is_prehashed { + flags |= 1; + } + let mut pfi = PassphraseInfo { + len: options.passphrase.len() as _, + flags, + passphrase: [0; 64], + }; + pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase); + Timer::after_millis(3).await; + self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes()) + .await; + } + + if wpa3 { + let mut pfi = SaePassphraseInfo { + len: options.passphrase.len() as _, + passphrase: [0; 128], + }; + pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase); + Timer::after_millis(3).await; + self.set_iovar("sae_password", &pfi.to_bytes()).await; + } + + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAuth, 0, auth).await; + self.set_iovar_u32("mfp", mfp).await; + self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, wpa_auth).await; + } + + let mut i = SsidInfo { + len: ssid.len() as _, + ssid: [0; 32], + }; + i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + + self.wait_for_join(i).await + } + + async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> { + self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]); + let mut subscriber = self.events.queue.subscriber().unwrap(); + // the actual join operation starts here + // we make sure to enable events before so we don't miss any + + self.ioctl(IoctlType::Set, Ioctl::SetSsid, 0, &mut i.to_bytes()).await; + + // to complete the join, we wait for a SET_SSID event + // we also save the AUTH status for the user, it may be interesting + let mut auth_status = 0; + let status = loop { + let msg = subscriber.next_message_pure().await; + if msg.header.event_type == Event::AUTH && msg.header.status != EStatus::SUCCESS { + auth_status = msg.header.status; + } else if msg.header.event_type == Event::SET_SSID { + // join operation ends with SET_SSID event + break msg.header.status; + } + }; + + self.events.mask.disable_all(); + if status == EStatus::SUCCESS { + // successful join + self.state_ch.set_link_state(LinkState::Up); + debug!("JOINED"); + Ok(()) + } else { + warn!("JOIN failed with status={} auth={}", status, auth_status); + Err(Error { status }) + } + } + + /// Set GPIO pin on WiFi chip. + pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) { + assert!(gpio_n < 3); + self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 }) + .await + } + + /// Start open access point. + pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) { + self.start_ap(ssid, "", Security::OPEN, channel).await; + } + + /// Start WPA2 protected access point. + pub async fn start_ap_wpa2(&mut self, ssid: &str, passphrase: &str, channel: u8) { + self.start_ap(ssid, passphrase, Security::WPA2_AES_PSK, channel).await; + } + + async fn start_ap(&mut self, ssid: &str, passphrase: &str, security: Security, channel: u8) { + if security != Security::OPEN + && (passphrase.as_bytes().len() < MIN_PSK_LEN || passphrase.as_bytes().len() > MAX_PSK_LEN) + { + panic!("Passphrase is too short or too long"); + } + + // Temporarily set wifi down + self.down().await; + + // Turn off APSTA mode + self.set_iovar_u32("apsta", 0).await; + + // Set wifi up again + self.up().await; + + // Turn on AP mode + self.ioctl_set_u32(Ioctl::SetAp, 0, 1).await; + + // Set SSID + let mut i = SsidInfoWithIndex { + index: 0, + ssid_info: SsidInfo { + len: ssid.as_bytes().len() as _, + ssid: [0; 32], + }, + }; + i.ssid_info.ssid[..ssid.as_bytes().len()].copy_from_slice(ssid.as_bytes()); + self.set_iovar("bsscfg:ssid", &i.to_bytes()).await; + + // Set channel number + self.ioctl_set_u32(Ioctl::SetChannel, 0, channel as u32).await; + + // Set security + self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await; + + if security != Security::OPEN { + self.set_iovar_u32x2("bsscfg:wpa_auth", 0, 0x0084).await; // wpa_auth = WPA2_AUTH_PSK | WPA_AUTH_PSK + + Timer::after_millis(100).await; + + // Set passphrase + let mut pfi = PassphraseInfo { + len: passphrase.as_bytes().len() as _, + flags: 1, // WSEC_PASSPHRASE + passphrase: [0; 64], + }; + pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes()); + self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes()) + .await; + } + + // Change mutlicast rate from 1 Mbps to 11 Mbps + self.set_iovar_u32("2g_mrate", 11000000 / 500000).await; + + // Start AP + self.set_iovar_u32x2("bss", 0, 1).await; // bss = BSS_UP + } + + /// Closes access point. + pub async fn close_ap(&mut self) { + // Stop AP + self.set_iovar_u32x2("bss", 0, 0).await; // bss = BSS_DOWN + + // Turn off AP mode + self.ioctl_set_u32(Ioctl::SetAp, 0, 0).await; + + // Temporarily set wifi down + self.down().await; + + // Turn on APSTA mode + self.set_iovar_u32("apsta", 1).await; + + // Set wifi up again + self.up().await; + } + + /// Add specified address to the list of hardware addresses the device + /// listens on. The address must be a Group address (I/G bit set). Up + /// to 10 addresses are supported by the firmware. Returns the number of + /// address slots filled after adding, or an error. + pub async fn add_multicast_address(&mut self, address: [u8; 6]) -> Result { + // The firmware seems to ignore non-multicast addresses, so let's + // prevent the user from adding them and wasting space. + if address[0] & 0x01 != 1 { + return Err(AddMulticastAddressError::NotMulticast); + } + + let mut buf = [0; 64]; + self.get_iovar("mcast_list", &mut buf).await; + + let n = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize; + let (used, free) = buf[4..].split_at_mut(n * 6); + + if used.chunks(6).any(|a| a == address) { + return Ok(n); + } + + if free.len() < 6 { + return Err(AddMulticastAddressError::NoFreeSlots); + } + + free[..6].copy_from_slice(&address); + let n = n + 1; + buf[..4].copy_from_slice(&(n as u32).to_le_bytes()); + + self.set_iovar_v::<80>("mcast_list", &buf).await; + Ok(n) + } + + /// Retrieve the list of configured multicast hardware addresses. + pub async fn list_mulistcast_addresses(&mut self, result: &mut [[u8; 6]; 10]) -> usize { + let mut buf = [0; 64]; + self.get_iovar("mcast_list", &mut buf).await; + + let n = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize; + let used = &buf[4..][..n * 6]; + + for (addr, output) in zip(used.chunks(6), result.iter_mut()) { + output.copy_from_slice(addr) + } + + n + } + + async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { + let mut buf = [0; 8]; + buf[0..4].copy_from_slice(&val1.to_le_bytes()); + buf[4..8].copy_from_slice(&val2.to_le_bytes()); + self.set_iovar(name, &buf).await + } + + async fn set_iovar_u32(&mut self, name: &str, val: u32) { + self.set_iovar(name, &val.to_le_bytes()).await + } + + async fn get_iovar_u32(&mut self, name: &str) -> u32 { + let mut buf = [0; 4]; + let len = self.get_iovar(name, &mut buf).await; + assert_eq!(len, 4); + u32::from_le_bytes(buf) + } + + async fn set_iovar(&mut self, name: &str, val: &[u8]) { + self.set_iovar_v::<196>(name, val).await + } + + async fn set_iovar_v(&mut self, name: &str, val: &[u8]) { + debug!("iovar set {} = {:02x}", name, Bytes(val)); + + let mut buf = [0; BUFSIZE]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + buf[name.len() + 1..][..val.len()].copy_from_slice(val); + + let total_len = name.len() + 1 + val.len(); + self.ioctl_inner(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..total_len]) + .await; + } + + // TODO this is not really working, it always returns all zeros. + async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { + debug!("iovar get {}", name); + + let mut buf = [0; 64]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + + let total_len = max(name.len() + 1, res.len()); + let res_len = self + .ioctl_inner(IoctlType::Get, Ioctl::GetVar, 0, &mut buf[..total_len]) + .await; + + let out_len = min(res.len(), res_len); + res[..out_len].copy_from_slice(&buf[..out_len]); + out_len + } + + async fn ioctl_set_u32(&mut self, cmd: Ioctl, iface: u32, val: u32) { + let mut buf = val.to_le_bytes(); + self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; + } + + async fn ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { + if kind == IoctlType::Set { + debug!("ioctl set {:?} iface {} = {:02x}", cmd, iface, Bytes(buf)); + } + let n = self.ioctl_inner(kind, cmd, iface, buf).await; + n + } + + async fn ioctl_inner(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { + struct CancelOnDrop<'a>(&'a IoctlState); + + impl CancelOnDrop<'_> { + fn defuse(self) { + core::mem::forget(self); + } + } + + impl Drop for CancelOnDrop<'_> { + fn drop(&mut self) { + self.0.cancel_ioctl(); + } + } + + let ioctl = CancelOnDrop(self.ioctl_state); + let resp_len = ioctl.0.do_ioctl(kind, cmd, iface, buf).await; + ioctl.defuse(); + + resp_len + } + + /// Start a wifi scan + /// + /// Returns a `Stream` of networks found by the device + /// + /// # Note + /// Device events are currently implemented using a bounded queue. + /// To not miss any events, you should make sure to always await the stream. + pub async fn scan(&mut self, scan_opts: ScanOptions) -> Scanner<'_> { + const SCANTYPE_ACTIVE: u8 = 0; + const SCANTYPE_PASSIVE: u8 = 1; + + let dwell_time = match scan_opts.dwell_time { + None => !0, + Some(t) => { + let mut t = t.as_millis() as u32; + if t == !0 { + t = !0 - 1; + } + t + } + }; + + let mut active_time = !0; + let mut passive_time = !0; + let scan_type = match scan_opts.scan_type { + ScanType::Active => { + active_time = dwell_time; + SCANTYPE_ACTIVE + } + ScanType::Passive => { + passive_time = dwell_time; + SCANTYPE_PASSIVE + } + }; + + let scan_params = ScanParams { + version: 1, + action: 1, + sync_id: 1, + ssid_len: scan_opts.ssid.as_ref().map(|e| e.as_bytes().len() as u32).unwrap_or(0), + ssid: scan_opts + .ssid + .map(|e| { + let mut ssid = [0; 32]; + ssid[..e.as_bytes().len()].copy_from_slice(e.as_bytes()); + ssid + }) + .unwrap_or([0; 32]), + bssid: scan_opts.bssid.unwrap_or([0xff; 6]), + bss_type: 2, + scan_type, + nprobes: scan_opts.nprobes.unwrap_or(!0).into(), + active_time, + passive_time, + home_time: scan_opts.home_time.map(|e| e.as_millis() as u32).unwrap_or(!0), + channel_num: 0, + channel_list: [0; 1], + }; + + self.events.mask.enable(&[Event::ESCAN_RESULT]); + let subscriber = self.events.queue.subscriber().unwrap(); + self.set_iovar_v::<256>("escan", &scan_params.to_bytes()).await; + + Scanner { + subscriber, + events: &self.events, + } + } + /// Leave the wifi, with which we are currently associated. + pub async fn leave(&mut self) { + self.ioctl(IoctlType::Set, Ioctl::Disassoc, 0, &mut []).await; + info!("Disassociated") + } + + /// Gets the MAC address of the device + pub async fn address(&mut self) -> [u8; 6] { + let mut mac_addr = [0; 6]; + assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); + mac_addr + } +} + +/// WiFi network scanner. +pub struct Scanner<'a> { + subscriber: EventSubscriber<'a>, + events: &'a Events, +} + +impl Scanner<'_> { + /// Wait for the next found network. + pub async fn next(&mut self) -> Option { + let event = self.subscriber.next_message_pure().await; + if event.header.status != EStatus::PARTIAL { + self.events.mask.disable_all(); + return None; + } + + if let events::Payload::BssInfo(bss) = event.payload { + Some(bss) + } else { + None + } + } +} + +impl Drop for Scanner<'_> { + fn drop(&mut self) { + self.events.mask.disable_all(); + } +} diff --git a/embassy/cyw43/src/countries.rs b/embassy/cyw43/src/countries.rs new file mode 100644 index 0000000..fa1e8ca --- /dev/null +++ b/embassy/cyw43/src/countries.rs @@ -0,0 +1,481 @@ +#![allow(unused)] + +pub struct Country { + pub code: [u8; 2], + pub rev: u16, +} + +/// AF Afghanistan +pub const AFGHANISTAN: Country = Country { code: *b"AF", rev: 0 }; +/// AL Albania +pub const ALBANIA: Country = Country { code: *b"AL", rev: 0 }; +/// DZ Algeria +pub const ALGERIA: Country = Country { code: *b"DZ", rev: 0 }; +/// AS American_Samoa +pub const AMERICAN_SAMOA: Country = Country { code: *b"AS", rev: 0 }; +/// AO Angola +pub const ANGOLA: Country = Country { code: *b"AO", rev: 0 }; +/// AI Anguilla +pub const ANGUILLA: Country = Country { code: *b"AI", rev: 0 }; +/// AG Antigua_and_Barbuda +pub const ANTIGUA_AND_BARBUDA: Country = Country { code: *b"AG", rev: 0 }; +/// AR Argentina +pub const ARGENTINA: Country = Country { code: *b"AR", rev: 0 }; +/// AM Armenia +pub const ARMENIA: Country = Country { code: *b"AM", rev: 0 }; +/// AW Aruba +pub const ARUBA: Country = Country { code: *b"AW", rev: 0 }; +/// AU Australia +pub const AUSTRALIA: Country = Country { code: *b"AU", rev: 0 }; +/// AT Austria +pub const AUSTRIA: Country = Country { code: *b"AT", rev: 0 }; +/// AZ Azerbaijan +pub const AZERBAIJAN: Country = Country { code: *b"AZ", rev: 0 }; +/// BS Bahamas +pub const BAHAMAS: Country = Country { code: *b"BS", rev: 0 }; +/// BH Bahrain +pub const BAHRAIN: Country = Country { code: *b"BH", rev: 0 }; +/// 0B Baker_Island +pub const BAKER_ISLAND: Country = Country { code: *b"0B", rev: 0 }; +/// BD Bangladesh +pub const BANGLADESH: Country = Country { code: *b"BD", rev: 0 }; +/// BB Barbados +pub const BARBADOS: Country = Country { code: *b"BB", rev: 0 }; +/// BY Belarus +pub const BELARUS: Country = Country { code: *b"BY", rev: 0 }; +/// BE Belgium +pub const BELGIUM: Country = Country { code: *b"BE", rev: 0 }; +/// BZ Belize +pub const BELIZE: Country = Country { code: *b"BZ", rev: 0 }; +/// BJ Benin +pub const BENIN: Country = Country { code: *b"BJ", rev: 0 }; +/// BM Bermuda +pub const BERMUDA: Country = Country { code: *b"BM", rev: 0 }; +/// BT Bhutan +pub const BHUTAN: Country = Country { code: *b"BT", rev: 0 }; +/// BO Bolivia +pub const BOLIVIA: Country = Country { code: *b"BO", rev: 0 }; +/// BA Bosnia_and_Herzegovina +pub const BOSNIA_AND_HERZEGOVINA: Country = Country { code: *b"BA", rev: 0 }; +/// BW Botswana +pub const BOTSWANA: Country = Country { code: *b"BW", rev: 0 }; +/// BR Brazil +pub const BRAZIL: Country = Country { code: *b"BR", rev: 0 }; +/// IO British_Indian_Ocean_Territory +pub const BRITISH_INDIAN_OCEAN_TERRITORY: Country = Country { code: *b"IO", rev: 0 }; +/// BN Brunei_Darussalam +pub const BRUNEI_DARUSSALAM: Country = Country { code: *b"BN", rev: 0 }; +/// BG Bulgaria +pub const BULGARIA: Country = Country { code: *b"BG", rev: 0 }; +/// BF Burkina_Faso +pub const BURKINA_FASO: Country = Country { code: *b"BF", rev: 0 }; +/// BI Burundi +pub const BURUNDI: Country = Country { code: *b"BI", rev: 0 }; +/// KH Cambodia +pub const CAMBODIA: Country = Country { code: *b"KH", rev: 0 }; +/// CM Cameroon +pub const CAMEROON: Country = Country { code: *b"CM", rev: 0 }; +/// CA Canada +pub const CANADA: Country = Country { code: *b"CA", rev: 0 }; +/// CA Canada Revision 950 +pub const CANADA_REV950: Country = Country { code: *b"CA", rev: 950 }; +/// CV Cape_Verde +pub const CAPE_VERDE: Country = Country { code: *b"CV", rev: 0 }; +/// KY Cayman_Islands +pub const CAYMAN_ISLANDS: Country = Country { code: *b"KY", rev: 0 }; +/// CF Central_African_Republic +pub const CENTRAL_AFRICAN_REPUBLIC: Country = Country { code: *b"CF", rev: 0 }; +/// TD Chad +pub const CHAD: Country = Country { code: *b"TD", rev: 0 }; +/// CL Chile +pub const CHILE: Country = Country { code: *b"CL", rev: 0 }; +/// CN China +pub const CHINA: Country = Country { code: *b"CN", rev: 0 }; +/// CX Christmas_Island +pub const CHRISTMAS_ISLAND: Country = Country { code: *b"CX", rev: 0 }; +/// CO Colombia +pub const COLOMBIA: Country = Country { code: *b"CO", rev: 0 }; +/// KM Comoros +pub const COMOROS: Country = Country { code: *b"KM", rev: 0 }; +/// CG Congo +pub const CONGO: Country = Country { code: *b"CG", rev: 0 }; +/// CD Congo,_The_Democratic_Republic_Of_The +pub const CONGO_THE_DEMOCRATIC_REPUBLIC_OF_THE: Country = Country { code: *b"CD", rev: 0 }; +/// CR Costa_Rica +pub const COSTA_RICA: Country = Country { code: *b"CR", rev: 0 }; +/// CI Cote_D'ivoire +pub const COTE_DIVOIRE: Country = Country { code: *b"CI", rev: 0 }; +/// HR Croatia +pub const CROATIA: Country = Country { code: *b"HR", rev: 0 }; +/// CU Cuba +pub const CUBA: Country = Country { code: *b"CU", rev: 0 }; +/// CY Cyprus +pub const CYPRUS: Country = Country { code: *b"CY", rev: 0 }; +/// CZ Czech_Republic +pub const CZECH_REPUBLIC: Country = Country { code: *b"CZ", rev: 0 }; +/// DK Denmark +pub const DENMARK: Country = Country { code: *b"DK", rev: 0 }; +/// DJ Djibouti +pub const DJIBOUTI: Country = Country { code: *b"DJ", rev: 0 }; +/// DM Dominica +pub const DOMINICA: Country = Country { code: *b"DM", rev: 0 }; +/// DO Dominican_Republic +pub const DOMINICAN_REPUBLIC: Country = Country { code: *b"DO", rev: 0 }; +/// AU G'Day mate! +pub const DOWN_UNDER: Country = Country { code: *b"AU", rev: 0 }; +/// EC Ecuador +pub const ECUADOR: Country = Country { code: *b"EC", rev: 0 }; +/// EG Egypt +pub const EGYPT: Country = Country { code: *b"EG", rev: 0 }; +/// SV El_Salvador +pub const EL_SALVADOR: Country = Country { code: *b"SV", rev: 0 }; +/// GQ Equatorial_Guinea +pub const EQUATORIAL_GUINEA: Country = Country { code: *b"GQ", rev: 0 }; +/// ER Eritrea +pub const ERITREA: Country = Country { code: *b"ER", rev: 0 }; +/// EE Estonia +pub const ESTONIA: Country = Country { code: *b"EE", rev: 0 }; +/// ET Ethiopia +pub const ETHIOPIA: Country = Country { code: *b"ET", rev: 0 }; +/// FK Falkland_Islands_(Malvinas) +pub const FALKLAND_ISLANDS_MALVINAS: Country = Country { code: *b"FK", rev: 0 }; +/// FO Faroe_Islands +pub const FAROE_ISLANDS: Country = Country { code: *b"FO", rev: 0 }; +/// FJ Fiji +pub const FIJI: Country = Country { code: *b"FJ", rev: 0 }; +/// FI Finland +pub const FINLAND: Country = Country { code: *b"FI", rev: 0 }; +/// FR France +pub const FRANCE: Country = Country { code: *b"FR", rev: 0 }; +/// GF French_Guina +pub const FRENCH_GUINA: Country = Country { code: *b"GF", rev: 0 }; +/// PF French_Polynesia +pub const FRENCH_POLYNESIA: Country = Country { code: *b"PF", rev: 0 }; +/// TF French_Southern_Territories +pub const FRENCH_SOUTHERN_TERRITORIES: Country = Country { code: *b"TF", rev: 0 }; +/// GA Gabon +pub const GABON: Country = Country { code: *b"GA", rev: 0 }; +/// GM Gambia +pub const GAMBIA: Country = Country { code: *b"GM", rev: 0 }; +/// GE Georgia +pub const GEORGIA: Country = Country { code: *b"GE", rev: 0 }; +/// DE Germany +pub const GERMANY: Country = Country { code: *b"DE", rev: 0 }; +/// E0 European_Wide Revision 895 +pub const EUROPEAN_WIDE_REV895: Country = Country { code: *b"E0", rev: 895 }; +/// GH Ghana +pub const GHANA: Country = Country { code: *b"GH", rev: 0 }; +/// GI Gibraltar +pub const GIBRALTAR: Country = Country { code: *b"GI", rev: 0 }; +/// GR Greece +pub const GREECE: Country = Country { code: *b"GR", rev: 0 }; +/// GD Grenada +pub const GRENADA: Country = Country { code: *b"GD", rev: 0 }; +/// GP Guadeloupe +pub const GUADELOUPE: Country = Country { code: *b"GP", rev: 0 }; +/// GU Guam +pub const GUAM: Country = Country { code: *b"GU", rev: 0 }; +/// GT Guatemala +pub const GUATEMALA: Country = Country { code: *b"GT", rev: 0 }; +/// GG Guernsey +pub const GUERNSEY: Country = Country { code: *b"GG", rev: 0 }; +/// GN Guinea +pub const GUINEA: Country = Country { code: *b"GN", rev: 0 }; +/// GW Guinea-bissau +pub const GUINEA_BISSAU: Country = Country { code: *b"GW", rev: 0 }; +/// GY Guyana +pub const GUYANA: Country = Country { code: *b"GY", rev: 0 }; +/// HT Haiti +pub const HAITI: Country = Country { code: *b"HT", rev: 0 }; +/// VA Holy_See_(Vatican_City_State) +pub const HOLY_SEE_VATICAN_CITY_STATE: Country = Country { code: *b"VA", rev: 0 }; +/// HN Honduras +pub const HONDURAS: Country = Country { code: *b"HN", rev: 0 }; +/// HK Hong_Kong +pub const HONG_KONG: Country = Country { code: *b"HK", rev: 0 }; +/// HU Hungary +pub const HUNGARY: Country = Country { code: *b"HU", rev: 0 }; +/// IS Iceland +pub const ICELAND: Country = Country { code: *b"IS", rev: 0 }; +/// IN India +pub const INDIA: Country = Country { code: *b"IN", rev: 0 }; +/// ID Indonesia +pub const INDONESIA: Country = Country { code: *b"ID", rev: 0 }; +/// IR Iran,_Islamic_Republic_Of +pub const IRAN_ISLAMIC_REPUBLIC_OF: Country = Country { code: *b"IR", rev: 0 }; +/// IQ Iraq +pub const IRAQ: Country = Country { code: *b"IQ", rev: 0 }; +/// IE Ireland +pub const IRELAND: Country = Country { code: *b"IE", rev: 0 }; +/// IL Israel +pub const ISRAEL: Country = Country { code: *b"IL", rev: 0 }; +/// IT Italy +pub const ITALY: Country = Country { code: *b"IT", rev: 0 }; +/// JM Jamaica +pub const JAMAICA: Country = Country { code: *b"JM", rev: 0 }; +/// JP Japan +pub const JAPAN: Country = Country { code: *b"JP", rev: 0 }; +/// JE Jersey +pub const JERSEY: Country = Country { code: *b"JE", rev: 0 }; +/// JO Jordan +pub const JORDAN: Country = Country { code: *b"JO", rev: 0 }; +/// KZ Kazakhstan +pub const KAZAKHSTAN: Country = Country { code: *b"KZ", rev: 0 }; +/// KE Kenya +pub const KENYA: Country = Country { code: *b"KE", rev: 0 }; +/// KI Kiribati +pub const KIRIBATI: Country = Country { code: *b"KI", rev: 0 }; +/// KR Korea,_Republic_Of +pub const KOREA_REPUBLIC_OF: Country = Country { code: *b"KR", rev: 1 }; +/// 0A Kosovo +pub const KOSOVO: Country = Country { code: *b"0A", rev: 0 }; +/// KW Kuwait +pub const KUWAIT: Country = Country { code: *b"KW", rev: 0 }; +/// KG Kyrgyzstan +pub const KYRGYZSTAN: Country = Country { code: *b"KG", rev: 0 }; +/// LA Lao_People's_Democratic_Repubic +pub const LAO_PEOPLES_DEMOCRATIC_REPUBIC: Country = Country { code: *b"LA", rev: 0 }; +/// LV Latvia +pub const LATVIA: Country = Country { code: *b"LV", rev: 0 }; +/// LB Lebanon +pub const LEBANON: Country = Country { code: *b"LB", rev: 0 }; +/// LS Lesotho +pub const LESOTHO: Country = Country { code: *b"LS", rev: 0 }; +/// LR Liberia +pub const LIBERIA: Country = Country { code: *b"LR", rev: 0 }; +/// LY Libyan_Arab_Jamahiriya +pub const LIBYAN_ARAB_JAMAHIRIYA: Country = Country { code: *b"LY", rev: 0 }; +/// LI Liechtenstein +pub const LIECHTENSTEIN: Country = Country { code: *b"LI", rev: 0 }; +/// LT Lithuania +pub const LITHUANIA: Country = Country { code: *b"LT", rev: 0 }; +/// LU Luxembourg +pub const LUXEMBOURG: Country = Country { code: *b"LU", rev: 0 }; +/// MO Macao +pub const MACAO: Country = Country { code: *b"MO", rev: 0 }; +/// MK Macedonia,_Former_Yugoslav_Republic_Of +pub const MACEDONIA_FORMER_YUGOSLAV_REPUBLIC_OF: Country = Country { code: *b"MK", rev: 0 }; +/// MG Madagascar +pub const MADAGASCAR: Country = Country { code: *b"MG", rev: 0 }; +/// MW Malawi +pub const MALAWI: Country = Country { code: *b"MW", rev: 0 }; +/// MY Malaysia +pub const MALAYSIA: Country = Country { code: *b"MY", rev: 0 }; +/// MV Maldives +pub const MALDIVES: Country = Country { code: *b"MV", rev: 0 }; +/// ML Mali +pub const MALI: Country = Country { code: *b"ML", rev: 0 }; +/// MT Malta +pub const MALTA: Country = Country { code: *b"MT", rev: 0 }; +/// IM Man,_Isle_Of +pub const MAN_ISLE_OF: Country = Country { code: *b"IM", rev: 0 }; +/// MQ Martinique +pub const MARTINIQUE: Country = Country { code: *b"MQ", rev: 0 }; +/// MR Mauritania +pub const MAURITANIA: Country = Country { code: *b"MR", rev: 0 }; +/// MU Mauritius +pub const MAURITIUS: Country = Country { code: *b"MU", rev: 0 }; +/// YT Mayotte +pub const MAYOTTE: Country = Country { code: *b"YT", rev: 0 }; +/// MX Mexico +pub const MEXICO: Country = Country { code: *b"MX", rev: 0 }; +/// FM Micronesia,_Federated_States_Of +pub const MICRONESIA_FEDERATED_STATES_OF: Country = Country { code: *b"FM", rev: 0 }; +/// MD Moldova,_Republic_Of +pub const MOLDOVA_REPUBLIC_OF: Country = Country { code: *b"MD", rev: 0 }; +/// MC Monaco +pub const MONACO: Country = Country { code: *b"MC", rev: 0 }; +/// MN Mongolia +pub const MONGOLIA: Country = Country { code: *b"MN", rev: 0 }; +/// ME Montenegro +pub const MONTENEGRO: Country = Country { code: *b"ME", rev: 0 }; +/// MS Montserrat +pub const MONTSERRAT: Country = Country { code: *b"MS", rev: 0 }; +/// MA Morocco +pub const MOROCCO: Country = Country { code: *b"MA", rev: 0 }; +/// MZ Mozambique +pub const MOZAMBIQUE: Country = Country { code: *b"MZ", rev: 0 }; +/// MM Myanmar +pub const MYANMAR: Country = Country { code: *b"MM", rev: 0 }; +/// NA Namibia +pub const NAMIBIA: Country = Country { code: *b"NA", rev: 0 }; +/// NR Nauru +pub const NAURU: Country = Country { code: *b"NR", rev: 0 }; +/// NP Nepal +pub const NEPAL: Country = Country { code: *b"NP", rev: 0 }; +/// NL Netherlands +pub const NETHERLANDS: Country = Country { code: *b"NL", rev: 0 }; +/// AN Netherlands_Antilles +pub const NETHERLANDS_ANTILLES: Country = Country { code: *b"AN", rev: 0 }; +/// NC New_Caledonia +pub const NEW_CALEDONIA: Country = Country { code: *b"NC", rev: 0 }; +/// NZ New_Zealand +pub const NEW_ZEALAND: Country = Country { code: *b"NZ", rev: 0 }; +/// NI Nicaragua +pub const NICARAGUA: Country = Country { code: *b"NI", rev: 0 }; +/// NE Niger +pub const NIGER: Country = Country { code: *b"NE", rev: 0 }; +/// NG Nigeria +pub const NIGERIA: Country = Country { code: *b"NG", rev: 0 }; +/// NF Norfolk_Island +pub const NORFOLK_ISLAND: Country = Country { code: *b"NF", rev: 0 }; +/// MP Northern_Mariana_Islands +pub const NORTHERN_MARIANA_ISLANDS: Country = Country { code: *b"MP", rev: 0 }; +/// NO Norway +pub const NORWAY: Country = Country { code: *b"NO", rev: 0 }; +/// OM Oman +pub const OMAN: Country = Country { code: *b"OM", rev: 0 }; +/// PK Pakistan +pub const PAKISTAN: Country = Country { code: *b"PK", rev: 0 }; +/// PW Palau +pub const PALAU: Country = Country { code: *b"PW", rev: 0 }; +/// PA Panama +pub const PANAMA: Country = Country { code: *b"PA", rev: 0 }; +/// PG Papua_New_Guinea +pub const PAPUA_NEW_GUINEA: Country = Country { code: *b"PG", rev: 0 }; +/// PY Paraguay +pub const PARAGUAY: Country = Country { code: *b"PY", rev: 0 }; +/// PE Peru +pub const PERU: Country = Country { code: *b"PE", rev: 0 }; +/// PH Philippines +pub const PHILIPPINES: Country = Country { code: *b"PH", rev: 0 }; +/// PL Poland +pub const POLAND: Country = Country { code: *b"PL", rev: 0 }; +/// PT Portugal +pub const PORTUGAL: Country = Country { code: *b"PT", rev: 0 }; +/// PR Pueto_Rico +pub const PUETO_RICO: Country = Country { code: *b"PR", rev: 0 }; +/// QA Qatar +pub const QATAR: Country = Country { code: *b"QA", rev: 0 }; +/// RE Reunion +pub const REUNION: Country = Country { code: *b"RE", rev: 0 }; +/// RO Romania +pub const ROMANIA: Country = Country { code: *b"RO", rev: 0 }; +/// RU Russian_Federation +pub const RUSSIAN_FEDERATION: Country = Country { code: *b"RU", rev: 0 }; +/// RW Rwanda +pub const RWANDA: Country = Country { code: *b"RW", rev: 0 }; +/// KN Saint_Kitts_and_Nevis +pub const SAINT_KITTS_AND_NEVIS: Country = Country { code: *b"KN", rev: 0 }; +/// LC Saint_Lucia +pub const SAINT_LUCIA: Country = Country { code: *b"LC", rev: 0 }; +/// PM Saint_Pierre_and_Miquelon +pub const SAINT_PIERRE_AND_MIQUELON: Country = Country { code: *b"PM", rev: 0 }; +/// VC Saint_Vincent_and_The_Grenadines +pub const SAINT_VINCENT_AND_THE_GRENADINES: Country = Country { code: *b"VC", rev: 0 }; +/// WS Samoa +pub const SAMOA: Country = Country { code: *b"WS", rev: 0 }; +/// MF Sanit_Martin_/_Sint_Marteen +pub const SANIT_MARTIN_SINT_MARTEEN: Country = Country { code: *b"MF", rev: 0 }; +/// ST Sao_Tome_and_Principe +pub const SAO_TOME_AND_PRINCIPE: Country = Country { code: *b"ST", rev: 0 }; +/// SA Saudi_Arabia +pub const SAUDI_ARABIA: Country = Country { code: *b"SA", rev: 0 }; +/// SN Senegal +pub const SENEGAL: Country = Country { code: *b"SN", rev: 0 }; +/// RS Serbia +pub const SERBIA: Country = Country { code: *b"RS", rev: 0 }; +/// SC Seychelles +pub const SEYCHELLES: Country = Country { code: *b"SC", rev: 0 }; +/// SL Sierra_Leone +pub const SIERRA_LEONE: Country = Country { code: *b"SL", rev: 0 }; +/// SG Singapore +pub const SINGAPORE: Country = Country { code: *b"SG", rev: 0 }; +/// SK Slovakia +pub const SLOVAKIA: Country = Country { code: *b"SK", rev: 0 }; +/// SI Slovenia +pub const SLOVENIA: Country = Country { code: *b"SI", rev: 0 }; +/// SB Solomon_Islands +pub const SOLOMON_ISLANDS: Country = Country { code: *b"SB", rev: 0 }; +/// SO Somalia +pub const SOMALIA: Country = Country { code: *b"SO", rev: 0 }; +/// ZA South_Africa +pub const SOUTH_AFRICA: Country = Country { code: *b"ZA", rev: 0 }; +/// ES Spain +pub const SPAIN: Country = Country { code: *b"ES", rev: 0 }; +/// LK Sri_Lanka +pub const SRI_LANKA: Country = Country { code: *b"LK", rev: 0 }; +/// SR Suriname +pub const SURINAME: Country = Country { code: *b"SR", rev: 0 }; +/// SZ Swaziland +pub const SWAZILAND: Country = Country { code: *b"SZ", rev: 0 }; +/// SE Sweden +pub const SWEDEN: Country = Country { code: *b"SE", rev: 0 }; +/// CH Switzerland +pub const SWITZERLAND: Country = Country { code: *b"CH", rev: 0 }; +/// SY Syrian_Arab_Republic +pub const SYRIAN_ARAB_REPUBLIC: Country = Country { code: *b"SY", rev: 0 }; +/// TW Taiwan,_Province_Of_China +pub const TAIWAN_PROVINCE_OF_CHINA: Country = Country { code: *b"TW", rev: 0 }; +/// TJ Tajikistan +pub const TAJIKISTAN: Country = Country { code: *b"TJ", rev: 0 }; +/// TZ Tanzania,_United_Republic_Of +pub const TANZANIA_UNITED_REPUBLIC_OF: Country = Country { code: *b"TZ", rev: 0 }; +/// TH Thailand +pub const THAILAND: Country = Country { code: *b"TH", rev: 0 }; +/// TG Togo +pub const TOGO: Country = Country { code: *b"TG", rev: 0 }; +/// TO Tonga +pub const TONGA: Country = Country { code: *b"TO", rev: 0 }; +/// TT Trinidad_and_Tobago +pub const TRINIDAD_AND_TOBAGO: Country = Country { code: *b"TT", rev: 0 }; +/// TN Tunisia +pub const TUNISIA: Country = Country { code: *b"TN", rev: 0 }; +/// TR Turkey +pub const TURKEY: Country = Country { code: *b"TR", rev: 0 }; +/// TM Turkmenistan +pub const TURKMENISTAN: Country = Country { code: *b"TM", rev: 0 }; +/// TC Turks_and_Caicos_Islands +pub const TURKS_AND_CAICOS_ISLANDS: Country = Country { code: *b"TC", rev: 0 }; +/// TV Tuvalu +pub const TUVALU: Country = Country { code: *b"TV", rev: 0 }; +/// UG Uganda +pub const UGANDA: Country = Country { code: *b"UG", rev: 0 }; +/// UA Ukraine +pub const UKRAINE: Country = Country { code: *b"UA", rev: 0 }; +/// AE United_Arab_Emirates +pub const UNITED_ARAB_EMIRATES: Country = Country { code: *b"AE", rev: 0 }; +/// GB United_Kingdom +pub const UNITED_KINGDOM: Country = Country { code: *b"GB", rev: 0 }; +/// US United_States +pub const UNITED_STATES: Country = Country { code: *b"US", rev: 0 }; +/// US United_States Revision 4 +pub const UNITED_STATES_REV4: Country = Country { code: *b"US", rev: 4 }; +/// Q1 United_States Revision 931 +pub const UNITED_STATES_REV931: Country = Country { code: *b"Q1", rev: 931 }; +/// Q2 United_States_(No_DFS) +pub const UNITED_STATES_NO_DFS: Country = Country { code: *b"Q2", rev: 0 }; +/// UM United_States_Minor_Outlying_Islands +pub const UNITED_STATES_MINOR_OUTLYING_ISLANDS: Country = Country { code: *b"UM", rev: 0 }; +/// UY Uruguay +pub const URUGUAY: Country = Country { code: *b"UY", rev: 0 }; +/// UZ Uzbekistan +pub const UZBEKISTAN: Country = Country { code: *b"UZ", rev: 0 }; +/// VU Vanuatu +pub const VANUATU: Country = Country { code: *b"VU", rev: 0 }; +/// VE Venezuela +pub const VENEZUELA: Country = Country { code: *b"VE", rev: 0 }; +/// VN Viet_Nam +pub const VIET_NAM: Country = Country { code: *b"VN", rev: 0 }; +/// VG Virgin_Islands,_British +pub const VIRGIN_ISLANDS_BRITISH: Country = Country { code: *b"VG", rev: 0 }; +/// VI Virgin_Islands,_U.S. +pub const VIRGIN_ISLANDS_US: Country = Country { code: *b"VI", rev: 0 }; +/// WF Wallis_and_Futuna +pub const WALLIS_AND_FUTUNA: Country = Country { code: *b"WF", rev: 0 }; +/// 0C West_Bank +pub const WEST_BANK: Country = Country { code: *b"0C", rev: 0 }; +/// EH Western_Sahara +pub const WESTERN_SAHARA: Country = Country { code: *b"EH", rev: 0 }; +/// Worldwide Locale Revision 983 +pub const WORLD_WIDE_XV_REV983: Country = Country { code: *b"XV", rev: 983 }; +/// Worldwide Locale (passive Ch12-14) +pub const WORLD_WIDE_XX: Country = Country { code: *b"XX", rev: 0 }; +/// Worldwide Locale (passive Ch12-14) Revision 17 +pub const WORLD_WIDE_XX_REV17: Country = Country { code: *b"XX", rev: 17 }; +/// YE Yemen +pub const YEMEN: Country = Country { code: *b"YE", rev: 0 }; +/// ZM Zambia +pub const ZAMBIA: Country = Country { code: *b"ZM", rev: 0 }; +/// ZW Zimbabwe +pub const ZIMBABWE: Country = Country { code: *b"ZW", rev: 0 }; diff --git a/embassy/cyw43/src/events.rs b/embassy/cyw43/src/events.rs new file mode 100644 index 0000000..44bfa98 --- /dev/null +++ b/embassy/cyw43/src/events.rs @@ -0,0 +1,400 @@ +#![allow(dead_code)] +#![allow(non_camel_case_types)] + +use core::cell::RefCell; + +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pubsub::{PubSubChannel, Subscriber}; + +use crate::structs::BssInfo; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum Event { + #[num_enum(default)] + Unknown = 0xFF, + /// indicates status of set SSID + SET_SSID = 0, + /// differentiates join IBSS from found (START) IBSS + JOIN = 1, + /// STA founded an IBSS or AP started a BSS + START = 2, + /// 802.11 AUTH request + AUTH = 3, + /// 802.11 AUTH indication + AUTH_IND = 4, + /// 802.11 DEAUTH request + DEAUTH = 5, + /// 802.11 DEAUTH indication + DEAUTH_IND = 6, + /// 802.11 ASSOC request + ASSOC = 7, + /// 802.11 ASSOC indication + ASSOC_IND = 8, + /// 802.11 REASSOC request + REASSOC = 9, + /// 802.11 REASSOC indication + REASSOC_IND = 10, + /// 802.11 DISASSOC request + DISASSOC = 11, + /// 802.11 DISASSOC indication + DISASSOC_IND = 12, + /// 802.11h Quiet period started + QUIET_START = 13, + /// 802.11h Quiet period ended + QUIET_END = 14, + /// BEACONS received/lost indication + BEACON_RX = 15, + /// generic link indication + LINK = 16, + /// TKIP MIC error occurred + MIC_ERROR = 17, + /// NDIS style link indication + NDIS_LINK = 18, + /// roam attempt occurred: indicate status & reason + ROAM = 19, + /// change in dot11FailedCount (txfail) + TXFAIL = 20, + /// WPA2 pmkid cache indication + PMKID_CACHE = 21, + /// current AP's TSF value went backward + RETROGRADE_TSF = 22, + /// AP was pruned from join list for reason + PRUNE = 23, + /// report AutoAuth table entry match for join attempt + AUTOAUTH = 24, + /// Event encapsulating an EAPOL message + EAPOL_MSG = 25, + /// Scan results are ready or scan was aborted + SCAN_COMPLETE = 26, + /// indicate to host addts fail/success + ADDTS_IND = 27, + /// indicate to host delts fail/success + DELTS_IND = 28, + /// indicate to host of beacon transmit + BCNSENT_IND = 29, + /// Send the received beacon up to the host + BCNRX_MSG = 30, + /// indicate to host loss of beacon + BCNLOST_MSG = 31, + /// before attempting to roam + ROAM_PREP = 32, + /// PFN network found event + PFN_NET_FOUND = 33, + /// PFN network lost event + PFN_NET_LOST = 34, + RESET_COMPLETE = 35, + JOIN_START = 36, + ROAM_START = 37, + ASSOC_START = 38, + IBSS_ASSOC = 39, + RADIO = 40, + /// PSM microcode watchdog fired + PSM_WATCHDOG = 41, + /// CCX association start + CCX_ASSOC_START = 42, + /// CCX association abort + CCX_ASSOC_ABORT = 43, + /// probe request received + PROBREQ_MSG = 44, + SCAN_CONFIRM_IND = 45, + /// WPA Handshake + PSK_SUP = 46, + COUNTRY_CODE_CHANGED = 47, + /// WMMAC excedded medium time + EXCEEDED_MEDIUM_TIME = 48, + /// WEP ICV error occurred + ICV_ERROR = 49, + /// Unsupported unicast encrypted frame + UNICAST_DECODE_ERROR = 50, + /// Unsupported multicast encrypted frame + MULTICAST_DECODE_ERROR = 51, + TRACE = 52, + /// BT-AMP HCI event + BTA_HCI_EVENT = 53, + /// I/F change (for wlan host notification) + IF = 54, + /// P2P Discovery listen state expires + P2P_DISC_LISTEN_COMPLETE = 55, + /// indicate RSSI change based on configured levels + RSSI = 56, + /// PFN best network batching event + PFN_BEST_BATCHING = 57, + EXTLOG_MSG = 58, + /// Action frame reception + ACTION_FRAME = 59, + /// Action frame Tx complete + ACTION_FRAME_COMPLETE = 60, + /// assoc request received + PRE_ASSOC_IND = 61, + /// re-assoc request received + PRE_REASSOC_IND = 62, + /// channel adopted (xxx: obsoleted) + CHANNEL_ADOPTED = 63, + /// AP started + AP_STARTED = 64, + /// AP stopped due to DFS + DFS_AP_STOP = 65, + /// AP resumed due to DFS + DFS_AP_RESUME = 66, + /// WAI stations event + WAI_STA_EVENT = 67, + /// event encapsulating an WAI message + WAI_MSG = 68, + /// escan result event + ESCAN_RESULT = 69, + /// action frame off channel complete + ACTION_FRAME_OFF_CHAN_COMPLETE = 70, + /// probe response received + PROBRESP_MSG = 71, + /// P2P Probe request received + P2P_PROBREQ_MSG = 72, + DCS_REQUEST = 73, + /// credits for D11 FIFOs. [AC0,AC1,AC2,AC3,BC_MC,ATIM] + FIFO_CREDIT_MAP = 74, + /// Received action frame event WITH wl_event_rx_frame_data_t header + ACTION_FRAME_RX = 75, + /// Wake Event timer fired, used for wake WLAN test mode + WAKE_EVENT = 76, + /// Radio measurement complete + RM_COMPLETE = 77, + /// Synchronize TSF with the host + HTSFSYNC = 78, + /// request an overlay IOCTL/iovar from the host + OVERLAY_REQ = 79, + CSA_COMPLETE_IND = 80, + /// excess PM Wake Event to inform host + EXCESS_PM_WAKE_EVENT = 81, + /// no PFN networks around + PFN_SCAN_NONE = 82, + /// last found PFN network gets lost + PFN_SCAN_ALLGONE = 83, + GTK_PLUMBED = 84, + /// 802.11 ASSOC indication for NDIS only + ASSOC_IND_NDIS = 85, + /// 802.11 REASSOC indication for NDIS only + REASSOC_IND_NDIS = 86, + ASSOC_REQ_IE = 87, + ASSOC_RESP_IE = 88, + /// association recreated on resume + ASSOC_RECREATED = 89, + /// rx action frame event for NDIS only + ACTION_FRAME_RX_NDIS = 90, + /// authentication request received + AUTH_REQ = 91, + /// fast assoc recreation failed + SPEEDY_RECREATE_FAIL = 93, + /// port-specific event and payload (e.g. NDIS) + NATIVE = 94, + /// event for tx pkt delay suddently jump + PKTDELAY_IND = 95, + /// AWDL AW period starts + AWDL_AW = 96, + /// AWDL Master/Slave/NE master role event + AWDL_ROLE = 97, + /// Generic AWDL event + AWDL_EVENT = 98, + /// NIC AF txstatus + NIC_AF_TXS = 99, + /// NAN event + NAN = 100, + BEACON_FRAME_RX = 101, + /// desired service found + SERVICE_FOUND = 102, + /// GAS fragment received + GAS_FRAGMENT_RX = 103, + /// GAS sessions all complete + GAS_COMPLETE = 104, + /// New device found by p2p offload + P2PO_ADD_DEVICE = 105, + /// device has been removed by p2p offload + P2PO_DEL_DEVICE = 106, + /// WNM event to notify STA enter sleep mode + WNM_STA_SLEEP = 107, + /// Indication of MAC tx failures (exhaustion of 802.11 retries) exceeding threshold(s) + TXFAIL_THRESH = 108, + /// Proximity Detection event + PROXD = 109, + /// AWDL RX Probe response + AWDL_RX_PRB_RESP = 111, + /// AWDL RX Action Frames + AWDL_RX_ACT_FRAME = 112, + /// AWDL Wowl nulls + AWDL_WOWL_NULLPKT = 113, + /// AWDL Phycal status + AWDL_PHYCAL_STATUS = 114, + /// AWDL OOB AF status + AWDL_OOB_AF_STATUS = 115, + /// Interleaved Scan status + AWDL_SCAN_STATUS = 116, + /// AWDL AW Start + AWDL_AW_START = 117, + /// AWDL AW End + AWDL_AW_END = 118, + /// AWDL AW Extensions + AWDL_AW_EXT = 119, + AWDL_PEER_CACHE_CONTROL = 120, + CSA_START_IND = 121, + CSA_DONE_IND = 122, + CSA_FAILURE_IND = 123, + /// CCA based channel quality report + CCA_CHAN_QUAL = 124, + /// to report change in BSSID while roaming + BSSID = 125, + /// tx error indication + TX_STAT_ERROR = 126, + /// credit check for BCMC supported + BCMC_CREDIT_SUPPORT = 127, + /// psta primary interface indication + PSTA_PRIMARY_INTF_IND = 128, + /// Handover Request Initiated + BT_WIFI_HANDOVER_REQ = 130, + /// Southpaw TxInhibit notification + SPW_TXINHIBIT = 131, + /// FBT Authentication Request Indication + FBT_AUTH_REQ_IND = 132, + /// Enhancement addition for RSSI + RSSI_LQM = 133, + /// Full probe/beacon (IEs etc) results + PFN_GSCAN_FULL_RESULT = 134, + /// Significant change in rssi of bssids being tracked + PFN_SWC = 135, + /// a STA been authroized for traffic + AUTHORIZED = 136, + /// probe req with wl_event_rx_frame_data_t header + PROBREQ_MSG_RX = 137, + /// PFN completed scan of network list + PFN_SCAN_COMPLETE = 138, + /// RMC Event + RMC_EVENT = 139, + /// DPSTA interface indication + DPSTA_INTF_IND = 140, + /// RRM Event + RRM = 141, + /// ULP entry event + ULP = 146, + /// TCP Keep Alive Offload Event + TKO = 151, + /// authentication request received + EXT_AUTH_REQ = 187, + /// authentication request received + EXT_AUTH_FRAME_RX = 188, + /// mgmt frame Tx complete + MGMT_FRAME_TXSTATUS = 189, + /// highest val + 1 for range checking + LAST = 190, +} + +// TODO this PubSub can probably be replaced with shared memory to make it a bit more efficient. +pub type EventQueue = PubSubChannel; +pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, Message, 2, 1, 1>; + +pub struct Events { + pub queue: EventQueue, + pub mask: SharedEventMask, +} + +impl Events { + pub fn new() -> Self { + Self { + queue: EventQueue::new(), + mask: SharedEventMask::default(), + } + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Status { + pub event_type: Event, + pub status: u32, +} + +#[derive(Copy, Clone)] +pub enum Payload { + None, + BssInfo(BssInfo), +} + +#[derive(Copy, Clone)] + +pub struct Message { + pub header: Status, + pub payload: Payload, +} + +impl Message { + pub fn new(status: Status, payload: Payload) -> Self { + Self { + header: status, + payload, + } + } +} + +#[derive(Default)] +struct EventMask { + mask: [u32; Self::WORD_COUNT], +} + +impl EventMask { + const WORD_COUNT: usize = ((Event::LAST as u32 + (u32::BITS - 1)) / u32::BITS) as usize; + + fn enable(&mut self, event: Event) { + let n = event as u32; + let word = n / u32::BITS; + let bit = n % u32::BITS; + + self.mask[word as usize] |= 1 << bit; + } + + fn disable(&mut self, event: Event) { + let n = event as u32; + let word = n / u32::BITS; + let bit = n % u32::BITS; + + self.mask[word as usize] &= !(1 << bit); + } + + fn is_enabled(&self, event: Event) -> bool { + let n = event as u32; + let word = n / u32::BITS; + let bit = n % u32::BITS; + + self.mask[word as usize] & (1 << bit) > 0 + } +} + +#[derive(Default)] + +pub struct SharedEventMask { + mask: RefCell, +} + +impl SharedEventMask { + pub fn enable(&self, events: &[Event]) { + let mut mask = self.mask.borrow_mut(); + for event in events { + mask.enable(*event); + } + } + + #[allow(dead_code)] + pub fn disable(&self, events: &[Event]) { + let mut mask = self.mask.borrow_mut(); + for event in events { + mask.disable(*event); + } + } + + pub fn disable_all(&self) { + let mut mask = self.mask.borrow_mut(); + mask.mask = Default::default(); + } + + pub fn is_enabled(&self, event: Event) -> bool { + let mask = self.mask.borrow(); + mask.is_enabled(event) + } +} diff --git a/embassy/cyw43/src/fmt.rs b/embassy/cyw43/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/cyw43/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/cyw43/src/ioctl.rs b/embassy/cyw43/src/ioctl.rs new file mode 100644 index 0000000..f8b2d9a --- /dev/null +++ b/embassy/cyw43/src/ioctl.rs @@ -0,0 +1,127 @@ +use core::cell::{Cell, RefCell}; +use core::future::poll_fn; +use core::task::{Poll, Waker}; + +use embassy_sync::waitqueue::WakerRegistration; + +use crate::consts::Ioctl; +use crate::fmt::Bytes; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum IoctlType { + Get = 0, + Set = 2, +} + +#[derive(Clone, Copy)] +pub struct PendingIoctl { + pub buf: *mut [u8], + pub kind: IoctlType, + pub cmd: Ioctl, + pub iface: u32, +} + +#[derive(Clone, Copy)] +enum IoctlStateInner { + Pending(PendingIoctl), + Sent { buf: *mut [u8] }, + Done { resp_len: usize }, +} + +struct Wakers { + control: WakerRegistration, + runner: WakerRegistration, +} + +impl Default for Wakers { + fn default() -> Self { + Self { + control: WakerRegistration::new(), + runner: WakerRegistration::new(), + } + } +} + +pub struct IoctlState { + state: Cell, + wakers: RefCell, +} + +impl IoctlState { + pub fn new() -> Self { + Self { + state: Cell::new(IoctlStateInner::Done { resp_len: 0 }), + wakers: Default::default(), + } + } + + fn wake_control(&self) { + self.wakers.borrow_mut().control.wake(); + } + + fn register_control(&self, waker: &Waker) { + self.wakers.borrow_mut().control.register(waker); + } + + fn wake_runner(&self) { + self.wakers.borrow_mut().runner.wake(); + } + + fn register_runner(&self, waker: &Waker) { + self.wakers.borrow_mut().runner.register(waker); + } + + pub async fn wait_complete(&self) -> usize { + poll_fn(|cx| { + if let IoctlStateInner::Done { resp_len } = self.state.get() { + Poll::Ready(resp_len) + } else { + self.register_control(cx.waker()); + Poll::Pending + } + }) + .await + } + + pub async fn wait_pending(&self) -> PendingIoctl { + let pending = poll_fn(|cx| { + if let IoctlStateInner::Pending(pending) = self.state.get() { + Poll::Ready(pending) + } else { + self.register_runner(cx.waker()); + Poll::Pending + } + }) + .await; + + self.state.set(IoctlStateInner::Sent { buf: pending.buf }); + pending + } + + pub fn cancel_ioctl(&self) { + self.state.set(IoctlStateInner::Done { resp_len: 0 }); + } + + pub async fn do_ioctl(&self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { + self.state + .set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface })); + self.wake_runner(); + self.wait_complete().await + } + + pub fn ioctl_done(&self, response: &[u8]) { + if let IoctlStateInner::Sent { buf } = self.state.get() { + trace!("IOCTL Response: {:02x}", Bytes(response)); + + // TODO fix this + (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); + + self.state.set(IoctlStateInner::Done { + resp_len: response.len(), + }); + self.wake_control(); + } else { + warn!("IOCTL Response but no pending Ioctl"); + } + } +} diff --git a/embassy/cyw43/src/lib.rs b/embassy/cyw43/src/lib.rs new file mode 100644 index 0000000..3cd0e49 --- /dev/null +++ b/embassy/cyw43/src/lib.rs @@ -0,0 +1,299 @@ +#![no_std] +#![no_main] +#![allow(async_fn_in_trait)] +#![deny(unused_must_use)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +#[cfg(feature = "bluetooth")] +/// Bluetooth module. +pub mod bluetooth; +mod bus; +mod consts; +mod control; +mod countries; +mod events; +mod ioctl; +mod nvram; +mod runner; +mod structs; +mod util; + +use embassy_net_driver_channel as ch; +use embedded_hal_1::digital::OutputPin; +use events::Events; +use ioctl::IoctlState; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +pub use crate::control::{ + AddMulticastAddressError, Control, Error as ControlError, JoinAuth, JoinOptions, ScanOptions, Scanner, +}; +pub use crate::runner::Runner; +pub use crate::structs::BssInfo; + +const MTU: usize = 1514; + +#[allow(unused)] +#[derive(Clone, Copy, PartialEq, Eq)] +enum Core { + WLAN = 0, + SOCSRAM = 1, + SDIOD = 2, +} + +impl Core { + fn base_addr(&self) -> u32 { + match self { + Self::WLAN => CHIP.arm_core_base_address, + Self::SOCSRAM => CHIP.socsram_wrapper_base_address, + Self::SDIOD => CHIP.sdiod_core_base_address, + } + } +} + +#[allow(unused)] +struct Chip { + arm_core_base_address: u32, + socsram_base_address: u32, + bluetooth_base_address: u32, + socsram_wrapper_base_address: u32, + sdiod_core_base_address: u32, + pmu_base_address: u32, + chip_ram_size: u32, + atcm_ram_base_address: u32, + socram_srmem_size: u32, + chanspec_band_mask: u32, + chanspec_band_2g: u32, + chanspec_band_5g: u32, + chanspec_band_shift: u32, + chanspec_bw_10: u32, + chanspec_bw_20: u32, + chanspec_bw_40: u32, + chanspec_bw_mask: u32, + chanspec_bw_shift: u32, + chanspec_ctl_sb_lower: u32, + chanspec_ctl_sb_upper: u32, + chanspec_ctl_sb_none: u32, + chanspec_ctl_sb_mask: u32, +} + +const WRAPPER_REGISTER_OFFSET: u32 = 0x100000; + +// Data for CYW43439 +const CHIP: Chip = Chip { + arm_core_base_address: 0x18003000 + WRAPPER_REGISTER_OFFSET, + socsram_base_address: 0x18004000, + bluetooth_base_address: 0x19000000, + socsram_wrapper_base_address: 0x18004000 + WRAPPER_REGISTER_OFFSET, + sdiod_core_base_address: 0x18002000, + pmu_base_address: 0x18000000, + chip_ram_size: 512 * 1024, + atcm_ram_base_address: 0, + socram_srmem_size: 64 * 1024, + chanspec_band_mask: 0xc000, + chanspec_band_2g: 0x0000, + chanspec_band_5g: 0xc000, + chanspec_band_shift: 14, + chanspec_bw_10: 0x0800, + chanspec_bw_20: 0x1000, + chanspec_bw_40: 0x1800, + chanspec_bw_mask: 0x3800, + chanspec_bw_shift: 11, + chanspec_ctl_sb_lower: 0x0000, + chanspec_ctl_sb_upper: 0x0100, + chanspec_ctl_sb_none: 0x0000, + chanspec_ctl_sb_mask: 0x0700, +}; + +/// Driver state. +pub struct State { + ioctl_state: IoctlState, + net: NetState, + #[cfg(feature = "bluetooth")] + bt: bluetooth::BtState, +} + +struct NetState { + ch: ch::State, + events: Events, +} + +impl State { + /// Create new driver state holder. + pub fn new() -> Self { + Self { + ioctl_state: IoctlState::new(), + net: NetState { + ch: ch::State::new(), + events: Events::new(), + }, + #[cfg(feature = "bluetooth")] + bt: bluetooth::BtState::new(), + } + } +} + +/// Power management modes. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PowerManagementMode { + /// Custom, officially unsupported mode. Use at your own risk. + /// All power-saving features set to their max at only a marginal decrease in power consumption + /// as oppposed to `Aggressive`. + SuperSave, + + /// Aggressive power saving mode. + Aggressive, + + /// The default mode. + PowerSave, + + /// Performance is prefered over power consumption but still some power is conserved as opposed to + /// `None`. + Performance, + + /// Unlike all the other PM modes, this lowers the power consumption at all times at the cost of + /// a much lower throughput. + ThroughputThrottling, + + /// No power management is configured. This consumes the most power. + None, +} + +impl Default for PowerManagementMode { + fn default() -> Self { + Self::PowerSave + } +} + +impl PowerManagementMode { + fn sleep_ret_ms(&self) -> u16 { + match self { + PowerManagementMode::SuperSave => 2000, + PowerManagementMode::Aggressive => 2000, + PowerManagementMode::PowerSave => 200, + PowerManagementMode::Performance => 20, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn beacon_period(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 1, + PowerManagementMode::PowerSave => 1, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn dtim_period(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 1, + PowerManagementMode::PowerSave => 1, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn assoc(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 10, + PowerManagementMode::PowerSave => 10, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn mode(&self) -> u32 { + match self { + PowerManagementMode::ThroughputThrottling => 1, + PowerManagementMode::None => 0, + _ => 2, + } + } +} + +/// Embassy-net driver. +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +/// Create a new instance of the CYW43 driver. +/// +/// Returns a handle to the network device, control handle and a runner for driving the low level +/// stack. +pub async fn new<'a, PWR, SPI>( + state: &'a mut State, + pwr: PWR, + spi: SPI, + firmware: &[u8], +) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>) +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + let (ch_runner, device) = ch::new(&mut state.net.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); + let state_ch = ch_runner.state_runner(); + + let mut runner = Runner::new( + ch_runner, + Bus::new(pwr, spi), + &state.ioctl_state, + &state.net.events, + #[cfg(feature = "bluetooth")] + None, + ); + + runner.init(firmware, None).await; + let control = Control::new(state_ch, &state.net.events, &state.ioctl_state); + + (device, control, runner) +} + +/// Create a new instance of the CYW43 driver. +/// +/// Returns a handle to the network device, control handle and a runner for driving the low level +/// stack. +#[cfg(feature = "bluetooth")] +pub async fn new_with_bluetooth<'a, PWR, SPI>( + state: &'a mut State, + pwr: PWR, + spi: SPI, + wifi_firmware: &[u8], + bluetooth_firmware: &[u8], +) -> ( + NetDriver<'a>, + bluetooth::BtDriver<'a>, + Control<'a>, + Runner<'a, PWR, SPI>, +) +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + let (ch_runner, device) = ch::new(&mut state.net.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); + let state_ch = ch_runner.state_runner(); + + let (bt_runner, bt_driver) = bluetooth::new(&mut state.bt); + let mut runner = Runner::new( + ch_runner, + Bus::new(pwr, spi), + &state.ioctl_state, + &state.net.events, + #[cfg(feature = "bluetooth")] + Some(bt_runner), + ); + + runner.init(wifi_firmware, Some(bluetooth_firmware)).await; + let control = Control::new(state_ch, &state.net.events, &state.ioctl_state); + + (device, bt_driver, control, runner) +} diff --git a/embassy/cyw43/src/nvram.rs b/embassy/cyw43/src/nvram.rs new file mode 100644 index 0000000..3d1b535 --- /dev/null +++ b/embassy/cyw43/src/nvram.rs @@ -0,0 +1,48 @@ +pub static NVRAM: &'static [u8] = b" + NVRAMRev=$Rev$\x00\ + manfid=0x2d0\x00\ + prodid=0x0727\x00\ + vendid=0x14e4\x00\ + devid=0x43e2\x00\ + boardtype=0x0887\x00\ + boardrev=0x1100\x00\ + boardnum=22\x00\ + macaddr=00:A0:50:b5:59:5e\x00\ + sromrev=11\x00\ + boardflags=0x00404001\x00\ + boardflags3=0x04000000\x00\ + xtalfreq=37400\x00\ + nocrc=1\x00\ + ag0=255\x00\ + aa2g=1\x00\ + ccode=ALL\x00\ + pa0itssit=0x20\x00\ + extpagain2g=0\x00\ + pa2ga0=-168,6649,-778\x00\ + AvVmid_c0=0x0,0xc8\x00\ + cckpwroffset0=5\x00\ + maxp2ga0=84\x00\ + txpwrbckof=6\x00\ + cckbw202gpo=0\x00\ + legofdmbw202gpo=0x66111111\x00\ + mcsbw202gpo=0x77711111\x00\ + propbw202gpo=0xdd\x00\ + ofdmdigfilttype=18\x00\ + ofdmdigfilttypebe=18\x00\ + papdmode=1\x00\ + papdvalidtest=1\x00\ + pacalidx2g=45\x00\ + papdepsoffset=-30\x00\ + papdendidx=58\x00\ + ltecxmux=0\x00\ + ltecxpadnum=0x0102\x00\ + ltecxfnsel=0x44\x00\ + ltecxgcigpio=0x01\x00\ + il0macaddr=00:90:4c:c5:12:38\x00\ + wl0id=0x431b\x00\ + deadman_to=0xffffffff\x00\ + muxenab=0x100\x00\ + spurconfig=0x3\x00\ + glitch_based_crsmin=1\x00\ + btc_mode=1\x00\ + \x00"; diff --git a/embassy/cyw43/src/runner.rs b/embassy/cyw43/src/runner.rs new file mode 100644 index 0000000..77910b2 --- /dev/null +++ b/embassy/cyw43/src/runner.rs @@ -0,0 +1,666 @@ +use embassy_futures::select::{select4, Either4}; +use embassy_net_driver_channel as ch; +use embassy_time::{block_for, Duration, Timer}; +use embedded_hal_1::digital::OutputPin; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +use crate::consts::*; +use crate::events::{Event, Events, Status}; +use crate::fmt::Bytes; +use crate::ioctl::{IoctlState, IoctlType, PendingIoctl}; +use crate::nvram::NVRAM; +use crate::structs::*; +use crate::util::slice8_mut; +use crate::{events, Core, CHIP, MTU}; + +#[cfg(feature = "firmware-logs")] +struct LogState { + addr: u32, + last_idx: usize, + buf: [u8; 256], + buf_count: usize, +} + +#[cfg(feature = "firmware-logs")] +impl Default for LogState { + fn default() -> Self { + Self { + addr: Default::default(), + last_idx: Default::default(), + buf: [0; 256], + buf_count: Default::default(), + } + } +} + +/// Driver communicating with the WiFi chip. +pub struct Runner<'a, PWR, SPI> { + ch: ch::Runner<'a, MTU>, + pub(crate) bus: Bus, + + ioctl_state: &'a IoctlState, + ioctl_id: u16, + sdpcm_seq: u8, + sdpcm_seq_max: u8, + + events: &'a Events, + + #[cfg(feature = "firmware-logs")] + log: LogState, + + #[cfg(feature = "bluetooth")] + pub(crate) bt: Option>, +} + +impl<'a, PWR, SPI> Runner<'a, PWR, SPI> +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + pub(crate) fn new( + ch: ch::Runner<'a, MTU>, + bus: Bus, + ioctl_state: &'a IoctlState, + events: &'a Events, + #[cfg(feature = "bluetooth")] bt: Option>, + ) -> Self { + Self { + ch, + bus, + ioctl_state, + ioctl_id: 0, + sdpcm_seq: 0, + sdpcm_seq_max: 1, + events, + #[cfg(feature = "firmware-logs")] + log: LogState::default(), + #[cfg(feature = "bluetooth")] + bt, + } + } + + pub(crate) async fn init(&mut self, wifi_fw: &[u8], bt_fw: Option<&[u8]>) { + self.bus.init(bt_fw.is_some()).await; + + // Init ALP (Active Low Power) clock + debug!("init alp"); + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) + .await; + + debug!("set f2 watermark"); + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 0x10) + .await; + let watermark = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK).await; + debug!("watermark = {:02x}", watermark); + assert!(watermark == 0x10); + + debug!("waiting for clock..."); + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {} + debug!("clock ok"); + + // clear request for ALP + debug!("clear request for ALP"); + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0).await; + + let chip_id = self.bus.bp_read16(0x1800_0000).await; + debug!("chip ID: {}", chip_id); + + // Upload firmware. + self.core_disable(Core::WLAN).await; + self.core_disable(Core::SOCSRAM).await; // TODO: is this needed if we reset right after? + self.core_reset(Core::SOCSRAM).await; + + // this is 4343x specific stuff: Disable remap for SRAM_3 + self.bus.bp_write32(CHIP.socsram_base_address + 0x10, 3).await; + self.bus.bp_write32(CHIP.socsram_base_address + 0x44, 0).await; + + let ram_addr = CHIP.atcm_ram_base_address; + + debug!("loading fw"); + self.bus.bp_write(ram_addr, wifi_fw).await; + + debug!("loading nvram"); + // Round up to 4 bytes. + let nvram_len = (NVRAM.len() + 3) / 4 * 4; + self.bus + .bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM) + .await; + + let nvram_len_words = nvram_len as u32 / 4; + let nvram_len_magic = (!nvram_len_words << 16) | nvram_len_words; + self.bus + .bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic) + .await; + + // Start core! + debug!("starting up core..."); + self.core_reset(Core::WLAN).await; + assert!(self.core_is_up(Core::WLAN).await); + + // wait until HT clock is available; takes about 29ms + debug!("wait for HT clock"); + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} + + // "Set up the interrupt mask and enable interrupts" + debug!("setup interrupt mask"); + self.bus + .bp_write32(CHIP.sdiod_core_base_address + SDIO_INT_HOST_MASK, I_HMB_SW_MASK) + .await; + + // Set up the interrupt mask and enable interrupts + if bt_fw.is_some() { + debug!("bluetooth setup interrupt mask"); + self.bus + .bp_write32(CHIP.sdiod_core_base_address + SDIO_INT_HOST_MASK, I_HMB_FC_CHANGE) + .await; + } + + self.bus + .write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, IRQ_F2_PACKET_AVAILABLE) + .await; + + // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." + // Sounds scary... + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, SPI_F2_WATERMARK) + .await; + + // wait for F2 to be ready + debug!("waiting for F2 to be ready..."); + while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {} + + // Some random configs related to sleep. + // These aren't needed if we don't want to sleep the bus. + // TODO do we need to sleep the bus to read the irq line, due to + // being on the same pin as MOSI/MISO? + + /* + let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL).await; + val |= 0x02; // WAKE_TILL_HT_AVAIL + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val).await; + self.bus.write8(FUNC_BUS, 0xF0, 0x08).await; // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1 + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02).await; // SBSDIO_FORCE_HT + + let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR).await; + val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val).await; + */ + + // clear pulls + debug!("clear pad pulls"); + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await; + let _ = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await; + + // start HT clock + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10) + .await; // SBSDIO_HT_AVAIL_REQ + debug!("waiting for HT clock..."); + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} + debug!("clock ok"); + + #[cfg(feature = "firmware-logs")] + self.log_init().await; + + #[cfg(feature = "bluetooth")] + if let Some(bt_fw) = bt_fw { + self.bt.as_mut().unwrap().init_bluetooth(&mut self.bus, bt_fw).await; + } + + debug!("cyw43 runner init done"); + } + + #[cfg(feature = "firmware-logs")] + async fn log_init(&mut self) { + // Initialize shared memory for logging. + + let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size; + let shared_addr = self.bus.bp_read32(addr).await; + debug!("shared_addr {:08x}", shared_addr); + + let mut shared = [0; SharedMemData::SIZE]; + self.bus.bp_read(shared_addr, &mut shared).await; + let shared = SharedMemData::from_bytes(&shared); + + self.log.addr = shared.console_addr + 8; + } + + #[cfg(feature = "firmware-logs")] + async fn log_read(&mut self) { + // Read log struct + let mut log = [0; SharedMemLog::SIZE]; + self.bus.bp_read(self.log.addr, &mut log).await; + let log = SharedMemLog::from_bytes(&log); + + let idx = log.idx as usize; + + // If pointer hasn't moved, no need to do anything. + if idx == self.log.last_idx { + return; + } + + // Read entire buf for now. We could read only what we need, but then we + // run into annoying alignment issues in `bp_read`. + let mut buf = [0; 0x400]; + self.bus.bp_read(log.buf, &mut buf).await; + + while self.log.last_idx != idx as usize { + let b = buf[self.log.last_idx]; + if b == b'\r' || b == b'\n' { + if self.log.buf_count != 0 { + let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) }; + debug!("LOGS: {}", s); + self.log.buf_count = 0; + } + } else if self.log.buf_count < self.log.buf.len() { + self.log.buf[self.log.buf_count] = b; + self.log.buf_count += 1; + } + + self.log.last_idx += 1; + if self.log.last_idx == 0x400 { + self.log.last_idx = 0; + } + } + } + + /// Run the CYW43 event handling loop. + pub async fn run(mut self) -> ! { + let mut buf = [0; 512]; + loop { + #[cfg(feature = "firmware-logs")] + self.log_read().await; + + if self.has_credit() { + let ioctl = self.ioctl_state.wait_pending(); + let wifi_tx = self.ch.tx_buf(); + #[cfg(feature = "bluetooth")] + let bt_tx = async { + match &mut self.bt { + Some(bt) => bt.tx_chan.receive().await, + None => core::future::pending().await, + } + }; + #[cfg(not(feature = "bluetooth"))] + let bt_tx = core::future::pending::<()>(); + + // interrupts aren't working yet for bluetooth. Do busy-polling instead. + // Note for this to work `ev` has to go last in the `select()`. It prefers + // first futures if they're ready, so other select branches don't get starved.` + #[cfg(feature = "bluetooth")] + let ev = core::future::ready(()); + #[cfg(not(feature = "bluetooth"))] + let ev = self.bus.wait_for_event(); + + match select4(ioctl, wifi_tx, bt_tx, ev).await { + Either4::First(PendingIoctl { + buf: iobuf, + kind, + cmd, + iface, + }) => { + self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }, &mut buf).await; + self.check_status(&mut buf).await; + } + Either4::Second(packet) => { + trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); + + let buf8 = slice8_mut(&mut buf); + + // There MUST be 2 bytes of padding between the SDPCM and BDC headers. + // And ONLY for data packets! + // No idea why, but the firmware will append two zero bytes to the tx'd packets + // otherwise. If the packet is exactly 1514 bytes (the max MTU), this makes it + // be oversized and get dropped. + // WHD adds it here https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/include/whd_sdpcm.h#L90 + // and adds it to the header size her https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/whd_sdpcm.c#L597 + // ¯\_(ツ)_/¯ + const PADDING_SIZE: usize = 2; + let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE + packet.len(); + + let seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: seq, + channel_and_flags: CHANNEL_TYPE_DATA, + next_length: 0, + header_length: (SdpcmHeader::SIZE + PADDING_SIZE) as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let bdc_header = BdcHeader { + flags: BDC_VERSION << BDC_VERSION_SHIFT, + priority: 0, + flags2: 0, + data_offset: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", bdc_header); + + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BdcHeader::SIZE] + .copy_from_slice(&bdc_header.to_bytes()); + buf8[SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE..][..packet.len()] + .copy_from_slice(packet); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); + + self.bus.wlan_write(&buf[..(total_len / 4)]).await; + self.ch.tx_done(); + self.check_status(&mut buf).await; + } + Either4::Third(_) => { + #[cfg(feature = "bluetooth")] + self.bt.as_mut().unwrap().hci_write(&mut self.bus).await; + } + Either4::Fourth(()) => { + self.handle_irq(&mut buf).await; + + // If we do busy-polling, make sure to yield. + // `handle_irq` will only do a 32bit read if there's no work to do, which is really fast. + // Depending on optimization level, it is possible that the 32-bit read finishes on + // first poll, so it never yields and we starve all other tasks. + #[cfg(feature = "bluetooth")] + embassy_futures::yield_now().await; + } + } + } else { + warn!("TX stalled"); + self.bus.wait_for_event().await; + self.handle_irq(&mut buf).await; + } + } + } + + /// Wait for IRQ on F2 packet available + async fn handle_irq(&mut self, buf: &mut [u32; 512]) { + // Receive stuff + let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; + if irq != 0 { + trace!("irq{}", FormatInterrupt(irq)); + } + + if irq & IRQ_F2_PACKET_AVAILABLE != 0 { + self.check_status(buf).await; + } + + if irq & IRQ_DATA_UNAVAILABLE != 0 { + // this seems to be ignorable with no ill effects. + trace!("IRQ DATA_UNAVAILABLE, clearing..."); + self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await; + } + + #[cfg(feature = "bluetooth")] + if let Some(bt) = &mut self.bt { + bt.handle_irq(&mut self.bus).await; + } + } + + /// Handle F2 events while status register is set + async fn check_status(&mut self, buf: &mut [u32; 512]) { + loop { + let status = self.bus.status(); + trace!("check status{}", FormatStatus(status)); + + if status & STATUS_F2_PKT_AVAILABLE != 0 { + let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; + self.bus.wlan_read(buf, len).await; + trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)])); + self.rx(&mut slice8_mut(buf)[..len as usize]); + } else { + break; + } + } + } + + fn rx(&mut self, packet: &mut [u8]) { + let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { + return; + }; + + self.update_credit(&sdpcm_header); + + let channel = sdpcm_header.channel_and_flags & 0x0f; + + match channel { + CHANNEL_TYPE_CONTROL => { + let Some((cdc_header, response)) = CdcHeader::parse(payload) else { + return; + }; + trace!(" {:?}", cdc_header); + + if cdc_header.id == self.ioctl_id { + if cdc_header.status != 0 { + // TODO: propagate error instead + panic!("IOCTL error {}", cdc_header.status as i32); + } + + self.ioctl_state.ioctl_done(response); + } + } + CHANNEL_TYPE_EVENT => { + let Some((_, bdc_packet)) = BdcHeader::parse(payload) else { + warn!("BDC event, incomplete header"); + return; + }; + + let Some((event_packet, evt_data)) = EventPacket::parse(bdc_packet) else { + warn!("BDC event, incomplete data"); + return; + }; + + const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h + if event_packet.eth.ether_type != ETH_P_LINK_CTL { + warn!( + "unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}", + event_packet.eth.ether_type, ETH_P_LINK_CTL + ); + return; + } + const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18]; + if event_packet.hdr.oui != BROADCOM_OUI { + warn!( + "unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}", + Bytes(&event_packet.hdr.oui), + Bytes(BROADCOM_OUI) + ); + return; + } + const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769; + if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG { + warn!("unexpected subtype {}", event_packet.hdr.subtype); + return; + } + + const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1; + if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT { + warn!("unexpected user_subtype {}", event_packet.hdr.subtype); + return; + } + + let evt_type = events::Event::from(event_packet.msg.event_type as u8); + debug!( + "=== EVENT {:?}: {:?} {:02x}", + evt_type, + event_packet.msg, + Bytes(evt_data) + ); + + if self.events.mask.is_enabled(evt_type) { + let status = event_packet.msg.status; + let event_payload = match evt_type { + Event::ESCAN_RESULT if status == EStatus::PARTIAL => { + let Some((_, bss_info)) = ScanResults::parse(evt_data) else { + return; + }; + let Some(bss_info) = BssInfo::parse(bss_info) else { + return; + }; + events::Payload::BssInfo(*bss_info) + } + Event::ESCAN_RESULT => events::Payload::None, + _ => events::Payload::None, + }; + + // this intentionally uses the non-blocking publish immediate + // publish() is a deadlock risk in the current design as awaiting here prevents ioctls + // The `Runner` always yields when accessing the device, so consumers always have a chance to receive the event + // (if they are actively awaiting the queue) + self.events + .queue + .immediate_publisher() + .publish_immediate(events::Message::new( + Status { + event_type: evt_type, + status, + }, + event_payload, + )); + } + } + CHANNEL_TYPE_DATA => { + let Some((_, packet)) = BdcHeader::parse(payload) else { + return; + }; + trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); + + match self.ch.try_rx_buf() { + Some(buf) => { + buf[..packet.len()].copy_from_slice(packet); + self.ch.rx_done(packet.len()) + } + None => warn!("failed to push rxd packet to the channel."), + } + } + _ => {} + } + } + + fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) { + if sdpcm_header.channel_and_flags & 0xf < 3 { + let mut sdpcm_seq_max = sdpcm_header.bus_data_credit; + if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 { + sdpcm_seq_max = self.sdpcm_seq + 2; + } + self.sdpcm_seq_max = sdpcm_seq_max; + } + } + + fn has_credit(&self) -> bool { + self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 + } + + async fn send_ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, data: &[u8], buf: &mut [u32; 512]) { + let buf8 = slice8_mut(buf); + + let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); + + let sdpcm_seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + self.ioctl_id = self.ioctl_id.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: sdpcm_seq, + channel_and_flags: CHANNEL_TYPE_CONTROL, + next_length: 0, + header_length: SdpcmHeader::SIZE as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let cdc_header = CdcHeader { + cmd: cmd as u32, + len: data.len() as _, + flags: kind as u16 | (iface as u16) << 12, + id: self.ioctl_id, + status: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", cdc_header); + + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); + buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); + + self.bus.wlan_write(&buf[..total_len / 4]).await; + } + + async fn core_disable(&mut self, core: Core) { + let base = core.base_addr(); + + // Dummy read? + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + + // Check it isn't already reset + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + if r & AI_RESETCTRL_BIT_RESET != 0 { + return; + } + + self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + block_for(Duration::from_millis(1)); + + self.bus + .bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET) + .await; + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + } + + async fn core_reset(&mut self, core: Core) { + self.core_disable(core).await; + + let base = core.base_addr(); + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; + + Timer::after_millis(1).await; + + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + Timer::after_millis(1).await; + } + + async fn core_is_up(&mut self, core: Core) -> bool { + let base = core.base_addr(); + + let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN { + debug!("core_is_up: returning false due to bad ioctrl {:02x}", io); + return false; + } + + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + if r & (AI_RESETCTRL_BIT_RESET) != 0 { + debug!("core_is_up: returning false due to bad resetctrl {:02x}", r); + return false; + } + + true + } +} diff --git a/embassy/cyw43/src/structs.rs b/embassy/cyw43/src/structs.rs new file mode 100644 index 0000000..81ae6a9 --- /dev/null +++ b/embassy/cyw43/src/structs.rs @@ -0,0 +1,555 @@ +use crate::events::Event; +use crate::fmt::Bytes; + +macro_rules! impl_bytes { + ($t:ident) => { + impl $t { + /// Bytes consumed by this type. + pub const SIZE: usize = core::mem::size_of::(); + + /// Convert to byte array. + #[allow(unused)] + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + unsafe { core::mem::transmute(*self) } + } + + /// Create from byte array. + #[allow(unused)] + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + unsafe { core::mem::transmute(bytes) } + } + + /// Create from mutable byte array. + #[allow(unused)] + pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + + unsafe { core::mem::transmute(bytes) } + } + } + }; +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SharedMemData { + pub flags: u32, + pub trap_addr: u32, + pub assert_exp_addr: u32, + pub assert_file_addr: u32, + pub assert_line: u32, + pub console_addr: u32, + pub msgtrace_addr: u32, + pub fwid: u32, +} +impl_bytes!(SharedMemData); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SharedMemLog { + pub buf: u32, + pub buf_size: u32, + pub idx: u32, + pub out_idx: u32, +} +impl_bytes!(SharedMemLog); + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SdpcmHeader { + pub len: u16, + pub len_inv: u16, + /// Rx/Tx sequence number + pub sequence: u8, + /// 4 MSB Channel number, 4 LSB arbitrary flag + pub channel_and_flags: u8, + /// Length of next data frame, reserved for Tx + pub next_length: u8, + /// Data offset + pub header_length: u8, + /// Flow control bits, reserved for Tx + pub wireless_flow_control: u8, + /// Maximum Sequence number allowed by firmware for Tx + pub bus_data_credit: u8, + /// Reserved + pub reserved: [u8; 2], +} +impl_bytes!(SdpcmHeader); + +impl SdpcmHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + let packet_len = packet.len(); + if packet_len < Self::SIZE { + warn!("packet too short, len={}", packet.len()); + return None; + } + let (sdpcm_header, sdpcm_packet) = packet.split_at_mut(Self::SIZE); + let sdpcm_header = Self::from_bytes_mut(sdpcm_header.try_into().unwrap()); + trace!("rx {:?}", sdpcm_header); + + if sdpcm_header.len != !sdpcm_header.len_inv { + warn!("len inv mismatch"); + return None; + } + + if sdpcm_header.len as usize != packet_len { + warn!("len from header doesn't match len from spi"); + return None; + } + + let sdpcm_packet = &mut sdpcm_packet[(sdpcm_header.header_length as usize - Self::SIZE)..]; + Some((sdpcm_header, sdpcm_packet)) + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C, packed(2))] +pub struct CdcHeader { + pub cmd: u32, + pub len: u32, + pub flags: u16, + pub id: u16, + pub status: u32, +} +impl_bytes!(CdcHeader); + +#[cfg(feature = "defmt")] +impl defmt::Format for CdcHeader { + fn format(&self, fmt: defmt::Formatter) { + fn copy(t: T) -> T { + t + } + + defmt::write!( + fmt, + "CdcHeader{{cmd: {=u32:08x}, len: {=u32:08x}, flags: {=u16:04x}, id: {=u16:04x}, status: {=u32:08x}}}", + copy(self.cmd), + copy(self.len), + copy(self.flags), + copy(self.id), + copy(self.status), + ) + } +} + +impl CdcHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + warn!("payload too short, len={}", packet.len()); + return None; + } + + let (cdc_header, payload) = packet.split_at_mut(Self::SIZE); + let cdc_header = Self::from_bytes_mut(cdc_header.try_into().unwrap()); + + let payload = &mut payload[..cdc_header.len as usize]; + Some((cdc_header, payload)) + } +} + +pub const BDC_VERSION: u8 = 2; +pub const BDC_VERSION_SHIFT: u8 = 4; + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct BdcHeader { + pub flags: u8, + /// 802.1d Priority (low 3 bits) + pub priority: u8, + pub flags2: u8, + /// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers. + pub data_offset: u8, +} +impl_bytes!(BdcHeader); + +impl BdcHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + return None; + } + + let (bdc_header, bdc_packet) = packet.split_at_mut(Self::SIZE); + let bdc_header = Self::from_bytes_mut(bdc_header.try_into().unwrap()); + trace!(" {:?}", bdc_header); + + let packet_start = 4 * bdc_header.data_offset as usize; + + let bdc_packet = bdc_packet.get_mut(packet_start..)?; + trace!(" {:02x}", Bytes(&bdc_packet[..bdc_packet.len().min(36)])); + + Some((bdc_header, bdc_packet)) + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EthernetHeader { + pub destination_mac: [u8; 6], + pub source_mac: [u8; 6], + pub ether_type: u16, +} + +impl EthernetHeader { + /// Swap endianness. + pub fn byteswap(&mut self) { + self.ether_type = self.ether_type.to_be(); + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EventHeader { + pub subtype: u16, + pub length: u16, + pub version: u8, + pub oui: [u8; 3], + pub user_subtype: u16, +} + +impl EventHeader { + pub fn byteswap(&mut self) { + self.subtype = self.subtype.to_be(); + self.length = self.length.to_be(); + self.user_subtype = self.user_subtype.to_be(); + } +} + +#[derive(Debug, Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +pub struct EventMessage { + /// version + pub version: u16, + /// see flags below + pub flags: u16, + /// Message (see below) + pub event_type: u32, + /// Status code (see below) + pub status: u32, + /// Reason code (if applicable) + pub reason: u32, + /// WLC_E_AUTH + pub auth_type: u32, + /// data buf + pub datalen: u32, + /// Station address (if applicable) + pub addr: [u8; 6], + /// name of the incoming packet interface + pub ifname: [u8; 16], + /// destination OS i/f index + pub ifidx: u8, + /// source bsscfg index + pub bsscfgidx: u8, +} +impl_bytes!(EventMessage); + +#[cfg(feature = "defmt")] +impl defmt::Format for EventMessage { + fn format(&self, fmt: defmt::Formatter) { + let event_type = self.event_type; + let status = self.status; + let reason = self.reason; + let auth_type = self.auth_type; + let datalen = self.datalen; + + defmt::write!( + fmt, + "EventMessage {{ \ + version: {=u16}, \ + flags: {=u16}, \ + event_type: {=u32}, \ + status: {=u32}, \ + reason: {=u32}, \ + auth_type: {=u32}, \ + datalen: {=u32}, \ + addr: {=[u8; 6]:x}, \ + ifname: {=[u8; 16]:x}, \ + ifidx: {=u8}, \ + bsscfgidx: {=u8}, \ + }} ", + self.version, + self.flags, + event_type, + status, + reason, + auth_type, + datalen, + self.addr, + self.ifname, + self.ifidx, + self.bsscfgidx + ); + } +} + +impl EventMessage { + pub fn byteswap(&mut self) { + self.version = self.version.to_be(); + self.flags = self.flags.to_be(); + self.event_type = self.event_type.to_be(); + self.status = self.status.to_be(); + self.reason = self.reason.to_be(); + self.auth_type = self.auth_type.to_be(); + self.datalen = self.datalen.to_be(); + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +pub struct EventPacket { + pub eth: EthernetHeader, + pub hdr: EventHeader, + pub msg: EventMessage, +} +impl_bytes!(EventPacket); + +impl EventPacket { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + return None; + } + + let (event_header, event_packet) = packet.split_at_mut(Self::SIZE); + let event_header = Self::from_bytes_mut(event_header.try_into().unwrap()); + // warn!("event_header {:x}", event_header as *const _); + event_header.byteswap(); + + let event_packet = event_packet.get_mut(..event_header.msg.datalen as usize)?; + + Some((event_header, event_packet)) + } + + pub fn byteswap(&mut self) { + self.eth.byteswap(); + self.hdr.byteswap(); + self.msg.byteswap(); + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct DownloadHeader { + pub flag: u16, // + pub dload_type: u16, + pub len: u32, + pub crc: u32, +} +impl_bytes!(DownloadHeader); + +#[allow(unused)] +pub const DOWNLOAD_FLAG_NO_CRC: u16 = 0x0001; +pub const DOWNLOAD_FLAG_BEGIN: u16 = 0x0002; +pub const DOWNLOAD_FLAG_END: u16 = 0x0004; +pub const DOWNLOAD_FLAG_HANDLER_VER: u16 = 0x1000; + +// Country Locale Matrix (CLM) +pub const DOWNLOAD_TYPE_CLM: u16 = 2; + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct CountryInfo { + pub country_abbrev: [u8; 4], + pub rev: i32, + pub country_code: [u8; 4], +} +impl_bytes!(CountryInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SsidInfo { + pub len: u32, + pub ssid: [u8; 32], +} +impl_bytes!(SsidInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct PassphraseInfo { + pub len: u16, + pub flags: u16, + pub passphrase: [u8; 64], +} +impl_bytes!(PassphraseInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SaePassphraseInfo { + pub len: u16, + pub passphrase: [u8; 128], +} +impl_bytes!(SaePassphraseInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SsidInfoWithIndex { + pub index: u32, + pub ssid_info: SsidInfo, +} +impl_bytes!(SsidInfoWithIndex); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EventMask { + pub iface: u32, + pub events: [u8; 24], +} +impl_bytes!(EventMask); + +impl EventMask { + pub fn unset(&mut self, evt: Event) { + let evt = evt as u8 as usize; + self.events[evt / 8] &= !(1 << (evt % 8)); + } +} + +/// Parameters for a wifi scan +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct ScanParams { + pub version: u32, + pub action: u16, + pub sync_id: u16, + pub ssid_len: u32, + pub ssid: [u8; 32], + pub bssid: [u8; 6], + pub bss_type: u8, + pub scan_type: u8, + pub nprobes: u32, + pub active_time: u32, + pub passive_time: u32, + pub home_time: u32, + pub channel_num: u32, + pub channel_list: [u16; 1], +} +impl_bytes!(ScanParams); + +/// Wifi Scan Results Header, followed by `bss_count` `BssInfo` +#[derive(Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +pub struct ScanResults { + pub buflen: u32, + pub version: u32, + pub sync_id: u16, + pub bss_count: u16, +} +impl_bytes!(ScanResults); + +impl ScanResults { + pub fn parse(packet: &mut [u8]) -> Option<(&mut ScanResults, &mut [u8])> { + if packet.len() < ScanResults::SIZE { + return None; + } + + let (scan_results, bssinfo) = packet.split_at_mut(ScanResults::SIZE); + let scan_results = ScanResults::from_bytes_mut(scan_results.try_into().unwrap()); + + if scan_results.bss_count > 0 && bssinfo.len() < BssInfo::SIZE { + warn!("Scan result, incomplete BssInfo"); + return None; + } + + Some((scan_results, bssinfo)) + } +} + +/// Wifi Scan Result +#[derive(Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +#[non_exhaustive] +pub struct BssInfo { + /// Version. + pub version: u32, + /// Length. + pub length: u32, + /// BSSID. + pub bssid: [u8; 6], + /// Beacon period. + pub beacon_period: u16, + /// Capability. + pub capability: u16, + /// SSID length. + pub ssid_len: u8, + /// SSID. + pub ssid: [u8; 32], + reserved1: [u8; 1], + /// Number of rates in the rates field. + pub rateset_count: u32, + /// Rates in 500kpbs units. + pub rates: [u8; 16], + /// Channel specification. + pub chanspec: u16, + /// Announcement traffic indication message. + pub atim_window: u16, + /// Delivery traffic indication message. + pub dtim_period: u8, + reserved2: [u8; 1], + /// Receive signal strength (in dbM). + pub rssi: i16, + /// Received noise (in dbM). + pub phy_noise: i8, + /// 802.11n capability. + pub n_cap: u8, + reserved3: [u8; 2], + /// 802.11n BSS capabilities. + pub nbss_cap: u32, + /// 802.11n control channel number. + pub ctl_ch: u8, + reserved4: [u8; 3], + reserved32: [u32; 1], + /// Flags. + pub flags: u8, + /// VHT capability. + pub vht_cap: u8, + reserved5: [u8; 2], + /// 802.11n BSS required MCS. + pub basic_mcs: [u8; 16], + /// Information Elements (IE) offset. + pub ie_offset: u16, + /// Length of Information Elements (IE) in bytes. + pub ie_length: u32, + /// Average signal-to-noise (SNR) ratio during frame reception. + pub snr: i16, + // there will be more stuff here +} +impl_bytes!(BssInfo); + +impl BssInfo { + pub(crate) fn parse(packet: &mut [u8]) -> Option<&mut Self> { + if packet.len() < BssInfo::SIZE { + return None; + } + + Some(BssInfo::from_bytes_mut( + packet[..BssInfo::SIZE].as_mut().try_into().unwrap(), + )) + } +} diff --git a/embassy/cyw43/src/util.rs b/embassy/cyw43/src/util.rs new file mode 100644 index 0000000..a4adbd4 --- /dev/null +++ b/embassy/cyw43/src/util.rs @@ -0,0 +1,20 @@ +#![allow(unused)] + +use core::slice; + +pub(crate) fn slice8_mut(x: &mut [u32]) -> &mut [u8] { + let len = x.len() * 4; + unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } +} + +pub(crate) fn is_aligned(a: u32, x: u32) -> bool { + (a & (x - 1)) == 0 +} + +pub(crate) fn round_down(x: u32, a: u32) -> u32 { + x & !(a - 1) +} + +pub(crate) fn round_up(x: u32, a: u32) -> u32 { + ((x + a - 1) / a) * a +} diff --git a/embassy/docs/Makefile b/embassy/docs/Makefile new file mode 100644 index 0000000..834802d --- /dev/null +++ b/embassy/docs/Makefile @@ -0,0 +1,8 @@ +all: + asciidoctor -d book -D book/ index.adoc + cp -r images book + +clean: + rm -rf book + +.PHONY: all clean diff --git a/embassy/docs/README.md b/embassy/docs/README.md new file mode 100644 index 0000000..d766a86 --- /dev/null +++ b/embassy/docs/README.md @@ -0,0 +1,29 @@ +# embassy docs + +The documentation hosted at [https://embassy.dev/book](https://embassy.dev/book). Building the documentation requires the [asciidoctor](https://asciidoctor.org/) tool, and can built running `make` in this folder: + +``` +make +``` + +Then open the generated file `thebook/index.html`. + +## License + +The Embassy Docs (this folder) is distributed under the following licenses: + +* The code samples and free-standing Cargo projects contained within these docs are licensed under the terms of both the [MIT License] and the [Apache License v2.0]. +* The written prose contained within these docs are licensed under the terms of the Creative Commons [CC-BY-SA v4.0] license. + +Copies of the licenses used by this project may also be found here: + +* [MIT License Hosted] +* [Apache License v2.0 Hosted] +* [CC-BY-SA v4.0 Hosted] + +[MIT License]: ./../LICENSE-MIT +[Apache License v2.0]: ./../LICENSE-APACHE +[CC-BY-SA v4.0]: ./../LICENSE-CC-BY-SA +[MIT License Hosted]: https://opensource.org/licenses/MIT +[Apache License v2.0 Hosted]: http://www.apache.org/licenses/LICENSE-2.0 +[CC-BY-SA v4.0 Hosted]: https://creativecommons.org/licenses/by-sa/4.0/legalcode diff --git a/embassy/docs/examples/basic/.cargo/config.toml b/embassy/docs/examples/basic/.cargo/config.toml new file mode 100644 index 0000000..8ca28df --- /dev/null +++ b/embassy/docs/examples/basic/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF82840_xxAA with your chip as listed in `probe-run --list-chips` +runner = "probe-run --chip nRF52840_xxAA" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/docs/examples/basic/Cargo.toml b/embassy/docs/examples/basic/Cargo.toml new file mode 100644 index 0000000..daf8387 --- /dev/null +++ b/embassy/docs/examples/basic/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["Dario Nieuwenhuis "] +edition = "2018" +name = "embassy-basic-example" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.6.3", path = "../../../embassy-executor", features = ["defmt", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt"] } +embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] } + +defmt = "0.3" +defmt-rtt = "0.3" + +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"] } diff --git a/embassy/docs/examples/basic/build.rs b/embassy/docs/examples/basic/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/docs/examples/basic/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/embassy/docs/examples/basic/memory.x b/embassy/docs/examples/basic/memory.x new file mode 100644 index 0000000..9b04ede --- /dev/null +++ b/embassy/docs/examples/basic/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/embassy/docs/examples/basic/src/main.rs b/embassy/docs/examples/basic/src/main.rs new file mode 100644 index 0000000..4412712 --- /dev/null +++ b/embassy/docs/examples/basic/src/main.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; // global logger + +#[embassy_executor::task] +async fn blinker(mut led: Output<'static>, interval: Duration) { + loop { + led.set_high(); + Timer::after(interval).await; + led.set_low(); + Timer::after(interval).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard); + unwrap!(spawner.spawn(blinker(led, Duration::from_millis(300)))); +} diff --git a/embassy/docs/examples/examples b/embassy/docs/examples/examples new file mode 120000 index 0000000..d15735c --- /dev/null +++ b/embassy/docs/examples/examples @@ -0,0 +1 @@ +../../examples \ No newline at end of file diff --git a/embassy/docs/examples/layer-by-layer/.cargo/config.toml b/embassy/docs/examples/layer-by-layer/.cargo/config.toml new file mode 100644 index 0000000..3012f05 --- /dev/null +++ b/embassy/docs/examples/layer-by-layer/.cargo/config.toml @@ -0,0 +1,14 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-run --chip STM32L475VG" + +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/docs/examples/layer-by-layer/Cargo.toml b/embassy/docs/examples/layer-by-layer/Cargo.toml new file mode 100644 index 0000000..0f233ea --- /dev/null +++ b/embassy/docs/examples/layer-by-layer/Cargo.toml @@ -0,0 +1,21 @@ +[workspace] +resolver = "2" +members = [ + "blinky-pac", + "blinky-hal", + "blinky-irq", + "blinky-async", +] + +[patch.crates-io] +embassy-executor = { path = "../../../embassy-executor" } +embassy-stm32 = { path = "../../../embassy-stm32" } + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 's' +overflow-checks = false diff --git a/embassy/docs/examples/layer-by-layer/blinky-async/Cargo.toml b/embassy/docs/examples/layer-by-layer/blinky-async/Cargo.toml new file mode 100644 index 0000000..f6d2e4f --- /dev/null +++ b/embassy/docs/examples/layer-by-layer/blinky-async/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "blinky-async" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"] } +embassy-executor = { version = "0.6.3", features = ["arch-cortex-m", "executor-thread"] } + +defmt = "0.3.0" +defmt-rtt = "0.3.0" +panic-probe = { version = "0.3.0", features = ["print-defmt"] } diff --git a/embassy/docs/examples/layer-by-layer/blinky-async/src/main.rs b/embassy/docs/examples/layer-by-layer/blinky-async/src/main.rs new file mode 100644 index 0000000..0046028 --- /dev/null +++ b/embassy/docs/examples/layer-by-layer/blinky-async/src/main.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut led = Output::new(p.PB14, Level::Low, Speed::VeryHigh); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); + + loop { + button.wait_for_any_edge().await; + if button.is_low() { + led.set_high(); + } else { + led.set_low(); + } + } +} diff --git a/embassy/docs/examples/layer-by-layer/blinky-hal/Cargo.toml b/embassy/docs/examples/layer-by-layer/blinky-hal/Cargo.toml new file mode 100644 index 0000000..c15de2d --- /dev/null +++ b/embassy/docs/examples/layer-by-layer/blinky-hal/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "blinky-hal" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"] } + +defmt = "0.3.0" +defmt-rtt = "0.3.0" +panic-probe = { version = "0.3.0", features = ["print-defmt"] } diff --git a/embassy/docs/examples/layer-by-layer/blinky-hal/src/main.rs b/embassy/docs/examples/layer-by-layer/blinky-hal/src/main.rs new file mode 100644 index 0000000..d0c9f49 --- /dev/null +++ b/embassy/docs/examples/layer-by-layer/blinky-hal/src/main.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use {defmt_rtt as _, panic_probe as _}; + +#[entry] +fn main() -> ! { + let p = embassy_stm32::init(Default::default()); + let mut led = Output::new(p.PB14, Level::High, Speed::VeryHigh); + let button = Input::new(p.PC13, Pull::Up); + + loop { + if button.is_low() { + led.set_high(); + } else { + led.set_low(); + } + } +} diff --git a/embassy/docs/examples/layer-by-layer/blinky-irq/Cargo.toml b/embassy/docs/examples/layer-by-layer/blinky-irq/Cargo.toml new file mode 100644 index 0000000..9733658 --- /dev/null +++ b/embassy/docs/examples/layer-by-layer/blinky-irq/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "blinky-irq" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = { version = "0.7" } +embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "unstable-pac"] } + +defmt = "0.3.0" +defmt-rtt = "0.3.0" +panic-probe = { version = "0.3.0", features = ["print-defmt"] } diff --git a/embassy/docs/examples/layer-by-layer/blinky-irq/src/main.rs b/embassy/docs/examples/layer-by-layer/blinky-irq/src/main.rs new file mode 100644 index 0000000..743c9d9 --- /dev/null +++ b/embassy/docs/examples/layer-by-layer/blinky-irq/src/main.rs @@ -0,0 +1,93 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use cortex_m::interrupt::Mutex; +use cortex_m::peripheral::NVIC; +use cortex_m_rt::entry; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::{interrupt, pac}; +use {defmt_rtt as _, panic_probe as _}; + +static BUTTON: Mutex>>> = Mutex::new(RefCell::new(None)); +static LED: Mutex>>> = Mutex::new(RefCell::new(None)); + +#[entry] +fn main() -> ! { + let p = embassy_stm32::init(Default::default()); + let led = Output::new(p.PB14, Level::Low, Speed::Low); + let mut button = Input::new(p.PC13, Pull::Up); + + cortex_m::interrupt::free(|cs| { + enable_interrupt(&mut button); + + LED.borrow(cs).borrow_mut().replace(led); + BUTTON.borrow(cs).borrow_mut().replace(button); + + unsafe { NVIC::unmask(pac::Interrupt::EXTI15_10) }; + }); + + loop { + cortex_m::asm::wfe(); + } +} + +#[interrupt] +fn EXTI15_10() { + cortex_m::interrupt::free(|cs| { + let mut button = BUTTON.borrow(cs).borrow_mut(); + let button = button.as_mut().unwrap(); + + let mut led = LED.borrow(cs).borrow_mut(); + let led = led.as_mut().unwrap(); + if check_interrupt(button) { + if button.is_low() { + led.set_high(); + } else { + led.set_low(); + } + } + clear_interrupt(button); + }); +} +// +// +// +// +// +// +// "Hidden" HAL-like methods for doing interrupts with embassy. Hardcode pin just to give audience an idea of what it looks like + +const PORT: u8 = 2; +const PIN: usize = 13; +fn check_interrupt(_pin: &mut Input<'static>) -> bool { + let exti = pac::EXTI; + let pin = PIN; + let lines = exti.pr(0).read(); + lines.line(pin) +} + +fn clear_interrupt(_pin: &mut Input<'static>) { + let exti = pac::EXTI; + let pin = PIN; + let mut lines = exti.pr(0).read(); + lines.set_line(pin, true); + exti.pr(0).write_value(lines); +} + +fn enable_interrupt(_pin: &mut Input<'static>) { + cortex_m::interrupt::free(|_| { + let rcc = pac::RCC; + rcc.apb2enr().modify(|w| w.set_syscfgen(true)); + + let port = PORT; + let pin = PIN; + let syscfg = pac::SYSCFG; + let exti = pac::EXTI; + syscfg.exticr(pin / 4).modify(|w| w.set_exti(pin % 4, port)); + exti.imr(0).modify(|w| w.set_line(pin, true)); + exti.rtsr(0).modify(|w| w.set_line(pin, true)); + exti.ftsr(0).modify(|w| w.set_line(pin, true)); + }); +} diff --git a/embassy/docs/examples/layer-by-layer/blinky-pac/Cargo.toml b/embassy/docs/examples/layer-by-layer/blinky-pac/Cargo.toml new file mode 100644 index 0000000..f872b94 --- /dev/null +++ b/embassy/docs/examples/layer-by-layer/blinky-pac/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "blinky-pac" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +stm32-metapac = { version = "1", features = ["stm32l475vg", "memory-x"] } + +defmt = "0.3.0" +defmt-rtt = "0.3.0" +panic-probe = { version = "0.3.0", features = ["print-defmt"] } diff --git a/embassy/docs/examples/layer-by-layer/blinky-pac/src/main.rs b/embassy/docs/examples/layer-by-layer/blinky-pac/src/main.rs new file mode 100644 index 0000000..990d46c --- /dev/null +++ b/embassy/docs/examples/layer-by-layer/blinky-pac/src/main.rs @@ -0,0 +1,53 @@ +#![no_std] +#![no_main] + +use pac::gpio::vals; +use {defmt_rtt as _, panic_probe as _, stm32_metapac as pac}; + +#[cortex_m_rt::entry] +fn main() -> ! { + // Enable GPIO clock + let rcc = pac::RCC; + unsafe { + rcc.ahb2enr().modify(|w| { + w.set_gpioben(true); + w.set_gpiocen(true); + }); + + rcc.ahb2rstr().modify(|w| { + w.set_gpiobrst(true); + w.set_gpiocrst(true); + w.set_gpiobrst(false); + w.set_gpiocrst(false); + }); + } + + // Setup button + let gpioc = pac::GPIOC; + const BUTTON_PIN: usize = 13; + unsafe { + gpioc.pupdr().modify(|w| w.set_pupdr(BUTTON_PIN, vals::Pupdr::PULLUP)); + gpioc.otyper().modify(|w| w.set_ot(BUTTON_PIN, vals::Ot::PUSHPULL)); + gpioc.moder().modify(|w| w.set_moder(BUTTON_PIN, vals::Moder::INPUT)); + } + + // Setup LED + let gpiob = pac::GPIOB; + const LED_PIN: usize = 14; + unsafe { + gpiob.pupdr().modify(|w| w.set_pupdr(LED_PIN, vals::Pupdr::FLOATING)); + gpiob.otyper().modify(|w| w.set_ot(LED_PIN, vals::Ot::PUSHPULL)); + gpiob.moder().modify(|w| w.set_moder(LED_PIN, vals::Moder::OUTPUT)); + } + + // Main loop + loop { + unsafe { + if gpioc.idr().read().idr(BUTTON_PIN) == vals::Idr::LOW { + gpiob.bsrr().write(|w| w.set_bs(LED_PIN, true)); + } else { + gpiob.bsrr().write(|w| w.set_br(LED_PIN, true)); + } + } + } +} diff --git a/embassy/docs/images/bootloader_flash.png b/embassy/docs/images/bootloader_flash.png new file mode 100644 index 0000000000000000000000000000000000000000..635783b05cd18b70f02c7d69c1c9faef7927b4bc GIT binary patch literal 32147 zcmc$_XH=6*^ggOsK#`)TC|xBYDuhnxy@Y`D-VV}h0tvmzp~+E1qYygML_|R8JqiLM zN(m*>J28|{LWhL=at{1%`>%Ds+`H}v-h_E~rtI0XXV3HOiPL+e!Ell7;;B=o7@(S} z2B%KZ5P&~Uy7NFuCSzYU@NpVxprL%Kq>F=i>eRUhC^a*bXMmHdhvO-bsLFreKq5je za3l&OstOVjQS)%LNBO|LfUiKgC)~l+$<@)}zjq=+A|e7pVge!%BVh@UsFJu8@DLRi zl#&p)_)p%>+0pB`B5^?>pn`ikcJ{7bC^*suB&rPj5A{MhdH|n5G4Rwi1RiF>-)!h;J&~UVOhadgK z!N<;D&=n4d3rh-$2_AJ^#l;8i2?(hR3keAd35r3aBm^anI`qGfPhA)Syb%V<4DFok zd|dwztsT|i=jelU1@!Q*R!-_Ydf|w&bN;W?*GyPn%GN?xz!t875H-{CdaMl8awLNa zL^%E@9wvIi9wJJfcEYB>jH#<>0*?T9Q#%Jk9Zf$U zJ$ICbAJRp|+s#PT#LU>w$ko8e7v-Vh0Zg#6sh1|gUf97y+{siD_?5A$T>wyOE)F*n zcZ2F3Nr-F0M3AD7R3z2ZghfPL;Y#*UA*~<>A3Y5xQ!i5uZ7EarKz(yNWtcuh)z}=4 zaI%wfG4OyI78AbJ6s1 z2Fy&|$->nKu41nQ6L<9SQbtL+xY{}Ri|eY2dpt&oc$hvm*Y|WbLi+kbG<3a;A3J#q z`AB*x>H8aNYN5o9P-+gkdLEMc{(vc|s{}#}A)+3R%3@lkf$Bi(8YW_@zA74l!fM*8 zUc!upR5)uj2aPbiebaU45Q4VnN@b|ZX zJ6Z%7nQPcdIV$-=oYlltE$r2l5pGZu9i$`lF-+S?!U5s$X{;IaSX(&2$l1WzPR}_| z)BuUlSJCm)fLlCL)j|1~8*3OrT_D1`J|=p)-bzTIVNrJvVO>Y4vcIDd1m*_xU)8}t zU&T$u9O7*1>;`po)^c~yb%cpI0I%To#+qV*Vj4;keoBuW9;+XfnWHoSLsEw*0ZS72 zJXw|hE>YnBW4|b>wqxB(bLte&DX6NFQGm_T*tr^GV`|$P>k>!%=lV1BzE|rE*Sb6wx@&RE+%C} za?%l`Zm8Cs)aiTqy(z?22gf3?@h1>^hBxCXjk|y}Nl~^wFAEgPF8C=o&mH9Z3S}=K zINU6SPuh5zCkL{&Xy3ALw2{54==nFZSvq%! zkD!+Q-{FigZ|>+vi`JipK{ zlfM0`LL;U35_+8Bbf^h53;*RM!sbkWUIA}9U*cnhs<4zl=dN{SEY!;dMpGleIIiRg~#Mb#M>RCg|CltL>m# zF$Yw@r?N^4$6H-S!k#{^GrOWSqPF5Ah$zm-s5H8~wQHr&WaX09-DvbDGP1U%CVc)2 zX@YSlIp6^Uff}!jUp=$kZmaHe(N^6mh8%nk>m3}Lx!c@C{TpFu;q@+QSP7!LPuDAd ztRl^4ij6I`DhtSLGGi@c8DsXi$(s$-4w;G;e2J>e8&hGd5%Zl^z_CXg2&~9<~y|3Q zpTy2taUC}=a;N7Hf=i`VaDPb8pTx7LV&>;PB?66oTsl8JOftSgBH&U6n`@mLUt9GN zEW|>j5Pb)hr|?hrZ|PRIGkHpx{lJxwSW-)isuS=r6PFz{`IUI9W!6oo%pRr2rPYq&kLLpD#EG7@*Xo(?ETJ0}k-jD?M zfzYP)6c=p1ScmwG$W_ctO$dl_k~%6n$w(-X3cvRz|4q~Ecd*U+4>86WZwOWo7iQPd z+NVWQ$4>#(RuAf0`M4?{=^2U3#gi&c$k!E|VHJ>j2yz2<# z54zdTi{H2?ViP(S2F+CSANmI?v_t=-nhPz+GzVs!9%rPdkiFAlx~CEHmdQ7_+ih4V ztv%rGpTUJ~YP`D<2F-zs+oHidPZT51Wpb=Hm`wB_>1Vt;QC6z##nojmmOsbd&FD|K zXdI5@q0W|S zXysp?-toKXcJD@+QY7&B3zZ(fy^puA1wrOm@|gd)ktwwuEKGIZ-%DE&o4AsZ`-D1V z$>f%la%jA{b-AnP(T@w472kHzxH1uZ=O=wCfp7q?oQS;r@=?TEG_pD%anld~vcMK6 z1D+_}onNzx5%cLUmEQd+A2j(YJ7{s4sdADA>ALu9u=m#f@@n-uW^jvr=bKQ5kDu*s zmgp}?qckPh+j?|gQ%>R!cIIb#wO@(ZHW}R18(%H=o|ZdM3)~>9TvXDpUsO_oGWvI` z(p5OjG3;*A5?oxX8eHgfjbo&DNwa=o1ngPH__18el@Qey+h9wWK$D))Lb)?uK)hFV zIoE!SN)~L=mtz{y>BjEF2-Z!TWT1Z94Cj=y?mt#J712P?+(nje=KsSzC3`MXk%jsF z>n}UayL~M&?yZ&neLkJ2p7Mn4XBJ{^{cwxG#u9zHr>V1{67<-Je)u-JsSY8;|zzlra|N@dtkWyShM7_r!2 z9*40FEh?6@)xg^ZmfcNpn0_b1kwqBWZZyN7W{eBm>IFnMskPE$-d5)$4uA-cEcvc* z@$!_cuZnUc^ZOJhu$G>~ndjBXDdHxhnP#FfN0MX9;~?GJcO5Y|&*&dH4{Q$v%tla!S8%L08@EXl{;q z$hPB@W7Sh-GHvNtQSE%P8o@dwl2N{_x@zdLwKdWetRugh7qq$QUpYNouMUz0Tx2() zX&)BZQjNXmQANqtw_T~Nt(F)QEEv1`$3-o%HEBY*E+maxci;o%mpvIahd2tcm zM9G%F>5X+t^KE+v?8AiLufeVt36@KU8q(5Xve&YIOx5tul@a!O2DZJwO-A0P`3-yB z*{nbK_72fRO3m2l8DsShzV$@$E713I?5pZ13}T$lwszguiG_cPR{Ec{XsCkjfwAi*EFW8TX~@3{p@)^qV)P{GJ=`)iHr6dyfXU?| z!TYKIpUjZ;j#NKR@df<7rTRx>|8C?T*Kx_g$wjw&T!3&oSL5v|JYPENUoVNbX_Vep z(kfe*Hy*IntNIcW#)AvrWq4M)47FbFo>-GqiJdmTZU+9a%ypobrMh2xzM&G)L~L{C zmB%mM88?ibt9mg!H$hXQ9un7jy=St(<~i3e3bIWQ{aNl;J~g`kv9V)ZXKFOmH|3ei zj_Pu|{DFC$K*Qu{-Rcq-gXP|T_-;%+HIblV}bb=RWER!rawl}R{FaWr`+TMzS( z?CzIq`g(qUQT8Yro25jGP2Fcp*~NyrckaY0_ipV5TlcBa#iJSL+Rw<7r;r9O$ki{_ z+^H+HmMQ)AVENO&aNX1(Z4RR8yE6GbMd84WKE7NwkNaTb%-gj~JL~RC9CBiN(=XX8 z`mfN7Zw_FMZ>$Z{rjob~-M5CEZl{y|FGlr!wc0t#ELVdorIw-Iw4iUG%a$ zs73_M3_St+oFkue8zS+ce(Ow9@N$!*(bg1CSf_Y_#)G79BN1|4gXb{g{qvM*1Q-6p ze#jj7mR8WOnoD!VtSirdnC6_K?&9_1Yq5IY#0iU@b#S93gFSSv!XXEz9P@$@S1g&r zBx873dvHA?=%7h@p=T_0FKE};-=+JEu5oE*usYKdiG_S4TVPX zzbBl`1EDS6bR-jx9!CUjz{2YM75K#AD&U{C-LAUj=AWmvDZc5vQ;-=-GWNAVVqB`} zyBD$p2`K_WitEpjrEyy719VG?U9eUo+}$k5#?wv~5t$t1j1-y0?&(P`}^w z8Qk`wWw#(JG=GS7Txjp->Bbo}{o)Y8ruRaD4OiuMJi#l0>mDebm9RoDr}kC~FxvI~ z+l+)@Xx+nIyCo=uz@_*WLKAkOdx6~#Ue!_}>Mzx~rs0dP=+hpk=;qwM^d|CCWN<^^ zT+&FXj3N1_xMkY5%;ai0YRXYkqkZ>l7r{12MaEa=k>rLPC@An{H81{Pwhvsn{k5Rz zkfJM5X@?O!EXb&yfLKs_mN~an6)3?(#=70D*orYPxwI|hZ~4QYEjhG*fw7Y=z9YWm zKhE6;+t+-FL8!jHIqFZQMjP)bVT-~(bgw*qO!lwzt?LBFoB}!?z|_N-0E4omcXqnH>{@0Mb*tVbuvy9}t+24I{>`nUz<`h}Y%jL>P_*Lx?<8cPHTeZh zFps|g;XCu2l__+cXqp;6E|qGMvgQnK5K}hP6?fba0b%vt*9w#fAO3mWEk5#4RGlfM zZFA`yn@`<8r)UJ|X8F@~`H8AfYDV_6GZU{-(Rnal%FR86=sbH~Paan#s13`a3A9l~ zAn>PHC9iJ8$LsPp?sb#~lZP$lL;R#ztTgIQjvl=$CCCjGckdBZvmpw;9&B0?>Zod1@XSL6UUZxxkI7 z7&R%&a$X88b_Xq65!STl=I9889M{>RkJZJ0^}7mWdvC@&bemV;qmq16=sV1Y&OKk^ z4YzJNdin67fZC+7%~@~rXV|llZweev_?a=e;3lto@!o~nQBaVLgsx$$msV%RCMOsQ}QS+Rv7zf#;4xQ$Gw?Wv~M8_;X*_sa`zr#t)oae*Aq zVZS*rhoek(UiV#tp_1oX0@P*0cs9mA{hXGt6HiRu15R6-!y{my*Bg5GVBVjwf=goh z0X+Q=YQ9Y!H0yPeK&-kCZc1W9TfqF%@(-8I2)A@50Obl>04zIM`lBN*_Vj;W|6M2~SC(*nCIQihSf(Ne0hQqxf+wP0g zDc-ajte{;szNl-D4-js|Gx@}ZGYdWUn*%vt)}B2c$gJ|wKq9AD=>uD@;Un79Fse)X z>o4D9%r^}F$=?r{OA-Jz+WMrMdd4|VQa(FRFumzFMLV}qeD1(Ac*l5Lo6z=+F+q}0 z6{L5pQ8r5y`96=&#bf)s7p0ilTub|Nj?ZRd6i&bSyn3;zQxf~hMITxs7cM8ODg7>m zav*ZM`}^Tl8?$~Kee^UNQsMbRiVqfAQA@ry9phH=I*Q!PD95RBe4LiJI4h7|{ds@M zh6!-1a&KzSpA19^zzAdYwF9|

h==HTGDIg#cn#;B;?><8|tUW37#5{b@(GdnV zXFYz=F?ZDp92?=O2nPTRba=F69C2!@fku{5?YLQGAZn!-AB`tIoN5pzSzSBE6Uz7$ zA>HH2jwP*8c?*n}?&1wG}2ZUU4$-gfg*TT#^ zm7ex?9%wn01$k2TJq~~)`Oc)wdog$Q$x2r+9KXQv0#3NkWwt_q$)j>8Io&Vb>v%|~ zXpC81OgBY+l+S9N<>7JMk<-bBEpz~wW?H`yfN2`qJbH1gA*m|>rs*Qo zH+-!*&_W}|??gkxN0=u2D~_sIL}rUpKI{b3WC0YfV_%35YAL zs?`B{*38whJr;j^1!xl##F3^6O!CvXjFag)eYvalf}5Zsq*O|n^#pNA2YOI0{&E?l zBS@5lhCAdFqsSkhUtLx`o=bK5Z&M*`P=$(~WH5*U4d91(_OZ#UR@kh(O^^UT5eZ$< zO2tO|joorZLW{K6tfH$*1&EiS!CG|<388=IUjK)GKT$k=_D9QZJ9_@%qQ4B3RdX18K>k8g zQ_#;Q&D zXup*D9`ueP0Ik+q;B?Ek-{c2zf?PSFxy`#-FDqj6n$VeEbJFtNC(vuNlx*&Sb4O25 zRc%4u9B(LyCv$YYA@J$UhACc{6>s&Hj2E=>v@}a2}&imWyc(3PqH3vHRDsI zD@Mvm#U#iVy;ni zZl#-kYd=Bn^>`xk0HBYsAKTMVpO?W~yEVKy=-u(`Oim{xCh7|{HP4i{n7706S^~_6 zj*F9~!Tl5GQuw9Q&TY8dX4YTPKk0mnda<`OU0FuVEi4wW0EwE@o@Cw9SF7QR)9#-s!U!Z|iY&ah{c% z;e?CAEOL4Ef(Ez8(LGC)M{iJS$#K3<{9j@%&?*g)J9%i*1<`uOK2xZ)Do)DodO$mK zp4{SoNDU$o0>-{@Ug5q}zQ~x$SXx56jX9(6aR`V2g!-0w7Kdiqd23ajF+DM7c&(EW z+_n>v~gqCVvw{%#;-q5kG%7$w(erUCax;T!AM(slJwNaYesbIz{!{MAT6e` z1%5ZA>GBlp`zTuel3ioNylkOeYxp%f!Zj8jnv-??R1wJ(Rn_l{$>*@Rk|~Ge!qk3i zZ(vg{aE_dLyPO4}$R(T-)rnVE2q|8e)Ng5>4ps{WTwR@|ETw->A3?=0wm;m{)v7bW zb)mhID&q5CEv%PHHASIjuC2DJ9A>YVW?(gkw6Q)cr9lQAKX<3lU4pLL)7cKKwvdCl z?^8k9pPK*SmX4Ri(C;esL(+o>!{>y8Nht`p;DdEctZ$21Ky=1D7oCn9Fbn7Y@3SD6 zJ5uy<#WpI)3op}dX*FnIvA;C8H*Eo@oy7UpSZ7hOv36d@g{zRI5@5Q?^nrciKT7AXipy`W~)OR_)NCqt)HbKWkf>s&6*4K3fyW zzfn3^_BeSiq0>U*B2!Y^av26MxLtdle;2uXCEj~EyfBubJ{o-kvlS564pbWgoBCfX z9G6P=c>zgpzcs9ofY#ch(tt>mZc{NRSs@`*C7 z*jr{sICk_jfYhg*lK98+Q35q5Y#n7@nPE*cGB?U7|? z%DLZe+!t7z|FR@RGS-!UHM_b1rt$8G?UZR?dv5CP{eBY@$-7_k<{Jn^W%zQaRYI7! zUnAC@4^rtQ-6>U{S!ad!LHC+?(?`BsUz;DWqb;5L-Q4Ba$ zhQbc&iV9kDkHnwn?HeD)>o&Ndlfb5M|@r#Fh44~Im6mre{1YZ09WYccEtBY`~>qfG0>f^EwH&itC zkxbBrOX$WfA?Wb6uK3zF7S|GU=YjO{0C6h`VEs;lBiv%ib1l-tO@DW4jnkKwY_CT! zwveMfS=D8^k8(OSy1^PA+5>~M8xfuH{uHW@?k3)J!2hcE8euXBvi6}h$NUITW5MSI zZ?Y(8E`Tpk%X)_84==!!gEntw<)VsnT$URck5OOCF_`!5u>pS_ZOMq={~zpxIcYuJ2=3cQD1 zd)QR!ZDpp@F1ioB^nkjr3@J^1$-<*&9I;uqQpk6>XexgYn&!Pn++1mue(zlEW#P59 z{-I?-e5#&1tVj2bhFj82r=D*ty9z0|1NIjg`vHWo?zMinW%(j0Xzo|_#=u{tAietK zl&O}=iH-ud9m}xIc3aB=P*P7JE$7=r%+}wI_SN$t=&Fxs``0#+6lTS@RW!ilIKNH{ zR&hZJAYS^_pomeneuj0Pmz%S?Z^CVATbY8}nC$kD;5ovOWvSNmuSMTr^)b=!jM+9} z-XVD6Rb_vUKcS464?9!eYrcJR6tyYqJza&W2d9tBfs2=C$QeW0anKNXtp31xU>T>4 zOoMOc7Yzb*8%%evVQn5PNNv{JJ7lyOxLX3>4r3e)lPSXT>`0!KZ(o|~j9w(pET?`t z-^i`Lc_ZK*1#1`Qt~0fLk_*sT!#HtRy0`@rez<5da+ru1_Y-}g_%?~gePvun#U?<> zT4G$YPe*i1VPJx?lRF-A7)se5_?+Q* zQ>WvPYDoH_OvL~|Z>i*mAu3PUE6qCf^UF7 z^pTa<4CTP`VyP?(9Lm7A!*wfNSBR7LwjN6aBpus($n{IKKK$}7-Y+PtNqQ>hSuS(n zV0OvRBC1IRB5d`h^8z{z9w)nOQJ3Mr>?^%KO3ndmh3fi<-<@jM^XG6@<*}rxyhteK z4;UOPpDrodvJ`0CwS!@N0`87|l?%I9R*7^Z67r`)Mkn~43;8@<9!xbbm@WOI$axlU zyWHFo+U!fU)qX=2<$^Nf#t8fB0^1;SXKUZe%+OYFSdxjE&jrrvZ5xZnO5OPfm6cT! znT?P7pD0Cc4pdL5=W`rZ@3~0_5HeCT{$=Bhxj96Pm@aXBq&?3R7sm2w7>gOGdrpNXjWVmyT@D6(mJ>FvZMs;j&gx@uZD{4Mo4T$o6n)i zc5yW>hfLXgHr)6~mcMTBMcP4D7nT&%NbQng3n{ie6eeBATplB5(N~OXg}H9ojz~^~ zmwGZvY197{cb*+XCnUHOXU)h+K(iJQi{2N`GHdemyhm_hGeq`Qbdo!Kzh;}t4~h0e z`CP*0{cfc@dwq8iqS<9};ZHx@+Fw}+`suA-*gD2)^LEpei=7u9zH@mQ@$FS_mZXiR zIAy1^TSt8Hj9{&qyYKoPg^sO}t)3R~)fDHA88BA$Ygl>ZV1IA0A2;6-KFhGppoh0_ z4~u)cSY=(tQb}kS*AH$STc1=J-o2*~T>8F*T3(7BVWgB82H3$$u z{qp$9Zn?#W`h^MGwKE6giiI+2G~u^Mx_)jOwYhJOnE5z43irsQ^S2*psGi$eWM?otL{qj z9ek#|F=M(pa1ka@ctlRxIcIPtrUA#WpUJ`&_V|+ZmNX1U@N>U~2I$5Yw^7sZlut?= zU7lqdx1as_-MIbS>Dx@KWhXqoTH!$Nf!~mDUD$!+Uz9}C!5^Iuiy2JJr7mveTOu75 zpnF8*8}5-LdB>fkcSO2bt+%hHWKCb9-Z=vKME1N=HcRi8NBPB3mvJ3lJpkqADZ;N4UK;GBpSMhP7>&N^tgeOaA-PxC~#QMj?L;S(0iL-mb4OnAbkIDb*ZR2fjiU; zKOw28u_0eQ6nm4xlgsUba`A`gmSw4gxidx8#mEqAM<0Q81D53|o6}D(2N7};Qhyjv zBf``H|3%Gt=1aL*;NIzr-yL}xpgtlxVxuK2n7@X3J;!Q$0N<}29JdSkYvMtrLHi3G zM*&;e-GFDH`vMFU{NcW>m`wO$Bop!7exqhe&dr?yubPrpL}p&ruV`}B27h$*eI4bu zFM)% zY+a1a^sN%CTYLAuYCi=N%QxIDJ_)caGgPF04$`w?;e-C}KUlp0@};W9Izkrm5oaj% z+&?X~kxgg9v3-vQBo_By0h-16b~QHSj?5#PW|hOkd&K2)@J;)FCY}i-1rRl8*G@;C zcO{uS_bqe=W|v^$)W@H8oNOY@g2+wO-e+;C4|Jfo?)m0G%f0^L(3Wn1iIxY(vIk+^ zyAKSBNg+cwThVjHRh>7Lu zKB?IKMlX9wynS~iX|FdjBU5VI?OqxwTyX>R%~oBjy1Qvl>u`Un-NpDCzK+itL+Jx! z`$O10#KvjWDd(mNS#5EKc-w+}R%-{2EazA(`u+do>_j86QvH>)j15_GYss16%bWLZ z)G_Y2j==j=?Gi7?4g}&Sgi;;Dg1-wkA`*Mplm5 z$@M$t#`kZ}es+%vmf0=q%sp%sCD@?7a%NSvP1(pJ&}yd^*BO? zmaj>>F7O{v#ej&qH)y{lA(hKScK?$~r7dPTH>=fAP!Kv)NIrC(eXlQFzB16;MBomL z+8T9Q8Uep1`41Yza$2Ho`J8QJH(F%nqtO7U>iK7(=w>ul&O5(IeFwSX_z4q z=1%eGl5ou`3pMQvI=g;HFApIfo0o6|-o{EW+JV}u)(mz>G7yrYM1`B$P% z>4`O~w|nXRr^UM(yQ?P--^vAV{$p~9f9#+QM!Vy;d@wyVyD#;|EBLov5U}WT*%0`@ z+eHUgMinH?09Oh73pgjZUae{jynC2x_-N2~d;CEu0||V!ByPJi%x^hR+o*&E&iEsY zZV=4pWB#oAD2Iqeun7{>Q;CwXn+Qey5tPyBgati-!`=O(xPLD7*8BIhd|0D;t%Qxh z3tId~Dv4ilKt#S(UJGoDg_ZKF$g|Av69CjSMHKimEDyf?9}sE4J9*_ocapBcm_o(( z2C_tA=o`t;0{av)cVATiSC(W)ZG+QpxNWGhD3zR*3THed1TD>9b9k2#I zs%PJ3+;aT|MbUs88n#$aeGA>Nl{9gCsye+6HNWl9@^Yn-jADbs)jR0GrmM+=gn&wHcT!@qW83hEpc$Pl^*1F)P5}}^!Ja?y0JVY zFl|`HVTlm^#1PoGXk5>6TN}WDUvLVXVEcO)XF=%`4_(=z7%gdK9245=TeZVIowdlf zzoL=dHKiO*-Eu4>&x=rhn1!i9tlss$(a&MkJ&0HT2AhZVjdrD$zojD=i`XAT2Y5|Z?58`j%VwA4lr4@DqCRmH0~!z~K@5?%!qMD;l^DjPUAqyne@*2u`2n1!y6L{}nBS6nW`N zIzJ`*D_3RYgm=~f>K2Pve>9>D=_Y=`37o9XI)I5<$V>kO+yLm3i#VPZI$jnaM+Nz~ z^eE%&+NAfrV~t+b0ywqzQ(oH67YOm*gbycXCkOOwtL-6D;O_D9^uHHk0FO1!vvb=z z31O4h`>ym@To2&<8m`E_&^hAxzI}W2KNnQFfK^2r{{Ot7U{7mk z-N3+VYZNv;AtFQQgpipD-rBxkz#(vPv7Kz7{D3wJT!hw#uQmtTXe5Q5IF8CAO8WX- z$jlKX&40pi{{KTsPl8nc-}Df4gwd-f)~qN3ED906tq+R8{GaBE{h#D@U`a)@k=js6 zi18c#B`2P62v~8>@Sh_1$IXHBG0)GR>~xWUIK$syoZ|(88k^_diFo1>|J*zCTNSWp zRqdsdYzn&7E4N7z!+^=S!+%~oS^L~HKhp{J9EM}`&4C|cW7|$<;vb-HezxZg62NsH z{HuK1llAEd^t@6{Y61d8U0x?y4nWj(6m+r#8w2@K|0ZSsX8>>YXk0H53^f;cT<=1; zVL=)=a5P(Dx^EI9v;1_v%t@W15n|Eg6p-~T*Vj%#!8LjGyc84BxZ(7g#=IdAbMc zWs1gKrk(N!{bJo3vBB|XSL7X#(|9*8oSvv>dR6h)z3N(HG|VszQ(@{-q*BdowHa0( zn;xEiv@Ei#DKZw$Ftg}4x03yk+SwB&thXF>Ov=?d@@}@yM&Z6IzkGkG}}pf zh3|Du6^MV$miCT1nxy;tuqpHrfB+%QxSNxvWw?;#VQ|6`ti)`M@2T$uBE6 z*QubX(d?n}Ka2#HSbI53tX1;zBZEPov^)V=lU|L^qGk}JKd-(xWgKXVOP(#^cz5!> z3N#gNfct=^3J`0fLgDTZ{~q7f7Xavu2`80Z?{2HlYt5D#AC<4}EBd%(b)`!ejk4+p zqt%iwowe)cof^fHeZdU*BJyLcQ{9Jr9p#Jv%OGhXd#b@TG8V<;p(bq4Q z59VrZZ7=QCq0ehs)bpm-U#IS&DOL2HciIR#SGpQY_F1Pm`eMV|It9C4ex1A1UK@IW z;CsOSEB|CQ_R+Y~5xg^Y?9g4VO2F;PNEMf@O93!ZLIF%VCnc4c~PMtj0u<*HgD4=_~vlw$f-IGMAzP?DCCLBXUjNVU)Y$-g)E<5GiphOP}mAzE=Pp zB`h{r1Nmw6qOGPe1Lh5Tai%VDErEf%P^%;Zs-g=HK>~4FgU5wG;;t}7E_Sdo7Bz&R zdD5#L=ST6YK_sd&!p^LnatcaSo*u9j!2)-j6rxZkB>w_0aQAFnP?l~%aXHbTVbrd{ zE$(L3J@b(eFDQ3RDgulRP&RwqyZ?*DM0G9b4+~ot=ZkInPWGV2&45L&ACDVUbGaYJ zo4UKY&Ns-W*B6h(ycU;qU#T2dNSwXG3ZH=a*uwC^rF{bWlulp=g#WmCl2nuRuL3`X zftj&4x&GcjM|H;1ic#*A%7yvFhGZr{Ov{LTcIzum#)5fq&$IHmL}U?NF=yIUq2 z!`Zr+vtN#HRw?pkw=yIR7$4wr>tSq*L*Au{q!d5!$e-oE(Zg%4&2@xdmB7uw?pXT+ z%O^|=LR8<{vr`R%KdsIl2M0bpFW(`!kU`N`N|UEq*BS&nME?kQ+NiZ=kC&6elFKbD zt?cEUlli&1oCtgy2j!YRyIsyRQW2$dXwK4H(q=yrxS@s=eS zUIdx|a_RnA1NN+1mH!W-fsykotel}BS85ff$x`D5LMvBz(1|DM078+D;s(M~<}Op$ z5xds$G=$C4TEX_ot^)+cnWO*6@##H5$VVVJVopviESkX(tO3p+UGfU`G_RFc089kv z8!hR#9^XhWH>&curpSw+M)s{0epe8%C$5J%V;bUsJ46^5a0N5lQx@xQE7#!hJ@m#y zqB@YPnyQ4;4{Ra>fh?Ex-bo_^SAj+jm2p?ITl%8jE(gJ}07c!%4}VK1>$C^=E2&KL1pm}sdYsrRljtW&a{ca(!q zd{8lsIURxVb2m)d26qNcb+L!|3t>%gHt4Wd74PyQ(0i~2z-_)JnR0bm%6KgZb*@mP zp+P3NZ)m~9is0l9zVw1kINkDbjC%U)!o8P?2okMO?3 zZis6J)xcthH!7l$_{89WlyWVTSKj5;>N7rUK%NbGf_eC4is<_Fd5QoQWP%J`Z%I<1 zEk!Ep_zySlUPNc2XTdC-%8m7DAs_QtfDNEwr1mNeorA^XdrAUMu6hg4f$Ywv&1|w= z9jBG{WtogLt~F zfGg(H!asP~?O{Uw`DCD{l8ZJ__Smhx?!%;hPz5H1>^9z;P7hpB=U;8vR&Z&JrhNG4 z^T;Np)UbU{x~jI+He@e)(89^nv!S(3nmjFwXzzpbyqNe36Bd#+V0V`8U7=wLJwYGx8dd5W=Ic7G4ptPP z79Ub0Q>&1ly8?&RqqcZn%0-Nn)a$f!`~5wi63*3l*tI|Gh4=JbB2v#ej8p-X!${Yc zaljfBJ0695!1Js6VmiehU=Gm#WtTX9>Mo z4!WzBe~mCy^*sPL3OOvxSIAY_IK?(Lq|;Cjml6Z}B`2&-4xcwljr#JccF&uCcWPw!XhwWln; zTaM1|9gayoL7m0i(c64BK@I#4hr8`r7T&KCH^{mQiz(HagZ+Kl#r90{CjO%kzsn{0 z7+VYOhW&^p%HGI(uQ5VKz!fa~#Zm6AOB&$jJ#ARZs-3%Np%&)oPV)ohoIu`UcBcbV z%*JL^I^;n+fPRP=RIo&#E^f&0T5ay?a%bQPR>k#ebwN8#vA#VdTK8gxL0q({ZBPH7 zFwAD(&GxLDp46{d+?23S681jqtr+1sekd+jv7{7+Jg;I9@PFjw#lAeNKyE z;C#18s+G7nHlE43{?TuwtVm+SPc*}a+QGKBqvs2lVUmSk;k~E(&%TsmsUIl*a*8Tk z>{mkL-iFC#Y_k+A^DG0~(%$oz6Fha1c?`V8cZma^{_Z5+`h+jI#!z?hD0$cFdISgE z0bmFguKL}c?+c%|=0E7{S|lvAhs~RN!81%pj8+aJm$@=P~)V&++UB-qmj{ zx-MB6Tk$`PB*xw$`^@3>)UEeXv&nrgdbjF5`M|GgzInjZ6~cFCFk-#mSj0#E2J#36 zvZM})v044D+1GM<4rf>SX3^Kfl@Ou9RtsfoKMq;tlIw5_58QAa)DOXj%XnMyjktoG zQ!6~(M?SH_7{Gf>gty@3fD!z1-~fVAC7#S|uM2rCXin6kO7))t?r?*GbEZ#~55BN+ zdG>;#knOK>TT;r~W8PU_VUu?$J1x4y_Gxkz-FEvf6D?bDU5y7c8995%*wwhyz#U}f zE@7azV9S962p=LOfRJHCyX~|maG|Q&_&MdW;uo5Ukj;1#%g388=l4t6Nk;WsATTG} z9nZQpr{1MqIoZfnB&PB_<)oB1;)4l%^bt%b7c zI_}>(6Jcb9K>qgADl2`XAdHCp7)>kdam=l?{)rFe(wGL ze!rjPmUyJ@q%)dg?AKMF#GHS9iSQavZ;MI~?eS9UEL1s?wt8}9a1lj7WU1<5Q>-1k z;*p{Cem!-SneUq!RWW{gX3f6d9k;uyy5?@<^Ecsc!g|u8e)%+QD|K4! zaqf9-i+=teUujE4U8H9_`KKN;`3J0{q(t#OvhcA|z{?j^Oe^=FWG`n5TQK%&UQl;1e;{ToV-Xy`xSpUTRdbe4vHCkyyun%I#l*3`IlV8?mz=gDxP<{ z$yq3U>JT2SnvC5k=Xelg>S%b6Ik9Lb@py_C4lJ!1SA9Og59x2l8=O`7X;;oxdx!!(lE-eYQSYx|__PlJ+*#vYUEczvTyMr;jmO48$vSVt{` z+EggeoSy#0<>ulMf4!j=mGhsUSH5S=nd_#ye-d^GK3?W+Z7Js5(X9cBjW-XZf$9*9 zn1f=he2|ixM+YZjpUO=fq8^2ro6b%NGL2f#HWXIJg%)COlan(%V0LGS=TW$nuRf?q z`0SKyjj7!^;$c`Vq!#i~dEIP?j^fVZ2(=6>+%fRL$Fuub`Rc*~)z>>7t53nw4T%dV z?bgAHbBpi%yT@t80BC00_y=O5_RvbEgpCmVaY?GBfEX^fxZRUuoLZBrbK0w(pI%e-N`G{ zQ~a&Yi1xZ72%dlPI4q{&*0LXJ-+fhCOtZI7jLocDF0OWF=6A49><+y8P-!c(!a(U* zeS}it?bDnQlGyZGv`vbCy~e(q4ND($S{kz|(7VvcQ!-xXzIFShRhn38i^ZLO@Hp&7 z65K=lB_1_AJsF5@h4{!&7f@EN+SewtYHp2O^LKJ?SXpU4&J8PGEdwl67dAdVmiai0 zYKCicK7e!TY6}rzS36wwcM^72;^br+f*&qH!$iU)A|s$-*-UR4J1X+9y>8IQx*FuY z-*AVEtP!@xsD`9*YPEczjqdm#&{4UCb>=Rw7BWkp8@II$p{t8>2(no=(S^WjH3!b20N6t-B3pc zDHDF6MH>O{wFde6K_%w>dgcXKSfXu!p3Rv0IcRj$la+jwU7()LvK=%>iWS_tIBiie ziPUcfwTh^IrLOl{2zB~&e!~i@3_xj*vi=68Nn=ta-bO)ucGOojJ855=5ITo@oz(1v z%T2Nl*O1v->Cq=K%ba=OmLcQn)_t*m z?+OpaHDM?{gxxI==_@EX%{bQLzcRPD-jc1M1P{0p%SovF0xC0gUuEUWCLZEjL0#;Bt@*e@ zQ>{&o9LL?wz4p>zGKCy(j!s#0)0-*x2oFfJK7TNVP=eN@9YsD?qc}ZogiBAJnH`kL z+V5o9Q8wx{*q-66J*-I+t4ASc<{7^gbXkQRJcV=I+4CbkjF1`C8j8HEmu*LKk{S|L zUcxyw>yR9pCXuIV-?{pNweOpVO5v=6WG>>aHt)VK=@^n9Z%E}rOGTFneY7H76Q}jF z$xqmaItcizcKhn;H`kJkr)=4`OQt*{j_cWawS>!0P= zRn3`^Glp+xTawCG8VAUc2iKVOXj2C-IDdK5zHzO6MR58M73r8AY9eg`2Sshg5DoP; zC~Fu7a{K&_Q(n%X1}-6z&N)Gzf)C~Un#^`d*GI74-<9Ol%6-6x%K}CUr%j)Q1(^IKu+Ys8#v^064s ze>Vkx^WKku%d(*9vim~+%(laMx6}0}K<%PTeRK<_Rw1z7y>0GD%F{_Mvi6q-gCS0; zZwS7pcMuC)e! zK(14vEQvIoqhIyU93$rha5}m=8aFrZEe66VO2|d3@LJQ)*#n%#=S-V=3J~mNY~U;a zlYd08=XBzojztt`=tOcA$KP$(;L5|f=v%JOqJU@C{B%2Xt)nKZj%y39sY%8|BvL2|MAc)a_*dZ8V8yM znZ*uIbZ&Y3UGXhHyH;Ku;SRP{L+a#l)X7)$L%5VGUD81HOKs$`+v=SJgady7vj5DT zbP`J0S5O7+l+kw%9Ed0o@Nx(r^`BIU0q#jEzYm9MqYAFUPE}D2A3m~n{ziDkTEnx) zzzy*1PGEiDsx2l*y8jQ^V*Yz2{d*<-%NF{-qu>2|CH=pBCH?oXg(9|4*!NWxZjsKT zeEr-Z#dk|YAigj1D7&|AI%oJUogXc`d@DkA3QPh5}FL{P(4tw5AujyhG1WnOoOKQ>K?RQIhwE9|rh6rp)8#^ga3LGUVub_SHbh+DPt#gRk`MrP|Hb zp(F+7zgr_7?;84Ir!7`z!PB2-nn)_4jGKxc@R~pol<${@Ez+3~$8)nP^%`WH4SPPj zVlBI~2#LobgNk)n1z5Ec7S4|qFl<5hHxl*CX^*AR>AJX&$XYoGaaRBMW39y|F*3&A zX&#@A5nSuaO}R9>|6~U(lH!L5+*RB+Xg3SA=i|9iF|-sHU?@x5uVlNQd1snD=CYaO zrl{mKb1W6Rm}CB*v_cjUl(&@Ae#evsG-EjsbUy8Fu-!*O4f64>RCMqKo}uD|>Ogc& z_>K2FPjn}-`CK6_#u3HXgJ&G_>K;I{@~6efxM}bJ1?Q_4(&l7s7j#BHuqVq>X}K$a zR(0QC4o$*_qI5If3HE%X)=U`l^9vOSM=p$fE;PQ&_V}wF5C0HpA=`ZRffV@qwXIxV6KFLj+e{bxjUK1s7n8 z(6C6$KfG)%J+Pt^Qtqi*5=p2wRB0}@2x0~SWRqGsAEl?yv4(Qn?tF$5;i&ThUA6X> zS%;-dVAbb{QmArXHZ3)pwN0*yHmUl|h~7U0LC)S0odfO=o9F z;-i#%G-a6U36cM@#E<{YGqpJ^G>sPJRONr-Qn5*p#*!Hd!Zs3JSsDC({mLO2 z|CCZ#_bK9Tl&`X_+Iy8Btg2=kT^`E38V-*a4^E>+!l252#m#mOmxjP^k>1%e5(e#H zT3s%T@j%}C2*(FiJe)bwU!|b9^o9HwA@{|9eS3@)%TqFst?j5gyq=H=odP`#(1{hl+%@kf-$WWHssMk;cHfVHp?Hlrz#?i5Io|rNzgtatgKv+_GzMw`*we=Xm z9@r7_p)zkWM?dwLL(Lz90bIMMB0*`-Sq{@`OUE;_Df=5XX2KhID)3o4`eSh(otC)z zPp&y8Vu@2xCvm+WA{N@I2T?3J>x-$QQPpkMCw@$YFjoQrSmFF@89)Fj+u7<}0B|2? zuV7!kuVz02GE+B^0kr*GU$dqpE zcZmOofXT9r$PId#E-Ir}lIddvO|1iL&v+wVg^1aJiDO?+iI3O0`IqdUxFTu0%rcjT|Ab05uQn#>ow}RyCl|25XLf=|y zq+>|Kqx7R+T-7hH&Zyc3v#pfwEe9uE%Ktzt)&s=CTwsF*k?Cn(Tb8~$O%11=x=q6!VAjS34yNG|_;IqVYZq$e^K@{X>IMr4ft&eTUH%6MAtlUsCed2GM#!=kTRm0A=h zD7;uH&QR-gPxw23R^v5#hmZO|L3h_cPO2}uc3<9hZB5`a}g}|;Vu#;`ElW;o>}Yr)Rwa; zU;3nC*SEoO#Kx?+# zw^W$b|8Y}&;4>nbk?To)%Z~Lbn4f&50d@dG(Kv#N2g$L=-5$>xWLL0bLaNG_ zGeWrQW}Y_^ynj|tQO^>pwgP4iuzl1GzQ?rFC_XbOEb{8!uHS}n5NYJzD{n4=iJfa? zmsvP#drAQtdZdKT+5MRdW$~`YDuI(s0+b4`4*c5K2{!Y{oofD2#OehT;F^yr~ zdbmuI8z6p`2<;FUB^iZ)h%Vlg%aWH8yT8PlH5VK&%nsg2^Z$@!fDMF+6MC7prNZz`^w9&nQ$UU4(7 zi5=|R*tNLC=$&tlq6pv~1-Id{yh?a?KL067S;e*PnnLk5#Ofzd(4hD=vhuOZ+3@_4aV{q8%PM_>LvowYkmWH8SAsGX><=wLK3@{3WqG9gWc zz`8Z9_0Tjz?7Fy3?a@%Y6(DSs&j#tmQZZO>t(1Nnq8Cb6vM#O$k~*wMe4g7F3Z$i% zh4O0Gf@BM{{uXld3m_v&-j>troF(Ixh<(2CCvJLaAfcF^M$2l*_8@Doe3MzRg>}C& zPMn?~Fxje8TPX{==B7Pg36ij$L(%80eZ+zsfqc^RIEOx+P2O-25wtU`O~cukIOL)l zW9f_SlOg4i-$vz4SL~Pd-B0C1xhEjclz4{+le&DFMo>TI zFSp=byG+zDaYi8H^i3-bLpGW>9H^9f|GC)CH=3zH4MLqqc&TKzz0P*kBLvXJ7au`di!0qb9;Nt8I(`HuIDZV|XGq#5 zDB%Q67xiNNCDR(ndeW6X26Z6$C?#?e12Z{N`{tt;V&o!xLd^36pzPMe^IBF!!!m}JoB`>EA_hkz3_%>*=w*?v*S1j~m9BEW5^1|BJYaUEP$P!h1 zt)I6}NCS>_$a}mzRTPF!blVXJp`LXayJ>A{tJNYLW}|0H3`-37gbXsbt+F5np>T-Y zE-gaw_t|YQm4@XpVUtMf#lGf)k(ZM5%SvPSVk%g>*KsQKTee_#v{j@H&$rfUe!@(@ zd6NGq+8Sl;`_V7f% z{*v&=p8GkAVHGs3*`QddOU7{-K1t&2wBIGCMyHRWR^41VVVK|n01hhyH&;23y2|}h zh1vmCsGn8U_^J)A(RZ(m{rfBT*YRb$hry(gkk*zzpU(wJKHOX(=5W(}qVQ4eIt#9Rlclovj#1`(8+KA`ON3froA2`JGV z<&p)~Gt&j%r#4Se^jWfG++fYa&NWiVx(&Da-U+su;qGCt$!Y9LZ8qbhZl&=*XSid5%|piZ#s*a*=Judo@ORqo4tDT+1`K#3##-A@ZTxs~D3(&;3|qEe9sivt ze~teZu5`&ZotSs)`{v4P`z0jHqfZgl>phlk&xt`M`pH3eK~||@5%uW?)o>p1jm(VP|EetThjLBGJ-{v%1cIt4pIj^yvFMsW=w+f zFvgUa`XI>ZtwhT&!7yUe>Im6Qz8s%;Fr9dsyLV$f9Vh=uFX7DC<%5lSCYt6%;>&$i zl;>F@b!cbP04y_l`LKD7&^ zxXbh+-B#CE-odFzm*r14r;RLAzlHfqT7}uG-=t|Coyh6q-s$?cjc1=V^>uy#uN)fjflEa!xo_e9_O{+w?)JE~ zBH96YaO%o8#!lH>@n@w_p`7Zi9XO5sY~POmP$J{_!4oZO1-v8RsB|r~yXdnIV|@N_ z`X9xBbsNgSY3!ZnM@ZnMOg#N%mJkRI;lNmN$5qw!x*Lvb7ty-)FaVKX%LI(^sM9T6 zYd@9V4(=4$gv){IJQTARzwqgO;L{oBl1~D!1oP6D!5l)4JaCe*29Gmofh4JK=i1G3 zMgIu+$dUaE^5BIO<@i5eZETkZKYsqn(HvMM%>Qy0xDK2xB%NDxR~cZ{s6P$C3jiC< zX=ilCYD99j>`!BhyMQ~61}g6ppi?LQuJR_WcIGcC?``Xi+Byqd^8mfbFF4{)qlDj0 z7_u*5S7rFm?)?&Zmw^oG5}-=~ABDfLi*^C}n=mkb*a80Hj$b|2MWGtlB;+0XF{@uMQ0@UEvPUOH5yalgf46=3v13gv6Te;k4}Zf0`v3p{ literal 0 HcmV?d00001 diff --git a/embassy/docs/images/embassy_executor.drawio b/embassy/docs/images/embassy_executor.drawio new file mode 100644 index 0000000..b76587d --- /dev/null +++ b/embassy/docs/images/embassy_executor.drawio @@ -0,0 +1 @@ +7Vnhb6IwFP9r+LgFKFX8OJ13u2S3LLcld98uFQo0Q2qgTt1ff2UUsa0IU5y6XLIs9PX1WX7v1997VQOMpsvvKZpFP6mPY8M2/aUBbg3bHjg9/j83rAoDdPuFIUyJX5isyvBE3rAwmsI6Jz7OJEdGaczITDZ6NEmwxyQbSlO6kN0CGsufOkMh1gxPHop162/is6iwutCs7HeYhFH5yZYpZqaodBaGLEI+XWyYwNgAo5RSVjxNlyMc59iVuBTrvtXMrjeW4oS1WfDAsjuIh89/fv8NyGhxv0zMH1dOEeUVxXPxwuMl9uaMpmLTbFUikdJ54uM8mGWA4SIiDD/NkJfPLnjquS1i01hMZy+YeZEYBCSORzTmMfNAIHA97Hm5E0vpC96YmbjQ4ciCodgVThle1r6utQaRkw/TKWbpiruUC3oC95UyXlRptMrcRBspLP2QYE64Dl2Byx8Evh/AGmpYP6PshVus42HtI+wGW7HueS6eBN1g7djnhnVPw1rDGCf+TS4QfOTFKMuIJ8Na5cDsCmTsa2KjQMx3SOeph5uPLENpiFkT3fSUpThGjLzK+9iGv1j6SAnf4TrVoC+nGlhKCov9i1WbcqQEcpoCFS+oBXqnw/p99mdIX2OIdc3HjzRfqlKF5+0eTXhl47TAGXlDk/cpU2YMikmY5HTiOcWcAcP8UBFeS27ExJT4fr5QZpNUwETkqmw0HMx69h+Q+oOOnlsnc/oRvLCSosqc3T+1zNl6/RZgP3QI9hGgA4OTQ/exEpHQJD+4PsqiNYbtikVb6dex28AGboGmtB2o6I6rpKa/r6KrgZzPVXQAWmSUp+FJDDMcTnkKxpVp+LGeQCJDJ/LUSYPgtmwQnJOyzlTIYu7LOqiIMmzHOp5ntNpwm+UOWf2Ge9b2DdftS/Uf7Ha37V3u/KHYb7cHRq8ezroHAjf87xdG/mrrISrbob3aH7WHEtjz4HBowNtdJaeuTWrsiQp1qC1QV+a12Td7Ug6sw5heutAgyPBxFE+/Up6J4n3ulQheguJB2HDhaat4XHpkmqoFuyPFU+u5ZTVIGNzpfyQN09s4W9awR05xkoRfRMVqbvUXrWL6jfFUKtahJrX9msY+qShZqijt2/zbaj9nH0eU1D7JsT9DZAYaQ2GrL4suR1fcJl2xXXjuQlLeN89ASE56AWwrPTU5/38BbNXeKBfA4yiPY2mUBl9LeRzT2KU85jXouRLuV/bpdIgPqx9sC/fqV28w/gc= \ No newline at end of file diff --git a/embassy/docs/images/embassy_executor.png b/embassy/docs/images/embassy_executor.png new file mode 100644 index 0000000000000000000000000000000000000000..2a83a3adbbd25e671562b374ddf04fcff0a75384 GIT binary patch literal 121382 zcmb@ubzGEd*ET$efdOK>5kzzg(%sVCHFP<^&^^r1p%NzOMpDWEX^?IZM5GO1Xiy}i zmF{m{gZsXp@A>}t-alS`o9)aM=XIXzTyd;pt&97b>I#&{XpW&!C`u(oS#1>R5C(-h zs7Y}cjyw{I+(e-cdw9qhcsO}k+c{!U*Lh|3KV9eHvUS0ET<4X&&clOob>*H(vB*(gIatu2nZpC^{>)VXtL$v8sBEX8;i|%= ziud6&FxS~1#2MrCXAn)8E<5-BAcA_%wiW_Pp7NT)?$>#wRc+)|WOL)KHeQQovaHD4}JI1k~l6JiWZNEOZ<-1RT`4WE60CYYhiATW%XKO+^=1 zZUN*8b~ph$PrQc=PEZkRXe+PC?`>r0YUGA-v699psLCn9(hT&y4L#Luq+Iy5b@c4L z)E&LmEcu1)^zhPXcY=q3n!7z#pU*{yTi;q%3TKa3vK2xmYfTVR_g1j=B5?VrDRNtB z$YFK$ybLtGJn?Fd!g!3DtCK3)LSEO-!AhB*53eTVq@XXwr=w&i3(6(0?x1X~sp@R+ zhS%Y?y zh?Y_?aFlg`Yb{-Mc?{L;9GndK714GYmim@bmYPDk((+gx9zi)TWi+3)JHM8ZtGceI z9NNLn$&*V_%}YQSBqFVaJX1r%*%9Y#2_v~^6I5k%TySodyl6PX?W67}0Ee}WY&3bb zl!R>+Y^3-!&~TGH)=SP+QA-*2R+>)}=VqsFXyIY!pl2aqh4tbQl2Wj6QE>8*=5Y~F z5^#4_!N48v0xIq{Mn0N)23k`3P9P(=RbE+ER?QOa?4aW1s)rmCtgr=-td+F~ zh*4hGT2K+gE2zhdhc7CMh8BXBirQ{MSRZ#6MJao0Awe%I1vum6?yjWcZsB6Vi%}HR zSCsRx_u*5NcJWmAuu{fg@o0VULRzleZZh0jdhi?!HxJrMP}fEP=gDu2#d}EEcxy^~ z$;-hh%ygYej~J=l7hOA ztF^b5I+rkxPhS}0sm;aj<=~=it*xf#WsD!Us9JK+rBUN;?E zBR4yH55&-YEHL)M&Wc=EU2a)HWgJ>q)=0__qs61(uF8kQYjay$Dm(Ic85r2(wH#Hk z>O#U&I&%C1Qu?0mnrK~6BzayTX$y4&tctX?6rZ=9rXi1$uC$}DDi(~9M@3hN-`>Cl zudRxQmCIYWS}ECRx_JrXKz-${-Sl{b5GA#-M0_zXa_nFKOLE5W{ZD}5mGcl0^GBhs zpp;~#&|V3%LkqaG*qXh)-CeSb7Ix><>F3|&on7xL8_J#2jmD>?zhrBTcz)bkdhS=@ z7K8bQePdh7XPt)?si!?mO)=K)ddpLr`Rwb{yp<728QoSgnK{o5&7moIc3q@qZF za_@@7Kd$~RPaf^~ZpP2YB^~Wl(A8%i@?Yr|`x==?xn$`*$259Y}fip3au+G2=Y${Qd=$jPsJmZTZ}i3-`aI- zA;0h6%UaES`0pbRQYMAdOXObmU)EZgdKHn$Zd@XJ-gd*z!9gb!b1>;Vn?Z_Rw!w?x zfUUXAHK&mp|7fRyyS>KF<*9rwbN$7-GffrvnWFHgn2YwqRo>H~mz*_ch zBj2sX1w04c6s+cYzE>S2EuxP0^xq!w@$uR!F&ylNj2>R#3}^fFn>TRRZ!XAP1*)Peic;QP1y@^_w?IKAHAA4|UAV=^5DeCG-8l z-yTM_0oB)w9D=EPxK0#XHUE=26d(~JZP38gmAt$BCUb?GBRC(qI!HM6itJ2B2X^nf z;JIL1c4GM1m*pm+qM~xXl!T*Y*3XV7h;L3^Q@C|Ofuhpmr#60b*{*4M$a5rPXKO7h z$G9|8m2qrp=BtK;X;e+O1BLI8kEe)Z9&i=@`}?CS^OXykk1q*k9y)$rD7?EIH<{Vf zDMVclTikZ)UHu*Go_20xV&Y4&c)~W3UiE%)HTCU&#RqeDl^FcC&iq)AD<}BvCT)hO z*>oo1bL1ai3!a;rqA2R<$uVa4H!ZT&(%sa{W5^zmsUP&)+gY2gn8_{7u5=nG$z0*3 zLiS7cO2uN9-z8Den4O1bU-sp3N;RaVOJ}|NQ4kd{cUI>r{bKzHuj4Vl&*l>l}M^#%z5y##*$+p|+_0&NEH3+s;;Vuis0a zZR%U{f73@lG&YOxQ0P%tJ~2}?hW9(B#C^VTOQBRki;ljN$eK1b)~7Dpq%c#J{_YoZ z+Iv~rCZ^0a40_IMUSD+~IdkDcQ`24*ML_%MpCg>fgqsqA+a7p)`EZ3X@_E zLO+Qk7ptYtHaTP{b+5-ld(gD-K~8#raBY9IO=?4j`sZ9XGNJD{t>>T8pJe*1k9%@Y z_t1Ba(V7}iSAAg_Um7>z-F`5wQZwnM{@||2oLA`GiWhgAgwyD#e2UL#Q1E>7PPjI$ zVZx9-xsmG@aPA)qwwGgL{s}9q4)v2e**c#m`smv`AK$*Qn)T##L()$9<_0a>6O{*d zpB9Z3n)624z6;mTUXBs&xV@>P_@Mpia+6K1HOq>9zj~kY2a0eUx=G;;UF{l9YwYHV z?-0L)U{$A1b&E;o5^E-*NIZE*TyU8sqk2tc64$n{Acvlt0wK}cJe~7%W`k6++GXX)i=C;P9)O$yY9 z=7ayUZ;fW3PK`0s=B!@+=O66~XB$`7{Gzt3yqf7)dd6Tezx90Vknch@(R1V5H_S>M z`N{NDm6*)5G)C;j(0GZ!nu@uSfv&|x*A%BDi9j>4z3nBIr;6#`OA}EgVlKlsi@knX ztZa?!i7%3O6XyC;L9gF9SN)Rj{_^Eze}6w(K_Sd%XNB1P-9tk;&b2!P z{MKCQ5t`$kU(?i5$9;W#o?$NT#f;yq_L*!??-Uj{OV}6|i#_e&-?5gdUo-RLQs}87 z+xwV1LA}p7Oj-itj{0!ZsEx;H+|%pY2fW?FH+ zRCp%Qe7JO==~u?`7OuVqBJUQ;L~ zhSRLd)^NmO&$;Z|;Z}D7M!L6CVsEfuej#wrmpIO0UNb9{=BxuU6mk0=Jh{5MFhV~S zdnxpVna|JbodiZECex-pehpT=Y|XWiz*>jvv-Kgz*(JEvb-$}?Xc*Ujk0Um`oM-tp zw!U?4Xyt`X_`y24l#_?r7g;k0ru?LHNTqb@pYsaVCFz`WqsfvQ`)48Qyu%RiFLb1u7BLVWjJ)8|=z%6_-wWo@NPBM?KCnY%R^X2J5=83v+Q_1Tt^ z{eCMEM?0LhjX`cXrNh&`A517Pb7PYgy8XM04JYk?eQ&&dN2h@cZEXBp#DBBsRU#<8 zO01n(O-@dGagOwhQJ=wRcH{G95`~e%!E*}>rER`zL!M$0pNFe`>AvNUf#EOMp^0HW zL;`%~#()9BOP19J&a=VHu7@r38o zv?FmWJm|wro_h-Q|DbzdB4G|7UN*{D?a%sap-hEb{cvDOD{%y%QwhN}k zy?N=IXXHphn1NYbAA4X%H zE9V3hL_5W|vd30@$D{PSdV6Cx0UvaQ(}|iic;F~}x7XcTtA>ZoI>(Bv+i6-0Y&sI$ ze$d|DH8aUMoYOqAmYy59ojcP;oa80;1`zSLYn!x=9=GL5mULS?gkYJ`c3Pq8wPD}r zx}S*6*qIHIl&iaRBw!|1Jy9f8d}mpw*s_VL*rM^6!g`4VkwfJ3#y6i7E=_i3T~~=V2%piA*s(I4_;AUE zt$ksBo-m}JnvHP+J;n}?x5lu4^FmbGXF2`VV_ut%bh(G;y+iX?ciWx|cH(PiWlnhz z2rQ{yTo-~wxR=lbX#-@zwy25G7FySxdC%IygW7Ik`BAE?(_c7ix4-a=u+#GUg0E$7 zT^On?<>PduxOC2!!nk?4Lv*36q}sllMMYCsTztMS0(PYB%S4IO0NYYKX5!PQCpMya zHJh2(=hmw%lXC;5z9qajZ_Zw-?;r4iI8Eo|xA^WzG1X3*eKNfR&XqZQ zc{w?1Y8o0_tIZ~q4ks~z>)(#Fx|PA^)6G1mm+$H7dW~cT&#nd^(qy7#$nF#}My40! z#hglO=@tNj375O61?k9u}nS-yBy{k6K{%IuNNa;)AKxIeFG1g!0z4`m&wHk)m0hd~* zzN8V?Gl5$JBg#uxMK@4aPW(|a+O*mqN=rJNzyk7(}^|25M-$?VfwVfm$l z?kXA8#xB+TpG5A}sC1`%FnUDkC6OT7xhMf;~zgW*h3ndxFBn>{h3yHfmqLG}Lzl#qh6S z%4Tvar9+%TG3grO8-HsqzG0oFUJVzWzfoKYj%iLDSypPG%QCed^UTF8xXiLCJZhqk z6|+#eAP%WhZr#`2oh=oooiX}7*PLgjm4qA`T3WQZ->;Ag*h6jQ3;>~rGJ|*KTmp>= zduiSR*5&F4!zunNz*ywdtKw7v;H`OD^b0pNq8c9rP!oIlP~O@r64u2lMO8 zmlPE}GYzn?s4qyKctJBEO|dnI>on{U^O(}KORE089A}f1Q5LakKLZc*=BRWVlkcQn zg{1LD`*H1N$IF{_tid|~r<{KdgwY421CFrSCSDUal^3+gB*<#Ng zu~ARDUA9Gz==tI*WVu-qdmEkDH|JRO3=XI+RpBN(4&D2{EJs)GJW?}sI+sq!wa#-R z#yp^}U^_|7S9uAUU%x}1VGj}<*p~+S`-`MIVYQi!4=-dPzmv&!9C#<~NNT z#c^bMs0qFi**2?&P|DO>nOgL1`~m{jMGfyu&cdgZm%piG7}#Wx%O2X#r4LUQAa>ZrWb%&?pdqiqM~$=w9phI z?Mo_5ly-~wc0K*>;*lmE#IlBlhN8#KyvNVA@?d0oTVptl!@nka4*%>QG2i_ilZu@^ zS*gupLLqwdrlX9b%W+M&z*DF;D2l~C9n~5U$mbZTR zj`t3i{$%^HUvEY-KTlzp1ysq#py%o7`AJO(eJywgA~_vqL``t$bx)poUWxNi=4J06 zr&JDQ139s~y%DXcm9fY$3;W7G<2#pJ%?W_hV5|4dhTrhpwlf+xzoo027Fs{}>^RZOvC|up1S9(pK)7%`_@RjkN7*-*RVgrJh(^>Uoaf7b&Wfae=Z+mxpq}A} zArHxbg((%8o8ebkmF+I6&* zn;Z&Vze%h3PJAQZP6Gu#OoiDSlh|!|TO+*9e;i-`?%aQ5cjqu6aC?ki(}1aSXXwFn3TBab9yjv&vsQem z^N>i>W(E9-MKPi^w=y1Mo_YH=Susop-zm11e!1O*8iAXU6_ZK6Xmj&qpWThgZqWLRWskv^$~yHG$ce3w-ktueK(G5; zz)9QE(lUIUicaM9u-}r}N=)sx98iM#Nju))oOK1B>pJvgYaxJP^LHy}H%Pl^%?W~b z`*Xp(v-P>Al|8t=vd+Hpd(`LC>mW5ZQ@#JxLi^Eq9gEjjWE}?mNn~HySM{tQ;VCet zx9IxI!tU>JeX$yS!uR1GNdUmlHIjgm`7p$UC`wQz&4h{MkWq=FA#;!#V`NP$@OK?@ z!TFMhjxlpPihCbp{(QkR8MzcSZ~x4V3_spPABYC05E-A8q=%SO>M;T!*_xRTek~nJ zjnAeMn8ubV@c!vBBQlfLjXw1U>e&8`hWsBZgDS?()nC5p zl|NhYHYp#)^^lsxQPL<-{^m%T&o-~EIa>v0t}*1d)IR-?vxcI`d2o^qbum^;ojulfp6g8`(2_xzi~GC$N%1>XQIPzZHR0o^P|SW51Zud< zLferuEtrL|akXsg0N*1EZQ7#X{8l;rKY~R`?l4n)w(ofQeg5PpE|84i(oEw!$R-pS z;>YICkc5Pq)?m1P`j0Z9P@32O`w^tNe+f$S|Ky`&2s}Z5e}gYXTA+`8rv0-KD9u0$ z%C(!7fB)~ZrS9oT8>Afg_em6)v8Tx!pT*u_lnX>+TRrHE`k$zQLg`x2 zH{IPAHcafQ?0--E|9mvnzBf^}h2r~zX;7%C8;p%ps^NU6JrbzdkpcDNn0A0r_T=pIvH6pKq!lR^JRWY3LVa318&q+2%?1LlBzPz?aA9XW zKOL+y9ZaT#OWha{b;79+N}WM@x&;crq4y!k)Z2vJj;Nsq8+ZEP5cLH_t+*!N)A4PT zyMDVP;yG`UV(0E&FtH;FEU?`c5=h$+;#F-^KunBo{bC}}F!np?f)E*XfLGR1h`5Yz zTk;*6R{DQEH(VdJ44^t)-`rn!e94={W<#tuB$&vN?>aI@(E64D_} zs$|^Zb@5%BABy%yP@Uayf3ZVWPZ$CujGEFjGL+a23uMI>>&T~ z;j;+@HAH(a)b0g55?j$Z zsLCN!*D2i8QX6mQL8`@x_UCT^SLt_Cvp|Hs7;02tbsyYBuF+oGfc<-F0^uKRy_`U| zE685jIsz+Rc|G{tOa6IrGJGDS;GngdwbtaxV!o?oddlD++ozL!=id&RNr)B~7iR#g zugf0H6PAyBc%zX_PzBhW*k2o1i-gq{E1qz1NJ&AE$=;$Xr|akYi(6NO9zTAZ@$Q{e zROe*(okPdjW{LomKt8UQ6Hz>Iq28>*Ln+6s+6c?@S5&iA7sM1gOO2iG3B;CD#6e+k z4A(=CBQ6m1S`zGI#YteX{Ab#8ws+m8oX@xWiiC^Dnh?+G?ztAie?}<037*ModWq?PaQhveKO8V<4XN)yP z7)xiF?`;y3N`KLV^DaP$r>yhRx~GvO2l7CdpJQEJ=stYfn>XD+P37tKIq=z1WIfXc z99NJX(>i{@WvL~KHHtuF_~`H|E1toTe{&|)nw}IJXppi-mmuIc*njpH-51RLzFnfq zF)D5;@GB1eJDo4swA!aQH4t*{+*U+(Bij>#5U+*qj00lKK(bp;QTdP51Xowh>g6vg zB>ofPmAzamnx;NU826!II8!(4W(=V6C!0G9fq8&U(h$8kF*Y4rCph9)=rdPL>;j-9 zM)d!FpQ#J*ZZ1MvuKawH%#xaWxDFy9bI3S?J%C@;30!ZW?}BBTmn;D6)90*Oyki7d z$sXc9FbnpdU&>AIZY}7;d?D|G%)k5YN%3?NQbzdJR|*k&)wN?|W7CivXw~knOV{FNh5M8mt#@L8ndi~LAeD%}Z z5A0PHKeGqV%8ABRnOt&J_A36G7?V@%{Dz5hz<;2*xiYVh;Qo7l zzRGdM{*55#^RL3?JdUChyEjt1K0Lmpa=W|Ad#NQJd2hcg)dlv z;il(oFLx79;6|%CT1O|+@`T|QLWO#VwoQz)9L2Z*3951_icQCk!XZ$y8^PHXgRFT~ z+N&4Y4e}-Z0o3QtJP#9FUrOG~cCKF08!0)@VPbHMafnvPHRbFh^TZ=KxuFqw zeD4|=4pIwyq+%Di1-Vg_w(`$yy0#zVMtMu7z{c0MYMC-k6-73;i02W$81{+ca

k zqB>gClA4%Ux9*gTdD5kR4R;A_F>gI&Z!@ue(4HwWvOp`G7i{4sb$yTCljFt<_^?oZ zjvieXGXdXahN(k@*E_%LnMN;ED{e)0G;1aAK9Vgm7nyssRm4lJC91W;5;?@wXM}TToH!eSKwoj?dPk+Z(PIPlXP<> zkEj_Odw1uDt3^L;uF{h$Gh|PUNafL+z1;MTZ>FN_vTNEBk;;-tO3r#}%48Mx;kyvH zsJMHtC7zlR<(7BvPi<1eiZ4@O9FPrfHT{%^bFXvQi8~f@>zg{kwmyq4=gG%6iRm?M zvTNfENrcRu)Ek+aLg7T#L^_(KKF&nf^3AQ(&+6!{abkG6gfcTM(0ip_a3md&nS7$S zf4^WAvKIEJd&keSEt-lyx^cM;C>pkI!f+1c<7};4w|b?=IQ+^NGecjvR^iYrc%^P2j=LebO%dPtai-Y1Jw7uyKWcgH_jqf# zrbJ|OI%1;J>%YeAA%P3Wh*`f$mN7_tpjf+PI=0~JK|DIV^SL)q)!9}kp5Pbw_W-So z=+hnlJRdKbh7r!%U65tnU9n*H*Y8~i{o}K&odr+ee~I8!yk8Ffz`t|3sNXP}ls(&G zg?%i`s`PI!fO&cI!s=X7m1FH@h_!9mAewNIZ0Yx9b4F_xjg}$ni<>I1I%0~9f`^{! zq8iU=E9fzdn@XjvTsQZwezP(x@ws>mpBq0CCc8!}C0|IMFo>;Y)lH$FHQF4=lH9LnooPS^Q_M2DTEF9{9+e-{40yms#3QH*)u z4nC^g8c7I%A61oWg=`$;4yEAbDXn791+%EuaauB@DB_ZFJ_%CX;3C*x%`<1t^Z)gcTY~?+>G%Lg@RzUyT>omxy`h3;oFeV1&O=mwee3-U_cOJ7vl1!RSiNGO z(}_%ZClnSC^DbZDtWs$4q{l$HJ)$4?WERG;Hb zjC?LO@cD#>SnY|K#~G7kx=-b= zt5OVn{W_Lz;y;s9%GT-!8E4IaMBr`}b_04c3?`mW@>@_0eEIVIVyGlD_Al#Oz}8~7 zZsl^6Nc6VT+A~HfjeuW_r$4(2jI-fpHL&v|1f*?4QMy-o%NOA#1r8rR>^JaMVmte6 z{bump)s!!5ej``pQaT?c(B9a()toRmdx_&P#b5psCj}K>EN#rL37hA#oLLE*F+kO^ zDNv|HA52N=WtE<=2ss!uwGko%S$!r{p-v40&cfS6MOM(@!5wvq0pOxf-w!qdlmZl^ zhZAQ^Yg9CpMFLX9W3$fAb%#&P2`ZELWrQxd>^sZN54?PT+QvI;;5()vr(aD3Fw8bk z=C|RV!H(1PhSYw2Ud6`7#;#9vzUKbAz&!Eb_Od0i3yZLVU z_VV)bvwq_W54#jR6mUO$5rMK|ej6`+jL%Wbt911e36zQ_PXxUK6+eC8w%f>@Pe?oh zbW(RftJX^p6DA3I3#uIkC78=D#P$rGHxwAR_CkqU0`uVZz5^AQl8ekISd1Ex%gv1; zU~`}@frm2C2p6;c|6-OWe#uoHQ!lL9vF6jBIK8puKbi{leCB}@Tn*vY7d0*>w4yWL>lCAWBlqv}!aeePVL1T8pTxQn z`}+RM%()@m4m@0|cmNPLHFLB#y|{X)dtA1hlc0D?A}P*D8(cZaEQ^EYh_Ws280>Ez zEuRPOgsEGDJ7k|O0?0s24M_FV2N4)m^G=IhmP87hvgZ43jK@I9sIRc;&vWBc)4HIQ zX{kleT^x`n!rmdjnU$3TJe&_SagL5{1rZVAo9Qm1z?eMd4;I5sJi=ZA;^?x|^Q5JRy8-~J~i!GWJM7_$@DgGg=udC}>#kbH)%B|oZ zg)b{wJ3GMgjZ2-2i^{RB=R2_)2|^gd)o)?+C2bK`yAtB#3$_)xp!uLP43{Y8+rypa zmLp@3nk^&JmMk%{gduR&tiMc^*k|S$*fj4;D=!m$sFqdr9=~ zSkbL_cOFD%$Ww?43uho$)V>fB=DDFt;@d$;WZ7jdTnxpf2c=7^Z3PT_jXg$4xo9jS zBV!j7!0BFC(=yBgzP0F6hYEjcZDeHRbY9?2)~h_y%!7D?$2;s(ZYoebKke_fb`-V9 zoB9k=3LNF#JL(gzKY^o+_Ff(MivO4SjrvD$$RiTz0wXcFKb|VEoy!l^xc**z#3-(9jK0we>sMQAKYl?PIBn5RA}OSeEPpcV@z_?cS{4e1dHvy zGOj><%XpY`f;pUe74T6wF&E(lj7wDEx6N&~?F>S$mh~tx#6M)_)>OTJ-$Hx2X^1`r zsohBW1G|0VTpbPNdB_dXFEke^-?e>xDfh7J^D70@0d7t~B0|~sm$_Mvu1SI*W@H9m z9^;SFo?iUca65oY@$b{o-;RH$03LbzVi2^id zVy*l>4}6Yf0}wGmn}q4Ej86=+(1E7>F4F(eC+J$cWVOM4#2MPp&XtZ$fB>=N1+cT7 zZxx~nLi1&0WTL$xFHZHI>B+&Fj`*<#>gedmwSqIe6dmBV{i}&a;YAfV3Ij+->Xt6@ z^YbG~8Kk(T#M<4>8I@Kfv#%Mb07R+My1Kkv#<2u-af5n|QsV4jIWGF!*)|#L1X0h9 zf-3*mkn}B4F|i!r4utWbZiXcg+Fn8(wYzemCQE#G-IACh_TG+?F9ASb=a1L%aUV#0 z)StCYJm+b512{0<%2|shCnvWbs=y;WgcjnGH%ITDq#b)~)(Q#9!CzrcMA!(y$Ac`;4=!jNb@hJ2M>eBly z-bQBA1&DjBa&{qLtx}r;sZq!5ip_T(PKhR!0;FLOx}^{YMkZZG&NHCuk~>+1i-$}e zwl$m_1hYwMO@x@m6EFug3a$7H3rysi#w7BP;Z%K(JfvO&G6t9JQCIF69n`KXo`8I+ z;L*}1Q0yYn!iZ~{HGAQ80U=_e--XgVPG3jT*3?CsAtFSB;n#ugNC&IgdC2Ns`5 z3vy^=O7{EiW!noif|U94$;-t)EB3{ZRW)t%JM3>6+Wx z5nGCIYC`$cgGvqDDX5eyH$KxM!wo}ygjOnx=mEec6Y@Nxo`@Mk{ zVWS+?MS~2~Hl8~zi*UdVTu7|R{SbH7RSn`h8rVWz-_=>IhK7b|2v_<*G}O$z2P(4b zG;-e~c)$=+GtZ#GYNGfMj`Dw>D7(vun*YA*h_cT>GXB07Z{ucqcagf|y$i6&ivN`O z4uC-LS-=#`F*u5_`i{+CfMf^wbt(l4Ruc11^8vVBju6}MN_w232-A@-FqQ|w_G%(s z{8UFPgs%M_*_4LM^&b%baTeNB)811sQIW<$wSZl-)CpbQ|3;CSZ{h)PBA%o~aE$sH zQWZT-j-b)BgPoORcSs~2N>g2gbtx|4nV4bUIZt!AkcJ9zSindEgtv0nZdVX+u)e>9)6lv*G%=6=~0Qi^yc z1Nhl=8XDANAXFM@HH1D>HdK-#?^_b-NV;2CF|TXLZi0yA;lpXB<_=GIkJ^-TEB zLp>by2&5KD5+15f0~|~tgCir;Mh6U@@{qcOzymbcj~`wlObWc0Uf-*ffa8+X|8Xzs zprvgW^8P>jPV#%|tIs*jc)jE9Rcau23Gd$(C(|pfe4=S6xtuLB%75Y^5j;2;_kXS^ zglfnGC2lj)?m`6uoCZ{+t_SBplg1@P;;5i2$V#jSbt%y)2!^`OXf53`Lr&U^k`dIxd%^2!cz`}Cs30X zpgKuvf;vd@SJVfuFui*g$8m&T|;6JaAMz}h04TZ1>g^OGzS@k$3tg*?(- z`!=P8*pxLi4?%_V>d{>el+-!W z;?9JOzhs@SY5TKrXp<%wto?@~tZSMCr%*KKh;JdhAP3^F1rJ@3+b{6l;WBSp{ekrb zLqNR`1Ltju3(iMOH5n!ubmfb%u3~;oaZSQKU?(`SZQfgU|aSh0!;4RqvDO zP>7!L3=9f=m$&cvQ4|(j%(4v3AE23BPw{>Y=-|KgxB#$TjmaOjjI5^}ilE6Cn^IBUzBp2>y&@y(f`qE8zQ08Hoh&Ljzq6K^4&*&15*# zzB?z?%m3X~>$_*cR%Jy*`3U%bz?zPdZjw9#-u4A}+l~Ywo_|d^B>=Sf98dC1c#Q%H z3Z$Q|H&8Zy=KlKi#fOy=l1LuAKL9mcbQZK2Y?-9RAfNpTZBVj@-ULub|8z7zg$luA zk~5`jI$i(!IP!8WDiRL1|9H)!_OHIugy)T)c-ugD9ER}A1FyKpQH)s|8?zQ~&$h?I+;$-PSN1tx&fChW&XWM6mq`1fz){MR(+_9>zvn z?(1E*w5d<*n{r(gdvGFzH_3*Js>d)Q-iM(-0Ir9hX=s}!vC$A(<-r>#+}uBY;|=)W zeI)v>-u?4A#IX0f8R|4hfQq&g;wH(_|4;Dr1?oj{3FuU2)-6)aU(%HMp84Rf)dKUu z`N~Ju6u$TqeOS7ucz3Fgj!*Jn;gdYVee{OUz2g)oLxqsAd=E(jNH%u)SgYu+!7=?v zIveDzpDVIimgPYVj$oV6|1dRM0K7tG-=6%_zAKHhhwMa<{DBDmD{G(tl96Z4&A$Ku zRIq=29@CDbh~O(y9}ZIfGj;;hsF$Q_>o*uhOF9pyl_(+I0EMj(yP;JjegF#CC)3{d z!{^GPuf|)|nG!#=pf5O5trQ@`v!U7r@r^Qhok7E4<2Khk_LGY!Ql`&jxY5w&99J)_*;@wN3iZ%4JhAeKQaIpYnm*lrxLk9$r+h*Iu>=WSO}}NZ%ijN zz>a&jT@b6Uc3F9?%I}!cToV8uQGl81L$&$J$5fhLEki)nPs0()BcSPu-q}RQ( z1suBhG=O!vefhJ+tki6LFrK@ZNq(09aN9CxKJpffV4rK0>A(| z%Z7dytA|Q%t^x^o;|P}CTh<>u`H$pY+L7)7uim@u3VEF4<2RGY(^05hSO)#7o9RQP zUzT9=87KjTm(f}HG9rParJ#P{8JN)l)~_N?|*%*k8Fo5Ks8$+Gc7HZ2(6&X9@?>`X3r#NJtZ9tOeN5)91|^&EC)o7cRjRR* zI*0xQA2o>U;B9!0wFdlBQ2?p^&DPp`xmWI-Bi3iBpxmb6(_cE&tTvM?;QX$qv^Vbx zZ6SYj0rE82eFJ_6v2%d;I*Ks_TkF+${=IUm-x4Pc`d#Qh9W7?1_PrkJ&H{VB5a>S) z=)i4t#*D06)=7A-7CIC7N(RZ{N@gfKD7J3?=bvDckV)1r2j$)d;TDS(Ip79&5dXTp zPFaWqu9G3prV0fnu@CN+Jm@`@dFz!; zo?o%L4B%D7QQiQo1sQ`h(h%Zra6kt;`>t$DFs2OMfFv4Fv3pSI0+%fk!Tn|7WcIY3 zl_x_*c)ZI6qM2DsZQQl>L{bZR76xj5(h%@w&R?dD)raYRtoJc}NP^knLM;sr3|xGB zPac*V-xLZ_QW?^)???tMdCm3Y&G8nVcjgQpoG0Z+ zcXz&dt_bEjbQpspo%$DCOUqnTXx)%^AI(}h6)N>k&FhOcH(FVspRA$~*H?Qm?m-5Y z?k@A&l7EmFFj;i8ANdRiND$MqPWJvo7PGP!=-|5|e7$j1dBo+ZYBQF#HSx@V-mn|) z=S#M&jtJ7n!AU-FzJ?lRCB_HP$)n3g$5atQ0nDOo@L5OmZB;;MJWfxp0`69R13-1N z#6_GjYMHa{7mikAAkj({`{tMGp>uTsFI)zbl^}MFznEv{ru7Uw@DyA&V@wVkG{(hU z$tXI*8wnrNEbI-#&c5Wk9Ik(H`hwqiovY@I?+aX>-@niN{ilRJw(Hf&n)F?qZrX1` z?r!WA{F{~lYQvsW%V9CeOrI}LPO^YmSCs$ty5exsbAOpy)H_QJGxwp>Pc~NWRdPB* z2oU?^kQ8M)eqao049`y99}{Q#o@gyDns7T$BZ{-Tx^58DabsFMc!-qIzr8;idH?>; z_~h9azg2Y$cbWjMaq)I+w;7~Eys|nb@nMV#8w)=#W39=D`@$N3JrPLRjjjDk#8Vho z89k*5T-17_gTQHK?rh$3kuPhZA<69Pk=k;b(b~CsmX3fs|HwniKk$C#fkJ`Z;u zG7%D945b;^{ysAv{n%&Icx`TPY`ug?71p<_I3ly>Qir8$nj8|<6Wj83-U->8?#$dd zDbAIRcZ{@H8htRnu&|q4JNRZR=>}4+Ucjc=rg;XeH-D%!*Xw&BW#GlV>2jN{=G%f@f-c*EOyozrq>Ich_Ko0w)rs?q znqL0vbH(`N?~jUGjpSQD8h5sQS%v3U=gcquWtU^Mb9_r;I?HP7!rHDCr z5QvR{&9IS`Bw8<-MEIrinTXIh=s$8n=Vd7fe_>lg+#MlQ%vZY1i(Z4GWpg!HO{xCI&KJwzonMiy~j}(1u?glfk zOM!kn^n2ny(#0QkyeUl{PKyMy!@FVe3=Oh26Sk0;?*Cl?==Sm)srY_BFKH{{8q<+s zu8@0Mm3N$pv@hF3gDPHhfi$dA88WY-x=)bJF(f$$%?#FJsBP9?zbUBI2t&sb)pf3!SzcJl#N4iNsFq7P-hkZN+Yl*3i!C&)Oab8?(Lo zj|xYJ=0pQ;QUnYS5c6!eK*WX4=6RG$zMZ=j7qqWx!5e7SEWNBJ?;&qrpY`OmB17Hc zXa<~$EAD5Nq;|ubbWIYrmrOUB?eJ4So{4z4D?bir&8dC7caytMuyX!Iq|WrG`x&`) zGfDBM!_?Iucjj(&p3$)s84zF2u`kWb`x>~l8K$mE71qDo+H*YZ5Mj$}>I#TqnjpWn`#L%+ZT;$EAC0!4OV8eXCPP zyY0Q_G}P6m4!W8xcX-r-wCV@R#ZjQ{J+vzh|~2<21|A zA40w*FylDzw#NF&n1T5#nlIObx{iHI(0%4ZHXrgd;&k*JeXpnH<3|T~$Efi^mk9nt z)St9(XKnQV70T;JJjz2yX5EVtiUxP&pSY&piz5E&cQ`z%b+|0bZ{B3EwpL%Tj-BGO zNbFgQNlu--s>0v;F&+&Dmk%FC#U4kZkm4eO$qL7=_1{fsV0u$Le>wiBmYS62BK6n5 zB-PbuH}wwfKKJ7fE)2C5nF*tG9g?on93Y->J((P<+ACewE=A-&L8Rtf+zdw*-C64Z z;qJVUet&~}@WHTe&r0zDlL6n-dX&EI=CIrDx~5jpr6FI9vEECeOU0a?+kDb}zdZDu zEy@n0Y)(YVHqd@=(S9v&Z^gFm3ZV*~duRxP`O0g6;5Hxp@U?RSqI^%A zn*)6FM^BKgu>944K3#Dz-$+W8U%KeYOjcl0{4u}wpFXqF!M5i@UC)JhId7S@qI$U! z7))Z1navS9oc0_pj*1-Uq6ynuC30+U`Xq~TbPqho_egkHWU2(X8&xj`?qS@y*M{Agd{gj~J3!uLHZ#E5>>=|ppCRGez0VZfLkA2JSyW?KKDg?} zV#o)dkY(>W*kp}%pLo)6xGa3p{MCc6>_L?L2W1YCIrpwKY7(fmUjDFX*lDVLWgusA^2H_3aU-C-5&+LZGnUNpv=H6&~aDI%nBS-IlZWElW{m*_=1{ zIPZ3d!$^bPs6EvSY63+V*JSvX1AXotJx)|{^;*NpZ`7;Rm)5H%E1Y81kUwbw?in(7 zR-JQ|%(k^;sCx}y?wiPxPA10pd#u&gRyObpeWZH4ap*|N6(!MNj||yCTHET#$c;~A zj`vtTX8BWGs248H6ApLmoeMJv^f2KE57U+Iy^~VWysC|Z%Kh~fi+^{6r+7l9HPz4XC)oUxR?2!;Nj_jjsTnwJ%1PInVq z_Xe(CoZ?(V#i?A|l8vSQnsZE>DfsxAI~6CAHNR4GIo>-wgulqTFj|4e*!xoiS5r!;9&+X*a~?le23sj5t+_X+zd9!O3DuJeS$sg$ z+Y@R)vDg%rjyd;jMCq-}ux*j!>%))y#i#0a_|~~c*X-$!@cr0)(Ny0>Cx7KTb-vzM znHgpNELpbMy@}J1+q^&SHhbdQoq>I1chdsO={mkK5Ojc=w9x5#7 z>SOUB%F>$oixrp;$yB)cvZPX)ydA(8;=Sdw^k$atf9G?cB&#?&u) zm>v)sLUnP~Q!<>gra07aZ?so9RPs}H0M)6QN?p#GD$mSQbstP_8+V80LtT`cH@w7w zx>7`9TL!vu4-JV2i3sQ#{@nnP-&0>X=xFVZV&`Tl#qJI&+q%cd9S$O z;+cKU-utY*_B#78ibfuipg)G17OE6PgNKJI5tS3yJJ<)GI!ECP z71Aba*bo#F&+|5-)|QmIM4b{BPTtiW&#_BO@YHZMocYrv7clLc`FU{gN~@9eA;4G^ zEb9?TcNS72Y1M|Hd4HgavUgV6IPHyhxAcXDw~}BXmo9F9tRqy3I)Z0)?(_FWK^YeQ zp*l@qkMa%yxkPt7n*+V@MvKyQ?i*q|D5{ls`fotW(auk;+XYWsoAnekM~y_oD*pqqa_@<3ic2rpvE5oa6jmeoJduT zYJ|x5pOn^kn`~xV4}51=MY>tfaeKawo!U>%kAdd&`Rg&S4_BS60f!-cuaGZt3yHCW z#yZ61W?`E&I6|^ub5-gQhxzQsK6F!&H z3){@bfB(Uczxu+Z6bb@#9gc|xQW=ABqTLB$&nPB?LpAaB`U@<>J?z87D?^Ls@(O{N4z^2vv^Q`Qz57$Fx&J}n5I?q$chgTn~ z&N_ZsPBXN+UhfpC@O|}i?A+QyR<`If58zppc!PL8<2njSV`@q9j^ypV89-Mevi!HP z;~0r)+lvE3 zT8R=+3RvyORe!@rX8&Qe$GPQ~&vI$Y;j6#SY~;M~`Vce)#&eDkD46?4pwWU;FXbO; zVxQSj{KDH>19BXAlXaJ}=-bkrAMI)K-mKlmt_~dHkeFx6kjNDC<*p1kiK@F2q}90!AnOkPKE4mBl`*)hmbRO3|wVq{SCM zIq)MIELdVQ$G_s4?=Pu?nh!~Ux2i8aDp;ihhak&ZOhv-9P*`7McHc=$(eD_oR+z;WG$Ck6=MRm=s3G`n%jPL3-rx+ zqH)ZdR6Fr2vLT=ZK-2%5-1J{_^{}g?Y*E%0Z+??&0{_5J1gLDo_<+PY;+O?ud~3jc ziIBNy`4O9Csgd9GmHbD}5ukN#eVZ0-!i0QQs5kD zYk=hW!6Sdxouax)KrZ?)gsk}O?g{=XA}7AZGL`aAzfQmuF@8Ii#3bsWutp3!Rx0+V zjh>wI<7wuQFE$EOID8D6MbZB9j@zAeKk$5ddm^|&f42>*}>4o){u)$P&%2uKR@~w(c*#NHe}_u%CIg)Hwk3{L->3Gc^5?Y#Qna_4#`ZRNN_}C znj+RI-FMTgdG`p$?*e(gESgIp{(0`h76O*_eO$Q-Y5b-WSR=81(vQl_dhvo}CS{RF z;}2P|A7mERCoV*1cM7eO1J&I|!5?Wpp|=D%o*gpWI#m+=;^HKz4m4+CWVUO>d!P523 zP=Y+sOlip7F~*n3F8N6}o<$Tol+=77?OC;vgVFwdukU~S^N$p?URfC7uZrazj9pE8feh0NV`SNeHBFz0)*k%kQ9q{Qg$9!4uj7-~@Gymc0RXX1GoSs-# zPp+&GkYIiaRK_T3j27us^^s$ZZRtKDr<8MY)%l5{=r!QX4?w0u00hU3G|8#t4sQj&^4d#7Nv@Jil2~GgQ+zm z4jMe1F5VsjGRYI{Sfei?s9Agr0|NMO;9pa(-@U-Esm;(35?Jyfc?{QV{* zpd=Mz3?084S?_hh8|s8ln;={!4E98zm9SfC^kc;7FFHWLuD`|?_&q0(@E~L?7Gn0E z9JBp_1kk4W`FVIbTbnkp3;%h%+1w!uN~dP?Z8MnVk8e>@q8=l<(w`tIq{Au_OqH%| z6{zU8G(7(UfNI0`4U;&0_M!oQEEy_U`Wlp8H22vh#qSz&1A>u-Itd%9z} zyi8~Cke^@8sc>3_S|LWCT-2blUSgX)+^0OOCs~=Dwh$*HTS-v2W9&_a!jo%KVP*P@ zpLY~EH=BoInvwwH`r|wpc7q)==eZ3U@1awS0xULBcdSgmdHN&4xf!|vt34hRE_ao7cMUvT*wluNg>As zDpcj?=z$ypJ${g;7NFfq8Ro1Z7_Xed6}>r7!9RCSNvNQ}GiE|j3z}?Pd$UpZTF$iG z>0)xd-WMhJ;s%z#Mp^vy!9sXK>lhVDoe?nJd%3QTTg%;}4^3;d7SCeH)`hh>-mgBR zAkx!`4nryfjL;i!^$xHo7b*HNB6Y6&6#<$CAtKeE!Vuzd^{x6soQDTm$K`J`Zb1TM z!D6SJP6y|A{v7=9M&zxnsITApeY3UH*Ld%$7x=Kn;ql2S ztCRgzi0B(`=l*{Q>NWqD7segN7aW0>z8zfG5Ak*7Ka<%f*0d)OP*)rNiY|vDDnlDY zck@*B&-IlaTk9K}AF4=j6-Z3*d4^1*w$yQ`)5YNEVc+;ERb zAmB}Bz+*tb(u)Q>+qF9JJ>KYn9HQ|ec-U4vtijRQ>C)~p$Mt} zd&LN3+S5@*Sa(Qn4zQGH$0x`J^1GE5(Xm$WLcFe8DgI(^8%j?6XYir1*g*Q>J3y6L`Uxm4@{sqok>Hwbskg|wJ~tExYv$#{pr8o49Yq(YKs8f z$SW|VEvrDJv04=}`1bxZednx4R6&U12@ZUpXt~x=>DIYUZti+*&&cPVksTzj(E5SY zdat5W{vJ0KZW8iLNPBzwW%@{YbbK~UzL;|QJx3224ibhI@==n^GQEDX zGbEu4LeN$JlJgcp5&V?rK4Nep;H!2Q zn77D)8T(};@-O1fLZI+e91lU1cYL+q1)w3ue=ncgQ>e_LA7onUOOF(ak>XNKk4EUW z&|hM7ex1e;c$4A)Q|nljXYqUi?MxRCqwwN89PXa?K%_gj#nMna@%r^Yz`3bbIwIC6FF$eWSpD7^b%1v7Ywk zp%nIB6j&omeoaBGW^-$n%DlweBa{hUL=QMEx)JFr5wcRlwOZ~Y>*q(<|@yL_BV zhV$Gmsg9Pxx&ex2dcrAB;kt(jeoo3P)HvhZu)!{B$GreS0`+%HVd_<7q!0N*HG>kr z)89baA7*R)O~~u?0Ado%Bm!p_G%cEK87YMy{aA_;vVD#13_Uk&C>?^-b)SbjsJb)P ztDp;QcDk+PPK3Y2+g-YhDf};Ck@8<9B{->C-BxI|ZAVkX(G3V_#H$IP_(zeU4SKmB z$Ll#^)L=uPy&#Dsp97H-tY-u9(?k4Mf2eShvtw$Mo@>_>vC9C2 z9LB$Om$?dTri=qH01lHkIx?>_ztzLV)|xUR@GrwigMD$tF2ALu9V`zSz!C?N~1i#J|- z|BQU0B>MLnMl0$lzV=M=#950{;)SdIh6%MPUgpwaJ&(E8qMA>Cf3 zU$0nqs-1Rz?5_cddw=K0m)l;D7SzOqiP7s6p%H|Zn5K7BH!IkIJ91*sUaXtZDmy3I zmYU$psI``n&&*}nLy6($-g!*q-Wz^@vICZx2zRTnkXb11pV80WU*IlJmK^h*N)X-s>$~?DJvAd< zv_Nj2+BXsr6hn5|VZoJ|k+{ykICQV=!6rhgA(9FJag>r9W8lryoCbi<`UkiiG&aORY07$WRJFmgIuAu+~+g5T3IX!R~JWJrg_KtQ| zcYHvE)P*EGZmVWtv5=5ycSUY}kjQ$S{O^EzDBYAUlw)AFeW1wvikt>94fP^R+(GH} zzOW;;cMM zhoKb_k%}7AOs{U(BNr)>4gN+<|MTUsu&y#c^3ZLB1%enU z^dKeJhgzqS0rwg^p4y_C*B{-GvCH0T#WG;2XqeQS^BvvG!KCJ&VxtSPcf9xogZ*Y& zTl`qzQ0s}XK}+A4@8g?rMIVK>nTlcj81}28H8uu76jba*z+u-L>c3*eN>e z-(}Z;!>0b97Qn6sV$w;oblYpB#hycfex))x9}Vv+M>hIh2#0o@Iapbw{{1eBw?BpN z&c7;hMCteZ9=x>wsWtWXN+l=U&Gf+5bG3ZR{~-gBfuW#>Fs~Xl?a!jQai&^NDsuAY z=Ao9iHZ$|;E4%)@uJ7*!N@$C~Nb>^XLt=gGAjnh<+sBTeCO2N_${|Eaov_HA4DD^V z)IyJx@_}^uK+8#h;+|=E|N_BlbK&ph51%1ia(JGMKco$dRNF$I5K8f?x#W`MeQlJ&0&jFAXUO!NxFcngj$ou8%p zWsu_}YV=K2m-~xmOL^R>pc&b)maK^mA0YSmfFHC>9M;alFef7qMZT6xGGMN14lyOJ zvxuzx#;O!Z6Z$)E-I~Y)=fA-I?UuAkAvMisy|MNTwyj{PtYAD0*WP4}m65IE@b8!@ z!O;sQr2G{IC#1(x=VeC22uMpb`tJv!ETsO*oY>T)2BQSAG~F6r7y1R8ErCWy@v7T> z4H-|YG5hx0tnMI_6%4JcxPhC;;f0OGp~8=R6WrY`rk;~JU-D%77+0b?l1OGMb~QbKkkb&81H)u z?j<7rc*4RDzod2dUYA#C5?K?|J)@2U#%a{Lb8YJH>r>-M`tNrYr(!gW02E&=I|l(Z zN@yCIyH>D)b7coAIVlV;f3dx#XWaUb_Q4yuC0ZVt_2K|HrH>tFh#oBDI`A?&6a+d? z9}P2MC^?h&nmnAHtOtpG`=0J*Q2dv`jS7FI&m82BpG+_r_9cbUSWTj|u|JI+DRndl z9ym27?eyGp)lYV^|E0iJ7L60E!DcsNZ=zl*m(8}s_&i=v5f=|a9-SxO-#(kXVHeQ6WpH-r%QtCYJ6Q(1AokEfdnBOqT92P1w}Hi zQ>$k;jswG}#+UDWI3oeqt0#uq6rv36>B1U-)-XlJOA59{nDViM~HBzu4%bJ z!2Z@;fs4Ew3BgeB<2kJY(u1uO8rz~d-OZ=^wGd56{8o_D4J-PP)W%?;d zDvLT>56yk+&AnV3774c{(bU77OD0deeY{@Tq~?UJ|q9c z^g0cU+q&qz1Eb!Ss^iL@QxOh&qOn|L6>7R&gJD#JvX%C4ccV(SPEC1iH-Yq)ZBd^Y z%WnmneQbGmdt}F!PJY8779auP2pO(mH7bsuepWx$@G<(Ncl(I!mHf(!5ueQec+0eZ zM^dpO)}LrB)fN!US*{oe-T1R1t8xoRDb~n?BJsh4BamZk3 z+qy`JzbB-Y)=b7~Cb72WI_#Nf?)&R><6`rWsNnl)gNQJDk^J~~E2!Vy^EhA@0Mvo` zX+4t;6#gI}yGH{11ZYTU^q^Rv;-ctH=dg@^*os*_7a~6BE?;lr6fhkprV(1eFPf%L zKzg(3bU_xVbVXveYdux_?3&%y5%SGc3r(5sk02BV?1*M}-dt_bXl37J4iSZ^Z3}y% zg!{T`x+O9zN9X^AD3>TV6s$R_ z4&KnuO*Lf9mYuTICuG9o?8Edqomuh+YV48my)pKKt4h$CQsEwIV7S*(qvke5)Qt1; zP5diI)mf#@f|;!X_DiCED|I$GhP7Q_cV*l@25v3YpQH0SoSP`rGw)53D4|ef4~la5 zY%5qtvy;!P-r$$XpmfCoc^-nT?x{jMM?&z=8{X+}!}1{gn&dtFX!9N^JVg-?v*_um z=O^NRF{bq{BZeLRhe>|=f*fb^g@X|~o{ z*2615?Qi#n(K6iyc5aCBy)Mu*%;&}`q$GO@-v!SS@I0NYj|wYGwjoITBjPFiZblg> z-~%d$toq*iHULweqt9Qf7>`_W=%&0@-z;)*FrBj-BXc8IH+cYc^so%wAgov}1sdsVS%jcL22d^Z@D-f|n z{Bmx94&$lU%n~X?7s2|gp0G0DC%elU7!w6#Lh1?~#&GMxn#)G9tPaIsp!((EilUO* z!DsFjD_n%C_H zE1IgmB*gt4w~NYv=Kx1Hm?8&-hldut*@@V)AD6q`FmMG5k2GB4_3Pn}A|vf6?$S&| zf5MdZu?b-!K)+3~8OwJk#i$NkJ!|M_;K-*B@H_o|o}|WSLCPiHH`*6y`|(`y^i!u7 zvZ`ZI8V{>Jhr&YYc-7!Im@{Joy=`4qi8xOQ5i3V-Zn@b}e_=tAv%D-QRMgvC3d^+^ z3r^JRb_hz5kori2bYcwP2ey7CJU(IBLZ&?-N|~T!*lwU=(^F=v^w@45z8yR2OX8Q% z60xv1YwXrNsZMo|`!lEhA;m>d*s>xigfe@7z)=0=?s=xW;Iw8mEfrNO@1Tk;31ZW! z%UDJR1hzcc%4Pi68|NgY_Am2xxK7^t?y1`nZ^x8dbT(4@Ta%-;|0K>PrXL zN68xE$8*UXxC#w8#)ga}+XZ%gjNiv`QJ)dL3e+wHEU_{Dw-H^lGwDwm16Dh%L^N&7k!BioO$ zW@&`V(cqpT3*g=S;mUHb=wTb!PU&!v*GTBYTysj!5n7kwFtdB%l?M!%^++LtH|8gr z+wnNBw2;QHQ0E= za*wp%ph5|FWeV(3Zgx6T^~tUZUmB)>Q6N7ecRB2|Z2#hw>VY+RezX+Rr~o9S_%AM5_ws7LsY~We#(tte{thA_1a)>v#uw3_lKoF zmam@==Sff_#zE0hQOwRv7`Nf+5E6T9AsfzG8Z} zm8!x;4f{(FHi}iR$_7vxGkqyYj{dam{Qt5zXJ{I=UQyG*rzZB#D+Zu7kNLz2R~NHx z$o!lbBJa_-20U3aaeo*JzoI_4tDIA`hi%aRv zonNoSuwT<=m3;kHN)vb?8KE;D|FUCe__~C8UJ}U!Wtoc6L)+hjAAvf+9@Zo zEdiU}CW!rXIIP~P3q?$Q67fX6uxF8_uLyg>(ao+K#%J+FAc}(ES^|xPERXWuKtid80;hS0~*V=!%Dq3Q&!}JOkR1nsdt3N>cfEo=T z*5lJY0IK+|;HI9c;sCU4vAfWK>$2HRy}9jm^j=FwvcQWedF&>co*kG;eo*yUhKLG3 z>uSIuMZJDtY-)a&_M7@@5&2QKtFOrKwtIoTzinx7{I)RneT`wf^}|0$w~bCZ@4sq4 zei^eNBTi%tbIB4q#303YTJ1Sd7%gs|Zv^)R>`OwPZYZP&(VIXOX>pUHVx;RZx(a-S zdb0-5VXMVSADi>ZRu!JcA+R-gGMSoP-gBhYl6W;9+j+PaUd7tc+JN@Qg-1OP`$}(=~GX`6Ltj*UIb`)tF3m!1To>VGWe< zd5dqxt~MO4m&ge65IgzptSSw)yquzm=FAI=AWU_#o;*x*0&NDoE}} z5|j*2F7eNpLwDJhRNjYttg9aQ=JVQ8td_q5*TxRlCnkw1#hLN{Mas&X$WZVCwA4;E z=d?AjZ*1J#)xidOqJkCV3s zsLrUsgC@EiC|5E^*o@zA=e4cc&EU|UHfy(aH;k7TigcZ$2Ye zaUP}yKG7&o0#g;@$~Nyz2VG&?V4}pg8ALsvRAbEb2R(a=nHRgJ9`ofZgqK67>FU zU@g*k;I_gCz&KAiu7+cS318?A7yaG8>L?Z{vJ<~2NbfKm)!> zNbnkk>VTvyCFA+Dn%_3W-nV$Rkm)#y;WOU+6BOYa>H^~iE@lCeH?oQ^$l^aTRZLqt zg5*uAinCf0CQc*9-61iSk#2bsVJkNo36Kf5bnvCYxY7L|`d;SXMmZ zaaOc2F7hj(!V8wetXxytnj7{n0pnHfjYbc;AYI;0al0j8&HB-hu}7c(VzP|$g!JhK zyQR*>ot;O9F!ZH#=-Eh%d880@WwP5fciia|v@J{V;qeh2wt4Lkl6W>|G3#Xk^WE(g z$d3mN{6{E6)DeussIlzP4|;=Q%c`e1VN-N#QA0t)Mp^KNPA@K6ZG;q2kV9f}9z*N# z#Le6xS4V~i3~G1#*&@Y|(F0LSTE?qaX|%y4@nPx>;6;x+?)fTsmUXfd^E97QL7ECdyX1qS2xLPfoCX5v4} zFNxM}%{s7oD%_2;YKdEXrTX>Ii24TP=gb3`N;s~`_G(rdBVLyG1PvoFKuB+LMGs1T zkt}Bud>f~@(^r9EbcsEerXvEEK8)v+O>B|D_BL$(S)6GLAmy)3vWx&oA2&(%gG~j` z4Ilx7M+pROd_IjieXBv}2?jN$Ja#$`&qYZGiZQk{^+2Rem$e<5MWKLUIIg#t?!665 zKk2iHKiWRdAmGAlCeV*4qz(e_^v`@x_FqX>VF>HKfvixUl(vesm$YN_Wx_r#Bw^)2 z;UvGv=$hnp$7!_wVnX&bpYRjaC1W0?a_FsvsuebLLp8AO6YcJ24apx=4;dHeXL*UC z@9!@5YhiaDXYK+XQ`vlfepGFv{U8)*^_w*tt$H<`TbUObN?pFL0Z;r1!PZ9VQ#!B9 zYc%23HmDm}N^`ZJR%63VtKcSeVeLLFOVVy<^#BM~vCzE;)IH|?)KvmtA8%8JJV-vO z77uYJ@Di_R#Jo}YO;6-4+@s7F4Ju;~U@OI1v@p|_Im{sBPeN_XI{xHC>!N*d7*vUk z=HSYr==h9y`vyM+F8+4$6YaVGNbd4yt_a#OR9cs+vwZD)W?RO$Wj`im%8InWWvX40 z_mp|5N%_$9(6EJicR)*GX>yiIT+<*oks5n%MEM?HALpnNdCzmTCt2KfE6+d}M*
{P_G$QAWyv2Bdl zQ#8S(S1|k@8OSEDVB}B&&~6L!{fM%PxnA}NODvhG!<<;9QX!yEDP`O1eHu5;U>2!M zOe6`F>;WJlf>J^IhNptR8P-OL67&nKqWp}hS%Z@Oi_!T10cp-$F8 zUI?fU$n-StJqmA`DFOs{3+USY_-a7Ao0)^Q$>n(9>TB+rV zTcZ3J$R4bkr%8b53?DywVkuYB9QWz62zuJnyB8hp`JhciOQo(=;p$iGEtmtIGD6!@^xV@xwK)f%Jm1n&sQ%R?=f1AGza$-x!D`d z2Qgi1!5tx^4X z$rgLj9r?p&J+amVJ`W3pShkiqIvf_SMe>M-!-QppxkBjV(}U{ZTu%R!cjBv+dKGaa zTkGiD#3b3&n(-pF5sDvnz3wiXlwdqA1r?Va(jBQm>fUS5WmL`z9p)a%q-uUrgT2@2 zzc-u(1M35(`Y2K)eP`E`EO9_u3Swd~0LNLKuO0DB>!@9C{u!=X=g+@Ye%5g+>n$qR zOjr+o@qdNr0qdzLcPT-iwXmpAEDm{}WMEunYSw8({qP9Y6ee?K%szU@lZ}(OojJqD zq*0Z&SbzKHUxWpOT`!ZHDFjYk+HaH;xYwoA8xt{U3PUI!TQBxJy6aFi-^>ff)g;9i zN(K;p(5_|O?WUpqS$y!S@ZTZ!ztB#J4*bsAR<@l*j}I@o)gI)Gr)MA7)LGk`S04ny z{DlR*XL|Up(SwAg*U*LH4|8@C^@-sAmXVqgVm+OjQ+p)w@sW3g&*^;{3&yH$scH|G zKDlu*Ed7^G`~=Yh>(LJWTqQ+Cm2378m+_{$e#BVrntMw_an0P>Q@r|8qgUzY^S_hZ zqmeDjmQkhB6inyQg^Pp{H88=};r`O|;l!9ZW8z8|S1Zr-A>Yc+bXPq<+ z#eX+Nx$c9oxvEJy)S&31bzS>fCNC}Wr+PtqgZUKXhFKZp_6N13=3Cuw%_`m8wO`%l zyV<@FQ~ZzF8vcLg&uPV1w$dYMdb>$M192mTzx%pmZm8-;02p>UQCIS~0H}J9%DPt< zKbiJA&a>=CL5S>R(kDRexVcCli6q8`jXqIk;gbzd-3i}1)jSm+a`^l2dUAxd_SrvWl!v406*T?jJ!PZ1!dOXm~C0p96( z_u&0O%lY@>zJd8jQA&gcyYymU@l zv23|=!<*0FL(8l31kZuYfsHO8*B>PAn{fp3s>dOqWs1b&?jpM`jFzSWIz zQ`KsGo3dRH7NugsNu+Iq^DOvLKqc3klq2_K!#Ds$V7o@8Ha`pP%^eUUh%Rr?7-h!h zD}6gW^N~S5Je~|4Dj>spnc&mhfjoJ>)+@8Zj60L`k#@cV<^*}|!R*ieFVO#ayX@Nd zk@WA{9m?$>j9GLVLSiKBPB6;#T7%p+6q3S1^o16R?r@rj&ACOO+G5jBhU0&$Q_hZ= z6xwJppQ(GDNSCE9;8Kj~*Xp}}Tu$#r<2-5ZD0XY>Aqs2zD}#Tn{fm#Ssb4r;pKBj2 z?BN>l9e)OQ|B?8Ac^QDEiSkZS%yUt=}#6` zfHCPYAM_zXd)hWd3PKktbt~ZiX#tw!6dMAiy)p?0721t#SGeuy3~Z8`UCRGd?6Y=+ zo8!UT1NE_Le)Ds6%9#|eUnDW(H}T|C#l++zr)j%A!D9-)_w;e#JHX1#{7D;&pR}I? zt77UU$@`Sc2FDlhXlqSHh`a&{a-5B$hrW_Qn`p{w@jP+N1q1sf>FuFEf&8D%{3MB# zPHND42@!o$ULBCpp*GQGNA{<-rMSadzTx5>E|NbtIPFL$ZN3f=ZU6oEd3>PRrogSa z`VIAr6dDnU&zL~B_GBJ68z+8ynE#R5bm-5$=Wk}7Jk%PyEL^4AF77u5&|HW_KEG-9 zUDlw3oN02hWk~O*R{W7J`nC5F(UVX}M>H%7FNy&gW0)%6Eg4h9)d-!izF09noE&g4 zI0N4$L4$xtcwxB#{(%^8o9%xsn&ZuwK}n=>+vSLeIVn`{j58a3y>eVNa`)42GZ>B zlCs}Kat9X<{H0*&|6R`11aYM~I4qh~aT|sMy9$yb%owI^MVBA@zpU&A^4YS7A3KwB zT>hfFwIh)dG(jEOvDdSnK^6ZH#1^032cgB#q1BR*QW4!R; zb*a0w8mc@gd-PW6{N+slFe~KB z@cw8jJsh|Z8P$iSaZO>0_*3p0QuI>-YzIc%H(otM1Li=^P4T$wao`PniK5--g8;M*z0U|g1O}XcL&3RRfT=%AVJ>BlHeuzWuMR%}*GI$p+!N&^oN@I3zRM`xt|!gW<)YA>HZvXl^aWuEAc z9X@;ML|LzE9@Usg53mqXra+joed)SeMP-jS)9}$D`Hf954N_ULbI?s?Eymth5LZXW zpP?`J0+5DhK;;6K96p|Xvzvbnm^Mc0bNa1qSx5xh%4_jvBtLtvnLO>nkIsnm#tVIm0BeT+yHu*#p>HB+^gmcp#eW|K43Fw!I$6 zt(~EzghNbk_mr$5c@uM!3(Z37cyj&cZyvn+jOQ169Q0L9CvU%*?PG;2%WUWO_3j1< zat*8qeI^Cll*Im0?)4t`t1HhO z6$sNvIx~I6WU`xGGA65=DXBT$&z}EM~MvCQwZ9{@G797Cd82BfeO^A z9 z#)Uuo&zI@W4wS$+kIe5R3zrp3KFL+1UjVUWIXN75I$xGe!VYg$v zSOCjL_FWF8Q0G5xjGLM9Vz*jkaZESXWOSq4hMqCC)$nG% z&|c2|UiHe{j|~y{9Khw%U0mF{ZDJmHcPl{ndc^5L;9=$60I2L%wNGcc`<*t?kHT|n zQs;k0{6}1`50tJiN`AMxBNIl%;|P{4uj#SzE-Jn=%A(=??3^U~sdEJfffe={je*i4 za@|3!R76SH|9mY&Fi_^%Wu}h7!xY-?XBI9s7&aKNzF_b^sZ~hseOOKf$%D;~zY_$% z&E5Q!JT5(N0)}U2ZS7WpOE&QGv-zD;WCib-R2B9mpD4AbH5_Tm1<;z|ZwEG{rzFnCY!yejk6l#+*wsU@8ep7kyko)u2q*};e zc}diVX>J2pcd}2S3b5y-8b2Vh3D_ZU#))q{i)MI4b9BgwpXhBi;RF zrki+eW+Hi^20im?yKwxEkF1M7GKzfQ*mjf{AJc+#&7V)8mC*XJB-`t#ue}>SJ&$kQ z3M#93?zk^@X=C%{cFhYrq$h&KZ1mT}fudrM^#4%2JHLV2#&M&|3gTb| zr!N7Sr(<57d+nd&<+n$BkWd)M%gaHsZn#c9FhKabysGq3rJ)=J~!B zM+=c2qeK|ho>702?xo+QV|i>&A8qHDvh77>(C*J(h-OZEqJd zA8~9c|GYqBsy2zESb;19E_~&WK-1zeJO0PzJQ|1!nMkGwon*_d%kz0FY}-DriKz4b z+Mj!K`Zy8v4-b zdX=XnqC6WaXpir-J)2ZEWsfl;Mt?$RlPI@W>{2=*P@Keomi0#DI;Vd?K~wnuN7Z}B zQystkF>}uU3d(y@UugQvIAm^?ZHEHWhTu- zuW&mF=BcU~$p!QKs%T79cOYSDi2EVLh%f5-#||M!ZBM1=w0B!p&tdA9AI=S zNfu7KlE4u_^!%?8b?hYV%Lvt};r+zWANjxjzddx7-0H0e-V@jazD_BOtMXB)k_dPp zn)Od*k7& zm{f1aGqW1e^il8c+){s$e|rZBl_OLhpC8LTkqI}GDtZyH?x&)Yp!dO?JsYyv&n7Y2H8jagdJP3RA6b#&o zJ2@EB7IT@Xx`;R&ZqE)nHpZ-h*@val_279e{89D=`A;dYu4Z1gqejUUjsU^xf?^ty zu?Msd4Tq9<#Sy(6j#R9n&%F#Actt9EW$aAygZzW;0`=m)Zjq7GIj5(6STMKp)XjBx zpY9~b+X9=4zq>XV#np=UrF)3H#*X4~AANR(f+xJP^nSMNi>!bE`FlsT{Ax@C|==@XT2!8 zqADMa>`{Ps1_=3vYKgzSH#eH>%k~_y)_bBuo=Y4YJoY5NyoTs4N!M^L65uTc zo-X~FT0!|c`rStMH~U+~doB|u#1p|AlB;T< zbG0+B^{;|j!tzRk!mboBMGeS7h`upy8kd$N1gfBx%U2izC`Id!?*S9CO80#FWaCxc zr)BA#-i9uItr96{?;q)aTA^k(sU=-fLDC#Z-WPc(RaEZ@wn9HI(7!zleMqHD+`BLy zTWwg%Uzyj`wT1ByiNO-LhEGVF68?%AuxgTYW>!6CADH!W{ml?Q=QU|&*gy0c)^795 z_e0p7dgwENh{=f_F?uOLR=x(_Mk^$|c7>eJcX2H(J1Gy3=lcQ>)zj_pQQJ=_;5;w7$oi z7EL%TQs#I0UM?_CT9&<_cK6W(*BZo zTC6y$mq#_d6!7f+wVZX2sD|L6zf2W=D=(o$)PpQ#-?m@=DkEW@nB&eneS7WHj7V#hZ&PebP z3YDInzQfg;Jp|Zs^viFZoWXeUZe#ku#^|--y%RB&P?vYcB|lRH)(7d9f_s5dOdy?o zWA%bUckf_hQp=6Cq_GBh_?lVEKhQMu<~Z|BmhLmu$qG*wDyDo&O-XGEZGuTS^2s6s3%(2Bz3#+fUoJi=N=(sh8f> zSvcd+5Vi$Rq;H${KIM6cCJ_bcN1?A!qu z*i_R4uRzlB7uc*n%|2go4?kqKI^sP}yLuEW-p*_K?0{|zvpl7a+E*yq;Ai96$yxWW zUb51WUHP~L=DK2)z)(m{ACyaj9gC^E>hV{dpr0_pF&3LRcmm!MB@-aIhZj@b&hb*Ra$zV6cwe^Qvpj@Srq6Y9&GM zKWaCreAlIAn-W3blpzWej|F3&UXJs(cK}T5%iJ=?vdXw-G*urkbTc9kjmGW-oq~tl8=4rF^&v5ofX-_jKwGscX{yI z4=I4@r+3k*f5e&{c<?2}H)8&3xq!iT9JsjE$yK`iU6@mP{h zRCJSr+Ent4eGPJ_Cear!kKRw_T{5bEm)0jKG|V-k-u0rv>}?G*>{t^MVgzV25(br* zIAnl)9%H!M#GL1^cVOv~oWmy*xc@_-roFx08BCO$)=)mv!^qxCqCeiMLDF*Fm6J>3 z1oP<%mY;&js~*>~!Qc25*R{c1fF3DJXN_|i7t{sCnlaqKg&$kZdF1+3>cd?!OAnsB z(4yz)_X@+Nk3-5Nj#iT%Xcjf+)y=w51?yPdE_A^*CLU#G@eUjFKQ@RG1Hxg)H z54owF2`F=)+lrW+TIg8QUx}#f^@jbGpl&@@!oNkKDTrVpUzbyi0EQ)GLXd99qsWQMja#hbz2OYa;*8 z?DQ@;R+%qQ`DvCPIRfazWHmm(~HW8!X<#k-Nu)WNe#l;y2U6yU{q`W7{!jA=)uE%_b?PPSCr z?=(@T6GeC7mzufROu~Z+|F-L445z2Bfz$*VIOX4#=^e}oZp(s}@Y)At^R~3Y#&9aYtGE*VP3mn%oDq&Ku?KrP}COI>vEM|il2B-DoW4DjEnoe zn(CJl1XZTvOP?`=l`N=T@I zzU^cY74I+PfX~{jKjH*Z8;}~x{@*o}2-h&^eA13L>wsqoSNO1v*b6$BA=-4THxXTb zigwmm6TY*bo3n~08838M3-eoP3+n;MCg1y@+6p;o0hpLzkrhHx+f`#Kctyx#+UFzB7Gjy0bG{4-R~9s*r^ngpkp7Qki)SFk4IZsB=30TvFkH z7G50sZJ*aYK`jqR8mmJ>uF;vc{Ke{?@GoF|rmW}zg2bWmoBdGCoX;b}WcWWbmNK54 zJ=Fz#9t41`*_<5fX%NQFPtQBrDdg7_lYH9OM~F|9+vsuM&*}djY8%}5O*`$|%H6CG zGHVbB_C>W_TpY^YGoG9=tl=vYghGo(C8Dfbnd#iL|F?0pXnEJ@n2a*BcAE5t zxh`VFu|q?ZOa1&Am+gGDzr{)5aqL~B-w6@dNjZNxem(NNCNQXC(Bo0=8ANgtagYTH zf*teZJqv8d0>sR2d+uxkr9}LNGFXb8HPwoKqPE~xROH;;9Gr<>YOs21+YJsoX#9NF z{yn+{%-5^=k|1{*Ws|6=6%@1^#y!L=Z1hf%aI*Zj3M{3Tb5ApmuBl5qJ885zf&7$* zpu-9;sm-LOr7(q6%8}yj$l~a9QL3BIXrEm{s5j6HEeKw=Z0T{Kw;bS9CReN@f9F}X zvuHKnI|woA|FNZNRN^)+Ut7YE)QKRdP48hvhj$AYgmO@ zrlATa($`k*k!|BYno9a{TMV$Y;nOXCZ2B+ju^`V{-AbcdEHDiYMQh1RPWlO){dKNW zzdG_XNEz@V+k1ZoBxz*;j&27)R0SZryLM-((eOfR?FbQGe8{BEs(_wsIRTfInlLJK z>WnKfY#Tu2f6K%u(*UUXpi?BOP$&Jx>$O%)AUmm*7%BSeDdaUFB@@1rb1)~LsSp1q zPf&nQkQ)P)iXY*;fmCejU7nwKomqQ4=zUg{TaU`Pz4{_mr;K|sTlWs)r?Xw#1=%+Q zGSS@bNERv;NpLBvHbvcF6Xzp!%1Y_vKjYFrC_LQU?fa`J23q0ZL*G>Ac^Wihdi(iC zAupq;Rg{VQQfl-*G?5kT8z!TI+vAKC7nH<=jrDo?mGK!p<;Joa!koux?PP`HX7QwS z|AoG*5NhRF_OkA_{K-OC8=Z3qeLd8`ci!I~v-ai7m+7xXS#0=*Bku~Bna>{s!`z@A zl_{-q3s}&eUuoBGoQ>B!629$OW0m-?6iLELTFZIV0#1iUQ$y+FdU>2p?1SoUA3N|L zLsK=G?HC%Dc3l=1LzTMP2g!3!q#VSb$FS^D)AhEj0o}&s8Q0%&P%B~hp*R$32R%*> zCx`xltk|!jEtkzfcZF4a_IdN+V+HJz(`?g8>-a<#;I~-s-wz)TdeQNHqyDsJ+kWU| zWMQ{2umowmj>wCf(te)vRUgl$@^1<#VL)+Fdy2r(zigvLh0`;G1=u zur&miRU-R7%Q2(4e=0`6HgV&MZxBeLD$_fUaGC82(3ZJc$JiLOaqYs5OJoWtM_)9a z(#WckJc^@RO?97OlaaAg4|ffEwr?(XSY{N>l>ecYJ^fLvorX35Jn45(>cnMenD7d$-5-)vSzdgsmv*m?UG?a54$&f9`?l@VB zb%2N=BAu9V7#4HC+)14q;dRJDWV4haVuypSG2{NokNo=MolmExE7wBQWl%Hb$kjSu z<%nSsavOaf;rcow*i#ysIImvE*>5)x@rl+seL8Y>Z~yHvB%RiT_(K~nRr!mZx4C1| zihhM!+o0QQw{SY~=a}~~3nwLZzMrz*S`b&U@Xf#=$j9uKe%*th1G6w4u-f!dra&B5 zAx$#t)3!P0eU`XiDDJBruOxqITRVf}JvZ*JXOQ!8-!pD^3pjW>TwH%M{0Z_3Q(4ef z@Q2nd_lS0UPA5MJdmH{w=V=t+97qW$%DJISLxZKiF9n2} z)h{^MFYqYX^lX}3$%8T@Xsk0g^ZjdVoj*C;fxpR?dngC0@NkuPE%CBZ%4~oS)vH9Sfj)dpHLTN21!zST&t*_ah{kMu1DYEbLK)l7s zJ~G`a+Q8X9CDW%lUB-8y-vl3gwL7_gSZ&JL(y}jV9+&m(cw1MefsAJ-ewfmLbqgtt zKFq&Ea-Y)WQ@IXs=s(JYa{aV}yPbh!TT|Sa_w7?voia&~Ic!ZL%zG!|UDc0OuyfO$ zPkafvR9HJFSWp$zT1He8HzklSa=Du;U_BQsFBOk|k0Iel-z+`_w{MoWygENWpYDri z2Wyb(+}zx)_u8U@M0VMrfa8Jdi63Vqpz?J}Hpme8HkAR@mN>wjzn;vq737qa5UDFG)4tZ#U#;^;2}9UkWNtZcJUG~T_eF=b zxxg(xpp6kFv&fD{D#3p(&Z8AsEc};we7FQZx0stGW{4W_{x26GL6m?aHaOKFL98f| zWKR^q((3~%e7v_8upJHaky6Je{RhZ|cgrSTND9=A9ZtvD=wdfwQ;1q1sM&Y&$gFn* zp=3u;DdCKD!+2osQFJ%cSG`wI@11zX_uU{+!Ne%an{=t^Q?M%UOnj`%M&F$~dxSv~ zEceOLdt5F)wrlv;fcn0YkSG|nfHe!U^FjYHdfHO;lu@q<3_wK_H;puuA0 zp(KsaE2w-B{=M?6UX?wZY`Axh7`0V4X4A}6nDsap+pa+cvDmCd>6yl=L*>2tIj2pZ z|068NB70ZLrEoVqZRfnj{39)CJ%6xPZaD}dR?Md~IUAdzPqA5G#eRxTP`2q_3<>&Y zeBGw1Md0G?u^5{$w~!IduK8D2KXSG;yysyC=Pi~s=Gg*azqYNaey)T6w4xJebhcT* zM!~c>Y@U0l?dR_h>=QO7JOk}fv=FyxrPbu|{d%HE$6b*Iq z?;PK*TE3qBc4=3dg)%b6q}#C}GkEijs6r^O0^Q>FY>d`_`#~qB0Hi4D3=8)0WxgQzNnb_A{q& zhx0a3rqJy$Fj~qKy1A-c!Nq3wP4Pe5D;7~XaBQ{r!j$gCYBs3mVg~`*M>8r7WI~z# z4sohEoK!a)si>@Wbi~F&2EHFHxL9D$WN}*uPw5d4hGdH)^w0{2zoa`>|DfF7oDI@u z%L|Zoy7?x)J;@*ic26*_^7$ud?3|Mh6OC60A9R)0QlH?qhXYMny;u+VdAAON+G4Yt zaU;*z>kb)^_{8(YkIwLR>5~qmUx8Ls}Ug5@$st%sz-CDm&nj^Pa&;QLj!gAdV@YVFH;LxYE@t2Ot9s$f%>TmM{_c@HnI+_36TB;NLnREIW zQ!F;BX2jBc!@2jeoTh}8uKr3-en<>$@#vq?mY+69kY_^~lDIz_m+mD{LyYF*HNir) zqYTI)2ma;2lv?PSbCO77LygwFB=5zLwvab@J~~sb^HZS*Zu61FY?ec<`bVA!|2>Pc zk&kw&FvdKW|NXLWHp>Q?;GJuu9pssSiT1Lbd0=rzl(-z#to zH<=$*$vUJtqW>ItUY*v$mzdB-Or6v6HcY#DTh;4vaqXUDtY-QDP#{7 z8rcn|lZ_7e;#F?LE^(Ij`<6R^R9VVe zz#v>44${%$%$yjzS9jh2xcwX{-WV_R*6h1X8-6}u4s*%p5e8;sBvl|rt~=HZ=WBU8 z?3i9~o8CR`QjM2UKfDJd2beqL=>zMhMW($bc~_+x=cE|l{?b!6B8i~Me<}(tQ8JYk zQV~15RghwSyUxG+lE6K=n>VAnm_&8T~W9uzh zC5H+ZM;9$^#0DlOCRi0$DT?u~}|Jg0=DDMC*4y9IZIV+6C$}hx@t>M1_ zm43|KTwExC$bzs`Dq$>972cy^y zV)(_w>*Q+m;vcpKxAlD_9YzZkx94@1AYw;N0%pAkzWMBQ-OLqT#MxTYF^a-TmP8>Y z@=dz?MyrJDYqVaORQ*(dO1q4^MW1FCZz{Ed9N~e7@W!m6iYYJ>^0+^lF?+B>`QR`Y z6rWH3u{eG0GoD9XogVBpnONo08()@B9woT+Ds73|n>YKf4(vQl%QXDw*8K44rLJEh zFpw{U@yYvW?%Dw3xA>2Co^Dc-I&i$L>+ z$u+|(VyaCqLGVU<-^=v^YzT7uPag?FQ1f=Pb00cR-m4kWls>O!GKAg$!<-!@fftb9 z#kYXTKN^8zGi}W5{PYx+t?VA4!&YxRZKK=p_;a|){p)IFhv(pnj8wu`>DX)3W`4}{ zT-B-0wiwSWGdfDya?;?!`meQcg^S238-A+D z89p5cXVODM$#cQ*;~ho9p6Hr=5KwtJyGaPS-lLX-s%77_S5=-(bmMp_)$O<8^*O1P}M`{K!gZ&zP-5 z`6xoCHQrr<{i(B^SNB_ukQ?`2AUwjXO`0=hd2^pyqNFZa?JZf5M~jB*fyKXYE4D=F zKD=|DD&C@4+{_HWW7BY4JfU-?(`DU6?;jQf4DcC1q>H5cAEtxIfaaRqteq_|lbZ!; zMpKC)9!b82;*#KJMHMZ(F!hnVv^e+tgO8u!yA4CCVfLycuF`r`E=tvgPW-u-NJ&T`#Wua zFmrbO#^x^{^&y!6YTzp({hjy9>H;#NlJr~l$u*CG5M4r$%;w3D0;Ixj(G@D&?Uld| zGAoc$O74z-;qW`%Xy=$3uxC_#xo0omwz56#LQ2fEz;Sj?&-wncD#N<}N^8|kxaD{H z)3B|?!S<4xkwWP8jPG&gxM*v4hv3_bJw5fP7NW=-ZZbaUypfQAw|YZvBcINyZ~boQ zA*VsypGxI(1y}nI$f&C_jsxCg@vV^@pmyu8e#`I&*V~Q^QJ>c$?F&AW`Tf*ORJtIe z;y9qUBmY{44m!wiwU2&F8sYC*ZWnc7f z4lIZ}!Wz&nOtl{ho5UL~ z$|sU^O3j}1A8uQ>bSP|U6yPk=%Np8?b`Nfb^IIUqokV-R)2Lw{3v$!^meTwLvX;m( zn;&Zto}!g_XTgC2o~>7>8obX~7es>8M#Lw7Xa@}~%70bjJ(pCSvW9`pp4Bc+taVpy zBege{MH*f&d&laDzJA&E+~xtUW<4}>(%kJ@Y`k9Aq$z8-H)p-*gSQaKAu~bzw~AST z8gvN4bXnJi>g^}fpnMz2v9@FeetMG14E8ENF3l@thF798_3-n?Yo_}&R9~b+Dr_=* z(*N6YWt;n)kF%HtxcGDxOumvvE3%UGb9>D+*ZC28volY&$LH3gnb)Sa`s58gb`5V( zn9rN6$3|369i_yDHQG1l+aT2uv&=ty*XEVA#jMjz|pf$Ke%m#bF*QTo+=hpMtk zrJU|W7H0fDPw6e#i>$8CL2K(@v^t!JWB#+mMTVErp$?^I|x0+=XrJ z7tvx4uk4UHh@uVLOs4}IlQ%8sK?;LnUZ@z<8aDOeIDbkADGhST;LdOBch= zO8O519EeXHjRPxar#*VfKi7(&Zp_O;-N1W?@XB>;jJBaZ*FFdmJ1pyk@Wwl&^@@Zp}_i*oIt zg2K%9U_vj>`QHpzc+V(`W8iP@Hc{?cH%EL`;3hbvaS+jPH@0r##2Q~xvLEs z=vfX@h^3^Mv(|P~5Oua$*!?deMHVv}N-0ln>KlEa?JjIwYE)3)nEEBfXtSU)p!t9d zQJ5!Rf7F~R@sl%=3D4K}Pj%{wfM;js{U;P_7}4|3xWVT>c5B9PR@}^sg*DkZ>WJ50 zraL?1qEgI=%Hu^(yiopE$IqYkVn#0ZL(@DgZMmbssR}i54v5dW+yHStk8vF_zx8w(woIoq!e85gxs`r!Yr zq22qv{u!@*oumdNiTr?IXr;sz4x{k<23V1mW>BdrozNMiGOKLI!=6+W_iBmZKy^-9 z;(!uiraV2E%JJ~R7-H-{B<$XfHn&uLc4DXxV|Z^)-0w>dR|Q{}yuW&_oQ+j2hBr16jR%wtG#uJu1U)6fhNM>aLUZe^~Vao}_`^a1z296SRDMPLhiGRP-;SS=q z8;DXshGyV=nj84d7F10`+v#wD#U0v{f%Zgso9SzKi@0`5U32A3>ef7S@G|BaI1T)3 zCkC6g>BTgk7TM5mY8AD?fQ35$>lZr(o#A!7FvRKm2B-LY12emXL?50_x;ZyL#=&a3 z(B>&3FWng^ECr!0+I#Y__D!>CuQse@!lvB-*y%p$Q$8P`oD0UZ{nMhcP39?`2uUC@ zB5x$i$wZB@(5d_PL;K$caJhEu+*K+0$O)V(FlP@+bK^fbQ4=jpqkn-sJgSg^j+;;z zey?8|m97*NP;d2};uv?q#c`xzmv(g~fV!fuuQ~r1XZJ!TZ~B2X9UAQB{hr$gB^QE> zK0eue=ipm6YtveO8ALYgNCFdc6lM|#@9WpZEtxm7zRY~cApP{^cIN$3wBvaF2eqlI zv1r_a68GTF+4(!_Y99bGo^ia7hI)F zJfJ(%+OS8tw7B>jP1|GQvjZfU11f9)sLzPFkDj;i$W0mn+tMW5YhhEpi5qz~t7PP{ApkhK zYy-r>URXdF{!)&%C+K$YMws2e!!Cz4yZBXw7_o}kHP1yjaw4wZmd!1qbH(Cnc-vO> zpq2|bL+t(QOWaTCupKXQ^Nis1{;gM#4IVIqvWKd@?(^T$mv1vMc)#P_)w`@(EP`j? z31b?2w$kAFRQJip5OH<}R6Xa#9uTT~!lZ6(ef0)UbWHqIz@|$UDJ1-SqhKm2i=f={Dlg^JB2C7Y@P>%eU2Kr`P<#U)@ZOs9YbU4*9?+sATvjC^poT zrnCIo&sR+Syn<>>ev9 zf;~Kg)vT~&Va~T|a61Qz)-)!nHq?O!Efug~r>uab1e6(~?>%8(RG+A7UfSJM_@qbu8)%Lm5$$OJ-ctf)q<1bgxG}a~N`R*( zFyyS7%0)ePen5TFF&RKFrA)A3A!r^*{|s5c@C~+molOrDI{dXX%tp;Yaz(G<{CZ~8 zLFNZwmj+EO+V*0Syq5ocn4Tpl--zmQLdWD`9?&ayr@>pZJ-qdkp?SP_I*FI zu&`0y8_!xdMzo#CE%rAYCua>aDPTb7FPXt}8;j+$H|j3Lls;DRYvn&AD9BmY-;zA~ z4Hv-qsOo}h>#sEiAEZrwvFDJSq`^g%H;0__J0@d!r~FrkLONyU|J5Iz+az|)CZd4O zhWpDW0fSxB&FDGNCW1W4zP2%8+7;zeGv_nCqM_mz6}pBCV9Ca2y4h>Gx)4sU1A!2> z%BCqp*!;u2tR|2$ZHoy&q4GOY&n=*c4G@vByC~9g!E+mR0lDo_)AxM69c)bU4qega z6w~j<%9>ix1~O#vj$ggzy|PQZzKnJUE!oooSG)LFsaNr!-g)L3BT33jDC~44B_%5) z#mvX}zl&Kjg!CszZ2h`VH#{PbY5fm#soc01{GGjlFyyB|4>p` z#qxAd`*1Umhv)PBDZ6E101+{`IK}ThFFzzTnn~)71RwPgjm zjZvPGObC$#zBA!ObTt ztGQ8JX&_SPWzG&3cLpV?lQT2(ZS8e!=KjGllvZEW6h1zK2*0>wPIb>%BYjC>fWw3` zG6@hMnGf$nJEZvBr=Vi1O3;*(Qj@a(z2|!#d`tc$dYQlpw3Ek2S7QJbYM^yqk>HCx zcRVLgF;72V?`N?mdYq1x5_1K_PZ}kGAqvEOVBtA%ayqpJ)8O>o-xn(HxUb*_rqac~ zcemhhm~gRt+~sqdv@*u0m4!bsVM1Z8QMeWmWpiIBK%5wORW-D3qY( zC0)3~va0l__3%;9s_t@1LIk2y8^Ci?{kM*`G@fmTgEI^Y$eMwnnE}Z=LsO54kMYU( z?%jL-dM(3iENQKsX$}(IQ$bLMUx%k-wlff2{|HBNNAG z_^F{uOB`8vd`aF6dIwB31P6YL!dF3vFC!*v)zaI z1Ef869_DsjlYK610#cHFK<=CU#l7Z(4#L_Ym5G5X=T8pnq@I8tNH5H;v|0n{iuYhmo|MsH64oO6yEeC`bq-eKE%fB5AZoc6|7r$za7spF5xY@a2G^E~y`=gZ7PJlF$%8(~8n%CeZ1Nibdp;`XEFY9L7`| zOPs8eC`r)h_lWu>Jh}msBJX@{`U~`@yoTVvbMKSNFlZ`q((A@K`M^GB_emQ+1p;|> zW%d0_?C*1p5anT`uZ^*(kC(3q1V0Es>YJPttf>q)te%msuXWsVYtMj8DiRJ#Ur~=>iqrJBJD+>N(E==ESmS_uM6(hFC?4CVT4%9v|A@M+cr^Q4d>Y zJv7UMw0tCP?%0LCy4=&~2HC~w?vrRQae>Qtr@#p$zc;U*GAvt=rxt;N8-V4h1c&B02|IEWR zEijVTgM{QHZKOW;-M@PIX3_hbXD`zW>rJ8ZM6_#@wwD4e|1zZXmA3Nc>)$eL8)ZL4 zdNEeyrPhm2-0?K;-mC>4?e={VrHS1zG<^{5!SL`Ev4rQ_mWAojB{a)3S z!xK|B=u0JEmd=L{qUhCEbk}ZCm_$_@KmDlwOO?|UbA^0+Kq{#|RWiqiK)Qe=eH)AN zrCXjvf{)h6|7c+|JKF3MVMSH+%whD+9MhhQLV}t?4Y|mpe3kb%b(q46ZGd_*y8`jY zFXcb4@o|Gw2%oliB7=)8a6LUk&gCWXzIbHhW`Y*J^H+vkl zJ7yogzJ78qHA5?H-N_|wW`7yl49o8?RC&ahFQ~$tW^;}7_pRvVIB*A9_jLp~ym7g- zN-sV$7pm+t+q^_v>w6G-YcWH@^;uGEl7hL%b`}2ljzY1uQ*Kk=y({#!vkm%q!JEoV z$bI#S>~!|li$Cx^!jw&8cZSk*;1AQ)>oeV125H`#N1PiAlZ{b=P0S ztk77s=-2!AKE#IX0TZamRx&3cGCT3;+urrhL}%Ysu9LoYk`d_ZN~z_v@Dw&wD3icc zvr_l?|0q}F>b&{T{+3mt!{Z^->WJS^Xu@2@vt2qBdqSTr#D;v<+D4}KskyMyv%dt1 z|K$Rxc%8r9lUC04MsRAIIZ<^JP7PC(pDSfWwci}~bIR-b`+N+PM2`P{AspV@@IWZW zvH&pZ`%?^g{fvjzq)F)&e%90vuy=Sacalyid3)77tdYbzV)^rywRbucx-y~i_=;q0PNpR%@3JLl zqiDF<3AZo2Sq3Wst{2ER00+VjS$PZ(;$@8A%}7~$DhgVLGdfylHl`l@K6hhWi1D~g zvM^%ecQu%9Oxt{yVHx*DaG-kdsOG^mpAq;E$%wuUd}}@C z1-=dIaq2+X((aQf^~=gstzW)<+`Vy-n;T!##=?^8anL({Tj=J)x(Jf-a9AqI-j#mx zifn4D6s+>Gt|NXmQu4FWlK`l;I@G}uxSVZ^TKLRL>d8?~de`@tV`iYyaRIe23BAnE z5a}h`_JsKBOo*>m;wq-e_Vk@GvHqu%O0~zDz_)069=Ak15q@x|%XK5j5J}<8}X1b_LCiM9UF-{$H~Y$|?&+VOPm2Z)v_z@PYy*o}+z3%JRWoGlEJfZi;7>yp-J~9M`}z^7{Xr z5s*@z`DOJv@0O4<)XONFJPTucw$>1nI z>iw!>EDmci5SzqYB0jNGgj<&>5foeGBv14^GZX>&khyKDo_$6~*=9@$C)@#vyE9*{}{j$Go|f z@aWk%i~IFNgXTp2OdPQ1!A#c5RKW@AgnfQ5W>3VSK|JdJzD2j)8zxz-(c04q@l5$& zNjhmMs8E_HkJtmtN~yjkjYFXX2KTvG8{h@a_gexvwX9eCTkrKDQ#Y}_t7(#FEOKmhw6lnV2JJBcU=ja9 zCMwq2QU)f<0pyoMZP9jOO~^NC-_5E1QNzl6T@?)#a2Z*4H`W8?XOnr)FxRH+jnuF&WylOYJ8MNL3b? z>nobexJrbR^3-<-sWMU@ygSI5O_bOyw}$>}srXzMTi*5W>>-A|bl2pu#MfVv(LbvH zG&X(SS}2BrQA&?grMC#5jz1%D8!5+jx%M?bZ1W^Hc!VK|J-Z)=lAfR_Kt_|S_?VjB zB}9Q`93^iAFfgaqcF}CN#G8?I-l8lZ`MDTT*}a<_OY*Bz*9{j;hQA%yLoCh(j5lsx zju(7G=T4vA99(Q;Ruz=}Q#8RCEux)X)E&KtGIQ3 zUl;E%()Ay6hZ4rnN}~j)_pxoU!C)sOUl zlK?{*mhc08rnSyE2xa9g_N~*COC}8xqCX>J8AbS3;yZ4!$n`nr7S&^H=s$**nKM3I z$f7n2EAf2uwd%PZP+mOcy+CT@VypE<2W*}6(~`mD*Rm7r@jj`O9x?e_kLuLN?b`U| zr0vwu2&D^r|&!^Z#bgE%rbIm5XyINlUIh|`o;2# zNGneRF9skN?m!HbR{DT2uxk0i@Nhy+MvF~j^nw_}s_2G1K zCi}9|yk@PMt3B5tB|!Jy`JgJ}@~a0G=>WmMztU?#P1lZB3~;4Zgwr)X9lLd+ee|TM z{L0k7IqDD`fUf4I>`HfeR{qjf*c2i=OwC>d_r9X0tZ!&|uhNee?2>tvTn8UquHq` zxNSPGOkh|wFTZNyGg+8~@uY`XF$P|>ZybB7b|r3=D*I7GC}rQTf-}NyadYZRgQx4N z!h=%c%v%K-znKGi6t&9i=-$xA*ZVC)XIt`@r5Qp=#hc0p{CCO!E}7@$c(Hnt2-sTK zZ2EOIHB{UInk3v+6_q2<-e2;hg1*izOtLUrHu(wcu zxv>mHqqKIqzk!}-PIFEIl$CNoT$k8(vt3TdRJ zq(t;woq6WGI!_o@wiaOt7x*q&)?}?EYoMY*XoJJj60vJ|soZh%uaE-X;j`G1OZ0zh-I!=l6*kDQH{-r*^T`f>3ZKnb$G=iut?~2`bHL&i-U6!0r+1xL3gW{kF%_(hWHvj-PNT)^;KMK5#@Ttmy99{%*#n6 zDrnW_JPniIv51@i)-8FhuwCXbWR=svFvB0U1EXk-PU%mvckUI6Bl!27KW|3O38fvB zmmtiXA=3Y5|6D6K57hJqUa)dkiQ3^)!?2M^PWP7oIVj9c@O3c^d@I)>I#NaRi7MNj zaXsTH3a2QxSZacG=ao@R0A0B*D73T35IU~6jL4xe%DF_cb8_D!IQ@B`RhhG|!CKm4 zEcvP0mMbde>cH?yY%j3<|7iLWN2dQj{<)VUD~S+RsoYdTG|zxjQU=(Ajz{AAKjHE`Bbu(Jx;8F&E$uo z#qZpxv@c+9t|Fh1U>Z_!YOX9k^A|Lx{>Ko9nsH0P9xItaUuor+GoH_+t*f_tM3kPm z{uA-jclz;xe3MTvFQ}Gf`ktRu>6NNa?C+B;2H!Pa!|6?o$(wC&akKkV-?pA`Vgl)D zlXz*f%BTr_5d1{jdvvyNwB7HEJKFsArlup+m90jCjuy-lX^ZHSLqqIZ1>)r*hOr}95xGcBj8u%tZQnE;E zA)IY9WmyfIy_RssajW^0d%?JO9*>T(paf}mB&J#`xWBj-#jJeu~YE#U_+<+7Lp}l$p`$cL{ObNgz{^^yE|Ow*`uv|ExNt9jboh zF1@W1rynfngE-{?YrA#!G4+hC@|kJ9WwD0~UWVVzw}6NWVdiqb zmOfT^YJ<23N}Kw9eRC!>CzCI3c~isYa7uGEQ>^6`+`xhNS9n#@o`8E$mipF&+J))p z%5lFwQ1xoLG)MIVQ9X(Mnm*{>Kq3;YVn>#td&^x3*TuR5 zN?JiTEe0jc?!KCL0<48NO@v2%7VpMQd0*2t7C}|`nVgHbEPYN1>3T7`A#I&WhP`Y) zt%zy!)zfAUqUGVHALeQflf#2lIN=aJRUIbF(M65sP%r2xspVad%U+t+YYoV(IZ1hE zebUh%Gm%3rz0KQ(ES>zc|M@5G3Z#wsN$70mIR=7i5;d{zD7Qg&hD>yr9qAT8DL&`_ zm`5koDd`t;V@5N|yON4!c|9m_ZuC#7z9G;HQ-PNFM%!_pl}#Au&^mjfM3fupuzzhT z?X>%qM7xJlpf_w;&cAtGmb?Rs8Mg$}duvnjAP)8Hg}akN;R2MTUT}-pR{d^{DN8$7 z(YV8WuD_)@U|ca^_Qu$m`cBNR{PsjI}tUiY{LXJ1Nn=u}(f; zR9E6qBXcBvhg6wq?Qz?QrCL=_Rna5d2$Q;_SGn1$1lo=SY zvDK_b>%?mW{|RepksNZT9vWP}r9sL_i7CrN`*Wf1j>l>4x*SK>`YKq=C}eLB!U>xH zO$goS!cO^w7CbVJutae8KpwBpc<^ldQN(z@3WI5~=t1=Bn6^Wj=f$Z`j8`Q~S^5Wr zfMvA(0}VSkW46wvS|r;436rlU4-PmkudS(5@Kr!MI1f@aOhy7bF+>Eh8eXbTK_CO8 z<0MqATj({Ng8ytE`M|@t)h-WyxDkxb7uEPgt`}<=J30KGJ(w-q_(3ZFi4rrYGv@uH zDfA8S8@=p;!T{Y%fA~tWyVFLae@9V1NO@exTy@FRS#QMf4@@#)D~R1RfoXVl0gq<%uuQw9{-a_c~dlY)z_n5K*-wqe`Yj>Nwp<)5`lmTOl zTt!~0x7qJm?dtO5Rv&pR-Uup8)>4`?jP)CPHPsVNl%T-v5XOQjx09L*frT#AoXx_y zNx>r@>*#91wWQrT$d9L4R{J=CBCllGE9rb<3|OB#{?X9AtQQHNY8dQC-5z*Dp}2fn z(`&-*K*CS$i5==?N<1JA!TRY~!TD!8sye#{T>fj6ya;z+*x4&5J!JRk0RrpXlTaD8 z$D|_8Qy-Gw^z_T0jj@*cB-`E+)3fxF)5;LMoNmXKKFnoRkXwwDcMrBV<~3atDl@Y; zWK`Grr#=&YKdv{fG9YV1C=rRtn{2L(vPR%luY^&qme@+h$cF5KRJ`6j08e!+PS-yc zn@oe~bK1uCRQ&yf-Cqq*GWz%VEkU|Majug-u7ve%A3M@CT)SJL#-X|K%b?+ys^TBa zT85Mv@6!xaNO1a7s_z5st9Je;AG7>?JY_JSS2@WR zlO!dgUGHK4Gcl*brVlpvbESfBdo`Q!I59SNDAS)Oz5@G1eD(dbC*FHBS;-G}MonAs zj+L#;mL8Q0InIRm*4s=9oqu(PRdLi{Ax-_iq&Wjy9s%UX`U*4_3Vs#qz5}{RKd2{N ze`AzuSxTEjUh9h%Y+12G#ch|>^($}Psc8&wQ?YDxfU`{KP9>&`UBfbANtvZH&C~C6 zuCx01344L0E1Cv8ZF!tD8jp%Shy?z2Czfx0rGv+Pi0>>v3Xy$^U5(iL;~-Msyfk~$(zqOz z7x|NuI2{9gMK&mD)@tfIs#eSs@1YJ0^tVjDGqevLS1xdD9ZCI7*K&9arsA$Ym2f9pB?B#K!M4uyt7{&B)rB&Ku9!d;iTy!Ly zFZ|)_xBY%Jba1X`{A=T@Yq)zk%SSw+kx%d3lS8r>zUs04Kwzn&R8_OJ{;GnWOKvC+ z?@&r^SO|bm`Yh3+sNGhr*k&Rp#;pjtxiUU0#n~J=yxbjM_VO(AI^+2(63K`Zi#7Jf zPujuC*~|LOrFKNwvoT$3{^X#taOn2XmZe?&{B+(}_N0$VYkhf>z5Q!)k|$ik1&fRz z9UCU+rl>b9I%oUZi>C@+JffCK=06Wy0>OreZYP5wmB+m(2(kcjLZ|#?_{r zWyV#))!SXcUqT4Xpv2#&+YD$%UP#T0Y7#u?>s^4UTpctcx#T8mR$hA$1}hBEAvHdB zvbghNjBR{Jua=A2eRDWU`=sMVG<79BuFWma0WqUELO$g+cWSr0iImOA;Na4d2z&rZ z_Y{8r=$EhtD#>70e>BV+bnZO+CyZmtSfW@E{*W&K~SI6qPLgHHav)WQO zsevS^!(5M^I%K>T*Uhs*$t*AZ^1H-D{FNeb6IK-`Nd|1eI0d8iCF^RKWXEs9nmb?@ z=5jXdic1wd`la_*C2jprc0g*D=}DrnY7M#=`QgdZu6^FC3o9l=uW#l=4c~fEDiz8w zQjJI+E1_|lLa_mf=Rlym%i0r$<2j63`y-VAcZuazsvz@3K1M9#L)^MqTe`l2Y4N47 z#jR-b-yv_}`(kBTIjP+?Qm1mDewV;TYV`08Z`$1VIa-WEOA5iX%Or>v$5cZ*dL0*E z4!S8Q8i2aLs@9NL`S3}Ea6_h|$pf=qH`3tSCfJ2HpEwTJ+7RV_(}GQUz=Pa8+v5Hr zSI0`S`|6ru;R!QL_ITg&k=H!ZL+8BvYD%DZlGzE&Nypgr$r%HUF`SluQqTP0^S@Xf zVb)mwFlAua>{HwCsGdhi(AVd^d1$SJQ~xbD0%+pw9Js7u|jG60mhniFe{@&@*wmNEI20e8GcDaN#icwb#Fo(I4f32B3U4@V`A5TS@ zn)_N;?d}`&2Ecq&>xc=zX9m7@GLU*llN9!LFtipwHS*qqA zYa<a(cie#EwvV$KS)(VP{7XLHqqH?1`gP?-#PEW7zW!<0d3p zDV?so#R`>)t^rkE6mGjGbvVL^dOxgpb5L*8EJ=zZyH4+me|}fjyAZd%dw8L%c`SGA z1uwHDr5V(f#k)yFm~X+tL~d96jcf}BR(!%{zxwe#fBQK(je@Bf9AFO}YLc1%GNPgC zx%LS5K!$aG9pQla3ltL)vYNSsAI*qs%vO@em8{*$DB7f!QVjC&WhR4zKn8(s+~;ev zDO$Z;C5!ENQWn^H zg-Nf?-a&nBRAsK%N(Z$ANdMVYm&gjqZQaU5`j>+s1JZTW;uh`8V~Ie*&gIY@o1#Qh zCTtVRFFf%aqS;K|ekE7T*GMDGjAbG@T?w_mUVYi-r`_RV^QJAHL#6kfD_B^V+jYZG z@=lvQU%^Nv?|@mZ1;WE7dSJRMD|&shm-J=SBwx;qFJ9vub459&#!ZbK%G&r@xRY(A zez_*6^)nRI-?&`Sl@(GtqFFvtDa|&JPjA;|{7nxOl0e$|M^PLZovi`pzo_4y%06Z* z%Ht?{!DCyL+RVt4KMCMlWl#p#qGq(ZqE*}~-?WTv-G4`sT|)!!kd_f9R>buG9w zdghJsAiThx)ekjNL??qDaWNStoGoRX7rNpN89BY|Ua%NvFr9Kd^I0mxa%t<@vdh1gS zJ0B$pf_uu?PU34qXI|H~%Cf6o`gGe1Ppm?YnGTcBzl$$hIc{5Jh@QCi2K|>4Z9dL! z<;0d=DG{M!f;o-JAA7Vb_`-|Lrx#K_qABI2f$P>K<6H{jwr+pfsP<=>*cQnz3L0+B zQ}6g!Nb*L%f2YN_d|9KpH(2bkV#G}|M}yIE>dK4XeneJ{20ZQEzoIrvxgd#ITa0x}~Y)j6If!GUAAm zCC_ULv)vS=aYP9wblF4wbw^{9^CVGU#>CjZOM)QnSL9|&y%NLr4qC|2y3~DD*dPbI z(6Mg*gfaH~36rPFfg$2g0DnW;t2Q%Gkt$gqY-I~s%&EAtgX)O4&@CrsyDzBu-^`6be_A6CKZD=0DxOTjpsQn9tm z=4uJUa08C4@(uO+w>tN-q}lO3J^tCwxalKp97D_w0gs2ARe6emdh+SG$ArJ{5ib#H zF^&^TGsz9lGP@)AKB{}Vr+?-R=d3>eH_11rF=)6({oJ$<)`uu0eO1p->fFz4%T9

}l#dn7>x#QM7sX zX+(Bl^A5ZzNum5ED7gf&$g9CYOg=azUiEf@UF%#H7k~AGj2{FTeN$QeI-Pdhk@wZ( z0qALgP#b4tglt6bDqOOPw}4eDP4ga5McKz!#7)mV2SSmC$a?b0*4){gF$II*GYvO& z-=$MuD#($+jmHjE>(=F=qro#jLw|XUtI0i1PP^WXxfu~4cY1nkU-khZ$gQm0@tpdK z-6h={6%kjxpmXUaOx`LSeG)9&f88u`+AXyWV(RN-H1ar%ZVL$XTOK{)aVmno%R|8{ zFoPyUf4`bNin@mJ+Sj=s(;wJy| zJeIt+0n2f&$HCmHf*^}(y1uU$XM39kxDH|sPOOrFXWrrckZEfV7E<%p;fkDqT!!;M z*%W+%G~>&JE!4ect)3ih2JlAu@xz5W%83%B4)`-)gXIaW+`8a={DWhmOULJnY?1%pT=qvP_*^T{J-58h2!_j@Nme zr!U(*kOsqr6P|HJ1j{e1(uKJlO(RQ!iC3FT(^fU?$NgdfPm1egbT`)@Ep7g5j`m1F zM&84mR1Ogl%;iD?wdGE#Z)n&9O1tO1Q z8L_$>;>&%G$#Y)?_~mb2?WEC4mbpJlpU?y%veZ1?=f7Qtb1f;A_9rmoJmcO{HDVb$ z|1%Cnx*i>fh}SHmRX>HYPMppEHJZ9ZNsP0yE~2Ctp=4$S<37h}-@V-C_RzJOUAr7K zaXLmqD(k1x^eYl~A6PLKE?@NO+Ug2M1^t-qg(?5UFQ&(%9;C)SjU64$8TE_fGxX;Z zHj-9U(CtsyH2bnm>KyGtCTuevyf!q{}c zt1HiV9b?7Yj;(xMvqn+^&(AAlH@Gk7Z1?Xvlt&t+1b`b=%%R_Xr>no*;Vnd|2TZ3x zd@NU)JdhvnJmWFI{LQTqM~{Si;;P6wQ_gWi+IcP z82+Aw2drg_$-n>^s5M~x1U3K6(jzfi!l>xDe0)_wt!wKsm&s^ z&6->#Z7Z*M*k}p1cUYPGdz>$)*&;w<4Y0p^hU}BtprS@HCc5U|gGjeZuK*SL)!{w5 zQS}lrU@CmV>ktwD-4q%lD_StD$(Ajp`^xM`q1fBA%toY7IcGx6qs!kI+%XlrO#bl) zK4XI9&xjw<$V_*HDZG)oqHEM>X)h=e^0ARz_u9)1j#9Kn%2qBEAjGmg#?ENlpnMD7 zhuXjji14+a}XTp2Rjb?ZSsDJ=Fg&om+j%y@3JUlS9DxVvc z)nb@*rE#?L4SHIzAzucCnnJo1ikB-GldFadXc)_yow6kp(~+FW2XkJWieEVA{f`7+ zn}6(?3ri21>0)UFo@IukXl%BfHlEn2=bjJU*egtYqw5H>A!`# z8c#((mw+zM(k~{De5N71xm0I0+N%pz0_l- zEWa&3DV01s8s|`d(mgeitoW>!*6RA?zuMFNf^qku zb50y(s?b0zqqL9|X`=P{I(JWP18A&vzw#5#p#j!21v9ei3doFn!84)VoyDg4ocPR; zIIk-1X^M~Uf9Af4 zX_WD2he{v{#~QxfVC|_Bg%hVnc2?5kZ9C`P%*QxaN4CpoNcm@!#_Dh?1T&|0A$uDu zRVG`kJX^CjS2ghA1rTnN06TD~N;(T#164(edEN8dV%gykdvYL8M%`r39$Rca*!fx- zu7QT#t|bUZ3@=#4mOak05OQ>9$>_N8;t2@O_25c1^5AL+q(zFk_?e(7q-9G=%47qh zY#8;dx2l%q<9li^_>?qJDP-mtz9g1e@^gPd_NN?@#2*jFXvQKnTQ8*Dm*++ zJv@#ZIDu!(5^Fm_UXbL8=A-8xig&JCTre2@_-ijk1-~45Z&b>)T_G=;qMzP1LOOH2 zJz&Qc7~1?{1yPSX(&p=wu+h44`tlfCYf=^Ovccn(U3HC}1K^$7w(T2>{ddHQA5#cF zr5g`-h$|0^!#C9Ux-ur)m{eo|p7D#VmSD&mJNuskQ$b4U23|0pK?6}XUhU6ml+#5j z6c{ejOdDLlsTj0K;Jp_0%=SQFg8@IW<18zKZO`f)bMansTw77C%}~+o86ZO^!jeJ2YEgYb% zk4iDUCmt4EKtewdq2{ogw__yFbKXT*3o%RVoOP@d#$6Kd&1oRZf?Yn9ma9x+Lb-n&Fb zii;Z;wgSZ6GL9c(vlM%qbf;vaBd<3r

Or8~)?)C*{ z*NpUqG@k%3&;#h-=}cN{dt9Y{ad!@3^OaQqp}>{4LU1Sloo!n-L$QENv;j(n1%l9c zxQB>AN4o9L8?i*jHM^ehdtb25mwrvhM78^7 ze1aOBY&|p zt_;TL(|9tY(c-SO2f0b=Ujo_<1uZhq>U78*sx0zq zJT;}R5mrSiD-QR0R$W>gVQ#Sy*moE!9+M{^19D^s;^ae`|AJWn90ce7FP)FwX^8@Q z%NF%Zsyl7n+FZipJ_NYYQdko6nyn?}-^YIpb)6Q%KQ|?HESq1)nE$zF zu+6VbcecM+s*lDrnkZkN#5U4->-4=$Ax^h0fATWWWnbB!*klJt4i1-Ch=0g zJ9Wf3seajUqT`z7?7#A-eamvZVgvB~`)Lph?hCxo2p*|Hzx)~nyk^zS~%SP1SS?o z4=gY>&;+k>+`r4R-@$vW4SlbwHIi)}aV$Cj)3Bw)xsBaq(0j#WdIi}z(k=9^#mIhs zCTFf_!!nT;p8repqFaL5dQGvDflR^(yJ9UtY<3E5D?H z224UB=RFKGZAo#}jIH!3{^g=&thEH+%ok2a|EL;Wwvr>1*-wf>T*AdsxV~^qUK?ZN z)`j^T$*|Yy8am)}P4UHSnokfe2eO$gNUoR9!CU{3IS*9Imh@y-ZX99v1o_EgV)l-h$D>;?YB!Y4aPB)G4+NanyEm(L06(b zJ>8IPb%*lWj>3gQT2w1@jB9V4*r>c6j(-qS_Bpwu#eK~EFxhyD0yMl{T)k~XOOoF= zvm0!1TYnTB0QwS&wLJCgBR8Lc`L+jH*5dh=PyKblKcfU#iTYTql|*v;Vy<|JH{m6x z9Vy&}=yc+gn5d4`=YkV$kBdQ%$CE~?a(=DDdzHi;ZpXo!b3=h*<7Xzy`@qv3HE%ad ziQ9L%OS)B--o7Ii0sbcj(UOVbkJf|(b4^L#I32G2V<%`f+Ahsdcxq-}mZ1OEuTex+`F|5Pt;+sJ(0jf9@F3n#k1D)Fc* zN5~e=r2rm-{sJC|1`Vrp7lv}DkX|!kcH`qut%>|S396W;EHv@c;6FiCeqtYwLAqcr zkP4?W6g$B8{O4{{t$D=*GtHjp#20Y=*y=R#>!)K1+ta)0q$vURmcGgMT(gLYmW$hO zW3tlruCk)6WffvTKgdXfXhtI?k=?`o{=(oPM7v2{l9dFE&%6S|IWbr6NOKveY{z=EeKeEExtZ`MV^wsc z{njm?l&)Xx20bsfH|-h%Vk9E?b-LfUZ`aaHhQ}RlCGr)0$HSQ<#`eIM7#5KDKYrt-bmNb{ zWQy*ela58vHGWd!rtK9TW03Q6u2zNs{u5A#(Ht1DJ{P94s;IHr1_jB}^v^6K`A48G zsh6fi3HIu9rmH`gCtSBnsi!U+iSlp{4BJtY)C=;F0C2RtO74mi^T~yGen-0Y=2MN2 zW^IswrmKy)tM@?ca3%$IR*DILdF=a#!tBXLIYA|!sV0~xOX2*Eqwc4a7OzWpWVqUH zz|rPt8|^!5YFOxh{=2mUX8!r1HWw z?vRf_mYj}78&}WiR{sdekvHHOdXD=krhPg{%raQC3|En&`knHf4n`L=% z=2BkOgV^eIki}MZU6wV%Gxfbhch3nBprgV(gNx=oWF7y>pNiWKxio2Tfxm@w%39P# z!wS{8g6|FxK%m)~t$J6k+Ow#x$Sdg+dv8-#T&7}}FqP2rIDFc?!d|-+WmskFi!2Uc={K6!!sd?-zRj?ivAdKYuj$9Jh2iVknmF5d4;%{7! z;L*NxSEKhC$P)uX(P7V>It#oJZLo4bt=C9BQ1|a-bc5+;bAS8{aZvOpuL^lMZ3L zUJ#*^k+$Vqw%j=zic5E@@siCucNh! zSOCK7(NfO-(BW`gb-lLO(BSW({_EdPUpotOGXt?hpE`iPC?`qYpr0-R-vFf<=n3$;#RE30Mceh*h>wa(+4_s1NbMq2h}!UP?0%(^R_%o9C< zV@yx`?uM?8v91=vEf6zzOYPxQNbUP7^$7l{cO#aKzq^u7wZvtH?1;-6viaO!s`p=m z&u!ojzrB>Q(zcpQU9CW(02vVxwf6cPG|K8_A@SlD)wkifxu2f&Diq;*e(3YNS@YUG z;m-yvVo?MP{f=2M=9HIhMR+EDkkEhKh$bKKF@+IGwsmAH>Z_Rt_WUoriBn19x;M!u zQ%YvoLlugelsk~ZB_V5wqOIwlB|rXy+&!=ugakd{ZUosH*Cp zp3sjtLGe81^i3!2IL&W266z$(ht5@fni8DQ{*x|+(|uOuj0>7yA=)*3Dyig;SW?`N zMjDQ`q|v)})a%v_dL}@!zmHtJ6xN$qazQHJ;`e5s>-ODU*yEG#=fT_Vf^O@GS=BPD z)Xiejh+pT4^lIg%d;j!rRmWp={8XdPq?1w%p&2p%x(aIS%#P0t5MqLrgqn4=LYsJF#m}nlW*t1WzzGc4I5s;apChxS7RMW;IxO+}ogqgsYU3ktVkcx|Xg46vTo zCk3Q>HtQ_O@g{4kmY|2og|ji_S7P2#8v!mZPksL21qBlKwTbmdp8r8ywmXHf?{vRy^XY-KDj-n5|YiAVZMZ z6ppgm9cf*UbYk-te70WAi?+wp zHD{aX)8^9o6e-6&7OUPM%YN$hQR0(-lrz?@<^>Y5fTl?|h7hW>vvPT%SE3;Ze&;)X zFuaZzwf>WgkOlvDVLrP!^U1vKLArX?OZBpRkV!$VXD+9C0Jk>(w>;iwn`&lEo*tm| zYu)e#-2Hv1L%~cl1Z2zNxpG<~>QY~H%@#Ye31LOv0wj!>yR36ikC5e;3vXCyhg7+3 zec(}<3ucVe!n1=?6^>o;k~YzrPJc)RULh_~VssJs3!K216&}2>cCb%9 zu^paI{#r<%sQ6La^q593vt!`a_u#U`i_p#YfiR$CSXx-xpzUao{pLrY(lK5 zoq8`9p&kUGG;o{2(W845AMp1W(1abEwHgYeo}Z*wP;8!<#}u|Hf{}U@3+est^PGlb z@&=H=1OF(Hpdt`*2FB;0Jb(i3F!AEk_8?(_on-#UAde8&49;s6{g2M^V>BWW*>6a-Z^!jN=;nN;znD zr+SQ10JRQbyS{%+yZsusr>FL!?=kgrAS)=s8aQiSZFR!w!}Uus3Q($I*oOGGRhN+- z1%+GOdgm^`Y}AFbYmO4|yH^{mez}3Qeu(9uE#!oxQ>C?1mZpD>Wg@s$)t<%btpcs8 zr`sw!qHO5FZ?ii7gx}+D39|um&6&PCyu9T-?n{V|$E$zsdeIum)YX4!sul=Qt30yB z)uQu*+sQH(Q{+I(T2SH^yuB94d1oe%?V^n=le`IT>+ zgSnu-Bf&7pS7Ds;8~w%`TsE5ALCOK>q1Rn_&4bLD_OW0*aL%-cWqLuq1_y_661_&N zov0^z-5iRoW@hYQy=vhrF~kyG{6^34iK)eQzJCh(AHF*VK21j)C{Gp3`ygR%=WFUc z8+N&`9-d>r7a!~{#wNe1CrNI(;VD+AYd7Fcv^x+)J`yv9%lCOeK0mrE89R~Y0>OxX zS<}MSZk!R|)YLtk{4<1x;f9=NbCON`$g8=9 zyp82oUZCsTPI*mbqip((cVrvI4WkRhTa?V^CE_C_nKOnQ3V=88`VpG??l*x==tSmg zj88FKVB86OI%mFFJ*@v|`e%S}e>Tup*}A>bra2fmlR$Y_&hT{rZKMy}(owAN@xzaf zNs>m3TG9NmOr8qh;-^#t>8O`t-M41@t;sw9vp$Q;Qx-TLWb8O>6EC*8Y#xECUTPWb z++XLaoO(*mN)JXyMczpQ`P{82KRqnzp0gp!#nNBnbH6y*YdF?u>F)IfCy+eBam!tLh zr1u5`04DO{e@JV(%r{DOlsF(#sC~8H*LQ}_o=)1(qbdq3xs>tS@XEc z!po}qt!@+hEsp%<(KNU4d3MmBKR*q}eT5v$qF$fQ1bRGfG#Ialdimv%?d(}#(yNni78Snmt)(KL-!m4If4{@*=6a89$BqsWw z`ARKbO`CVMj!^G~zkMJUkplZ%@X9M>*yJ<-k^3Eavu!_r^%S)ppimotJZ{>f40w~1 zt(-?mouS<}QP}qen(mBch^(ON6Rb8*f2NfFYa>5P4Q-;G4*$S{E@DQNTie7X;flF9 z9Dh-$g`fNRb=YkLliye^AL@>R1w??f&12&>fk9oQxs6i10WC4h9B}DwM9CKj6y}^x zE(9k~E*o^yNj;exaWErbR4<#|4v}P>C+swklr}{OeE5@)r{?iA2XT*XTQ+JnE+n#wPmQAoLya$8aG1qtivZWXM_0_ z*+pIm;9FJj(k$lNLyTapdg37BFGtyj+nm?g@Sf`$dtUnXqhsKF!@w!?GKY@xV)jeP`zvf4`MFGV%J?KeTdU|<*$>`k={YO zOLZ7IPgd|hVm21^t}3hhyzZDL7yIuOdAoX5rmRBF7wIA`8%3qDggr!e8Uom)X1_!( zZ)(k#H0BXktN+OS|xGA&nFmwW`}wo zke*2ZL}Y%YGa@8JzT!=sq&#VVA>o@stpuvk>?oW7ZrmW|zAckIGP zmWg90;}n;xhnt;M&6Y@BuLRp`5i;Ky-}P4Ct~CAa;#g@r3rT^xE+C5zX7#I}3gL9s z_7-3wrI58`Y9^(*M^g@vvzQilV5%;aC5r0!T<)$ZJsMOaU;XZtXhqi)FOM1)byOB= zb03#iyXSMB*#gPC2PxN=$ibk!YP@zJDyCFU)fR`rt=mE134M3r=rYdHG4__)2?Z>b zh65nB-?$)rJlrP%ZyN_vX=3cV2^L2+zMxe-!GqpqeWkeFTdElyuS~I|y^gfHZ#lqK zS-?C}!x(bX)#6WA?2tSG;hoJ9@8*=Y@-qLZ%>_0ES69a!JDEKej+iD3ytPA+L3fHm z59i4r=IisKEKeqq_y^uneB_Fo#>n)ymC$G65ykc77*L>9VZ!;qzK~HsxARI2{ghHIeL=Kow7<^;DdXjHa*nHQ&yk7+J-?<|HBap>%s*i4v7>@&3>Hd%3lI7d~1+ky6ME3HDDN8Nz1 zFrL5V)|Hs9vGuhmdg}u2cWK7_zd^JOskwg|(3eb`d9&|<%_;99`&64DCpbcf7R&R{ zJC-^PZ^SRxa}L$si&K1HF)~>Mj!%Jn3#7%-K9RM0&*_>~hZ}-zaP8Hui61MFJL1wh z2X=KxcV7Guy|=B>=jhYUI?|`s2h-|rx}rgvDaSKNv91R=a=Y=Ay!99e)Z$bdoTrR> z4y;#7sk(5J`@$RO`!lYB+5-zt_eMbzHf%N%hCftvWx$EiT?52BHC{f@K~MSKe{!Tk zq)5ZT59w!2&^Th}A}>avcicFf*Et&*+sLEY+9-YOn}geO#Iw<&^D4hg`RL1DIck#046)?3Rt9LvPgG*Zvz zsvt$f`mXJZE#ZRyWKKH%F3DKvpZy*v*-}-R)y>$Ah^o+4mo_4fZDJOZ$8)%z`0FS3D}6I{u{RxLWNx| zYLYwQC{6pa*M`VO`95X)QOV(LO#Q(1skC`-A#c`PGNLi~Fcg2Z&lKTOFihSXRH!1v z;|~U|PoefWnl+9;S;BttKz~FF*1p>JWp`J-5*M5=zwD70sf4Ea1YV4C$R*LtqhnwMs0+t(s8!!ZzHH zugQbz%>q(@muo2s$WuZE1)7yb z8pB!_^{n~&{0z6|b!9j#Z- zB5CsU9l~HaZ0dkYq~qM0cU%hhl;}s5{o#vUQ4YB&@NWvo@;uO3=!?4qWf_8W`Ue&( z0s>5Fbca~DQ(w88`501BjO5wr*x!v^a(3&D|I!4*BkxAvwIeccmJ4Xjune4opPKkW zve6B2`d9ABYtWl+kK^c*m3!!hoGqGx*psvIlINY}e-A-Qg`%ukR)?he-k${xk;j1N zcF6jP9u%+yx$*8w71K++eY|0hOzy7RvCzI0%_H2o*mMqL&HHS}<;^u2KTB!r_4Trh ziu{GjDZs;ivqr!nV z{SyqfnP5+f3W{V=TQ! zv^%@5XNcHrc<8ElD1~ zHd?B=M_#EAIYx%(X6oAm$?~iA`U`5VrIoZFGvtfJ%@B>9rBgA9apHLuoI|QBfj7r3 z(>UihnLP5)?(PzE&?4cY3HSMI|&^Ye6YJnL}VSyi3HlxpME2Jdbcld2bee% zkfT_vGsp0TuP-4!F;>5{%QqY}IuXBUh=kK0Hd72!kao6=R&k4;pSd+DqALV7YOO{l zxJ`Zcvt=`K$B#IRWx3_h@NTsz2#7uuuFUW!3gP%;YO*jz?p^%PPob8k6qu4Gz3nw# zIJ}Ue-0v=($iR=&shMx^l(Dnmp9DQAChr-LtUL9eyBy1xB2fqbyyBPyv=6Aiq-o8A z;U@*;?D0)Najk#AY4i{vJu7I(*zu1Q*C(tdn&?B@#duoAJiPx-9+&vO)=!IS0-|ty ze3s>Z-CVn_ndXUtZ5gTd}3b!o3)tqyYalO9#{%p6#tsjl-4ZgGv~Zf;-fk%??4*b||!onIhCK`56@<-+0ixIXrPi zyJe_r^#Okcf|?0tdM%n$ZRvD!`2rO8-QRmP-PWL_F^hA!cKevW(zlzFaiyp$h&{UY z9mRhgPW+8}aV3D@09V_!+Qgxjb|~r$9LLuNycczTqyLWD@dg^Tzh$UFLge^?zw)Ln$5125Py# z{jc;m1%4N-#HdqtXcdm}RVsxYXOuFQf)gm4EA(UIznS(;!G+akNLprpbKUV_Wp!N6 z_)62!a)z7w&aPe_$|96DL~IPD*2;Tw>y`3qFnWUI9R4*W)b|TN`Mj%AW6oqNHQlWP zu*0j(a?V$z9ujz`8wbq0t%abLZGKY$%J_v1os4bgb%;NH%(^mDMJ#UInEt&8oi2K4 zAB-T_uQ-f(-liWNeL&-j;(Wd3BhKGii7O}k-!I;Q5c|XFYACmvAmHCERS%dajB6b1 zmr?vR_6RQ-#)FIx<0p`n$Gx;GtY-^fuz}JqQ`LC#oa`Hd&rgoibLQ06NRfCt4xR%# zwA7%!ZGKJjAA+CJMEwndeK}Ozy>(Q|^{K=M2GGiRE7HC-@9e?#41JQv=A#85X(S`M z5`U|?ox8-lpM(shJkMn>UCfD{e18HoMt+iMD z_FC)Ioj$VPdaY0wFt{-1iGguNct@PnDAcurU~E@OZ0$otn3YZ|;AGR#GeWO59j*xo7AJ{pCM8{I<7>KKo0=1q7YzFQDcmW+Av46SMJJ3%o*Cngq8G zWes+p)HyLcow-J`oR9HK+}lCVpci6hX(14Zuqzfq5n6Y^+^qLO=FL1kt}F7%5`1y@ zG&1ctYH4JKtlw_xTHlky1tsL^bmXfGv;ON~`?R$!&zSH0FmLYDA_6c9aMNCaG+a4C zF*A@Ij6G(BS-{4@r6$E|%-m-m2>1Pybx~?S=`cy&r1;dlXq3-T{rB}_6O2sL$TG}O z{wOY;oPV(zcu&Z?4N#VhJ+itgSp1W7?GU`6#Ks!Dc(RM*_X*#(#+{qzOSnq)+d7B0 zeG0!FU@)9xC;6j)Zv1`K|M@xK3*_xt9sZva!ckv%8p@&1y>73U|M{r#HMV9Q192U* zMKe>CK*B!)RZ?p3d)WKoR)k{f`;hj*Xsqs8?|nhfu9>x@^~DytQE{wUxm48WA^v+H z@D%r}J?(h?!8%(uuX*>@@>n4kYkLv>{T@$#U!#M)CLKH=C&2x&heOh(Cfp@L{Vdn#A-EiSnePHPiRFUqUoKSr;licAT-d^L0StBx40at9 z$zTt5$-iYxnEAidzf5Et_pI*L*?9;gXa!4u8V>&@YL?$0%|3tX;3>JWW)tV=6c)2? z?}qTHS-?!@kK+Bg<^TUL<=`uC{+zkZIu$!J!6+;dzpwM>qgk0%U~_yRVjC40hmmz0 zUr6TB*{R>s!~;}SUp3FlW1yQ4O!p)ew=n9W^Uy|eA9X>a&=V3-7!DL5Q#fc!2UZ5b2R-2E1lbYGPaVTsOsT6E#;?EN=@f}?Ig z{-i;62KERS!xnnY08u9Whlqs6FP*#rodu>U7{O@R%X}9Avwq*nLbCLiwC$kJ2$&0C z9Ln2Sgg%<2r`gzba%b{_XApBg&oTeMC$4z z#rQSq%YW${{+G^F|Ii5(c6IFof~fx``&+O{_?zs5A}iE?h}P&Rhuy*zlN!tUQHG)T zJKom+9q-2rDE87-?4{VEG3>!Ei(L;q@EXKUcd5Yz2fBZMArPRxY7NwFfBocb9mRJ* zB{AR?@xcGXoMgb9gOl$5=dA2KJ`L}N?GQz-6F&9db}F$$g7~V`0IPWazmHjqwTj~l zfDOQv3;=TR=MKepn7M`KL2K;#AQDIR_jr*0KEnzkNYLcx-Fvq_TM;aet~~n^O^J02 zF#H%#bi-9rEVExgh5n21nn{2?T5upo#z2llkDS)Zj=R3X?XXMY_rUk_RxsDV8hDId z1F~70XxLZl)W1qd3+R&f^*}ig|J7ZpfHP)T3FiFEjE8&-P5i-h#b^BZhsdojunX@H z7!YB*|Gr!jJj7F1umr&$XOE^B@|t`EU43?GEZx7E{{gTlIksWS_HgNJ<^20B;1qTp zfv55yudO%+EAcm-97K~{`lcq~hu4ahWU5DYu&k*8?f?+nQ>gv7TADwE{(t{P{>}C< z?>BSSfYSVPF}orN;4(%h+~KZR{+#Dug3JfB zp`sr!F<`{dlimE$-=@jvj1*3uI2!|-0VNjlfcON4iaZVc2eRe=tRHPG!DAKcXrV#n`6|4m=OytJO^|HH+9Q`7e?*20%A0#ye7I2$|Z zGS~=|=n$+;P2BhJU3k_B8Hbmo6f_vbPiL53!5mrBn zP4~O#o~-?V_Iud=z7K2L-^*4~H-;PhWepkn6NwmbG5?2wfYy9~I43LrEUGhq>3^t+ z9gY=Pm4CF|N36Ev26q2HstM3n`F{xge;GVzd59nw55zhwWDb-6JwR)DEGoGI>89)B zK4Og%p7HN5gg}yY1`Ic^V}J-31L!Sz z|IcE46ow1ZlbHIitxU#ZU9K9@g>tM#GMwQ3)-Vdxh+Vh6i*AZD$=SDt(_`hi7IDEy zj`!|I;1ibcX4}?hpPg%T1gUR8M*^0p5E&nS_`P!5uZ-g>Kb!t90qzj=<=?gA_ZPqO zZ8(YPuLb(Zgn)GQ)O3=&`oz8wdH9cx8w9d$N_)TF{l6G^wI3wuLu&rdhgbu9#+;u2 zGCXI@!KyR#@R>SW{+Fc#>g*PY@yfI6__rUw?Qs2>&l$^xH_sPy-7#o?{rNXs*#OQb zJNu;n#&}Zpzx)p`{^wtsGn;Mw=Tl$G9KhAznr-^uECR|0`zffg`>nPi7x!wRpz^LK zfAlwFm9ZK^$f1uEb!vzu`)&qr7O;=5h@GI+tn2H0F2LR)S@f>! z4co}s_s>=_1DXF1eLpaBpG632qu`5vqXRC8h}vBp1T@dv1&kbAG&{qdbz*hssdg`g zE7qAl1uZw7gV947q+F+jSP){@%RhW=Oi^c=uszITkz9$fh^(ZhVDJ&{T)$h3um2=| za)&qj)dD~#hR?V5L_!?&uxj-m2KX+>6F<(Aw5(h*48~LQ&!~OzWMx>P-b!lj3-SC@ zII<-_8D%bKT|45jShB60C}`g}hjqRG!``5WR4I>!GuefNr?$$x4R=9$lNG>+NQw@d zJ$l!45=ORxi#Pb9klg^C8nfmMKxe?k%817D7qx~t2bX!^0#qkDuV%2AAna5wB@Z~y zszn7$MOakhYC89AZ>;wCN6ykLtyeJPDyKuhFW%e|7ZpcGembEdm1#KH3n;hOKN;(5 zhh;eG=i z@$ZFm>4LF2Ha2kZqEio~PrWU-m31vA*7FQlX=5)0*hQSr5qnDM-Z!t^LnR3m=5&RO zz+U3CbEV8LA%VofO~_7fd`hpO55e9sUvKsTzPA{qty*DdM` zAMd8%DdqvcD|`zyX=t?jhyBHCFQB7<{WYGF-}0qN3~FC&i(V`O(U1M3Q@7mZm(TRA z-DiOA1bb*AAQEgJto>^e(}r!KJAD{pNtd4v7<2gYOnse~WtBH3b8c(-$ie(}JP5qw zezd#)%svH>*jB&SIzS7~t~r5Y=6{x(r_j{@dK*!Ap-$DDzVPR~%ZsAehNq*jgVt29 z=a}JEc4WhsUyGP>IYd#`$#~&_^{;)yUyW;(alE8*MbOg@URNG*M>zx>b*OX#FM|J) zj?nSkso$?jJ^Vg3%u6O$)NoWV6I_8IG??z+w3*bNn9^h1((py-svKLuFaelNvii#0 z)6l)CVW3(};WN}S)>D6T0aRuttga$1aYA$|ikJ0*jqX^?DvU6)e^5@e-CA zP=lyIw6L7P{js?3v(x=~C5bQf`{)J~?}B98R$JQ#*VN{(vprkOVP8$jKfset3<>;7 zYZBK{4-hBKTBqv>zf41SCH`9Ap||;cOl&yWFuE7)fuWXQB5SicjIkevF3iWeT-do(>vK ztr*nQ(5@q5nMyp68-QvYR-s z*7~Orf+L1!m?+xtX#QuB#8IO3wt?NDXYx;i8=NMfnxXCUS$JmIwl3@lu!TwBp8{-d zs^N2hmUNa08FhbmGQx%!_TI~PPSG;KrX;E#A+ByP?`l&|Yv}i>u&~x>CJQ&` zI-&YHv)gaWPCjggF&mKM+7-@l6U|}a>oSDs%47uodVME(-I#Zhs|wV zh{c>~a$i`Xb04oo^C-r_xCA)=%#C->bC^JMug-#6q2#Y&htW(NUhl@QLbP!8K>MtR zzPgy>o^}TOY!PN1R|vO%a2rJA*9LZa?EVgc)>>cOT=9jL6%Gtl#qCJb@>cq2m=tiH z50=A7(%ghb;u4QHGB*K`8mDvVu~%x!E||vekQ!zIR#we^j*=ai%XXs?`0s)=oi4dr z!F6s@hfNJorl|d@c%F0McFP@7Dh{H#gT9#~O&D_mA7p9Kr41^L$MiwV#+i?WvgXwUk32rO>AlN0=n$YAZ-p4Ut>ZFek7cr?^}I z?+tr-@(WeTLu{nL8QBUMR>tAc8dyUF$wR%g3+sj$4Ik+;$%9w7pn-X0^O;pGV)PAV z=*Akr@O*6iUcS3BJ)&73jwrY%Wq;A6iBn3AFpk#$<8-qssg_Q_wR>H*B)$QgPjDOt z+V{x0ERwkx1J&zJ9_ikXZ#umV(Fh7w{b^?lRiW^e<1`v>=Ow5*LVtRFz|4yBv4rT_ z8XJ)!4NrdtF@$L}Q!wEiUew`q8R5LcRUUHE~inuRhnDbj_c5T7;(OM zVn0rEKyXk(rV2HT<77#Oh+Z>aVPH4~nynM+F-Uu*W5g0Ifuc|jpB7e=nqh`O6e|@G zysbK9A5X-Y33EXn7S^g6YN+AFd{zH2|xqJd~}MHbZEcDul(tiW|i!P7l< z?vyvsxgcVBG1oxHCl9fs>-7)^;_#&<*J%AD`93i(s)%T>P=VE|JY-mc zgag`m%hq^SPg_<;l(2_lR+QSH;nM+G6VuJ!6Ig2X{^ZgLB#Oj{YNeqp;HC49$W7vY zg|pgL+7vDAue3IX-kj;6jp!!&+>2l^59hYOp+c5!+}(df_?Ehn_?$P1q-im}G)QJk*9p(K3n|2Rj0LOKZ&>6| z<+%c*PCD_ha8J&}14;OC6!Y_Pv^}S$jLi~lA=RaAdk zcy)3VKL@;!N#eeu70w4DC(ta8P3DPB(HHC-XmalNkw)60@&}r}9vzYw*JstyyVr)#5#A$$(d0Vq`4VQF!2)N zghdRtH|4+bNjRKybR>IF=JWL+ z*IKC_`6e%J_uL}>*3)%JqZOKJ(*<6zBn}wx3B+2zQLw}ucmobO2tiq~@nMn#hysnh zn6x%hDG3}o7}I&_gTKjOCvsnQreF1Hm)g$NI=;xaxJH{Ik-3CCl12hm6$jg)8jRGo z_CIFU^$b|&gReG@B>DYVNgiwv7HU*HU!1_Cp8ZAe{QKU0m6#*;QGyQewp_Cr6NoiE zg!=e}dc#)KnnU3z^f+CP2Ms#35iKxpVDb9TXYzU!ilPtQ_Y1>&w8q&J{qR8Y*ZXUY z%bFCTS_VX%7jf<+5pV<#;WwK2Y7IyWvdg@CC5%{@9flY(nu<`L_!`l)P+h}|nKbmn zvAN;jLyISPkXsX^im={lc(Gr667=SW=gr*N%za%@VKm*^wytQAYRteSbmBH{h#9f} zc~bx~I{-Ejj?kyPG~a=J12Vi(cD#u>EfEsv2K!j;N>c+NLq6eB=WPec^OB4oWU7t( zNoof~3|hacjf#e7=TGxoVCoeIJ%vk)X$I*`my+C8t`E-$;Os=;)Am1TQrK%y*q)SX}otuM8Z170A<{f5iH;00}`>kX3& zj`XWQd$JvZHRhOZu5?M8&PCZQ!1mU4r6BoR=83wmMb~jm`&#iIUYmfZdsyn^rRAm8 zE!DQa>iB*_IhT1jDZ-Ar!;@n)tOxCcXu5B{9&QfUWO;O%_GY!_-6DMId zu<^7#HCXXyB!4_=_t9Hsp2kMhx0k3m4`&|RSF>Bkq`eMh@I`JLl-|+!BQsDrmKn^N zwX{0+!O$_&{o>x!?QD-Jt01=Dt{uxQP0JXy0JmaWVZz*-zU7*OVekAqRAu#6l^GQx zuO>-L2kY#N$B;nY)VH4q7){nw1g?7ijKwo9Ik=R=#7EOe!zig#XQ&^4GatR&B9i@7 z;e&19(Mgzp)>^-~>jt{WR4iz6&WsF2Td`FQ*M6@+;Ox2dhWMgVJ#PjwS!sCZ~hF`!SvUeA5ad*BUt^V4U9SF-SGgiX$j^ybv?Q z>!v3Q%GIQ$%I6VRuh&s|`bU6c}XVr0-#v3fAfizIDib47q2UMq$~U zR@f32;8bBy7;}ZVt9(UWSz=O?5E{m;&fPO)04LoI2 zW#}pA!Q)Itv>mxRT%^!wDIr$w#&D$&tVi&cxO`Xtxtvs*+p%JoTTfl@{CL#%SM%t! zIkx5v?7j_+dAB&YF}*!Bv`Q-lXi}ygNTdA*JC5+0-vsB^kXkmp!i$YoOviT((N8K3bGE<^4cH03@D3mt6wLlh z@C{&}=YVf1en8MwYle`g$~?NX!-g-}Is&2u9qC5>oap{(h0$8ujjD3A?RDR<{c{>Y zjZ(Dd6(ZSF&rKc7*P{&PS&nmXG^@`oUSHuIkGPv1&tSu??)UTiKJ5jG^3nTc`~LfU zCu5MB1oEmfE1mjfC!L#%a5qaIueGFIl4V@t|1#I+$M91gW*bY$O-asWY`p^?Y!)PrhlG6a3vSl~BEv+=&Lqrf*vTw32+x%OJ&m zPP8(FE}(`sW=n-(JS)tb()xlFOpMe5Pl7tlgZwxw7m99TI>k z!tSoi5E@sS*Rg>Gs4oI~uLlomMhFfEeXstk{D9;>k92L*>mQ#mZ&oC)LEf|%ObU{0 zZs6EhLI=jwJ-ccA1qpLES9 zus|(6tG@r`dBb?%X{UFhg6Eh{{hb3Sil_Gkbf)(psfOy|WMNT#tQQ4(MFNx+EZi#824=p^Lf6csPpP>3Xor`e|9uEUuXIaiYLEb zi5IzD-`gr~Gd$|M-=1Yfh(5~=nY%5BhDgue#bU9*(FGgoAO|Btz&{L+4|Fk$3AAT1 zxe3#eTT=0nPP%@f(l|x7{7cn`yofr?sM0&= z7`!|#cjunGM*alxz|hDA7ALAQAM&^Xsm8l_1vliw50F%S()y4@=P6NkExxR9LqaC4 zOxWvkieQN&`zpK4{vM}(x>}_hzE3nVVyD;pWTB|f3>72BM%Spyh8udf3VjaeMAArc z(uQo&vcM@PVcvN6+sRSqG>$Vl)%!^EM4yNaW%mS?_D-gWZ=dBWt2b-nkJmZ$T^Zww zw0Hu;enMAF*3Q|aU;p+~_{BrADQ$u5%Tgm{OdVhBVAi)aeW&Rz;+vQGRlQmm)1Ij? zs2tpVmfuu{H#@jbixgP-;O$_GWcIN;vc7+`{ZLJo{(`YH+WcIWN;|q5>)KQt_nsxK zkU7u+CcYjxu7j=%;6Qv=hy0681d`J!@;|z*S6S{k@>m%gO*Y(pCr2q2g5c;kUKOF` zpqFAh_#gmPO%WpJ$iAnT6m$d;L|$^>^Qt@_iN@JjbPuhQTNGx)9sj~~dm1%1A}1h7 z&aDxEkca60YF4qKuP`<`1RXZW1@p+j+eoV`rkcqG*ZG(_+7$M&UWhRLn$*}Y8ApBA zcs)!uqk+Knz}JEIr7wOZ;lZQ%@%laG^)>?H)js1+ru3*6R5`cs!p>X83dpys6x(l? zD(rLj5;4kMM=9?){(1qS1$7lq@!LzZaKtXnkLp*J%udiGu^$aSD3U&GC-&~Xu+jw= zrtyr}xWX{fwUP_ z_#ntPwtNn<45@z_IS#iNm`W5ufY( zu`*8{n=W#q9iXL3A~$_daRUDBz|$>#IOBBOu8xvXYUMT z?eSTx41N75c=@fA>1MSXKtP__%nS+WAzr1W5tu7uY>1fJPsXj%7|`4SFoW_*pgWDN zmz?s09H(4vYIy4w>8|=zs;50d8jG9^F{ZY0SpexdKHaGEj@LJHN1lG4pw2u@rt_fE zeG(y}E}m79c}YQ`e637;SSOeJGy6L!o*en}i>$AtO6o&()O7@Mc+Qt6Y*U55!Xp!X z8*CaWr10!ndx%D!ptHt>%NK}Jn>)_wv}@W*JaL~ngV!>=Ap4E|T873U$$TQz$kM~P#%YttI2j-tJ5kyM5p$Y3TM4HV!uyTAtt+i zJymUUYf{dX%30B0&TcP|$06!>VjToRaEUUbRa?J_Q^Q7*KQ}D1kt&)n{1T@~OzoPx zjQV#D{t2!~=UjW|I7uyk%_ysz`m28JQKD`WC0y^Xuo|7q3ukTd9CgHO92WX$Ri&7) z#P@_bywA$-ZgU zn7}xPt5WC{mS%?E`9a(;8zL^A^oubnCX_k;;X5J0p%x)5Ayu=vZSIy2ouA3-2a~Iw zw`nYKUU^LG=6xbn(Vz3dHI(_sJ-x6rn`_II#T+q+3o!D;#xR0ZYb55ny2=|=5x3Ej z_BqnbLtWqns=nM1JxvN?rK(1{*)lqou=k=l?s`a5R)JZ0vB|w5tqp=0HJEv- z8E$64E{_e`HHw2TTF2Xc>eq(bRE=QY?{(&X66U*J9kxr}676PPxvP4u8tj zxzUaCR+>!2AERnS4|>Ml4jejb3|u$H*(qVT8$s%M4?=J~F_bTp?~~ZZOFEmqNBeFl ziH{X(g@9-dKh;iOIqi-YjVE3=YGqT68jfgN?3UqacQH(n%b>Yab}3Hp{remCFj@|t zP+jU`<^_9}3)&q}(&&c52g((XcZtT{{Rkya|8Q6P{>T#SPD&bw>iNSeXXM7-i=OJm zHbw3&xejKX{b|LXo|V?HvRJ&Tw14QAfBf~=y_h)M3K!ctL<-XtLI~Iba6hIbWLz5{ zmyZ@MaW2vzYwt0%Gxsb>*PcBnBtabQJIlBKSd*pAd8JizQT4pFfMU%DbsDt=iNyEQ zEFJf=Rb>$>;N z#|~K!`>X7-hqACK-O(_}Yv0mu@Ss{YI&V{YE#4J5f&2LuCIM>Q*Kk$5#KCSF-++o^ z5Qn1AQ%(fVCkT2zg#DVmj!wD27!=K%SXp=b^p$^dbX%(PDK6ax6A(Yiri+9(fX~*r z!?JYY(bmwZ=(v7o4BhBY?!j6*HJHq`AL8{lke?TAn^ZmY8v`})Dh9P5c9fD%OtWgP zk18G38CkAg|MW^L*QlV4qvN6D<6f)n_oUtkcLs*459cUkEHWqr){fO?tly)OPR6MY z2ILZ(lQzpyiEPHO#3CGSbL@irMe2JmviKY>5lG@HK9-Lvo>!gKi8a2ds$no0JzpDA zCiqc#fetsAk*N)w7O2a9wY2^F;gG3fd~z-ykTa%NDzM zjDMd-ehlX&Hn~sRr{$5hC5J-@o;qjKD^!MNG`?m)c~sVfa1bOtvsKt%J+;gE$~0lN zRChXQjHvCR2QgKTBtOsHW=)+VsJ%wmCmD0@@R=?q*QuHV#gN7YhY${Q)F?pyaxCwqXxSx@0Hl7^9#p>OTo;mVAP=P@oiE!Fa3K{-skO1`&b^AHIZt+iUeOGP3WI zY_@+`C;xNiTMOZR&mR}bclV|h#8>9hO1DJEW~}B0-gywG3|4_f*JW-OnicxnC!#~% zm+2VOdkpm-2yI2YUlmuqlQ*zEL5ParY|13Wv4sv{xVRf;u6@=IsM*%hqcVHsAE+*n zs9Fcg%ehyP)pjm)&R0QmG&{Ns2mYlL=IMNk!M;sr|k*O*$~;Y@cwB_6fTVj4_FB~RSrQuPSrqBY zv^AQ>$xioP#~&NU`BE`h+}%oj@Vym$S&FZVot9k8$jjdTqjm}{qFi;oO3LIpixV?S z1Yw>dgVQ^yp9j_3Jh~Bvo@$uV!&p*t@o35n0Fy*E;lp|uU?d{K=Zu`n#%_z~Y^**U zD>0718cF=iUKyYN<^sG?YN{Aimm622ed2QcvrNJ9h+x9&^Jdk#V2SS9!?wglawVm- z3fV%f37=`11-8p|kdAFsTM&=Yt5uKINcEk@FiH_FOpQ0Bo;=b=pG5g&RrX78Byt+T zWPjAE66H>zLVhashAGCLO!x(H9G50?fz(!l!aMFGw6~ zhpyhcMa0!r>ASQm7{*#1#V@5Zv`%5{;&jIXje_xJx-l9S+$ZgDj>&`QT3B3m=RrhT z<={?heAQ+2yyIE!*dU_;pYVS`BYz2Zf7eEE)oL1Av8Na5Zs(}mt|35I6Dof2HAvH) z##3+U3jy_PcJ<&(T#ekL@WYL6s>U|%9!$YXivH(Em%vQGG0P(fh#Jh{sjY?}R_dg?N+^IO2@MPwcctR4jk$*dJg#IzL>j?J2(CG>jB*W@*Z z_7^5hw7IdG`Pdqeiv(mkyHyxAAyMVDWtkvudZ<2xw)A-G%1-&(0x?Jk19-y$veaD4;(Cr)t7SB$+eZ0Cm~pkqz|%^ zrAPMPRGowqPq(6#h1+>}d1Z&UZP zRPd1Ida6ca5wO9&_$$|UUwxsR)rg*-qbl0PpaZwJ&L^F;_4~a(9WBo}y2=)A1S&k8 zL!fo(rw_R);X&e3iABoq)h-Dfs(oWre`%c}; zm$!=X51Mcn`S^4~BlAcgXaU3sJ?c6Q=->t6CZnMkp};}wG>XRV;fKjaU*n*P3cL3` zl3R;a?i)L7Li@-*RIcN^0l~vpZR)#Accq+(7IQ;1a~)h?4`@$N6qDxM;?9c;AAb8; zvYo8Amz3Fzqv56z<>xnm)GHFkbrn(IA^dPXurrKN-c?xU}hJ6?h+vaeZyk-*Hlk@OIiraemK5}b+HynVBXQbx6)y@ zSQygrNb~4`S@X8sqA}+R#o&ParQ$kI`CJ-O+a#)2+|7$iKkI8XJsE6X@=oPlcqOuN z;?mg+cKE|aD;b|Q26np6xVM;Pw+WE%(u=v0O!)Z?5bMAa4hQ#Ph}2#%xff@XyLMxv z@waEkdxI$PdMQ4e(Yf4tRZcAdnwXfB-44r;8$20yc_AgYcD+;;^`hxHN;0|UB9~_E zu2nD{d<=g5%)q`DFQ1aelBsj{sGk-UQJm_Yr&Cqg-v+-xZyoMyd$)OKL(Y9R@?ACe zL-YXXu@yWVmD>;7V%hvE+0)E5cZ^PGU_Nu|a2M~G{l~l%H~h#V91Cm=nG6~U<#-OQ zXnfR*7enJw?K8u1U!0G`R)f-tpF?K3x{vu2NA&Ys?@7b1w5Gf(m=H5iwzKNGT88e6 zqh8O8q>30$e#-dPGOtf7ZLcsxhB$k;-&S2gix7FZ$EF#TN?lDM##;aNLF7yrtmekX zaZh;IZmHN85z-%!*mKzE>k|ous}XQJA&^+=18U6JrdnK@mT&^!d5zZA2Lu*lChhET z_o%XU%QizVl|FeZ^MQ$M#l&o{mbF;AGOxq3<&(PcEzP;L?gLv6T6V^D!r_kj^4(Vb zv6%PK%Jy{D&z?!MsD4#BED-cj2%aWKN}A)_`IJAGT`-nw5qN%g$f-J49xzpxn>Z2IK|;l#51?*v_U!U%%jUwqoAOb!eWl;=E(sZl)xUr!dyCEsm8^Ari97Q2J(osj zpn6|mkCx3|pexw}vjMigy30IK-Acdsv|>|RPtMT{Ac4@{J>5+Nd2E+Msimk6eGv#3f=M(blIXsJ#O*Yu!TwR`sc6w5V?%Hv<+cpt$>->mKa19tL$7esxGzFFTroX ziEMud{8N(OzTb7tCc)gREL~%Mh_5Z~HCj*v%F0ui;U~_2_1;_lRuKO^qjg1kWZ!&)Z1t1;(4va2c_*7F86* zZ(9#CGN)48_M}e`a{t6|X@(K>!0KN0#};{o%f6L0}+(xi?P2Te>P{gwP@~9aeyuwCH!oP@OifswCHL~&4 z16sM6QO0km3QHGo z;Dhz(zFB*v=bd#EBWOT71}uu7fj5c}e@r zTw%C}*Pj6dxDEQ;?8gBx9=5klLdPvbAKKMJL=kUHmSnQx6X|%m09+@ru?@~Q8WBYm zNBfgJCDxORijtIKVO3ILxzYaZ5PeB1Cu~Eb`U8Lc=Ygp69y!;F{arfB#L3)htQXDD zbJaQNYL-W=`Bl&cUt1Z~yY+9sGYTHJ?~leb9EX}u(%m*UwQsUU=e)J=WAIkE^>sxO7a}V6@Hcf!b?@AFw>;nCcSfq)}~}BQ91{A zJfS&@7M7A#=W}?i|3yiO%$#nY@TSSAU|YO74amUx4W4LeWzDfefNsv)-WpRCNE`+ zeZX^l=?dq?Dtsd4eR10DaaNyavONI<^0#u_=gw75s{iLva44+}Puf~}XOdya8Bv|u zOxtVg(Zx@qDA1#+oPR!>N0w~%Y2{2%;bzJV`>TM-lzc`U3)3d!8!NE+XPVK zegUiUfapfvG*0E)uqHJM<$Ie9o3VitrEXlVsc;P!t1f_9P-x~hg^m{~Wc#a^jpGWJ zQQR`+mbi4N?Dj?3ES!Z{(suFfQ}J3iSC86~+VN5)R(vL_i+P?*vQ1We7h_1?=0aB!+8ZylfdtYy_dg)S#5K{ugg)u($icK2pz4Bals4m{f{ zFB^pB1z&EUYT`fMj@(qYWW32oo!hd6+VGlfrro^5C3n5u0E*T*vn?B!6WPUDw>k%} zuOY93C(>Rf(S3Z2JS~p5{(yd!e1w zksd3OsqwOxzcixXSy_l!&6VA7M7uHh~#+S<1=GAd^7ib3DDoZqBVTvHS0&9j~k@#Cy1BN6OHOqj9>wlHU~3R z<~at#5mtUGzJWv6W5XK~b9Whc40^31UQdw(t#eNOF@V`W_}e~go1myr3A@T+kVjoY=tjQ&_GFELeJRRw zf5uMdSGDqa9sw#eJnnd^VWbkiRho*qXj!;6iq3t4+!R~3KRqgf>n6g&p1G8uzq}dF zAetY&>VD1Vhu>TG)j8R;mggD;?O~GHM*Z3IVQ`1x%VTDhH54EsQNoDd-6(<31&6*5 zfGWNS;bZd1JX$i%3eKH`_#z(3*vpC+? zg&wDGJ-;M=0C~?M&_G?FFu*4SDu+~pIYrmNSY%6LNYm@ML8b}+#5Em^K85| z-?S8&=+RP7&AJh0UVDTVcuzisr_hb# z(%Da}LTqOGs_of?6%5r$XU6gTo$oNGD=ha#tMf%+r0B)>&lCm%{R=>I^OhBw?eoyX z1*in-P7uRUdV>gZl3>QMZ+wZ%;N)}QJ@JhJ>CEh4_N%Z8-3Ig%SWjXYpLu*h?l8|m z{q*yRJhY6fK=1H6{N8>JGCy_2?@s0BaSprqs{JO$>i{{EMJ&Scts>#nn)eXB;}&re zwgW$&?4Pm=9e);j7;sdf+PW{+CxS?M+dx=rV{-gzy^iI?=Y#KNa(Cf7R6#~mEUL7f z!{{8;IaH<8)NiJtY2`tary=#&!8u{DH*gOdH=czYV}?6{;tY@wAS@-pV%2>B)_Z6ewXJKMnRV-iDR{C+TPv3!x)v*ftX%tu} z=&2^L7-ibDzAHhSD%!ETz!o2U396YA=;c&!%eW!-;JRl1Lz80HEkz<-XizVF|DeAA z`^xeBY0AVs!GqEY<&#M>IDBIc+F)gmahusa8a;g}kbi1MkIHx^*>kYn-T$KPO{$G7 z+wp9wT{)K|HT;0O$YTl4-8Z-wF0KTh_$Kj;KI`~6GcH^8iA~a#SW7MgWVP*E)vYas zC9Yv5n=3ijdEX{Xa|}{Ta;?&ZmDR$cT8emDq}!Mz?!WF(ydojA!%$B+dJ!w6KIfPs z&LR@|v2$;+(C-;;G1FZh@%n?d_&7)3IIxbbs06UFHsMp@8e;NUO;21z$HuK*v~^fl zEa0fs6~KzK<8p46hXwViuLn(R_)MDXLPy8>O5Gl#Bw|nQGg!l*Loe3mfA)zXxs`cvsqz8gbv{KN?S@w#7OD5s%4|35n1g{u&I@WmJ2%vGqjgi%&AUXuFT+=eJkXlutwR zX`EZ5S&U>~ztfH}f9&PVWUj^-)HBGrZL&c5k#&>w^~QU-CAwE!@6VY=sx|eE@{9LN ztjeJ8oHQszyZbylTI;u&usGf7E=b2j?eES+wn2deV4Ad&hboIA43GORY<|l(piXuk zX%gw%xS4w53`IR$zq(XGRu5O{7+38rygnsX1LIV0ycJpwzqR$n*z>T-(6dmSY=*Dx z-cv;TJkdk`A;*5s9sY-nG~e=ZN%fnG>QBhihRaufZ2jQ7LFcw}@Im<$l0+xIH7093A03uFZ#ByJ2n3mJ zOd?~2-_;r-pSXtT7i_S5G4^%5iN{oZ{HlELjbr(vH?TSn`~t?d)rWrSf&!XqlhzQU zPWPo4t&5KxV$JIP1=`-(dm<3{g7ue)!~is#v9&VzA#WP`61hK(mMXYzWec54rPW8S z#3NsJH(A4SKc--3k=btr|JLEBFY{)&cwhUKqKe}4@H#DYPmHO}kCn7QPQG)wgA-SZ z!^}ofxV5y@7)OQ6@C{0aYfL_6PjWw!&2J04Y`yCW`}m1Ol{Me{B0K#q!BU9yMGp85 zyU)BatVgbV^84ZV>dtZm{QF6zQL@YAh5Xdi%-%VnB8-iDUuu$i$}T&546pmit{otE z^U<&b5qfsdcF7sO8dqX{T##$;HkZJx4mG^)e_MeRd*8vW zT41c3FZ{iaRVIyQ;YEMI@&m3XwI(-;4^BwKZd-UQv)(1U%sJZHP{2y0E8ap#&+DJ= zEjKwKvbEUSpm1-7lKsJ(sA;#B-oA!6$1{}sChty=HYW|=j!s?G+ZVaRS1+$vz-RU` zy(1ibju=cPH6=*}#|vN>I~E{x`HunibbnCdBiM6y=?OBEHv`sj?A|zsV9i1KuLxz| z8_&DC2s^dCOQ`xe6uW}q%Cg|Xbhr3&y9eq460UKw8YVH0cZ3&#AQ%1<^? zo0T}II>>m&OFYVHbk9@jfCa;+9&2{^vzaVTj=|k@K>?v6N3W6MI$^oA-Oe~eUdE-F zf3sn~z@GH6(*(tVt-YtNd%rm9u7TU6qn!Ifm{2U^uSS9{Sh&b1$BU(t}S^SU=iIO_52~*3H*~Mu@c*b}Bk5?#uG;i9QtOoM`h#ROrW0 zBj`Hba^#$N=4i}s0u=qq9Rm)2$!IT?6*{IATfFyzw~iBT8t<<+Fg4o@mQ{+C)=w;l zZyIt(z&qb~QyicQQsbU6lO3#NmUwQ}Wx_N1yq+AsT9I&@m`0H48QJl>B$m+;V{SxN%RQffv&`+F4Hs{J3 zy|ZDJz)WW)9|e_*OeP<>i^xkc_-pbjV%+#0G6w+#FduK-M4wEdj=gW$Q*Wal)3#oD zvtVfqwGCrGKq_q_$FkkGF4yR*Rw;$%k!{6oH6PWNd*k}{VbO+n@nla&6Di4h5?b16r`l3 zrABgqA@&aHclWo??(^)kyY~-eZk~JIbI*I?bIvPb37YeICt%3de&)qw$_vPy%gw?%px`A33h>GJ(aCpZ==?!978}{Xx!M{A z3G8^z3_J*@Ilt!Ci)tw}T7iS>Q(gPspbBfGlD&{q)H)N!uG?^hw%5X*FbC_G`w{kk zpc*ku8Zzwn0N>u9gXK#19XLvm@!fRPj5E!3quGlG(P7QbZi@Q0El#l_?~?SltArEk zcQC&&ZW{Znt^1r!GWLm`ha00by{UM&Kxvp~^5VhZ4jHuLqUfkEHYO#&ZOI!boZZgn z&AE~8PhkUpc9JFjln`|7)LkPFQn;uu7TVZ#;$J&)okw3bUV?4tXUY$W8XO__2|4K9+M`tc0 zStQ?ee?zJb)m}ofYBG?Gp_{7lvO~4yzDAi?9&08r83Dae)$QkzMk?L zZn-enqULKtaR~m-n@!ZRFzU@1AW|?q_HnE%+FMCy9RH2-zi$&@Kg-Gc;nP^K-EC?# zfj-Sy{?I~0@1>FZfybC#e%W9`AKy!b!CDsW3cZH&-u=}`Hjlx6-u9o5B0JlF>6gkh zO{_HN#Zax^bX|6XMtHq#(xtEQiUi1ivtFFB7 zGvz8|oIEX|TJ9*(O2-Aka@677sW5qT>*rx5x|@{EZp@>1DK1otVlm1-_WAa)50lD? zw~_B2;ES6@dMqP+&kkS7)+JvxCQ~;wdv)tVEFDn6rf4=WU<$G`d~|6WPfU2!7DRvS zSJrR!0NWIJ~h@>i^d^Yvx804#T2BawhBHxe4lYkD`N}2*ok@hbneky4FvEN$ot!2 z)Ebm+XytTl_iEGW6JZ_NCan}cL4KBeX!bqesyzmpZYm>TEch(G?~6Q$atno8V?$1A z64$}t!nfvX*9mgi(<_{rW2pW47ZXnw{ohx7uqQCEUwia5-;s*n7IM@}o0GZP*0AW(@LNunV??*Zl?6$s87zAWH*m$C^9VLc=LA(G@6XR1+T~nM z<_e|!i2a8J81o&iI^dq(I{uJzQqUjc{S#|wrb47;;mzj@?Cdwg>{VQSSDv6@xANsF zx^niuRJ+@sltwT8$Bh?nBT_ocg#ahg@b=l2$@)yd^~}!E*iBZ>RSjhuJV(UM!uzO( ztto&96y=@A?rJabDG=N|Z+Lu7>NIDB+ACE*;Jp)!!aG`2% zz6twD8F4Q&xO5`$zH!IK?Nr&}Msvhd>*Cky8IQ{6bS+oslzr8nd5KgAxtyA7Pti4~ ztH6we(^lvG;RVVb^Pg{HQp$cR4|^3H`yd9)vlG6!(MxD-VB~k$yJ6_LSU5NW7*7R$ zXLUIp=Y>#uHgDN#qG)=}ZsGHl_uzSVfh>k_qvh<`IIJ(IB=ROxT`aPB8 zq?Tl8?|#2#I1W>A)}vhsT}H|Fpn<{Qfz)a9jn8t|%T&MWnrEK6xrO;vY{UIL{aDN7 zSHx*C?0*oc_7c>KD03ITxteRV?Tvo+&J4ADa`M+tN6%=(AKMx7viJKjsQCqXpLcgL znwIUJ-!!#*r_>04V7nOVY`?ylhm?=Zk55C(fS_T|)f~5W3Pbxz;aXp+! zI!z*CDu#Wj;`cV&YdTe2SXq6QpH}x3^oR0M_;{gJ7P}Z}#n^41&*N104PZx#HFX^H ztSxKaMnR5Cm6tn3c-ByWoKs3S5vLV_Ig2P4gzZ9KQ1WLfQ5Mk)DIa{*r9M7Z?{Td? z@gRl6eTHXWnkojHKg;i=-gjY@C7IyyR<94mArqnm8uy_MyeOnJR64QGx5>|SW|S`6gc zTxRxLDDS9(ZA$qbSj)Zya|qW#7Vo3)`4o6q^ztA~1*3RRlIF9x;yuS>4y31|TA zS^gZGHsx3VwiVA*Nk3BQrjk!cvtGmHiO3`rSfkAN7f9()PX$%FNMwdGnU-DnT3h-Y z?@s3|z`QRjA8pyg$e`ty(2Sy>YmvTG(9PEOY>zALD#%V7h4;re>>oT9sFZ%c$6uz*xPUfv=y7etYYema%Bd^K7@7(#JGBRG+v$eix?ry z%17QlutiU{12@3<5CSwMVSxX7ymT^b$+ybu3Sp(XK-|wl6?6j08VP#`FTZ@0OJ?nR zOgAtSamh0mf#ibeXF#v#dTROYhKyreUA1_Sk$Z@PLtR}D;uy)1ld3vOEmzL%YEC=4 z7A|s`k4^HrTWh9fP!aqK%fAeId@@?H3!b>H>-&UvhIyKk^?PEusVeKl>FjI=ru}Mz z@yvw~=047E^+W6!voAGcr}G%_=^ZZ+o+SVZqhO5G%qY9~LB5Cx?6<7OiC=E69(6*_ z1%IG;OdGz^9raUdscnpZGX>W>vPCT6O~DR#)ZWG7z22aEmj*8+onRJi0(87M@MG1pQ3)HQa!Sv3mZnYM;p=UdQcK|W;hFz}!+w@z4 z6V2|M*(W((;Oi~elHUjQF!G}UhDF`$gPP9k`7;+%yyV1OJi-!>>j?A$8}RbSz{8vzpZaZb+N-Xo@IzJmnP4I1cwSit^6$Z1p4t zkLR9Iy}GJfxH+|6kBtFf&P&gZ!s9cDp?q7fTUdF~lUnEZ^j_Rs9-3RPnikHJYUd4Y z(-l#)^W(O z&l%fQ_i`HAFS4t-BS`19eUzxZMupT819*!@!Z@hi(=fVT47H%l#c22{LKVGiKL zCRa6p%kX%LF*W9g@*1vN8q>5}YvD{sJ@m8jpr)-+qee=mq5?OJk-Ee_TsMkSe!xXO zrG(Gk;uV~aMvnRJf0epf$l>a1UnV+M+}N7h6^Uj~ApvFIej7g76fCZEB7-BoSr`#W z*gT3KI4MQSdhql+v$n9uCbQ9@NST{Api14YxBA<+gKwMIwK)a*4gg=!bxJ)O2->AY zw+}EQ?|b_%r5lgU z8KjgZQJ1>-*DLvRXQO4pV3EH9h|v=#`gaPhhvLeId{^{wtL?Rgh~y)jsB75 z{4FBUJ%2tZHCQ-|TU`VHK=agg-chaTiMiI^EE;CLx?Up`-%jnJ#H#CN;&F9sz-(^l z8CYq__j@#D4ZR_KEJY@y;;N0;;C$st_U|BgG!4;R(vECNaN#(LUF6fP-*>bvSQdPQ z^48iLxcDXm6i~>Hb%=iZdy_Z2!Ha^s#VUM!#@4OD)1!a#IH%518{8>!@kGQwN7s6Z ze#C8A2MRxW@dIU=NIe@!ksG~DY1`|FNq@iR4Q(?M5u*|EL*Ze*q5BOX++u%izGZnN zH*)M3kI>8SE#K>&ubDToKZ64;1iIe#JmOtYaa?GwS!i|vd0%X#pno0q;obvcD#VVj zX^Db@3>$2xR&Q8aw`riz09f1dKIxaq$;s8RxN7H*W?*C|MF}jZNG2D12FITcP`lL^PGTk%KSE~ans$w7$Rxr55m-!Ce&fgD_Xo4?*|h^PE+ZS! zGtR!$hQ3r@5)dnUU;7WDx+S9Hx^7c!&OWl^!3MkK03dg3ymVd2`0FZQl8Docz!kLY z7BwIjj#MTjf;J)K(yDw_?n=JJh zeReiUW7NH|aT#W2aUP9TlKik0QDkOIMFtF=Uv+Go;9~AE@jLMW&?Uyn(jfxCV!z6CB@l0*Vlj{i3JADHrUPFv4^Uz zLH#KZ&ko_)unWFM6Oon*J9_D&ThA`%M&5rTBqoh7wxhPwMgOe<%z{~yjbuAO9ZQ5) z7Dr3+PL_j-p}UJ9{@wG1$z~GC0kuWlM^edY%4v_ z7SGx2+(p|eImuK7=Xc)%@UVVTju>jt^8mvkD5xsM;{dXA*jIZRRpdGG<*~!)PBUtG z6^cguEO>_H4RNQ&+jJI_d7N#mHEob``g|MnM?`tqOM4)%Mu3#e*tFzhm-EtZrfq5C z>4%)Fbq2Wq?KLzYAP>(S7$09is$u)bw3HWNmu<_pH?SdmW-XSR1DoQkX_y-wOQ3>y zH><|IgG27s6535hOP0tO!x(6y83i@nK>2ornEs|FilS~Fln-5A4lS%&K#ano8pCz? zCjkh+*u1)Un(0=S`#@*o%JA@UZc_h*fepMq{veG9tz4N(t|h+&MJqDq4=-VArlx3TQLdyzq7EEjcbS702PmH+ZOvBH0O^U#*9V?(f9P?}tzV1nRB7)_!;Db|odPv&P)}eg!pG}~G%`~5OHyW$jLPJgNJcjPR73=0G4 z8fkKO4e!k~t7KAMN0u>>+p8lW?`eA#qao|liw{I|m8|R7GG5Ef)+UafCaNnjvbF8i zx*l(iY#^e%N4jG^TFYaFS31C0M!hfvHEN>j9o23pZk(4sJ=Rzcr(smH=4QqttX0{! zzMn&saLn?BIo^#H*^i@@*rggI@g$eMnJKlwZY#nQqeO_njI%Mw@~rfT{dG_gOK=K} z2nSlZ26(SLn;>Ng-O(;;*3Xtzyz7~yC&nS#c#duhwvrhq*a1^;IZK#flM^Vaq6!7F zlW2h!a`cZ7?-G*vrejp-SV7hG0E*YhGIRtCh}CcJY}Y}?jrZ8Xv$N=U1frHeMzZ>R z1EN0MYtO7v-@t70YARTbz&vbN*6`i_Txe3poHq5&oU*NWZlMcTfsT1dc_zBvNH87G z{%$rC3O90DRzSXu);>%O)t*z@!^hDGuw2RKHLufLZ)&jF)XcEB>Wj=E7AKVp8#sq- z8g^s72Oyg(8|(msN7ZIHAO)1dPc$o202LE6;j4J|X2dEC6qRHN$ zgpl9CkRttwU4$xdf?QZ?Ivcr9X@Wa%e<=qD$R+}*>nt$5Wj#SY;Vj?fc!W2lOsm2* zyNns?0k}&*A*8DQ?T^zLW8-Vy!1vie(6L<0lpDV_D^Wg;3AJ8GBW1x=mR(yid(Mw3 z|ARMutj`v1eYv4vXsV@U!tdw^H5?}9-D6fsrk?c$f!GSRUBarY>W2Y`Fp@_NL13Ag zXZ7J}>cY=5J2f8dv^t*65{|m$?g$V_pVk~N;r6S{PkS2te8p+CPs{h1ssW|p1#$*_ zMFOuGayUc3XQ*EpLC^`jE^y-uKSB{56$lYMvif{j@ng>RP04GeMhc}1KH>9nI`aQ zsTtB9{}O<|bTo#gdY9#6>g-Fv z1?p^&qwwL+*Lzo*7%-JdxP^^~H*{X(Ewk)wCLDN4s*t}Dx?6;+fq*idq1E7NCE!#% znAP=6F7uVGhh*vrYQ>zF59P;&s8qUz{ra@@i7X~@MNbn%|GwbZ9%EJldGeFqX&B#3 zCKfDDN0Ct@J0>(UQeF7|dQM1Y8?=?X)SdR=38|ug+mch?C&l@9rOQbeVT-5Wpg|Qz z0a^(Yo`c+;P${(yui?|URdul?X#i<6kz9=d%u}-|e@7wY#j}(rUm(*+@OMO*^OmfI z?6nIn`^rPLZ)DpGL0{#o#rx+zg_VEJPo=+gH-RwKQ;JXWvrxmsuLUqlBqUL$V0O!5c1u4X`H3{U^&eE& z8CovD%20k39uM8hVy1Cj#p1AnH;1}U_@j4tLW$&T2G$mZk4VCfjXp$xlTv`$&}S;( zYfBw|X`s7IjWDJjyu$>U**q{r?&T{EY6u>;B-I1r=C5f(K%L5fx%Qf%@o703^7t(h zJ=d5C=bAu{r*Ak>JmBQCnwdMB3fN5M?48DB!*UflhA`pGOa(zf$8v(p??$n=2{%J9=y;R1VqI$=kIzdiOT;EEYqUjOFa=BF&Ok9z2{w+*huFy z-2trhEb9<9rh}X@ry@fxz0UnDS)5IZiK}a&H%u+Y|+CfdK>_*gD z%ji8+MET2b2u4`&{$*_BpG6gjP`(g+7z)4pTHh*n%ZbsS>O`!#YBdyUt(TzCiS=2d z@Vw#o3)Xo{!v5G7?u9(ROuR(lR_8qtQ?gL14?4MT;)p!IZ6*S#XVWj|F2lrkA*`n} zEYnjc$y4}xleEdKNzuA0XqM5}yWlByT$5D;|Aa$v<0a_@AY3=zss>+Pto<-raJ2LR z3ZH=w7RdzTf8Z8SV`gF7xIi?}BH}5TZ4_OkiFC>I?ec6~C%sdpgOJ%oorJ@?F}C%7 z`eezOseD;S-FLTWOD)`Et)B^j>0z!T4xgT8Yp2_^^i&8aBTsOgFT9#6mNTm6Q>Omp zM!C%8c&K+CQ)2%68TT9c3Noj)pKsao6dqG_t}S^&JR5v}10mZ-{|mCxgYZikU?qWy z6$9TA-F-)w4|(VKgrWz#E_od+YtX}lwkx`C$Z zb=X7u9x@h;*@D4dfycz9ykBa~ow-M)65rEI8`R)q&HqaJ?tnSF)3A`N1quDvHSQR)ToeSc%e5p_N@QKzBW*@^}v zD$ulciAbFU~L3ERqqbL7A!}gN$Ra?7j`1^&6o~`x)l^d(l(P-OE;; z<>IrXC~bAJYgxlH*W5L2v~pTy{vvN}!|uLpV3iS3R&`6+ZmDb42MCduA4sakA|LsX zF-P}cHjzp0)C*T^2%q~~TpLBrGcN51mG)01HP*S~w#-V>RR+Qr`JKX@D!esMTwcw5 zCJ0UPvmKD#xQt9|dJBs#v#pPIgJDWAy3$%KMIWv+2%i0laK>nX25kpt&4IVXyq&4# zJjc#CQSE(V-pFygM+I^t93`YL7K1E6>FGZXNhMVC7w|N=XsT{*GPXKn&VJkYjbB2( z*z1CdvD|l9D5PjNeC$D6`GZ`Ye`1EHY5037W5XawTe3Eqwy~SmB&l>9HN5zhv$F%O z;VvLddT7PU?*!1w^L^^_h>=YerE#Zv5{6HCQS8|IUJo8VipG`=4|JAsJB7SPIHfP| zOfjIw#r~3R4*)KVLe>?eJHr5-Vlb;@vx2Yl)+#Fdbgq2jjXZJgTaDzvXhS^82@Hi# zv-1hG;D)u!o;*5;B=YX|bEuvaG(tqBfA^OWWer=vdWid2u#7UMw!f10o0>{j&J$gZd~iY=5i?V8jOcN`uH{ z7nMr`@sqRB9WvU2W{Fe2-@$1; z*5W<@!22ZHlCB_M##>$!L2d}u>s1?A+p_@XIa?ZwC<9Bu{t+QiMZK%IiD@PQ5}9k0=8L$pVn0btP6JO2V8E#vEWUIw!%Cz`zmzJ=r{o*5|p z5M7hoOx)UsiLMtT5Cb_w4O54q05>(UCY@>YGM3a>kue-fgv>nRd3$s6P@}Be>E6cJbKdCFWOw!=#E#(u;Z_P2`IfONeyG&f+B!I@r(Z@X`Rh?*J zHQjHKj|OU$kuWR<@9NEvYD)rIl;f*QP^PCezB1suCgYIiTXpmei1xRjv;7*wukJfP z=5jEd)fa%2`N!CxXr*}1c|?0d!<~GM$7z^>5PYFUL6;hH^(#28tM!L;8RE51sAmgR zJKR{!=zA?G2l?M3G}r~ca0W2Wdz~e{VQVhy+=!SoK(-PNzucRNP3`bgrG}P^RX!l|2XkH0G|2$uA>tJ5l-n{{fTjYn^i~a5B zoBb=*hlOX}}o#$#QR?>$Ms zD9})s!>a`d8XZ$`deCvRoL#!h z7MoIN9@R=pdvE`M$VNt|)Z}k9&0`ibI(ucQh8HGT^JUR-C}?r=U2_Rvo0QvpINOL- z3lgz#tJSm|DhK2$itIJoJglarF?IS~u2Hn&OfXlOG`sqi^hqGktmpXDn60Lc8v!R5 zTPYJR=R3y0%khM{=h^emeHX(mLJi^roWMZES@ z44FfIIQ9{NsNSS#)WfG^B0Bnuz*$sCvJ$T~#JU=5%JoLS+iRh;)vBvYB-f=+B#GOO z0rlhT)DhIOG?yg$8#276569v0qnw+~OKyizUy_VTXnqz-nH|d|hI(F|8F)!F8z7S! zIXLEPs)b4f@_}9^`igKnO6H|Uwlx}PPKy)+Wf6cI4NMbvTZlqfP!g*BCyjxp)fsAy zWf+9jj_mXs&6eD>5pe@^!UY$eviT1&i47HPhK2x>Y^_qk=)%t(c~<~iu{D_M;{$%r zWwN>X4Gf8y(}$xg(SA@arjiW&>d#OS+0E8XWt)Yhg#iVJ!Q_NsQfH`OqipGF#o0_; zbv%sYF9mz(C$h5*y*}_Q!27V$ZGNsoll%cIsVpNZzU-+XeE6%2gPpFsjAMn>FNih; z53IPKq?s0#WPX27^Scy=PDlC-qX}n$zT3kQIkjbflKDrXRF{3Z8Zqc!O_mp|v;%zC zb0<+*z&Gm;4n97=Fa5aYtgFZYS)cMf5OjedGzwyaBLl{~&b3u7hN684KDCxw%3Hg& z${u+I?xG{Os&3RfoJax=F>dGli0PI$BD!LIyzeHCJhx~|sky_q1+g6-o7Hii!lwh4 z=jVf2yi?+ss_c%7c|7F8S+^-Wvja%jOezK)#$G*-e&v2c21R#5^O1X8W4H7HwVeAt z!X`v)U9SV5nf5-I#Yjs@A1c92RH3HDSjZam&(FAL;r?Fv(+-x6_4?rLiL79F=Eh#5 zsNpB6#Ed+~dHKU9OTNmHY%*VeCQ6tO_}6bo>Zs9D863S_hqy`UN@Od89=}4-7x37* z;?HA2$s-~|(Q5IWa=Io}j}S{*+%gn!ww5^IK228C~aytqhVhHM=2Rt4k) z?LYmhxC9WOmd1gF*T{t>cIQaoRnwQ#nSg@cC2yp+k_oT|G>XK!IW@Hj&=;%Yh<|2B zQOh=WRDZ5W(^AU}>NALM%`_Zbj@FGiz%yaQW7}9z&*=Jr!XQ*HvcS#&k}95+fh;Pn!L*c;wf0Aul7*p>EbR)<*so`HDK~;>_Grc;)*ET^sxU# zi_j{3fhF>@yGlYghWs35`;O~hA9eYxO2VP>YryA|hWi)`ZtYQ=k^=O@eNG>#hGYHO z?HNY-3wHTq@KnxdO9I^+Wk`Q!8_hlOGKTlGtOlP@rrB>RpGU;OVzh;&FvjDyAC5tN z3oEepk;NJV@i)7OiQ|d#S0dgw?{^S!S^F8H>S$nu%wTS zU>y;TIK{M5iNzzq)!RH?`5;!1aSSIg{vq2@|)6ZUbFV z{aRa(s=c(g)l<pzBuRLlw|MJYMC6r-qV`MUk>_ zAIA7>yTwKa-j%=8fwEoHhCk9OVfBbkxWOSg_sx<407;d*qbB1YHBz?2_~J^OFI3-2 z4k+ikx_@9l%-SKP;V^qR!C!!p_U)mj6(M|Ja6VH=wFFtcATX)y_FZ~WA?B@so+L=_ zjnod_8!JwZeqr*Y53i3V!Pvpt0sVd8b;vU5R_^+bkoMb6Abi^|)QV=W_qdu<&D`Tx z?mIeDm`7U~<<0>g&qgyKDm8EZIGt1(WSC_SW&;m4^|CFzPHj57N_*tRUwWJ8(S?XH zGvSA)E#k;OaaOWP(eN%?*Yi2E&~}=pVd-wvE+Z62EqCs0P;n=7tqKDv8@xNP-&^O) znQYNtSehDsAr{sL5T!xhzvm=Or9I7E@(YOX2Q!46fa@EP4qN8jm;#9T`&V#QfqaCrEIzsd*3cM zo+Q9TVd+|nL34d>mCGHo6|T1f*Vtm!=xR-dNIw`)wOwf}O<^WG-U8oVlm-^^e`}PD zR<#xi)C)UOA#IkIl0K$&# zr`e^(*}brhj`_2reTX;a(&}V}w}U|r0?<(ZIa4>GdN&lv*#VT?BPzr57=*!fQM49# z{1`hXQJ+g$YW4IHoJ7CI$pc`sid`)nLZH|(?l%u`(<_yZs7Ern;`cJ(jp_W1Fo(E- zhW@3zKr8`D_k(*!)~{B1ca{kAo@Zh^BpyHV`9@1&>>aq$Mp23JP*H5F$5J9Hz-qw1 zO7K^Zjed6m8ekK^6Hfb5v-(nB^rdcFhyoNLRd)fgiKk4a^wb1QF0L6mvc!q(9P0DR zVfF6zq%X{a`jkq$-tagLzH$uurto#VJpZe4PMkV3W3cUOSPRxM(R0`Rnx^L~@A`fY z(2|b{I2Hs(M}t&iGM@fi5x`zPl?gW;2p{=|v&wY|_|;gKh=xjPV5#MXjZ&8QM+gFd zO+2saxKX@&69cH)ej|En9!%G*hH5{^gTIfVBY$52HB%lnSVQzI+R&~jsVz#-$rrjH zpOjeRRw>(DAf*+540%IgmR$k!Bb!Hi#pkT48hY?0)eE^yB8HB;hC?a`eYs;L(loBp zO6j}6qhD_Q*~&I;wo6X0V0%ogMeS?q+J1uW6bQW!Y$jnrGCGXdZ;UNI$q^?4t!5(R zf6%7fljnG8f$39@;i`#piB@V9PU`xIT)y>sMjkIL9#7+gGd0ajc2nvsfWLE=v7h17 zaG~&8_Tf-@4+S$~&$uu4u34IYvfcXWdt2D|vY^k>dKUT?5&t@Mv2ktnzg;{@*JhKO zJ($ffO5%9@hY%zpL_B7|3{mlYsGKTwu=AKR6 zk(y$*G9}$UL+J{!ht{_Am;M9KmJnl(i~BLGTFI4 zFOrj!wM0SllCH<%>)WBPh4kOlD$PCyF77{}#B&%Bl?`g$>`GcBp?#M*RQevZG(X~{ zJN);~kM+Hk2dX#dMr}lgMBWrjE;5>1>}$@fpJRmxeIKj$xY&l2wsr>EZ4bZHIw;JX zeTcasaF#WOnCX@(Z#&j5{q7ouB0ixYp#DV-p1{UkGe;ff+stz2sS+!88!(+E#$wN}C>Lzav)MTc5BJpufR@j`lN!|=ihP#4TBjHLh z&O}ZU^sT`z>?UvTz_V1zDgBHe*bVunPwR-M5sMDL- zR(?|X)!}s;N~Zl`nH>gUccpC)I#$%{c8W5v7IvUVULjy5)&T3C7MKHYe3fN z@?SuirhN>O#jjR7a9bv`{!#Yg)wvEyDD8vKj3ygGb3XWdcfkXp-?HK3ez|)1Mx$ESKo?#;Gs9-qffE%H+XA3Th+rNPk~xhmkdDD!95+b7iAT|@FvlXOCgyK+z?glC z`C_vRkREM@i^Rd7$&J`{K~wf7PqXNmMog;%8&YB!)&n=WJn%+EZ23FuJLE5AWs*=p_`MWPK%5+;>poFB*t(010O)9Lkn$v}Y9YjacE(j_@YLMc>pPNcYIt~l> zY^UmPl&CUYH(Kc3`-C?cirl zYfu*yihZf(WsGmNa|Dl=y!s(9alKY%sYzlL9ZEJP6}^gae&sAAf*sK6du5S{O zEngWp(-@l+9(~^qehb>{r~%8U zKXaLvxyW?eH#=75u1Cjsi?TEy#^?dJN8W9@buDP~du$VbKYQbL=her^-FmWl4P@pR zTB--1FYPI0i8%o)?eRY=ZR^oAWLvqV5tq(H;(pm&nvfdmS!A3Fb%9KeJu)dxyeo$W zvn%uEX5oX;R2DVakHNg%wPt)bcCI%wtDox48R4eU_&O#G`Q7cL4>ud1vLy*s9VF&PD->Zju5mf>i zdW~v0v94rl`uhw$G=@D|U7+FVw@t2YTMZG0H~A^3I<_}Xa>b)hak1}$kNhV#62LRJYAMk&X8FsWgKRz7!#I$Ku#7iHp%m|@k@1IUH z-6s!qzD5yg0%~;|PFva;Jof&Of>fPDZTY40#k97_G5|WrW-;N8$NL~@yHDGMtAmBd zr9y+L85RHNt!uisjc$4_p0ZsKzR+3EcG!cOboQc4{yN;0=`a@ley+R+IvpOWFi1me zApbWrL9?b@wrw zor3V;=AFa#5H(l-T9I3AA8WjHQUmEH=cXuH4l7%+s&RSuXej;o`Y8R#)$-(-_E>b_ zxkP^-PS$mj{$@WOa-ISdWp)5iT(i=z^<*Pt$b!&co2+C3o-t8~*#^zRp1{WOnUxY+ zMLl>}?WTw?R?yS?hNCp+35m?6deU4MtJQ@c`9%j|GIfO}5<2E=)b>N@ zISAE0-&N%5Ce|VOPUL`xqf;e>EdpH&=siUAXiNBz=W&=m&&&14Cb36HbTyx&>UT?S z*f1#uzw2ky;@q^SbU6Up=Y2IDDe1yr4>;jCAj3@mU}~qZgy-*mg-p{B4O4IsY1@2q zKq-As1bV@o!~c1l82>RQ8CPugDHgD-reJ>-P?_z4MZ7q^1$+eGo|XBsA$VXNkLaiH zB+G0R4|x&zvmAhL(l~eyHo88k;7g;^Py@n*^4*e~Hk+H(2<~V4I^N?2O_as2go6;O7# zDFi%r&U}GL{mf__c^5&%iY3t7JKnr&dVKZ8d}KExzNZqSkO(z8$J zn6ibEe~5bzIYfg$dpf8>5}DNb0Acx?7dPvDsOp%ZShfO|3GSj|w5zK5`4|zHvF2Zjz;p_>9Q}U=Ap5EOcwr69Cm;G z^(Dug|u7jM(Tpesji2t4nbGX^JspX-teOr-GOqL+103#^ufzIo^K1rg2 ze0!5xqowBw5(Z1Mh$} zF^Nh5pe7KxS|@rdgPHQ7SBEqp2I;`b9G{W;IOXTo0|Q@T?{f|xQ)2(bz(fg?(T3P8 zxB+s8+kVv7p%rea#MICIgzHXnPwJ*Qfd?F~;RfV*@n}^uK}f%uzYJ&wsj> zisokoa;NieaGT9Z^XK)2C55T>KaSU74YDJouXN=Ma@zYw8$~;ubC-N{qD5w3kA~<)CixJM+&%2Sgy81VH2NUC zyOgHe(>BL4CUf!XvO_aE14qtJ3q;7dD136L8*=NH6#hO=i*DBYgfyCX`)g{srQc}~ z`Wr<0AprVdM%WsTxZ8N6dw<8aZ_ZUu^li%qr^V@IN+)DHbH@rYbC#!*_C06>=t$@F zWs(8n*~>6A&*m@f{oT%p{3LJ?hUl^Yh`h`;Lgk&I{V@l?3QpepO0pCNo)efqP*#EV zG0&aOkF^hWWan3A_6d!-U?VUGmd*=E^hV6_SewtT^Varq+;#(O}XmeHN^j$)n~3IoHS&5Ha&$D0ll$rP(j|M*Hn&fMMmb&I|}g)gR#q=>f#0kunU+ zXwJ?w{McjJZvN2DseL1p6Xt)>?l!1D>OX?rMBaJU7q?$JQ&IH_9xu0wkfi_-OMUn` zZ;t!r!N7i~$XL5qA%uX!(gER=GQjWw4kB>1z$R`!-)c4@QB0H*H4w4U8%bF8{$`5N zLkVbTFl7g55Ds91cK`}>8{H)}3IzZibAxne0Go)IAeMjtZU+qyVN_H&3F?^zXf@OJ zSA5#L1L#KyU*IxHC;crz{n(cd#m1!&RCPtpd_LRRh*kn0u77as1bU(m>5si?Yrh#Y zI*gu$DV)xEbZ@6LQFNX?8-sE$74v3!&z%ENvf`^t-&;%QUHFh1>d-HLz3|OwdG_Rh zcL|6Q9mSO0Kn)jbcsmAf_>)S2m%83#2Lkn9;`;CKS_DHitIU!ZhXP?nxVl|mHUSWj zqCIWpSuEv}Xr)7lw>=_Y5dEkxl@T=D%x-!568-Kn1b6-~H#j@NjgcN(_eHU*TmFnKo%%xd$12*>j>@3N6y%YykDJon$$)kVa4r^5d1 z2z<+K`L|^3ZpS^)FyLn_{)vtJIi>4!e<~0!*>Da;NT>5kT`la7n11?ur$DV)$K;z& zB*g#WquKBuPSxsyE^j}PknULD-O423KL?1}{}K^L%!y3k&A{LXMCRAUH&T=&O(TI{&^u;uQ5F@YF8!{+`w+SLVgcDftFyFI|006m`(e^%7A{$uUmGXU0F0IX#S5&9!A zvjM4pMlaA_O5v7L6ZD1bj~B3~y~lpNOsIwJ&V{%W`t$#Y9Ebk!glF*KU*oLH;vbtt ztwyTvq|EsvC&0i%~)b$$daQ&0;L<+nPX8+D+I+PppH_Qs$ zV}%OzSI7`|L=1T9e;sh(3X`o6e#E5xXA68E?E>7T_H(zI+G}Qq>{D3X+kC^e{ z{O!Yu*yoJH@n?*xJ30ULEkx|+!${)*#AirF8X-PZ?Em%H#qE3>jIV8|dkLl-JFVwV zSYlT>w})>iSJTNtnQsUKYxu)G3a0Ofp%4@Y~!p}l{X!eDncvQ>+QRAuM)F` zbg%V9y)Ipvr%x60BtJ?iFg4-PVFpvlBtS1fmh^Tb;Doe`iX`t& zb0KJuhEH

O?~Dp4J0cXVSQTU^L}iKWlCVR_S0~^d-uvGBJm2>`U;a3R zPM_1&)z#J2Rds%C@k$l|7bcI4EeB0MmIzm=?(^*rpV8pY$jn54g}~V^2ikrWyLmJ6?T@MVvisMnX|pUD5R5q&(;kRZQV zjRL)~0Bnmx>a6kU;Z{x~KFH(k+<{e!9&Fi)dU3~J!KET#T4SYOgM`*so4PPj!D;IC zAI&qR263Mr`s~};wPoqOq5i2#hIt=5Hj`HB#!&gZ)i;JhzOi|U<6KbbU9}_xpu*|y ztUW-S<7Nzk6rf1xcOTA(yD`*|aHmgXal(hHDyrzx`K(Z75A?NJjnj}>N}m7pFi9Nu z(#5p3AHhs9&bLT#NFyj>*Z^Y2(#9k%9zgr}kC^s6bEQuO zLrM|DtyvBW>_Js5_R=$Ah-HQE?e+qkhPB%C+w-Pn?)DUROu+!WF|}`}zUbr!!s2jN z(kFJZvPbB)IzW#oO&U^xu}Tsb=68T${~;(Nr0)Sl9CdkcKfKSKaLU=OP_u z_Mk4_->_9lIc=$zRcmE($tG9GyEaTj87dUt`dAHb)1SH1Z-8U!X!oDDtxEnZ9hBeTnITcS;2YP z-0|++ABe!zh!fjQ9Zsd3w@u%}N74?y+EwA_VJI62?}*E}#3M*fn*_u9T@M}{>!8e* zWGYkV-^gtD8-tf(6C)xb#%Za9*)HA4hifgP>7vZ}+Du2^cbdZXpqF*8-iGS}sMbEu z-rF=Wr5~+eX0F)YXoxQ&SP;20s=IXnDoGvGgRaam;*yT@C-xb z5QPe>vp!c8O;Yw48i4s+Z-ljsL9pI*n>UzCXmE%1H-FnOnorIjjxQjLJFh3 z{qW0y4J;+bt)V`cI_v^{KvaVLkkTK>cxhfZHRKK4-=6O<(lwTCV4@Bm*QY{ zw-#~FyY|~Mm-^;Lh+s2>r8S-WvgC^jZkIQ)MzGG}ZaPj^1G-Vx9oXTIoeTGWm1X7hRJA7Q7uCW2Gcp#kdMu4n z`G#ZFL?$w+k3?pzeV|-3W#E&&Ce<;eYv9w=_s+t|u5HQXgg8{GA!OvO)_8r1qdN;ZQ}#A~z%n@T*s zuN-chD^xu4)P8SmC+(J;@4KD0K_Xm6m4`NG$5nr=$?lfZSOJ|pd(D>HwC4lp*CS&$0e^HN4X*-TnA}QUC7w@ zX%tY1Q_DP95k0;&qJ4$qU@J2G!I*OM0~{3Im>g|ChYZ?zF#)*@^Di3QRt@er;HS>K zPg|y^xgf8yrl}#9(}1$L!tp8yVDuxJQ>igQ-k~4t2}R1#se&%qfY~wHjpkHtQVrl| zyhD~i?w^3m{dW$IJHqpA%dwjIa^UWM_FMi1kgb+KoZBomUG)o?$h=(B;vjAvmcnN4 zIQ=2m3&6UF72crPss3-ItZw5R99S}{s;a~?-pW!z7oN%unqq_AV#3OBd$QhY_Ibc8 z!-q+p>vBi~NS+Lt%27Zs11OP!!!ipUU?uTfCM)MwhOJ-;UVI}HG?fk5D;j3XuH7%v z#>!Z!8hy5rXC}rzf1ba3=@Ko{YQ9={<;zAH-`8Tq`MX5lC3vNNndsNYN%iTO1P#nw z>}9FXZEp5!@^D+IWG+=s#`R5>g&Qcwj66;$3W(6$h%&RtQ@~Zg`RBft_962sNJGh< zEf^{4%1iVnMY&vlu1>GPOz1yXq{~G0CJp4HHFVigXbmm4 z48{%95;ahZ=0y)IqNPZI^7M|VImL^uwsYpXi$* z%Z0R+lpHm{z%=QxXVE-;k~}Fnbs!JZmpm|lK6+$mom{uFyX9uNmLwx18ukKS_G1TLW^D^hHAOj! zNYzC=Y|nhqyb)!6>~z*PAt6ABizHIuWp7Hi@JH)oU8I3ww0@@26^+=wv)n5rT$a+6 z0*t%9|Av{7=}jXNN6JoA{<0V_Ecjjty`TrBJS}%i_#@mZ`m_MU?t(h?@KqymfCOK6 z+_;pkt^qd`kd`Ci|BY}TF=606e5GE-r>h^J^HQOiMf9Z%r7P4UQ1N$kaf|5FIemP3 zkQ1pAzN$VybcKgElD>8#y*T=0LA1N32qBtbvS?Mf6J^#|(GqaZ9P3XU*@CM2E8f^Y zje6_s>3Ge=IFK?*sRHw|LZ^vQuMnaN4O?Ec zh;CY>{i+L~Vx{@rg9;mDe{<=5CC6YeN={CTJDa)MGs{19jQ;8>`$tRZ~v+n2Gpe$13Q!MgB8@H)>VVx+hCVt_u_oHepnK(?)dinpBLePdai^XfF}Y#zUkjhR#5XFTzj{K5J- ztk@w`W+H%Dvb+SHJ>~|i_My_TtJVI@8Jnlvw5%aw#SWq}v*Tv-n#1)mSCXc4HlTih zMbx^&GiFHD#!!piTjr4yu@hz3hTBo3zM<+#&&sbtZ=^K8L5)4gKY2OC@`dBoHlc5< zv$JRNnz?DxnZWR!C>=k`3?)kb#1bBb^44<@L08XCPhS%|08P1Kta?i?;%}NHk&nH) zh^)Q+c4tF2rW-f_4JDx^3%y$TJE62Hzb9a_GVOTkjy>ry%S3{*Hkb4+`O0@ahce z_kkBJ%b)H$=a69;L!nd;(;uL_0EWmggaOw#41r*D{Ybr64JsI;lsB>q%smH8kP&lO zSf*~{+ncFc>?JTK(tl>y(=;8eO0vE|y-g)8KcckC96WwjYVjcm&y_#Tx^4@ZT^)yT z#)~YpPlwL}+~TOIt>qDtV)@a2L$KsisPix;NTKtHL~#36ki27jBN%y%%#7|zD!|+lqL{6% zhL*0VbPR0kpI+gQH5pAm0l&jIYMKP^xt4fn?APyQL{U2D_;NQ!LNjylZ1|}iesb~o zElv=Xnf`BG;8`#SU|W^<3@E+!)Yu{f#uP&$=C0kfN{RH(V9cTEJo9yR9l>VuVt&q@ zy$0cSLlVACX#v>ee2j0dy(j=qCj|=aNk6q}I(1`71?~T>1}^&_((Bx(hzFwD6!Iq?fs2N=fa6ggH{e3b&GZ4G)Y=9QKXz>UR22__X z2=LGYv(F0;LmPofr ziij3~VZ)M2+kB8=)AoOYC>Vs@%X5p;2n#Sf{;N{!C>a^b!+9qv_22N_f@f8=*zIfe zs6VY5yC~GSBt2=ZDz^fWr`+PTS+??sQjZTSOi4-EJ-Z3DFIO5FFSD!uGV0FS|B%OX z>>Z8^)m2BG_}IknUG3H@*tL1MpZSVE4|5jc){TRoZNDCWjt#}GFFpUYbZA|va{28n z9LRz(M}Ph3+I_97x~}wO*4Mgu>z|POwRC2E=@Tg3N1>UO+%(dx6ab7}Nu+oG0!N4U zRg!7ADi_RXH=cM-R#sMGZ`G59nF3-8__S;-XyEY8UMbvUQt3Z4%LxF8&m+s+GmD=e zZ%~&3P-Hw!fd-!Dq`s9Rns`!rafaY={*%qH-WI_sQfGFdf)!)zZuINnLQ>3{z`2o0 zPNUgY4C~^v7i;#VPc2IWs?m2uBI2SAZmz$pKK-X$mrBjt$1hwxw74z<@Q){9! z`P=6Q51Kci(p?XC72M8}sn$DjaO|(W$E#xv*a$GxdyjjC3iCRMLlKouINVkGPn~1c z;sIktUFBY*PltBeINj3ZSAtEfRL1dR89Jf`-$m)*dxswu8rDM{_a4_$K~*j4@4i5Z zV8AjM)eo~Zas{*=Ug$K?0ze>{u$$Y3VmC>E)&+^wdC*!H(KuIoQcq5iPIN*ZGYxK~ zcRy@M^B;MZt?$&JwN?qy?RrJlo*J|v$~xN8e#luJ;kN4`3;Dx2*7-P%u06G>YII8ItZ{~c5bgehN=PT zPYuOYLUmo~iVzEjxP!lIaJC08;hn!UL6*r%@$SCo zVHvbA*adFJbPok_C5sU5hs_$Nt@}m`s6l^4mQq)QW;2uA>H=U}lBtoT>~SM#sfW2; z`7Us84zzXf_a{zN3a1V3P6IqYyX+XD>(_?Ei?A^UE0vQ}a%kdlx(ASSG%fJ?7VW5_ zi=9b$ir-(>-9*IkcBg3uK#sQi@@m2j^^_0mt1k=$`|XPqIaW=9sD@}9DruXzjS_er zVQZztUHJ6o;oao(AsMeX-pS>nWSuO6ArkU5Crs{Ei~&WZHw1hrI6D01JX zt8dzx6dnm@&w`t<9uC{-6X5jTsu5J>DvKVkK0<%6TWV898dxMgZ|6HYtM#or zQL;vDTAaC`lNPx=a46Qx>Kpz((6tB?E`wThp|c=xP)e7U>eX&~M;!eAi*R_>0bN?Y zCHduWJUpy)cuKzM^da+7H!;}`*HVV!hy7{bJIl1)C*d5%rv%`wls+&U!j<|?*ESmz z|9Q}bR55r0@8qAGljHn0>nhBqFHPs#38;7l>9gPyY7o2b8S!cSu76LhTtMqo(BGgCL?}=iX^$+b9>}buFRTmD+#VENyNS z6jj{jH|9g(q-5G6YDVcy5S;yV>Wun(!#!v^KofR)j=`pwG40!LJF|rh?s0+URx>S3 zp0Q|Smc0qgqRkS4x2l$w0k}}X8kwtjWUlV+PLVl#Xyci7hZ%N)r()v_ z{Iw*t>%M%vd5(GevB=iYge>y3+w#R?Wi_98KmUtzmk7tjZlu0T)+SiUHK^s(ehkbE zw~6>eJ-?*?%kA))K;vy5>b47NJLbG3>ec$a)H$IE4#(426E;Jp>UGNP5x3N98e3cO zo`UktWq0;#beb51;*^?12G!!?K6Vu4pIlZkmS9DHBz|e>@@cq**C-9!|AEH2D{zOq z(@6opIovpIh&Z_vXCN-~*c_X9-Gy{?M3u10ECj+3ytPjrKR$$*|Aia`kW7~Yj@;bg zsYnRN=|&=Zsq;-G;22LPt}OH*Dm_sgpUiq*FI^Af$jSGqbgrU?o8v3Jen|V{4?$s@ zsDY830yqB(cqjtQ*D43|eh9pjus8D(*f*N#{lrAM`|~Y75yA(Ng1=8&6*;xZa>BeL zqWc;@_%;}dUXLiMeeHF=p;cLrWmxw&bwxSRubUefx>z1LGkVI3RqU2LBr?M67?W#~ zf6sErWfgng^DD1Ct?zozPfL>}-@8LO``%1nYthz?^1@3?*8FP z+9MDtm+Z-DO_N2@(XAfgwcr(rMNVpP>H}pHD(bfPi;wrzr#HybcV=j=<1{TJ&MMKT zqF-#o5QRJ20AaLe+>WQpZls`MvV19ld+cndX#Ql&s$@%b5I;7c&E`!Lz zreqqjbsdL5hr;O+#Da1>o>ZxOzCkW8%+-V97S9o>SF3r`TGSwKVPVyAoiojgp{Jx| zDe7^Ki`A%UH_A(Lw$06$y}Ql{8=U0a;`Pzo5$2TdNpg5PyL5+STZ&6ev^o*qVE#4G;Q6YiDTbh3OVi@c3jHnD$jKf`{$dFB>JA-_laQoMsV zQ?5j)Hs3;KF>>Rf2KhwgoA&dg)*X9?hQ)1C-+jx_wVzi#!-{!#xAWum{$Fn=vU*Q0 zrjtG6{mN;w&9~Chq%OugSc>>pnp_Eeb>27f%FLZ-BDynSRNQOpvp^h~QdR zjJx8U7dc2M41A-E!R9WE*tSlXh~6Y?R7 zZ#33z*rE128i}F@M^5%bz$?DVR@U}DNEs496hn-R!CF!L4>r%1iutUmdSY${&4=rs zduys?+BZ41GUauy-wreJ>?>z1U){24z1;!lR1|V%cf>#}8rIu*Fox7Sa6|$C@KbD9 zNT|I>p#$vf9%R*vv-CD?wMEhlzD(MImDjb~HoOpqwKKTcgO_E!(ZTcl z4!VkjaNS>6+FBxnz&ves!A!~ud$3;9Br!~}Iu%g=b!n~;JnQt+R~deC;LsSX3!A^C z|DizH*$Kh9L~-Zj9AXHdh>r%dipEDq>frZFfobPOoI`X;pcUMn63GXTUs~NrA8Grw zr9vppQp@yOVvS3`iDfA9k5ZDI5g`k_1Ba?x>|O}Xf|)8h!+3&Q{v^D4*>aqC)Pe8(Ihck3(3hIg`A-X3&RH&$^ywva+GZ}XY zu_SxvZK=23z8dz?Nz6alP#`rIo{=%P_wiyW_LcBybZ<~0bVvIp~{oa0O zOOnKUpT&d6x#5anKps_}y8lPem}ES_uj$n`$Vk4Yn{VT<-3RLgB$yDQD3fb^?Pkwg zn_-`i_3y`xl6p;;u=@(TJ2TqBN@6bGkfzLksu;DLtOGN?81_uaNfjF+2(XQH=gxGn z>TxUX6j%y};2=GTTMe-%%v)MpqmUGjvM<$JJs_!s1*fMqBimBQX+Xn)dNjL0lPfWV zwV8FpN`y9V7B>)z*xMT&l>_32wwQ}HJHt?*WWOT?W+752VAk|vRYeA9Kd)FT*}D3K z@e_0<#FX2UKB!i(>fY!h1?H_*(q>q zoiNXKUY!iHF$3L;ZU{M&`qL-c_)a#|*(EJ^BSIrUR(WQ`o$;=0nZ^fl7wkh(S9y_WPhun?g- z{V+{HQ>Co%S?#iC>$ydRgn5LAZ2k3}U5;F17jt+$6C1vkj;t%S3tD4h!Q495vY8`f zWDGL`GuAi9Bd-xiTF-+%gSE~it}oRCe#IR(1vz;U(fn`2cAZd4b$oXa9`9xGuJl?5 z_yeJn<5Kjr4xc61b;D7QxrM6WSnmI~PIWB0QRS+eQdCRJ$53ZU7TxP^#bDQZ@dmkN z>|Wv4>@qV%W>rtnM@Lt?V$lgV)J1IRF=}k%bJYnG@s)j)^^{v-nd51ok-$Ya`YL^W z|GQ=wh}XCD65lZzCx*IAqW;^Ex2;KUpD8YTZ9YiR&wVYlT30AA!w7P4$O2JU8S01| zS)EQxS}mr`@wm|NAVg@oz;&)%Nx8PF3BhkD0{4IfnbDukWR!n(9ag-c^zeP<3D|Xy zx57M!-BBj^xd-)fW!k<@Bs()CSqyH?8=PM>KvJ~8ACJRWMMKev&&?y+$R?ccYw`;s zfr7AGI>Lhtw}My5aLhofHaa;Y{!jxo_5|ImO9DrMD|4V3#-tk=lVuFynH!Sc3a(1< ziYv2{4ZVC(Kl-^65)kJjxbI8R=6c|t7*AS8T+3qOsLR`PWLnqAi?w4Q+{LrwWN=o< zjmm`-oweSZlbRJScBZDLX4h;$(FJDg*BH=8TK04594LVO|Hv2A+2_P{O;K_C6c+%D zJXb{x_4}APE^}>2@DQp_(qbMV(n*Jn?ruhrDzznxSAQelg|mW!G>3Zz5DDP_=#&xd zXMZU;&&W3cdVun71A|4i)K}}9U}JW*ftC-SW7cz1kAy27wWp%LMdf`0@Y``}#@jh2 zpB1W*;5R8vNa8|x1&hR0UQjapjJkwu$WrCVdvmCMBnfOWEdyVy0ioyYr43crUS>p} z%u}HLg!S>lB8l`6J&Fw#+8al#jOKyemhvk)bCCF&()t4bI)JvR5C85%9>PHW; z?&uD4&%vAGsJ9alkPRTSTOWhEQX-fU9S&Ww#D2_Jy$e|wa97n13!Ke>rQ+o?1?EQb zi*hL>Rwz3W0SSehA7-IZPa9z68Dd5cZBZ?#8%5@hY}20OCbx8*I+yWq>coh+fUN!U z2$t&qTw!x(v_Ynkj}cTt)moAw&Ojh`X^6+JmnkU6pmanK_YMAdqrgk!kB1Q&;(vmz zT)>m*jlpmvR(8_2RnfPW#!GpYBEG_7zmDW1@rkMsQ?O)rXB_wjWcM252+G?>DHa?Y z9?+4l-;jplO)%RN0OWgi=i9b6Bd=ssXA0N7pmJ7){Aw#=MP$aFa&BKPeW@=p9%vP*bm4X&zhU24J={u_1xV^{iKQtrh5<1&)QJyneLGP4tVmlpXl0s z5FS>yTCA>m;na#u%kKj=ovrVRwR0sAb=ddOczt&rlEyxp`zgj+rbe4Kb+!>A5!M*K zfkm7*B3$`d(_9PpWBi!_Z$-CW6ProY)4${~6I$$WwD94DdNjn{@2VEPXp+98vGH{# zg$B0e-gcmB!bzKyMRL(%UnVU@^~McMpmU;@f1>Xo^(GI5 zLZ-rTZFUSAciG*DMAc?bp&=Z2g*10qS%!`~tSm(*>bskfl#VLPRqF4MRiaOP2n)og+o)9Y;;$284$z z$(*AYH~mRY+=>TjG%8Sv-a!hKqnBXRQwE|i>WOx@NxX!CO3aE1y$jP9J3u|Ge2PxT ztfcMXRJ&$0F@8X~O&T9{-AZJ6qGQqIN>>8Hx)b(JPWLuJ zo^6^*G!MXNQA!K0b5t zwXi{G0q^OE9OUEtS$+TA5nIT83Hk&M-JlQYcea=Fsy6Ico--}Jk*E40ctg-BkD&$} z<wOVi&WGPvO}=8Tf=Fj_)hPC za-QI*F>V8Lndav>!I3r#VC#x6{55JdS5Ct%b$7V=H#%Dt+sux2QI)|MmN*0IR>E%v z@|YyBx!eg)*tu9!eR(xp^5cn)Xa zKe5VlNc9owEP01HJOxLdsxx0Bokpk0zZRM0p4ywl6wBmMRWWDIr6+EBf4QKQ6tIbQcPOittLLe7j5J0ELdyeKa4Wn4lQYQtSwjWLTQVcuy2rYrnhJYZoDjXWTAiJEgg8wc6JM3UKCh0BkJICl15JRGnEzLoE/jGHxewjBjNC13G9KoPOsUj0AD+j6OdPSZBDzMUM8xcvwGyChcz2wayjLB684KSEIc0LQAoV4LdRmlPPs2WXQhktytecnGfXnHunkxBjGvM+CeJzcOdB5/PL8MSTe9W8TG7ZmdeZnjaKYW3FuAP+OUqZfmyzUTjM7iAKQzs4U6aUg49KfYl9ZUhF5gIZ9EypyMgfuhagxJFHVpJHxKRyjA4A192YkzOoaCxfU9GAyFRV+bWu4cGIdFAVJrvQY6Ac6Wosva6irel5V2mofRXMcmLIRw3Q+rzBltXOfkii+K3w9w7WhcP+JkfOo829Zn49nVeNY4hji4lOIgWn6Ek4T4ZVrzGBgNkpzQGfNhd3VyzEbAd2cWBCUt00PGIMKczMvSto1/NfSBEvHKm1Cji3KokVkJYbYgNaooRRVH9i5H2Yo1R6t02Kxn/wy50DLEPG/JHHUjQXNnIMTPHfFV6DIkIPM19ECl/wwWkxcsepaJeNzhgdgQRUZBQt7wYGUyysmGIzKKZSaK/ACRPB1ZfERsQZfKMCFBIAeWE1FPq99Vg9of1Rvku1KNQq+dNX9UtZa+Fd1KPthsKjPnBseCgwZ3paHng7+1kAeeYztGM7sS8g6oli9P8/YrfvVf3O8/A3Y9f5oTdGZqtDeolrVJrkjZfvJp1dVPuxG5tNrlWIvD7znybNd01NPdUz1Rxa9nn+dOxdNuSky3JpClJZBTW0zvKSfD5SeS09oyWc7I9yvrAHK6dXL0f5X1Vg6cZsq6Un6ovecpCFX0wW7uFLSVHn3DtWXhPuMxnHztoWPWnn7RewBGpiEwLMf2OeY6wwc4yFy2u3avIywjhgMiCCyOWn2Ut756rRD74YxBYcQVYSJEhMpYppDkOr0jSB+7Qx7/VPQ575B/RT6do6qnXRE9ZO2pnm5VhquOGlZP/Q5p1T72XKaYyKXcipm//aOnH/eYCuyd7Oln513FauiyUpFgx9izLqvHo037QHXZ1kKPZF0Wfks48cLymiks0cz/i8iCkf+hg3q/AA== \ No newline at end of file diff --git a/embassy/docs/images/embassy_irq.png b/embassy/docs/images/embassy_irq.png new file mode 100644 index 0000000000000000000000000000000000000000..154d336b6e34a09f18c9f2a8ad9ae53c611f295b GIT binary patch literal 134158 zcmX_H1z1$y(_Xr}OIm8_md;&z>7}H*78aI9xke876-97D`9h~j$ z{XN%D#Oxgo5A z9QuD_M0V}`->Agw)jb@*>dqh?n1+C+Z=eX&QvY8+9@g&m|2lyn2gWP-uM<%tH*H%< zPd^oifFM|1$6wP9?(Z+4=;;b`6$mud(-MG*1o*j$`6;?<=@}a@}=Kj6Fo$bWL=9^!;3nH4Su)Y<&ZSyiL44 zoYYNBJ(XRc#s&(`>MD+o5`MN~>RQ5v8cI$AimvKvN^XLp>Utm#l|W5T6?P$27ZBLr z-p@tRMO#Tw7$m9T3DJQYx(FyBg{0^rreg=uH&s*))H2kP@V73JI`YJ$>ZFLO2OdZ?<0u2pZ zw8WhiG;{;uB8FnthK?eV8V)9IlBR}Es-li+rjjZFumCMb2VW;^M}Kuyun|lh?6090 z;G!Uc+%rNZu7Nt9)=K^khLRwK098Gt)D>-veMM|RYIdp$M(W-OT@x_{TQOr#IKo=Y z+1uVt)JVwP(?JiaW$Uh~rspTDi+sX6P|?i?s^j45?2J6q5cZSw6E}9U{WDH=R|g$W z2_Z>S4-rWZBYSNV5hoXC5jPz*M`I5$6;}}h5mO`YK#-!Nwks@97v!MrWG8GC5U66K zU?*g2tf*w@BOm|)scC8025KSneLRsHM^)QFUr_}C)^Hb8a&Z>c@N_kDRX4FQan!c< z^wiTc0!uoJf`v>x1@**y1cWsFJ&=$11&9hLYpOs%$YXUd*w956s_mhsWdH#?s2YhW ztGTK{v;-wpl-&Nb^)q($w}sootkvzc1Dzc09Ch^!O%-*0#QjV_h5-R^caU13hK8xW zwFa`af|4FYQP9*uMGvlP>Y$;eq^hW z8!Bn&V`_~&RJ9Y-fcQAUHC!Mbig0@+M?X_xm^IkLQ5<gA(s@9E}^6q&1wsRKeCCTM2_@%0CZ z`e=K*8LJ_b#REM8Y_#nJ1hpY{hJk{5{??Kn$fZ%V)A6^{_SJU{5LSmNxv85f8W;-% zA~f_JltuOJ^#vq71B`9O)g%KD-U?t1F*~THfP*Mh(8y8D))ej~X6Pa2iLkeYDXBsQ z{EWqYRWmx;UTVP@23cYIji_-YB>n$*qey?`PqsD zs0Il63R@%80{mQ@9JQR4lmc{&J%kK>#6`ut^_0a$O#Bs1Y;}!H^wm|Iv|uJMn6j&m zw}6YjB68^By8g(eQZ<6;ii(ROFR1Dnh}n6Hd3iwfR3uGxgkT=-qDD?$zB(W`Q{-k; z)fI&a8i)%)HLOhnG@!Q5Fn8odI5tZMD+9AF4jb@Z2X z4%GBQfIS6-#dX0@Q$be^h^eZ8v4bmIM8Z}xz}?vuE?^soeBN2YS;^G_IW8whVWg;4 z6%_thygwf$ZVuoEgcX$|r}WRaHVaxoZwv8pg$QuBh*(3UJRypFgzvdQL4m)^>iW_cR0_T}tl+F!@dwPRZUMULLosQT13g z-8GG_cA7lXvCJ;#vDj1pqTK!AzOh9n#AJRh$6n@PG5*hYHDs|^_^zokyG6{T(QY)I zeRQ4U<$&Tzq}v-W?L+-6t1}u`aYcgl%nEmMQDXC_1zy|JZQrw z4<|na-ZeRQ9h{jR7~A`~Pw;oQgc|O?Z+I)YvvRiSda_Wzr1!fc`Z^^3(#$TnE98pT zalS5KLkF+UBIEZ~dQgtq(7Sg2Y6e+xvnN4o1n-BQfQQ(wrE8VuePMOH zatunjrY!{iom%qi$=&HUR~Po6s|$ymD>g=!KdUm()mYedIN~J-C|rDXB$*)ZTa?ip z8FaJaws$?%c@&>9ZC3aycxHOxsJ%uQ?1^P zoaVzlZUu%HU(LSKVyF`T5EDZeaA0b|_~J3m`(lFILZ$v>x63<6oli;aX1`}*%(v)0 z+MbnJ8vZ^VS+RNedjCcIJKaHd6XdtUXs4_AcEScvM zroWf8Zns)4gR3rN!@jHTI~v!$%vx;`oIYO4wQSz%4KNpQbMT!1JeM~qd6ks+J@o$2 zY{~#7HUus4_j$RvEF{?T^~SvJd3KG2%UU{x4Dxz=5SExHMxs|{bvu816H@X9=jIvg z8rjHY+ey5=Oolv#q z*}G`KD5cdfF8<&Psge(?j^Zl|z<|)KyXa|;qc_248!+42=5@RKgXEW2zj`l|_rBg) zKzwc%O_uIOFNx+s-!s=NtZ4?@c2GaK18Vs5Tu}=D?Djv~+sr=B;qTVgc+*_?%gt2h z+~?&F8G7i-YR>1-z}t;Wm1hbc*8RHg(>v!j?uLB)rX3eg%pRh9H`6Fqf-3J-F3qm* zx9`Q(jxj1^rA7;9KdfU`+|i{+2A_v^7wXhb$n>tfUE>c%n0=pmT=@z&>~^fN|8VwR zoPa=F%o&jKxqo`*`d zzBc}ea8I%7Ttt|}j;Puy<<6saOQFpwyn>0g_k0)C2ds1I?(g|NsalN$cl$JWvBV4{ zM&s?{d?!RbjO#(chtj%QRiGxA%B$@K9q?Uz)KXWr0?&m`#@hT*>YYrtyR`DP(?r{; z)*m#?GfiBtUH{41y%(vCWRkM|f5hqAYW;BhJVLlirrNiAeFeTpgjV|t6_YS`pX!dT zuG?l+Sp67u)sFkD_|Ov?qD3ZPcS%*&lPc^tX!|8!D$ue;=-RLc(%i-|HAV6jAGO|5 z-_KL5o7LHj*{ZzV&&tBf>k0wA>nX^YJl=Rg8Q1_{Px%m6Kf_&c9>dcVy``+yU1d%-!il8l|OFGeF?7YMY39liWn5g;x- zf)U_w6jmZ6)!Za~illsHmDiPDFCj!lK>Z9sX8CpooJ;4&*M@86Kl*+0rAcc0K3 ze5K=YdW&U}=~VbfyK~lVX7ptTI?LtqR97t}Z?k1#2jky?l<9-G7zlK5$)!ERbOAN$nI5)vT1|=0uBM{O|hW8)=P@4T-XLnr8xH#J;tj;ExlhO5g zHE(if{>48d=1w41EtiMfx~qF?N_Ey)y~pqM19Pz0{{V6s#c>nhc(xEun21B)NeSz)wZiWOA*$4SX(DWAVKSR>@4ndi=2%ju!7Wr~RCK9~c3wh(?f92@OZ=fG z_|9R<1BCaXHKB0-V%{B-i{m1=G3gImd_1vr#(-nQ*9)f$1Gu)nE{U;nPa z_Ck>M1+m30&(Ji4c-~VYZs?fNb07Cx)3ZM0#+b^n~9~7hCh85#6&Woh6B5b z$Z(=fKE8@XXJh^bRByJq9JJcG2()C=QJ+=+V;b$unp&;4f&?7;U zHx0<~*QD2e#{44wX1(isW$;x8`!sX;_?nDcv}|wDF4rxh^gg70H=^dqXy=g>I7gyF z`~G#%{QiUL9eUkchYR85ZRIypsdnJpVGsIB9sx_k;}{mR($8$)9M{TPw`TihAR1Ew0PhIq5WlRgFY^JGz;A~iF#Hv2Xjl-7&u=g<2HGVE^)3h ze@skt$4h7o=CH9aCUG-RtkwFGqBerL#F6SnhW^U0n`ZXI`}n(dwoBtijiLHq=Wp*< zUA1p+CkYsPl!Ty!mm2c+L`MveMr3zrg;CoDZ<7&^s=h{zQ`dQwq#XJ^-j*$mq_WF1&d@`TR z)}`0ZcfLZ?@UYpZ;fL~J{Q<{FX{4{dJ8?N$TS{jp{v_>^Nu?#`grVHbo9CJF94dE* zCHc~A58|q|&>@|>w7EC(AKj_AzcN>OKMv(bs~2Wl*+99G6QS1VDm(lGb2YMS=Dw@m z!wI8mW1qZKlKG?~_-1_=HuV}y@iV9Ux2`Xow;mL{n>Tc`VGQWzG}RRuKQKQ}*zkNl z#3#Fq6wMmn_e}ETHl4WhG{1y~D*ygkt`@eFSwnBWsd`k8ok_qpQ2j+N$i}|Guprd9 zY{g{^k(eqkayRD7t)@??ih{Px1sB-&?6xrs!+3 zvMPo}?QiPt;9)=5m}7<&AU<-+$H&j%CzSvwLr>LQnO zze7b83P>l?HKSNrF7pB6mrSTk%Xqj5YBk0U-s%qNm6hFuF=#lIQcyQ+)g+WYswpmT z;%4~Gn*4*cp_ir~^pUJV|J%yC5S*I=l@ELjDmA)b>k(=T=x;)c8`L$BQQ5fa){}#; zsiGlrl_O-3wvB7yf2cg#uoZ!dMZDW(f?q!}0{(^~Rkw?|-u5}YBKt1fUJ~3&k<4`w z*FAjorTG^o#v%B6*(}d0@h6j{OLHd8g5ceU`8&~HG5d=uHI~~aQ4%o%xUuSG&)8jt z2@bsIlK4bAs2&g5B(^Bfz2vH8R6125kyAy5;!RIvUmd75q{flV-V~GQQ2MgWj$)wV zg3lil1mpn>tyF`(?H8P&;xzM82eXHDgg)8ztXZ&;FdolR!-r}s+7LWW4N$tHe|EHD#A^PE77)b z#K|pvZKuol#(*ygp;Wl4_hB8};hq8&LX~29MrLwef?D#2!(}Q5-eY-uN_pSk3TMo3 zE+v~J@YL(9f0;>6RNr3+n~$S%OM3?t5o80fYghU9w!nVo&R$d71r8ZhX>%&aNtEDM zxiDb!Qd~16Qf`I1HTM|dhdu^aG(R=D69||3s|s#E-I0`G&~Ui#+T-0w+Dw-uQs=F9 z?gV%iNrQ!BID>d}QfZj5kAg-FfXvj`R%jq#>;@@5=SfdR4%5v)oF>WjP)zVX!W*fvf0uCaBk!Bl1Hr%AiZ$5 z>FER;5Eh~gIp&T@2gb29rLP^<{WwX{+v|b*4#};kOr;pNdGspW7Cje??Z|=uqnNSRM9&CQEyon#9gu~6E54+7**@C zHo9ivVdmfFm-)u)EyX~c)P||mR*%;$d)Flua2XrEyu0~8`Ye2b|4Zw2X(j9`jv4U) z5VO>IiEAW_&KD-N8e&o#)pPCW#GF}5_Oe;liUZR)>4}K`lTE>QFtYl7xwbfxORLev zOPQdfhmuoSJdKbj5qa0O$Fs3~vkwuOkG?dguLBBjc$0ks2cHU5SNUubs5i$kpj8}r z(}MT4{^E*J8?$MyMIR7zwdw=q@-sDhhNPID>w9YMRy#>j55-cJT*I4wT1gb*=mV7t z{1Q~Dn1{wJ+fm#@<(*2wvtB}i@d{y)fyJY{WCdbC+*^-iXrrFr0xC(wbQhA%M@LAh zpm&w~g_%p>gbm&I;k%Dq*GTWi5jOsv2MeeJ+J8`@EF;j%P9#yD@{&0EdJqdSAuXu4 z=!(~>=M-5*)W8mylr%r?U4DGzBhQ#>sNBH*z+x-uathq5rtQy&ayf*pkNOQxXk1-Co zRNO$vPkm&phd~_P2$eA-avf4CbAkHgyfb@(vUT2m?1lk)NsqW>5L za8_(B+Zs)};mux6a2AR5I0eE@+*!H>!*f(uYrTO?j+mVF20}Vp zYMNG9S~%$dc* zb4y>&+i{}J->P3}&@hw4mnu@v>XZ6<7He1DnNqFkClgCMFSvZ)V2Y2{ zzw|g6<#6O&%)N(@JCY;GXf7s}B{Ay)%^jEb2pg1E#9)e}xIduz(L>1iX|Hf9l4+?G z|A$)pkaRwsOt4fg&-ABM+wc9<;AWzQ230L;R>ni!wZ6`BcJ;#DNR!rq_d6Wn4Aujf z@==*gXcf_K!WT2MYNRDu+7L-k23h7=Y8hWY2$VId0J4?4K5R3|Uey^*BtnhXyJY`oaaY`p zo{6otm{mNS9x}OdQFwx7joXq2rY;22wUr(>DdF{ho<$BWN&VwZSNiu z3UXpvC5pU1U4gQC)Yu49GmW^nKiBaEI*G^K*OKTw{_5uZklWo`%gEt5ud;;EQ@~5Q zTXxvOC!$9H0sHJn_K;FNh<#r(nS1gir)iw-nP(i2?MJ;H>S~YvJy6Z5Hr}+(_=L2Y zdf50&bq508WRlt_)W`_G3exO}g)M7cm)Vl!DD?S3(UTR|`JeHn;Pij2)c2!#R?Uk} z6XOF<-7tpmQ_MD<`V*6d`xxG9(DSqGqrB3?RXOjV=jE-<;z(GQ%Nh-&ie3}3`wFK%aqc7ICBc4L$7b;aznzEy1rhblQ4PyAN0;WAVe`vl zCHvHk19_*)8>J((UFbE>o#!?d(*b33yG4v}D5#)I1TSs%-&V6Lcvslv#y5oW1;=Xm z{j<|!{aTlzejyKyFAA9w`xSXaT_99=^NNcH-2#ohUO#)itgv6xMq@sz(f4R{D5**yK z0X&hHLr=uN4jppRgt4=t2+2uL#|@YS=v9c_P%eG!{6*woT>pS+g>S*xwj!U_C&jyG zWk>VThc8knTTMuI3h{tW&hHaL7^qJL1M!Q>IMh3jTMQ>x%yAc(gXPaBlpvz z35+XOeHp=_$_D;8UqOw>CqBWkH9K8%B zb0r29f1+sL=Ykx+I%Btq5G7&H?%TA`(7A=fsaW^JS!}-rvsKhXMoWq(Et;cR?v{8_ zsDQIVyiITBtiKg;6j8cCYyeU*am5Il7-C(53M7?_*G*|}b1&#&qq+DCZhBDK;4#>R;GYNgJA``=B%iSb-jVM1y|w4TomSK zo7$-jYQxEyjryRK=2u+Rq1!i$;SBe~s4@@4QP(8)aiyWnW6*2^A)x6HttmGS!RxV9 zX;3mcv+ojTvRKI&Npx)BdOb4Si9tn@`MuDnfSyQ0Ru)nftv+@(TTbp-6~!DR*vs_`QEI@z!MzF{eCc4dq;JGiwfViIrm`;M3(I)p*F(_ zAB5WBZ5u4r8V>a>on++y2VI8764pzj#btaHy{1`Zi2GS5_d4kc)JYWz9CM#Bktv-< zB{Vvo>(f#sz+eNaQ5r`kXg%7=X60`?8q$wcal+hbP~SL}GM5Tvj7&FigXtof>Sa0i z-(Ee#&kU211!Crht= zRINLaLYu&BT`W z&{n$kgu|Ntwx)N5I-iM&|HGJiJe3Dmj&qm?tnirq!7RwXzT{D**7sy}p z8=MgLLE3>lsXB8fcQmxEpGW&YVkYEYzETv+q zG@H>EGi-qP1* z;evFx%@?;^l3pQQ5u#s5FUi`2noOQ5Zd<9?fATrs#bYJZKNHEP7x4*0hVj3LZwh&L zq=KjIYQ63mSexh4#Wmmb;fs4`kEJpXOp=@Kpdf&fU#fnHt6Dfy0S`tKx0MuH%$^`* z2l2AiqXe=9hoqLdvGgy}zyE9xSYEi%-_Z8`c^l^aZ%S_jd0JVuj!sdfjC>#Uc1;K7 zs%9}S#uDpt^xh{J84d~Idir0bG+gcT_5ZNxD%j6FFvLUo7`D@(K#ZLrsq}@zCY`M=?A9>kbE}mD4>Bo)Nk4zUJ=~onINlLooMUNmh@VW9o3I~Kwcm^=0rA0H>H);G*kxS0z&)4;w} zTC{j@P)>s98@Hypt1FRU@Q??}gVC%@|G0C1t3}e8KTFTia#(ouBytfND?Bo`( zogHKRc@4ZyROml^Jt|`Kgux*~>WS6xn+;rI0HSqII;OuF>8&EwT(^d^g)V}O;xp(E zh~HT}4Zgz{HBO+02onp~hOR+B8Y3|rP*IAv`m7OmpCi~e(F%WfI@t@mW>SO`<%F+Q zX?uoHQd%l2$D&Fk^0XkeTLp6=o_m?Ax9Pl*WR7lmLVf5!AX_!lPD8P)H`ZLz3RE=c7zDT{Tb^A_pKZYNrdAq8N z-@ja+P@-hl4aWcI3TSAAx%xuPR2AJ<(CuV9vF?j9b%qJ*O6KG3 zg+ah4%{>ETlH~fA(=Aj=1whZKWGttWFN+x=IIm>&L^$TlGm=*QP0C8~d1u3N1Y(~=yUuc=L!!Ev9T4{yE+9xGCfh(0sYACN+b%e>|5Rwg zh7_qnR>KJ&uip3w5*h-h?8k%i6@a)HKGVGVgFfTLKe8aPUL)t{#75PuNxZ@Qtr-lD zqi9U5L_Hc|Zz0Fw)L*ZG8Jj7O@1~Z}aFU4y{k`Z^)Ia?wLmz2py zil3LBM8HT-M)FV?gF83%JFp3MOKYO=qNmm;w86${_19-8QutG({dlFxH>WDJqpXkV zVO*-FDHgKr-?#|`y<2Sap9Yi;`RCE={vJkM5ZtcDlNCWy$G6SUSN>DHzG{sBW$U0r z7=C3vgxk~!*ULWYxcuJ5{Vqj7+-U6d9m;N{uh&q*a7N0a(Wq9pV_h7GI)May2& zcERI3TYznLB;>>IV0rRODoI%cy1j(=Drt=jJ8&55*x#;v+nHfxpqSG10_6*x=p)&2d*!iCs`O0I^%4M-iV!6K6RP10*g38>IQu zue{?HQf#`pwEDZnvh~89BJy^Uqr6ccmQqrFDPaypV$3kU=(IjHHge)ZpE|1t729wr zS#paB|6~+&%3ltE6|w5jEN<+_^C5^)Yt2u@fUY-m8mF4y8!ev}_GIqwI{P`TK7XQuHa2{{_y; zzQ4h3f5uUw{UO+XNaFU&D;d_+Nx73gM>X}RiZy*_2<%ry&~PK5VNd%pBJP3l;Hl3s zxPmbbNOf#og#kUYU#J%ncCso<)bDP2u%r36_mm%cuZH7}%X^ubk3gNXgQ$L0X+r8) zbT1@iMM$M8{iwLLJF_#%@w$`ClboEH-xzQY2?I_$1nA3GO7^$V$_^6=?nA>Q03e{% z@XPmHW5&Z`)GxINd*=pdKzsBFKf=$lS${&UimbTH zw1+6|fOs6XnKymdC!p~-Dw!e_<1=RKtqiQR>#Kg!JsW~b_xH@GBo;{D!d>65)F@~* z#e%{)*-|GP6tLE8EXh3Zf;C@BP!_W09W^b_y0|8E*eUn@g8kZH=H}nNbEC&c^NWjI z;j@%P5#>7RMKNC`=wOu6s0y5vi0P71n(hSQeRB5II>Y>eIeO@Yt2 zNR_1DjiXYC0c0t1#FICH3+b|6<6b7RtOox|#`rYXp>zzdQNE@6ZN1ia-xX!V4IVEd zPZ&ulN#FS}LIr%5Om|7bdOgV^gH4R@cX&3R40dn-&8WtD$VSpV}(UMa05z?-btHn{%vhpNX|ItcYpZmftlYB?gCAsfu;+5Wlp?a;(zfV zR{^S(+eJ~LVBC8|^jPJg_)^YOpvns7HnaJx;?J^PF;4sTM59TujrEgF?wTJ<+&dU9 zFMNs0JH-?1#NX5mYM~qgZiT*B#*c7Ruklcgaia04T4_Y-a}wIv&WJ%H#LeOBLHw=o zO9r|U){!o?ZxN`uTatN#%fu~zCZ<@l#22YJh_-=yRRVtfezblfRo_EG^fY?gRL{DZ zk-mDXjk2iWbfu^$J2UES-w#%-LEz?xmy+bHUvhw;hARRt_?0fi^&&)PL;J4-UN)V; z@T)Vs?bZQz2Q76vjH6-+)u1g>+t94ZJXaM_Y}#o)6%JI^=SCmHEOGK!W}jBTs~bo? z918GG;+~+CGZyfJwfTiNl8HLHp)esrYY9OgZo?TI9Ae($a~;JtA!>d;ygI&$kXp2$ z`;0bUQy(#u;Y44Y^coch!ZwS27?*ozfhARvQ3){S{ze`6Lvw4=!(wHoSsu)5b|#HV z!VvaA4x;O{Lm4iK%6^g<^~Cd~bi6G}qQ`I7wRJi%jAHMyb84Pgx+4|od((pxoE5cy zIse4A4ee3K4KNlNy`hx8EC+TW6!y|Ja_n)1H+H#YPYH$tVjM-XEpQWmNFx)3P{PLi8zCVHgaKn7T zAol^bU4FZf(%iQ{@I#vx-)ml4KxDsHVuO-Rf7>pdgXU{o>?EI~p3~Pr?7hC**fDSp zb=ybf-+A928~8OAe$55YGS|;`@nhCUrg?%grFVcUpCz}jKHIn@06OR>v)tM)o(4~4 z-WT>1{MD^@Ez~&2uo2Ha^2ed-m;@Er^uc*hO!*Bf&ida2S1U#=rlZuT{;i?~4X3EM zxDS=YXhb(e8>8-mTl$Wy_!iV*KvgGa5v$=Idj4eE1ck4ZE=?+{>ZC`8A%4}DkN@AO ze}6*qWu;*g1)(0V7u^!l%^&@;fk^&ZD?vs_9#A&E99z2qN#UU^;8WyJvxkq-Z|>U>-YB?Y0Lzui3_6{JFCeGUsXSuc|@ig zO!ZypsNf~WuauAK%{Jo-d*~&Y8dy0@rrq;Q+S6UecoEV6>L9MKhY+7ip?|X~DhRwL? zG&*PI^gjd)JU=B^8FJQ6L_9WnN~(ZMm+%YM5&@9HB_dWkqi1GuP)Vj4f??xnWG5AP z!8dqgZ-EfVNKT*HXLbxkhtSbuUmgW$fPIF>ycr7OuJI>`s_v(c^BVU}=RbfbRB4zW zx8|n3`)Hpy?uh=L$j)DvQT=I$9?jDpB^nGSxlARUW0UKQm6zLSqtsRMb!p0dN&OH! zPG!{~v$puw^d0K@ft3@pCVCre43ls{fQ)Z^6xG;rEVlsl8~XTEixP@lg>hVZY^>gb z7o9ZKvw(q!qZc9rk$SKDt;zvzdXG!dSpi}*jUAF2gpXpv)L#s@IF+c?Emy0vVLwkA zX!|;awm1C907Ps?2)W6-V@7n63m{w4g5IblK&y(C})PL_1p-N>aOt(ede|X1!I-AdM$3>8{BO1 zItjK#h!%~-#315Xu5{b@7;7~Bnlt~@6!mkqO7cq@4`2;Ii`gEip2c(e(Ikw)5?mYq zO}SwKu-oP#eFV`cY*J7wRW#tNdG4AmZSd;weMQ|!4X5QTh!l6e>UQT7!a0oZ3GKnJnVQ4ui*qlPw+i33-BFIMQ zLHPJGT#D9;&!n+-@yvZ;D99~?J<;C$70R;bn4XX{mJt3FZqmAb%T5EV&??Adv#_br zk4#zaM$arEXopxX&S3>`q5XlKeVB}Q-rh}Ir)pKb%Z>PF9QL8Xcxn}~R9D?LliA%* zh`dN9#W##kxtVmKu(;Jwz`o=L)(5`U^G>*DkRtjq1iSI15CVNYqo5bf9{7}!E4(Vf zvD8Aftm4I=DEmh;9NzQJ=)A`_hnMsmA94n^kj7c_ zUa}LvyoP79XTb(zf)cK}gxi@bB}eyMw7AAJiDz2C9zx&TiG}xMu^YWzb8wJ+d-(gj z;XU=&X@W%Tyb+A%Ow-Gu3EsU`MegYD%9?4gIXousgDep^bCzcq4Fg3UKdgEATH$8_ z<8ZOoQXB&Ol9Li2?1D%;-7;kHbBJ}RdSPsd!^#JcT)vw7D{EJ zoO4A)7Zh9zmOrTR=_wPnT;)SwbVe_T7SE3_2 z%G^eVRc4;8j(cq2xwI%nlw|MEbGxWuCl7otVB3-SL^O?AR+N6M#AXfv^L-tjOx^jAU`ZoEqIp12R0H9o@r#r}K29y;f~!ug5xDt~OzT zKYl4(6$QJ5V}ujc@#HXGEROIHrZ-xNjjO#CW7xQi#^)1#eE9>-YoG8|^XnmQba$Al z22Mz=x2S41rd^#c(hAw$FxdHZW~-BCCEH~uFaHND}R`ytlV|8Ry)-B%3Eyp-95e8Br{W3 z^7$ZXn>7VJv;8pv+XoW1?toAm^b}ei0nDOx{^1QARQVmEVYzp;;;Ixfs*L_e(DhtP z^BkQgCs%1qt&yWJ4AHLyQIOsbwfu?6@>wDLVOmDS<0hG$w$aqTLZ%_(TK$_6I9Uo; zmZv{8wmEMjq#tHu_I`LHSj&nx6SK`tMoUP;-35Xfx!lq|!O)^p{SlvXnJLILqHW1y zpTCns@`J}OeQ}`ycYEdMiF_Y8yLU?x&$4)o5mmHrldl&ZYfz4J!eWW@D!j&$eyq-) z1!s=!D!U{VG2Bq&687(17Jzl*>(aJ+-2APjUXV_3w(_^9d-P~G z;DMXOGCZJ~u4ZCQ(S2XtqfkWlJ?b6(w$(+F+sAXNQEazw06V8Q`8y-r0d?5V(R$WV zrqsZh`POLSsjiugW)*TD#h~zusPkBX#=SP@89iL(161N)uR^|+va1OWW3RO2FO^9JK4 zYOo8hD>Ozh_A7lw%{X~$Wd1L@+_7kFLdpQEU=z*sotN0Cs8*!qji2aRa9_Svg%4(| zef{*3!Q7)=usQUa|28g~l6!a*6@UIL9e^rR4wHSzwNo!BFHYTY}b; zr~B)Mc;b-PylXpqmJ);MJ0qz4lh<`y zcVesv_1|x^rQd@kXph-@8Hn~m8ttYX56~;TuNEEOhckfx5JLxe0k)1lTmu~}&-oo#2s8S7?91-C6m>1wJZF}febo@pp7Xmkd>@yn7v ze0rQEnVPhGj(k#_u&Z%putan=2Az?QxBpq}kcCl-;7N=9NC{8IE0NSi!>4+-Q+4>M z>HNgGx1;E2`!0Rh^zJ?emH6ljUhroaZPV~r@@XPE;RDdG4_UwO?U1EHT5O3ZCDK<5 zwzp^7-XwCgltGgBGg3D4FXMA{45LXW=NGLdSW`IP=6;do<~cza8A5m zn5`taCmWYnsKm|9RoWin8#^>fdZYwm#L?S`L%@H^RKNYYEk*0$uORbWC_`pUV$5B)7Lu;8;xEkI=-ALA%8`Kr<_a4PpMy$qTQ9_YZ_IypKtq$ z`~^C+s%T_>bP9+k6H>9GuQCReB|S(r544XEEY(tMOC0j9aXVU}dKc2YsZ?v|TRO6# zd?a?-kvY~-QO%r~d7>20d#d+|Zt^mjBII!f%)pEO7O)q9cXF@ycL|zMITNY)>%t_D zv;L4ux9_1D>g#oa>fadgM$cyoV^5_PV!eG#iRJVP@(Rf!>lF3Gm9$JK6v&Y>F5*)` z)tCVDVy@#YVrt-;wLh?pFq4`0MvzEcr@>WU7HrZFjD?ur-XJ3Yp=D3orG0>?x)F~U zTfTCT|K8-tS!vglUVKRZCW-)MZM_dgb$la4_^7SVBdC~RVCzR&BU6=hsk}bdgr26= zcz)}W7tyvlT+*W^;O(HdIZT_>H@bLlMXVK&Eb3^?mgk)Mh}V>f>hs&W$8M4APpTHy z#=h6BRb&mN;ZU~aiM)7X9!xs==M5xe8EgI@a|L$5fM8-`lKv@wi%mxN`>GstPG9SV ziQr73|5j{HKMHsJt)`;x)T+Gfeoe25k=4Kgx(5TextkPq+L@O51994ALT#M7NA{$` zLZ+?zI(2`wQDtG4XFYz3Rf z&Ai?f#*J|4qeupR>G7YPZwqqX!I&DY%DO8}G6oAFHpJ2Z! zg9%{_nVscJERZvd(MVFZ>Cy}Ca-;jkW;NiGD1R3$#YuH74hreQS$1lU-BxdQL>;X#CzA{`n_gbIXKWDAv=Y&7BSZTup%?#v8?+k91{u4e8e=`L-$ z+9mZyHnX$e*Swds`F!SK=ImUBMl^V4u;4_|mWst?DIvcR6eo#oD_yv0R~WcQyY{mE zf8NIade!f5HPOiWIMKWjRV@z9yFbeuQfAek?Is|^EZjhk(nOHp`p z*fh@wbK@3_fSgHVdWm$6jKXD6P&Z>_VURf4VZHMzjDTt^xbkEfLrlKTiR3_wRItoW$=>4da>3d(`rr#VPH;~-Eke1jYdaVbP zt7N5|^A?%KV~`WkCHn}<-sS*s+$-e=NBaki^`x+@x6``MLn~06H1}a~ht(gnr_ayB zrIapJzx;U<5Rs7$T%30*KsEZSb<%TTQco+rhSn+@EsY;JU=S*|cU;rnsw3YS4_{YE zu`OnJN<0KOoVlm^{p)jHLV5DlChqo(uBqu#bg+|u65BM+7@4kvI=TlZV2c=9%~Vst zC`bKf$DH#rBMX%pDLB2y*_0g8^4G~BIww82!mYL;Rtx=KzgGvcf8kBv>5C(=9EcRp zdhNYFV&1S7z!az&g$_!W7UxIwJ#S&Joxp8uJ%0ta#7ED&aY|&HTMjub`q=dx>ozTi zMdaVD|fAX>qN&DBr%4R+u??>v{kAi8-|6mA)U+m0}%`*QfuntY2S{ zul#s~>#fI{+2Nn%ow>JTz(^kJ{g`8R*m&+PnJS8eo0LK;kIR~*qd4EE?(JrBupmIn z+=>-j=!Qj%r6NzaCtD&;At25}%icAIwMf2aq;QofedZ>5Ai{^fxMCOIBt?~?uKfR) z`s=8u-mrTd9!ff;Lqd=SMG%ngl#fhVD-3mJn&_6andwZl$D#W`JSfclbQd z_j%tx_`}6wEzf<a39^6862^Jj(V z*gLMAx_!5A><1EVd*ps~lA_S2xezrI0 zK`gAqu&iO%%@;>#-K;-1M2U;DQek@DlixH`Bhsmw?qJ`jhQj$@9h9fkL+AL(c_b0p z275UIdTDs0TvYP+wdJ62l9<-{5x8S&Ni{%fs?$UO3{}?Fubp6~6nv7rJA7(&38r-U z8;|C>mg3iObx7%o_p)0fi;k2WHC5wTpw0o2vsl_qY zCeDL3Of()O+#Oz}9+aRLI{ebkY!osqkve+rX?2QDlt{)zK^Z9-(XkUM8y?jF39g(x zz4&Jmf|@<*C42T)bFKq7y9*^Wm zUX{1>f%qS%k|8PdCY%hVqqzeVNM~rt`!Fpl-_e%=<(kg!Bi%0QTB){vvJ!S#Z&@s+ z04*(!{dowPvqFVO5^y)|{_k$O_2wEP4Yx)1DG1H;?%O%aqx-nl-vg?uy0f&Oiv7F8 z@ovw3nZK-dUu*Z>iOZz^Zx#_de-pjcs{d~8^;&W(WIMd*w)gbAVj;_W$HTB`}Zlne?u(S3cG$r6}HKw}b6~uJcti#3zE_ifmw$*$L3OtHN;w$AD zM}6$qq*d-JnDU*%!U_7+de?=81^nL<>b6>WswNrz$$4N4!D2qaEaRQ?JBP+h$U9As zKTlKtN+WZZ9J8YVpzmG)K#o@5VqAX@xcGlufU5rq&U#U7M-yLKyBTvx-EZ4>vaO?N zi)5cOl^fON$|g9!v+b6byRwnxG}^)FNA(pRRxY2xK#_IXY3H4Y>Vd+b7 zRxYZ><##S(^$$x=f36qpm;FjFy#&6XtM8or@pE_yMH96fO9H~UD@l1v`Y=JFB0~uh z(A7nv21g9TPrLKUaK8zI<$cWv=j6q6;XWyxyM8;j$2ZVj6Eg5yfjZyHlkJ!YoFf7G$@%&_J+cXZ05RiyoAV<;N1bP9k^K(E^W?^VG0w591u%WLEb- zItV~>L8j4ysvYJXW6k(f@)Fj-4(n5c`Lrvqef!_V2E=ouDN6m3>V#;;Q`?q^>1A z*Jk?O8+s_VoBlATJXk@~2o$jyuQ15`#VBi$EZv_dI_PE<#`q!HL!&2N_Dqyitl|1< zUF-=R$g@&X*&^4n?sfMGPYY#GWPG=IRA96zXyY^VX1;S=^!7UCTy8`!>cAoMvJ*G0 z-^iKt{ZN{Q9R`Xa29bj@w4t@a$ZBYDUj8;r4OiG5ZE|D$P&?x;&{!Ub_^kiGh!0>I zzxi4SnzS;@Y?k^KJZP$V2)-X51#St~&1;~EkZW*B8m(f0tR*9Q=;(Qg4~QmvrqDF< zn)|QffzC~}E5-FaZ#N_7hU zXiE@Bei;FR26>iEdX5#(+gu=#q2Z49(z|Xi&k25M$1RgS>zK)Cl@NO%io}EIn87$| zwKrpFF7JL08-yo_#cdrhPf)pcKgHSf-k%7tH7plwoscReDFi+M@9GY>uDfd4w*vp9 zd&KJTkFGX*Oisib@rkvFN-a*uUZ+?8=%I@*dJjCl!v8%!6$UJEdW23bzdE&eiwr$6 zM*akrA#A5Ndu;Nm(B4)15ll^IqUppAeU4m$OuBj@qiuge$s=QRQ5DeW2=Z$p%L zRT6uKW$3Mk++Npew8*x)UCL+hM+Z`gopZ49ei}j9C@vc{K574LpRx7rpf{ zH=#GI+d1u9Ipv5eOzzMVfxg6cXjhinWuAa9KgiIdp|2356b+ahYZ)r%v5`c$h zV`B916KFGyb5%MpC}QsatXM1k`6=$2wJX@jxZZ&E4~P_tvbFzn7v5+d+Lz(^m9|S9 z3@XN307nB5vtFK}+s>d!28ujKKcw7a{e`wymnM4rcTTs$_-oPrb5tJslCCFNm<2}% zn-BN>GQ4+3_}&M>9A4*U@VvhQh?a+2tfmQi>K!@dBw~ptL+2k55?+&x8Sml!Z?Q_Q zuBUaFzVnfnigWpSvS3#JcYM@^6N!~IN=pgfRh9^~SNooOKH7WuzPo>{-}ks?&|+{U zJSg54Zvon<7MbMTY3#Bz(He&6V#g-MJl`T9|LtZNIb{u3C^e{Ydw<*C+_#@d42&F) z*!t?)MdfDwX!!s{HZgR97K`{#+FUbT_0q`Gt?c>ownWcWXR+Vi}z>n`GO8R zys7aev>U)f^Wb1~dV6Lx8ys@VZ>V+3Y{@{sx8eUyZcfbmpTzlHnQXj&Xjq$RdU8$l zFvWIdZw0xhL$6-Ajs5(yn5|YzZNtXDgKebWv^Un300hQyb`%Tey_WOt4_scZZ<`b* z&OxpCn{R7}UWB^Ctt84pynvc+fg!Uuq_l;#Jv;t42u{&-3Wq7ewny4U( z$g_RyxMw>_f?$!5$)eh}!WOZ1S*z1YYm%g8U?A16Wf*pf8!TcY!*WwJd=X2aoEJmKm9{1Pw*Zv^T{pNt znOIC4)sx`v7h{eoG5%`6%ZswILJzBb>{+oMO&~-YJU3j>H==OPS}eiMXV84-?slU;s3BXelsRlG zUv~r><)hF}SSI{G&gS?`UY)nNesm-VB>l7mwV(ru65u&wiu80}EE%8YL1?>Cv&8!` zK+fd+$Y@?EDZX5G?|WX*OEi!gN}vj8V;6Md4!Q<`4)7(<0KaOafd!8RZX~nJ!U*wl1cDt_Go3-dB^fA3?3{( zO*&!QVi)vPwZ5pqIz_a`Ww{1FqqqpKT=7|>(_k+x?K8ta@)Hb%^cbEWwa(Rq9Q;3? zdP2S4rXy~pDr5CKKFs&6tb^Y;W6tgpO1q=*n!b(~#3p-_?{G>vpTu;e~@-#4R8x>tYK;{*3d3kTNbA1$;S87lmYO`&|I4M~9+J zDk(;X1hFfdnh*tA7ugdy1=!EU_NW8E>PH8q(f@|-_@E=RznMIaox&^UGZmdj}&@qCFMNW*?slZ{;avvf`0tjoe^ju`%FM# zxs!%Jh!JA=aI?DRS1TTT8#N9|C|p_~D(O%?vJ;dHkXn#e<#NCx%xw<)FulyG|3rroj_o(PnL@Ah-vOE$AD&L*? zy?*V3f#!Lkb$*%D4_Fxa&Gtcu9*#^Atuf<1J;mx~r$ybXg_?Iq^9e|vtDtLp&^1aZ z#Re_tOuy5r`7Wc|lyC6&T_?&0;mTP-)JFTqt zEa}lbu*}q`*os-t9I5ora$Io)N`8YFowT7_$%5ZlNZI8|0V#?nZ_6euZ5(;Y1iSaf zHGpo%rQIs@Mwuqv4juSos!izM!CpDs>-5kyuY$$yvN_B#W3r||exEs9%kf;tRnl_N zIabQ^Crw0TQ}NAa4+3js(%B1YBP*N~ZqxwP@(M}6noE*6K^f3Z0GtpHZH{r9huU`5(4_LCyt_Pv`DN zRuy*hg)hF|d^KyJY4YkCWFN#BkRJ?|?OpC(-~8swZHK(aechFJ+;Dt|G(!Bm(1Shfbi-k9P~5(_GSdEbQ^y1PeQPkAM;+jl%EdI{B((wxSYupF4`V z6qBknWBN&(V0OxXiTeL$ly6+BE;|2>oB=%kxmA%`Dl~6^ zg3Jp1$Shhu-G~nZ%lEszzk6bQjc*}r2f(WJ--Z>@0$=LKr=}#e6MlTgl)Mq|`+)CR z)ra*Nvry@L5)Gt)*5kK(e6!x|U|qGkEajGPnC^}W8buF0Rtwl0S?S(Yp!H{(OCV{{ zc7U7vGe1m^7S|$gF#Rkri3TSyB+(cbI*O*P$Jqxlx!zYc%jIXIDSN+q@+H5QD}?|x z3jL6Ogkuwf+Ygvks|JaDz$`th^(wnYk>k)S!{*nNS1o0*D5lkE*t&7HVBFm{Y-i8E zdvJQzfr*3rbG&B39SgKZ+@^L{quqUe@YQM9cs?wHyVVivVIhE^GG?;t+Z$S!(Hw9a z6x`@QHn-cE=qkP$|G-ObwNT_qIgPyk3o*OQT0J|+sNoiCPP3;6-vCKXXqkN^uDm=@ zB&c=$9towLGtK0*<}vUJ(OzebbX6u+tyxCCyNUBe64Nw8gZ2ku_D=7YA>GJtzgo8Q z5g*j=Hye0O8rBi(4-=EB?>VV%lHgnmx<4R>sW_7Ez0XY6Kl3QPcgPGoA1r$EFXkkr zD*GS%=)XaZ|DfalzgOw-sBfk01Q^h>k0wmMJ`KXp60Ny0w|aYWzh=}>a0D-*Q-T0a zCV8#J)^;P&P1A_Ea-Oy?eq1nzB*PMWEfnCD7yjZ*{?xY5S+qgm@xWfj?F{d*Mj4+v zKci3&*pumBCs?@(Y=`=HYR{qCpo)*V(8RrIJ!uxy0hRhb^IO8eT7vpcAP8FT`1&uCbOG>XF{m(k^f0OccfBF~*4uXP z5kZ&F-L&X^pCQx@Mgxi5?X+!+!I(U?)(savoT8S?r+36qKe$0VZGPPx@s%CaxGk^m zz+GrRg1la(N{}bWq>Q#&^0YiP;!91x?TSj(g%&aSU%-vcuxXa$TCy?GfQ*zPBj57up{ z7@e&z&>C!Qyh`$ot@%11P4OOm{A#xMVIu@bdlPDhzh<*dmjIJM3m12|r9G7`{a<_I zH);=nwUWr-*3G5WxyUQbZ^wgP-J8qFtt%pr{(+$49(AyXo98h|0JD>U@Cob!@Qo94 zluq*|Wx$|1{UOvp*uDPTZOk2)pjDK>ntd!<=Z_9Yd3cMfUqFUBG5_ZQ77^L1K#>_F zmd2S@<$eDB1Vp)8Y;#3r6?ssWQ-4)7;J7p2t;=!nBP@p3z0%>~2x#%1(G@4%xq%OR z?`k&M6kxMqe_1Fce`*boaQssqW-R@|dA#>)1!v)@fe0$G-P=4SA0AH^#9=1lz6}j? z!_2viC2U*(j#4{o8_VZ$bgOw`R;yvpGJ$RD7PTe(rMe$q(7w>Kt|ynyGi%FD!}E!C z?(z;QQ;QvKu;GX_kF~z)U$(rIsD1`b!I$zxl=50~fp&bpi|0+49)--Z{J~www z@qp|}p#7nQR??G8&TyqxR*loruo*8h#(lpNnxek#SF7$m><{t9BbMH!Qh2tDLE~qK z;$W^&z&2Gs?4wDa8A2S@@GCYgM}lpC9K*wJ*dX{FhqsTOO$6bJOMciDX-&!t6d9k} z`sca?r39Z_8lLvZwjItlIc$nGE&uyop+Mkp>51BC7sP6ua(O{eWfQ0C3D!en3lRt0 zA=N7EH5n2xR(iF#O8n0P@!GWglp5n$<2b_G$Q8~^h8+#~lh;{a5(NFj4Cf=?!ApsE zADX`&gQR!molUKsLLT5tzh5CdM@firp=0*lO#i3DzK4CwmO_{} zXw71Y(DwuL=e(oWKKDZp*V9kf*j(a_uvZK(Przr;<6G(--y@kg<0t?9mCb#T*;q2PqW84k3x>Ymh|C&>? z!6@=&y{p(H(uyP@-5Tr$Phb=ZGGa;rNxr1Y?W--QdS!!kR#+8-r={6IP z=YKYq1b{8zDqz1Q+HP+VFm{Gd=(l@lEdDW$d=ECZ(~wh4Am>=ZX}Vkwusgok-|?z_ zB**moD=*b5IXb#_Snisgg+T5cN5@O;aa7pb_U77A^FILcvT)YFERB$!D4U!6U_zY_ zWhH<1P13|dUzm!245+4EIzF>{vw=9`M(WB&grLEyu2*-+etKaH7U@2GgF}8v@FTTL z#m<0!$hqt>%swu zlnA*05#(n4AmO}>MjQBET!*3@s4^b|g&%!nL(V!hU|#JPGA=v^4?rikKCf+?5m`3dv8Z0`O^y>HD;k60+Q1KTQr~GpDSG6 zLFRvys0Dw1?|cD|@77MP=7P8y;BhsX0G0(m01s3(g=+2kJ-~3av99GUS?SA1`dQrX zhsRdH;`Hv(w2&?{WB_i_v3}}!zLXp=YkCJ?3nHtgy^XzhumkSv^WmkLynNU?>PGxk zwkb#PzFRH)Iom|!)OzBrn0S8iXX@g}PuN+kX(p-RV8;|__d8jfFw!pNjd4@^e92|i zm3B0KZ#jw64mFI48B9=?8y1ZMAp=J>%@Lud z{3*>6AbKJLV}xm;|KxZSqcJ^?s_l1S?0H*V&MH{wZ~3c-3mH&y9-} zh#K=Lk%w}1{d&&cjPK@HKIisqg7$WHGQzdOH{tJFe2{s6)*vD0JwhA-<8`fHrNE%H zz^rBAYPd!`2vyM_T9W?f%>hZjO=kch@#&kz$&wPflK#(A*FF9Qcmkg0}l8;&VY z3)&l=eCOC&ejAzBw7a`k#L}_8+uv-Yp>?*- z_g@#(I*dq3cSlG(H#8>BC5rc%Gfc1Tye2P|A^tzV0QuE2MQGIINWYG8Df75nv-GdtQ4>s^>qVXTUVjX zDK@0rUb9$4l71hSS~LE{V{w1&)-SP>VSQkifp{O4Z9H^c13rd94nf1-e@7zTwE5p-YrVr* zVHXdvgo}={qcxaD!&{Y28J#119y;6zqLSY?k%4R^wWzI>8rPGb40j2VGg5cLCdXg4+8)6WQ1_#!3_D4*Fgh&}X{MCi_ZbUEoieIK1Lis#veP z)ttIx=g|teOw#HEINIW&!Wx9_YfiI7y3;2;z%xdeRE_ zIkS@MA)xxZpK!-sJp#<4f8QVfrU1sI%}%3_eyNMJv{5wAO)b0}N8SW%+F|k)xyTu| z25bF%w(O%e^d)0Q3i77x48C@#wZ5_kqcad}5Io>m9YpsGuf*&%g+ZkC*kx*uGk)>3 zlb;v{yEiZQq>8fs-cIBAZ1#62u$U$?-~{GhB|8aFH!?;Nen0W7Jl`d9zDp$ZyMD^L z1|O8rN45jx_4e*l;PnT9Q9S(!Zi_6xo&lw>=Tnf$!JO}8Z+>gc(q{LWuJ!}o7}j%8 zV1|r9%Aaycq_W0L+#NQ;_+Nv*^IgFmzbp~v&U!S<1hOImDE5Olarq!&f)aJebRw1}?@r^NG^|YqZZDjq>5HpAqx?hU{C?mE%PCyP|bPFp!HPM42+ zvn!DdT*{_P0dLdT54n^+W|*D}+u`J+xxE=l-c7oGCv*h43i3FU>4^t`v4sOHLKn`h zLCieg}S%;mCAu?o94Oq}=C5th~q zni6JVKeEjTkefsP=A+@du1QA727znvn0RXw*JKwTa}qi;!tXMSo2t}YU<>q4T!&ai z>Q7O!YFH3XOW&U>V&|MKg=eqJ8i|hT4^r>05cI-?pf1!|xqWUYVaW2qDk~}UyiT3| z7He}6>3N!m3-y(0KLYBZbEn*V_~oDC{A;*%bT_#EFC5H%_tF0kB!S}AG>iA^vwM~M zcu&|tvJ#*U(uQ7*O#9Iw6}g>{P;GW!XtB+8LMTt~o^$xZ- zzg&?)1??+ddf~4>2WSHKO8P1DKMo1K0WP2`=yR*sJGIxm<-Z@(AF8`ic#uecw+gyE z+w<96A!3nm89LBgBgIbzN5ZN+urix<**xj`Qc+XoG=;+#Lg9K$|NeTlZj9%KB-|6a zEN}O(ulhWMk14jtAVxfj)1M*L!G- z-`s^Q_Vusd=L7v4+W15|#e=xpT(d;Dzk{{^xk#D|=t;|YvG?wW&3lxxob8P$@weA0 z`MleEY7$!#weSK|n1&5szounWvMtqjA(cL^I$za0Gm@8?1Q^GE)2FytxqOw^j3KFS zc6ZGhcGLmBQq#BP|5G769ZZ}th{eq9XCC6yrQm(4`#Vrw$ zQUZ)N(BDJWU;e1Txn!Y+QXrn5uxp&ukJlLe>p;w}+~0H*jzx&-z>giBt$~oVWWA15 zfj*8onlI2$6g-w}r^DwU>z*4ITAgZQKg zHZPkd$lYpX-;~7B8u+{AV0d|Rx+6KV`#wa(#*5;7E9>vqNG2&Z=Xy5Gd5RltF##Kb zTIO2}m6*T5T;)GNVpc59yT{>7g&#sBF8&UsBwz*> zM;*9xZ=F?tSKq2ZO0;K?7XVRylP0(6@X4&)xd>?c#_b0bxquw=0eNvw=Oe)|Z~;Y3j`G(Gie7v@1c1w1*x2mneN|j9dwuYycNh+n=;3hXviGv2W{A z!VO8_wM!q46iUsuBj!lz7N`gRw)tJ`37R&sd=$5M`syun8=HE6wc<8W==i9qdGqRVRYqD0>LP$vw*qrr8Fp(;`5=+csV=1M z^k3G^C1wh{kVyn7bWoHrMGZw$#PH@tZy)zBYMGEN;kRa}&b$Bq9;1VzIyHX&a+ZofZh-yJrXQ+zYsxd7wFQ`ITdmpH1%B2s@*5a7h9=_N)w-uBdDIXW(Z!AU z@Z$b&s>`-n=;=>%I4s#Tyr!kdLbHn4*~Spf7ZG00(HCTbu7jVB>PM=^3 zCIGeCF5_)+0GHsuPO876=8%U6HpmSk9u!Z5lWz6zCRp7%+S7h z?^9AmuIPII6^v+8NbEL!GGZg5pne3vw%dfil1Zbn>vJu)a0D)3;f&pQ5GA<8C8cbQioRX+`Ge)N~ev# zbwH}oeZ8FIr`9NPZ)E~slg`f{ zs}&BY??VK^J;7A?Q9&k-j>*Y0-cW%iv!;1zf%(*qwUaMmXW6iTUxF}Ssm#|A9cHzw z9`-03rLqX$&yKf+zb8mqe&6;y1IBmd2vgMN1nD&4{EjZ4pLI9>(7V^)oxk_eWvp;F znzym)0j`$HC@L)8PK-_pAddI(9XN@n-@()5p0xi>Qw?z$T2Fr9&aT%grM%e2CFlCb z)^c7*=wEKSemY1GzFq#-yp{!wjyA0xhkR@rtOLzxXbH@pFg{Ox><~3Cdmi!jM?hkd z#CR9rbTX}y$EjT@9c`4GE?P!=LFc&=g#p8xr7;Ei+oY{!XDprPJLf<=`EClYkU*4P z`?|quDBk<7{Do*ZTRV#7`#jcof6|*rIKkd*)^|&rf$~*1{yNj@2`K}iK zekQ};F?-PBnJjNWn(Q|Mnt5;b9nTNF1y^7go?M|eAE`+je(QbU0d;Q)Q}jB`vZgr= zFi*6m=-&jbkO3y?pu`Epjoi(ob8&L~<$gtb*j5I3H{E@v`N6)Cx6~%?s8=)KM|I5w zZG#-@lmx5>^s(2;R0P(deIMR@c=Nnq3QB3T{u%l>Vu+GDxJ3i`QD-se)m5)~P=$F~ zP1Wn((5y9LVy>WoyadoeH2|L7C`T6)K(So#8-P?M)`S@7Z8E7z>&rrwT&_BwnSknwhX(rs(+TBe?%6QBe zr|}JPhKWBe3sT9gm$pQA9Xq}tQnMP?-#x3F@s0ZUm1K-;EvH9ilgHp|M1HO=N_%-Z zBgWpVS&wF}biQ~d7N3en0(7jSNkaKfO_l#xB&013 zHd94@n*(ua_N#5~Hv;n}DR4M^7vl3fTN$pJ$;Se{I~{zyia!jJ2p?CrxUpU@`fU*C zU$t3UrLo9TMRr0?jxy1a4Bo*x{)Bs!94r&u?7FjxtYxO}%@ugPonlje?04Vf`KwcYAS!iaGTjmZYwZyT4xL_h=K^kBFs)3=>kwSU!sCMTgw< zmUV{>?)%?(Nvsl@DXW0uww0_nOKSW>4d(Ds6^OB4rXy*1(^gc&E3P_@?NL$G*&R`&7tqc5V7TCVvGdP}j^0;6J>@HRsQDHAy8)=DAzuA&jc4evWbwx;Ik zzmTw+pR7;Ee5}mQc4|Wol~@{RpFBbQp?SU6p|IP*l#4l%JSEq~rqfz0NubPt1uPd? zlwxGi{(hxlNm9U;mR6$Mto;?``J&XfZtwpQ%SY@#k9qW%v_dzTv{f3Xu|m0#s;5iy zl}D%|MV355QzBHDzbb)k*JHi}W@^7L^4nTq4fG&&`0?9371sSQyOo)NlK zTv}i1)0!x1gU}d`t_m6#=X_&#?h0_r_h-08V@1)cOf0gt&`f}(Tq*2ajx^or8 z?JX|KH-DKS(~DPl!?TU$n8uF6ULjnd+x(RcVvpjudjp(EkrDZzB6=qKCI%7fe#F*o z62n30Icwc;vDL;(5@NHa zM?68jzA@^k2xQJ0)w6iYtD&=$AO3pS-($ryPucPVJct_dCqnP-z1-I=8j)j@N4Xro zJqojt^X`-aJ@+cSpErOQx#~6{mceUK%kXe$Es=fPv`nu1GuLxX_+bN6;`756t3-uj z&VRO594&5#RDxoh@7U|gQ!fmyLv9U#)<0j7Q5QU@7WlwYz1xPu0Fnx&5Vvo&J!oL(GCU{)M%}U9wT>= zL$NK;W{yo+`9^%&osj%Y0-^3E2l{snb?~ngd&*2MeJYidu4yNc;BbO834L8eDIg(IyOR?LYG-)9#S@ta{NLM9anJau)$B%zBrxpruCv6s)KiAwP z0B&`)d{j|zeg^C@Z%;1;RbYea_m@5K?(1`1%*2%Fl3Ps}BC_?0dG^|sFNe|R)u(1g zlvvN?mG@fH77(KvH01#=fBCixxuYcpevI>ljxcX1w;uD_%lIL44Qj!!6^4g+g-LIlP3YF-#*&x{8kjJu5l_jz9(q_Zd{h=RnwmPHm#f6nu z{$>Au&1>N&5j41^#7_&u#!1$OTLo3o2aPW{h?%S8)H0;C?IhG_d6+i1o1%UpuTVuk zQWwj{y`$DL%JdzB>l9b(CxIz)m<*UFEJ~+u1|%B)xutcoIa`klH~Y0LU^=b^0Go%h z4SRD-RWj0H*@6(Wz2i$Cv3x#DWsF~${I*)+wj|Xpu7IHJzpq8}>le#Y|0lQ$@5N^d z(ZQ6yJ6gi2eS3{^fAQ>>Bn%)`oDUKKLb{910>>5#>WSj%Z|coj9+-=*naYK{snpY2frT|xMg+SU#sM)NGiwNavL@C{na2jA_e@wa%kKPEbvII z!6M~;5)~oJwa|taykr8ThrDNrK%f=ppGi*t*&-;5KKO1og@YN8bGqRa?ptPrUhyqW z=BS{zgoi)DRa_~@kSDW>%3-qoO3K3kG}R0yJiNYAMODbf<+n+T%-L?9Z#8~fmC^UgAA_60 zZ@5KvK3f1Bi-W3$M-?5_zaBa!<^1k-iZAOKu%krkDhOfFt>7?3U?;x?!E-1c`$n3? z7Lq0kmp40kPOj?fvDZNOm2b$)4hSje@azFaq=#^Dt4X0iMS|+rQMBSMZ~i={A}$Ch z_>6AuB*(7YSapLyNtukXDk+y>>-hlF(jW2Kxu|abGtegaAGNjNk~G=amYla^9-nFL zk8q3sD8GMo=O*n|XUz-Vl66P*YAx(=S6SXd8@i)^mPeg^VW^E8YxSpB!v;3^1S9nu z2gVN~+p%lJ@#Xqcbk9^fnxSOJZ$LYqWe@1GSthq@`v^FZNFe0O1?2+kAztD9Lmoq+ zkC6mTxWyvwL&X|;-eHCe9ITWGy`abAtUfP{}e*5WoYpEH! zC)fWxI8df%0x^Mwj&tJN4@k6Zaz~+u0IMqi0T(&$aQpAE0|B4=>M84&krA>&i77B| z49O7rp-5xibS=438y!%PPqCg8#ZkG>Y$&D0j&ASwXXpyQ4hsTvKf5kZvpLq&>a_HC z!=LN4{&X+ydb_>;fk^^OqN=8AVS_r5_ZE*gMz4Fev(F@8Ou79l&ZKG1ZO4Y1Csb{&6aCyDA{E>?z?Hb zjjgtwbnLE)EEU3BY0215Es1Vtv72kwov!N`R zfR~{?@p?xn7*8?-ZCp$rnU+TnT|4pkgf>c4DJ;ub?{HytHjOFT}g`Cu%P3T z4vJl#|JCC3F>lAo55P`odHufW(?p88cqY(sK3y}xECRNUYIc7wDP{02Wz*W$LU~fX zfsv(F6v@x9)SW3pK1Fj*O(J~jZ!G;0~<bQoZ{{c_1nyM!@N{M&@`7lj}ZxAE5PzT~S%0PGdc z%AZP0&8tZn!n9%IwokSvd6pyTX)yLm00#tUwt!i9q2Ch~qiB|C!L1cHLcLBBiD%@(Fns*z<#jLYoQ#WdQL~?z1*RDdbR;2n4nbUlLt{F|C z)XiIpj9EuDYb2bzaqM*Cof$jj?=C`b+m-v!GcOq%HqWW)02?vasUi$x>Q|(k6VwSh zDPh)4s@Z*qXyoY0yY(|laDNDj2oB!~$4g6wBXXvH3qp!+>jImElBGn$J8Hn!0xKI? zdJqHljd1MA0CFUX1E8{Bc5F@?ZhyE(guqQ6^Np`wjalx0--XZy!4DA&m5>BRU7om} z0&RX}N5__SA!c$}Gsb;4cmd)|(sV34!C6)i{)Sm6FBQ}ZGk-1$kWr_ggnVW;qj3j76>_) z==7usREX!nlJWEXqp#8$3a`+BXshkqbD>Lfj$xO)e2lQBL^jl)L;i$BiJmvG@0H9| z)d{Mc=hUR+!1A;vP2ecbfl$(=vj#Y`1;b68dofCV^8k$W5g<4rNA-M}N2@*aj@DeE z#Ev71AxbVMCS4cM^e^`5+`N<5Q_OLg?)?r}iVXyR;UtP2$Aa54(#vJt(*xfwyTKMr zP@qCqXNzp-I7zfAiDkm#lvL9(N@~TF|44LFD-YDTquQWG++vYiE91gEF(ns*?m>h} zCW&%L37pZmbGsS5R4TPD*z#K3byQ9TQHbANJGx>l_fsQQ)V>l)jo!NW;zp!ElZL_7 z-+sm8L&4vrcdl~6fgD0ssXSzi|GZ$nqUNlMe;rp2zYmD~M{9M*S+V7(E12@#gH$+Gk-{ra- zmTa|>Y5blhBYmg*m-gVK)soCvj`)u{y((sET`XqdT3crFZpZ=6Z{Xsp^nCZo)FCCB zCWlZLg0MXHDN+X81G2*obMaw1QU0N^e`Zu5)}pMKc$y|E+H8GbCpIMd3|yP?B%$netxFNxf(gk%krmG-56c-!%Xl~V7@1_#_bc}#a2KX zDaSk!1s^hwM+qBf$(7w2d`0>L&(?o#%&ZRfzb*;RtP|(SjW@lR^oPi!$8JSEldlws zU=9N|x&3By#$0Uv6-cL2Qlsf3VnFcZkRigBI)e18CuspTtUYlwdMxrpkwTZ3oAN`3 z=XP3(G(6QAM=OqZEtzy1B+;pgubM`*f2zMU8p%=V-iUTd$bSQY2zZr!vK- z|K?a*loU#d>yE^$e}7nb4FPlt_1KoV=$=-ipQT*?wKM_P$uxwwW#d0@j3+`Z7A{(# z#Li@9It3TX7wnh_*dWt*|32NSAf`W7u-9Ug36`AyhURvT*wakq{*~AEexg!Tce_@L zydQkU@oPhr^AO7)#IP=B-#ufp=&A#(%f7~zYJ5%!tpAvwc~1U%^a1atsxBzE(5HBW zGtA7Od^SZsxZwR4xmgZeGMq#7M2+TSfO^MguCRmGe9&)3ZF^tT`-(d)M3Frl#+YcZ z`jZS*MO-o_TX`E>*X9+YtDfs(;C2tkZBNg2e@}7S>5{8s4%y{Yx#C^5}Ia4NjmAcq}GBg!b(m@c-Cnw zFY{AvoLy7bE_v!QIc+3~w9H{s^GGjYknj}p1J8`IaAdyp*fyY(0R`|Fyi=yibqqq> z=FNP|Dp~XJ#tBgWg#Z3a@J*m4Z_8`M42wkhO8G`iEndz}slts*=u<+3*bAVeJ61FL zPOaFf>p5;ck(ZnAHZFW)Xq}q| zy%_=XpomDGoc|}>6qpZcxr)dCI*TAgUrCJoZcy6nznyqCK~P|xBw7O!R$c$vc1)AW zdFrls`5s7^+xE_b!Lg0Ui5%3J3w5`xS}b}Xx^C;(5DgI1w?waia^5(lJj|XtKwL<& z@cvvNDTwurv)%=Pf#Rl2S2&Z2p&CFQDC z7-}gE|44JtD-kI*91JdL*dyL9EipGJ4jG`-LS9@wWHHyH!m z@nmvJ(%=!h4K4&pfJaWS{d_sO9p@{GKv?NuAW`f`u1Gef&BgD(0-RYPCmHxgn1^)@I#5VoS8Ehi(8LgNSA4r7RR*)L!7pCSb4HrO` z>WXKsDQj*6=pfH7K>r5l8At{btQ$T*76D^MprL*WunDT7UbP7o#)|FYM8Fj=qi;AL z7ugkD^Lp<6pdfKObp`1@&pl4P_W*Fh)L|dM*e&VjAIbm61$ew~*{|U3jsM0K!i|0P zov8lZk4bk`t+W%fGYJ6@U~3~z<>ycC6(&F4*E|b+L#~|TU~d)1S}LDB9wxMKhxUqh zo{$go|0C+F1Dfu>ho^)hpduilfYL}zS{NY;BHf+R-8E9-L8YZehjhn)(I6`2fJu!Q zrNrn7qx1dX^LxJU{@gvEdrzNx&pr3vVM&-r=SL>1C(kS_7_M_aXS+_dQ*as1hI(na zsw1e?CU2pRW*I7YoQocLW0%7u3i(31o#}nNV3-_cMd%}8Colz#W|9x*6er+&EYP!i zSM0odJz4gahP9j2-DH6a>3~B<4g~|Ilg@<1rSE34{3$B2GIftRXeEbp|M}?gF`%#O z;dO|K;H)#*@KX{JgQ5Uy3RlkcNSfz}09_&(zU{YuB(J_;d{Cw>HdEotPi|g&Lk%l$ zWv+=brt+OSIjs1 z#M8n z^TeR3J@aMgvGz%0<*4O7{S1r`v(Zq;YM|3RC6TW~%#do<#Lc!(ON<&^cPDVXpFS80 zPST&Nq^FYY1vd&;pBzexd|08&F4n`#>09>H966kYs!CXLEQXrSAE&7zJyCV7An;aj z#nEx0N*}(Ymeqg$pK=PPA(}+%yY-IW2c+T-u;yA`5{?)+E9lMPN}$qvJL}^^)38@6 zb7R*3&@1i08*_p+rV3z{PG|OUgThUgtrc>*ncQ1MHzzXTtaI0E|w}$1}&+j3A%DyudTPRzkXCgriR>9#we~eVo70UPQnPcYIM~9c) z1h1YrqUAWaMD)zOMg^R&E5Z3of^Ms#(Jw6XG(IKAKOPqFRQK&Ctsqw&7Tea7bx_u_ z>xjEu(wk42^K>R3Q`&AEH!z_5s+QL8e)|&p3W@*jJD9uNjJ1Z5QMeeL3Ks-k;#d3b zv!#cuR}K9vB`InZf7G}Tt5AsJ9JOmAy(&iYSYUMhh+~_>p&HDcgLf2{)cI+Q#IIx-Ut| zvJP3|7eicaC^K4I59B58*dGcR#>Ln_zsJcuaV_7<=#*PMQazfxrldV+MfDrk_l zq7xM3`4$n~8S3JBx$SoDf{k^eT=S`RRvo>KP99eF0EST(VIpK*dz^Ol-sMODWt zgB_(FzfeetbBkmNPod&0!cM{DN1Smx1y5Yg>yV`=QKyZNb3)%5oEyX2G_|UEqepkx zyBIPy3G8W12R$FnG9yAseQl=bLw#@&_g5o%QF3&nr5IR4u&zBcd$)soVr{rDAt7F6 zefeG1u`|>qVgJppRI0I`&Md;==}xwDD{j7p*ErRVFNu0m!Nees>5&TbvS_y{WaXfH z=XUb%9$em((IJ;bA?uB<`Xh6c#-n0r$9Zet!Z%7eg`R4@&;%i(BpjWI`k*`_T*U6Ksk3= z#aY;|4E$TELq45O@5c^B zh7(gcaJ*lsx?>c{Tx;(Nb0g1{NiSC1a9@jcP1JqzF z-IU6!ev+*suJl3J?rI2b@e#ZW#+nW4CATlJlPOniA8rs6D?4G*9c8&gI(6tYAJi~(%6Z4epix5aZzxJiQebm0nHjEV%5cl+Q4! zQ2xu&?XeJBjbPzEZTE5L({D68DtyhrPEoEG?2 z-qXRMUx%XJaE_aOoJ6v41jHyuK!8|*LVq}XbMjcv=HMT><9GF*I?LnN?({$6 z61_Z!fCnT-fEVH}CanWQdI~L3k84;YWX6x;A-MHcmzW#Au#laeoH8j!zt8Ufb9hg> z7-q{ag5t(2>X?HV`!xf{i>Ql~#O}$(ExuzgDDr9h^<%CDW=Mw|*#$x{OPnsAy+NBA zxcl}Zqn7M7WU;4cPGKJ32WzmWS`ek(a$S~Y^4aRfwJ}olM zQM_zQ+z&J;q)K@Aru(hK?Q((^%MPc(_RqR*^2O7L*FQ92V>F){q7|2O*&Lu%l;->< zxAR8mxE^=u6N>Yeq<(F!70Jgt6%^u!G5e?8GA zM&5F+GEXJk`|U*<%fXm)a^C+;;hxpZYJtzK!#9H!kvnRn-w_16=vGIj}|LcH--M#~hR zD7Rmxkt=%DwR!Xj97@zZ4^x?n8*nZrraV1to{|Msx))djI5tLKhlqJuMI}@u4X^*M zJUO1SI{k>KrkmqK$#{5$3B+li$g_6s-p^Y%7= zuQFb>QHrRt(7#YG&tWy-DZCeBvL8S*AMAFz7_p#QNk_Z&4FsB0B;8Im-p46IY)4A@ ziy>2WTi59WvM=z&<2Jkz%5kzQqe&}>Ll>>WW$0}crQK9>WcEwM9qR^(36h~Hs;-y> zgtMC1B_%b3C<#v_mTQ;w?RVp6jFTGAu%cXI@h5@l@AyYmty-!%wgOmhzNVij66i-q zx=0H*2@c=v3Al6b;hqZ>N@JCa2c7HZFR<+V5t8 zmp$ex{)q3|879adht(g9e~q^~2bT*x%~&ED8=DC}4v9SB9)=`taXxv>QjJSwDRGov3@yH#p-9c@y5rD~ zXPS}kiwJ!NSVC`YurK0*cxv+N7TK#83UClQ>20kim_3PB;a>M514pAJXVzs7IyjSE zjJ4fwD&)i4ll0Oe5#=}1hXdZ2_Z)mMbeD#kD6&b}q=U^^fr4Sqd&9W+64PjvqUwHJ zwSoysLfq2Yn?w8`v})Sl^oRF-XWQ(x*A711+gK^Qe{1!3FM` zho}v=-OZGk*9c#^rO>Z&J)jW zaMo~sFz&uIOQ$4CpRSSm1m6&lz4|u(oZvAOcS`Uel0re_6Q^J<8&{r+wPD@qeXAdR5eNny5Fx66U?MD){ z&QDr9qdZmQ**%F5ZdFFEgvi3KEZj^}rFGr63_fcG8<$nHu0~$MhtfYwwo7)vO(kxg zHon&pHB{H(I-!UW z#H?0cx>_2_GCtkm-cKc``Q2NQ^`Ej{s888&CEINpo>rZG@c6sbZu{m$28kuXbVCE6T&U%a_H`{lVu+Ft1{?kNL)f@{EA0aC^jTj(HE+VgUB-N9B+`Qf=X1h^tn>vcoqjQv$JWX zX(dr!iJm=lli&sAs+*I9 zy2{u6Ber@wz)-7OWfE$PNHrp9k#)U}k?Wy#xW~Zp#pfEbA#r?0p4G!*DS=%7?1v|h zVeeX7Z`eHWGdVezqjzIT{o)aurP8zayj`Z5oei}+V}RauI<-{Z?FLAlYIr^$Ts?pC&$d`f1#S67LiUo}n?X3~v5%bQGaTIH^P?;( zZiGb2gyOKFZi0mE@Xb&C>}zn4jIJ#Y23n@!rY6pQe9D=MPaP+qW_wv6d{c1%@5391 zVi0hIEPs0Y(|Y$DmCs@{k5`cD90R3whc7HiD1kp#w(JQBHYF-O)<#C(7|(hIcU}go z!*5ocWbmacZv@(p+(!Ixv?ZVTV}1U7s^gCRA!^kwN$gnfJKv_CY8%n_F8Vg8ERrr4QY-W(xUbvS`iShe(n#y~hC&)% zneSN=gWguI;)Zv@b8ZzwOV6tLMA;2cd9cMY%!cF#x!IJ>SK#;9!7?nvZmd-)X3Kd# zZ`t3KEZrnRV1-N7Fgs%`;s?vOS-jP0dteoN`usZDM=_^5&FdRAI6T25I(ev0*(e+7 zkiR~DtkH2(5*%a0niJsQhRqxI1&?Mu+@SJaXR0}SRGPk>q){rhPs-}hTJCGQ?9Fj=}k)=#P|gve93U&0Tigqsq3yQY0C zDjv=}<*^eHC9d0+!t1P=+V;lFelGUvb-{-Vlf)`)aN85!V71S(;ff`G$ACgk9*&@A zB0IraKNhLM1Y@L6A`h-*?T^%cQD(ISJpvMdLXbARXnD6tWEh{H=?!E$~PIkFq0rHynMxKLrtjI>kET&^+ zBgc+#v6;}9)$y`Ng}CChs}PYjMEl{-^L&jjjn#s>0yUO~Yo^EFM0q!p_6ZugnZFe7 zP8tVWS#@zGeu-T$uN2Twn+?%o1m9*YHIq@43Z76*q*BU=+(HojQVvT9vV3Yh!ZGdZ zRh^p}J1f-wjB!Q`>o^}0fD@)ID!dLd|7z9oNuI(qQ9K+c1POJR_v~L$ z=E80!EW1vpfwOH7j?+w!QaWsA-xgG`nEW_INYU}YZ*Qk|{29}AD62e!-%7XkE{3m1 zQybmOZ+@?3xD(L&>cLl8UzTp=;-pBtxS4+9#{!}CN{WY{Ug1?Df2Z-;kCvp|V>^i- zCvjk_mRhd!(dB4*e3Q(e8j2n|;XjH`#UKnwU#=nWB%1}nxQ#(`>%F74m4LX5rzf++ zbkZP@h!>U=F~}mJX6VbZFZK@>(tIDKl2d0jW~H5u>w}NQAVust4%w9!?x7y16)19y zm(pDB!xop48`zE?juJ$P)k}z$+HxC)mPn!$jP4abn2fJR)22wWqsD>s8JBf0!On&r zj+pk6tRBq#fcRTw7^LT2RuVujQ>HV;itM;U%<`j%Tt%l@C$y)v_CqF7x^MTpZw9X1 z6UpM{Ob_pJTF>9hewpBH1@{^E%FCfo9d+s~-Jf2}s!f<(NIGD`=msJ<6!UPXh3k-= z+=MxW(30}hkyOF!5Q9_KKyv5CjL+M?_dy^daXgXzscDQqR}s0t3Z zz8?vqo8KKw5ij}?JU7FHREL+jmcTvAFGk9C<|YHmmUB*LEMIX9f>`E|qLf9+M7h z#|~H6ayvCt8Bhv)cfiyR6xIVpt1Vxl9pkNkeeCcRM;$ipPp!iL8c#wxaw6k zES6IVY0B3f{@;q`;^sRFV^?v>)KIw*vwmeZ=+agA-4oy6q z!cS9n{DwCs#9rUJ#p%dPu-9g#u73D7vCY3QJ9`!2P`_22UeFeL>c!6D``?R#DHdEd zx4TITw#c_v+b=<&B^mFIN6n8!tLk_Y-}B~O?-9rBwQW*gx5t7F_Mhtv1yvYkD-kF; zV9DWM63}89E3Cr#lxc}D?8t4)mM)S+XVMgbe(BDE6@ZPc^43hAoLhH$cB-KCOxApJOHpn6kD@czV0*?@8 zw7+QNtSvHxeX`i>i4D}?z^T|Rhu~i)*~na7jUE}T>eTcV8G(GMXl83eVb>xb4sWjgeDLgyj73A>ZC^dLTf?2+7=Ice0seU*65N!TJ z^MEy|6_#d^&5`43mxD(V1e{cN)!2U&dYW=?mHIo^-U{h`8OdYc=RucKEi5x3gNKag zKrW-V=vz#EYC=y`<64#VXzW1I`I{7>FP~fLzq!#F+aQv<>0cysKOQ*|5|?MNtmD2c|;Nf_R-y)PqDLOs%6N=Ww)rPFw`u41{% z_=GE^{;ouwf`fzkSHw5N~pbyKjI8Yf@Z2b1f;IG2vYGoo5USkoK;P!M(J0MWX3RwdGVdrnXPh$qxg$|GK!l<+{y2Izh#GjL zKr&Qt|Fh1+PA7|MA-zx3RcJ2hHx&BoBUuGSH`q5}ml=e5Rx~<-NbpVI%uzf)Y+vFQ z`THIfgySxK!M+^1Y1qy>S(sR(RVS0)tj&crm<(RX3>>pvh;=`mmk@txKqZtIZDQY= zof7YEHGC2fWzp^9j!2qbnq(g>btTIq3GSIx>0D4meWu7O~@W zGwC|y9J|p19I#1AlVN6f$!M_4P8krs#qIk6J}7i3Vc@KM?5IYryh@zR<51R<{Swmm z#^~e=-U?f}M)ObI^~fh_7Ddi;-Il{Shf_ZHVigUxa>uh&Z0T|q0xV}aUqOtX_{<~_ z`@&R+PmzoT^u~R5g(8JQ3(-H;(z=o#LM+*&&Z32Lq4gJY7L&g?csbvQb?MT#vSJiG zxAM%nk`y!qfPF2Mo-|-V7QX6nb6~3KRAn|8{wQx(9T+uKRaA; z7+9OgdbLVr{&dc0aCty)bG{T`8?Pb7>`k^N^SB|(U4(|v8PyEz&$VNJW)@5facR(o z@O4oqAM(cm%TmvkO&zX-a>0K*dBY~msV4aYXTNSC6BU+#%@)oFpFOj4UiN58Y+z$Q z1V6sBJ*G$4ft@O(H}HKAcgo1#6s+!e_`N7K9cwMSqjLxG9(ZBM+lB15-_As?=9qHJ zKSMTKzYl;LPBjEZEt-Vy``<>|wEBj< zt#OmaARK$Vm_Ek+LRX@K_5~o>|rK3!n;!7}uwGh?d%j#H>fr)uSD`?M!^T%F#91$UOYINkgQ9}eD|V|`gsZ)>m)4Z zk3`oxaaMYnm=#a|N)*=AJ4K?&bG`WiPB3T~kAAP_-diU&iG{nM1@1Xa|KiC1+UWuj z-D*Grp=&5*ob?3E%=JRD>fl*jw4i}u`FbiIP+3ANNwqNw=P$e{XJ7>)j1s4C3)`R0 z^VjZQNZ(F=Yo#vEI8!Q2u$ANq0y#zSiA-ktPAbd=taKtai4!W}QjHj14dBShkcKCQNxCtmSzK%4RY4W|I=4dUjshhrmAl(CW2QK)F8$ z<-)d~iYw*u82+9r!lvEV+K@D*SJ65BdUIZSJoi(ZNuy~yP1pdry?MXKRvy&6x8hUH zp5(w}h+J2}!@MTL)0(XoGt8LHDDy}7)<(398{9l#xxc{fXlM!-E7y~IG^yiAh_pyc zhG?5T0HO(nu~X_a-1#u69~!1(-s@qJ<0GS8sr%8yymuv2p(QBBlMi!ShJES=UE0nM z%z=c4`S)C}Fne>3;a%fS>&8D8Hm}V2;-ZeAn-KU;d2|;7cz%21>CH_*qBZRwdHKZo z7+j5~Yq8UNJRa&e;fr}?G)Ai# zyHHFM`alnQ8zl%h@obAhsQ?AEwQ2a zAWV7@F*i>%gs{KoQ+u?6=8=K#@ukH5pF`=Q(>4)NJ=G_SOYRi&KJ)Q0X6G|gz=rX% zx7Ai}f^zc!zwcDl&%4!5BcvroY}aS5HiK{{1MhJ*#J&!L=H=_e&KyUjj(RG$j;9d< zqkiP@*XipUjipUlI~^)q9YpPMn7~1drwjTAac$QArVh4(%Y{}x zM6>re^a-4xE*Z5A-5!IUXU|KujTC>f&pL3Ge61}klV@K< zkoxt`S@SNKDEB&yX!gPnhs?=}ni`ju&wr+^u`SccBMTZz6}ZjP@=LOH2YPB(bSJGt z#@Pb&;4~I%0QmKkmxU5)D_9Q3Mw_B^IeCZzgZ`RdI7W^8L0!<{koSDZ?~mUlK2N1l z6kYflJuIRKvTsDH-)8_G>M7 z+I2M`MYa$-2&BI81Q^(aJq=!f00K3Ru6u@!cQO-Ink;+yNg*pYNi18}yh{W*gVzkB zT&sTkVMxDdzi_wdBbe(!o=Jy{rjfUV=V|Dd1Q+uGyk=-Q7cOk@M=GC&xm?|ls#Eye zl|VM$rp6%L2>c}Si`v5!!1cT4QAGD%0(w;H`gV(O9?)64Q!$rnu{jgI6Vu2a zqGR-302P$4z*Ax1J`!CV1z9_ z^GZE8@1ZgF#J^-&)aMwl9CDI-pTvi|RzaLOcy^SSP>Qy`l|9yje<*M6L)%-p&No_2 z(E|q=iYa3`K}VLjA-ke-9*?zX>(p9n$OO`I9#|L%ypy^9e#o~72elGT5SaV$-L|+n zT8&D77+fP*0nq>Ar?>#+WjyPA#tfI9_nJ zpAu#1SCdrlo}oEgrLuwSaMve%_WLw;xeHz8SG64swu8EO*7UJNR(j@HdnQ<7s0WsI zc6b^B5L+G6!JDI1o&|va_5ZRBk*eb%1uZ;pi+-saqndGLml~zZG*qEvTqe*WZ3MV2 zwRHH8oL5JgH@{92)ZD;mxJ)(Lr6<{3tZitq@@h}D=N=E6W`^U^(}_A0Rn~yp-w4U_ zqmJMM$rXoP;<;Q#vv1rV8%pOs!9`P_GpZ;P--zP7ewbZ>CabzX^Z29dn3wHVyB(CY zg)6xiBkNw+T~Bq#Z{&5lgIiCYx95+^1~^SZPR{g_vGHpaPcikp(N94#LYT0uU0ZM& zMUuSw@EL2D&){ZT@$wUiU}Nu1U6U?ZlXh(>y#D(^NsOh< zdTO|-E(&>?H~y$y@f*2P*`zXdBgp1lXsk(5F_{Kwmh5RGE#lvQRAKIG22 zct2d5?cF=B@!lvS(GBZ%+QxDk)o5WbAt`*on-NUkx$gM1otD!Q1ch&IbSRZJHzVu|g}~=N(+P!A+|E9LZ~bRoTd!La^2W zBsZ2Ne9~_GSVZM>8o)U#rn}IHXe8B`h=5g&MN?zpA^CM?_}hPJXc z>X2zax57o8_1N#+!mnTZk4fY5@yoRR3z88Yy<051AP|{;`*1UFWnbqOQR$Ib)OIp1 zX-IrA4TET-<)H6GTk-<(8xoaJ6Ohq+hNZE70MyWkA}0mtAm5J28O5f8sJG#Xq@tzXZJd8G1Ul85UUbm;sO@^(EqDD=85o#(3u&5z5Gv z7h1t?aG%rjpXgj@F#(%xq@m1)mZElP*$Hg6UYiu>lh%?Qb7Y}*gi)SK*=dBCAA;$a7iM$3rVUMSD0z}|BB$OS{ZVn>^hV<-RtusG;p-Yd8r`) zY@j>avTCTS9e$WzF$ZUrJRsip(2um)KQ=1Xovv|CmPJB}ew$B;~RdHjXbm$@CpJp((#T ziQl|M3C%V?Pw&<&Xbx+b_9)A2A)pJIDxgqqeF68O10%BDks0bw^AIJSO>(vqXQ23Y zk7TLz_upD-Ju}=pdI-C>W*ud4p>9|?E-E3@uODkY+w+wBX^WG1!_gG%ew=>@mZmW0eDZY-G|GdC^drMm3cU|?7g z>Rx=shr9_)@Gi*; z4InW?=cNyfMCdkiI8$Mo)Mnoi)EUkc`WdfFyeX!D=gZooS&-mXtbyt$-m>!8L zIsVk@DMYo8%LNPX)M35qJ>|N}l^!OD0^A8&l}w-R8C3BQ-rb|am;rx?1UzX2uA2Z- z*Vu9Eh%)B+nl}ja^yW@^ZxQl6WoKtPWx+$f6*_Q#4ewV+%rE)WTC0$uy!s$a)Izqc znbh-y+QPO>p5P7_qc%*(Uid4$hGRS5>cmQzd{5%9K;F1Q^E@ZX48kNC@4YCXrZE;Z zp!!VKUK+AKxUe*K4TCOKkT7j;jm1v!5>ZM@eNfy7b{Y}nkd3|>> zVxBV(kW$i7_ySO7p3b?FCZqn*%NV;OLMdb*Iw>=LpX$Ck0{K%EdO1L#6&kf*h(VY{ z+8!UXp(cdpAyI{HBHdAvs1C|Xz;mxrSVxgRsab^P0Kcs!SPUZY=QYrYw@siKQ7$G|t1~j*#d5?b+idTUIRs{Y8DGhev=L!vO&M;ISQOd+^j?%hhhi6b-!*E2cwq>oazcH zThga|Md7^dZ)T}g9M8}uvpjX(PS+u@K^xY6;TXZH`c~QpJOq`(5VqsC&w}|GylFL} zFTf8B-vZ<=2|{hIb$562$JniVFonl4UtMM$F1J# zv!m}R!XJXvY-e3|&4njnScGswg<$NaV6aoLRb!8I>%)<+r@)HVN3r46Qv*k0Bb`w3 zGVehU$W29ep$>>`hILwz^U}fm>H(oJRQS@xd{9(uCIY(w&B1}w$nNtw`^+)72J!kW z3@jYJElr?+=d8|C_}XXORLS$$Zf>9OJGH+5g1pT2-Us>wfjm^X3nYwy?~N1vh}^lRb<6N@GH>y9 zl8H9M;&7Y+Hs_CB0-#08HyRXIZR>Ucm|M{^jOrwFc0mru^@rmrSR^R`V$8g$;68QN!6 zCfZmCJky_clmw>x5VJt_9g^%J!oh>*IB2#n>R?t6E8ILmJ}_A%I!%1TDTTlwWXf4E z+eu4{H_bUhQT-xphR0njpwv!2(`pYy<4XXK6-*O3&I9pkf|B9TL z{g>y4;$rx>>sWP1FCR307aU5yzDMqd+1|;(1+lkTd<*-PmNK8Pp(^~K>(+=1Q}faL z4|$Lod2q9P#%9Nr^3{)KT@EtM*d#FOI}-O+ci5j6*%4m0m2Zp7aI}N{i57a8B=-fw zyo(WuryZPm2iW<>pw?ws#3GZG9zTkbaAgw)Umn?&;ARkQ!Bw_y6=OyYX6v~%b_n*_ z${U^ZRLh--yIQ3icZycW%j1G}igIj%Ch{Bam^K}MFqjVr=hG%=^R`(B_Tq!&7Vc~= zkb%B{3WeVMI=J4^?zlYga@Z5`eMxHZpaW$V@Zq6Ghay9xZ{ z17_}gD#kWqo4X}wHE&~E8oz7wb$ja^>)O%^BqPVtvpxU`*eb(1{GU0z(9XXNr}`5) zM2)|i$LX_9Z_tmeUFAr%y*%2lCM6xre;fzR9ekSK;YB}SK)$l!Z;b)Taa3ncm87Pp z`=1^aib^L_k5SIfoj1pb%7xfTdF+tiS#vpEN)~+3ipgm|89gcLJv7vjy-m%3LXP460S zo){bEZ>?W)m`aGWPVBm17?Zd!eEp?!(ZzHiQ z?;2C^H4tY35Vt>)w$DWRm$O}TE(R*kykETP_rUf85jO{Y*O8P~fG1!~O~5KPa*mz8 ztBJGH1(ytx`SI`?i%UFa3U_Jj*A2N8K3lSo)}- zRH;eIJ9+usUOy9|8+hD=f6=)`&dPCEepLWY=b2b8cY0pj8MrZ?QrRqOJbp18yDji# zdxvWafg-ezbOKf;PbUbh1@-McKRwvHwi)hT7a-l*s5Nx4UF6e!2SY9Gr1R=9)8oJw z+woKEa#BSobVr7W>i5;GdmU~2V-(Hs>q5Lc=Nbsu2-EM7IBHmxSo)4KIkc?`^O0*a zsXy7arm5_J2d0#7*eox2~6AIzXoD9 zv<`U!YpC;uqEn@}{hh9$W*~*o*{Bmi->PHD2}|GwHHYJi7aHatHW{v3O>kniCyjYs zSrKYCLKgg$L2nI)E3$N_Iy+5vQsUbQ0FXxxzuOkN&(k1X*Q$C6L4OhqAeHX4B5DV?@6|9~ycuK%IONRn-ih=T zJ+%HiWLax)+~I}DN?hXKVRAPL1VuY4z@EjmJC6WF-+rd4ZlF9jS~xvzpi4l-)6CkSN#|9OjxGpR%i$CM*91SRz^w?S*32b85QZYF)%Y|s}Y#GlEoLd z^dql7H?F8RYxxT!i&pJ6V{LbB9%KJI+Vk^G6)+ApLvq{miZI2UiY&j!%N?gaj@wD6 zVGt|a75(3A6+F+G4y?D8eQx{y2jwfx3?BH){z)!1}3ID<@O*3&=d=SCf!H6{mo-Ir+cP34qxhDwhRqdlLgd5WV8 z;Nc(2t8cqXD)ler0lYWjysicm-$AcbMoP1guXE1y%>kMUdf%JSZ~jIIDAPB-)cwnF zW(P+;;MJcQgl+d|1Zyerr8l+3IhBW4z)A^6yp6WK#0YJK0b&i7rS2$4_)JUgJ15sZuItC zl$<`?itJRTU3&bl=x#Oi^n{&+LIzd)uLNf|Kx|VWleK-0)|Q_iNlDgXfa>f)ug?R= zAP@9zWjh*plmnE24%j#@n#0RDf$QfwHUtLP$I}O&oTcmSv`5DYoynd;J_5^UwGrn;P z0M7m7FgSF%z5#2nSYf(S^t*y42Wrv8zD2#SE~bHDfgjb&_U2y=L9=lBF34Yy(W;~3 zS78vxCl^LntN?+Y^Yb`+UF3xr=(@FC*{p>PCL1+AL-&C0$}oUmcTIimul@7NpODMn zR|c8tGv&HR9}%!yz7-rKH8@!(Hu}~GvephfDeFQ?`fXMd;i|p?!(F0}#ZAi`%JpdzbIsDs&p5px4225usZJB}gIy8CT!f6e`_ad`o|UsCF#kVF z{PVS9iF~E7e;akDLO$f??rv4}5&@$CQiw@&a56wc{|Ygc@05ehar?-~IF1x(ea=6Y z-i5K8h4Z;xQ7Kz4v1=+LvwXwkzt)Vb#gkTRZ@#}wkPUEJA_JP`|JVGW3j%}<@sz(j z&NJEJAQJY?_WB=#hDVh`j>csU@<%jke>}!;7We7^~j45R)=lv zv!DM1pv%0AoN1o9(gzB_0~k`xG*=l0#c$MJiO;QCYD%nD8o0Z*>@WKP?aK!)lFqV> zg{J)nF-Hdb4zoW0@E2g(`cg#04cj{2c}g&0c@?!5Oo6)NkG#kewSO_-E@X-fg*cW+ znXUa_Tr5``;)&r!wv7CX1N<`awK{gkw6|ituiQ8eC=~>~4cNDF^AKv%a`o!?S2nuc zAwE?^{%hN&xwc+=;QxWG3~<$28_fIsWyZWmg~!^-c8FSvlB<^_kSqs*w#OfI1zb7u zE{7|025tq0T)svRQEqwIzdXF00G!)z|G+E2nQ}Fsxrt65Zo8|9WRCVK-HQYO!vGc) z(QI<1T%h-luHc&V90xM*BWDaYuGGT7QC{ARp?{{iPKPhZ(IpLS*Z&`wDKYV)0*QRZbnh=4ldK{M%b)f&<#{{#Km(O(&2|4XPt+%Yb*yjH8F zUWY44L{t6Pq^kESMt^BH%*U77xfKIDHCHD3r9!t*h{6Eo?NV+mZp$~h*opd&0!KpI z{TsgGe?zpnZmXt$2&)!$K)3tSk987H@gD*06q%zmGxmg{S2kv@8&I*=-+cW?;T@F} zC8qWBknYQ*jh%m03%x3I;;=0wF8VJ^+Im!5f9KF2Ul#obFh{1_1GAp}>wJNnPUjN< z?HtUqChrLEhQ$BIqYLEIRX@H8oWPVS<@|D!_ZvO8&UErf=g|1f4lrv2iuSGqpOJ;P zNQ2B@MSXzC?wnYjZzmYQjO7b*cT@hyvsZ31A%j^9xBu-z?~Q_c^a~&g-1}{eIuC zM+k_wN7MmraJ{EwPfTdgH98&i?8b;s!-CFSrTy!uq;dq>nIMyIVFjVDL(MTJ5eERQVfp|PS@NMjb+@{qDjlA)!tkuquYE#0$~ z?;Fl^ND)XzLL7ebG$@g>cy_Fl)1ofNZe81a7&_Y1bN5U!Ju$6x1q=o7$>tt2)Q?>K z8Ku7ji3vMaGN?IhTC&VD*YYW#(nykUPOo24Bq)(I{TK-`%2iR)GTh8FVH5%+&jtgc z%#$TJzjm*uFcQ30;a=U09PX~e+|q8IonynyRC|B8sYA2prC7#~H_0GRA zb(KwPcorzMurE1u&Zrf*sUG5{#jJu}Oz+7ThCRYet~U`LSmiKl;^Xm*g^eA{`Icv; zg2XRW6|1SMT5PWzPW2Cm4L7=`zs7($=2hz@k}Hxe*-xLfRJ!^2t`}Cu<$1hTagM_H z5!}xNg}mJ&h`4OK%C@x2C%P<=Xa@qgH}ZVIXURBTp@Fo+lN+PE+viu8JH{RbD&;q) z0i+7s^mQ_kLCz^p(0#kDik3S!W#2PO(9~-2<@x+N$sfPxB9UGCv8>lEeqGgE_`2%PkH-rp7*O6|8kSW$i-D~2gseARMuSgy z&n$k~q+D`QI*n|IQ3ov#4y+D%L;!6cuH7(x7hU&T!40-_&^HTiL`-pwo}aURMYiZ& z^wkLogln8khxOBUeKo!~ww{65E6>%@I82Q(Z`sy0LdeVKGVAemkm9fOfC$1BR*a_w z$s8uoG8cfT^2h?by)s!IM{+k$zXFa^{L4OchLe9HI;-wm`T$HlfcKNmy_igzR|7mG z=iyBMDnp&_pOa)*cyO7Ep-~XG5YaIAr^RVKlIxQNx!1%z0a5(i$M6W7+wKK=xTNqH z305FE(QL{Ugt_2Fo*&=3CI&?R9=@F13w8COQIoB_NyiOhQ_R7S)nX!2)p|HNeklK@ z@8lX2upFmrYr)a_2(adEiovlGxHF#>7W zYHsR2;Z4R~ZWn2^pCY^?;iSLmnAr_73hB|kv`tI-`e^14PBlL$xDf`Q8+A&};06Am z6%E+UMk&*&#(vAas~ELN@jg58xo>#;w_B%NL3P&?$U%~gQ+e|;BaHx^%UBzYq5Qdd zcSvcN#90c?nA)ct73aVMjHuh~n2slHxNj{P{JzHG+i~^DPYYCn39LHM*maT8Q+Lrs z^Jg28q2AgQlKY(lxtX2O_5!&=yHIPgir$okRts>r^2acjZj`~-b7pf$tA9wt^m<^D zouU1Hw6Q-(>!3WrI6+n(b9&Ut{-+3D33N*9SkP1N7=CXG zsm6@=Xm;xc(u5uFiC7aJ?X-meWU2PKkwiIpp?!?7t-ITR&K|05vcg-v6{*kYq16Wl z7I}eh;)CY#at%{L1ibive`qwjdBaAYI;ftk$;)nH^Y#UkGVAKLBl$Mh$jQ+o^@C}> zjeKGC{O-%U?HCixr(B1;OI{lLhFBjK=7o0q7V3MX&oF1EabD)G;z%J0gEx3ng(D-J z7hcLid3=bY*t#aal}=sO_dIgBhcB^7S2Yg!K%hT2)qJLz7FheYv3FhzO$FWQ_fvmW zH4c@|%`ENa6^V`y2zttu0t&FZjOtlGFLG40XUKtEY<1`N#j_eFnl({Zw*jZ#GSf_` z>vO8udP#b49<@>ql-+x7J>Y_#JLctJ=P!zuGvKvN(16dX3-Bqi{+h9}D=$85^^%>4 zo!?!K>pg}3)0*qGxP_mH9Cn?i@C?oT=`6&uPSc%EeJ0?1txIC__z(k*mt=%Z1f`8I z6KwMEw4+BdVgu%U_zj7h`o*y4*)#uuuc+ILfIyb!eU7>^+(Fwq?W^fzYdGeFZ~iuC zl-Ot`6VRL$-C$0VgDQlu$QtMu^>SzUHIKj?1%lV!BvMGSr1rYmkwCw+iSKKYUkWj} zz7B4~67%AYZM`Ei5SG>P3b;Jt4=Ik92eo>ONLI!omp&r|{ULOc53aUn97XfW5hUDP z8nA7~M8UZxc_LqfZEK)~V;}KO7TpWa%b<#%C(EIo;5Epw=*`n4B*^@l8t?eVqnUN< z_R`;Qp!?A5&E6q66L2C3(bn<9Iw=v8%g;L}wo->o`0kUHZQ|RD3_ttZ7mB&CR^RX2 zgpc*`wRPa3mx#xsT+@2FbS#up+f6=qd?PoP|2)VE@@LeDhs)!bnbx;DhZZJ3de`OZ ziLTR+*5FajR+T?~l#6^O2nR4~q{HP$8M5*t_+=_{G1`eTEgOkO=^sLRQ3rs=E`sH| zs+%mjb-&^JU-Q`*6xo$+OU@&2t115Bt0MKFFekCl>zEZ!6E-Ko?PK`9lZY=}H%kVe z#6j`;qHP^&OGTeuuN<%o9Eh3xvPj8DpLBF#PbSy9_qkLO)~4h5d$LdFe#Slo%_PMG%eBHln_+l`-=|&fUUJN z^3utH2)XMbkE*s_8c*Ncl4w3oK4OHm=@ec*zjJ%nRw)3$8|-7gy`TlHfi+g*J{?lE z;%OJQ>3WV%ELzVu)`?m%G*cvZF=}b%yAHBj+G~t$YL6<^MRwA;vLiC{GA7Ac>jDHo zS2BDzG=zRa#FHMG69 zu#d8~c*t8{pG2OVoiBfYECm)?HSDWJr@+3#7xdlQ6TN2@L(;*5WyFU)(O{gm@jZzJ z*C%Mj`b)MPvznh23fPb(IMnMaP~WH_A{l()yqx)t08eFMBMQfC;Nw|He~j6&-Z^d}5i^IXD1GZ}Y$&2w z!N*S4PZ4IIYx%1!Er}|S1AtI!E@;KcRaK&b8VT`i)+R<;%%CMW%C;7pybk;)Tt;D_^4ESZNwN#o z7XI;*fdu;xOrqS~fE5hX@z0;i6EeLBAApp+;po7s{-3m+*36^1eubg`UG!KPrF~Yp zz?c!BvR5!K4u@;pMN2HmZ|^QWE0Z)Qw%>~JQk%I65nZOzE*v>2xI%-Qh>Il6!m{>G z(B=$v(EL|9^&m&gA2c_v2PG9ncwYL#jXvkLP>q|SmA=esuAD0hW$apaYZs(kQ=|(|O0R<6EqTjI>U0RAj~96VTB@DbM?*fvr{tPwDZAIGGA7v|XZ z&Op#J6vkc`!3(ZrnEj80xwg{S#LN&ROdii4a2j0ZSRWdsqz{s6^;SY?%UYOM;V!eX zuBlg9H%Ah6;I@0h=oyf~>(&4YfkA@8X_GkGp?QI!L1Oy0o1!->f;cxG0Q2GnO#oon zCycOH+L48!PzM6@fu0K@K?ieSnPU;Q;;_u-7MpW*YKohvB!~Z^hO-ekst6J}P{3Pc zTZ+%=rxqdB{{34msFpgJz5tt@1n<4&8A8Y>dY1l@4tIB3tbj8llK13QHU0i=1jta^ zz5wvwljmjNeXi_HfRBG3Z_!$3#Wv%oWUm$9EF#=|{RpH~#Mop_?RxPv z+B}1E&Pagg0;r(1XJ7=SU z=BLLPI3&(w)8JdE{M-7}x7I&eT7BTA&CaUMH=d;Oa_n0+(7;=Is zc-HOfztzj2G$arB*%Rx+lO<_L)8&H`5uSSShStBc2r^R&0LWmUh)uwN--)cL4(L|A zKM}}JOD@pdz6dekUL}9ib88jmpDmETZR~xywLXdv3BB8+*4!Y(7xnEzH7x|7F{#<)3pB!6 zf0i9eer)5rVcVZq$@n2?l2)^87|qEORXFA@uh#T+vrP7h2>%ewZC1jr7Y&+6FplT) zo$@<)8Q!$0^-7%$%je3)J>}H*dTDBW)_vfd(acFek>lBXCt`{*I~#`6h&RdoDt zbaeqkqwtDuJnJZu#BLk9jh@4nXJ|Dfw&q-zvSk+`=CjeC;}!fu7Sf9vKg1t`az_c` zW!@)FwnV}pe7E_>59a*(S9@*J@n^vg*O|mEhER|k&qG(Q^1aySE0ORhpKUICWMpJs z2ub8%82UWupt_wW_%N4OksoOQ*3|H}CC3~(NQKO%z7C^#jmEh%@@W1y7|L3R8eSU= z3ydAX!Rc^#Y4`%4D^92HJ(qdC#!p?T3cpq zl`3ao2=1<3mtHXPb0`qaSP|>ly`!N-VoL*OA8NUou*bS%J+FVUl)IX}X&eh=uAX@4 zdlqlNz+ANPfyT{#?W-+U2uNOjeoXJ?(Vgv#mVEQ|IU9B}0#Rlzp1&Hi?86P=)ue)< z7N%cL`5p-u)>-hlE89t`Y=RyliwD)E^kCK2b!yW?Lmx|puqwCcW`hB!-5AebBxVuk z(>-)zj92pi_m>ieDQ7B{ZO`KI`bWXY%tfQa&%Pn;9My@{ zUAgYG*=}jJCMqyjxyQ_-{Xya4A=^YkH|78dGZTsI!y^~=A!Es#YT*1MFJSKaVzkHz zuJ2nd-HcZyQH2*Nc8t7caoJ=t4rP&L7g`?I`E$$$j&*Ohw@*=VMUtl3DNYCLk*`I2 z@fNRBe2wFY1Sw;V$4lt*Pm~yv;tnu)a$l)Ac`=w0who@Ha?JmBrN* zbh~7E2wvW65m}_zKJt3A!@VM3&iJ7ge)yqvo0`_rGFRCwK~tf`b~~5R^N1=}03_TTnzJ3CI0XZ^sYoX7 zW(K3ooZX67-aWqahtl*=31`(C4JQSv^F87o3GDy;%ogsZY`?p7^;Gw2v1sSJ{a2Of z9^p&c?h+SMM8nOhaU1+fcC~J3g^LH;)6Da|*dz`kpR+37Ge$tjqwP*R^4WEx62p!W z7J_YO7hgvI7r0Muo3wGpK9@ZcZt33Uxz|hAHI!f}gF-8dR~jvkmhtV?Zf}1iEF|Rm z>%`S8lhCyImP{KxkI~?yOu??$Bd)KWjUfZx-$e_+H3%5b7=k-0a^+}dYn;#APF5|M66r%v^x5KXL({X zAbI)2ZPClCX6@CW&Q8pvCjy0BzE|N|+>B_u z+g{&Ww+b#~JKRv>b;7wo`yc1e-#)|b+A+Oo{aB9<|MzUQvD%9q2HAbEY0{0G|dGQPQy4rN{ za@Ai(Hi_OG2o13wY3aU$_P*@NJ|l8p+t$i?e^62xCWr+ok39Z>4(pR@HP1ojmGAN0 zaC{LGj$FQN{kgnnX1rOZpf8ztzv|+rds!jtH-UsF%XAo>fs4cB?&)NH6Id=;ZTv7uaH|fvj3=^t`h#6Y+*2 z7q1SqURqwCII)cafNy2{Xhg|WrsI9u$0!BS&5w^eMBV%t=4rp*7u(Et0-|Gzru#r{50AzSsfl?k`)tU(iWCqmPGM9p#>&83@hbIMPO zj#UNs_oYwe4XSc?pDSE61ua)s@9}7>nA1wWU|8RQ2bpjQ%>QwVVjK z2QGh4W9MEER60HB!n*3^mB(~@yzVt}D~!iP`y+M>6Rr!ZwBk6bo<*E0J_TC?x%rq& z4B4(O*^J^H&~Zi+Ptg6()%LzRVnXw-A?X#bSL7|f9Uz@|z~$F=K|ur6AB8i6PD~X>PkfM} zU$p;fnSw*gY$~<{E+2TmMp%825Np%)0WL{XwQVo?3W4ZBs4Nk)yVP>O!wR z-M7Zx+wiAuKr5gRbe`7&T6%VZ=kFHd&Rcp%FZ(vZgp(4RKJxLRxePwGu`2C zL;kWaqAtbV)+K45_irdDI!sn82&Pm-@2HHFC1#$AW%nltc*u$Piat9{_n}8KJ*Rq$ zXNX4W;{|~wkJfuL)VxNMysOWOEwzv-Lmr(hXgq2k)#}F?yyNbqsi`SG*!sR>wdI8R zOqOrYN#Rvgve#Ij7w>9$n3PFXU2`+>B~=O2`PdscRvp`OFsnhCt;QM1WFXgyTs|wv zOU=xzY01YqwJDi;Oe~5Jw-ZAdV@fk5*^GQWyk}b)OtZ*)VrrmEB}=wVL@n8 zdeM|!O-TJEErv38;;&Dg(HjY8v@oGyh zBd`C0!{R8{DiwBZGjFwk^ct$u5fT=HRDF(R#*MQx%;oEaTxskg_EObwo&A4RA%)y2 zRRU#meuxB}VGrwR*bi=8aYZYMfcg@QHmum(1|l5Af?s$Fi46YpYrm={vh&0w_g=US zd-L|S7dX5S-D3yq_mlaJPUo))ij4E$T0 zV>`TO#T9W_V7g4u#Ujbdm@zcu+_M|(w-RRGAwm1z>I5wp zBwNJHd^@HkrM6;Ggb{ktL$Ta1U16rwd`2FTtBg#6W3~!TntcEMUAb9LXA&|#%4BbH zrNc^xR=56oQgeU`!l&d#aD73g<^hFzQ z`rGq6o&gTu!eBV+soOPuvSmEoAxl-lcIU!8>wTvoPSiJ#W`!1iFD-3>Snz`&XCG$s zwc1}S=5kOMB(!zD6^F#Z1f8`xZ?Z&~`zw|q@3{vV0v)($K1EtCS|f^`#L4hSojhis%>k%ZsE|E)hdJiCQ+AnA3oyH zbd>!o?&KX`X<#lm5B;MVeBwSbop zy^omgKEK6f#ux~#DtB%T0bh|(`FK>Dv0KHfR(AJ=0zsfV)nq(7pO(sG>izlQn6**u zY8l}JJ-tO2h(8X%%X$?hwfW%wr%+HTvVXh~iAE~mUq>?JVTqzXTRMW2G0wDrsZqLx z;2$1Rq&S2({E#lw&m?%{49QxTP8THq6}`V!uC(^7i4-o!mN>tkB^UOV3q0lRlAu7qmP>=6Sm(d z6NC+$Iu+Kj*sZM=z5WTX({E3<#*{`LzWxxctZasl0VbGB<8j$4%{o!?sj5$rhbePO{_7sDluJ8To5)C39Pr(JB-i}iH!qH(RBcQ`Y0mXhTdnY^Ai#qKoj zcuqf?6QIaR6oXs6J!3Yl)eI7HBi1F`bOuSrZOYy<1=*{O(OTFnd!hTwYMGQAUi?qp z`~Fc>((w0P{mm`TC2PmX98Kj1W9c26Hfv<uAsUocQ<>EDi9v zOY!|FQ5?933f4wXsq3ZJ-fvZw1DD6Ii?qr}B<&8PzEF8IaKFH|=G{9ZaWe%)QQ~-f zK+OY?p3S5Q|2eJG`lG1=!MW?#XO{9&sWkkztw#4%OTp(3 zIAHS`A735oU`_O2dnk&t)7f%q+h#2wil=6nB_$;>4IOyBOa9vNdQtL~Zst=Wh}E`0 z3{LCMLk6|IooTX9_tX_XUnaYvPW~p-&px#czF2N5`B_c z8gJ(AYU!b;TQ>DG)~sFYDfGAP^Sv0l$KPiPR1wXs>0@Iiul(tO-Fwtr#dm6f%qj;NNc2Mv|775iu`<7wn};P z&CLP+PghIlzUJZ%H)h-@+r-{Qn>al~yVgROWYHUVttr_R?In*|pceaT4(*ZrE1&;i zXOv50(P(r+K)TLYXW)bnbOcz`9JhYMD*KK7!~6DxkzaSO`}G|ZYYo1^!PIQU7_gr=#nucIL4HccauD5H$*|h=%7EmS6?@0^OnE05{9pzpqgU- ztP|Y6eZ@ZYj@BDzmwo*H_OhPlEiopQRMV1S72t|?xm5N>BjBwAqrt+vat5Z|W4=RT zKt41C-63v{Tyl31Wpz`9zHXYIhlk>? z{o9P{P4ZmdH`8b`f^NZOH~R2os7qVmUh!I~`rf(xi0B0#?O{s)#ehN~@2`pRM^XouJ5Y}PEabp5Rnvs7zZ zP?8#2u0c~3K|%jYYE9y8bcpc>p*#bJyo)LFNseJd<|0LdLVgl zK7Q@Kgv77P3XT)ElVTNa-mO$7sObFf$VY@r?FayTis8bf&HFr4VrK?yV(>J$Lhg>Q z$t)6{jZZ_i7Lxe*?=jb&=QdA%xs(bD{Bd%2{@}-hfZ7w|-efz5M^rI3DCF6>QSZ$O zsv{Htcj!eOPb8EF?RZ)yZ+1Bq44->J-N@NW(}4 z=*C=#%1g_K7MWg@TuOaF#Xw`G3TMhF8RC^)4m1aUXSRR@ds?I=e|M5mySoL*6Auz^ zXUaVYK||B@{7g|D(MVG@kX%InWLxuAAaG`#kaAB9k`=4e2f}rxPAtFD_nv^d6oRAy zg!-j)QXp`sVBLU$LEW*-qrk|*^P37ZSd^Pp;6}+igI_sTYG$_kk;tkx)2pX#Nw-ns z8264#P4xf*P~~psRa*`~piVW-k$3o8e~D;NRfGilE4%Ox0`TwZaU1?nEGHBPhLPCJ zE@XOUhny2>F7(Fz`|-MQ7zZhavhBmDW!9Nv9Gi-Wb|Dq7qm1Uw8^WkcJ`*3rNMlp| z>6)sPiHTg-NH%D!32q6uS$we;+1k4G>%BJ_WHk%osO^pJ7uGAHE?yj5@_Zi&+xg?g z@xzC-gQ+epZ9Wzt2c-F5g_GS;EgAvaNQmKDpw1}6;xGFOee8Z_W$cK<<#-Q&6Uawu z0kej4Y9x_u&o(<{+{LDqmm#gZRG4$4UhwhkK7%dz3tuu)y!QOv$-9dXDYc2u>;DPr zO6OcRZp~AE0})O6gM*DtA_toF4bZ-|S8+qn6KW?CkM z3tlIOx@gSb?_Y5Jp_28bT8!MZehcrxtT-`I-XNsaHI?q1M~CVivl4#R=rW+ZQT*`g zc1j2SoP??ZY^pdz(jHp-G46d?4HnIxeEV@wFL?=FFAElp;UMM3O>bsnrr3}o)QW3v zDagxjn=0UL)V!Kw>PQlGzS96DE>txb^Bhl%4QG6?y0oxAE&nBr_@R^16>e$4^LD69 zRstlm;=GUqR6Ec;CgYICd*E7V$ub2MGSdYBj;NZldl3*7X z^;EVJeI+(&5-x}NXk7m2qh8zd8XV_iQ43_%ZIcC$)(g9nCo7#Ej<41|*8A4$u=U3e znO*igOW(1&F){(T{~bC~znUJ{a^WoFF>y&i839-DS8$BXq>(Lq53d(I)+ue4tDmP_ zI3RCdFNqM?xF7}pN4svn*P6T)cK6=$^?B=6MxW;+Y5Ivonll^;Y2l!FWI%Ipx3MVA z390jD*fgdp90o9uHm3i^<(l%)6P9r`EfGElyJ`CIR{pMOCy5?|g(_+t+uoA%%6qKN zC_jga3Hapr3!$uO7KKgbK$n1OrcnT@Zdvf6f!X`hIui<^GdzQI2t zInKFx9=qLX@4KM0sAUnV!v#Ag=sl{?n|jf_i+cvCMoyd5?)x6~xcB;|+l$`mDPOGX z=}AHo5wNp-K|#T>uN0(7@{aS0e^T^Ib$`D3Qw8Cg(tW7KIEi%~UVG4GSYsKYdh8gKeCXV=VeWL?AkB-^J7d;Xhf zc-W;Xi2%h-E==h=U^n$n?IJQ&&{@Zwf6x6`n5qw{bS2UYI)sk7p7FG-iREj&bl=ej z!~1QfB9>ga-MhI;Mr7{aAN6L-yc3#V#cxOC+%|M_d=HOBsP131{mRr=R{4)n{%O?s z)x5!g_ocAb>&lnjM_NLo2MsaVre#gIQx5Q7qFwX2jYGZx|G~J3og?4+d>;h=-KG_Y z&hPX|(Z{$qaD9i@Mg__W3Zp(HXr-YqYX5nOjST(W+=Q#o8SL$|GvGgw`|hE7nQ_Y* zMv9t~9}BJ~D7Us_6ngjT1d}Gx}CHBgsKRi4)_65o_?(C0>KRHR_Pu7Ogt& z)ER;*UF5-qu?JomHtl&iv%gb{n)JBdsoUKOKOOgqnX>3ZI2n>k@^IAVxhS9-~G7mmvCwI%VZh3Fm-%ejt)HI;qbn?X6(e(PRi5| z2kiLHJsU?~{Etk@3Z%aS#2N4La}?fDzD?x{TZA{P5=WnJHq403Qs3KCGCzHr9-}j$ zZYB$lXFTNQ%qmJWT*-XZPIhO8y#C-h=EF+Hvb)6BO%Z z{WTewNV5GL442_CVwSG!z(<^7Sj2aK7vIkQbgX&IF|M29T$>$w%R1x`hVEoK2fsav zn8afivG9)YxAa-1CggL>pS?nlkApikR)jTW6TOeo8sT1U96geV8odf?^m9Q_m_ zIl*11PpRBiirUuGi~&%7a8^zoL>=XXdGn5FBsN8a8O)$+GEi4mU8Z?cgjX#MjST=h zRjz43rWVVY6MQkW*(2^w<>v?4@52rJM&gS1TUAUVsB<2{$1%%M@BS^#PF!7+G0wN0 zkXqva6WQ=4TZ(>UeZy*T9FAu&4-LSR zI&ErwU4VoDVevb7tUp_BG=@EGB0{<@`~=yJuqd_X6hRu!+IJRZ>5x(KN{mTenru!nn@}hKunvZ}A2^(2*5?p50JK&@J$365`_psNA zuGX}ioVj0kYBCgsH`_6$sjjzBzv{)6yzx@DfB%e+ei-~os@R-0Snr05t^LUlUlB$J zl~G!Pwet(-0N9jL8oE>rkw(os{UJzDD&o zHsszvsMo0mm`J+)*{HEX`t;SW?&!X6;okY&f>3^DIwWK1T7UGN)mqn8M5{qr1QuCZ z^{vL~3D=v&?mHM!n)K?k`Refc!1-F1b`pt|F*|$4xZYo#oaD|UdJQ4y z$O_@C`Ar6+S%Ri8E|@ zoCp-5_d(R&EWLFt()Q%b!q8ptHIv{!;B+M>f_iWZ(HOisX?Ehw@q5qI%f@LSZds#@ zTsE48Go4dKK^ftW#nr47fphTsjYBFxw_cMm>Ppy_Z7yoVOqYrDt2ZXsEhzd}=xI#p zDkat|3}LMlZ$x=bjq6?j>)5L+IK~_SPGLu zGOJd!NJs5RcVORiVy4eqyh~e`6hrOBs*2Xq;Dt!O)gg_3`9a7m$(EoGh-3{Kj*fG+ z=Jse&bh$8Hbt|#c$7DN+l`^lu(5$!TNMbdD+2$&XUS_r!en-xduGk%T8SM=Sxr5(k z$56h`7bY~NuplP4tSR=nL*|ZBgj`IdM2@+#l(S*v3rO&@%%R4dQ!Rr~uiXH`vx zg@YTPuvAEFAsBqQlv40Pa_S=AH4Q0EIklRM&LceWW@`s0rqk*&!b~dv;lSHq`msk} zZW6S2eG4V(z2bvg)Sh;rmU{3}%eJdW-1r8ZT-L~zy%R%zI1T>nHVrazcMtMTJqmXVoWfclR7 zCnPQTFzp>INMKEEf^ zo>M1raa_s=z7EFNH@GLxFeF19E|yv83S^VWb@QPqxpi-CBk6oZm`3k1)S>s${e2rx zcTMXTJ!{#s50^X-iwD6h2cTv66*UFR zycLBB@{N0rnzEQzSls2AB@^~7B3wxQmm{MvdbiIEeu^=v`_cjreJhG-?W3RnN?iDJ)}&% z>~l&{q)UISPt31hbg59wjBA!e&a9u6wMy}CD(tl3M)S)|^KgaikM#Kn2s#kBZ)>c! zsh`O=LQ{NUA-Odv@wzZbP}a!XuT!3vwt&K&6F%1uj`n4HVG?^T@ukq)9{Tquv&zhX z_D&3Yj>vz~_)6&1A#1tuG~1`_)ND}wDL@PdZ}byS33CgY zog6dHH5uW@)8!0inVCp!7pZz-G@(G>hW`FL&xm;h$?1h!^?o123VN9gRcU8Dg=?)s z3K^U#@Slx-1_zV`R>!-=YKnGawLrN5A4H9E6hp0%C{~6 zl$Q@qt%Vr6nRr7ss6mr)1I^PL^7B3+LJA?(&ItmA-D&{lbtKU|;wJ+miuFVLvZ%Vo zG#m%n5ZS<-gXNqHfz33*$MUDYZkhg3D*VSH*~hPojA<0Hf~jKZH1YH_rwL z37Ud)k^ZU4tkampJU*2IC3+J+?5uyZm~Au2lnX|i3q(9dYP#FTne_G3@rN*_TMa2D z*8EL|M*HePmSZb%IpxOQE`wYxX_nr8ekFXxFi!E8gdXFPlLB7~sL6hBC)L#LDo|rt zKI_zLNYt5=Sjo3eC>V`8qya3e`OWEWsNoaFI(7BBh37}8e{2%fhJud*6*zFWn9GMq z(dE;`POflmh?+6lelI^OTXzN21wDe7VEF0BJ2bkbVLR!1)mmz7mXRETNm15PW5R_48Aa&S4-l&lstnLBUP9#dZ2b6ev8K4Q~@Gs%~PBWr5L>WXI? zE1HrLs*xiTcqplgHpH%eflla4<=dwrw%kW|dnEQAkEPu{|W#F+hh3`H70 z^%iQ$hd6D~p-8@@@{Z4NEjUi|`<=eq=Y8^>m2U$d)l-?~0xO7DXA)sPAayO|cMYnI zbLhys;WwsI-KqQDD}cY7(9ChQRb_nc$r^cVZLf^?Fpl)yLXZB8c1HD+rn+Z*sxu!y@@vdh^&Y=gJX34XG^&OAS;z5u8 z9=Ka7%nIDSuv-^cP1M}b$;p1+z8fa>WPU2N0A6AVWw+k|nH-&Incl*5b)9}VC##!_ z?Tqllr&MEp;_xOqaH^0NiP#L}4>hG`xsAeLBNR(D^GXY|JOo|GXVQbIy?s-Qfz-Bi z_)2(r<2AO|VgFp33llPCZ28_#DZ#QdzV;a^oB`U#U^M3|hhK~!q)zVQtoPVca)c(N zdVdh!VDItOuOui)-m~SBkN|V@W;O6NtGR*w8P(mimtweN8nXG~Rq4dcG-5v+{9ouI z|0Ls(eXBCv=;SJKOM7sv+g;`x`;jPC%Pq>zVFf+7R-V6xUpy2a@~kBE$-RS5J6#Sl z0Ipsatk|EIPc!6sJ2k|v9`o?Co2?zJw?3)67ku}prL3R;^sQ4c(kwG6|IEBZ`a}~= zqLG3FlxXVAz6zfLU5HmVIw?jv@b!ZalEmQhfh19v`Wsb~0aG;ZV|uXr=7phUjRFy` z>gYa=uJB>L@(d`pN}e?xGnci)Tej3ps>b~pu@0Hl4pAR-T2us=CO<2I7KM9W%D*PI zvLI)i&F}04qXWz4!>3_vIJ{f*rZ*Rb3bP8ujvv2XhRu&3XLA{8L96ggh3$J0JC;Bb zP`c~~fw&;)%YG3YX52W7%%q&mziK#QU$S{{f0yddb$?<)vtG2dzVUOVws z)5SMt4KBWg7_@A)xDl^%cA$7;1zzDa!__x=oQM1ddBd-&1WyOV{9RYM+L4%T`@MCrR0$pIo|mCqB9X;Zkcm? z50z1hx>qJ>lQy^a%*g%+qE-D^Ysh!I!I{WWvn8JKbXyEO@%7HNrX6eV_t_(>hkYG0jSMiv*1ApL;#!}UdBeY0g7Got8>Gk99j>}tVRvG z<+)i1RbP*#LGYD%KT{Z8`4V!`?3>n2m*XzYIN>oix-M73X7>x+6$5S8altIIQAXs0 zG4*oesP-_h_3>Rm^)!O(GNg~rNvPM*2xNrQhp)0KGUa&(Ri5S3_v@2yu-HQ+*`cag zx2U7FOzw&6*3BAcYss^GujbWzE~w`6Oc=bYqf_Iu2qjrA&UWXe9>UmY4>F;(6?w?K zfAjpnqRxu-_0;#=kXa#9p^n}btk`UiPror}@a*o-mK}3;mat5=*bH4B*X`}=Q5R2; za{33qj1psOc`=n#BRz&I&bMM7XQN7%>tIg5>RIU2kGpd}>SO7VO|)_)Z*$+Q6rjhE z^S16N4&`?07Qt5>j!C0#TNx9*tELL4eh|#8QvjBf*2^5utC+N!dADp7rDuNb)D3Nl zMjhI!SXju*JZ~z-GJ%an&%}Sf&_hUkH8*pnw(gz}CWf&gNMN|44(*=&F|yU*;4>3t zb6FjJIb58gZaMbm%84$!dJy&YO33m_VLv=S<**y1$}_}`IUj+?!A9TB0**59Mpjl) z%KmjTe>8->P?NnjNR?^|uADg@GIsYT)O>bx?71RFZ73-C6?z9^g#DK=kpg!9(Ht)| zF3$w<{78D=($M@oZn5jEdqSh4@pvVn`gkH@ip^ioAM4MsiM?qYWv;*JQ`JQD^+`@`l6)CnW=4KK zJ>1D)gS@~&7IXKOBf+NWVq8`4iha0lqbdY}SJXwr5Ap-Uz#rP(^7zFtQ5pHGEecu0+5w4AuAVAeg>O7Jo zf4c7mm&AK2+baH)>RAU3TKKE@8J^5slM-gId*U^J(?1(4$PJo(H)O-p`Wb$vkbs`c z?_xZH(Bk{tTkOPLWxKsy_|@u-s{PR1g^`D3o>PtWebam4i%pwHPWIdhXit|MMok*sffe`8Q%epFnia{oET zC7RVS_!`)Kq$fd5B87M+tJz!b-RJFiWMNI4M>$C&56SX^@ToQR1a<{&Yp~LF@G9fD z;t`+!HXt;N*r2G!RR@K-15NVda5nPs#lS~0LX_dJ8ww`FDm!D}UEJ!~F&9dukTA;Q zEd`bZBal9#yq96QiN0?>BKcitB1$}MB^ew@n%*%tBpnWXo|F-^dA=o<5rHdF|0dlS zBtY+nv$KqlW(@3eP6O1x#!~%LV|Z)W-R;-W<;s%#aM|))nkrH!!Cysrc(kww)TLKT zVqfv+=^UtpL#D)C;Co&O-GxqWfzSIXUl5tq!1Lz|grP7541ahP#`N?urjYIn{6&Pz z98&<}i91e+)Er)fTGC}^osfGi?0HWwQhmT53;N)CIt(*26CTtr|DaZglPVlCI(3n% zePQP*ekWl_Esn=S6n$L3%zB#^)I<5!NO1{x^MuBg#$ggGaw&YP2GQRM(I}E`81?lvpN5mxotd2I5E%wmcSSx~KgAt7+pCL|6jL9xFG9U~59vTM_kv-!!0g~dlTgE6 z_1kN^Ijk+RhVFAphv9>qXPs8DzFKshu3cC0cF7u07~7q!_MBQ~^&LsWfN{*EeQ#%g zZq_fl>qL&ksDa+*oG7KLl`owmn&~=K;n8|@Z3<;7no1V%mMR zj_2JVvK{P$;#v2CzUTY|mT{euE3_#()Nv@?%6-qq7VYQc0?i5UZGc|$bu6S1tzdK% z5?Eo^Bj!|k^e@TtaGd3+`dz-m!Toon?Nk;V7Qm(I@vV1%jvqtJ4pI4;Pnq$6cBiED z%jCtwAxfk}t)zCVe%kNelYc>}OO`DS{(-2)sni3(j@iA^U8die88BNIZNb zamuWyr-A_5T)*$`6N++)%t};~o|3&M1VbtzXZ%H9MCv_c!{%WZ>Z03222OOho4ZJ&!Ubic`HgJD2rRLyEW(`rCOlw+j|qX5f`a z*%pFEP9*17n>@`t1j>DvjFcloPpQ2Ozwo)8;9O#{chx?YAV#_?4((bvFOzG;a=-8m z;K4GL%W#@{Qh8nYjF)MV?LfI!&ga6|de5RMhnfT0-m9TYd^p}6bve*M&$6a@W)Z;@ zpUtV6QR0+`xDh)CinJ*1O~duEmCA5V4h@&gJBm~LS*%dMZexyYUTMOBVfNr=9XuVP zu#E8G^BHzGER*B+PnSKjiW;~6$3NuRbz<1sKqYST$mhf1%@O%N&aS7%xo2pZ5oO7_ zWl0I9bU7%-7otb)!znZ z2^@6&qN#k*Be!tU$(Tnw(tW+dFki_eC#5i{t}NZUELE;>yv*J!UrirQMBb}}=i&o& z<52KWr*_T&`s!rcWm?AP--N3xA6VUsM~ANsls3t>b$S;zG`!E&PVmif%>L=aq!|n? zP*C=8h`zd}*XQI|$*F1lttQfyI=yWgqHn_-nxd|L+7fJnC^qAqw`}<`O@VJ}dd~tS zn-!;a#NTPybX48OF7)j^>pX1PGk7y16=*8~PHB?Erd@7DNt{C6&gO6Q>ErhAQtb|Z z{}knxNfb8Uyb)~wvt?Q7lnJXSLi=BtUAtRIJ}irTQL#Rgy@|nDMg|$&xeg1!dd*r&uKlUW%p9`s09n`+|SOa`qFk3751`r4fF z`6Jq@IsMNkT=(1Px8>QsUrwppBn}5|RB$}!*j2nm%8^=4hN|}Vz&6g|z-N7j(W7uR z*_PCujS9Y#Z9R|4AsM%v2Pjteu4BU;cF$D6ti*T6#RB*%`I*Wq=3x=bJ(hF-uK7?k zlM@#k{r@oamT^%>-P$k;h=hQ2cc)0VNOyM+-Q6Wh#{fe+3`lp(&>$%xAl;30cjr6a z_c`ZzKLMZqzr~7uUDsNB?RH03$K&%Z=u`Uo#-@I;4xeoQ`>?K4HZ*Yn%>5 zQiH6{D2XTxvV32h7Prg9Le^?E1K+Jfi|?C$BLm!sL5|(WmpKt5C*jt~i_WGbefyM{E4%d-ML&|09@71+qa3tmJeCWcnB$h~XzlT-G z^rqkYZbJ|1=2;Umc~LzBI9a`X6ib~wUJW@a+QF zBIVp)vFkO}<;ce4CXVBq;D7&s9I@M5^7@mf7L;u9#`)`Kn$zcJnab)W&!KkQG)mIc zTGq>q1%ZbBag>nz+n)9Z%(%F$8hgvt%PrFWHzJFLT+*!S8jKDuoI8hp@aC;sg#{Cx zHjd|nWik>{GtGePil3&K7t)?1?p84J6Fo z2sQ*A88vgB4St0m0o5gy3|=3c)15O zm~Q;h-XeV_UD++<2)CY-tMFmIJkYyEUfPc~-zz>P+QOx{sbdCO{9Ws^CGSDRjGMB1qs3jwi>|U_9w3saB8n<2YQFd zLWl8fFPKgLpc~0)FNu#e>p0dHE(xmgDTuc5)-^WWF#jXpP)5tvd2Y88JWv(n4 zzyaxejB-9BKX?%42+v3ueV&)>{XTF(Zo(Sl;GpE1U!w!NVyPLkE;EJWjY?lszK)3; z=FgWFC$Z`G;-MC*?9D$emgS2(K*!;mIwnyn4|AltWb8ihx|WyTD7xt!Vip;2QzUA> zqimD+M)k=Bpz*U5=9DwW2Gtx{XdM*2{!_CRSgZhGy!spT>r7T<*L#GWH6ueq(9H5A zXXaF_m$UUbojyZ)rsZ|6EAprN1$@=I`~0@Y(Q2Q(ne0J=i%5n0r546PJP=4heByZ= zkSqWwU9uQ;)TrhbX%G&3uZOk!_vf2D&?c^?F=2lYMLDPz!VnGVHJ67Z5|(9UycL{FS#cfkq^MM< zEJJy03K^+6#)8YA)*3QFwkDxtEWwqxR?~N|!lW!Y;cr#v%SoayP1|ELO%FXkc&h$) z7C==2P~6LCuTLwx_Y;>)XYulG_Huzy7v78;^QihTjStV{?M`^GHHgv-|Mz( z<~*-%epUOuyE(OxRmS^OqndTTyGLt3c9~A@5NWmJ_9SFMv#ahw80{p-ImAMPgARV9 zb3BVW7likY4qy8MPx;>dA5iJibdYslY(4tRzOmSFEiIdzJz5czogs$6)VmhO2o-vq}flxUWzsMsS9OZeJT#;GLk7BwIk z(gWJZGM-?ivZM316nC!_Q9m$g1JQ?V@Zrq-GtB=;_j8d=>1ZV~yjAQiewjR}i`jk~ z@iORM+nuwZ+s@0mS!RNbW_LhmUt7qvFTvPMl^Ij8Y(1?%;BCYO%{9#b4Wm2a3rGLk z(qH8a)^tB~xWd=mbzw4=n_&mzJRb7Mk}->P9EY39hfrV6W$M`}3W01c(jdbRB!E{| zAzh>HBbcjPo8e`_^AmE1yCa6FULUlD?1Xsz!**0tq*X11l2qKyK309IE9+BZ$?w$& zKABP_XwS{OPd&G2d&a{R?u*eKvS%wL3z!J20RZeTvxRf?Zc0Pmg0rXJ-$3Dvs}K(R zV#8GWpRMz~*c>#3`jZ!$fyr~~jgHsg!R}5R+Dt>iz&kS}EF6X$A2GiG7 z^K&9luJX|6fPs+4M7BaDyTYTAv}2G^#=WMYZYKKpR7jqI`z~EM z#5T|1jp$bQB6AkoCEE9Q{iIhP^`ptR?ylR*#qW86>x|Ncv;??fFBe14!j1}ua!dX2 z#=bU#5cw6REGv$1ix;s~zD^zolX9J=_nY7Y{6!_+7FJs2J|?eW=3Rxehl8nI(?6Q6 ziEM@t=HvDjFLHK8jdabw7wGz@(l*}F3sHrgHpLxf21`NgisGf^A=E23aCPT)yt4Xo z?o>0x{bYN`lyI~W9DI4%_}Pca=BU@aH#y(_FpNP8PsP(N%a%=%GnP$soR!S;ob|?@ zkrZ-6C0~g(`aJPB=xk%wxRB~dNYJ1^dq0}vNw7MGHDE8E1X^K<{9`hskHGy|ZLm{S zCO-dG95zu}Vy}xG7+rc~3ceAwKY?HR!uZD}x^dbNhZ|;<4+~6o3gug=9YfO#oMe{Q z$6-Bb6H%luy&uB)YORzNUYQp@J)M;|_*nI;F5Rd$R^?j_JUAF|8%1oG{z>J)E0|xk zwTh=({$3>%&AdFW(}Kvs>N>On>!A?`|T^8dSW4 zXAtYQL$O6(f4J8h6ms2i0@-#5aq<SZbh=Z%%~<|QwUwZG2w%bPTU83qUeodI@6)Pjk9C4}+-jv4H( zPP|T@A}SV>pb7F7ria|vfGY(PTBf%3MXN5q`uxQ%XvL*&f3&^F z0H=sEnE6?`b~wXq!1<*FKmPr7jMMcLJk?z37ru9v&_a6cd16h=FZJ12c?5CGjhPNu z?c?<{c8d*k6k?BT|BQ??i#h2}j60VF)k_7v8$zp2@aiujbV*RExUq2(cAUQ$BP6Lf z$-A^tbm{?a4={KN{vtxD{HYUVKwy$h7IQ5YPKs%tL6B8KRCd40`ELLs*m<*az z7kK^^f!dhY2Rg><)Y=YEOyiht^T)s0mz~qnf~?QNdb9*>MlW)bH^kt3$CLNwG}_MW zj|wzesMKaPs#O%VS|!rvGHre66jXLi;$UoTJvzVf&Nm5h@j3ejWSr_?2-Q}|i}5T8 zxjA#FRbNG_T)Ft@xCjr3&C0~BmXAn^O>B^NXQklXKZ zVVH^(y-llwztbKHC%IjoZ=M`2G`|%i+fqUH$%{$c=j({q-1JL8?QH(DRYAhj%!DkMI&uJ>|434JtvE!ki*Zpa$E@ee}RoM0z ziaa^252{|X1qH7@G{X6z?(QzReo&1IA~seyOo-By+;Hc>OJ{<8?( zx8lY!&2`SQK2HiiceCH^L`>8PehWg-csg)l#?Uv6r9n%pi+20{!`CvBYm)ULccV< zuHPwF>_RN-aTPbvre`E&$eG_^rN+RPYplBoqWHU|(DX2TPr5-{v zon|}adep2xSBW4QGb zDJg3yLY4GnULhqB8;p^j+mA)ufIeY+NgF+zn1o!NF(vmy-T*AGU#xqwoz`gLDlS?C=#I zQ)owNat27*B5rXtqpn9Kd2~daw;Z;07mp&-;QbpUoQ?a#`HcYF*CQKJLHg?$(+WZh zMe>i(c2_&PG>sgM0udX7hE2b7b#Av}4e7Xy=v1O{<9TKPFJC2Y<-QM-yu>e_2(5#v zF*ALCO+l~y%>rmy=H*hU@0HuolHicsF#ldvr|D->)R?c{2N;bt4K;B^LNe!LC z1g5=QtHj>tEGy*Vo)R{rT;T~l4Iv5U;FoaA)OHyX%`Qu>o}hQ3BSBv+bUTo~{H$K6 zSSAtVrnh!ygJ@@j_gI6pYk2R$=k%-fI^D`9liIan>Psi>FM)w@LiA5sVpTl$U3vQ= zFSGSag%gU)rjQ2bL46X{LQ*U`k6byzG}m*ChRgOlEf)-4MN_{?(A-on)irZ*ax5q1 z4w)X+4yxQ;54omf9hD6LvVOLOCzi!LHagiUz=!4W%JCGLpejV#MQgG4A4~)-rer>* z2nZ7m@B&xmsF3CiE|swnN*Dx{r(&unEauj)8e*3|0q6gQiU-D&DpHDnj<3p&pgo>{9%Q@awGQnuX$ryt^1~ zu@AJ>!uSe}?Vu4Z-DDYeIyT(O$DjA&r_ng1@}_W#ku_j2%{DM-lD(-iLJ`#}j(}ii z*zV@|BYSDSu2Jl(kx+MAxMKED^+IGLsiiy~1hdSM-t0<wd9o(hwv5eM@82FZGW_z{!#gUoEk8M z)*i_+ndy0uw{Idf%lW0e(bD_LEs(Yt#PK~zMp->|_B|hc@jMq1+;KjSVX0VS#8OG! z55IN)IKL4) zoz)YqZi2~0zY-J9)Aiu6dOTW%>ZujQT^QsrrXKs3x2 zqpL6zfM4|Uq%)ppiWiK%BBW5_=*r_^FwiOWDRWgK0_=4%5r}l3N2P)pLZaU?I_8abL!E?KB=gXJ*~g*q=sKF zpvBkK$iN4*PVh!Y-6+rzzSOZ(l988sQRz4?2y@4yx7YzQqVI>X3>QT9KsysazdUvH zfrX{_L6Etlsx=;-dbk1us2gEvN7X69p^&|OT#v6)$?sFWMef}WTK!G)TlvjvskD%> zjKUx16uUFFpkC~oTX0-U$@f}ToP-fVm*^o?N7983=ZgznezfI;<4ZapHbqc)!kN`H zM1gvOIB%8>IvE&cSmROoksdBZ=$*)tNT*Ji3@Uq(7|5wt>76k`MhFD~vMy8c!2mmf zyR20K##;d>&*(Esg5OagI-D-i>tEjf0!bGA>b*ObF%cZs6Vtc~!r*~10F7Vni2qI2 zv|yIYTr1z)5T+;+-a1tF{7fueFu??0rz{LA#5i4juM=)$Ibx89P!;y1=$tvopKa0A z!tvI7cy8u;zMqD5+vxXXZoSv(rcznt5`uEy4ZIstDE5q00gQ#Ls0;e+Ug~;2%q0n; zh0jaC(7XP9jBs=`dqfhHr$ep#oT`G8n~*w!CRlH1Yt0^yTgdaAfnqF2ZCHq_qNiR_ zzs2t`QQU4P1((Q{;z%1mVn}l7;R{W-rq%SSxhOoqM z(R!g18>AgxQ9i7|+Xku(&4>sYXgwp6Yu!{h9 zMeWC>Czxq6_rvz+d@fh+J5aCl?(z2kz)`&3)DiLvdx`uiNJ zA_AQZR6 z*<9^>uMtySKYtaCBRp`xHgHcCjq=_$_+C;)guwUXA zF8G#FLX_sLUj6%9`#>mZgNp&LY~D)3>H2UCx#hqB&GD(;@>jjI)Ij3j1(SE*J^xsU z`plsU#rFV@VMhy!+5C5pUOGtS@)kWYDu0hk_Oyn9Pf_xEe`U818cs=%n>EE(Wo6Ft zl;%ynVYfexR&ZDUxc_@{R!>ZzMS!Q7`{Rk?i~GOtxxsv4m_Z`(vnVare9AbHrIl$=B)gr|J%Phc;?whqul9Y3{n`M-vD> z62)RU0HN!X#Nkl?EUPGWNSP)l_uVY9v3{DPimvj#hofm*OO_D1!_Wj}eUi*8iy;MX z38DDz)s~=!F%r>V<4-%*=nZrbjRN;W!?cRg>Y3vy!C+4rvT&2PK;jNhZT6=Hy_RZm z*A+cDGNIyW#`WPP*c&Se(@msj{IjXsjxY9wr${TTXZ2Dt_RnT%T%ei}v3!b*L`A{S zCkErMHSp|;-&A6WCLd;1E0Y9kZ{oV(GiBrr(U0mM6= zK|9?g#Jw2pR^e=Z6pba?u66R0lqJ^FTlF}?(AXc+jNUx4pI9*yv`O=%Hdy^F}~2d-@Ejfa(B%EIECqO5l)W- zVj!~2uSoWCZaXsRW!{aJMJrbC)U%n2aE{OE+wkSA=*^q}%_xk3#1s$PV=Fu;b^<o>K&x{}aPli19vU8)6@54w~`N5PI_q#2sC>=t8=XLx)bzYpQGK#XJu!7OT+Q z^Gztx%-2?O5!uEHs#WJ728@Z07jZ49)T?CrI#(%!-vyqO{424R@SEOn17D>EJskxd zB`r`N9j=wkSR84Dfc4AYI-m&R1eE#J|3 ze+K^Au0>j>pY=?8YG7-7MRe7FT8%~+pk2MGIpF!gdftV5_OPqGkH82q#x=0+6SvY~ zZl>*6ui%TP@c~#DNNms>-cR?3>zQA@&a@WT;``d<~Wm)YH!+xK^iDrW@4a(thS=-7kaUvMXq`v8>8pBm~29FwZq@H?uR zNWUa~-UC`rzQs2Qn7Wb4ca%9i2_hMqhQH(e{^`#a_-0{z077Bd;&IPv(TgP{Lk3l$ z$jub##k9BOgUaWzULwxwN2qWGFJrWiTrieuQNqDxGMNG+G+(e#G03XoHq&u0K`O6# z=A61zLHimpK5kPtTWh6bjM63?*J&3GUr3V2;y;$6`#gV8r-i?>OrHG}!j&uxdrvqV ze3Lvg9oxFu{Q3RNr$u=$v^qKoyaylQisk9DR9AZ19*dhh%R)-6MaY!^UU67>-CgRU zqWWas_j=*Nen|BRRe<<+MsJ3Yc^5mfy+5aNBJpaK)u|%XWdU3wWgWR!_h7P%Z1q%X zTA%F!Z>5$gFZh!Q$E{AbAxjx~AS4!6O?qWKsi3u%q0(M*KU^Y`^oi3Rfl+bdXovvu zZV9JjYfyZgdB#(qNZKy((iHR0Rk;deai70-xtD`nHgF^H)3I z!;Nnhk4u0&NC=-~L7Tvi;k@}{bSE1WP9x_dIfJiMho4>1%31A9pAy<&x*xdmQ zsiisXBX0WenDKbI+ftu>EhD*G8dEbZx*k0>%|vMv`(396;Q$HNz!%n0(Ns~DWT{Y< z>&Aft-HgU^(^4^clxY}r^(oGb-&=yM2__XhqIz7hd&YQX{M>}eP083pv$CoEM0y%ns!+VJxuir(_pdFHEc~#LmfdO*;wj=@7 zP5x4N!5Qv)*L+TKBQ@`vm8j(36G=JUIwON)V=3gnx5(NMu zFBI+9-y!g#bzMD78ePi?7aU^1OY$Ez0!184_o5n9<_7gZc56_fM!PzQWkP=Ip~sT< zw~;8+y=OG2VOoJlHWl+slw_qz!pKy}$wdSs5m?mZM@|!b3TQ_!vCbGglN5hPygOBD z*K#EiPW*DO+LD=odn}?S2L7DNqnUtLhR}?NK@2#ru@{^HwT> z_MyA)=`>HMWC3#UH~07Zx5{F!;XOLAo){~6SQDyPM*;a%!NnUez4vp^vRb18GCH*^8+W5X`XT)EyOSA=EVSSl|6m-rWq8lmF>+JHZl0sSve2_m% z<2!c$K#B1yMxSo`yjKVxGS}&RG`=fLUf!&%_LV%fGq6kiX#K3RyU}wVkAV^qN$~K(5P%^sZkK+;9mPK(TH*Lj1_GWVbSLGy zEt1_2x&hQTdOh3;1r==%P4_25O~W6XLKvWYtAMkI}@_3dPA4iYe^jrheYAJr@F{1>0W#bX!>Bh0UIvNDfiZ z-e#9q_mC2_Y8d}s>$Tk~u%7sjlePAc;$g(ldnG$|T~0`5@{b%M;bz*sEGDe#N#ga{ zL$tuDu5=v~UYdxpZ+sPl?fVyxvY+7hd;a@IfZ~oy8J?x0z|Q!?Wm4fQY3{3dht?wv-MXWfA&STP$ynpNuRBUMytkF zTD??_A3L3fCRD0`g;zoQlX@M?0?pUoMLPLS)J$&3NYpGi*w}d3bbc3K@nzvA6UJR;0`?i$&azadAJKmY3+!_fWf z;KS@X)y(|JH8d0Br_!c>AZQpNY$pNfSSob=4nY`i#AYPmHaBG(5q9LZd-RdZHMYUBK)WaG^@nGr3m8Yr^^p7TxUa z$;lJ2yesY*;VK3o$_*j1N+CW1xH6{c0ix;Ca=z|%LgWNAqVCf+sS``s+^)2AZGO8A zVB%4TPk*{r_B$p)!<8%O1b%BwE~8|Cj7CnX&sGf^l}rQ`3)EoNhSngu8iHkKeLjY| zS&B$sJAAr3_FFKZTfo2yrtFr0ao1QO4#pg;zCC7xKc6>I+N!u;CFDSSqt{#MVdGlqMy>05uQ14iDEjQROot|K1FZ-7EZrieH8Pe2QlrJ>i zI$T)l5FzHt5yM_13nrf7jeeWjsn^0`R~9tm7wQv%M%=nfooGGk52R3 z)Ia}zfdBZ2z>kJ7duoFp3KOpRwtJ^CAKLTHF>hrf^PCLP+VTEK68s+d>Klym4P-QR zQWKB7BhOBx>@1cqLl0$LM4e$qlaP57dcN|W;u#@2y;7REb#Q|b$1GAH7;g@$*P$mK zx2CdA*;wtR&-Bx5iGB(umI=~&V{cp_GTZ0i4_v+46lcL(DOC{AvD;5`{ z_9gKdWDm@VGNC*u6vO6cHF2EDE;9yWvsi)C4*D67=aEKiQB$;grh9nTz~t16Sp7F| zK2@5rQHtyaAT3c(6aTXlZmjyiWg%GhZMHCN%}MdVCmz3n{h25+DB{8x#do zJ|p^(7KWl)K}2B!tFx9vgrXF(K5KGgz`D|SrN#6ueL{#iyP5iHjsYFY%L zKGa_JFq|aTq!Y6(D7>6#z@wX9GL#2%>9^-+4RX0LoyF|nDa;y+#_{i+p-eECxku@? z6m0Pa?^=|VHQPhV4FcW=-L^Lv$8}R5J}+4e062mGK3)#Ke+z?HE&exh?+GxLpoJzM zvH~4CGKoTAirwf4yh&wV)@(@+{o+GF@QNDz4JfPp6$&D?8_DXMR)&MM-VE*OwYKnA z{=d6i?V6MY5C72PR&SHj%pVXn8KnOxV)@v$Sy@mBq`EXkWx>t&4KIuPymm9Lw3A0@#Lal(~?aVCVJ${ts%B*DUDO$ba@EXw!x!_Mwd3dQ&e&N9XE;;DY z>_lFZk66IApj#+XoYKqZ-#Covp*=~v%R!2)Aj0VE9)T5)byv+IGld*J6N<sE1A{HoXFnjqY67>4G2}IcUpM1qZ*DhjzDUyOW zzf>4v%F9{O)VjG+P;!gL9MpB?wbGhi3ztdewhYDNERFV)xi9OG+ zd(`T8*orsVxFGqY8S}fwdf7&H<9^kcu`DU%=>upnj~Y)vGry1v)r9BWI#Z(A6nQ+H zQuvrLE}0qzGf>Wou)HGK!lQ*xReTp4U-rO17-0#VNVgMPBAu*JoDIVEp?<4 zN}%44IKd^b5eWY+rbyoOYxeJFg#S%lt)#d}cYq7`(l zc<21L2w!@s{1Qs#%-29cdaEaWx$u~K7Tt{3zrVk5@V#3TZYIIplLEpngy)LRe5^ z(h!iR;y{`Ry)6>CIcs|L39CNPmb)S}^iY6pPnd@8W!m3tH=9<*BtAa;gpgnu)GYql zHS7&Gm5Ig-5Fz9Ec-m)F($mI80^o_>ux*?TQ3cUSi;oG>p!hs~A?Lpl5RI-38T5ca zEicJxY{ltwPmh@@hOCQy-Qwg5&|k+j6{OC!Gkqsqwp{{T&^ymn>32B}UgXgb{F?bf zVGq{2SCfQ227{| z2JO(Ov^pO7tq<~VSggG^BaULn?WKxztEzj?5m-^yqV&`$xq2iR9G6w~edouyjzph^ z#$-0xFwN`aDL>C#4Agk?R{}q2MGifK83QEyZ4`-xdl$nOTrw>px5GlEQRjRY%6GYI zglLd`94#39#;r=ZM8DvzI{_@rm>)*ln&#jG!(JyTJ5sYs{l3b^V|BUxSVZ4$UK z)?8yxYi@KVcb8<;rd_ce$XapmsN;;^8)L(aZTI$*nj6KX7$o>?(REYqn9mFqzlZ?r5_7pYS?e(Q-eXOPfV9{#1Q04*DPW zuCq?2JFfr`k8Lq{j(pJXOwyYR;@+s?j@?`Lmn2{ELmCxWFo>)V)KcXIE!VGxPs0}du1!=q)M;A0P9!0ph+9dpxf94Q znn1Jc)MOaQ-jA5$$`^#FO&gLGg~}n0=X2`mSE_!;xH#+J^JR`B*hN#bP=U1MWgvDO z;EASRB+fFMDu}$r(YC={fVUG!-c)ck7sImE44A|bN1GbvoeEJ-LnD=zI7H8+H(|3$ za(jjT3a{^n^qKOCw~6!gF|@S+YOyxbt@t3+@$kP!)8zgAf#}2YGiBj~AkU%Ndn-*V z$cMaH=JRTh5I^PM`&}7?qm*-zrFJZ*h}eEvs*mj7;8Xr_hf!gV{8nxytb3KjhV-5v z21IeEvzj$*Z{g?Om_Ulg!n>=kF1Y;786Yb=w$PvmEI8yxJ4_5^FW!l`{?>Hj`%PGk z<7-iqEIA3K_MnI78Ic724faQqK$9k>brbD~s@R=xr5JB2=A7MIt^h*K>aP;lYk8(E zUSB(k`8E^_+?FPjpI3n;b5eFja^(n&6d8*AbN<+4o@7r3R1e_*7KFK6ZA{cd@%Q}y z3!0#}X!uL&p(J<^-U3cKmPQ?2?X;MC#KLMZbK;+7LSVvCth-NgGt)u1y>mo2%%c@X zKj&opq>`qitxL+W(zF|5N|Mj75p`sQ^yo)uDV|+=$7vUcXW;%-A%pu@8uCdG(O#9Y zw(@Ir`ryY!n@A6-@hh*KJ8=8qfrgDM%KV1`PKc_lRAsTqY@HJt>Y37=ivQo54|ydT z*Qa{x(jJBI8yWzi zSwA2jc`S=z?&l`(h!sP5r*4R&52DzBKzK=sPF_p@Wk$>ck*R=2Aq@eoJJJLk!I_t` zGgqsHV5sxEb(HM`K%>_&TCB=I_*j16z? zHDIaH|2SESUQ^dmP=kl8h+}z4|EKC!>Au-gB|*o0O0Y~OvEmf<&o~eCfUb0@x&8}e zs9%=fljEz`get0a#zr=V141g?OJ38}^v{T*7}I@F{XO zc*~~UV$5m?cHW#*A?N1Ctl-GculkQ2$1(_H1 zU4!dvbfEdJ{l*fUF9~f4uHUUofdBqEumQuF4{~Y2e6Ign9)vQOkg&vNDB3piMuO)+ z5BggAxe@ZAz`3IS!Q-HmtQR)<-;KSn%ho^F^UO}j=!VO@8)p(-SA8aY4bZ!7n8366XtcIi$Cg@<~0=&M3O&E`oPU_WUaFrhTo;jXxQnOBS- zPUg0f;yt_!E9b1DY7kiJy6iIZRtOVUY0^1Og=Qop`J02KJ~?Ck52p$fE> z0_^pF3~%-GE1xkJ1_>M9pjuuTPY3-_Kln5SOmWVZ|9M4wbCr)O$)Q4U*aO|PAMZ1_CMCv? zEYqJNyxzL9?aqE*asMCo&*&uA1%66y-@;H4m}5ooB@!Ao)E_9-+n;)Qi_@znYFguH z<^?|%MaMh^d-~dXZ8ZA|YH{g0nGiF~I=>8Jx@B^M>%Soid#`1bk5nV+z710K69_I? z)NzdgX=D#sj&qD+PTn{gaJbovwZrrfQjK^;=Ni&;OWH}H3blP9L`SJe(SQ_QHM9xUrOQuV) zf7+t*K;wRc3`a5x5h0|Cm~xcxX9Z+sAhx1^e%!f<3}CUcuF;eN5c5o828kvT$Hpki zkqLWI6bjbr38S66gNlhbIp`L|I)S0r-F_#oNZ~rI0?;)et*4=JrhNMCbF4>A?s*V* z(+2t>5eECwx5;-+e4*sMD1*^9ZxK;N_zVx4bae^E4UtT@`WRp1ws`Ws8w~0rhx)uKj&yC|^8-kOz7^(4X z3+fSz{};ReQ72ejQ3NyGw@YiLLsBTX(^e>%s9DD4bbJpMeD{PR>CC;+wO1vEgu*0| z0f^X4*&V!o+zLI)O4)g}xIJF@ane>f&F=a4zR?!OJXHu59$I-9j8w5FWNXsU z{+BJnU+&X4mLp+lmm4U))>8wbcNh%wuUhEu$glowL+Pd^))@XSZpPwT-mgFMm%p0o z1oC*Vi{zoedg}AtO{kQ!+9@_XPG5P~Mg9nb9{k*Nd@wfFZkB+FqXs1GH%lWP3XGf9 zXlZZl&#tU^h7v64pU<-L&GE_X)!Ch^Z;C9Uw19soR{8lsoBY_#EnHU|$=6Mc3B?v% zP|P9~vdYVT6$LRnGESzkH{s#Gsw(9;)CW*7HpE0@DqRi6Nk7i5AACt8j>WmzM?6&P z|8eP=Pj&mInp*dxbdp;0&k{4#n->VP*Z)x5*lM%25eFBSmQPcp#xT2(elWHOM8ED!LT@{59ZOZ`bkdlApucG->U( zE2$(W0uy+t;#U$$^B?o>=G|ayCB6^qD((C1XKpO^QI9D8h+)UWJ>NcdA6QD-dT@bQ zWB6;gG@iZ@r0UIO@*#n%c(PFn@bYLe$#volz;p$52>narNhuy7QLPS8^Xd3>7(< zIF^!OlP^dA6bwIIRJ)Re6vy8#j#!=}O?L?w{WAEIIcc6Z=-0*9{fi#H(9wh@CBD1W z*lN|_Mp(hDIOaGoua+OO%Nce?+6B1_^5<04)=pD5rbRq^<;^2iV+mlI?%Crrlm-SA zC&VX!a~*y%aF!)Ncx~e8-JHC%txB7^zkYc$TnNoH1I{ zCXolFJ@{tk{q^oh%Gv+0{QtytD}GL#B0Wo^hL_GI!i0vY4zWI;JS_sAyz1K|G?EkK z;?n~rSPiK5Bee;40|jO1oQB{nT_$RR#0>tH+u!?qq!m~1*Xsisc?v|d`!!nC%sIcL ztn`ahcD(V(5bVvmwPyZA?;UeVz6!@1FfjT;PMla9!`3bCLm>)!CFsj7Q)+WeTPno*!cF5A@_m%&69udAw?&U-Hik5 zP_L~*Xf&5etf7VX@WD;?V2uOZ;`k}`Q;pXbxTJXU3GX;b=(Y27L}WD9Vvxe~Sh!f4 zXLas}-}DWYh&~?=oMW+20sn(IGo++Tl-R3>zADb9<41F{NsBV zrsOAOX|s`36C(tCv|f!fE8{A_!tWhpdeq-NL_#`cLX6_Tb)m{QHn`~ode%51(Wk1T zW_Mz4K`!L|fM>8Gq=4zVo7+0dD9kzPGYs85NzNo60f`TNb%T z>}S=N=cqHqlUU|O<)lF~8)p@PtQ*WWi!D=u1rQ+EB38pk!iOhxK34q3VnLkXe|rgf zMwSb(VXLY&AW6y@BTYOLcbew;+n5oYx5sW9nH3QwCuaU-mGFc6UFtWQlqjV}bnCk5rQuTgV5K{MgD;$tV&j1sGt%H-6PTzA0VirH`U@da8%35 zMPo$*c{K6-DH>@>y>EOxvm8Ry=m$miR;$A5S}SO;;BbpAgB?4b2?$tEyO6jY`JWL- zUlftH5_HkmSHJc?G|_oSgge}?Gl{jD&{~E$h|W1{p^8-edZM7_b8kjLLZQ_X>-yhX ziCZ6Lr^8z^kZ**gt8@Jj_m7C~(oRTxt##nZdG3F~Ha&r1=09av*Hv~3-zg64NL~`^ z@N(qwE?U<&V9|2pa%|R;7QXOHYKs^5t#@Zp3x7Cp@p%iO7US|EiW}8&v0LIz6;%>4Fb|7D5=EI-6fLJf*{>pGjx|IJq+E$4Bhcw zp7(pdKki!OS~GXf{hfXG-sc=G-umdDuRMk0-de78N+PE-wKy6-4!0O}&HkH1k4USQ z*LbFBAd9y)l#(j^hRu&_>PgE1VS&6^`1W+U){~c-fnpg$CN*~x|EsT%MMpOUiM^GS zrQ@}1iv!5SL}Dw=_P~MRhy`|6-_icR12`02Jt;$-!YniaA52vb`&zof_|e3evv`g( z5ULDTtM!F1K^m3x7V}OcDoc_Z%_DoCF;kMnE3l1I#r#{qYuWSE*<;c z(QP?&are&f?DVsZb3b*aX0zwq&kKAx?GQy{pYY7S{+0hmeEKvH+ZnscntP0$$0K6m-S-R$|x?d-VrC!NYOy7 z&p-E4He(F|!Q|y}~|5 zfJAb4ocu--R$`7>px?z9!MIZ`)>;vKl|xLi(B`eEah?UHdj#$>G2+FHzp7MRSbF_* zI&UeuAouj`7tqV)e9QS1H>owk=@tb7ccZ=OvV+Xpxi97Z*XST4ERbqdkfc%5JYkQ# zw;nD2$32I&_%a1yY`r1M1(6f1+N@4`MtPO2u@Rvsig9TUKQhD!Ks4a2VuU~fe(XHt ziPLW94|<|vnolbWXLQG(Yc@W;VP7p{-&czKX@~Z^WN2#iu`q&;(P_h%hI|Z@ZR+bAS+YoCQ|I zOKx3>rOuO#=udf?Vv2hC{rx?6`6)A1IA`93iH08I>#SZ>YOh==$ZDdXrKCnD8ErbW zD+*82`Tx8CKYtT(wLH&9RRpAO9lk$MqD98&PxvOTYbj7LC&0D_CL;gCwh7~^*nUwg1V*-!XorRcG&LDkJz_o!e+Lr{|FO) z#JD;s#_Vph`@WuD|CbtfIWFC(%mNQ&#Qcv>98l9lsg>xS_lIpge>9a&w7c3#57O^X zwH~zUPW&d}4nFWMT>G|Ee!V>8K!8k+yU~}-x1D_x@FOc4jw2k$ZOq~N_EO)XI{6Bl z$)Z@I7*$U>Jg#ToL7!6k1#siGG$QZk7?4{A|x{C0?rA5X)H=d}^ADdb=DqsrFmqy0+7I zF}rkC(OAj}4(_jayam}JjJQsQ`!?75vboo+w?t+yB|FFh?q%BeL#8I>pgxz>8RZ>7 z2oJt0O?-iEX@CySQx5%2d%wHH==QH!HbcP{O5epNuNiFVE*)F!kY8yH6o1$=;ttnI zvF?XSY`w8~Szt>1XmM|rCbf+iS!Ri1 z8%=99P+>dR#V?%!jJ~JcvR87NoBV#gN-66S$qADU!_w8o#MIfGCRE0LJKlUv+XO{U|=uD4of(xNfDo!M?> zc2-LB2>ZP!gceXKtsh-(SZB64{yNy$c{7a>y(1cQ@ZCdwbH3s0t_#Ytp>MzrhnNyc zG-s&!Osw;aup~>bv?C5@BPw=(3vbujS}E`A&;sZ5-&*TS@~F!KatsZJ54nNi9a%D| zMn1vL{%N*l%?T`D`c@b_t1f&dmZSyeej@@NW4w@46)wsUP6iVcKv!P}ds1D$IEHWG^@jNUs;Q&a7sJMq@iMYiwpa-P(rCP=JWlz4P<~yEnA!p6t`004syoMZ&rZ^ zRox9-xnCzHLN*f(qL=ptus~GS6|L`1=QKpj0HtX;7>c3dtmIbo;_+B|Ox;^U&irnm zs`wa?b-F5gitg^*Dp!E|*qMb*^q5P}N{XkTjsR2{Q02ZQc@|M}ivcnd#I~sGcEyR5~ln(9JW-(b!H%wmVFlEe-uk-?4_PpS;~l# zXw*7Ym|Wr!!DIrlqI)a&tLz8rBY2{ioeOUcm7rUuJbF(pF~0wxf_2KO`Z&*<@t{Jo8i7oyA>r3)hNJC#clNa9qgbg}8oBLDWO z)!3xc$~jlWd9UJ)3B$GP%3i0-L?CBjaMj0DN$se)H$_=0Zv9v-r2 zQc1g?K7u1mJxBBd_PS93Wk#8zWCyX38b(k>CKyAzdAC5OlYTg9>qV30b;9nIlU$!Y z0dguvZwx)uUb?f78XYXJu`Ud~b30q}`*3}hPe`!ZS7fWlS&_dMRWpebjQ+0G;~iF4 zr5FQj4L>`C}h*GX;35zdM}wD(C~R`NPiP@jZ8Ee z*DKS{l@Ke#P@SU=XqG_QhiyVhxa1F} zY;9Ndy_m72%QQk{5iunx5A~qF(A=jZW*du$CmcB@g+f9kym4xe44lI;$jVO@Ko{iB zd&m`knA-H^J*<48)n~3BNfTDiI0%p8n2Tf>2 zR?@I(*;ru-@S%R;4xwJkN`r!LzW&K;6wHtZCQcH3(l8eXiGy|!Z$mo@ob|YGsFAH( z4L|9XbA&LzzU>YsVfb^6s91s@H_|O4)pZ=(S0+_>{l5Mi7<5WeGiQWceQmi<*AAfL zX+K%*RhMYqADv`JENxW2xy?G&7~}>WER48G|DJ`!!;`{t77vruug;O94D@FtXJ;1# zhUbPuoUe6nOBq`&X$-HA>_2tOz}uJPMqNfLwn+6%Z!`25{A|~@>9A{8gR<>Uh=S)L zWP(Qw0>6M^9{!w$N^X?1$>wg&|8joqV z70kN_Z}-N0vev;GL|p>DZvSa6K~iu>@d zS#Lbe$2qt%!6urW7JBhYI7lm+`aoH4?ygifaEh(31b*IPtbe&Hznf9XsJs=VE6K!*)do&Fn zCV~fY#0lsJEnlvEX!Kj2QewE1TxKWRrg6RU214(8V!yHcc45Qcyz_=K8$9MbFsm5> ze)GvpuT9L3)w|XezZI2YsEF1E4fT#7n2=Xpf`Zm&g?ewW2#Y%G>-F<*4~ZOa+-8qtA4{3x=w*Y3qMv!9ER zls|)ALTclC?f%E03wq~ewc(phbB$-;X}ZF#Z#NIRXUzA}^Mwaf*~ACFx_qrWM%#J4 z60CE2P&`u!DT;6~uFj0arv`EL4dt<+5$9&UU~)jSjox#-B$|O#EW>DJ$^}yN?RzjE zz5DS}#!J|a4n`~CZ{dVqxiys>T2A=JIA)E#Pa#qTm^@Yse6EWmvTxEN*yt zwiyFa#uezzI5>?Q_o{j|AdRWGix*L?nVDiM|tC2lR%nyU_9;cYk%NyIQMNu zluxypl;jb;-kG(eGT4rOc1sU32^Uljj9Q;V;@W2WXc!44Z|g6kq+Jm3w@^r1)$46XvB5a8R0KFvxFI0t8j;Rt`1 z!wsS1YT_=q4Jh6udOyzbR?gY$X)x3I{J8 zd@Ak(_pWRC(Q$1^JJnTQ{T(r=5Z6uGjbAh7i`EC5f2!}P#3fjL4*sm`L9ZTz(o2RO z@!w-x)HNy^gZJ%x=(Uj$@9g~>Ai)%mK@=*t`o9qCj>Kt*%6Hs&s4Qg(NDAlOQFGcMAq^5(bd zoUTG>nLi57g_@A_f8;+7#1iAEXEM(8gr`)S<}Xx;XQS^Y+2yg(-qgfDvjLcDm|&*; zH!N=@2Rf#e+FRU>uRA6TXMfOD*xd3U0!OFxqWm~#jtBc0hFfyJT%`b2Oi{M4cfiY^ z2Y18Ph}3HoRIW`J`bpb;vbz6<4n$UR_F+@lrdWPsdc?;*H$Y4*aghA#v`M<8)S>h} z;wYQC2C{gUkDe}x9$*CF;X7^F{J?d_Qi3ol8BKXw!u7uq7L8AHu@cUx4}j5$^OA)P$Kbm#AL(R3m~;Dnj*b2O91W$G|ijsdcDTayBCC$%sS*~ ze?(kdzRsQ;uC+2*PR$t(kKI}pejDHUhj%)wHs+&Q0R43gRx4Cv4DO1%dO*4cQcxel z8lf2X?=Srm?%)8WCv=gZqW=?`vsk^xrcBR#D^W!&@7zy0;Ztc(HH{hvEn_Q(uUac4 z9?^+`l=jOXBBO#{-Ms(qtnuFXWIf-(;CdywrhfHC+G`&eU^au+!A<1iViV!(kdVqY zulG#s+Y5jOPHPW9KVJCAty&OSe zXt_02B4!z*dLU;s)0zItQFGO!qn_)RmJ;$8CtVV2y0?;8pU%fEtW%AhJ1JLv$x&?> z-iH&apEQ=D=Z47{tn5s@4sU_g4|Z?b{dT21OtWyxj4@9qejy1&t5 zfgSJd{2TVOD07>=4p4GV(wn|P8*K!(TzGgSfnoqVH0sC$5%(tAnc;Qw_@($w=BkIu zo8GO5RDE#*Rr3!B;#vVU$;cWL9!jL=YBUysBkA?cTpm^#5NsB{N>5MU>@@?WT88dX zfXDnHopR-|#(hsLWZ_ex@3ZOp?ZLs^SPaky>BqQ4^$U3hwGwH75Jlx*gBg@p2P7x+ z!6dflBo&4IF?~tmkyDJI3c_9dGs4)K<45_69)@Tgl^1?Lw6e7aN7w6eU9|+CHHZ`7 z_b;n04(?H(iXPwZ_J-RQ1q23S;Cx&v{;Mt}ftmSFsHvMK(Zf$?XVmbE_g4Ekd>EgA zp!ObewOjg!Kbo4CcuK=sjfhsx$*77EiwG4O$?1A5LCBJ=Ry}N51wNc2t z(M}L0!kcvp0&OT@W$7}Fw{-=bmLNWGINR9pd&;tEtEI)Z2avrqoF|aX&$n=ArE?CS z$$}W1QSG_Fn=R%Xm_YmivuMn|g&z_11t{fZwU_>kP}W$9CIC?0$zVM^?e8H6FrmEc zxH-dUv}Ju0(+>b35<|qpcrUIMi6A_`py42rshiLy4~8eAC(MVCNHCmv^qBX22|D!k zPWI|Lx!pUmxI5TS5F71BK+N5kx9`DtrCapU3zlauSN4k4bgIUJW{RE5u>!7Jv%GfW zPmi|--4iZBMh%VeR%}qmf7=|ni)^6fj-Lr;<>qIjp@li{xPLr(g;1bw5c)U>@$-Yt zHm~~90*+YztrkqNfe*>@$-2W-|KxV@%%6R5-dgC8Ka^DZEdTH_gaG(_f*K<0guC8YCnTBG+HAO)v_1Tm`hw)WRLrSBeRvx z^YB^KW^y>=By&~z_1|#=tZ5KBG9rx2-yh0AZBI3y+L0v%mhum8QVg=W1ZaTS==pR( z6p}Y~pqackG*ImP`23k^I-hdArns16-L{N7vzDo?MB|9l7{tRe7axzfIq|N^j~^ER7R`Gf?-TO)H_{PXNb91-g1sShEP8VvD=fswf?09&V~8;&rTdIcV=eF z4XIlSOG=_tv&BWOQLL)zKN)x|`(p5t4Iw_B+vn<1w@LIaBYgSoQ2+Zo`|)fEEjkKk zo7pP!+!i(QL;15&;U(qs_Jb~hKEEfx-<9R%J=LjrZ+fNgm%#sR4bF}-)_8K$-d43G zz9FfhU*+eg*_jnhfM2up^eu;L$?~8UqNWGb#4+{ck}!{_oXwsI(?9Rr zrp*_1SU=3*EOe<{UX7iQ>k7p_7Gv(?j9NeHGc#=@L$9Q9)v{RP%)rStGOIfkYS}Tx z%h9zddyFBAPJL&b1)O2Cydw~q{kE8XHL6+J>|a)p4Ut~TPrcIDg>r$>;j@vJ;!i{;2CZ=5IFzIVLTX1|I;B8x}o*qGazWPxcH=r;U z;yv+`CB$2gX|dyxYW30<{;4?Y`=>#iKyjyRbm0*CG*{JeJ6SJ%lMpI(93wBNWh;h> zG=m%5+;S638LhJ=bYy)q%^;tr=(&WRjr%Ih{IghVi)Qp7n1O$cr88we;|n6d0A~lD zM2^22n^(PM)iw(0JuadDbHSdAv%0_kD=dfCVT3fGm@UC47R}Hn_x+(s^)K5I5T8 zSDocJ^?4@~qh?OQ!4L9TOQ#s(OrdjU!ea z7w?|KOv^rfLz5QPz3>FIWdV`|`o)?8Wd)|u9Ng7Ncln&-F(OZl^31Cn9JH!mxNE|r zO9G%O?yU}tbCda7AMzg_{HEaJzF1!Bt#K@vrgif0c5E!uzMEC6iD{r~0^J*}$e#FK z?QFuu`<{rEJxY|}G-U9`|9!qZ^SA`d8CtE=OR>rr|2|%+lV$f@hCv9`%#%(?iae!S zZ>4#%t?WwO#<~}#x>BzRHEmOH0)fHrufN$>am;1Z_&|C0UFORr!mIj7$|0dDIqXETidn`bE%}-eKM%$p-^d(8hwnsnyj*_S@{LNv234U z0BSHi+>M6F88j%&-^Njmn4lsPg9OHsg7XCE>t*>X>V3HqJ|5t@E4{yY9*s>Tgas8I z9JZW_#LxD5oQUvpT+9o-Fvku3;9zpa+L9d& z+%}T+KHiUoYf79<$mR04y6@xf(*a-u_g2znb7TUQYV2+h@xv21e-W|5@PRNzwEV#5 znmWs40E0Hi-3TN5{L8XG&VD$BTZ{g9Yn)^;H793$b3A9f!D(qstC*s+s3^W8N62B` zYIx>+{tC}%(9_jPdT;o#cqx{KD&{1AT5XNX%SJsJynEC`d@n;DZL=5wE)87aEZr0u zP(9D*p(<&)V{u#t0$RK|Gy`9yN;})rteBZV&83xRcK3Bso8X*7w{l<41{y^Q z(GkTqsv*$NQVrJp!e9{6-fJ(mVzv#(i-NJF7A1M@)1~+CAYLx)(5?Nhrw4WE*U;7Xb;%$5Sg4Iunr`}cmK0@GkT_KQ(aCuv_-laj=Hwr$qL5iX7SJ}%PQC3rlWybN zi?`^;rkG$4@8~>KRQ3l`9+QwzzPD$wwp$Wz=S7tYS(i;h4)RX9@xHNWoS4_5&q~_E zyh)PsoPDaX-Cr8Y)z6HoIeywELjGKr1!f!xWf5btNK^SWScjJBlZ{FnAB{WwGD6~xMC zs0S$eYR8qM7E8a7t}SE}|FqVpzAgcfb^B2iLSUMvX*5){7B2mO%%~(p*z3$gh3Hgf z;DEz))uu0OZqtbeVuY8#l*WwSE5EKH+ez;?a&HKU;r9KV)b2d)A%yH>wqNe`)dyIt zQnaObUb4MU38|^0aTCpQIBSZ>KGS%Ri^jzIMAJC(K=nAP0}p zA9c_(D7V#sD<>{kmtG~PLQ#-?f7_V~DQ8r;vvGH7^G4L_gsA;|5h>%_OIjhEq@=AR zin0iP^9^oA)mnxCv5iwFir$&~@6oB%mi-@>8Ubx6p6!isuviS=bfeRfeMWP0v;8@8 zac)U|(mJ~|jLiT!U2a&U#)mN*f=4b3J!W`S{-ItAsP2NWa=jqHqT~q_O;arx1=m;t z1po-L7}1J?BK{_~aZxnn=# zg)WEd&`sp=0d!+{HXtugh4~hCkNs)EhM4K9HK|uvv-xy6nD|5YqGfVgEPBPmv3C{) zPKcx~tY$+#8H*zDssVyZ)$Z~eXa)QZ3;8G@EBLJ=qi8~-Q%ja#I!6qj)shZdqEeJQ zJV_Jm+xtzT*6GFm-toBj@zu`xVykgorAiq5>X(LMU1hUh(LOw>b3wx;!fB%*!t3$l zO*kD4=xF_|W|-x}iL22#8zcstvG+5$pQ4kO)!gzdLYlVGmSCxYZSfA3N*DNsjkbqw z2MeF}7wFc`JVNdPH_6s7?2yr$7=gJ7IC`htk6L<9TC`X z+@FE~#xbU~*asTX6*eP=2fNH&&05bV1zTW_{N`-E#5(S9`e&?u*$w_(o#QdWXP~u_ zayeSdqt#0Y>WAvh2n|pE-+gwe58+3GzhC=gQ8xAQ=NlgOFjGFmRAnli%8O)$u$qxE#DICx& z5Of*04s$iuxl53{t38C%#>PhOk_Z20BzgA@soiuLUj1C9DHP$vq7g;kW-zHJ+4j=YvH0O52Ucsk7_~MY#!iqhypZa#a+7Z!a_=x`E#I>v) zxKD_#-R09F?fLqJ(W&J6M)zkNg}5Ex{<;DD5YK9V*MjEnOM*6GzcQm>*>9**3~~QWt&M^azY2wdXD_PabLurZ8S3qMe`Qv`$D z_l21`5r9+vPdq>q134Oaw0F?kS8J-ZVaP195QS078hv>D?k9KC-~GD3JsSKY+89`1 z9pvYLU@;-bFC}(gH4Hd34Z2vy91QE{^D_katSxH$Vx?rM%58sqL^%O|A=7GQz_pYB zyp|H3Z97~TyZ_CfjEg0uCFkx887Sd+_BgCA%#?Rq#fc7op!_*0EYMY`EPk2!H8U?S zOH-}aX3P#1W3gKov^K-^C0M-a?`vKsh+~@=S+aJEyZCc&e@C^kna(DT->Q@genGs^ zp8EUB@2OxSMXcUqE3uDn0|YZ}J+h`DzT52*2dPk)LMG0JuF-@x43WgBVo!)i|}47hD1O_vPD-ff`?6&diU0W{;XupZ4V`cMM;H+upRsi@yLj%Y%k{ ziiY&LIqc7sFq_POGqkpO$0!vrgfe!jy&BvDhkQi)9YbkHLL zCi+Z32LeALU>-m}FI7NSRl~vL;`gG=yo2{uGvKb<26XVn%Lm%?#E}%Xqmo%0pUrcV zQr{RgCZSX!mpRkt>4|c2E5|58TYs7Xt%570L(LebM>9CRsK1|r8DN?~il0xF)(nCd ztwxXzy#fVjG|_>o{717->F*t5+uWBm?#>ey!YZ@`7oKdJMNdNTPVDmfHS*8gm_F{Q&DYSeXM2jo<(*yE#Y%mAE_Qei0^ou?JcB8TS>a*HxE^1JLG#4VPM z+gvfaJO9s#+^&n7<*|oTFQmu;lv04ZBWe1P6gtrB>Hl%$2cx~o<1gH49rSsInE;>X zxUs-e?HUS*DrLirqCQ|}wK0r%Np2C0Nxp^sP;XvzTqq(ax*%zaE8dL$05EQq5eQY0 zn6Q&y>pte-yy<#FfjIWR;6)I&y~Am6dpL#|Fp zN{FMyxJ+!x)#IRTWR!V(9m$sc<5p}F;Ic)wto)&;(}sVpJk*%qx$n>S-knzZSI^bj z)Fvd5xv#|v*Dy$URRQG{FuX2MZ1$DUg|owg&;3H(r|xWTq{#kMtv$LGNt42}V`Y&N91rySvP=lrpJoM2v!#QF?!v58Rz>tMu<-|7dtjd+$zk+n8-8G$3j@JPNF{)7L1I z9`EtQ`RaO&1xYiuL>(a3Fs@Ges&g;49c0D;&MIQQ*A4t>?PD6;!_~=LLBlZB_9hX^ zIL`#-4PN{cL5gYfZSv#3z_q?b74 zpwA|cfG5%!hN6Cy?JEZvDc)DdMdMsmSpq@{r8Ao0$Phe?iDX&{r=cmp1eYX6t^y)U zq9oz?H^%-3-7*~s3_i+Dg;hm$H1Gw#c+&%jFC*!z_nmrM?Gzn+q>rn~f#Le#goVWP zyqVgr>XsyYa1&e$e)+Qb3l3;k)uVD%cQ`@p1_K|%{kW_h*&CC#UKgnjYlN1GX=SM3 z7`dVSwuO4S-S@Na_mM@Hi=V?>a>2*JB$CpdVOQwjt8-M212B?2hp%r|&(xQ4xAwTwNzjwD0Nm-q z0`86lCAmkvr+OWYE(@lWMvhf8FKGv6_>Sgf(&UDt#)?aZxaHZxqI)hq{cc`20Jndt zm19mg&yPDSc=5Us%8hLxdv^N!dGz$py+GW7-L5%7RA6-(w_;F$SH5rf^F$);$f3Ch z|7(X~jU-D(%GpV}Y(RQilMQp5{usrcC}5kbg$dA3bP1XpII}qrhF^-x3psUA-_qP= z3umX_Ft4mI;)520?WW~OvIfUxt{b2#U)9d=%KVXfa(;b}buMf-*_3WNjDvvQV^>r- zfSltgoU)2&Ix%EpiN)-B^*(0Jo&i*EMr^L+;SbA(j0?fJdSwcG7gJ%wYkWJ*)N>4k**ThBO7!y+@<%Obi{YF@Q#dD73Xf+pR$s%P=l z=QA=ghQo;H!c4?xomV5-KHf{ZBlMMK!&tQo!@8BF?r&UxV!KWw2fRQ3JlXqv5iGYM zd3&NQ1aPYpOF#+4mTXx6Ihj=d)8F5!0}FTo*vJDHqWH0d-b8tvryozgHUqHJj_USv zX35lamn^rf1cULbYD)VplO#Kw@WxK7js=$ zPkEn<#F+kDLR+@5mkl~x^tXF`Z9b?WnDk~2qCmAfvEsGl=${~i7YvyLihWrV#qk*W z(aAmP!cGtj{B>})7+?Od&;;?aHxt^mbbs>W&q6oKq?ZhrJTJ6^o{%EB#yWK7D=r!eZ2oOH}YY1bq_}dxLr8@E@qp!cB&&1FgnVkAzn1g z;bscg^w%H0R3t*`hv=GJb^YoNi=_3^@C}y&)lPxmxtL)fr4`kM#Rd32&#}m5EdZB0(csQ!e>C&cJGf7x8Nwl}Mv`#UAJwPi`YuOMR-Y+*0b zijJPEcU4|}Xf8Q(@Tc}FP7pBF;TDXHDOK7ULmiKA7vL~nlzNQMWdTO(B@vhWQ9c%% zgY!*5C<5GduK*O)W)r&1Gox4J^hA->M!yosI1`zfeCTXCMdJbtKFjY_jr~ctxa0@k zy99*vo4deO;@TX{Yv5*0%jecox7TvmyC0#Gp3c#bg8sU_Byg4 zzSkf3=ITBrQXuR7ZoP`{YR5!5udfxTjpcK=)INhZdXBUcj^J3mUBbUyJW)Zbo}pN^ zZYz2ZdZa}#6N768wBWy$UD6!tD%(4VH#0{Ae$nztz(QXaOz*IN}WmU=ZxoOG`_3>6yh zK5T5UT{fhzZ&Wwtc&DHv5Z(`|nrB4&lq9)Sc8$^hSQSiQ`5jTd#PFrZ@J7PUG`rjz zRd*I!kOjWDgI@F!-$6Pc?>**25q1M@dXKZ8uE7g_##Hqw8zsCe1}6Dk3p{QLX8Q1K zjrGyU=l~{SAj74Xth*<7nb_%tCZ4Ux_t+KZRwV+53Xqim!gSV-$KMoqy&b;qGW%=M zm_mfUL3b1pGNnxlvg4ls`8eo@ajC&tpH{kEh0hzHayHi0q46qyu!8`8)|!Db|MHja z*q{e8QTBF!s`(gIea*B_&w?(lu<8pYe*B_ckkiBiNk7zP)BoH?m@F_B=pXn;swAFV zbKU^(<-%vJ_AE=&fHj4>Uy20un7~aCJK)c(k80a7aTzaJ(=YT35a{v7t1?`qUOtcM zNaI|CF6Y@{rry$;dZMIZo%B0qTBE(5hRZ50$tUsGMW$^34k@i*$)3+YAuYdEv3h!1 zcndR+GhLgwUfiOzZ=TqS-z5e4*>cy^G~54MEmj4(rL|^ioL6Ll3`@Ay8%}Y${fFiH zT*PJbH5!Rfmp3e82B;5At($=(PSe1#FNSvV7H~!pEe4aS;P9Jwg-o>n*4b_YK6!SX zv$o^0MCoUehVK(uRcC?Lg?bl3^DfNkYD*>H>gRISRZ@A8k`n+_#at`W8Qd0UoNyHz z6LXn2o&e?8a#gfU%)HU0Ng-d8-(oMIg{?JPFkIcy9z6H#QBM_qy?Hiwbqcl7giI*)-4~v5h!M{(6Bt_NI3!u2JD2_xvTUD8I=sO zln*f0Rw}720#>Qq`JdB)nO!`tSO7_>Yt630N>}0QIqltYEnvysz3Y&+l<9lANw!lk zSL5+T?r$58toSWp-4i}(<&tMrdN)Ul4o1>As}{AdzbgR1;pDjKNC~ThKA>IJkOpJJ z`s&#O#rPw>E8c)v6WIXzoH@f!9Qx@m{VHg_(Sb9&?jc$Jb(ivYxreu>Z7ZZSn}pYimAv@6dkT26c1wn| zR)S+mEmRU@rDaO&gz}7N7kXe{9)?x6P;pyyya9-GyR%l&XF4Bas`X4fu@=M8^Q@BX z;cW)Z;Q-ov(~^Vp-Id{u)Xf3L9+Q=JH5%9*1s@f4N6>KGj|hy1AUy$FWIgn_=F3{& zM7s=Yg{Xma^}kjt+=P1eNdQr%L_84}1$Wb{pK_PJH_1hJe|N2# z)Dah5m>Xy7H(gW7S-%{IQsfR5j1Qm}DJ4XkMSIkd)xiD1RUL1qR236g^KgNXeE^mk zbz-y!Sl*XVgHUID*je&Yu})Lp9?WLJg&_7wfv2WT3FY67&6{r4!_qRp1A_{>a1 zv;aUc89d@jF3ODc5qz()3s}IY{H}iO%GZ=S#oV5y!2K%f-$DL6PmHU z;XuA{XFQy;gbmG^hP2~9v%_1V`0?A02qdU0jzJ51gVFsygB3)KuQe0I>_b6GId|@# z&r%sIw%*(@o<=+IjWBitF=_|kp?xBBYwH9qIldX z-0tFE`n_9fjhsjRlrp`+%?~8Xy32NeI+UhlMC{>!oLd6I9ig%24B|<96EGO%y|=f) zz!+7!{C;^Pn0S1CcFjr4`)^e05QLr9tMv7`ZL1A<*OfrZFV#)+>>#*(qCKo?Y5Rs}a>)C7Tyxg+wBE^S_{x|Ppn>-$p>Ba9U#n@MlM*;C^|2T#7F_Di(L)6ibEA3~q zEQ|!VyumNME@Fxq#qp55XAA0YPcy&&Vrmbxr8X_$h-vIBHBw^!Vp2Z=IyVk?bs%;aNh>oz#VJ3EqeatxaTDw24)V=OYc5oVOp*8L*l;A z5GPzgcSeN`(WU(tXVJ{2Cks__-s%=us(l|#zkh8ta~$cg@IGW34QxTt7LH!*@@qbUWg!zob3e-`jj`jtI@pw|I)Owy2b4XZE#8{{1RyM6gF4hW}& ztTJ%B3XKFsL#MtcDGI=V$9GZ-hs|uRC!VmCw(%{{#b`kjPkiu@rz~S8X;u-JAI) z0kBZ&AKS%FE#O2RlG_$TLEG($*AJ7x8gHF0&Z{B@B$rAPBt4Uh6oM$_QvoNjw7a5mg zxB2A$&$hjg_>v+e0f@&hQsq*&%&BnNX`$i{xHO51>d2Vc^{C^M(zV-N_682|?z zkbpe$fkt{@%C}!8qC7E6I7x#`5EUOsv`FG<9>eG(qLzV;8NaDzA{Q%Q$%y@_v2!K< zqpy?^CXS@>g{ZeX?8p60i{`IVv}GgEYx&*Qkeg{A`Cjtpgk}n>F)9$#dj964(5+dP z`wOwwHk)z!2haXm{N1cDUwv$WM&XFL#pa3Kmby>oBG3kA0GRI|e896yl~;Sz-06bN z6d>ZsD0fvnc-@^6 z$yFKJMA4<@MgQ20hFUVzaPlxEp(Z1=+aQL>K}7rJV9DpQpHWb}<<0wqVjG|K%UhtN zua(MXH(vZwQ`1u{gGU%Gw^cZwmAVkwT>G&k%#0lGMWGdZsl?HZ<|*(>_p#q44OTP_ zCsz1r!}x6&vn>#6m@qggha99cmtAd*vt~FRuJ5MY8!NnG=7_9 zx%$&MJ>(vG5Vi`6_pEWvNK$-`vCsQB2Q)`qQdgV#A1?ryw=00m{*19N2TT5(WWPbR zqF=)GV~HU2)p8k5_+WJc?{}@{?_bGQ+PS1yQ#n?$>B!;Tzftg`3}UnOiw~?~^X^6- z&0E2|&Hj$G#%b|HVlDf=yB!|;Iud|5@6rp?ZQJvu7d6Tae|5s6gBdezAmgb{eWw(w zSgb7-4f;x?v-B=LxL)nM5p{|{V=cnHZ)G7B}H*@k- z1F+Pr?g^o21YyT#8(o0%HK-qr<|36cr|tOdYG-l#7j!Q-8Ya!6*R1gLn&r@K5*>!3&7qsi1?61L#iQN~3S`a+Yc$&dH@MSlN zx;Ou4)W>T85)W}{Qlvog5xF{n)DmI|OOA1NeJRJUcv3LDNoaVh`|FQ(ez&zx(>!TF zY3mxIcL7kO9i3hvs%`6%$*P0T9b+defF3*R>ZHG3z{-PqtTT`-yfJi6N|HPFLzqX_ z+xXt3?P5sXvO{y2U&VO~y!!%LHiv@%oYna!c>W>N!=i%g`q3r%o2N3Ov>d&f8bF;> z1LYlsi07;ba!wjPqIsWi^g^YYwhj?Pq3@~waP=n}qOlA-*E7DmKIG-T-r|311#4(X z{0l|$w8-D=$!z|l7-H^73<}|F#caL=4Rv*yd+4u@n9KpB{S(48G&E#kwqKpdwPusb2musxnYOHWCbWVgALbfUxW;LKgWgW;W2w0f}8l3jq{@WU^Kn?Q8; zQZ7H1Rx_{;%*s$S+$p66Ppd+9piJ}(&>LG_Rh`XfEC4AAZR#elpX1|7XPL}*?+VW( zo}n+MKP(3{{kVJ*^bPe@G){MnE3#Yd@jJ@|{VP?>S7=k=1vFFb@9d%qpAz?!(%kvL%9X z_(L58R->3-23nMF?((qWRjV_~jw=d@dLRJm+)kHMpHiazWYxhCUN;yd`@e`2$?AtZ zfBx)||2Q$JtmwA*q&!QY-|y%ebl9n@Hy|n^3Wan$kVzW+M)*zGwWZOkgm?Cy?fvNU zAU)0S^m4b*>DAGDYN%Mbk6HsBO{5?*U@dYcjTj>e1CUv=fUum zG}3vi`4R_pqb3#E5{VrptL>sQ*)PH`6526e{#5vne*UAEMX(5xN8RC}8pM>Nz%bKJ z_g|}YVlBo>7|Mj5v29I#)tU%ilAIl()#P?RE)8wNY0u3!&PsK8XW4ig4^_b4vNnx) z=VZ{=bUbA9oVGn%u&82a@fH3T7bXIG4a&*G-Tpguwwm2tB!E`X$&o#DEvm|o37->S ze()RFQOXgy4pI+cCECh_QhAr{Y~i%xSYQxEK2Y_;d!Lto0$>}IjHL1GwPnUF41Iv9 z16sGU?zf5O*UqfUhFn)SbC+Y97%&dUz{`KwLWoV~UXka0lj$WR5N8s9$-zkYlp!;r zEj)G;Bh`)Dft9RasHYQ|u&vu@bsMN=wa-C(Hzz%?MJ`R(i!}4Ge(cc|g#x{)JWD=G zafJ4c7%gHvR(^Z3h|Nex+}B^VhW+CshsCLS0LG(nrMo9Ia(1eD%~aUj778QW5HjO! zpYUd`OE2z*{h|F``PkcT=o*qz)=L&MWhsbqFY8zEdBJBupbVk&N6eNxrP(-(+%CdG z5I_JBXWE!qh=#8IApr(ZjpC9lT5JWT>tdHP9udtk5%EK1t6=2_SJ|+7;dcARXAl!m zMy%~Th*EinQI%s$*u&epgBnYye<3s61&2u6A=_4~5%tFlJ5J~uI9i5>ls=uLP+OB% zDMeZ)av0C`Jq$Wx02pSg*)vW=b05+V`Gbub?wO$qhX3rH?b(K+3m3%0TBrS}u3h^X}Mw4{%JU0mo?@NX#AO{Xcu z>f$Tb(DTsU?hL^)?B@UgrowC8U}4h#qVV|ik(F#nVA(j!`E4iF>#sQUi^Z?%4dl*$ zUBm(}Om4Wx(m5fQqGc4KHR?0y>#A2-qc~ZZqBC=}_|!V3rGg_$ zmii)%k%~zOW0^cQ7hViM`DDTnEABxBJuu%LM{aM}Qng+GSF=W9weFZPm~Gl_CLg%L zqOTC&3iRDrB~3Ad`Q4Gl1XKbNTL%!{2+Kcm>PEczhl7>dEaDbwbe_yc`wppWj|5S6QF$vL!dMTjMZpkRk1N`YgG zVy|tNE|6$`x+BY|EHv3S0K5Jh9q&aOpDMi_E6m8fdR2gbp^}IR5epu;LFNlWUbwD< zhA!MQ$S}g|X8h#n> zwR|#$^K~A}JC-h{>rdX{kO$#~J7HNSecC%THwG#y_-5($t1?W9ufI;!G@c|pSO{u^ z!#fwS;A{6JUYN%!ON?R~kbrCbPK78|efY64S?V;5)7<8cNK1B!KSO@y9Ge#|Tb_Ry zaE{V=eizu_jVffI$@L^HO@aCo$;L-!x%QfsEhm%Fs0T@dLbX^}aF5^FOCrPnDyIs1 zhiW_kp@}oNYlhI|Hdjl#C0c$8x6-bqX>>Vspse}Z_6<3eWxO<2+qB^SM@CAu+b~8u zZ}oyf2s*@|ffCi?C2ndpiM^@?Sz@92B>`mUGqrXgI_e5O)_I|Qh0&ucS@$Buk;Fo> zqV8ia4`z_rh=$pv_AGhOul)#dP(Xyun2W)W94ku(qd6Bdio#36x!VCMlLXL4o%4{t za4j5g*O3$=CC}$ZZ@1KZRa)3uZ1eLbP%Z5&cvvkL93oD2W|yCecuDVwIe18JrTD{g z+KDC3NG|pGgJSq0pGYQJXOk3`h`MqZx%5akK~1;GLqx%B2D z&gMy6!+^=Y-4N>7XZu8G@>Ti&ur*u9pg~7z%K?Dy-q^T%6!-WYlBMmT`9JYDFtidp zVf(FCIO9uHM=Dh8uH9#*UO+M|y~KmXFM&3vNrc|Bv{1xP>hkc7PWd;c(Y>OLRXX9k+WztS8PLmpxaFW{Eu+wGfDhzz4!xDbRrE zBA?r+R^!eHpA(9U6%75m4Yjw7a-ZH32Ej1(*V?=G>qerWyHV)WKOKgZi2n}1#)e{S zr)B%-oexVOnYbkxFcv2y!!P(OJ6zb7yLam8a&w|r8@E6~KTqWXV_o1fX7N^WkKo65 za@XyUH^%9qcmaW0Y8PGFFla>2vP(lWP^&8M9Y<2eIJI0Z%Bz+ak*a|y{C3{|N5{<* z|K}b14)fBfjJxT*38l~F;-|%95*Q;x)a&lBmq^1Bv4P(>$|B9M?Qk~U-Ib`y@V|f8 zK}sG~SVxrXRitUvNHb9lO$jnMBi9nrk?#jNx?>tWra1;rK=rQ4j!l%3;aeko{B(C&HeQxj*REpjGHEMrSRXJ=Fg zqPg;%58BlBUcx`amq!Z>+pd+3%Dw}j^4EW~&~jy{L@&posntall)QH{z_8{Lh~FNA zbE&qP@=SF_qe~Le9Pq2Fs;O_IN7^|GxE-rP z--T9$8d7RT;@FP(OR!Wu(2t%>`iTpRnLpf9(zX^I)nYN2X@viwHaWR6ku;6I@5?32`D9xC!Xb7V4dX5tM^rJ!*6F=rFTz1u>IDwY;AC%bi@+ zNAGMnIa4j53QP$%j4L4PEH?zr#XXe~0f9Sre}_A{m16C(!+cYd7U<;4e6Tp{9}(sFo^^DcIic`{ zOJiiZ7}MXu{h*`fq>S)uXTe{qL?Qb>b|OW`tzN`-GAUtjfd;YvAW%3Q#K+FYf3*; zexa8PWEhO#7d_b(uF_(OhzHJXF zB<_9ZjliwihKC|M8w*<{eHxC)o{WZ#84zr~zytg8^>Oir47OFlS6cjf<_Q<(!|=?1 zm785)S-j!C;}J4w->2!_C)32)Il89+CrRM2eMQ&~Ut#~J4EEnkR3zXNje-Ns*ZvZP!g_9o@aIx*b7u?d?y*)U?XUjtAjTRS z8p^zrdDa+&9Mmfo;^S*^v(_q4vDHT6uCYreLu|=Ri4w*+RYaj9@dXr9o!Ph~p=w68 zcZT|Jigl=KbsLpFzYE};#A-a9sMwnc!|_gI($6sHcUuP?x7nHWw z^`NiDTl&QhH)>hOEc4qKG_rW93gek!ZXNkgB9H!>qpcZ!5o(oc8_Kcp0}nK@k^gcu zKJ2Gpi?U}NXiqkTaJ$sOi@;VHv*<>x$j|)$ixkAgUDIjz46*TThBhk$`jD=&XSKi& zXa2{LM--M@yfhWem<`!&v_(k*88`Y*Qa@gyG*F36C*$1t)Nip`6$bE7|35e0zz|pu-EFWWyYJt zPaFGPg3!@h;|Ze*cjW3ms?!nE9)#2$NkIZH?|U7$ zt@>puHEvB_KRb~g?NgY6fjQDSHuZ~vfR=Sh5|J=BaBL|T~X%@Aq7SZq7!?8$R^mihBs-mbxO>~ao$^?Sbt5kBzD z8#yS=uNn=XhP2=9Y5e9%^L;NcjnY<7))Cvl0zY@=Eda!MUXO@yaRV95ow&=!&|G1( zt=)R(b_qup88`3yMjDLKF$%Jx#3m=aP?c+#GaIRi8!y}vXS*m^!@3T%RU~gIy_w?( zd>aVY6dQMuXwR@PYOm|S#5efBwaNBtBoG?{-{imAV9;zSGQ0DezMz?5cNv#A805~lcA56p_olM+zIkRUkRlTGuS`8v(;l2=cYc$l zTT=j>oza^Op$hQ}K^U0#g_T>e)zl>pskL|aejB>E;XsGQI5Gc&^$c#BQ-q3&d7OF& zJ>#-vCpVue>%2KAP+$#JH#f~mV(M;X1sn=CYB~3vTavwfIRAYtKg9O4ev#)K3o9uY`_M97sjX1Ptw4Q&>G-(`R)D&?3ccmpy3`kbvRg`?g0CwZM% zXqoh3%S~6JyO?O0@X%KzrWVQdv7UOGVH4lCXK6t^-C=yAnrY7GYw$n2mm zYE|F^cGNMZv`jS94<;f^aPiaOmaHp4m**Em|;*wFY?tmdyi?mf`mFQC8N-ig>)!%G%ky`%lr;ZtL& z<^sDzC!bp<5L&lQ7WtL%J@iT=A?NcT0?-wQX467)cmm!!tY+ly(WV&(9A*)jwJ%sb zfi{T%C`+~2mU7*~kkl(}bEg$OC%*kM?y}<6aUPa!LjqP4IKpU)M}Le5*?HydckH#< zF(w)6>+AzuQfoqpkpBuZ$W4^(ulxDoR|D2f0H9g&-PJABD@-3 zOySA22_p45H^j@=dKtPKyQ!`C#_s3?XsCyKA=6p3hEz`eGLG~c=eNIA7dFmr!}Y=~ z-JE*r0Sdstk4knqB*D|d$@Ts~i98W#|2@SDJxBhy`@oF|G(9JWgLzL)*NU^6P;a9r zG4674=9a`6H>yg6=m9phx0J5|x=oX0Gukg38u2|6ST=2MWJ<^N3YN*KRYv;4_t~s}PM=o|lE$r9yyou)n9K%pEeYLwho$DD$Yx(e zfIus{=^&pE^qAl{aHCb)ujR;-%!?Dcd2M~Jvw8V8cx>oa7@JWmp1V{ken;qj%Jma9 zl$Emfc;vrcHwA?D+hXxxK<1{%#ir5#jdFXXgqjUaCueGDx7kE}>Q=1U&`HzcD#$YZ z{t)%)lLe;0-iUYMp69*GBWw6vEcbO+lqVJhwEuX|uB;o?IMJJ4ESysO;A%^zHYt2V z%cc)H-OpDa93!9XW+w8U;}Spz&wx-wNC znZZ)~;k_^=pK<$#Ggd=eTd()fLoEp4^!JW{E;SH@R%#&_3bJfk&_l?#>g56+|MKR=hU_D z+)kaWR?^(6Z8CNceBI?nGZmXg&dJu;@oADYeqnj_weQh(gL6GFwsojilcKfcO1RC2 zpH|KE46If#c{V9NNe9T>J$iPh36<4S+?W-R1YcO0JUKyLubSP5>-EbH;iH^H8tZ_{ ztF~fwwG(-*zh2v$i<)~{+|dmeNY!>;k1 zU39tXj0i4T*Tqm->d5b^#rEIEI@Hqk_}NzRx+M& z)voFc(FSoCViQJHDbu2Smy2TSxZCcuY~d1OWUQTsJbI;`d!SCXUt^x{_LR**S6o#4 z?^Op(cUzoaX0pX-ZI)upf)2mCo#8DrmSgA<$y+DaXS?PE=A5(HW#rV!m2>0H5LJ+c zTtbUnlB@R#Yqu?Uxd#E)>$qyo+M?8^600w7Tg14VOh-ud2!7wJ<{9{E$vGm!sFYw( ze+B6G<@B3Zgls_~+V?^tu zvLf`p)Tb@QuP`%p$YILdhpP)D`|A7aa4Xv3V<1(lhTJ&h^5eqnNDiak%!cu(uo0FJ zaF8c!j6&WJ+LiJtZO=^gy%MZv3g`t({xnE&Ay>H&CDKG5*mscjSaRHSu~F~z^N@pV z67WqMy3)GEK6(doxfmDBNnK;xg4r++AAfAD!=rzev2_qO4spAK$nMEPZHEw_c$bTzW%)GG%%kz4)n zbUMJ}_Z3DujkSU#yg~k}X!DG#s8zF|tntsvZ|$CE4X>t*jc%4in?ffiv9c#qKFCJk zn|ILI0nrk!3;l9YJ@>=1oElTFs z`{KgF-EsPfBqZzkc}||egE^SZjL|@W%6LS7Ix`?2Md}QMIQ&ISag&%OtS$F*g6PJz zU}35ybkJtH$O@Q+<^oL9oQ_SoM($&3nwrHy()q8Ojmw{|=nX54uGpT1|123F(}m~a z-6zK^=94TVGDRXi#ipQgt(620DIK$2``LxJM`Bgl@d3^3E1EHd*{Yy{m@<0w_f>)0lnH$0J(VVg$c9%(d zJomTq`6GskN~?iOiE6{Pf6GdKobAwg=RzX{WYWwBH6tA6h5U5_$v`9&^Ve&SCV)bZ z>T^NinidewLOjKH`2boE9X*s2w_v4*>fYF+L5pAZHS%9$b$V>3@jq2N#R+Z83h2um z@3{8lwkG*0#8B$73vPn!yP{OYy$%BbZ{v}tk8LY5?jff*(3(%lwrw4yo!3iiRCoL* zO}~4duU1D3u*i+B;zQSCMey@EZ(~S6bwGV*NDy5uFP8j~sr{E#dG1ybh-t5#vj3Y9 z2HNRhlVIAK53gl=8os)VVMA7G5Ms?~#?m5=dy_tad;L`82Fxt60}qBB^gmQKlDa60pc7d%^xEAFD=m0^L6Rkr$8TBvh92+1RMNKzhh)9 zF@-a;x!~?|APu39cr9R6ou5ZI02xa9^<;DnzuDKd#LBGYSQ(wfro8o< zu56U!#_r=bNJxn%TfZ8ZS&ZYN&^)29z=zAQV`JsoQvlV4XcG`eD+$Y(ctys#zX5N@ z$SRl{S=u@m`KChKm|597Zg$B}_Nu?n%nZyP5LmJiDDN@1x7?E>uVptCup z+MEgEX%a|g{umwD&kZrzivH)`(r|PYC$`t=&;+m&v|1n*(X~^dxbh~v14ES>TFa>JrMQ@-WKj7J4%eMy%98_0lUvN zTTl+NO1L0un(DPGUO=LPh}34Vh2Zk|DX$6Fg61rb5 zTYX9>e-q!I()aDTBniCAac%0^?sn5f2PyV7U3~=OKl6YbZ8zPzDwF@djJkH^^z@~5 z#f}lE|)y-0y-^2NbcgtUTr}DF?v!bV`;+C)Gs?|CzP*x1jiz_VkHin@I$%> z^v2HCqaGg+_u;UXi`dPeE)@O;LtDOjzUaWves%yb5n^z0`svNqZs0%$3tb^W@Q~Ax zZ2Z|st}1sc^?;qo3;IWlO7qWN2|&o>zJC9;?mQweIWy-aDLf$T z#I6QJk#%Ib#$y~?qV5BBJ$a$L^tSOLE1;z?-AA{BmO670&ECpsR32eBC)4bXo-0C_ z3D4T;X0m0|g6DJ_6C{U*%%q{j1laNWIBymz0yW;n8ad7+@HZ^g8EiX9q~Z<5|+E!8~irpa-o zl{6nej>gl2(f01bTSHm;IiL8rs@2lPQd`s!sN+75lXvR3b6_YAqyr0+(&F?7VY`5S z)?FCc#^9HjXV~vgX~V|oBDhRi&!dL+y6-@1sfU&X`LL`yhP{SDWV`)^#Q>;dXi}W+ z7GKz?RCCa{7{{@WGs39gjeK0T{abHjFdGoX~6@*61as6#Bj~~MJn8e9? zm*3^iv?JAQK~V>RKTAKzrFWmA{5L$2j}G%(MtLFeh6_ySGh6`J>VyOAFK}3SX%Y>^jsGTQC`!putH04!RQY@; z-Cytib#3qW-QFx&!w5Yc{!{7DRP1`cJBvRw(`O zP>K!{DYY?L{h4St7yy0DnsRS3Tqm^a2@CTBk7O-@N1uJc)z#ieGpw!{r*>B=)BQQ3 zYg_^j(4KnG+#74@*8gVc+Hl*3V%tu2o^^}sLJe*b7|5_o^snK{)7nZb?e`fuxRvKv zH?>)|ludj$rmJH*`lM?Eo)Q4DnYcwwABY$z&|G;Lsya#=g=nODs>+!Z`<~0%pDtGg z;)#hdY+_C58V$Sf$N0V$`bU;A)PD7$2!=yrKO1Wx#RFG8&$tbvdI~jjDTA%FExn9? zO9tL?zq=?T9NucmOTVb52Geb&v%f5-HAFA8#`!oNWOV%fsp(8f+rG`zrf2st+>Xe+HV&m;&&t)`=!UyHm z1&TiF{lw^9a3t8Fi@hAdvSzoo#5=D5gcoYfwOUg&hNyl1YvHlP&NoG7;Wp2GHeSJ` zZ!9!hc?gyAYBh)w4&%>QD7xK9S7kG$hl8p4cIxgpx}}sYDf*gBu8XTqm7*naP;TFS zmOWt_LPD%JjoLmw_X~OXJ-l?UI;Q%Q@!OeAH?{ck=v*=^&IiJUZhXA}t>-uXhP~4j zrS2WpqG1gyg+O_o7IeA;%jxOq>TafEUt}23k6f*0y1HA>K4CV(J(wcx*r?YZhc~8c z+A`>Exppuo%<*g_&R4e4Pj>;mQ-(FktCKv?YCDrlS(@M1beRXedNKJ|K6Gy0y;`4S zcv&2tR4>BA0ahzh;t|m2y)Ra!b(rM$4a4m>XpYUvmSkH!%-TSKWF;Si0jq_^1uX9& z$Vxh)3D8cwZI-st1bXY85c0v&bm67?8_~r+$(+9mw2jPCds2cm5y9R^N0+4mTTx^Z zvsFkat;U-DCMIQO5>nCH{1t7R_2XQ(52$9^-7#hR6FW9z>#jU=f+de4>h$q3rJyHk zoi+b~8wN&A!c1DzDhMEL;9rwr9BSBrOXn#(F0Rl6hRm)oa^4jc>`d8hx>eT*yQBl|<;ho_58sH&MP{ zsHl%tRqx5mvOsI|wBp*~=nmkUd?AB%_09`Amri6K6Fg6}0vQJ8`Qb-7D& zWMdhpa4BBz1!r~87{=+->fbpIKGzsLACXZ;)zsjU;Qo%R(kf?^VlTbqNz&^;(MR1IxmlMaEg*}WWZ%hi z7fu0pMnz2pDnlD9=D3p~AK1N>Y59fg&i91zwoHt#ADRUQ9Vbtns`Q4(uuIS(_9M?1 z?L?%BA=qTgA5tg2od}3LBLGcT!+=<8{wPtgXYv%p=2t?pB*C1*>z*O>XeFoP@n3)< za6PqhM9S%g^x4*8KVe4dc~+heJt(*m)?N#UKG*f}+j!+Wp4t3WfA)8G_($nb*@{Ikk3&YzIDQP`U=KQ3a9>aCdA@-ux)x(n*EJ_POO1 zoA;llLktG*s()ad%4`76?xnP!B($eFw}cjIRNhUh#9^GSb{s_lP^R^a4;}_L_Mk-a z^$3BFL!Z6YOVzH7o|Y+yPIg%x&nzYaO)C|#{R(H?Q7>1zAztW+XPrxA2PaI3emZ8s zaCUX{_FtP-f6IbNhwdUijvsS1-4^co5D^&(KS#7d4!#kvok!!jokuF1+55Y%Kp-v^ z7cWWi2S_ma=qJ*pPX^F~e3Q)<_jn_n-o`;YZL_&VE=_IG3J*h>I{N9UfiO#3#^Hx= z3`(~0=t`49Kdp>^^7Ub*+YGyEJDb~jOt3{ECcD`k_zu@3@9VN}-4b;31_hWOHdXI- zXf2Il&o}j)Jg^jHRjXZ%_~``Z+buWhaHS%3MACfg3XHgpI4DO)#Tlhx zEvAE%5*2%kV&(~~qhgx4=HFv)#*YqRdd*h?puZB*K;x7GVn zr5D^SmU=bJ+dKxw-vnsPe#VP&bg#Ni{_2=Ie*0@OK8*&7PAqYy661zUdYl|Mv|SxK zQP*jMWSluQl37YoqER}&ho<_@(0ypVGr>d+bu*8`VAZi{3**U-_?`KQzt+XoD-UW` zyDtb;k4jzARsCfDnyq#v!m%V{f4DmS8=kgwtOLXcNB?sbr_@0Vp!gGJ^FU)vw}wGj za@K{S{Vs_L$a&f)_Iy90Davolub~WQ1Gy$Tjs<}3*RQ%TWE88aIW-S%4>q3T?ZaST zZu*OGq^Cb40*mE)CCU75cngE*tm;@gFu;*Pa{tcNIhdo%jE0q28!+)Nis1<@G%JbZEsp8SIg@oBj==b%ydZ~HRM;1q@DZzXnV6uRJ=}J4TyvdjJi`>;R3|aW zy#O&BZ6{8qX2N;Fqb_#nk@|=)wkkB(;6zxyY`!?Tvpa=GZ@do6%27q{$MjC8s+3m6 zvi)~~I+50P_V-XuYc{$0_3H;=dpbequ-|F_3?GZ zFzVb3jJNEBACEC1FlQy$wkcv}#eA{#MgPQBb_Js(T1T@FGPweJPmw)KP@S6c_D*)| zhk=Hf;2WHL_n_rzTde=A_g2ps<&%$vHyQNQ=3-s1&$~6F%h=wOM+MFTO}A~{yu?L2 zM5eLK_cm40=oLX;Xe))d&ZsNTn)XA8G}BLvPsK<`LZ>;D=7Rlc?oZ!gs0>>g$VRp6 z+~MFRCp4v${E6vXS z8m6=TuGZudGyI4s*zx#o-pL@d*G zyf>BHs=j0P-_m+QCkeoELU%!%03Bp~yQ<9Jr#I;rv1-HE`0VQq)SK|@&V0{p(z?f` zCy!`#u~$tJMM=-MeK}tBJbw#92~<+-*sQxq!PkQBlcj30(5EZ!Y@MD1n!@**Dwnby zsnZ%m*>;i;-J{1d`=baiurN*s8;u38)`l7C@5z#L5tep`=HWN~UfKf8wyy#pO18Y* z;~qi&jx1)&3vPb)G_O=Uu+T}!plU|C)SZD=56-@IAA){5h^KP45nk#OOxa55c`j}& zwc5S%nfzq37TbPTR#O(CX`N@G)YWQfgx7l3Vn|tfLm`FKyP-y;S~Z;3X4u;;z`FZ1 z#m9ufm^71&f`qajUjO-y*UI&MmG{K3ATzSE4x#H^9)Mfos*5jz;Z=COjjx%?O zKTK`4YL*weo0ny~PPoIf8ODAL%E1BTZc91mZ-oC=QlHQW(d<}WJk*&wF?;WaUWM#| zkW@NwKfgfYY8*zOv8VYTSMh zfXR0R9!6BB9w*%0(W;$Eblaa(ZLWRAdGcI{pHtt(Yg&t@f&_hC$t&ktNJ@(!Q2*yhqnrb$xvV712&d2629Zdn0{_JvK^WYajc=Y_;uqtoV=Vm@ z9D^v3uBd0#JwyvCDY{`#A#Fhwmnx~m?&hO}veB6RL8@HR$8OkA8B-KVb>HTeFR)$y zG^K7I4g;c5ti8;#x+yacR<#txTH_CRmISUbm~}uiNLFj4=$J<0X4Oe@N>pKTI>Z~d09evWEJp`0RgpW{qB zT+n122bngbpXJTR6BZ9h{mI zp}&058$?ncx0EV!JvK8CVDgz)Omohlw>u3WF{iU{WTea zEiD#L$?+B!x8yR!O$-(~n7hE7>aHam0|)mF5gk~=O_Il_-XI4>tuw{Ld^2A6SbNOh zVUu{Y^6X|E^j1R)GINN7a%iXDrm~ixy?JXcO*~hno#spV15D^$Occ7@F1=>KG|7`B zcTSl&-BMVR(hOEkF=9z$Kn*+U$t#gActxkjwHa3^@1C+*twU`OH#XN7W3NS!&El+L z#83ajH;DWGw4%N%;4)m7QPFV@;-<~oEC7V%hKj@D!$>dz3r_~p1erkqsxMVp7@&^1 z=z_yh17m0sSFghPV{l8$`XG8Y5n;xj-B|Wzmt|B}!l$aeqilMFMV-iweeNzlRjoQt zg^!;R&1q3+GC(Zu z2-{@3A}bw{lwl4(pyByYas2yV@y0gL`xgl-=w<2cOX8nzvARrtP0k$}cZsZZN1#NbY16vpo?xN^Ql8OZ%iraBpbc*(1!se9*~dsdxkkKw2@NdaG; z3@>a>_`Nn@cqe&-QWJ<$T^>`EXMl^S4xZ$^MlFcy1KrQtbx4yueN36jsA$YW0!}yR zHjY0RWnPq8`)R34VI(J9p6%%sKX8^DhUS!+FODf5#XvNU42c(#kU+`xjbv$VKd*^{ zi&OF%yW`b_zI&28zA#!mlJnW%TDK!SAXzHpwQ=dOT`qH;G5xyi*aIw7(%Yc?6IYV4 z&Y}BTa`Y9$IkMlbT@0wFGW5XulcxL=qyz2>6L0VNh^_YYP!hGmYl7Us592x`#tUvt8PX%YDg1W(S4G`M#wnlKO`t-b2w>^d}>i&tU%i!|2*uZayH5S+5ohp z^TCI#?kw>R&EvjD;qxoLtr}xE$J1L-az^gg&XV;AB!oPfEp1pf&r^73LaZth6n3Y^ z#X?D-CzVg;=g~?f-QZ!Rcm;5np^F?nC^I7SKBgAh6*X1TtgkFoR|oU{AODg~*| zhwszAe5gfjEd;nXkt|4Qi&3cuedTRIAXQt>^EOM+8irzW6#?Wm3L}<(?pKpoR==}O zy9F%Feg;h|PL@SYvh`!f-(3abvWGqxX? zgSD?UcvD1H#l@ZUYN&&jiBrLh09b&j0FyvtI#_+`DA+SXs*3XtBbz$U1Eb9)eB?Mg z#!NcZDi|;#kQDCa{JV)up7S9;DaTwk!5-WjB=eZ0-kYiFtI}_!w*yI2vG(u&w9d4c z1=pQ;fVC1{Wzar-H(X+FDDSju2XkBA_cr9OnAdf2C%2M1-}aTpjO~(!Y;?8QZl27{ zDfchwZ9!v(R;ZHBKlKuCnUBZQUbwZv$(DmokAD}~;eo6#EOtAUc?d1?2-_Zf*L%!-k{5vsj|v@s_1&kGA+GcU!#Y$-j8K zQ@CO{&7$M5Zh4BUI<;!lV>|HU`|G4seIoRxaLYU%>)e(wL)ZG*dva3r!p73KA+C05 zG_f+m(0%wC?KHhX+7=8JcpPi!eBJQ>x+LwYRUdFa$u<3cV=ykg1~2U^;uJxglt@c$ zd<{)Re)L6E*-a=VY;P-8jYYsyfUW|pg@Fo~SF_H<)%njCM=R@kL6VtKB#&lvx*P(U zs#_{qh?*Qp`a7K#?dV-E^2)jByp=ivWkNB#Lpg>3CdSK!BDn5C9@p-jTD%m9FIoGv zgD@CIZpnvam=bHu>s-1edQRkXQF#N-Lduk5AXi)<*~zNiIRCeE{cNoBo99?)z+OFc z7CFDbyi_2zaXAj6?my}B9RIKR@;t+vg!9e3=VOW=Cob9a+0Z%?9}OL{I8uG+hxg$o zuB&YeOZ4lXRO;(4AV!+%!^gO?S2r9`3mF_>ZaM7s5nlN3ZAUEYsV+~4knGf9rl@(U zCHKjw?%5VK66<3>f${>ZK?(w5*Tm4;OWMGp65$_H$2qzCZF+EUU0#=GFdLwIU;%88F}4X z-DeX-T&+2RUF_0U#J@9gC!K>WwT&#}SGEAdiI;bLVdzONH9OM83uQCP?tZE+ousl-+dgQntNHLSzltk5IJpEg9u#voc)J<*y&UYp zXOEkDvipG^vU0LHb)6mZMD|kc&1^;0MQZhJnKUP&&ug$CaF_r9YFTZvxR;0b78|i= zW#G>{Tf%VR{@Yf+-^y09*9O$$Q8Z9AF{bw;GS=SywgW--d5cpb6*3$6y)X+65|qWy zY_}Bhgv3(rlqgZ!-)U_DxUu(?ZwhidNoV!8g6XZtD(MPlkeaNK>bai#Ps_!&(#b@6td= zt9q3z7Xh5L5ALOzph}dX-AkZ0R zMT2-CS2&)dEK679RxTj{6U|M2KfuB;eC@6duC8?!V0Rk6>hjhM`+u6d_J5||_peea z$-7i4v3etksGQ2Nb2%p=r%mM)3pq_=TlA4n#}y%m(TSB~&J06}mQ#+)sS#mj<}_@x z?R$@+&wuc}f7s)(`*9zx`?{~|y6@|H-S_+{Hfh@&Q6 z#>HFq6ZRYkSSh5 zOzVBG{lWDCVp=|TMGqL$>=jd*-i>V>hnq*L*)g3fysqU;cNv8IJ9sAZxw7XO$lC2~ zCC?;J#1x)*j&rYbzk1-ozoHDG|DZdCw=?8FW6a?Y*iC3xyNMme$nNRQSq*p9%hyEL ziwL{^#0&xqJdflGohy;ALsIQ@=v{6?LYEa`-B*G1SR@yY?7t^J_WIYwmdcB}a5-m8 z?RAS(bN-v@-u{?J`gi6i}+8nz$#Q@TfyDVB{HY_f&!8<*GstLH-u|JK7R;_2$jIxMK+b#< zRB<5O2#$n@aB55Dpe-&=Jj_`A+UJhLFH8E6uTEPNJ@4}N(=UEi`eQp~{boBe`O;^Hc2#IzSA5%l?`4g7 z!k}yFrQG;bt5~mZ!Uflc9!%Czg^f#|Kj4pjeKD+D* zRnkz4^X+e<$+f>uL`p>G@3lVOT^G~(rFD(Gg|qF0B=rxn8%*4bRIe%~GYXAJ2Y7RcUKHAY!GJD1AM#mA)b-x_ABJ{19*0Q$I zd`@IX`9`90_{@oB0%^iyfK`9@L&n38(!y6eB6JHh@_&`6f7pQ$IZYV95-W3HY-(@$ znt%Mqj2L?_bWKq8bu1ztew%|9q52$!Z#yXvx$osaL;(-Hg5fD7KQ>vQHl27a49$IVax?r*4@I5##1iWl>Ebvo#g+PPAj;Q+i zXSJRSUN`rXPE`-;B9B{*3}%c;pe6?GUTExdEP9{N69HE>6u)dFv2&}dL-bRPeFr}( zrA=-lx10EkeI4I>0R&)3Po(K*PX%!d`=xhYt+96Iw7Nzr!!;r|7(2P8Fm?(Fy?X$= zgP)!!eb2hf*wZs@!X6@`T-xqGgHAvHW_u7F9;R+w_MdwSSG8I`Ydrb+(2ZsU0}ISY z zD73B9x%dK$`CL)^q$anz8!65fPE?W+VZ7k&?(@yHMD-7rr|j|%*qd(McG(F1EG2U? zAF=R)zB67a5nkUBCz=?6d$~&J-BbBWzxo|xxBX`dBsCwIWrE%9CBN;?D)RnnV3Hxh+dH26FiNSEuM)_n(p~*Cm7pN_rL{S zy1nLjBK!NjocH!(Yoo9Y9XfSxlRHPpiq)f>ujVCX6qp(9He~mjJM!|Z^=|tYKkMo8 z*wj|IX>$mC_LXXl!;8H}F>B7gHI$}k5etK_?P%#V+Obo+bNoa-qUPyP#LtUP9sl`o zsoY1=G44=ZxTNBAulz}gwoTfdhU0tdy^?i08-r_}q&8v!wym7)F$YNTYb$e^o9$6= z3a9azsk?-P6qFprl3q2F?n<3YmFWeRa`x3TGQK><=do!o;nr|l?Nn9Lf`;@1Ro^*Plw<=`(HOtd+$txz&Ba=WTal<>WOpOnYei0?tWESz9}oe!P) zHMz#SFzHwbsRm~WnZ6CjOhse(Q$N-iczW}HCauX1hhv-;SlwOS#G~lFddJXC6^ufu zb6AJwF6LLQB;?fj8=NDPpRlP~4DAqxfQu*@egL^jx{w}G^P~-aJ!qEXMs}^`k3}_Q ziQQ)8c8~thv2;8#kdx;fLa)go`Qe__M@}st;f|Hfl9o@<2*9GRr8|HrK zykMF6PM?dn`b$)Vr9N$L*+# zrBC$|+bp$n4PCvqqOCLvhF>$s0$s{YP!H>LyD0X8v zj5ChsC3ib?yFi*UiPQ7g@d_;zsPMcGyAcKr|(6DwLjR_uG5z_~|jU4saD?+K}XJlIh(mPkKT*OH#~(oF9-xU3*l z`5q3|5Pb3zmj-0p?& zlqH_~>lZ?aaB@}?RLhb%Y58+tXQ#H%oz~BSPe?C3qrDs>KLdzb8QpsE>XrRK8LrO{WW?`_Q zrt;0y`Mjh(M{#OHVNIR^!|^5O`+L-j-op8lsNldcBGmxa;SjIfv@YMC>5L)o93}DU zD>7xW6gI4q7OIRO2{bDL_*7sg(v?${*z5-fJI5zL2$aZJ6K3~DB-!`1&th z{lJ;glYNgwuv;NZ1@@9B9>k`3bxULrR<9I;!Y-#!Qh1Ysb&Z85$AX4{;DQn5a z`JJ3EesXgs&Y|?y*cV@$W|sKllECqBLpu;eJZv`Vst_`?p&x#j^j+`K`Lo!N6A?m9 zhD;w6en=+uMBUetm)`xQCYj*P*-y1*ufq~S(%7n%1t7}tuP>%C`i#a z8eNZXk6qn-fBoYBzQID^wSyUbb5Gt_BwYrwm@A~LZmW~<<;SYUf5+C2d`BL<9p??V(IIy+4%H}jY?(%eIEY(evo3hnu0HLorZ_;nl z-aYlsluq$o8%=Wzj}`X?eH$s~`dRbR8{&PBtgpZqVZ#>Sm(1 z6E`iExg!p_*=y$kS89nw4l@l#7i*sJo~h^CPfW<_Yzz4j>zB4xfm+ogXpL;Z+xAj558gR zpzV=t)!%bh?VRPY99dd4$_?nYuS=#AVw!h zm5OP$*}XXN@v%L779eRdl8d=Qy7{986|#a$SRna4@X(B841sfP#F-XYCemT4%*3}0 zauT@aa?fD$J}iADH;oLHTdCRM$$z}u*FnhlS3`d~zSdn?X+U0!7>QZC)XheBF%_01 zxYjqzs6vYV@!gRS@Cbt*0{*Z$_w<^@hbYi{1m3u(W;G|W4v)1jq~l0Ame?2@$F zbxU<4T;BvH?<<~eN|TXV%Gm?l{JSc}mdxVcl)M6)jBQ~$%q|fttmsmnOgwXt6yk&L zyznD?g)ts&Nn~NfkH1(2Z%b9L%XR%9wu`H;jixL!*=Hy3lLFe5toYE&cByUrosO22 zird!|t$hd2_}~M}Mst1(&Q-t2g+Tm&ef4fDT3%$)SH{pE8p@ZADw(d92uq3J*0Sc_y6c-ng)@2I(ug9iGYcZJnLBeqzXIyA?*JDKP7EGptj2 zjm;|^0>fY#2glXs-fT`^=3D&ZE{0o-%guUW1T_19+HKf&8ND~^vtRMbP>P|P0YF4W zBPg7;Qn=ldFQ2!KrWB`~B>7j%%&uOAqlicywa}!yE5mCzM<<#I%u|zj{#kSM%Lz0+{xb zGoCPx9l@hL&EMVqKrevkM9xq2-^MLaK-Kq`(x!s|BFQk5)oi~V!d|K_lN&{^ju6KS z@N8{P$n$sZ*<-@*hw!dK_wzBVSWG9gr%7+lp(%;>9-m}H&ajQjvd!C%8hI6goYjSB z7S0uJN|}!z;~UFj!Np;g+7`%q>ec=Vfh+ zx+x@hEGKL4*W1ImSTi%TK+{i@pn=CTmdWhPWD2(#!nH78vql(Uh7x=S0cj*h-Ly3{ zG_+ATr1|w(syT*jNk7BIA$}M(9Uo3?q@WiDAdDMMCwBL+`tFLii0jW+(!5iuq$$Ib zN7U3>qbflBcc8X)j)I}kyagZlK>Bw5MtrJU(rs6_)#zGj#1#cvQID@Nxg`VY3)KE* z*K=qS#xWXAv!@ufs+%UG+0>bbu!U<#l66KB~NB`gPA9@f9TKm-NjX)CaOeAdGufk2a23- z(86m`Lj2fYWU+5Sb#r}|GqTeqHUR1gxQ?9sEQyCL{FH1>=8m~pFlZqV+Ty?yV_aQb z3HvG4)zuv@D<5oE(#DiJRjsB`jKB%~Wd6di4qpI07wB?-LvUU6 zcX}2fZMq1&uJ1yw=ta^p!L@;(50qNKOUg}byCwiHGy^1aBO3PWyy_x_7a8SqP~u*(y zY5D67YmglzW@c=&{ zxSX1r&;nkZeG9b3V}ddZut?pTUoYxEd#X+qzu2R$Zla2PVFt|`EO9L_=L!@=)E8L< z(M%=r{hIV*fB4oNqJrlZVxd9(;o&6SuUHt=W6M?Cx4uRoZ218J&|rE&mi0KHG9vw@ zuf8XBEU(E7E3K0%J=dT`>Cwmn7}^MKsfv>y1vPhLZu?YG`KIx~*u0b*@c`DSH2nH? zAfb+!(+=;%AS3*6gfeXO*72+mOM#v~~fPXjfy zTcc?UZcU?Y4pYT>ec}Y39^?Z_mHU9(4PA?XpDl!iOn%O)D4*@}Eal;TZvgaMXV?{3jg0cSDCe3g=|sWuL`&f1)_u-11{ z)6j6~G5oc6@EyFsp2P6xs6b9zfXPym z-m|A|T@d_GMaro#9U#?(0WcVM)J6vr45o1huXlS-$;j)6+I%zs{!GDeiZGt2Hjjd= zoC?4m9Li;lCLzZ;eBq{Qn2vsEhG%wp$FXz2_ir;V{DzVy!CWHRaTZ1?Fg}}nI&h1e zT(MvCSxMVDd9iWjD<3N3NrOmd+O#uhNdB7bK8)1Xo|37rO66fXo&S>dv#hCVYHB#l z@2%De>~Xj8uNW<`sI!Qp!U&bpv7_02 zjX)&4dd-1+gAo&;G!CPJ!qWOf;`g}fXA(AXY@@=^R7(YB-Z&KU5m(F^=Pf=4*%^JI#^$Ah>=jAs|MYlgJ};3k=Z?iYoPu;+l(T4yy zR>bz1={rR+Z7@t5M@#~&>-^I0`;H8-+H)-c;}h>0h6bv&eg7mRWa1-o$9Q0GvL_LgW!zxg^?wH;-JnJ3L(JL=9WY6 z0Kg|QuC1wgR25$G=uzXqs|Xq2b#f*G7|bjbcfQtOoIR%+(l3g`_yv4m(<*qhMv4kp zRXAE;uy@poqf&3M+bqok3hp);2qp`FUF)C#FAAD~k)HGC&Ax$f-v#vS!*tIA36NTI zRIpoP1I>90a13F?6~8=3o$u-Z`)M{u?eU|bG_{=O*s-gAv%>=Y zFK@Qn8_$_zm}`0Uyx>ujc{h-)rdlb8-T)i+_v_dckYyp;GyT{mzkssuh zU2sm-nna?16%P82{e96d?aTj`7pJpy#04nhvG5uA&=boYzctfrpI$=A!1J@ewaD^9 zlk4~~m)~2*zHKdwRRv^99f25-=tH~jv8TJO8~_qJPKzbs9EcF_eocIdKR zRc(Y#d#(PWAvo5cAyrTf&UzN^Htb3x6Dd4OLrOvDil5^8X4Y4sxb;HPvB?S%k3X(} z&}I-1c^8#wnN6n&BVtra-Y)wkuWZfHJsZZ=Jq*+^X)g97bWEK_GDuoKB4?nz zWyFxjSm^V#ZolVN6oJnL87mEeb72Cv%bm~YKS!rnFSi67?;Mk&3<)go&HnOm z9uJlRzjnfD>e*jyznLx8^mB9SL##Nb@sFL~Xck*m%wYa@v7!n<<-*X=yp@7>%&zjT zJ~M9Q*_B~uB7I{Ib9O%v5)$Sbo;i70rFgnG#Tb*mLUrc_;yZebRXR8OuUhBmJAiv$ z7b(v-{>ztsmho*Zf~Yo6qP$!2-!wfcvK?bI4u5#L(q@4=>-nqgY=gfDZjT~>zUSp* z7tB}s0ily9FpLi1@c&g*GZGCt=k)dQuPpm_-*%gzZNSSTvnjDWk7bgoJF2=WI$XzR z1hJ4mK7aL)9P8#vKt|wNzZV^cL(Ar4SGa#JR+$PenBweVpUibQ&l)`7?PT8y%Jjz+q7T?hP-g!e8FyI$P5)nwmlfr zNTfV!rHw)mTS0>JZ2>+L#lYr3MYZm9F@EAOo_Upf+TzG3e zTXOp$Pp3YZ)k<|il%4)fWE~p69B*s&DW9zcd(K6L`x9;}7&Xos-3SyNM1Pmb1Rwnf z1Q#*ol6T9ME&Y7@DE?~^yVvlumNQ#{P|>qoY?*JN%oj^E_TBI&MG~|68e6)4Efc;f z{_!9#&<9_lUXnXHxm0lPiF6B&p{s2N(+ayZw?eDpnO#X{Cx0R7*mRl2c!QA8YIRxx zBXJ}qI^_LdHIqW#So+~DO7VZ?J?nY)zGS)}4V<B*7^7zC=a#nI [u8; 1024] { + // do stuff and return new buffer + for elem in buf.iter_mut() { + *elem = 0; + } + buf +} + +pub fn main() -> () { + let buf = [1u8; 1024]; + let buf_new = process_buffer(buf); + // do stuff with buf_new + () +} +---- +When calling `process_buffer` in your program, a copy of the buffer you pass to the function will be created, +consuming another 1024 bytes. +After the processing, another 1024 byte buffer will be placed on the stack to be returned to the caller. +(You can check the assembly, there will be two memcopy operations, e.g., `bl __aeabi_memcpy` when compiling for a Cortex-M processor.) + +*Possible Solution:* + +Pass the data by reference and not by value on both, the way in and the way out. +For example, you could return a slice of the input buffer as the output. +Requiring the lifetime of the input slice and the output slice to be the same, the memory safetly of this procedure will be enforced by the compiler. + +[,rust] +---- +fn process_buffer<'a>(buf: &'a mut [u8]) -> &'a mut[u8] { + for elem in buf.iter_mut() { + *elem = 0; + } + buf +} + +pub fn main() -> () { + let mut buf = [1u8; 1024]; + let buf_new = process_buffer(&mut buf); + // do stuff with buf_new + () +} +---- diff --git a/embassy/docs/pages/bootloader.adoc b/embassy/docs/pages/bootloader.adoc new file mode 100644 index 0000000..53f85d9 --- /dev/null +++ b/embassy/docs/pages/bootloader.adoc @@ -0,0 +1,97 @@ += Bootloader + +`embassy-boot` a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks. + +The bootloader can be used either as a library or be flashed directly if you are happy with the default configuration and capabilities. + +By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. + +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 + +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. + +STM32L0x1 devices require the `flash-erase-zero` feature to be enabled. + +== Design + +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, 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. + +NOTE: The linker scripts for the application and bootloader look similar, but the FLASH region must point to the BOOTLOADER partition for the bootloader, and the ACTIVE partition for the application. + +=== 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 | head -c128 | xxd -p -r > $SECRETS_DIR/message.txt +signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig +cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed +tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed +---- + +Remember, guard the `$SECRETS_DIR/key.sec` key as compromising it means that another party can sign your firmware. diff --git a/embassy/docs/pages/developer.adoc b/embassy/docs/pages/developer.adoc new file mode 100644 index 0000000..e03ee51 --- /dev/null +++ b/embassy/docs/pages/developer.adoc @@ -0,0 +1 @@ += Developer Documentation \ No newline at end of file diff --git a/embassy/docs/pages/developer_stm32.adoc b/embassy/docs/pages/developer_stm32.adoc new file mode 100644 index 0000000..7c04ab1 --- /dev/null +++ b/embassy/docs/pages/developer_stm32.adoc @@ -0,0 +1,79 @@ += Developer Documentation: STM32 + +== Understanding metapac + +When a project that imports `embassy-stm32` is compiled, that project selects the feature corresponding to the chip that project is using. Based on that feature, `embassy-stm32` selects supported link:https://anysilicon.com/ip-intellectual-property-core-semiconductors/[IP] for the chip, and enables the corresponding HAL implementations. But how does `embassy-stm32` know what IP the chip contains, out of the hundreds of chips that we support? It's a long story that starts with `stm32-data-sources`. + +== `stm32-data-sources` + +link:https://github.com/embassy-rs/stm32-data-sources[`stm32-data-sources`] is as mostly barren repository. It has no README, no documentation, and few watchers. But it's the core of what makes `embassy-stm32` possible. The data for every chip that we support is taken in part from a corresponding XML file like link:https://github.com/embassy-rs/stm32-data-sources/blob/b8b85202e22a954d6c59d4a43d9795d34cff05cf/cubedb/mcu/STM32F051K4Ux.xml[`STM32F051K4Ux.xml`]. In that file, you'll see lines like the following: + +[source,xml] +---- + + + +---- + +These lines indicate that this chip has an i2c, and that it's version is "v1_1". It also indicates that it has a general purpose timer that with a version of "v2_x". From this data, it's possible to determine which implementations should be included in `embassy-stm32`. But actually doing that is another matter. + + +== `stm32-data` + +While all users of this project are familiar with `embassy-stm32`, fewer are familiar with the project that powers it: `stm32-data`. This project doesn't just aim to generate data for `embassy-stm32`, but for machine consumption in general. To acheive this, information from multiple files from the `stm32-data-sources` project are combined and parsed to assign register block implementations for each supported IP. The core of this matching resides in `chips.rs`: + +[source,rust] +---- + (".*:I2C:i2c2_v1_1", ("i2c", "v2", "I2C")), + // snip + (r".*TIM\d.*:gptimer.*", ("timer", "v1", "TIM_GP16")), +---- + +In this case, the i2c version corresponds to our "v2" and the general purpose timer version corresponds to our "v1". Therefore, the `i2c_v2.yaml` and `timer_v1.yaml` register block implementations are assigned to those IP, respectively. The result is that these lines arr generated in `STM32F051K4.json`: + +[source,json] +---- + { + "name": "I2C1", + "address": 1073763328, + "registers": { + "kind": "i2c", + "version": "v2", + "block": "I2C" + }, + // snip + } + // snip + { + "name": "TIM1", + "address": 1073818624, + "registers": { + "kind": "timer", + "version": "v1", + "block": "TIM_ADV" + }, + // snip + } +---- + +In addition to register blocks, data for pin and RCC mapping is also generated and consumed by `embassy-stm32`. `stm32-metapac-gen` is used to package and publish the data as a crate. + + +== `embassy-stm32` + +In the `lib.rs` file located in the root of `embassy-stm32`, you'll see this line: + +[source,rust] +---- +#[cfg(i2c)] +pub mod i2c; +---- + +And in the `mod.rs` of the i2c mod, you'll see this: + +[source,rust] +---- +#[cfg_attr(i2c_v2, path = "v2.rs")] +---- + +Because i2c is supported for STM32F051K4 and its version corresponds to our "v2", the `i2c` and `i2c_v2`, configuration directives will be present, and `embassy-stm32` will include these files, respectively. This and other configuration directives and tables are generated from the data for chip, allowing `embassy-stm32` to expressively and clearly adapt logic and implementations to what is required for each chip. Compared to other projects across the embedded ecosystem, `embassy-stm32` is the only project that can re-use code across the entire stm32 lineup and remove difficult-to-implement unsafe logic to the HAL. \ No newline at end of file diff --git a/embassy/docs/pages/embassy_in_the_wild.adoc b/embassy/docs/pages/embassy_in_the_wild.adoc new file mode 100644 index 0000000..620794c --- /dev/null +++ b/embassy/docs/pages/embassy_in_the_wild.adoc @@ -0,0 +1,26 @@ += Embassy in the wild! + +Here are known examples of real-world projects which make use of Embassy. Feel free to link:https://github.com/embassy-rs/embassy/blob/main/docs/pages/embassy_in_the_wild.adoc[add more]! + +* link:https://github.com/1-rafael-1/simple-robot[A simple tracked robot based on Raspberry Pi Pico 2] +** A hobbyist project building a tracked robot with basic autonomous and manual drive mode. +* link:https://github.com/1-rafael-1/pi-pico-alarmclock-rust[A Raspberry Pi Pico W Alarmclock] +** A hobbyist project building an alarm clock around a Pi Pico W complete with code, components list and enclosure design files. +* link:https://github.com/haobogu/rmk/[RMK: A feature-rich Rust keyboard firmware] +** RMK has built-in layer support, wireless(BLE) support, real-time key editing support using vial, and more! +** Targets STM32, RP2040, nRF52 and ESP32 MCUs +* link:https://github.com/cbruiz/printhor/[Printhor: The highly reliable but not necessarily functional 3D printer firmware] +** Targets some STM32 MCUs +* link:https://github.com/card-io-ecg/card-io-fw[Card/IO firmware] - firmware for an open source ECG device +** Targets the ESP32-S3 or ESP32-C6 MCU +* The link:https://github.com/lora-rs/lora-rs[lora-rs] project includes link:https://github.com/lora-rs/lora-rs/tree/main/examples/stm32l0/src/bin[various standalone examples] for NRF52840, RP2040, STM32L0 and STM32WL +* link:https://github.com/matoushybl/air-force-one[Air force one: A simple air quality monitoring system] +** Targets nRF52 and uses nrf-softdevice + +* link:https://github.com/schmettow/ylab-edge-go[YLab Edge Go] and link:https://github.com/schmettow/ylab-edge-pro[YLab Edge Pro] projects develop +firmware (RP2040, STM32) for capturing physiological data in behavioural science research. Included so far are: +** biopotentials (analog ports) +** motion capture (6-axis accelerometers) +** air quality (CO2, Temp, Humidity) +** comes with an app for capturing and visualizing data [link:https://github.com/schmettow/ystudio-zero[Ystudio]] + diff --git a/embassy/docs/pages/examples.adoc b/embassy/docs/pages/examples.adoc new file mode 100644 index 0000000..82381a2 --- /dev/null +++ b/embassy/docs/pages/examples.adoc @@ -0,0 +1,11 @@ += Examples + +Embassy provides examples for all HALs supported. You can find them in the `examples/` folder. + + +Main loop example + +[source,rust] +---- +include::../examples/examples/std/src/bin/tick.rs[] +---- diff --git a/embassy/docs/pages/faq.adoc b/embassy/docs/pages/faq.adoc new file mode 100644 index 0000000..3e32563 --- /dev/null +++ b/embassy/docs/pages/faq.adoc @@ -0,0 +1,433 @@ += Frequently Asked Questions + +These are a list of unsorted, commonly asked questions and answers. + +Please feel free to add items to link:https://github.com/embassy-rs/embassy/edit/main/docs/pages/faq.adoc[this page], especially if someone in the chat answered a question for you! + +== How to deploy to RP2040 without a debugging probe. + +Install link:https://github.com/JoNil/elf2uf2-rs[elf2uf2-rs] for converting the generated elf binary into a uf2 file. + +Configure the runner to use this tool, add this to `.cargo/config.toml`: +[source,toml] +---- +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "elf2uf2-rs --deploy --serial --verbose" +---- + +The command-line parameters `--deploy` will detect your device and upload the binary, `--serial` starts a serial connection. See the documentation for more info. + +== Missing main macro + +If you see an error like this: + +[source,rust] +---- +#[embassy_executor::main] +| ^^^^ could not find `main` in `embassy_executor` +---- + +You are likely missing some features of the `embassy-executor` crate. + +For Cortex-M targets, check whether ALL of the following features are enabled in your `Cargo.toml` for the `embassy-executor` crate: + +* `arch-cortex-m` +* `executor-thread` + +For ESP32, consider using the executors and `#[main]` macro provided by your appropriate link:https://crates.io/crates/esp-hal-common[HAL crate]. + +== Why is my binary so big? + +The first step to managing your binary size is to set up your link:https://doc.rust-lang.org/cargo/reference/profiles.html[profiles]. + +[source,toml] +---- +[profile.release] +lto = true +opt-level = "s" +incremental = false +codegen-units = 1 +# note: debug = true is okay - debuginfo isn't flashed to the device! +debug = true +---- + +All of these flags are elaborated on in the Rust Book page linked above. + +=== My binary is still big... filled with `std::fmt` stuff! + +This means your code is sufficiently complex that `panic!` invocation's formatting requirements could not be optimized out, despite your usage of `panic-halt` or `panic-reset`. + +You can remedy this by adding the following to your `.cargo/config.toml`: + +[source,toml] +---- +[unstable] +build-std = ["core"] +build-std-features = ["panic_immediate_abort"] +---- + +This replaces all panics with a `UDF` (undefined) instruction. + +Depending on your chipset, this will exhibit different behavior. + +Refer to the spec for your chipset, but for `thumbv6m`, it results in a hardfault. Which can be configured like so: + +[source,rust] +---- +#[exception] +unsafe fn HardFault(_frame: &ExceptionFrame) -> ! { + SCB::sys_reset() // <- you could do something other than reset +} +---- + +Refer to cortex-m's link:https://docs.rs/cortex-m-rt/latest/cortex_m_rt/attr.exception.html[exception handling] for more info. + +== `embassy-time` throws linker errors + +If you see linker error like this: + +[source,text] +---- + = note: rust-lld: error: undefined symbol: _embassy_time_now + >>> referenced by driver.rs:127 (src/driver.rs:127) + >>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::now::hefb1f99d6e069842) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib + + rust-lld: error: undefined symbol: _embassy_time_allocate_alarm + >>> referenced by driver.rs:134 (src/driver.rs:134) + >>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::allocate_alarm::hf5145b6bd46706b2) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib + + rust-lld: error: undefined symbol: _embassy_time_set_alarm_callback + >>> referenced by driver.rs:139 (src/driver.rs:139) + >>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::set_alarm_callback::h24f92388d96eafd2) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib + + rust-lld: error: undefined symbol: _embassy_time_set_alarm + >>> referenced by driver.rs:144 (src/driver.rs:144) + >>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::set_alarm::h530a5b1f444a6d5b) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib +---- + +You probably need to enable a time driver for your HAL (not in `embassy-time`!). For example with `embassy-stm32`, you might need to enable `time-driver-any`: + +[source,toml] +---- +[dependencies.embassy-stm32] +version = "0.1.0" +features = [ + # ... + "time-driver-any", # Add this line! + # ... +] +---- + +If you are in the early project setup phase and not using anything from the HAL, make sure the HAL is explicitly used to prevent the linker removing it as dead code by adding this line to your source: + +[source,rust] +---- +use embassy_stm32 as _; +---- + +== Error: `Only one package in the dependency graph may specify the same links value.` + +You have multiple versions of the same crate in your dependency tree. This means that some of your +embassy crates are coming from crates.io, and some from git, each of them pulling in a different set +of dependencies. + +To resolve this issue, make sure to only use a single source for all your embassy crates! +To do this, you should patch your dependencies to use git sources using `[patch.crates.io]` +and maybe `[patch.'https://github.com/embassy-rs/embassy.git']`. + +Example: + +[source,toml] +---- +[patch.crates-io] +embassy-time-queue-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" } +embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" } +# embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" } +---- + +Note that the git revision should match any other embassy patches or git dependencies that you are using! + +== How can I optimize the speed of my embassy-stm32 program? + +* Make sure RCC is set up to go as fast as possible +* Make sure link:https://docs.rs/cortex-m/latest/cortex_m/peripheral/struct.SCB.html[flash cache] is enabled +* build with `--release` +* Set the following keys for the release profile in your `Cargo.toml`: + ** `opt-level = "s"` + ** `lto = "fat"` +* Set the following keys in the `[unstable]` section of your `.cargo/config.toml` + ** `build-std = ["core"]` + ** `build-std-features = ["panic_immediate_abort"]` +* Enable feature `embassy-time/generic-queue`, disable feature `embassy-executor/integrated-timers` +* When using `InterruptExecutor`: + ** disable `executor-thread` + ** make `main`` spawn everything, then enable link:https://docs.rs/cortex-m/latest/cortex_m/peripheral/struct.SCB.html#method.set_sleeponexit[SCB.SLEEPONEXIT] and `loop { cortex_m::asm::wfi() }` + ** *Note:* If you need 2 priority levels, using 2 interrupt executors is better than 1 thread executor + 1 interrupt executor. + +== How do I set up the task arenas on stable? + +When you aren't using the `nightly` feature of `embassy-executor`, the executor uses a bump allocator, which may require configuration. + +Something like this error will occur at **compile time** if the task arena is *too large* for the target's RAM: + +[source,plain] +---- +rust-lld: error: section '.bss' will not fit in region 'RAM': overflowed by _ bytes +rust-lld: error: section '.uninit' will not fit in region 'RAM': overflowed by _ bytes +---- + +And this message will appear at **runtime** if the task arena is *too small* for the tasks running: + +[source,plain] +---- +ERROR panicked at 'embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/' +---- + +NOTE: If all tasks are spawned at startup, this panic will occur immediately. + +Check out link:https://docs.embassy.dev/embassy-executor/git/cortex-m/index.html#task-arena[Task Arena Documentation] for more details. + +== Can I use manual ISRs alongside Embassy? + +Yes! This can be useful if you need to respond to an event as fast as possible, and the latency caused by the usual “ISR, wake, return from ISR, context switch to woken task” flow is too much for your application. Simply define a `#[interrupt] fn INTERRUPT_NAME() {}` handler as you would link:https://docs.rust-embedded.org/book/start/interrupts.html[in any other embedded rust project]. + +== How can I measure resource usage (CPU, RAM, etc.)? + +=== For CPU Usage: + +There are a couple techniques that have been documented, generally you want to measure how long you are spending in the idle or low priority loop. + +We need to document specifically how to do this in embassy, but link:https://blog.japaric.io/cpu-monitor/[this older post] describes the general process. + +If you end up doing this, please update this section with more specific examples! + +=== For Static Memory Usage + +Tools like `cargo size` and `cargo nm` can tell you the size of any globals or other static usage. Specifically you will want to see the size of the `.data` and `.bss` sections, which together make up the total global/static memory usage. + +=== For Max Stack Usage + +Check out link:https://github.com/Dirbaio/cargo-call-stack/[`cargo-call-stack`] for statically calculating worst-case stack usage. There are some caveats and inaccuracies possible with this, but this is a good way to get the general idea. See link:https://github.com/dirbaio/cargo-call-stack#known-limitations[the README] for more details. + +== The memory definition for my STM chip seems wrong, how do I define a `memory.x` file? + +It could happen that your project compiles, flashes but fails to run. The following situation can be true for your setup: + +The `memory.x` is generated automatically when enabling the `memory-x` feature on the `embassy-stm32` crate in the `Cargo.toml` file. +This, in turn, uses `stm32-metapac` to generate the `memory.x` file for you. Unfortunately, more often than not this memory definition is not correct. + +You can override this by adding your own `memory.x` file. Such a file could look like this: +``` +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K +} + +_stack_start = ORIGIN(RAM) + LENGTH(RAM); +``` + +Please refer to the STM32 documentation for the specific values suitable for your board and setup. The STM32 Cube examples often contain a linker script `.ld` file. +Look for the `MEMORY` section and try to determine the FLASH and RAM sizes and section start. + +If you find a case where the memory.x is wrong, please report it on [this Github issue](https://github.com/embassy-rs/stm32-data/issues/301) so other users are not caught by surprise. + +== The USB examples are not working on my board, is there anything else I need to configure? + +If you are trying out the USB examples and your device doesn not connect, the most common issues are listed below. + +=== Incorrect RCC config + +Check your board and crystal/oscillator, in particular make sure that `HSE` is set to the correct value, e.g. `8_000_000` Hertz if your board does indeed run on a 8 MHz oscillator. + +=== VBUS detection on STM32 platform + +The USB specification requires that all USB devices monitor the bus for detection of plugging/unplugging actions. The devices must pull-up the D+ or D- lane as soon as the host supplies VBUS. + +See the docs, for example at link:https://docs.embassy.dev/embassy-stm32/git/stm32f401vc/usb/struct.Config.html[`usb/struct.Config.html`] for information on how to enable/disable `vbus_detection`. + +When the device is powered only from the USB bus that simultaneously serves as the data connection, this is optional. (If there's no power in VBUS the device would be off anyway, so it's safe to always assume there's power in VBUS, i.e. the USB cable is always plugged in). If your device doesn't have the required connections in place to allow VBUS sensing (see below), then this option needs to be set to `false` to work. + +When the device is powered from another power source and therefore can stay powered through USB cable plug/unplug events, then this must be implemented and `vbus_detection` MUST be set to `true`. + +If your board is powered from the USB and you are unsure whether it supports `vbus_detection`, consult the schematics of your board to see if VBUS is connected to PA9 for USB Full Speed or PB13 for USB High Speed, vice versa, possibly with a voltage divider. When designing your own hardware, see ST application note AN4879 (in particular section 2.6) and the reference manual of your specific chip for more details. + +== Known issues (details and/or mitigations) + +These are issues that are commonly reported. Help wanted fixing them, or improving the UX when possible! + +=== STM32H5 and STM32H7 power issues + +STM32 chips with built-in power management (SMPS and LDO) settings often cause user problems when the configuration does not match how the board was designed. + +Settings from the examples, or even from other working boards, may not work on YOUR board, because they are wired differently. + +Additionally, some PWR settings require a full device reboot (and enough time to discharge any power capacitors!), making this hard to troubleshoot. Also, some +"wrong" power settings will ALMOST work, meaning it will sometimes work on some boots, or for a while, but crash unexpectedly. + +There is not a fix for this yet, as it is board/hardware dependant. See link:https://github.com/embassy-rs/embassy/issues/2806[this tracking issue] for more details + +=== STM32 BDMA only working out of some RAM regions + +The STM32 BDMA controller included in some STM32H7 chips has to be configured to use only certain regions of RAM, +otherwise the transfer will fail. + +If you see errors that look like this: + +[source,plain] +---- +DMA: error on BDMA@1234ABCD channel 4 +---- + +You need to set up your linker script to define a special region for this area and copy data to that region before using with BDMA. + +General steps: + +1. Find out which memory region BDMA has access to. You can get this information from the bus matrix and the memory mapping table in the STM32 datasheet. +2. Add the memory region to `memory.x`, you can modify the generated one from https://github.com/embassy-rs/stm32-data-generated/tree/main/data/chips. +3. You might need to modify `build.rs` to make cargo pick up the modified `memory.x`. +4. In your code, access the defined memory region using `#[link_section = ".xxx"]` +5. Copy data to that region before using BDMA. + +See link:https://github.com/embassy-rs/embassy/blob/main/examples/stm32h7/src/bin/spi_bdma.rs[SMT32H7 SPI BDMA example] for more details. + +== How do I switch to the `main` branch? + +Sometimes to test new changes or fixes, you'll want to switch your project to using a version from GitHub. + +You can add a section to your `Cargo.toml` file like this, you'll need to patch ALL embassy crates to the same revision: + +Using `patch` will replace all direct AND indirect dependencies. + +See the link:https://embassy.dev/book/#_starting_a_new_project[new project docs] for more details on this approach. + +[source,toml] +---- +[patch.crates-io] +# make sure to get the latest git rev from github, you can see the latest one here: +# https://github.com/embassy-rs/embassy/commits/main/ +embassy-embedded-hal = { git = "https://github.com/embassy-rs/embassy", rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" } +embassy-usb = { git = "https://github.com/embassy-rs/embassy", rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" } +embassy-usb-driver = { git = "https://github.com/embassy-rs/embassy", rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" } +---- + +== How do I add support for a new microcontroller to embassy? + +This is particularly for cortex-m, and potentially risc-v, where there is already support for basics like interrupt handling, or even already embassy-executor support for your architecture. + +This is a *much harder path* than just using Embassy on an already supported chip. If you are a beginner, consider using embassy on an existing, well supported chip for a while, before you decide to write drivers from scratch. It's also worth reading the existing source of supported Embassy HALs, to get a feel for how drivers are implemented for various chips. You should already be comfortable reading and writing unsafe code, and understanding the responsibilities of writing safe abstractions for users of your HAL. + +This is not the only possible approach, but if you are looking for where to start, this is a reasonable way to tackle the task: + +1. First, drop by the Matrix room or search around to see if someone has already started writing drivers, either in Embassy or otherwise in Rust. You might not have to start from scratch! +2. Make sure the target is supported in probe-rs, it likely is, and if not, there is likely a cmsis-pack you can use to add support so that flashing and debugging is possible. You will definitely appreciate being able to debug with SWD or JTAG when writing drivers! +3. See if there is an SVD (or SVDs, if it's a family) available, if it is, run it through chiptool to create a PAC for low level register access. If not, there are other ways (like scraping the PDF datasheets or existing C header files), but these are more work than starting from the SVD file to define peripheral memory locations necessary for writing drivers. +4. Either make a fork of embassy repo, and add your target there, or make a repo that just contains the PAC and an empty HAL. It doesn't necessarily have to live in the embassy repo at first. +5. Get a hello world binary working on your chip, either with minimal HAL or just PAC access, use delays and blink a light or send some raw data on some interface, make sure it works and you can flash, debug with defmt + RTT, write a proper linker script, etc. +6. Get basic timer operations and timer interrupts working, upgrade your blinking application to use hardware timers and interrupts, and ensure they are accurate (with a logic analyzer or oscilloscope, if possible). +7. Implement the embassy-time driver API with your timer and timer interrupt code, so that you can use embassy-time operations in your drivers and applications. +8. Then start implementing whatever peripherals you need, like GPIOs, UART, SPI, I2C, etc. This is the largest part of the work, and will likely continue for a while! Don't feel like you need 100% coverage of all peripherals at first, this is likely to be an ongoing process over time. +9. Start implementing the embedded-hal, embedded-io, and embedded-hal-async traits on top of your HAL drivers, once you start having more features completed. This will allow users to use standard external device drivers (e.g. sensors, actuators, displays, etc.) with your HAL. +10. Discuss upstreaming the PAC/HAL for embassy support, or make sure your drivers are added to the awesome-embedded-rust list so that people can find it. + +== Multiple Tasks, or one task with multiple futures? + +Some examples end like this in main: + +[source,rust] +---- +// Run everything concurrently. +// If we had made everything `'static` above instead, we could do this using separate tasks instead. +join(usb_fut, join(echo_fut, log_fut)).await; +---- + +There are two main ways to handle concurrency in Embassy: + +1. Spawn multiple tasks, e.g. with `#[embassy_executor::task]` +2. Manage multiple futures inside ONE task using `join()` or `select()` (as shown above) + +In general, either of these approaches will work. The main differences of these approaches are: + +When using **separate tasks**, each task needs its own RAM allocation, so there's a little overhead for each task, so one task that does three things will likely be a little bit smaller than three tasks that do one thing (not a lot, probably a couple dozen bytes). In contrast, with **multiple futures in one task**, you don't need multiple task allocations, and it will generally be easier to share data, or use borrowed resources, inside of a single task. +An example showcasing some methods for sharing things between tasks link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/sharing.rs[can be found here]. + +But when it comes to "waking" tasks, for example when a data transfer is complete or a button is pressed, it's faster to wake a dedicated task, because that task does not need to check which future is actually ready. `join` and `select` must check ALL of the futures they are managing to see which one (or which ones) are ready to do more work. This is because all Rust executors (like Embassy or Tokio) only have the ability to wake tasks, not specific futures. This means you will use slightly less CPU time juggling futures when using dedicated tasks. + +Practically, there's not a LOT of difference either way - so go with what makes it easier for you and your code first, but there will be some details that are slightly different in each case. + +== splitting peripherals resources between tasks + +There are two ways to split resources between tasks, either manually assigned or by a convenient macro. See link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[this example] + +== My code/driver works in debug mode, but not release mode (or with LTO) + +Issues like these while implementing drivers often fall into one of the following general causes, which are a good list of common errors to check for: + +1. Some kind of race condition - the faster code means you miss an interrupt or something +2. Some kind of UB, if you have unsafe code, or something like DMA with fences missing +3. Some kind of hardware errata, or some hardware misconfiguration like wrong clock speeds +4. Some issue with an interrupt handler, either enabling, disabling, or re-enabling of interrupts when necessary +5. Some kind of async issue, like not registering wakers fully before checking flags, or not registering or pending wakers at the right time + +== How can I prevent the thread-mode executor from going to sleep? == + +In some cases you might want to prevent the thread-mode executor from going to sleep, for example when doing so would result in current spikes that reduce analog performance. +As a workaround, you can spawn a task that yields in a loop, preventing the executor from going to sleep. Note that this may increase power consumption. + +[source,rust] +---- +#[embassy_executor::task] +async fn idle() { + loop { embassy_futures::yield_now().await; } +} +---- + +== Why is my bootloader restarting in loop? + +== Troubleshooting Bootloader Restart Loops + +If your bootloader restarts in a loop, there could be multiple reasons. Here are some things to check: + +=== Validate the `memory.x` File +The bootloader performs critical checks when creating partitions using the addresses defined in `memory.x`. Ensure the following assertions hold true: + +[source,rust] +---- +const { + core::assert!(Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32 == 0); + core::assert!(Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32 == 0); + core::assert!(Self::PAGE_SIZE % DFU::WRITE_SIZE as u32 == 0); + core::assert!(Self::PAGE_SIZE % DFU::ERASE_SIZE as u32 == 0); +} + +// Ensure enough progress pages to store copy progress +assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); +assert!(aligned_buf.len() >= STATE::WRITE_SIZE); +assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); +assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); +---- + +If any of these assertions fail, the bootloader will likely restart in a loop. This failure might not log any messages (e.g., when using `defmt`). Confirm that your `memory.x` file and flash memory align with these requirements. + +=== Handling Panic Logging +Some panic errors might log messages, but certain microcontrollers reset before the message is fully printed. To ensure panic messages are logged, add a delay using no-operation (NOP) instructions before the reset: + +[source,rust] +---- +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + for _ in 0..10_000_000 { + cortex_m::asm::nop(); + } + cortex_m::asm::udf(); +} +---- + +=== Feed the watchdog + + +Some `embassy-boot` implementations (like `embassy-boot-nrf` and `embassy-boot-rp`) rely on a watchdog timer to detect application failure. The bootloader will restart if your application code does not properly feed the watchdog timer. Make sure to feed it correctly. diff --git a/embassy/docs/pages/getting_started.adoc b/embassy/docs/pages/getting_started.adoc new file mode 100644 index 0000000..0174090 --- /dev/null +++ b/embassy/docs/pages/getting_started.adoc @@ -0,0 +1,146 @@ += Getting started + +So you want to try Embassy, great! To get started, there are a few tools you need to install: + +* link:https://rustup.rs/[rustup] - the Rust toolchain is needed to compile Rust code. +* link:https://probe.rs/[probe-rs] - to flash the firmware on your device. If you already have other tools like `OpenOCD` setup, you can use that as well. + +If you don't have any supported board, don't worry: you can also run embassy on your PC using the `std` examples. + +== Getting a board with examples + +Embassy supports many microcontroller families, but the quickest way to get started is by using a board which Embassy has existing example code for. + +This list is non-exhaustive. If your board isn’t included here, check the link:https://github.com/embassy-rs/embassy/tree/main/examples[examples folder] to see if example code has been written for it. + +=== nRF kits + +* link:https://www.nordicsemi.com/Products/Development-hardware/nrf52-dk[nRF52 DK] +* link:https://www.nordicsemi.com/Products/Development-hardware/nRF9160-DK[nRF9160 DK] + +=== STM32 kits + +* link:https://www.st.com/en/evaluation-tools/nucleo-h743zi.html[STM32 Nucleo-144 development board with STM32H743ZI MCU] +* link:https://www.st.com/en/evaluation-tools/nucleo-f429zi.html[STM32 Nucleo-144 development board with STM32F429ZI MCU] +* link:https://www.st.com/en/evaluation-tools/b-l4s5i-iot01a.html[STM32L4+ Discovery kit IoT node, low-power wireless, BLE, NFC, WiFi] +* link:https://www.st.com/en/evaluation-tools/b-l072z-lrwan1.html[STM32L0 Discovery kit LoRa, Sigfox, low-power wireless] +* link:https://www.st.com/en/evaluation-tools/nucleo-wl55jc.html[STM32 Nucleo-64 development board with STM32WL55JCI MCU] +* link:https://www.st.com/en/evaluation-tools/b-u585i-iot02a.html[Discovery kit for IoT node with STM32U5 series] + + +=== RP2040 kits + +* link:https://www.raspberrypi.com/products/raspberry-pi-pico/[Raspberry Pi Pico] + +=== ESP32 + +* link:https://github.com/esp-rs/esp-rust-board[ESP32C3] + +== Running an example + +First you need to clone the link:https://github.com/embassy-rs/embassy[github repository]; + +[source, bash] +---- +git clone https://github.com/embassy-rs/embassy.git +cd embassy +---- + +Once you have a copy of the repository, find examples folder for your board and, and build an example program. `blinky` is a good choice as all it does is blink an LED – the embedded world’s equivalent of “Hello World”. + +[source, bash] +---- +cd examples/nrf52840 +cargo build --bin blinky --release +---- + +Once you’ve confirmed you can build the example, connect your computer to your board with a debug probe and run it on hardware: + +[source, bash] +---- +cargo run --bin blinky --release +---- + +If everything worked correctly, you should see a blinking LED on your board, and debug output similar to this on your computer: + +[source] +---- + Finished dev [unoptimized + debuginfo] target(s) in 1m 56s + Running `probe-run --chip STM32F407VGTx target/thumbv7em-none-eabi/debug/blinky` +(HOST) INFO flashing program (71.36 KiB) +(HOST) INFO success! +──────────────────────────────────────────────────────────────────────────────── +0 INFO Hello World! +└─ blinky::__embassy_main::task::{generator#0} @ src/bin/blinky.rs:18 +1 INFO high +└─ blinky::__embassy_main::task::{generator#0} @ src/bin/blinky.rs:23 +2 INFO low +└─ blinky::__embassy_main::task::{generator#0} @ src/bin/blinky.rs:27 +3 INFO high +└─ blinky::__embassy_main::task::{generator#0} @ src/bin/blinky.rs:23 +4 INFO low +└─ blinky::__embassy_main::task::{generator#0} @ src/bin/blinky.rs:27 +---- + +NOTE: How does the `+cargo run+` command know how to connect to our board and program it? In each `examples` folder, there’s a `.cargo/config.toml` file which tells cargo to use link:https://probe.rs/[probe-rs] as the runner for ARM binaries in that folder. probe-rs handles communication with the debug probe and MCU. In order for this to work, probe-rs needs to know which chip it’s programming, so you’ll have to edit this file if you want to run examples on other chips. + +=== It didn’t work! + +If you hare having issues when running `+cargo run --release+`, please check the following: + +* You are specifying the correct `+--chip+` on the command line, OR +* You have set `+.cargo/config.toml+`’s run line to the correct chip, AND +* You have changed `+examples/Cargo.toml+`’s HAL (e.g. embassy-stm32) dependency's feature to use the correct chip (replace the existing stm32xxxx feature) + +At this point the project should run. If you do not see a blinky LED for blinky, for example, be sure to check the code is toggling your board's LED pin. + +If you are trying to run an example with `+cargo run --release+` and you see the following output: +[source] +---- +0.000000 INFO Hello World! +└─ +0.000000 DEBUG rcc: Clocks { sys: Hertz(80000000), apb1: Hertz(80000000), apb1_tim: Hertz(80000000), apb2: Hertz(80000000), apb2_tim: Hertz(80000000), ahb1: Hertz(80000000), ahb2: Hertz(80000000), ahb3: Hertz(80000000) } +└─ +0.000061 TRACE allocating type=Interrupt mps=8 interval_ms=255, dir=In +└─ +0.000091 TRACE index=1 +└─ +---- + +To get rid of the frame-index error add the following to your `Cargo.toml`: + +[source,toml] +---- +[profile.release] +debug = 2 +---- + +If you’re getting an extremely long error message containing something like the following: + +[source] +---- +error[E0463]: can't find crate for `std` + | + = note: the `thumbv6m-none-eabi` target may not support the standard library + = note: `std` is required by `stable_deref_trait` because it does not declare `#![no_std]` +---- + +Make sure that you didn’t accidentally run `+cargo add probe-rs+` (which adds it as a dependency) instead of link:https://probe.rs/docs/getting-started/installation/[correctly installing probe-rs]. + +If you’re using a raspberry pi pico-w, make sure you’re running `+cargo run --bin wifi_blinky --release+` rather than the regular blinky. The pico-w’s on-board LED is connected to the WiFi chip, which needs to be initialized before the LED can be blinked. + +If you’re using an rp2040 debug probe (e.g. the pico probe) and are having issues after running `probe-rs info`, unplug and reconnect the probe, letting it power cycle. Running `probe-rs info` is link:https://github.com/probe-rs/probe-rs/issues/1849[known to put the pico probe into an unusable state]. + +:embassy-dev-faq-link-with-hash: https://embassy.dev/book/#_frequently_asked_questions +:embassy-matrix-channel: https://matrix.to/#/#embassy-rs:matrix.org + +If you’re still having problems, check the {embassy-dev-faq-link-with-hash}[FAQ], or ask for help in the {embassy-matrix-channel}[Embassy Chat Room]. + +== What's next? + +Congratulations, you have your first Embassy application running! Here are some suggestions for where to go from here: + +* Read more about the xref:_embassy_executor[executor]. +* Read more about the xref:_hardware_abstraction_layer_hal[HAL]. +* Start xref:_a_basic_embassy_application[writing your application]. +* Learn how to xref:_starting_a_new_project[start a new embassy project by adapting an example]. diff --git a/embassy/docs/pages/hal.adoc b/embassy/docs/pages/hal.adoc new file mode 100644 index 0000000..14b85e1 --- /dev/null +++ b/embassy/docs/pages/hal.adoc @@ -0,0 +1,14 @@ += Hardware Abstraction Layer (HAL) + +Embassy provides HALs for several microcontroller families: + +* `embassy-nrf` for the nRF microcontrollers from Nordic Semiconductor +* `embassy-stm32` for STM32 microcontrollers from ST Microelectronics +* `embassy-rp` for the Raspberry Pi RP2040 microcontrollers + +These HALs implement async/await functionality for most peripherals while also implementing the +async traits in `embedded-hal` and `embedded-hal-async`. You can also use these HALs with another executor. + +For the ESP32 series, there is an link:https://github.com/esp-rs/esp-hal[esp-hal] which you can use. + +For the WCH 32-bit RISC-V series, there is an link:https://github.com/ch32-rs/ch32-hal[ch32-hal], which you can use. diff --git a/embassy/docs/pages/layer_by_layer.adoc b/embassy/docs/pages/layer_by_layer.adoc new file mode 100644 index 0000000..7852d27 --- /dev/null +++ b/embassy/docs/pages/layer_by_layer.adoc @@ -0,0 +1,85 @@ += From bare metal to async Rust + +If you're new to Embassy, it can be overwhelming to grasp all the terminology and concepts. This guide aims to clarify the different layers in Embassy, which problem each layer solves for the application writer. + +This guide uses the STM32 IOT01A board, but should be easy to translate to any STM32 chip. For nRF, the PAC itself is not maintained within the Embassy project, but the concepts and the layers are similar. + +The application we'll write is a simple 'push button, blink led' application, which is great for illustrating input and output handling for each of the examples we'll go through. We'll start at the Peripheral Access Crate (PAC) example and end at the async example. + +== 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 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. + +The blinky app using PAC is shown below: + +[source,rust] +---- +include::../examples/layer-by-layer/blinky-pac/src/main.rs[] +---- + +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 as: + +* Automatically enabling the peripheral clock when you're using the peripheral +* Deriving and applying register configuration from higher level types +* Implementing the embedded-hal traits to make peripherals useful in third party drivers + +The HAL example is shown below: + +[source,rust] +---- +include::../examples/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` 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. + +== Interrupt driven + +To save power, we need to configure the application so that it can be notified when the button is pressed using an interrupt. + +Once the interrupt is configured, the application can instruct the microcontroller to enter a sleep mode, consuming very little power. + +Given Embassy focus on async Rust (which we'll come back to after this example), the example application must use a combination of the HAL and PAC in order to use interrupts. For this reason, the application also contains some helper functions to access the PAC (not shown below). + +[source,rust] +---- +include::../examples/layer-by-layer/blinky-irq/src/main.rs[lines="1..57"] +---- + +The simple application is now more complex again, primarily because of the need to keep the button and LED states in the global scope where it is accessible by the main application loop, as well as the interrupt handler. + +To do that, the types must be guarded by a mutex, and interrupts must be disabled whenever we are accessing this global state to gain access to the peripherals. + +Luckily, there is an elegant solution to this problem when using Embassy. + +== Async version + +It's time to use the Embassy capabilities to its fullest. At the core, Embassy has an async executor, or a runtime for async tasks if you will. The executor polls a set of tasks (defined at compile time), and whenever a task `blocks`, the executor will run another task, or put the microcontroller to sleep. + +[source,rust] +---- +include::../examples/layer-by-layer/blinky-async/src/main.rs[] +---- + +The async version looks very similar to the HAL version, apart from a few minor details: + +* The main entry point is annotated with a different macro and has an async type signature. This macro creates and starts an Embassy runtime instance and launches the main application task. Using the `Spawner` instance, the application may spawn other tasks. +* The peripheral initialization is done by the main macro, and is handed to the main task. +* Before checking the button state, the application is awaiting a transition in the pin state (low -> high or high -> low). + +When `button.await_for_any_edge().await` is called, the executor will pause the main task and put the microcontroller in sleep mode, unless there are other tasks that can run. Internally, the Embassy HAL has configured the interrupt handler for the button (in `ExtiInput`), so that whenever an interrupt is raised, the task awaiting the button will be woken up. + +The minimal overhead of the executor and the ability to run multiple tasks "concurrently" combined with the enormous simplification of the application, makes `async` a great fit for embedded. + +== Summary + +We have seen how the same application can be written at the different abstraction levels in Embassy. First starting out at the PAC level, then using the HAL, then using interrupts, and then using interrupts indirectly using async Rust. diff --git a/embassy/docs/pages/new_project.adoc b/embassy/docs/pages/new_project.adoc new file mode 100644 index 0000000..6334001 --- /dev/null +++ b/embassy/docs/pages/new_project.adoc @@ -0,0 +1,202 @@ += Starting a new project + +Once you’ve successfully xref:#_getting_started[run some example projects], the next step is to make a standalone Embassy project. + +== Tools for generating Embassy projects + +=== CLI +- link:https://github.com/adinack/cargo-embassy[cargo-embassy] (STM32 and NRF) + +=== cargo-generate +- link:https://github.com/lulf/embassy-template[embassy-template] (STM32, NRF, and RP) +- link:https://github.com/bentwire/embassy-rp2040-template[embassy-rp2040-template] (RP) + + +== Starting a project from scratch + +As an example, let’s create a new embassy project from scratch for a STM32G474. The same instructions are applicable for any supported chip with some minor changes. + +Run: + +[source,bash] +---- +cargo new stm32g474-example +cd stm32g474-example +---- + +to create an empty rust project: + +[source] +---- +stm32g474-example +├── Cargo.toml +└── src + └── main.rs +---- + +Looking in link:https://github.com/embassy-rs/embassy/tree/main/examples[the Embassy examples], we can see there’s a `stm32g4` folder. Find `src/blinky.rs` and copy its contents into our `src/main.rs`. + +=== The .cargo/config.toml + +Currently, we’d need to provide cargo with a target triple every time we run `cargo build` or `cargo run`. Let’s spare ourselves that work by copying `.cargo/config.toml` from `examples/stm32g4` into our project. + +[source] +---- +stm32g474-example +├── .cargo +│   └── config.toml +├── Cargo.toml +└── src + └── main.rs +---- + +In addition to a target triple, `.cargo/config.toml` contains a `runner` key which allows us to conveniently run our project on hardware with `cargo run` via probe-rs. In order for this to work, we need to provide the correct chip ID. We can do this by checking `probe-rs chip list`: + +[source,bash] +---- +$ probe-rs chip list | grep -i stm32g474re + STM32G474RETx +---- + +and copying `STM32G474RETx` into `.cargo/config.toml` as so: + +[source,toml] +---- +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32G071C8Rx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32G474RETx" +---- + +=== Cargo.toml + +Now that cargo knows what target to compile for (and probe-rs knows what chip to run it on), we’re ready to add some dependencies. + +Looking in `examples/stm32g4/Cargo.toml`, we can see that the examples require a number of embassy crates. For blinky, we’ll only need three of them: `embassy-stm32`, `embassy-executor` and `embassy-time`. + + +At the time of writing, embassy is already published to crates.io. Therefore, dependencies can easily added via Cargo.toml. + +[source,toml] +---- +[dependencies] +embassy-stm32 = { version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"] } +embassy-executor = { version = "0.6.3", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +---- + +Prior, embassy needed to be installed straight from the git repository. Installing from git is still useful, if you want to checkout a specic revision of an embassy crate which is not yet published. +The recommended way of doing so is as follows: + +* Copy the required `embassy-*` lines from the example `Cargo.toml` +* Make any necessary changes to `features`, e.g. requiring the `stm32g474re` feature of `embassy-stm32` +* Remove the `path = ""` keys in the `embassy-*` entries +* Create a `[patch.crates-io]` section, with entries for each embassy crate we need. These should all contain identical values: a link to the git repository, and a reference to the commit we’re checking out. Assuming you want the latest commit, you can find it by running `git ls-remote https://github.com/embassy-rs/embassy.git HEAD` + +NOTE: When using this method, it’s necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crate’s `Cargo.toml` in the specificed `rev` under `[patch.crates.io]`. This means that when updating, you have to a pick a new revision, change everything in `[patch.crates.io]` to match it, and then correct any versions under `[dependencies]` which have changed. + +An example Cargo.toml file might look as follows: + +[source,toml] +---- +[dependencies] +embassy-stm32 = {version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"]} +embassy-executor = { version = "0.3.3", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } + +[patch.crates-io] +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" } +embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" } +---- + +There are a few other dependencies we need to build the project, but fortunately they’re much simpler to install. Copy their lines from the example `Cargo.toml` to the the `[dependencies]` section in the new `Cargo.toml`: + +[source,toml] +---- +defmt = "0.3.5" +defmt-rtt = "0.4.0" +cortex-m = {version = "0.7.7", features = ["critical-section-single-core"]} +cortex-m-rt = "0.7.3" +panic-probe = "0.3.1" +---- + +These are the bare minimum dependencies required to run `blinky.rs`, but it’s worth taking a look at the other dependencies specified in the example `Cargo.toml`, and noting what features are required for use with embassy – for example `futures = { version = "0.3.17", default-features = false, features = ["async-await"] }`. + +Finally, copy the `[profile.release]` section from the example `Cargo.toml` into ours. + +[source,toml] +---- +[profile.release] +debug = 2 +---- + +=== rust-toolchain.toml + +Before we can build our project, we need to add an additional file to tell cargo to use the nightly toolchain. Copy the `rust-toolchain.toml` from the embassy repo to ours, and trim the list of targets down to only the target triple relevent for our project — in this case, `thumbv7em-none-eabi`: + +[source] +---- +stm32g474-example +├── .cargo +│   └── config.toml +├── Cargo.toml +├── rust-toolchain.toml +└── src + └── main.rs +---- + +[source,toml] +---- +# Before upgrading check that everything is available on all tier1 targets here: +# https://rust-lang.github.io/rustup-components-history +[toolchain] +channel = "nightly-2023-11-01" +components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ] +targets = ["thumbv7em-none-eabi"] +---- + +=== build.rs + +In order to produce a working binary for our target, cargo requires a custom build script. Copy `build.rs` from the example to our project: + +[source] +---- +stm32g474-example +├── build.rs +├── .cargo +│ └── config.toml +├── Cargo.toml +├── rust-toolchain.toml +└── src + └── main.rs +---- + +=== Building and running + +At this point, we‘re finally ready to build and run our project! Connect your board via a debug probe and run: + +[source,bash] +---- +cargo run --release +---- + +should result in a blinking LED (if there’s one attached to the pin in `src/main.rs` – change it if not!) and the following output: + +[source] +---- + Compiling stm32g474-example v0.1.0 (/home/you/stm32g474-example) + Finished release [optimized + debuginfo] target(s) in 0.22s + Running `probe-rs run --chip STM32G474RETx target/thumbv7em-none-eabi/release/stm32g474-example` + Erasing sectors ✔ [00:00:00] [#########################################################] 18.00 KiB/18.00 KiB @ 54.09 KiB/s (eta 0s ) + Programming pages ✔ [00:00:00] [#########################################################] 17.00 KiB/17.00 KiB @ 35.91 KiB/s (eta 0s ) Finished in 0.817s +0.000000 TRACE BDCR configured: 00008200 +└─ embassy_stm32::rcc::bd::{impl#3}::init::{closure#4} @ /home/you/.cargo/git/checkouts/embassy-9312dcb0ed774b29/7703f47/embassy-stm32/src/fmt.rs:117 +0.000000 DEBUG rcc: Clocks { sys: Hertz(16000000), pclk1: Hertz(16000000), pclk1_tim: Hertz(16000000), pclk2: Hertz(16000000), pclk2_tim: Hertz(16000000), hclk1: Hertz(16000000), hclk2: Hertz(16000000), pll1_p: None, adc: None, adc34: None, rtc: Some(Hertz(32000)) } +└─ embassy_stm32::rcc::set_freqs @ /home/you/.cargo/git/checkouts/embassy-9312dcb0ed774b29/7703f47/embassy-stm32/src/fmt.rs:130 +0.000000 INFO Hello World! +└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:14 +0.000091 INFO high +└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:19 +0.300201 INFO low +└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:23 +---- diff --git a/embassy/docs/pages/nrf.adoc b/embassy/docs/pages/nrf.adoc new file mode 100644 index 0000000..de052b6 --- /dev/null +++ b/embassy/docs/pages/nrf.adoc @@ -0,0 +1,29 @@ += Embassy nRF HAL + +The link:https://github.com/embassy-rs/embassy/tree/main/embassy-nrf[Embassy nRF HAL] is based on the PACs (Peripheral Access Crate) from link:https://github.com/nrf-rs/[nrf-rs]. + +== Timer driver + +The nRF timer driver operates at 32768 Hz by default. + +== Peripherals + +The following peripherals have a HAL implementation at present + +* PWM +* SPIM +* QSPI +* NVMC +* GPIOTE +* RNG +* TIMER +* WDT +* TEMP +* PPI +* UARTE +* TWIM +* SAADC + +== Bluetooth + +For bluetooth, you can use the link:https://github.com/embassy-rs/nrf-softdevice[nrf-softdevice] crate. diff --git a/embassy/docs/pages/overview.adoc b/embassy/docs/pages/overview.adoc new file mode 100644 index 0000000..8f97d93 --- /dev/null +++ b/embassy/docs/pages/overview.adoc @@ -0,0 +1,83 @@ += Introduction + +Embassy is a project to make async/await a first-class option for embedded development. + +== What is async? + +When handling I/O, software must call functions that block program execution until the I/O operation completes. When running inside of an OS such as Linux, such functions generally transfer control to the kernel so that another task (known as a “thread”) can be executed if available, or the CPU can be put to sleep until another task is ready. + +Because an OS cannot presume that threads will behave cooperatively, threads are relatively resource-intensive, and may be forcibly interrupted they do not transfer control back to the kernel within an allotted time. If tasks could be presumed to behave cooperatively, or at least not maliciously, it would be possible to create tasks that appear to be almost free when compared to a traditional OS thread. + +In other programming languages, these lightweight tasks are known as “coroutines” or ”goroutines”. In Rust, they are implemented with async. Async-await works by transforming each async function into an object called a future. When a future blocks on I/O the future yields, and the scheduler, called an executor, can select a different future to execute. + +Compared to alternatives such as an RTOS, async can yield better performance and lower power consumption because the executor doesn't have to guess when a future is ready to execute. However, program size may be higher than other alternatives, which may be a problem for certain space-constrained devices with very low memory. On the devices Embassy supports, such as stm32 and nrf, memory is generally large enough to accommodate the modestly-increased program size. + +== What is Embassy? + +The Embassy project consists of several crates that you can use together or independently: + +=== Executor +The link:https://docs.embassy.dev/embassy-executor/[embassy-executor] is an async/await executor that generally executes a fixed number of tasks, allocated at startup, though more can be added later. The executor may also provide a system timer that you can use for both async and blocking delays. For less than one microsecond, blocking delays should be used because the cost of context-switching is too high and the executor will be unable to provide accurate timing. + +=== Hardware Abstraction Layers +HALs implement safe Rust API which let you use peripherals such as USART, UART, I2C, SPI, CAN, and USB without having to directly manipulate registers. + +Embassy provides implementations of both async and blocking APIs where it makes sense. DMA (Direct Memory Access) is an example where async is a good fit, whereas GPIO states are a better fit for a blocking API. + +The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy. + +* link:https://docs.embassy.dev/embassy-stm32/[embassy-stm32], for all STM32 microcontroller families. +* link:https://docs.embassy.dev/embassy-nrf/[embassy-nrf], for the Nordic Semiconductor nRF52, nRF53, nRF91 series. +* link:https://docs.embassy.dev/embassy-rp/[embassy-rp], for the Raspberry Pi RP2040 microcontroller. +* link:https://github.com/esp-rs[esp-rs], for the Espressif Systems ESP32 series of chips. +* link:https://github.com/ch32-rs/ch32-hal[ch32-hal], for the WCH 32-bit RISC-V(CH32V) series of chips. + +NOTE: A common question is if one can use the Embassy HALs standalone. Yes, it is possible! There are no dependency on the executor within the HALs. You can even use them without async, +as they implement both the link:https://github.com/rust-embedded/embedded-hal[Embedded HAL] blocking and async traits. + +=== Networking +The link:https://docs.embassy.dev/embassy-net/[embassy-net] network stack implements extensive networking functionality, including Ethernet, IP, TCP, UDP, ICMP and DHCP. Async drastically simplifies managing timeouts and serving multiple connections concurrently. Several drivers for WiFi and Ethernet chips can be found. + +=== Bluetooth +The link:https://github.com/embassy-rs/nrf-softdevice[nrf-softdevice] crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers. + +=== LoRa +link:https://github.com/lora-rs/lora-rs[lora-rs] supports LoRa networking on a wide range of LoRa radios, fully integrated with a Rust LoRaWAN implementation. It provides four crates — lora-phy, lora-modulation, lorawan-encoding, and lorawan-device — and basic examples for various development boards. It has support for STM32WL wireless microcontrollers or Semtech SX127x transceivers, among others. + +=== USB +link:https://docs.embassy.dev/embassy-usb/[embassy-usb] implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own. + +=== Bootloader and DFU +link:https://github.com/embassy-rs/embassy/tree/main/embassy-boot[embassy-boot] is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks. + +== What is DMA? + +For most I/O in embedded devices, the peripheral doesn't directly support the transmission of multiple bytes at once, with CAN being a notable exception. Instead, the MCU must write each byte, one at a time, and then wait until the peripheral is ready to send the next. For high I/O rates, this can pose a problem if the MCU must devote an increasing portion of its time handling each byte. The solution to this problem is to use the Direct Memory Access controller. + +The Direct Memory Access controller (DMA) is a controller that is present in MCUs that Embassy supports, including stm32 and nrf. The DMA allows the MCU to set up a transfer, either send or receive, and then wait for the transfer to complete. With DMA, once started, no MCU intervention is required until the transfer is complete, meaning that the MCU can perform other computation, or set up other I/O while the transfer is in progress. For high I/O rates, DMA can cut the time that the MCU spends handling I/O by over half. However, because DMA is more complex to set-up, it is less widely used in the embedded community. Embassy aims to change that by making DMA the first choice rather than the last. Using Embassy, there's no additional tuning required once I/O rates increase because your application is already set-up to handle them. + +== Examples + +Embassy provides examples for all HALs supported. You can find them in the `examples/` folder. + + +Main loop example + +[source,rust] +---- +include::../examples/examples/std/src/bin/tick.rs[] +---- + +include::embassy_in_the_wild.adoc[leveloffset = 2] + +== Resources + +For more reading material on async Rust and Embassy: + +* link:https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown[Comparsion of FreeRTOS and Embassy] +* link:https://dev.to/apollolabsbin/series/20707[Tutorials] +* link:https://blog.drogue.io/firmware-updates-part-1/[Firmware Updates with Embassy] + +Videos: + +* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust] diff --git a/embassy/docs/pages/project_structure.adoc b/embassy/docs/pages/project_structure.adoc new file mode 100644 index 0000000..722ec8d --- /dev/null +++ b/embassy/docs/pages/project_structure.adoc @@ -0,0 +1,93 @@ += Project Structure + +There are many ways to configure embassy and its components for your exact application. The link:https://github.com/embassy-rs/embassy/tree/main/examples[examples] directory for each chipset demonstrates how your project structure should look. Let's break it down: + +The toplevel file structure of your project should look like this: +[source,plain] +---- +{} = Maybe + +my-project +|- .cargo +| |- config.toml +|- src +| |- main.rs +|- build.rs +|- Cargo.toml +|- {memory.x} +|- rust-toolchain.toml +---- + +[discrete] +== .cargo/config.toml + +This directory/file describes what platform you're on, and configures link:https://github.com/probe-rs/probe-rs[probe-rs] to deploy to your device. + +Here is a minimal example: + +[source,toml] +---- +[target.thumbv6m-none-eabi] # <-change for your platform +runner = 'probe-rs run --chip STM32F031K6Tx' # <- change for your chip + +[build] +target = "thumbv6m-none-eabi" # <-change for your platform + +[env] +DEFMT_LOG = "trace" # <- can change to info, warn, or error +---- + +[discrete] +== build.rs + +This is the build script for your project. It links defmt (what is link:https://defmt.ferrous-systems.com[defmt]?) and the `memory.x` file if needed. This file is pretty specific for each chipset, just copy and paste from the corresponding link:https://github.com/embassy-rs/embassy/tree/main/examples[example]. + +[discrete] +== Cargo.toml + +This is your manifest file, where you can configure all of the embassy components to use the features you need. + +[discrete] +=== Features + +[discrete] +==== Time +- tick-hz-x: Configures the tick rate of `embassy-time`. Higher tick rate means higher precision, and higher CPU wakes. +- defmt-timestamp-uptime: defmt log entries will display the uptime in seconds. + +...more to come + +[discrete] +== memory.x + +This file outlines the flash/ram usage of your program. It is especially useful when using link:https://github.com/embassy-rs/nrf-softdevice[nrf-softdevice] on an nRF5x. + +Here is an example for using S140 with an nRF52840: + +[source,x] +---- +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* These values correspond to the NRF52840 with Softdevices S140 7.0.1 */ + FLASH : ORIGIN = 0x00027000, LENGTH = 868K + RAM : ORIGIN = 0x20020000, LENGTH = 128K +} +---- + +[discrete] +== rust-toolchain.toml + +This file configures the rust version and configuration to use. + +A minimal example: + +[source,toml] +---- +[toolchain] +channel = "nightly-2023-08-19" # <- as of writing, this is the exact rust version embassy uses +components = [ "rust-src", "rustfmt" ] # <- optionally add "llvm-tools-preview" for some extra features like "cargo size" +targets = [ + "thumbv6m-none-eabi" # <-change for your platform +] +---- diff --git a/embassy/docs/pages/runtime.adoc b/embassy/docs/pages/runtime.adoc new file mode 100644 index 0000000..f2812dd --- /dev/null +++ b/embassy/docs/pages/runtime.adoc @@ -0,0 +1,46 @@ += Embassy executor + +The Embassy executor is an async/await executor designed for embedded usage along with support functionality for interrupts and timers. + +== Features + +* No `alloc`, no heap needed. Task are statically allocated. +* No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning. +* Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`. +* No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`. +* Efficient polling: a wake will only poll the woken task, not all of them. +* Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time. +* Creating multiple executor instances is supported, to run tasks at different priority levels. This allows higher-priority tasks to preempt lower-priority tasks. + +== Executor + +The executor function is described below. The executor keeps a queue of tasks that it should poll. When a task is created, it is polled (1). The task will attempt to make progress until it reaches a point where it would be blocked. This may happen whenever a task is .await'ing an async function. When that happens, the task yields execution by (2) returning `Poll::Pending`. Once a task yields, the executor enqueues the task at the end of the run queue, and proceeds to (3) poll the next task in the queue. When a task is finished or canceled, it will not be enqueued again. + +IMPORTANT: The executor relies on tasks not blocking indefinitely, as this prevents the executor to regain control and schedule another task. + +image::embassy_executor.png[Executor model] + +If you use the `#[embassy_executor::main]` macro in your application, it creates the `Executor` for you and spawns the main entry point as the first task. You can also create the Executor manually, and you can in fact create multiple Executors. + + +== Interrupts + +Interrupts are a common way for peripherals to signal completion of some operation and fits well with the async execution model. The following diagram describes a typical application flow where (1) a task is polled and is attempting to make progress. The task then (2) instructs the peripheral to perform some operation, and awaits. After some time has passed, (3) an interrupt is raised, marking the completion of the operation. + +The peripheral HAL then (4) ensures that interrupt signals are routed to the peripheral and updating the peripheral state with the results of the operation. The executor is then (5) notified that the task should be polled, which it will do. + +image::embassy_irq.png[Interrupt handling] + +NOTE: There exists a special executor named `InterruptExecutor` which can be driven by an interrupt. This can be used to drive tasks at different priority levels by creating multiple `InterruptExecutor` instances. + +== Time + +Embassy features an internal timer queue enabled by the `time` feature flag. When enabled, Embassy assumes a time `Driver` implementation existing for the platform. Embassy provides time drivers for the nRF, STM32, RPi Pico, WASM and Std platforms. + +The timer driver implementations for the embedded platforms might support only a fixed number of alarms that can be set. Make sure the number of tasks you expect wanting to use the timer at the same time do not exceed this limit. + +The timer speed is configurable at compile time using the `time-tick-`. At present, the timer may be configured to run at 1000 Hz, 32768 Hz, or 1 MHz. Before changing the defaults, make sure the target HAL supports the particular frequency setting. + + + +NOTE: If you do not require timers in your application, not enabling the `time` feature can save some CPU cycles and reduce power usage. diff --git a/embassy/docs/pages/sharing_peripherals.adoc b/embassy/docs/pages/sharing_peripherals.adoc new file mode 100644 index 0000000..dfb8c1f --- /dev/null +++ b/embassy/docs/pages/sharing_peripherals.adoc @@ -0,0 +1,134 @@ += Sharing peripherals between tasks + +Often times, more than one task needs access to the same resource (pin, communication interface, etc.). Embassy provides many different synchronization primitives in the link:https://crates.io/crates/embassy-sync[embassy-sync] crate. + +The following examples shows different ways to use the on-board LED on a Raspberry Pi Pico board by two tasks simultaneously. + +== Sharing using a Mutex + +Using mutual exclusion is the simplest way to share a peripheral. + +TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here]. +[,rust] +---- +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_time::{Duration, Ticker}; +use gpio::{AnyPin, Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +type LedType = Mutex>>; +static LED: LedType = Mutex::new(None); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + // set the content of the global LED reference to the real LED pin + let led = Output::new(AnyPin::from(p.PIN_25), Level::High); + // inner scope is so that once the mutex is written to, the MutexGuard is dropped, thus the + // Mutex is released + { + *(LED.lock().await) = Some(led); + } + let dt = 100 * 1_000_000; + let k = 1.003; + + unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos(dt)))); + unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos((dt as f64 * k) as u64)))); +} + +// A pool size of 2 means you can spawn two instances of this task. +#[embassy_executor::task(pool_size = 2)] +async fn toggle_led(led: &'static LedType, delay: Duration) { + let mut ticker = Ticker::every(delay); + loop { + { + let mut led_unlocked = led.lock().await; + if let Some(pin_ref) = led_unlocked.as_mut() { + pin_ref.toggle(); + } + } + ticker.next().await; + } +} +---- + +The structure facilitating access to the resource is the defined `LedType`. + +=== Why so complicated + +Unwrapping the layers gives insight into why each one is needed. + +==== `Mutex` + +The mutex is there so if one task gets the resource first and begins modifying it, all other tasks wanting to write will have to wait (the `led.lock().await` will return immediately if no task has locked the mutex, and will block if it is accessed somewhere else). + +==== `Option` + +The `LED` variable needs to be defined outside the main task as references accepted by tasks need to be `'static`. However, if it is outside the main task, it cannot be initialised to point to any pin, as the pins themselves are not initialised. Thus, it is set to `None`. + +==== `Output` + +To indicate that the pin will be set to an Output. The `AnyPin` could have been `embassy_rp::peripherals::PIN_25`, however this option lets the `toggle_led` function be more generic. + +== Sharing using a Channel + +A channel is another way to ensure exclusive access to a resource. Using a channel is great in the cases where the access can happen at a later point in time, allowing you to enqueue operations and do other things. + +TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here]. +[,rust] +---- +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::{Channel, Sender}; +use embassy_time::{Duration, Ticker}; +use gpio::{AnyPin, Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +enum LedState { + Toggle, +} +static CHANNEL: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(AnyPin::from(p.PIN_25), Level::High); + + let dt = 100 * 1_000_000; + let k = 1.003; + + unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos(dt)))); + unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos((dt as f64 * k) as u64)))); + + loop { + match CHANNEL.receive().await { + LedState::Toggle => led.toggle(), + } + } +} + +// A pool size of 2 means you can spawn two instances of this task. +#[embassy_executor::task(pool_size = 2)] +async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>, delay: Duration) { + let mut ticker = Ticker::every(delay); + loop { + control.send(LedState::Toggle).await; + ticker.next().await; + } +} +---- + +This example replaces the Mutex with a Channel, and uses another task (the main loop) to drive the LED. The advantage of this approach is that only a single task references the peripheral, separating concerns. However, using a Mutex has a lower overhead and might be necessary if you need to ensure +that the operation is completed before continuing to do other work in your task. + +An example showcasing more methods for sharing link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/sharing.rs[can be found here]. + +== Sharing an I2C or SPI bus between multiple devices + +An example of how to deal with multiple devices sharing a common I2C or SPI bus link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/shared_bus.rs[can be found here]. diff --git a/embassy/docs/pages/stm32.adoc b/embassy/docs/pages/stm32.adoc new file mode 100644 index 0000000..df139a4 --- /dev/null +++ b/embassy/docs/pages/stm32.adoc @@ -0,0 +1,24 @@ += Embassy STM32 HAL + +The link:https://github.com/embassy-rs/embassy/tree/main/embassy-stm32[Embassy STM32 HAL] is based on the `stm32-metapac` project. + +== The infinite variant problem + +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 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 + +The `stm32-metapac` module uses pre-generated chip and register definitions for STM32 chip families to generate register types. This is done at compile time based on Cargo feature flags. + +The chip and register definitions are located in a separate module, `stm32-data`, which is modified whenever a bug is found in the definitions, or when adding support for new chip families. + +=== The HAL + +The `embassy-stm32` module contains the HAL implementation for all STM32 families. The implementation uses automatically derived feature flags to support the correct version of a given peripheral for a given chip family. + +== Timer driver + +The STM32 timer driver operates at 32768 Hz by default. diff --git a/embassy/docs/pages/system.adoc b/embassy/docs/pages/system.adoc new file mode 100644 index 0000000..985f92b --- /dev/null +++ b/embassy/docs/pages/system.adoc @@ -0,0 +1,13 @@ += System description + +This section describes different parts of Embassy in more detail. + +include::runtime.adoc[leveloffset = 2] +include::bootloader.adoc[leveloffset = 2] +include::time_keeping.adoc[leveloffset = 2] +include::hal.adoc[leveloffset = 2] +include::nrf.adoc[leveloffset = 2] +include::stm32.adoc[leveloffset = 2] +include::sharing_peripherals.adoc[leveloffset = 2] +include::developer.adoc[leveloffset = 2] +include::developer_stm32.adoc[leveloffset = 2] diff --git a/embassy/docs/pages/time_keeping.adoc b/embassy/docs/pages/time_keeping.adoc new file mode 100644 index 0000000..11ddb2b --- /dev/null +++ b/embassy/docs/pages/time_keeping.adoc @@ -0,0 +1,62 @@ += Time-keeping + +In an embedded program, delaying a task is one of the most common actions taken. In an event loop, delays will need to be inserted to ensure +that other tasks have a chance to run before the next iteration of the loop is called, if no other I/O is performed. Embassy provides abstractions +to delay the current task for a specified interval of time. + +The interface for time-keeping in Embassy is handled by the link:https://crates.io/crates/embassy-time[embassy-time] crate. The types can be used with the internal +timer queue in link:https://crates.io/crates/embassy-executor[embassy-executor] or a custom timer queue implementation. + +== Timer + +The `embassy::time::Timer` type provides two timing methods. + +`Timer::at` creates a future that completes at the specified `Instant`, relative to the system boot time. +`Timer::after` creates a future that completes after the specified `Duration`, relative to when the future was created. + +An example of a delay is provided as follows: + +TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here]. +[,rust] +---- +use embassy::executor::{task, Executor}; +use embassy::time::{Duration, Timer}; + +#[task] +/// Task that ticks periodically +async fn tick_periodic() -> ! { + loop { + rprintln!("tick!"); + // async sleep primitive, suspends the task for 500ms. + Timer::after(Duration::from_millis(500)).await; + } +} +---- + +== Delay + +The `embassy::time::Delay` type provides an implementation of the link:https://docs.rs/embedded-hal/1.0.0/embedded_hal/delay/index.html[embedded-hal] and +link:https://docs.rs/embedded-hal-async/latest/embedded_hal_async/delay/index.html[embedded-hal-async] traits. This can be used for drivers +that expect a generic delay implementation to be provided. + +An example of how this can be used: + +TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here]. +[,rust] +---- +use embassy::executor::{task, Executor}; + +#[task] +/// Task that ticks periodically +async fn tick_periodic() -> ! { + loop { + rprintln!("tick!"); + // async sleep primitive, suspends the task for 500ms. + generic_delay(embassy::time::Delay).await + } +} + +async fn generic_delay(delay: D) { + delay.delay_ms(500).await; +} +---- diff --git a/embassy/embassy-boot-nrf/Cargo.toml b/embassy/embassy-boot-nrf/Cargo.toml new file mode 100644 index 0000000..dd5c078 --- /dev/null +++ b/embassy/embassy-boot-nrf/Cargo.toml @@ -0,0 +1,46 @@ +[package] +edition = "2021" +name = "embassy-boot-nrf" +version = "0.3.0" +description = "Bootloader lib for nRF chips" +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-boot-nrf" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot-nrf/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot-nrf/src/" +features = ["embassy-nrf/nrf52840"] +target = "thumbv7em-none-eabi" + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.17", optional = true } + +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-nrf = { version = "0.2.0", path = "../embassy-nrf", default-features = false } +embassy-boot = { version = "0.3.0", path = "../embassy-boot" } +cortex-m = { version = "0.7.6" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +cfg-if = "1.0.0" + +nrf-softdevice-mbr = { version = "0.2.0", optional = true } + +[features] +defmt = [ + "dep:defmt", + "embassy-boot/defmt", + "embassy-nrf/defmt", +] +softdevice = [ + "nrf-softdevice-mbr", +] diff --git a/embassy/embassy-boot-nrf/README.md b/embassy/embassy-boot-nrf/README.md new file mode 100644 index 0000000..b77cf80 --- /dev/null +++ b/embassy/embassy-boot-nrf/README.md @@ -0,0 +1,30 @@ +# embassy-boot-nrf + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for nRF. + +## Features + +- Load applications with or without the softdevice. +- Configure bootloader partitions based on linker script. +- Using watchdog timer to detect application failure. + +## Working with a SoftDevice + +When a SoftDevice is present, it handles starting the bootloader and the application as needed. + +The SoftDevice architecture supports the bootloader via a configurable base address, referred to as `BOOTLOADERADDR`, in the application flash region. This address can be specified either: + +1. At the `MBR_BOOTLOADER_ADDR` location in flash memory (defined in `nrf_mbr.h`), or +2. In the `UICR.NRFFW[0]` register. + +The `UICR.NRFFW[0]` register is used only if `MBR_BOOTLOADER_ADDR` has its default value of `0xFFFFFFFF`. This bootloader relies on the latter approach. + +In the `memory.x` linker script, there is a section `.uicr_bootloader_start_address` (origin `0x10001014`, length `0x4`) that stores the `BOOTLOADERADDR` value. +Ensure that `__bootloader_start` is set to the origin address of the bootloader partition. + +When a bootloader is present, the SoftDevice forwards interrupts to it and executes the bootloader reset handler, defined in the bootloader's vector table at `BOOTLOADERADDR`. + +Once the bootloader loads the application, the SoftDevice initiates the Application Reset Handler, defined in the application’s vector table at APP_CODE_BASE hardcoded in the SoftDevice. +The active partition's origin **must** match the `APP_CODE_BASE` value hardcoded within the SoftDevice. This value can be found in the release notes for each SoftDevice version. diff --git a/embassy/embassy-boot-nrf/src/fmt.rs b/embassy/embassy-boot-nrf/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-boot-nrf/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-boot-nrf/src/lib.rs b/embassy/embassy-boot-nrf/src/lib.rs new file mode 100644 index 0000000..e5bc870 --- /dev/null +++ b/embassy/embassy-boot-nrf/src/lib.rs @@ -0,0 +1,158 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +pub use embassy_boot::{ + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootError, BootLoaderConfig, FirmwareState, + FirmwareUpdater, FirmwareUpdaterConfig, +}; +use embassy_nrf::nvmc::PAGE_SIZE; +use embassy_nrf::peripherals::WDT; +use embassy_nrf::wdt; +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; + +/// A bootloader for nRF devices. +pub struct BootLoader; + +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + if let Ok(loader) = Self::try_prepare::(config) { + loader + } else { + // Use explicit panic instead of .expect() to ensure this gets routed via defmt/etc. + // properly + panic!("Boot prepare error") + } + } + + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn try_prepare( + config: BootLoaderConfig, + ) -> Result { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + let _state = boot.prepare_boot(aligned_buf.as_mut())?; + Ok(Self) + } + + /// Boots the application without softdevice mechanisms. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + #[cfg(not(feature = "softdevice"))] + pub unsafe fn load(self, start: u32) -> ! { + let mut p = cortex_m::Peripherals::steal(); + p.SCB.invalidate_icache(); + p.SCB.vtor.write(start); + cortex_m::asm::bootload(start as *const u32) + } + + /// Boots the application assuming softdevice is present. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + #[cfg(feature = "softdevice")] + pub unsafe fn load(self, _app: u32) -> ! { + use nrf_softdevice_mbr as mbr; + const NRF_SUCCESS: u32 = 0; + + // Address of softdevice which we'll forward interrupts to + let addr = 0x1000; + let mut cmd = mbr::sd_mbr_command_t { + command: mbr::NRF_MBR_COMMANDS_SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, + params: mbr::sd_mbr_command_t__bindgen_ty_1 { + irq_forward_address_set: mbr::sd_mbr_command_irq_forward_address_set_t { address: addr }, + }, + }; + let ret = mbr::sd_mbr_command(&mut cmd); + assert_eq!(ret, NRF_SUCCESS); + + let msp = *(addr as *const u32); + let rv = *((addr + 4) as *const u32); + + trace!("msp = {=u32:x}, rv = {=u32:x}", msp, rv); + + // These instructions perform the following operations: + // + // * Modify control register to use MSP as stack pointer (clear spsel bit) + // * Synchronize instruction barrier + // * Initialize stack pointer (0x1000) + // * Set link register to not return (0xFF) + // * Jump to softdevice reset vector + core::arch::asm!( + "mrs {tmp}, CONTROL", + "bics {tmp}, {spsel}", + "msr CONTROL, {tmp}", + "isb", + "msr MSP, {msp}", + "mov lr, {new_lr}", + "bx {rv}", + // `out(reg) _` is not permitted in a `noreturn` asm! call, + // so instead use `in(reg) 0` and don't restore it afterwards. + tmp = in(reg) 0, + spsel = in(reg) 2, + new_lr = in(reg) 0xFFFFFFFFu32, + msp = in(reg) msp, + rv = in(reg) rv, + options(noreturn), + ); + } +} + +/// A flash implementation that wraps any flash and will pet a watchdog when touching flash. +pub struct WatchdogFlash { + flash: FLASH, + wdt: wdt::WatchdogHandle, +} + +impl WatchdogFlash { + /// Start a new watchdog with a given flash and WDT peripheral and a timeout + pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self { + let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { + Ok(x) => x, + Err(_) => { + // In case the watchdog is already running, just spin and let it expire, since + // we can't configure it anyway. This usually happens when we first program + // the device and the watchdog was previously active + info!("Watchdog already active with wrong config, waiting for it to timeout..."); + loop {} + } + }; + Self { flash, wdt } + } +} + +impl ErrorType for WatchdogFlash { + type Error = FLASH::Error; +} + +impl NorFlash for WatchdogFlash { + const WRITE_SIZE: usize = FLASH::WRITE_SIZE; + const ERASE_SIZE: usize = FLASH::ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.wdt.pet(); + self.flash.erase(from, to) + } + fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.wdt.pet(); + self.flash.write(offset, data) + } +} + +impl ReadNorFlash for WatchdogFlash { + const READ_SIZE: usize = FLASH::READ_SIZE; + fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.wdt.pet(); + self.flash.read(offset, data) + } + fn capacity(&self) -> usize { + self.flash.capacity() + } +} diff --git a/embassy/embassy-boot-rp/Cargo.toml b/embassy/embassy-boot-rp/Cargo.toml new file mode 100644 index 0000000..fa60304 --- /dev/null +++ b/embassy/embassy-boot-rp/Cargo.toml @@ -0,0 +1,79 @@ +[package] +edition = "2021" +name = "embassy-boot-rp" +version = "0.3.0" +description = "Bootloader lib for RP2040 chips" +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-boot-rp" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[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" +features = ["embassy-rp/rp2040"] + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4", optional = true } + +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-rp = { version = "0.2.0", path = "../embassy-rp", default-features = false } +embassy-boot = { version = "0.3.0", path = "../embassy-boot" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } + +cortex-m = { version = "0.7.6" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +cfg-if = "1.0.0" + +[features] +defmt = [ + "dep:defmt", + "embassy-boot/defmt", + "embassy-rp/defmt", +] +log = [ + "dep:log", + "embassy-boot/log", + "embassy-rp/log", +] + +[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/embassy-boot-rp/README.md b/embassy/embassy-boot-rp/README.md new file mode 100644 index 0000000..b664145 --- /dev/null +++ b/embassy/embassy-boot-rp/README.md @@ -0,0 +1,12 @@ +# 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. diff --git a/embassy/embassy-boot-rp/build.rs b/embassy/embassy-boot-rp/build.rs new file mode 100644 index 0000000..bfaee35 --- /dev/null +++ b/embassy/embassy-boot-rp/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let target = env::var("TARGET").unwrap(); + if target.starts_with("thumbv6m-") { + println!("cargo:rustc-cfg=armv6m"); + } + println!("cargo:rustc-check-cfg=cfg(armv6m)"); +} diff --git a/embassy/embassy-boot-rp/src/fmt.rs b/embassy/embassy-boot-rp/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-boot-rp/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-boot-rp/src/lib.rs b/embassy/embassy-boot-rp/src/lib.rs new file mode 100644 index 0000000..3e1731f --- /dev/null +++ b/embassy/embassy-boot-rp/src/lib.rs @@ -0,0 +1,103 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +pub use embassy_boot::{ + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootError, BootLoaderConfig, FirmwareState, + FirmwareUpdater, FirmwareUpdaterConfig, State, +}; +use embassy_rp::flash::{Blocking, Flash, ERASE_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; + +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + if let Ok(loader) = Self::try_prepare::(config) { + loader + } else { + // Use explicit panic instead of .expect() to ensure this gets routed via defmt/etc. + // properly + panic!("Boot prepare error") + } + } + + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn try_prepare( + config: BootLoaderConfig, + ) -> Result { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + let _state = boot.prepare_boot(aligned_buf.as_mut())?; + Ok(Self) + } + + /// 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(self, start: u32) -> ! { + 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); + + cortex_m::asm::bootload(start as *const u32) + } +} + +/// A flash implementation that will feed a watchdog when touching flash. +pub struct WatchdogFlash<'d, const SIZE: usize> { + flash: Flash<'d, FLASH, Blocking, 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::<_, Blocking, SIZE>::new_blocking(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.blocking_erase(from, to) + } + fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.blocking_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.blocking_read(offset, data) + } + fn capacity(&self) -> usize { + self.flash.capacity() + } +} diff --git a/embassy/embassy-boot-stm32/Cargo.toml b/embassy/embassy-boot-stm32/Cargo.toml new file mode 100644 index 0000000..1afdde7 --- /dev/null +++ b/embassy/embassy-boot-stm32/Cargo.toml @@ -0,0 +1,69 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32" +version = "0.2.0" +description = "Bootloader lib for STM32 chips" +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-boot-stm32" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-stm32-v$VERSION/embassy-boot-stm32/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot-stm32/src/" +features = ["embassy-stm32/stm32f429zi"] +target = "thumbv7em-none-eabi" + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4", optional = true } + +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false } +embassy-boot = { version = "0.3.0", path = "../embassy-boot" } +cortex-m = { version = "0.7.6" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +cfg-if = "1.0.0" + +[features] +defmt = ["dep:defmt", "embassy-boot/defmt", "embassy-stm32/defmt"] +log = ["dep:log", "embassy-boot/log", "embassy-stm32/log"] + +[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/embassy-boot-stm32/README.md b/embassy/embassy-boot-stm32/README.md new file mode 100644 index 0000000..f6dadc8 --- /dev/null +++ b/embassy/embassy-boot-stm32/README.md @@ -0,0 +1,10 @@ +# embassy-boot-stm32 + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for STM32. + +## Features + +* Configure bootloader partitions based on linker script. +* Load applications from active partition. diff --git a/embassy/embassy-boot-stm32/build.rs b/embassy/embassy-boot-stm32/build.rs new file mode 100644 index 0000000..bfaee35 --- /dev/null +++ b/embassy/embassy-boot-stm32/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let target = env::var("TARGET").unwrap(); + if target.starts_with("thumbv6m-") { + println!("cargo:rustc-cfg=armv6m"); + } + println!("cargo:rustc-check-cfg=cfg(armv6m)"); +} diff --git a/embassy/embassy-boot-stm32/src/fmt.rs b/embassy/embassy-boot-stm32/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-boot-stm32/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-boot-stm32/src/lib.rs b/embassy/embassy-boot-stm32/src/lib.rs new file mode 100644 index 0000000..387cc0c --- /dev/null +++ b/embassy/embassy-boot-stm32/src/lib.rs @@ -0,0 +1,57 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +pub use embassy_boot::{ + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootError, BootLoaderConfig, FirmwareState, + FirmwareUpdater, FirmwareUpdaterConfig, State, +}; +use embedded_storage::nor_flash::NorFlash; + +/// A bootloader for STM32 devices. +pub struct BootLoader { + /// The reported state of the bootloader after preparing for boot + pub state: State, +} + +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + if let Ok(loader) = Self::try_prepare::(config) { + loader + } else { + // Use explicit panic instead of .expect() to ensure this gets routed via defmt/etc. + // properly + panic!("Boot prepare error") + } + } + + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn try_prepare( + config: BootLoaderConfig, + ) -> Result { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + let state = boot.prepare_boot(aligned_buf.as_mut())?; + Ok(Self { state }) + } + + /// 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(self, start: u32) -> ! { + 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); + + cortex_m::asm::bootload(start as *const u32) + } +} diff --git a/embassy/embassy-boot/Cargo.toml b/embassy/embassy-boot/Cargo.toml new file mode 100644 index 0000000..b385020 --- /dev/null +++ b/embassy/embassy-boot/Cargo.toml @@ -0,0 +1,53 @@ +[package] +edition = "2021" +name = "embassy-boot" +version = "0.3.0" +description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-boot" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +digest = "0.10" +log = { version = "0.4", optional = true } +ed25519-dalek = { version = "2", default-features = false, features = ["digest"], optional = true } +embassy-embedded-hal = { version = "0.2.0", path = "../embassy-embedded-hal" } +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +salty = { version = "0.3", optional = true } +signature = { version = "2.0", default-features = false } + +[dev-dependencies] +log = "0.4" +env_logger = "0.9" +rand = "0.8" +futures = { version = "0.3", features = ["executor"] } +sha1 = "0.10.5" +critical-section = { version = "1.1.1", features = ["std"] } +ed25519-dalek = { version = "2", default-features = false, features = ["std", "rand_core", "digest"] } + +[features] +ed25519-dalek = ["dep:ed25519-dalek", "_verify"] +ed25519-salty = ["dep:salty", "_verify"] +flash-erase-zero = [] + +#Internal features +_verify = [] diff --git a/embassy/embassy-boot/README.md b/embassy/embassy-boot/README.md new file mode 100644 index 0000000..c893c83 --- /dev/null +++ b/embassy/embassy-boot/README.md @@ -0,0 +1,35 @@ +# embassy-boot + +An [Embassy](https://embassy.dev) project. + +A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks. + +The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts. + +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. + +## Overview + +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, 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. The minimum size required for this partition is 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. +* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. + +For any partition, the following preconditions are required: + +* Partitions must be aligned on the page size. +* Partitions must be a multiple of the page size. + +The linker scripts for the application and bootloader look similar, but the FLASH region must point to the BOOTLOADER partition for the bootloader, and the ACTIVE partition for the application. + +For more details on the bootloader, see [the documentation](https://embassy.dev/book/#_bootloader). + +## Hardware support + +The bootloader supports different hardware in separate crates: + +* `embassy-boot-nrf` - for the nRF microcontrollers. +* `embassy-boot-rp` - for the RP2040 microcontrollers. +* `embassy-boot-stm32` - for the STM32 microcontrollers. diff --git a/embassy/embassy-boot/src/boot_loader.rs b/embassy/embassy-boot/src/boot_loader.rs new file mode 100644 index 0000000..5bffdc5 --- /dev/null +++ b/embassy/embassy-boot/src/boot_loader.rs @@ -0,0 +1,451 @@ +use core::cell::RefCell; + +use embassy_embedded_hal::flash::partition::BlockingPartition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; + +use crate::{State, DFU_DETACH_MAGIC, REVERT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// Errors returned by bootloader +#[derive(PartialEq, Eq, Debug)] +pub enum BootError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Invalid bootloader magic + 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, +{ + fn from(error: E) -> Self { + BootError::Flash(error.kind()) + } +} + +/// Bootloader flash configuration holding the three flashes used by the bootloader +/// +/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. +/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition +/// the provided flash according to symbols defined in the linkerfile. +pub struct BootLoaderConfig { + /// Flash type used for the active partition - the partition which will be booted from. + pub active: ACTIVE, + /// Flash type used for the dfu partition - the partition which will be swapped in when requested. + pub dfu: DFU, + /// Flash type used for the state partition. + pub state: STATE, +} + +impl<'a, ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> + BootLoaderConfig< + BlockingPartition<'a, NoopRawMutex, ACTIVE>, + BlockingPartition<'a, NoopRawMutex, DFU>, + BlockingPartition<'a, NoopRawMutex, STATE>, + > +{ + /// Constructs a `BootLoaderConfig` instance from flash memory and address symbols defined in the linker file. + /// + /// This method initializes `BlockingPartition` instances for the active, DFU (Device Firmware Update), + /// and state partitions, leveraging start and end addresses specified by the linker. These partitions + /// are critical for managing firmware updates, application state, and boot operations within the bootloader. + /// + /// # Parameters + /// - `active_flash`: A reference to a mutex-protected `RefCell` for the active partition's flash interface. + /// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface. + /// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface. + /// + /// # Safety + /// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses + /// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined + /// in the memory.x file to prevent undefined behavior. + /// + /// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory + /// interfaces provided are compatible with these regions. + /// + /// # Returns + /// A `BootLoaderConfig` instance with `BlockingPartition` instances for the active, DFU, and state partitions. + /// + /// # Example + /// ```ignore + /// // Assume `active_flash`, `dfu_flash`, and `state_flash` all share the same flash memory interface. + /// let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); + /// let flash = Mutex::new(RefCell::new(layout.bank1_region)); + /// + /// let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); + /// // `config` can now be used to create a `BootLoader` instance for managing boot operations. + /// ``` + /// Working examples can be found in the bootloader examples folder. + // #[cfg(target_os = "none")] + pub fn from_linkerfile_blocking( + active_flash: &'a Mutex>, + dfu_flash: &'a Mutex>, + state_flash: &'a Mutex>, + ) -> 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 { + let start = &__bootloader_active_start as *const u32 as u32; + let end = &__bootloader_active_end as *const u32 as u32; + trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(active_flash, start, end - start) + }; + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(dfu_flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(state_flash, start, end - start) + }; + + Self { active, dfu, state } + } +} + +/// BootLoader works with any flash implementing embedded_storage. +pub struct BootLoader { + active: ACTIVE, + dfu: DFU, + /// The state partition has the following format: + /// All ranges are in multiples of WRITE_SIZE bytes. + /// | Range | Description | + /// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | + /// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | + /// | 2..2 + N | Progress index used while swapping or reverting + state: STATE, +} + +impl BootLoader { + /// Get the page size which is the "unit of operation" within the bootloader. + const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { + ACTIVE::ERASE_SIZE as u32 + } else { + DFU::ERASE_SIZE as u32 + }; + + /// Create a new instance of a bootloader with the flash partitions. + /// + /// - All partitions must be aligned with the PAGE_SIZE const generic parameter. + /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition. + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: config.active, + dfu: config.dfu, + state: config.state, + } + } + + /// Perform necessary boot preparations like swapping images. + /// + /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap + /// algorithm to work correctly. + /// + /// The provided aligned_buf argument must satisfy any alignment requirements + /// given by the partition flashes. All flash operations will use this buffer. + /// + /// ## SWAPPING + /// + /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. + /// The swap index contains the copy progress, as to allow continuation of the copy process on + /// power failure. The index counter is represented within 1 or more pages (depending on total + /// flash size), where a page X is considered swapped if index at location (`X + WRITE_SIZE`) + /// contains a zero value. This ensures that index updates can be performed atomically and + /// avoid a situation where the wrong index value is set (page write size is "atomic"). + /// + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 0 | 1 | 2 | 3 | - | + /// | DFU | 0 | 4 | 5 | 6 | X | + /// + /// The algorithm starts by copying 'backwards', and after the first step, the layout is + /// as follows: + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 1 | 1 | 2 | 6 | - | + /// | DFU | 1 | 4 | 5 | 6 | 3 | + /// + /// The next iteration performs the same steps + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 2 | 1 | 5 | 6 | - | + /// | DFU | 2 | 4 | 5 | 2 | 3 | + /// + /// And again until we're done + /// + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|------------|--------|--------|--------|--------| + /// | Active | 3 | 4 | 5 | 6 | - | + /// | DFU | 3 | 4 | 1 | 2 | 3 | + /// + /// ## REVERTING + /// + /// The reverting algorithm uses the swap index to discover that images were swapped, but that + /// the application failed to mark the boot successful. In this case, the revert algorithm will + /// run. + /// + /// The revert index is located separately from the swap index, to ensure that revert can continue + /// on power failure. + /// + /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. + /// + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|--------------|--------|--------|--------|--------| + /// | Active | 3 | 1 | 5 | 6 | - | + /// | DFU | 3 | 4 | 1 | 2 | 3 | + /// + /// + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|--------------|--------|--------|--------|--------| + /// | Active | 3 | 1 | 2 | 6 | - | + /// | DFU | 3 | 4 | 5 | 2 | 3 | + /// + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// |-----------|--------------|--------|--------|--------|--------| + /// | Active | 3 | 1 | 2 | 3 | - | + /// | DFU | 3 | 4 | 5 | 6 | 3 | + /// + pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result { + const { + core::assert!(Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32 == 0); + core::assert!(Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32 == 0); + core::assert!(Self::PAGE_SIZE % DFU::WRITE_SIZE as u32 == 0); + core::assert!(Self::PAGE_SIZE % DFU::ERASE_SIZE as u32 == 0); + } + + // Ensure we have enough progress pages to store copy progress + assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); + assert!(aligned_buf.len() >= STATE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); + + // Ensure our partitions are able to handle boot operations + assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); + + // Copy contents from partition N to active + let state = self.read_state(aligned_buf)?; + if state == State::Swap { + // + // Check if we already swapped. If we're in the swap state, this means we should revert + // since the app has failed to mark boot as successful + // + if !self.is_swapped(aligned_buf)? { + trace!("Swapping"); + self.swap(aligned_buf)?; + trace!("Swapping done"); + } else { + trace!("Reverting"); + self.revert(aligned_buf)?; + + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + + // Invalidate progress + state_word.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, state_word)?; + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32)?; + + // Set magic + state_word.fill(REVERT_MAGIC); + self.state.write(0, state_word)?; + } + } + Ok(state) + } + + fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result { + let page_count = self.active.capacity() / Self::PAGE_SIZE as usize; + let progress = self.current_progress(aligned_buf)?; + + Ok(progress >= page_count * 2) + } + + fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result { + let write_size = STATE::WRITE_SIZE as u32; + let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2; + let state_word = &mut aligned_buf[..write_size as usize]; + + self.state.read(write_size, state_word)?; + if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) { + // Progress is invalid + return Ok(max_index); + } + + for index in 0..max_index { + self.state.read((2 + index) as u32 * write_size, state_word)?; + + if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { + return Ok(index); + } + } + Ok(max_index) + } + + fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + state_word.fill(!STATE_ERASE_VALUE); + self.state + .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?; + Ok(()) + } + + fn copy_page_once_to_active( + &mut self, + progress_index: usize, + from_offset: u32, + to_offset: u32, + aligned_buf: &mut [u8], + ) -> Result<(), BootError> { + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; + + self.active.erase(to_offset, to_offset + page_size)?; + + for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { + self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.active.write(to_offset + offset_in_page as u32, aligned_buf)?; + } + + self.update_progress(progress_index, aligned_buf)?; + } + Ok(()) + } + + fn copy_page_once_to_dfu( + &mut self, + progress_index: usize, + from_offset: u32, + to_offset: u32, + aligned_buf: &mut [u8], + ) -> Result<(), BootError> { + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; + + self.dfu.erase(to_offset as u32, to_offset + page_size)?; + + for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { + self.active.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?; + } + + self.update_progress(progress_index, aligned_buf)?; + } + Ok(()) + } + + fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; + for page_num in 0..page_count { + let progress_index = (page_num * 2) as usize; + + // Copy active page to the 'next' DFU page. + let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE; + //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; + + // Copy DFU page to the active page + let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; + } + + Ok(()) + } + + fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; + for page_num in 0..page_count { + let progress_index = (page_count * 2 + page_num * 2) as usize; + + // Copy the bad active page to the DFU page + let active_from_offset = page_num * Self::PAGE_SIZE; + let dfu_to_offset = page_num * Self::PAGE_SIZE; + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; + + // Copy the DFU page back to the active page + let active_to_offset = page_num * Self::PAGE_SIZE; + let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE; + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; + } + + Ok(()) + } + + fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + self.state.read(0, state_word)?; + + if !state_word.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) { + Ok(State::DfuDetach) + } else if !state_word.iter().any(|&b| b != REVERT_MAGIC) { + Ok(State::Revert) + } else { + Ok(State::Boot) + } + } +} + +fn assert_partitions( + active: &ACTIVE, + dfu: &DFU, + state: &STATE, + page_size: u32, +) { + assert_eq!(active.capacity() as u32 % page_size, 0); + assert_eq!(dfu.capacity() as u32 % page_size, 0); + // DFU partition has to be bigger than ACTIVE partition to handle swap algorithm + assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); + assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + #[should_panic] + fn test_range_asserts() { + const ACTIVE_SIZE: usize = 4194304 - 4096; + const DFU_SIZE: usize = 4194304; + const STATE_SIZE: usize = 4096; + static ACTIVE: MemFlash = MemFlash::new(0xFF); + static DFU: MemFlash = MemFlash::new(0xFF); + static STATE: MemFlash = MemFlash::new(0xFF); + assert_partitions(&ACTIVE, &DFU, &STATE, 4096); + } +} diff --git a/embassy/embassy-boot/src/digest_adapters/ed25519_dalek.rs b/embassy/embassy-boot/src/digest_adapters/ed25519_dalek.rs new file mode 100644 index 0000000..2e4e03d --- /dev/null +++ b/embassy/embassy-boot/src/digest_adapters/ed25519_dalek.rs @@ -0,0 +1,30 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; +use ed25519_dalek::Digest; + +pub struct Sha512(ed25519_dalek::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(ed25519_dalek::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + Digest::update(&mut self.0, data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +impl HashMarker for Sha512 {} diff --git a/embassy/embassy-boot/src/digest_adapters/mod.rs b/embassy/embassy-boot/src/digest_adapters/mod.rs new file mode 100644 index 0000000..9b4b4b6 --- /dev/null +++ b/embassy/embassy-boot/src/digest_adapters/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "ed25519-dalek")] +pub(crate) mod ed25519_dalek; + +#[cfg(feature = "ed25519-salty")] +pub(crate) mod salty; diff --git a/embassy/embassy-boot/src/digest_adapters/salty.rs b/embassy/embassy-boot/src/digest_adapters/salty.rs new file mode 100644 index 0000000..2b5dcf3 --- /dev/null +++ b/embassy/embassy-boot/src/digest_adapters/salty.rs @@ -0,0 +1,29 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; + +pub struct Sha512(salty::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(salty::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + self.0.update(data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +impl HashMarker for Sha512 {} diff --git a/embassy/embassy-boot/src/firmware_updater/asynch.rs b/embassy/embassy-boot/src/firmware_updater/asynch.rs new file mode 100644 index 0000000..0dc09e1 --- /dev/null +++ b/embassy/embassy-boot/src/firmware_updater/asynch.rs @@ -0,0 +1,462 @@ +use digest::Digest; +#[cfg(target_os = "none")] +use embassy_embedded_hal::flash::partition::Partition; +#[cfg(target_os = "none")] +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embedded_storage_async::nor_flash::NorFlash; + +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to +/// 'mess up' the internal bootloader state +pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { + dfu: DFU, + state: FirmwareState<'d, STATE>, + last_erased_dfu_sector_index: Option, +} + +#[cfg(target_os = "none")] +impl<'a, DFU: NorFlash, STATE: NorFlash> + FirmwareUpdaterConfig, Partition<'a, NoopRawMutex, STATE>> +{ + /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + pub fn from_linkerfile( + dfu_flash: &'a embassy_sync::mutex::Mutex, + state_flash: &'a embassy_sync::mutex::Mutex, + ) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + Partition::new(dfu_flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + Partition::new(state_flash, start, end - start) + }; + + Self { dfu, state } + } +} + +impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self { + dfu: config.dfu, + state: FirmwareState::new(config.state, aligned), + last_erased_dfu_sector_index: None, + } + } + + /// Obtain the current state. + /// + /// 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) -> Result { + self.state.get_state().await + } + + /// 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 succeeds. + /// + /// 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. + #[cfg(feature = "_verify")] + pub async fn verify_and_mark_updated( + &mut self, + _public_key: &[u8; 32], + _signature: &[u8; 64], + _update_len: u32, + ) -> Result<(), FirmwareUpdaterError> { + assert!(_update_len <= self.dfu.capacity() as u32); + + self.state.verify_booted().await?; + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature); + + let mut chunk_buf = [0; 2]; + let mut message = [0; 64]; + self.hash::(_update_len, &mut chunk_buf, &mut message).await?; + + public_key.verify(&message, &signature).map_err(into_signature_error)?; + return self.state.mark_updated().await; + } + #[cfg(feature = "ed25519-salty")] + { + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; + let signature = Signature::try_from(_signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + let mut chunk_buf = [0; 2]; + self.hash::(_update_len, &mut chunk_buf, &mut message).await?; + + 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)?; + return self.state.mark_updated().await; + } + #[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))] + { + Err(FirmwareUpdaterError::Signature(signature::Error::new())) + } + } + + /// Verify the update in DFU with any digest. + pub async fn hash( + &mut self, + update_len: u32, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read(offset, chunk_buf).await?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + #[cfg(not(feature = "_verify"))] + pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_updated().await + } + + /// Mark to trigger USB DFU on next boot. + pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.verify_booted().await?; + self.state.mark_dfu().await + } + + /// Mark firmware boot successful and stop rollback on reset. + pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_booted().await + } + + /// Writes firmware data to the device. + /// + /// This function writes the given data to the firmware area starting at the specified offset. + /// It handles sector erasures and data writes while verifying the device is in a proper state + /// for firmware updates. The function ensures that only unerased sectors are erased before + /// writing and efficiently handles the writing process across sector boundaries and in + /// various configurations (data size, sector size, etc.). + /// + /// # Arguments + /// + /// * `offset` - The starting offset within the firmware area where data writing should begin. + /// * `data` - A slice of bytes representing the firmware data to be written. It must be a + /// multiple of NorFlash WRITE_SIZE. + /// + /// # Returns + /// + /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation. + /// + /// # Errors + /// + /// This function will return an error if: + /// + /// - The device is not in a proper state to receive firmware updates (e.g., not booted). + /// - There is a failure erasing a sector before writing. + /// - There is a failure writing data to the device. + pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { + // Make sure we are running a booted firmware to avoid reverting to a bad state. + self.state.verify_booted().await?; + + // Initialize variables to keep track of the remaining data and the current offset. + let mut remaining_data = data; + let mut offset = offset; + + // Continue writing as long as there is data left to write. + while !remaining_data.is_empty() { + // Compute the current sector and its boundaries. + let current_sector = offset / DFU::ERASE_SIZE; + let sector_start = current_sector * DFU::ERASE_SIZE; + let sector_end = sector_start + DFU::ERASE_SIZE; + // Determine if the current sector needs to be erased before writing. + let need_erase = self + .last_erased_dfu_sector_index + .map_or(true, |last_erased_sector| current_sector != last_erased_sector); + + // If the sector needs to be erased, erase it and update the last erased sector index. + if need_erase { + self.dfu.erase(sector_start as u32, sector_end as u32).await?; + self.last_erased_dfu_sector_index = Some(current_sector); + } + + // Calculate the size of the data chunk that can be written in the current iteration. + let write_size = core::cmp::min(remaining_data.len(), sector_end - offset); + // Split the data to get the current chunk to be written and the remaining data. + let (data_chunk, rest) = remaining_data.split_at(write_size); + + // Write the current data chunk. + self.dfu.write(offset as u32, data_chunk).await?; + + // Update the offset and remaining data for the next iteration. + remaining_data = rest; + offset += write_size; + } + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { + self.state.verify_booted().await?; + self.dfu.erase(0, self.dfu.capacity() as u32).await?; + + Ok(&mut self.dfu) + } +} + +/// Manages the state partition of the firmware update. +/// +/// Can be used standalone for more fine grained control, or as part of the updater. +pub struct FirmwareState<'d, STATE> { + state: STATE, + aligned: &'d mut [u8], +} + +impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { + /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self::new(config.state, aligned) + } + + /// Create a firmware state instance with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of maximum of STATE::WRITE_SIZE and STATE::READ_SIZE, + /// and follow the alignment rules for the flash being read from and written to. + pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { + assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE)); + Self { state, aligned } + } + + // Make sure we are running a booted firmware to avoid reverting to a bad state. + async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + let state = self.get_state().await?; + if state == State::Boot || state == State::DfuDetach || state == State::Revert { + Ok(()) + } else { + Err(FirmwareUpdaterError::BadState) + } + } + + /// Obtain the current state. + /// + /// 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) -> Result { + self.state.read(0, &mut self.aligned).await?; + Ok(State::from(&self.aligned[..STATE::WRITE_SIZE])) + } + + /// Mark to trigger firmware swap on next boot. + pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(SWAP_MAGIC).await + } + + /// Mark to trigger USB DFU on next boot. + pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(DFU_DETACH_MAGIC).await + } + + /// Mark firmware boot successful and stop rollback on reset. + pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(BOOT_MAGIC).await + } + + async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, &mut self.aligned).await?; + + if self.aligned[..STATE::WRITE_SIZE].iter().any(|&b| b != magic) { + // Read progress validity + if STATE::READ_SIZE <= 2 * STATE::WRITE_SIZE { + self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; + } else { + self.aligned.rotate_left(STATE::WRITE_SIZE); + } + + if self.aligned[..STATE::WRITE_SIZE] + .iter() + .any(|&b| b != STATE_ERASE_VALUE) + { + // The current progress validity marker is invalid + } else { + // Invalidate progress + self.aligned.fill(!STATE_ERASE_VALUE); + self.state + .write(STATE::WRITE_SIZE as u32, &self.aligned[..STATE::WRITE_SIZE]) + .await?; + } + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32).await?; + + // Set magic + self.aligned.fill(magic); + self.state.write(0, &self.aligned[..STATE::WRITE_SIZE]).await?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use embassy_embedded_hal::flash::partition::Partition; + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + use embassy_sync::mutex::Mutex; + use futures::executor::block_on; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + fn can_verify_sha1() { + let flash = Mutex::::new(MemFlash::<131072, 4096, 8>::default()); + let state = Partition::new(&flash, 0, 4096); + let dfu = Partition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } + + #[test] + fn can_verify_sha1_sector_bigger_than_chunk() { + let flash = Mutex::::new(MemFlash::<131072, 4096, 8>::default()); + let state = Partition::new(&flash, 0, 4096); + let dfu = Partition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(1024) { + block_on(updater.write_firmware(offset, chunk)).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } + + #[test] + fn can_verify_sha1_sector_smaller_than_chunk() { + let flash = Mutex::::new(MemFlash::<131072, 1024, 8>::default()); + let state = Partition::new(&flash, 0, 4096); + let dfu = Partition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(2048) { + block_on(updater.write_firmware(offset, chunk)).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } + + #[test] + fn can_verify_sha1_cross_sector_boundary() { + let flash = Mutex::::new(MemFlash::<131072, 1024, 8>::default()); + let state = Partition::new(&flash, 0, 4096); + let dfu = Partition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(896) { + block_on(updater.write_firmware(offset, chunk)).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } +} diff --git a/embassy/embassy-boot/src/firmware_updater/blocking.rs b/embassy/embassy-boot/src/firmware_updater/blocking.rs new file mode 100644 index 0000000..08062b0 --- /dev/null +++ b/embassy/embassy-boot/src/firmware_updater/blocking.rs @@ -0,0 +1,497 @@ +use digest::Digest; +#[cfg(target_os = "none")] +use embassy_embedded_hal::flash::partition::BlockingPartition; +#[cfg(target_os = "none")] +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embedded_storage::nor_flash::NorFlash; + +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to +/// 'mess up' the internal bootloader state +pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { + dfu: DFU, + state: BlockingFirmwareState<'d, STATE>, + last_erased_dfu_sector_index: Option, +} + +#[cfg(target_os = "none")] +impl<'a, DFU: NorFlash, STATE: NorFlash> + FirmwareUpdaterConfig, BlockingPartition<'a, NoopRawMutex, STATE>> +{ + /// Constructs a `FirmwareUpdaterConfig` instance from flash memory and address symbols defined in the linker file. + /// + /// This method initializes `BlockingPartition` instances for the DFU (Device Firmware Update), and state + /// partitions, leveraging start and end addresses specified by the linker. These partitions are critical + /// for managing firmware updates, application state, and boot operations within the bootloader. + /// + /// # Parameters + /// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface. + /// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface. + /// + /// # Safety + /// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses + /// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined + /// in the memory.x file to prevent undefined behavior. + /// + /// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory + /// interfaces provided are compatible with these regions. + /// + /// # Returns + /// A `FirmwareUpdaterConfig` instance with `BlockingPartition` instances for the DFU, and state partitions. + /// + /// # Example + /// ```ignore + /// // Assume `dfu_flash`, and `state_flash` share the same flash memory interface. + /// let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); + /// let flash = Mutex::new(RefCell::new(layout.bank1_region)); + /// + /// let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + /// // `config` can now be used to create a `FirmwareUpdater` instance for managing boot operations. + /// ``` + /// Working examples can be found in the bootloader examples folder. + pub fn from_linkerfile_blocking( + dfu_flash: &'a embassy_sync::blocking_mutex::Mutex>, + state_flash: &'a embassy_sync::blocking_mutex::Mutex>, + ) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(dfu_flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(state_flash, start, end - start) + }; + + Self { dfu, state } + } +} + +impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self { + dfu: config.dfu, + state: BlockingFirmwareState::new(config.state, aligned), + last_erased_dfu_sector_index: None, + } + } + + /// Obtain the current state. + /// + /// 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(&mut self) -> Result { + self.state.get_state() + } + + /// 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 succeeds. + /// + /// 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. + #[cfg(feature = "_verify")] + pub fn verify_and_mark_updated( + &mut self, + _public_key: &[u8; 32], + _signature: &[u8; 64], + _update_len: u32, + ) -> Result<(), FirmwareUpdaterError> { + assert!(_update_len <= self.dfu.capacity() as u32); + + self.state.verify_booted()?; + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature); + + let mut message = [0; 64]; + let mut chunk_buf = [0; 2]; + self.hash::(_update_len, &mut chunk_buf, &mut message)?; + + public_key.verify(&message, &signature).map_err(into_signature_error)?; + return self.state.mark_updated(); + } + #[cfg(feature = "ed25519-salty")] + { + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; + let signature = Signature::try_from(_signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + let mut chunk_buf = [0; 2]; + self.hash::(_update_len, &mut chunk_buf, &mut message)?; + + 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)?; + return self.state.mark_updated(); + } + #[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))] + { + Err(FirmwareUpdaterError::Signature(signature::Error::new())) + } + } + + /// Verify the update in DFU with any digest. + pub fn hash( + &mut self, + update_len: u32, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read(offset, chunk_buf)?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + #[cfg(not(feature = "_verify"))] + pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_updated() + } + + /// Mark to trigger USB DFU device on next boot. + pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.verify_booted()?; + self.state.mark_dfu() + } + + /// Mark firmware boot successful and stop rollback on reset. + pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.mark_booted() + } + + /// Writes firmware data to the device. + /// + /// This function writes the given data to the firmware area starting at the specified offset. + /// It handles sector erasures and data writes while verifying the device is in a proper state + /// for firmware updates. The function ensures that only unerased sectors are erased before + /// writing and efficiently handles the writing process across sector boundaries and in + /// various configurations (data size, sector size, etc.). + /// + /// # Arguments + /// + /// * `offset` - The starting offset within the firmware area where data writing should begin. + /// * `data` - A slice of bytes representing the firmware data to be written. It must be a + /// multiple of NorFlash WRITE_SIZE. + /// + /// # Returns + /// + /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation. + /// + /// # Errors + /// + /// This function will return an error if: + /// + /// - The device is not in a proper state to receive firmware updates (e.g., not booted). + /// - There is a failure erasing a sector before writing. + /// - There is a failure writing data to the device. + pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { + // Make sure we are running a booted firmware to avoid reverting to a bad state. + self.state.verify_booted()?; + + // Initialize variables to keep track of the remaining data and the current offset. + let mut remaining_data = data; + let mut offset = offset; + + // Continue writing as long as there is data left to write. + while !remaining_data.is_empty() { + // Compute the current sector and its boundaries. + let current_sector = offset / DFU::ERASE_SIZE; + let sector_start = current_sector * DFU::ERASE_SIZE; + let sector_end = sector_start + DFU::ERASE_SIZE; + // Determine if the current sector needs to be erased before writing. + let need_erase = self + .last_erased_dfu_sector_index + .map_or(true, |last_erased_sector| current_sector != last_erased_sector); + + // If the sector needs to be erased, erase it and update the last erased sector index. + if need_erase { + self.dfu.erase(sector_start as u32, sector_end as u32)?; + self.last_erased_dfu_sector_index = Some(current_sector); + } + + // Calculate the size of the data chunk that can be written in the current iteration. + let write_size = core::cmp::min(remaining_data.len(), sector_end - offset); + // Split the data to get the current chunk to be written and the remaining data. + let (data_chunk, rest) = remaining_data.split_at(write_size); + + // Write the current data chunk. + self.dfu.write(offset as u32, data_chunk)?; + + // Update the offset and remaining data for the next iteration. + remaining_data = rest; + offset += write_size; + } + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { + self.state.verify_booted()?; + self.dfu.erase(0, self.dfu.capacity() as u32)?; + + Ok(&mut self.dfu) + } +} + +/// Manages the state partition of the firmware update. +/// +/// Can be used standalone for more fine grained control, or as part of the updater. +pub struct BlockingFirmwareState<'d, STATE> { + state: STATE, + aligned: &'d mut [u8], +} + +impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { + /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self::new(config.state, aligned) + } + + /// Create a firmware state instance with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + Self { state, aligned } + } + + // Make sure we are running a booted firmware to avoid reverting to a bad state. + fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + let state = self.get_state()?; + if state == State::Boot || state == State::DfuDetach || state == State::Revert { + Ok(()) + } else { + Err(FirmwareUpdaterError::BadState) + } + } + + /// Obtain the current state. + /// + /// 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(&mut self) -> Result { + self.state.read(0, &mut self.aligned)?; + Ok(State::from(&self.aligned)) + } + + /// Mark to trigger firmware swap on next boot. + pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(SWAP_MAGIC) + } + + /// Mark to trigger USB DFU on next boot. + pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(DFU_DETACH_MAGIC) + } + + /// Mark firmware boot successful and stop rollback on reset. + pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(BOOT_MAGIC) + } + + fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, &mut self.aligned)?; + + if self.aligned.iter().any(|&b| b != magic) { + // Read progress validity + self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned)?; + + if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { + // The current progress validity marker is invalid + } else { + // Invalidate progress + self.aligned.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, &self.aligned)?; + } + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32)?; + + // Set magic + self.aligned.fill(magic); + self.state.write(0, &self.aligned)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use core::cell::RefCell; + + use embassy_embedded_hal::flash::partition::BlockingPartition; + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + use embassy_sync::blocking_mutex::Mutex; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + fn can_verify_sha1() { + let flash = Mutex::::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); + let state = BlockingPartition::new(&flash, 0, 4096); + let dfu = BlockingPartition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + updater.write_firmware(0, to_write.as_slice()).unwrap(); + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + updater + .hash::(update.len() as u32, &mut chunk_buf, &mut hash) + .unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } + + #[test] + fn can_verify_sha1_sector_bigger_than_chunk() { + let flash = Mutex::::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); + let state = BlockingPartition::new(&flash, 0, 4096); + let dfu = BlockingPartition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(1024) { + updater.write_firmware(offset, chunk).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + updater + .hash::(update.len() as u32, &mut chunk_buf, &mut hash) + .unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } + + #[test] + fn can_verify_sha1_sector_smaller_than_chunk() { + let flash = Mutex::::new(RefCell::new(MemFlash::<131072, 1024, 8>::default())); + let state = BlockingPartition::new(&flash, 0, 4096); + let dfu = BlockingPartition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(2048) { + updater.write_firmware(offset, chunk).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + updater + .hash::(update.len() as u32, &mut chunk_buf, &mut hash) + .unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } + + #[test] + fn can_verify_sha1_cross_sector_boundary() { + let flash = Mutex::::new(RefCell::new(MemFlash::<131072, 1024, 8>::default())); + let state = BlockingPartition::new(&flash, 0, 4096); + let dfu = BlockingPartition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(896) { + updater.write_firmware(offset, chunk).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + updater + .hash::(update.len() as u32, &mut chunk_buf, &mut hash) + .unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } +} diff --git a/embassy/embassy-boot/src/firmware_updater/mod.rs b/embassy/embassy-boot/src/firmware_updater/mod.rs new file mode 100644 index 0000000..4814786 --- /dev/null +++ b/embassy/embassy-boot/src/firmware_updater/mod.rs @@ -0,0 +1,49 @@ +mod asynch; +mod blocking; + +pub use asynch::{FirmwareState, FirmwareUpdater}; +pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater}; +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; + +/// Firmware updater flash configuration holding the two flashes used by the updater +/// +/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. +/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition +/// the provided flash according to symbols defined in the linkerfile. +pub struct FirmwareUpdaterConfig { + /// The dfu flash partition + pub dfu: DFU, + /// The state flash partition + pub state: STATE, +} + +/// Errors returned by FirmwareUpdater +#[derive(Debug)] +pub enum FirmwareUpdaterError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Signature errors. + Signature(signature::Error), + /// Bad state. + BadState, +} + +#[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(_)"), + FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"), + } + } +} + +impl From for FirmwareUpdaterError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + FirmwareUpdaterError::Flash(error.kind()) + } +} diff --git a/embassy/embassy-boot/src/fmt.rs b/embassy/embassy-boot/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-boot/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-boot/src/lib.rs b/embassy/embassy-boot/src/lib.rs new file mode 100644 index 0000000..e2c4cf7 --- /dev/null +++ b/embassy/embassy-boot/src/lib.rs @@ -0,0 +1,351 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +mod boot_loader; +mod digest_adapters; +mod firmware_updater; +#[cfg(test)] +mod mem_flash; +#[cfg(test)] +mod test_flash; + +// The expected value of the flash after an erase +// TODO: Use the value provided by NorFlash when available +#[cfg(not(feature = "flash-erase-zero"))] +pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; +#[cfg(feature = "flash-erase-zero")] +pub(crate) const STATE_ERASE_VALUE: u8 = 0x00; + +pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; +pub use firmware_updater::{ + BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig, + FirmwareUpdaterError, +}; + +pub(crate) const REVERT_MAGIC: u8 = 0xC0; +pub(crate) const BOOT_MAGIC: u8 = 0xD0; +pub(crate) const SWAP_MAGIC: u8 = 0xF0; +pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; + +/// The state of the bootloader after running prepare. +#[derive(PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum State { + /// Bootloader is ready to boot the active partition. + Boot, + /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. + Swap, + /// Bootloader has reverted the active partition with the dfu partition and will attempt boot. + Revert, + /// Application has received a request to reboot into DFU mode to apply an update. + DfuDetach, +} + +impl From for State +where + T: AsRef<[u8]>, +{ + fn from(magic: T) -> State { + let magic = magic.as_ref(); + if !magic.iter().any(|&b| b != SWAP_MAGIC) { + State::Swap + } else if !magic.iter().any(|&b| b != REVERT_MAGIC) { + State::Revert + } else if !magic.iter().any(|&b| b != DFU_DETACH_MAGIC) { + State::DfuDetach + } else { + State::Boot + } + } +} + +/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. +#[repr(align(32))] +pub struct AlignedBuffer(pub [u8; N]); + +impl AsRef<[u8]> for AlignedBuffer { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsMut<[u8]> for AlignedBuffer { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +#[cfg(test)] +mod tests { + #![allow(unused_imports)] + + use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; + use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; + use futures::executor::block_on; + + use super::*; + use crate::boot_loader::BootLoaderConfig; + use crate::firmware_updater::FirmwareUpdaterConfig; + use crate::mem_flash::MemFlash; + use crate::test_flash::{AsyncTestFlash, BlockingTestFlash}; + + /* + #[test] + fn test_bad_magic() { + let mut flash = MemFlash([0xff; 131072]); + let mut flash = SingleFlashConfig::new(&mut flash); + + let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); + + assert_eq!( + bootloader.prepare_boot(&mut flash), + Err(BootError::BadMagic) + ); + } + */ + + #[test] + fn test_boot_state() { + let flash = BlockingTestFlash::new(BootLoaderConfig { + active: MemFlash::<57344, 4096, 4>::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); + + flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); + + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + + let mut page = [0; 4096]; + assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); + } + + #[test] + #[cfg(not(feature = "_verify"))] + fn test_swap_state() { + const FIRMWARE_SIZE: usize = 57344; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); + + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; + + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); + + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated()).unwrap(); + + // Writing after marking updated is not allowed until marked as booted. + let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE)); + assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + + let mut page = [0; 1024]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + // First DFU page is untouched + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + + // Running again should cause a revert + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + // Next time we know it was reverted + assert_eq!(State::Revert, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + // Last DFU page is untouched + flash.dfu().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + + // Mark as booted + let flash = flash.into_async(); + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.mark_booted()).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); + } + + #[test] + #[cfg(not(feature = "_verify"))] + fn test_swap_state_active_page_biggest() { + const FIRMWARE_SIZE: usize = 12288; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::<12288, 4096, 8>::random(), + dfu: MemFlash::<16384, 2048, 8>::random(), + state: MemFlash::<2048, 128, 4>::random(), + }); + + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; + + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); + + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated()).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + + let mut page = [0; 4096]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + // First DFU page is untouched + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + } + + #[test] + #[cfg(not(feature = "_verify"))] + fn test_swap_state_dfu_page_biggest() { + const FIRMWARE_SIZE: usize = 12288; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::::random(), + dfu: MemFlash::<16384, 4096, 8>::random(), + state: MemFlash::<2048, 128, 4>::random(), + }); + + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; + + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); + + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated()).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + let mut page = [0; 4096]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); + // First DFU page is untouched + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + } + + #[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::{Digest, Sha512, Signature, Signer, SigningKey, VerifyingKey}; + use rand::rngs::OsRng; + + let mut csprng = OsRng {}; + let keypair = SigningKey::generate(&mut csprng); + + 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); + + let public_key = keypair.verifying_key(); + + // Setup flash + let flash = BlockingTestFlash::new(BootLoaderConfig { + active: MemFlash::<0, 0, 0>::default(), + dfu: MemFlash::<4096, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); + + let firmware_len = firmware.len(); + + let mut write_buf = [0; 4096]; + write_buf[0..firmware_len].copy_from_slice(firmware); + flash.dfu().write(0, &write_buf).unwrap(); + + // On with the test + let flash = flash.into_async(); + let mut aligned = [0; 4]; + let mut updater = FirmwareUpdater::new( + FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }, + &mut aligned, + ); + + assert!(block_on(updater.verify_and_mark_updated( + &public_key.to_bytes(), + &signature.to_bytes(), + firmware_len as u32, + )) + .is_ok()); + } +} diff --git a/embassy/embassy-boot/src/mem_flash.rs b/embassy/embassy-boot/src/mem_flash.rs new file mode 100644 index 0000000..40f352c --- /dev/null +++ b/embassy/embassy-boot/src/mem_flash.rs @@ -0,0 +1,170 @@ +#![allow(unused)] + +use core::ops::{Bound, Range, RangeBounds}; + +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +pub struct MemFlash { + pub mem: [u8; SIZE], + pub pending_write_successes: Option, +} + +#[derive(Debug)] +pub struct MemFlashError; + +impl MemFlash { + pub const fn new(fill: u8) -> Self { + Self { + mem: [fill; SIZE], + pending_write_successes: None, + } + } + + #[cfg(test)] + pub fn random() -> Self { + let mut mem = [0; SIZE]; + for byte in mem.iter_mut() { + *byte = rand::random::(); + } + Self { + mem, + pending_write_successes: None, + } + } + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> { + let len = bytes.len(); + bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); + Ok(()) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + let offset = offset as usize; + assert!(bytes.len() % WRITE_SIZE == 0); + assert!(offset % WRITE_SIZE == 0); + assert!(offset + bytes.len() <= SIZE); + + if let Some(pending_successes) = self.pending_write_successes { + if pending_successes > 0 { + self.pending_write_successes = Some(pending_successes - 1); + } else { + return Err(MemFlashError); + } + } + + for ((offset, mem_byte), new_byte) in self + .mem + .iter_mut() + .enumerate() + .skip(offset) + .take(bytes.len()) + .zip(bytes) + { + assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); + *mem_byte = *new_byte; + } + + Ok(()) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> { + let from = from as usize; + let to = to as usize; + assert!(from % ERASE_SIZE == 0); + assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); + for i in from..to { + self.mem[i] = 0xFF; + } + Ok(()) + } + + pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + let offset = offset as usize; + assert!(bytes.len() % WRITE_SIZE == 0); + assert!(offset % WRITE_SIZE == 0); + assert!(offset + bytes.len() <= SIZE); + + self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); + + Ok(()) + } +} + +impl Default + for MemFlash +{ + fn default() -> Self { + Self::new(0xFF) + } +} + +impl ErrorType + for MemFlash +{ + type Error = MemFlashError; +} + +impl NorFlashError for MemFlashError { + fn kind(&self) -> NorFlashErrorKind { + NorFlashErrorKind::Other + } +} + +impl ReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl NorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) + } +} + +impl AsyncReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl AsyncNorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) + } +} diff --git a/embassy/embassy-boot/src/test_flash/asynch.rs b/embassy/embassy-boot/src/test_flash/asynch.rs new file mode 100644 index 0000000..c67f249 --- /dev/null +++ b/embassy/embassy-boot/src/test_flash/asynch.rs @@ -0,0 +1,64 @@ +use embassy_embedded_hal::flash::partition::Partition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::mutex::Mutex; +use embedded_storage_async::nor_flash::NorFlash; + +use crate::BootLoaderConfig; + +pub struct AsyncTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + active: Mutex, + dfu: Mutex, + state: Mutex, +} + +impl AsyncTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: Mutex::new(config.active), + dfu: Mutex::new(config.dfu), + state: Mutex::new(config.state), + } + } + + pub fn active(&self) -> Partition { + Self::create_partition(&self.active) + } + + pub fn dfu(&self) -> Partition { + Self::create_partition(&self.dfu) + } + + pub fn state(&self) -> Partition { + Self::create_partition(&self.state) + } + + fn create_partition(mutex: &Mutex) -> Partition { + Partition::new(mutex, 0, unwrap!(mutex.try_lock()).capacity() as u32) + } +} + +impl AsyncTestFlash +where + ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash, + DFU: NorFlash + embedded_storage::nor_flash::NorFlash, + STATE: NorFlash + embedded_storage::nor_flash::NorFlash, +{ + pub fn into_blocking(self) -> super::BlockingTestFlash { + let config = BootLoaderConfig { + active: self.active.into_inner(), + dfu: self.dfu.into_inner(), + state: self.state.into_inner(), + }; + super::BlockingTestFlash::new(config) + } +} diff --git a/embassy/embassy-boot/src/test_flash/blocking.rs b/embassy/embassy-boot/src/test_flash/blocking.rs new file mode 100644 index 0000000..5ec476c --- /dev/null +++ b/embassy/embassy-boot/src/test_flash/blocking.rs @@ -0,0 +1,68 @@ +use core::cell::RefCell; + +use embassy_embedded_hal::flash::partition::BlockingPartition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::NorFlash; + +use crate::BootLoaderConfig; + +pub struct BlockingTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + active: Mutex>, + dfu: Mutex>, + state: Mutex>, +} + +impl BlockingTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: Mutex::new(RefCell::new(config.active)), + dfu: Mutex::new(RefCell::new(config.dfu)), + state: Mutex::new(RefCell::new(config.state)), + } + } + + pub fn active(&self) -> BlockingPartition { + Self::create_partition(&self.active) + } + + pub fn dfu(&self) -> BlockingPartition { + Self::create_partition(&self.dfu) + } + + pub fn state(&self) -> BlockingPartition { + Self::create_partition(&self.state) + } + + pub fn create_partition( + mutex: &Mutex>, + ) -> BlockingPartition { + BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32) + } +} + +impl BlockingTestFlash +where + ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, + DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash, + STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, +{ + pub fn into_async(self) -> super::AsyncTestFlash { + let config = BootLoaderConfig { + active: self.active.into_inner().into_inner(), + dfu: self.dfu.into_inner().into_inner(), + state: self.state.into_inner().into_inner(), + }; + super::AsyncTestFlash::new(config) + } +} diff --git a/embassy/embassy-boot/src/test_flash/mod.rs b/embassy/embassy-boot/src/test_flash/mod.rs new file mode 100644 index 0000000..79b15a0 --- /dev/null +++ b/embassy/embassy-boot/src/test_flash/mod.rs @@ -0,0 +1,5 @@ +mod asynch; +mod blocking; + +pub(crate) use asynch::AsyncTestFlash; +pub(crate) use blocking::BlockingTestFlash; diff --git a/embassy/embassy-embedded-hal/CHANGELOG.md b/embassy/embassy-embedded-hal/CHANGELOG.md new file mode 100644 index 0000000..f8e2721 --- /dev/null +++ b/embassy/embassy-embedded-hal/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog for embassy-embedded-hal + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-08-05 + +- Add Clone derive to flash Partition in embassy-embedded-hal +- Add support for all word sizes to async shared spi +- Add Copy and 'static constraint to Word type in SPI structs +- Improve flexibility by introducing SPI word size as a generic parameter +- Allow changing Spi/I2cDeviceWithConfig's config at runtime +- Impl `MultiwriteNorFlash` for `BlockingAsync` + +## 0.1.0 - 2024-01-10 + +- First release diff --git a/embassy/embassy-embedded-hal/Cargo.toml b/embassy/embassy-embedded-hal/Cargo.toml new file mode 100644 index 0000000..b7728c4 --- /dev/null +++ b/embassy/embassy-embedded-hal/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "embassy-embedded-hal" +version = "0.2.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy." +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-embedded-hal" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-embedded-hal-v$VERSION/embassy-embedded-hal/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-embedded-hal/src/" +features = ["std"] +target = "x86_64-unknown-linux-gnu" + +[package.metadata.docs.rs] +features = ["std"] + +[features] +std = [] +time = ["dep:embassy-time"] +default = ["time"] + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ + "unproven", +] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +nb = "1.0.0" + +defmt = { version = "0.3", optional = true } + +[dev-dependencies] +critical-section = { version = "1.1.1", features = ["std"] } +futures-test = "0.3.17" diff --git a/embassy/embassy-embedded-hal/README.md b/embassy/embassy-embedded-hal/README.md new file mode 100644 index 0000000..69581c7 --- /dev/null +++ b/embassy/embassy-embedded-hal/README.md @@ -0,0 +1,12 @@ +# embassy-embedded-hal + +Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy. + +- Shared SPI and I2C buses, both blocking and async, with a `SetConfig` trait allowing changing bus configuration (e.g. frequency) between devices on the same bus. +- Async utilities + - Adapters to convert from blocking to (fake) async. + - Adapters to insert yields on trait operations. +- Flash utilities + - Split a flash memory into smaller partitions. + - Concatenate flash memories together. + - Simulated in-memory flash. diff --git a/embassy/embassy-embedded-hal/src/adapter/blocking_async.rs b/embassy/embassy-embedded-hal/src/adapter/blocking_async.rs new file mode 100644 index 0000000..bafc315 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/adapter/blocking_async.rs @@ -0,0 +1,149 @@ +use embedded_hal_02::blocking; + +/// Wrapper that implements async traits using blocking implementations. +/// +/// This allows driver writers to depend on the async traits while still supporting embedded-hal peripheral implementations. +/// +/// BlockingAsync will implement any async trait that maps to embedded-hal traits implemented for the wrapped driver. +/// +/// Driver users are then free to choose which implementation that is available to them. +pub struct BlockingAsync { + wrapped: T, +} + +impl BlockingAsync { + /// Create a new instance of a wrapper for a given peripheral. + pub fn new(wrapped: T) -> Self { + Self { wrapped } + } +} + +// +// I2C implementations +// +impl embedded_hal_1::i2c::ErrorType for BlockingAsync +where + E: embedded_hal_1::i2c::Error + 'static, + T: blocking::i2c::WriteRead + blocking::i2c::Read + blocking::i2c::Write, +{ + type Error = E; +} + +impl embedded_hal_async::i2c::I2c for BlockingAsync +where + E: embedded_hal_1::i2c::Error + 'static, + T: blocking::i2c::WriteRead + blocking::i2c::Read + blocking::i2c::Write, +{ + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(address, read) + } + + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(address, write) + } + + async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.write_read(address, write, read) + } + + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + let _ = address; + let _ = operations; + todo!() + } +} + +// +// SPI implementatinos +// + +impl embedded_hal_async::spi::ErrorType for BlockingAsync +where + E: embedded_hal_1::spi::Error, + T: blocking::spi::Transfer + blocking::spi::Write, +{ + type Error = E; +} + +impl embedded_hal_async::spi::SpiBus for BlockingAsync +where + E: embedded_hal_1::spi::Error + 'static, + T: blocking::spi::Transfer + blocking::spi::Write, +{ + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(data)?; + Ok(()) + } + + async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.transfer(data)?; + Ok(()) + } + + async fn transfer(&mut self, read: &mut [u8], write: &[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(()) + } + + async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.transfer(data)?; + Ok(()) + } +} + +/// NOR flash wrapper +use embedded_storage::nor_flash::{ErrorType, MultiwriteNorFlash, NorFlash, ReadNorFlash}; +use embedded_storage_async::nor_flash::{ + MultiwriteNorFlash as AsyncMultiwriteNorFlash, NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash, +}; + +impl ErrorType for BlockingAsync +where + T: ErrorType, +{ + type Error = T::Error; +} + +impl AsyncNorFlash for BlockingAsync +where + T: NorFlash, +{ + const WRITE_SIZE: usize = ::WRITE_SIZE; + const ERASE_SIZE: usize = ::ERASE_SIZE; + + async fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(offset, data) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.wrapped.erase(from, to) + } +} + +impl AsyncReadNorFlash for BlockingAsync +where + T: ReadNorFlash, +{ + const READ_SIZE: usize = ::READ_SIZE; + async fn read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(address, data) + } + + fn capacity(&self) -> usize { + self.wrapped.capacity() + } +} + +impl AsyncMultiwriteNorFlash for BlockingAsync where T: MultiwriteNorFlash {} diff --git a/embassy/embassy-embedded-hal/src/adapter/mod.rs b/embassy/embassy-embedded-hal/src/adapter/mod.rs new file mode 100644 index 0000000..28da561 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/adapter/mod.rs @@ -0,0 +1,7 @@ +//! Adapters between embedded-hal traits. + +mod blocking_async; +mod yielding_async; + +pub use blocking_async::BlockingAsync; +pub use yielding_async::YieldingAsync; diff --git a/embassy/embassy-embedded-hal/src/adapter/yielding_async.rs b/embassy/embassy-embedded-hal/src/adapter/yielding_async.rs new file mode 100644 index 0000000..fe9c9c3 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/adapter/yielding_async.rs @@ -0,0 +1,169 @@ +use embassy_futures::yield_now; + +/// Wrapper that yields for each operation to the wrapped instance +/// +/// This can be used in combination with BlockingAsync to enforce yields +/// between long running blocking operations. +pub struct YieldingAsync { + wrapped: T, +} + +impl YieldingAsync { + /// Create a new instance of a wrapper that yields after each operation. + pub fn new(wrapped: T) -> Self { + Self { wrapped } + } +} + +// +// I2C implementations +// +impl embedded_hal_1::i2c::ErrorType for YieldingAsync +where + T: embedded_hal_1::i2c::ErrorType, +{ + type Error = T::Error; +} + +impl embedded_hal_async::i2c::I2c for YieldingAsync +where + T: embedded_hal_async::i2c::I2c, +{ + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(address, read).await?; + yield_now().await; + Ok(()) + } + + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(address, write).await?; + yield_now().await; + Ok(()) + } + + async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.write_read(address, write, read).await?; + yield_now().await; + Ok(()) + } + + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + self.wrapped.transaction(address, operations).await?; + yield_now().await; + Ok(()) + } +} + +// +// SPI implementations +// + +impl embedded_hal_async::spi::ErrorType for YieldingAsync +where + T: embedded_hal_async::spi::ErrorType, +{ + type Error = T::Error; +} + +impl embedded_hal_async::spi::SpiBus for YieldingAsync +where + T: embedded_hal_async::spi::SpiBus, +{ + async fn flush(&mut self) -> Result<(), Self::Error> { + self.wrapped.flush().await?; + yield_now().await; + Ok(()) + } + + async fn write(&mut self, data: &[Word]) -> Result<(), Self::Error> { + self.wrapped.write(data).await?; + yield_now().await; + Ok(()) + } + + async fn read(&mut self, data: &mut [Word]) -> Result<(), Self::Error> { + self.wrapped.read(data).await?; + yield_now().await; + Ok(()) + } + + async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.wrapped.transfer(read, write).await?; + yield_now().await; + Ok(()) + } + + async fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + self.wrapped.transfer_in_place(words).await?; + yield_now().await; + Ok(()) + } +} + +/// +/// NOR flash implementations +/// +impl embedded_storage::nor_flash::ErrorType for YieldingAsync { + type Error = T::Error; +} + +impl embedded_storage_async::nor_flash::ReadNorFlash + for YieldingAsync +{ + const READ_SIZE: usize = T::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(offset, bytes).await?; + Ok(()) + } + + fn capacity(&self) -> usize { + self.wrapped.capacity() + } +} + +impl embedded_storage_async::nor_flash::NorFlash for YieldingAsync { + const WRITE_SIZE: usize = T::WRITE_SIZE; + const ERASE_SIZE: usize = T::ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(offset, bytes).await?; + yield_now().await; + Ok(()) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + // Yield between each actual erase + for from in (from..to).step_by(T::ERASE_SIZE) { + let to = core::cmp::min(from + T::ERASE_SIZE as u32, to); + self.wrapped.erase(from, to).await?; + yield_now().await; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use embedded_storage_async::nor_flash::NorFlash; + + use super::*; + use crate::flash::mem_flash::MemFlash; + + #[futures_test::test] + async fn can_erase() { + let flash = MemFlash::<1024, 128, 4>::new(0x00); + let mut yielding = YieldingAsync::new(flash); + + yielding.erase(0, 256).await.unwrap(); + + let flash = yielding.wrapped; + assert_eq!(2, flash.erases.len()); + assert_eq!((0, 128), flash.erases[0]); + assert_eq!((128, 256), flash.erases[1]); + } +} diff --git a/embassy/embassy-embedded-hal/src/flash/concat_flash.rs b/embassy/embassy-embedded-hal/src/flash/concat_flash.rs new file mode 100644 index 0000000..499941d --- /dev/null +++ b/embassy/embassy-embedded-hal/src/flash/concat_flash.rs @@ -0,0 +1,225 @@ +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, ReadNorFlash}; +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +/// Convenience helper for concatenating two consecutive flashes into one. +/// This is especially useful if used with "flash regions", where one may +/// want to concatenate multiple regions into one larger region. +pub struct ConcatFlash(First, Second); + +impl ConcatFlash { + /// Create a new flash that concatenates two consecutive flashes. + pub fn new(first: First, second: Second) -> Self { + Self(first, second) + } +} + +const fn get_read_size(first_read_size: usize, second_read_size: usize) -> usize { + if first_read_size != second_read_size { + panic!("The read size for the concatenated flashes must be the same"); + } + first_read_size +} + +const fn get_write_size(first_write_size: usize, second_write_size: usize) -> usize { + if first_write_size != second_write_size { + panic!("The write size for the concatenated flashes must be the same"); + } + first_write_size +} + +const fn get_max_erase_size(first_erase_size: usize, second_erase_size: usize) -> usize { + let max_erase_size = if first_erase_size > second_erase_size { + first_erase_size + } else { + second_erase_size + }; + if max_erase_size % first_erase_size != 0 || max_erase_size % second_erase_size != 0 { + panic!("The erase sizes for the concatenated flashes must have have a gcd equal to the max erase size"); + } + max_erase_size +} + +impl ErrorType for ConcatFlash +where + First: ErrorType, + Second: ErrorType, + E: NorFlashError, +{ + type Error = E; +} + +impl ReadNorFlash for ConcatFlash +where + First: ReadNorFlash, + Second: ReadNorFlash, + E: NorFlashError, +{ + const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE); + + fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.read(offset, &mut bytes[..len])?; + offset += len as u32; + bytes = &mut bytes[len..]; + } + + if !bytes.is_empty() { + self.1.read(offset - self.0.capacity() as u32, bytes)?; + } + + Ok(()) + } + + fn capacity(&self) -> usize { + self.0.capacity() + self.1.capacity() + } +} + +impl NorFlash for ConcatFlash +where + First: NorFlash, + Second: NorFlash, + E: NorFlashError, +{ + const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE); + const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE); + + fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.write(offset, &bytes[..len])?; + offset += len as u32; + bytes = &bytes[len..]; + } + + if !bytes.is_empty() { + self.1.write(offset - self.0.capacity() as u32, bytes)?; + } + + Ok(()) + } + + fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> { + if from < self.0.capacity() as u32 { + let to = core::cmp::min(self.0.capacity() as u32, to); + self.0.erase(from, to)?; + from = self.0.capacity() as u32; + } + + if from < to { + self.1 + .erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)?; + } + + Ok(()) + } +} + +impl AsyncReadNorFlash for ConcatFlash +where + First: AsyncReadNorFlash, + Second: AsyncReadNorFlash, + E: NorFlashError, +{ + const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE); + + async fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.read(offset, &mut bytes[..len]).await?; + offset += len as u32; + bytes = &mut bytes[len..]; + } + + if !bytes.is_empty() { + self.1.read(offset - self.0.capacity() as u32, bytes).await?; + } + + Ok(()) + } + + fn capacity(&self) -> usize { + self.0.capacity() + self.1.capacity() + } +} + +impl AsyncNorFlash for ConcatFlash +where + First: AsyncNorFlash, + Second: AsyncNorFlash, + E: NorFlashError, +{ + const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE); + const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE); + + async fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.write(offset, &bytes[..len]).await?; + offset += len as u32; + bytes = &bytes[len..]; + } + + if !bytes.is_empty() { + self.1.write(offset - self.0.capacity() as u32, bytes).await?; + } + + Ok(()) + } + + async fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> { + if from < self.0.capacity() as u32 { + let to = core::cmp::min(self.0.capacity() as u32, to); + self.0.erase(from, to).await?; + from = self.0.capacity() as u32; + } + + if from < to { + self.1 + .erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32) + .await?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; + + use super::ConcatFlash; + use crate::flash::mem_flash::MemFlash; + + #[test] + fn can_write_and_read_across_flashes() { + let first = MemFlash::<64, 16, 4>::default(); + let second = MemFlash::<64, 64, 4>::default(); + let mut f = ConcatFlash::new(first, second); + + f.write(60, &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]).unwrap(); + + assert_eq!(&[0x11, 0x22, 0x33, 0x44], &f.0.mem[60..]); + assert_eq!(&[0x55, 0x66, 0x77, 0x88], &f.1.mem[0..4]); + + let mut read_buf = [0; 8]; + f.read(60, &mut read_buf).unwrap(); + + assert_eq!(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], &read_buf); + } + + #[test] + fn can_erase_across_flashes() { + let first = MemFlash::<128, 16, 4>::new(0x00); + let second = MemFlash::<128, 64, 4>::new(0x00); + let mut f = ConcatFlash::new(first, second); + + f.erase(64, 192).unwrap(); + + assert_eq!(&[0x00; 64], &f.0.mem[0..64]); + assert_eq!(&[0xff; 64], &f.0.mem[64..128]); + assert_eq!(&[0xff; 64], &f.1.mem[0..64]); + assert_eq!(&[0x00; 64], &f.1.mem[64..128]); + } +} diff --git a/embassy/embassy-embedded-hal/src/flash/mem_flash.rs b/embassy/embassy-embedded-hal/src/flash/mem_flash.rs new file mode 100644 index 0000000..d24c618 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/flash/mem_flash.rs @@ -0,0 +1,125 @@ +use alloc::vec::Vec; + +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +extern crate alloc; + +pub(crate) struct MemFlash { + pub mem: [u8; SIZE], + pub writes: Vec<(u32, usize)>, + pub erases: Vec<(u32, u32)>, +} + +impl MemFlash { + #[allow(unused)] + pub const fn new(fill: u8) -> Self { + Self { + mem: [fill; SIZE], + writes: Vec::new(), + erases: Vec::new(), + } + } + + fn read(&mut self, offset: u32, bytes: &mut [u8]) { + let len = bytes.len(); + bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); + } + + fn write(&mut self, offset: u32, bytes: &[u8]) { + self.writes.push((offset, bytes.len())); + let offset = offset as usize; + assert_eq!(0, bytes.len() % WRITE_SIZE); + assert_eq!(0, offset % WRITE_SIZE); + assert!(offset + bytes.len() <= SIZE); + + self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); + } + + fn erase(&mut self, from: u32, to: u32) { + self.erases.push((from, to)); + let from = from as usize; + let to = to as usize; + assert_eq!(0, from % ERASE_SIZE); + assert_eq!(0, to % ERASE_SIZE); + self.mem[from..to].fill(0xff); + } +} + +impl Default + for MemFlash +{ + fn default() -> Self { + Self::new(0xff) + } +} + +impl ErrorType + for MemFlash +{ + type Error = core::convert::Infallible; +} + +impl ReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes); + Ok(()) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl NorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes); + Ok(()) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to); + Ok(()) + } +} + +impl AsyncReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes); + Ok(()) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl AsyncNorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes); + Ok(()) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to); + Ok(()) + } +} diff --git a/embassy/embassy-embedded-hal/src/flash/mod.rs b/embassy/embassy-embedded-hal/src/flash/mod.rs new file mode 100644 index 0000000..7e4ef29 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/flash/mod.rs @@ -0,0 +1,8 @@ +//! Utilities related to flash. + +mod concat_flash; +#[cfg(test)] +pub(crate) mod mem_flash; +pub mod partition; + +pub use concat_flash::ConcatFlash; diff --git a/embassy/embassy-embedded-hal/src/flash/partition/asynch.rs b/embassy/embassy-embedded-hal/src/flash/partition/asynch.rs new file mode 100644 index 0000000..1b0c912 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/flash/partition/asynch.rs @@ -0,0 +1,149 @@ +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use embedded_storage::nor_flash::ErrorType; +use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash}; + +use super::Error; + +/// A logical partition of an underlying shared flash +/// +/// A partition holds an offset and a size of the flash, +/// and is restricted to operate with that range. +/// There is no guarantee that muliple partitions on the same flash +/// operate on mutually exclusive ranges - such a separation is up to +/// the user to guarantee. +pub struct Partition<'a, M: RawMutex, T: NorFlash> { + flash: &'a Mutex, + offset: u32, + size: u32, +} + +impl<'a, M: RawMutex, T: NorFlash> Clone for Partition<'a, M, T> { + fn clone(&self) -> Self { + Self { + flash: self.flash, + offset: self.offset, + size: self.size, + } + } +} + +impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> { + /// Create a new partition + pub const fn new(flash: &'a Mutex, offset: u32, size: u32) -> Self { + if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0 + { + panic!("Partition offset must be a multiple of read, write and erase size"); + } + if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 { + panic!("Partition size must be a multiple of read, write and erase size"); + } + Self { flash, offset, size } + } + + /// Get the partition offset within the flash + pub const fn offset(&self) -> u32 { + self.offset + } + + /// Get the partition size + pub const fn size(&self) -> u32 { + self.size + } +} + +impl ErrorType for Partition<'_, M, T> { + type Error = Error; +} + +impl ReadNorFlash for Partition<'_, M, T> { + const READ_SIZE: usize = T::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash.read(self.offset + offset, bytes).await.map_err(Error::Flash) + } + + fn capacity(&self) -> usize { + self.size as usize + } +} + +impl NorFlash for Partition<'_, M, T> { + const WRITE_SIZE: usize = T::WRITE_SIZE; + const ERASE_SIZE: usize = T::ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash.write(self.offset + offset, bytes).await.map_err(Error::Flash) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + if to > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash + .erase(self.offset + from, self.offset + to) + .await + .map_err(Error::Flash) + } +} + +#[cfg(test)] +mod tests { + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + + use super::*; + use crate::flash::mem_flash::MemFlash; + + #[futures_test::test] + async fn can_read() { + let mut flash = MemFlash::<1024, 128, 4>::default(); + flash.mem[132..132 + 8].fill(0xAA); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 128, 256); + + let mut read_buf = [0; 8]; + partition.read(4, &mut read_buf).await.unwrap(); + + assert!(read_buf.iter().position(|&x| x != 0xAA).is_none()); + } + + #[futures_test::test] + async fn can_write() { + let flash = MemFlash::<1024, 128, 4>::default(); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 128, 256); + + let write_buf = [0xAA; 8]; + partition.write(4, &write_buf).await.unwrap(); + + let flash = flash.try_lock().unwrap(); + assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none()); + } + + #[futures_test::test] + async fn can_erase() { + let flash = MemFlash::<1024, 128, 4>::new(0x00); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 128, 256); + + partition.erase(0, 128).await.unwrap(); + + let flash = flash.try_lock().unwrap(); + assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none()); + } +} diff --git a/embassy/embassy-embedded-hal/src/flash/partition/blocking.rs b/embassy/embassy-embedded-hal/src/flash/partition/blocking.rs new file mode 100644 index 0000000..a68df78 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/flash/partition/blocking.rs @@ -0,0 +1,159 @@ +use core::cell::RefCell; + +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; + +use super::Error; + +/// A logical partition of an underlying shared flash +/// +/// A partition holds an offset and a size of the flash, +/// and is restricted to operate with that range. +/// There is no guarantee that muliple partitions on the same flash +/// operate on mutually exclusive ranges - such a separation is up to +/// the user to guarantee. +pub struct BlockingPartition<'a, M: RawMutex, T: NorFlash> { + flash: &'a Mutex>, + offset: u32, + size: u32, +} + +impl<'a, M: RawMutex, T: NorFlash> Clone for BlockingPartition<'a, M, T> { + fn clone(&self) -> Self { + Self { + flash: self.flash, + offset: self.offset, + size: self.size, + } + } +} + +impl<'a, M: RawMutex, T: NorFlash> BlockingPartition<'a, M, T> { + /// Create a new partition + pub const fn new(flash: &'a Mutex>, offset: u32, size: u32) -> Self { + if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0 + { + panic!("Partition offset must be a multiple of read, write and erase size"); + } + if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 { + panic!("Partition size must be a multiple of read, write and erase size"); + } + Self { flash, offset, size } + } + + /// Get the partition offset within the flash + pub const fn offset(&self) -> u32 { + self.offset + } + + /// Get the partition size + pub const fn size(&self) -> u32 { + self.size + } +} + +impl ErrorType for BlockingPartition<'_, M, T> { + type Error = Error; +} + +impl ReadNorFlash for BlockingPartition<'_, M, T> { + const READ_SIZE: usize = T::READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + self.flash.lock(|flash| { + flash + .borrow_mut() + .read(self.offset + offset, bytes) + .map_err(Error::Flash) + }) + } + + fn capacity(&self) -> usize { + self.size as usize + } +} + +impl NorFlash for BlockingPartition<'_, M, T> { + const WRITE_SIZE: usize = T::WRITE_SIZE; + const ERASE_SIZE: usize = T::ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + self.flash.lock(|flash| { + flash + .borrow_mut() + .write(self.offset + offset, bytes) + .map_err(Error::Flash) + }) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + if to > self.size { + return Err(Error::OutOfBounds); + } + + self.flash.lock(|flash| { + flash + .borrow_mut() + .erase(self.offset + from, self.offset + to) + .map_err(Error::Flash) + }) + } +} + +#[cfg(test)] +mod tests { + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + + use super::*; + use crate::flash::mem_flash::MemFlash; + + #[test] + fn can_read() { + let mut flash = MemFlash::<1024, 128, 4>::default(); + flash.mem[132..132 + 8].fill(0xAA); + + let flash = Mutex::::new(RefCell::new(flash)); + let mut partition = BlockingPartition::new(&flash, 128, 256); + + let mut read_buf = [0; 8]; + partition.read(4, &mut read_buf).unwrap(); + + assert!(read_buf.iter().position(|&x| x != 0xAA).is_none()); + } + + #[test] + fn can_write() { + let flash = MemFlash::<1024, 128, 4>::default(); + + let flash = Mutex::::new(RefCell::new(flash)); + let mut partition = BlockingPartition::new(&flash, 128, 256); + + let write_buf = [0xAA; 8]; + partition.write(4, &write_buf).unwrap(); + + let flash = flash.into_inner().take(); + assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none()); + } + + #[test] + fn can_erase() { + let flash = MemFlash::<1024, 128, 4>::new(0x00); + + let flash = Mutex::::new(RefCell::new(flash)); + let mut partition = BlockingPartition::new(&flash, 128, 256); + + partition.erase(0, 128).unwrap(); + + let flash = flash.into_inner().take(); + assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none()); + } +} diff --git a/embassy/embassy-embedded-hal/src/flash/partition/mod.rs b/embassy/embassy-embedded-hal/src/flash/partition/mod.rs new file mode 100644 index 0000000..6177ed9 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/flash/partition/mod.rs @@ -0,0 +1,28 @@ +//! Flash Partition utilities + +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; + +mod asynch; +mod blocking; + +pub use asynch::Partition; +pub use blocking::BlockingPartition; + +/// Partition error +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The requested flash area is outside the partition + OutOfBounds, + /// Underlying flash error + Flash(T), +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + match self { + Error::OutOfBounds => NorFlashErrorKind::OutOfBounds, + Error::Flash(f) => f.kind(), + } + } +} diff --git a/embassy/embassy-embedded-hal/src/lib.rs b/embassy/embassy-embedded-hal/src/lib.rs new file mode 100644 index 0000000..99a2d93 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/lib.rs @@ -0,0 +1,39 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(async_fn_in_trait)] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +pub mod adapter; +pub mod flash; +pub mod shared_bus; + +/// Set the configuration of a peripheral driver. +/// +/// This trait is intended to be implemented by peripheral drivers such as SPI +/// and I2C. It allows changing the configuration at runtime. +/// +/// The exact type of the "configuration" is defined by each individual driver, since different +/// drivers support different options. Therefore it is defined as an associated type. +/// +/// For example, it is used by [`SpiDeviceWithConfig`](crate::shared_bus::asynch::spi::SpiDeviceWithConfig) and +/// [`I2cDeviceWithConfig`](crate::shared_bus::asynch::i2c::I2cDeviceWithConfig) to allow different +/// devices on the same bus to use different communication settings. +pub trait SetConfig { + /// The configuration type used by this driver. + type Config; + + /// The error type that can occur if `set_config` fails. + type ConfigError; + + /// Set the configuration of the driver. + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError>; +} + +/// Get the configuration of a peripheral driver. +pub trait GetConfig { + /// The configuration type used by this driver. + type Config; + + /// Get the configuration of the driver. + fn get_config(&self) -> Self::Config; +} diff --git a/embassy/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs b/embassy/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs new file mode 100644 index 0000000..71ce09d --- /dev/null +++ b/embassy/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs @@ -0,0 +1,166 @@ +//! Asynchronous shared I2C bus +//! +//! # Example (nrf52) +//! +//! ```rust,ignore +//! use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; +//! use embassy_sync::mutex::Mutex; +//! use embassy_sync::blocking_mutex::raw::NoopRawMutex; +//! +//! static I2C_BUS: StaticCell>> = StaticCell::new(); +//! let config = twim::Config::default(); +//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, config); +//! let i2c_bus = Mutex::new(i2c); +//! let i2c_bus = I2C_BUS.init(i2c_bus); +//! +//! // Device 1, using embedded-hal-async compatible driver for QMC5883L compass +//! let i2c_dev1 = I2cDevice::new(i2c_bus); +//! let compass = QMC5883L::new(i2c_dev1).await.unwrap(); +//! +//! // Device 2, using embedded-hal-async compatible driver for Mpu6050 accelerometer +//! let i2c_dev2 = I2cDevice::new(i2c_bus); +//! let mpu = Mpu6050::new(i2c_dev2); +//! ``` + +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use embedded_hal_async::i2c; + +use crate::shared_bus::I2cDeviceError; +use crate::SetConfig; + +/// I2C device on a shared bus. +pub struct I2cDevice<'a, M: RawMutex, BUS> { + bus: &'a Mutex, +} + +impl<'a, M: RawMutex, BUS> I2cDevice<'a, M, BUS> { + /// Create a new `I2cDevice`. + pub fn new(bus: &'a Mutex) -> Self { + Self { bus } + } +} + +impl<'a, M: RawMutex, BUS> i2c::ErrorType for I2cDevice<'a, M, BUS> +where + BUS: i2c::ErrorType, +{ + type Error = I2cDeviceError; +} + +impl i2c::I2c for I2cDevice<'_, M, BUS> +where + M: RawMutex + 'static, + BUS: i2c::I2c + 'static, +{ + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.read(address, read).await.map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.write(address, write).await.map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.write_read(address, write, read) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_async::i2c::Operation<'_>], + ) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.transaction(address, operations) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) + } +} + +/// I2C device on a shared bus, with its own configuration. +/// +/// This is like [`I2cDevice`], with an additional bus configuration that's applied +/// to the bus before each use using [`SetConfig`]. This allows different +/// devices on the same bus to use different communication settings. +pub struct I2cDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig> { + bus: &'a Mutex, + config: BUS::Config, +} + +impl<'a, M: RawMutex, BUS: SetConfig> I2cDeviceWithConfig<'a, M, BUS> { + /// Create a new `I2cDeviceWithConfig`. + pub fn new(bus: &'a Mutex, config: BUS::Config) -> Self { + Self { bus, config } + } + + /// Change the device's config at runtime + pub fn set_config(&mut self, config: BUS::Config) { + self.config = config; + } +} + +impl<'a, M, BUS> i2c::ErrorType for I2cDeviceWithConfig<'a, M, BUS> +where + BUS: i2c::ErrorType, + M: RawMutex, + BUS: SetConfig, +{ + type Error = I2cDeviceError; +} + +impl i2c::I2c for I2cDeviceWithConfig<'_, M, BUS> +where + M: RawMutex + 'static, + BUS: i2c::I2c + SetConfig + 'static, +{ + async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn write_read( + &mut self, + address: u8, + wr_buffer: &[u8], + rd_buffer: &mut [u8], + ) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.write_read(address, wr_buffer, rd_buffer) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn transaction(&mut self, address: u8, operations: &mut [i2c::Operation<'_>]) -> Result<(), Self::Error> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.transaction(address, operations) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) + } +} diff --git a/embassy/embassy-embedded-hal/src/shared_bus/asynch/mod.rs b/embassy/embassy-embedded-hal/src/shared_bus/asynch/mod.rs new file mode 100644 index 0000000..2e660b7 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/shared_bus/asynch/mod.rs @@ -0,0 +1,3 @@ +//! Asynchronous shared bus implementations for embedded-hal-async +pub mod i2c; +pub mod spi; diff --git a/embassy/embassy-embedded-hal/src/shared_bus/asynch/spi.rs b/embassy/embassy-embedded-hal/src/shared_bus/asynch/spi.rs new file mode 100644 index 0000000..30d4ecc --- /dev/null +++ b/embassy/embassy-embedded-hal/src/shared_bus/asynch/spi.rs @@ -0,0 +1,193 @@ +//! Asynchronous shared SPI bus +//! +//! # Example (nrf52) +//! +//! ```rust,ignore +//! use embassy_embedded_hal::shared_bus::spi::SpiDevice; +//! use embassy_sync::mutex::Mutex; +//! use embassy_sync::blocking_mutex::raw::NoopRawMutex; +//! +//! static SPI_BUS: StaticCell>> = StaticCell::new(); +//! let mut config = spim::Config::default(); +//! config.frequency = spim::Frequency::M32; +//! let spi = spim::Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, config); +//! let spi_bus = Mutex::new(spi); +//! let spi_bus = SPI_BUS.init(spi_bus); +//! +//! // Device 1, using embedded-hal-async compatible driver for ST7735 LCD display +//! let cs_pin1 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard); +//! let spi_dev1 = SpiDevice::new(spi_bus, cs_pin1); +//! let display1 = ST7735::new(spi_dev1, dc1, rst1, Default::default(), 160, 128); +//! +//! // Device 2 +//! let cs_pin2 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard); +//! let spi_dev2 = SpiDevice::new(spi_bus, cs_pin2); +//! let display2 = ST7735::new(spi_dev2, dc2, rst2, Default::default(), 160, 128); +//! ``` + +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use embedded_hal_1::digital::OutputPin; +use embedded_hal_1::spi::Operation; +use embedded_hal_async::spi; + +use crate::shared_bus::SpiDeviceError; +use crate::SetConfig; + +/// SPI device on a shared bus. +pub struct SpiDevice<'a, M: RawMutex, BUS, CS> { + bus: &'a Mutex, + cs: CS, +} + +impl<'a, M: RawMutex, BUS, CS> SpiDevice<'a, M, BUS, CS> { + /// Create a new `SpiDevice`. + pub fn new(bus: &'a Mutex, cs: CS) -> Self { + Self { bus, cs } + } +} + +impl<'a, M: RawMutex, BUS, CS> spi::ErrorType for SpiDevice<'a, M, BUS, CS> +where + BUS: spi::ErrorType, + CS: OutputPin, +{ + type Error = SpiDeviceError; +} + +impl spi::SpiDevice for SpiDevice<'_, M, BUS, CS> +where + M: RawMutex, + BUS: spi::SpiBus, + CS: OutputPin, + Word: Copy + 'static, +{ + async fn transaction(&mut self, operations: &mut [spi::Operation<'_, Word>]) -> Result<(), Self::Error> { + if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) { + return Err(SpiDeviceError::DelayNotSupported); + } + + let mut bus = self.bus.lock().await; + self.cs.set_low().map_err(SpiDeviceError::Cs)?; + + let op_res = 'ops: { + for op in operations { + let res = match op { + Operation::Read(buf) => bus.read(buf).await, + Operation::Write(buf) => bus.write(buf).await, + Operation::Transfer(read, write) => bus.transfer(read, write).await, + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await, + #[cfg(not(feature = "time"))] + Operation::DelayNs(_) => unreachable!(), + #[cfg(feature = "time")] + Operation::DelayNs(ns) => match bus.flush().await { + Err(e) => Err(e), + Ok(()) => { + embassy_time::Timer::after_nanos(*ns as _).await; + Ok(()) + } + }, + }; + if let Err(e) = res { + break 'ops Err(e); + } + } + Ok(()) + }; + + // 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 op_res = op_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; + + Ok(op_res) + } +} + +/// SPI device on a shared bus, with its own configuration. +/// +/// This is like [`SpiDevice`], with an additional bus configuration that's applied +/// to the bus before each use using [`SetConfig`]. This allows different +/// devices on the same bus to use different communication settings. +pub struct SpiDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig, CS> { + bus: &'a Mutex, + cs: CS, + config: BUS::Config, +} + +impl<'a, M: RawMutex, BUS: SetConfig, CS> SpiDeviceWithConfig<'a, M, BUS, CS> { + /// Create a new `SpiDeviceWithConfig`. + pub fn new(bus: &'a Mutex, cs: CS, config: BUS::Config) -> Self { + Self { bus, cs, config } + } + + /// Change the device's config at runtime + pub fn set_config(&mut self, config: BUS::Config) { + self.config = config; + } +} + +impl<'a, M, BUS, CS> spi::ErrorType for SpiDeviceWithConfig<'a, M, BUS, CS> +where + BUS: spi::ErrorType + SetConfig, + CS: OutputPin, + M: RawMutex, +{ + type Error = SpiDeviceError; +} + +impl spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS> +where + M: RawMutex, + BUS: spi::SpiBus + SetConfig, + CS: OutputPin, + Word: Copy + 'static, +{ + async fn transaction(&mut self, operations: &mut [spi::Operation<'_, Word>]) -> Result<(), Self::Error> { + if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) { + return Err(SpiDeviceError::DelayNotSupported); + } + + let mut bus = self.bus.lock().await; + bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?; + self.cs.set_low().map_err(SpiDeviceError::Cs)?; + + let op_res = 'ops: { + for op in operations { + let res = match op { + Operation::Read(buf) => bus.read(buf).await, + Operation::Write(buf) => bus.write(buf).await, + Operation::Transfer(read, write) => bus.transfer(read, write).await, + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await, + #[cfg(not(feature = "time"))] + Operation::DelayNs(_) => unreachable!(), + #[cfg(feature = "time")] + Operation::DelayNs(ns) => match bus.flush().await { + Err(e) => Err(e), + Ok(()) => { + embassy_time::Timer::after_nanos(*ns as _).await; + Ok(()) + } + }, + }; + if let Err(e) = res { + break 'ops Err(e); + } + } + Ok(()) + }; + + // 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 op_res = op_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; + + Ok(op_res) + } +} diff --git a/embassy/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs b/embassy/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs new file mode 100644 index 0000000..627767c --- /dev/null +++ b/embassy/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs @@ -0,0 +1,187 @@ +//! Blocking shared I2C bus +//! +//! # Example (nrf52) +//! +//! ```rust,ignore +//! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; +//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; +//! +//! static I2C_BUS: StaticCell>>> = StaticCell::new(); +//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, Config::default()); +//! let i2c_bus = NoopMutex::new(RefCell::new(i2c)); +//! let i2c_bus = I2C_BUS.init(i2c_bus); +//! +//! let i2c_dev1 = I2cDevice::new(i2c_bus); +//! let mpu = Mpu6050::new(i2c_dev1); +//! ``` + +use core::cell::RefCell; + +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_hal_1::i2c::{ErrorType, I2c, Operation}; + +use crate::shared_bus::I2cDeviceError; +use crate::SetConfig; + +/// I2C device on a shared bus. +pub struct I2cDevice<'a, M: RawMutex, BUS> { + bus: &'a Mutex>, +} + +impl<'a, M: RawMutex, BUS> I2cDevice<'a, M, BUS> { + /// Create a new `I2cDevice`. + pub fn new(bus: &'a Mutex>) -> Self { + Self { bus } + } +} + +impl<'a, M: RawMutex, BUS> ErrorType for I2cDevice<'a, M, BUS> +where + BUS: ErrorType, +{ + type Error = I2cDeviceError; +} + +impl I2c for I2cDevice<'_, M, BUS> +where + M: RawMutex, + BUS: I2c, +{ + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.bus + .lock(|bus| bus.borrow_mut().read(address, buffer).map_err(I2cDeviceError::I2c)) + } + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.bus + .lock(|bus| bus.borrow_mut().write(address, bytes).map_err(I2cDeviceError::I2c)) + } + + fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + bus.borrow_mut() + .write_read(address, wr_buffer, rd_buffer) + .map_err(I2cDeviceError::I2c) + }) + } + + fn transaction<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + bus.borrow_mut() + .transaction(address, operations) + .map_err(I2cDeviceError::I2c) + }) + } +} + +impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::Write for I2cDevice<'_, M, BUS> +where + M: RawMutex, + BUS: embedded_hal_02::blocking::i2c::Write, +{ + type Error = I2cDeviceError; + + fn write<'w>(&mut self, addr: u8, bytes: &'w [u8]) -> Result<(), Self::Error> { + self.bus + .lock(|bus| bus.borrow_mut().write(addr, bytes).map_err(I2cDeviceError::I2c)) + } +} + +impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::Read for I2cDevice<'_, M, BUS> +where + M: RawMutex, + BUS: embedded_hal_02::blocking::i2c::Read, +{ + type Error = I2cDeviceError; + + fn read<'w>(&mut self, addr: u8, bytes: &'w mut [u8]) -> Result<(), Self::Error> { + self.bus + .lock(|bus| bus.borrow_mut().read(addr, bytes).map_err(I2cDeviceError::I2c)) + } +} + +impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::WriteRead for I2cDevice<'_, M, BUS> +where + M: RawMutex, + BUS: embedded_hal_02::blocking::i2c::WriteRead, +{ + type Error = I2cDeviceError; + + fn write_read<'w>(&mut self, addr: u8, bytes: &'w [u8], buffer: &'w mut [u8]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + bus.borrow_mut() + .write_read(addr, bytes, buffer) + .map_err(I2cDeviceError::I2c) + }) + } +} + +/// I2C device on a shared bus, with its own configuration. +/// +/// This is like [`I2cDevice`], with an additional bus configuration that's applied +/// to the bus before each use using [`SetConfig`]. This allows different +/// devices on the same bus to use different communication settings. +pub struct I2cDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig> { + bus: &'a Mutex>, + config: BUS::Config, +} + +impl<'a, M: RawMutex, BUS: SetConfig> I2cDeviceWithConfig<'a, M, BUS> { + /// Create a new `I2cDeviceWithConfig`. + pub fn new(bus: &'a Mutex>, config: BUS::Config) -> Self { + Self { bus, config } + } + + /// Change the device's config at runtime + pub fn set_config(&mut self, config: BUS::Config) { + self.config = config; + } +} + +impl<'a, M, BUS> ErrorType for I2cDeviceWithConfig<'a, M, BUS> +where + M: RawMutex, + BUS: ErrorType + SetConfig, +{ + type Error = I2cDeviceError; +} + +impl I2c for I2cDeviceWithConfig<'_, M, BUS> +where + M: RawMutex, + BUS: I2c + SetConfig, +{ + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.read(address, buffer).map_err(I2cDeviceError::I2c) + }) + } + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.write(address, bytes).map_err(I2cDeviceError::I2c) + }) + } + + fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.write_read(address, wr_buffer, rd_buffer) + .map_err(I2cDeviceError::I2c) + }) + } + + fn transaction<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.transaction(address, operations).map_err(I2cDeviceError::I2c) + }) + } +} diff --git a/embassy/embassy-embedded-hal/src/shared_bus/blocking/mod.rs b/embassy/embassy-embedded-hal/src/shared_bus/blocking/mod.rs new file mode 100644 index 0000000..c2063ed --- /dev/null +++ b/embassy/embassy-embedded-hal/src/shared_bus/blocking/mod.rs @@ -0,0 +1,3 @@ +//! Blocking shared bus implementations for embedded-hal +pub mod i2c; +pub mod spi; diff --git a/embassy/embassy-embedded-hal/src/shared_bus/blocking/spi.rs b/embassy/embassy-embedded-hal/src/shared_bus/blocking/spi.rs new file mode 100644 index 0000000..eb9c0c4 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/shared_bus/blocking/spi.rs @@ -0,0 +1,167 @@ +//! Blocking shared SPI bus +//! +//! # Example (nrf52) +//! +//! ```rust,ignore +//! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; +//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; +//! +//! static SPI_BUS: StaticCell>>> = StaticCell::new(); +//! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default()); +//! let spi_bus = NoopMutex::new(RefCell::new(spi)); +//! let spi_bus = SPI_BUS.init(spi_bus); +//! +//! // Device 1, using embedded-hal compatible driver for ST7735 LCD display +//! let cs_pin1 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard); +//! let spi_dev1 = SpiDevice::new(spi_bus, cs_pin1); +//! let display1 = ST7735::new(spi_dev1, dc1, rst1, Default::default(), false, 160, 128); +//! ``` + +use core::cell::RefCell; + +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_hal_1::digital::OutputPin; +use embedded_hal_1::spi::{self, Operation, SpiBus}; + +use crate::shared_bus::SpiDeviceError; +use crate::SetConfig; + +/// SPI device on a shared bus. +pub struct SpiDevice<'a, M: RawMutex, BUS, CS> { + bus: &'a Mutex>, + cs: CS, +} + +impl<'a, M: RawMutex, BUS, CS> SpiDevice<'a, M, BUS, CS> { + /// Create a new `SpiDevice`. + pub fn new(bus: &'a Mutex>, cs: CS) -> Self { + Self { bus, cs } + } +} + +impl<'a, M: RawMutex, BUS, CS> spi::ErrorType for SpiDevice<'a, M, BUS, CS> +where + BUS: spi::ErrorType, + CS: OutputPin, +{ + type Error = SpiDeviceError; +} + +impl embedded_hal_1::spi::SpiDevice for SpiDevice<'_, M, BUS, CS> +where + M: RawMutex, + BUS: SpiBus, + CS: OutputPin, + Word: Copy + 'static, +{ + fn transaction(&mut self, operations: &mut [embedded_hal_1::spi::Operation<'_, Word>]) -> Result<(), Self::Error> { + if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) { + return Err(SpiDeviceError::DelayNotSupported); + } + + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + self.cs.set_low().map_err(SpiDeviceError::Cs)?; + + let op_res = operations.iter_mut().try_for_each(|op| match op { + Operation::Read(buf) => bus.read(buf), + Operation::Write(buf) => bus.write(buf), + Operation::Transfer(read, write) => bus.transfer(read, write), + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), + #[cfg(not(feature = "time"))] + Operation::DelayNs(_) => unreachable!(), + #[cfg(feature = "time")] + Operation::DelayNs(ns) => { + embassy_time::block_for(embassy_time::Duration::from_nanos(*ns as _)); + Ok(()) + } + }); + + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush(); + let cs_res = self.cs.set_high(); + + let op_res = op_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; + + Ok(op_res) + }) + } +} + +/// SPI device on a shared bus, with its own configuration. +/// +/// This is like [`SpiDevice`], with an additional bus configuration that's applied +/// to the bus before each use using [`SetConfig`]. This allows different +/// devices on the same bus to use different communication settings. +pub struct SpiDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig, CS> { + bus: &'a Mutex>, + cs: CS, + config: BUS::Config, +} + +impl<'a, M: RawMutex, BUS: SetConfig, CS> SpiDeviceWithConfig<'a, M, BUS, CS> { + /// Create a new `SpiDeviceWithConfig`. + pub fn new(bus: &'a Mutex>, cs: CS, config: BUS::Config) -> Self { + Self { bus, cs, config } + } + + /// Change the device's config at runtime + pub fn set_config(&mut self, config: BUS::Config) { + self.config = config; + } +} + +impl<'a, M, BUS, CS> spi::ErrorType for SpiDeviceWithConfig<'a, M, BUS, CS> +where + M: RawMutex, + BUS: spi::ErrorType + SetConfig, + CS: OutputPin, +{ + type Error = SpiDeviceError; +} + +impl embedded_hal_1::spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS> +where + M: RawMutex, + BUS: SpiBus + SetConfig, + CS: OutputPin, + Word: Copy + 'static, +{ + fn transaction(&mut self, operations: &mut [embedded_hal_1::spi::Operation<'_, Word>]) -> Result<(), Self::Error> { + if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) { + return Err(SpiDeviceError::DelayNotSupported); + } + + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?; + self.cs.set_low().map_err(SpiDeviceError::Cs)?; + + let op_res = operations.iter_mut().try_for_each(|op| match op { + Operation::Read(buf) => bus.read(buf), + Operation::Write(buf) => bus.write(buf), + Operation::Transfer(read, write) => bus.transfer(read, write), + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), + #[cfg(not(feature = "time"))] + Operation::DelayNs(_) => unreachable!(), + #[cfg(feature = "time")] + Operation::DelayNs(ns) => { + embassy_time::block_for(embassy_time::Duration::from_nanos(*ns as _)); + Ok(()) + } + }); + + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush(); + let cs_res = self.cs.set_high(); + + let op_res = op_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; + Ok(op_res) + }) + } +} diff --git a/embassy/embassy-embedded-hal/src/shared_bus/mod.rs b/embassy/embassy-embedded-hal/src/shared_bus/mod.rs new file mode 100644 index 0000000..d835306 --- /dev/null +++ b/embassy/embassy-embedded-hal/src/shared_bus/mod.rs @@ -0,0 +1,59 @@ +//! Shared bus implementations +use core::fmt::Debug; + +use embedded_hal_1::{i2c, spi}; + +pub mod asynch; +pub mod blocking; + +/// Error returned by I2C device implementations in this crate. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum I2cDeviceError { + /// An operation on the inner I2C bus failed. + I2c(BUS), + /// Configuration of the inner I2C bus failed. + Config, +} + +impl i2c::Error for I2cDeviceError +where + BUS: i2c::Error + Debug, +{ + fn kind(&self) -> i2c::ErrorKind { + match self { + Self::I2c(e) => e.kind(), + Self::Config => i2c::ErrorKind::Other, + } + } +} + +/// Error returned by SPI device implementations in this crate. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum SpiDeviceError { + /// An operation on the inner SPI bus failed. + Spi(BUS), + /// Setting the value of the Chip Select (CS) pin failed. + Cs(CS), + /// Delay operations are not supported when the `time` Cargo feature is not enabled. + DelayNotSupported, + /// The SPI bus could not be configured. + Config, +} + +impl spi::Error for SpiDeviceError +where + BUS: spi::Error + Debug, + CS: Debug, +{ + fn kind(&self) -> spi::ErrorKind { + match self { + Self::Spi(e) => e.kind(), + Self::Cs(_) => spi::ErrorKind::Other, + Self::DelayNotSupported => spi::ErrorKind::Other, + Self::Config => spi::ErrorKind::Other, + } + } +} diff --git a/embassy/embassy-executor-macros/Cargo.toml b/embassy/embassy-executor-macros/Cargo.toml new file mode 100644 index 0000000..5a38f2f --- /dev/null +++ b/embassy/embassy-executor-macros/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "embassy-executor-macros" +version = "0.6.2" +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" +documentation = "https://docs.embassy.dev/embassy-executor-macros" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[dependencies] +syn = { version = "2.0.15", features = ["full", "visit"] } +quote = "1.0.9" +darling = "0.20.1" +proc-macro2 = "1.0.29" + +[lib] +proc-macro = true + +[features] +nightly = [] diff --git a/embassy/embassy-executor-macros/README.md b/embassy/embassy-executor-macros/README.md new file mode 100644 index 0000000..3a8d49a --- /dev/null +++ b/embassy/embassy-executor-macros/README.md @@ -0,0 +1,5 @@ +# embassy-executor-macros + +An [Embassy](https://embassy.dev) project. + +NOTE: Do not use this crate directly. The macros are re-exported by `embassy-executor`. diff --git a/embassy/embassy-executor-macros/src/lib.rs b/embassy/embassy-executor-macros/src/lib.rs new file mode 100644 index 0000000..5f2182f --- /dev/null +++ b/embassy/embassy-executor-macros/src/lib.rs @@ -0,0 +1,175 @@ +#![doc = include_str!("../README.md")] +extern crate proc_macro; + +use proc_macro::TokenStream; + +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 { + task::run(args.into(), item.into()).into() +} + +#[proc_macro_attribute] +pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_AVR).into() +} + +/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into() +} + +/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning +/// the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// A user-defined entry macro must provided via the `entry` argument +/// +/// ## Examples +/// Spawning a task: +/// ``` rust +/// #[embassy_executor::main(entry = "qingke_rt::entry")] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_SPIN).into() +} + +/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// A user-defined entry macro can be optionally provided via the `entry` argument to override the default of `riscv_rt::entry`. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +/// +/// Spawning a task using a custom entry macro: +/// ``` rust +/// #[embassy_executor::main(entry = "esp_riscv_rt::entry")] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_RISCV).into() +} + +/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task. +/// +/// 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 { + main::run(args.into(), item.into(), &main::ARCH_STD).into() +} + +/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task. +/// +/// 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 { + main::run(args.into(), item.into(), &main::ARCH_WASM).into() +} diff --git a/embassy/embassy-executor-macros/src/macros/main.rs b/embassy/embassy-executor-macros/src/macros/main.rs new file mode 100644 index 0000000..14b1d9d --- /dev/null +++ b/embassy/embassy-executor-macros/src/macros/main.rs @@ -0,0 +1,177 @@ +use std::str::FromStr; + +use darling::export::NestedMeta; +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ReturnType, Type}; + +use crate::util::*; + +enum Flavor { + Standard, + Wasm, +} + +pub(crate) struct Arch { + default_entry: Option<&'static str>, + flavor: Flavor, +} + +pub static ARCH_AVR: Arch = Arch { + default_entry: Some("avr_device::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_RISCV: Arch = Arch { + default_entry: Some("riscv_rt::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_CORTEX_M: Arch = Arch { + default_entry: Some("cortex_m_rt::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_SPIN: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, +}; + +pub static ARCH_STD: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, +}; + +pub static ARCH_WASM: Arch = Arch { + default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"), + flavor: Flavor::Wasm, +}; + +#[derive(Debug, FromMeta, Default)] +struct Args { + #[darling(default)] + entry: Option, +} + +pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { + let mut errors = TokenStream::new(); + + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let f: ItemFn = match syn::parse2(item.clone()) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match NestedMeta::parse_meta_list(args) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match Args::from_list(&args) { + Ok(x) => x, + Err(e) => { + errors.extend(e.write_errors()); + Args::default() + } + }; + + let fargs = f.sig.inputs.clone(); + + if f.sig.asyncness.is_none() { + error(&mut errors, &f.sig, "main function must be async"); + } + if !f.sig.generics.params.is_empty() { + error(&mut errors, &f.sig, "main function must not be generic"); + } + if !f.sig.generics.where_clause.is_none() { + error(&mut errors, &f.sig, "main function must not have `where` clauses"); + } + if !f.sig.abi.is_none() { + error(&mut errors, &f.sig, "main function must not have an ABI qualifier"); + } + if !f.sig.variadic.is_none() { + error(&mut errors, &f.sig, "main function must not be variadic"); + } + match &f.sig.output { + ReturnType::Default => {} + ReturnType::Type(_, ty) => match &**ty { + Type::Tuple(tuple) if tuple.elems.is_empty() => {} + Type::Never(_) => {} + _ => error( + &mut errors, + &f.sig, + "main function must either not return a value, return `()` or return `!`", + ), + }, + } + + if fargs.len() != 1 { + error(&mut errors, &f.sig, "main function must have 1 argument: the spawner."); + } + + let entry = match args.entry.as_deref().or(arch.default_entry) { + None => TokenStream::new(), + Some(x) => match TokenStream::from_str(x) { + Ok(x) => quote!(#[#x]), + Err(e) => { + error(&mut errors, &f.sig, e); + TokenStream::new() + } + }, + }; + + let f_body = f.body; + let out = &f.sig.output; + + let (main_ret, mut main_body) = match arch.flavor { + Flavor::Standard => ( + quote!(!), + quote! { + unsafe fn __make_static(t: &mut T) -> &'static mut T { + ::core::mem::transmute(t) + } + + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + }, + ), + Flavor::Wasm => ( + quote!(Result<(), wasm_bindgen::JsValue>), + quote! { + let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new())); + + executor.start(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }); + + Ok(()) + }, + ), + }; + + if !errors.is_empty() { + main_body = quote! {loop{}}; + } + + let result = quote! { + #[::embassy_executor::task()] + async fn __embassy_main(#fargs) #out { + #f_body + } + + #entry + fn main() -> #main_ret { + #main_body + } + + #errors + }; + + result +} diff --git a/embassy/embassy-executor-macros/src/macros/mod.rs b/embassy/embassy-executor-macros/src/macros/mod.rs new file mode 100644 index 0000000..572094c --- /dev/null +++ b/embassy/embassy-executor-macros/src/macros/mod.rs @@ -0,0 +1,2 @@ +pub mod main; +pub mod task; diff --git a/embassy/embassy-executor-macros/src/macros/task.rs b/embassy/embassy-executor-macros/src/macros/task.rs new file mode 100644 index 0000000..e8134c6 --- /dev/null +++ b/embassy/embassy-executor-macros/src/macros/task.rs @@ -0,0 +1,220 @@ +use std::str::FromStr; + +use darling::export::NestedMeta; +use darling::FromMeta; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +use syn::visit::{self, Visit}; +use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type}; + +use crate::util::*; + +#[derive(Debug, FromMeta, Default)] +struct Args { + #[darling(default)] + pool_size: Option, + /// Use this to override the `embassy_executor` crate path. Defaults to `::embassy_executor`. + #[darling(default)] + embassy_executor: Option, +} + +pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { + let mut errors = TokenStream::new(); + + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let f: ItemFn = match syn::parse2(item.clone()) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match NestedMeta::parse_meta_list(args) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match Args::from_list(&args) { + Ok(x) => x, + Err(e) => { + errors.extend(e.write_errors()); + Args::default() + } + }; + + let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit { + attrs: vec![], + lit: Lit::Int(LitInt::new("1", Span::call_site())), + })); + + let embassy_executor = args + .embassy_executor + .unwrap_or(Expr::Verbatim(TokenStream::from_str("::embassy_executor").unwrap())); + + if f.sig.asyncness.is_none() { + error(&mut errors, &f.sig, "task functions must be async"); + } + if !f.sig.generics.params.is_empty() { + error(&mut errors, &f.sig, "task functions must not be generic"); + } + if !f.sig.generics.where_clause.is_none() { + error(&mut errors, &f.sig, "task functions must not have `where` clauses"); + } + if !f.sig.abi.is_none() { + error(&mut errors, &f.sig, "task functions must not have an ABI qualifier"); + } + if !f.sig.variadic.is_none() { + error(&mut errors, &f.sig, "task functions must not be variadic"); + } + match &f.sig.output { + ReturnType::Default => {} + ReturnType::Type(_, ty) => match &**ty { + Type::Tuple(tuple) if tuple.elems.is_empty() => {} + Type::Never(_) => {} + _ => error( + &mut errors, + &f.sig, + "task functions must either not return a value, return `()` or return `!`", + ), + }, + } + + let mut args = Vec::new(); + let mut fargs = f.sig.inputs.clone(); + + for arg in fargs.iter_mut() { + match arg { + syn::FnArg::Receiver(_) => { + error(&mut errors, arg, "task functions must not have `self` arguments"); + } + syn::FnArg::Typed(t) => { + check_arg_ty(&mut errors, &t.ty); + match t.pat.as_mut() { + syn::Pat::Ident(id) => { + id.mutability = None; + args.push((id.clone(), t.attrs.clone())); + } + _ => { + error( + &mut errors, + arg, + "pattern matching in task arguments is not yet supported", + ); + } + } + } + } + } + + let task_ident = f.sig.ident.clone(); + let task_inner_ident = format_ident!("__{}_task", task_ident); + + let mut task_inner = f.clone(); + let visibility = task_inner.vis.clone(); + task_inner.vis = syn::Visibility::Inherited; + task_inner.sig.ident = task_inner_ident.clone(); + + // assemble the original input arguments, + // including any attributes that may have + // been applied previously + let mut full_args = Vec::new(); + for (arg, cfgs) in args { + full_args.push(quote!( + #(#cfgs)* + #arg + )); + } + + #[cfg(feature = "nightly")] + let mut task_outer_body = quote! { + trait _EmbassyInternalTaskTrait { + type Fut: ::core::future::Future + 'static; + fn construct(#fargs) -> Self::Fut; + } + + impl _EmbassyInternalTaskTrait for () { + type Fut = impl core::future::Future + 'static; + fn construct(#fargs) -> Self::Fut { + #task_inner_ident(#(#full_args,)*) + } + } + + const POOL_SIZE: usize = #pool_size; + static POOL: #embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = #embassy_executor::raw::TaskPool::new(); + unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) } + }; + #[cfg(not(feature = "nightly"))] + let mut task_outer_body = quote! { + const POOL_SIZE: usize = #pool_size; + static POOL: #embassy_executor::_export::TaskPoolRef = #embassy_executor::_export::TaskPoolRef::new(); + unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } + }; + + let task_outer_attrs = task_inner.attrs.clone(); + + if !errors.is_empty() { + task_outer_body = quote! { + #![allow(unused_variables, unreachable_code)] + let _x: #embassy_executor::SpawnToken<()> = ::core::todo!(); + _x + }; + } + + // Copy the generics + where clause to avoid more spurious errors. + let generics = &f.sig.generics; + let where_clause = &f.sig.generics.where_clause; + + let result = quote! { + // This is the user's task function, renamed. + // We put it outside the #task_ident fn below, because otherwise + // the items defined there (such as POOL) would be in scope + // in the user's code. + #[doc(hidden)] + #task_inner + + #(#task_outer_attrs)* + #visibility fn #task_ident #generics (#fargs) -> #embassy_executor::SpawnToken #where_clause{ + #task_outer_body + } + + #errors + }; + + result +} + +fn check_arg_ty(errors: &mut TokenStream, ty: &Type) { + struct Visitor<'a> { + errors: &'a mut TokenStream, + } + + impl<'a, 'ast> Visit<'ast> for Visitor<'a> { + fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) { + // only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`. + if i.lifetime.is_none() { + error( + self.errors, + i.and_token, + "Arguments for tasks must live forever. Try using the `'static` lifetime.", + ) + } + visit::visit_type_reference(self, i); + } + + fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) { + if i.ident.to_string() != "static" { + error( + self.errors, + i, + "Arguments for tasks must live forever. Try using the `'static` lifetime.", + ) + } + } + + fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) { + error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic."); + } + } + + Visit::visit_type(&mut Visitor { errors }, ty); +} diff --git a/embassy/embassy-executor-macros/src/util.rs b/embassy/embassy-executor-macros/src/util.rs new file mode 100644 index 0000000..ebd032a --- /dev/null +++ b/embassy/embassy-executor-macros/src/util.rs @@ -0,0 +1,74 @@ +use std::fmt::Display; + +use proc_macro2::{TokenStream, TokenTree}; +use quote::{ToTokens, TokenStreamExt}; +use syn::parse::{Parse, ParseStream}; +use syn::{braced, bracketed, token, AttrStyle, Attribute, Signature, Token, Visibility}; + +pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream { + tokens.extend(error.into_compile_error()); + tokens +} + +pub fn error(s: &mut TokenStream, obj: A, msg: T) { + s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error()) +} + +/// Function signature and body. +/// +/// Same as `syn`'s `ItemFn` except we keep the body as a TokenStream instead of +/// parsing it. This makes the macro not error if there's a syntax error in the body, +/// which helps IDE autocomplete work better. +#[derive(Debug, Clone)] +pub struct ItemFn { + pub attrs: Vec, + pub vis: Visibility, + pub sig: Signature, + pub brace_token: token::Brace, + pub body: TokenStream, +} + +impl Parse for ItemFn { + fn parse(input: ParseStream) -> syn::Result { + let mut attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let sig: Signature = input.parse()?; + + let content; + let brace_token = braced!(content in input); + while content.peek(Token![#]) && content.peek2(Token![!]) { + let content2; + attrs.push(Attribute { + pound_token: content.parse()?, + style: AttrStyle::Inner(content.parse()?), + bracket_token: bracketed!(content2 in content), + meta: content2.parse()?, + }); + } + + let mut body = Vec::new(); + while !content.is_empty() { + body.push(content.parse::()?); + } + let body = body.into_iter().collect(); + + Ok(ItemFn { + attrs, + vis, + sig, + brace_token, + body, + }) + } +} + +impl ToTokens for ItemFn { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(self.attrs.iter().filter(|a| matches!(a.style, AttrStyle::Outer))); + self.vis.to_tokens(tokens); + self.sig.to_tokens(tokens); + self.brace_token.surround(tokens, |tokens| { + tokens.append_all(self.body.clone()); + }); + } +} diff --git a/embassy/embassy-executor/CHANGELOG.md b/embassy/embassy-executor/CHANGELOG.md new file mode 100644 index 0000000..59ea88d --- /dev/null +++ b/embassy/embassy-executor/CHANGELOG.md @@ -0,0 +1,97 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +- embassy-executor no longer provides an `embassy-time-queue-driver` implementation +- Added `TaskRef::executor` to obtain a reference to a task's executor +- integrated-timers are no longer processed when polling the executor. +- Added the option to store data in timer queue items + +## 0.6.3 - 2024-11-12 + +- Building with the `nightly` feature now works with the Xtensa Rust compiler 1.82. +- Compare vtable address instead of contents. Saves 44 bytes of flash on cortex-m. + +## 0.6.2 - 2024-11-06 + +- The `nightly` feature no longer requires `nightly-2024-09-06` or newer. + +## 0.6.1 - 2024-10-21 + +- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, + and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types + for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. +- Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature. +- Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires `nightly-2024-09-06` or newer. +- Improve macro error messages. + +## 0.6.0 - 2024-08-05 + +- Add collapse_debuginfo to fmt.rs macros. +- initial support for AVR +- use nightly waker_getters APIs + +## 0.5.1 - 2024-10-21 + +- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, + and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types + for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. + +## 0.5.0 - 2024-01-11 + +- Updated to `embassy-time-driver 0.1`, `embassy-time-queue-driver 0.1`, compatible with `embassy-time v0.3` and higher. + +## 0.4.0 - 2023-12-05 + +- Removed `arch-xtensa`. Use the executor provided by the HAL crate (`esp-hal`, `esp32s3-hal`, etc...) instead. +- Added an arena allocator for tasks, allowing using the `main` and `task` macros on Rust 1.75 stable. (it is only used if the `nightly` feature is not enabled. When `nightly` is enabled, `type_alias_impl_trait` is used to statically allocate tasks, as before). + +## 0.3.3 - 2023-11-15 + +- Add `main` macro reexport for Xtensa arch. +- Remove use of `atomic-polyfill`. The executor now has multiple implementations of its internal data structures for cases where the target supports atomics or doesn't. + +## 0.3.2 - 2023-11-06 + +- Use `atomic-polyfill` for `riscv32` +- Removed unused dependencies (static_cell, futures-util) + +## 0.3.1 - 2023-11-01 + +- Fix spurious "Found waker not created by the Embassy executor" error in recent nightlies. + +## 0.3.0 - 2023-08-25 + +- Replaced Pender. Implementations now must define an extern function called `__pender`. +- Made `raw::AvailableTask` public +- Made `SpawnToken::new_failed` public +- You can now use arbitrary expressions to specify `#[task(pool_size = X)]` + +## 0.2.1 - 2023-08-10 + +- Avoid calling `pend()` when waking expired timers +- Properly reset finished task state with `integrated-timers` enabled +- Introduce `InterruptExecutor::spawner()` +- Fix incorrect critical section in Xtensa executor + +## 0.2.0 - 2023-04-27 + +- Replace unnecessary atomics in runqueue +- add Pender, rework Cargo features. +- add support for turbo-wakers. +- Allow TaskStorage to auto-implement `Sync` +- Use AtomicPtr for signal_ctx, removes 1 unsafe. +- Replace unsound critical sections with atomics + +## 0.1.1 - 2022-11-23 + +- Fix features for documentation + +## 0.1.0 - 2022-11-23 + +- First release diff --git a/embassy/embassy-executor/Cargo.toml b/embassy/embassy-executor/Cargo.toml new file mode 100644 index 0000000..5effaca --- /dev/null +++ b/embassy/embassy-executor/Cargo.toml @@ -0,0 +1,205 @@ +[package] +name = "embassy-executor" +version = "0.6.3" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "async/await executor designed for embedded usage" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-executor" +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/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/" +features = ["defmt"] +flavors = [ + { name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] }, + { name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] }, + { name = "cortex-m", target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] }, + { name = "riscv32", target = "riscv32imac-unknown-none-elf", features = ["arch-riscv32", "executor-thread"] }, +] + +[package.metadata.docs.rs] +default-target = "thumbv7em-none-eabi" +targets = ["thumbv7em-none-eabi"] +features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +rtos-trace = { version = "0.1.3", optional = true } + +embassy-executor-macros = { version = "0.6.2", path = "../embassy-executor-macros" } +embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver", optional = true } +critical-section = "1.1" + +document-features = "0.2.7" + +# needed for AVR +portable-atomic = { version = "1.5", optional = true } + +# arch-cortex-m dependencies +cortex-m = { version = "0.7.6", optional = true } + +# arch-wasm dependencies +wasm-bindgen = { version = "0.2.82", optional = true } +js-sys = { version = "0.3", optional = true } + +# arch-avr dependencies +avr-device = { version = "0.5.3", optional = true } + +[dev-dependencies] +critical-section = { version = "1.1", features = ["std"] } +trybuild = "1.0" +embassy-sync = { path = "../embassy-sync" } + +[features] + +## Enable nightly-only features +nightly = ["embassy-executor-macros/nightly"] + +# Enables turbo wakers, which requires patching core. Not surfaced in the docs by default due to +# being an complicated advanced and undocumented feature. +# See: https://github.com/embassy-rs/embassy/pull/1263 +turbowakers = [] + +#! ### Architecture +_arch = [] # some arch was picked +## std +arch-std = ["_arch", "critical-section/std"] +## Cortex-M +arch-cortex-m = ["_arch", "dep:cortex-m"] +## RISC-V 32 +arch-riscv32 = ["_arch"] +## WASM +arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"] +## AVR +arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] +## spin (architecture agnostic; never sleeps) +arch-spin = ["_arch"] + +#! ### Executor + +## Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs) +executor-thread = [] +## Enable the interrupt-mode executor (available in Cortex-M only) +executor-interrupt = [] +## Enable tracing support (adds some overhead) +trace = [] +## Enable support for rtos-trace framework +rtos-trace = ["dep:rtos-trace", "trace", "dep:embassy-time-driver"] + +#! ### Timer Item Payload Size +#! Sets the size of the payload for timer items, allowing integrated timer implementors to store +#! additional data in the timer item. The payload field will be aligned to this value as well. +#! If these features are not defined, the timer item will contain no payload field. + +_timer-item-payload = [] # A size was picked + +## 1 bytes +timer-item-payload-size-1 = ["_timer-item-payload"] +## 2 bytes +timer-item-payload-size-2 = ["_timer-item-payload"] +## 4 bytes +timer-item-payload-size-4 = ["_timer-item-payload"] +## 8 bytes +timer-item-payload-size-8 = ["_timer-item-payload"] + +#! ### Task Arena Size +#! Sets the [task arena](#task-arena) size. Necessary if you’re not using `nightly`. +#! +#!

+#! Preconfigured Task Arena Sizes: +#! +#! + +# BEGIN AUTOGENERATED CONFIG FEATURES +# Generated by gen_config.py. DO NOT EDIT. +## 64 +task-arena-size-64 = [] +## 128 +task-arena-size-128 = [] +## 192 +task-arena-size-192 = [] +## 256 +task-arena-size-256 = [] +## 320 +task-arena-size-320 = [] +## 384 +task-arena-size-384 = [] +## 512 +task-arena-size-512 = [] +## 640 +task-arena-size-640 = [] +## 768 +task-arena-size-768 = [] +## 1024 +task-arena-size-1024 = [] +## 1280 +task-arena-size-1280 = [] +## 1536 +task-arena-size-1536 = [] +## 2048 +task-arena-size-2048 = [] +## 2560 +task-arena-size-2560 = [] +## 3072 +task-arena-size-3072 = [] +## 4096 (default) +task-arena-size-4096 = [] # Default +## 5120 +task-arena-size-5120 = [] +## 6144 +task-arena-size-6144 = [] +## 8192 +task-arena-size-8192 = [] +## 10240 +task-arena-size-10240 = [] +## 12288 +task-arena-size-12288 = [] +## 16384 +task-arena-size-16384 = [] +## 20480 +task-arena-size-20480 = [] +## 24576 +task-arena-size-24576 = [] +## 32768 +task-arena-size-32768 = [] +## 40960 +task-arena-size-40960 = [] +## 49152 +task-arena-size-49152 = [] +## 65536 +task-arena-size-65536 = [] +## 81920 +task-arena-size-81920 = [] +## 98304 +task-arena-size-98304 = [] +## 131072 +task-arena-size-131072 = [] +## 163840 +task-arena-size-163840 = [] +## 196608 +task-arena-size-196608 = [] +## 262144 +task-arena-size-262144 = [] +## 327680 +task-arena-size-327680 = [] +## 393216 +task-arena-size-393216 = [] +## 524288 +task-arena-size-524288 = [] +## 655360 +task-arena-size-655360 = [] +## 786432 +task-arena-size-786432 = [] +## 1048576 +task-arena-size-1048576 = [] + +# END AUTOGENERATED CONFIG FEATURES + +#!
diff --git a/embassy/embassy-executor/README.md b/embassy/embassy-executor/README.md new file mode 100644 index 0000000..aa9d599 --- /dev/null +++ b/embassy/embassy-executor/README.md @@ -0,0 +1,37 @@ +# embassy-executor + +An async/await executor designed for embedded usage. + +- No `alloc`, no heap needed. +- With nightly Rust, task futures can be fully statically allocated. +- No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning. +- Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`. +- No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`. +- Efficient polling: a wake will only poll the woken task, not all of them. +- Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time. +- Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks. + +## Task arena + +When the `nightly` Cargo feature is not enabled, `embassy-executor` allocates tasks out of an arena (a very simple bump allocator). + +If the task arena gets full, the program will panic at runtime. To guarantee this doesn't happen, you must set the size to the sum of sizes of all tasks. + +Tasks are allocated from the arena when spawned for the first time. If the task exists, the allocation is not released to the arena, but can be reused to spawn the task again. For multiple-instance tasks (like `#[embassy_executor::task(pool_size = 4)]`), the first spawn will allocate memory for all instances. This is done for performance and to increase predictability (for example, spawning at least 1 instance of every task at boot guarantees an immediate panic if the arena is too small, while allocating instances on-demand could delay the panic to only when the program is under load). + +The arena size can be configured in two ways: + +- Via Cargo features: enable a Cargo feature like `task-arena-size-8192`. Only a selection of values + is available, see [Task Area Sizes](#task-arena-size) for reference. +- Via environment variables at build time: set the variable named `EMBASSY_EXECUTOR_TASK_ARENA_SIZE`. For example + `EMBASSY_EXECUTOR_TASK_ARENA_SIZE=4321 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`. + Any value can be set, unlike with Cargo features. + +Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting +with different values, compilation fails. + +## Statically allocating tasks + +When using nightly Rust, enable the `nightly` Cargo feature. This will make `embassy-executor` use the `type_alias_impl_trait` feature to allocate all tasks in `static`s. Each task gets its own `static`, with the exact size to hold the task (or multiple instances of it, if using `pool_size`) calculated automatically at compile time. If tasks don't fit in RAM, this is detected at compile time by the linker. Runtime panics due to running out of memory are not possible. + +The configured arena size is ignored, no arena is used at all. diff --git a/embassy/embassy-executor/build.rs b/embassy/embassy-executor/build.rs new file mode 100644 index 0000000..8a41d75 --- /dev/null +++ b/embassy/embassy-executor/build.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; +use std::fmt::Write; +use std::path::PathBuf; +use std::{env, fs}; + +#[path = "./build_common.rs"] +mod common; + +static CONFIGS: &[(&str, usize)] = &[ + // BEGIN AUTOGENERATED CONFIG FEATURES + // Generated by gen_config.py. DO NOT EDIT. + ("TASK_ARENA_SIZE", 4096), + // END AUTOGENERATED CONFIG FEATURES +]; + +struct ConfigState { + value: usize, + seen_feature: bool, + seen_env: bool, +} + +fn main() { + let crate_name = env::var("CARGO_PKG_NAME") + .unwrap() + .to_ascii_uppercase() + .replace('-', "_"); + + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any + // other file changed. + println!("cargo:rerun-if-changed=build.rs"); + + // Rebuild if config envvar changed. + for (name, _) in CONFIGS { + println!("cargo:rerun-if-env-changed={crate_name}_{name}"); + } + + let mut configs = HashMap::new(); + for (name, default) in CONFIGS { + configs.insert( + *name, + ConfigState { + value: *default, + seen_env: false, + seen_feature: false, + }, + ); + } + + let prefix = format!("{crate_name}_"); + for (var, value) in env::vars() { + if let Some(name) = var.strip_prefix(&prefix) { + let Some(cfg) = configs.get_mut(name) else { + panic!("Unknown env var {name}") + }; + + let Ok(value) = value.parse::() else { + panic!("Invalid value for env var {name}: {value}") + }; + + cfg.value = value; + cfg.seen_env = true; + } + + if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") { + if let Some(i) = feature.rfind('_') { + let name = &feature[..i]; + let value = &feature[i + 1..]; + if let Some(cfg) = configs.get_mut(name) { + let Ok(value) = value.parse::() else { + panic!("Invalid value for feature {name}: {value}") + }; + + // envvars take priority. + if !cfg.seen_env { + if cfg.seen_feature { + panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value); + } + + cfg.value = value; + cfg.seen_feature = true; + } + } + } + } + } + + let mut data = String::new(); + + for (name, cfg) in &configs { + writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap(); + } + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("config.rs").to_string_lossy().to_string(); + fs::write(out_file, data).unwrap(); + + let mut rustc_cfgs = common::CfgSet::new(); + common::set_target_cfgs(&mut rustc_cfgs); +} diff --git a/embassy/embassy-executor/build_common.rs b/embassy/embassy-executor/build_common.rs new file mode 100644 index 0000000..b15a836 --- /dev/null +++ b/embassy/embassy-executor/build_common.rs @@ -0,0 +1,126 @@ +// NOTE: this file is copy-pasted between several Embassy crates, because there is no +// straightforward way to share this code: +// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path = +// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate +// reside in the crate's directory, +// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because +// symlinks don't work on Windows. + +use std::collections::HashSet; +use std::env; + +/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring +/// them (`cargo:rust-check-cfg=cfg(X)`). +#[derive(Debug)] +pub struct CfgSet { + enabled: HashSet, + declared: HashSet, +} + +impl CfgSet { + pub fn new() -> Self { + Self { + enabled: HashSet::new(), + declared: HashSet::new(), + } + } + + /// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation. + /// + /// All configs that can potentially be enabled should be unconditionally declared using + /// [`Self::declare()`]. + pub fn enable(&mut self, cfg: impl AsRef) { + if self.enabled.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-cfg={}", cfg.as_ref()); + } + } + + pub fn enable_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.enable(cfg.as_ref()); + } + } + + /// Declare a valid config for conditional compilation, without enabling it. + /// + /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. + pub fn declare(&mut self, cfg: impl AsRef) { + if self.declared.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); + } + } + + pub fn declare_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.declare(cfg.as_ref()); + } + } + + pub fn set(&mut self, cfg: impl Into, enable: bool) { + let cfg = cfg.into(); + if enable { + self.enable(cfg.clone()); + } + self.declare(cfg); + } +} + +/// Sets configs that describe the target platform. +pub fn set_target_cfgs(cfgs: &mut CfgSet) { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + cfgs.enable_all(&["cortex_m", "armv6m"]); + } else if target.starts_with("thumbv7m-") { + cfgs.enable_all(&["cortex_m", "armv7m"]); + } else if target.starts_with("thumbv7em-") { + cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]); + } else if target.starts_with("thumbv8m.base") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]); + } else if target.starts_with("thumbv8m.main") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]); + } + cfgs.declare_all(&[ + "cortex_m", + "armv6m", + "armv7m", + "armv7em", + "armv8m", + "armv8m_base", + "armv8m_main", + ]); + + cfgs.set("has_fpu", target.ends_with("-eabihf")); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CompilerDate { + year: u16, + month: u8, + day: u8, +} + +impl CompilerDate { + fn parse(date: &str) -> Option { + let mut parts = date.split('-'); + let year = parts.next()?.parse().ok()?; + let month = parts.next()?.parse().ok()?; + let day = parts.next()?.parse().ok()?; + Some(Self { year, month, day }) + } +} + +impl PartialEq<&str> for CompilerDate { + fn eq(&self, other: &&str) -> bool { + let Some(other) = Self::parse(other) else { + return false; + }; + self.eq(&other) + } +} + +impl PartialOrd<&str> for CompilerDate { + fn partial_cmp(&self, other: &&str) -> Option { + Self::parse(other).map(|other| self.cmp(&other)) + } +} diff --git a/embassy/embassy-executor/gen_config.py b/embassy/embassy-executor/gen_config.py new file mode 100644 index 0000000..cf32bd5 --- /dev/null +++ b/embassy/embassy-executor/gen_config.py @@ -0,0 +1,88 @@ +import os + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +features = [] + + +def feature(name, default, min=None, max=None, pow2=None, vals=None, factors=[]): + if vals is None: + assert min is not None + assert max is not None + + vals = set() + val = min + while val <= max: + vals.add(val) + for f in factors: + if val * f <= max: + vals.add(val * f) + if (pow2 == True or (isinstance(pow2, int) and val >= pow2)) and val > 0: + val *= 2 + else: + val += 1 + vals.add(default) + vals = sorted(list(vals)) + + features.append( + { + "name": name, + "default": default, + "vals": vals, + } + ) + + +feature( + "task_arena_size", default=4096, min=64, max=1024 * 1024, pow2=True, factors=[3, 5] +) + +# ========= Update Cargo.toml + +things = "" +for f in features: + name = f["name"].replace("_", "-") + for val in f["vals"]: + things += f"## {val}" + if val == f["default"]: + things += " (default)\n" + else: + things += "\n" + + 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/embassy-executor/src/arch/avr.rs b/embassy/embassy-executor/src/arch/avr.rs new file mode 100644 index 0000000..70085d0 --- /dev/null +++ b/embassy/embassy-executor/src/arch/avr.rs @@ -0,0 +1,72 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-avr`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + + pub use embassy_executor_macros::main_avr as main; + use portable_atomic::{AtomicBool, Ordering}; + + use crate::{raw, Spawner}; + + static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + + #[export_name = "__pender"] + fn __pender(_context: *mut ()) { + SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); + } + + /// avr Executor + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(core::ptr::null_mut()), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + avr_device::interrupt::disable(); + if !SIGNAL_WORK_THREAD_MODE.swap(false, Ordering::SeqCst) { + avr_device::interrupt::enable(); + avr_device::asm::sleep(); + } else { + avr_device::interrupt::enable(); + self.inner.poll(); + } + } + } + } + } +} diff --git a/embassy/embassy-executor/src/arch/cortex_m.rs b/embassy/embassy-executor/src/arch/cortex_m.rs new file mode 100644 index 0000000..5c517e0 --- /dev/null +++ b/embassy/embassy-executor/src/arch/cortex_m.rs @@ -0,0 +1,231 @@ +#[export_name = "__pender"] +#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] +fn __pender(context: *mut ()) { + unsafe { + // Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt + // request number given to `InterruptExecutor::start`. + + let context = context as usize; + + #[cfg(feature = "executor-thread")] + // Try to make Rust optimize the branching away if we only use thread mode. + if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER { + core::arch::asm!("sev"); + return; + } + + #[cfg(feature = "executor-interrupt")] + { + use cortex_m::interrupt::InterruptNumber; + use cortex_m::peripheral::NVIC; + + #[derive(Clone, Copy)] + struct Irq(u16); + unsafe impl InterruptNumber for Irq { + fn number(self) -> u16 { + self.0 + } + } + + let irq = Irq(context as u16); + + // STIR is faster, but is only available in v7 and higher. + #[cfg(not(armv6m))] + { + let mut nvic: NVIC = core::mem::transmute(()); + nvic.request(irq); + } + + #[cfg(armv6m)] + NVIC::pend(irq); + } + } +} + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + pub(super) const THREAD_PENDER: usize = usize::MAX; + + use core::arch::asm; + use core::marker::PhantomData; + + pub use embassy_executor_macros::main_cortex_m as main; + + use crate::{raw, Spawner}; + + /// Thread mode executor, using WFE/SEV. + /// + /// This is the simplest and most common kind of executor. It runs on + /// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction + /// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction + /// is executed, to make the `WFE` exit from sleep and poll the task. + /// + /// This executor allows for ultra low power consumption for chips where `WFE` + /// triggers low-power sleep without extra steps. If your chip requires extra steps, + /// you may use [`raw::Executor`] directly to program custom behavior. + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(THREAD_PENDER as *mut ()), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + asm!("wfe"); + }; + } + } + } +} + +#[cfg(feature = "executor-interrupt")] +pub use interrupt::*; +#[cfg(feature = "executor-interrupt")] +mod interrupt { + use core::cell::{Cell, UnsafeCell}; + use core::mem::MaybeUninit; + + use cortex_m::interrupt::InterruptNumber; + use cortex_m::peripheral::NVIC; + use critical_section::Mutex; + + use crate::raw; + + /// Interrupt mode executor. + /// + /// This executor runs tasks in interrupt mode. The interrupt handler is set up + /// to poll tasks, and when a task is woken the interrupt is pended from software. + /// + /// This allows running async tasks at a priority higher than thread mode. One + /// use case is to leave thread mode free for non-async tasks. Another use case is + /// to run multiple executors: one in thread mode for low priority tasks and another in + /// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower + /// priority ones. + /// + /// It is even possible to run multiple interrupt mode executors at different priorities, + /// by assigning different priorities to the interrupts. For an example on how to do this, + /// See the 'multiprio' example for 'embassy-nrf'. + /// + /// To use it, you have to pick an interrupt that won't be used by the hardware. + /// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI). + /// If this is not the case, you may use an interrupt from any unused peripheral. + /// + /// It is somewhat more complex to use, it's recommended to use the thread-mode + /// [`Executor`] instead, if it works for your use case. + pub struct InterruptExecutor { + started: Mutex>, + executor: UnsafeCell>, + } + + unsafe impl Send for InterruptExecutor {} + unsafe impl Sync for InterruptExecutor {} + + impl InterruptExecutor { + /// Create a new, not started `InterruptExecutor`. + #[inline] + pub const fn new() -> Self { + Self { + started: Mutex::new(Cell::new(false)), + executor: UnsafeCell::new(MaybeUninit::uninit()), + } + } + + /// Executor interrupt callback. + /// + /// # Safety + /// + /// - You MUST call this from the interrupt handler, and from nowhere else. + /// - You must not call this before calling `start()`. + pub unsafe fn on_interrupt(&'static self) { + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + executor.poll(); + } + + /// Start the executor. + /// + /// This initializes the executor, enables the interrupt, and returns. + /// The executor keeps running in the background through the interrupt. + /// + /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`] + /// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a + /// different "thread" (the interrupt), so spawning tasks on it is effectively + /// sending them. + /// + /// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from + /// a task running in it. + /// + /// # Interrupt requirements + /// + /// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt). + /// + /// This method already enables (unmasks) the interrupt, you must NOT do it yourself. + /// + /// You must set the interrupt priority before calling this method. You MUST NOT + /// do it after. + /// + pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner { + if critical_section::with(|cs| self.started.borrow(cs).replace(true)) { + panic!("InterruptExecutor::start() called multiple times on the same executor."); + } + + unsafe { + (&mut *self.executor.get()) + .as_mut_ptr() + .write(raw::Executor::new(irq.number() as *mut ())) + } + + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + + unsafe { NVIC::unmask(irq) } + + executor.spawner().make_send() + } + + /// Get a SendSpawner for this executor + /// + /// This returns a [`SendSpawner`] you can use to spawn tasks on this + /// executor. + /// + /// This MUST only be called on an executor that has already been started. + /// The function will panic otherwise. + pub fn spawner(&'static self) -> crate::SendSpawner { + if !critical_section::with(|cs| self.started.borrow(cs).get()) { + panic!("InterruptExecutor::spawner() called on uninitialized executor."); + } + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + executor.spawner().make_send() + } + } +} diff --git a/embassy/embassy-executor/src/arch/riscv32.rs b/embassy/embassy-executor/src/arch/riscv32.rs new file mode 100644 index 0000000..01e63a9 --- /dev/null +++ b/embassy/embassy-executor/src/arch/riscv32.rs @@ -0,0 +1,80 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-riscv32`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + use core::sync::atomic::{AtomicBool, Ordering}; + + pub use embassy_executor_macros::main_riscv as main; + + use crate::{raw, Spawner}; + + /// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV + static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + + #[export_name = "__pender"] + fn __pender(_context: *mut ()) { + SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); + } + + /// RISCV32 Executor + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(core::ptr::null_mut()), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + // we do not care about race conditions between the load and store operations, interrupts + //will only set this value to true. + critical_section::with(|_| { + // if there is work to do, loop back to polling + // TODO can we relax this? + if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { + SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); + } + // if not, wait for interrupt + else { + core::arch::asm!("wfi"); + } + }); + // if an interrupt occurred while waiting, it will be serviced here + } + } + } + } +} diff --git a/embassy/embassy-executor/src/arch/spin.rs b/embassy/embassy-executor/src/arch/spin.rs new file mode 100644 index 0000000..3400236 --- /dev/null +++ b/embassy/embassy-executor/src/arch/spin.rs @@ -0,0 +1,58 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-spin`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + + pub use embassy_executor_macros::main_spin as main; + + use crate::{raw, Spawner}; + + #[export_name = "__pender"] + fn __pender(_context: *mut ()) {} + + /// Spin Executor + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(core::ptr::null_mut()), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { self.inner.poll() }; + } + } + } +} diff --git a/embassy/embassy-executor/src/arch/std.rs b/embassy/embassy-executor/src/arch/std.rs new file mode 100644 index 0000000..b02b159 --- /dev/null +++ b/embassy/embassy-executor/src/arch/std.rs @@ -0,0 +1,94 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-std`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use std::marker::PhantomData; + use std::sync::{Condvar, Mutex}; + + pub use embassy_executor_macros::main_std as main; + + use crate::{raw, Spawner}; + + #[export_name = "__pender"] + fn __pender(context: *mut ()) { + let signaler: &'static Signaler = unsafe { std::mem::transmute(context) }; + signaler.signal() + } + + /// Single-threaded std-based executor. + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + signaler: &'static Signaler, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + let signaler = Box::leak(Box::new(Signaler::new())); + Self { + inner: raw::Executor::new(signaler as *mut Signaler as *mut ()), + not_send: PhantomData, + signaler, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { self.inner.poll() }; + self.signaler.wait() + } + } + } + + struct Signaler { + mutex: Mutex, + condvar: Condvar, + } + + impl Signaler { + fn new() -> Self { + Self { + mutex: Mutex::new(false), + condvar: Condvar::new(), + } + } + + fn wait(&self) { + let mut signaled = self.mutex.lock().unwrap(); + while !*signaled { + signaled = self.condvar.wait(signaled).unwrap(); + } + *signaled = false; + } + + fn signal(&self) { + let mut signaled = self.mutex.lock().unwrap(); + *signaled = true; + self.condvar.notify_one(); + } + } +} diff --git a/embassy/embassy-executor/src/arch/wasm.rs b/embassy/embassy-executor/src/arch/wasm.rs new file mode 100644 index 0000000..f9d0f93 --- /dev/null +++ b/embassy/embassy-executor/src/arch/wasm.rs @@ -0,0 +1,83 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-wasm`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + + use core::marker::PhantomData; + + pub use embassy_executor_macros::main_wasm as main; + use js_sys::Promise; + use wasm_bindgen::prelude::*; + + use crate::raw::util::UninitCell; + use crate::{raw, Spawner}; + + #[export_name = "__pender"] + fn __pender(context: *mut ()) { + let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) }; + let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() }); + } + + pub(crate) struct WasmContext { + promise: Promise, + closure: UninitCell>, + } + + impl WasmContext { + pub fn new() -> Self { + Self { + promise: Promise::resolve(&JsValue::undefined()), + closure: UninitCell::uninit(), + } + } + } + + /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. + pub struct Executor { + inner: raw::Executor, + ctx: &'static WasmContext, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + let ctx = Box::leak(Box::new(WasmContext::new())); + Self { + inner: raw::Executor::new(ctx as *mut WasmContext as *mut ()), + ctx, + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + pub fn start(&'static mut self, init: impl FnOnce(Spawner)) { + unsafe { + let executor = &self.inner; + let future = Closure::new(move |_| { + executor.poll(); + }); + self.ctx.closure.write_in_place(|| future); + init(self.inner.spawner()); + } + } + } +} diff --git a/embassy/embassy-executor/src/fmt.rs b/embassy/embassy-executor/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-executor/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-executor/src/lib.rs b/embassy/embassy-executor/src/lib.rs new file mode 100644 index 0000000..d816539 --- /dev/null +++ b/embassy/embassy-executor/src/lib.rs @@ -0,0 +1,150 @@ +#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)] +#![allow(clippy::new_without_default)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +pub use embassy_executor_macros::task; + +macro_rules! check_at_most_one { + (@amo [$($feats:literal)*] [] [$($res:tt)*]) => { + #[cfg(any($($res)*))] + compile_error!(concat!("At most one of these features can be enabled at the same time:", $(" `", $feats, "`",)*)); + }; + (@amo $feats:tt [$curr:literal $($rest:literal)*] [$($res:tt)*]) => { + check_at_most_one!(@amo $feats [$($rest)*] [$($res)* $(all(feature=$curr, feature=$rest),)*]); + }; + ($($f:literal),*$(,)?) => { + check_at_most_one!(@amo [$($f)*] [$($f)*] []); + }; +} +check_at_most_one!( + "arch-avr", + "arch-cortex-m", + "arch-riscv32", + "arch-std", + "arch-wasm", + "arch-spin", +); + +#[cfg(feature = "_arch")] +#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")] +#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")] +#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")] +#[cfg_attr(feature = "arch-std", path = "arch/std.rs")] +#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")] +#[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")] +mod arch; + +#[cfg(feature = "_arch")] +#[allow(unused_imports)] // don't warn if the module is empty. +pub use arch::*; + +pub mod raw; + +mod spawner; +pub use spawner::*; + +mod config { + #![allow(unused)] + include!(concat!(env!("OUT_DIR"), "/config.rs")); +} + +/// Implementation details for embassy macros. +/// Do not use. Used for macros and HALs only. Not covered by semver guarantees. +#[doc(hidden)] +#[cfg(not(feature = "nightly"))] +pub mod _export { + use core::alloc::Layout; + use core::cell::{Cell, UnsafeCell}; + use core::future::Future; + use core::mem::MaybeUninit; + use core::ptr::null_mut; + + use critical_section::{CriticalSection, Mutex}; + + use crate::raw::TaskPool; + + struct Arena { + buf: UnsafeCell>, + ptr: Mutex>, + } + + unsafe impl Sync for Arena {} + unsafe impl Send for Arena {} + + impl Arena { + const fn new() -> Self { + Self { + buf: UnsafeCell::new(MaybeUninit::uninit()), + ptr: Mutex::new(Cell::new(null_mut())), + } + } + + fn alloc(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit { + let layout = Layout::new::(); + + let start = self.buf.get().cast::(); + let end = unsafe { start.add(N) }; + + let mut ptr = self.ptr.borrow(cs).get(); + if ptr.is_null() { + ptr = self.buf.get().cast::(); + } + + let bytes_left = (end as usize) - (ptr as usize); + let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize); + + if align_offset + layout.size() > bytes_left { + panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/"); + } + + let res = unsafe { ptr.add(align_offset) }; + let ptr = unsafe { ptr.add(align_offset + layout.size()) }; + + self.ptr.borrow(cs).set(ptr); + + unsafe { &mut *(res as *mut MaybeUninit) } + } + } + + static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new(); + + pub struct TaskPoolRef { + // type-erased `&'static mut TaskPool` + // Needed because statics can't have generics. + ptr: Mutex>, + } + unsafe impl Sync for TaskPoolRef {} + unsafe impl Send for TaskPoolRef {} + + impl TaskPoolRef { + pub const fn new() -> Self { + Self { + ptr: Mutex::new(Cell::new(null_mut())), + } + } + + /// Get the pool for this ref, allocating it from the arena the first time. + /// + /// safety: for a given TaskPoolRef instance, must always call with the exact + /// same generic params. + pub unsafe fn get(&'static self) -> &'static TaskPool { + critical_section::with(|cs| { + let ptr = self.ptr.borrow(cs); + if ptr.get().is_null() { + let pool = ARENA.alloc::>(cs); + pool.write(TaskPool::new()); + ptr.set(pool as *mut _ as _); + } + + unsafe { &*(ptr.get() as *const _) } + }) + } + } +} diff --git a/embassy/embassy-executor/src/raw/mod.rs b/embassy/embassy-executor/src/raw/mod.rs new file mode 100644 index 0000000..e38a2af --- /dev/null +++ b/embassy/embassy-executor/src/raw/mod.rs @@ -0,0 +1,571 @@ +//! Raw executor. +//! +//! This module exposes "raw" Executor and Task structs for more low level control. +//! +//! ## WARNING: here be dragons! +//! +//! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe +//! [executor wrappers](crate::Executor) and the [`embassy_executor::task`](embassy_executor_macros::task) macro, which are fully safe. + +#[cfg_attr(target_has_atomic = "ptr", path = "run_queue_atomics.rs")] +#[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")] +mod run_queue; + +#[cfg_attr(all(cortex_m, target_has_atomic = "8"), path = "state_atomics_arm.rs")] +#[cfg_attr(all(not(cortex_m), target_has_atomic = "8"), path = "state_atomics.rs")] +#[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")] +mod state; + +pub mod timer_queue; +#[cfg(feature = "trace")] +mod trace; +pub(crate) mod util; +#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")] +mod waker; + +use core::future::Future; +use core::marker::PhantomData; +use core::mem; +use core::pin::Pin; +use core::ptr::NonNull; +use core::sync::atomic::{AtomicPtr, Ordering}; +use core::task::{Context, Poll}; + +use self::run_queue::{RunQueue, RunQueueItem}; +use self::state::State; +use self::util::{SyncUnsafeCell, UninitCell}; +pub use self::waker::task_from_waker; +use super::SpawnToken; + +/// Raw task header for use in task pointers. +/// +/// A task can be in one of the following states: +/// +/// - Not spawned: the task is ready to spawn. +/// - `SPAWNED`: the task is currently spawned and may be running. +/// - `RUN_ENQUEUED`: the task is enqueued to be polled. Note that the task may be `!SPAWNED`. +/// In this case, the `RUN_ENQUEUED` state will be cleared when the task is next polled, without +/// polling the task's future. +/// +/// A task's complete life cycle is as follows: +/// +/// ```text +/// ┌────────────┐ ┌────────────────────────┐ +/// │Not spawned │◄─5┤Not spawned|Run enqueued│ +/// │ ├6─►│ │ +/// └─────┬──────┘ └──────▲─────────────────┘ +/// 1 │ +/// │ ┌────────────┘ +/// │ 4 +/// ┌─────▼────┴─────────┐ +/// │Spawned|Run enqueued│ +/// │ │ +/// └─────┬▲─────────────┘ +/// 2│ +/// │3 +/// ┌─────▼┴─────┐ +/// │ Spawned │ +/// │ │ +/// └────────────┘ +/// ``` +/// +/// Transitions: +/// - 1: Task is spawned - `AvailableTask::claim -> Executor::spawn` +/// - 2: During poll - `RunQueue::dequeue_all -> State::run_dequeue` +/// - 3: Task wakes itself, waker wakes task, or task exits - `Waker::wake -> wake_task -> State::run_enqueue` +/// - 4: A run-queued task exits - `TaskStorage::poll -> Poll::Ready` +/// - 5: Task is dequeued. The task's future is not polled, because exiting the task replaces its `poll_fn`. +/// - 6: A task is waken when it is not spawned - `wake_task -> State::run_enqueue` +pub(crate) struct TaskHeader { + pub(crate) state: State, + pub(crate) run_queue_item: RunQueueItem, + pub(crate) executor: AtomicPtr, + poll_fn: SyncUnsafeCell>, + + /// Integrated timer queue storage. This field should not be accessed outside of the timer queue. + pub(crate) timer_queue_item: timer_queue::TimerQueueItem, +} + +/// This is essentially a `&'static TaskStorage` where the type of the future has been erased. +#[derive(Clone, Copy, PartialEq)] +pub struct TaskRef { + ptr: NonNull, +} + +unsafe impl Send for TaskRef where &'static TaskHeader: Send {} +unsafe impl Sync for TaskRef where &'static TaskHeader: Sync {} + +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), + } + } + + /// # Safety + /// + /// The result of this function must only be compared + /// for equality, or stored, but not used. + pub const unsafe fn dangling() -> Self { + Self { + ptr: NonNull::dangling(), + } + } + + pub(crate) fn header(self) -> &'static TaskHeader { + unsafe { self.ptr.as_ref() } + } + + /// Returns a reference to the executor that the task is currently running on. + pub unsafe fn executor(self) -> Option<&'static Executor> { + let executor = self.header().executor.load(Ordering::Relaxed); + executor.as_ref().map(|e| Executor::wrap(e)) + } + + /// Returns a reference to the timer queue item. + pub fn timer_queue_item(&self) -> &'static timer_queue::TimerQueueItem { + &self.header().timer_queue_item + } + + /// The returned pointer is valid for the entire TaskStorage. + pub(crate) fn as_ptr(self) -> *const TaskHeader { + self.ptr.as_ptr() + } +} + +/// Raw storage in which a task can be spawned. +/// +/// This struct holds the necessary memory to spawn one task whose future is `F`. +/// At a given time, the `TaskStorage` may be in spawned or not-spawned state. You +/// may spawn it with [`TaskStorage::spawn()`], which will fail if it is already spawned. +/// +/// A `TaskStorage` must live forever, it may not be deallocated even after the task has finished +/// running. Hence the relevant methods require `&'static self`. It may be reused, however. +/// +/// Internally, the [embassy_executor::task](embassy_executor_macros::task) macro allocates an array of `TaskStorage`s +/// in a `static`. The most common reason to use the raw `Task` is to have control of where +/// the memory for the task is allocated: on the stack, or on the heap with e.g. `Box::leak`, etc. + +// repr(C) is needed to guarantee that the Task is located at offset 0 +// This makes it safe to cast between TaskHeader and TaskStorage pointers. +#[repr(C)] +pub struct TaskStorage { + raw: TaskHeader, + future: UninitCell, // Valid if STATE_SPAWNED +} + +unsafe fn poll_exited(_p: TaskRef) { + // Nothing to do, the task is already !SPAWNED and dequeued. +} + +impl TaskStorage { + const NEW: Self = Self::new(); + + /// Create a new TaskStorage, in not-spawned state. + pub const fn new() -> Self { + Self { + raw: TaskHeader { + state: State::new(), + run_queue_item: RunQueueItem::new(), + executor: AtomicPtr::new(core::ptr::null_mut()), + // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` + poll_fn: SyncUnsafeCell::new(None), + + timer_queue_item: timer_queue::TimerQueueItem::new(), + }, + future: UninitCell::uninit(), + } + } + + /// Try to spawn the task. + /// + /// The `future` closure constructs the future. It's only called if spawning is + /// actually possible. It is a closure instead of a simple `future: F` param to ensure + /// the future is constructed in-place, avoiding a temporary copy in the stack thanks to + /// NRVO optimizations. + /// + /// This function will fail if the task is already spawned and has not finished running. + /// In this case, the error is delayed: a "poisoned" SpawnToken is returned, which will + /// cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. + /// + /// 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 { + let task = AvailableTask::claim(self); + match task { + Some(task) => task.initialize(future), + None => SpawnToken::new_failed(), + } + } + + unsafe fn poll(p: TaskRef) { + let this = &*p.as_ptr().cast::>(); + + let future = Pin::new_unchecked(this.future.as_mut()); + let waker = waker::from_task(p); + let mut cx = Context::from_waker(&waker); + match future.poll(&mut cx) { + Poll::Ready(_) => { + // As the future has finished and this function will not be called + // again, we can safely drop the future here. + this.future.drop_in_place(); + + // We replace the poll_fn with a despawn function, so that the task is cleaned up + // when the executor polls it next. + this.raw.poll_fn.set(Some(poll_exited)); + + // Make sure we despawn last, so that other threads can only spawn the task + // after we're done with it. + this.raw.state.despawn(); + } + Poll::Pending => {} + } + + // the compiler is emitting a virtual call for waker drop, but we know + // it's a noop for our waker. + mem::forget(waker); + } + + #[doc(hidden)] + #[allow(dead_code)] + fn _assert_sync(self) { + fn assert_sync(_: T) {} + + assert_sync(self) + } +} + +/// An uninitialized [`TaskStorage`]. +pub struct AvailableTask { + task: &'static TaskStorage, +} + +impl AvailableTask { + /// Try to claim a [`TaskStorage`]. + /// + /// This function returns `None` if a task has already been spawned and has not finished running. + pub fn claim(task: &'static TaskStorage) -> Option { + task.raw.state.spawn().then(|| Self { task }) + } + + fn initialize_impl(self, future: impl FnOnce() -> F) -> SpawnToken { + unsafe { + self.task.raw.poll_fn.set(Some(TaskStorage::::poll)); + self.task.future.write_in_place(future); + + let task = TaskRef::new(self.task); + + SpawnToken::new(task) + } + } + + /// Initialize the [`TaskStorage`] to run the given future. + pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken { + self.initialize_impl::(future) + } + + /// Initialize the [`TaskStorage`] to run the given future. + /// + /// # Safety + /// + /// `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` + /// is an `async fn`, NOT a hand-written `Future`. + #[doc(hidden)] + pub unsafe fn __initialize_async_fn(self, future: impl FnOnce() -> F) -> SpawnToken { + // When send-spawning a task, we construct the future in this thread, and effectively + // "send" it to the executor thread by enqueuing it in its queue. Therefore, in theory, + // send-spawning should require the future `F` to be `Send`. + // + // The problem is this is more restrictive than needed. Once the future is executing, + // it is never sent to another thread. It is only sent when spawning. It should be + // enough for the task's arguments to be Send. (and in practice it's super easy to + // accidentally make your futures !Send, for example by holding an `Rc` or a `&RefCell` across an `.await`.) + // + // We can do it by sending the task args and constructing the future in the executor thread + // on first poll. However, this cannot be done in-place, so it'll waste stack space for a copy + // of the args. + // + // Luckily, an `async fn` future contains just the args when freshly constructed. So, if the + // args are Send, it's OK to send a !Send future, as long as we do it before first polling it. + // + // (Note: this is how the generators are implemented today, it's not officially guaranteed yet, + // but it's possible it'll be guaranteed in the future. See zulip thread: + // https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/.22only.20before.20poll.22.20Send.20futures ) + // + // The `FutFn` captures all the args, so if it's Send, the task can be send-spawned. + // This is why we return `SpawnToken` below. + // + // 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`. + self.initialize_impl::(future) + } +} + +/// Raw storage that can hold up to N tasks of the same type. +/// +/// This is essentially a `[TaskStorage; N]`. +pub struct TaskPool { + pool: [TaskStorage; N], +} + +impl TaskPool { + /// Create a new TaskPool, with all tasks in non-spawned state. + pub const fn new() -> Self { + Self { + pool: [TaskStorage::NEW; N], + } + } + + fn spawn_impl(&'static self, future: impl FnOnce() -> F) -> SpawnToken { + match self.pool.iter().find_map(AvailableTask::claim) { + Some(task) => task.initialize_impl::(future), + None => SpawnToken::new_failed(), + } + } + + /// Try to spawn a task in the pool. + /// + /// See [`TaskStorage::spawn()`] for details. + /// + /// This will loop over the pool and spawn the task in the first storage that + /// 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 { + self.spawn_impl::(future) + } + + /// Like spawn(), but allows the task to be send-spawned if the args are Send even if + /// the future is !Send. + /// + /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used + /// by the Embassy macros ONLY. + /// + /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` + /// is an `async fn`, NOT a hand-written `Future`. + #[doc(hidden)] + pub unsafe fn _spawn_async_fn(&'static self, future: FutFn) -> SpawnToken + where + FutFn: FnOnce() -> F, + { + // See the comment in AvailableTask::__initialize_async_fn for explanation. + self.spawn_impl::(future) + } +} + +#[derive(Clone, Copy)] +pub(crate) struct Pender(*mut ()); + +unsafe impl Send for Pender {} +unsafe impl Sync for Pender {} + +impl Pender { + pub(crate) fn pend(self) { + extern "Rust" { + fn __pender(context: *mut ()); + } + unsafe { __pender(self.0) }; + } +} + +pub(crate) struct SyncExecutor { + run_queue: RunQueue, + pender: Pender, +} + +impl SyncExecutor { + pub(crate) fn new(pender: Pender) -> Self { + Self { + run_queue: RunQueue::new(), + pender, + } + } + + /// Enqueue a task in the task queue + /// + /// # Safety + /// - `task` must be a valid pointer to a spawned task. + /// - `task` must be set up to run in this executor. + /// - `task` must NOT be already enqueued (in this executor or another one). + #[inline(always)] + unsafe fn enqueue(&self, task: TaskRef, l: state::Token) { + #[cfg(feature = "trace")] + trace::task_ready_begin(self, &task); + + if self.run_queue.enqueue(task, l) { + self.pender.pend(); + } + } + + pub(super) unsafe fn spawn(&'static self, task: TaskRef) { + task.header() + .executor + .store((self as *const Self).cast_mut(), Ordering::Relaxed); + + #[cfg(feature = "trace")] + trace::task_new(self, &task); + + state::locked(|l| { + self.enqueue(task, l); + }) + } + + /// # Safety + /// + /// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created. + pub(crate) unsafe fn poll(&'static self) { + self.run_queue.dequeue_all(|p| { + let task = p.header(); + + #[cfg(feature = "trace")] + trace::task_exec_begin(self, &p); + + // Run the task + task.poll_fn.get().unwrap_unchecked()(p); + + #[cfg(feature = "trace")] + trace::task_exec_end(self, &p); + }); + + #[cfg(feature = "trace")] + trace::executor_idle(self) + } +} + +/// Raw executor. +/// +/// This is the core of the Embassy executor. It is low-level, requiring manual +/// handling of wakeups and task polling. If you can, prefer using one of the +/// [higher level executors](crate::Executor). +/// +/// The raw executor leaves it up to you to handle wakeups and scheduling: +/// +/// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks +/// that "want to run"). +/// - You must supply a pender function, as shown below. The executor will call it to notify you +/// it has work to do. You must arrange for `poll()` to be called as soon as possible. +/// - Enabling `arch-xx` features will define a pender function for you. This means that you +/// are limited to using the executors provided to you by the architecture/platform +/// implementation. If you need a different executor, you must not enable `arch-xx` features. +/// +/// The pender can be called from *any* context: any thread, any interrupt priority +/// level, etc. It may be called synchronously from any `Executor` method call as well. +/// You must deal with this correctly. +/// +/// In particular, you must NOT call `poll` directly from the pender callback, as this violates +/// the requirement for `poll` to not be called reentrantly. +/// +/// The pender function must be exported with the name `__pender` and have the following signature: +/// +/// ```rust +/// #[export_name = "__pender"] +/// fn pender(context: *mut ()) { +/// // schedule `poll()` to be called +/// } +/// ``` +/// +/// The `context` argument is a piece of arbitrary data the executor will pass to the pender. +/// You can set the `context` when calling [`Executor::new()`]. You can use it to, for example, +/// differentiate between executors, or to pass a pointer to a callback that should be called. +#[repr(transparent)] +pub struct Executor { + pub(crate) inner: SyncExecutor, + + _not_sync: PhantomData<*mut ()>, +} + +impl Executor { + pub(crate) unsafe fn wrap(inner: &SyncExecutor) -> &Self { + mem::transmute(inner) + } + + /// Create a new executor. + /// + /// When the executor has work to do, it will call the pender function and pass `context` to it. + /// + /// See [`Executor`] docs for details on the pender. + pub fn new(context: *mut ()) -> Self { + Self { + inner: SyncExecutor::new(Pender(context)), + _not_sync: PhantomData, + } + } + + /// Spawn a task in this executor. + /// + /// # Safety + /// + /// `task` must be a valid pointer to an initialized but not-already-spawned task. + /// + /// 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: TaskRef) { + self.inner.spawn(task) + } + + /// Poll all queued tasks in this executor. + /// + /// This loops over all tasks that are queued to be polled (i.e. they're + /// freshly spawned or they've been woken). Other tasks are not polled. + /// + /// You must call `poll` after receiving a call to the pender. It is OK + /// to call `poll` even when not requested by the pender, but it wastes + /// energy. + /// + /// # Safety + /// + /// You must call `initialize` before calling this method. + /// + /// You must NOT call `poll` reentrantly on the same executor. + /// + /// In particular, note that `poll` may call the pender synchronously. Therefore, you + /// must NOT directly call `poll()` from the pender callback. Instead, the callback has to + /// 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) { + self.inner.poll() + } + + /// Get a spawner that spawns tasks in this executor. + /// + /// It is OK to call this method multiple times to obtain multiple + /// `Spawner`s. You may also copy `Spawner`s. + pub fn spawner(&'static self) -> super::Spawner { + super::Spawner::new(self) + } +} + +/// Wake a task by `TaskRef`. +/// +/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. +pub fn wake_task(task: TaskRef) { + let header = task.header(); + header.state.run_enqueue(|l| { + // We have just marked the task as scheduled, so enqueue it. + unsafe { + let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked(); + executor.enqueue(task, l); + } + }); +} + +/// Wake a task by `TaskRef` without calling pend. +/// +/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. +pub fn wake_task_no_pend(task: TaskRef) { + let header = task.header(); + header.state.run_enqueue(|l| { + // We have just marked the task as scheduled, so enqueue it. + unsafe { + let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked(); + executor.run_queue.enqueue(task, l); + } + }); +} diff --git a/embassy/embassy-executor/src/raw/run_queue_atomics.rs b/embassy/embassy-executor/src/raw/run_queue_atomics.rs new file mode 100644 index 0000000..ce511d7 --- /dev/null +++ b/embassy/embassy-executor/src/raw/run_queue_atomics.rs @@ -0,0 +1,88 @@ +use core::ptr; +use core::ptr::NonNull; +use core::sync::atomic::{AtomicPtr, Ordering}; + +use super::{TaskHeader, TaskRef}; +use crate::raw::util::SyncUnsafeCell; + +pub(crate) struct RunQueueItem { + next: SyncUnsafeCell>, +} + +impl RunQueueItem { + pub const fn new() -> Self { + Self { + next: SyncUnsafeCell::new(None), + } + } +} + +/// Atomic task queue using a very, very simple lock-free linked-list queue: +/// +/// To enqueue a task, task.next is set to the old head, and head is atomically set to task. +/// +/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with +/// null. Then the batch is iterated following the next pointers until null is reached. +/// +/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK +/// for our purposes: it can't create fairness problems since the next batch won't run until the +/// current batch is completely processed, so even if a task enqueues itself instantly (for example +/// by waking its own waker) can't prevent other tasks from running. +pub(crate) struct RunQueue { + head: AtomicPtr, +} + +impl RunQueue { + pub const fn new() -> Self { + Self { + head: AtomicPtr::new(ptr::null_mut()), + } + } + + /// Enqueues an item. Returns true if the queue was empty. + /// + /// # Safety + /// + /// `item` must NOT be already enqueued in any queue. + #[inline(always)] + pub(crate) unsafe fn enqueue(&self, task: TaskRef, _: super::state::Token) -> bool { + let mut was_empty = false; + + self.head + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| { + was_empty = prev.is_null(); + unsafe { + // safety: the pointer is either null or valid + let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())); + // safety: there are no concurrent accesses to `next` + task.header().run_queue_item.next.set(prev); + } + Some(task.as_ptr() as *mut _) + }) + .ok(); + + was_empty + } + + /// 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(TaskRef)) { + // Atomically empty the queue. + let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); + + // safety: the pointer is either null or valid + let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) }; + + // Iterate the linked list of tasks that were previously in the queue. + while let Some(task) = next { + // If the task re-enqueues itself, the `next` pointer will get overwritten. + // Therefore, first read the next pointer, and only then process the task. + // safety: there are no concurrent accesses to `next` + next = unsafe { task.header().run_queue_item.next.get() }; + + task.header().state.run_dequeue(); + on_task(task); + } + } +} diff --git a/embassy/embassy-executor/src/raw/run_queue_critical_section.rs b/embassy/embassy-executor/src/raw/run_queue_critical_section.rs new file mode 100644 index 0000000..86c4085 --- /dev/null +++ b/embassy/embassy-executor/src/raw/run_queue_critical_section.rs @@ -0,0 +1,74 @@ +use core::cell::Cell; + +use critical_section::{CriticalSection, Mutex}; + +use super::TaskRef; + +pub(crate) struct RunQueueItem { + next: Mutex>>, +} + +impl RunQueueItem { + pub const fn new() -> Self { + Self { + next: Mutex::new(Cell::new(None)), + } + } +} + +/// Atomic task queue using a very, very simple lock-free linked-list queue: +/// +/// To enqueue a task, task.next is set to the old head, and head is atomically set to task. +/// +/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with +/// null. Then the batch is iterated following the next pointers until null is reached. +/// +/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK +/// for our purposes: it can't create fairness problems since the next batch won't run until the +/// current batch is completely processed, so even if a task enqueues itself instantly (for example +/// by waking its own waker) can't prevent other tasks from running. +pub(crate) struct RunQueue { + head: Mutex>>, +} + +impl RunQueue { + pub const fn new() -> Self { + Self { + head: Mutex::new(Cell::new(None)), + } + } + + /// Enqueues an item. Returns true if the queue was empty. + /// + /// # Safety + /// + /// `item` must NOT be already enqueued in any queue. + #[inline(always)] + pub(crate) unsafe fn enqueue(&self, task: TaskRef, cs: CriticalSection<'_>) -> bool { + let prev = self.head.borrow(cs).replace(Some(task)); + task.header().run_queue_item.next.borrow(cs).set(prev); + + prev.is_none() + } + + /// 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(TaskRef)) { + // Atomically empty the queue. + let mut next = critical_section::with(|cs| self.head.borrow(cs).take()); + + // Iterate the linked list of tasks that were previously in the queue. + while let Some(task) = next { + // If the task re-enqueues itself, the `next` pointer will get overwritten. + // Therefore, first read the next pointer, and only then process the task. + + critical_section::with(|cs| { + next = task.header().run_queue_item.next.borrow(cs).get(); + task.header().state.run_dequeue(cs); + }); + + on_task(task); + } + } +} diff --git a/embassy/embassy-executor/src/raw/state_atomics.rs b/embassy/embassy-executor/src/raw/state_atomics.rs new file mode 100644 index 0000000..b6576bf --- /dev/null +++ b/embassy/embassy-executor/src/raw/state_atomics.rs @@ -0,0 +1,58 @@ +use core::sync::atomic::{AtomicU32, Ordering}; + +#[derive(Clone, Copy)] +pub(crate) struct Token(()); + +/// Creates a token and passes it to the closure. +/// +/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking. +pub(crate) fn locked(f: impl FnOnce(Token) -> R) -> R { + f(Token(())) +} + +/// Task is spawned (has a future) +pub(crate) const STATE_SPAWNED: u32 = 1 << 0; +/// Task is in the executor run queue +pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; + +pub(crate) struct State { + state: AtomicU32, +} + +impl State { + pub const fn new() -> State { + Self { + state: AtomicU32::new(0), + } + } + + /// If task is idle, mark it as spawned + run_queued and return true. + #[inline(always)] + pub fn spawn(&self) -> bool { + self.state + .compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + } + + /// Unmark the task as spawned. + #[inline(always)] + pub fn despawn(&self) { + self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); + } + + /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given + /// function if the task was successfully marked. + #[inline(always)] + pub fn run_enqueue(&self, f: impl FnOnce(Token)) { + let prev = self.state.fetch_or(STATE_RUN_QUEUED, Ordering::AcqRel); + if prev & STATE_RUN_QUEUED == 0 { + locked(f); + } + } + + /// Unmark the task as run-queued. Return whether the task is spawned. + #[inline(always)] + pub fn run_dequeue(&self) { + self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); + } +} diff --git a/embassy/embassy-executor/src/raw/state_atomics_arm.rs b/embassy/embassy-executor/src/raw/state_atomics_arm.rs new file mode 100644 index 0000000..b743dcc --- /dev/null +++ b/embassy/embassy-executor/src/raw/state_atomics_arm.rs @@ -0,0 +1,83 @@ +use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering}; + +#[derive(Clone, Copy)] +pub(crate) struct Token(()); + +/// Creates a token and passes it to the closure. +/// +/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking. +pub(crate) fn locked(f: impl FnOnce(Token) -> R) -> R { + f(Token(())) +} + +// Must be kept in sync with the layout of `State`! +pub(crate) const STATE_SPAWNED: u32 = 1 << 0; +pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8; + +#[repr(C, align(4))] +pub(crate) struct State { + /// Task is spawned (has a future) + spawned: AtomicBool, + /// Task is in the executor run queue + run_queued: AtomicBool, + pad: AtomicBool, + pad2: AtomicBool, +} + +impl State { + pub const fn new() -> State { + Self { + spawned: AtomicBool::new(false), + run_queued: AtomicBool::new(false), + pad: AtomicBool::new(false), + pad2: AtomicBool::new(false), + } + } + + fn as_u32(&self) -> &AtomicU32 { + unsafe { &*(self as *const _ as *const AtomicU32) } + } + + /// If task is idle, mark it as spawned + run_queued and return true. + #[inline(always)] + pub fn spawn(&self) -> bool { + compiler_fence(Ordering::Release); + let r = self + .as_u32() + .compare_exchange( + 0, + STATE_SPAWNED | STATE_RUN_QUEUED, + Ordering::Relaxed, + Ordering::Relaxed, + ) + .is_ok(); + compiler_fence(Ordering::Acquire); + r + } + + /// Unmark the task as spawned. + #[inline(always)] + pub fn despawn(&self) { + compiler_fence(Ordering::Release); + self.spawned.store(false, Ordering::Relaxed); + } + + /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given + /// function if the task was successfully marked. + #[inline(always)] + pub fn run_enqueue(&self, f: impl FnOnce(Token)) { + let old = self.run_queued.swap(true, Ordering::AcqRel); + + if !old { + locked(f); + } + } + + /// Unmark the task as run-queued. Return whether the task is spawned. + #[inline(always)] + pub fn run_dequeue(&self) { + compiler_fence(Ordering::Release); + + self.run_queued.store(false, Ordering::Relaxed); + } +} diff --git a/embassy/embassy-executor/src/raw/state_critical_section.rs b/embassy/embassy-executor/src/raw/state_critical_section.rs new file mode 100644 index 0000000..6b627ff --- /dev/null +++ b/embassy/embassy-executor/src/raw/state_critical_section.rs @@ -0,0 +1,73 @@ +use core::cell::Cell; + +pub(crate) use critical_section::{with as locked, CriticalSection as Token}; +use critical_section::{CriticalSection, Mutex}; + +/// Task is spawned (has a future) +pub(crate) const STATE_SPAWNED: u32 = 1 << 0; +/// Task is in the executor run queue +pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; + +pub(crate) struct State { + state: Mutex>, +} + +impl State { + pub const fn new() -> State { + Self { + state: Mutex::new(Cell::new(0)), + } + } + + fn update(&self, f: impl FnOnce(&mut u32) -> R) -> R { + critical_section::with(|cs| self.update_with_cs(cs, f)) + } + + fn update_with_cs(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut u32) -> R) -> R { + let s = self.state.borrow(cs); + let mut val = s.get(); + let r = f(&mut val); + s.set(val); + r + } + + /// If task is idle, mark it as spawned + run_queued and return true. + #[inline(always)] + pub fn spawn(&self) -> bool { + self.update(|s| { + if *s == 0 { + *s = STATE_SPAWNED | STATE_RUN_QUEUED; + true + } else { + false + } + }) + } + + /// Unmark the task as spawned. + #[inline(always)] + pub fn despawn(&self) { + self.update(|s| *s &= !STATE_SPAWNED); + } + + /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given + /// function if the task was successfully marked. + #[inline(always)] + pub fn run_enqueue(&self, f: impl FnOnce(Token)) { + critical_section::with(|cs| { + if self.update_with_cs(cs, |s| { + let ok = *s & STATE_RUN_QUEUED == 0; + *s |= STATE_RUN_QUEUED; + ok + }) { + f(cs); + } + }); + } + + /// Unmark the task as run-queued. Return whether the task is spawned. + #[inline(always)] + pub fn run_dequeue(&self, cs: CriticalSection<'_>) { + self.update_with_cs(cs, |s| *s &= !STATE_RUN_QUEUED) + } +} diff --git a/embassy/embassy-executor/src/raw/timer_queue.rs b/embassy/embassy-executor/src/raw/timer_queue.rs new file mode 100644 index 0000000..e52453b --- /dev/null +++ b/embassy/embassy-executor/src/raw/timer_queue.rs @@ -0,0 +1,73 @@ +//! Timer queue operations. + +use core::cell::Cell; + +use super::TaskRef; + +#[cfg(feature = "_timer-item-payload")] +macro_rules! define_opaque { + ($size:tt) => { + /// An opaque data type. + #[repr(align($size))] + pub struct OpaqueData { + data: [u8; $size], + } + + impl OpaqueData { + const fn new() -> Self { + Self { data: [0; $size] } + } + + /// Access the data as a reference to a type `T`. + /// + /// Safety: + /// + /// The caller must ensure that the size of the type `T` is less than, or equal to + /// the size of the payload, and must ensure that the alignment of the type `T` is + /// less than, or equal to the alignment of the payload. + /// + /// The type must be valid when zero-initialized. + pub unsafe fn as_ref(&self) -> &T { + &*(self.data.as_ptr() as *const T) + } + } + }; +} + +#[cfg(feature = "timer-item-payload-size-1")] +define_opaque!(1); +#[cfg(feature = "timer-item-payload-size-2")] +define_opaque!(2); +#[cfg(feature = "timer-item-payload-size-4")] +define_opaque!(4); +#[cfg(feature = "timer-item-payload-size-8")] +define_opaque!(8); + +/// An item in the timer queue. +pub struct TimerQueueItem { + /// The next item in the queue. + /// + /// If this field contains `Some`, the item is in the queue. The last item in the queue has a + /// value of `Some(dangling_pointer)` + pub next: Cell>, + + /// The time at which this item expires. + pub expires_at: Cell, + + /// Some implementation-defined, zero-initialized piece of data. + #[cfg(feature = "_timer-item-payload")] + pub payload: OpaqueData, +} + +unsafe impl Sync for TimerQueueItem {} + +impl TimerQueueItem { + pub(crate) const fn new() -> Self { + Self { + next: Cell::new(None), + expires_at: Cell::new(0), + #[cfg(feature = "_timer-item-payload")] + payload: OpaqueData::new(), + } + } +} diff --git a/embassy/embassy-executor/src/raw/trace.rs b/embassy/embassy-executor/src/raw/trace.rs new file mode 100644 index 0000000..b34387b --- /dev/null +++ b/embassy/embassy-executor/src/raw/trace.rs @@ -0,0 +1,84 @@ +#![allow(unused)] +use crate::raw::{SyncExecutor, TaskRef}; + +#[cfg(not(feature = "rtos-trace"))] +extern "Rust" { + fn _embassy_trace_task_new(executor_id: u32, task_id: u32); + fn _embassy_trace_task_exec_begin(executor_id: u32, task_id: u32); + fn _embassy_trace_task_exec_end(excutor_id: u32, task_id: u32); + fn _embassy_trace_task_ready_begin(executor_id: u32, task_id: u32); + fn _embassy_trace_executor_idle(executor_id: u32); +} + +#[inline] +pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_new(executor as *const _ as u32, task.as_ptr() as u32) + } + + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_new(task.as_ptr() as u32); +} + +#[inline] +pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_ready_begin(executor as *const _ as u32, task.as_ptr() as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_ready_begin(task.as_ptr() as u32); +} + +#[inline] +pub(crate) fn task_exec_begin(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_exec_begin(executor as *const _ as u32, task.as_ptr() as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_exec_begin(task.as_ptr() as u32); +} + +#[inline] +pub(crate) fn task_exec_end(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_exec_end(executor as *const _ as u32, task.as_ptr() as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_exec_end(); +} + +#[inline] +pub(crate) fn executor_idle(executor: &SyncExecutor) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_executor_idle(executor as *const _ as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::system_idle(); +} + +#[cfg(feature = "rtos-trace")] +impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor { + fn task_list() { + // We don't know what tasks exist, so we can't send them. + } + fn time() -> u64 { + const fn gcd(a: u64, b: u64) -> u64 { + if b == 0 { + a + } else { + gcd(b, a % b) + } + } + + const GCD_1M: u64 = gcd(embassy_time_driver::TICK_HZ, 1_000_000); + embassy_time_driver::now() * (1_000_000 / GCD_1M) / (embassy_time_driver::TICK_HZ / GCD_1M) + } +} + +#[cfg(feature = "rtos-trace")] +rtos_trace::global_os_callbacks! {SyncExecutor} diff --git a/embassy/embassy-executor/src/raw/util.rs b/embassy/embassy-executor/src/raw/util.rs new file mode 100644 index 0000000..c46085e --- /dev/null +++ b/embassy/embassy-executor/src/raw/util.rs @@ -0,0 +1,57 @@ +use core::cell::UnsafeCell; +use core::mem::MaybeUninit; +use core::ptr; + +pub(crate) struct UninitCell(MaybeUninit>); +impl UninitCell { + pub const fn uninit() -> Self { + Self(MaybeUninit::uninit()) + } + + pub unsafe fn as_mut_ptr(&self) -> *mut T { + (*self.0.as_ptr()).get() + } + + #[allow(clippy::mut_from_ref)] + pub unsafe fn as_mut(&self) -> &mut T { + &mut *self.as_mut_ptr() + } + + #[inline(never)] + pub unsafe fn write_in_place(&self, func: impl FnOnce() -> T) { + ptr::write(self.as_mut_ptr(), func()) + } + + pub unsafe fn drop_in_place(&self) { + ptr::drop_in_place(self.as_mut_ptr()) + } +} + +unsafe impl Sync for UninitCell {} + +#[repr(transparent)] +pub struct SyncUnsafeCell { + value: UnsafeCell, +} + +unsafe impl Sync for SyncUnsafeCell {} + +impl SyncUnsafeCell { + #[inline] + pub const fn new(value: T) -> Self { + Self { + value: UnsafeCell::new(value), + } + } + + pub unsafe fn set(&self, value: T) { + *self.value.get() = value; + } + + pub unsafe fn get(&self) -> T + where + T: Copy, + { + *self.value.get() + } +} diff --git a/embassy/embassy-executor/src/raw/waker.rs b/embassy/embassy-executor/src/raw/waker.rs new file mode 100644 index 0000000..b7d57c3 --- /dev/null +++ b/embassy/embassy-executor/src/raw/waker.rs @@ -0,0 +1,42 @@ +use core::task::{RawWaker, RawWakerVTable, Waker}; + +use super::{wake_task, TaskHeader, TaskRef}; + +static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop); + +unsafe fn clone(p: *const ()) -> RawWaker { + RawWaker::new(p, &VTABLE) +} + +unsafe fn wake(p: *const ()) { + wake_task(TaskRef::from_ptr(p as *const TaskHeader)) +} + +unsafe fn drop(_: *const ()) { + // nop +} + +pub(crate) unsafe fn from_task(p: TaskRef) -> Waker { + Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE)) +} + +/// Get a task pointer from a waker. +/// +/// This can be used as an optimization in wait queues to store task pointers +/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps +/// avoid dynamic dispatch. +/// +/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task). +/// +/// # Panics +/// +/// Panics if the waker is not created by the Embassy executor. +pub fn task_from_waker(waker: &Waker) -> TaskRef { + // make sure to compare vtable addresses. Doing `==` on the references + // will compare the contents, which is slower. + if waker.vtable() as *const _ != &VTABLE as *const _ { + panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.") + } + // safety: our wakers are always created with `TaskRef::as_ptr` + unsafe { TaskRef::from_ptr(waker.data() as *const TaskHeader) } +} diff --git a/embassy/embassy-executor/src/raw/waker_turbo.rs b/embassy/embassy-executor/src/raw/waker_turbo.rs new file mode 100644 index 0000000..435a0ff --- /dev/null +++ b/embassy/embassy-executor/src/raw/waker_turbo.rs @@ -0,0 +1,34 @@ +use core::ptr::NonNull; +use core::task::Waker; + +use super::{wake_task, TaskHeader, TaskRef}; + +pub(crate) unsafe fn from_task(p: TaskRef) -> Waker { + Waker::from_turbo_ptr(NonNull::new_unchecked(p.as_ptr() as _)) +} + +/// Get a task pointer from a waker. +/// +/// This can be used as an optimization in wait queues to store task pointers +/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps +/// avoid dynamic dispatch. +/// +/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task). +/// +/// # Panics +/// +/// Panics if the waker is not created by the Embassy executor. +pub fn task_from_waker(waker: &Waker) -> TaskRef { + let ptr = waker.as_turbo_ptr().as_ptr(); + + // safety: our wakers are always created with `TaskRef::as_ptr` + unsafe { TaskRef::from_ptr(ptr as *const TaskHeader) } +} + +#[inline(never)] +#[no_mangle] +fn _turbo_wake(ptr: NonNull<()>) { + // safety: our wakers are always created with `TaskRef::as_ptr` + let task = unsafe { TaskRef::from_ptr(ptr.as_ptr() as *const TaskHeader) }; + wake_task(task) +} diff --git a/embassy/embassy-executor/src/spawner.rs b/embassy/embassy-executor/src/spawner.rs new file mode 100644 index 0000000..16347ad --- /dev/null +++ b/embassy/embassy-executor/src/spawner.rs @@ -0,0 +1,210 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::mem; +use core::sync::atomic::Ordering; +use core::task::Poll; + +use super::raw; + +/// Token to spawn a newly-created task in an executor. +/// +/// When calling a task function (like `#[embassy_executor::task] async fn my_task() { ... }`), the returned +/// value is a `SpawnToken` that represents an instance of the task, ready to spawn. You must +/// then spawn it into an executor, typically with [`Spawner::spawn()`]. +/// +/// The generic parameter `S` determines whether the task can be spawned in executors +/// in other threads or not. If `S: Send`, it can, which allows spawning it into a [`SendSpawner`]. +/// If not, it can't, so it can only be spawned into the current thread's executor, with [`Spawner`]. +/// +/// # Panics +/// +/// Dropping a SpawnToken instance panics. You may not "abort" spawning a task in this way. +/// 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, + phantom: PhantomData<*mut S>, +} + +impl SpawnToken { + pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self { + Self { + raw_task: Some(raw_task), + phantom: PhantomData, + } + } + + /// Return a SpawnToken that represents a failed spawn. + pub fn new_failed() -> Self { + Self { + raw_task: None, + phantom: PhantomData, + } + } +} + +impl Drop for SpawnToken { + fn drop(&mut self) { + // TODO deallocate the task instead. + panic!("SpawnToken instances may not be dropped. You must pass them to Spawner::spawn()") + } +} + +/// Error returned when spawning a task. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SpawnError { + /// Too many instances of this task are already running. + /// + /// By default, a task marked with `#[embassy_executor::task]` can only have one instance + /// running at a time. You may allow multiple instances to run in parallel with + /// `#[embassy_executor::task(pool_size = 4)]`, at the cost of higher RAM usage. + Busy, +} + +/// Handle to spawn tasks into an executor. +/// +/// This Spawner can spawn any task (Send and non-Send ones), but it can +/// only be used in the executor thread (it is not Send itself). +/// +/// If you want to spawn tasks from another thread, use [SendSpawner]. +#[derive(Copy, Clone)] +pub struct Spawner { + executor: &'static raw::Executor, + not_send: PhantomData<*mut ()>, +} + +impl Spawner { + pub(crate) fn new(executor: &'static raw::Executor) -> Self { + Self { + executor, + not_send: PhantomData, + } + } + + /// Get a Spawner for the current executor. + /// + /// This function is `async` just to get access to the current async + /// context. It returns instantly, it does not block/yield. + /// + /// # Panics + /// + /// Panics if the current executor is not an Embassy executor. + pub async fn for_current_executor() -> Self { + poll_fn(|cx| { + let task = raw::task_from_waker(cx.waker()); + let executor = unsafe { + task.header() + .executor + .load(Ordering::Relaxed) + .as_ref() + .unwrap_unchecked() + }; + let executor = unsafe { raw::Executor::wrap(executor) }; + Poll::Ready(Self::new(executor)) + }) + .await + } + + /// Spawn a task into an executor. + /// + /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`). + pub fn spawn(&self, token: SpawnToken) -> Result<(), SpawnError> { + let task = token.raw_task; + mem::forget(token); + + match task { + Some(task) => { + unsafe { self.executor.spawn(task) }; + Ok(()) + } + None => Err(SpawnError::Busy), + } + } + + // Used by the `embassy_executor_macros::main!` macro to throw an error when spawn + // fails. This is here to allow conditional use of `defmt::unwrap!` + // without introducing a `defmt` feature in the `embassy_executor_macros` package, + // which would require use of `-Z namespaced-features`. + /// Spawn a task into an executor, panicking on failure. + /// + /// # Panics + /// + /// Panics if the spawning fails. + pub fn must_spawn(&self, token: SpawnToken) { + unwrap!(self.spawn(token)); + } + + /// Convert this Spawner to a SendSpawner. This allows you to send the + /// spawner to other threads, but the spawner loses the ability to spawn + /// non-Send tasks. + pub fn make_send(&self) -> SendSpawner { + SendSpawner::new(&self.executor.inner) + } +} + +/// Handle to spawn tasks into an executor from any thread. +/// +/// This Spawner can be used from any thread (it is Send), but it can +/// only spawn Send tasks. The reason for this is spawning is effectively +/// "sending" the tasks to the executor thread. +/// +/// If you want to spawn non-Send tasks, use [Spawner]. +#[derive(Copy, Clone)] +pub struct SendSpawner { + executor: &'static raw::SyncExecutor, +} + +impl SendSpawner { + pub(crate) fn new(executor: &'static raw::SyncExecutor) -> Self { + Self { executor } + } + + /// Get a Spawner for the current executor. + /// + /// This function is `async` just to get access to the current async + /// context. It returns instantly, it does not block/yield. + /// + /// # Panics + /// + /// Panics if the current executor is not an Embassy executor. + pub async fn for_current_executor() -> Self { + poll_fn(|cx| { + let task = raw::task_from_waker(cx.waker()); + let executor = unsafe { + task.header() + .executor + .load(Ordering::Relaxed) + .as_ref() + .unwrap_unchecked() + }; + Poll::Ready(Self::new(executor)) + }) + .await + } + + /// Spawn a task into an executor. + /// + /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`). + pub fn spawn(&self, token: SpawnToken) -> Result<(), SpawnError> { + let header = token.raw_task; + mem::forget(token); + + match header { + Some(header) => { + unsafe { self.executor.spawn(header) }; + Ok(()) + } + None => Err(SpawnError::Busy), + } + } + + /// Spawn a task into an executor, panicking on failure. + /// + /// # Panics + /// + /// Panics if the spawning fails. + pub fn must_spawn(&self, token: SpawnToken) { + unwrap!(self.spawn(token)); + } +} diff --git a/embassy/embassy-executor/tests/test.rs b/embassy/embassy-executor/tests/test.rs new file mode 100644 index 0000000..78c49c0 --- /dev/null +++ b/embassy/embassy-executor/tests/test.rs @@ -0,0 +1,285 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +use std::boxed::Box; +use std::future::poll_fn; +use std::sync::{Arc, Mutex}; +use std::task::Poll; + +use embassy_executor::raw::Executor; +use embassy_executor::task; + +#[export_name = "__pender"] +fn __pender(context: *mut ()) { + unsafe { + let trace = &*(context as *const Trace); + trace.push("pend"); + } +} + +#[derive(Clone)] +struct Trace { + trace: Arc>>, +} + +impl Trace { + fn new() -> Self { + Self { + trace: Arc::new(Mutex::new(Vec::new())), + } + } + fn push(&self, value: &'static str) { + self.trace.lock().unwrap().push(value) + } + + fn get(&self) -> Vec<&'static str> { + self.trace.lock().unwrap().clone() + } +} + +fn setup() -> (&'static Executor, Trace) { + let trace = Trace::new(); + let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut (); + let executor = &*Box::leak(Box::new(Executor::new(context))); + + (executor, trace) +} + +#[test] +fn executor_noop() { + let (executor, trace) = setup(); + unsafe { executor.poll() }; + assert!(trace.get().is_empty()) +} + +#[test] +fn executor_task() { + #[task] + async fn task1(trace: Trace) { + trace.push("poll task1") + } + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone())).unwrap(); + + unsafe { executor.poll() }; + unsafe { executor.poll() }; + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "poll task1", // poll only once. + ] + ) +} + +#[test] +fn executor_task_self_wake() { + #[task] + async fn task1(trace: Trace) { + poll_fn(|cx| { + trace.push("poll task1"); + cx.waker().wake_by_ref(); + Poll::Pending + }) + .await + } + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone())).unwrap(); + + unsafe { executor.poll() }; + unsafe { executor.poll() }; + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "poll task1", // + "pend", // task self-wakes + "poll task1", // + "pend", // task self-wakes + ] + ) +} + +#[test] +fn executor_task_self_wake_twice() { + #[task] + async fn task1(trace: Trace) { + poll_fn(|cx| { + trace.push("poll task1"); + cx.waker().wake_by_ref(); + trace.push("poll task1 wake 2"); + cx.waker().wake_by_ref(); + Poll::Pending + }) + .await + } + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone())).unwrap(); + + unsafe { executor.poll() }; + unsafe { executor.poll() }; + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "poll task1", // + "pend", // task self-wakes + "poll task1 wake 2", // task self-wakes again, shouldn't pend + "poll task1", // + "pend", // task self-wakes + "poll task1 wake 2", // task self-wakes again, shouldn't pend + ] + ) +} + +#[test] +fn waking_after_completion_does_not_poll() { + use embassy_sync::waitqueue::AtomicWaker; + + #[task] + async fn task1(trace: Trace, waker: &'static AtomicWaker) { + poll_fn(|cx| { + trace.push("poll task1"); + waker.register(cx.waker()); + Poll::Ready(()) + }) + .await + } + + let waker = Box::leak(Box::new(AtomicWaker::new())); + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); + + unsafe { executor.poll() }; + waker.wake(); + unsafe { executor.poll() }; + + // Exited task may be waken but is not polled + waker.wake(); + waker.wake(); + unsafe { executor.poll() }; // Clears running status + + // Can respawn waken-but-dead task + executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); + + unsafe { executor.poll() }; + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "poll task1", // + "pend", // manual wake, gets cleared by poll + "pend", // manual wake, single pend for two wakes + "pend", // respawning a task pends the executor + "poll task1", // + ] + ) +} + +#[test] +fn waking_with_old_waker_after_respawn() { + use embassy_sync::waitqueue::AtomicWaker; + + async fn yield_now(trace: Trace) { + let mut yielded = false; + poll_fn(|cx| { + if yielded { + Poll::Ready(()) + } else { + trace.push("yield_now"); + yielded = true; + cx.waker().wake_by_ref(); + Poll::Pending + } + }) + .await + } + + #[task] + async fn task1(trace: Trace, waker: &'static AtomicWaker) { + yield_now(trace.clone()).await; + poll_fn(|cx| { + trace.push("poll task1"); + waker.register(cx.waker()); + Poll::Ready(()) + }) + .await; + } + + let waker = Box::leak(Box::new(AtomicWaker::new())); + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); + + unsafe { executor.poll() }; + unsafe { executor.poll() }; // progress to registering the waker + waker.wake(); + unsafe { executor.poll() }; + // Task has exited + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "yield_now", // + "pend", // yield_now wakes the task + "poll task1", // + "pend", // task self-wakes + ] + ); + + // Can respawn task on another executor + let (other_executor, other_trace) = setup(); + other_executor + .spawner() + .spawn(task1(other_trace.clone(), waker)) + .unwrap(); + + unsafe { other_executor.poll() }; // just run to the yield_now + waker.wake(); // trigger old waker registration + unsafe { executor.poll() }; + unsafe { other_executor.poll() }; + + // First executor's trace has not changed + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "yield_now", // + "pend", // yield_now wakes the task + "poll task1", // + "pend", // task self-wakes + ] + ); + + assert_eq!( + other_trace.get(), + &[ + "pend", // spawning a task pends the executor + "yield_now", // + "pend", // manual wake, gets cleared by poll + "poll task1", // + ] + ); +} + +#[test] +fn executor_task_cfg_args() { + // simulate cfg'ing away argument c + #[task] + async fn task1(a: u32, b: u32, #[cfg(any())] c: u32) { + let (_, _) = (a, b); + } + + #[task] + async fn task2(a: u32, b: u32, #[cfg(all())] c: u32) { + let (_, _, _) = (a, b, c); + } +} diff --git a/embassy/embassy-executor/tests/ui.rs b/embassy/embassy-executor/tests/ui.rs new file mode 100644 index 0000000..be46794 --- /dev/null +++ b/embassy/embassy-executor/tests/ui.rs @@ -0,0 +1,23 @@ +#[cfg(not(miri))] +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/abi.rs"); + t.compile_fail("tests/ui/bad_return.rs"); + t.compile_fail("tests/ui/generics.rs"); + t.compile_fail("tests/ui/impl_trait_nested.rs"); + t.compile_fail("tests/ui/impl_trait.rs"); + t.compile_fail("tests/ui/impl_trait_static.rs"); + t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs"); + t.compile_fail("tests/ui/nonstatic_ref_anon.rs"); + t.compile_fail("tests/ui/nonstatic_ref_elided.rs"); + t.compile_fail("tests/ui/nonstatic_ref_generic.rs"); + t.compile_fail("tests/ui/nonstatic_struct_anon.rs"); + #[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly. + t.compile_fail("tests/ui/nonstatic_struct_elided.rs"); + t.compile_fail("tests/ui/nonstatic_struct_generic.rs"); + t.compile_fail("tests/ui/not_async.rs"); + t.compile_fail("tests/ui/self_ref.rs"); + t.compile_fail("tests/ui/self.rs"); + t.compile_fail("tests/ui/where_clause.rs"); +} diff --git a/embassy/embassy-executor/tests/ui/abi.rs b/embassy/embassy-executor/tests/ui/abi.rs new file mode 100644 index 0000000..fd52f7e --- /dev/null +++ b/embassy/embassy-executor/tests/ui/abi.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async extern "C" fn task() {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/abi.stderr b/embassy/embassy-executor/tests/ui/abi.stderr new file mode 100644 index 0000000..e264e37 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/abi.stderr @@ -0,0 +1,5 @@ +error: task functions must not have an ABI qualifier + --> tests/ui/abi.rs:6:1 + | +6 | async extern "C" fn task() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy/embassy-executor/tests/ui/bad_return.rs b/embassy/embassy-executor/tests/ui/bad_return.rs new file mode 100644 index 0000000..f09a520 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/bad_return.rs @@ -0,0 +1,10 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task() -> u32 { + 5 +} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/bad_return.stderr b/embassy/embassy-executor/tests/ui/bad_return.stderr new file mode 100644 index 0000000..e9d94df --- /dev/null +++ b/embassy/embassy-executor/tests/ui/bad_return.stderr @@ -0,0 +1,5 @@ +error: task functions must either not return a value, return `()` or return `!` + --> tests/ui/bad_return.rs:6:1 + | +6 | async fn task() -> u32 { + | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy/embassy-executor/tests/ui/generics.rs b/embassy/embassy-executor/tests/ui/generics.rs new file mode 100644 index 0000000..b83123b --- /dev/null +++ b/embassy/embassy-executor/tests/ui/generics.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_t: T) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/generics.stderr b/embassy/embassy-executor/tests/ui/generics.stderr new file mode 100644 index 0000000..197719a --- /dev/null +++ b/embassy/embassy-executor/tests/ui/generics.stderr @@ -0,0 +1,5 @@ +error: task functions must not be generic + --> tests/ui/generics.rs:6:1 + | +6 | async fn task(_t: T) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy/embassy-executor/tests/ui/impl_trait.rs b/embassy/embassy-executor/tests/ui/impl_trait.rs new file mode 100644 index 0000000..a21402a --- /dev/null +++ b/embassy/embassy-executor/tests/ui/impl_trait.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: impl Sized) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/impl_trait.stderr b/embassy/embassy-executor/tests/ui/impl_trait.stderr new file mode 100644 index 0000000..099b182 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/impl_trait.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait.rs:4:18 + | +4 | async fn foo(_x: impl Sized) {} + | ^^^^^^^^^^ diff --git a/embassy/embassy-executor/tests/ui/impl_trait_nested.rs b/embassy/embassy-executor/tests/ui/impl_trait_nested.rs new file mode 100644 index 0000000..07442b8 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/impl_trait_nested.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo(T); + +#[embassy_executor::task] +async fn foo(_x: Foo) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/impl_trait_nested.stderr b/embassy/embassy-executor/tests/ui/impl_trait_nested.stderr new file mode 100644 index 0000000..39592f9 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/impl_trait_nested.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait_nested.rs:6:22 + | +6 | async fn foo(_x: Foo) {} + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy/embassy-executor/tests/ui/impl_trait_static.rs b/embassy/embassy-executor/tests/ui/impl_trait_static.rs new file mode 100644 index 0000000..272470f --- /dev/null +++ b/embassy/embassy-executor/tests/ui/impl_trait_static.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: impl Sized + 'static) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/impl_trait_static.stderr b/embassy/embassy-executor/tests/ui/impl_trait_static.stderr new file mode 100644 index 0000000..0032a20 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/impl_trait_static.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait_static.rs:4:18 + | +4 | async fn foo(_x: impl Sized + 'static) {} + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy/embassy-executor/tests/ui/nonstatic_ref_anon.rs b/embassy/embassy-executor/tests/ui/nonstatic_ref_anon.rs new file mode 100644 index 0000000..417c360 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_ref_anon.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &'_ u32) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/nonstatic_ref_anon.stderr b/embassy/embassy-executor/tests/ui/nonstatic_ref_anon.stderr new file mode 100644 index 0000000..0544de8 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_ref_anon.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_anon.rs:4:19 + | +4 | async fn foo(_x: &'_ u32) {} + | ^^ diff --git a/embassy/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs b/embassy/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs new file mode 100644 index 0000000..175ebcc --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &'static &'_ u32) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr b/embassy/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr new file mode 100644 index 0000000..79f262e --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_anon_nested.rs:4:28 + | +4 | async fn foo(_x: &'static &'_ u32) {} + | ^^ diff --git a/embassy/embassy-executor/tests/ui/nonstatic_ref_elided.rs b/embassy/embassy-executor/tests/ui/nonstatic_ref_elided.rs new file mode 100644 index 0000000..cf49ad7 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_ref_elided.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &u32) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/nonstatic_ref_elided.stderr b/embassy/embassy-executor/tests/ui/nonstatic_ref_elided.stderr new file mode 100644 index 0000000..7e2b9eb --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_ref_elided.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_elided.rs:4:18 + | +4 | async fn foo(_x: &u32) {} + | ^ diff --git a/embassy/embassy-executor/tests/ui/nonstatic_ref_generic.rs b/embassy/embassy-executor/tests/ui/nonstatic_ref_generic.rs new file mode 100644 index 0000000..3f8a26c --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_ref_generic.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo<'a>(_x: &'a u32) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/nonstatic_ref_generic.stderr b/embassy/embassy-executor/tests/ui/nonstatic_ref_generic.stderr new file mode 100644 index 0000000..af8491a --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_ref_generic.stderr @@ -0,0 +1,11 @@ +error: task functions must not be generic + --> tests/ui/nonstatic_ref_generic.rs:4:1 + | +4 | async fn foo<'a>(_x: &'a u32) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_generic.rs:4:23 + | +4 | async fn foo<'a>(_x: &'a u32) {} + | ^^ diff --git a/embassy/embassy-executor/tests/ui/nonstatic_struct_anon.rs b/embassy/embassy-executor/tests/ui/nonstatic_struct_anon.rs new file mode 100644 index 0000000..ba95d14 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_struct_anon.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_x: Foo<'_>) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/nonstatic_struct_anon.stderr b/embassy/embassy-executor/tests/ui/nonstatic_struct_anon.stderr new file mode 100644 index 0000000..5df2a6e --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_struct_anon.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_struct_anon.rs:6:23 + | +6 | async fn task(_x: Foo<'_>) {} + | ^^ diff --git a/embassy/embassy-executor/tests/ui/nonstatic_struct_elided.rs b/embassy/embassy-executor/tests/ui/nonstatic_struct_elided.rs new file mode 100644 index 0000000..4cfe296 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_struct_elided.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_x: Foo) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/nonstatic_struct_elided.stderr b/embassy/embassy-executor/tests/ui/nonstatic_struct_elided.stderr new file mode 100644 index 0000000..099ef8b --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_struct_elided.stderr @@ -0,0 +1,10 @@ +error[E0726]: implicit elided lifetime not allowed here + --> tests/ui/nonstatic_struct_elided.rs:6:19 + | +6 | async fn task(_x: Foo) {} + | ^^^ expected lifetime parameter + | +help: indicate the anonymous lifetime + | +6 | async fn task(_x: Foo<'_>) {} + | ++++ diff --git a/embassy/embassy-executor/tests/ui/nonstatic_struct_generic.rs b/embassy/embassy-executor/tests/ui/nonstatic_struct_generic.rs new file mode 100644 index 0000000..ec3d908 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_struct_generic.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task<'a>(_x: Foo<'a>) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/nonstatic_struct_generic.stderr b/embassy/embassy-executor/tests/ui/nonstatic_struct_generic.stderr new file mode 100644 index 0000000..61d5231 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/nonstatic_struct_generic.stderr @@ -0,0 +1,11 @@ +error: task functions must not be generic + --> tests/ui/nonstatic_struct_generic.rs:6:1 + | +6 | async fn task<'a>(_x: Foo<'a>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_struct_generic.rs:6:27 + | +6 | async fn task<'a>(_x: Foo<'a>) {} + | ^^ diff --git a/embassy/embassy-executor/tests/ui/not_async.rs b/embassy/embassy-executor/tests/ui/not_async.rs new file mode 100644 index 0000000..f3f7e9b --- /dev/null +++ b/embassy/embassy-executor/tests/ui/not_async.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +fn task() {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/not_async.stderr b/embassy/embassy-executor/tests/ui/not_async.stderr new file mode 100644 index 0000000..27f040d --- /dev/null +++ b/embassy/embassy-executor/tests/ui/not_async.stderr @@ -0,0 +1,5 @@ +error: task functions must be async + --> tests/ui/not_async.rs:6:1 + | +6 | fn task() {} + | ^^^^^^^^^ diff --git a/embassy/embassy-executor/tests/ui/self.rs b/embassy/embassy-executor/tests/ui/self.rs new file mode 100644 index 0000000..f83a962 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/self.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(self) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/self.stderr b/embassy/embassy-executor/tests/ui/self.stderr new file mode 100644 index 0000000..aaf0315 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/self.stderr @@ -0,0 +1,13 @@ +error: task functions must not have `self` arguments + --> tests/ui/self.rs:6:15 + | +6 | async fn task(self) {} + | ^^^^ + +error: `self` parameter is only allowed in associated functions + --> tests/ui/self.rs:6:15 + | +6 | async fn task(self) {} + | ^^^^ not semantically valid as function parameter + | + = note: associated functions are those in `impl` or `trait` definitions diff --git a/embassy/embassy-executor/tests/ui/self_ref.rs b/embassy/embassy-executor/tests/ui/self_ref.rs new file mode 100644 index 0000000..5e49bba --- /dev/null +++ b/embassy/embassy-executor/tests/ui/self_ref.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(&mut self) {} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/self_ref.stderr b/embassy/embassy-executor/tests/ui/self_ref.stderr new file mode 100644 index 0000000..dd20529 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/self_ref.stderr @@ -0,0 +1,13 @@ +error: task functions must not have `self` arguments + --> tests/ui/self_ref.rs:6:15 + | +6 | async fn task(&mut self) {} + | ^^^^^^^^^ + +error: `self` parameter is only allowed in associated functions + --> tests/ui/self_ref.rs:6:15 + | +6 | async fn task(&mut self) {} + | ^^^^^^^^^ not semantically valid as function parameter + | + = note: associated functions are those in `impl` or `trait` definitions diff --git a/embassy/embassy-executor/tests/ui/where_clause.rs b/embassy/embassy-executor/tests/ui/where_clause.rs new file mode 100644 index 0000000..848d781 --- /dev/null +++ b/embassy/embassy-executor/tests/ui/where_clause.rs @@ -0,0 +1,12 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task() +where + (): Sized, +{ +} + +fn main() {} diff --git a/embassy/embassy-executor/tests/ui/where_clause.stderr b/embassy/embassy-executor/tests/ui/where_clause.stderr new file mode 100644 index 0000000..eba45af --- /dev/null +++ b/embassy/embassy-executor/tests/ui/where_clause.stderr @@ -0,0 +1,7 @@ +error: task functions must not have `where` clauses + --> tests/ui/where_clause.rs:6:1 + | +6 | / async fn task() +7 | | where +8 | | (): Sized, + | |______________^ diff --git a/embassy/embassy-futures/Cargo.toml b/embassy/embassy-futures/Cargo.toml new file mode 100644 index 0000000..47cefa5 --- /dev/null +++ b/embassy/embassy-futures/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "embassy-futures" +version = "0.1.1" +edition = "2021" +description = "no-std, no-alloc utilities for working with futures" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-futures" +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-futures-v$VERSION/embassy-futures/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-futures/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } diff --git a/embassy/embassy-futures/README.md b/embassy/embassy-futures/README.md new file mode 100644 index 0000000..b28a843 --- /dev/null +++ b/embassy/embassy-futures/README.md @@ -0,0 +1,13 @@ +# embassy-futures + +An [Embassy](https://embassy.dev) project. + +Utilities for working with futures, compatible with `no_std` and not using `alloc`. Optimized for code size, +ideal for embedded systems. + +- Future combinators, like [`join`](join) and [`select`](select) +- Utilities to use `async` without a fully fledged executor: [`block_on`](block_on::block_on) and [`yield_now`](yield_now::yield_now). + +## Interoperability + +Futures from this crate can run on any executor. diff --git a/embassy/embassy-futures/src/block_on.rs b/embassy/embassy-futures/src/block_on.rs new file mode 100644 index 0000000..7769521 --- /dev/null +++ b/embassy/embassy-futures/src/block_on.rs @@ -0,0 +1,45 @@ +use core::future::Future; +use core::pin::Pin; +use core::ptr; +use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + +static VTABLE: RawWakerVTable = RawWakerVTable::new(|_| RawWaker::new(ptr::null(), &VTABLE), |_| {}, |_| {}, |_| {}); + +/// Run a future to completion using a busy loop. +/// +/// This calls `.poll()` on the future in a busy loop, which blocks +/// the current thread at 100% cpu usage until the future is done. The +/// future's `Waker` mechanism is not used. +/// +/// You can use this to run multiple futures concurrently with [`join`][crate::join]. +/// +/// It's suitable for systems with no or limited concurrency and without +/// strict requirements around power consumption. For more complex use +/// cases, prefer using a "real" executor like `embassy-executor`, which +/// supports multiple tasks, and putting the core to sleep when no task +/// needs to do work. +pub fn block_on(mut fut: F) -> F::Output { + // safety: we don't move the future after this line. + let mut fut = unsafe { Pin::new_unchecked(&mut fut) }; + + let raw_waker = RawWaker::new(ptr::null(), &VTABLE); + let waker = unsafe { Waker::from_raw(raw_waker) }; + let mut cx = Context::from_waker(&waker); + loop { + if let Poll::Ready(res) = fut.as_mut().poll(&mut cx) { + return res; + } + } +} + +/// Poll a future once. +pub fn poll_once(mut fut: F) -> Poll { + // safety: we don't move the future after this line. + let mut fut = unsafe { Pin::new_unchecked(&mut fut) }; + + let raw_waker = RawWaker::new(ptr::null(), &VTABLE); + let waker = unsafe { Waker::from_raw(raw_waker) }; + let mut cx = Context::from_waker(&waker); + + fut.as_mut().poll(&mut cx) +} diff --git a/embassy/embassy-futures/src/fmt.rs b/embassy/embassy-futures/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-futures/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-futures/src/join.rs b/embassy/embassy-futures/src/join.rs new file mode 100644 index 0000000..bc0cb53 --- /dev/null +++ b/embassy/embassy-futures/src/join.rs @@ -0,0 +1,322 @@ +//! Wait for multiple futures to complete. + +use core::future::Future; +use core::mem::MaybeUninit; +use core::pin::Pin; +use core::task::{Context, Poll}; +use core::{fmt, mem}; + +#[derive(Debug)] +enum MaybeDone { + /// A not-yet-completed future + Future(/* #[pin] */ Fut), + /// The output of the completed future + Done(Fut::Output), + /// The empty variant after the result of a [`MaybeDone`] has been + /// taken using the [`take_output`](MaybeDone::take_output) method. + Gone, +} + +impl MaybeDone { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool { + let this = unsafe { self.get_unchecked_mut() }; + match this { + Self::Future(fut) => match unsafe { Pin::new_unchecked(fut) }.poll(cx) { + Poll::Ready(res) => { + *this = Self::Done(res); + true + } + Poll::Pending => false, + }, + _ => true, + } + } + + fn take_output(&mut self) -> Fut::Output { + match &*self { + Self::Done(_) => {} + Self::Future(_) | Self::Gone => panic!("take_output when MaybeDone is not done."), + } + match mem::replace(self, Self::Gone) { + MaybeDone::Done(output) => output, + _ => unreachable!(), + } + } +} + +impl Unpin for MaybeDone {} + +macro_rules! generate { + ($( + $(#[$doc:meta])* + ($Join:ident, <$($Fut:ident),*>), + )*) => ($( + $(#[$doc])* + #[must_use = "futures do nothing unless you `.await` or poll them"] + #[allow(non_snake_case)] + pub struct $Join<$($Fut: Future),*> { + $( + $Fut: MaybeDone<$Fut>, + )* + } + + impl<$($Fut),*> fmt::Debug for $Join<$($Fut),*> + where + $( + $Fut: Future + fmt::Debug, + $Fut::Output: fmt::Debug, + )* + { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!($Join)) + $(.field(stringify!($Fut), &self.$Fut))* + .finish() + } + } + + impl<$($Fut: Future),*> $Join<$($Fut),*> { + #[allow(non_snake_case)] + fn new($($Fut: $Fut),*) -> Self { + Self { + $($Fut: MaybeDone::Future($Fut)),* + } + } + } + + impl<$($Fut: Future),*> Future for $Join<$($Fut),*> { + type Output = ($($Fut::Output),*); + + fn poll( + self: Pin<&mut Self>, cx: &mut Context<'_> + ) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let mut all_done = true; + $( + all_done &= unsafe { Pin::new_unchecked(&mut this.$Fut) }.poll(cx); + )* + + if all_done { + Poll::Ready(($(this.$Fut.take_output()), *)) + } else { + Poll::Pending + } + } + } + )*) +} + +generate! { + /// Future for the [`join`](join()) function. + (Join, ), + + /// Future for the [`join3`] function. + (Join3, ), + + /// Future for the [`join4`] function. + (Join4, ), + + /// Future for the [`join5`] function. + (Join5, ), +} + +/// Joins the result of two futures, waiting for them both to complete. +/// +/// This function will return a new future which awaits both futures to +/// complete. The returned future will finish with a tuple of both results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let pair = embassy_futures::join::join(a, b).await; +/// +/// assert_eq!(pair, (1, 2)); +/// # }); +/// ``` +pub fn join(future1: Fut1, future2: Fut2) -> Join +where + Fut1: Future, + Fut2: Future, +{ + Join::new(future1, future2) +} + +/// Joins the result of three futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let res = embassy_futures::join::join3(a, b, c).await; +/// +/// assert_eq!(res, (1, 2, 3)); +/// # }); +/// ``` +pub fn join3(future1: Fut1, future2: Fut2, future3: Fut3) -> Join3 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, +{ + Join3::new(future1, future2, future3) +} + +/// Joins the result of four futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let d = async { 4 }; +/// let res = embassy_futures::join::join4(a, b, c, d).await; +/// +/// assert_eq!(res, (1, 2, 3, 4)); +/// # }); +/// ``` +pub fn join4( + future1: Fut1, + future2: Fut2, + future3: Fut3, + future4: Fut4, +) -> Join4 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, + Fut4: Future, +{ + Join4::new(future1, future2, future3, future4) +} + +/// Joins the result of five futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let d = async { 4 }; +/// let e = async { 5 }; +/// let res = embassy_futures::join::join5(a, b, c, d, e).await; +/// +/// assert_eq!(res, (1, 2, 3, 4, 5)); +/// # }); +/// ``` +pub fn join5( + future1: Fut1, + future2: Fut2, + future3: Fut3, + future4: Fut4, + future5: Fut5, +) -> Join5 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, + Fut4: Future, + Fut5: Future, +{ + Join5::new(future1, future2, future3, future4, future5) +} + +// ===================================================== + +/// Future for the [`join_array`] function. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct JoinArray { + futures: [MaybeDone; N], +} + +impl fmt::Debug for JoinArray +where + Fut: Future + fmt::Debug, + Fut::Output: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("JoinArray").field("futures", &self.futures).finish() + } +} + +impl Future for JoinArray { + type Output = [Fut::Output; N]; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let mut all_done = true; + for f in this.futures.iter_mut() { + all_done &= unsafe { Pin::new_unchecked(f) }.poll(cx); + } + + if all_done { + let mut array: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + for i in 0..N { + array[i].write(this.futures[i].take_output()); + } + Poll::Ready(unsafe { (&array as *const _ as *const [Fut::Output; N]).read() }) + } else { + Poll::Pending + } + } +} + +/// Joins the result of an array of futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// async fn foo(n: u32) -> u32 { n } +/// let a = foo(1); +/// let b = foo(2); +/// let c = foo(3); +/// let res = embassy_futures::join::join_array([a, b, c]).await; +/// +/// assert_eq!(res, [1, 2, 3]); +/// # }); +/// ``` +pub fn join_array(futures: [Fut; N]) -> JoinArray { + JoinArray { + futures: futures.map(MaybeDone::Future), + } +} diff --git a/embassy/embassy-futures/src/lib.rs b/embassy/embassy-futures/src/lib.rs new file mode 100644 index 0000000..8c769bd --- /dev/null +++ b/embassy/embassy-futures/src/lib.rs @@ -0,0 +1,15 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +mod block_on; +mod yield_now; + +pub mod join; +pub mod select; + +pub use block_on::*; +pub use yield_now::*; diff --git a/embassy/embassy-futures/src/select.rs b/embassy/embassy-futures/src/select.rs new file mode 100644 index 0000000..014fee5 --- /dev/null +++ b/embassy/embassy-futures/src/select.rs @@ -0,0 +1,545 @@ +//! Wait for the first of several futures to complete. + +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +/// Result for [`select`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), +} + +impl Either { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either::Second(_)) + } +} + +/// Wait for one of two futures to complete. +/// +/// This function returns a new future which polls all the futures. +/// When one of them completes, it will complete with its result value. +/// +/// The other future is dropped. +pub fn select(a: A, b: B) -> Select +where + A: Future, + B: Future, +{ + Select { a, b } +} + +/// Future for the [`select`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select { + a: A, + b: B, +} + +impl Unpin for Select {} + +impl Future for Select +where + A: Future, + B: Future, +{ + type Output = Either; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either::Second(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Result for [`select3`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either3 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), +} + +impl Either3 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either3::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either3::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either3::Third(_)) + } +} + +/// Same as [`select`], but with more futures. +pub fn select3(a: A, b: B, c: C) -> Select3 +where + A: Future, + B: Future, + C: Future, +{ + Select3 { a, b, c } +} + +/// Future for the [`select3`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select3 { + a: A, + b: B, + c: C, +} + +impl Future for Select3 +where + A: Future, + B: Future, + C: Future, +{ + type Output = Either3; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either3::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either3::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either3::Third(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Result for [`select4`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either4 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), + /// Fourth future finished first. + Fourth(D), +} + +impl Either4 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either4::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either4::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either4::Third(_)) + } + + /// Did the fourth future complete first? + pub fn is_fourth(&self) -> bool { + matches!(self, Either4::Fourth(_)) + } +} + +/// Same as [`select`], but with more futures. +pub fn select4(a: A, b: B, c: C, d: D) -> Select4 +where + A: Future, + B: Future, + C: Future, + D: Future, +{ + Select4 { a, b, c, d } +} + +/// Future for the [`select4`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select4 { + a: A, + b: B, + c: C, + d: D, +} + +impl Future for Select4 +where + A: Future, + B: Future, + C: Future, + D: Future, +{ + type Output = Either4; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + let d = unsafe { Pin::new_unchecked(&mut this.d) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either4::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either4::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either4::Third(x)); + } + if let Poll::Ready(x) = d.poll(cx) { + return Poll::Ready(Either4::Fourth(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Result for [`select5`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either5 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), + /// Fourth future finished first. + Fourth(D), + /// Fifth future finished first. + Fifth(E), +} + +impl Either5 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either5::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either5::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either5::Third(_)) + } + + /// Did the fourth future complete first? + pub fn is_fourth(&self) -> bool { + matches!(self, Either5::Fourth(_)) + } + + /// Did the fifth future complete first? + pub fn is_fifth(&self) -> bool { + matches!(self, Either5::Fifth(_)) + } +} + +/// Same as [`select`], but with more futures. +pub fn select5(a: A, b: B, c: C, d: D, e: E) -> Select5 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, +{ + Select5 { a, b, c, d, e } +} + +/// Future for the [`select5`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select5 { + a: A, + b: B, + c: C, + d: D, + e: E, +} + +impl Future for Select5 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, +{ + type Output = Either5; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + let d = unsafe { Pin::new_unchecked(&mut this.d) }; + let e = unsafe { Pin::new_unchecked(&mut this.e) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either5::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either5::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either5::Third(x)); + } + if let Poll::Ready(x) = d.poll(cx) { + return Poll::Ready(Either5::Fourth(x)); + } + if let Poll::Ready(x) = e.poll(cx) { + return Poll::Ready(Either5::Fifth(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Result for [`select6`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either6 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), + /// Fourth future finished first. + Fourth(D), + /// Fifth future finished first. + Fifth(E), + /// Sixth future finished first. + Sixth(F), +} + +impl Either6 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either6::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either6::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either6::Third(_)) + } + + /// Did the fourth future complete first? + pub fn is_fourth(&self) -> bool { + matches!(self, Either6::Fourth(_)) + } + + /// Did the fifth future complete first? + pub fn is_fifth(&self) -> bool { + matches!(self, Either6::Fifth(_)) + } + + /// Did the sixth future complete first? + pub fn is_sixth(&self) -> bool { + matches!(self, Either6::Sixth(_)) + } +} + +/// Same as [`select`], but with more futures. +pub fn select6(a: A, b: B, c: C, d: D, e: E, f: F) -> Select6 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, + F: Future, +{ + Select6 { a, b, c, d, e, f } +} + +/// Future for the [`select6`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select6 { + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, +} + +impl Future for Select6 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, + F: Future, +{ + type Output = Either6; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + let d = unsafe { Pin::new_unchecked(&mut this.d) }; + let e = unsafe { Pin::new_unchecked(&mut this.e) }; + let f = unsafe { Pin::new_unchecked(&mut this.f) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either6::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either6::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either6::Third(x)); + } + if let Poll::Ready(x) = d.poll(cx) { + return Poll::Ready(Either6::Fourth(x)); + } + if let Poll::Ready(x) = e.poll(cx) { + return Poll::Ready(Either6::Fifth(x)); + } + if let Poll::Ready(x) = f.poll(cx) { + return Poll::Ready(Either6::Sixth(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Future for the [`select_array`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SelectArray { + inner: [Fut; N], +} + +/// Creates a new future which will select over an array of futures. +/// +/// The returned future will wait for any future to be ready. Upon +/// completion the item resolved will be returned, along with the index of the +/// future that was ready. +/// +/// If the array is empty, the resulting future will be Pending forever. +pub fn select_array(arr: [Fut; N]) -> SelectArray { + SelectArray { inner: arr } +} + +impl Future for SelectArray { + type Output = (Fut::Output, usize); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Safety: Since `self` is pinned, `inner` cannot move. Since `inner` cannot move, + // its elements also cannot move. Therefore it is safe to access `inner` and pin + // references to the contained futures. + let item = unsafe { + self.get_unchecked_mut() + .inner + .iter_mut() + .enumerate() + .find_map(|(i, f)| match Pin::new_unchecked(f).poll(cx) { + Poll::Pending => None, + Poll::Ready(e) => Some((i, e)), + }) + }; + + match item { + Some((idx, res)) => Poll::Ready((res, idx)), + None => Poll::Pending, + } + } +} + +// ==================================================================== + +/// Future for the [`select_slice`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SelectSlice<'a, Fut> { + inner: Pin<&'a mut [Fut]>, +} + +/// Creates a new future which will select over a slice of futures. +/// +/// The returned future will wait for any future to be ready. Upon +/// completion the item resolved will be returned, along with the index of the +/// future that was ready. +/// +/// If the slice is empty, the resulting future will be Pending forever. +pub fn select_slice<'a, Fut: Future>(slice: Pin<&'a mut [Fut]>) -> SelectSlice<'a, Fut> { + SelectSlice { inner: slice } +} + +impl<'a, Fut: Future> Future for SelectSlice<'a, Fut> { + type Output = (Fut::Output, usize); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Safety: refer to + // https://users.rust-lang.org/t/working-with-pinned-slices-are-there-any-structurally-pinning-vec-like-collection-types/50634/2 + #[inline(always)] + fn pin_iter(slice: Pin<&mut [T]>) -> impl Iterator> { + unsafe { slice.get_unchecked_mut().iter_mut().map(|v| Pin::new_unchecked(v)) } + } + for (i, fut) in pin_iter(self.inner.as_mut()).enumerate() { + if let Poll::Ready(res) = fut.poll(cx) { + return Poll::Ready((res, i)); + } + } + + Poll::Pending + } +} diff --git a/embassy/embassy-futures/src/yield_now.rs b/embassy/embassy-futures/src/yield_now.rs new file mode 100644 index 0000000..4d4e535 --- /dev/null +++ b/embassy/embassy-futures/src/yield_now.rs @@ -0,0 +1,49 @@ +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +/// Yield from the current task once, allowing other tasks to run. +/// +/// This can be used to easily and quickly implement simple async primitives +/// without using wakers. The following snippet will wait for a condition to +/// hold, while still allowing other tasks to run concurrently (not monopolizing +/// the executor thread). +/// +/// ```rust +/// # use embassy_futures::{block_on, yield_now}; +/// # async fn test_fn() { +/// # let mut iter_count: u32 = 0; +/// # let mut some_condition = || { iter_count += 1; iter_count > 10 }; +/// while !some_condition() { +/// yield_now().await; +/// } +/// # } +/// # block_on(test_fn()); +/// ``` +/// +/// The downside is this will spin in a busy loop, using 100% of the CPU, while +/// using wakers correctly would allow the CPU to sleep while waiting. +/// +/// The internal implementation is: on first poll the future wakes itself and +/// returns `Poll::Pending`. On second poll, it returns `Poll::Ready`. +pub fn yield_now() -> impl Future { + YieldNowFuture { yielded: false } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct YieldNowFuture { + yielded: bool, +} + +impl Future for YieldNowFuture { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.yielded { + Poll::Ready(()) + } else { + self.yielded = true; + cx.waker().wake_by_ref(); + Poll::Pending + } + } +} diff --git a/embassy/embassy-hal-internal/Cargo.toml b/embassy/embassy-hal-internal/Cargo.toml new file mode 100644 index 0000000..d5ca95a --- /dev/null +++ b/embassy/embassy-hal-internal/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "embassy-hal-internal" +version = "0.2.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Internal implementation details for Embassy HALs. DO NOT USE DIRECTLY." +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-hal-internal" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[features] + +# Define the number of NVIC priority bits. +prio-bits-0 = [] +prio-bits-1 = [] +prio-bits-2 = [] +prio-bits-3 = [] +prio-bits-4 = [] +prio-bits-5 = [] +prio-bits-6 = [] +prio-bits-7 = [] +prio-bits-8 = [] + +cortex-m = ["dep:cortex-m", "dep:critical-section"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +num-traits = { version = "0.2.14", default-features = false } + +cortex-m = { version = "0.7.6", optional = true } +critical-section = { version = "1", optional = true } diff --git a/embassy/embassy-hal-internal/README.md b/embassy/embassy-hal-internal/README.md new file mode 100644 index 0000000..1adce5b --- /dev/null +++ b/embassy/embassy-hal-internal/README.md @@ -0,0 +1,6 @@ +# embassy-hal-internal + +An [Embassy](https://embassy.dev) project. + +Internal implementation details for Embassy HALs. DO NOT USE DIRECTLY. Embassy HALs (`embassy-nrf`, `embassy-stm32`, `embassy-rp`) already reexport +everything you need to use them effectively. diff --git a/embassy/embassy-hal-internal/build.rs b/embassy/embassy-hal-internal/build.rs new file mode 100644 index 0000000..ecd2c0c --- /dev/null +++ b/embassy/embassy-hal-internal/build.rs @@ -0,0 +1,7 @@ +#[path = "./build_common.rs"] +mod common; + +fn main() { + let mut cfgs = common::CfgSet::new(); + common::set_target_cfgs(&mut cfgs); +} diff --git a/embassy/embassy-hal-internal/build_common.rs b/embassy/embassy-hal-internal/build_common.rs new file mode 100644 index 0000000..4f24e6d --- /dev/null +++ b/embassy/embassy-hal-internal/build_common.rs @@ -0,0 +1,94 @@ +// NOTE: this file is copy-pasted between several Embassy crates, because there is no +// straightforward way to share this code: +// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path = +// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate +// reside in the crate's directory, +// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because +// symlinks don't work on Windows. + +use std::collections::HashSet; +use std::env; + +/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring +/// them (`cargo:rust-check-cfg=cfg(X)`). +#[derive(Debug)] +pub struct CfgSet { + enabled: HashSet, + declared: HashSet, +} + +impl CfgSet { + pub fn new() -> Self { + Self { + enabled: HashSet::new(), + declared: HashSet::new(), + } + } + + /// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation. + /// + /// All configs that can potentially be enabled should be unconditionally declared using + /// [`Self::declare()`]. + pub fn enable(&mut self, cfg: impl AsRef) { + if self.enabled.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-cfg={}", cfg.as_ref()); + } + } + + pub fn enable_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.enable(cfg.as_ref()); + } + } + + /// Declare a valid config for conditional compilation, without enabling it. + /// + /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. + pub fn declare(&mut self, cfg: impl AsRef) { + if self.declared.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); + } + } + + pub fn declare_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.declare(cfg.as_ref()); + } + } + + pub fn set(&mut self, cfg: impl Into, enable: bool) { + let cfg = cfg.into(); + if enable { + self.enable(cfg.clone()); + } + self.declare(cfg); + } +} + +/// Sets configs that describe the target platform. +pub fn set_target_cfgs(cfgs: &mut CfgSet) { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + cfgs.enable_all(&["cortex_m", "armv6m"]); + } else if target.starts_with("thumbv7m-") { + cfgs.enable_all(&["cortex_m", "armv7m"]); + } else if target.starts_with("thumbv7em-") { + cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]); + } else if target.starts_with("thumbv8m.base") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]); + } else if target.starts_with("thumbv8m.main") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]); + } + cfgs.declare_all(&[ + "cortex_m", + "armv6m", + "armv7m", + "armv7em", + "armv8m", + "armv8m_base", + "armv8m_main", + ]); + + cfgs.set("has_fpu", target.ends_with("-eabihf")); +} diff --git a/embassy/embassy-hal-internal/src/atomic_ring_buffer.rs b/embassy/embassy-hal-internal/src/atomic_ring_buffer.rs new file mode 100644 index 0000000..00b7a12 --- /dev/null +++ b/embassy/embassy-hal-internal/src/atomic_ring_buffer.rs @@ -0,0 +1,604 @@ +//! Atomic reusable ringbuffer. +use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; +use core::{ptr, slice}; + +/// 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 { + #[doc(hidden)] + pub buf: AtomicPtr, + len: AtomicUsize, + + // start and end wrap at len*2, not at len. + // This allows distinguishing "full" and "empty". + // full is when start+len == end (modulo len*2) + // empty is when start == end + // + // This avoids having to consider the ringbuffer "full" at len-1 instead of len. + // The usual solution is adding a "full" flag, but that can't be made atomic + #[doc(hidden)] + pub start: AtomicUsize, + #[doc(hidden)] + pub end: AtomicUsize, +} + +/// A type which can only read from a ring buffer. +pub struct Reader<'a>(&'a RingBuffer); + +/// A type which can only write to a ring buffer. +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.buf.store(ptr::null_mut(), Ordering::Relaxed); + 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. + /// - Ringbuffer must be initialized. + pub unsafe fn reader(&self) -> Reader<'_> { + Reader(self) + } + + /// Try creating a reader, fails if not initialized. + /// + /// # Safety + /// + /// Only one reader can exist at a time. + pub unsafe fn try_reader(&self) -> Option> { + if self.buf.load(Ordering::Relaxed).is_null() { + return None; + } + Some(Reader(self)) + } + + /// Create a writer. + /// + /// # Safety + /// + /// - Only one writer can exist at a time. + /// - Ringbuffer must be initialized. + pub unsafe fn writer(&self) -> Writer<'_> { + Writer(self) + } + + /// Try creating a writer, fails if not initialized. + /// + /// # Safety + /// + /// Only one writer can exist at a time. + pub unsafe fn try_writer(&self) -> Option> { + if self.buf.load(Ordering::Relaxed).is_null() { + return None; + } + Some(Writer(self)) + } + + /// Return if buffer is available. + pub fn is_available(&self) -> bool { + !self.buf.load(Ordering::Relaxed).is_null() && self.len.load(Ordering::Relaxed) != 0 + } + + /// Return length of buffer. + pub fn len(&self) -> usize { + self.len.load(Ordering::Relaxed) + } + + /// Check if buffer is full. + 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); + + self.wrap(start + len) == end + } + + /// Check if buffer is empty. + 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, mut n: usize) -> usize { + let len = self.len.load(Ordering::Relaxed); + + if n >= len * 2 { + n -= len * 2 + } + 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 successfully. + 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 up to two buffers where data can be pushed to. + /// + /// Equivalent to [`Self::push_bufs`] but returns slices. + pub fn push_slices(&mut self) -> [&mut [u8]; 2] { + let [(d0, l0), (d1, l1)] = self.push_bufs(); + unsafe { [slice::from_raw_parts_mut(d0, l0), slice::from_raw_parts_mut(d1, l1)] } + } + + /// 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 mut start = self.0.start.load(Ordering::Acquire); + let buf = self.0.buf.load(Ordering::Relaxed); + let len = self.0.len.load(Ordering::Relaxed); + let mut end = self.0.end.load(Ordering::Relaxed); + + let empty = start == end; + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + if start == end && !empty { + // full + return (buf, 0); + } + let n = if start > end { start - end } else { len - end }; + + trace!(" ringbuf: push_buf {:?}..{:?}", end, end + n); + (unsafe { buf.add(end) }, n) + } + + /// Get up to two buffers where data can be pushed to. + /// + /// Write data starting at the beginning of the first buffer, then call + /// `push_done` with however many bytes you've pushed. + /// + /// The buffers are suitable to DMA to. + /// + /// If the ringbuf is full, both buffers will be zero length. + /// If there is only area available, the second buffer will be zero length. + /// + /// 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_bufs(&mut self) -> [(*mut u8, usize); 2] { + // Ordering: as per push_buf() + let mut start = self.0.start.load(Ordering::Acquire); + let buf = self.0.buf.load(Ordering::Relaxed); + let len = self.0.len.load(Ordering::Relaxed); + let mut end = self.0.end.load(Ordering::Relaxed); + + let empty = start == end; + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + if start == end && !empty { + // full + return [(buf, 0), (buf, 0)]; + } + let n0 = if start > end { start - end } else { len - end }; + let n1 = if start <= end { start } else { 0 }; + + trace!(" ringbuf: push_bufs [{:?}..{:?}, {:?}..{:?}]", end, end + n0, 0, n1); + [(unsafe { buf.add(end) }, n0), (buf, n1)] + } + + /// Mark n bytes as written and advance the write index. + 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 successfully. + 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 mut end = self.0.end.load(Ordering::Acquire); + let buf = self.0.buf.load(Ordering::Relaxed); + let len = self.0.len.load(Ordering::Relaxed); + let mut start = self.0.start.load(Ordering::Relaxed); + + if start == end { + return (buf, 0); + } + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + let n = if end > start { end - start } else { len - start }; + + trace!(" ringbuf: pop_buf {:?}..{:?}", start, start + n); + (unsafe { buf.add(start) }, n) + } + + /// Mark n bytes as read and allow advance the read index. + 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| { + assert_eq!(4, buf.len()); + buf[0] = 1; + buf[1] = 2; + buf[2] = 3; + buf[3] = 4; + 4 + }); + + 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!(4, 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!(3, buf.len()); + 0 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), false); + + rb.reader().pop(|buf| { + assert_eq!(3, buf.len()); + assert_eq!(2, buf[0]); + assert_eq!(3, buf[1]); + 2 + }); + rb.reader().pop(|buf| { + assert_eq!(1, buf.len()); + assert_eq!(4, buf[0]); + 1 + }); + + 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!(4, buf.len()); + buf[0] = 10; + 1 + }); + + rb.writer().push(|buf| { + assert_eq!(3, buf.len()); + buf[0] = 11; + buf[1] = 12; + 2 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), false); + + rb.writer().push(|buf| { + assert_eq!(1, buf.len()); + buf[0] = 13; + 1 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), true); + } + } + + #[test] + fn zero_len() { + let mut b = [0; 0]; + + let rb = RingBuffer::new(); + unsafe { + rb.init(b.as_mut_ptr(), b.len()); + + 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 + }); + } + } + + #[test] + fn push_slices() { + let mut b = [0; 4]; + let rb = RingBuffer::new(); + unsafe { + rb.init(b.as_mut_ptr(), 4); + + /* push 3 -> [1 2 3 x] */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(4, ps[0].len()); + assert_eq!(0, ps[1].len()); + ps[0][0] = 1; + ps[0][1] = 2; + ps[0][2] = 3; + w.push_done(3); + drop(w); + + /* pop 2 -> [x x 3 x] */ + rb.reader().pop(|buf| { + assert_eq!(3, buf.len()); + assert_eq!(1, buf[0]); + assert_eq!(2, buf[1]); + assert_eq!(3, buf[2]); + 2 + }); + + /* push 3 -> [5 6 3 4] */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(1, ps[0].len()); + assert_eq!(2, ps[1].len()); + ps[0][0] = 4; + ps[1][0] = 5; + ps[1][1] = 6; + w.push_done(3); + drop(w); + + /* buf is now full */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(0, ps[0].len()); + assert_eq!(0, ps[1].len()); + + /* pop 2 -> [5 6 x x] */ + rb.reader().pop(|buf| { + assert_eq!(2, buf.len()); + assert_eq!(3, buf[0]); + assert_eq!(4, buf[1]); + 2 + }); + + /* should now have one push slice again */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(2, ps[0].len()); + assert_eq!(0, ps[1].len()); + drop(w); + + /* pop 2 -> [x x x x] */ + rb.reader().pop(|buf| { + assert_eq!(2, buf.len()); + assert_eq!(5, buf[0]); + assert_eq!(6, buf[1]); + 2 + }); + + /* should now have two push slices */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(2, ps[0].len()); + assert_eq!(2, ps[1].len()); + drop(w); + + /* make sure we exercise all wrap around cases properly */ + for _ in 0..10 { + /* should be empty, push 1 */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(4, ps[0].len() + ps[1].len()); + w.push_done(1); + drop(w); + + /* should have 1 element */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(3, ps[0].len() + ps[1].len()); + drop(w); + + /* pop 1 */ + rb.reader().pop(|buf| { + assert_eq!(1, buf.len()); + 1 + }); + } + } + } +} diff --git a/embassy/embassy-hal-internal/src/drop.rs b/embassy/embassy-hal-internal/src/drop.rs new file mode 100644 index 0000000..8383fcc --- /dev/null +++ b/embassy/embassy-hal-internal/src/drop.rs @@ -0,0 +1,56 @@ +//! Types for controlling when drop is invoked. +use core::mem; +use core::mem::MaybeUninit; + +/// A type to delay the drop handler invocation. +#[must_use = "to delay the drop handler invocation to the end of the scope"] +pub struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + /// Create a new instance. + pub fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } + + /// Prevent drop handler from running. + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} + +/// An explosive ordinance that panics if it is improperly disposed of. +/// +/// This is to forbid dropping futures, when there is absolutely no other choice. +/// +/// To correctly dispose of this device, call the [defuse](struct.DropBomb.html#method.defuse) +/// method before this object is dropped. +#[must_use = "to delay the drop bomb invokation to the end of the scope"] +pub struct DropBomb { + _private: (), +} + +impl DropBomb { + /// Create a new instance. + pub fn new() -> Self { + Self { _private: () } + } + + /// Defuses the bomb, rendering it safe to drop. + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for DropBomb { + fn drop(&mut self) { + panic!("boom") + } +} diff --git a/embassy/embassy-hal-internal/src/fmt.rs b/embassy/embassy-hal-internal/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-hal-internal/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-hal-internal/src/interrupt.rs b/embassy/embassy-hal-internal/src/interrupt.rs new file mode 100644 index 0000000..5e64dce --- /dev/null +++ b/embassy/embassy-hal-internal/src/interrupt.rs @@ -0,0 +1,870 @@ +//! Interrupt handling for cortex-m devices. +use core::mem; +use core::sync::atomic::{compiler_fence, Ordering}; + +use cortex_m::interrupt::InterruptNumber; +use cortex_m::peripheral::NVIC; +use critical_section::CriticalSection; + +/// Generate a standard `mod interrupt` for a HAL. +#[macro_export] +macro_rules! interrupt_mod { + ($($irqs:ident),* $(,)?) => { + #[cfg(feature = "rt")] + pub use cortex_m_rt::interrupt; + + /// Interrupt definitions. + pub mod interrupt { + pub use $crate::interrupt::{InterruptExt, Priority}; + pub use crate::pac::Interrupt::*; + pub use crate::pac::Interrupt; + + /// Type-level interrupt infrastructure. + /// + /// This module contains one *type* per interrupt. This is used for checking at compile time that + /// the interrupts are correctly bound to HAL drivers. + /// + /// As an end user, you shouldn't need to use this module directly. Use the [`crate::bind_interrupts!`] macro + /// to bind interrupts, and the [`crate::interrupt`] module to manually register interrupt handlers and manipulate + /// interrupts directly (pending/unpending, enabling/disabling, setting the priority, etc...) + pub mod typelevel { + use super::InterruptExt; + + trait SealedInterrupt {} + + /// Type-level interrupt. + /// + /// This trait is implemented for all typelevel interrupt types in this module. + pub trait Interrupt: SealedInterrupt { + + /// Interrupt enum variant. + /// + /// This allows going from typelevel interrupts (one type per interrupt) to + /// non-typelevel interrupts (a single `Interrupt` enum type, with one variant per interrupt). + const IRQ: super::Interrupt; + + /// Enable the interrupt. + #[inline] + unsafe fn enable() { + Self::IRQ.enable() + } + + /// Disable the interrupt. + #[inline] + fn disable() { + Self::IRQ.disable() + } + + /// Check if interrupt is enabled. + #[inline] + fn is_enabled() -> bool { + Self::IRQ.is_enabled() + } + + /// Check if interrupt is pending. + #[inline] + fn is_pending() -> bool { + Self::IRQ.is_pending() + } + + /// Set interrupt pending. + #[inline] + fn pend() { + Self::IRQ.pend() + } + + /// Unset interrupt pending. + #[inline] + fn unpend() { + Self::IRQ.unpend() + } + + /// Get the priority of the interrupt. + #[inline] + fn get_priority() -> crate::interrupt::Priority { + Self::IRQ.get_priority() + } + + /// Set the interrupt priority. + #[inline] + fn set_priority(prio: crate::interrupt::Priority) { + Self::IRQ.set_priority(prio) + } + + /// Set the interrupt priority with an already-acquired critical section + #[inline] + fn set_priority_with_cs(cs: critical_section::CriticalSection, prio: crate::interrupt::Priority) { + Self::IRQ.set_priority_with_cs(cs, prio) + } + } + + $( + #[allow(non_camel_case_types)] + #[doc=stringify!($irqs)] + #[doc=" typelevel interrupt."] + pub enum $irqs {} + impl SealedInterrupt for $irqs{} + impl Interrupt for $irqs { + const IRQ: super::Interrupt = super::Interrupt::$irqs; + } + )* + + /// Interrupt handler trait. + /// + /// Drivers that need to handle interrupts implement this trait. + /// The user must ensure `on_interrupt()` is called every time the interrupt fires. + /// Drivers must use use [`Binding`] to assert at compile time that the user has done so. + pub trait Handler { + /// Interrupt handler function. + /// + /// Must be called every time the `I` interrupt fires, synchronously from + /// the interrupt handler context. + /// + /// # Safety + /// + /// This function must ONLY be called from the interrupt handler for `I`. + unsafe fn on_interrupt(); + } + + /// Compile-time assertion that an interrupt has been bound to a handler. + /// + /// For the vast majority of cases, you should use the `bind_interrupts!` + /// macro instead of writing `unsafe impl`s of this trait. + /// + /// # Safety + /// + /// By implementing this trait, you are asserting that you have arranged for `H::on_interrupt()` + /// to be called every time the `I` interrupt fires. + /// + /// This allows drivers to check bindings at compile-time. + pub unsafe trait Binding> {} + } + } + }; +} + +/// Represents an interrupt type that can be configured by embassy to handle +/// interrupts. +pub unsafe trait InterruptExt: InterruptNumber + Copy { + /// Enable the interrupt. + #[inline] + unsafe fn enable(self) { + compiler_fence(Ordering::SeqCst); + NVIC::unmask(self) + } + + /// Disable the interrupt. + #[inline] + fn disable(self) { + NVIC::mask(self); + compiler_fence(Ordering::SeqCst); + } + + /// Check if interrupt is being handled. + #[inline] + #[cfg(not(armv6m))] + fn is_active(self) -> bool { + NVIC::is_active(self) + } + + /// Check if interrupt is enabled. + #[inline] + fn is_enabled(self) -> bool { + NVIC::is_enabled(self) + } + + /// Check if interrupt is pending. + #[inline] + fn is_pending(self) -> bool { + NVIC::is_pending(self) + } + + /// Set interrupt pending. + #[inline] + fn pend(self) { + NVIC::pend(self) + } + + /// Unset interrupt pending. + #[inline] + fn unpend(self) { + NVIC::unpend(self) + } + + /// Get the priority of the interrupt. + #[inline] + fn get_priority(self) -> Priority { + Priority::from(NVIC::get_priority(self)) + } + + /// Set the interrupt priority. + #[inline] + fn set_priority(self, prio: Priority) { + unsafe { + let mut nvic: cortex_m::peripheral::NVIC = mem::transmute(()); + + // On thumbv6, set_priority must do a RMW to change 8bit in a 32bit reg. + #[cfg(armv6m)] + critical_section::with(|_| nvic.set_priority(self, prio.into())); + // On thumbv7+, set_priority does an atomic 8bit write, so no CS needed. + #[cfg(not(armv6m))] + nvic.set_priority(self, prio.into()); + } + } + + /// Set the interrupt priority with an already-acquired critical section + /// + /// Equivalent to `set_priority`, except you pass a `CriticalSection` to prove + /// you've already acquired a critical section. This prevents acquiring another + /// one, which saves code size. + #[inline] + fn set_priority_with_cs(self, _cs: CriticalSection, prio: Priority) { + unsafe { + let mut nvic: cortex_m::peripheral::NVIC = mem::transmute(()); + nvic.set_priority(self, prio.into()); + } + } +} + +unsafe impl InterruptExt for T {} + +impl From for Priority { + fn from(priority: u8) -> Self { + unsafe { mem::transmute(priority & PRIO_MASK) } + } +} + +impl From for u8 { + fn from(p: Priority) -> Self { + p as u8 + } +} + +#[cfg(feature = "prio-bits-0")] +const PRIO_MASK: u8 = 0x00; +#[cfg(feature = "prio-bits-1")] +const PRIO_MASK: u8 = 0x80; +#[cfg(feature = "prio-bits-2")] +const PRIO_MASK: u8 = 0xc0; +#[cfg(feature = "prio-bits-3")] +const PRIO_MASK: u8 = 0xe0; +#[cfg(feature = "prio-bits-4")] +const PRIO_MASK: u8 = 0xf0; +#[cfg(feature = "prio-bits-5")] +const PRIO_MASK: u8 = 0xf8; +#[cfg(feature = "prio-bits-6")] +const PRIO_MASK: u8 = 0xfc; +#[cfg(feature = "prio-bits-7")] +const PRIO_MASK: u8 = 0xfe; +#[cfg(feature = "prio-bits-8")] +const PRIO_MASK: u8 = 0xff; + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-0")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-1")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x80, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-2")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x40, + P2 = 0x80, + P3 = 0xc0, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-3")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x20, + P2 = 0x40, + P3 = 0x60, + P4 = 0x80, + P5 = 0xa0, + P6 = 0xc0, + P7 = 0xe0, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-4")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x10, + P2 = 0x20, + P3 = 0x30, + P4 = 0x40, + P5 = 0x50, + P6 = 0x60, + P7 = 0x70, + P8 = 0x80, + P9 = 0x90, + P10 = 0xa0, + P11 = 0xb0, + P12 = 0xc0, + P13 = 0xd0, + P14 = 0xe0, + P15 = 0xf0, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-5")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x8, + P2 = 0x10, + P3 = 0x18, + P4 = 0x20, + P5 = 0x28, + P6 = 0x30, + P7 = 0x38, + P8 = 0x40, + P9 = 0x48, + P10 = 0x50, + P11 = 0x58, + P12 = 0x60, + P13 = 0x68, + P14 = 0x70, + P15 = 0x78, + P16 = 0x80, + P17 = 0x88, + P18 = 0x90, + P19 = 0x98, + P20 = 0xa0, + P21 = 0xa8, + P22 = 0xb0, + P23 = 0xb8, + P24 = 0xc0, + P25 = 0xc8, + P26 = 0xd0, + P27 = 0xd8, + P28 = 0xe0, + P29 = 0xe8, + P30 = 0xf0, + P31 = 0xf8, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-6")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x4, + P2 = 0x8, + P3 = 0xc, + P4 = 0x10, + P5 = 0x14, + P6 = 0x18, + P7 = 0x1c, + P8 = 0x20, + P9 = 0x24, + P10 = 0x28, + P11 = 0x2c, + P12 = 0x30, + P13 = 0x34, + P14 = 0x38, + P15 = 0x3c, + P16 = 0x40, + P17 = 0x44, + P18 = 0x48, + P19 = 0x4c, + P20 = 0x50, + P21 = 0x54, + P22 = 0x58, + P23 = 0x5c, + P24 = 0x60, + P25 = 0x64, + P26 = 0x68, + P27 = 0x6c, + P28 = 0x70, + P29 = 0x74, + P30 = 0x78, + P31 = 0x7c, + P32 = 0x80, + P33 = 0x84, + P34 = 0x88, + P35 = 0x8c, + P36 = 0x90, + P37 = 0x94, + P38 = 0x98, + P39 = 0x9c, + P40 = 0xa0, + P41 = 0xa4, + P42 = 0xa8, + P43 = 0xac, + P44 = 0xb0, + P45 = 0xb4, + P46 = 0xb8, + P47 = 0xbc, + P48 = 0xc0, + P49 = 0xc4, + P50 = 0xc8, + P51 = 0xcc, + P52 = 0xd0, + P53 = 0xd4, + P54 = 0xd8, + P55 = 0xdc, + P56 = 0xe0, + P57 = 0xe4, + P58 = 0xe8, + P59 = 0xec, + P60 = 0xf0, + P61 = 0xf4, + P62 = 0xf8, + P63 = 0xfc, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-7")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x2, + P2 = 0x4, + P3 = 0x6, + P4 = 0x8, + P5 = 0xa, + P6 = 0xc, + P7 = 0xe, + P8 = 0x10, + P9 = 0x12, + P10 = 0x14, + P11 = 0x16, + P12 = 0x18, + P13 = 0x1a, + P14 = 0x1c, + P15 = 0x1e, + P16 = 0x20, + P17 = 0x22, + P18 = 0x24, + P19 = 0x26, + P20 = 0x28, + P21 = 0x2a, + P22 = 0x2c, + P23 = 0x2e, + P24 = 0x30, + P25 = 0x32, + P26 = 0x34, + P27 = 0x36, + P28 = 0x38, + P29 = 0x3a, + P30 = 0x3c, + P31 = 0x3e, + P32 = 0x40, + P33 = 0x42, + P34 = 0x44, + P35 = 0x46, + P36 = 0x48, + P37 = 0x4a, + P38 = 0x4c, + P39 = 0x4e, + P40 = 0x50, + P41 = 0x52, + P42 = 0x54, + P43 = 0x56, + P44 = 0x58, + P45 = 0x5a, + P46 = 0x5c, + P47 = 0x5e, + P48 = 0x60, + P49 = 0x62, + P50 = 0x64, + P51 = 0x66, + P52 = 0x68, + P53 = 0x6a, + P54 = 0x6c, + P55 = 0x6e, + P56 = 0x70, + P57 = 0x72, + P58 = 0x74, + P59 = 0x76, + P60 = 0x78, + P61 = 0x7a, + P62 = 0x7c, + P63 = 0x7e, + P64 = 0x80, + P65 = 0x82, + P66 = 0x84, + P67 = 0x86, + P68 = 0x88, + P69 = 0x8a, + P70 = 0x8c, + P71 = 0x8e, + P72 = 0x90, + P73 = 0x92, + P74 = 0x94, + P75 = 0x96, + P76 = 0x98, + P77 = 0x9a, + P78 = 0x9c, + P79 = 0x9e, + P80 = 0xa0, + P81 = 0xa2, + P82 = 0xa4, + P83 = 0xa6, + P84 = 0xa8, + P85 = 0xaa, + P86 = 0xac, + P87 = 0xae, + P88 = 0xb0, + P89 = 0xb2, + P90 = 0xb4, + P91 = 0xb6, + P92 = 0xb8, + P93 = 0xba, + P94 = 0xbc, + P95 = 0xbe, + P96 = 0xc0, + P97 = 0xc2, + P98 = 0xc4, + P99 = 0xc6, + P100 = 0xc8, + P101 = 0xca, + P102 = 0xcc, + P103 = 0xce, + P104 = 0xd0, + P105 = 0xd2, + P106 = 0xd4, + P107 = 0xd6, + P108 = 0xd8, + P109 = 0xda, + P110 = 0xdc, + P111 = 0xde, + P112 = 0xe0, + P113 = 0xe2, + P114 = 0xe4, + P115 = 0xe6, + P116 = 0xe8, + P117 = 0xea, + P118 = 0xec, + P119 = 0xee, + P120 = 0xf0, + P121 = 0xf2, + P122 = 0xf4, + P123 = 0xf6, + P124 = 0xf8, + P125 = 0xfa, + P126 = 0xfc, + P127 = 0xfe, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-8")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x1, + P2 = 0x2, + P3 = 0x3, + P4 = 0x4, + P5 = 0x5, + P6 = 0x6, + P7 = 0x7, + P8 = 0x8, + P9 = 0x9, + P10 = 0xa, + P11 = 0xb, + P12 = 0xc, + P13 = 0xd, + P14 = 0xe, + P15 = 0xf, + P16 = 0x10, + P17 = 0x11, + P18 = 0x12, + P19 = 0x13, + P20 = 0x14, + P21 = 0x15, + P22 = 0x16, + P23 = 0x17, + P24 = 0x18, + P25 = 0x19, + P26 = 0x1a, + P27 = 0x1b, + P28 = 0x1c, + P29 = 0x1d, + P30 = 0x1e, + P31 = 0x1f, + P32 = 0x20, + P33 = 0x21, + P34 = 0x22, + P35 = 0x23, + P36 = 0x24, + P37 = 0x25, + P38 = 0x26, + P39 = 0x27, + P40 = 0x28, + P41 = 0x29, + P42 = 0x2a, + P43 = 0x2b, + P44 = 0x2c, + P45 = 0x2d, + P46 = 0x2e, + P47 = 0x2f, + P48 = 0x30, + P49 = 0x31, + P50 = 0x32, + P51 = 0x33, + P52 = 0x34, + P53 = 0x35, + P54 = 0x36, + P55 = 0x37, + P56 = 0x38, + P57 = 0x39, + P58 = 0x3a, + P59 = 0x3b, + P60 = 0x3c, + P61 = 0x3d, + P62 = 0x3e, + P63 = 0x3f, + P64 = 0x40, + P65 = 0x41, + P66 = 0x42, + P67 = 0x43, + P68 = 0x44, + P69 = 0x45, + P70 = 0x46, + P71 = 0x47, + P72 = 0x48, + P73 = 0x49, + P74 = 0x4a, + P75 = 0x4b, + P76 = 0x4c, + P77 = 0x4d, + P78 = 0x4e, + P79 = 0x4f, + P80 = 0x50, + P81 = 0x51, + P82 = 0x52, + P83 = 0x53, + P84 = 0x54, + P85 = 0x55, + P86 = 0x56, + P87 = 0x57, + P88 = 0x58, + P89 = 0x59, + P90 = 0x5a, + P91 = 0x5b, + P92 = 0x5c, + P93 = 0x5d, + P94 = 0x5e, + P95 = 0x5f, + P96 = 0x60, + P97 = 0x61, + P98 = 0x62, + P99 = 0x63, + P100 = 0x64, + P101 = 0x65, + P102 = 0x66, + P103 = 0x67, + P104 = 0x68, + P105 = 0x69, + P106 = 0x6a, + P107 = 0x6b, + P108 = 0x6c, + P109 = 0x6d, + P110 = 0x6e, + P111 = 0x6f, + P112 = 0x70, + P113 = 0x71, + P114 = 0x72, + P115 = 0x73, + P116 = 0x74, + P117 = 0x75, + P118 = 0x76, + P119 = 0x77, + P120 = 0x78, + P121 = 0x79, + P122 = 0x7a, + P123 = 0x7b, + P124 = 0x7c, + P125 = 0x7d, + P126 = 0x7e, + P127 = 0x7f, + P128 = 0x80, + P129 = 0x81, + P130 = 0x82, + P131 = 0x83, + P132 = 0x84, + P133 = 0x85, + P134 = 0x86, + P135 = 0x87, + P136 = 0x88, + P137 = 0x89, + P138 = 0x8a, + P139 = 0x8b, + P140 = 0x8c, + P141 = 0x8d, + P142 = 0x8e, + P143 = 0x8f, + P144 = 0x90, + P145 = 0x91, + P146 = 0x92, + P147 = 0x93, + P148 = 0x94, + P149 = 0x95, + P150 = 0x96, + P151 = 0x97, + P152 = 0x98, + P153 = 0x99, + P154 = 0x9a, + P155 = 0x9b, + P156 = 0x9c, + P157 = 0x9d, + P158 = 0x9e, + P159 = 0x9f, + P160 = 0xa0, + P161 = 0xa1, + P162 = 0xa2, + P163 = 0xa3, + P164 = 0xa4, + P165 = 0xa5, + P166 = 0xa6, + P167 = 0xa7, + P168 = 0xa8, + P169 = 0xa9, + P170 = 0xaa, + P171 = 0xab, + P172 = 0xac, + P173 = 0xad, + P174 = 0xae, + P175 = 0xaf, + P176 = 0xb0, + P177 = 0xb1, + P178 = 0xb2, + P179 = 0xb3, + P180 = 0xb4, + P181 = 0xb5, + P182 = 0xb6, + P183 = 0xb7, + P184 = 0xb8, + P185 = 0xb9, + P186 = 0xba, + P187 = 0xbb, + P188 = 0xbc, + P189 = 0xbd, + P190 = 0xbe, + P191 = 0xbf, + P192 = 0xc0, + P193 = 0xc1, + P194 = 0xc2, + P195 = 0xc3, + P196 = 0xc4, + P197 = 0xc5, + P198 = 0xc6, + P199 = 0xc7, + P200 = 0xc8, + P201 = 0xc9, + P202 = 0xca, + P203 = 0xcb, + P204 = 0xcc, + P205 = 0xcd, + P206 = 0xce, + P207 = 0xcf, + P208 = 0xd0, + P209 = 0xd1, + P210 = 0xd2, + P211 = 0xd3, + P212 = 0xd4, + P213 = 0xd5, + P214 = 0xd6, + P215 = 0xd7, + P216 = 0xd8, + P217 = 0xd9, + P218 = 0xda, + P219 = 0xdb, + P220 = 0xdc, + P221 = 0xdd, + P222 = 0xde, + P223 = 0xdf, + P224 = 0xe0, + P225 = 0xe1, + P226 = 0xe2, + P227 = 0xe3, + P228 = 0xe4, + P229 = 0xe5, + P230 = 0xe6, + P231 = 0xe7, + P232 = 0xe8, + P233 = 0xe9, + P234 = 0xea, + P235 = 0xeb, + P236 = 0xec, + P237 = 0xed, + P238 = 0xee, + P239 = 0xef, + P240 = 0xf0, + P241 = 0xf1, + P242 = 0xf2, + P243 = 0xf3, + P244 = 0xf4, + P245 = 0xf5, + P246 = 0xf6, + P247 = 0xf7, + P248 = 0xf8, + P249 = 0xf9, + P250 = 0xfa, + P251 = 0xfb, + P252 = 0xfc, + P253 = 0xfd, + P254 = 0xfe, + P255 = 0xff, +} diff --git a/embassy/embassy-hal-internal/src/lib.rs b/embassy/embassy-hal-internal/src/lib.rs new file mode 100644 index 0000000..89f20e9 --- /dev/null +++ b/embassy/embassy-hal-internal/src/lib.rs @@ -0,0 +1,17 @@ +#![no_std] +#![allow(clippy::new_without_default)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// 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; +pub mod ratio; +pub use peripheral::{Peripheral, PeripheralRef}; + +#[cfg(feature = "cortex-m")] +pub mod interrupt; diff --git a/embassy/embassy-hal-internal/src/macros.rs b/embassy/embassy-hal-internal/src/macros.rs new file mode 100644 index 0000000..07cd894 --- /dev/null +++ b/embassy/embassy-hal-internal/src/macros.rs @@ -0,0 +1,135 @@ +/// Types for the peripheral singletons. +#[macro_export] +macro_rules! peripherals_definition { + ($($(#[$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])? + impl $name { + /// Unsafely create an instance of this peripheral out of thin air. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of this type at a time. + #[inline] + pub unsafe fn steal() -> Self { + Self{ _private: ()} + } + } + + $(#[$cfg])? + $crate::impl_peripheral!($name); + )* + } + }; +} + +/// Define the peripherals struct. +#[macro_export] +macro_rules! peripherals_struct { + ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + /// 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, + )* + } + + impl Peripherals { + ///Returns all the peripherals *once* + #[inline] + pub(crate) fn take() -> Self { + critical_section::with(Self::take_with_cs) + } + + ///Returns all the peripherals *once* + #[inline] + pub(crate) fn take_with_cs(_cs: critical_section::CriticalSection) -> Self { + #[no_mangle] + static mut _EMBASSY_DEVICE_PERIPHERALS: bool = false; + + // safety: OK because we're inside a CS. + unsafe { + if _EMBASSY_DEVICE_PERIPHERALS { + panic!("init called more than once!") + } + _EMBASSY_DEVICE_PERIPHERALS = true; + Self::steal() + } + } + } + + impl Peripherals { + /// Unsafely create an instance of this peripheral out of thin air. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of this type at a time. + #[inline] + pub unsafe fn steal() -> Self { + Self { + $( + $(#[$cfg])? + $name: peripherals::$name::steal(), + )* + } + } + } + }; +} + +/// Defining peripheral type. +#[macro_export] +macro_rules! peripherals { + ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + $crate::peripherals_definition!( + $( + $(#[$cfg])? + $name, + )* + ); + $crate::peripherals_struct!( + $( + $(#[$cfg])? + $name, + )* + ); + }; +} + +/// Convenience converting into reference. +#[macro_export] +macro_rules! into_ref { + ($($name:ident),*) => { + $( + let mut $name = $name.into_ref(); + )* + } +} + +/// Implement the peripheral trait. +#[macro_export] +macro_rules! impl_peripheral { + ($type:ident) => { + impl $crate::Peripheral for $type { + type P = $type; + + #[inline] + unsafe fn clone_unchecked(&self) -> Self::P { + #[allow(clippy::needless_update)] + $type { ..*self } + } + } + }; +} diff --git a/embassy/embassy-hal-internal/src/peripheral.rs b/embassy/embassy-hal-internal/src/peripheral.rs new file mode 100644 index 0000000..0b0f133 --- /dev/null +++ b/embassy/embassy-hal-internal/src/peripheral.rs @@ -0,0 +1,177 @@ +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; + +/// An exclusive reference to a peripheral. +/// +/// This is functionally the same as a `&'a mut T`. There's a few advantages in having +/// a dedicated struct instead: +/// +/// - 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>, +} + +impl<'a, T> PeripheralRef<'a, T> { + /// Create a new reference to a peripheral. + #[inline] + pub fn new(inner: T) -> Self { + Self { + inner, + _lifetime: PhantomData, + } + } + + /// Unsafely clone (duplicate) a peripheral singleton. + /// + /// # Safety + /// + /// This returns an owned clone of the peripheral. You must manually ensure + /// only one copy of the peripheral is in use at a time. For example, don't + /// create two SPI drivers on `SPI1`, because they will "fight" each other. + /// + /// You should strongly prefer using `reborrow()` instead. It returns a + /// `PeripheralRef` that borrows `self`, which allows the borrow checker + /// to enforce this at compile time. + pub unsafe fn clone_unchecked(&self) -> PeripheralRef<'a, T> + where + T: Peripheral

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

, + { + // safety: we're returning the clone inside a new PeripheralRef that borrows + // self, so user code can't use both at the same time. + PeripheralRef::new(unsafe { self.inner.clone_unchecked() }) + } + + /// Map the inner peripheral using `Into`. + /// + /// This converts from `PeripheralRef<'a, T>` to `PeripheralRef<'a, U>`, using an + /// `Into` impl to convert from `T` to `U`. + /// + /// For example, this can be useful to degrade GPIO pins: converting from PeripheralRef<'a, PB11>` to `PeripheralRef<'a, AnyPin>`. + #[inline] + pub fn map_into(self) -> PeripheralRef<'a, U> + where + T: Into, + { + PeripheralRef { + inner: self.inner.into(), + _lifetime: PhantomData, + } + } +} + +impl<'a, T> Deref for PeripheralRef<'a, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// Trait for any type that can be used as a peripheral of type `P`. +/// +/// This is used in driver constructors, to allow passing either owned peripherals (e.g. `TWISPI0`), +/// or borrowed peripherals (e.g. `&mut TWISPI0`). +/// +/// For example, if you have a driver with a constructor like this: +/// +/// ```ignore +/// impl<'d, T: Instance> Twim<'d, T> { +/// pub fn new( +/// twim: impl Peripheral

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

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

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

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

+ 'd, + timer: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ppi_group: impl Peripheral

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

+ 'd, + txd: impl Peripheral

+ 'd, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(uarte, timer, rxd, txd, ppi_ch1, ppi_ch2, ppi_group); + Self::new_inner( + uarte, + timer, + ppi_ch1.map_into(), + ppi_ch2.map_into(), + ppi_group.map_into(), + rxd.map_into(), + txd.map_into(), + None, + None, + config, + rx_buffer, + tx_buffer, + ) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] + pub fn new_with_rtscts( + uarte: impl Peripheral

+ 'd, + timer: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ppi_group: impl Peripheral

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

+ 'd, + txd: impl Peripheral

+ 'd, + cts: impl Peripheral

+ 'd, + rts: impl Peripheral

+ 'd, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(uarte, timer, rxd, txd, cts, rts, ppi_ch1, ppi_ch2, ppi_group); + Self::new_inner( + uarte, + timer, + ppi_ch1.map_into(), + ppi_ch2.map_into(), + ppi_group.map_into(), + rxd.map_into(), + txd.map_into(), + Some(cts.map_into()), + Some(rts.map_into()), + config, + rx_buffer, + tx_buffer, + ) + } + + #[allow(clippy::too_many_arguments)] + fn new_inner( + peri: PeripheralRef<'d, U>, + timer: PeripheralRef<'d, T>, + ppi_ch1: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_ch2: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_group: PeripheralRef<'d, AnyGroup>, + rxd: PeripheralRef<'d, AnyPin>, + txd: PeripheralRef<'d, AnyPin>, + cts: Option>, + rts: Option>, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + configure(U::regs(), config, cts.is_some()); + + let tx = BufferedUarteTx::new_innerer(unsafe { peri.clone_unchecked() }, txd, cts, tx_buffer); + let rx = BufferedUarteRx::new_innerer(peri, timer, ppi_ch1, ppi_ch2, ppi_group, rxd, rts, rx_buffer); + + U::regs().enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + U::Interrupt::pend(); + unsafe { U::Interrupt::enable() }; + + U::state().tx_rx_refcount.store(2, Ordering::Relaxed); + + Self { tx, rx } + } + + /// Adjust the baud rate to the provided value. + pub fn set_baudrate(&mut self, baudrate: Baudrate) { + let r = U::regs(); + r.baudrate().write(|w| w.set_baudrate(baudrate)); + } + + /// Split the UART in reader and writer parts. + /// + /// This allows reading and writing concurrently from independent tasks. + pub fn split(self) -> (BufferedUarteRx<'d, U, T>, BufferedUarteTx<'d, U>) { + (self.rx, self.tx) + } + + /// Split the UART in reader and writer parts, by reference. + /// + /// The returned halves borrow from `self`, so you can drop them and go back to using + /// the "un-split" `self`. This allows temporarily splitting the UART. + pub fn split_by_ref(&mut self) -> (&mut BufferedUarteRx<'d, U, T>, &mut BufferedUarteTx<'d, U>) { + (&mut self.rx, &mut self.tx) + } + + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.read(buf).await + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + pub async fn fill_buf(&mut self) -> Result<&[u8], Error> { + self.rx.fill_buf().await + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + self.rx.consume(amt) + } + + /// Write a buffer into this writer, returning how many bytes were written. + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.tx.write(buf).await + } + + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + self.tx.try_write(buf) + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub async fn flush(&mut self) -> Result<(), Error> { + self.tx.flush().await + } +} + +/// Reader part of the buffered UARTE driver. +pub struct BufferedUarteTx<'d, U: UarteInstance> { + _peri: PeripheralRef<'d, U>, +} + +impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { + /// Create a new BufferedUarteTx without hardware flow control. + pub fn new( + uarte: impl Peripheral

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

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

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

+ 'd, + cts: impl Peripheral

+ 'd, + config: Config, + tx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(uarte, txd, cts); + Self::new_inner(uarte, txd.map_into(), Some(cts.map_into()), config, tx_buffer) + } + + fn new_inner( + peri: PeripheralRef<'d, U>, + txd: PeripheralRef<'d, AnyPin>, + cts: Option>, + config: Config, + tx_buffer: &'d mut [u8], + ) -> Self { + configure(U::regs(), config, cts.is_some()); + + let this = Self::new_innerer(peri, txd, cts, tx_buffer); + + U::regs().enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + U::Interrupt::pend(); + unsafe { U::Interrupt::enable() }; + + U::state().tx_rx_refcount.store(1, Ordering::Relaxed); + + this + } + + fn new_innerer( + peri: PeripheralRef<'d, U>, + txd: PeripheralRef<'d, AnyPin>, + cts: Option>, + tx_buffer: &'d mut [u8], + ) -> Self { + let r = U::regs(); + + configure_tx_pins(r, txd, cts); + + // Initialize state + let s = U::buffered_state(); + s.tx_count.store(0, Ordering::Relaxed); + let len = tx_buffer.len(); + unsafe { s.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + + r.events_txstarted().write_value(0); + + // Enable interrupts + r.intenset().write(|w| { + w.set_endtx(true); + }); + + Self { _peri: peri } + } + + /// Write a buffer into this writer, returning how many bytes were written. + pub async fn write(&mut self, buf: &[u8]) -> Result { + poll_fn(move |cx| { + //trace!("poll_write: {:?}", buf.len()); + let ss = U::state(); + let s = U::buffered_state(); + let mut tx = unsafe { s.tx_buf.writer() }; + + let tx_buf = tx.push_slice(); + if tx_buf.is_empty() { + //trace!("poll_write: pending"); + ss.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + let n = min(tx_buf.len(), buf.len()); + tx_buf[..n].copy_from_slice(&buf[..n]); + tx.push_done(n); + + //trace!("poll_write: queued {:?}", n); + + compiler_fence(Ordering::SeqCst); + U::Interrupt::pend(); + + Poll::Ready(Ok(n)) + }) + .await + } + + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + //trace!("poll_write: {:?}", buf.len()); + let s = U::buffered_state(); + let mut tx = unsafe { s.tx_buf.writer() }; + + let tx_buf = tx.push_slice(); + if tx_buf.is_empty() { + return Ok(0); + } + + let n = min(tx_buf.len(), buf.len()); + tx_buf[..n].copy_from_slice(&buf[..n]); + tx.push_done(n); + + //trace!("poll_write: queued {:?}", n); + + compiler_fence(Ordering::SeqCst); + U::Interrupt::pend(); + + Ok(n) + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub async fn flush(&mut self) -> Result<(), Error> { + poll_fn(move |cx| { + //trace!("poll_flush"); + let ss = U::state(); + let s = U::buffered_state(); + if !s.tx_buf.is_empty() { + //trace!("poll_flush: pending"); + ss.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + .await + } +} + +impl<'a, U: UarteInstance> Drop for BufferedUarteTx<'a, U> { + fn drop(&mut self) { + let r = U::regs(); + + r.intenclr().write(|w| { + w.set_txdrdy(true); + w.set_txstarted(true); + w.set_txstopped(true); + }); + r.events_txstopped().write_value(0); + r.tasks_stoptx().write_value(1); + while r.events_txstopped().read() == 0 {} + + let s = U::buffered_state(); + unsafe { s.tx_buf.deinit() } + + let s = U::state(); + drop_tx_rx(r, s); + } +} + +/// Reader part of the buffered UARTE driver. +pub struct BufferedUarteRx<'d, U: UarteInstance, T: TimerInstance> { + _peri: PeripheralRef<'d, U>, + timer: Timer<'d, T>, + _ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 1>, + _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 2>, + _ppi_group: PpiGroup<'d, AnyGroup>, +} + +impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { + /// Create a new BufferedUarte without hardware flow control. + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] + pub fn new( + uarte: impl Peripheral

+ 'd, + timer: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ppi_group: impl Peripheral

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

+ 'd, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(uarte, timer, rxd, ppi_ch1, ppi_ch2, ppi_group); + Self::new_inner( + uarte, + timer, + ppi_ch1.map_into(), + ppi_ch2.map_into(), + ppi_group.map_into(), + rxd.map_into(), + None, + config, + rx_buffer, + ) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] + pub fn new_with_rts( + uarte: impl Peripheral

+ 'd, + timer: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ppi_group: impl Peripheral

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

+ 'd, + rts: impl Peripheral

+ 'd, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(uarte, timer, rxd, rts, ppi_ch1, ppi_ch2, ppi_group); + Self::new_inner( + uarte, + timer, + ppi_ch1.map_into(), + ppi_ch2.map_into(), + ppi_group.map_into(), + rxd.map_into(), + Some(rts.map_into()), + config, + rx_buffer, + ) + } + + #[allow(clippy::too_many_arguments)] + fn new_inner( + peri: PeripheralRef<'d, U>, + timer: PeripheralRef<'d, T>, + ppi_ch1: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_ch2: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_group: PeripheralRef<'d, AnyGroup>, + rxd: PeripheralRef<'d, AnyPin>, + rts: Option>, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + configure(U::regs(), config, rts.is_some()); + + let this = Self::new_innerer(peri, timer, ppi_ch1, ppi_ch2, ppi_group, rxd, rts, rx_buffer); + + U::regs().enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + U::Interrupt::pend(); + unsafe { U::Interrupt::enable() }; + + U::state().tx_rx_refcount.store(1, Ordering::Relaxed); + + this + } + + #[allow(clippy::too_many_arguments)] + fn new_innerer( + peri: PeripheralRef<'d, U>, + timer: PeripheralRef<'d, T>, + ppi_ch1: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_ch2: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_group: PeripheralRef<'d, AnyGroup>, + rxd: PeripheralRef<'d, AnyPin>, + rts: Option>, + rx_buffer: &'d mut [u8], + ) -> Self { + assert!(rx_buffer.len() % 2 == 0); + + let r = U::regs(); + + configure_rx_pins(r, rxd, rts); + + // Initialize state + let s = U::buffered_state(); + s.rx_started_count.store(0, Ordering::Relaxed); + s.rx_ended_count.store(0, Ordering::Relaxed); + s.rx_started.store(false, Ordering::Relaxed); + let rx_len = rx_buffer.len().min(EASY_DMA_SIZE * 2); + unsafe { s.rx_buf.init(rx_buffer.as_mut_ptr(), rx_len) }; + + // clear errors + let errors = r.errorsrc().read(); + r.errorsrc().write_value(errors); + + r.events_rxstarted().write_value(0); + r.events_error().write_value(0); + r.events_endrx().write_value(0); + + // Enable interrupts + r.intenset().write(|w| { + w.set_endtx(true); + w.set_rxstarted(true); + w.set_error(true); + w.set_endrx(true); + }); + + // Configure byte counter. + let timer = Timer::new_counter(timer); + timer.cc(1).write(rx_len as u32 * 2); + timer.cc(1).short_compare_clear(); + timer.clear(); + timer.start(); + + let mut ppi_ch1 = Ppi::new_one_to_one(ppi_ch1, Event::from_reg(r.events_rxdrdy()), timer.task_count()); + ppi_ch1.enable(); + + s.rx_ppi_ch.store(ppi_ch2.number() as u8, Ordering::Relaxed); + let mut ppi_group = PpiGroup::new(ppi_group); + let mut ppi_ch2 = Ppi::new_one_to_two( + ppi_ch2, + Event::from_reg(r.events_endrx()), + Task::from_reg(r.tasks_startrx()), + ppi_group.task_disable_all(), + ); + ppi_ch2.disable(); + ppi_group.add_channel(&ppi_ch2); + + Self { + _peri: peri, + timer, + _ppi_ch1: ppi_ch1, + _ppi_ch2: ppi_ch2, + _ppi_group: ppi_group, + } + } + + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + let data = self.fill_buf().await?; + let n = data.len().min(buf.len()); + buf[..n].copy_from_slice(&data[..n]); + self.consume(n); + Ok(n) + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + pub async fn fill_buf(&mut self) -> Result<&[u8], Error> { + poll_fn(move |cx| { + compiler_fence(Ordering::SeqCst); + //trace!("poll_read"); + + let r = U::regs(); + let s = U::buffered_state(); + let ss = U::state(); + + // Read the RXDRDY counter. + T::regs().tasks_capture(0).write_value(1); + let mut end = T::regs().cc(0).read() as usize; + //trace!(" rxdrdy count = {:?}", end); + + // We've set a compare channel that resets the counter to 0 when it reaches `len*2`. + // However, it's unclear if that's instant, or there's a small window where you can + // still read `len()*2`. + // This could happen if in one clock cycle the counter is updated, and in the next the + // clear takes effect. The docs are very sparse, they just say "Task delays: After TIMER + // is started, the CLEAR, COUNT, and STOP tasks are guaranteed to take effect within one + // clock cycle of the PCLK16M." :shrug: + // So, we wrap the counter ourselves, just in case. + if end > s.rx_buf.len() * 2 { + end = 0 + } + + // This logic mirrors `atomic_ring_buffer::Reader::pop_buf()` + let mut start = s.rx_buf.start.load(Ordering::Relaxed); + let len = s.rx_buf.len(); + if start == end { + //trace!(" empty"); + ss.rx_waker.register(cx.waker()); + r.intenset().write(|w| w.set_rxdrdy(true)); + return Poll::Pending; + } + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + let n = if end > start { end - start } else { len - start }; + assert!(n != 0); + //trace!(" uarte ringbuf: pop_buf {:?}..{:?}", start, start + n); + + let buf = s.rx_buf.buf.load(Ordering::Relaxed); + Poll::Ready(Ok(unsafe { slice::from_raw_parts(buf.add(start), n) })) + }) + .await + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + if amt == 0 { + return; + } + + let s = U::buffered_state(); + let mut rx = unsafe { s.rx_buf.reader() }; + rx.pop_done(amt); + U::regs().intenset().write(|w| w.set_rxstarted(true)); + } + + /// we are ready to read if there is data in the buffer + fn read_ready() -> Result { + let state = U::buffered_state(); + Ok(!state.rx_buf.is_empty()) + } +} + +impl<'a, U: UarteInstance, T: TimerInstance> Drop for BufferedUarteRx<'a, U, T> { + fn drop(&mut self) { + self._ppi_group.disable_all(); + + let r = U::regs(); + + self.timer.stop(); + + r.intenclr().write(|w| { + w.set_rxdrdy(true); + w.set_rxstarted(true); + w.set_rxto(true); + }); + r.events_rxto().write_value(0); + r.tasks_stoprx().write_value(1); + while r.events_rxto().read() == 0 {} + + let s = U::buffered_state(); + unsafe { s.rx_buf.deinit() } + + let s = U::state(); + drop_tx_rx(r, s); + } +} + +mod _embedded_io { + use super::*; + + impl embedded_io_async::Error for Error { + fn kind(&self) -> embedded_io_async::ErrorKind { + match *self {} + } + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::ErrorType for BufferedUarte<'d, U, T> { + type Error = Error; + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::ErrorType for BufferedUarteRx<'d, U, T> { + type Error = Error; + } + + impl<'d, U: UarteInstance> embedded_io_async::ErrorType for BufferedUarteTx<'d, U> { + type Error = Error; + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::Read for BufferedUarte<'d, U, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf).await + } + } + + impl<'d: 'd, U: UarteInstance, T: TimerInstance> embedded_io_async::Read for BufferedUarteRx<'d, U, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf).await + } + } + + impl<'d, U: UarteInstance, T: TimerInstance + 'd> embedded_io_async::ReadReady for BufferedUarte<'d, U, T> { + fn read_ready(&mut self) -> Result { + BufferedUarteRx::<'d, U, T>::read_ready() + } + } + + impl<'d, U: UarteInstance, T: TimerInstance + 'd> embedded_io_async::ReadReady for BufferedUarteRx<'d, U, T> { + fn read_ready(&mut self) -> Result { + Self::read_ready() + } + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::BufRead for BufferedUarte<'d, U, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.consume(amt) + } + } + + impl<'d: 'd, U: UarteInstance, T: TimerInstance> embedded_io_async::BufRead for BufferedUarteRx<'d, U, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.consume(amt) + } + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::Write for BufferedUarte<'d, U, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush().await + } + } + + impl<'d: 'd, U: UarteInstance> embedded_io_async::Write for BufferedUarteTx<'d, U> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush().await + } + } +} diff --git a/embassy/embassy-nrf/src/chips/nrf51.rs b/embassy/embassy-nrf/src/chips/nrf51.rs new file mode 100644 index 0000000..a0365a6 --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf51.rs @@ -0,0 +1,174 @@ +pub use nrf_pac as pac; + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 14) - 1; + +pub const FLASH_SIZE: usize = 128 * 1024; + +embassy_hal_internal::peripherals! { + // RTC + RTC0, + RTC1, + + // WDT + WDT, + + // NVMC + NVMC, + + // RNG + RNG, + + // UARTE + UART0, + + // SPI/TWI + TWI0, + SPI0, + + // ADC + ADC, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + P0_09, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // TEMP + TEMP, + + // Radio + RADIO, +} + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); + +impl_rng!(RNG, RNG, RNG); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +impl_pin!(P0_09, 0, 9); +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_radio!(RADIO, RADIO, RADIO); + +embassy_hal_internal::interrupt_mod!( + CLOCK_POWER, + RADIO, + UART0, + TWISPI0, + TWISPI1, + GPIOTE, + ADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + AAR_CCM, + WDT, + RTC1, + QDEC, + LPCOMP, + SWI0, + SWI1, + SWI2, + SWI3, + SWI4, + SWI5, +); diff --git a/embassy/embassy-nrf/src/chips/nrf52805.rs b/embassy/embassy-nrf/src/chips/nrf52805.rs new file mode 100644 index 0000000..a9cbcce --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf52805.rs @@ -0,0 +1,249 @@ +pub use nrf_pac as pac; + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 14) - 1; +pub const FORCE_COPY_BUFFER_SIZE: usize = 256; + +pub const FLASH_SIZE: usize = 192 * 1024; + +pub const RESET_PIN: u32 = 21; +pub const APPROTECT_MIN_BUILD_CODE: u8 = b'B'; + +embassy_hal_internal::peripherals! { + // RTC + RTC0, + RTC1, + + // WDT + WDT, + + // NVMC + NVMC, + + // RNG + RNG, + + // UARTE + UARTE0, + + // SPI/TWI + TWI0, + SPI0, + + // SAADC + SAADC, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + PPI_CH16, + PPI_CH17, + PPI_CH18, + PPI_CH19, + PPI_CH20, + PPI_CH21, + PPI_CH22, + PPI_CH23, + PPI_CH24, + PPI_CH25, + PPI_CH26, + PPI_CH27, + PPI_CH28, + PPI_CH29, + PPI_CH30, + PPI_CH31, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + P0_09, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + #[cfg(feature="reset-pin-as-gpio")] + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // TEMP + TEMP, + + // QDEC + QDEC, + + // Radio + RADIO, + + // EGU + EGU0, + EGU1, +} + +impl_uarte!(UARTE0, UARTE0, UARTE0); + +impl_spim!(SPI0, SPIM0, SPI0); + +impl_spis!(SPI0, SPIS0, SPI0); + +impl_twim!(TWI0, TWIM0, TWI0); + +impl_twis!(TWI0, TWIS0, TWI0); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +impl_pin!(P0_09, 0, 9); +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH20, 20 => static); +impl_ppi_channel!(PPI_CH21, 21 => static); +impl_ppi_channel!(PPI_CH22, 22 => static); +impl_ppi_channel!(PPI_CH23, 23 => static); +impl_ppi_channel!(PPI_CH24, 24 => static); +impl_ppi_channel!(PPI_CH25, 25 => static); +impl_ppi_channel!(PPI_CH26, 26 => static); +impl_ppi_channel!(PPI_CH27, 27 => static); +impl_ppi_channel!(PPI_CH28, 28 => static); +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, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); + +impl_radio!(RADIO, RADIO, RADIO); + +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); + +embassy_hal_internal::interrupt_mod!( + CLOCK_POWER, + RADIO, + UARTE0, + TWI0, + SPI0, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + AAR_CCM, + WDT, + RTC1, + QDEC, + EGU0_SWI0, + EGU1_SWI1, + SWI2, + SWI3, + SWI4, + SWI5, +); diff --git a/embassy/embassy-nrf/src/chips/nrf52810.rs b/embassy/embassy-nrf/src/chips/nrf52810.rs new file mode 100644 index 0000000..ca31c35 --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf52810.rs @@ -0,0 +1,278 @@ +pub use nrf_pac as pac; + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 10) - 1; +pub const FORCE_COPY_BUFFER_SIZE: usize = 256; + +pub const FLASH_SIZE: usize = 192 * 1024; + +pub const RESET_PIN: u32 = 21; +pub const APPROTECT_MIN_BUILD_CODE: u8 = b'E'; + +embassy_hal_internal::peripherals! { + // RTC + RTC0, + RTC1, + + // WDT + WDT, + + // NVMC + NVMC, + + // RNG + RNG, + + // UARTE + UARTE0, + + // SPI/TWI + TWI0, + SPI0, + + // SAADC + SAADC, + + // PWM + PWM0, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + PPI_CH16, + PPI_CH17, + PPI_CH18, + PPI_CH19, + PPI_CH20, + PPI_CH21, + PPI_CH22, + PPI_CH23, + PPI_CH24, + PPI_CH25, + PPI_CH26, + PPI_CH27, + PPI_CH28, + PPI_CH29, + PPI_CH30, + PPI_CH31, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + P0_09, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + #[cfg(feature="reset-pin-as-gpio")] + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // TEMP + TEMP, + + // QDEC + QDEC, + + // PDM + PDM, + + // Radio + RADIO, + + // EGU + EGU0, + EGU1, +} + +impl_uarte!(UARTE0, UARTE0, UARTE0); + +impl_spim!(SPI0, SPIM0, SPI0); + +impl_spis!(SPI0, SPIS0, SPI0); + +impl_twim!(TWI0, TWIM0, TWI0); + +impl_twis!(TWI0, TWIS0, TWI0); + +impl_pwm!(PWM0, PWM0, PWM0); + +impl_pdm!(PDM, PDM, PDM); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +impl_pin!(P0_09, 0, 9); +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH10, 10 => configurable); +impl_ppi_channel!(PPI_CH11, 11 => configurable); +impl_ppi_channel!(PPI_CH12, 12 => configurable); +impl_ppi_channel!(PPI_CH13, 13 => configurable); +impl_ppi_channel!(PPI_CH14, 14 => configurable); +impl_ppi_channel!(PPI_CH15, 15 => configurable); +impl_ppi_channel!(PPI_CH16, 16 => configurable); +impl_ppi_channel!(PPI_CH17, 17 => configurable); +impl_ppi_channel!(PPI_CH18, 18 => configurable); +impl_ppi_channel!(PPI_CH19, 19 => configurable); +impl_ppi_channel!(PPI_CH20, 20 => static); +impl_ppi_channel!(PPI_CH21, 21 => static); +impl_ppi_channel!(PPI_CH22, 22 => static); +impl_ppi_channel!(PPI_CH23, 23 => static); +impl_ppi_channel!(PPI_CH24, 24 => static); +impl_ppi_channel!(PPI_CH25, 25 => static); +impl_ppi_channel!(PPI_CH26, 26 => static); +impl_ppi_channel!(PPI_CH27, 27 => static); +impl_ppi_channel!(PPI_CH28, 28 => static); +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, 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_radio!(RADIO, RADIO, RADIO); + +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); + +embassy_hal_internal::interrupt_mod!( + CLOCK_POWER, + RADIO, + UARTE0, + TWI0, + SPI0, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + AAR_CCM, + WDT, + RTC1, + QDEC, + COMP, + EGU0_SWI0, + EGU1_SWI1, + SWI2, + SWI3, + SWI4, + SWI5, + PWM0, + PDM, +); diff --git a/embassy/embassy-nrf/src/chips/nrf52811.rs b/embassy/embassy-nrf/src/chips/nrf52811.rs new file mode 100644 index 0000000..caf3fdc --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf52811.rs @@ -0,0 +1,280 @@ +pub use nrf_pac as pac; + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 14) - 1; +pub const FORCE_COPY_BUFFER_SIZE: usize = 256; + +pub const FLASH_SIZE: usize = 192 * 1024; + +pub const RESET_PIN: u32 = 21; +pub const APPROTECT_MIN_BUILD_CODE: u8 = b'B'; + +embassy_hal_internal::peripherals! { + // RTC + RTC0, + RTC1, + + // WDT + WDT, + + // NVMC + NVMC, + + // RNG + RNG, + + // UARTE + UARTE0, + + // SPI/TWI + TWI0_SPI1, + SPI0, + + // SAADC + SAADC, + + // PWM + PWM0, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + PPI_CH16, + PPI_CH17, + PPI_CH18, + PPI_CH19, + PPI_CH20, + PPI_CH21, + PPI_CH22, + PPI_CH23, + PPI_CH24, + PPI_CH25, + PPI_CH26, + PPI_CH27, + PPI_CH28, + PPI_CH29, + PPI_CH30, + PPI_CH31, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + P0_09, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + #[cfg(feature="reset-pin-as-gpio")] + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // TEMP + TEMP, + + // QDEC + QDEC, + + // PDM + PDM, + + // Radio + RADIO, + + // EGU + EGU0, + EGU1, +} + +impl_uarte!(UARTE0, UARTE0, UARTE0); + +impl_spim!(SPI0, SPIM0, SPI0); +impl_spim!(TWI0_SPI1, SPIM1, TWI0_SPI1); + +impl_spis!(SPI0, SPIS0, SPI0); +impl_spis!(TWI0_SPI1, SPIS1, TWI0_SPI1); + +impl_twim!(TWI0_SPI1, TWIM0, TWI0_SPI1); + +impl_twis!(TWI0_SPI1, TWIS0, TWI0_SPI1); + +impl_pwm!(PWM0, PWM0, PWM0); + +impl_pdm!(PDM, PDM, PDM); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +impl_pin!(P0_09, 0, 9); +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH10, 10 => configurable); +impl_ppi_channel!(PPI_CH11, 11 => configurable); +impl_ppi_channel!(PPI_CH12, 12 => configurable); +impl_ppi_channel!(PPI_CH13, 13 => configurable); +impl_ppi_channel!(PPI_CH14, 14 => configurable); +impl_ppi_channel!(PPI_CH15, 15 => configurable); +impl_ppi_channel!(PPI_CH16, 16 => configurable); +impl_ppi_channel!(PPI_CH17, 17 => configurable); +impl_ppi_channel!(PPI_CH18, 18 => configurable); +impl_ppi_channel!(PPI_CH19, 19 => configurable); +impl_ppi_channel!(PPI_CH20, 20 => static); +impl_ppi_channel!(PPI_CH21, 21 => static); +impl_ppi_channel!(PPI_CH22, 22 => static); +impl_ppi_channel!(PPI_CH23, 23 => static); +impl_ppi_channel!(PPI_CH24, 24 => static); +impl_ppi_channel!(PPI_CH25, 25 => static); +impl_ppi_channel!(PPI_CH26, 26 => static); +impl_ppi_channel!(PPI_CH27, 27 => static); +impl_ppi_channel!(PPI_CH28, 28 => static); +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, 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_radio!(RADIO, RADIO, RADIO); + +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); + +embassy_hal_internal::interrupt_mod!( + CLOCK_POWER, + RADIO, + UARTE0, + TWI0_SPI1, + SPI0, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + AAR_CCM, + WDT, + RTC1, + QDEC, + COMP, + EGU0_SWI0, + EGU1_SWI1, + SWI2, + SWI3, + SWI4, + SWI5, + PWM0, + PDM, +); diff --git a/embassy/embassy-nrf/src/chips/nrf52820.rs b/embassy/embassy-nrf/src/chips/nrf52820.rs new file mode 100644 index 0000000..39573e4 --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf52820.rs @@ -0,0 +1,274 @@ +pub use nrf_pac as pac; + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 15) - 1; +pub const FORCE_COPY_BUFFER_SIZE: usize = 512; + +pub const FLASH_SIZE: usize = 256 * 1024; + +pub const RESET_PIN: u32 = 18; +pub const APPROTECT_MIN_BUILD_CODE: u8 = b'D'; + +embassy_hal_internal::peripherals! { + // USB + USBD, + + // RTC + RTC0, + RTC1, + + // WDT + WDT, + + // NVMC + NVMC, + + // RNG + RNG, + + // UARTE + UARTE0, + + // SPI/TWI + TWISPI0, + TWISPI1, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + TIMER3, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + PPI_CH16, + PPI_CH17, + PPI_CH18, + PPI_CH19, + PPI_CH20, + PPI_CH21, + PPI_CH22, + PPI_CH23, + PPI_CH24, + PPI_CH25, + PPI_CH26, + PPI_CH27, + PPI_CH28, + PPI_CH29, + PPI_CH30, + PPI_CH31, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + P0_09, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + #[cfg(feature="reset-pin-as-gpio")] + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // TEMP + TEMP, + + // QDEC + QDEC, + + // Radio + RADIO, + + // EGU + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, +} + +impl_usb!(USBD, USBD, USBD); + +impl_uarte!(UARTE0, UARTE0, UARTE0); + +impl_spim!(TWISPI0, SPIM0, TWISPI0); +impl_spim!(TWISPI1, SPIM1, TWISPI1); + +impl_spis!(TWISPI0, SPIS0, TWISPI0); +impl_spis!(TWISPI1, SPIS1, TWISPI1); + +impl_twim!(TWISPI0, TWIM0, TWISPI0); +impl_twim!(TWISPI1, TWIM1, TWISPI1); + +impl_twis!(TWISPI0, TWIS0, TWISPI0); +impl_twis!(TWISPI1, TWIS1, TWISPI1); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); +impl_timer!(TIMER3, TIMER3, TIMER3, extended); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +impl_pin!(P0_09, 0, 9); +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +#[cfg(feature = "reset-pin-as-gpio")] +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH10, 10 => configurable); +impl_ppi_channel!(PPI_CH11, 11 => configurable); +impl_ppi_channel!(PPI_CH12, 12 => configurable); +impl_ppi_channel!(PPI_CH13, 13 => configurable); +impl_ppi_channel!(PPI_CH14, 14 => configurable); +impl_ppi_channel!(PPI_CH15, 15 => configurable); +impl_ppi_channel!(PPI_CH16, 16 => configurable); +impl_ppi_channel!(PPI_CH17, 17 => configurable); +impl_ppi_channel!(PPI_CH18, 18 => configurable); +impl_ppi_channel!(PPI_CH19, 19 => configurable); +impl_ppi_channel!(PPI_CH20, 20 => static); +impl_ppi_channel!(PPI_CH21, 21 => static); +impl_ppi_channel!(PPI_CH22, 22 => static); +impl_ppi_channel!(PPI_CH23, 23 => static); +impl_ppi_channel!(PPI_CH24, 24 => static); +impl_ppi_channel!(PPI_CH25, 25 => static); +impl_ppi_channel!(PPI_CH26, 26 => static); +impl_ppi_channel!(PPI_CH27, 27 => static); +impl_ppi_channel!(PPI_CH28, 28 => static); +impl_ppi_channel!(PPI_CH29, 29 => static); +impl_ppi_channel!(PPI_CH30, 30 => static); +impl_ppi_channel!(PPI_CH31, 31 => static); + +impl_radio!(RADIO, RADIO, RADIO); + +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); +impl_egu!(EGU2, EGU2, EGU2_SWI2); +impl_egu!(EGU3, EGU3, EGU3_SWI3); +impl_egu!(EGU4, EGU4, EGU4_SWI4); +impl_egu!(EGU5, EGU5, EGU5_SWI5); + +embassy_hal_internal::interrupt_mod!( + CLOCK_POWER, + RADIO, + UARTE0, + TWISPI0, + TWISPI1, + GPIOTE, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + AAR_CCM, + WDT, + RTC1, + QDEC, + COMP, + EGU0_SWI0, + EGU1_SWI1, + EGU2_SWI2, + EGU3_SWI3, + EGU4_SWI4, + EGU5_SWI5, + TIMER3, + USBD, +); diff --git a/embassy/embassy-nrf/src/chips/nrf52832.rs b/embassy/embassy-nrf/src/chips/nrf52832.rs new file mode 100644 index 0000000..2d93462 --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf52832.rs @@ -0,0 +1,328 @@ +pub use nrf_pac as pac; + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 8) - 1; +pub const FORCE_COPY_BUFFER_SIZE: usize = 255; + +// There are two variants. We set the higher size to make the entire flash +// usable in xxAA, but we'll probably split this in two cargi features later. +// nrf52832xxAA = 512kb +// nrf52832xxAB = 256kb +pub const FLASH_SIZE: usize = 512 * 1024; + +pub const RESET_PIN: u32 = 21; +pub const APPROTECT_MIN_BUILD_CODE: u8 = b'G'; + +embassy_hal_internal::peripherals! { + // RTC + RTC0, + RTC1, + RTC2, + + // WDT + WDT, + + // NVMC + NVMC, + + // RNG + RNG, + + // UARTE + UARTE0, + + // SPI/TWI + TWISPI0, + TWISPI1, + SPI2, + + // SAADC + SAADC, + + // PWM + PWM0, + PWM1, + PWM2, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + TIMER3, + TIMER4, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + PPI_CH16, + PPI_CH17, + PPI_CH18, + PPI_CH19, + PPI_CH20, + PPI_CH21, + PPI_CH22, + PPI_CH23, + PPI_CH24, + PPI_CH25, + PPI_CH26, + PPI_CH27, + PPI_CH28, + PPI_CH29, + PPI_CH30, + PPI_CH31, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + #[cfg(feature = "nfc-pins-as-gpio")] + P0_09, + #[cfg(feature = "nfc-pins-as-gpio")] + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + #[cfg(feature="reset-pin-as-gpio")] + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // TEMP + TEMP, + + // QDEC + QDEC, + + // I2S + I2S, + + // PDM + PDM, + + // Radio + RADIO, + + // EGU + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, + + // NFC + NFCT, +} + +impl_uarte!(UARTE0, UARTE0, UARTE0); + +impl_spim!(TWISPI0, SPIM0, TWISPI0); +impl_spim!(TWISPI1, SPIM1, TWISPI1); +impl_spim!(SPI2, SPIM2, SPI2); + +impl_spis!(TWISPI0, SPIS0, TWISPI0); +impl_spis!(TWISPI1, SPIS1, TWISPI1); +impl_spis!(SPI2, SPIS2, SPI2); + +impl_twim!(TWISPI0, TWIM0, TWISPI0); +impl_twim!(TWISPI1, TWIM1, TWISPI1); + +impl_twis!(TWISPI0, TWIS0, TWISPI0); +impl_twis!(TWISPI1, TWIS1, TWISPI1); + +impl_pwm!(PWM0, PWM0, PWM0); +impl_pwm!(PWM1, PWM1, PWM1); +impl_pwm!(PWM2, PWM2, PWM2); + +impl_pdm!(PDM, PDM, PDM); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); +impl_timer!(TIMER3, TIMER3, TIMER3, extended); +impl_timer!(TIMER4, TIMER4, TIMER4, extended); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +#[cfg(feature = "nfc-pins-as-gpio")] +impl_pin!(P0_09, 0, 9); +#[cfg(feature = "nfc-pins-as-gpio")] +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH10, 10 => configurable); +impl_ppi_channel!(PPI_CH11, 11 => configurable); +impl_ppi_channel!(PPI_CH12, 12 => configurable); +impl_ppi_channel!(PPI_CH13, 13 => configurable); +impl_ppi_channel!(PPI_CH14, 14 => configurable); +impl_ppi_channel!(PPI_CH15, 15 => configurable); +impl_ppi_channel!(PPI_CH16, 16 => configurable); +impl_ppi_channel!(PPI_CH17, 17 => configurable); +impl_ppi_channel!(PPI_CH18, 18 => configurable); +impl_ppi_channel!(PPI_CH19, 19 => configurable); +impl_ppi_channel!(PPI_CH20, 20 => static); +impl_ppi_channel!(PPI_CH21, 21 => static); +impl_ppi_channel!(PPI_CH22, 22 => static); +impl_ppi_channel!(PPI_CH23, 23 => static); +impl_ppi_channel!(PPI_CH24, 24 => static); +impl_ppi_channel!(PPI_CH25, 25 => static); +impl_ppi_channel!(PPI_CH26, 26 => static); +impl_ppi_channel!(PPI_CH27, 27 => static); +impl_ppi_channel!(PPI_CH28, 28 => static); +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, 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); + +impl_radio!(RADIO, RADIO, RADIO); + +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); +impl_egu!(EGU2, EGU2, EGU2_SWI2); +impl_egu!(EGU3, EGU3, EGU3_SWI3); +impl_egu!(EGU4, EGU4, EGU4_SWI4); +impl_egu!(EGU5, EGU5, EGU5_SWI5); + +embassy_hal_internal::interrupt_mod!( + CLOCK_POWER, + RADIO, + UARTE0, + TWISPI0, + TWISPI1, + NFCT, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + AAR_CCM, + WDT, + RTC1, + QDEC, + COMP_LPCOMP, + EGU0_SWI0, + EGU1_SWI1, + EGU2_SWI2, + EGU3_SWI3, + EGU4_SWI4, + EGU5_SWI5, + TIMER3, + TIMER4, + PWM0, + PDM, + MWU, + PWM1, + PWM2, + SPI2, + RTC2, + I2S, + FPU, +); diff --git a/embassy/embassy-nrf/src/chips/nrf52833.rs b/embassy/embassy-nrf/src/chips/nrf52833.rs new file mode 100644 index 0000000..4e4915c --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf52833.rs @@ -0,0 +1,374 @@ +pub use nrf_pac as pac; + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; +pub const FORCE_COPY_BUFFER_SIZE: usize = 512; + +pub const FLASH_SIZE: usize = 512 * 1024; + +pub const RESET_PIN: u32 = 18; +pub const APPROTECT_MIN_BUILD_CODE: u8 = b'B'; + +embassy_hal_internal::peripherals! { + // USB + USBD, + + // RTC + RTC0, + RTC1, + RTC2, + + // WDT + WDT, + + // NVMC + NVMC, + + // RNG + RNG, + + // UARTE + UARTE0, + UARTE1, + + // SPI/TWI + TWISPI0, + TWISPI1, + SPI2, + SPI3, + + // SAADC + SAADC, + + // PWM + PWM0, + PWM1, + PWM2, + PWM3, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + TIMER3, + TIMER4, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + PPI_CH16, + PPI_CH17, + PPI_CH18, + PPI_CH19, + PPI_CH20, + PPI_CH21, + PPI_CH22, + PPI_CH23, + PPI_CH24, + PPI_CH25, + PPI_CH26, + PPI_CH27, + PPI_CH28, + PPI_CH29, + PPI_CH30, + PPI_CH31, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + #[cfg(feature = "nfc-pins-as-gpio")] + P0_09, + #[cfg(feature = "nfc-pins-as-gpio")] + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + #[cfg(feature="reset-pin-as-gpio")] + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // GPIO port 1 + P1_00, + P1_01, + P1_02, + P1_03, + P1_04, + P1_05, + P1_06, + P1_07, + P1_08, + P1_09, + P1_10, + P1_11, + P1_12, + P1_13, + P1_14, + P1_15, + + // TEMP + TEMP, + + // QDEC + QDEC, + + // PDM + PDM, + + // I2S + I2S, + + // Radio + RADIO, + + // EGU + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, + + // NFC + NFCT, +} + +impl_usb!(USBD, USBD, USBD); + +impl_uarte!(UARTE0, UARTE0, UARTE0); +impl_uarte!(UARTE1, UARTE1, UARTE1); + +impl_spim!(TWISPI0, SPIM0, TWISPI0); +impl_spim!(TWISPI1, SPIM1, TWISPI1); +impl_spim!(SPI2, SPIM2, SPI2); +impl_spim!(SPI3, SPIM3, SPIM3); + +impl_spis!(TWISPI0, SPIS0, TWISPI0); +impl_spis!(TWISPI1, SPIS1, TWISPI1); +impl_spis!(SPI2, SPIS2, SPI2); + +impl_twim!(TWISPI0, TWIM0, TWISPI0); +impl_twim!(TWISPI1, TWIM1, TWISPI1); + +impl_twis!(TWISPI0, TWIS0, TWISPI0); +impl_twis!(TWISPI1, TWIS1, TWISPI1); + +impl_pwm!(PWM0, PWM0, PWM0); +impl_pwm!(PWM1, PWM1, PWM1); +impl_pwm!(PWM2, PWM2, PWM2); +impl_pwm!(PWM3, PWM3, PWM3); + +impl_pdm!(PDM, PDM, PDM); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); +impl_timer!(TIMER3, TIMER3, TIMER3, extended); +impl_timer!(TIMER4, TIMER4, TIMER4, extended); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +#[cfg(feature = "nfc-pins-as-gpio")] +impl_pin!(P0_09, 0, 9); +#[cfg(feature = "nfc-pins-as-gpio")] +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +#[cfg(feature = "reset-pin-as-gpio")] +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_pin!(P1_00, 1, 0); +impl_pin!(P1_01, 1, 1); +impl_pin!(P1_02, 1, 2); +impl_pin!(P1_03, 1, 3); +impl_pin!(P1_04, 1, 4); +impl_pin!(P1_05, 1, 5); +impl_pin!(P1_06, 1, 6); +impl_pin!(P1_07, 1, 7); +impl_pin!(P1_08, 1, 8); +impl_pin!(P1_09, 1, 9); +impl_pin!(P1_10, 1, 10); +impl_pin!(P1_11, 1, 11); +impl_pin!(P1_12, 1, 12); +impl_pin!(P1_13, 1, 13); +impl_pin!(P1_14, 1, 14); +impl_pin!(P1_15, 1, 15); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH10, 10 => configurable); +impl_ppi_channel!(PPI_CH11, 11 => configurable); +impl_ppi_channel!(PPI_CH12, 12 => configurable); +impl_ppi_channel!(PPI_CH13, 13 => configurable); +impl_ppi_channel!(PPI_CH14, 14 => configurable); +impl_ppi_channel!(PPI_CH15, 15 => configurable); +impl_ppi_channel!(PPI_CH16, 16 => configurable); +impl_ppi_channel!(PPI_CH17, 17 => configurable); +impl_ppi_channel!(PPI_CH18, 18 => configurable); +impl_ppi_channel!(PPI_CH19, 19 => configurable); +impl_ppi_channel!(PPI_CH20, 20 => static); +impl_ppi_channel!(PPI_CH21, 21 => static); +impl_ppi_channel!(PPI_CH22, 22 => static); +impl_ppi_channel!(PPI_CH23, 23 => static); +impl_ppi_channel!(PPI_CH24, 24 => static); +impl_ppi_channel!(PPI_CH25, 25 => static); +impl_ppi_channel!(PPI_CH26, 26 => static); +impl_ppi_channel!(PPI_CH27, 27 => static); +impl_ppi_channel!(PPI_CH28, 28 => static); +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, 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); + +impl_radio!(RADIO, RADIO, RADIO); + +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); +impl_egu!(EGU2, EGU2, EGU2_SWI2); +impl_egu!(EGU3, EGU3, EGU3_SWI3); +impl_egu!(EGU4, EGU4, EGU4_SWI4); +impl_egu!(EGU5, EGU5, EGU5_SWI5); + +embassy_hal_internal::interrupt_mod!( + CLOCK_POWER, + RADIO, + UARTE0, + TWISPI0, + TWISPI1, + NFCT, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + AAR_CCM, + WDT, + RTC1, + QDEC, + COMP_LPCOMP, + EGU0_SWI0, + EGU1_SWI1, + EGU2_SWI2, + EGU3_SWI3, + EGU4_SWI4, + EGU5_SWI5, + TIMER3, + TIMER4, + PWM0, + PDM, + MWU, + PWM1, + PWM2, + SPI2, + RTC2, + I2S, + FPU, + USBD, + UARTE1, + PWM3, + SPIM3, +); diff --git a/embassy/embassy-nrf/src/chips/nrf52840.rs b/embassy/embassy-nrf/src/chips/nrf52840.rs new file mode 100644 index 0000000..70a5697 --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf52840.rs @@ -0,0 +1,381 @@ +pub use nrf_pac as pac; + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; +pub const FORCE_COPY_BUFFER_SIZE: usize = 512; + +pub const FLASH_SIZE: usize = 1024 * 1024; + +pub const RESET_PIN: u32 = 18; +pub const APPROTECT_MIN_BUILD_CODE: u8 = b'F'; + +embassy_hal_internal::peripherals! { + // USB + USBD, + + // RTC + RTC0, + RTC1, + RTC2, + + // WDT + WDT, + + // NVMC + NVMC, + + // RNG + RNG, + + // QSPI + QSPI, + + // QDEC + QDEC, + + // UARTE + UARTE0, + UARTE1, + + // SPI/TWI + TWISPI0, + TWISPI1, + SPI2, + SPI3, + + // SAADC + SAADC, + + // PWM + PWM0, + PWM1, + PWM2, + PWM3, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + TIMER3, + TIMER4, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + PPI_CH16, + PPI_CH17, + PPI_CH18, + PPI_CH19, + PPI_CH20, + PPI_CH21, + PPI_CH22, + PPI_CH23, + PPI_CH24, + PPI_CH25, + PPI_CH26, + PPI_CH27, + PPI_CH28, + PPI_CH29, + PPI_CH30, + PPI_CH31, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + #[cfg(feature = "nfc-pins-as-gpio")] + P0_09, + #[cfg(feature = "nfc-pins-as-gpio")] + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + #[cfg(feature="reset-pin-as-gpio")] + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // GPIO port 1 + P1_00, + P1_01, + P1_02, + P1_03, + P1_04, + P1_05, + P1_06, + P1_07, + P1_08, + P1_09, + P1_10, + P1_11, + P1_12, + P1_13, + P1_14, + P1_15, + + // TEMP + TEMP, + + // PDM + PDM, + + // I2S + I2S, + + // Radio + RADIO, + + // EGU + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, + + // NFC + NFCT, +} + +impl_usb!(USBD, USBD, USBD); + +impl_uarte!(UARTE0, UARTE0, UARTE0); +impl_uarte!(UARTE1, UARTE1, UARTE1); + +impl_spim!(TWISPI0, SPIM0, TWISPI0); +impl_spim!(TWISPI1, SPIM1, TWISPI1); +impl_spim!(SPI2, SPIM2, SPI2); +impl_spim!(SPI3, SPIM3, SPIM3); + +impl_spis!(TWISPI0, SPIS0, TWISPI0); +impl_spis!(TWISPI1, SPIS1, TWISPI1); +impl_spis!(SPI2, SPIS2, SPI2); + +impl_twim!(TWISPI0, TWIM0, TWISPI0); +impl_twim!(TWISPI1, TWIM1, TWISPI1); + +impl_twis!(TWISPI0, TWIS0, TWISPI0); +impl_twis!(TWISPI1, TWIS1, TWISPI1); + +impl_pwm!(PWM0, PWM0, PWM0); +impl_pwm!(PWM1, PWM1, PWM1); +impl_pwm!(PWM2, PWM2, PWM2); +impl_pwm!(PWM3, PWM3, PWM3); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); +impl_timer!(TIMER3, TIMER3, TIMER3, extended); +impl_timer!(TIMER4, TIMER4, TIMER4, extended); + +impl_qspi!(QSPI, QSPI, QSPI); + +impl_pdm!(PDM, PDM, PDM); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +#[cfg(feature = "nfc-pins-as-gpio")] +impl_pin!(P0_09, 0, 9); +#[cfg(feature = "nfc-pins-as-gpio")] +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +#[cfg(feature = "reset-pin-as-gpio")] +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_pin!(P1_00, 1, 0); +impl_pin!(P1_01, 1, 1); +impl_pin!(P1_02, 1, 2); +impl_pin!(P1_03, 1, 3); +impl_pin!(P1_04, 1, 4); +impl_pin!(P1_05, 1, 5); +impl_pin!(P1_06, 1, 6); +impl_pin!(P1_07, 1, 7); +impl_pin!(P1_08, 1, 8); +impl_pin!(P1_09, 1, 9); +impl_pin!(P1_10, 1, 10); +impl_pin!(P1_11, 1, 11); +impl_pin!(P1_12, 1, 12); +impl_pin!(P1_13, 1, 13); +impl_pin!(P1_14, 1, 14); +impl_pin!(P1_15, 1, 15); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH10, 10 => configurable); +impl_ppi_channel!(PPI_CH11, 11 => configurable); +impl_ppi_channel!(PPI_CH12, 12 => configurable); +impl_ppi_channel!(PPI_CH13, 13 => configurable); +impl_ppi_channel!(PPI_CH14, 14 => configurable); +impl_ppi_channel!(PPI_CH15, 15 => configurable); +impl_ppi_channel!(PPI_CH16, 16 => configurable); +impl_ppi_channel!(PPI_CH17, 17 => configurable); +impl_ppi_channel!(PPI_CH18, 18 => configurable); +impl_ppi_channel!(PPI_CH19, 19 => configurable); +impl_ppi_channel!(PPI_CH20, 20 => static); +impl_ppi_channel!(PPI_CH21, 21 => static); +impl_ppi_channel!(PPI_CH22, 22 => static); +impl_ppi_channel!(PPI_CH23, 23 => static); +impl_ppi_channel!(PPI_CH24, 24 => static); +impl_ppi_channel!(PPI_CH25, 25 => static); +impl_ppi_channel!(PPI_CH26, 26 => static); +impl_ppi_channel!(PPI_CH27, 27 => static); +impl_ppi_channel!(PPI_CH28, 28 => static); +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, 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); + +impl_radio!(RADIO, RADIO, RADIO); + +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); +impl_egu!(EGU2, EGU2, EGU2_SWI2); +impl_egu!(EGU3, EGU3, EGU3_SWI3); +impl_egu!(EGU4, EGU4, EGU4_SWI4); +impl_egu!(EGU5, EGU5, EGU5_SWI5); + +embassy_hal_internal::interrupt_mod!( + CLOCK_POWER, + RADIO, + UARTE0, + TWISPI0, + TWISPI1, + NFCT, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + AAR_CCM, + WDT, + RTC1, + QDEC, + COMP_LPCOMP, + EGU0_SWI0, + EGU1_SWI1, + EGU2_SWI2, + EGU3_SWI3, + EGU4_SWI4, + EGU5_SWI5, + TIMER3, + TIMER4, + PWM0, + PDM, + MWU, + PWM1, + PWM2, + SPI2, + RTC2, + I2S, + FPU, + USBD, + UARTE1, + QSPI, + CRYPTOCELL, + PWM3, + SPIM3, +); diff --git a/embassy/embassy-nrf/src/chips/nrf5340_app.rs b/embassy/embassy-nrf/src/chips/nrf5340_app.rs new file mode 100644 index 0000000..bc613e3 --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf5340_app.rs @@ -0,0 +1,514 @@ +/// Peripheral Access Crate +#[allow(unused_imports)] +#[rustfmt::skip] +pub mod pac { + pub use nrf_pac::*; + + #[cfg(feature = "_ns")] + #[doc(no_inline)] + pub use nrf_pac::{ + CLOCK_NS as CLOCK, + COMP_NS as COMP, + CTRLAP_NS as CTRLAP, + DCNF_NS as DCNF, + DPPIC_NS as DPPIC, + EGU0_NS as EGU0, + EGU1_NS as EGU1, + EGU2_NS as EGU2, + EGU3_NS as EGU3, + EGU4_NS as EGU4, + EGU5_NS as EGU5, + FPU_NS as FPU, + GPIOTE1_NS as GPIOTE1, + I2S0_NS as I2S0, + IPC_NS as IPC, + KMU_NS as KMU, + LPCOMP_NS as LPCOMP, + MUTEX_NS as MUTEX, + NFCT_NS as NFCT, + NVMC_NS as NVMC, + OSCILLATORS_NS as OSCILLATORS, + P0_NS as P0, + P1_NS as P1, + PDM0_NS as PDM0, + POWER_NS as POWER, + PWM0_NS as PWM0, + PWM1_NS as PWM1, + PWM2_NS as PWM2, + PWM3_NS as PWM3, + QDEC0_NS as QDEC0, + QDEC1_NS as QDEC1, + QSPI_NS as QSPI, + REGULATORS_NS as REGULATORS, + RESET_NS as RESET, + RTC0_NS as RTC0, + RTC1_NS as RTC1, + SAADC_NS as SAADC, + SPIM0_NS as SPIM0, + SPIM1_NS as SPIM1, + SPIM2_NS as SPIM2, + SPIM3_NS as SPIM3, + SPIM4_NS as SPIM4, + SPIS0_NS as SPIS0, + SPIS1_NS as SPIS1, + SPIS2_NS as SPIS2, + SPIS3_NS as SPIS3, + TIMER0_NS as TIMER0, + TIMER1_NS as TIMER1, + TIMER2_NS as TIMER2, + TWIM0_NS as TWIM0, + TWIM1_NS as TWIM1, + TWIM2_NS as TWIM2, + TWIM3_NS as TWIM3, + TWIS0_NS as TWIS0, + TWIS1_NS as TWIS1, + TWIS2_NS as TWIS2, + TWIS3_NS as TWIS3, + UARTE0_NS as UARTE0, + UARTE1_NS as UARTE1, + UARTE2_NS as UARTE2, + UARTE3_NS as UARTE3, + USBD_NS as USBD, + USBREGULATOR_NS as USBREGULATOR, + VMC_NS as VMC, + WDT0_NS as WDT0, + WDT1_NS as WDT1, + }; + + #[cfg(feature = "_s")] + #[doc(no_inline)] + pub use nrf_pac::{ + CACHEDATA_S as CACHEDATA, + CACHEINFO_S as CACHEINFO, + CACHE_S as CACHE, + CLOCK_S as CLOCK, + COMP_S as COMP, + CRYPTOCELL_S as CRYPTOCELL, + CTI_S as CTI, + CTRLAP_S as CTRLAP, + DCNF_S as DCNF, + DPPIC_S as DPPIC, + EGU0_S as EGU0, + EGU1_S as EGU1, + EGU2_S as EGU2, + EGU3_S as EGU3, + EGU4_S as EGU4, + EGU5_S as EGU5, + FICR_S as FICR, + FPU_S as FPU, + GPIOTE0_S as GPIOTE0, + I2S0_S as I2S0, + IPC_S as IPC, + KMU_S as KMU, + LPCOMP_S as LPCOMP, + MUTEX_S as MUTEX, + NFCT_S as NFCT, + NVMC_S as NVMC, + OSCILLATORS_S as OSCILLATORS, + P0_S as P0, + P1_S as P1, + PDM0_S as PDM0, + POWER_S as POWER, + PWM0_S as PWM0, + PWM1_S as PWM1, + PWM2_S as PWM2, + PWM3_S as PWM3, + QDEC0_S as QDEC0, + QDEC1_S as QDEC1, + QSPI_S as QSPI, + REGULATORS_S as REGULATORS, + RESET_S as RESET, + RTC0_S as RTC0, + RTC1_S as RTC1, + SAADC_S as SAADC, + SPIM0_S as SPIM0, + SPIM1_S as SPIM1, + SPIM2_S as SPIM2, + SPIM3_S as SPIM3, + SPIM4_S as SPIM4, + SPIS0_S as SPIS0, + SPIS1_S as SPIS1, + SPIS2_S as SPIS2, + SPIS3_S as SPIS3, + SPU_S as SPU, + TAD_S as TAD, + TIMER0_S as TIMER0, + TIMER1_S as TIMER1, + TIMER2_S as TIMER2, + TWIM0_S as TWIM0, + TWIM1_S as TWIM1, + TWIM2_S as TWIM2, + TWIM3_S as TWIM3, + TWIS0_S as TWIS0, + TWIS1_S as TWIS1, + TWIS2_S as TWIS2, + TWIS3_S as TWIS3, + UARTE0_S as UARTE0, + UARTE1_S as UARTE1, + UARTE2_S as UARTE2, + UARTE3_S as UARTE3, + UICR_S as UICR, + USBD_S as USBD, + USBREGULATOR_S as USBREGULATOR, + VMC_S as VMC, + WDT0_S as WDT0, + WDT1_S as WDT1, + }; +} + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; +pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; + +pub const FLASH_SIZE: usize = 1024 * 1024; + +embassy_hal_internal::peripherals! { + // USB + USBD, + + // RTC + RTC0, + RTC1, + + // WDT + WDT, + + // NVMC + NVMC, + + // NFC + NFCT, + + // UARTE, TWI & SPI + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, + SPIM4, + + // SAADC + SAADC, + + // PWM + PWM0, + PWM1, + PWM2, + PWM3, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + + // QSPI + QSPI, + + // PDM + PDM0, + + // QDEC + QDEC0, + QDEC1, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + PPI_CH16, + PPI_CH17, + PPI_CH18, + PPI_CH19, + PPI_CH20, + PPI_CH21, + PPI_CH22, + PPI_CH23, + PPI_CH24, + PPI_CH25, + PPI_CH26, + PPI_CH27, + PPI_CH28, + PPI_CH29, + PPI_CH30, + PPI_CH31, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + #[cfg(feature = "nfc-pins-as-gpio")] + P0_02, + #[cfg(feature = "nfc-pins-as-gpio")] + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + P0_09, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // GPIO port 1 + P1_00, + P1_01, + P1_02, + P1_03, + P1_04, + P1_05, + P1_06, + P1_07, + P1_08, + P1_09, + P1_10, + P1_11, + P1_12, + P1_13, + P1_14, + P1_15, + + // EGU + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, +} + +impl_usb!(USBD, USBD, USBD); + +impl_uarte!(SERIAL0, UARTE0, SERIAL0); +impl_uarte!(SERIAL1, UARTE1, SERIAL1); +impl_uarte!(SERIAL2, UARTE2, SERIAL2); +impl_uarte!(SERIAL3, UARTE3, SERIAL3); + +impl_spim!(SERIAL0, SPIM0, SERIAL0); +impl_spim!(SERIAL1, SPIM1, SERIAL1); +impl_spim!(SERIAL2, SPIM2, SERIAL2); +impl_spim!(SERIAL3, SPIM3, SERIAL3); +impl_spim!(SPIM4, SPIM4, SPIM4); + +impl_spis!(SERIAL0, SPIS0, SERIAL0); +impl_spis!(SERIAL1, SPIS1, SERIAL1); +impl_spis!(SERIAL2, SPIS2, SERIAL2); +impl_spis!(SERIAL3, SPIS3, SERIAL3); + +impl_twim!(SERIAL0, TWIM0, SERIAL0); +impl_twim!(SERIAL1, TWIM1, SERIAL1); +impl_twim!(SERIAL2, TWIM2, SERIAL2); +impl_twim!(SERIAL3, TWIM3, SERIAL3); + +impl_twis!(SERIAL0, TWIS0, SERIAL0); +impl_twis!(SERIAL1, TWIS1, SERIAL1); +impl_twis!(SERIAL2, TWIS2, SERIAL2); +impl_twis!(SERIAL3, TWIS3, SERIAL3); + +impl_pwm!(PWM0, PWM0, PWM0); +impl_pwm!(PWM1, PWM1, PWM1); +impl_pwm!(PWM2, PWM2, PWM2); +impl_pwm!(PWM3, PWM3, PWM3); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); + +impl_qspi!(QSPI, QSPI, QSPI); + +impl_pdm!(PDM0, PDM0, PDM0); + +impl_qdec!(QDEC0, QDEC0, QDEC0); +impl_qdec!(QDEC1, QDEC1, QDEC1); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +#[cfg(feature = "nfc-pins-as-gpio")] +impl_pin!(P0_02, 0, 2); +#[cfg(feature = "nfc-pins-as-gpio")] +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +impl_pin!(P0_09, 0, 9); +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_pin!(P1_00, 1, 0); +impl_pin!(P1_01, 1, 1); +impl_pin!(P1_02, 1, 2); +impl_pin!(P1_03, 1, 3); +impl_pin!(P1_04, 1, 4); +impl_pin!(P1_05, 1, 5); +impl_pin!(P1_06, 1, 6); +impl_pin!(P1_07, 1, 7); +impl_pin!(P1_08, 1, 8); +impl_pin!(P1_09, 1, 9); +impl_pin!(P1_10, 1, 10); +impl_pin!(P1_11, 1, 11); +impl_pin!(P1_12, 1, 12); +impl_pin!(P1_13, 1, 13); +impl_pin!(P1_14, 1, 14); +impl_pin!(P1_15, 1, 15); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH10, 10 => configurable); +impl_ppi_channel!(PPI_CH11, 11 => configurable); +impl_ppi_channel!(PPI_CH12, 12 => configurable); +impl_ppi_channel!(PPI_CH13, 13 => configurable); +impl_ppi_channel!(PPI_CH14, 14 => configurable); +impl_ppi_channel!(PPI_CH15, 15 => configurable); +impl_ppi_channel!(PPI_CH16, 16 => configurable); +impl_ppi_channel!(PPI_CH17, 17 => configurable); +impl_ppi_channel!(PPI_CH18, 18 => configurable); +impl_ppi_channel!(PPI_CH19, 19 => configurable); +impl_ppi_channel!(PPI_CH20, 20 => configurable); +impl_ppi_channel!(PPI_CH21, 21 => configurable); +impl_ppi_channel!(PPI_CH22, 22 => configurable); +impl_ppi_channel!(PPI_CH23, 23 => configurable); +impl_ppi_channel!(PPI_CH24, 24 => configurable); +impl_ppi_channel!(PPI_CH25, 25 => configurable); +impl_ppi_channel!(PPI_CH26, 26 => configurable); +impl_ppi_channel!(PPI_CH27, 27 => configurable); +impl_ppi_channel!(PPI_CH28, 28 => configurable); +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, 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); + +impl_egu!(EGU0, EGU0, EGU0); +impl_egu!(EGU1, EGU1, EGU1); +impl_egu!(EGU2, EGU2, EGU2); +impl_egu!(EGU3, EGU3, EGU3); +impl_egu!(EGU4, EGU4, EGU4); +impl_egu!(EGU5, EGU5, EGU5); + +embassy_hal_internal::interrupt_mod!( + FPU, + CACHE, + SPU, + CLOCK_POWER, + SERIAL0, + SERIAL1, + SPIM4, + SERIAL2, + SERIAL3, + GPIOTE0, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + RTC1, + WDT0, + WDT1, + COMP_LPCOMP, + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, + PWM0, + PWM1, + PWM2, + PWM3, + PDM0, + I2S0, + IPC, + QSPI, + NFCT, + GPIOTE1, + QDEC0, + QDEC1, + USBD, + USBREGULATOR, + KMU, + CRYPTOCELL, +); diff --git a/embassy/embassy-nrf/src/chips/nrf5340_net.rs b/embassy/embassy-nrf/src/chips/nrf5340_net.rs new file mode 100644 index 0000000..6c6ac3f --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf5340_net.rs @@ -0,0 +1,323 @@ +/// Peripheral Access Crate +#[allow(unused_imports)] +#[rustfmt::skip] +pub mod pac { + pub use nrf_pac::*; + + #[doc(no_inline)] + pub use nrf_pac::{ + AAR_NS as AAR, + ACL_NS as ACL, + APPMUTEX_NS as APPMUTEX, + APPMUTEX_S as APPMUTEX_S, + CCM_NS as CCM, + CLOCK_NS as CLOCK, + CTI_NS as CTI, + CTRLAP_NS as CTRLAP, + DCNF_NS as DCNF, + DPPIC_NS as DPPIC, + ECB_NS as ECB, + EGU0_NS as EGU0, + FICR_NS as FICR, + GPIOTE_NS as GPIOTE, + IPC_NS as IPC, + NVMC_NS as NVMC, + P0_NS as P0, + P1_NS as P1, + POWER_NS as POWER, + RADIO_NS as RADIO, + RESET_NS as RESET, + RNG_NS as RNG, + RTC0_NS as RTC0, + RTC1_NS as RTC1, + SPIM0_NS as SPIM0, + SPIS0_NS as SPIS0, + SWI0_NS as SWI0, + SWI1_NS as SWI1, + SWI2_NS as SWI2, + SWI3_NS as SWI3, + TEMP_NS as TEMP, + TIMER0_NS as TIMER0, + TIMER1_NS as TIMER1, + TIMER2_NS as TIMER2, + TWIM0_NS as TWIM0, + TWIS0_NS as TWIS0, + UARTE0_NS as UARTE0, + UICR_NS as UICR, + VMC_NS as VMC, + VREQCTRL_NS as VREQCTRL, + WDT_NS as WDT, + }; +} + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; +pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; + +pub const FLASH_SIZE: usize = 256 * 1024; + +embassy_hal_internal::peripherals! { + // RTC + RTC0, + RTC1, + + // WDT + WDT, + + // NVMC + NVMC, + + // UARTE, TWI & SPI + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, + + // SAADC + SAADC, + + // RNG + RNG, + + // PWM + PWM0, + PWM1, + PWM2, + PWM3, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + PPI_CH16, + PPI_CH17, + PPI_CH18, + PPI_CH19, + PPI_CH20, + PPI_CH21, + PPI_CH22, + PPI_CH23, + PPI_CH24, + PPI_CH25, + PPI_CH26, + PPI_CH27, + PPI_CH28, + PPI_CH29, + PPI_CH30, + PPI_CH31, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + P0_09, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // GPIO port 1 + P1_00, + P1_01, + P1_02, + P1_03, + P1_04, + P1_05, + P1_06, + P1_07, + P1_08, + P1_09, + P1_10, + P1_11, + P1_12, + P1_13, + P1_14, + P1_15, + + // Radio + RADIO, + + // EGU + EGU0, +} + +impl_uarte!(SERIAL0, UARTE0, SERIAL0); +impl_spim!(SERIAL0, SPIM0, SERIAL0); +impl_spis!(SERIAL0, SPIS0, SERIAL0); +impl_twim!(SERIAL0, TWIM0, SERIAL0); +impl_twis!(SERIAL0, TWIS0, SERIAL0); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); + +impl_rng!(RNG, RNG, RNG); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +impl_pin!(P0_09, 0, 9); +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_pin!(P1_00, 1, 0); +impl_pin!(P1_01, 1, 1); +impl_pin!(P1_02, 1, 2); +impl_pin!(P1_03, 1, 3); +impl_pin!(P1_04, 1, 4); +impl_pin!(P1_05, 1, 5); +impl_pin!(P1_06, 1, 6); +impl_pin!(P1_07, 1, 7); +impl_pin!(P1_08, 1, 8); +impl_pin!(P1_09, 1, 9); +impl_pin!(P1_10, 1, 10); +impl_pin!(P1_11, 1, 11); +impl_pin!(P1_12, 1, 12); +impl_pin!(P1_13, 1, 13); +impl_pin!(P1_14, 1, 14); +impl_pin!(P1_15, 1, 15); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH10, 10 => configurable); +impl_ppi_channel!(PPI_CH11, 11 => configurable); +impl_ppi_channel!(PPI_CH12, 12 => configurable); +impl_ppi_channel!(PPI_CH13, 13 => configurable); +impl_ppi_channel!(PPI_CH14, 14 => configurable); +impl_ppi_channel!(PPI_CH15, 15 => configurable); +impl_ppi_channel!(PPI_CH16, 16 => configurable); +impl_ppi_channel!(PPI_CH17, 17 => configurable); +impl_ppi_channel!(PPI_CH18, 18 => configurable); +impl_ppi_channel!(PPI_CH19, 19 => configurable); +impl_ppi_channel!(PPI_CH20, 20 => configurable); +impl_ppi_channel!(PPI_CH21, 21 => configurable); +impl_ppi_channel!(PPI_CH22, 22 => configurable); +impl_ppi_channel!(PPI_CH23, 23 => configurable); +impl_ppi_channel!(PPI_CH24, 24 => configurable); +impl_ppi_channel!(PPI_CH25, 25 => configurable); +impl_ppi_channel!(PPI_CH26, 26 => configurable); +impl_ppi_channel!(PPI_CH27, 27 => configurable); +impl_ppi_channel!(PPI_CH28, 28 => configurable); +impl_ppi_channel!(PPI_CH29, 29 => configurable); +impl_ppi_channel!(PPI_CH30, 30 => configurable); +impl_ppi_channel!(PPI_CH31, 31 => configurable); + +impl_radio!(RADIO, RADIO, RADIO); + +impl_egu!(EGU0, EGU0, EGU0); + +embassy_hal_internal::interrupt_mod!( + CLOCK_POWER, + RADIO, + RNG, + GPIOTE, + WDT, + TIMER0, + ECB, + AAR_CCM, + TEMP, + RTC0, + IPC, + SERIAL0, + EGU0, + RTC1, + TIMER1, + TIMER2, + SWI0, + SWI1, + SWI2, + SWI3, +); diff --git a/embassy/embassy-nrf/src/chips/nrf54l15_app.rs b/embassy/embassy-nrf/src/chips/nrf54l15_app.rs new file mode 100644 index 0000000..b133eb5 --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf54l15_app.rs @@ -0,0 +1,346 @@ +/// Peripheral Access Crate +#[allow(unused_imports)] +#[rustfmt::skip] +pub mod pac { + pub use nrf_pac::*; + + #[cfg(feature = "_ns")] + #[doc(no_inline)] + pub use nrf_pac::{ + FICR_NS as FICR, + DPPIC00_NS as DPPIC00, + PPIB00_NS as PPIB00, + PPIB01_NS as PPIB01, + AAR00_NS as AAR00, + CCM00_NS as CCM00, + ECB00_NS as ECB00, + SPIM00_NS as SPIM00, + SPIS00_NS as SPIS00, + UARTE00_NS as UARTE00, + VPR00_NS as VPR00, + P2_NS as P2, + CTRLAP_NS as CTRLAP, + TAD_NS as TAD, + TIMER00_NS as TIMER00, + DPPIC10_NS as DPPIC10, + PPIB10_NS as PPIB10, + PPIB11_NS as PPIB11, + TIMER10_NS as TIMER10, + RTC10_NS as RTC10, + EGU10_NS as EGU10, + RADIO_NS as RADIO, + DPPIC20_NS as DPPIC20, + PPIB20_NS as PPIB20, + PPIB21_NS as PPIB21, + PPIB22_NS as PPIB22, + SPIM20_NS as SPIM20, + SPIS20_NS as SPIS20, + TWIM20_NS as TWIM20, + TWIS20_NS as TWIS20, + UARTE20_NS as UARTE20, + SPIM21_NS as SPIM21, + SPIS21_NS as SPIS21, + TWIM21_NS as TWIM21, + TWIS21_NS as TWIS21, + UARTE21_NS as UARTE21, + SPIM22_NS as SPIM22, + SPIS22_NS as SPIS22, + TWIM22_NS as TWIM22, + TWIS22_NS as TWIS22, + UARTE22_NS as UARTE22, + EGU20_NS as EGU20, + TIMER20_NS as TIMER20, + TIMER21_NS as TIMER21, + TIMER22_NS as TIMER22, + TIMER23_NS as TIMER23, + TIMER24_NS as TIMER24, + MEMCONF_NS as MEMCONF, + PDM20_NS as PDM20, + PDM21_NS as PDM21, + PWM20_NS as PWM20, + PWM21_NS as PWM21, + PWM22_NS as PWM22, + SAADC_NS as SAADC, + NFCT_NS as NFCT, + TEMP_NS as TEMP, + P1_NS as P1, + GPIOTE20_NS as GPIOTE20, + I2S20_NS as I2S20, + QDEC20_NS as QDEC20, + QDEC21_NS as QDEC21, + GRTC_NS as GRTC, + DPPIC30_NS as DPPIC30, + PPIB30_NS as PPIB30, + SPIM30_NS as SPIM30, + SPIS30_NS as SPIS30, + TWIM30_NS as TWIM30, + TWIS30_NS as TWIS30, + UARTE30_NS as UARTE30, + RTC30_NS as RTC30, + COMP_NS as COMP, + LPCOMP_NS as LPCOMP, + WDT31_NS as WDT31, + P0_NS as P0, + GPIOTE30_NS as GPIOTE30, + CLOCK_NS as CLOCK, + POWER_NS as POWER, + RESET_NS as RESET, + OSCILLATORS_NS as OSCILLATORS, + REGULATORS_NS as REGULATORS, + TPIU_NS as TPIU, + ETM_NS as ETM, + }; + + #[cfg(feature = "_s")] + #[doc(no_inline)] + pub use nrf_pac::{ + SICR_S as SICR, + ICACHEDATA_S as ICACHEDATA, + ICACHEINFO_S as ICACHEINFO, + SWI00_S as SWI00, + SWI01_S as SWI01, + SWI02_S as SWI02, + SWI03_S as SWI03, + SPU00_S as SPU00, + MPC00_S as MPC00, + DPPIC00_S as DPPIC00, + PPIB00_S as PPIB00, + PPIB01_S as PPIB01, + KMU_S as KMU, + AAR00_S as AAR00, + CCM00_S as CCM00, + ECB00_S as ECB00, + CRACEN_S as CRACEN, + SPIM00_S as SPIM00, + SPIS00_S as SPIS00, + UARTE00_S as UARTE00, + GLITCHDET_S as GLITCHDET, + RRAMC_S as RRAMC, + VPR00_S as VPR00, + P2_S as P2, + CTRLAP_S as CTRLAP, + TAD_S as TAD, + TIMER00_S as TIMER00, + SPU10_S as SPU10, + DPPIC10_S as DPPIC10, + PPIB10_S as PPIB10, + PPIB11_S as PPIB11, + TIMER10_S as TIMER10, + RTC10_S as RTC10, + EGU10_S as EGU10, + RADIO_S as RADIO, + SPU20_S as SPU20, + DPPIC20_S as DPPIC20, + PPIB20_S as PPIB20, + PPIB21_S as PPIB21, + PPIB22_S as PPIB22, + SPIM20_S as SPIM20, + SPIS20_S as SPIS20, + TWIM20_S as TWIM20, + TWIS20_S as TWIS20, + UARTE20_S as UARTE20, + SPIM21_S as SPIM21, + SPIS21_S as SPIS21, + TWIM21_S as TWIM21, + TWIS21_S as TWIS21, + UARTE21_S as UARTE21, + SPIM22_S as SPIM22, + SPIS22_S as SPIS22, + TWIM22_S as TWIM22, + TWIS22_S as TWIS22, + UARTE22_S as UARTE22, + EGU20_S as EGU20, + TIMER20_S as TIMER20, + TIMER21_S as TIMER21, + TIMER22_S as TIMER22, + TIMER23_S as TIMER23, + TIMER24_S as TIMER24, + MEMCONF_S as MEMCONF, + PDM20_S as PDM20, + PDM21_S as PDM21, + PWM20_S as PWM20, + PWM21_S as PWM21, + PWM22_S as PWM22, + SAADC_S as SAADC, + NFCT_S as NFCT, + TEMP_S as TEMP, + P1_S as P1, + GPIOTE20_S as GPIOTE20, + TAMPC_S as TAMPC, + I2S20_S as I2S20, + QDEC20_S as QDEC20, + QDEC21_S as QDEC21, + GRTC_S as GRTC, + SPU30_S as SPU30, + DPPIC30_S as DPPIC30, + PPIB30_S as PPIB30, + SPIM30_S as SPIM30, + SPIS30_S as SPIS30, + TWIM30_S as TWIM30, + TWIS30_S as TWIS30, + UARTE30_S as UARTE30, + RTC30_S as RTC30, + COMP_S as COMP, + LPCOMP_S as LPCOMP, + WDT30_S as WDT30, + WDT31_S as WDT31, + P0_S as P0, + GPIOTE30_S as GPIOTE30, + CLOCK_S as CLOCK, + POWER_S as POWER, + RESET_S as RESET, + OSCILLATORS_S as OSCILLATORS, + REGULATORS_S as REGULATORS, + CRACENCORE_S as CRACENCORE, + CPUC_S as CPUC, + ICACHE_S as ICACHE, + }; +} + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; +//pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; + +//pub const FLASH_SIZE: usize = 1024 * 1024; + +embassy_hal_internal::peripherals! { + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + + // GPIO port 1 + P1_00, + P1_01, + P1_02, + P1_03, + P1_04, + P1_05, + P1_06, + P1_07, + P1_08, + P1_09, + P1_10, + P1_11, + P1_12, + P1_13, + P1_14, + P1_15, + P1_16, + + + // GPIO port 2 + P2_00, + P2_01, + P2_02, + P2_03, + P2_04, + P2_05, + P2_06, + P2_07, + P2_08, + P2_09, + P2_10, +} + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); + +impl_pin!(P1_00, 1, 0); +impl_pin!(P1_01, 1, 1); +impl_pin!(P1_02, 1, 2); +impl_pin!(P1_03, 1, 3); +impl_pin!(P1_04, 1, 4); +impl_pin!(P1_05, 1, 5); +impl_pin!(P1_06, 1, 6); +impl_pin!(P1_07, 1, 7); +impl_pin!(P1_08, 1, 8); +impl_pin!(P1_09, 1, 9); +impl_pin!(P1_10, 1, 10); +impl_pin!(P1_11, 1, 11); +impl_pin!(P1_12, 1, 12); +impl_pin!(P1_13, 1, 13); +impl_pin!(P1_14, 1, 14); +impl_pin!(P1_15, 1, 15); +impl_pin!(P1_16, 1, 16); + +impl_pin!(P2_00, 2, 0); +impl_pin!(P2_01, 2, 1); +impl_pin!(P2_02, 2, 2); +impl_pin!(P2_03, 2, 3); +impl_pin!(P2_04, 2, 4); +impl_pin!(P2_05, 2, 5); +impl_pin!(P2_06, 2, 6); +impl_pin!(P2_07, 2, 7); +impl_pin!(P2_08, 2, 8); +impl_pin!(P2_09, 2, 9); +impl_pin!(P2_10, 2, 10); + +embassy_hal_internal::interrupt_mod!( + SWI00, + SWI01, + SWI02, + SWI03, + SPU00, + MPC00, + AAR00_CCM00, + ECB00, + CRACEN, + SERIAL00, + RRAMC, + VPR00, + CTRLAP, + TIMER00, + SPU10, + TIMER10, + RTC10, + EGU10, + RADIO_0, + RADIO_1, + SPU20, + SERIAL20, + SERIAL21, + SERIAL22, + EGU20, + TIMER20, + TIMER21, + TIMER22, + TIMER23, + TIMER24, + PDM20, + PDM21, + PWM20, + PWM21, + PWM22, + SAADC, + NFCT, + TEMP, + GPIOTE20_0, + GPIOTE20_1, + TAMPC, + I2S20, + QDEC20, + QDEC21, + GRTC_0, + GRTC_1, + GRTC_2, + GRTC_3, + SPU30, + SERIAL30, + RTC30, + COMP_LPCOMP, + WDT30, + WDT31, + GPIOTE30_0, + GPIOTE30_1, + CLOCK_POWER, +); diff --git a/embassy/embassy-nrf/src/chips/nrf9120.rs b/embassy/embassy-nrf/src/chips/nrf9120.rs new file mode 100644 index 0000000..b02b8c6 --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf9120.rs @@ -0,0 +1,377 @@ +/// Peripheral Access Crate +#[allow(unused_imports)] +#[rustfmt::skip] +pub mod pac { + pub use nrf_pac::*; + + #[cfg(feature = "_ns")] + #[doc(no_inline)] + pub use nrf_pac::{ + CLOCK_NS as CLOCK, + DPPIC_NS as DPPIC, + EGU0_NS as EGU0, + EGU1_NS as EGU1, + EGU2_NS as EGU2, + EGU3_NS as EGU3, + EGU4_NS as EGU4, + EGU5_NS as EGU5, + FPU_NS as FPU, + GPIOTE1_NS as GPIOTE1, + I2S_NS as I2S, + IPC_NS as IPC, + KMU_NS as KMU, + NVMC_NS as NVMC, + P0_NS as P0, + PDM_NS as PDM, + POWER_NS as POWER, + PWM0_NS as PWM0, + PWM1_NS as PWM1, + PWM2_NS as PWM2, + PWM3_NS as PWM3, + REGULATORS_NS as REGULATORS, + RTC0_NS as RTC0, + RTC1_NS as RTC1, + SAADC_NS as SAADC, + SPIM0_NS as SPIM0, + SPIM1_NS as SPIM1, + SPIM2_NS as SPIM2, + SPIM3_NS as SPIM3, + SPIS0_NS as SPIS0, + SPIS1_NS as SPIS1, + SPIS2_NS as SPIS2, + SPIS3_NS as SPIS3, + TIMER0_NS as TIMER0, + TIMER1_NS as TIMER1, + TIMER2_NS as TIMER2, + TWIM0_NS as TWIM0, + TWIM1_NS as TWIM1, + TWIM2_NS as TWIM2, + TWIM3_NS as TWIM3, + TWIS0_NS as TWIS0, + TWIS1_NS as TWIS1, + TWIS2_NS as TWIS2, + TWIS3_NS as TWIS3, + UARTE0_NS as UARTE0, + UARTE1_NS as UARTE1, + UARTE2_NS as UARTE2, + UARTE3_NS as UARTE3, + VMC_NS as VMC, + WDT_NS as WDT, + }; + + #[cfg(feature = "_s")] + #[doc(no_inline)] + pub use nrf_pac::{ + CC_HOST_RGF_S as CC_HOST_RGF, + CLOCK_S as CLOCK, + CRYPTOCELL_S as CRYPTOCELL, + CTRL_AP_PERI_S as CTRL_AP_PERI, + DPPIC_S as DPPIC, + EGU0_S as EGU0, + EGU1_S as EGU1, + EGU2_S as EGU2, + EGU3_S as EGU3, + EGU4_S as EGU4, + EGU5_S as EGU5, + FICR_S as FICR, + FPU_NS as FPU, + GPIOTE0_S as GPIOTE0, + I2S_S as I2S, + IPC_S as IPC, + KMU_S as KMU, + NVMC_S as NVMC, + P0_S as P0, + PDM_S as PDM, + POWER_S as POWER, + PWM0_S as PWM0, + PWM1_S as PWM1, + PWM2_S as PWM2, + PWM3_S as PWM3, + REGULATORS_S as REGULATORS, + RTC0_S as RTC0, + RTC1_S as RTC1, + SAADC_S as SAADC, + SPIM0_S as SPIM0, + SPIM1_S as SPIM1, + SPIM2_S as SPIM2, + SPIM3_S as SPIM3, + SPIS0_S as SPIS0, + SPIS1_S as SPIS1, + SPIS2_S as SPIS2, + SPIS3_S as SPIS3, + SPU_S as SPU, + TAD_S as TAD, + TIMER0_S as TIMER0, + TIMER1_S as TIMER1, + TIMER2_S as TIMER2, + TWIM0_S as TWIM0, + TWIM1_S as TWIM1, + TWIM2_S as TWIM2, + TWIM3_S as TWIM3, + TWIS0_S as TWIS0, + TWIS1_S as TWIS1, + TWIS2_S as TWIS2, + TWIS3_S as TWIS3, + UARTE0_S as UARTE0, + UARTE1_S as UARTE1, + UARTE2_S as UARTE2, + UARTE3_S as UARTE3, + UICR_S as UICR, + VMC_S as VMC, + WDT_S as WDT, + }; +} + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +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_internal::peripherals! { + // RTC + RTC0, + RTC1, + + // WDT + WDT, + + // NVMC + NVMC, + + // UARTE, TWI & SPI + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, + + // SAADC + SAADC, + + // PWM + PWM0, + PWM1, + PWM2, + PWM3, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + P0_09, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // PDM + PDM, + + // EGU + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, +} + +impl_uarte!(SERIAL0, UARTE0, SERIAL0); +impl_uarte!(SERIAL1, UARTE1, SERIAL1); +impl_uarte!(SERIAL2, UARTE2, SERIAL2); +impl_uarte!(SERIAL3, UARTE3, SERIAL3); + +impl_spim!(SERIAL0, SPIM0, SERIAL0); +impl_spim!(SERIAL1, SPIM1, SERIAL1); +impl_spim!(SERIAL2, SPIM2, SERIAL2); +impl_spim!(SERIAL3, SPIM3, SERIAL3); + +impl_spis!(SERIAL0, SPIS0, SERIAL0); +impl_spis!(SERIAL1, SPIS1, SERIAL1); +impl_spis!(SERIAL2, SPIS2, SERIAL2); +impl_spis!(SERIAL3, SPIS3, SERIAL3); + +impl_twim!(SERIAL0, TWIM0, SERIAL0); +impl_twim!(SERIAL1, TWIM1, SERIAL1); +impl_twim!(SERIAL2, TWIM2, SERIAL2); +impl_twim!(SERIAL3, TWIM3, SERIAL3); + +impl_twis!(SERIAL0, TWIS0, SERIAL0); +impl_twis!(SERIAL1, TWIS1, SERIAL1); +impl_twis!(SERIAL2, TWIS2, SERIAL2); +impl_twis!(SERIAL3, TWIS3, SERIAL3); + +impl_pwm!(PWM0, PWM0, PWM0); +impl_pwm!(PWM1, PWM1, PWM1); +impl_pwm!(PWM2, PWM2, PWM2); +impl_pwm!(PWM3, PWM3, PWM3); + +impl_pdm!(PDM, PDM, PDM); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +impl_pin!(P0_09, 0, 9); +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH10, 10 => configurable); +impl_ppi_channel!(PPI_CH11, 11 => configurable); +impl_ppi_channel!(PPI_CH12, 12 => configurable); +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, 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); + +impl_egu!(EGU0, EGU0, EGU0); +impl_egu!(EGU1, EGU1, EGU1); +impl_egu!(EGU2, EGU2, EGU2); +impl_egu!(EGU3, EGU3, EGU3); +impl_egu!(EGU4, EGU4, EGU4); +impl_egu!(EGU5, EGU5, EGU5); + +embassy_hal_internal::interrupt_mod!( + SPU, + CLOCK_POWER, + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, + GPIOTE0, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + RTC1, + WDT, + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, + PWM0, + PWM1, + PWM2, + PWM3, + PDM, + I2S, + IPC, + FPU, + GPIOTE1, + KMU, + CRYPTOCELL, +); diff --git a/embassy/embassy-nrf/src/chips/nrf9160.rs b/embassy/embassy-nrf/src/chips/nrf9160.rs new file mode 100644 index 0000000..b0981e3 --- /dev/null +++ b/embassy/embassy-nrf/src/chips/nrf9160.rs @@ -0,0 +1,377 @@ +/// Peripheral Access Crate +#[allow(unused_imports)] +#[rustfmt::skip] +pub mod pac { + pub use nrf_pac::*; + + #[cfg(feature = "_ns")] + #[doc(no_inline)] + pub use nrf_pac::{ + CLOCK_NS as CLOCK, + DPPIC_NS as DPPIC, + EGU0_NS as EGU0, + EGU1_NS as EGU1, + EGU2_NS as EGU2, + EGU3_NS as EGU3, + EGU4_NS as EGU4, + EGU5_NS as EGU5, + FPU_NS as FPU, + GPIOTE1_NS as GPIOTE1, + I2S_NS as I2S, + IPC_NS as IPC, + KMU_NS as KMU, + NVMC_NS as NVMC, + P0_NS as P0, + PDM_NS as PDM, + POWER_NS as POWER, + PWM0_NS as PWM0, + PWM1_NS as PWM1, + PWM2_NS as PWM2, + PWM3_NS as PWM3, + REGULATORS_NS as REGULATORS, + RTC0_NS as RTC0, + RTC1_NS as RTC1, + SAADC_NS as SAADC, + SPIM0_NS as SPIM0, + SPIM1_NS as SPIM1, + SPIM2_NS as SPIM2, + SPIM3_NS as SPIM3, + SPIS0_NS as SPIS0, + SPIS1_NS as SPIS1, + SPIS2_NS as SPIS2, + SPIS3_NS as SPIS3, + TIMER0_NS as TIMER0, + TIMER1_NS as TIMER1, + TIMER2_NS as TIMER2, + TWIM0_NS as TWIM0, + TWIM1_NS as TWIM1, + TWIM2_NS as TWIM2, + TWIM3_NS as TWIM3, + TWIS0_NS as TWIS0, + TWIS1_NS as TWIS1, + TWIS2_NS as TWIS2, + TWIS3_NS as TWIS3, + UARTE0_NS as UARTE0, + UARTE1_NS as UARTE1, + UARTE2_NS as UARTE2, + UARTE3_NS as UARTE3, + VMC_NS as VMC, + WDT_NS as WDT, + }; + + #[cfg(feature = "_s")] + #[doc(no_inline)] + pub use nrf_pac::{ + CC_HOST_RGF_S as CC_HOST_RGF, + CLOCK_S as CLOCK, + CRYPTOCELL_S as CRYPTOCELL, + CTRL_AP_PERI_S as CTRL_AP_PERI, + DPPIC_S as DPPIC, + EGU0_S as EGU0, + EGU1_S as EGU1, + EGU2_S as EGU2, + EGU3_S as EGU3, + EGU4_S as EGU4, + EGU5_S as EGU5, + FICR_S as FICR, + FPU_S as FPU, + GPIOTE0_S as GPIOTE0, + I2S_S as I2S, + IPC_S as IPC, + KMU_S as KMU, + NVMC_S as NVMC, + P0_S as P0, + PDM_S as PDM, + POWER_S as POWER, + PWM0_S as PWM0, + PWM1_S as PWM1, + PWM2_S as PWM2, + PWM3_S as PWM3, + REGULATORS_S as REGULATORS, + RTC0_S as RTC0, + RTC1_S as RTC1, + SAADC_S as SAADC, + SPIM0_S as SPIM0, + SPIM1_S as SPIM1, + SPIM2_S as SPIM2, + SPIM3_S as SPIM3, + SPIS0_S as SPIS0, + SPIS1_S as SPIS1, + SPIS2_S as SPIS2, + SPIS3_S as SPIS3, + SPU_S as SPU, + TAD_S as TAD, + TIMER0_S as TIMER0, + TIMER1_S as TIMER1, + TIMER2_S as TIMER2, + TWIM0_S as TWIM0, + TWIM1_S as TWIM1, + TWIM2_S as TWIM2, + TWIM3_S as TWIM3, + TWIS0_S as TWIS0, + TWIS1_S as TWIS1, + TWIS2_S as TWIS2, + TWIS3_S as TWIS3, + UARTE0_S as UARTE0, + UARTE1_S as UARTE1, + UARTE2_S as UARTE2, + UARTE3_S as UARTE3, + UICR_S as UICR, + VMC_S as VMC, + WDT_S as WDT, + }; +} + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +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_internal::peripherals! { + // RTC + RTC0, + RTC1, + + // WDT + WDT, + + // NVMC + NVMC, + + // UARTE, TWI & SPI + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, + + // SAADC + SAADC, + + // PWM + PWM0, + PWM1, + PWM2, + PWM3, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + PPI_GROUP4, + PPI_GROUP5, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + P0_09, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // PDM + PDM, + + // EGU + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, +} + +impl_uarte!(SERIAL0, UARTE0, SERIAL0); +impl_uarte!(SERIAL1, UARTE1, SERIAL1); +impl_uarte!(SERIAL2, UARTE2, SERIAL2); +impl_uarte!(SERIAL3, UARTE3, SERIAL3); + +impl_spim!(SERIAL0, SPIM0, SERIAL0); +impl_spim!(SERIAL1, SPIM1, SERIAL1); +impl_spim!(SERIAL2, SPIM2, SERIAL2); +impl_spim!(SERIAL3, SPIM3, SERIAL3); + +impl_spis!(SERIAL0, SPIS0, SERIAL0); +impl_spis!(SERIAL1, SPIS1, SERIAL1); +impl_spis!(SERIAL2, SPIS2, SERIAL2); +impl_spis!(SERIAL3, SPIS3, SERIAL3); + +impl_twim!(SERIAL0, TWIM0, SERIAL0); +impl_twim!(SERIAL1, TWIM1, SERIAL1); +impl_twim!(SERIAL2, TWIM2, SERIAL2); +impl_twim!(SERIAL3, TWIM3, SERIAL3); + +impl_twis!(SERIAL0, TWIS0, SERIAL0); +impl_twis!(SERIAL1, TWIS1, SERIAL1); +impl_twis!(SERIAL2, TWIS2, SERIAL2); +impl_twis!(SERIAL3, TWIS3, SERIAL3); + +impl_pwm!(PWM0, PWM0, PWM0); +impl_pwm!(PWM1, PWM1, PWM1); +impl_pwm!(PWM2, PWM2, PWM2); +impl_pwm!(PWM3, PWM3, PWM3); + +impl_pdm!(PDM, PDM, PDM); + +impl_timer!(TIMER0, TIMER0, TIMER0); +impl_timer!(TIMER1, TIMER1, TIMER1); +impl_timer!(TIMER2, TIMER2, TIMER2); + +impl_pin!(P0_00, 0, 0); +impl_pin!(P0_01, 0, 1); +impl_pin!(P0_02, 0, 2); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +impl_pin!(P0_09, 0, 9); +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_ppi_channel!(PPI_CH0, 0 => configurable); +impl_ppi_channel!(PPI_CH1, 1 => configurable); +impl_ppi_channel!(PPI_CH2, 2 => configurable); +impl_ppi_channel!(PPI_CH3, 3 => configurable); +impl_ppi_channel!(PPI_CH4, 4 => configurable); +impl_ppi_channel!(PPI_CH5, 5 => configurable); +impl_ppi_channel!(PPI_CH6, 6 => configurable); +impl_ppi_channel!(PPI_CH7, 7 => configurable); +impl_ppi_channel!(PPI_CH8, 8 => configurable); +impl_ppi_channel!(PPI_CH9, 9 => configurable); +impl_ppi_channel!(PPI_CH10, 10 => configurable); +impl_ppi_channel!(PPI_CH11, 11 => configurable); +impl_ppi_channel!(PPI_CH12, 12 => configurable); +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, 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); + +impl_egu!(EGU0, EGU0, EGU0); +impl_egu!(EGU1, EGU1, EGU1); +impl_egu!(EGU2, EGU2, EGU2); +impl_egu!(EGU3, EGU3, EGU3); +impl_egu!(EGU4, EGU4, EGU4); +impl_egu!(EGU5, EGU5, EGU5); + +embassy_hal_internal::interrupt_mod!( + SPU, + CLOCK_POWER, + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, + GPIOTE0, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + RTC1, + WDT, + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, + PWM0, + PWM1, + PWM2, + PWM3, + PDM, + I2S, + IPC, + FPU, + GPIOTE1, + KMU, + CRYPTOCELL, +); diff --git a/embassy/embassy-nrf/src/egu.rs b/embassy/embassy-nrf/src/egu.rs new file mode 100644 index 0000000..7f9abda --- /dev/null +++ b/embassy/embassy-nrf/src/egu.rs @@ -0,0 +1,115 @@ +//! EGU driver. +//! +//! The event generator driver provides a higher level API for task triggering +//! and events to use with PPI. + +#![macro_use] + +use core::marker::PhantomData; + +use embassy_hal_internal::into_ref; + +use crate::ppi::{Event, Task}; +use crate::{interrupt, pac, Peripheral, PeripheralRef}; + +/// An instance of the EGU. +pub struct Egu<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Egu<'d, T> { + /// Create a new EGU instance. + pub fn new(_p: impl Peripheral

+ 'd) -> Self { + into_ref!(_p); + Self { _p } + } + + /// Get a handle to a trigger for the EGU. + pub fn trigger(&mut self, number: TriggerNumber) -> Trigger<'d, T> { + Trigger { + number, + _p: PhantomData, + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::egu::Egu; +} + +/// Basic Egu instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_egu { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::egu::SealedInstance for peripherals::$type { + fn regs() -> pac::egu::Egu { + pac::$pac_type + } + } + impl crate::egu::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +/// Represents a trigger within the EGU. +pub struct Trigger<'d, T: Instance> { + number: TriggerNumber, + _p: PhantomData<&'d T>, +} + +impl<'d, T: Instance> Trigger<'d, T> { + /// Get task for this trigger to use with PPI. + pub fn task(&self) -> Task<'d> { + let nr = self.number as usize; + let regs = T::regs(); + Task::from_reg(regs.tasks_trigger(nr)) + } + + /// Get event for this trigger to use with PPI. + pub fn event(&self) -> Event<'d> { + let nr = self.number as usize; + let regs = T::regs(); + Event::from_reg(regs.events_triggered(nr)) + } + + /// Enable interrupts for this trigger + pub fn enable_interrupt(&mut self) { + let regs = T::regs(); + regs.intenset().modify(|w| w.set_triggered(self.number as usize, true)); + } + + /// Enable interrupts for this trigger + pub fn disable_interrupt(&mut self) { + let regs = T::regs(); + regs.intenset().modify(|w| w.set_triggered(self.number as usize, false)); + } +} + +/// Represents a trigger within an EGU. +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum TriggerNumber { + Trigger0 = 0, + Trigger1 = 1, + Trigger2 = 2, + Trigger3 = 3, + Trigger4 = 4, + Trigger5 = 5, + Trigger6 = 6, + Trigger7 = 7, + Trigger8 = 8, + Trigger9 = 9, + Trigger10 = 10, + Trigger11 = 11, + Trigger12 = 12, + Trigger13 = 13, + Trigger14 = 14, + Trigger15 = 15, +} diff --git a/embassy/embassy-nrf/src/fmt.rs b/embassy/embassy-nrf/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-nrf/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-nrf/src/gpio.rs b/embassy/embassy-nrf/src/gpio.rs new file mode 100644 index 0000000..130339c --- /dev/null +++ b/embassy/embassy-nrf/src/gpio.rs @@ -0,0 +1,793 @@ +//! General purpose input/output (GPIO) driver. +#![macro_use] + +use core::convert::Infallible; +use core::hint::unreachable_unchecked; + +use cfg_if::cfg_if; +use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; + +use crate::pac::common::{Reg, RW}; +use crate::pac::gpio; +use crate::pac::gpio::vals; +#[cfg(not(feature = "_nrf51"))] +use crate::pac::shared::{regs::Psel, vals::Connect}; +use crate::{pac, Peripheral}; + +/// A GPIO port with up to 32 pins. +#[derive(Debug, Eq, PartialEq)] +pub enum Port { + /// Port 0, available on nRF9160 and all nRF52 and nRF51 MCUs. + Port0, + + /// Port 1, only available on some MCUs. + #[cfg(feature = "_gpio-p1")] + Port1, + + /// Port 2, only available on some MCUs. + #[cfg(feature = "_gpio-p2")] + Port2, +} + +/// Pull setting for an input. +#[derive(Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pull { + /// No pull. + None, + /// Internal pull-up resistor. + Up, + /// Internal pull-down resistor. + Down, +} + +/// GPIO input driver. +pub struct Input<'d> { + pub(crate) pin: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, pull: Pull) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_input(pull); + + Self { pin } + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Get the pin input level. + #[inline] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } +} + +/// Digital input or output level. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Level { + /// Logical low. + Low, + /// Logical high. + High, +} + +impl From for Level { + fn from(val: bool) -> Self { + match val { + true => Self::High, + false => Self::Low, + } + } +} + +impl From for bool { + fn from(level: Level) -> bool { + match level { + Level::Low => false, + Level::High => true, + } + } +} + +/// Drive strength settings for a given output level. +// These numbers match vals::Drive exactly so hopefully the compiler will unify them. +#[cfg(feature = "_nrf54l")] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum LevelDrive { + /// Disconnect (do not drive the output at all) + Disconnect = 2, + /// Standard + Standard = 0, + /// High drive + High = 1, + /// Extra high drive + ExtraHigh = 3, +} + +/// Drive strength settings for an output pin. +/// +/// This is a combination of two drive levels, used when the pin is set +/// low and high respectively. +#[cfg(feature = "_nrf54l")] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OutputDrive { + low: LevelDrive, + high: LevelDrive, +} + +#[cfg(feature = "_nrf54l")] +#[allow(non_upper_case_globals)] +impl OutputDrive { + /// Standard '0', standard '1' + pub const Standard: Self = Self { + low: LevelDrive::Standard, + high: LevelDrive::Standard, + }; + /// High drive '0', standard '1' + pub const HighDrive0Standard1: Self = Self { + low: LevelDrive::High, + high: LevelDrive::Standard, + }; + /// Standard '0', high drive '1' + pub const Standard0HighDrive1: Self = Self { + low: LevelDrive::Standard, + high: LevelDrive::High, + }; + /// High drive '0', high 'drive '1' + pub const HighDrive: Self = Self { + low: LevelDrive::High, + high: LevelDrive::High, + }; + /// Disconnect '0' standard '1' (normally used for wired-or connections) + pub const Disconnect0Standard1: Self = Self { + low: LevelDrive::Disconnect, + high: LevelDrive::Standard, + }; + /// Disconnect '0', high drive '1' (normally used for wired-or connections) + pub const Disconnect0HighDrive1: Self = Self { + low: LevelDrive::Disconnect, + high: LevelDrive::High, + }; + /// Standard '0'. disconnect '1' (also known as "open drain", normally used for wired-and connections) + pub const Standard0Disconnect1: Self = Self { + low: LevelDrive::Standard, + high: LevelDrive::Disconnect, + }; + /// High drive '0', disconnect '1' (also known as "open drain", normally used for wired-and connections) + pub const HighDrive0Disconnect1: Self = Self { + low: LevelDrive::High, + high: LevelDrive::Disconnect, + }; +} + +/// Drive strength settings for an output pin. +// These numbers match vals::Drive exactly so hopefully the compiler will unify them. +#[cfg(not(feature = "_nrf54l"))] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum OutputDrive { + /// Standard '0', standard '1' + Standard = 0, + /// High drive '0', standard '1' + HighDrive0Standard1 = 1, + /// Standard '0', high drive '1' + Standard0HighDrive1 = 2, + /// High drive '0', high 'drive '1' + HighDrive = 3, + /// Disconnect '0' standard '1' (normally used for wired-or connections) + Disconnect0Standard1 = 4, + /// Disconnect '0', high drive '1' (normally used for wired-or connections) + Disconnect0HighDrive1 = 5, + /// Standard '0'. disconnect '1' (also known as "open drain", normally used for wired-and connections) + Standard0Disconnect1 = 6, + /// High drive '0', disconnect '1' (also known as "open drain", normally used for wired-and connections) + HighDrive0Disconnect1 = 7, +} + +/// GPIO output driver. +pub struct Output<'d> { + pub(crate) pin: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create GPIO output driver for a [Pin] with the provided [Level] and [OutputDriver] configuration. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, initial_output: Level, drive: OutputDrive) -> Self { + let mut pin = Flex::new(pin); + match initial_output { + Level::High => pin.set_high(), + Level::Low => pin.set_low(), + } + pin.set_as_output(drive); + + Self { pin } + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high() + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low() + } + + /// Toggle the output level. + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle() + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level) + } + + /// Get whether the output level is set to high. + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Get whether the output level is set to low. + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } + + /// Get the current output level. + #[inline] + pub fn get_output_level(&self) -> Level { + self.pin.get_output_level() + } +} + +pub(crate) fn convert_drive(w: &mut pac::gpio::regs::PinCnf, drive: OutputDrive) { + #[cfg(not(feature = "_nrf54l"))] + { + let drive = match drive { + OutputDrive::Standard => vals::Drive::S0S1, + OutputDrive::HighDrive0Standard1 => vals::Drive::H0S1, + OutputDrive::Standard0HighDrive1 => vals::Drive::S0H1, + OutputDrive::HighDrive => vals::Drive::H0H1, + OutputDrive::Disconnect0Standard1 => vals::Drive::D0S1, + OutputDrive::Disconnect0HighDrive1 => vals::Drive::D0H1, + OutputDrive::Standard0Disconnect1 => vals::Drive::S0D1, + OutputDrive::HighDrive0Disconnect1 => vals::Drive::H0D1, + }; + w.set_drive(drive); + } + + #[cfg(feature = "_nrf54l")] + { + fn convert(d: LevelDrive) -> vals::Drive { + match d { + LevelDrive::Disconnect => vals::Drive::D, + LevelDrive::Standard => vals::Drive::S, + LevelDrive::High => vals::Drive::H, + LevelDrive::ExtraHigh => vals::Drive::E, + } + } + + w.set_drive0(convert(drive.low)); + w.set_drive0(convert(drive.high)); + } +} + +fn convert_pull(pull: Pull) -> vals::Pull { + match pull { + Pull::None => vals::Pull::DISABLED, + Pull::Up => vals::Pull::PULLUP, + Pull::Down => vals::Pull::PULLDOWN, + } +} + +/// GPIO flexible pin. +/// +/// This pin can either be a disconnected, input, or output pin, or both. The level register bit will remain +/// set while not in output mode, so the pin's level will be 'remembered' when it is not in output +/// mode. +pub struct Flex<'d> { + pub(crate) pin: PeripheralRef<'d, AnyPin>, +} + +impl<'d> Flex<'d> { + /// Wrap the pin in a `Flex`. + /// + /// The pin remains disconnected. The initial output level is unspecified, but can be changed + /// before the pin is put into output mode. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd) -> Self { + into_ref!(pin); + // Pin will be in disconnected state. + Self { pin: pin.map_into() } + } + + /// Put the pin into input mode. + #[inline] + pub fn set_as_input(&mut self, pull: Pull) { + self.pin.conf().write(|w| { + w.set_dir(vals::Dir::INPUT); + w.set_input(vals::Input::CONNECT); + w.set_pull(convert_pull(pull)); + convert_drive(w, OutputDrive::Standard); + w.set_sense(vals::Sense::DISABLED); + }); + } + + /// Put the pin into output mode. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + #[inline] + pub fn set_as_output(&mut self, drive: OutputDrive) { + self.pin.conf().write(|w| { + w.set_dir(vals::Dir::OUTPUT); + w.set_input(vals::Input::DISCONNECT); + w.set_pull(vals::Pull::DISABLED); + convert_drive(w, drive); + w.set_sense(vals::Sense::DISABLED); + }); + } + + /// Put the pin into input + output mode. + /// + /// This is commonly used for "open drain" mode. If you set `drive = Standard0Disconnect1`, + /// the hardware will drive the line low if you set it to low, and will leave it floating if you set + /// it to high, in which case you can read the input to figure out whether another device + /// is driving the line low. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + #[inline] + pub fn set_as_input_output(&mut self, pull: Pull, drive: OutputDrive) { + self.pin.conf().write(|w| { + w.set_dir(vals::Dir::OUTPUT); + w.set_input(vals::Input::CONNECT); + w.set_pull(convert_pull(pull)); + convert_drive(w, drive); + w.set_sense(vals::Sense::DISABLED); + }); + } + + /// Put the pin into disconnected mode. + #[inline] + pub fn set_as_disconnected(&mut self) { + self.pin.conf().write(|_| ()); + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.block().in_().read().pin(self.pin.pin() as _) + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + !self.is_high() + } + + /// Get the pin input level. + #[inline] + pub fn get_level(&self) -> Level { + self.is_high().into() + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high() + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low() + } + + /// Toggle the output level. + #[inline] + pub fn toggle(&mut self) { + if self.is_set_low() { + self.set_high() + } else { + self.set_low() + } + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + match level { + Level::Low => self.pin.set_low(), + Level::High => self.pin.set_high(), + } + } + + /// Get whether the output level is set to high. + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.block().out().read().pin(self.pin.pin() as _) + } + + /// Get whether the output level is set to low. + #[inline] + pub fn is_set_low(&self) -> bool { + !self.is_set_high() + } + + /// Get the current output level. + #[inline] + pub fn get_output_level(&self) -> Level { + self.is_set_high().into() + } +} + +impl<'d> Drop for Flex<'d> { + fn drop(&mut self) { + self.pin.conf().write(|_| ()) + } +} + +pub(crate) trait SealedPin { + fn pin_port(&self) -> u8; + + #[inline] + fn _pin(&self) -> u8 { + cfg_if! { + if #[cfg(feature = "_gpio-p1")] { + self.pin_port() % 32 + } else { + self.pin_port() + } + } + } + + #[inline] + fn block(&self) -> gpio::Gpio { + match self.pin_port() / 32 { + #[cfg(feature = "_nrf51")] + 0 => pac::GPIO, + #[cfg(not(feature = "_nrf51"))] + 0 => pac::P0, + #[cfg(feature = "_gpio-p1")] + 1 => pac::P1, + #[cfg(feature = "_gpio-p2")] + 2 => pac::P2, + _ => unsafe { unreachable_unchecked() }, + } + } + + #[inline] + fn conf(&self) -> Reg { + self.block().pin_cnf(self._pin() as usize) + } + + /// Set the output as high. + #[inline] + fn set_high(&self) { + self.block().outset().write(|w| w.set_pin(self._pin() as _, true)) + } + + /// Set the output as low. + #[inline] + fn set_low(&self) { + self.block().outclr().write(|w| w.set_pin(self._pin() as _, true)) + } +} + +/// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an [AnyPin]. +#[allow(private_bounds)] +pub trait Pin: Peripheral

+ Into + SealedPin + Sized + 'static { + /// Number of the pin within the port (0..31) + #[inline] + fn pin(&self) -> u8 { + self._pin() + } + + /// Port of the pin + #[inline] + fn port(&self) -> Port { + match self.pin_port() / 32 { + 0 => Port::Port0, + #[cfg(feature = "_gpio-p1")] + 1 => Port::Port1, + #[cfg(feature = "_gpio-p2")] + 2 => Port::Port2, + _ => unsafe { unreachable_unchecked() }, + } + } + + /// Peripheral port register value + #[inline] + #[cfg(not(feature = "_nrf51"))] + fn psel_bits(&self) -> pac::shared::regs::Psel { + pac::shared::regs::Psel(self.pin_port() as u32) + } + + /// Convert from concrete pin type PX_XX to type erased `AnyPin`. + #[inline] + fn degrade(self) -> AnyPin { + AnyPin { + pin_port: self.pin_port(), + } + } +} + +/// Type-erased GPIO pin +pub struct AnyPin { + pin_port: u8, +} + +impl AnyPin { + /// Create an [AnyPin] for a specific pin. + /// + /// # Safety + /// - `pin_port` should not in use by another driver. + #[inline] + pub unsafe fn steal(pin_port: u8) -> Self { + Self { pin_port } + } +} + +impl_peripheral!(AnyPin); +impl Pin for AnyPin {} +impl SealedPin for AnyPin { + #[inline] + fn pin_port(&self) -> u8 { + self.pin_port + } +} + +// ==================== + +#[cfg(not(feature = "_nrf51"))] +#[cfg_attr(feature = "_nrf54l", allow(unused))] // TODO +pub(crate) trait PselBits { + fn psel_bits(&self) -> pac::shared::regs::Psel; +} + +#[cfg(not(feature = "_nrf51"))] +impl<'a, P: Pin> PselBits for Option> { + #[inline] + fn psel_bits(&self) -> pac::shared::regs::Psel { + match self { + Some(pin) => pin.psel_bits(), + None => DISCONNECTED, + } + } +} + +#[cfg(not(feature = "_nrf51"))] +#[cfg_attr(feature = "_nrf54l", allow(unused))] // TODO +pub(crate) const DISCONNECTED: Psel = Psel(1 << 31); + +#[cfg(not(feature = "_nrf51"))] +#[allow(dead_code)] +pub(crate) fn deconfigure_pin(psel: Psel) { + if psel.connect() == Connect::DISCONNECTED { + return; + } + unsafe { AnyPin::steal(psel.0 as _).conf().write(|_| ()) } +} + +// ==================== + +macro_rules! impl_pin { + ($type:ident, $port_num:expr, $pin_num:expr) => { + impl crate::gpio::Pin for peripherals::$type {} + impl crate::gpio::SealedPin for peripherals::$type { + #[inline] + fn pin_port(&self) -> u8 { + $port_num * 32 + $pin_num + } + } + + impl From for crate::gpio::AnyPin { + fn from(val: peripherals::$type) -> Self { + crate::gpio::Pin::degrade(val) + } + } + }; +} + +// ==================== + +mod eh02 { + use super::*; + + impl<'d> embedded_hal_02::digital::v2::InputPin for Input<'d> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::OutputPin for Output<'d> { + type Error = Infallible; + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } + } + + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Output<'d> { + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'d> { + type Error = Infallible; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } + } + + /// Implement [`embedded_hal_02::digital::v2::InputPin`] for [`Flex`]; + /// + /// If the pin is not in input mode the result is unspecified. + impl<'d> embedded_hal_02::digital::v2::InputPin for Flex<'d> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::OutputPin for Flex<'d> { + type Error = Infallible; + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } + } + + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'d> { + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'d> { + type Error = Infallible; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Input<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::InputPin for Input<'d> { + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Output<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::OutputPin for Output<'d> { + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Output<'d> { + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Flex<'d> { + type Error = Infallible; +} + +/// Implement [`InputPin`] for [`Flex`]; +/// +/// If the pin is not in input mode the result is unspecified. +impl<'d> embedded_hal_1::digital::InputPin for Flex<'d> { + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_1::digital::OutputPin for Flex<'d> { + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Flex<'d> { + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} diff --git a/embassy/embassy-nrf/src/gpiote.rs b/embassy/embassy-nrf/src/gpiote.rs new file mode 100644 index 0000000..8771f9f --- /dev/null +++ b/embassy/embassy-nrf/src/gpiote.rs @@ -0,0 +1,594 @@ +//! GPIO task/event (GPIOTE) driver. + +use core::convert::Infallible; +use core::future::{poll_fn, Future}; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{impl_peripheral, into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::gpio::{AnyPin, Flex, Input, Output, Pin as GpioPin, SealedPin as _}; +use crate::interrupt::InterruptExt; +#[cfg(not(feature = "_nrf51"))] +use crate::pac::gpio::vals::Detectmode; +use crate::pac::gpio::vals::Sense; +use crate::pac::gpiote::vals::{Mode, Outinit, Polarity}; +use crate::ppi::{Event, Task}; +use crate::{interrupt, pac, peripherals}; + +#[cfg(feature = "_nrf51")] +/// Amount of GPIOTE channels in the chip. +const CHANNEL_COUNT: usize = 4; +#[cfg(not(feature = "_nrf51"))] +/// Amount of GPIOTE channels in the chip. +const CHANNEL_COUNT: usize = 8; + +#[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] +const PIN_COUNT: usize = 48; +#[cfg(not(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340")))] +const PIN_COUNT: usize = 32; + +#[allow(clippy::declare_interior_mutable_const)] +static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT]; +static PORT_WAKERS: [AtomicWaker; PIN_COUNT] = [const { AtomicWaker::new() }; PIN_COUNT]; + +/// Polarity for listening to events for GPIOTE input channels. +pub enum InputChannelPolarity { + /// 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 OUT task operation for GPIOTE output channels. +pub enum OutputChannelPolarity { + /// Set the pin high. + Set, + /// Set the pin low. + Clear, + /// Toggle the pin. + Toggle, +} + +fn regs() -> pac::gpiote::Gpiote { + cfg_if::cfg_if! { + if #[cfg(any(feature="nrf5340-app-s", feature="nrf9160-s", feature="nrf9120-s"))] { + pac::GPIOTE0 + } else if #[cfg(any(feature="nrf5340-app-ns", feature="nrf9160-ns", feature="nrf9120-ns"))] { + pac::GPIOTE1 + } else { + pac::GPIOTE + } + } +} + +pub(crate) fn init(irq_prio: crate::interrupt::Priority) { + // no latched GPIO detect in nrf51. + #[cfg(not(feature = "_nrf51"))] + { + #[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] + let ports = &[pac::P0, pac::P1]; + #[cfg(not(any(feature = "_nrf51", feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340")))] + let ports = &[pac::P0]; + + for &p in ports { + // Enable latched detection + p.detectmode().write(|w| w.set_detectmode(Detectmode::LDETECT)); + // Clear latch + p.latch().write(|w| w.0 = 0xFFFFFFFF) + } + } + + // Enable interrupts + #[cfg(any(feature = "nrf5340-app-s", feature = "nrf9160-s", feature = "nrf9120-s"))] + let irq = interrupt::GPIOTE0; + #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns", feature = "nrf9120-ns"))] + let irq = interrupt::GPIOTE1; + #[cfg(any(feature = "_nrf51", feature = "_nrf52", feature = "nrf5340-net"))] + let irq = interrupt::GPIOTE; + + irq.unpend(); + irq.set_priority(irq_prio); + unsafe { irq.enable() }; + + let g = regs(); + g.intenset().write(|w| w.set_port(true)); +} + +#[cfg(any(feature = "nrf5340-app-s", feature = "nrf9160-s", feature = "nrf9120-s"))] +#[cfg(feature = "rt")] +#[interrupt] +fn GPIOTE0() { + unsafe { handle_gpiote_interrupt() }; +} + +#[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns", feature = "nrf9120-ns"))] +#[cfg(feature = "rt")] +#[interrupt] +fn GPIOTE1() { + unsafe { handle_gpiote_interrupt() }; +} + +#[cfg(any(feature = "_nrf51", feature = "_nrf52", feature = "nrf5340-net"))] +#[cfg(feature = "rt")] +#[interrupt] +fn GPIOTE() { + unsafe { handle_gpiote_interrupt() }; +} + +unsafe fn handle_gpiote_interrupt() { + let g = regs(); + + for i in 0..CHANNEL_COUNT { + if g.events_in(i).read() != 0 { + g.intenclr().write(|w| w.0 = 1 << i); + CHANNEL_WAKERS[i].wake(); + } + } + + if g.events_port().read() != 0 { + g.events_port().write_value(0); + + #[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] + let ports = &[pac::P0, pac::P1]; + #[cfg(not(any(feature = "_nrf51", feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340")))] + let ports = &[pac::P0]; + #[cfg(feature = "_nrf51")] + let ports = &[pac::GPIO]; + + #[cfg(feature = "_nrf51")] + for (port, &p) in ports.iter().enumerate() { + let inp = p.in_().read(); + for pin in 0..32 { + let fired = match p.pin_cnf(pin as usize).read().sense() { + Sense::HIGH => inp.pin(pin), + Sense::LOW => !inp.pin(pin), + _ => false, + }; + + if fired { + PORT_WAKERS[port * 32 + pin as usize].wake(); + p.pin_cnf(pin as usize).modify(|w| w.set_sense(Sense::DISABLED)); + } + } + } + + #[cfg(not(feature = "_nrf51"))] + for (port, &p) in ports.iter().enumerate() { + let bits = p.latch().read().0; + for pin in BitIter(bits) { + p.pin_cnf(pin as usize).modify(|w| w.set_sense(Sense::DISABLED)); + PORT_WAKERS[port * 32 + pin as usize].wake(); + } + p.latch().write(|w| w.0 = bits); + } + } +} + +#[cfg(not(feature = "_nrf51"))] +struct BitIter(u32); + +#[cfg(not(feature = "_nrf51"))] +impl Iterator for BitIter { + type Item = u32; + + fn next(&mut self) -> Option { + match self.0.trailing_zeros() { + 32 => None, + b => { + self.0 &= !(1 << b); + Some(b) + } + } + } +} + +/// GPIOTE channel driver in input mode +pub struct InputChannel<'d> { + ch: PeripheralRef<'d, AnyChannel>, + pin: Input<'d>, +} + +impl<'d> Drop for InputChannel<'d> { + fn drop(&mut self) { + let g = regs(); + let num = self.ch.number(); + g.config(num).write(|w| w.set_mode(Mode::DISABLED)); + g.intenclr().write(|w| w.0 = 1 << num); + } +} + +impl<'d> InputChannel<'d> { + /// Create a new GPIOTE input channel driver. + pub fn new(ch: impl Peripheral

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

+ 'd, pin: Output<'d>, polarity: OutputChannelPolarity) -> Self { + into_ref!(ch); + let g = regs(); + let num = ch.number(); + + g.config(num).write(|w| { + w.set_mode(Mode::TASK); + match pin.is_set_high() { + true => w.set_outinit(Outinit::HIGH), + false => w.set_outinit(Outinit::LOW), + }; + match polarity { + OutputChannelPolarity::Set => w.set_polarity(Polarity::HI_TO_LO), + OutputChannelPolarity::Clear => w.set_polarity(Polarity::LO_TO_HI), + OutputChannelPolarity::Toggle => w.set_polarity(Polarity::TOGGLE), + }; + #[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] + w.set_port(match pin.pin.pin.port() { + crate::gpio::Port::Port0 => false, + crate::gpio::Port::Port1 => true, + }); + w.set_psel(pin.pin.pin.pin()); + }); + + OutputChannel { + ch: ch.map_into(), + _pin: pin, + } + } + + /// 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_value(1); + } + + /// 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_value(1); + } + + /// Triggers the CLEAR task (set associated pin low). + #[cfg(not(feature = "_nrf51"))] + pub fn clear(&self) { + let g = regs(); + g.tasks_clr(self.ch.number()).write_value(1); + } + + /// Returns the OUT task, for use with PPI. + pub fn task_out(&self) -> Task<'d> { + let g = regs(); + Task::from_reg(g.tasks_out(self.ch.number())) + } + + /// Returns the CLR task, for use with PPI. + #[cfg(not(feature = "_nrf51"))] + pub fn task_clr(&self) -> Task<'d> { + let g = regs(); + Task::from_reg(g.tasks_clr(self.ch.number())) + } + + /// Returns the SET task, for use with PPI. + #[cfg(not(feature = "_nrf51"))] + pub fn task_set(&self) -> Task<'d> { + let g = regs(); + Task::from_reg(g.tasks_set(self.ch.number())) + } +} + +// ======================= + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub(crate) struct PortInputFuture<'a> { + pin: PeripheralRef<'a, AnyPin>, +} + +impl<'a> PortInputFuture<'a> { + fn new(pin: impl Peripheral

+ 'a) -> Self { + Self { + pin: pin.into_ref().map_into(), + } + } +} + +impl<'a> Unpin for PortInputFuture<'a> {} + +impl<'a> Drop for PortInputFuture<'a> { + fn drop(&mut self) { + self.pin.conf().modify(|w| w.set_sense(Sense::DISABLED)); + } +} + +impl<'a> Future for PortInputFuture<'a> { + type Output = (); + + fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + PORT_WAKERS[self.pin.pin_port() as usize].register(cx.waker()); + + if self.pin.conf().read().sense() == Sense::DISABLED { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +impl<'d> Input<'d> { + /// 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> Flex<'d> { + /// Wait until the pin is high. If it is already high, return immediately. + pub async fn wait_for_high(&mut self) { + self.pin.conf().modify(|w| w.set_sense(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.set_sense(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.set_sense(Sense::LOW)); + } else { + self.pin.conf().modify(|w| w.set_sense(Sense::HIGH)); + } + PortInputFuture::new(&mut self.pin).await + } +} + +// ======================= + +trait SealedChannel {} + +/// GPIOTE channel trait. +/// +/// Implemented by all GPIOTE channels. +#[allow(private_bounds)] +pub trait Channel: SealedChannel + Into + Sized + 'static { + /// Get the channel number. + fn number(&self) -> usize; + + /// Convert this channel to a type-erased `AnyChannel`. + /// + /// This allows using several channels in situations that might require + /// them to be the same type, like putting them in an array. + fn degrade(self) -> AnyChannel { + AnyChannel { + number: self.number() as u8, + } + } +} + +/// Type-erased channel. +/// +/// Obtained by calling `Channel::degrade`. +/// +/// 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, +} +impl_peripheral!(AnyChannel); +impl SealedChannel for AnyChannel {} +impl Channel for AnyChannel { + fn number(&self) -> usize { + self.number as usize + } +} + +macro_rules! impl_channel { + ($type:ident, $number:expr) => { + impl SealedChannel for peripherals::$type {} + impl Channel for peripherals::$type { + fn number(&self) -> usize { + $number as usize + } + } + + impl From for AnyChannel { + fn from(val: peripherals::$type) -> Self { + Channel::degrade(val) + } + } + }; +} + +impl_channel!(GPIOTE_CH0, 0); +impl_channel!(GPIOTE_CH1, 1); +impl_channel!(GPIOTE_CH2, 2); +impl_channel!(GPIOTE_CH3, 3); +#[cfg(not(feature = "_nrf51"))] +impl_channel!(GPIOTE_CH4, 4); +#[cfg(not(feature = "_nrf51"))] +impl_channel!(GPIOTE_CH5, 5); +#[cfg(not(feature = "_nrf51"))] +impl_channel!(GPIOTE_CH6, 6); +#[cfg(not(feature = "_nrf51"))] +impl_channel!(GPIOTE_CH7, 7); + +// ==================== + +mod eh02 { + use super::*; + + impl<'d> embedded_hal_02::digital::v2::InputPin for InputChannel<'d> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.pin.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.pin.is_low()) + } + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for InputChannel<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::InputPin for InputChannel<'d> { + fn is_high(&mut self) -> Result { + Ok(self.pin.is_high()) + } + + fn is_low(&mut self) -> Result { + Ok(self.pin.is_low()) + } +} + +impl<'d> embedded_hal_async::digital::Wait for Input<'d> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_high().await) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_low().await) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_rising_edge().await) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_falling_edge().await) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_any_edge().await) + } +} + +impl<'d> embedded_hal_async::digital::Wait for Flex<'d> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_high().await) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_low().await) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_rising_edge().await) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_falling_edge().await) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_any_edge().await) + } +} diff --git a/embassy/embassy-nrf/src/i2s.rs b/embassy/embassy-nrf/src/i2s.rs new file mode 100644 index 0000000..384a163 --- /dev/null +++ b/embassy/embassy-nrf/src/i2s.rs @@ -0,0 +1,1171 @@ +//! 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, AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::gpio::{AnyPin, Pin as GpioPin, PselBits}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::i2s::vals; +use crate::util::slice_in_ram_or; +use crate::{interrupt, pac, 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 [vals::Mckfreq] = &[ + vals::Mckfreq::_32MDIV8, + vals::Mckfreq::_32MDIV10, + vals::Mckfreq::_32MDIV11, + vals::Mckfreq::_32MDIV15, + vals::Mckfreq::_32MDIV16, + vals::Mckfreq::_32MDIV21, + vals::Mckfreq::_32MDIV23, + vals::Mckfreq::_32MDIV30, + vals::Mckfreq::_32MDIV31, + vals::Mckfreq::_32MDIV32, + vals::Mckfreq::_32MDIV42, + vals::Mckfreq::_32MDIV63, + vals::Mckfreq::_32MDIV125, + ]; + + const FREQUENCIES: &'static [u32] = &[ + 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) -> vals::Mckfreq { + 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) -> vals::Ratio { + vals::Ratio::from_bits(*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 vals::Swidth { + fn from(variant: SampleWidth) -> Self { + vals::Swidth::from_bits(variant as u8) + } +} + +/// 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 vals::Align { + fn from(variant: Align) -> Self { + match variant { + Align::Left => vals::Align::LEFT, + Align::Right => vals::Align::RIGHT, + } + } +} + +/// Frame format. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Format { + /// I2S frame format + I2S, + /// Aligned frame format + Aligned, +} + +impl From for vals::Format { + fn from(variant: Format) -> Self { + match variant { + Format::I2S => vals::Format::I2S, + Format::Aligned => vals::Format::ALIGNED, + } + } +} + +/// 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 vals::Channels { + fn from(variant: Channels) -> Self { + vals::Channels::from_bits(variant as u8) + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + 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(); + } + } +} + +/// I2S driver. +pub struct I2S<'d, T: Instance> { + i2s: PeripheralRef<'d, T>, + mck: Option>, + sck: PeripheralRef<'d, AnyPin>, + lrck: PeripheralRef<'d, AnyPin>, + sdin: Option>, + sdout: Option>, + master_clock: Option, + config: Config, +} + +impl<'d, T: Instance> I2S<'d, T> { + /// Create a new I2S in master mode + pub fn new_master( + i2s: impl Peripheral

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

+ 'd, + sck: impl Peripheral

+ 'd, + lrck: impl Peripheral

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

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

+ 'd, + lrck: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(i2s, sck, lrck); + Self { + i2s, + 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.set_mode(vals::Mode::MASTER)); + c.mcken().write(|w| w.set_mcken(true)); + c.mckfreq().write(|w| w.set_mckfreq(freq.to_register_value())); + c.ratio().write(|w| w.set_ratio(ratio.to_register_value())); + } + None => { + c.mode().write(|w| w.set_mode(vals::Mode::SLAVE)); + } + }; + + c.swidth().write(|w| w.set_swidth(self.config.sample_width.into())); + c.align().write(|w| w.set_align(self.config.align.into())); + c.format().write(|w| w.set_format(self.config.format.into())); + c.channels().write(|w| w.set_channels(self.config.channels.into())); + } + + fn select_pins(&self) { + let psel = T::regs().psel(); + psel.mck().write_value(self.mck.psel_bits()); + psel.sck().write_value(self.sck.psel_bits()); + psel.lrck().write_value(self.lrck.psel_bits()); + psel.sdin().write_value(self.sdin.psel_bits()); + psel.sdout().write_value(self.sdout.psel_bits()); + } + + fn setup_interrupt(&self) { + T::Interrupt::unpend(); + unsafe { T::Interrupt::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(); + } + + 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(pac::i2s::I2s, PhantomData); + +impl Device { + fn new() -> Self { + Self(T::regs(), PhantomData) + } + + #[inline(always)] + pub fn enable(&self) { + trace!("ENABLED"); + self.0.enable().write(|w| w.set_enable(true)); + } + + #[inline(always)] + pub fn disable(&self) { + trace!("DISABLED"); + self.0.enable().write(|w| w.set_enable(false)); + } + + #[inline(always)] + fn enable_tx(&self) { + trace!("TX ENABLED"); + self.0.config().txen().write(|w| w.set_txen(true)); + } + + #[inline(always)] + fn disable_tx(&self) { + trace!("TX DISABLED"); + self.0.config().txen().write(|w| w.set_txen(false)); + } + + #[inline(always)] + fn enable_rx(&self) { + trace!("RX ENABLED"); + self.0.config().rxen().write(|w| w.set_rxen(true)); + } + + #[inline(always)] + fn disable_rx(&self) { + trace!("RX DISABLED"); + self.0.config().rxen().write(|w| w.set_rxen(false)); + } + + #[inline(always)] + fn start(&self) { + trace!("START"); + self.0.tasks_start().write_value(1); + } + + #[inline(always)] + fn stop(&self) { + self.0.tasks_stop().write_value(1); + } + + #[inline(always)] + fn is_stopped(&self) -> bool { + self.0.events_stopped().read() != 0 + } + + #[inline(always)] + fn reset_stopped_event(&self) { + trace!("STOPPED EVENT: Reset"); + self.0.events_stopped().write_value(0); + } + + #[inline(always)] + fn disable_stopped_interrupt(&self) { + trace!("STOPPED INTERRUPT: Disabled"); + self.0.intenclr().write(|w| w.set_stopped(true)); + } + + #[inline(always)] + fn enable_stopped_interrupt(&self) { + trace!("STOPPED INTERRUPT: Enabled"); + self.0.intenset().write(|w| w.set_stopped(true)); + } + + #[inline(always)] + fn reset_tx_ptr_event(&self) { + trace!("TX PTR EVENT: Reset"); + self.0.events_txptrupd().write_value(0); + } + + #[inline(always)] + fn reset_rx_ptr_event(&self) { + trace!("RX PTR EVENT: Reset"); + self.0.events_rxptrupd().write_value(0); + } + + #[inline(always)] + fn disable_tx_ptr_interrupt(&self) { + trace!("TX PTR INTERRUPT: Disabled"); + self.0.intenclr().write(|w| w.set_txptrupd(true)); + } + + #[inline(always)] + fn disable_rx_ptr_interrupt(&self) { + trace!("RX PTR INTERRUPT: Disabled"); + self.0.intenclr().write(|w| w.set_rxptrupd(true)); + } + + #[inline(always)] + fn enable_tx_ptr_interrupt(&self) { + trace!("TX PTR INTERRUPT: Enabled"); + self.0.intenset().write(|w| w.set_txptrupd(true)); + } + + #[inline(always)] + fn enable_rx_ptr_interrupt(&self) { + trace!("RX PTR INTERRUPT: Enabled"); + self.0.intenset().write(|w| w.set_rxptrupd(true)); + } + + #[inline(always)] + fn is_tx_ptr_updated(&self) -> bool { + self.0.events_txptrupd().read() != 0 + } + + #[inline(always)] + fn is_rx_ptr_updated(&self) -> bool { + self.0.events_rxptrupd().read() != 0 + } + + #[inline] + fn update_tx(&self, buffer_ptr: *const [S]) -> Result<(), Error> { + let (ptr, maxcnt) = Self::validated_dma_parts(buffer_ptr)?; + self.0.rxtxd().maxcnt().write(|w| w.0 = maxcnt); + self.0.txd().ptr().write_value(ptr); + Ok(()) + } + + #[inline] + fn update_rx(&self, buffer_ptr: *const [S]) -> Result<(), Error> { + let (ptr, maxcnt) = Self::validated_dma_parts(buffer_ptr)?; + self.0.rxtxd().maxcnt().write(|w| w.0 = maxcnt); + self.0.rxd().ptr().write_value(ptr); + Ok(()) + } + + fn validated_dma_parts(buffer_ptr: *const [S]) -> Result<(u32, u32), Error> { + let ptr = buffer_ptr as *const S as u32; + let bytes_len = buffer_ptr.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] + } +} + +/// Peripheral static state +pub(crate) struct State { + started: AtomicBool, + rx_waker: AtomicWaker, + tx_waker: AtomicWaker, + stop_waker: AtomicWaker, +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + started: AtomicBool::new(false), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + stop_waker: AtomicWaker::new(), + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::i2s::I2s; + fn state() -> &'static State; +} + +/// I2S peripheral instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_i2s { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::i2s::SealedInstance for peripherals::$type { + fn regs() -> pac::i2s::I2s { + pac::$pac_type + } + fn state() -> &'static crate::i2s::State { + static STATE: crate::i2s::State = crate::i2s::State::new(); + &STATE + } + } + impl crate::i2s::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy/embassy-nrf/src/lib.rs b/embassy/embassy-nrf/src/lib.rs new file mode 100644 index 0000000..ec5e9f8 --- /dev/null +++ b/embassy/embassy-nrf/src/lib.rs @@ -0,0 +1,738 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![cfg_attr( + docsrs, + doc = "

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

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

\n\n" +)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +#[cfg(not(any( + feature = "_nrf51", + feature = "nrf52805", + feature = "nrf52810", + feature = "nrf52811", + feature = "nrf52820", + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", + feature = "nrf5340-app-s", + feature = "nrf5340-app-ns", + feature = "nrf5340-net", + feature = "nrf54l15-app-s", + feature = "nrf54l15-app-ns", + feature = "nrf9160-s", + feature = "nrf9160-ns", + feature = "nrf9120-s", + feature = "nrf9120-ns", + feature = "nrf9151-s", + feature = "nrf9151-ns", + feature = "nrf9161-s", + feature = "nrf9161-ns", +)))] +compile_error!( + "No chip feature activated. You must activate exactly one of the following features: + nrf51, + nrf52805, + nrf52810, + nrf52811, + nrf52820, + nrf52832, + nrf52833, + nrf52840, + nrf5340-app-s, + nrf5340-app-ns, + nrf5340-net, + nrf54l15-app-s, + nrf54l15-app-ns, + nrf9160-s, + nrf9160-ns, + nrf9120-s, + nrf9120-ns, + nrf9151-s, + nrf9151-ns, + nrf9161-s, + nrf9161-ns, + " +); + +#[cfg(all(feature = "reset-pin-as-gpio", not(feature = "_nrf52")))] +compile_error!("feature `reset-pin-as-gpio` is only valid for nRF52 series chips."); + +#[cfg(all(feature = "nfc-pins-as-gpio", not(any(feature = "_nrf52", feature = "_nrf5340-app"))))] +compile_error!("feature `nfc-pins-as-gpio` is only valid for nRF52, or nRF53's application core."); + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; +pub(crate) mod util; + +#[cfg(feature = "_time-driver")] +mod time_driver; + +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] +pub mod buffered_uarte; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] +pub mod egu; +pub mod gpio; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(feature = "gpiote")] +pub mod gpiote; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] +pub mod i2s; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(any( + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-app" +))] +pub mod nfct; +#[cfg(not(feature = "_nrf54l"))] // TODO +pub mod nvmc; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(any( + feature = "nrf52810", + feature = "nrf52811", + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-app", + feature = "_nrf91", +))] +pub mod pdm; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(any(feature = "nrf52840", feature = "nrf9160-s", feature = "nrf9160-ns"))] +pub mod power; +#[cfg(not(feature = "_nrf54l"))] // TODO +pub mod ppi; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(any( + feature = "_nrf51", + feature = "nrf52805", + feature = "nrf52820", + feature = "_nrf5340-net" +)))] +pub mod pwm; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(any(feature = "_nrf51", feature = "_nrf91", feature = "_nrf5340-net")))] +pub mod qdec; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(any(feature = "nrf52840", feature = "_nrf5340-app"))] +pub mod qspi; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(any(feature = "_nrf91", feature = "_nrf5340-app")))] +pub mod radio; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(feature = "_nrf5340")] +pub mod reset; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(any(feature = "_nrf5340-app", feature = "_nrf91")))] +pub mod rng; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(any(feature = "_nrf51", feature = "nrf52820", feature = "_nrf5340-net")))] +pub mod saadc; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] +pub mod spim; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] +pub mod spis; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] +pub mod temp; +#[cfg(not(feature = "_nrf54l"))] // TODO +pub mod timer; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] +pub mod twim; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] +pub mod twis; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] +pub mod uarte; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(any( + feature = "_nrf5340-app", + feature = "nrf52820", + feature = "nrf52833", + feature = "nrf52840" +))] +pub mod usb; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf5340"))] +pub mod wdt; + +// This mod MUST go last, so that it sees all the `impl_foo!` macros +#[cfg_attr(feature = "_nrf51", path = "chips/nrf51.rs")] +#[cfg_attr(feature = "nrf52805", path = "chips/nrf52805.rs")] +#[cfg_attr(feature = "nrf52810", path = "chips/nrf52810.rs")] +#[cfg_attr(feature = "nrf52811", path = "chips/nrf52811.rs")] +#[cfg_attr(feature = "nrf52820", path = "chips/nrf52820.rs")] +#[cfg_attr(feature = "nrf52832", path = "chips/nrf52832.rs")] +#[cfg_attr(feature = "nrf52833", path = "chips/nrf52833.rs")] +#[cfg_attr(feature = "nrf52840", path = "chips/nrf52840.rs")] +#[cfg_attr(feature = "_nrf5340-app", path = "chips/nrf5340_app.rs")] +#[cfg_attr(feature = "_nrf5340-net", path = "chips/nrf5340_net.rs")] +#[cfg_attr(feature = "_nrf54l15-app", path = "chips/nrf54l15_app.rs")] +#[cfg_attr(feature = "_nrf9160", path = "chips/nrf9160.rs")] +#[cfg_attr(feature = "_nrf9120", path = "chips/nrf9120.rs")] +mod chip; + +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +/// +/// Example of how to bind one interrupt: +/// +/// ```rust,ignore +/// use embassy_nrf::{bind_interrupts, spim, peripherals}; +/// +/// bind_interrupts!(struct Irqs { +/// SPIM3 => spim::InterruptHandler; +/// }); +/// ``` +/// +/// Example of how to bind multiple interrupts in a single macro invocation: +/// +/// ```rust,ignore +/// use embassy_nrf::{bind_interrupts, spim, twim, peripherals}; +/// +/// bind_interrupts!(struct Irqs { +/// SPIM3 => spim::InterruptHandler; +/// TWISPI0 => twim::InterruptHandler; +/// }); +/// ``` + +// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + )* + } + + $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@inner + $( + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + ); + )* + }; + (@inner $($t:tt)*) => { + $($t)* + } +} + +// Reexports + +#[cfg(feature = "unstable-pac")] +pub use chip::pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use chip::pac; +pub use chip::{peripherals, Peripherals, EASY_DMA_SIZE}; +pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; + +pub use crate::chip::interrupt; +#[cfg(feature = "rt")] +pub use crate::pac::NVIC_PRIO_BITS; + +pub mod config { + //! Configuration options used when initializing the HAL. + + /// High frequency clock source. + pub enum HfclkSource { + /// Internal source + Internal, + /// External source from xtal. + ExternalXtal, + } + + /// Low frequency clock source + pub enum LfclkSource { + /// Internal RC oscillator + InternalRC, + /// Synthesized from the high frequency clock source. + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + Synthesized, + /// External source from xtal. + ExternalXtal, + /// External source from xtal with low swing applied. + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91", feature = "_nrf54l")))] + ExternalLowSwing, + /// External source from xtal with full swing applied. + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91", feature = "_nrf54l")))] + ExternalFullSwing, + } + + /// SWD access port protection setting. + #[non_exhaustive] + pub enum Debug { + /// Debugging is allowed (APPROTECT is disabled). Default. + Allowed, + /// Debugging is not allowed (APPROTECT is enabled). + Disallowed, + /// APPROTECT is not configured (neither to enable it or disable it). + /// This can be useful if you're already doing it by other means and + /// you don't want embassy-nrf to touch UICR. + NotConfigured, + } + + /// Settings for enabling the built in DCDC converters. + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + pub struct DcdcConfig { + /// Config for the first stage DCDC (VDDH -> VDD), if disabled LDO will be used. + #[cfg(feature = "nrf52840")] + pub reg0: bool, + /// Configure the voltage of the first stage DCDC. It is stored in non-volatile memory (UICR.REGOUT0 register); pass None to not touch it. + #[cfg(feature = "nrf52840")] + pub reg0_voltage: Option, + /// Config for the second stage DCDC (VDD -> DEC4), if disabled LDO will be used. + pub reg1: bool, + } + + /// Output voltage setting for REG0 regulator stage. + #[cfg(feature = "nrf52840")] + pub enum Reg0Voltage { + /// 1.8 V + _1V8 = 0, + /// 2.1 V + _2V1 = 1, + /// 2.4 V + _2V4 = 2, + /// 2.7 V + _2V7 = 3, + /// 3.0 V + _3V0 = 4, + /// 3.3 V + _3v3 = 5, + //ERASED = 7, means 1.8V + } + + /// Settings for enabling the built in DCDC converters. + #[cfg(feature = "_nrf5340-app")] + pub struct DcdcConfig { + /// Config for the high voltage stage, if disabled LDO will be used. + pub regh: bool, + /// Config for the main rail, if disabled LDO will be used. + pub regmain: bool, + /// Config for the radio rail, if disabled LDO will be used. + pub regradio: bool, + } + + /// Settings for enabling the built in DCDC converter. + #[cfg(feature = "_nrf91")] + pub struct DcdcConfig { + /// Config for the main rail, if disabled LDO will be used. + pub regmain: bool, + } + + /// Configuration for peripherals. Default configuration should work on any nRF chip. + #[non_exhaustive] + pub struct Config { + /// High frequency clock source. + pub hfclk_source: HfclkSource, + /// Low frequency clock source. + pub lfclk_source: LfclkSource, + #[cfg(not(any(feature = "_nrf5340-net", feature = "_nrf54l")))] + /// DCDC configuration. + pub dcdc: DcdcConfig, + /// GPIOTE interrupt priority. Should be lower priority than softdevice if used. + #[cfg(feature = "gpiote")] + pub gpiote_interrupt_priority: crate::interrupt::Priority, + /// Time driver interrupt priority. Should be lower priority than softdevice if used. + #[cfg(feature = "_time-driver")] + pub time_interrupt_priority: crate::interrupt::Priority, + /// Enable or disable the debug port. + pub debug: Debug, + } + + impl Default for Config { + fn default() -> Self { + Self { + // There are hobby nrf52 boards out there without external XTALs... + // Default everything to internal so it Just Works. User can enable external + // xtals if they know they have them. + hfclk_source: HfclkSource::Internal, + lfclk_source: LfclkSource::InternalRC, + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91", feature = "_nrf54l")))] + dcdc: DcdcConfig { + #[cfg(feature = "nrf52840")] + reg0: false, + #[cfg(feature = "nrf52840")] + reg0_voltage: None, + reg1: false, + }, + #[cfg(feature = "_nrf5340-app")] + dcdc: DcdcConfig { + regh: false, + regmain: false, + regradio: false, + }, + #[cfg(feature = "_nrf91")] + dcdc: DcdcConfig { regmain: false }, + #[cfg(feature = "gpiote")] + gpiote_interrupt_priority: crate::interrupt::Priority::P0, + #[cfg(feature = "_time-driver")] + time_interrupt_priority: crate::interrupt::Priority::P0, + + // In NS mode, default to NotConfigured, assuming the S firmware will do it. + #[cfg(feature = "_ns")] + debug: Debug::NotConfigured, + #[cfg(not(feature = "_ns"))] + debug: Debug::Allowed, + } + } + } +} + +#[cfg(feature = "_nrf91")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32; + pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF802C as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; +} + +#[cfg(feature = "_nrf5340-app")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32; + pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF801C as *mut u32; + pub const UICR_NFCPINS: *mut u32 = 0x00FF8028 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x50FA50FA; +} + +#[cfg(feature = "_nrf5340-net")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x01FF8000 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x50FA50FA; +} + +#[cfg(feature = "_nrf52")] +#[allow(unused)] +mod consts { + pub const UICR_PSELRESET1: *mut u32 = 0x10001200 as *mut u32; + pub const UICR_PSELRESET2: *mut u32 = 0x10001204 as *mut u32; + pub const UICR_NFCPINS: *mut u32 = 0x1000120C as *mut u32; + pub const UICR_APPROTECT: *mut u32 = 0x10001208 as *mut u32; + pub const UICR_REGOUT0: *mut u32 = 0x10001304 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x0000_005a; +} + +#[cfg(not(any(feature = "_nrf51", feature = "_nrf54l")))] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum WriteResult { + /// Word was written successfully, needs reset. + Written, + /// Word was already set to the value we wanted to write, nothing was done. + Noop, + /// Word is already set to something else, we couldn't write the desired value. + Failed, +} + +#[cfg(not(any(feature = "_nrf51", feature = "_nrf54l")))] +unsafe fn uicr_write(address: *mut u32, value: u32) -> WriteResult { + uicr_write_masked(address, value, 0xFFFF_FFFF) +} + +#[cfg(not(any(feature = "_nrf51", feature = "_nrf54l")))] +unsafe fn uicr_write_masked(address: *mut u32, value: u32, mask: u32) -> WriteResult { + let curr_val = address.read_volatile(); + if curr_val & mask == value & mask { + return WriteResult::Noop; + } + + // We can only change `1` bits to `0` bits. + if curr_val & value & mask != value & mask { + return WriteResult::Failed; + } + + let nvmc = pac::NVMC; + nvmc.config().write(|w| w.set_wen(pac::nvmc::vals::Wen::WEN)); + while !nvmc.ready().read().ready() {} + address.write_volatile(value | !mask); + while !nvmc.ready().read().ready() {} + nvmc.config().write(|_| {}); + while !nvmc.ready().read().ready() {} + + WriteResult::Written +} + +/// Initialize the `embassy-nrf` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once at startup, otherwise it panics. +pub fn init(config: config::Config) -> Peripherals { + // Do this first, so that it panics if user is calling `init` a second time + // before doing anything important. + let peripherals = Peripherals::take(); + + #[allow(unused_mut)] + let mut needs_reset = false; + + // Setup debug protection. + #[cfg(not(feature = "_nrf54l"))] // TODO + #[cfg(not(feature = "_nrf51"))] + match config.debug { + config::Debug::Allowed => { + #[cfg(feature = "_nrf52")] + unsafe { + let variant = (0x1000_0104 as *mut u32).read_volatile(); + // Get the letter for the build code (b'A' .. b'F') + let build_code = (variant >> 8) as u8; + + if build_code >= chip::APPROTECT_MIN_BUILD_CODE { + // Chips with a certain chip type-specific build code or higher have an + // improved APPROTECT ("hardware and software controlled access port protection") + // which needs explicit action by the firmware to keep it unlocked + // See https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/working-with-the-nrf52-series-improved-approtect + + // UICR.APPROTECT = SwDisabled + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; + // APPROTECT.DISABLE = SwDisabled + (0x4000_0558 as *mut u32).write_volatile(consts::APPROTECT_DISABLED); + } else { + // nothing to do on older chips, debug is allowed by default. + } + } + + #[cfg(feature = "_nrf5340")] + unsafe { + let p = pac::CTRLAP; + + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; + p.approtect().disable().write_value(consts::APPROTECT_DISABLED); + + #[cfg(feature = "_nrf5340-app")] + { + let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; + p.secureapprotect().disable().write_value(consts::APPROTECT_DISABLED); + } + } + + // nothing to do on the nrf9160, debug is allowed by default. + } + config::Debug::Disallowed => unsafe { + // UICR.APPROTECT = Enabled + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_ENABLED); + needs_reset |= res == WriteResult::Written; + #[cfg(any(feature = "_nrf5340-app", feature = "_nrf91"))] + { + let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_ENABLED); + needs_reset |= res == WriteResult::Written; + } + }, + config::Debug::NotConfigured => {} + } + + #[cfg(feature = "_nrf52")] + unsafe { + let value = if cfg!(feature = "reset-pin-as-gpio") { + !0 + } else { + chip::RESET_PIN + }; + let res1 = uicr_write(consts::UICR_PSELRESET1, value); + let res2 = uicr_write(consts::UICR_PSELRESET2, value); + needs_reset |= res1 == WriteResult::Written || res2 == WriteResult::Written; + if res1 == WriteResult::Failed || res2 == WriteResult::Failed { + #[cfg(not(feature = "reset-pin-as-gpio"))] + warn!( + "You have requested enabling chip reset functionality on the reset pin, by not enabling the Cargo feature `reset-pin-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + #[cfg(feature = "reset-pin-as-gpio")] + warn!( + "You have requested using the reset pin as GPIO, by enabling the Cargo feature `reset-pin-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + } + } + + #[cfg(any(feature = "_nrf52", feature = "_nrf5340-app"))] + unsafe { + let value = if cfg!(feature = "nfc-pins-as-gpio") { 0 } else { 1 }; + let res = uicr_write_masked(consts::UICR_NFCPINS, value, 1); + needs_reset |= res == WriteResult::Written; + if res == WriteResult::Failed { + // with nfc-pins-as-gpio, this can never fail because we're writing all zero bits. + #[cfg(not(feature = "nfc-pins-as-gpio"))] + warn!( + "You have requested to use P0.09 and P0.10 pins for NFC, by not enabling the Cargo feature `nfc-pins-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + } + } + + #[cfg(feature = "nrf52840")] + unsafe { + if let Some(value) = config.dcdc.reg0_voltage { + let value = value as u32; + let res = uicr_write_masked(consts::UICR_REGOUT0, value, 0b00000000_00000000_00000000_00000111); + needs_reset |= res == WriteResult::Written; + if res == WriteResult::Failed { + warn!( + "Failed to set regulator voltage, as UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + } + } + } + + if needs_reset { + cortex_m::peripheral::SCB::sys_reset(); + } + + let r = pac::CLOCK; + + // Start HFCLK. + match config.hfclk_source { + config::HfclkSource::Internal => {} + config::HfclkSource::ExternalXtal => { + #[cfg(feature = "_nrf54l")] + { + r.events_xostarted().write_value(0); + r.tasks_xostart().write_value(1); + while r.events_xostarted().read() == 0 {} + } + + #[cfg(not(feature = "_nrf54l"))] + { + // Datasheet says this is likely to take 0.36ms + r.events_hfclkstarted().write_value(0); + r.tasks_hfclkstart().write_value(1); + while r.events_hfclkstarted().read() == 0 {} + } + } + } + + // Configure LFCLK. + #[cfg(not(any(feature = "_nrf51", feature = "_nrf5340", feature = "_nrf91", feature = "_nrf54l")))] + match config.lfclk_source { + config::LfclkSource::InternalRC => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::RC)), + config::LfclkSource::Synthesized => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::SYNTH)), + config::LfclkSource::ExternalXtal => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::XTAL)), + config::LfclkSource::ExternalLowSwing => r.lfclksrc().write(|w| { + w.set_src(pac::clock::vals::Lfclksrc::XTAL); + w.set_external(true); + w.set_bypass(false); + }), + config::LfclkSource::ExternalFullSwing => r.lfclksrc().write(|w| { + w.set_src(pac::clock::vals::Lfclksrc::XTAL); + w.set_external(true); + w.set_bypass(true); + }), + } + #[cfg(feature = "_nrf91")] + match config.lfclk_source { + config::LfclkSource::InternalRC => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFRC)), + config::LfclkSource::ExternalXtal => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFXO)), + } + #[cfg(feature = "_nrf54l")] + match config.lfclk_source { + config::LfclkSource::InternalRC => r.lfclk().src().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFRC)), + config::LfclkSource::Synthesized => r.lfclk().src().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFSYNT)), + config::LfclkSource::ExternalXtal => r.lfclk().src().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFXO)), + } + + // Start LFCLK. + // Datasheet says this could take 100us from synth source + // 600us from rc source, 0.25s from an external source. + r.events_lfclkstarted().write_value(0); + r.tasks_lfclkstart().write_value(1); + while r.events_lfclkstarted().read() == 0 {} + + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91", feature = "_nrf54l")))] + { + // Setup DCDCs. + #[cfg(feature = "nrf52840")] + if config.dcdc.reg0 { + pac::POWER.dcdcen0().write(|w| w.set_dcdcen(true)); + } + if config.dcdc.reg1 { + pac::POWER.dcdcen().write(|w| w.set_dcdcen(true)); + } + } + #[cfg(feature = "_nrf91")] + { + // Setup DCDC. + if config.dcdc.regmain { + pac::REGULATORS.dcdcen().write(|w| w.set_dcdcen(true)); + } + } + #[cfg(feature = "_nrf5340-app")] + { + // Setup DCDC. + let reg = pac::REGULATORS; + if config.dcdc.regh { + reg.vregh().dcdcen().write(|w| w.set_dcdcen(true)); + } + if config.dcdc.regmain { + reg.vregmain().dcdcen().write(|w| w.set_dcdcen(true)); + } + if config.dcdc.regradio { + reg.vregradio().dcdcen().write(|w| w.set_dcdcen(true)); + } + } + + // Init GPIOTE + #[cfg(not(feature = "_nrf54l"))] // TODO + #[cfg(feature = "gpiote")] + gpiote::init(config.gpiote_interrupt_priority); + + // init RTC time driver + #[cfg(feature = "_time-driver")] + time_driver::init(config.time_interrupt_priority); + + // Disable UARTE (enabled by default for some reason) + #[cfg(feature = "_nrf91")] + { + use pac::uarte::vals::Enable; + pac::UARTE0.enable().write(|w| w.set_enable(Enable::DISABLED)); + pac::UARTE1.enable().write(|w| w.set_enable(Enable::DISABLED)); + } + + peripherals +} diff --git a/embassy/embassy-nrf/src/nfct.rs b/embassy/embassy-nrf/src/nfct.rs new file mode 100644 index 0000000..8b4b6df --- /dev/null +++ b/embassy/embassy-nrf/src/nfct.rs @@ -0,0 +1,428 @@ +//! NFC tag emulator driver. +//! +//! This driver implements support for emulating an ISO14443-3 card. Anticollision and selection +//! are handled automatically in hardware, then the driver lets you receive and reply to +//! raw ISO14443-3 frames in software. +//! +//! Higher layers such as ISO14443-4 aka ISO-DEP and ISO7816 must be handled on top +//! in software. + +#![macro_use] + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +pub use vals::{Bitframesdd as SddPat, Discardmode as DiscardMode}; + +use crate::interrupt::InterruptExt; +use crate::pac::nfct::vals; +use crate::pac::NFCT; +use crate::peripherals::NFCT; +use crate::util::slice_in_ram; +use crate::{interrupt, pac, Peripheral}; + +/// NFCID1 (aka UID) of different sizes. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum NfcId { + /// 4-byte UID. + SingleSize([u8; 4]), + /// 7-byte UID. + DoubleSize([u8; 7]), + /// 10-byte UID. + TripleSize([u8; 10]), +} + +/// The protocol field to be sent in the `SEL_RES` response byte (b6-b7). +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum SelResProtocol { + /// Configured for Type 2 Tag platform. + #[default] + Type2 = 0, + /// Configured for Type 4A Tag platform, compliant with ISO/IEC_14443. + Type4A = 1, + /// Configured for the NFC-DEP Protocol. + NfcDep = 2, + /// Configured for the NFC-DEP Protocol and Type 4A Tag platform. + NfcDepAndType4A = 3, +} + +/// Config for the `NFCT` peripheral driver. +#[derive(Clone)] +pub struct Config { + /// NFCID1 to use during autocollision. + pub nfcid1: NfcId, + /// SDD pattern to be sent in `SENS_RES`. + pub sdd_pat: SddPat, + /// Platform config to be sent in `SEL_RES`. + pub plat_conf: u8, + /// Protocol to be sent in the `SEL_RES` response. + pub protocol: SelResProtocol, +} + +/// Interrupt handler. +pub struct InterruptHandler { + _private: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + trace!("irq"); + pac::NFCT.inten().write(|w| w.0 = 0); + WAKER.wake(); + } +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// NFC error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Rx Error received while waiting for frame + RxError, + /// Rx buffer was overrun, increase your buffer size to resolve this + RxOverrun, + /// Lost field. + Deactivated, + /// Collision + Collision, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, +} + +/// NFC tag emulator driver. +pub struct NfcT<'d> { + _p: PeripheralRef<'d, NFCT>, + rx_buf: [u8; 256], + tx_buf: [u8; 256], +} + +impl<'d> NfcT<'d> { + /// Create an Nfc Tag driver + pub fn new( + _p: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, + config: &Config, + ) -> Self { + into_ref!(_p); + + let r = pac::NFCT; + + unsafe { + let reset = (r.as_ptr() as *mut u32).add(0xFFC / 4); + reset.write_volatile(0); + reset.read_volatile(); + reset.write_volatile(1); + } + + let nfcid_size = match &config.nfcid1 { + NfcId::SingleSize(bytes) => { + r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*bytes)); + + vals::Nfcidsize::NFCID1SINGLE + } + NfcId::DoubleSize(bytes) => { + let (bytes, chunk) = bytes.split_last_chunk::<4>().unwrap(); + r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*chunk)); + + let mut chunk = [0u8; 4]; + chunk[1..].copy_from_slice(bytes); + r.nfcid1_2nd_last().write(|w| w.0 = u32::from_be_bytes(chunk)); + + vals::Nfcidsize::NFCID1DOUBLE + } + NfcId::TripleSize(bytes) => { + let (bytes, chunk) = bytes.split_last_chunk::<4>().unwrap(); + r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*chunk)); + + let (bytes, chunk2) = bytes.split_last_chunk::<3>().unwrap(); + let mut chunk = [0u8; 4]; + chunk[1..].copy_from_slice(chunk2); + r.nfcid1_2nd_last().write(|w| w.0 = u32::from_be_bytes(chunk)); + + let mut chunk = [0u8; 4]; + chunk[1..].copy_from_slice(bytes); + r.nfcid1_3rd_last().write(|w| w.0 = u32::from_be_bytes(chunk)); + + vals::Nfcidsize::NFCID1TRIPLE + } + }; + + r.sensres().write(|w| { + w.set_nfcidsize(nfcid_size); + w.set_bitframesdd(config.sdd_pat); + w.set_platfconfig(config.plat_conf & 0xF); + }); + + r.selres().write(|w| { + w.set_protocol(config.protocol as u8); + }); + + // errata + #[cfg(feature = "nrf52832")] + unsafe { + // Errata 57 nrf52832 only + //(0x40005610 as *mut u32).write_volatile(0x00000005); + //(0x40005688 as *mut u32).write_volatile(0x00000001); + //(0x40005618 as *mut u32).write_volatile(0x00000000); + //(0x40005614 as *mut u32).write_volatile(0x0000003F); + + // Errata 98 + (0x4000568C as *mut u32).write_volatile(0x00038148); + } + + r.inten().write(|w| w.0 = 0); + + interrupt::NFCT.unpend(); + unsafe { interrupt::NFCT.enable() }; + + // clear all shorts + r.shorts().write(|_| {}); + + let res = Self { + _p, + tx_buf: [0u8; 256], + rx_buf: [0u8; 256], + }; + + assert!(slice_in_ram(&res.tx_buf), "TX Buf not in ram"); + assert!(slice_in_ram(&res.rx_buf), "RX Buf not in ram"); + + res + } + + /// Wait for field on and select. + /// + /// This waits for the field to become on, and then for a reader to select us. The ISO14443-3 + /// sense, anticollision and select procedure is handled entirely in hardware. + /// + /// When this returns, we have successfully been selected as a card. You must then + /// loop calling [`receive`](Self::receive) and responding with [`transmit`](Self::transmit). + pub async fn activate(&mut self) { + let r = pac::NFCT; + loop { + r.events_fieldlost().write_value(0); + r.events_fielddetected().write_value(0); + r.tasks_sense().write_value(1); + + // enable autocoll + #[cfg(not(feature = "nrf52832"))] + r.autocolresconfig().write(|w| w.0 = 0b10); + + // framedelaymax=4096 is needed to make it work with phones from + // a certain company named after some fruit. + r.framedelaymin().write(|w| w.set_framedelaymin(1152)); + r.framedelaymax().write(|w| w.set_framedelaymax(4096)); + r.framedelaymode().write(|w| { + w.set_framedelaymode(vals::Framedelaymode::WINDOW_GRID); + }); + + info!("waiting for field"); + poll_fn(|cx| { + WAKER.register(cx.waker()); + + if r.events_fielddetected().read() != 0 { + r.events_fielddetected().write_value(0); + return Poll::Ready(()); + } + + r.inten().write(|w| { + w.set_fielddetected(true); + }); + Poll::Pending + }) + .await; + + #[cfg(feature = "time")] + embassy_time::Timer::after_millis(1).await; // workaround errata 190 + + r.events_selected().write_value(0); + r.tasks_activate().write_value(1); + + trace!("Waiting to be selected"); + poll_fn(|cx| { + let r = pac::NFCT; + + WAKER.register(cx.waker()); + + if r.events_selected().read() != 0 || r.events_fieldlost().read() != 0 { + return Poll::Ready(()); + } + + r.inten().write(|w| { + w.set_selected(true); + w.set_fieldlost(true); + }); + Poll::Pending + }) + .await; + if r.events_fieldlost().read() != 0 { + continue; + } + + // disable autocoll + #[cfg(not(feature = "nrf52832"))] + r.autocolresconfig().write(|w| w.0 = 0b11u32); + + // once anticoll is done, set framedelaymax to the maximum possible. + // this gives the firmware as much time as possible to reply. + // higher layer still has to reply faster than the FWT it specifies in the iso14443-4 ATS, + // but that's not our concern. + // + // nrf52832 field is 16bit instead of 20bit. this seems to force a too short timeout, maybe it's a SVD bug? + #[cfg(not(feature = "nrf52832"))] + r.framedelaymax().write(|w| w.set_framedelaymax(0xF_FFFF)); + #[cfg(feature = "nrf52832")] + r.framedelaymax().write(|w| w.set_framedelaymax(0xFFFF)); + + return; + } + } + + /// Transmit an ISO14443-3 frame to the reader. + /// + /// You must call this only after receiving a frame with [`receive`](Self::receive), + /// and only once. Higher-layer protocols usually define timeouts, so calling this + /// too late can cause things to fail. + /// + /// This will fail with [`Error::Deactivated`] if we have been deselected due to either + /// the field being switched off or due to the ISO14443 state machine. When this happens, + /// you must stop calling [`receive`](Self::receive) and [`transmit`](Self::transmit), reset + /// all protocol state, and go back to calling [`activate`](Self::activate). + pub async fn transmit(&mut self, buf: &[u8]) -> Result<(), Error> { + let r = pac::NFCT; + + //Setup DMA + self.tx_buf[..buf.len()].copy_from_slice(buf); + r.packetptr().write_value(self.tx_buf.as_ptr() as u32); + r.maxlen().write(|w| w.0 = buf.len() as _); + + // Set packet length + r.txd().amount().write(|w| { + w.set_txdatabits(0); + w.set_txdatabytes(buf.len() as _); + }); + + r.txd().frameconfig().write(|w| { + w.set_crcmodetx(true); + w.set_discardmode(DiscardMode::DISCARD_END); + w.set_parity(true); + w.set_sof(true); + }); + + r.events_error().write_value(0); + r.events_txframeend().write_value(0); + r.errorstatus().write(|w| w.0 = 0xffff_ffff); + + // Start starttx task + compiler_fence(Ordering::SeqCst); + r.tasks_starttx().write_value(1); + + poll_fn(move |cx| { + trace!("polling tx"); + let r = pac::NFCT; + WAKER.register(cx.waker()); + + if r.events_fieldlost().read() != 0 { + return Poll::Ready(Err(Error::Deactivated)); + } + + if r.events_txframeend().read() != 0 { + trace!("Txframend hit, should be finished trasmitting"); + return Poll::Ready(Ok(())); + } + + if r.events_error().read() != 0 { + trace!("Got error?"); + let errs = r.errorstatus().read(); + r.errorstatus().write(|w| w.0 = 0xFFFF_FFFF); + trace!("errors: {:08x}", errs.0); + r.events_error().write_value(0); + return Poll::Ready(Err(Error::RxError)); + } + + r.inten().write(|w| { + w.set_txframeend(true); + w.set_error(true); + w.set_fieldlost(true); + }); + + Poll::Pending + }) + .await + } + + /// Receive an ISO14443-3 frame from the reader. + /// + /// After calling this, you must send back a response with [`transmit`](Self::transmit), + /// and only once. Higher-layer protocols usually define timeouts, so calling this + /// too late can cause things to fail. + pub async fn receive(&mut self, buf: &mut [u8]) -> Result { + let r = pac::NFCT; + + r.rxd().frameconfig().write(|w| { + w.set_crcmoderx(true); + w.set_parity(true); + w.set_sof(true); + }); + + //Setup DMA + r.packetptr().write_value(self.rx_buf.as_mut_ptr() as u32); + r.maxlen().write(|w| w.0 = self.rx_buf.len() as _); + + // Reset and enable the end event + r.events_rxframeend().write_value(0); + r.events_rxerror().write_value(0); + + // Start enablerxdata only after configs are finished writing + compiler_fence(Ordering::SeqCst); + r.tasks_enablerxdata().write_value(1); + + poll_fn(move |cx| { + trace!("polling rx"); + let r = pac::NFCT; + WAKER.register(cx.waker()); + + if r.events_fieldlost().read() != 0 { + return Poll::Ready(Err(Error::Deactivated)); + } + + if r.events_rxerror().read() != 0 { + trace!("RXerror got in recv frame, should be back in idle state"); + r.events_rxerror().write_value(0); + let errs = r.framestatus().rx().read(); + r.framestatus().rx().write(|w| w.0 = 0xFFFF_FFFF); + trace!("errors: {:08x}", errs.0); + return Poll::Ready(Err(Error::RxError)); + } + + if r.events_rxframeend().read() != 0 { + trace!("RX Frameend got in recv frame, should have data"); + r.events_rxframeend().write_value(0); + return Poll::Ready(Ok(())); + } + + r.inten().write(|w| { + w.set_rxframeend(true); + w.set_rxerror(true); + w.set_fieldlost(true); + }); + + Poll::Pending + }) + .await?; + + let n = r.rxd().amount().read().rxdatabytes() as usize - 2; + buf[..n].copy_from_slice(&self.rx_buf[..n]); + Ok(n) + } +} + +/// Wake the system if there if an NFC field close to the antenna +pub fn wake_on_nfc_sense() { + NFCT.tasks_sense().write_value(0x01); +} diff --git a/embassy/embassy-nrf/src/nvmc.rs b/embassy/embassy-nrf/src/nvmc.rs new file mode 100644 index 0000000..6973b48 --- /dev/null +++ b/embassy/embassy-nrf/src/nvmc.rs @@ -0,0 +1,187 @@ +//! Non-Volatile Memory Controller (NVMC, AKA internal flash) driver. + +use core::{ptr, slice}; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embedded_storage::nor_flash::{ + ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, +}; + +use crate::pac::nvmc::vals; +use crate::peripherals::NVMC; +use crate::{pac, Peripheral}; + +#[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; + +/// Error type for NVMC operations. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Operation using a location not in flash. + OutOfBounds, + /// Unaligned operation or using unaligned buffers. + Unaligned, +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + match self { + Self::OutOfBounds => NorFlashErrorKind::OutOfBounds, + Self::Unaligned => NorFlashErrorKind::NotAligned, + } + } +} + +/// Non-Volatile Memory Controller (NVMC) that implements the `embedded-storage` traits. +pub struct Nvmc<'d> { + _p: PeripheralRef<'d, NVMC>, +} + +impl<'d> Nvmc<'d> { + /// Create Nvmc driver. + pub fn new(_p: impl Peripheral

+ 'd) -> Self { + into_ref!(_p); + Self { _p } + } + + fn regs() -> pac::nvmc::Nvmc { + pac::NVMC + } + + fn wait_ready(&mut self) { + let p = Self::regs(); + while !p.ready().read().ready() {} + } + + #[cfg(not(any(feature = "_nrf91", feature = "_nrf5340")))] + fn wait_ready_write(&mut self) { + self.wait_ready(); + } + + #[cfg(any(feature = "_nrf91", feature = "_nrf5340"))] + fn wait_ready_write(&mut self) { + let p = Self::regs(); + while !p.readynext().read().readynext() {} + } + + #[cfg(not(any(feature = "_nrf91", feature = "_nrf5340")))] + fn erase_page(&mut self, page_addr: u32) { + Self::regs().erasepage().write_value(page_addr); + } + + #[cfg(any(feature = "_nrf91", 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(feature = "_ns"))] + Self::regs().config().write(|w| w.set_wen(vals::Wen::EEN)); + #[cfg(feature = "_ns")] + Self::regs().configns().write(|w| w.set_wen(vals::ConfignsWen::EEN)); + } + + fn enable_read(&self) { + #[cfg(not(feature = "_ns"))] + Self::regs().config().write(|w| w.set_wen(vals::Wen::REN)); + #[cfg(feature = "_ns")] + Self::regs().configns().write(|w| w.set_wen(vals::ConfignsWen::REN)); + } + + fn enable_write(&self) { + #[cfg(not(feature = "_ns"))] + Self::regs().config().write(|w| w.set_wen(vals::Wen::WEN)); + #[cfg(feature = "_ns")] + Self::regs().configns().write(|w| w.set_wen(vals::ConfignsWen::WEN)); + } +} + +impl<'d> MultiwriteNorFlash for Nvmc<'d> {} + +impl<'d> ErrorType for Nvmc<'d> { + type Error = Error; +} + +impl<'d> ReadNorFlash for Nvmc<'d> { + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + if offset as usize >= FLASH_SIZE || offset as usize + bytes.len() > FLASH_SIZE { + return Err(Error::OutOfBounds); + } + + let flash_data = unsafe { slice::from_raw_parts(offset as *const u8, bytes.len()) }; + bytes.copy_from_slice(flash_data); + Ok(()) + } + + fn capacity(&self) -> usize { + FLASH_SIZE + } +} + +impl<'d> NorFlash for Nvmc<'d> { + const WRITE_SIZE: usize = 4; + const ERASE_SIZE: usize = PAGE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + if to < from || to as usize > FLASH_SIZE { + return Err(Error::OutOfBounds); + } + if from as usize % PAGE_SIZE != 0 || to as usize % PAGE_SIZE != 0 { + return Err(Error::Unaligned); + } + + self.enable_erase(); + self.wait_ready(); + + for page_addr in (from..to).step_by(PAGE_SIZE) { + self.erase_page(page_addr); + self.wait_ready(); + } + + self.enable_read(); + self.wait_ready(); + + Ok(()) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + if offset as usize + bytes.len() > FLASH_SIZE { + return Err(Error::OutOfBounds); + } + if offset as usize % 4 != 0 || bytes.len() % 4 != 0 { + return Err(Error::Unaligned); + } + + self.enable_write(); + self.wait_ready(); + + unsafe { + let p_src = bytes.as_ptr() as *const u32; + let p_dst = offset as *mut u32; + let words = bytes.len() / 4; + 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_write(); + } + } + + self.enable_read(); + self.wait_ready(); + + Ok(()) + } +} diff --git a/embassy/embassy-nrf/src/pdm.rs b/embassy/embassy-nrf/src/pdm.rs new file mode 100644 index 0000000..483d1a6 --- /dev/null +++ b/embassy/embassy-nrf/src/pdm.rs @@ -0,0 +1,475 @@ +//! Pulse Density Modulation (PDM) microphone driver + +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use fixed::types::I7F1; + +use crate::chip::EASY_DMA_SIZE; +use crate::gpio::{AnyPin, Pin as GpioPin, SealedPin, DISCONNECTED}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::pdm::vals; +pub use crate::pac::pdm::vals::Freq as Frequency; +#[cfg(any( + feature = "nrf52840", + feature = "nrf52833", + feature = "_nrf5340-app", + feature = "_nrf91", +))] +pub use crate::pac::pdm::vals::Ratio; +use crate::{interrupt, pac, Peripheral}; + +/// Interrupt handler +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + + if r.events_end().read() != 0 { + r.intenclr().write(|w| w.set_end(true)); + } + + if r.events_started().read() != 0 { + r.intenclr().write(|w| w.set_started(true)); + } + + if r.events_stopped().read() != 0 { + r.intenclr().write(|w| w.set_stopped(true)); + } + + T::state().waker.wake(); + } +} + +/// PDM microphone interface +pub struct Pdm<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, +} + +/// 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, + /// PDM is already running + AlreadyRunning, +} + +static DUMMY_BUFFER: [i16; 1] = [0; 1]; + +/// 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 PDM implementation +/// can then tear down its infrastructure +#[derive(PartialEq)] +pub enum SamplerState { + /// The sampler processed the samples and is ready for more + Sampled, + /// The sampler is done processing samples + Stopped, +} + +impl<'d, T: Instance> Pdm<'d, T> { + /// Create PDM driver + pub fn new( + pdm: impl Peripheral

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

+ 'd, + din: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(pdm, clk, din); + Self::new_inner(pdm, clk.map_into(), din.map_into(), config) + } + + fn new_inner( + pdm: PeripheralRef<'d, T>, + clk: PeripheralRef<'d, AnyPin>, + din: PeripheralRef<'d, AnyPin>, + config: Config, + ) -> Self { + into_ref!(pdm); + + let r = T::regs(); + + // setup gpio pins + din.conf().write(|w| w.set_input(gpiovals::Input::CONNECT)); + r.psel().din().write_value(din.psel_bits()); + clk.set_low(); + clk.conf().write(|w| w.set_dir(gpiovals::Dir::OUTPUT)); + r.psel().clk().write_value(clk.psel_bits()); + + // configure + r.pdmclkctrl().write(|w| w.set_freq(config.frequency)); + #[cfg(any( + feature = "nrf52840", + feature = "nrf52833", + feature = "_nrf5340-app", + feature = "_nrf91", + ))] + r.ratio().write(|w| w.set_ratio(config.ratio)); + r.mode().write(|w| { + w.set_operation(config.operation_mode.into()); + w.set_edge(config.edge.into()); + }); + + Self::_set_gain(r, config.gain_left, config.gain_right); + + // Disable all events interrupts + r.intenclr().write(|w| w.0 = 0x003F_FFFF); + + // IRQ + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + r.enable().write(|w| w.set_enable(true)); + + Self { _peri: pdm } + } + + fn _set_gain(r: pac::pdm::Pdm, gain_left: I7F1, gain_right: I7F1) { + let gain_to_bits = |gain: I7F1| -> vals::Gain { + let gain: i8 = gain.saturating_add(I7F1::from_bits(0x28)).to_bits().clamp(0, 0x50); + vals::Gain::from_bits(gain as u8) + }; + r.gainl().write(|w| w.set_gainl(gain_to_bits(gain_left))); + r.gainr().write(|w| w.set_gainr(gain_to_bits(gain_right))); + } + + /// Adjust the gain of the PDM microphone on the fly + pub fn set_gain(&mut self, gain_left: I7F1, gain_right: I7F1) { + Self::_set_gain(T::regs(), gain_left, gain_right) + } + + /// Start sampling microphone data into a dummy buffer. + /// Useful to start the microphone and keep it active between recording samples. + pub async fn start(&mut self) { + let r = T::regs(); + + // start dummy sampling because microphone needs some setup time + r.sample().ptr().write_value(DUMMY_BUFFER.as_ptr() as u32); + r.sample().maxcnt().write(|w| w.set_buffsize(DUMMY_BUFFER.len() as _)); + + r.tasks_start().write_value(1); + } + + /// Stop sampling microphone data inta a dummy buffer + pub async fn stop(&mut self) { + let r = T::regs(); + r.tasks_stop().write_value(1); + r.events_started().write_value(0); + } + + /// Sample data into the given buffer + pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> { + if buffer.is_empty() { + return Err(Error::BufferZeroLength); + } + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::BufferTooLong); + } + + let r = T::regs(); + + if r.events_started().read() == 0 { + return Err(Error::NotRunning); + } + + let drop = OnDrop::new(move || { + r.intenclr().write(|w| w.set_end(true)); + r.events_stopped().write_value(0); + + // reset to dummy buffer + r.sample().ptr().write_value(DUMMY_BUFFER.as_ptr() as u32); + r.sample().maxcnt().write(|w| w.set_buffsize(DUMMY_BUFFER.len() as _)); + + while r.events_stopped().read() == 0 {} + }); + + // setup user buffer + let ptr = buffer.as_ptr(); + let len = buffer.len(); + r.sample().ptr().write_value(ptr as u32); + r.sample().maxcnt().write(|w| w.set_buffsize(len as _)); + + // wait till the current sample is finished and the user buffer sample is started + Self::wait_for_sample().await; + + // reset the buffer back to the dummy buffer + r.sample().ptr().write_value(DUMMY_BUFFER.as_ptr() as u32); + r.sample().maxcnt().write(|w| w.set_buffsize(DUMMY_BUFFER.len() as _)); + + // wait till the user buffer is sampled + Self::wait_for_sample().await; + + drop.defuse(); + + Ok(()) + } + + async fn wait_for_sample() { + let r = T::regs(); + + r.events_end().write_value(0); + r.intenset().write(|w| w.set_end(true)); + + compiler_fence(Ordering::SeqCst); + + poll_fn(|cx| { + T::state().waker.register(cx.waker()); + if r.events_end().read() != 0 { + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + } + + /// Continuous sampling with double buffers. + /// + /// A sampler closure is provided that receives the buffer of samples, noting + /// that the size of this buffer can be less than the original buffer's size. + /// A command is return from the closure that indicates whether the sampling + /// should continue or stop. + /// + /// NOTE: The time spent within the callback supplied should not exceed the time + /// taken to acquire the samples into a single buffer. You should measure the + /// time taken by the callback and set the sample buffer size accordingly. + /// Exceeding this time can lead to samples becoming dropped. + pub async fn run_task_sampler( + &mut self, + bufs: &mut [[i16; N]; 2], + mut sampler: S, + ) -> Result<(), Error> + where + S: FnMut(&[i16; N]) -> SamplerState, + { + let r = T::regs(); + + if r.events_started().read() != 0 { + return Err(Error::AlreadyRunning); + } + + r.sample().ptr().write_value(bufs[0].as_mut_ptr() as u32); + r.sample().maxcnt().write(|w| w.set_buffsize(N as _)); + + // Reset and enable the events + r.events_end().write_value(0); + r.events_started().write_value(0); + r.events_stopped().write_value(0); + r.intenset().write(|w| { + w.set_end(true); + w.set_started(true); + w.set_stopped(true); + }); + + // Don't reorder the start event before the previous writes. Hopefully self + // wouldn't happen anyway + compiler_fence(Ordering::SeqCst); + + r.tasks_start().write_value(1); + + let mut current_buffer = 0; + + let mut done = false; + + let drop = OnDrop::new(|| { + r.tasks_stop().write_value(1); + // N.B. It would be better if this were async, but Drop only support sync code + while r.events_stopped().read() != 0 {} + }); + + // Wait for events and complete when the sampler indicates it has had enough + poll_fn(|cx| { + let r = T::regs(); + + T::state().waker.register(cx.waker()); + + if r.events_end().read() != 0 { + compiler_fence(Ordering::SeqCst); + + r.events_end().write_value(0); + r.intenset().write(|w| w.set_end(true)); + + if !done { + // Discard the last buffer after the user requested a stop + if sampler(&bufs[current_buffer]) == SamplerState::Sampled { + let next_buffer = 1 - current_buffer; + current_buffer = next_buffer; + } else { + r.tasks_stop().write_value(1); + done = true; + }; + }; + } + + if r.events_started().read() != 0 { + r.events_started().write_value(0); + r.intenset().write(|w| w.set_started(true)); + + let next_buffer = 1 - current_buffer; + r.sample().ptr().write_value(bufs[next_buffer].as_mut_ptr() as u32); + } + + if r.events_stopped().read() != 0 { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + drop.defuse(); + Ok(()) + } +} + +/// 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, + /// Clock frequency + pub frequency: Frequency, + /// Clock ratio + #[cfg(any( + feature = "nrf52840", + feature = "nrf52833", + feature = "_nrf5340-app", + feature = "_nrf91", + ))] + pub ratio: Ratio, + /// Gain left in dB + pub gain_left: I7F1, + /// Gain right in dB + pub gain_right: I7F1, +} + +impl Default for Config { + fn default() -> Self { + Self { + operation_mode: OperationMode::Mono, + edge: Edge::LeftFalling, + frequency: Frequency::DEFAULT, + #[cfg(any( + feature = "nrf52840", + feature = "nrf52833", + feature = "_nrf5340-app", + feature = "_nrf91", + ))] + ratio: Ratio::RATIO80, + gain_left: I7F1::ZERO, + gain_right: I7F1::ZERO, + } + } +} + +/// PDM operation mode +#[derive(PartialEq)] +pub enum OperationMode { + /// Mono (1 channel) + Mono, + /// Stereo (2 channels) + Stereo, +} + +impl From for vals::Operation { + fn from(mode: OperationMode) -> Self { + match mode { + OperationMode::Mono => vals::Operation::MONO, + OperationMode::Stereo => vals::Operation::STEREO, + } + } +} + +/// PDM edge polarity +#[derive(PartialEq)] +pub enum Edge { + /// Left edge is rising + LeftRising, + /// Left edge is falling + LeftFalling, +} + +impl From for vals::Edge { + fn from(edge: Edge) -> Self { + match edge { + Edge::LeftRising => vals::Edge::LEFT_RISING, + Edge::LeftFalling => vals::Edge::LEFT_FALLING, + } + } +} + +impl<'d, T: Instance> Drop for Pdm<'d, T> { + fn drop(&mut self) { + let r = T::regs(); + + r.tasks_stop().write_value(1); + + r.enable().write(|w| w.set_enable(false)); + + r.psel().din().write_value(DISCONNECTED); + r.psel().clk().write_value(DISCONNECTED); + } +} + +/// Peripheral static state +pub(crate) struct State { + waker: AtomicWaker, +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> crate::pac::pdm::Pdm; + fn state() -> &'static State; +} + +/// PDM peripheral instance +#[allow(private_bounds)] +pub trait Instance: Peripheral

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

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

+ 'd, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self { + Ppi::new_many_to_many(ch, [event], [task1, task2]) + } +} + +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<'d>; EVENT_COUNT], + tasks: [Task<'d>; TASK_COUNT], + ) -> Self { + into_ref!(ch); + + let val = DPPI_ENABLE_BIT | (ch.number() as u32 & DPPI_CHANNEL_MASK); + for task in tasks { + if unsafe { task.subscribe_reg().read_volatile() } != 0 { + panic!("Task is already in use"); + } + unsafe { task.subscribe_reg().write_volatile(val) } + } + for event in events { + if unsafe { event.publish_reg().read_volatile() } != 0 { + panic!("Event is already in use"); + } + unsafe { event.publish_reg().write_volatile(val) } + } + + Self { ch, events, tasks } + } +} + +impl<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Ppi<'d, C, EVENT_COUNT, TASK_COUNT> { + /// Enables the channel. + pub fn enable(&mut self) { + let n = self.ch.number(); + regs().chenset().write(|w| w.0 = 1 << n); + } + + /// Disables the channel. + pub fn disable(&mut self) { + let n = self.ch.number(); + regs().chenclr().write(|w| w.0 = 1 << n); + } +} + +impl<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Drop for Ppi<'d, C, EVENT_COUNT, TASK_COUNT> { + fn drop(&mut self) { + self.disable(); + + for task in self.tasks { + unsafe { task.subscribe_reg().write_volatile(0) } + } + for event in self.events { + unsafe { event.publish_reg().write_volatile(0) } + } + } +} diff --git a/embassy/embassy-nrf/src/ppi/mod.rs b/embassy/embassy-nrf/src/ppi/mod.rs new file mode 100644 index 0000000..325e4ce --- /dev/null +++ b/embassy/embassy-nrf/src/ppi/mod.rs @@ -0,0 +1,367 @@ +#![macro_use] + +//! 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 +//! configurable ones. Fixed channels can only connect specific events to specific tasks. For fully +//! configurable channels, it is possible to choose, via software, the event and the task that it +//! will triggered by the event. +//! +//! On nRF52 devices, there is also a fork task endpoint, where the user can configure one more task +//! to be triggered by the same event, even fixed PPI channels have a configurable fork task. +//! +//! The DPPI for nRF53 and nRF91 devices works in a different way. Every channel can support infinitely +//! many tasks and events, but any single task or event can only be coupled with one channel. +//! + +use core::marker::PhantomData; +use core::ptr::NonNull; + +use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; + +use crate::pac::common::{Reg, RW, W}; +use crate::{peripherals, Peripheral}; + +#[cfg_attr(feature = "_dppi", path = "dppi.rs")] +#[cfg_attr(feature = "_ppi", path = "ppi.rs")] +mod _version; +pub(crate) use _version::*; + +/// PPI channel driver. +pub struct Ppi<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> { + ch: PeripheralRef<'d, C>, + #[cfg(feature = "_dppi")] + events: [Event<'d>; EVENT_COUNT], + #[cfg(feature = "_dppi")] + tasks: [Task<'d>; TASK_COUNT], +} + +/// PPI channel group driver. +pub struct PpiGroup<'d, G: Group> { + g: PeripheralRef<'d, G>, +} + +impl<'d, G: Group> PpiGroup<'d, G> { + /// Create a new PPI group driver. + /// + /// The group is initialized as containing no channels. + pub fn new(g: impl Peripheral

+ 'd) -> Self { + into_ref!(g); + + let r = regs(); + let n = g.number(); + r.chg(n).write(|_| ()); + + Self { g } + } + + /// Add a PPI channel to this group. + /// + /// If the channel is already in the group, this is a no-op. + pub fn add_channel( + &mut self, + ch: &Ppi<'_, C, EVENT_COUNT, TASK_COUNT>, + ) { + let r = regs(); + let ng = self.g.number(); + let nc = ch.ch.number(); + r.chg(ng).modify(|w| w.set_ch(nc, true)); + } + + /// Remove a PPI channel from this group. + /// + /// If the channel is already not in the group, this is a no-op. + pub fn remove_channel( + &mut self, + ch: &Ppi<'_, C, EVENT_COUNT, TASK_COUNT>, + ) { + let r = regs(); + let ng = self.g.number(); + let nc = ch.ch.number(); + r.chg(ng).modify(|w| w.set_ch(nc, false)); + } + + /// Enable all the channels in this group. + pub fn enable_all(&mut self) { + let n = self.g.number(); + regs().tasks_chg(n).en().write_value(1); + } + + /// Disable all the channels in this group. + pub fn disable_all(&mut self) { + let n = self.g.number(); + regs().tasks_chg(n).dis().write_value(1); + } + + /// Get a reference to the "enable all" task. + /// + /// When triggered, it will enable all the channels in this group. + pub fn task_enable_all(&self) -> Task<'d> { + let n = self.g.number(); + Task::from_reg(regs().tasks_chg(n).en()) + } + + /// Get a reference to the "disable all" task. + /// + /// When triggered, it will disable all the channels in this group. + pub fn task_disable_all(&self) -> Task<'d> { + let n = self.g.number(); + Task::from_reg(regs().tasks_chg(n).dis()) + } +} + +impl<'d, G: Group> Drop for PpiGroup<'d, G> { + fn drop(&mut self) { + let r = regs(); + let n = self.g.number(); + r.chg(n).write(|_| ()); + } +} + +#[cfg(feature = "_dppi")] +const REGISTER_DPPI_CONFIG_OFFSET: usize = 0x80 / core::mem::size_of::(); + +/// Represents a task that a peripheral can do. +/// +/// When a task is subscribed to a PPI channel, it will run when the channel is triggered by +/// a published event. +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct Task<'d>(NonNull, PhantomData<&'d ()>); + +impl<'d> Task<'d> { + /// Create a new `Task` from a task register pointer + /// + /// # Safety + /// + /// `ptr` must be a pointer to a valid `TASKS_*` register from an nRF peripheral. + pub unsafe fn new_unchecked(ptr: NonNull) -> Self { + Self(ptr, PhantomData) + } + + /// Triggers this task. + pub fn trigger(&mut self) { + unsafe { self.0.as_ptr().write_volatile(1) }; + } + + pub(crate) fn from_reg(reg: Reg) -> Self { + Self(unsafe { NonNull::new_unchecked(reg.as_ptr()) }, PhantomData) + } + + /// Address of subscription register for this task. + #[cfg(feature = "_dppi")] + pub fn subscribe_reg(&self) -> *mut u32 { + unsafe { self.0.as_ptr().add(REGISTER_DPPI_CONFIG_OFFSET) } + } +} + +/// # Safety +/// +/// NonNull is not send, but this event is only allowed to point at registers and those exist in any context on the same core. +unsafe impl Send for Task<'_> {} + +/// Represents an event that a peripheral can publish. +/// +/// An event can be set to publish on a PPI channel when the event happens. +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct Event<'d>(NonNull, PhantomData<&'d ()>); + +impl<'d> Event<'d> { + /// Create a new `Event` from an event register pointer + /// + /// # Safety + /// + /// `ptr` must be a pointer to a valid `EVENTS_*` register from an nRF peripheral. + pub unsafe fn new_unchecked(ptr: NonNull) -> Self { + Self(ptr, PhantomData) + } + + pub(crate) fn from_reg(reg: Reg) -> Self { + Self(unsafe { NonNull::new_unchecked(reg.as_ptr()) }, PhantomData) + } + + /// Describes whether this Event is currently in a triggered state. + pub fn is_triggered(&self) -> bool { + unsafe { self.0.as_ptr().read_volatile() == 1 } + } + + /// Clear the current register's triggered state, reverting it to 0. + pub fn clear(&mut self) { + unsafe { self.0.as_ptr().write_volatile(0) }; + } + + /// Address of publish register for this event. + #[cfg(feature = "_dppi")] + pub fn publish_reg(&self) -> *mut u32 { + unsafe { self.0.as_ptr().add(REGISTER_DPPI_CONFIG_OFFSET) } + } +} + +/// # Safety +/// +/// NonNull is not send, but this event is only allowed to point at registers and those exist in any context on the same core. +unsafe impl Send for Event<'_> {} + +// ====================== +// traits + +pub(crate) trait SealedChannel {} +pub(crate) trait SealedGroup {} + +/// Interface for PPI channels. +#[allow(private_bounds)] +pub trait Channel: SealedChannel + Peripheral

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

+ Into + Sized + 'static { + /// Returns the number of the group. + fn number(&self) -> usize; + /// Convert into a type erased group. + fn degrade(self) -> AnyGroup { + AnyGroup { + number: self.number() as u8, + } + } +} + +// ====================== +// channels + +/// The any channel can represent any static channel at runtime. +/// This can be used to have fewer generic parameters in some places. +pub struct AnyStaticChannel { + pub(crate) number: u8, +} +impl_peripheral!(AnyStaticChannel); +impl SealedChannel for AnyStaticChannel {} +impl Channel for AnyStaticChannel { + fn number(&self) -> usize { + self.number as usize + } +} +impl StaticChannel for AnyStaticChannel { + fn degrade(self) -> AnyStaticChannel { + self + } +} + +/// The any configurable channel can represent any configurable channel at runtime. +/// This can be used to have fewer generic parameters in some places. +pub struct AnyConfigurableChannel { + pub(crate) number: u8, +} +impl_peripheral!(AnyConfigurableChannel); +impl SealedChannel for AnyConfigurableChannel {} +impl Channel for AnyConfigurableChannel { + fn number(&self) -> usize { + self.number as usize + } +} +impl ConfigurableChannel for AnyConfigurableChannel { + fn degrade(self) -> AnyConfigurableChannel { + self + } +} + +#[cfg(not(feature = "_nrf51"))] +macro_rules! impl_ppi_channel { + ($type:ident, $number:expr) => { + impl crate::ppi::SealedChannel for peripherals::$type {} + impl crate::ppi::Channel for peripherals::$type { + fn number(&self) -> usize { + $number + } + } + }; + ($type:ident, $number:expr => static) => { + impl_ppi_channel!($type, $number); + impl crate::ppi::StaticChannel for peripherals::$type { + fn degrade(self) -> crate::ppi::AnyStaticChannel { + use crate::ppi::Channel; + crate::ppi::AnyStaticChannel { + number: self.number() as u8, + } + } + } + + impl From for crate::ppi::AnyStaticChannel { + fn from(val: peripherals::$type) -> Self { + crate::ppi::StaticChannel::degrade(val) + } + } + }; + ($type:ident, $number:expr => configurable) => { + impl_ppi_channel!($type, $number); + impl crate::ppi::ConfigurableChannel for peripherals::$type { + fn degrade(self) -> crate::ppi::AnyConfigurableChannel { + use crate::ppi::Channel; + crate::ppi::AnyConfigurableChannel { + number: self.number() as u8, + } + } + } + + impl From for crate::ppi::AnyConfigurableChannel { + fn from(val: peripherals::$type) -> Self { + crate::ppi::ConfigurableChannel::degrade(val) + } + } + }; +} + +// ====================== +// groups + +/// A type erased PPI group. +pub struct AnyGroup { + number: u8, +} +impl_peripheral!(AnyGroup); +impl SealedGroup for AnyGroup {} +impl Group for AnyGroup { + fn number(&self) -> usize { + self.number as usize + } +} + +macro_rules! impl_group { + ($type:ident, $number:expr) => { + impl SealedGroup for peripherals::$type {} + impl Group for peripherals::$type { + fn number(&self) -> usize { + $number + } + } + + impl From for crate::ppi::AnyGroup { + fn from(val: peripherals::$type) -> Self { + crate::ppi::Group::degrade(val) + } + } + }; +} + +impl_group!(PPI_GROUP0, 0); +impl_group!(PPI_GROUP1, 1); +impl_group!(PPI_GROUP2, 2); +impl_group!(PPI_GROUP3, 3); +#[cfg(not(feature = "_nrf51"))] +impl_group!(PPI_GROUP4, 4); +#[cfg(not(feature = "_nrf51"))] +impl_group!(PPI_GROUP5, 5); diff --git a/embassy/embassy-nrf/src/ppi/ppi.rs b/embassy/embassy-nrf/src/ppi/ppi.rs new file mode 100644 index 0000000..a1beb9d --- /dev/null +++ b/embassy/embassy-nrf/src/ppi/ppi.rs @@ -0,0 +1,90 @@ +use embassy_hal_internal::into_ref; + +use super::{Channel, ConfigurableChannel, Event, Ppi, Task}; +use crate::{pac, Peripheral}; + +impl<'d> Task<'d> { + fn reg_val(&self) -> u32 { + self.0.as_ptr() as _ + } +} +impl<'d> Event<'d> { + fn reg_val(&self) -> u32 { + self.0.as_ptr() as _ + } +} + +pub(crate) fn regs() -> pac::ppi::Ppi { + pac::PPI +} + +#[cfg(not(feature = "_nrf51"))] // Not for nrf51 because of the fork task +impl<'d, C: super::StaticChannel> Ppi<'d, C, 0, 1> { + /// Configure PPI channel to trigger `task`. + pub fn new_zero_to_one(ch: impl Peripheral

+ 'd, task: Task) -> Self { + into_ref!(ch); + + let r = regs(); + let n = ch.number(); + r.fork(n).tep().write_value(task.reg_val()); + + Self { ch } + } +} + +impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { + /// Configure PPI channel to trigger `task` on `event`. + pub fn new_one_to_one(ch: impl Peripheral

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

+ 'd, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self { + into_ref!(ch); + + let r = regs(); + let n = ch.number(); + r.ch(n).eep().write_value(event.reg_val()); + r.ch(n).tep().write_value(task1.reg_val()); + r.fork(n).tep().write_value(task2.reg_val()); + + Self { ch } + } +} + +impl<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Ppi<'d, C, EVENT_COUNT, TASK_COUNT> { + /// Enables the channel. + pub fn enable(&mut self) { + let n = self.ch.number(); + regs().chenset().write(|w| w.set_ch(n, true)); + } + + /// Disables the channel. + pub fn disable(&mut self) { + let n = self.ch.number(); + regs().chenclr().write(|w| w.set_ch(n, true)); + } +} + +impl<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Drop for Ppi<'d, C, EVENT_COUNT, TASK_COUNT> { + fn drop(&mut self) { + self.disable(); + + let r = regs(); + let n = self.ch.number(); + r.ch(n).eep().write_value(0); + r.ch(n).tep().write_value(0); + #[cfg(not(feature = "_nrf51"))] + r.fork(n).tep().write_value(0); + } +} diff --git a/embassy/embassy-nrf/src/pwm.rs b/embassy/embassy-nrf/src/pwm.rs new file mode 100644 index 0000000..6247ff6 --- /dev/null +++ b/embassy/embassy-nrf/src/pwm.rs @@ -0,0 +1,915 @@ +//! Pulse Width Modulation (PWM) driver. + +#![macro_use] + +use core::sync::atomic::{compiler_fence, Ordering}; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use crate::gpio::{convert_drive, AnyPin, OutputDrive, Pin as GpioPin, PselBits, SealedPin as _, DISCONNECTED}; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::pwm::vals; +use crate::ppi::{Event, Task}; +use crate::util::slice_in_ram_or; +use crate::{interrupt, pac, Peripheral}; + +/// SimplePwm is the traditional pwm interface you're probably used to, allowing +/// to simply set a duty cycle across up to four channels. +pub struct SimplePwm<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, + duty: [u16; 4], + ch0: Option>, + ch1: Option>, + ch2: Option>, + ch3: Option>, +} + +/// SequencePwm allows you to offload the updating of a sequence of duty cycles +/// to up to four channels, as well as repeat that sequence n times. +pub struct SequencePwm<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, + ch0: Option>, + ch1: Option>, + ch2: Option>, + ch3: Option>, +} + +/// PWM error +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Max Sequence size is 32767 + SequenceTooLong, + /// Min Sequence count is 1 + SequenceTimesAtLeastOne, + /// EasyDMA can only read from data memory, read only buffers in flash will fail. + BufferNotInRAM, +} + +const MAX_SEQUENCE_LEN: usize = 32767; +/// The used pwm clock frequency +pub const PWM_CLK_HZ: u32 = 16_000_000; + +impl<'d, T: Instance> SequencePwm<'d, T> { + /// Create a new 1-channel PWM + #[allow(unused_unsafe)] + pub fn new_1ch( + pwm: impl Peripheral

+ 'd, + ch0: impl Peripheral

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

+ 'd, + ch0: impl Peripheral

+ 'd, + ch1: impl Peripheral

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

+ 'd, + ch0: impl Peripheral

+ 'd, + ch1: impl Peripheral

+ 'd, + ch2: impl Peripheral

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

+ 'd, + ch0: impl Peripheral

+ 'd, + ch1: impl Peripheral

+ 'd, + ch2: impl Peripheral

+ 'd, + ch3: impl Peripheral

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

+ 'd, + ch0: Option>, + ch1: Option>, + ch2: Option>, + ch3: Option>, + config: Config, + ) -> Result { + into_ref!(_pwm); + + let r = T::regs(); + + if let Some(pin) = &ch0 { + pin.set_low(); + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + convert_drive(w, config.ch0_drive); + }); + } + if let Some(pin) = &ch1 { + pin.set_low(); + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + convert_drive(w, config.ch1_drive); + }); + } + if let Some(pin) = &ch2 { + pin.set_low(); + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + convert_drive(w, config.ch2_drive); + }); + } + if let Some(pin) = &ch3 { + pin.set_low(); + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + convert_drive(w, config.ch3_drive); + }); + } + + r.psel().out(0).write_value(ch0.psel_bits()); + r.psel().out(1).write_value(ch1.psel_bits()); + r.psel().out(2).write_value(ch2.psel_bits()); + r.psel().out(3).write_value(ch3.psel_bits()); + + // Disable all interrupts + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); + r.shorts().write(|_| ()); + r.events_stopped().write_value(0); + r.events_loopsdone().write_value(0); + r.events_seqend(0).write_value(0); + r.events_seqend(1).write_value(0); + r.events_pwmperiodend().write_value(0); + r.events_seqstarted(0).write_value(0); + r.events_seqstarted(1).write_value(0); + + r.decoder().write(|w| { + w.set_load(vals::Load::from_bits(config.sequence_load as u8)); + w.set_mode(vals::Mode::REFRESH_COUNT); + }); + + r.mode().write(|w| match config.counter_mode { + CounterMode::UpAndDown => w.set_updown(vals::Updown::UP_AND_DOWN), + CounterMode::Up => w.set_updown(vals::Updown::UP), + }); + r.prescaler() + .write(|w| w.set_prescaler(vals::Prescaler::from_bits(config.prescaler as u8))); + r.countertop().write(|w| w.set_countertop(config.max_duty)); + + Ok(Self { + _peri: _pwm, + ch0, + ch1, + ch2, + ch3, + }) + } + + /// Returns reference to `Stopped` event endpoint for PPI. + #[inline(always)] + pub fn event_stopped(&self) -> Event<'d> { + let r = T::regs(); + + Event::from_reg(r.events_stopped()) + } + + /// Returns reference to `LoopsDone` event endpoint for PPI. + #[inline(always)] + pub fn event_loops_done(&self) -> Event<'d> { + let r = T::regs(); + + Event::from_reg(r.events_loopsdone()) + } + + /// Returns reference to `PwmPeriodEnd` event endpoint for PPI. + #[inline(always)] + pub fn event_pwm_period_end(&self) -> Event<'d> { + let r = T::regs(); + + Event::from_reg(r.events_pwmperiodend()) + } + + /// Returns reference to `Seq0 End` event endpoint for PPI. + #[inline(always)] + pub fn event_seq_end(&self) -> Event<'d> { + let r = T::regs(); + + Event::from_reg(r.events_seqend(0)) + } + + /// Returns reference to `Seq1 End` event endpoint for PPI. + #[inline(always)] + pub fn event_seq1_end(&self) -> Event<'d> { + let r = T::regs(); + + Event::from_reg(r.events_seqend(1)) + } + + /// Returns reference to `Seq0 Started` event endpoint for PPI. + #[inline(always)] + pub fn event_seq0_started(&self) -> Event<'d> { + let r = T::regs(); + + Event::from_reg(r.events_seqstarted(0)) + } + + /// Returns reference to `Seq1 Started` event endpoint for PPI. + #[inline(always)] + pub fn event_seq1_started(&self) -> Event<'d> { + let r = T::regs(); + + Event::from_reg(r.events_seqstarted(1)) + } + + /// Returns reference to `Seq0 Start` task endpoint for PPI. + /// # Safety + /// + /// Interacting with the sequence while it runs puts it in an unknown state + #[inline(always)] + pub unsafe fn task_start_seq0(&self) -> Task<'d> { + let r = T::regs(); + + Task::from_reg(r.tasks_seqstart(0)) + } + + /// Returns reference to `Seq1 Started` task endpoint for PPI. + /// # Safety + /// + /// Interacting with the sequence while it runs puts it in an unknown state + #[inline(always)] + pub unsafe fn task_start_seq1(&self) -> Task<'d> { + let r = T::regs(); + + Task::from_reg(r.tasks_seqstart(1)) + } + + /// Returns reference to `NextStep` task endpoint for PPI. + /// # Safety + /// + /// Interacting with the sequence while it runs puts it in an unknown state + #[inline(always)] + pub unsafe fn task_next_step(&self) -> Task<'d> { + let r = T::regs(); + + Task::from_reg(r.tasks_nextstep()) + } + + /// Returns reference to `Stop` task endpoint for PPI. + /// # Safety + /// + /// Interacting with the sequence while it runs puts it in an unknown state + #[inline(always)] + pub unsafe fn task_stop(&self) -> Task<'d> { + let r = T::regs(); + + Task::from_reg(r.tasks_stop()) + } +} + +impl<'a, T: Instance> Drop for SequencePwm<'a, T> { + fn drop(&mut self) { + let r = T::regs(); + + if let Some(pin) = &self.ch0 { + pin.set_low(); + pin.conf().write(|_| ()); + r.psel().out(0).write_value(DISCONNECTED); + } + if let Some(pin) = &self.ch1 { + pin.set_low(); + pin.conf().write(|_| ()); + r.psel().out(1).write_value(DISCONNECTED); + } + if let Some(pin) = &self.ch2 { + pin.set_low(); + pin.conf().write(|_| ()); + r.psel().out(2).write_value(DISCONNECTED); + } + if let Some(pin) = &self.ch3 { + pin.set_low(); + pin.conf().write(|_| ()); + r.psel().out(3).write_value(DISCONNECTED); + } + } +} + +/// Configuration for the PWM as a whole. +#[non_exhaustive] +pub struct Config { + /// Selects up mode or up-and-down mode for the counter + pub counter_mode: CounterMode, + /// Top value to be compared against buffer values + pub max_duty: u16, + /// Configuration for PWM_CLK + pub prescaler: Prescaler, + /// How a sequence is read from RAM and is spread to the compare register + pub sequence_load: SequenceLoad, + /// Drive strength for the channel 0 line. + pub ch0_drive: OutputDrive, + /// Drive strength for the channel 1 line. + pub ch1_drive: OutputDrive, + /// Drive strength for the channel 2 line. + pub ch2_drive: OutputDrive, + /// Drive strength for the channel 3 line. + pub ch3_drive: OutputDrive, +} + +impl Default for Config { + fn default() -> Config { + Config { + counter_mode: CounterMode::Up, + max_duty: 1000, + prescaler: Prescaler::Div16, + sequence_load: SequenceLoad::Common, + ch0_drive: OutputDrive::Standard, + ch1_drive: OutputDrive::Standard, + ch2_drive: OutputDrive::Standard, + ch3_drive: OutputDrive::Standard, + } + } +} + +/// Configuration per sequence +#[non_exhaustive] +#[derive(Clone)] +pub struct SequenceConfig { + /// Number of PWM periods to delay between each sequence sample + pub refresh: u32, + /// Number of PWM periods after the sequence ends before starting the next sequence + pub end_delay: u32, +} + +impl Default for SequenceConfig { + fn default() -> SequenceConfig { + SequenceConfig { + refresh: 0, + end_delay: 0, + } + } +} + +/// A composition of a sequence buffer and its configuration. +#[non_exhaustive] +pub struct Sequence<'s> { + /// The words comprising the sequence. Must not exceed 32767 words. + pub words: &'s [u16], + /// Configuration associated with the sequence. + pub config: SequenceConfig, +} + +impl<'s> Sequence<'s> { + /// Create a new `Sequence` + pub fn new(words: &'s [u16], config: SequenceConfig) -> Self { + Self { words, config } + } +} + +/// A single sequence that can be started and stopped. +/// Takes one sequence along with its configuration. +#[non_exhaustive] +pub struct SingleSequencer<'d, 's, T: Instance> { + sequencer: Sequencer<'d, 's, T>, +} + +impl<'d, 's, T: Instance> SingleSequencer<'d, 's, T> { + /// Create a new sequencer + pub fn new(pwm: &'s mut SequencePwm<'d, T>, words: &'s [u16], config: SequenceConfig) -> Self { + Self { + sequencer: Sequencer::new(pwm, Sequence::new(words, config), None), + } + } + + /// Start or restart playback. + #[inline(always)] + pub fn start(&self, times: SingleSequenceMode) -> Result<(), Error> { + let (start_seq, times) = match times { + SingleSequenceMode::Times(n) if n == 1 => (StartSequence::One, SequenceMode::Loop(1)), + SingleSequenceMode::Times(n) if n & 1 == 1 => (StartSequence::One, SequenceMode::Loop((n / 2) + 1)), + SingleSequenceMode::Times(n) => (StartSequence::Zero, SequenceMode::Loop(n / 2)), + SingleSequenceMode::Infinite => (StartSequence::Zero, SequenceMode::Infinite), + }; + self.sequencer.start(start_seq, times) + } + + /// Stop playback. Disables the peripheral. Does NOT clear the last duty + /// cycle from the pin. Returns any sequences previously provided to + /// `start` so that they may be further mutated. + #[inline(always)] + pub fn stop(&self) { + self.sequencer.stop(); + } +} + +/// A composition of sequences that can be started and stopped. +/// Takes at least one sequence along with its configuration. +/// Optionally takes a second sequence and its configuration. +/// In the case where no second sequence is provided then the first sequence +/// is used. +#[non_exhaustive] +pub struct Sequencer<'d, 's, T: Instance> { + _pwm: &'s mut SequencePwm<'d, T>, + sequence0: Sequence<'s>, + sequence1: Option>, +} + +impl<'d, 's, T: Instance> Sequencer<'d, 's, T> { + /// Create a new double sequence. In the absence of sequence 1, sequence 0 + /// will be used twice in the one loop. + pub fn new(pwm: &'s mut SequencePwm<'d, T>, sequence0: Sequence<'s>, sequence1: Option>) -> Self { + Sequencer { + _pwm: pwm, + sequence0, + sequence1, + } + } + + /// Start or restart playback. The sequence mode applies to both sequences combined as one. + #[inline(always)] + pub fn start(&self, start_seq: StartSequence, times: SequenceMode) -> Result<(), Error> { + let sequence0 = &self.sequence0; + let alt_sequence = self.sequence1.as_ref().unwrap_or(&self.sequence0); + + 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); + } + + if let SequenceMode::Loop(0) = times { + return Err(Error::SequenceTimesAtLeastOne); + } + + self.stop(); + + let r = T::regs(); + + r.seq(0).refresh().write(|w| w.0 = sequence0.config.refresh); + r.seq(0).enddelay().write(|w| w.0 = sequence0.config.end_delay); + r.seq(0).ptr().write_value(sequence0.words.as_ptr() as u32); + r.seq(0).cnt().write(|w| w.0 = sequence0.words.len() as u32); + + r.seq(1).refresh().write(|w| w.0 = alt_sequence.config.refresh); + r.seq(1).enddelay().write(|w| w.0 = alt_sequence.config.end_delay); + r.seq(1).ptr().write_value(alt_sequence.words.as_ptr() as u32); + r.seq(1).cnt().write(|w| w.0 = alt_sequence.words.len() as u32); + + r.enable().write(|w| w.set_enable(true)); + + // defensive before seqstart + compiler_fence(Ordering::SeqCst); + + let seqstart_index = if start_seq == StartSequence::One { 1 } else { 0 }; + + match times { + // just the one time, no loop count + SequenceMode::Loop(_) => { + r.loop_().write(|w| w.set_cnt(vals::LoopCnt::DISABLED)); + } + // to play infinitely, repeat the sequence one time, then have loops done self trigger seq0 again + SequenceMode::Infinite => { + r.loop_().write(|w| w.set_cnt(vals::LoopCnt::from_bits(1))); + r.shorts().write(|w| w.set_loopsdone_seqstart0(true)); + } + } + + r.tasks_seqstart(seqstart_index).write_value(1); + + Ok(()) + } + + /// Stop playback. Disables the peripheral. Does NOT clear the last duty + /// cycle from the pin. Returns any sequences previously provided to + /// `start` so that they may be further mutated. + #[inline(always)] + pub fn stop(&self) { + let r = T::regs(); + + r.shorts().write(|_| ()); + + compiler_fence(Ordering::SeqCst); + + r.tasks_stop().write_value(1); + r.enable().write(|w| w.set_enable(false)); + } +} + +impl<'d, 's, T: Instance> Drop for Sequencer<'d, 's, T> { + fn drop(&mut self) { + self.stop(); + } +} + +/// How many times to run a single sequence +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum SingleSequenceMode { + /// Run a single sequence n Times total. + Times(u16), + /// Repeat until `stop` is called. + Infinite, +} + +/// Which sequence to start a loop with +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum StartSequence { + /// Start with Sequence 0 + Zero, + /// Start with Sequence 1 + One, +} + +/// How many loops to run two sequences +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum SequenceMode { + /// Run two sequences n loops i.e. (n * (seq0 + seq1.unwrap_or(seq0))) + Loop(u16), + /// Repeat until `stop` is called. + Infinite, +} + +/// 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, +} + +/// How the sequence values are distributed across the channels +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum SequenceLoad { + /// Provided sequence will be used across all channels + Common, + /// Provided sequence contains grouped values for each channel ex: + /// [ch0_0_and_ch1_0, ch2_0_and_ch3_0, ... ch0_n_and_ch1_n, ch2_n_and_ch3_n] + Grouped, + /// Provided sequence contains individual values for each channel ex: + /// [ch0_0, ch1_0, ch2_0, ch3_0... ch0_n, ch1_n, ch2_n, ch3_n] + Individual, + /// Similar to Individual mode, but only three channels are used. The fourth + /// value is loaded into the pulse generator counter as its top value. + Waveform, +} + +/// Selects up mode or up-and-down mode for the counter +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum CounterMode { + /// Up counter (edge-aligned PWM duty cycle) + Up, + /// Up and down counter (center-aligned PWM duty cycle) + UpAndDown, +} + +impl<'d, T: Instance> SimplePwm<'d, T> { + /// Create a new 1-channel PWM + #[allow(unused_unsafe)] + pub fn new_1ch(pwm: impl Peripheral

+ 'd, ch0: impl Peripheral

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

+ 'd, + ch0: impl Peripheral

+ 'd, + ch1: impl Peripheral

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

+ 'd, + ch0: impl Peripheral

+ 'd, + ch1: impl Peripheral

+ 'd, + ch2: impl Peripheral

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

+ 'd, + ch0: impl Peripheral

+ 'd, + ch1: impl Peripheral

+ 'd, + ch2: impl Peripheral

+ 'd, + ch3: impl Peripheral

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

+ 'd, + ch0: Option>, + ch1: Option>, + ch2: Option>, + ch3: Option>, + ) -> Self { + into_ref!(_pwm); + + let r = T::regs(); + + for (i, ch) in [&ch0, &ch1, &ch2, &ch3].into_iter().enumerate() { + if let Some(pin) = ch { + pin.set_low(); + + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + w.set_drive(gpiovals::Drive::S0S1); + }); + } + r.psel().out(i).write_value(ch.psel_bits()); + } + + let pwm = Self { + _peri: _pwm, + ch0, + ch1, + ch2, + ch3, + duty: [0; 4], + }; + + // Disable all interrupts + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); + r.shorts().write(|_| ()); + + // Enable + r.enable().write(|w| w.set_enable(true)); + + r.seq(0).ptr().write_value((pwm.duty).as_ptr() as u32); + r.seq(0).cnt().write(|w| w.0 = 4); + r.seq(0).refresh().write(|w| w.0 = 0); + r.seq(0).enddelay().write(|w| w.0 = 0); + + r.decoder().write(|w| { + w.set_load(vals::Load::INDIVIDUAL); + w.set_mode(vals::Mode::REFRESH_COUNT); + }); + r.mode().write(|w| w.set_updown(vals::Updown::UP)); + r.prescaler().write(|w| w.set_prescaler(vals::Prescaler::DIV_16)); + r.countertop().write(|w| w.set_countertop(1000)); + r.loop_().write(|w| w.set_cnt(vals::LoopCnt::DISABLED)); + + pwm + } + + /// Returns the enable state of the pwm counter + #[inline(always)] + pub fn is_enabled(&self) -> bool { + let r = T::regs(); + r.enable().read().enable() + } + + /// Enables the PWM generator. + #[inline(always)] + pub fn enable(&self) { + let r = T::regs(); + r.enable().write(|w| w.set_enable(true)); + } + + /// Disables the PWM generator. Does NOT clear the last duty cycle from the pin. + #[inline(always)] + pub fn disable(&self) { + let r = T::regs(); + r.enable().write(|w| w.set_enable(false)); + } + + /// Returns the current duty of the channel + pub fn duty(&self, channel: usize) -> u16 { + self.duty[channel] + } + + /// Sets duty cycle (15 bit) for a PWM channel. + pub fn set_duty(&mut self, channel: usize, duty: u16) { + let r = T::regs(); + + self.duty[channel] = duty & 0x7FFF; + + // reload ptr in case self was moved + r.seq(0).ptr().write_value((self.duty).as_ptr() as u32); + + // defensive before seqstart + compiler_fence(Ordering::SeqCst); + + r.events_seqend(0).write_value(0); + + // tasks_seqstart() doesn't exist in all svds so write its bit instead + r.tasks_seqstart(0).write_value(1); + + // defensive wait until waveform is loaded after seqstart so set_duty + // can't be called again while dma is still reading + if self.is_enabled() { + while r.events_seqend(0).read() == 0 {} + } + } + + /// Sets the PWM clock prescaler. + #[inline(always)] + pub fn set_prescaler(&self, div: Prescaler) { + T::regs() + .prescaler() + .write(|w| w.set_prescaler(vals::Prescaler::from_bits(div as u8))); + } + + /// Gets the PWM clock prescaler. + #[inline(always)] + pub fn prescaler(&self) -> Prescaler { + match T::regs().prescaler().read().prescaler().to_bits() { + 0 => Prescaler::Div1, + 1 => Prescaler::Div2, + 2 => Prescaler::Div4, + 3 => Prescaler::Div8, + 4 => Prescaler::Div16, + 5 => Prescaler::Div32, + 6 => Prescaler::Div64, + 7 => Prescaler::Div128, + _ => unreachable!(), + } + } + + /// Sets the maximum duty cycle value. + #[inline(always)] + pub fn set_max_duty(&self, duty: u16) { + T::regs().countertop().write(|w| w.set_countertop(duty.min(32767u16))); + } + + /// Returns the maximum duty cycle value. + #[inline(always)] + pub fn max_duty(&self) -> u16 { + T::regs().countertop().read().countertop() + } + + /// Sets the PWM output frequency. + #[inline(always)] + pub fn set_period(&self, freq: u32) { + let clk = PWM_CLK_HZ >> (self.prescaler() as u8); + let duty = clk / freq; + self.set_max_duty(duty.min(32767) as u16); + } + + /// Returns the PWM output frequency. + #[inline(always)] + pub fn period(&self) -> u32 { + let clk = PWM_CLK_HZ >> (self.prescaler() as u8); + let max_duty = self.max_duty() as u32; + clk / max_duty + } + + /// Sets the PWM-Channel0 output drive strength + #[inline(always)] + pub fn set_ch0_drive(&self, drive: OutputDrive) { + if let Some(pin) = &self.ch0 { + pin.conf().modify(|w| convert_drive(w, drive)); + } + } + + /// Sets the PWM-Channel1 output drive strength + #[inline(always)] + pub fn set_ch1_drive(&self, drive: OutputDrive) { + if let Some(pin) = &self.ch1 { + pin.conf().modify(|w| convert_drive(w, drive)); + } + } + + /// Sets the PWM-Channel2 output drive strength + #[inline(always)] + pub fn set_ch2_drive(&self, drive: OutputDrive) { + if let Some(pin) = &self.ch2 { + pin.conf().modify(|w| convert_drive(w, drive)); + } + } + + /// Sets the PWM-Channel3 output drive strength + #[inline(always)] + pub fn set_ch3_drive(&self, drive: OutputDrive) { + if let Some(pin) = &self.ch3 { + pin.conf().modify(|w| convert_drive(w, drive)); + } + } +} + +impl<'a, T: Instance> Drop for SimplePwm<'a, T> { + fn drop(&mut self) { + let r = T::regs(); + + self.disable(); + + if let Some(pin) = &self.ch0 { + pin.set_low(); + pin.conf().write(|_| ()); + r.psel().out(0).write_value(DISCONNECTED); + } + if let Some(pin) = &self.ch1 { + pin.set_low(); + pin.conf().write(|_| ()); + r.psel().out(1).write_value(DISCONNECTED); + } + if let Some(pin) = &self.ch2 { + pin.set_low(); + pin.conf().write(|_| ()); + r.psel().out(2).write_value(DISCONNECTED); + } + if let Some(pin) = &self.ch3 { + pin.set_low(); + pin.conf().write(|_| ()); + r.psel().out(3).write_value(DISCONNECTED); + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::pwm::Pwm; +} + +/// PWM peripheral instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_pwm { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::pwm::SealedInstance for peripherals::$type { + fn regs() -> pac::pwm::Pwm { + pac::$pac_type + } + } + impl crate::pwm::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy/embassy-nrf/src/qdec.rs b/embassy/embassy-nrf/src/qdec.rs new file mode 100644 index 0000000..efd2a13 --- /dev/null +++ b/embassy/embassy-nrf/src/qdec.rs @@ -0,0 +1,295 @@ +//! Quadrature decoder (QDEC) driver. + +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::gpio::{AnyPin, Pin as GpioPin, SealedPin as _}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::qdec::vals; +use crate::{interrupt, pac, Peripheral}; + +/// Quadrature decoder driver. +pub struct Qdec<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +/// 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, +} + +impl Default for Config { + fn default() -> Self { + Self { + num_samples: NumSamples::_1smpl, + period: SamplePeriod::_256us, + led_polarity: LedPolarity::ActiveHigh, + debounce: true, + led_pre_usecs: 0, + } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + T::regs().intenclr().write(|w| w.set_reportrdy(true)); + T::state().waker.wake(); + } +} + +impl<'d, T: Instance> Qdec<'d, T> { + /// Create a new QDEC. + pub fn new( + qdec: impl Peripheral

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

+ 'd, + b: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(qdec, a, b); + Self::new_inner(qdec, 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 interrupt::typelevel::Binding> + 'd, + a: impl Peripheral

+ 'd, + b: impl Peripheral

+ 'd, + led: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(qdec, a, b, led); + Self::new_inner(qdec, a.map_into(), b.map_into(), Some(led.map_into()), config) + } + + fn new_inner( + p: PeripheralRef<'d, T>, + a: PeripheralRef<'d, AnyPin>, + b: PeripheralRef<'d, AnyPin>, + led: Option>, + config: Config, + ) -> Self { + let r = T::regs(); + + // Select pins. + a.conf().write(|w| { + w.set_input(gpiovals::Input::CONNECT); + w.set_pull(gpiovals::Pull::PULLUP); + }); + b.conf().write(|w| { + w.set_input(gpiovals::Input::CONNECT); + w.set_pull(gpiovals::Pull::PULLUP); + }); + r.psel().a().write_value(a.psel_bits()); + r.psel().b().write_value(b.psel_bits()); + if let Some(led_pin) = &led { + led_pin.conf().write(|w| w.set_dir(gpiovals::Dir::OUTPUT)); + r.psel().led().write_value(led_pin.psel_bits()); + } + + // Enables/disable input debounce filters + r.dbfen().write(|w| match config.debounce { + true => w.set_dbfen(true), + false => w.set_dbfen(false), + }); + + // Set LED output pin polarity + r.ledpol().write(|w| match config.led_polarity { + LedPolarity::ActiveHigh => w.set_ledpol(vals::Ledpol::ACTIVE_HIGH), + LedPolarity::ActiveLow => w.set_ledpol(vals::Ledpol::ACTIVE_LOW), + }); + + // Set time period the LED is switched ON prior to sampling (0..511 us). + r.ledpre().write(|w| w.set_ledpre(config.led_pre_usecs.min(511))); + + // Set sample period + r.sampleper().write(|w| match config.period { + SamplePeriod::_128us => w.set_sampleper(vals::Sampleper::_128US), + SamplePeriod::_256us => w.set_sampleper(vals::Sampleper::_256US), + SamplePeriod::_512us => w.set_sampleper(vals::Sampleper::_512US), + SamplePeriod::_1024us => w.set_sampleper(vals::Sampleper::_1024US), + SamplePeriod::_2048us => w.set_sampleper(vals::Sampleper::_2048US), + SamplePeriod::_4096us => w.set_sampleper(vals::Sampleper::_4096US), + SamplePeriod::_8192us => w.set_sampleper(vals::Sampleper::_8192US), + SamplePeriod::_16384us => w.set_sampleper(vals::Sampleper::_16384US), + SamplePeriod::_32ms => w.set_sampleper(vals::Sampleper::_32MS), + SamplePeriod::_65ms => w.set_sampleper(vals::Sampleper::_65MS), + SamplePeriod::_131ms => w.set_sampleper(vals::Sampleper::_131MS), + }); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + // Enable peripheral + r.enable().write(|w| w.set_enable(true)); + + // Start sampling + r.tasks_start().write_value(1); + + Self { _p: p } + } + + /// Perform an asynchronous read of the decoder. + /// The returned future can be awaited to obtain the number of steps. + /// + /// If the future is dropped, the read is cancelled. + /// + /// # Example + /// + /// ```no_run + /// use embassy_nrf::qdec::{self, Qdec}; + /// use embassy_nrf::{bind_interrupts, peripherals}; + /// + /// bind_interrupts!(struct Irqs { + /// QDEC => qdec::InterruptHandler; + /// }); + /// + /// # async { + /// # let p: embassy_nrf::Peripherals = todo!(); + /// let config = qdec::Config::default(); + /// let mut q = Qdec::new(p.QDEC, Irqs, p.P0_31, p.P0_30, config); + /// let delta = q.read().await; + /// # }; + /// ``` + pub async fn read(&mut self) -> i16 { + let t = T::regs(); + t.intenset().write(|w| w.set_reportrdy(true)); + t.tasks_readclracc().write_value(1); + + poll_fn(|cx| { + T::state().waker.register(cx.waker()); + if t.events_reportrdy().read() == 0 { + Poll::Pending + } else { + t.events_reportrdy().write_value(0); + let acc = t.accread().read(); + Poll::Ready(acc as i16) + } + }) + .await + } +} + +/// 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, +} + +/// Peripheral static state +pub(crate) struct State { + waker: AtomicWaker, +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::qdec::Qdec; + fn state() -> &'static State; +} + +/// qdec peripheral instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_qdec { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::qdec::SealedInstance for peripherals::$type { + fn regs() -> pac::qdec::Qdec { + pac::$pac_type + } + fn state() -> &'static crate::qdec::State { + static STATE: crate::qdec::State = crate::qdec::State::new(); + &STATE + } + } + impl crate::qdec::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy/embassy-nrf/src/qspi.rs b/embassy/embassy-nrf/src/qspi.rs new file mode 100755 index 0000000..255b43c --- /dev/null +++ b/embassy/embassy-nrf/src/qspi.rs @@ -0,0 +1,688 @@ +//! Quad Serial Peripheral Interface (QSPI) flash driver. + +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ptr; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; + +use crate::gpio::{self, Pin as GpioPin}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::qspi::vals; +pub use crate::pac::qspi::vals::{ + Addrmode as AddressMode, Ppsize as WritePageSize, Readoc as ReadOpcode, Spimode as SpiMode, Writeoc as WriteOpcode, +}; +use crate::{interrupt, pac, Peripheral}; + +/// Deep power-down config. +pub struct DeepPowerDownConfig { + /// Time required for entering DPM, in units of 16us + pub enter_time: u16, + /// Time required for exiting DPM, in units of 16us + 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, + /// Value is specified in number of 64 MHz periods (15.625 ns), valid values between 0 and 7 (inclusive) + pub rx_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, + /// Flash memory capacity in bytes. This is the value reported by the `embedded-storage` traits. + pub capacity: u32, +} + +impl Default for Config { + fn default() -> Self { + Self { + read_opcode: ReadOpcode::READ4IO, + write_opcode: WriteOpcode::PP4IO, + xip_offset: 0, + write_page_size: WritePageSize::_256BYTES, + deep_power_down: None, + frequency: Frequency::M8, + sck_delay: 80, + rx_delay: 2, + spi_mode: SpiMode::MODE0, + address_mode: AddressMode::_24BIT, + capacity: 0, + } + } +} + +/// 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 +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + if r.events_ready().read() != 0 { + s.waker.wake(); + r.intenclr().write(|w| w.set_ready(true)); + } + } +} + +/// QSPI flash driver. +pub struct Qspi<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, + dpm_enabled: bool, + capacity: u32, +} + +impl<'d, T: Instance> Qspi<'d, T> { + /// Create a new QSPI driver. + pub fn new( + qspi: impl Peripheral

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

+ 'd, + csn: impl Peripheral

+ 'd, + io0: impl Peripheral

+ 'd, + io1: impl Peripheral

+ 'd, + io2: impl Peripheral

+ 'd, + io3: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(qspi, sck, csn, io0, io1, io2, io3); + + let r = T::regs(); + + macro_rules! config_pin { + ($pin:ident) => { + $pin.set_high(); + $pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_drive(gpiovals::Drive::H0H1); + #[cfg(all(feature = "_nrf5340", feature = "_s"))] + w.set_mcusel(gpiovals::Mcusel::PERIPHERAL); + }); + r.psel().$pin().write_value($pin.psel_bits()); + }; + } + + config_pin!(sck); + config_pin!(csn); + config_pin!(io0); + config_pin!(io1); + config_pin!(io2); + config_pin!(io3); + + r.ifconfig0().write(|w| { + w.set_addrmode(config.address_mode); + w.set_dpmenable(config.deep_power_down.is_some()); + w.set_ppsize(config.write_page_size); + w.set_readoc(config.read_opcode); + w.set_writeoc(config.write_opcode); + }); + + if let Some(dpd) = &config.deep_power_down { + r.dpmdur().write(|w| { + w.set_enter(dpd.enter_time); + w.set_exit(dpd.exit_time); + }) + } + + r.ifconfig1().write(|w| { + w.set_sckdelay(config.sck_delay); + w.set_dpmen(false); + w.set_spimode(config.spi_mode); + w.set_sckfreq(config.frequency as u8); + }); + + r.iftiming().write(|w| { + w.set_rxdelay(config.rx_delay & 0b111); + }); + + r.xipoffset().write_value(config.xip_offset); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + // Enable it + r.enable().write(|w| w.set_enable(true)); + + let res = Self { + _peri: qspi, + dpm_enabled: config.deep_power_down.is_some(), + capacity: config.capacity, + }; + + r.events_ready().write_value(0); + r.intenset().write(|w| w.set_ready(true)); + + r.tasks_activate().write_value(1); + + Self::blocking_wait_ready(); + + res + } + + /// Do a custom QSPI instruction. + pub async fn custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> { + 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)?; + + self.wait_ready().await; + + self.custom_instruction_finish(resp)?; + + 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.custom_instruction_finish(resp)?; + + Ok(()) + } + + fn custom_instruction_start(&mut self, opcode: u8, req: &[u8], len: u8) -> Result<(), Error> { + assert!(req.len() <= 8); + + let mut dat0: u32 = 0; + let mut dat1: u32 = 0; + + for i in 0..4 { + if i < req.len() { + dat0 |= (req[i] as u32) << (i * 8); + } + } + for i in 0..4 { + if i + 4 < req.len() { + dat1 |= (req[i + 4] as u32) << (i * 8); + } + } + + let r = T::regs(); + r.cinstrdat0().write(|w| w.0 = dat0); + r.cinstrdat1().write(|w| w.0 = dat1); + + r.events_ready().write_value(0); + r.intenset().write(|w| w.set_ready(true)); + + r.cinstrconf().write(|w| { + w.set_opcode(opcode); + w.set_length(vals::Length::from_bits(len + 1)); + w.set_lio2(true); + w.set_lio3(true); + w.set_wipwait(true); + w.set_wren(true); + w.set_lfen(false); + w.set_lfstop(false); + }); + Ok(()) + } + + fn custom_instruction_finish(&mut self, resp: &mut [u8]) -> Result<(), Error> { + let r = T::regs(); + + let dat0 = r.cinstrdat0().read().0; + let dat1 = r.cinstrdat1().read().0; + for i in 0..4 { + if i < resp.len() { + resp[i] = (dat0 >> (i * 8)) as u8; + } + } + for i in 0..4 { + if i + 4 < resp.len() { + resp[i] = (dat1 >> (i * 8)) as u8; + } + } + Ok(()) + } + + async fn wait_ready(&mut self) { + poll_fn(move |cx| { + let r = T::regs(); + let s = T::state(); + s.waker.register(cx.waker()); + if r.events_ready().read() != 0 { + return Poll::Ready(()); + } + Poll::Pending + }) + .await + } + + fn blocking_wait_ready() { + loop { + let r = T::regs(); + if r.events_ready().read() != 0 { + break; + } + } + } + + fn start_read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { + // TODO: Return these as errors instead. + assert_eq!(data.as_ptr() as u32 % 4, 0); + assert_eq!(data.len() as u32 % 4, 0); + assert_eq!(address % 4, 0); + + let r = T::regs(); + + r.read().src().write_value(address); + r.read().dst().write_value(data.as_ptr() as u32); + r.read().cnt().write(|w| w.set_cnt(data.len() as u32)); + + r.events_ready().write_value(0); + r.intenset().write(|w| w.set_ready(true)); + r.tasks_readstart().write_value(1); + + Ok(()) + } + + fn start_write(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { + // TODO: Return these as errors instead. + assert_eq!(data.as_ptr() as u32 % 4, 0); + assert_eq!(data.len() as u32 % 4, 0); + assert_eq!(address % 4, 0); + + let r = T::regs(); + r.write().src().write_value(data.as_ptr() as u32); + r.write().dst().write_value(address); + r.write().cnt().write(|w| w.set_cnt(data.len() as u32)); + + r.events_ready().write_value(0); + r.intenset().write(|w| w.set_ready(true)); + r.tasks_writestart().write_value(1); + + Ok(()) + } + + fn start_erase(&mut self, address: u32) -> Result<(), Error> { + // TODO: Return these as errors instead. + assert_eq!(address % 4096, 0); + + let r = T::regs(); + r.erase().ptr().write_value(address); + r.erase().len().write(|w| w.set_len(vals::Len::_4KB)); + + r.events_ready().write_value(0); + r.intenset().write(|w| w.set_ready(true)); + r.tasks_erasestart().write_value(1); + + Ok(()) + } + + /// Raw QSPI read. + /// + /// The difference with `read` is that this does not do bounds checks + /// against the flash capacity. It is intended for use when QSPI is used as + /// a raw bus, not with flash memory. + pub async fn read_raw(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { + // Avoid blocking_wait_ready() blocking forever on zero-length buffers. + if data.is_empty() { + return Ok(()); + } + + let ondrop = OnDrop::new(Self::blocking_wait_ready); + + self.start_read(address, data)?; + self.wait_ready().await; + + ondrop.defuse(); + + Ok(()) + } + + /// Raw QSPI write. + /// + /// The difference with `write` is that this does not do bounds checks + /// against the flash capacity. It is intended for use when QSPI is used as + /// a raw bus, not with flash memory. + pub async fn write_raw(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { + // Avoid blocking_wait_ready() blocking forever on zero-length buffers. + if data.is_empty() { + return Ok(()); + } + + let ondrop = OnDrop::new(Self::blocking_wait_ready); + + self.start_write(address, data)?; + self.wait_ready().await; + + ondrop.defuse(); + + Ok(()) + } + + /// Raw QSPI read, blocking version. + /// + /// The difference with `blocking_read` is that this does not do bounds checks + /// against the flash capacity. It is intended for use when QSPI is used as + /// a raw bus, not with flash memory. + pub fn blocking_read_raw(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { + // Avoid blocking_wait_ready() blocking forever on zero-length buffers. + if data.is_empty() { + return Ok(()); + } + + self.start_read(address, data)?; + Self::blocking_wait_ready(); + Ok(()) + } + + /// Raw QSPI write, blocking version. + /// + /// The difference with `blocking_write` is that this does not do bounds checks + /// against the flash capacity. It is intended for use when QSPI is used as + /// a raw bus, not with flash memory. + pub fn blocking_write_raw(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { + // Avoid blocking_wait_ready() blocking forever on zero-length buffers. + if data.is_empty() { + return Ok(()); + } + + self.start_write(address, data)?; + Self::blocking_wait_ready(); + Ok(()) + } + + /// Read data from the flash memory. + pub async fn read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { + self.bounds_check(address, data.len())?; + self.read_raw(address, data).await + } + + /// Write data to the flash memory. + pub async fn write(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { + self.bounds_check(address, data.len())?; + self.write_raw(address, data).await + } + + /// Erase a sector on the flash memory. + pub async fn erase(&mut self, address: u32) -> Result<(), Error> { + if address >= self.capacity { + return Err(Error::OutOfBounds); + } + + let ondrop = OnDrop::new(Self::blocking_wait_ready); + + self.start_erase(address)?; + self.wait_ready().await; + + ondrop.defuse(); + + Ok(()) + } + + /// Read data from the flash memory, blocking version. + pub fn blocking_read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { + self.bounds_check(address, data.len())?; + self.blocking_read_raw(address, data) + } + + /// Write data to the flash memory, blocking version. + pub fn blocking_write(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { + self.bounds_check(address, data.len())?; + self.blocking_write_raw(address, data) + } + + /// Erase a sector on the flash memory, blocking version. + pub fn blocking_erase(&mut self, address: u32) -> Result<(), Error> { + if address >= self.capacity { + return Err(Error::OutOfBounds); + } + + self.start_erase(address)?; + Self::blocking_wait_ready(); + Ok(()) + } + + fn bounds_check(&self, address: u32, len: usize) -> Result<(), Error> { + let len_u32: u32 = len.try_into().map_err(|_| Error::OutOfBounds)?; + let end_address = address.checked_add(len_u32).ok_or(Error::OutOfBounds)?; + if end_address > self.capacity { + return Err(Error::OutOfBounds); + } + Ok(()) + } +} + +impl<'d, T: Instance> Drop for Qspi<'d, T> { + fn drop(&mut self) { + let r = T::regs(); + + if self.dpm_enabled { + trace!("qspi: doing deep powerdown..."); + + r.ifconfig1().modify(|w| w.set_dpmen(true)); + + // Wait for DPM enter. + // Unfortunately we must spin. There's no way to do this interrupt-driven. + // The READY event does NOT fire on DPM enter (but it does fire on DPM exit :shrug:) + while !r.status().read().dpm() {} + + // Wait MORE for DPM enter. + // I have absolutely no idea why, but the wait above is not enough :'( + // Tested with mx25r64 in nrf52840-dk, and with mx25r16 in custom board + cortex_m::asm::delay(4096); + } + + // it seems events_ready is not generated in response to deactivate. nrfx doesn't wait for it. + r.tasks_deactivate().write_value(1); + + // Workaround https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_122.html + // Note that the doc has 2 register writes, but the first one is really the write to tasks_deactivate, + // so we only do the second one here. + unsafe { ptr::write_volatile(0x40029054 as *mut u32, 1) } + + r.enable().write(|w| w.set_enable(false)); + + // Note: we do NOT deconfigure CSN here. If DPM is in use and we disconnect CSN, + // leaving it floating, the flash chip might read it as zero which would cause it to + // spuriously exit DPM. + gpio::deconfigure_pin(r.psel().sck().read()); + gpio::deconfigure_pin(r.psel().io0().read()); + gpio::deconfigure_pin(r.psel().io1().read()); + gpio::deconfigure_pin(r.psel().io2().read()); + gpio::deconfigure_pin(r.psel().io3().read()); + + trace!("qspi: dropped"); + } +} + +impl<'d, T: Instance> ErrorType for Qspi<'d, T> { + type Error = Error; +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + NorFlashErrorKind::Other + } +} + +impl<'d, T: Instance> ReadNorFlash for Qspi<'d, T> { + const READ_SIZE: usize = 4; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(offset, bytes)?; + Ok(()) + } + + fn capacity(&self) -> usize { + self.capacity as usize + } +} + +impl<'d, T: Instance> NorFlash for Qspi<'d, T> { + const WRITE_SIZE: usize = 4; + const ERASE_SIZE: usize = 4096; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + for address in (from..to).step_by(::ERASE_SIZE) { + self.blocking_erase(address)?; + } + Ok(()) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(offset, bytes)?; + Ok(()) + } +} + +#[cfg(feature = "qspi-multiwrite-flash")] +impl<'d, T: Instance> embedded_storage::nor_flash::MultiwriteNorFlash for Qspi<'d, T> {} + +mod _eh1 { + use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + + use super::*; + + impl<'d, T: Instance> AsyncNorFlash for Qspi<'d, T> { + const WRITE_SIZE: usize = ::WRITE_SIZE; + const ERASE_SIZE: usize = ::ERASE_SIZE; + + async fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.write(offset, data).await + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + for address in (from..to).step_by(::ERASE_SIZE) { + self.erase(address).await? + } + Ok(()) + } + } + + impl<'d, T: Instance> AsyncReadNorFlash for Qspi<'d, T> { + const READ_SIZE: usize = 4; + async fn read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.read(address, data).await + } + + fn capacity(&self) -> usize { + self.capacity as usize + } + } + + #[cfg(feature = "qspi-multiwrite-flash")] + impl<'d, T: Instance> embedded_storage_async::nor_flash::MultiwriteNorFlash for Qspi<'d, T> {} +} + +/// Peripheral static state +pub(crate) struct State { + waker: AtomicWaker, +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::qspi::Qspi; + fn state() -> &'static State; +} + +/// QSPI peripheral instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_qspi { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::qspi::SealedInstance for peripherals::$type { + fn regs() -> pac::qspi::Qspi { + pac::$pac_type + } + fn state() -> &'static crate::qspi::State { + static STATE: crate::qspi::State = crate::qspi::State::new(); + &STATE + } + } + impl crate::qspi::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy/embassy-nrf/src/radio/ble.rs b/embassy/embassy-nrf/src/radio/ble.rs new file mode 100644 index 0000000..682ca1c --- /dev/null +++ b/embassy/embassy-nrf/src/radio/ble.rs @@ -0,0 +1,396 @@ +//! Radio driver implementation focused on Bluetooth Low-Energy transmission. + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +pub use pac::radio::vals::Mode; +#[cfg(not(feature = "_nrf51"))] +use pac::radio::vals::Plen as PreambleLength; + +use crate::interrupt::typelevel::Interrupt; +use crate::pac::radio::vals; +use crate::radio::*; +pub use crate::radio::{Error, TxPower}; +use crate::util::slice_in_ram_or; + +/// Radio driver. +pub struct Radio<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Radio<'d, T> { + /// Create a new radio driver. + pub fn new( + radio: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + into_ref!(radio); + + let r = T::regs(); + + r.pcnf1().write(|w| { + // It is 0 bytes long in a standard BLE packet + w.set_statlen(0); + // MaxLen configures the maximum packet payload plus add-on size in + // number of bytes that can be transmitted or received by the RADIO. This feature can be used to ensure + // that the RADIO does not overwrite, or read beyond, the RAM assigned to the packet payload. This means + // that if the packet payload length defined by PCNF1.STATLEN and the LENGTH field in the packet specifies a + // packet larger than MAXLEN, the payload will be truncated at MAXLEN + // + // To simplify the implementation, It is setted as the maximum value + // and the length of the packet is controlled only by the LENGTH field in the packet + w.set_maxlen(255); + // Configure the length of the address field in the packet + // The prefix after the address fields is always appended, so is always 1 byte less than the size of the address + // The base address is truncated from the least significant byte if the BALEN is less than 4 + // + // BLE address is always 4 bytes long + w.set_balen(3); // 3 bytes base address (+ 1 prefix); + // Configure the endianess + // For BLE is always little endian (LSB first) + w.set_endian(vals::Endian::LITTLE); + // Data whitening is used to avoid long sequences of zeros or + // ones, e.g., 0b0000000 or 0b1111111, in the data bit stream. + // The whitener and de-whitener are defined the same way, + // using a 7-bit linear feedback shift register with the + // polynomial x7 + x4 + 1. + // + // In BLE Whitening shall be applied on the PDU and CRC of all + // Link Layer packets and is performed after the CRC generation + // in the transmitter. No other parts of the packets are whitened. + // De-whitening is performed before the CRC checking in the receiver + // Before whitening or de-whitening, the shift register should be + // initialized based on the channel index. + w.set_whiteen(true); + }); + + // Configure CRC + r.crccnf().write(|w| { + // In BLE the CRC shall be calculated on the PDU of all Link Layer + // packets (even if the packet is encrypted). + // It skips the address field + w.set_skipaddr(vals::Skipaddr::SKIP); + // In BLE 24-bit CRC = 3 bytes + w.set_len(vals::Len::THREE); + }); + + // Ch map between 2400 MHZ .. 2500 MHz + // All modes use this range + #[cfg(not(feature = "_nrf51"))] + r.frequency().write(|w| w.set_map(vals::Map::DEFAULT)); + + // Configure shortcuts to simplify and speed up sending and receiving packets. + r.shorts().write(|w| { + // start transmission/recv immediately after ramp-up + // disable radio when transmission/recv is done + w.set_ready_start(true); + w.set_end_disable(true); + }); + + // Enable NVIC interrupt + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + Self { _p: radio } + } + + fn state(&self) -> RadioState { + super::state(T::regs()) + } + + /// Set the radio mode + /// + /// The radio must be disabled before calling this function + pub fn set_mode(&mut self, mode: Mode) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + r.mode().write(|w| w.set_mode(mode)); + + #[cfg(not(feature = "_nrf51"))] + r.pcnf0().write(|w| { + w.set_plen(match mode { + Mode::BLE_1MBIT => PreambleLength::_8BIT, + Mode::BLE_2MBIT => PreambleLength::_16BIT, + #[cfg(any( + feature = "nrf52811", + feature = "nrf52820", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-net" + ))] + Mode::BLE_LR125KBIT | Mode::BLE_LR500KBIT => PreambleLength::LONG_RANGE, + _ => unimplemented!(), + }) + }); + } + + /// Set the header size changing the S1's len field + /// + /// The radio must be disabled before calling this function + pub fn set_header_expansion(&mut self, use_s1_field: bool) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + // s1 len in bits + let s1len: u8 = match use_s1_field { + false => 0, + true => 8, + }; + + r.pcnf0().write(|w| { + // Configure S0 to 1 byte length, this will represent the Data/Adv header flags + w.set_s0len(true); + // Configure the length (in bits) field to 1 byte length, this will represent the length of the payload + // and also be used to know how many bytes to read/write from/to the buffer + w.set_lflen(0); + // Configure the lengh (in bits) of bits in the S1 field. It could be used to represent the CTEInfo for data packages in BLE. + w.set_s1len(s1len); + }); + } + + /// Set initial data whitening value + /// Data whitening is used to avoid long sequences of zeros or ones, e.g., 0b0000000 or 0b1111111, in the data bit stream + /// On BLE the initial value is the channel index | 0x40 + /// + /// The radio must be disabled before calling this function + pub fn set_whitening_init(&mut self, whitening_init: u8) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + r.datawhiteiv().write(|w| w.set_datawhiteiv(whitening_init)); + } + + /// Set the central frequency to be used + /// It should be in the range 2400..2500 + /// + /// [The radio must be disabled before calling this function](https://devzone.nordicsemi.com/f/nordic-q-a/15829/radio-frequency-change) + pub fn set_frequency(&mut self, frequency: u32) { + assert!(self.state() == RadioState::DISABLED); + assert!((2400..=2500).contains(&frequency)); + + let r = T::regs(); + + r.frequency().write(|w| w.set_frequency((frequency - 2400) as u8)); + } + + /// Set the acess address + /// This address is always constants for advertising + /// And a random value generate on each connection + /// It is used to filter the packages + /// + /// The radio must be disabled before calling this function + pub fn set_access_address(&mut self, access_address: u32) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + // Configure logical address + // The byte ordering on air is always least significant byte first for the address + // So for the address 0xAA_BB_CC_DD, the address on air will be DD CC BB AA + // The package order is BASE, PREFIX so BASE=0xBB_CC_DD and PREFIX=0xAA + r.prefix0().write(|w| w.set_ap0((access_address >> 24) as u8)); + + // The base address is truncated from the least significant byte (because the BALEN is less than 4) + // So it shifts the address to the right + r.base0().write_value(access_address << 8); + + // Don't match tx address + r.txaddress().write(|w| w.set_txaddress(0)); + + // Match on logical address + // This config only filter the packets by the address, + // so only packages send to the previous address + // will finish the reception (TODO: check the explanation) + r.rxaddresses().write(|w| { + w.set_addr0(true); + w.set_addr1(true); + w.set_addr2(true); + w.set_addr3(true); + w.set_addr4(true); + }); + } + + /// Set the CRC polynomial + /// It only uses the 24 least significant bits + /// + /// The radio must be disabled before calling this function + pub fn set_crc_poly(&mut self, crc_poly: u32) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + r.crcpoly().write(|w| { + // Configure the CRC polynomial + // Each term in the CRC polynomial is mapped to a bit in this + // register which index corresponds to the term's exponent. + // The least significant term/bit is hard-wired internally to + // 1, and bit number 0 of the register content is ignored by + // the hardware. The following example is for an 8 bit CRC + // polynomial: x8 + x7 + x3 + x2 + 1 = 1 1000 1101 . + w.set_crcpoly(crc_poly & 0xFFFFFF) + }); + } + + /// Set the CRC init value + /// It only uses the 24 least significant bits + /// The CRC initial value varies depending of the PDU type + /// + /// The radio must be disabled before calling this function + pub fn set_crc_init(&mut self, crc_init: u32) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + r.crcinit().write(|w| w.set_crcinit(crc_init & 0xFFFFFF)); + } + + /// Set the radio tx power + /// + /// The radio must be disabled before calling this function + pub fn set_tx_power(&mut self, tx_power: TxPower) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + r.txpower().write(|w| w.set_txpower(tx_power)); + } + + /// Set buffer to read/write + /// + /// This method is unsound. You should guarantee that the buffer will live + /// for the life time of the transmission or if the buffer will be modified. + /// Also if the buffer is smaller than the packet length, the radio will + /// read/write memory out of the buffer bounds. + fn set_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; + + let r = T::regs(); + + // Here it consider that the length of the packet is + // correctly set in the buffer, otherwise it will send + // unowned regions of memory + let ptr = buffer.as_ptr(); + + // Configure the payload + r.packetptr().write_value(ptr as u32); + + Ok(()) + } + + /// Send packet + /// If the length byte in the package is greater than the buffer length + /// the radio will read memory out of the buffer bounds + pub async fn transmit(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.set_buffer(buffer)?; + + let r = T::regs(); + self.trigger_and_wait_end(move || { + // Initialize the transmission + // trace!("txen"); + + r.tasks_txen().write_value(1); + }) + .await; + + Ok(()) + } + + /// Receive packet + /// If the length byte in the received package is greater than the buffer length + /// the radio will write memory out of the buffer bounds + pub async fn receive(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.set_buffer(buffer)?; + + let r = T::regs(); + self.trigger_and_wait_end(move || { + // Initialize the transmission + // trace!("rxen"); + r.tasks_rxen().write_value(1); + }) + .await; + + Ok(()) + } + + async fn trigger_and_wait_end(&mut self, trigger: impl FnOnce()) { + let r = T::regs(); + let s = T::state(); + + // If the Future is dropped before the end of the transmission + // it disable the interrupt and stop the transmission + // to keep the state consistent + let drop = OnDrop::new(|| { + trace!("radio drop: stopping"); + + r.intenclr().write(|w| w.set_end(true)); + + r.tasks_stop().write_value(1); + + r.events_end().write_value(0); + + trace!("radio drop: stopped"); + }); + + // trace!("radio:enable interrupt"); + // Clear some remnant side-effects (TODO: check if this is necessary) + r.events_end().write_value(0); + + // Enable interrupt + r.intenset().write(|w| w.set_end(true)); + + compiler_fence(Ordering::SeqCst); + + // Trigger the transmission + trigger(); + + // On poll check if interrupt happen + poll_fn(|cx| { + s.event_waker.register(cx.waker()); + if r.events_end().read() == 1 { + // trace!("radio:end"); + return core::task::Poll::Ready(()); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + r.events_end().write_value(0); // ACK + + // Everthing ends fine, so it disable the drop + drop.defuse(); + } + + /// Disable the radio + fn disable(&mut self) { + let r = T::regs(); + + compiler_fence(Ordering::SeqCst); + // If it is already disabled, do nothing + if self.state() != RadioState::DISABLED { + trace!("radio:disable"); + // Trigger the disable task + r.tasks_disable().write_value(1); + + // Wait until the radio is disabled + while r.events_disabled().read() == 0 {} + + compiler_fence(Ordering::SeqCst); + + // Acknowledge it + r.events_disabled().write_value(0); + } + } +} + +impl<'d, T: Instance> Drop for Radio<'d, T> { + fn drop(&mut self) { + self.disable(); + } +} diff --git a/embassy/embassy-nrf/src/radio/ieee802154.rs b/embassy/embassy-nrf/src/radio/ieee802154.rs new file mode 100644 index 0000000..083842f --- /dev/null +++ b/embassy/embassy-nrf/src/radio/ieee802154.rs @@ -0,0 +1,539 @@ +//! IEEE 802.15.4 radio driver + +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::{state, Error, Instance, InterruptHandler, RadioState, TxPower}; +use crate::interrupt::typelevel::Interrupt; +use crate::interrupt::{self}; +use crate::pac::radio::vals; +use crate::Peripheral; + +/// Default (IEEE compliant) Start of Frame Delimiter +pub const DEFAULT_SFD: u8 = 0xA7; + +// TODO expose the other variants in `pac::CCAMODE_A` +/// Clear Channel Assessment method +pub enum Cca { + /// Carrier sense + CarrierSense, + /// Energy Detection / Energy Above Threshold + EnergyDetection { + /// Energy measurements above this value mean that the channel is assumed to be busy. + /// Note the measurement range is 0..0xFF - where 0 means that the received power was + /// less than 10 dB above the selected receiver sensitivity. This value is not given in dBm, + /// but can be converted. See the nrf52840 Product Specification Section 6.20.12.4 + /// for details. + ed_threshold: u8, + }, +} + +/// IEEE 802.15.4 radio driver. +pub struct Radio<'d, T: Instance> { + _p: PeripheralRef<'d, T>, + needs_enable: bool, +} + +impl<'d, T: Instance> Radio<'d, T> { + /// Create a new IEEE 802.15.4 radio driver. + pub fn new( + radio: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + into_ref!(radio); + + let r = T::regs(); + + // Disable and enable to reset peripheral + r.power().write(|w| w.set_power(false)); + r.power().write(|w| w.set_power(true)); + + // Enable 802.15.4 mode + r.mode().write(|w| w.set_mode(vals::Mode::IEEE802154_250KBIT)); + // Configure CRC skip address + r.crccnf().write(|w| { + w.set_len(vals::Len::TWO); + w.set_skipaddr(vals::Skipaddr::IEEE802154); + }); + // Configure CRC polynomial and init + r.crcpoly().write(|w| w.set_crcpoly(0x0001_1021)); + r.crcinit().write(|w| w.set_crcinit(0)); + r.pcnf0().write(|w| { + // 8-bit on air length + w.set_lflen(8); + // Zero bytes S0 field length + w.set_s0len(false); + // Zero bytes S1 field length + w.set_s1len(0); + // Do not include S1 field in RAM if S1 length > 0 + w.set_s1incl(vals::S1incl::AUTOMATIC); + // Zero code Indicator length + w.set_cilen(0); + // 32-bit zero preamble + w.set_plen(vals::Plen::_32BIT_ZERO); + // Include CRC in length + w.set_crcinc(vals::Crcinc::INCLUDE); + }); + r.pcnf1().write(|w| { + // Maximum packet length + w.set_maxlen(Packet::MAX_PSDU_LEN); + // Zero static length + w.set_statlen(0); + // Zero base address length + w.set_balen(0); + // Little-endian + w.set_endian(vals::Endian::LITTLE); + // Disable packet whitening + w.set_whiteen(false); + }); + + // Enable NVIC interrupt + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let mut radio = Self { + _p: radio, + needs_enable: false, + }; + + radio.set_sfd(DEFAULT_SFD); + radio.set_transmission_power(0); + radio.set_channel(11); + radio.set_cca(Cca::CarrierSense); + + radio + } + + /// Changes the radio channel + pub fn set_channel(&mut self, channel: u8) { + let r = T::regs(); + if channel < 11 || channel > 26 { + panic!("Bad 802.15.4 channel"); + } + let frequency_offset = (channel - 10) * 5; + self.needs_enable = true; + r.frequency().write(|w| { + w.set_frequency(frequency_offset); + w.set_map(vals::Map::DEFAULT); + }); + } + + /// Changes the Clear Channel Assessment method + pub fn set_cca(&mut self, cca: Cca) { + let r = T::regs(); + self.needs_enable = true; + match cca { + Cca::CarrierSense => r.ccactrl().write(|w| w.set_ccamode(vals::Ccamode::CARRIER_MODE)), + Cca::EnergyDetection { ed_threshold } => { + // "[ED] is enabled by first configuring the field CCAMODE=EdMode in CCACTRL + // and writing the CCAEDTHRES field to a chosen value." + r.ccactrl().write(|w| { + w.set_ccamode(vals::Ccamode::ED_MODE); + w.set_ccaedthres(ed_threshold); + }); + } + } + } + + /// Changes the Start of Frame Delimiter (SFD) + pub fn set_sfd(&mut self, sfd: u8) { + let r = T::regs(); + r.sfd().write(|w| w.set_sfd(sfd)); + } + + /// Clear interrupts + pub fn clear_all_interrupts(&mut self) { + let r = T::regs(); + r.intenclr().write(|w| w.0 = 0xffff_ffff); + } + + /// Changes the radio transmission power + pub fn set_transmission_power(&mut self, power: i8) { + let r = T::regs(); + self.needs_enable = true; + + let tx_power: TxPower = match power { + #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] + 8 => TxPower::POS8_DBM, + #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] + 7 => TxPower::POS7_DBM, + #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] + 6 => TxPower::POS6_DBM, + #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] + 5 => TxPower::POS5_DBM, + #[cfg(not(feature = "_nrf5340-net"))] + 4 => TxPower::POS4_DBM, + #[cfg(not(feature = "_nrf5340-net"))] + 3 => TxPower::POS3_DBM, + #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] + 2 => TxPower::POS2_DBM, + 0 => TxPower::_0_DBM, + #[cfg(feature = "_nrf5340-net")] + -1 => TxPower::NEG1_DBM, + #[cfg(feature = "_nrf5340-net")] + -2 => TxPower::NEG2_DBM, + #[cfg(feature = "_nrf5340-net")] + -3 => TxPower::NEG3_DBM, + -4 => TxPower::NEG4_DBM, + #[cfg(feature = "_nrf5340-net")] + -5 => TxPower::NEG5_DBM, + #[cfg(feature = "_nrf5340-net")] + -6 => TxPower::NEG6_DBM, + #[cfg(feature = "_nrf5340-net")] + -7 => TxPower::NEG7_DBM, + -8 => TxPower::NEG8_DBM, + -12 => TxPower::NEG12_DBM, + -16 => TxPower::NEG16_DBM, + -20 => TxPower::NEG20_DBM, + -30 => TxPower::NEG30_DBM, + -40 => TxPower::NEG40_DBM, + _ => panic!("Invalid transmission power value"), + }; + + r.txpower().write(|w| w.set_txpower(tx_power)); + } + + /// Waits until the radio state matches the given `state` + fn wait_for_radio_state(&self, state: RadioState) { + while self.state() != state {} + } + + /// Get the current radio state + fn state(&self) -> RadioState { + state(T::regs()) + } + + /// Moves the radio from any state to the DISABLED state + fn disable(&mut self) { + let r = T::regs(); + // See figure 110 in nRF52840-PS + loop { + match self.state() { + RadioState::DISABLED => return, + // idle or ramping up + RadioState::RX_RU | RadioState::RX_IDLE | RadioState::TX_RU | RadioState::TX_IDLE => { + r.tasks_disable().write_value(1); + self.wait_for_radio_state(RadioState::DISABLED); + return; + } + // ramping down + RadioState::RX_DISABLE | RadioState::TX_DISABLE => { + self.wait_for_radio_state(RadioState::DISABLED); + return; + } + // cancel ongoing transfer or ongoing CCA + RadioState::RX => { + r.tasks_ccastop().write_value(1); + r.tasks_stop().write_value(1); + self.wait_for_radio_state(RadioState::RX_IDLE); + } + RadioState::TX => { + r.tasks_stop().write_value(1); + self.wait_for_radio_state(RadioState::TX_IDLE); + } + _ => unreachable!(), + } + } + } + + fn set_buffer(&mut self, buffer: &[u8]) { + let r = T::regs(); + r.packetptr().write_value(buffer.as_ptr() as u32); + } + + /// Moves the radio to the RXIDLE state + fn receive_prepare(&mut self) { + // clear related events + T::regs().events_ccabusy().write_value(0); + T::regs().events_phyend().write_value(0); + // NOTE to avoid errata 204 (see rev1 v1.4) we do TX_IDLE -> DISABLED -> RXIDLE + let disable = match self.state() { + RadioState::DISABLED => false, + RadioState::RX_IDLE => self.needs_enable, + _ => true, + }; + if disable { + self.disable(); + } + self.needs_enable = false; + } + + /// Prepare radio for receiving a packet + fn receive_start(&mut self, packet: &mut Packet) { + // NOTE we do NOT check the address of `packet` because the mutable reference ensures it's + // allocated in RAM + let r = T::regs(); + + self.receive_prepare(); + + // Configure shortcuts + // + // The radio goes through following states when receiving a 802.15.4 packet + // + // enable RX → ramp up RX → RX idle → Receive → end (PHYEND) + r.shorts().write(|w| w.set_rxready_start(true)); + + // set up RX buffer + self.set_buffer(packet.buffer.as_mut()); + + // start transfer + dma_start_fence(); + + match self.state() { + // Re-start receiver + RadioState::RX_IDLE => r.tasks_start().write_value(1), + // Enable receiver + _ => r.tasks_rxen().write_value(1), + } + } + + /// Cancel receiving packet + fn receive_cancel() { + let r = T::regs(); + r.shorts().write(|_| {}); + r.tasks_stop().write_value(1); + loop { + match state(r) { + RadioState::DISABLED | RadioState::RX_IDLE => break, + _ => (), + } + } + // DMA transfer may have been in progress so synchronize with its memory operations + dma_end_fence(); + } + + /// Receives one radio packet and copies its contents into the given `packet` buffer + /// + /// This methods returns the `Ok` variant if the CRC included the packet was successfully + /// validated by the hardware; otherwise it returns the `Err` variant. In either case, `packet` + /// will be updated with the received packet's data + pub async fn receive(&mut self, packet: &mut Packet) -> Result<(), Error> { + let s = T::state(); + let r = T::regs(); + + // Start the read + self.receive_start(packet); + + let dropper = OnDrop::new(|| Self::receive_cancel()); + + self.clear_all_interrupts(); + // wait until we have received something + core::future::poll_fn(|cx| { + s.event_waker.register(cx.waker()); + + if r.events_phyend().read() != 0 { + r.events_phyend().write_value(0); + trace!("RX done poll"); + return Poll::Ready(()); + } else { + r.intenset().write(|w| w.set_phyend(true)); + }; + + Poll::Pending + }) + .await; + + dma_end_fence(); + dropper.defuse(); + + let crc = r.rxcrc().read().rxcrc() as u16; + if r.crcstatus().read().crcstatus() == vals::Crcstatus::CRCOK { + Ok(()) + } else { + Err(Error::CrcFailed(crc)) + } + } + + /// Tries to send the given `packet` + /// + /// This method performs Clear Channel Assessment (CCA) first and sends the `packet` only if the + /// channel is observed to be *clear* (no transmission is currently ongoing), otherwise no + /// packet is transmitted and the `Err` variant is returned + /// + /// NOTE this method will *not* modify the `packet` argument. The mutable reference is used to + /// ensure the `packet` buffer is allocated in RAM, which is required by the RADIO peripheral + // NOTE we do NOT check the address of `packet` because the mutable reference ensures it's + // allocated in RAM + pub async fn try_send(&mut self, packet: &mut Packet) -> Result<(), Error> { + let s = T::state(); + let r = T::regs(); + + // enable radio to perform cca + self.receive_prepare(); + + /// transmit result + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum TransmitResult { + /// Success + Success, + /// Clear channel assessment reported channel in use + ChannelInUse, + } + + // Configure shortcuts + // + // The radio goes through following states when sending a 802.15.4 packet + // + // enable RX → ramp up RX → clear channel assessment (CCA) → CCA result + // CCA idle → enable TX → start TX → TX → end (PHYEND) → disabled + // + // CCA might end up in the event CCABUSY in which there will be no transmission + r.shorts().write(|w| { + w.set_rxready_ccastart(true); + w.set_ccaidle_txen(true); + w.set_txready_start(true); + w.set_ccabusy_disable(true); + w.set_phyend_disable(true); + }); + + // Set transmission buffer + self.set_buffer(packet.buffer.as_mut()); + + // the DMA transfer will start at some point after the following write operation so + // we place the compiler fence here + dma_start_fence(); + // start CCA. In case the channel is clear, the data at packetptr will be sent automatically + + match self.state() { + // Re-start receiver + RadioState::RX_IDLE => r.tasks_ccastart().write_value(1), + // Enable receiver + _ => r.tasks_rxen().write_value(1), + } + + self.clear_all_interrupts(); + let result = core::future::poll_fn(|cx| { + s.event_waker.register(cx.waker()); + + if r.events_phyend().read() != 0 { + r.events_phyend().write_value(0); + r.events_ccabusy().write_value(0); + trace!("TX done poll"); + return Poll::Ready(TransmitResult::Success); + } else if r.events_ccabusy().read() != 0 { + r.events_ccabusy().write_value(0); + trace!("TX no CCA"); + return Poll::Ready(TransmitResult::ChannelInUse); + } + + r.intenset().write(|w| { + w.set_phyend(true); + w.set_ccabusy(true); + }); + + Poll::Pending + }) + .await; + + match result { + TransmitResult::Success => Ok(()), + TransmitResult::ChannelInUse => Err(Error::ChannelInUse), + } + } +} + +/// An IEEE 802.15.4 packet +/// +/// This `Packet` is a PHY layer packet. It's made up of the physical header (PHR) and the PSDU +/// (PHY service data unit). The PSDU of this `Packet` will always include the MAC level CRC, AKA +/// the FCS (Frame Control Sequence) -- the CRC is fully computed in hardware and automatically +/// appended on transmission and verified on reception. +/// +/// The API lets users modify the usable part (not the CRC) of the PSDU via the `deref` and +/// `copy_from_slice` methods. These methods will automatically update the PHR. +/// +/// See figure 119 in the Product Specification of the nRF52840 for more details +pub struct Packet { + buffer: [u8; Self::SIZE], +} + +// See figure 124 in nRF52840-PS +impl Packet { + // for indexing purposes + const PHY_HDR: usize = 0; + const DATA: core::ops::RangeFrom = 1..; + + /// Maximum amount of usable payload (CRC excluded) a single packet can contain, in bytes + pub const CAPACITY: u8 = 125; + const CRC: u8 = 2; // size of the CRC, which is *never* copied to / from RAM + const MAX_PSDU_LEN: u8 = Self::CAPACITY + Self::CRC; + const SIZE: usize = 1 /* PHR */ + Self::MAX_PSDU_LEN as usize; + + /// Returns an empty packet (length = 0) + pub fn new() -> Self { + let mut packet = Self { + buffer: [0; Self::SIZE], + }; + packet.set_len(0); + packet + } + + /// Fills the packet payload with given `src` data + /// + /// # Panics + /// + /// This function panics if `src` is larger than `Self::CAPACITY` + pub fn copy_from_slice(&mut self, src: &[u8]) { + assert!(src.len() <= Self::CAPACITY as usize); + let len = src.len() as u8; + self.buffer[Self::DATA][..len as usize].copy_from_slice(&src[..len.into()]); + self.set_len(len); + } + + /// Returns the size of this packet's payload + pub fn len(&self) -> u8 { + self.buffer[Self::PHY_HDR] - Self::CRC + } + + /// Changes the size of the packet's payload + /// + /// # Panics + /// + /// This function panics if `len` is larger than `Self::CAPACITY` + pub fn set_len(&mut self, len: u8) { + assert!(len <= Self::CAPACITY); + self.buffer[Self::PHY_HDR] = len + Self::CRC; + } + + /// Returns the LQI (Link Quality Indicator) of the received packet + /// + /// Note that the LQI is stored in the `Packet`'s internal buffer by the hardware so the value + /// returned by this method is only valid after a `Radio.recv` operation. Operations that + /// modify the `Packet`, like `copy_from_slice` or `set_len`+`deref_mut`, will overwrite the + /// stored LQI value. + /// + /// Also note that the hardware will *not* compute a LQI for packets smaller than 3 bytes so + /// this method will return an invalid value for those packets. + pub fn lqi(&self) -> u8 { + self.buffer[1 /* PHY_HDR */ + self.len() as usize /* data */] + } +} + +impl core::ops::Deref for Packet { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + &self.buffer[Self::DATA][..self.len() as usize] + } +} + +impl core::ops::DerefMut for Packet { + fn deref_mut(&mut self) -> &mut [u8] { + let len = self.len(); + &mut self.buffer[Self::DATA][..len as usize] + } +} + +/// NOTE must be followed by a volatile write operation +fn dma_start_fence() { + compiler_fence(Ordering::Release); +} + +/// NOTE must be preceded by a volatile read operation +fn dma_end_fence() { + compiler_fence(Ordering::Acquire); +} diff --git a/embassy/embassy-nrf/src/radio/mod.rs b/embassy/embassy-nrf/src/radio/mod.rs new file mode 100644 index 0000000..251f37d --- /dev/null +++ b/embassy/embassy-nrf/src/radio/mod.rs @@ -0,0 +1,105 @@ +//! Integrated 2.4 GHz Radio +//! +//! The 2.4 GHz radio transceiver is compatible with multiple radio standards +//! such as 1Mbps, 2Mbps and Long Range Bluetooth Low Energy. + +#![macro_use] + +/// Bluetooth Low Energy Radio driver. +pub mod ble; +#[cfg(any( + feature = "nrf52811", + feature = "nrf52820", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-net" +))] +/// IEEE 802.15.4 +pub mod ieee802154; + +use core::marker::PhantomData; + +use embassy_sync::waitqueue::AtomicWaker; +use pac::radio::vals::State as RadioState; +pub use pac::radio::vals::Txpower as TxPower; + +use crate::{interrupt, pac, Peripheral}; + +/// RADIO error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Buffer was too long. + BufferTooLong, + /// Buffer was too short. + BufferTooShort, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// Clear channel assessment reported channel in use + ChannelInUse, + /// CRC check failed + CrcFailed(u16), +} + +/// Interrupt handler +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + // clear all interrupts + r.intenclr().write(|w| w.0 = 0xffff_ffff); + s.event_waker.wake(); + } +} + +pub(crate) struct State { + /// end packet transmission or reception + event_waker: AtomicWaker, +} +impl State { + pub(crate) const fn new() -> Self { + Self { + event_waker: AtomicWaker::new(), + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> crate::pac::radio::Radio; + fn state() -> &'static State; +} + +macro_rules! impl_radio { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::radio::SealedInstance for peripherals::$type { + fn regs() -> crate::pac::radio::Radio { + pac::$pac_type + } + + fn state() -> &'static crate::radio::State { + static STATE: crate::radio::State = crate::radio::State::new(); + &STATE + } + } + impl crate::radio::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +/// Radio peripheral instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +/// Get the state of the radio +pub(crate) fn state(radio: pac::radio::Radio) -> RadioState { + radio.state().read().state() +} diff --git a/embassy/embassy-nrf/src/reset.rs b/embassy/embassy-nrf/src/reset.rs new file mode 100644 index 0000000..a33d8f3 --- /dev/null +++ b/embassy/embassy-nrf/src/reset.rs @@ -0,0 +1,82 @@ +//! Reset + +#![macro_use] + +use bitflags::bitflags; +use nrf_pac::reset::regs::Resetreas; +#[cfg(not(feature = "_nrf5340-net"))] +use nrf_pac::reset::vals::Forceoff; + +use crate::chip::pac::RESET; + +bitflags! { + #[derive(Debug, Copy, Clone, PartialEq)] + /// Bitflag representation of the `RESETREAS` register + pub struct ResetReason: u32 { + /// Reset Pin + const RESETPIN = 1; + /// Application watchdog timer 0 + const DOG0 = 1 << 1; + /// Application CTRL-AP + const CTRLAP = 1 << 2; + /// Application soft reset + const SREQ = 1 << 3; + /// Application CPU lockup + const LOCKUP = 1 << 4; + /// Wakeup from System OFF when wakeup is triggered by DETECT signal from GPIO + const OFF = 1 << 5; + /// Wakeup from System OFF when wakeup is triggered by ANADETECT signal from LPCOMP + const LPCOMP = 1 << 6; + /// Wakeup from System OFF when wakeup is triggered by entering the Debug Interface mode + const DIF = 1 << 7; + /// Network soft reset + #[cfg(feature = "_nrf5340-net")] + const LSREQ = 1 << 16; + /// Network CPU lockup + #[cfg(feature = "_nrf5340-net")] + const LLOCKUP = 1 << 17; + /// Network watchdog timer + #[cfg(feature = "_nrf5340-net")] + const LDOG = 1 << 18; + /// Force-OFF reset from application core + #[cfg(feature = "_nrf5340-net")] + const MFORCEOFF = 1 << 23; + /// Wakeup from System OFF mode due to NFC field being detected + const NFC = 1 << 24; + /// Application watchdog timer 1 + const DOG1 = 1 << 25; + /// Wakeup from System OFF mode due to VBUS rising into valid range + const VBUS = 1 << 26; + /// Network CTRL-AP + #[cfg(feature = "_nrf5340-net")] + const LCTRLAP = 1 << 27; + } +} + +/// Reads the bitflag of the reset reasons +pub fn read_reasons() -> ResetReason { + ResetReason::from_bits_retain(RESET.resetreas().read().0) +} + +/// Resets the reset reasons +pub fn clear_reasons() { + RESET.resetreas().write(|w| *w = Resetreas(ResetReason::all().bits())); +} + +/// Returns if the network core is held in reset +#[cfg(not(feature = "_nrf5340-net"))] +pub fn network_core_held() -> bool { + RESET.network().forceoff().read().forceoff() == Forceoff::HOLD +} + +/// Releases the network core from the FORCEOFF state +#[cfg(not(feature = "_nrf5340-net"))] +pub fn release_network_core() { + RESET.network().forceoff().write(|w| w.set_forceoff(Forceoff::RELEASE)); +} + +/// Holds the network core in the FORCEOFF state +#[cfg(not(feature = "_nrf5340-net"))] +pub fn hold_network_core() { + RESET.network().forceoff().write(|w| w.set_forceoff(Forceoff::HOLD)); +} diff --git a/embassy/embassy-nrf/src/rng.rs b/embassy/embassy-nrf/src/rng.rs new file mode 100644 index 0000000..7a98ab2 --- /dev/null +++ b/embassy/embassy-nrf/src/rng.rs @@ -0,0 +1,273 @@ +//! Random Number Generator (RNG) driver. + +#![macro_use] + +use core::cell::{RefCell, RefMut}; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ptr; +use core::task::Poll; + +use critical_section::{CriticalSection, Mutex}; +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::WakerRegistration; + +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, pac, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + + // Clear the event. + r.events_valrdy().write_value(0); + + // Mutate the slice within a critical section, + // so that the future isn't dropped in between us loading the pointer and actually dereferencing it. + critical_section::with(|cs| { + let mut state = T::state().borrow_mut(cs); + // We need to make sure we haven't already filled the whole slice, + // in case the interrupt fired again before the executor got back to the future. + if !state.ptr.is_null() && state.ptr != state.end { + // If the future was dropped, the pointer would have been set to null, + // so we're still good to mutate the slice. + // The safety contract of `Rng::new` means that the future can't have been dropped + // without calling its destructor. + unsafe { + *state.ptr = r.value().read().value(); + state.ptr = state.ptr.add(1); + } + + if state.ptr == state.end { + state.waker.wake(); + } + } + }); + } +} + +/// A wrapper around an nRF RNG peripheral. +/// +/// It has a non-blocking API, and a blocking api through `rand`. +pub struct Rng<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Rng<'d, T> { + /// Creates a new RNG driver from the `RNG` peripheral and interrupt. + /// + /// SAFETY: The future returned from `fill_bytes` must not have its lifetime end without running its destructor, + /// e.g. using `mem::forget`. + /// + /// The synchronous API is safe. + pub fn new( + rng: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + into_ref!(rng); + + let this = Self { _peri: rng }; + + this.stop(); + this.disable_irq(); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + this + } + + fn stop(&self) { + T::regs().tasks_stop().write_value(1) + } + + fn start(&self) { + T::regs().tasks_start().write_value(1) + } + + fn enable_irq(&self) { + T::regs().intenset().write(|w| w.set_valrdy(true)); + } + + fn disable_irq(&self) { + T::regs().intenclr().write(|w| w.set_valrdy(true)); + } + + /// Enable or disable the RNG's bias correction. + /// + /// Bias correction removes any bias towards a '1' or a '0' in the bits generated. + /// However, this makes the generation of numbers slower. + /// + /// Defaults to disabled. + pub fn set_bias_correction(&self, enable: bool) { + T::regs().config().write(|w| w.set_dercen(enable)) + } + + /// Fill the buffer with random bytes. + pub async fn fill_bytes(&mut self, dest: &mut [u8]) { + if dest.is_empty() { + return; // Nothing to fill + } + + let range = dest.as_mut_ptr_range(); + // Even if we've preempted the interrupt, it can't preempt us again, + // so we don't need to worry about the order we write these in. + critical_section::with(|cs| { + let mut state = T::state().borrow_mut(cs); + state.ptr = range.start; + state.end = range.end; + }); + + self.enable_irq(); + self.start(); + + let on_drop = OnDrop::new(|| { + self.stop(); + self.disable_irq(); + + critical_section::with(|cs| { + let mut state = T::state().borrow_mut(cs); + state.ptr = ptr::null_mut(); + state.end = ptr::null_mut(); + }); + }); + + poll_fn(|cx| { + critical_section::with(|cs| { + let mut s = T::state().borrow_mut(cs); + s.waker.register(cx.waker()); + if s.ptr == s.end { + // We're done. + Poll::Ready(()) + } else { + Poll::Pending + } + }) + }) + .await; + + // Trigger the teardown + drop(on_drop); + } + + /// Fill the buffer with random bytes, blocking version. + pub fn blocking_fill_bytes(&mut self, dest: &mut [u8]) { + self.start(); + + for byte in dest.iter_mut() { + let regs = T::regs(); + while regs.events_valrdy().read() == 0 {} + regs.events_valrdy().write_value(0); + *byte = regs.value().read().value(); + } + + self.stop(); + } +} + +impl<'d, T: Instance> Drop for Rng<'d, T> { + fn drop(&mut self) { + self.stop(); + critical_section::with(|cs| { + let mut state = T::state().borrow_mut(cs); + state.ptr = ptr::null_mut(); + state.end = ptr::null_mut(); + }); + } +} + +impl<'d, T: Instance> rand_core::RngCore for Rng<'d, T> { + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } + + fn next_u32(&mut self) -> u32 { + let mut bytes = [0; 4]; + self.blocking_fill_bytes(&mut bytes); + // We don't care about the endianness, so just use the native one. + u32::from_ne_bytes(bytes) + } + + fn next_u64(&mut self) -> u64 { + let mut bytes = [0; 8]; + self.blocking_fill_bytes(&mut bytes); + u64::from_ne_bytes(bytes) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.blocking_fill_bytes(dest); + Ok(()) + } +} + +impl<'d, T: Instance> rand_core::CryptoRng for Rng<'d, T> {} + +/// Peripheral static state +pub(crate) struct State { + inner: Mutex>, +} + +struct InnerState { + ptr: *mut u8, + end: *mut u8, + waker: WakerRegistration, +} + +unsafe impl Send for InnerState {} + +impl State { + pub(crate) const fn new() -> Self { + Self { + inner: Mutex::new(RefCell::new(InnerState::new())), + } + } + + fn borrow_mut<'cs>(&'cs self, cs: CriticalSection<'cs>) -> RefMut<'cs, InnerState> { + self.inner.borrow(cs).borrow_mut() + } +} + +impl InnerState { + const fn new() -> Self { + Self { + ptr: ptr::null_mut(), + end: ptr::null_mut(), + waker: WakerRegistration::new(), + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::rng::Rng; + fn state() -> &'static State; +} + +/// RNG peripheral instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_rng { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::rng::SealedInstance for peripherals::$type { + fn regs() -> crate::pac::rng::Rng { + pac::$pac_type + } + fn state() -> &'static crate::rng::State { + static STATE: crate::rng::State = crate::rng::State::new(); + &STATE + } + } + impl crate::rng::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy/embassy-nrf/src/saadc.rs b/embassy/embassy-nrf/src/saadc.rs new file mode 100644 index 0000000..70bda9f --- /dev/null +++ b/embassy/embassy-nrf/src/saadc.rs @@ -0,0 +1,723 @@ +//! Successive Approximation Analog-to-Digital Converter (SAADC) driver. + +#![macro_use] + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +pub(crate) use vals::Psel as InputChannel; + +use crate::interrupt::InterruptExt; +use crate::pac::saadc::vals; +use crate::ppi::{ConfigurableChannel, Event, Ppi, Task}; +use crate::timer::{Frequency, Instance as TimerInstance, Timer}; +use crate::{interrupt, pac, peripherals, Peripheral}; + +/// SAADC error +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error {} + +/// Interrupt handler. +pub struct InterruptHandler { + _private: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = pac::SAADC; + + if r.events_calibratedone().read() != 0 { + r.intenclr().write(|w| w.set_calibratedone(true)); + WAKER.wake(); + } + + if r.events_end().read() != 0 { + r.intenclr().write(|w| w.set_end(true)); + WAKER.wake(); + } + + if r.events_started().read() != 0 { + r.intenclr().write(|w| w.set_started(true)); + WAKER.wake(); + } + } +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// Used to configure the SAADC peripheral. +/// +/// See the `Default` impl for suitable default values. +#[non_exhaustive] +pub struct Config { + /// Output resolution in bits. + pub resolution: Resolution, + /// Average 2^`oversample` input samples before transferring the result into memory. + pub oversample: Oversample, +} + +impl Default for Config { + /// Default configuration for single channel sampling. + fn default() -> Self { + Self { + resolution: Resolution::_12BIT, + oversample: Oversample::BYPASS, + } + } +} + +/// Used to configure an individual SAADC peripheral channel. +/// +/// Construct using the `single_ended` or `differential` methods. These provide sensible defaults +/// for the public fields, which can be overridden if required. +#[non_exhaustive] +pub struct ChannelConfig<'d> { + /// Reference voltage of the SAADC input. + pub reference: Reference, + /// Gain used to control the effective input range of the SAADC. + pub gain: Gain, + /// Positive channel resistor control. + pub resistor: Resistor, + /// Acquisition time in microseconds. + pub time: Time, + /// Positive channel to sample + p_channel: PeripheralRef<'d, AnyInput>, + /// An optional negative channel to sample + n_channel: Option>, +} + +impl<'d> ChannelConfig<'d> { + /// Default configuration for single ended channel sampling. + pub fn single_ended(input: impl Peripheral

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

+ 'd, + n_input: impl Peripheral

+ 'd, + ) -> Self { + into_ref!(p_input, n_input); + Self { + reference: Reference::INTERNAL, + gain: Gain::GAIN1_6, + resistor: Resistor::BYPASS, + time: Time::_10US, + p_channel: p_input.map_into(), + n_channel: Some(n_input.map_into()), + } + } +} + +/// Value returned by the SAADC callback, deciding what happens next. +#[derive(PartialEq)] +pub enum CallbackResult { + /// The SAADC should keep sampling and calling the callback. + Continue, + /// The SAADC should stop sampling, and return. + Stop, +} + +/// One-shot and continuous SAADC. +pub struct Saadc<'d, const N: usize> { + _p: PeripheralRef<'d, peripherals::SAADC>, +} + +impl<'d, const N: usize> Saadc<'d, N> { + /// Create a new SAADC driver. + pub fn new( + saadc: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, + config: Config, + channel_configs: [ChannelConfig; N], + ) -> Self { + into_ref!(saadc); + + let r = pac::SAADC; + + let Config { resolution, oversample } = config; + + // Configure channels + r.enable().write(|w| w.set_enable(true)); + r.resolution().write(|w| w.set_val(resolution.into())); + r.oversample().write(|w| w.set_oversample(oversample.into())); + + for (i, cc) in channel_configs.iter().enumerate() { + r.ch(i).pselp().write(|w| w.set_pselp(cc.p_channel.channel())); + if let Some(n_channel) = &cc.n_channel { + r.ch(i).pseln().write(|w| w.set_pseln(n_channel.channel())); + } + r.ch(i).config().write(|w| { + w.set_refsel(cc.reference.into()); + w.set_gain(cc.gain.into()); + w.set_tacq(cc.time.into()); + w.set_mode(match cc.n_channel { + None => vals::ConfigMode::SE, + Some(_) => vals::ConfigMode::DIFF, + }); + w.set_resp(cc.resistor.into()); + w.set_resn(vals::Resn::BYPASS); + w.set_burst(!matches!(oversample, Oversample::BYPASS)); + }); + } + + // Disable all events interrupts + r.intenclr().write(|w| w.0 = 0x003F_FFFF); + + interrupt::SAADC.unpend(); + unsafe { interrupt::SAADC.enable() }; + + Self { _p: saadc } + } + + fn regs() -> pac::saadc::Saadc { + pac::SAADC + } + + /// Perform SAADC calibration. Completes when done. + pub async fn calibrate(&self) { + let r = Self::regs(); + + // Reset and enable the end event + r.events_calibratedone().write_value(0); + r.intenset().write(|w| w.set_calibratedone(true)); + + // Order is important + compiler_fence(Ordering::SeqCst); + + r.tasks_calibrateoffset().write_value(1); + + // Wait for 'calibratedone' event. + poll_fn(|cx| { + let r = Self::regs(); + + WAKER.register(cx.waker()); + + if r.events_calibratedone().read() != 0 { + r.events_calibratedone().write_value(0); + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + } + + /// One shot sampling. The buffer must be the same size as the number of channels configured. + /// The sampling is stopped prior to returning in order to reduce power consumption (power + /// consumption remains higher if sampling is not stopped explicitly). Cancellation will + /// 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. + let on_drop = OnDrop::new(Self::stop_sampling_immediately); + + let r = Self::regs(); + + // Set up the DMA + r.result().ptr().write_value(buf.as_mut_ptr() as u32); + r.result().maxcnt().write(|w| w.set_maxcnt(N as _)); + + // Reset and enable the end event + r.events_end().write_value(0); + r.intenset().write(|w| w.set_end(true)); + + // Don't reorder the ADC start event before the previous writes. Hopefully self + // wouldn't happen anyway. + compiler_fence(Ordering::SeqCst); + + r.tasks_start().write_value(1); + r.tasks_sample().write_value(1); + + // Wait for 'end' event. + poll_fn(|cx| { + let r = Self::regs(); + + WAKER.register(cx.waker()); + + if r.events_end().read() != 0 { + r.events_end().write_value(0); + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + + drop(on_drop); + } + + /// Continuous sampling with double buffers. + /// + /// A TIMER and two PPI peripherals are passed in so that precise sampling + /// can be attained. The sampling interval is expressed by selecting a + /// timer clock frequency to use along with a counter threshold to be reached. + /// For example, 1KHz can be achieved using a frequency of 1MHz and a counter + /// threshold of 1000. + /// + /// A sampler closure is provided that receives the buffer of samples, noting + /// that the size of this buffer can be less than the original buffer's size. + /// A command is return from the closure that indicates whether the sampling + /// should continue or stop. + /// + /// NOTE: The time spent within the callback supplied should not exceed the time + /// taken to acquire the samples into a single buffer. You should measure the + /// time taken by the callback and set the sample buffer size accordingly. + /// Exceeding this time can lead to samples becoming dropped. + /// + /// The sampling is stopped prior to returning in order to reduce power consumption (power + /// consumption remains higher if sampling is not stopped explicitly), and to + /// free the buffers from being used by the peripheral. Cancellation will + /// also cause the sampling to be stopped. + + pub async fn run_task_sampler( + &mut self, + timer: &mut T, + ppi_ch1: &mut impl ConfigurableChannel, + ppi_ch2: &mut impl ConfigurableChannel, + frequency: Frequency, + sample_counter: u32, + bufs: &mut [[[i16; N]; N0]; 2], + callback: F, + ) where + F: FnMut(&[[i16; N]]) -> CallbackResult, + { + let r = Self::regs(); + + // We want the task start to effectively short with the last one ending so + // we don't miss any samples. It'd be great for the SAADC to offer a SHORTS + // register instead, but it doesn't, so we must use PPI. + let mut start_ppi = Ppi::new_one_to_one( + ppi_ch1, + Event::from_reg(r.events_end()), + Task::from_reg(r.tasks_start()), + ); + start_ppi.enable(); + + let timer = Timer::new(timer); + timer.set_frequency(frequency); + timer.cc(0).write(sample_counter); + timer.cc(0).short_compare_clear(); + + let timer_cc = timer.cc(0); + + let mut sample_ppi = Ppi::new_one_to_one(ppi_ch2, timer_cc.event_compare(), Task::from_reg(r.tasks_sample())); + + timer.start(); + + self.run_sampler( + bufs, + None, + || { + sample_ppi.enable(); + }, + callback, + ) + .await; + } + + async fn run_sampler( + &mut self, + bufs: &mut [[[i16; N]; N0]; 2], + sample_rate_divisor: Option, + mut init: I, + mut callback: F, + ) where + I: FnMut(), + F: FnMut(&[[i16; N]]) -> CallbackResult, + { + // In case the future is dropped, stop the task and wait for it to end. + let on_drop = OnDrop::new(Self::stop_sampling_immediately); + + let r = Self::regs(); + + // Establish mode and sample rate + match sample_rate_divisor { + Some(sr) => { + r.samplerate().write(|w| { + w.set_cc(sr); + w.set_mode(vals::SamplerateMode::TIMERS); + }); + r.tasks_sample().write_value(1); // Need to kick-start the internal timer + } + None => r.samplerate().write(|w| { + w.set_cc(0); + w.set_mode(vals::SamplerateMode::TASK); + }), + } + + // Set up the initial DMA + r.result().ptr().write_value(bufs[0].as_mut_ptr() as u32); + r.result().maxcnt().write(|w| w.set_maxcnt((N0 * N) as _)); + + // Reset and enable the events + r.events_end().write_value(0); + r.events_started().write_value(0); + r.intenset().write(|w| { + w.set_end(true); + w.set_started(true); + }); + + // Don't reorder the ADC start event before the previous writes. Hopefully self + // wouldn't happen anyway. + compiler_fence(Ordering::SeqCst); + + r.tasks_start().write_value(1); + + let mut inited = false; + let mut current_buffer = 0; + + // Wait for events and complete when the sampler indicates it has had enough. + let r = poll_fn(|cx| { + let r = Self::regs(); + + WAKER.register(cx.waker()); + + if r.events_end().read() != 0 { + compiler_fence(Ordering::SeqCst); + + r.events_end().write_value(0); + r.intenset().write(|w| w.set_end(true)); + + 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() != 0 { + r.events_started().write_value(0); + r.intenset().write(|w| w.set_started(true)); + + if !inited { + init(); + inited = true; + } + + let next_buffer = 1 - current_buffer; + r.result().ptr().write_value(bufs[next_buffer].as_mut_ptr() as u32); + } + + Poll::Pending + }) + .await; + + drop(on_drop); + + r + } + + // Stop sampling and wait for it to stop in a blocking fashion + fn stop_sampling_immediately() { + let r = Self::regs(); + + compiler_fence(Ordering::SeqCst); + + r.events_stopped().write_value(0); + r.tasks_stop().write_value(1); + + while r.events_stopped().read() == 0 {} + r.events_stopped().write_value(0); + } +} + +impl<'d> Saadc<'d, 1> { + /// Continuous sampling on a single channel with double buffers. + /// + /// The internal clock is to be used with a sample rate expressed as a divisor of + /// 16MHz, ranging from 80..2047. For example, 1600 represents a sample rate of 10KHz + /// given 16_000_000 / 10_000_000 = 1600. + /// + /// A sampler closure is provided that receives the buffer of samples, noting + /// that the size of this buffer can be less than the original buffer's size. + /// A command is return from the closure that indicates whether the sampling + /// should continue or stop. + pub async fn run_timer_sampler( + &mut self, + bufs: &mut [[[i16; 1]; N0]; 2], + sample_rate_divisor: u16, + sampler: S, + ) where + S: FnMut(&[[i16; 1]]) -> CallbackResult, + { + self.run_sampler(bufs, Some(sample_rate_divisor), || {}, sampler).await; + } +} + +impl<'d, const N: usize> Drop for Saadc<'d, N> { + fn drop(&mut self) { + let r = Self::regs(); + r.enable().write(|w| w.set_enable(false)); + } +} + +impl From for vals::Gain { + fn from(gain: Gain) -> Self { + match gain { + Gain::GAIN1_6 => vals::Gain::GAIN1_6, + Gain::GAIN1_5 => vals::Gain::GAIN1_5, + Gain::GAIN1_4 => vals::Gain::GAIN1_4, + Gain::GAIN1_3 => vals::Gain::GAIN1_3, + Gain::GAIN1_2 => vals::Gain::GAIN1_2, + Gain::GAIN1 => vals::Gain::GAIN1, + Gain::GAIN2 => vals::Gain::GAIN2, + Gain::GAIN4 => vals::Gain::GAIN4, + } + } +} + +/// Gain control +#[non_exhaustive] +#[derive(Clone, Copy)] +pub enum Gain { + /// 1/6 + GAIN1_6 = 0, + /// 1/5 + GAIN1_5 = 1, + /// 1/4 + GAIN1_4 = 2, + /// 1/3 + GAIN1_3 = 3, + /// 1/2 + GAIN1_2 = 4, + /// 1 + GAIN1 = 5, + /// 2 + GAIN2 = 6, + /// 4 + GAIN4 = 7, +} + +impl From for vals::Refsel { + fn from(reference: Reference) -> Self { + match reference { + Reference::INTERNAL => vals::Refsel::INTERNAL, + Reference::VDD1_4 => vals::Refsel::VDD1_4, + } + } +} + +/// Reference control +#[non_exhaustive] +#[derive(Clone, Copy)] +pub enum Reference { + /// Internal reference (0.6 V) + INTERNAL = 0, + /// VDD/4 as reference + VDD1_4 = 1, +} + +impl From for vals::Resp { + fn from(resistor: Resistor) -> Self { + match resistor { + Resistor::BYPASS => vals::Resp::BYPASS, + Resistor::PULLDOWN => vals::Resp::PULLDOWN, + Resistor::PULLUP => vals::Resp::PULLUP, + Resistor::VDD1_2 => vals::Resp::VDD1_2, + } + } +} + +/// Positive channel resistor control +#[non_exhaustive] +#[derive(Clone, Copy)] +pub enum Resistor { + /// Bypass resistor ladder + BYPASS = 0, + /// Pull-down to GND + PULLDOWN = 1, + /// Pull-up to VDD + PULLUP = 2, + /// Set input at VDD/2 + VDD1_2 = 3, +} + +impl From

+ 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(), + } + } +} + +/// 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, +} + +impl_peripheral!(AnyInput); + +impl SealedInput for AnyInput { + fn channel(&self) -> InputChannel { + self.channel + } +} + +impl Input for AnyInput {} + +macro_rules! impl_saadc_input { + ($pin:ident, $ch:ident) => { + impl_saadc_input!(@local, crate::peripherals::$pin, $ch); + }; + (@local, $pin:ty, $ch:ident) => { + impl crate::saadc::SealedInput for $pin { + fn channel(&self) -> crate::saadc::InputChannel { + crate::saadc::InputChannel::$ch + } + } + impl crate::saadc::Input for $pin {} + + impl From<$pin> for crate::saadc::AnyInput { + fn from(val: $pin) -> Self { + crate::saadc::Input::degrade_saadc(val) + } + } + }; +} + +/// A dummy `Input` pin implementation for SAADC peripheral sampling from the +/// internal voltage. +pub struct VddInput; + +impl_peripheral!(VddInput); +#[cfg(not(feature = "_nrf91"))] +impl_saadc_input!(@local, VddInput, VDD); +#[cfg(feature = "_nrf91")] +impl_saadc_input!(@local, VddInput, VDD_GPIO); + +/// A dummy `Input` pin implementation for SAADC peripheral sampling from the +/// VDDH / 5 voltage. +#[cfg(any(feature = "_nrf5340-app", feature = "nrf52833", feature = "nrf52840"))] +pub struct VddhDiv5Input; + +#[cfg(any(feature = "_nrf5340-app", feature = "nrf52833", feature = "nrf52840"))] +impl_peripheral!(VddhDiv5Input); + +#[cfg(any(feature = "_nrf5340-app", feature = "nrf52833", feature = "nrf52840"))] +impl_saadc_input!(@local, VddhDiv5Input, VDDHDIV5); diff --git a/embassy/embassy-nrf/src/spim.rs b/embassy/embassy-nrf/src/spim.rs new file mode 100644 index 0000000..bd193cf --- /dev/null +++ b/embassy/embassy-nrf/src/spim.rs @@ -0,0 +1,653 @@ +//! Serial Peripheral Instance in master mode (SPIM) driver. + +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; +#[cfg(feature = "_nrf52832_anomaly_109")] +use core::sync::atomic::AtomicU8; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_embedded_hal::SetConfig; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; +pub use pac::spim::vals::{Frequency, Order as BitOrder}; + +use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; +use crate::gpio::{self, convert_drive, AnyPin, OutputDrive, Pin as GpioPin, PselBits, SealedPin as _}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::spim::vals; +use crate::util::slice_in_ram_or; +use crate::{interrupt, pac, Peripheral}; + +/// SPIM error +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// EasyDMA can only read from data memory, read only buffers in flash will fail. + BufferNotInRAM, +} + +/// SPIM configuration. +#[non_exhaustive] +#[derive(Clone)] +pub struct Config { + /// Frequency + pub frequency: Frequency, + + /// SPI mode + pub mode: Mode, + + /// Bit order + pub bit_order: BitOrder, + + /// 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, + + /// Drive strength for the SCK line. + pub sck_drive: OutputDrive, + + /// Drive strength for the MOSI line. + pub mosi_drive: OutputDrive, +} + +impl Default for Config { + fn default() -> Self { + Self { + frequency: Frequency::M1, + mode: MODE_0, + bit_order: BitOrder::MSB_FIRST, + orc: 0x00, + sck_drive: OutputDrive::HighDrive, + mosi_drive: OutputDrive::HighDrive, + } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + #[cfg(feature = "_nrf52832_anomaly_109")] + { + // Ideally we should call this only during the first chunk transfer, + // but so far calling this every time doesn't seem to be causing any issues. + if r.events_started().read() != 0 { + s.waker.wake(); + r.intenclr().write(|w| w.set_started(true)); + } + } + + if r.events_end().read() != 0 { + s.waker.wake(); + r.intenclr().write(|w| w.set_end(true)); + } + } +} + +/// SPIM driver. +pub struct Spim<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Spim<'d, T> { + /// Create a new SPIM driver. + pub fn new( + spim: impl Peripheral

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

+ 'd, + miso: impl Peripheral

+ 'd, + mosi: impl Peripheral

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

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

+ 'd, + mosi: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(sck, mosi); + Self::new_inner(spim, Some(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 interrupt::typelevel::Binding> + 'd, + sck: impl Peripheral

+ 'd, + miso: impl Peripheral

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

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

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

+ 'd, + sck: Option>, + miso: Option>, + mosi: Option>, + config: Config, + ) -> Self { + into_ref!(spim); + + let r = T::regs(); + + // Configure pins + if let Some(sck) = &sck { + sck.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + convert_drive(w, config.sck_drive); + }); + } + if let Some(mosi) = &mosi { + mosi.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + convert_drive(w, config.mosi_drive); + }); + } + if let Some(miso) = &miso { + miso.conf().write(|w| w.set_input(gpiovals::Input::CONNECT)); + } + + match config.mode.polarity { + Polarity::IdleHigh => { + if let Some(sck) = &sck { + sck.set_high(); + } + if let Some(mosi) = &mosi { + mosi.set_high(); + } + } + Polarity::IdleLow => { + if let Some(sck) = &sck { + sck.set_low(); + } + if let Some(mosi) = &mosi { + mosi.set_low(); + } + } + } + + // Select pins. + r.psel().sck().write_value(sck.psel_bits()); + r.psel().mosi().write_value(mosi.psel_bits()); + r.psel().miso().write_value(miso.psel_bits()); + + // Enable SPIM instance. + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + + let mut spim = Self { _p: spim }; + + // Apply runtime peripheral configuration + Self::set_config(&mut spim, &config).unwrap(); + + // Disable all events interrupts + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + spim + } + + fn prepare_dma_transfer(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) { + compiler_fence(Ordering::SeqCst); + + let r = T::regs(); + + fn xfer_params(ptr: u32, total: usize, offset: usize, length: usize) -> (u32, usize) { + if total > offset { + (ptr.wrapping_add(offset as _), core::cmp::min(total - offset, length)) + } else { + (ptr, 0) + } + } + + // Set up the DMA read. + let (rx_ptr, rx_len) = xfer_params(rx as *mut u8 as _, rx.len() as _, offset, length); + r.rxd().ptr().write_value(rx_ptr); + r.rxd().maxcnt().write(|w| w.set_maxcnt(rx_len as _)); + + // Set up the DMA write. + let (tx_ptr, tx_len) = xfer_params(tx as *const u8 as _, tx.len() as _, offset, length); + r.txd().ptr().write_value(tx_ptr); + r.txd().maxcnt().write(|w| w.set_maxcnt(tx_len as _)); + + /* + trace!("XFER: offset: {}, length: {}", offset, length); + trace!("RX(len: {}, ptr: {=u32:02x})", rx_len, rx_ptr as u32); + trace!("TX(len: {}, ptr: {=u32:02x})", tx_len, tx_ptr as u32); + */ + + #[cfg(feature = "_nrf52832_anomaly_109")] + if offset == 0 { + let s = T::state(); + + r.events_started().write_value(0); + + // Set rx/tx buffer lengths to 0... + r.txd().maxcnt().write(|_| ()); + r.rxd().maxcnt().write(|_| ()); + + // ...and keep track of original buffer lengths... + s.tx.store(tx_len as _, Ordering::Relaxed); + s.rx.store(rx_len as _, Ordering::Relaxed); + + // ...signalling the start of the fake transfer. + r.intenset().write(|w| w.set_started(true)); + } + + // Reset and enable the event + r.events_end().write_value(0); + r.intenset().write(|w| w.set_end(true)); + + // Start SPI transaction. + r.tasks_start().write_value(1); + } + + fn blocking_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) { + self.prepare_dma_transfer(rx, tx, offset, length); + + #[cfg(feature = "_nrf52832_anomaly_109")] + if offset == 0 { + while self.nrf52832_dma_workaround_status().is_pending() {} + } + + // Wait for 'end' event. + while T::regs().events_end().read() == 0 {} + + compiler_fence(Ordering::SeqCst); + } + + fn blocking_inner_from_ram(&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. + + let xfer_len = core::cmp::max(rx.len(), tx.len()); + for offset in (0..xfer_len).step_by(EASY_DMA_SIZE) { + let length = core::cmp::min(xfer_len - offset, EASY_DMA_SIZE); + self.blocking_inner_from_ram_chunk(rx, tx, offset, length); + } + Ok(()) + } + + fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { + match self.blocking_inner_from_ram(rx, tx) { + Ok(_) => Ok(()), + 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); + self.blocking_inner_from_ram(rx, tx_ram_buf) + } + } + } + + async fn async_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) { + self.prepare_dma_transfer(rx, tx, offset, length); + + #[cfg(feature = "_nrf52832_anomaly_109")] + if offset == 0 { + poll_fn(|cx| { + let s = T::state(); + + s.waker.register(cx.waker()); + + self.nrf52832_dma_workaround_status() + }) + .await; + } + + // Wait for 'end' event. + poll_fn(|cx| { + T::state().waker.register(cx.waker()); + if T::regs().events_end().read() != 0 { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + } + + async fn async_inner_from_ram(&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. + + let xfer_len = core::cmp::max(rx.len(), tx.len()); + for offset in (0..xfer_len).step_by(EASY_DMA_SIZE) { + let length = core::cmp::min(xfer_len - offset, EASY_DMA_SIZE); + self.async_inner_from_ram_chunk(rx, tx, offset, length).await; + } + Ok(()) + } + + 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::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); + self.async_inner_from_ram(rx, tx_ram_buf).await + } + } + } + + /// Reads data from the SPI bus without sending anything. Blocks until the buffer has been filled. + pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> { + self.blocking_inner(data, &[]) + } + + /// 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). + pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + self.blocking_inner(read, write) + } + + /// Same as [`blocking_transfer`](Spim::blocking_transfer) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub fn blocking_transfer_from_ram(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + self.blocking_inner(read, write) + } + + /// Simultaneously sends and receives data. + /// Places the received data into the same buffer and blocks until the transmission is completed. + pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> { + self.blocking_inner_from_ram(data, data) + } + + /// 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). + pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> { + self.blocking_inner(&mut [], data) + } + + /// Same as [`blocking_write`](Spim::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub fn blocking_write_from_ram(&mut self, data: &[u8]) -> Result<(), Error> { + self.blocking_inner(&mut [], data) + } + + /// Reads data from the SPI bus without sending anything. + pub async fn read(&mut self, data: &mut [u8]) -> Result<(), Error> { + self.async_inner(data, &[]).await + } + + /// Simultaneously sends and receives data. + /// If necessary, the write buffer will be copied into RAM (see struct description for detail). + pub async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + self.async_inner(read, write).await + } + + /// Same as [`transfer`](Spim::transfer) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub async fn transfer_from_ram(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + self.async_inner_from_ram(read, write).await + } + + /// Simultaneously sends and receives data. Places the received data into the same buffer. + pub async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> { + self.async_inner_from_ram(data, data).await + } + + /// Sends data, discarding any received data. + /// If necessary, the write buffer will be copied into RAM (see struct description for detail). + pub async fn write(&mut self, data: &[u8]) -> Result<(), Error> { + self.async_inner(&mut [], data).await + } + + /// Same as [`write`](Spim::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, data: &[u8]) -> Result<(), Error> { + self.async_inner_from_ram(&mut [], data).await + } + + #[cfg(feature = "_nrf52832_anomaly_109")] + fn nrf52832_dma_workaround_status(&mut self) -> Poll<()> { + let r = T::regs(); + if r.events_started().read() != 0 { + let s = T::state(); + + // Handle the first "fake" transmission + r.events_started().write_value(0); + r.events_end().write_value(0); + + // Update DMA registers with correct rx/tx buffer sizes + r.rxd().maxcnt().write(|w| w.set_maxcnt(s.rx.load(Ordering::Relaxed))); + r.txd().maxcnt().write(|w| w.set_maxcnt(s.tx.load(Ordering::Relaxed))); + + r.intenset().write(|w| w.set_end(true)); + // ... and start actual, hopefully glitch-free transmission + r.tasks_start().write_value(1); + return Poll::Ready(()); + } + Poll::Pending + } +} + +impl<'d, T: Instance> Drop for Spim<'d, T> { + fn drop(&mut self) { + trace!("spim drop"); + + // TODO check for abort, wait for xxxstopped + + // disable! + let r = T::regs(); + r.enable().write(|w| w.set_enable(vals::Enable::DISABLED)); + + gpio::deconfigure_pin(r.psel().sck().read()); + gpio::deconfigure_pin(r.psel().miso().read()); + gpio::deconfigure_pin(r.psel().mosi().read()); + + // Disable all events interrupts + T::Interrupt::disable(); + + trace!("spim drop: done"); + } +} + +pub(crate) struct State { + waker: AtomicWaker, + #[cfg(feature = "_nrf52832_anomaly_109")] + rx: AtomicU8, + #[cfg(feature = "_nrf52832_anomaly_109")] + tx: AtomicU8, +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + #[cfg(feature = "_nrf52832_anomaly_109")] + rx: AtomicU8::new(0), + #[cfg(feature = "_nrf52832_anomaly_109")] + tx: AtomicU8::new(0), + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::spim::Spim; + fn state() -> &'static State; +} + +/// SPIM peripheral instance +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_spim { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::spim::SealedInstance for peripherals::$type { + fn regs() -> pac::spim::Spim { + pac::$pac_type + } + fn state() -> &'static crate::spim::State { + static STATE: crate::spim::State = crate::spim::State::new(); + &STATE + } + } + impl crate::spim::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +// ==================== + +mod eh02 { + use super::*; + + impl<'d, T: Instance> embedded_hal_02::blocking::spi::Transfer for Spim<'d, T> { + type Error = Error; + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + self.blocking_transfer_in_place(words)?; + Ok(words) + } + } + + impl<'d, T: Instance> embedded_hal_02::blocking::spi::Write for Spim<'d, T> { + type Error = Error; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(words) + } + } +} + +impl embedded_hal_1::spi::Error for Error { + fn kind(&self) -> embedded_hal_1::spi::ErrorKind { + match *self { + Self::BufferNotInRAM => embedded_hal_1::spi::ErrorKind::Other, + } + } +} + +impl<'d, T: Instance> embedded_hal_1::spi::ErrorType for Spim<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_hal_1::spi::SpiBus for Spim<'d, T> { + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_transfer(words, &[]) + } + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(words) + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.blocking_transfer(read, write) + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_transfer_in_place(words) + } +} + +impl<'d, T: Instance> embedded_hal_async::spi::SpiBus for Spim<'d, T> { + async fn flush(&mut self) -> Result<(), Error> { + Ok(()) + } + + async fn read(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.read(words).await + } + + async fn write(&mut self, data: &[u8]) -> Result<(), Error> { + self.write(data).await + } + + async fn transfer(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { + self.transfer(rx, tx).await + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.transfer_in_place(words).await + } +} + +impl<'d, T: Instance> SetConfig for Spim<'d, T> { + type Config = Config; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + let r = T::regs(); + // Configure mode. + let mode = config.mode; + r.config().write(|w| { + w.set_order(config.bit_order); + match mode { + MODE_0 => { + w.set_cpol(vals::Cpol::ACTIVE_HIGH); + w.set_cpha(vals::Cpha::LEADING); + } + MODE_1 => { + w.set_cpol(vals::Cpol::ACTIVE_HIGH); + w.set_cpha(vals::Cpha::TRAILING); + } + MODE_2 => { + w.set_cpol(vals::Cpol::ACTIVE_LOW); + w.set_cpha(vals::Cpha::LEADING); + } + MODE_3 => { + w.set_cpol(vals::Cpol::ACTIVE_LOW); + w.set_cpha(vals::Cpha::TRAILING); + } + } + }); + + // Configure frequency. + let frequency = config.frequency; + r.frequency().write(|w| w.set_frequency(frequency)); + + // Set over-read character + let orc = config.orc; + r.orc().write(|w| w.set_orc(orc)); + + Ok(()) + } +} diff --git a/embassy/embassy-nrf/src/spis.rs b/embassy/embassy-nrf/src/spis.rs new file mode 100644 index 0000000..88230fa --- /dev/null +++ b/embassy/embassy-nrf/src/spis.rs @@ -0,0 +1,555 @@ +//! Serial Peripheral Instance in slave mode (SPIS) driver. + +#![macro_use] +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_embedded_hal::SetConfig; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; +pub use pac::spis::vals::Order as BitOrder; + +use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; +use crate::gpio::{self, convert_drive, AnyPin, OutputDrive, Pin as GpioPin, SealedPin as _}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::spis::vals; +use crate::util::slice_in_ram_or; +use crate::{interrupt, pac, Peripheral}; + +/// 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 configuration. +#[non_exhaustive] +pub struct Config { + /// SPI mode + pub mode: Mode, + + /// Bit order + pub bit_order: BitOrder, + + /// 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, + + /// Drive strength for the MISO line. + pub miso_drive: OutputDrive, +} + +impl Default for Config { + fn default() -> Self { + Self { + mode: MODE_0, + bit_order: BitOrder::MSB_FIRST, + orc: 0x00, + def: 0x00, + auto_acquire: true, + miso_drive: OutputDrive::HighDrive, + } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + if r.events_end().read() != 0 { + s.waker.wake(); + r.intenclr().write(|w| w.set_end(true)); + } + + if r.events_acquired().read() != 0 { + s.waker.wake(); + r.intenclr().write(|w| w.set_acquired(true)); + } + } +} + +/// SPIS driver. +pub struct Spis<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Spis<'d, T> { + /// Create a new SPIS driver. + pub fn new( + spis: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + '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, + cs.map_into(), + Some(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 interrupt::typelevel::Binding> + '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, + cs.map_into(), + Some(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 interrupt::typelevel::Binding> + '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, + cs.map_into(), + Some(sck.map_into()), + None, + Some(mosi.map_into()), + config, + ) + } + + /// Create a new SPIS driver, capable of TX only (MISO only) without SCK pin. + pub fn new_txonly_nosck( + spis: impl Peripheral

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

+ 'd, + miso: impl Peripheral

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

+ 'd, + cs: PeripheralRef<'d, AnyPin>, + sck: Option>, + miso: Option>, + mosi: Option>, + config: Config, + ) -> Self { + compiler_fence(Ordering::SeqCst); + + into_ref!(spis, cs); + + let r = T::regs(); + + // Configure pins. + cs.conf().write(|w| w.set_input(gpiovals::Input::CONNECT)); + r.psel().csn().write_value(cs.psel_bits()); + if let Some(sck) = &sck { + sck.conf().write(|w| w.set_input(gpiovals::Input::CONNECT)); + r.psel().sck().write_value(sck.psel_bits()); + } + if let Some(mosi) = &mosi { + mosi.conf().write(|w| w.set_input(gpiovals::Input::CONNECT)); + r.psel().mosi().write_value(mosi.psel_bits()); + } + if let Some(miso) = &miso { + miso.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + convert_drive(w, config.miso_drive); + }); + r.psel().miso().write_value(miso.psel_bits()); + } + + // Enable SPIS instance. + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + + let mut spis = Self { _p: spis }; + + // Apply runtime peripheral configuration + Self::set_config(&mut spis, &config).unwrap(); + + // Disable all events interrupts. + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + spis + } + + 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. + if tx.len() > EASY_DMA_SIZE { + return Err(Error::TxBufferTooLong); + } + r.txd().ptr().write_value(tx as *const u8 as _); + r.txd().maxcnt().write(|w| w.set_maxcnt(tx.len() as _)); + + // Set up the DMA read. + if rx.len() > EASY_DMA_SIZE { + return Err(Error::RxBufferTooLong); + } + r.rxd().ptr().write_value(rx as *mut u8 as _); + r.rxd().maxcnt().write(|w| w.set_maxcnt(rx.len() as _)); + + // Reset end event. + r.events_end().write_value(0); + + // Release the semaphore. + r.tasks_release().write_value(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().0 != 1 { + r.events_acquired().write_value(0); + r.tasks_acquire().write_value(1); + // Wait until CPU has acquired the semaphore. + while r.semstat().read().0 != 1 {} + } + + self.prepare(rx, tx)?; + + // Wait for 'end' event. + while r.events_end().read() == 0 {} + + let n_rx = r.rxd().amount().read().0 as usize; + let n_tx = r.txd().amount().read().0 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.set_overflow(true); + w.set_overread(true); + }); + + // Acquire semaphore. + if r.semstat().read().0 != 1 { + // Reset and enable the acquire event. + r.events_acquired().write_value(0); + r.intenset().write(|w| w.set_acquired(true)); + + // Request acquiring the SPIS semaphore. + r.tasks_acquire().write_value(1); + + // Wait until CPU has acquired the semaphore. + poll_fn(|cx| { + s.waker.register(cx.waker()); + if r.events_acquired().read() == 1 { + r.events_acquired().write_value(0); + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + } + + self.prepare(rx, tx)?; + + // Wait for 'end' event. + r.intenset().write(|w| w.set_end(true)); + poll_fn(|cx| { + s.waker.register(cx.waker()); + if r.events_end().read() != 0 { + r.events_end().write_value(0); + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + + let n_rx = r.rxd().amount().read().0 as usize; + let n_tx = r.txd().amount().read().0 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() + } + + /// Checks if last transaction overflowed. + pub fn is_overflow(&mut self) -> bool { + T::regs().status().read().overflow() + } +} + +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.set_enable(vals::Enable::DISABLED)); + + gpio::deconfigure_pin(r.psel().sck().read()); + gpio::deconfigure_pin(r.psel().csn().read()); + gpio::deconfigure_pin(r.psel().miso().read()); + gpio::deconfigure_pin(r.psel().mosi().read()); + + trace!("spis drop: done"); + } +} + +pub(crate) struct State { + waker: AtomicWaker, +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::spis::Spis; + fn state() -> &'static State; +} + +/// SPIS peripheral instance +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_spis { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::spis::SealedInstance for peripherals::$type { + fn regs() -> pac::spis::Spis { + pac::$pac_type + } + fn state() -> &'static crate::spis::State { + static STATE: crate::spis::State = crate::spis::State::new(); + &STATE + } + } + impl crate::spis::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +// ==================== + +impl<'d, T: Instance> SetConfig for Spis<'d, T> { + type Config = Config; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + let r = T::regs(); + // Configure mode. + let mode = config.mode; + r.config().write(|w| { + w.set_order(config.bit_order); + match mode { + MODE_0 => { + w.set_cpol(vals::Cpol::ACTIVE_HIGH); + w.set_cpha(vals::Cpha::LEADING); + } + MODE_1 => { + w.set_cpol(vals::Cpol::ACTIVE_HIGH); + w.set_cpha(vals::Cpha::TRAILING); + } + MODE_2 => { + w.set_cpol(vals::Cpol::ACTIVE_LOW); + w.set_cpha(vals::Cpha::LEADING); + } + MODE_3 => { + w.set_cpol(vals::Cpol::ACTIVE_LOW); + w.set_cpha(vals::Cpha::TRAILING); + } + } + }); + + // Set over-read character. + let orc = config.orc; + r.orc().write(|w| w.set_orc(orc)); + + // Set default character. + let def = config.def; + r.def().write(|w| w.set_def(def)); + + // Configure auto-acquire on 'transfer end' event. + let auto_acquire = config.auto_acquire; + r.shorts().write(|w| w.set_end_acquire(auto_acquire)); + + Ok(()) + } +} diff --git a/embassy/embassy-nrf/src/temp.rs b/embassy/embassy-nrf/src/temp.rs new file mode 100644 index 0000000..1488c5c --- /dev/null +++ b/embassy/embassy-nrf/src/temp.rs @@ -0,0 +1,101 @@ +//! Builtin temperature sensor driver. + +use core::future::poll_fn; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use fixed::types::I30F2; + +use crate::interrupt::InterruptExt; +use crate::peripherals::TEMP; +use crate::{interrupt, pac, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler { + _private: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = pac::TEMP; + r.intenclr().write(|w| w.set_datardy(true)); + WAKER.wake(); + } +} + +/// Builtin temperature sensor driver. +pub struct Temp<'d> { + _peri: PeripheralRef<'d, TEMP>, +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +impl<'d> Temp<'d> { + /// Create a new temperature sensor driver. + pub fn new( + _peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, + ) -> Self { + into_ref!(_peri); + + // Enable interrupt that signals temperature values + interrupt::TEMP.unpend(); + unsafe { interrupt::TEMP.enable() }; + + Self { _peri } + } + + /// Perform an asynchronous temperature measurement. The returned future + /// can be awaited to obtain the measurement. + /// + /// If the future is dropped, the measurement is cancelled. + /// + /// # Example + /// + /// ```no_run + /// use embassy_nrf::{bind_interrupts, temp}; + /// use embassy_nrf::temp::Temp; + /// + /// bind_interrupts!(struct Irqs { + /// TEMP => temp::InterruptHandler; + /// }); + /// + /// # async { + /// # let p: embassy_nrf::Peripherals = todo!(); + /// let mut t = Temp::new(p.TEMP, Irqs); + /// let v: u16 = t.read().await.to_num::(); + /// # }; + /// ``` + pub async fn read(&mut self) -> I30F2 { + // In case the future is dropped, stop the task and reset events. + let on_drop = OnDrop::new(|| { + let t = Self::regs(); + t.tasks_stop().write_value(1); + t.events_datardy().write_value(0); + }); + + let t = Self::regs(); + t.intenset().write(|w| w.set_datardy(true)); + t.tasks_start().write_value(1); + + let value = poll_fn(|cx| { + WAKER.register(cx.waker()); + if t.events_datardy().read() == 0 { + Poll::Pending + } else { + t.events_datardy().write_value(0); + let raw = t.temp().read(); + Poll::Ready(I30F2::from_bits(raw as i32)) + } + }) + .await; + on_drop.defuse(); + value + } + + fn regs() -> pac::temp::Temp { + pac::TEMP + } +} diff --git a/embassy/embassy-nrf/src/time_driver.rs b/embassy/embassy-nrf/src/time_driver.rs new file mode 100644 index 0000000..a27fae9 --- /dev/null +++ b/embassy/embassy-nrf/src/time_driver.rs @@ -0,0 +1,295 @@ +use core::cell::{Cell, RefCell}; +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; + +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex; +use embassy_time_driver::Driver; +use embassy_time_queue_driver::Queue; + +use crate::interrupt::InterruptExt; +use crate::{interrupt, pac}; + +#[cfg(feature = "_nrf54l")] +fn rtc() -> pac::rtc::Rtc { + pac::RTC30 +} +#[cfg(not(feature = "_nrf54l"))] +fn rtc() -> pac::rtc::Rtc { + pac::RTC1 +} + +/// Calculate the timestamp from the period count and the tick count. +/// +/// The RTC counter is 24 bit. Ticking at 32768hz, it overflows every ~8 minutes. This is +/// too short. We must make it "never" overflow. +/// +/// The obvious way would be to count overflow periods. Every time the counter overflows, +/// increase a `periods` variable. `now()` simply does `periods << 24 + counter`. So, the logic +/// around an overflow would look like this: +/// +/// ```not_rust +/// periods = 1, counter = 0xFF_FFFE --> now = 0x1FF_FFFE +/// periods = 1, counter = 0xFF_FFFF --> now = 0x1FF_FFFF +/// **OVERFLOW** +/// periods = 2, counter = 0x00_0000 --> now = 0x200_0000 +/// periods = 2, counter = 0x00_0001 --> now = 0x200_0001 +/// ``` +/// +/// The problem is this is vulnerable to race conditions if `now()` runs at the exact time an +/// overflow happens. +/// +/// If `now()` reads `periods` first and `counter` later, and overflow happens between the reads, +/// it would return a wrong value: +/// +/// ```not_rust +/// periods = 1 (OLD), counter = 0x00_0000 (NEW) --> now = 0x100_0000 -> WRONG +/// ``` +/// +/// It fails similarly if it reads `counter` first and `periods` second. +/// +/// To fix this, we define a "period" to be 2^23 ticks (instead of 2^24). One "overflow cycle" is 2 periods. +/// +/// - `period` is incremented on overflow (at counter value 0) +/// - `period` is incremented "midway" between overflows (at counter value 0x80_0000) +/// +/// Therefore, when `period` is even, counter is in 0..0x7f_ffff. When odd, counter is in 0x80_0000..0xFF_FFFF +/// This allows for now() to return the correct value even if it races an overflow. +/// +/// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches +/// the expected range for the `period` parity, we're done. If it doesn't, this means that +/// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value +/// corresponds to the next period. +/// +/// `period` is a 32bit integer, so It overflows on 2^32 * 2^23 / 32768 seconds of uptime, which is 34865 +/// years. For comparison, flash memory like the one containing your firmware is usually rated to retain +/// data for only 10-20 years. 34865 years is long enough! +fn calc_now(period: u32, counter: u32) -> u64 { + ((period as u64) << 23) + ((counter ^ ((period & 1) << 23)) as u64) +} + +fn compare_n(n: usize) -> u32 { + 1 << (n + 16) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_calc_now() { + assert_eq!(calc_now(0, 0x000000), 0x0_000000); + assert_eq!(calc_now(0, 0x000001), 0x0_000001); + assert_eq!(calc_now(0, 0x7FFFFF), 0x0_7FFFFF); + assert_eq!(calc_now(1, 0x7FFFFF), 0x1_7FFFFF); + assert_eq!(calc_now(0, 0x800000), 0x0_800000); + assert_eq!(calc_now(1, 0x800000), 0x0_800000); + assert_eq!(calc_now(1, 0x800001), 0x0_800001); + assert_eq!(calc_now(1, 0xFFFFFF), 0x0_FFFFFF); + assert_eq!(calc_now(2, 0xFFFFFF), 0x1_FFFFFF); + assert_eq!(calc_now(1, 0x000000), 0x1_000000); + assert_eq!(calc_now(2, 0x000000), 0x1_000000); + } +} + +struct AlarmState { + timestamp: Cell, +} + +unsafe impl Send for AlarmState {} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + } + } +} + +struct RtcDriver { + /// Number of 2^23 periods elapsed since boot. + period: AtomicU32, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, + queue: Mutex>, +} + +embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { + period: AtomicU32::new(0), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), + queue: Mutex::new(RefCell::new(Queue::new())), +}); + +impl RtcDriver { + fn init(&'static self, irq_prio: crate::interrupt::Priority) { + let r = rtc(); + r.cc(3).write(|w| w.set_compare(0x800000)); + + r.intenset().write(|w| { + w.set_ovrflw(true); + w.set_compare(3, true); + }); + + r.tasks_clear().write_value(1); + r.tasks_start().write_value(1); + + // Wait for clear + while r.counter().read().0 != 0 {} + + #[cfg(feature = "_nrf54l")] + { + interrupt::RTC30.set_priority(irq_prio); + unsafe { interrupt::RTC30.enable() }; + } + #[cfg(not(feature = "_nrf54l"))] + { + interrupt::RTC1.set_priority(irq_prio); + unsafe { interrupt::RTC1.enable() }; + } + } + + fn on_interrupt(&self) { + let r = rtc(); + if r.events_ovrflw().read() == 1 { + r.events_ovrflw().write_value(0); + self.next_period(); + } + + if r.events_compare(3).read() == 1 { + r.events_compare(3).write_value(0); + self.next_period(); + } + + let n = 0; + if r.events_compare(n).read() == 1 { + r.events_compare(n).write_value(0); + critical_section::with(|cs| { + self.trigger_alarm(cs); + }); + } + } + + fn next_period(&self) { + critical_section::with(|cs| { + let r = rtc(); + let period = self.period.load(Ordering::Relaxed) + 1; + self.period.store(period, Ordering::Relaxed); + let t = (period as u64) << 23; + + let n = 0; + let alarm = &self.alarms.borrow(cs); + let at = alarm.timestamp.get(); + + if at < t + 0xc00000 { + // just enable it. `set_alarm` has already set the correct CC val. + r.intenset().write(|w| w.0 = compare_n(n)); + } + }) + } + + fn trigger_alarm(&self, cs: CriticalSection) { + let n = 0; + let r = rtc(); + r.intenclr().write(|w| w.0 = compare_n(n)); + + let alarm = &self.alarms.borrow(cs); + alarm.timestamp.set(u64::MAX); + + // Call after clearing alarm, so the callback can set another alarm. + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + } + } + + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let n = 0; + let alarm = &self.alarms.borrow(cs); + alarm.timestamp.set(timestamp); + + 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| w.0 = 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. + // This way, when we enable it later, the right value is already set. + + // nrf52 docs say: + // If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event. + // To workaround this, we never write a timestamp smaller than N+3. + // N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc. + // + // It is impossible for rtc to tick more than once because + // - this code takes less time than 1 tick + // - it runs with interrupts disabled so nothing else can preempt it. + // + // This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed + // by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time, + // and we don't do that here. + let safe_timestamp = timestamp.max(t + 3); + r.cc(n).write(|w| w.set_compare(safe_timestamp as u32 & 0xFFFFFF)); + + let diff = timestamp - t; + if diff < 0xc00000 { + r.intenset().write(|w| w.0 = compare_n(n)); + } else { + // If it's too far in the future, don't setup the compare channel yet. + // It will be setup later by `next_period`. + r.intenclr().write(|w| w.0 = compare_n(n)); + } + + true + } +} + +impl Driver for RtcDriver { + fn now(&self) -> u64 { + // `period` MUST be read before `counter`, see comment at the top for details. + let period = self.period.load(Ordering::Relaxed); + compiler_fence(Ordering::Acquire); + let counter = rtc().counter().read().0; + calc_now(period, counter) + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} + +#[cfg(feature = "_nrf54l")] +#[cfg(feature = "rt")] +#[interrupt] +fn RTC30() { + DRIVER.on_interrupt() +} + +#[cfg(not(feature = "_nrf54l"))] +#[cfg(feature = "rt")] +#[interrupt] +fn RTC1() { + DRIVER.on_interrupt() +} + +pub(crate) fn init(irq_prio: crate::interrupt::Priority) { + DRIVER.init(irq_prio) +} diff --git a/embassy/embassy-nrf/src/timer.rs b/embassy/embassy-nrf/src/timer.rs new file mode 100644 index 0000000..a9aeb40 --- /dev/null +++ b/embassy/embassy-nrf/src/timer.rs @@ -0,0 +1,295 @@ +//! 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 embassy_hal_internal::{into_ref, PeripheralRef}; + +use crate::pac::timer::vals; +use crate::ppi::{Event, Task}; +use crate::{pac, Peripheral}; + +pub(crate) trait SealedInstance { + /// The number of CC registers this instance has. + const CCS: usize; + fn regs() -> pac::timer::Timer; +} + +/// Basic Timer instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: crate::interrupt::typelevel::Interrupt; +} + +/// Extended timer instance. +pub trait ExtendedInstance: Instance {} + +macro_rules! impl_timer { + ($type:ident, $pac_type:ident, $irq:ident, $ccs:literal) => { + impl crate::timer::SealedInstance for peripherals::$type { + const CCS: usize = $ccs; + fn regs() -> pac::timer::Timer { + unsafe { pac::timer::Timer::from_ptr(pac::$pac_type.as_ptr()) } + } + } + impl crate::timer::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; + ($type:ident, $pac_type:ident, $irq:ident) => { + impl_timer!($type, $pac_type, $irq, 4); + }; + ($type:ident, $pac_type:ident, $irq:ident, extended) => { + impl_timer!($type, $pac_type, $irq, 6); + impl crate::timer::ExtendedInstance for peripherals::$type {} + }; +} + +/// Timer frequency +#[repr(u8)] +pub enum Frequency { + /// 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, +} + +/// nRF Timer driver. +/// +/// The timer has an internal counter, which is incremented for every tick of the timer. +/// The counter is 32-bit, so it wraps back to 0 when it reaches 2^32. +/// +/// It has either 4 or 6 Capture/Compare registers, which can be used to capture the current state of the counter +/// or trigger an event when the counter reaches a certain value. + +/// Timer driver. +pub struct Timer<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Timer<'d, T> { + /// Create a new `Timer` driver. + /// + /// This can be useful for triggering tasks via PPI + /// `Uarte` uses this internally. + pub fn new(timer: impl Peripheral

+ 'd) -> Self { + Self::new_inner(timer, false) + } + + /// Create a new `Timer` driver in counter mode. + /// + /// This can be useful for triggering tasks via PPI + /// `Uarte` uses this internally. + pub fn new_counter(timer: impl Peripheral

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

+ 'd, _is_counter: bool) -> Self { + into_ref!(timer); + + let regs = T::regs(); + + let this = Self { _p: timer }; + + // Stop the timer before doing anything else, + // since changing BITMODE while running can cause 'unpredictable behaviour' according to the specification. + this.stop(); + + regs.mode().write(|w| { + w.set_mode(match _is_counter { + #[cfg(not(feature = "_nrf51"))] + true => vals::Mode::LOW_POWER_COUNTER, + #[cfg(feature = "_nrf51")] + true => vals::Mode::COUNTER, + false => vals::Mode::TIMER, + }) + }); + + // Make the counter's max value as high as possible. + // TODO: is there a reason someone would want to set this lower? + regs.bitmode().write(|w| w.set_bitmode(vals::Bitmode::_32BIT)); + + // Initialize the counter at 0. + this.clear(); + + // Default to the max frequency of the lower power clock + this.set_frequency(Frequency::F1MHz); + + for n in 0..T::CCS { + let cc = this.cc(n); + // Initialize all the shorts as disabled. + cc.unshort_compare_clear(); + cc.unshort_compare_stop(); + // Initialize the CC registers as 0. + cc.write(0); + } + + this + } + + /// Starts the timer. + pub fn start(&self) { + T::regs().tasks_start().write_value(1) + } + + /// Stops the timer. + pub fn stop(&self) { + T::regs().tasks_stop().write_value(1) + } + + /// Reset the timer's counter to 0. + pub fn clear(&self) { + T::regs().tasks_clear().write_value(1) + } + + /// Returns the START task, for use with PPI. + /// + /// When triggered, this task starts the timer. + pub fn task_start(&self) -> Task<'d> { + Task::from_reg(T::regs().tasks_start()) + } + + /// Returns the STOP task, for use with PPI. + /// + /// When triggered, this task stops the timer. + pub fn task_stop(&self) -> Task<'d> { + Task::from_reg(T::regs().tasks_stop()) + } + + /// Returns the CLEAR task, for use with PPI. + /// + /// When triggered, this task resets the timer's counter to 0. + pub fn task_clear(&self) -> Task<'d> { + Task::from_reg(T::regs().tasks_clear()) + } + + /// Returns the COUNT task, for use with PPI. + /// + /// When triggered, this task increments the timer's counter by 1. + /// Only works in counter mode. + pub fn task_count(&self) -> Task<'d> { + Task::from_reg(T::regs().tasks_count()) + } + + /// Change the timer's frequency. + /// + /// This will stop the timer if it isn't already stopped, + /// because the timer may exhibit 'unpredictable behaviour' if it's frequency is changed while it's running. + pub fn set_frequency(&self, frequency: Frequency) { + self.stop(); + + T::regs() + .prescaler() + // SAFETY: `frequency` is a variant of `Frequency`, + // whose values are all in the range of 0-9 (the valid range of `prescaler`). + .write(|w| w.set_prescaler(frequency as u8)) + } + + /// Returns this timer's `n`th CC register. + /// + /// # Panics + /// Panics if `n` >= the number of CC registers this timer has (4 for a normal timer, 6 for an extended timer). + pub fn cc(&self, n: usize) -> Cc<'d, T> { + if n >= T::CCS { + panic!("Cannot get CC register {} of timer with {} CC registers.", n, T::CCS); + } + Cc { + n, + _p: unsafe { self._p.clone_unchecked() }, + } + } +} + +/// A representation of a timer's Capture/Compare (CC) register. +/// +/// A CC register holds a 32-bit value. +/// This is used either to store a capture of the timer's current count, or to specify the value for the timer to compare against. +/// +/// The timer will fire the register's COMPARE event when its counter reaches the value stored in the register. +/// When the register's CAPTURE task is triggered, the timer will store the current value of its counter in the register +pub struct Cc<'d, T: Instance> { + n: usize, + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Cc<'d, T> { + /// Get the current value stored in the register. + pub fn read(&self) -> u32 { + return T::regs().cc(self.n).read(); + } + + /// Set the value stored in the register. + /// + /// `event_compare` will fire when the timer's counter reaches this value. + pub fn write(&self, value: u32) { + T::regs().cc(self.n).write_value(value); + } + + /// Capture the current value of the timer's counter in this register, and return it. + pub fn capture(&self) -> u32 { + T::regs().tasks_capture(self.n).write_value(1); + self.read() + } + + /// Returns this CC register's CAPTURE task, for use with PPI. + /// + /// When triggered, this task will capture the current value of the timer's counter in this register. + pub fn task_capture(&self) -> Task<'d> { + Task::from_reg(T::regs().tasks_capture(self.n)) + } + + /// Returns this CC register's COMPARE event, for use with PPI. + /// + /// This event will fire when the timer's counter reaches the value in this CC register. + pub fn event_compare(&self) -> Event<'d> { + Event::from_reg(T::regs().events_compare(self.n)) + } + + /// Enable the shortcut between this CC register's COMPARE event and the timer's CLEAR task. + /// + /// This means that when the COMPARE event is fired, the CLEAR task will be triggered. + /// + /// So, when the timer's counter reaches the value stored in this register, the timer's counter will be reset to 0. + pub fn short_compare_clear(&self) { + T::regs().shorts().modify(|w| w.set_compare_clear(self.n, true)) + } + + /// Disable the shortcut between this CC register's COMPARE event and the timer's CLEAR task. + pub fn unshort_compare_clear(&self) { + T::regs().shorts().modify(|w| w.set_compare_clear(self.n, false)) + } + + /// Enable the shortcut between this CC register's COMPARE event and the timer's STOP task. + /// + /// This means that when the COMPARE event is fired, the STOP task will be triggered. + /// + /// So, when the timer's counter reaches the value stored in this register, the timer will stop counting up. + pub fn short_compare_stop(&self) { + T::regs().shorts().modify(|w| w.set_compare_stop(self.n, true)) + } + + /// Disable the shortcut between this CC register's COMPARE event and the timer's STOP task. + pub fn unshort_compare_stop(&self) { + T::regs().shorts().modify(|w| w.set_compare_stop(self.n, false)) + } +} diff --git a/embassy/embassy-nrf/src/twim.rs b/embassy/embassy-nrf/src/twim.rs new file mode 100644 index 0000000..ebad39d --- /dev/null +++ b/embassy/embassy-nrf/src/twim.rs @@ -0,0 +1,942 @@ +//! I2C-compatible Two Wire Interface in master mode (TWIM) driver. + +#![macro_use] + +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::sync::atomic::compiler_fence; +use core::sync::atomic::Ordering::SeqCst; +use core::task::Poll; + +use embassy_embedded_hal::SetConfig; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +#[cfg(feature = "time")] +use embassy_time::{Duration, Instant}; +use embedded_hal_1::i2c::Operation; +pub use pac::twim::vals::Frequency; + +use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; +use crate::gpio::Pin as GpioPin; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::twim::vals; +use crate::util::slice_in_ram; +use crate::{gpio, interrupt, pac, Peripheral}; + +/// 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, +} + +impl Default for Config { + fn default() -> Self { + Self { + frequency: Frequency::K100, + scl_high_drive: false, + sda_pullup: false, + sda_high_drive: false, + scl_pullup: false, + } + } +} + +/// 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, + /// 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, +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + if r.events_suspended().read() != 0 { + s.end_waker.wake(); + r.intenclr().write(|w| w.set_suspended(true)); + } + if r.events_stopped().read() != 0 { + s.end_waker.wake(); + r.intenclr().write(|w| w.set_stopped(true)); + } + if r.events_error().read() != 0 { + s.end_waker.wake(); + r.intenclr().write(|w| w.set_error(true)); + } + } +} + +/// TWI driver. +pub struct Twim<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Twim<'d, T> { + /// Create a new TWI driver. + pub fn new( + twim: impl Peripheral

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

+ 'd, + scl: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(twim, sda, scl); + + let r = T::regs(); + + // Configure pins + sda.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(match config.sda_high_drive { + true => gpiovals::Drive::H0D1, + false => gpiovals::Drive::S0D1, + }); + if config.sda_pullup { + w.set_pull(gpiovals::Pull::PULLUP); + } + }); + scl.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(match config.scl_high_drive { + true => gpiovals::Drive::H0D1, + false => gpiovals::Drive::S0D1, + }); + if config.sda_pullup { + w.set_pull(gpiovals::Pull::PULLUP); + } + }); + + // Select pins. + r.psel().sda().write_value(sda.psel_bits()); + r.psel().scl().write_value(scl.psel_bits()); + + // Enable TWIM instance. + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + + let mut twim = Self { _p: twim }; + + // Apply runtime peripheral configuration + Self::set_config(&mut twim, &config).unwrap(); + + // Disable all events interrupts + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + twim + } + + /// Set TX buffer, checking that it is in RAM and has suitable length. + unsafe fn set_tx_buffer( + &mut self, + buffer: &[u8], + ram_buffer: Option<&mut [MaybeUninit; FORCE_COPY_BUFFER_SIZE]>, + ) -> Result<(), Error> { + let buffer = if slice_in_ram(buffer) { + buffer + } else { + let ram_buffer = ram_buffer.ok_or(Error::BufferNotInRAM)?; + trace!("Copying TWIM tx buffer into RAM for DMA"); + let ram_buffer = &mut ram_buffer[..buffer.len()]; + // Inline implementation of the nightly API MaybeUninit::copy_from_slice(ram_buffer, buffer) + let uninit_src: &[MaybeUninit] = unsafe { core::mem::transmute(buffer) }; + ram_buffer.copy_from_slice(uninit_src); + unsafe { &*(ram_buffer as *const [MaybeUninit] as *const [u8]) } + }; + + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::TxBufferTooLong); + } + + let r = T::regs(); + + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + r.txd().ptr().write_value(buffer.as_ptr() as u32); + r.txd().maxcnt().write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to `u8` is also fine. + // + // The MAXCNT field is 8 bits wide and accepts the full range of + // values. + w.set_maxcnt(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(); + + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + r.rxd().ptr().write_value(buffer.as_mut_ptr() as u32); + r.rxd().maxcnt().write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to the type of maxcnt + // 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.set_maxcnt(buffer.len() as _)); + + Ok(()) + } + + fn clear_errorsrc(&mut self) { + let r = T::regs(); + r.errorsrc().write(|w| { + w.set_anack(true); + w.set_dnack(true); + w.set_overrun(true); + }); + } + + /// Get Error instance, if any occurred. + fn check_errorsrc(&self) -> Result<(), Error> { + let r = T::regs(); + + let err = r.errorsrc().read(); + if err.anack() { + return Err(Error::AddressNack); + } + if err.dnack() { + return Err(Error::DataNack); + } + if err.overrun() { + return Err(Error::Overrun); + } + Ok(()) + } + + fn check_rx(&self, len: usize) -> Result<(), Error> { + let r = T::regs(); + if r.rxd().amount().read().0 != len as u32 { + Err(Error::Receive) + } else { + Ok(()) + } + } + + fn check_tx(&self, len: usize) -> Result<(), Error> { + let r = T::regs(); + if r.txd().amount().read().0 != len as u32 { + Err(Error::Transmit) + } else { + Ok(()) + } + } + + /// Wait for stop or error + fn blocking_wait(&mut self) { + let r = T::regs(); + loop { + if r.events_suspended().read() != 0 || r.events_stopped().read() != 0 { + r.events_suspended().write_value(0); + r.events_stopped().write_value(0); + break; + } + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + } + } + } + + /// Wait for stop or error + #[cfg(feature = "time")] + fn blocking_wait_timeout(&mut self, timeout: Duration) -> Result<(), Error> { + let r = T::regs(); + let deadline = Instant::now() + timeout; + loop { + if r.events_suspended().read() != 0 || r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + break; + } + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + } + if Instant::now() > deadline { + r.tasks_stop().write_value(1); + return Err(Error::Timeout); + } + } + + Ok(()) + } + + /// 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.end_waker.register(cx.waker()); + if r.events_suspended().read() != 0 || r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + + return Poll::Ready(()); + } + + // stop if an error occurred + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + } + + Poll::Pending + }) + } + + fn setup_operations( + &mut self, + address: u8, + operations: &mut [Operation<'_>], + tx_ram_buffer: Option<&mut [MaybeUninit; FORCE_COPY_BUFFER_SIZE]>, + last_op: Option<&Operation<'_>>, + inten: bool, + ) -> Result { + let r = T::regs(); + + compiler_fence(SeqCst); + + r.address().write(|w| w.set_address(address)); + + r.events_suspended().write_value(0); + r.events_stopped().write_value(0); + r.events_error().write_value(0); + self.clear_errorsrc(); + + if inten { + r.intenset().write(|w| { + w.set_suspended(true); + w.set_stopped(true); + w.set_error(true); + }); + } else { + r.intenclr().write(|w| { + w.set_suspended(true); + w.set_stopped(true); + w.set_error(true); + }); + } + + assert!(!operations.is_empty()); + match operations { + [Operation::Read(_), Operation::Read(_), ..] => { + panic!("Consecutive read operations are not supported!") + } + [Operation::Read(rd_buffer), Operation::Write(wr_buffer), rest @ ..] => { + let stop = rest.is_empty(); + + // Set up DMA buffers. + unsafe { + self.set_tx_buffer(wr_buffer, tx_ram_buffer)?; + self.set_rx_buffer(rd_buffer)?; + } + + r.shorts().write(|w| { + w.set_lastrx_starttx(true); + if stop { + w.set_lasttx_stop(true); + } else { + w.set_lasttx_suspend(true); + } + }); + + // Start read+write operation. + r.tasks_startrx().write_value(1); + if last_op.is_some() { + r.tasks_resume().write_value(1); + } + + // TODO: Handle empty write buffer + if rd_buffer.is_empty() { + // With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STARTTX ourselves. + r.tasks_starttx().write_value(1); + } + + Ok(2) + } + [Operation::Read(buffer)] => { + // Set up DMA buffers. + unsafe { + self.set_rx_buffer(buffer)?; + } + + r.shorts().write(|w| w.set_lastrx_stop(true)); + + // Start read operation. + r.tasks_startrx().write_value(1); + if last_op.is_some() { + r.tasks_resume().write_value(1); + } + + if buffer.is_empty() { + // With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STOP ourselves. + r.tasks_stop().write_value(1); + } + + Ok(1) + } + [Operation::Write(wr_buffer), Operation::Read(rd_buffer)] + if !wr_buffer.is_empty() && !rd_buffer.is_empty() => + { + // Set up DMA buffers. + unsafe { + self.set_tx_buffer(wr_buffer, tx_ram_buffer)?; + self.set_rx_buffer(rd_buffer)?; + } + + // Start write+read operation. + r.shorts().write(|w| { + w.set_lasttx_startrx(true); + w.set_lastrx_stop(true); + }); + + r.tasks_starttx().write_value(1); + if last_op.is_some() { + r.tasks_resume().write_value(1); + } + + Ok(2) + } + [Operation::Write(buffer), rest @ ..] => { + let stop = rest.is_empty(); + + // Set up DMA buffers. + unsafe { + self.set_tx_buffer(buffer, tx_ram_buffer)?; + } + + // Start write operation. + r.shorts().write(|w| { + if stop { + w.set_lasttx_stop(true); + } else { + w.set_lasttx_suspend(true); + } + }); + + r.tasks_starttx().write_value(1); + if last_op.is_some() { + r.tasks_resume().write_value(1); + } + + if buffer.is_empty() { + // With a zero-length buffer, LASTTX doesn't fire (because there's no last byte!), so do the STOP/SUSPEND ourselves. + if stop { + r.tasks_stop().write_value(1); + } else { + r.tasks_suspend().write_value(1); + } + } + + Ok(1) + } + [] => unreachable!(), + } + } + + fn check_operations(&mut self, operations: &[Operation<'_>]) -> Result<(), Error> { + compiler_fence(SeqCst); + self.check_errorsrc()?; + + assert!(operations.len() == 1 || operations.len() == 2); + match operations { + [Operation::Read(rd_buffer), Operation::Write(wr_buffer)] + | [Operation::Write(wr_buffer), Operation::Read(rd_buffer)] => { + self.check_rx(rd_buffer.len())?; + self.check_tx(wr_buffer.len())?; + } + [Operation::Read(buffer)] => { + self.check_rx(buffer.len())?; + } + [Operation::Write(buffer), ..] => { + self.check_tx(buffer.len())?; + } + _ => unreachable!(), + } + Ok(()) + } + + // =========================================== + + /// Execute the provided operations on the I2C bus. + /// + /// Each buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// + /// Consecutive `Operation::Read`s are not supported due to hardware + /// limitations. + /// + /// An `Operation::Write` following an `Operation::Read` must have a + /// non-empty buffer. + pub fn blocking_transaction(&mut self, address: u8, mut operations: &mut [Operation<'_>]) -> Result<(), Error> { + let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; + let mut last_op = None; + while !operations.is_empty() { + let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), last_op, false)?; + let (in_progress, rest) = operations.split_at_mut(ops); + self.blocking_wait(); + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; + } + Ok(()) + } + + /// Same as [`blocking_transaction`](Twim::blocking_transaction) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub fn blocking_transaction_from_ram( + &mut self, + address: u8, + mut operations: &mut [Operation<'_>], + ) -> Result<(), Error> { + let mut last_op = None; + while !operations.is_empty() { + let ops = self.setup_operations(address, operations, None, last_op, false)?; + let (in_progress, rest) = operations.split_at_mut(ops); + self.blocking_wait(); + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; + } + Ok(()) + } + + /// Execute the provided operations on the I2C bus with timeout. + /// + /// See [`blocking_transaction`]. + #[cfg(feature = "time")] + pub fn blocking_transaction_timeout( + &mut self, + address: u8, + mut operations: &mut [Operation<'_>], + timeout: Duration, + ) -> Result<(), Error> { + let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; + let mut last_op = None; + while !operations.is_empty() { + let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), last_op, false)?; + let (in_progress, rest) = operations.split_at_mut(ops); + self.blocking_wait_timeout(timeout)?; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; + } + Ok(()) + } + + /// Same as [`blocking_transaction_timeout`](Twim::blocking_transaction_timeout) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + #[cfg(feature = "time")] + pub fn blocking_transaction_from_ram_timeout( + &mut self, + address: u8, + mut operations: &mut [Operation<'_>], + timeout: Duration, + ) -> Result<(), Error> { + let mut last_op = None; + while !operations.is_empty() { + let ops = self.setup_operations(address, operations, None, last_op, false)?; + let (in_progress, rest) = operations.split_at_mut(ops); + self.blocking_wait_timeout(timeout)?; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; + } + Ok(()) + } + + /// Execute the provided operations on the I2C bus. + /// + /// Each buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// + /// Consecutive `Operation::Read`s are not supported due to hardware + /// limitations. + /// + /// An `Operation::Write` following an `Operation::Read` must have a + /// non-empty buffer. + pub async fn transaction(&mut self, address: u8, mut operations: &mut [Operation<'_>]) -> Result<(), Error> { + let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE]; + let mut last_op = None; + while !operations.is_empty() { + let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), last_op, true)?; + let (in_progress, rest) = operations.split_at_mut(ops); + self.async_wait().await; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; + } + Ok(()) + } + + /// Same as [`transaction`](Twim::transaction) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub async fn transaction_from_ram( + &mut self, + address: u8, + mut operations: &mut [Operation<'_>], + ) -> Result<(), Error> { + let mut last_op = None; + while !operations.is_empty() { + let ops = self.setup_operations(address, operations, None, last_op, true)?; + let (in_progress, rest) = operations.split_at_mut(ops); + self.async_wait().await; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; + } + Ok(()) + } + + // =========================================== + + /// Write to an I2C slave. + /// + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + pub fn blocking_write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { + self.blocking_transaction(address, &mut [Operation::Write(buffer)]) + } + + /// Same as [`blocking_write`](Twim::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub fn blocking_write_from_ram(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { + self.blocking_transaction_from_ram(address, &mut [Operation::Write(buffer)]) + } + + /// Read from an I2C slave. + /// + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + pub fn blocking_read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { + self.blocking_transaction(address, &mut [Operation::Read(buffer)]) + } + + /// 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 fn blocking_write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> { + self.blocking_transaction(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)]) + } + + /// Same as [`blocking_write_read`](Twim::blocking_write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub fn blocking_write_read_from_ram( + &mut self, + address: u8, + wr_buffer: &[u8], + rd_buffer: &mut [u8], + ) -> Result<(), Error> { + self.blocking_transaction_from_ram(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)]) + } + + // =========================================== + + /// Write to an I2C slave with timeout. + /// + /// See [`blocking_write`]. + #[cfg(feature = "time")] + pub fn blocking_write_timeout(&mut self, address: u8, buffer: &[u8], timeout: Duration) -> Result<(), Error> { + self.blocking_transaction_timeout(address, &mut [Operation::Write(buffer)], timeout) + } + + /// Same as [`blocking_write`](Twim::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + #[cfg(feature = "time")] + pub fn blocking_write_from_ram_timeout( + &mut self, + address: u8, + buffer: &[u8], + timeout: Duration, + ) -> Result<(), Error> { + self.blocking_transaction_from_ram_timeout(address, &mut [Operation::Write(buffer)], timeout) + } + + /// Read from an I2C slave. + /// + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + #[cfg(feature = "time")] + pub fn blocking_read_timeout(&mut self, address: u8, buffer: &mut [u8], timeout: Duration) -> Result<(), Error> { + self.blocking_transaction_timeout(address, &mut [Operation::Read(buffer)], timeout) + } + + /// 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. + #[cfg(feature = "time")] + pub fn blocking_write_read_timeout( + &mut self, + address: u8, + wr_buffer: &[u8], + rd_buffer: &mut [u8], + timeout: Duration, + ) -> Result<(), Error> { + self.blocking_transaction_timeout( + address, + &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], + timeout, + ) + } + + /// Same as [`blocking_write_read`](Twim::blocking_write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + #[cfg(feature = "time")] + pub fn blocking_write_read_from_ram_timeout( + &mut self, + address: u8, + wr_buffer: &[u8], + rd_buffer: &mut [u8], + timeout: Duration, + ) -> Result<(), Error> { + self.blocking_transaction_from_ram_timeout( + address, + &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], + timeout, + ) + } + + // =========================================== + + /// Read from an I2C slave. + /// + /// 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.transaction(address, &mut [Operation::Read(buffer)]).await + } + + /// 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.transaction(address, &mut [Operation::Write(buffer)]).await + } + + /// Same as [`write`](Twim::write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub async fn write_from_ram(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { + self.transaction_from_ram(address, &mut [Operation::Write(buffer)]) + .await + } + + /// Write data to an I2C slave, then read data from the slave without + /// triggering a stop condition between the two. + /// + /// 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.transaction(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)]) + .await + } + + /// Same as [`write_read`](Twim::write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub async fn write_read_from_ram( + &mut self, + address: u8, + wr_buffer: &[u8], + rd_buffer: &mut [u8], + ) -> Result<(), Error> { + self.transaction_from_ram(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)]) + .await + } +} + +impl<'a, T: Instance> Drop for Twim<'a, T> { + fn drop(&mut self) { + trace!("twim drop"); + + // TODO: check for abort + + // disable! + let r = T::regs(); + r.enable().write(|w| w.set_enable(vals::Enable::DISABLED)); + + gpio::deconfigure_pin(r.psel().sda().read()); + gpio::deconfigure_pin(r.psel().scl().read()); + + trace!("twim drop: done"); + } +} + +pub(crate) struct State { + end_waker: AtomicWaker, +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + end_waker: AtomicWaker::new(), + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::twim::Twim; + fn state() -> &'static State; +} + +/// TWIM peripheral instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_twim { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::twim::SealedInstance for peripherals::$type { + fn regs() -> pac::twim::Twim { + pac::$pac_type + } + fn state() -> &'static crate::twim::State { + static STATE: crate::twim::State = crate::twim::State::new(); + &STATE + } + } + impl crate::twim::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +// ==================== + +mod eh02 { + use super::*; + + impl<'a, T: Instance> embedded_hal_02::blocking::i2c::Write for Twim<'a, T> { + type Error = Error; + + fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + self.blocking_write(addr, bytes) + } + } + + impl<'a, T: Instance> embedded_hal_02::blocking::i2c::Read for Twim<'a, T> { + type Error = Error; + + fn read(&mut self, addr: u8, bytes: &mut [u8]) -> Result<(), Error> { + self.blocking_read(addr, bytes) + } + } + + impl<'a, T: Instance> embedded_hal_02::blocking::i2c::WriteRead for Twim<'a, T> { + type Error = Error; + + fn write_read<'w>(&mut self, addr: u8, bytes: &'w [u8], buffer: &'w mut [u8]) -> Result<(), Error> { + self.blocking_write_read(addr, bytes, buffer) + } + } +} + +impl embedded_hal_1::i2c::Error for Error { + fn kind(&self) -> embedded_hal_1::i2c::ErrorKind { + match *self { + Self::TxBufferTooLong => embedded_hal_1::i2c::ErrorKind::Other, + Self::RxBufferTooLong => embedded_hal_1::i2c::ErrorKind::Other, + Self::Transmit => embedded_hal_1::i2c::ErrorKind::Other, + Self::Receive => embedded_hal_1::i2c::ErrorKind::Other, + Self::BufferNotInRAM => embedded_hal_1::i2c::ErrorKind::Other, + Self::AddressNack => { + embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) + } + Self::DataNack => { + embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Data) + } + Self::Overrun => embedded_hal_1::i2c::ErrorKind::Overrun, + Self::Timeout => embedded_hal_1::i2c::ErrorKind::Other, + } + } +} + +impl<'d, T: Instance> embedded_hal_1::i2c::ErrorType for Twim<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_hal_1::i2c::I2c for Twim<'d, T> { + fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { + self.blocking_transaction(address, operations) + } +} + +impl<'d, T: Instance> embedded_hal_async::i2c::I2c for Twim<'d, T> { + async fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { + self.transaction(address, operations).await + } +} + +impl<'d, T: Instance> SetConfig for Twim<'d, T> { + type Config = Config; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + let r = T::regs(); + r.frequency().write(|w| w.set_frequency(config.frequency)); + + Ok(()) + } +} diff --git a/embassy/embassy-nrf/src/twis.rs b/embassy/embassy-nrf/src/twis.rs new file mode 100644 index 0000000..60de2ed --- /dev/null +++ b/embassy/embassy-nrf/src/twis.rs @@ -0,0 +1,814 @@ +//! I2C-compatible Two Wire Interface in slave mode (TWIM) driver. + +#![macro_use] + +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::sync::atomic::compiler_fence; +use core::sync::atomic::Ordering::SeqCst; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_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::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::twis::vals; +use crate::util::slice_in_ram_or; +use crate::{gpio, interrupt, pac, Peripheral}; + +/// 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), +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + if r.events_read().read() != 0 || r.events_write().read() != 0 { + s.waker.wake(); + r.intenclr().write(|w| { + w.set_read(true); + w.set_write(true); + }); + } + if r.events_stopped().read() != 0 { + s.waker.wake(); + r.intenclr().write(|w| w.set_stopped(true)); + } + if r.events_error().read() != 0 { + s.waker.wake(); + r.intenclr().write(|w| w.set_error(true)); + } + } +} + +/// 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 interrupt::typelevel::Binding> + 'd, + sda: impl Peripheral

+ 'd, + scl: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(twis, sda, scl); + + let r = T::regs(); + + // Configure pins + sda.conf().write(|w| { + w.set_dir(gpiovals::Dir::INPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(match config.sda_high_drive { + true => gpiovals::Drive::H0D1, + false => gpiovals::Drive::S0D1, + }); + if config.sda_pullup { + w.set_pull(gpiovals::Pull::PULLUP); + } + }); + scl.conf().write(|w| { + w.set_dir(gpiovals::Dir::INPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(match config.scl_high_drive { + true => gpiovals::Drive::H0D1, + false => gpiovals::Drive::S0D1, + }); + if config.sda_pullup { + w.set_pull(gpiovals::Pull::PULLUP); + } + }); + + // Select pins. + r.psel().sda().write_value(sda.psel_bits()); + r.psel().scl().write_value(scl.psel_bits()); + + // Enable TWIS instance. + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + + // Disable all events interrupts + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); + + // Set address + r.address(0).write(|w| w.set_address(config.address0)); + r.config().write(|w| w.set_address0(true)); + if let Some(address1) = config.address1 { + r.address(1).write(|w| w.set_address(address1)); + r.config().modify(|w| w.set_address1(true)); + } + + // Set over-read character + r.orc().write(|w| w.set_orc(config.orc)); + + // Generate suspend on read event + r.shorts().write(|w| w.set_read_suspend(true)); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + Self { _p: twis } + } + + /// 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(); + + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + r.txd().ptr().write_value(buffer.as_ptr() as u32); + r.txd().maxcnt().write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to `u8` is also fine. + // + // The MAXCNT field is 8 bits wide and accepts the full range of + // values. + w.set_maxcnt(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(); + + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + r.rxd().ptr().write_value(buffer.as_mut_ptr() as u32); + r.rxd().maxcnt().write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to the type of maxcnt + // 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.set_maxcnt(buffer.len() as _)); + + Ok(()) + } + + fn clear_errorsrc(&mut self) { + let r = T::regs(); + r.errorsrc().write(|w| { + w.set_overflow(true); + w.set_overread(true); + w.set_dnack(true); + }); + } + + /// Returns matched address for latest command. + pub fn address_match(&self) -> u8 { + let r = T::regs(); + r.address(r.match_().read().0 as usize).read().address() + } + + /// Returns the index of the address matched in the latest command. + pub fn address_match_index(&self) -> usize { + T::regs().match_().read().0 as _ + } + + /// Wait for read, write, stop or error + fn blocking_listen_wait(&mut self) -> Result { + let r = T::regs(); + loop { + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + while r.events_stopped().read() == 0 {} + return Err(Error::Overflow); + } + if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + return Err(Error::Bus); + } + if r.events_read().read() != 0 { + r.events_read().write_value(0); + return Ok(Status::Read); + } + if r.events_write().read() != 0 { + r.events_write().write_value(0); + 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 occurred + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + return Err(Error::Overflow); + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + return match status { + Status::Read => Ok(Command::Read), + Status::Write => { + let n = r.rxd().amount().read().0 as usize; + Ok(Command::Write(n)) + } + }; + } else if r.events_read().read() != 0 { + r.events_read().write_value(0); + let n = r.rxd().amount().read().0 as usize; + return Ok(Command::WriteRead(n)); + } + } + } + + /// Wait for stop or error + fn blocking_wait(&mut self) -> Result { + let r = T::regs(); + loop { + // stop if an error occurred + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + let errorsrc = r.errorsrc().read(); + if errorsrc.overread() { + return Err(Error::OverRead); + } else if errorsrc.dnack() { + return Err(Error::DataNack); + } else { + return Err(Error::Bus); + } + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + let n = r.txd().amount().read().0 as usize; + return Ok(n); + } + } + } + + /// 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 occurred + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + let errorsrc = r.errorsrc().read(); + if errorsrc.overread() { + return Err(Error::OverRead); + } else if errorsrc.dnack() { + return Err(Error::DataNack); + } else { + return Err(Error::Bus); + } + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + let n = r.txd().amount().read().0 as usize; + return Ok(n); + } else if Instant::now() > deadline { + r.tasks_stop().write_value(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() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + while r.events_stopped().read() == 0 {} + return Err(Error::Overflow); + } + if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + return Err(Error::Bus); + } + if r.events_read().read() != 0 { + r.events_read().write_value(0); + return Ok(Status::Read); + } + if r.events_write().read() != 0 { + r.events_write().write_value(0); + return Ok(Status::Write); + } + if Instant::now() > deadline { + r.tasks_stop().write_value(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 occurred + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + return Err(Error::Overflow); + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + return match status { + Status::Read => Ok(Command::Read), + Status::Write => { + let n = r.rxd().amount().read().0 as usize; + Ok(Command::Write(n)) + } + }; + } else if r.events_read().read() != 0 { + r.events_read().write_value(0); + let n = r.rxd().amount().read().0 as usize; + return Ok(Command::WriteRead(n)); + } else if Instant::now() > deadline { + r.tasks_stop().write_value(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 occurred + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + let errorsrc = r.errorsrc().read(); + if errorsrc.overread() { + return Poll::Ready(Err(Error::OverRead)); + } else if errorsrc.dnack() { + return Poll::Ready(Err(Error::DataNack)); + } else { + return Poll::Ready(Err(Error::Bus)); + } + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + let n = r.txd().amount().read().0 as usize; + return Poll::Ready(Ok(n)); + } + + 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 occurred + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + return Poll::Ready(Err(Error::Overflow)); + } else if r.events_read().read() != 0 { + r.events_read().write_value(0); + return Poll::Ready(Ok(Status::Read)); + } else if r.events_write().read() != 0 { + r.events_write().write_value(0); + return Poll::Ready(Ok(Status::Write)); + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + 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 occurred + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + return Poll::Ready(Err(Error::Overflow)); + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + return match status { + Status::Read => Poll::Ready(Ok(Command::Read)), + Status::Write => { + let n = r.rxd().amount().read().0 as usize; + Poll::Ready(Ok(Command::Write(n))) + } + }; + } else if r.events_read().read() != 0 { + r.events_read().write_value(0); + let n = r.rxd().amount().read().0 as usize; + return Poll::Ready(Ok(Command::WriteRead(n))); + } + Poll::Pending + }) + } + + 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().write_value(0); + r.events_error().write_value(0); + self.clear_errorsrc(); + + if inten { + r.intenset().write(|w| { + w.set_stopped(true); + w.set_error(true); + }); + } else { + r.intenclr().write(|w| { + w.set_stopped(true); + w.set_error(true); + }); + } + + // Start write operation. + r.tasks_preparetx().write_value(1); + r.tasks_resume().write_value(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().write_value(0); + r.events_write().write_value(0); + r.events_stopped().write_value(0); + r.events_error().write_value(0); + self.clear_errorsrc(); + + if inten { + r.intenset().write(|w| { + w.set_stopped(true); + w.set_error(true); + w.set_read(true); + w.set_write(true); + }); + } else { + r.intenclr().write(|w| { + w.set_stopped(true); + w.set_error(true); + w.set_read(true); + w.set_write(true); + }); + } + + // Start read operation. + r.tasks_preparerx().write_value(1); + + Ok(()) + } + + fn setup_listen_end(&mut self, inten: bool) -> Result<(), Error> { + let r = T::regs(); + compiler_fence(SeqCst); + + // Clear events + r.events_read().write_value(0); + r.events_write().write_value(0); + r.events_stopped().write_value(0); + r.events_error().write_value(0); + self.clear_errorsrc(); + + if inten { + r.intenset().write(|w| { + w.set_stopped(true); + w.set_error(true); + w.set_read(true); + }); + } else { + r.intenclr().write(|w| { + w.set_stopped(true); + w.set_error(true); + w.set_read(true); + }); + } + + 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.set_enable(vals::Enable::DISABLED)); + + gpio::deconfigure_pin(r.psel().sda().read()); + gpio::deconfigure_pin(r.psel().scl().read()); + + trace!("twis drop: done"); + } +} + +pub(crate) struct State { + waker: AtomicWaker, +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::twis::Twis; + fn state() -> &'static State; +} + +/// TWIS peripheral instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_twis { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::twis::SealedInstance for peripherals::$type { + fn regs() -> pac::twis::Twis { + pac::$pac_type + } + fn state() -> &'static crate::twis::State { + static STATE: crate::twis::State = crate::twis::State::new(); + &STATE + } + } + impl crate::twis::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy/embassy-nrf/src/uarte.rs b/embassy/embassy-nrf/src/uarte.rs new file mode 100644 index 0000000..ebb4dd9 --- /dev/null +++ b/embassy/embassy-nrf/src/uarte.rs @@ -0,0 +1,1088 @@ +//! Universal Asynchronous Receiver Transmitter (UART) driver. +//! +//! 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 +//! [crate::buffered_uarte::BufferedUarte] so that reads may continue while processing these +//! other futures. If you do not then you may lose data between reads. +//! +//! An advantage of the [Uarte] has over [crate::buffered_uarte::BufferedUarte] is that less +//! 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::marker::PhantomData; +use core::sync::atomic::{compiler_fence, AtomicU8, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +// Re-export SVD variants to allow user to directly set values. +pub use pac::uarte::vals::{Baudrate, ConfigParity as Parity}; + +use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; +use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits, SealedPin as _, DISCONNECTED}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::uarte::vals; +use crate::ppi::{AnyConfigurableChannel, ConfigurableChannel, Event, Ppi, Task}; +use crate::timer::{Frequency, Instance as TimerInstance, Timer}; +use crate::util::slice_in_ram_or; +use crate::{interrupt, pac, Peripheral}; + +/// UARTE config. +#[derive(Clone)] +#[non_exhaustive] +pub struct Config { + /// Parity bit. + pub parity: Parity, + /// Baud rate. + pub baudrate: Baudrate, +} + +impl Default for Config { + fn default() -> Self { + Self { + parity: Parity::EXCLUDED, + baudrate: Baudrate::BAUD115200, + } + } +} + +bitflags::bitflags! { + /// Error source flags + pub(crate) struct ErrorSource: u32 { + /// Buffer overrun + const OVERRUN = 0x01; + /// Parity error + const PARITY = 0x02; + /// Framing error + const FRAMING = 0x04; + /// Break condition + const BREAK = 0x08; + } +} + +impl ErrorSource { + #[inline] + fn check(self) -> Result<(), Error> { + if self.contains(ErrorSource::OVERRUN) { + Err(Error::Overrun) + } else if self.contains(ErrorSource::PARITY) { + Err(Error::Parity) + } else if self.contains(ErrorSource::FRAMING) { + Err(Error::Framing) + } else if self.contains(ErrorSource::BREAK) { + Err(Error::Break) + } else { + Ok(()) + } + } +} + +/// 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, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// Framing Error + Framing, + /// Parity Error + Parity, + /// Buffer Overrun + Overrun, + /// Break condition + Break, +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + let endrx = r.events_endrx().read(); + let error = r.events_error().read(); + if endrx != 0 || error != 0 { + s.rx_waker.wake(); + if endrx != 0 { + r.intenclr().write(|w| w.set_endrx(true)); + } + if error != 0 { + r.intenclr().write(|w| w.set_error(true)); + } + } + if r.events_endtx().read() != 0 { + s.tx_waker.wake(); + r.intenclr().write(|w| w.set_endtx(true)); + } + } +} + +/// UARTE driver. +pub struct Uarte<'d, T: Instance> { + tx: UarteTx<'d, T>, + rx: UarteRx<'d, T>, +} + +/// 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 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>, +} + +impl<'d, T: Instance> Uarte<'d, T> { + /// Create a new UARTE without hardware flow control + pub fn new( + uarte: impl Peripheral

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

+ 'd, + txd: impl Peripheral

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

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

+ 'd, + txd: impl Peripheral

+ 'd, + cts: impl Peripheral

+ 'd, + rts: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(uarte, rxd, txd, cts, rts); + Self::new_inner( + uarte, + rxd.map_into(), + txd.map_into(), + Some(cts.map_into()), + Some(rts.map_into()), + config, + ) + } + + fn new_inner( + uarte: PeripheralRef<'d, T>, + rxd: PeripheralRef<'d, AnyPin>, + txd: PeripheralRef<'d, AnyPin>, + cts: Option>, + rts: Option>, + config: Config, + ) -> Self { + let r = T::regs(); + + let hardware_flow_control = match (rts.is_some(), cts.is_some()) { + (false, false) => false, + (true, true) => true, + _ => panic!("RTS and CTS pins must be either both set or none set."), + }; + configure(r, config, hardware_flow_control); + configure_rx_pins(r, rxd, rts); + configure_tx_pins(r, txd, cts); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + + let s = T::state(); + s.tx_rx_refcount.store(2, Ordering::Relaxed); + + Self { + tx: UarteTx { + _p: unsafe { uarte.clone_unchecked() }, + }, + rx: UarteRx { _p: uarte }, + } + } + + /// 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 UART in reader and writer parts, by reference. + /// + /// The returned halves borrow from `self`, so you can drop them and go back to using + /// the "un-split" `self`. This allows temporarily splitting the UART. + pub fn split_by_ref(&mut self) -> (&mut UarteTx<'d, T>, &mut UarteRx<'d, T>) { + (&mut self.tx, &mut 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>) { + (self.tx, self.rx.with_idle(timer, ppi_ch1, ppi_ch2)) + } + + /// Return the endtx event for use with PPI + pub fn event_endtx(&self) -> Event { + let r = T::regs(); + Event::from_reg(r.events_endtx()) + } + + /// 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 + } + + /// Same as [`write`](Uarte::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> { + 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) + } + + /// Same as [`blocking_write`](Uarte::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub fn blocking_write_from_ram(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.blocking_write_from_ram(buffer) + } +} + +pub(crate) fn configure_tx_pins( + r: pac::uarte::Uarte, + txd: PeripheralRef<'_, AnyPin>, + cts: Option>, +) { + txd.set_high(); + txd.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + w.set_drive(gpiovals::Drive::H0H1); + }); + r.psel().txd().write_value(txd.psel_bits()); + + if let Some(pin) = &cts { + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::INPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(gpiovals::Drive::H0H1); + }); + } + r.psel().cts().write_value(cts.psel_bits()); +} + +pub(crate) fn configure_rx_pins( + r: pac::uarte::Uarte, + rxd: PeripheralRef<'_, AnyPin>, + rts: Option>, +) { + rxd.conf().write(|w| { + w.set_dir(gpiovals::Dir::INPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(gpiovals::Drive::H0H1); + }); + r.psel().rxd().write_value(rxd.psel_bits()); + + if let Some(pin) = &rts { + pin.set_high(); + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + w.set_drive(gpiovals::Drive::H0H1); + }); + } + r.psel().rts().write_value(rts.psel_bits()); +} + +pub(crate) fn configure(r: pac::uarte::Uarte, config: Config, hardware_flow_control: bool) { + r.config().write(|w| { + w.set_hwfc(hardware_flow_control); + w.set_parity(config.parity); + }); + r.baudrate().write(|w| w.set_baudrate(config.baudrate)); + + // Disable all interrupts + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); + + // Reset rxstarted, txstarted. These are used by drop to know whether a transfer was + // stopped midway or not. + r.events_rxstarted().write_value(0); + r.events_txstarted().write_value(0); + + // reset all pins + r.psel().txd().write_value(DISCONNECTED); + r.psel().rxd().write_value(DISCONNECTED); + r.psel().cts().write_value(DISCONNECTED); + r.psel().rts().write_value(DISCONNECTED); + + apply_workaround_for_enable_anomaly(r); +} + +impl<'d, T: Instance> UarteTx<'d, T> { + /// Create a new tx-only UARTE without hardware flow control + pub fn new( + uarte: impl Peripheral

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

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

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

+ 'd, + cts: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(uarte, txd, cts); + Self::new_inner(uarte, txd.map_into(), Some(cts.map_into()), config) + } + + fn new_inner( + uarte: PeripheralRef<'d, T>, + txd: PeripheralRef<'d, AnyPin>, + cts: Option>, + config: Config, + ) -> Self { + let r = T::regs(); + + configure(r, config, cts.is_some()); + configure_tx_pins(r, txd, cts); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + + let s = T::state(); + s.tx_rx_refcount.store(1, Ordering::Relaxed); + + 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::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); + self.write_from_ram(ram_buf).await + } + Err(error) => Err(error), + } + } + + /// 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> { + if buffer.is_empty() { + return Ok(()); + } + + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; + 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(); + + let drop = OnDrop::new(move || { + trace!("write drop: stopping"); + + r.intenclr().write(|w| w.set_endtx(true)); + r.events_txstopped().write_value(0); + r.tasks_stoptx().write_value(1); + + // TX is stopped almost instantly, spinning is fine. + while r.events_endtx().read() == 0 {} + trace!("write drop: stopped"); + }); + + r.txd().ptr().write_value(ptr as u32); + r.txd().maxcnt().write(|w| w.set_maxcnt(len as _)); + + r.events_endtx().write_value(0); + r.intenset().write(|w| w.set_endtx(true)); + + compiler_fence(Ordering::SeqCst); + + trace!("starttx"); + r.tasks_starttx().write_value(1); + + poll_fn(|cx| { + s.tx_waker.register(cx.waker()); + if r.events_endtx().read() != 0 { + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + r.events_txstarted().write_value(0); + drop.defuse(); + + 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::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); + self.blocking_write_from_ram(ram_buf) + } + Err(error) => Err(error), + } + } + + /// 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> { + if buffer.is_empty() { + return Ok(()); + } + + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::BufferTooLong); + } + + let ptr = buffer.as_ptr(); + let len = buffer.len(); + + let r = T::regs(); + + r.txd().ptr().write_value(ptr as u32); + r.txd().maxcnt().write(|w| w.set_maxcnt(len as _)); + + r.events_endtx().write_value(0); + r.intenclr().write(|w| w.set_endtx(true)); + + compiler_fence(Ordering::SeqCst); + + trace!("starttx"); + r.tasks_starttx().write_value(1); + + while r.events_endtx().read() == 0 {} + + compiler_fence(Ordering::SeqCst); + r.events_txstarted().write_value(0); + + Ok(()) + } +} + +impl<'a, T: Instance> Drop for UarteTx<'a, T> { + fn drop(&mut self) { + trace!("uarte tx drop"); + + let r = T::regs(); + + let did_stoptx = r.events_txstarted().read() != 0; + trace!("did_stoptx {}", did_stoptx); + + // Wait for txstopped, if needed. + while did_stoptx && r.events_txstopped().read() == 0 {} + + let s = T::state(); + + drop_tx_rx(r, s); + } +} + +impl<'d, T: Instance> UarteRx<'d, T> { + /// Create a new rx-only UARTE without hardware flow control + pub fn new( + uarte: impl Peripheral

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

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

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

+ 'd, + rts: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(uarte, rxd, rts); + Self::new_inner(uarte, rxd.map_into(), Some(rts.map_into()), config) + } + + /// Check for errors and clear the error register if an error occured. + fn check_and_clear_errors(&mut self) -> Result<(), Error> { + let r = T::regs(); + let err_bits = r.errorsrc().read(); + r.errorsrc().write_value(err_bits); + ErrorSource::from_bits_truncate(err_bits.0).check() + } + + fn new_inner( + uarte: PeripheralRef<'d, T>, + rxd: PeripheralRef<'d, AnyPin>, + rts: Option>, + config: Config, + ) -> Self { + let r = T::regs(); + + configure(r, config, rts.is_some()); + configure_rx_pins(r, rxd, rts); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + + let s = T::state(); + s.tx_rx_refcount.store(1, Ordering::Relaxed); + + Self { _p: uarte } + } + + /// Upgrade to an instance that supports idle line detection. + pub fn with_idle( + self, + timer: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ) -> UarteRxWithIdle<'d, T, U> { + let timer = Timer::new(timer); + + into_ref!(ppi_ch1, ppi_ch2); + + let r = T::regs(); + + // BAUDRATE register values are `baudrate * 2^32 / 16000000` + // 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(); + let timeout = 0x8000_0000 / (baudrate.to_bits() / 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(); + + UarteRxWithIdle { + rx: self, + timer, + ppi_ch1, + _ppi_ch2: ppi_ch2, + } + } + + /// Read bytes until the buffer is filled. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + if buffer.is_empty() { + return Ok(()); + } + 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(); + + let drop = OnDrop::new(move || { + trace!("read drop: stopping"); + + r.intenclr().write(|w| { + w.set_endrx(true); + w.set_error(true); + }); + r.events_rxto().write_value(0); + r.events_error().write_value(0); + r.tasks_stoprx().write_value(1); + + while r.events_endrx().read() == 0 {} + + trace!("read drop: stopped"); + }); + + r.rxd().ptr().write_value(ptr as u32); + r.rxd().maxcnt().write(|w| w.set_maxcnt(len as _)); + + r.events_endrx().write_value(0); + r.events_error().write_value(0); + r.intenset().write(|w| { + w.set_endrx(true); + w.set_error(true); + }); + + compiler_fence(Ordering::SeqCst); + + trace!("startrx"); + r.tasks_startrx().write_value(1); + + let result = poll_fn(|cx| { + s.rx_waker.register(cx.waker()); + + if let Err(e) = self.check_and_clear_errors() { + r.tasks_stoprx().write_value(1); + return Poll::Ready(Err(e)); + } + if r.events_endrx().read() != 0 { + return Poll::Ready(Ok(())); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + r.events_rxstarted().write_value(0); + drop.defuse(); + + result + } + + /// Read bytes until the buffer is filled. + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + if buffer.is_empty() { + return Ok(()); + } + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::BufferTooLong); + } + + let ptr = buffer.as_ptr(); + let len = buffer.len(); + + let r = T::regs(); + + r.rxd().ptr().write_value(ptr as u32); + r.rxd().maxcnt().write(|w| w.set_maxcnt(len as _)); + + r.events_endrx().write_value(0); + r.events_error().write_value(0); + r.intenclr().write(|w| { + w.set_endrx(true); + w.set_error(true); + }); + + compiler_fence(Ordering::SeqCst); + + trace!("startrx"); + r.tasks_startrx().write_value(1); + + while r.events_endrx().read() == 0 && r.events_error().read() == 0 {} + + compiler_fence(Ordering::SeqCst); + r.events_rxstarted().write_value(0); + + self.check_and_clear_errors() + } +} + +impl<'a, T: Instance> Drop for UarteRx<'a, T> { + fn drop(&mut self) { + trace!("uarte rx drop"); + + let r = T::regs(); + + let did_stoprx = r.events_rxstarted().read() != 0; + trace!("did_stoprx {}", did_stoprx); + + // Wait for rxto, if needed. + while did_stoprx && r.events_rxto().read() == 0 {} + + let s = T::state(); + + drop_tx_rx(r, s); + } +} + +/// 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.is_empty() { + 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.set_endrx(true); + w.set_error(true); + }); + r.events_rxto().write_value(0); + r.events_error().write_value(0); + r.tasks_stoprx().write_value(1); + + while r.events_endrx().read() == 0 {} + }); + + r.rxd().ptr().write_value(ptr as u32); + r.rxd().maxcnt().write(|w| w.set_maxcnt(len as _)); + + r.events_endrx().write_value(0); + r.events_error().write_value(0); + r.intenset().write(|w| { + w.set_endrx(true); + w.set_error(true); + }); + + compiler_fence(Ordering::SeqCst); + + r.tasks_startrx().write_value(1); + + let result = poll_fn(|cx| { + s.rx_waker.register(cx.waker()); + + if let Err(e) = self.rx.check_and_clear_errors() { + r.tasks_stoprx().write_value(1); + return Poll::Ready(Err(e)); + } + if r.events_endrx().read() != 0 { + return Poll::Ready(Ok(())); + } + + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + let n = r.rxd().amount().read().0 as usize; + + self.timer.stop(); + r.events_rxstarted().write_value(0); + + drop.defuse(); + + result.map(|_| 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.is_empty() { + 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_value(ptr as u32); + r.rxd().maxcnt().write(|w| w.set_maxcnt(len as _)); + + r.events_endrx().write_value(0); + r.events_error().write_value(0); + r.intenclr().write(|w| { + w.set_endrx(true); + w.set_error(true); + }); + + compiler_fence(Ordering::SeqCst); + + r.tasks_startrx().write_value(1); + + while r.events_endrx().read() == 0 && r.events_error().read() == 0 {} + + compiler_fence(Ordering::SeqCst); + let n = r.rxd().amount().read().0 as usize; + + self.timer.stop(); + r.events_rxstarted().write_value(0); + + self.rx.check_and_clear_errors().map(|_| n) + } +} + +#[cfg(not(any(feature = "_nrf9160", feature = "_nrf5340")))] +pub(crate) fn apply_workaround_for_enable_anomaly(_r: pac::uarte::Uarte) { + // Do nothing +} + +#[cfg(any(feature = "_nrf9160", feature = "_nrf5340"))] +pub(crate) fn apply_workaround_for_enable_anomaly(r: pac::uarte::Uarte) { + // Apply workaround for anomalies: + // - nRF9160 - anomaly 23 + // - nRF5340 - anomaly 44 + let rp = r.as_ptr() as *mut u32; + let rxenable_reg = unsafe { rp.add(0x564 / 4) }; + let txenable_reg = unsafe { rp.add(0x568 / 4) }; + + // NB Safety: This is taken from Nordic's driver - + // https://github.com/NordicSemiconductor/nrfx/blob/master/drivers/src/nrfx_uarte.c#L197 + if unsafe { core::ptr::read_volatile(txenable_reg) } == 1 { + r.tasks_stoptx().write_value(1); + } + + // NB Safety: This is taken from Nordic's driver - + // https://github.com/NordicSemiconductor/nrfx/blob/master/drivers/src/nrfx_uarte.c#L197 + if unsafe { core::ptr::read_volatile(rxenable_reg) } == 1 { + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + r.tasks_stoprx().write_value(1); + + let mut workaround_succeded = false; + // The UARTE is able to receive up to four bytes after the STOPRX task has been triggered. + // On lowest supported baud rate (1200 baud), with parity bit and two stop bits configured + // (resulting in 12 bits per data byte sent), this may take up to 40 ms. + for _ in 0..40000 { + // NB Safety: This is taken from Nordic's driver - + // https://github.com/NordicSemiconductor/nrfx/blob/master/drivers/src/nrfx_uarte.c#L197 + if unsafe { core::ptr::read_volatile(rxenable_reg) } == 0 { + workaround_succeded = true; + break; + } else { + // Need to sleep for 1us here + } + } + + if !workaround_succeded { + panic!("Failed to apply workaround for UART"); + } + + // write back the bits we just read to clear them + let errors = r.errorsrc().read(); + r.errorsrc().write_value(errors); + r.enable().write(|w| w.set_enable(vals::Enable::DISABLED)); + } +} + +pub(crate) fn drop_tx_rx(r: pac::uarte::Uarte, s: &State) { + if s.tx_rx_refcount.fetch_sub(1, Ordering::Relaxed) == 1 { + // Finally we can disable, and we do so for the peripheral + // i.e. not just rx concerns. + r.enable().write(|w| w.set_enable(vals::Enable::DISABLED)); + + gpio::deconfigure_pin(r.psel().rxd().read()); + gpio::deconfigure_pin(r.psel().txd().read()); + gpio::deconfigure_pin(r.psel().rts().read()); + gpio::deconfigure_pin(r.psel().cts().read()); + + trace!("uarte tx and rx drop: done"); + } +} + +pub(crate) struct State { + pub(crate) rx_waker: AtomicWaker, + pub(crate) tx_waker: AtomicWaker, + pub(crate) tx_rx_refcount: AtomicU8, +} +impl State { + pub(crate) const fn new() -> Self { + Self { + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + tx_rx_refcount: AtomicU8::new(0), + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::uarte::Uarte; + fn state() -> &'static State; + fn buffered_state() -> &'static crate::buffered_uarte::State; +} + +/// UARTE peripheral instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_uarte { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::uarte::SealedInstance for peripherals::$type { + fn regs() -> pac::uarte::Uarte { + pac::$pac_type + } + fn state() -> &'static crate::uarte::State { + static STATE: crate::uarte::State = crate::uarte::State::new(); + &STATE + } + fn buffered_state() -> &'static crate::buffered_uarte::State { + static STATE: crate::buffered_uarte::State = crate::buffered_uarte::State::new(); + &STATE + } + } + impl crate::uarte::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +// ==================== + +mod eh02 { + use super::*; + + impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for Uarte<'d, 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(()) + } + } + + impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for UarteTx<'d, 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(()) + } + } +} + +mod _embedded_io { + use super::*; + + impl embedded_io_async::Error for Error { + fn kind(&self) -> embedded_io_async::ErrorKind { + match *self { + Error::BufferTooLong => embedded_io_async::ErrorKind::InvalidInput, + Error::BufferNotInRAM => embedded_io_async::ErrorKind::Unsupported, + Error::Framing => embedded_io_async::ErrorKind::InvalidData, + Error::Parity => embedded_io_async::ErrorKind::InvalidData, + Error::Overrun => embedded_io_async::ErrorKind::OutOfMemory, + Error::Break => embedded_io_async::ErrorKind::ConnectionAborted, + } + } + } + + impl<'d, U: Instance> embedded_io_async::ErrorType for Uarte<'d, U> { + type Error = Error; + } + + impl<'d, U: Instance> embedded_io_async::ErrorType for UarteTx<'d, U> { + type Error = Error; + } + + impl<'d, U: Instance> embedded_io_async::Write for Uarte<'d, U> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await?; + Ok(buf.len()) + } + } + + impl<'d: 'd, U: Instance> embedded_io_async::Write for UarteTx<'d, U> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await?; + Ok(buf.len()) + } + } +} diff --git a/embassy/embassy-nrf/src/usb/mod.rs b/embassy/embassy-nrf/src/usb/mod.rs new file mode 100644 index 0000000..a9bf167 --- /dev/null +++ b/embassy/embassy-nrf/src/usb/mod.rs @@ -0,0 +1,867 @@ +//! Universal Serial Bus (USB) driver. + +#![macro_use] + +pub mod vbus_detect; + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; +use core::task::Poll; + +use cortex_m::peripheral::NVIC; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb_driver as driver; +use embassy_usb_driver::{Direction, EndpointAddress, EndpointError, EndpointInfo, EndpointType, Event, Unsupported}; + +use self::vbus_detect::VbusDetect; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::usbd::vals; +use crate::util::slice_in_ram; +use crate::{interrupt, pac, Peripheral}; + +static BUS_WAKER: AtomicWaker = AtomicWaker::new(); +static EP0_WAKER: AtomicWaker = AtomicWaker::new(); +static EP_IN_WAKERS: [AtomicWaker; 8] = [const { AtomicWaker::new() }; 8]; +static EP_OUT_WAKERS: [AtomicWaker; 8] = [const { AtomicWaker::new() }; 8]; +static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0); + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + + if regs.events_usbreset().read() != 0 { + regs.intenclr().write(|w| w.set_usbreset(true)); + BUS_WAKER.wake(); + EP0_WAKER.wake(); + } + + if regs.events_ep0setup().read() != 0 { + regs.intenclr().write(|w| w.set_ep0setup(true)); + EP0_WAKER.wake(); + } + + if regs.events_ep0datadone().read() != 0 { + regs.intenclr().write(|w| w.set_ep0datadone(true)); + EP0_WAKER.wake(); + } + + // USBEVENT and EPDATA events are weird. They're the "aggregate" + // of individual bits in EVENTCAUSE and EPDATASTATUS. We handle them + // differently than events normally. + // + // They seem to be edge-triggered, not level-triggered: when an + // individual bit goes 0->1, the event fires *just once*. + // Therefore, it's fine to clear just the event, and let main thread + // check the individual bits in EVENTCAUSE and EPDATASTATUS. It + // doesn't cause an infinite irq loop. + if regs.events_usbevent().read() != 0 { + regs.events_usbevent().write_value(0); + BUS_WAKER.wake(); + } + + if regs.events_epdata().read() != 0 { + regs.events_epdata().write_value(0); + + let r = regs.epdatastatus().read(); + regs.epdatastatus().write_value(r); + READY_ENDPOINTS.fetch_or(r.0, Ordering::AcqRel); + for i in 1..=7 { + if r.0 & In::mask(i) != 0 { + In::waker(i).wake(); + } + if r.0 & Out::mask(i) != 0 { + Out::waker(i).wake(); + } + } + } + } +} + +/// USB driver. +pub struct Driver<'d, T: Instance, V: VbusDetect> { + _p: PeripheralRef<'d, T>, + alloc_in: Allocator, + alloc_out: Allocator, + vbus_detect: V, +} + +impl<'d, T: Instance, V: VbusDetect> Driver<'d, T, V> { + /// Create a new USB driver. + pub fn new( + usb: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + vbus_detect: V, + ) -> Self { + into_ref!(usb); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + Self { + _p: usb, + alloc_in: Allocator::new(), + alloc_out: Allocator::new(), + vbus_detect, + } + } +} + +impl<'d, T: Instance, V: VbusDetect + 'd> driver::Driver<'d> for Driver<'d, T, V> { + type EndpointOut = Endpoint<'d, T, Out>; + type EndpointIn = Endpoint<'d, T, In>; + type ControlPipe = ControlPipe<'d, T>; + type Bus = Bus<'d, T, V>; + + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + packet_size: u16, + interval_ms: u8, + ) -> Result { + let index = self.alloc_in.allocate(ep_type)?; + let ep_addr = EndpointAddress::from_parts(index, Direction::In); + Ok(Endpoint::new(EndpointInfo { + addr: ep_addr, + ep_type, + max_packet_size: packet_size, + interval_ms, + })) + } + + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + packet_size: u16, + interval_ms: u8, + ) -> Result { + let index = self.alloc_out.allocate(ep_type)?; + let ep_addr = EndpointAddress::from_parts(index, Direction::Out); + Ok(Endpoint::new(EndpointInfo { + addr: ep_addr, + ep_type, + max_packet_size: packet_size, + interval_ms, + })) + } + + fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + ( + Bus { + _p: unsafe { self._p.clone_unchecked() }, + power_available: false, + vbus_detect: self.vbus_detect, + }, + ControlPipe { + _p: self._p, + max_packet_size: control_max_packet_size, + }, + ) + } +} + +/// USB bus. +pub struct Bus<'d, T: Instance, V: VbusDetect> { + _p: PeripheralRef<'d, T>, + power_available: bool, + vbus_detect: V, +} + +impl<'d, T: Instance, V: VbusDetect> driver::Bus for Bus<'d, T, V> { + async fn enable(&mut self) { + let regs = T::regs(); + + errata::pre_enable(); + + regs.enable().write(|w| w.set_enable(true)); + + // Wait until the peripheral is ready. + regs.intenset().write(|w| w.set_usbevent(true)); + poll_fn(|cx| { + BUS_WAKER.register(cx.waker()); + if regs.eventcause().read().ready() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + regs.eventcause().write(|w| w.set_ready(true)); + + errata::post_enable(); + + unsafe { NVIC::unmask(pac::Interrupt::USBD) }; + + regs.intenset().write(|w| { + w.set_usbreset(true); + w.set_usbevent(true); + w.set_epdata(true); + }); + + if self.vbus_detect.wait_power_ready().await.is_ok() { + // Enable the USB pullup, allowing enumeration. + regs.usbpullup().write(|w| w.set_connect(true)); + trace!("enabled"); + } else { + trace!("usb power not ready due to usb removal"); + } + } + + async fn disable(&mut self) { + let regs = T::regs(); + regs.enable().write(|x| x.set_enable(false)); + } + + async fn poll(&mut self) -> Event { + poll_fn(move |cx| { + BUS_WAKER.register(cx.waker()); + let regs = T::regs(); + + if regs.events_usbreset().read() != 0 { + regs.events_usbreset().write_value(0); + regs.intenset().write(|w| w.set_usbreset(true)); + + // Disable all endpoints except EP0 + regs.epinen().write(|w| w.0 = 0x01); + regs.epouten().write(|w| w.0 = 0x01); + READY_ENDPOINTS.store(In::mask(0), Ordering::Release); + for i in 1..=7 { + In::waker(i).wake(); + Out::waker(i).wake(); + } + + return Poll::Ready(Event::Reset); + } + + let r = regs.eventcause().read(); + + if r.isooutcrc() { + regs.eventcause().write(|w| w.set_isooutcrc(true)); + trace!("USB event: isooutcrc"); + } + if r.usbwuallowed() { + regs.eventcause().write(|w| w.set_usbwuallowed(true)); + trace!("USB event: usbwuallowed"); + } + if r.suspend() { + regs.eventcause().write(|w| w.set_suspend(true)); + regs.lowpower().write(|w| w.set_lowpower(vals::Lowpower::LOW_POWER)); + return Poll::Ready(Event::Suspend); + } + if r.resume() { + regs.eventcause().write(|w| w.set_resume(true)); + return Poll::Ready(Event::Resume); + } + if r.ready() { + regs.eventcause().write(|w| w.set_ready(true)); + trace!("USB event: ready"); + } + + if self.vbus_detect.is_usb_detected() != self.power_available { + self.power_available = !self.power_available; + if self.power_available { + trace!("Power event: available"); + return Poll::Ready(Event::PowerDetected); + } else { + trace!("Power event: removed"); + return Poll::Ready(Event::PowerRemoved); + } + } + + Poll::Pending + }) + .await + } + + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { + let regs = T::regs(); + if ep_addr.index() == 0 { + if stalled { + regs.tasks_ep0stall().write_value(1); + } + } else { + regs.epstall().write(|w| { + w.set_ep(ep_addr.index() as u8 & 0b111); + w.set_io(match ep_addr.direction() { + Direction::In => vals::Io::IN, + Direction::Out => vals::Io::OUT, + }); + w.set_stall(stalled); + }); + } + } + + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + let regs = T::regs(); + let i = ep_addr.index(); + match ep_addr.direction() { + Direction::Out => regs.halted().epout(i).read().getstatus() == vals::Getstatus::HALTED, + Direction::In => regs.halted().epin(i).read().getstatus() == vals::Getstatus::HALTED, + } + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + let regs = T::regs(); + + let i = ep_addr.index(); + let mask = 1 << i; + + debug!("endpoint_set_enabled {:?} {}", ep_addr, enabled); + + match ep_addr.direction() { + Direction::In => { + let mut was_enabled = false; + regs.epinen().modify(|w| { + was_enabled = (w.0 & mask) != 0; + if enabled { + w.0 |= mask + } else { + w.0 &= !mask + } + }); + + let ready_mask = In::mask(i); + if enabled { + if !was_enabled { + READY_ENDPOINTS.fetch_or(ready_mask, Ordering::AcqRel); + } + } else { + READY_ENDPOINTS.fetch_and(!ready_mask, Ordering::AcqRel); + } + + In::waker(i).wake(); + } + Direction::Out => { + regs.epouten() + .modify(|w| if enabled { w.0 |= mask } else { w.0 &= !mask }); + + let ready_mask = Out::mask(i); + if enabled { + // when first enabled, bulk/interrupt OUT endpoints will *not* receive data (the + // peripheral will NAK all incoming packets) until we write a zero to the SIZE + // register (see figure 203 of the 52840 manual). To avoid that we write a 0 to the + // SIZE register + regs.size().epout(i).write(|_| ()); + } else { + READY_ENDPOINTS.fetch_and(!ready_mask, Ordering::AcqRel); + } + + Out::waker(i).wake(); + } + } + } + + #[inline] + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + let regs = T::regs(); + + if regs.lowpower().read().lowpower() == vals::Lowpower::LOW_POWER { + errata::pre_wakeup(); + + regs.lowpower().write(|w| w.set_lowpower(vals::Lowpower::FORCE_NORMAL)); + + poll_fn(|cx| { + BUS_WAKER.register(cx.waker()); + let regs = T::regs(); + let r = regs.eventcause().read(); + + if regs.events_usbreset().read() != 0 { + Poll::Ready(()) + } else if r.resume() { + Poll::Ready(()) + } else if r.usbwuallowed() { + regs.eventcause().write(|w| w.set_usbwuallowed(true)); + regs.dpdmvalue().write(|w| w.set_state(vals::State::RESUME)); + regs.tasks_dpdmdrive().write_value(1); + + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + errata::post_wakeup(); + } + + Ok(()) + } +} + +/// Type-level marker for OUT endpoints. +pub enum Out {} + +/// Type-level marker for IN endpoints. +pub enum In {} + +trait EndpointDir { + fn waker(i: usize) -> &'static AtomicWaker; + fn mask(i: usize) -> u32; + fn is_enabled(regs: pac::usbd::Usbd, i: usize) -> bool; +} + +impl EndpointDir for In { + #[inline] + fn waker(i: usize) -> &'static AtomicWaker { + &EP_IN_WAKERS[i - 1] + } + + #[inline] + fn mask(i: usize) -> u32 { + 1 << i + } + + #[inline] + fn is_enabled(regs: pac::usbd::Usbd, i: usize) -> bool { + regs.epinen().read().in_(i) + } +} + +impl EndpointDir for Out { + #[inline] + fn waker(i: usize) -> &'static AtomicWaker { + &EP_OUT_WAKERS[i - 1] + } + + #[inline] + fn mask(i: usize) -> u32 { + 1 << (i + 16) + } + + #[inline] + fn is_enabled(regs: pac::usbd::Usbd, i: usize) -> bool { + regs.epouten().read().out(i) + } +} + +/// USB endpoint. +pub struct Endpoint<'d, T: Instance, Dir> { + _phantom: PhantomData<(&'d mut T, Dir)>, + info: EndpointInfo, +} + +impl<'d, T: Instance, Dir> Endpoint<'d, T, Dir> { + fn new(info: EndpointInfo) -> Self { + Self { + info, + _phantom: PhantomData, + } + } +} + +impl<'d, T: Instance, Dir: EndpointDir> driver::Endpoint for Endpoint<'d, T, Dir> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + self.wait_enabled_state(true).await + } +} + +#[allow(private_bounds)] +impl<'d, T: Instance, Dir: EndpointDir> Endpoint<'d, T, Dir> { + async fn wait_enabled_state(&mut self, state: bool) { + let i = self.info.addr.index(); + assert!(i != 0); + + poll_fn(move |cx| { + Dir::waker(i).register(cx.waker()); + if Dir::is_enabled(T::regs(), i) == state { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } + + /// Wait for the endpoint to be disabled + pub async fn wait_disabled(&mut self) { + self.wait_enabled_state(false).await + } +} + +impl<'d, T: Instance, Dir> Endpoint<'d, T, Dir> { + async fn wait_data_ready(&mut self) -> Result<(), ()> + where + Dir: EndpointDir, + { + let i = self.info.addr.index(); + assert!(i != 0); + poll_fn(|cx| { + Dir::waker(i).register(cx.waker()); + let r = READY_ENDPOINTS.load(Ordering::Acquire); + if !Dir::is_enabled(T::regs(), i) { + Poll::Ready(Err(())) + } else if r & Dir::mask(i) != 0 { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }) + .await?; + + // Mark as not ready + READY_ENDPOINTS.fetch_and(!Dir::mask(i), Ordering::AcqRel); + + Ok(()) + } +} + +unsafe fn read_dma(i: usize, buf: &mut [u8]) -> Result { + let regs = T::regs(); + + // Check that the packet fits into the buffer + let size = regs.size().epout(i).read().0 as usize; + if size > buf.len() { + return Err(EndpointError::BufferOverflow); + } + + regs.epout(i).ptr().write_value(buf.as_ptr() as u32); + // MAXCNT must match SIZE + regs.epout(i).maxcnt().write(|w| w.set_maxcnt(size as _)); + + dma_start(); + regs.events_endepout(i).write_value(0); + regs.tasks_startepout(i).write_value(1); + while regs.events_endepout(i).read() == 0 {} + regs.events_endepout(i).write_value(0); + dma_end(); + + regs.size().epout(i).write(|_| ()); + + Ok(size) +} + +unsafe fn write_dma(i: usize, buf: &[u8]) { + let regs = T::regs(); + assert!(buf.len() <= 64); + + let mut ram_buf: MaybeUninit<[u8; 64]> = MaybeUninit::uninit(); + let ptr = if !slice_in_ram(buf) { + // EasyDMA can't read FLASH, so we copy through RAM + let ptr = ram_buf.as_mut_ptr() as *mut u8; + core::ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len()); + ptr + } else { + buf.as_ptr() + }; + + // Set the buffer length so the right number of bytes are transmitted. + // Safety: `buf.len()` has been checked to be <= the max buffer length. + regs.epin(i).ptr().write_value(ptr as u32); + regs.epin(i).maxcnt().write(|w| w.set_maxcnt(buf.len() as u8)); + + regs.events_endepin(i).write_value(0); + + dma_start(); + regs.tasks_startepin(i).write_value(1); + while regs.events_endepin(i).read() == 0 {} + dma_end(); +} + +impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + let i = self.info.addr.index(); + assert!(i != 0); + + self.wait_data_ready().await.map_err(|_| EndpointError::Disabled)?; + + unsafe { read_dma::(i, buf) } + } +} + +impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + let i = self.info.addr.index(); + assert!(i != 0); + + self.wait_data_ready().await.map_err(|_| EndpointError::Disabled)?; + + unsafe { write_dma::(i, buf) } + + 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> { + fn max_packet_size(&self) -> usize { + usize::from(self.max_packet_size) + } + + async fn setup(&mut self) -> [u8; 8] { + let regs = T::regs(); + + // Reset shorts + regs.shorts().write(|_| ()); + + // Wait for SETUP packet + regs.intenset().write(|w| w.set_ep0setup(true)); + poll_fn(|cx| { + EP0_WAKER.register(cx.waker()); + let regs = T::regs(); + if regs.events_ep0setup().read() != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + regs.events_ep0setup().write_value(0); + + let mut buf = [0; 8]; + buf[0] = regs.bmrequesttype().read().0 as u8; + buf[1] = regs.brequest().read().0 as u8; + buf[2] = regs.wvaluel().read().0 as u8; + buf[3] = regs.wvalueh().read().0 as u8; + buf[4] = regs.windexl().read().0 as u8; + buf[5] = regs.windexh().read().0 as u8; + buf[6] = regs.wlengthl().read().0 as u8; + buf[7] = regs.wlengthh().read().0 as u8; + + buf + } + + async fn data_out(&mut self, buf: &mut [u8], _first: bool, _last: bool) -> Result { + let regs = T::regs(); + + regs.events_ep0datadone().write_value(0); + + // This starts a RX on EP0. events_ep0datadone notifies when done. + regs.tasks_ep0rcvout().write_value(1); + + // Wait until ready + regs.intenset().write(|w| { + w.set_usbreset(true); + w.set_ep0setup(true); + w.set_ep0datadone(true); + }); + poll_fn(|cx| { + EP0_WAKER.register(cx.waker()); + let regs = T::regs(); + if regs.events_ep0datadone().read() != 0 { + Poll::Ready(Ok(())) + } else if regs.events_usbreset().read() != 0 { + trace!("aborted control data_out: usb reset"); + Poll::Ready(Err(EndpointError::Disabled)) + } else if regs.events_ep0setup().read() != 0 { + trace!("aborted control data_out: received another SETUP"); + Poll::Ready(Err(EndpointError::Disabled)) + } else { + Poll::Pending + } + }) + .await?; + + unsafe { read_dma::(0, buf) } + } + + async fn data_in(&mut self, buf: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> { + let regs = T::regs(); + regs.events_ep0datadone().write_value(0); + + regs.shorts().write(|w| w.set_ep0datadone_ep0status(last)); + + // This starts a TX on EP0. events_ep0datadone notifies when done. + unsafe { write_dma::(0, buf) } + + regs.intenset().write(|w| { + w.set_usbreset(true); + w.set_ep0setup(true); + w.set_ep0datadone(true); + }); + + poll_fn(|cx| { + cx.waker().wake_by_ref(); + EP0_WAKER.register(cx.waker()); + let regs = T::regs(); + if regs.events_ep0datadone().read() != 0 { + Poll::Ready(Ok(())) + } else if regs.events_usbreset().read() != 0 { + trace!("aborted control data_in: usb reset"); + Poll::Ready(Err(EndpointError::Disabled)) + } else if regs.events_ep0setup().read() != 0 { + trace!("aborted control data_in: received another SETUP"); + Poll::Ready(Err(EndpointError::Disabled)) + } else { + Poll::Pending + } + }) + .await + } + + async fn accept(&mut self) { + let regs = T::regs(); + regs.tasks_ep0status().write_value(1); + } + + async fn reject(&mut self) { + let regs = T::regs(); + regs.tasks_ep0stall().write_value(1); + } + + async fn accept_set_address(&mut self, _addr: u8) { + self.accept().await; + // Nothing to do, the peripheral handles this. + } +} + +fn dma_start() { + compiler_fence(Ordering::Release); +} + +fn dma_end() { + compiler_fence(Ordering::Acquire); +} + +struct Allocator { + used: u16, +} + +impl Allocator { + fn new() -> Self { + Self { used: 0 } + } + + fn allocate(&mut self, ep_type: EndpointType) -> Result { + // Endpoint addresses are fixed in hardware: + // - 0x80 / 0x00 - Control EP0 + // - 0x81 / 0x01 - Bulk/Interrupt EP1 + // - 0x82 / 0x02 - Bulk/Interrupt EP2 + // - 0x83 / 0x03 - Bulk/Interrupt EP3 + // - 0x84 / 0x04 - Bulk/Interrupt EP4 + // - 0x85 / 0x05 - Bulk/Interrupt EP5 + // - 0x86 / 0x06 - Bulk/Interrupt EP6 + // - 0x87 / 0x07 - Bulk/Interrupt EP7 + // - 0x88 / 0x08 - Isochronous + + // Endpoint directions are allocated individually. + + let alloc_index = match ep_type { + EndpointType::Isochronous => 8, + EndpointType::Control => return Err(driver::EndpointAllocError), + EndpointType::Interrupt | EndpointType::Bulk => { + // Find rightmost zero bit in 1..=7 + let ones = (self.used >> 1).trailing_ones() as usize; + if ones >= 7 { + return Err(driver::EndpointAllocError); + } + ones + 1 + } + }; + + if self.used & (1 << alloc_index) != 0 { + return Err(driver::EndpointAllocError); + } + + self.used |= 1 << alloc_index; + + Ok(alloc_index) + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::usbd::Usbd; +} + +/// USB peripheral instance. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_usb { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::usb::SealedInstance for peripherals::$type { + fn regs() -> pac::usbd::Usbd { + pac::$pac_type + } + } + impl crate::usb::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +mod errata { + + /// Writes `val` to `addr`. Used to apply Errata workarounds. + #[cfg(any(feature = "nrf52840", feature = "nrf52833", feature = "nrf52820"))] + unsafe fn poke(addr: u32, val: u32) { + (addr as *mut u32).write_volatile(val); + } + + /// Reads 32 bits from `addr`. + #[cfg(feature = "nrf52840")] + unsafe fn peek(addr: u32) -> u32 { + (addr as *mut u32).read_volatile() + } + + pub fn pre_enable() { + // Works around Erratum 187 on chip revisions 1 and 2. + #[cfg(any(feature = "nrf52840", feature = "nrf52833", feature = "nrf52820"))] + unsafe { + poke(0x4006EC00, 0x00009375); + poke(0x4006ED14, 0x00000003); + poke(0x4006EC00, 0x00009375); + } + + pre_wakeup(); + } + + pub fn post_enable() { + post_wakeup(); + + // Works around Erratum 187 on chip revisions 1 and 2. + #[cfg(any(feature = "nrf52840", feature = "nrf52833", feature = "nrf52820"))] + unsafe { + poke(0x4006EC00, 0x00009375); + poke(0x4006ED14, 0x00000000); + poke(0x4006EC00, 0x00009375); + } + } + + pub fn pre_wakeup() { + // Works around Erratum 171 on chip revisions 1 and 2. + + #[cfg(feature = "nrf52840")] + unsafe { + if peek(0x4006EC00) == 0x00000000 { + poke(0x4006EC00, 0x00009375); + } + + poke(0x4006EC14, 0x000000C0); + poke(0x4006EC00, 0x00009375); + } + } + + pub fn post_wakeup() { + // Works around Erratum 171 on chip revisions 1 and 2. + + #[cfg(feature = "nrf52840")] + unsafe { + if peek(0x4006EC00) == 0x00000000 { + poke(0x4006EC00, 0x00009375); + } + + poke(0x4006EC14, 0x00000000); + poke(0x4006EC00, 0x00009375); + } + } +} diff --git a/embassy/embassy-nrf/src/usb/vbus_detect.rs b/embassy/embassy-nrf/src/usb/vbus_detect.rs new file mode 100644 index 0000000..bdc088d --- /dev/null +++ b/embassy/embassy-nrf/src/usb/vbus_detect.rs @@ -0,0 +1,180 @@ +//! Trait and implementations for performing VBUS detection. + +use core::future::poll_fn; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_sync::waitqueue::AtomicWaker; + +use super::BUS_WAKER; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, pac}; + +/// 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 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; + + /// 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<(), ()>; +} + +#[cfg(not(feature = "_nrf5340"))] +type UsbRegIrq = interrupt::typelevel::CLOCK_POWER; +#[cfg(feature = "_nrf5340")] +type UsbRegIrq = interrupt::typelevel::USBREGULATOR; + +#[cfg(not(feature = "_nrf5340"))] +const USB_REG_PERI: pac::power::Power = pac::POWER; +#[cfg(feature = "_nrf5340")] +const USB_REG_PERI: pac::usbreg::Usbreg = pac::USBREGULATOR; + +/// Interrupt handler. +pub struct InterruptHandler { + _private: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = USB_REG_PERI; + + if regs.events_usbdetected().read() != 0 { + regs.events_usbdetected().write_value(0); + BUS_WAKER.wake(); + } + + if regs.events_usbremoved().read() != 0 { + regs.events_usbremoved().write_value(0); + BUS_WAKER.wake(); + POWER_WAKER.wake(); + } + + if regs.events_usbpwrrdy().read() != 0 { + regs.events_usbpwrrdy().write_value(0); + POWER_WAKER.wake(); + } + } +} + +/// [`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`]. +pub struct HardwareVbusDetect { + _private: (), +} + +static POWER_WAKER: AtomicWaker = AtomicWaker::new(); + +impl HardwareVbusDetect { + /// Create a new `VbusDetectNative`. + pub fn new(_irq: impl interrupt::typelevel::Binding + 'static) -> Self { + let regs = USB_REG_PERI; + + UsbRegIrq::unpend(); + unsafe { UsbRegIrq::enable() }; + + regs.intenset().write(|w| { + w.set_usbdetected(true); + w.set_usbremoved(true); + w.set_usbpwrrdy(true); + }); + + Self { _private: () } + } +} + +impl VbusDetect for HardwareVbusDetect { + fn is_usb_detected(&self) -> bool { + let regs = USB_REG_PERI; + regs.usbregstatus().read().vbusdetect() + } + + async fn wait_power_ready(&mut self) -> Result<(), ()> { + poll_fn(move |cx| { + POWER_WAKER.register(cx.waker()); + let regs = USB_REG_PERI; + + if regs.usbregstatus().read().outputrdy() { + Poll::Ready(Ok(())) + } else if !self.is_usb_detected() { + Poll::Ready(Err(())) + } else { + Poll::Pending + } + }) + .await + } +} + +/// 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(); + + Self { + usb_detected: AtomicBool::new(usb_detected), + power_ready: AtomicBool::new(power_ready), + } + } + + /// 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); + BUS_WAKER.wake(); + 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 VbusDetect for &SoftwareVbusDetect { + fn is_usb_detected(&self) -> bool { + self.usb_detected.load(Ordering::Relaxed) + } + + async fn wait_power_ready(&mut self) -> Result<(), ()> { + poll_fn(move |cx| { + POWER_WAKER.register(cx.waker()); + + if self.power_ready.load(Ordering::Relaxed) { + Poll::Ready(Ok(())) + } else if !self.usb_detected.load(Ordering::Relaxed) { + Poll::Ready(Err(())) + } else { + Poll::Pending + } + }) + .await + } +} diff --git a/embassy/embassy-nrf/src/util.rs b/embassy/embassy-nrf/src/util.rs new file mode 100644 index 0000000..78f7171 --- /dev/null +++ b/embassy/embassy-nrf/src/util.rs @@ -0,0 +1,23 @@ +#![allow(dead_code)] + +const SRAM_LOWER: usize = 0x2000_0000; +const SRAM_UPPER: usize = 0x3000_0000; + +/// Does this slice reside entirely within RAM? +pub(crate) fn slice_in_ram(slice: *const [T]) -> bool { + if slice.is_empty() { + return true; + } + + let ptr = slice as *const T as usize; + ptr >= SRAM_LOWER && (ptr + slice.len() * core::mem::size_of::()) < SRAM_UPPER +} + +/// Return an error if slice is not in RAM. Skips check if slice is zero-length. +pub(crate) fn slice_in_ram_or(slice: *const [T], err: E) -> Result<(), E> { + if slice_in_ram(slice) { + Ok(()) + } else { + Err(err) + } +} diff --git a/embassy/embassy-nrf/src/wdt.rs b/embassy/embassy-nrf/src/wdt.rs new file mode 100644 index 0000000..f781225 --- /dev/null +++ b/embassy/embassy-nrf/src/wdt.rs @@ -0,0 +1,188 @@ +//! 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. + +use crate::pac::wdt::vals; +pub use crate::pac::wdt::vals::{Halt as HaltConfig, Sleep as SleepConfig}; +use crate::peripherals; + +const MIN_TICKS: u32 = 15; + +/// WDT configuration. +#[non_exhaustive] +pub struct Config { + /// Number of 32768 Hz ticks in each watchdog period. + /// + /// Note: there is a minimum of 15 ticks (458 microseconds). If a lower + /// number is provided, 15 ticks will be used as the configured value. + pub timeout_ticks: u32, + + /// Should the watchdog continue to count during sleep modes? + pub action_during_sleep: SleepConfig, + + /// Should the watchdog continue to count when the CPU is halted for debug? + pub action_during_debug_halt: HaltConfig, +} + +impl Config { + /// Create a config structure from the current configuration of the WDT + /// peripheral. + pub fn try_new(_wdt: &peripherals::WDT) -> Option { + let r = crate::pac::WDT; + + #[cfg(not(feature = "_nrf91"))] + let runstatus = r.runstatus().read().runstatus(); + #[cfg(feature = "_nrf91")] + let runstatus = r.runstatus().read().runstatuswdt(); + + if runstatus { + let config = r.config().read(); + Some(Self { + timeout_ticks: r.crv().read(), + action_during_sleep: config.sleep(), + action_during_debug_halt: config.halt(), + }) + } else { + None + } + } +} + +impl Default for Config { + fn default() -> Self { + Self { + timeout_ticks: 32768, // 1 second + action_during_debug_halt: HaltConfig::RUN, + action_during_sleep: SleepConfig::RUN, + } + } +} + +/// Watchdog driver. +pub struct Watchdog { + _private: (), +} + +impl Watchdog { + /// 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 + /// enabled handles. + /// + /// `N` must be between 1 and 8, inclusive. + #[inline] + pub fn try_new( + wdt: peripherals::WDT, + config: Config, + ) -> Result<(Self, [WatchdogHandle; N]), peripherals::WDT> { + assert!(N >= 1 && N <= 8); + + let r = crate::pac::WDT; + + let crv = config.timeout_ticks.max(MIN_TICKS); + let rren = crate::pac::wdt::regs::Rren((1u32 << N) - 1); + + #[cfg(not(feature = "_nrf91"))] + let runstatus = r.runstatus().read().runstatus(); + #[cfg(feature = "_nrf91")] + let runstatus = r.runstatus().read().runstatuswdt(); + + if runstatus { + let curr_config = r.config().read(); + if curr_config.halt() != config.action_during_debug_halt + || curr_config.sleep() != config.action_during_sleep + || r.crv().read() != crv + || r.rren().read() != rren + { + return Err(wdt); + } + } else { + r.config().write(|w| { + w.set_sleep(config.action_during_sleep); + w.set_halt(config.action_during_debug_halt); + }); + r.intenset().write(|w| w.set_timeout(true)); + + r.crv().write_value(crv); + r.rren().write_value(rren); + r.tasks_start().write_value(1); + } + + let this = Self { _private: () }; + + let mut handles = [const { WatchdogHandle { index: 0 } }; N]; + for i in 0..N { + handles[i] = WatchdogHandle { index: i as u8 }; + handles[i].pet(); + } + + Ok((this, handles)) + } + + /// Enable the watchdog interrupt. + /// + /// NOTE: Although the interrupt will occur, there is no way to prevent + /// the reset from occurring. From the time the event was fired, the + /// system will reset two LFCLK ticks later (61 microseconds) if the + /// interrupt has been enabled. + #[inline(always)] + pub fn enable_interrupt(&mut self) { + crate::pac::WDT.intenset().write(|w| w.set_timeout(true)); + } + + /// Disable the watchdog interrupt. + /// + /// NOTE: This has no effect on the reset caused by the Watchdog. + #[inline(always)] + pub fn disable_interrupt(&mut self) { + crate::pac::WDT.intenclr().write(|w| w.set_timeout(true)); + } + + /// Is the watchdog still awaiting pets from any handle? + /// + /// This reports whether sufficient pets have been received from all + /// handles to prevent a reset this time period. + #[inline(always)] + pub fn awaiting_pets(&self) -> bool { + let r = crate::pac::WDT; + let enabled = r.rren().read().0; + let status = r.reqstatus().read().0; + (status & enabled) == 0 + } +} + +/// Watchdog handle. +pub struct WatchdogHandle { + index: u8, +} + +impl WatchdogHandle { + /// Pet the watchdog. + /// + /// This function pets the given watchdog handle. + /// + /// NOTE: All active handles must be pet within the time interval to + /// prevent a reset from occurring. + #[inline] + pub fn pet(&mut self) { + let r = crate::pac::WDT; + r.rr(self.index as usize).write(|w| w.set_rr(vals::Rr::RELOAD)); + } + + /// Has this handle been pet within the current window? + pub fn is_pet(&self) -> bool { + let r = crate::pac::WDT; + !r.reqstatus().read().rr(self.index as usize) + } + + /// Steal a watchdog handle by index. + /// + /// # Safety + /// Watchdog must be initialized and `index` must be between `0` and `N-1` + /// where `N` is the handle count when initializing. + pub unsafe fn steal(index: u8) -> Self { + Self { index } + } +} diff --git a/embassy/embassy-nxp/Cargo.toml b/embassy/embassy-nxp/Cargo.toml new file mode 100644 index 0000000..b0f73b6 --- /dev/null +++ b/embassy/embassy-nxp/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "embassy-nxp" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.0" +critical-section = "1.1.2" +embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +lpc55-pac = "0.5.0" +defmt = "0.3.8" + +[features] +default = ["rt"] +rt = ["lpc55-pac/rt"] \ No newline at end of file diff --git a/embassy/embassy-nxp/src/gpio.rs b/embassy/embassy-nxp/src/gpio.rs new file mode 100644 index 0000000..d5d04ee --- /dev/null +++ b/embassy/embassy-nxp/src/gpio.rs @@ -0,0 +1,361 @@ +use embassy_hal_internal::impl_peripheral; + +use crate::pac_utils::*; +use crate::{peripherals, Peripheral, PeripheralRef}; + +pub(crate) fn init() { + // Enable clocks for GPIO, PINT, and IOCON + syscon_reg() + .ahbclkctrl0 + .modify(|_, w| w.gpio0().enable().gpio1().enable().mux().enable().iocon().enable()); +} + +/// The GPIO pin level for pins set on "Digital" mode. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Level { + /// Logical low. Corresponds to 0V. + Low, + /// Logical high. Corresponds to VDD. + High, +} + +/// Pull setting for a GPIO input set on "Digital" mode. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Pull { + /// No pull. + None, + /// Internal pull-up resistor. + Up, + /// Internal pull-down resistor. + Down, +} + +/// The LPC55 boards have two GPIO banks, each with 32 pins. This enum represents the two banks. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Bank { + Bank0 = 0, + Bank1 = 1, +} + +/// GPIO output driver. Internally, this is a specialized [Flex] pin. +pub struct Output<'d> { + pub(crate) pin: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create GPIO output driver for a [Pin] with the provided [initial output](Level). + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_output(); + let mut result = Self { pin }; + + match initial_output { + Level::High => result.set_high(), + Level::Low => result.set_low(), + }; + + result + } + + pub fn set_high(&mut self) { + gpio_reg().set[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) }) + } + + pub fn set_low(&mut self) { + gpio_reg().clr[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) }) + } + + pub fn toggle(&mut self) { + gpio_reg().not[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) }) + } + + /// Get the current output level of the pin. Note that the value returned by this function is + /// the voltage level reported by the pin, not the value set by the output driver. + pub fn level(&self) -> Level { + let bits = gpio_reg().pin[self.pin.pin_bank() as usize].read().bits(); + if bits & self.pin.bit() != 0 { + Level::High + } else { + Level::Low + } + } +} + +/// GPIO input driver. Internally, this is a specialized [Flex] pin. +pub struct Input<'d> { + pub(crate) pin: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create GPIO output driver for a [Pin] with the provided [Pull]. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, pull: Pull) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_input(); + let mut result = Self { pin }; + result.set_pull(pull); + + result + } + + /// Set the pull configuration for the pin. To disable the pull, use [Pull::None]. + pub fn set_pull(&mut self, pull: Pull) { + match_iocon!(register, iocon_reg(), self.pin.pin_bank(), self.pin.pin_number(), { + register.modify(|_, w| match pull { + Pull::None => w.mode().inactive(), + Pull::Up => w.mode().pull_up(), + Pull::Down => w.mode().pull_down(), + }); + }); + } + + /// Get the current input level of the pin. + pub fn read(&self) -> Level { + let bits = gpio_reg().pin[self.pin.pin_bank() as usize].read().bits(); + if bits & self.pin.bit() != 0 { + Level::High + } else { + Level::Low + } + } +} + +/// A flexible GPIO (digital mode) pin whose mode is not yet determined. Under the hood, this is a +/// reference to a type-erased pin called ["AnyPin"](AnyPin). +pub struct Flex<'d> { + pub(crate) pin: PeripheralRef<'d, AnyPin>, +} + +impl<'d> Flex<'d> { + /// Wrap the pin in a `Flex`. + /// + /// Note: you cannot assume that the pin will be in Digital mode after this call. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd) -> Self { + Self { + pin: pin.into_ref().map_into(), + } + } + + /// Get the bank of this pin. See also [Bank]. + /// + /// # Example + /// + /// ``` + /// use embassy_nxp::gpio::{Bank, Flex}; + /// + /// let p = embassy_nxp::init(Default::default()); + /// let pin = Flex::new(p.PIO1_15); + /// + /// assert_eq!(pin.pin_bank(), Bank::Bank1); + /// ``` + pub fn pin_bank(&self) -> Bank { + self.pin.pin_bank() + } + + /// Get the number of this pin within its bank. See also [Bank]. + /// + /// # Example + /// + /// ``` + /// use embassy_nxp::gpio::Flex; + /// + /// let p = embassy_nxp::init(Default::default()); + /// let pin = Flex::new(p.PIO1_15); + /// + /// assert_eq!(pin.pin_number(), 15 as u8); + /// ``` + pub fn pin_number(&self) -> u8 { + self.pin.pin_number() + } + + /// Get the bit mask for this pin. Useful for setting or clearing bits in a register. Note: + /// PIOx_0 is bit 0, PIOx_1 is bit 1, etc. + /// + /// # Example + /// + /// ``` + /// use embassy_nxp::gpio::Flex; + /// + /// let p = embassy_nxp::init(Default::default()); + /// let pin = Flex::new(p.PIO1_3); + /// + /// assert_eq!(pin.bit(), 0b0000_1000); + /// ``` + pub fn bit(&self) -> u32 { + 1 << self.pin.pin_number() + } + + /// Set the pin to digital mode. This is required for using a pin as a GPIO pin. The default + /// setting for pins is (usually) non-digital. + fn set_as_digital(&mut self) { + match_iocon!(register, iocon_reg(), self.pin_bank(), self.pin_number(), { + register.modify(|_, w| w.digimode().digital()); + }); + } + + /// Set the pin in output mode. This implies setting the pin to digital mode, which this + /// function handles itself. + pub fn set_as_output(&mut self) { + self.set_as_digital(); + gpio_reg().dirset[self.pin.pin_bank() as usize].write(|w| unsafe { w.dirsetp().bits(self.bit()) }) + } + + pub fn set_as_input(&mut self) { + self.set_as_digital(); + gpio_reg().dirclr[self.pin.pin_bank() as usize].write(|w| unsafe { w.dirclrp().bits(self.bit()) }) + } +} + +/// Sealed trait for pins. This trait is sealed and cannot be implemented outside of this crate. +pub(crate) trait SealedPin: Sized { + fn pin_bank(&self) -> Bank; + fn pin_number(&self) -> u8; +} + +/// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an +/// [AnyPin]. By default, this trait is sealed and cannot be implemented outside of the +/// `embassy-nxp` crate due to the [SealedPin] trait. +#[allow(private_bounds)] +pub trait Pin: Peripheral

+ Into + SealedPin + Sized + 'static { + /// Degrade to a generic pin struct + fn degrade(self) -> AnyPin { + AnyPin { + pin_bank: self.pin_bank(), + pin_number: self.pin_number(), + } + } + + /// Returns the pin number within a bank + #[inline] + fn pin(&self) -> u8 { + self.pin_number() + } + + /// Returns the bank of this pin + #[inline] + fn bank(&self) -> Bank { + self.pin_bank() + } +} + +/// Type-erased GPIO pin. +pub struct AnyPin { + pin_bank: Bank, + pin_number: u8, +} + +impl AnyPin { + /// Unsafely create a new type-erased pin. + /// + /// # Safety + /// + /// You must ensure that you’re only using one instance of this type at a time. + pub unsafe fn steal(pin_bank: Bank, pin_number: u8) -> Self { + Self { pin_bank, pin_number } + } +} + +impl_peripheral!(AnyPin); + +impl Pin for AnyPin {} +impl SealedPin for AnyPin { + #[inline] + fn pin_bank(&self) -> Bank { + self.pin_bank + } + + #[inline] + fn pin_number(&self) -> u8 { + self.pin_number + } +} + +macro_rules! impl_pin { + ($name:ident, $bank:expr, $pin_num:expr) => { + impl Pin for peripherals::$name {} + impl SealedPin for peripherals::$name { + #[inline] + fn pin_bank(&self) -> Bank { + $bank + } + + #[inline] + fn pin_number(&self) -> u8 { + $pin_num + } + } + + impl From for crate::gpio::AnyPin { + fn from(val: peripherals::$name) -> Self { + crate::gpio::Pin::degrade(val) + } + } + }; +} + +impl_pin!(PIO0_0, Bank::Bank0, 0); +impl_pin!(PIO0_1, Bank::Bank0, 1); +impl_pin!(PIO0_2, Bank::Bank0, 2); +impl_pin!(PIO0_3, Bank::Bank0, 3); +impl_pin!(PIO0_4, Bank::Bank0, 4); +impl_pin!(PIO0_5, Bank::Bank0, 5); +impl_pin!(PIO0_6, Bank::Bank0, 6); +impl_pin!(PIO0_7, Bank::Bank0, 7); +impl_pin!(PIO0_8, Bank::Bank0, 8); +impl_pin!(PIO0_9, Bank::Bank0, 9); +impl_pin!(PIO0_10, Bank::Bank0, 10); +impl_pin!(PIO0_11, Bank::Bank0, 11); +impl_pin!(PIO0_12, Bank::Bank0, 12); +impl_pin!(PIO0_13, Bank::Bank0, 13); +impl_pin!(PIO0_14, Bank::Bank0, 14); +impl_pin!(PIO0_15, Bank::Bank0, 15); +impl_pin!(PIO0_16, Bank::Bank0, 16); +impl_pin!(PIO0_17, Bank::Bank0, 17); +impl_pin!(PIO0_18, Bank::Bank0, 18); +impl_pin!(PIO0_19, Bank::Bank0, 19); +impl_pin!(PIO0_20, Bank::Bank0, 20); +impl_pin!(PIO0_21, Bank::Bank0, 21); +impl_pin!(PIO0_22, Bank::Bank0, 22); +impl_pin!(PIO0_23, Bank::Bank0, 23); +impl_pin!(PIO0_24, Bank::Bank0, 24); +impl_pin!(PIO0_25, Bank::Bank0, 25); +impl_pin!(PIO0_26, Bank::Bank0, 26); +impl_pin!(PIO0_27, Bank::Bank0, 27); +impl_pin!(PIO0_28, Bank::Bank0, 28); +impl_pin!(PIO0_29, Bank::Bank0, 29); +impl_pin!(PIO0_30, Bank::Bank0, 30); +impl_pin!(PIO0_31, Bank::Bank0, 31); +impl_pin!(PIO1_0, Bank::Bank1, 0); +impl_pin!(PIO1_1, Bank::Bank1, 1); +impl_pin!(PIO1_2, Bank::Bank1, 2); +impl_pin!(PIO1_3, Bank::Bank1, 3); +impl_pin!(PIO1_4, Bank::Bank1, 4); +impl_pin!(PIO1_5, Bank::Bank1, 5); +impl_pin!(PIO1_6, Bank::Bank1, 6); +impl_pin!(PIO1_7, Bank::Bank1, 7); +impl_pin!(PIO1_8, Bank::Bank1, 8); +impl_pin!(PIO1_9, Bank::Bank1, 9); +impl_pin!(PIO1_10, Bank::Bank1, 10); +impl_pin!(PIO1_11, Bank::Bank1, 11); +impl_pin!(PIO1_12, Bank::Bank1, 12); +impl_pin!(PIO1_13, Bank::Bank1, 13); +impl_pin!(PIO1_14, Bank::Bank1, 14); +impl_pin!(PIO1_15, Bank::Bank1, 15); +impl_pin!(PIO1_16, Bank::Bank1, 16); +impl_pin!(PIO1_17, Bank::Bank1, 17); +impl_pin!(PIO1_18, Bank::Bank1, 18); +impl_pin!(PIO1_19, Bank::Bank1, 19); +impl_pin!(PIO1_20, Bank::Bank1, 20); +impl_pin!(PIO1_21, Bank::Bank1, 21); +impl_pin!(PIO1_22, Bank::Bank1, 22); +impl_pin!(PIO1_23, Bank::Bank1, 23); +impl_pin!(PIO1_24, Bank::Bank1, 24); +impl_pin!(PIO1_25, Bank::Bank1, 25); +impl_pin!(PIO1_26, Bank::Bank1, 26); +impl_pin!(PIO1_27, Bank::Bank1, 27); +impl_pin!(PIO1_28, Bank::Bank1, 28); +impl_pin!(PIO1_29, Bank::Bank1, 29); +impl_pin!(PIO1_30, Bank::Bank1, 30); +impl_pin!(PIO1_31, Bank::Bank1, 31); diff --git a/embassy/embassy-nxp/src/lib.rs b/embassy/embassy-nxp/src/lib.rs new file mode 100644 index 0000000..80fdecb --- /dev/null +++ b/embassy/embassy-nxp/src/lib.rs @@ -0,0 +1,95 @@ +#![no_std] + +pub mod gpio; +mod pac_utils; +pub mod pint; + +pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +pub use lpc55_pac as pac; + +/// Initialize the `embassy-nxp` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once and at startup, otherwise it panics. +pub fn init(_config: config::Config) -> Peripherals { + gpio::init(); + pint::init(); + + crate::Peripherals::take() +} + +embassy_hal_internal::peripherals! { + // External pins. These are not only GPIOs, they are multi-purpose pins and can be used by other + // peripheral types (e.g. I2C). + PIO0_0, + PIO0_1, + PIO0_2, + PIO0_3, + PIO0_4, + PIO0_5, + PIO0_6, + PIO0_7, + PIO0_8, + PIO0_9, + PIO0_10, + PIO0_11, + PIO0_12, + PIO0_13, + PIO0_14, + PIO0_15, + PIO0_16, + PIO0_17, + PIO0_18, + PIO0_19, + PIO0_20, + PIO0_21, + PIO0_22, + PIO0_23, + PIO0_24, + PIO0_25, + PIO0_26, + PIO0_27, + PIO0_28, + PIO0_29, + PIO0_30, + PIO0_31, + PIO1_0, + PIO1_1, + PIO1_2, + PIO1_3, + PIO1_4, + PIO1_5, + PIO1_6, + PIO1_7, + PIO1_8, + PIO1_9, + PIO1_10, + PIO1_11, + PIO1_12, + PIO1_13, + PIO1_14, + PIO1_15, + PIO1_16, + PIO1_17, + PIO1_18, + PIO1_19, + PIO1_20, + PIO1_21, + PIO1_22, + PIO1_23, + PIO1_24, + PIO1_25, + PIO1_26, + PIO1_27, + PIO1_28, + PIO1_29, + PIO1_30, + PIO1_31, +} + +/// HAL configuration for the NXP board. +pub mod config { + #[derive(Default)] + pub struct Config {} +} diff --git a/embassy/embassy-nxp/src/pac_utils.rs b/embassy/embassy-nxp/src/pac_utils.rs new file mode 100644 index 0000000..86a807f --- /dev/null +++ b/embassy/embassy-nxp/src/pac_utils.rs @@ -0,0 +1,323 @@ +/// Get the GPIO register block. This is used to configure all GPIO pins. +/// +/// # Safety +/// Due to the type system of peripherals, access to the settings of a single pin is possible only +/// by a single thread at a time. Read/Write operations on a single registers are NOT atomic. You +/// must ensure that the GPIO registers are not accessed concurrently by multiple threads. +pub(crate) fn gpio_reg() -> &'static lpc55_pac::gpio::RegisterBlock { + unsafe { &*lpc55_pac::GPIO::ptr() } +} + +/// Get the IOCON register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn iocon_reg() -> &'static lpc55_pac::iocon::RegisterBlock { + unsafe { &*lpc55_pac::IOCON::ptr() } +} + +/// Get the INPUTMUX register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn inputmux_reg() -> &'static lpc55_pac::inputmux::RegisterBlock { + unsafe { &*lpc55_pac::INPUTMUX::ptr() } +} + +/// Get the SYSCON register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn syscon_reg() -> &'static lpc55_pac::syscon::RegisterBlock { + unsafe { &*lpc55_pac::SYSCON::ptr() } +} + +/// Get the PINT register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn pint_reg() -> &'static lpc55_pac::pint::RegisterBlock { + unsafe { &*lpc55_pac::PINT::ptr() } +} + +/// Match the pin bank and number of a pin to the corresponding IOCON register. +/// +/// # Example +/// ``` +/// use embassy_nxp::gpio::Bank; +/// use embassy_nxp::pac_utils::{iocon_reg, match_iocon}; +/// +/// // Make pin PIO1_6 digital and set it to pull-down mode. +/// match_iocon!(register, iocon_reg(), Bank::Bank1, 6, { +/// register.modify(|_, w| w.mode().pull_down().digimode().digital()); +/// }); +/// ``` +macro_rules! match_iocon { + ($register:ident, $iocon_register:expr, $pin_bank:expr, $pin_number:expr, $action:expr) => { + match ($pin_bank, $pin_number) { + (Bank::Bank0, 0) => { + let $register = &($iocon_register).pio0_0; + $action; + } + (Bank::Bank0, 1) => { + let $register = &($iocon_register).pio0_1; + $action; + } + (Bank::Bank0, 2) => { + let $register = &($iocon_register).pio0_2; + $action; + } + (Bank::Bank0, 3) => { + let $register = &($iocon_register).pio0_3; + $action; + } + (Bank::Bank0, 4) => { + let $register = &($iocon_register).pio0_4; + $action; + } + (Bank::Bank0, 5) => { + let $register = &($iocon_register).pio0_5; + $action; + } + (Bank::Bank0, 6) => { + let $register = &($iocon_register).pio0_6; + $action; + } + (Bank::Bank0, 7) => { + let $register = &($iocon_register).pio0_7; + $action; + } + (Bank::Bank0, 8) => { + let $register = &($iocon_register).pio0_8; + $action; + } + (Bank::Bank0, 9) => { + let $register = &($iocon_register).pio0_9; + $action; + } + (Bank::Bank0, 10) => { + let $register = &($iocon_register).pio0_10; + $action; + } + (Bank::Bank0, 11) => { + let $register = &($iocon_register).pio0_11; + $action; + } + (Bank::Bank0, 12) => { + let $register = &($iocon_register).pio0_12; + $action; + } + (Bank::Bank0, 13) => { + let $register = &($iocon_register).pio0_13; + $action; + } + (Bank::Bank0, 14) => { + let $register = &($iocon_register).pio0_14; + $action; + } + (Bank::Bank0, 15) => { + let $register = &($iocon_register).pio0_15; + $action; + } + (Bank::Bank0, 16) => { + let $register = &($iocon_register).pio0_16; + $action; + } + (Bank::Bank0, 17) => { + let $register = &($iocon_register).pio0_17; + $action; + } + (Bank::Bank0, 18) => { + let $register = &($iocon_register).pio0_18; + $action; + } + (Bank::Bank0, 19) => { + let $register = &($iocon_register).pio0_19; + $action; + } + (Bank::Bank0, 20) => { + let $register = &($iocon_register).pio0_20; + $action; + } + (Bank::Bank0, 21) => { + let $register = &($iocon_register).pio0_21; + $action; + } + (Bank::Bank0, 22) => { + let $register = &($iocon_register).pio0_22; + $action; + } + (Bank::Bank0, 23) => { + let $register = &($iocon_register).pio0_23; + $action; + } + (Bank::Bank0, 24) => { + let $register = &($iocon_register).pio0_24; + $action; + } + (Bank::Bank0, 25) => { + let $register = &($iocon_register).pio0_25; + $action; + } + (Bank::Bank0, 26) => { + let $register = &($iocon_register).pio0_26; + $action; + } + (Bank::Bank0, 27) => { + let $register = &($iocon_register).pio0_27; + $action; + } + (Bank::Bank0, 28) => { + let $register = &($iocon_register).pio0_28; + $action; + } + (Bank::Bank0, 29) => { + let $register = &($iocon_register).pio0_29; + $action; + } + (Bank::Bank0, 30) => { + let $register = &($iocon_register).pio0_30; + $action; + } + (Bank::Bank0, 31) => { + let $register = &($iocon_register).pio0_31; + $action; + } + (Bank::Bank1, 0) => { + let $register = &($iocon_register).pio1_0; + $action; + } + (Bank::Bank1, 1) => { + let $register = &($iocon_register).pio1_1; + $action; + } + (Bank::Bank1, 2) => { + let $register = &($iocon_register).pio1_2; + $action; + } + (Bank::Bank1, 3) => { + let $register = &($iocon_register).pio1_3; + $action; + } + (Bank::Bank1, 4) => { + let $register = &($iocon_register).pio1_4; + $action; + } + (Bank::Bank1, 5) => { + let $register = &($iocon_register).pio1_5; + $action; + } + (Bank::Bank1, 6) => { + let $register = &($iocon_register).pio1_6; + $action; + } + (Bank::Bank1, 7) => { + let $register = &($iocon_register).pio1_7; + $action; + } + (Bank::Bank1, 8) => { + let $register = &($iocon_register).pio1_8; + $action; + } + (Bank::Bank1, 9) => { + let $register = &($iocon_register).pio1_9; + $action; + } + (Bank::Bank1, 10) => { + let $register = &($iocon_register).pio1_10; + $action; + } + (Bank::Bank1, 11) => { + let $register = &($iocon_register).pio1_11; + $action; + } + (Bank::Bank1, 12) => { + let $register = &($iocon_register).pio1_12; + $action; + } + (Bank::Bank1, 13) => { + let $register = &($iocon_register).pio1_13; + $action; + } + (Bank::Bank1, 14) => { + let $register = &($iocon_register).pio1_14; + $action; + } + (Bank::Bank1, 15) => { + let $register = &($iocon_register).pio1_15; + $action; + } + (Bank::Bank1, 16) => { + let $register = &($iocon_register).pio1_16; + $action; + } + (Bank::Bank1, 17) => { + let $register = &($iocon_register).pio1_17; + $action; + } + (Bank::Bank1, 18) => { + let $register = &($iocon_register).pio1_18; + $action; + } + (Bank::Bank1, 19) => { + let $register = &($iocon_register).pio1_19; + $action; + } + (Bank::Bank1, 20) => { + let $register = &($iocon_register).pio1_20; + $action; + } + (Bank::Bank1, 21) => { + let $register = &($iocon_register).pio1_21; + $action; + } + (Bank::Bank1, 22) => { + let $register = &($iocon_register).pio1_22; + $action; + } + (Bank::Bank1, 23) => { + let $register = &($iocon_register).pio1_23; + $action; + } + (Bank::Bank1, 24) => { + let $register = &($iocon_register).pio1_24; + $action; + } + (Bank::Bank1, 25) => { + let $register = &($iocon_register).pio1_25; + $action; + } + (Bank::Bank1, 26) => { + let $register = &($iocon_register).pio1_26; + $action; + } + (Bank::Bank1, 27) => { + let $register = &($iocon_register).pio1_27; + $action; + } + (Bank::Bank1, 28) => { + let $register = &($iocon_register).pio1_28; + $action; + } + (Bank::Bank1, 29) => { + let $register = &($iocon_register).pio1_29; + $action; + } + (Bank::Bank1, 30) => { + let $register = &($iocon_register).pio1_30; + $action; + } + (Bank::Bank1, 31) => { + let $register = &($iocon_register).pio1_31; + $action; + } + _ => unreachable!(), + } + }; +} + +pub(crate) use match_iocon; diff --git a/embassy/embassy-nxp/src/pint.rs b/embassy/embassy-nxp/src/pint.rs new file mode 100644 index 0000000..809be4b --- /dev/null +++ b/embassy/embassy-nxp/src/pint.rs @@ -0,0 +1,442 @@ +//! Pin Interrupt module. +use core::cell::RefCell; +use core::future::Future; +use core::pin::Pin as FuturePin; +use core::task::{Context, Poll}; + +use critical_section::Mutex; +use embassy_hal_internal::{Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::gpio::{self, AnyPin, Level, SealedPin}; +use crate::pac::interrupt; +use crate::pac_utils::*; + +struct PinInterrupt { + assigned: bool, + waker: AtomicWaker, + /// If true, the interrupt was triggered due to this PinInterrupt. This is used to determine if + /// an [InputFuture] should return Poll::Ready. + at_fault: bool, +} + +impl PinInterrupt { + pub fn interrupt_active(&self) -> bool { + self.assigned + } + + /// Mark the interrupt as assigned to a pin. + pub fn enable(&mut self) { + self.assigned = true; + self.at_fault = false; + } + + /// Mark the interrupt as available. + pub fn disable(&mut self) { + self.assigned = false; + self.at_fault = false; + } + + /// Returns true if the interrupt was triggered due to this PinInterrupt. + /// + /// If this function returns true, it will also reset the at_fault flag. + pub fn at_fault(&mut self) -> bool { + let val = self.at_fault; + self.at_fault = false; + val + } + + /// Set the at_fault flag to true. + pub fn fault(&mut self) { + self.at_fault = true; + } +} + +const INTERUPT_COUNT: usize = 8; +static PIN_INTERRUPTS: Mutex> = Mutex::new(RefCell::new( + [const { + PinInterrupt { + assigned: false, + waker: AtomicWaker::new(), + at_fault: false, + } + }; INTERUPT_COUNT], +)); + +fn next_available_interrupt() -> Option { + critical_section::with(|cs| { + for (i, pin_interrupt) in PIN_INTERRUPTS.borrow(cs).borrow().iter().enumerate() { + if !pin_interrupt.interrupt_active() { + return Some(i); + } + } + + None + }) +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Edge { + Rising, + Falling, + Both, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum InterruptOn { + Level(Level), + Edge(Edge), +} + +pub(crate) fn init() { + syscon_reg().ahbclkctrl0.modify(|_, w| w.pint().enable()); + + // Enable interrupts + unsafe { + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT0); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT1); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT2); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT3); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT4); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT5); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT6); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT7); + }; +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct InputFuture<'d> { + #[allow(dead_code)] + pin: PeripheralRef<'d, AnyPin>, + interrupt_number: usize, +} + +impl<'d> InputFuture<'d> { + /// Create a new input future. Returns None if all interrupts are in use. + fn new(pin: impl Peripheral

+ 'd, interrupt_on: InterruptOn) -> Option { + let pin = pin.into_ref().map_into(); + let interrupt_number = next_available_interrupt()?; + + // Clear interrupt, just in case + pint_reg() + .rise + .write(|w| unsafe { w.rdet().bits(1 << interrupt_number) }); + pint_reg() + .fall + .write(|w| unsafe { w.fdet().bits(1 << interrupt_number) }); + + // Enable input multiplexing on pin interrupt register 0 for pin (32*bank + pin_number) + inputmux_reg().pintsel[interrupt_number] + .write(|w| unsafe { w.intpin().bits(32 * pin.pin_bank() as u8 + pin.pin_number()) }); + + match interrupt_on { + InterruptOn::Level(level) => { + // Set pin interrupt register to edge sensitive or level sensitive + // 0 = edge sensitive, 1 = level sensitive + pint_reg() + .isel + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << interrupt_number)) }); + + // Enable level interrupt. + // + // Note: Level sensitive interrupts are enabled by the same register as rising edge + // is activated. + + // 0 = no-op, 1 = enable + pint_reg() + .sienr + .write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) }); + + // Set active level + match level { + Level::Low => { + // 0 = no-op, 1 = select LOW + pint_reg() + .cienf + .write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) }); + } + Level::High => { + // 0 = no-op, 1 = select HIGH + pint_reg() + .sienf + .write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) }); + } + } + } + InterruptOn::Edge(edge) => { + // Set pin interrupt register to edge sensitive or level sensitive + // 0 = edge sensitive, 1 = level sensitive + pint_reg() + .isel + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << interrupt_number)) }); + + // Enable rising/falling edge detection + match edge { + Edge::Rising => { + // 0 = no-op, 1 = enable rising edge + pint_reg() + .sienr + .write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) }); + // 0 = no-op, 1 = disable falling edge + pint_reg() + .cienf + .write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) }); + } + Edge::Falling => { + // 0 = no-op, 1 = enable falling edge + pint_reg() + .sienf + .write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) }); + // 0 = no-op, 1 = disable rising edge + pint_reg() + .cienr + .write(|w| unsafe { w.cenrl().bits(1 << interrupt_number) }); + } + Edge::Both => { + // 0 = no-op, 1 = enable + pint_reg() + .sienr + .write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) }); + pint_reg() + .sienf + .write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) }); + } + } + } + } + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.enable(); + }); + + Some(Self { pin, interrupt_number }) + } + + /// Returns true if the interrupt was triggered for this pin. + fn interrupt_triggered(&self) -> bool { + let interrupt_number = self.interrupt_number; + + // Initially, we determine if the interrupt was triggered by this InputFuture by checking + // the flags of the interrupt_number. However, by the time we get to this point, the + // interrupt may have been triggered again, so we needed to clear the cpu flags immediately. + // As a solution, we mark which [PinInterrupt] is responsible for the interrupt ("at fault") + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.at_fault() + }) + } +} + +impl<'d> Drop for InputFuture<'d> { + fn drop(&mut self) { + let interrupt_number = self.interrupt_number; + + // Disable pin interrupt + // 0 = no-op, 1 = disable + pint_reg() + .cienr + .write(|w| unsafe { w.cenrl().bits(1 << interrupt_number) }); + pint_reg() + .cienf + .write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) }); + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.disable(); + }); + } +} + +impl<'d> Future for InputFuture<'d> { + type Output = (); + + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let interrupt_number = self.interrupt_number; + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.waker.register(cx.waker()); + }); + + if self.interrupt_triggered() { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +fn handle_interrupt(interrupt_number: usize) { + pint_reg() + .rise + .write(|w| unsafe { w.rdet().bits(1 << interrupt_number) }); + pint_reg() + .fall + .write(|w| unsafe { w.fdet().bits(1 << interrupt_number) }); + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.fault(); + pin_interrupt.waker.wake(); + }); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT0() { + handle_interrupt(0); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT1() { + handle_interrupt(1); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT2() { + handle_interrupt(2); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT3() { + handle_interrupt(3); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT4() { + handle_interrupt(4); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT5() { + handle_interrupt(5); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT6() { + handle_interrupt(6); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT7() { + handle_interrupt(7); +} + +impl gpio::Flex<'_> { + /// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you + /// try to wait for more than 8 pins, this function will return `None`. + pub async fn wait_for_any_edge(&mut self) -> Option<()> { + InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Both))?.await; + Some(()) + } + + /// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_falling_edge(&mut self) -> Option<()> { + InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Falling))?.await; + Some(()) + } + + /// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_rising_edge(&mut self) -> Option<()> { + InputFuture::new(&mut self.pin, InterruptOn::Edge(Edge::Rising))?.await; + Some(()) + } + + /// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_low(&mut self) -> Option<()> { + InputFuture::new(&mut self.pin, InterruptOn::Level(Level::Low))?.await; + Some(()) + } + + /// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_high(&mut self) -> Option<()> { + InputFuture::new(&mut self.pin, InterruptOn::Level(Level::High))?.await; + Some(()) + } +} + +impl gpio::Input<'_> { + /// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you + /// try to wait for more than 8 pins, this function will return `None`. + pub async fn wait_for_any_edge(&mut self) -> Option<()> { + self.pin.wait_for_any_edge().await + } + + /// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_falling_edge(&mut self) -> Option<()> { + self.pin.wait_for_falling_edge().await + } + + /// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_rising_edge(&mut self) -> Option<()> { + self.pin.wait_for_rising_edge().await + } + + /// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_low(&mut self) -> Option<()> { + self.pin.wait_for_low().await + } + + /// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_high(&mut self) -> Option<()> { + self.pin.wait_for_high().await + } +} + +impl gpio::Output<'_> { + /// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you + /// try to wait for more than 8 pins, this function will return `None`. + pub async fn wait_for_any_edge(&mut self) -> Option<()> { + self.pin.wait_for_any_edge().await + } + + /// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_falling_edge(&mut self) -> Option<()> { + self.pin.wait_for_falling_edge().await + } + + /// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_rising_edge(&mut self) -> Option<()> { + self.pin.wait_for_rising_edge().await + } + + /// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_low(&mut self) -> Option<()> { + self.pin.wait_for_low().await + } + + /// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_high(&mut self) -> Option<()> { + self.pin.wait_for_high().await + } +} diff --git a/embassy/embassy-rp/CHANGELOG.md b/embassy/embassy-rp/CHANGELOG.md new file mode 100644 index 0000000..7eef642 --- /dev/null +++ b/embassy/embassy-rp/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog for embassy-rp + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-08-05 + +- Add read_to_break_with_count +- add option to provide your own boot2 +- Add multichannel ADC +- Add collapse_debuginfo to fmt.rs macros. +- Use raw slices .len() method instead of unsafe hacks. +- Add missing word "pin" in rp pwm documentation +- Add Clone and Copy to Error types +- fix spinlocks staying locked after reset. +- wait until read matches for PSM accesses. +- Remove generics +- fix drop implementation of BufferedUartRx and BufferedUartTx +- implement `embedded_storage_async::nor_flash::MultiwriteNorFlash` +- rp usb: wake ep-wakers after stalling +- rp usb: add stall implementation +- Add parameter for enabling pull-up and pull-down in RP PWM input mode +- rp: remove mod sealed. +- rename pins data type and the macro +- rename pwm channels to pwm slices, including in documentation +- rename the Channel trait to Slice and the PwmPin to PwmChannel +- i2c: Fix race condition that appears on fast repeated transfers. +- Add a basic "read to break" function diff --git a/embassy/embassy-rp/Cargo.toml b/embassy/embassy-rp/Cargo.toml new file mode 100644 index 0000000..94de82f --- /dev/null +++ b/embassy/embassy-rp/Cargo.toml @@ -0,0 +1,152 @@ +[package] +name = "embassy-rp" +version = "0.2.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Embassy Hardware Abstraction Layer (HAL) for the Raspberry Pi RP2040 microcontroller" +keywords = ["embedded", "async", "raspberry-pi", "rp2040", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-rp" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-rp-v$VERSION/embassy-rp/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-rp/src/" +features = ["defmt", "unstable-pac", "time-driver"] +flavors = [ + { name = "rp2040", target = "thumbv6m-none-eabi", features = ["rp2040"] }, + { name = "rp235xa", target = "thumbv8m.main-none-eabihf", features = ["rp235xa"] }, + { name = "rp235xb", target = "thumbv8m.main-none-eabihf", features = ["rp235xb"] }, +] + +[package.metadata.docs.rs] +features = ["defmt", "unstable-pac", "time-driver"] + +[features] +default = [ "rt" ] +## Enable the rt feature of [`rp-pac`](https://docs.rs/rp-pac). This brings in the [`cortex-m-rt`](https://docs.rs/cortex-m-rt) crate, which adds startup code and minimal runtime initialization. +rt = [ "rp-pac/rt" ] + +## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers. +defmt = ["dep:defmt", "embassy-usb-driver/defmt", "embassy-hal-internal/defmt"] + +## Configure the [`critical-section`](https://docs.rs/critical-section) crate to use an implementation that is safe for multicore use on rp2040. +critical-section-impl = ["critical-section/restore-state-u8"] + +## Reexport the PAC for the currently enabled chip at `embassy_rp::pac`. +## This is unstable because semver-minor (non-breaking) releases of `embassy-rp` may major-bump (breaking) the PAC version. +## If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC. +## There are no plans to make this stable. +unstable-pac = [] + +## Enable the timer for use with `embassy-time` with a 1MHz tick rate. +time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000_000", "dep:embassy-time-queue-driver"] + +## Enable ROM function cache. This will store the address of a ROM function when first used, improving performance of subsequent calls. +rom-func-cache = [] +## Enable implementations of some compiler intrinsics using functions in the rp2040 Mask ROM. +## These should be as fast or faster than the implementations in compiler-builtins. They also save code space and reduce memory contention. +## Compiler intrinsics are used automatically, you do not need to change your code to get performance improvements from this feature. +intrinsics = [] +## Enable intrinsics based on extra ROM functions added in the v2 version of the rp2040 Mask ROM. +## This version added a lot more floating point operations - many f64 functions and a few f32 functions were added in ROM v2. +rom-v2-intrinsics = [] + +## Allow using QSPI pins as GPIO pins. This is mostly not what you want (because your flash is attached via QSPI pins) +## and adds code and memory overhead when this feature is enabled. +qspi-as-gpio = [] + +## Indicate code is running from RAM. +## Set this if all code is in RAM, and the cores never access memory-mapped flash memory through XIP. +## This allows the flash driver to not force pausing execution on both cores when doing flash operations. +run-from-ram = [] + +#! ### boot2 flash chip support +#! RP2040's internal bootloader is only able to run code from the first 256 bytes of flash. +#! A 2nd stage bootloader (boot2) is required to run larger programs from external flash. +#! Select from existing boot2 implementations via the following features. If none are selected, +#! boot2-w25q080 will be used (w25q080 is the flash chip used on the pico). +#! Each implementation uses flash commands and timings specific to a QSPI flash chip family for better performance. +## Use boot2 with support for Renesas/Dialog AT25SF128a SPI flash. +boot2-at25sf128a = [] +## Use boot2 with support for Gigadevice GD25Q64C SPI flash. +boot2-gd25q64cs = [] +## Use boot2 that only uses generic flash commands - these are supported by all SPI flash, but are slower. +boot2-generic-03h = [] +## Use boot2 with support for ISSI IS25LP080 SPI flash. +boot2-is25lp080 = [] +## Use boot2 that copies the entire program to RAM before booting. This uses generic flash commands to perform the copy. +boot2-ram-memcpy = [] +## Use boot2 with support for Winbond W25Q080 SPI flash. +boot2-w25q080 = [] +## Use boot2 with support for Winbond W25X10CL SPI flash. +boot2-w25x10cl = [] +## Have embassy not provide the boot2 so you can use your own. +## Place your own in the ".boot2" section like: +## ``` +## #[link_section = ".boot2"] +## #[used] +## static BOOT2: [u8; 256] = [0; 256]; // Provide your own with e.g. include_bytes! +## ``` +boot2-none = [] + +## Configure the hal for use with the rp2040 +rp2040 = ["rp-pac/rp2040"] +_rp235x = ["rp-pac/rp235x"] +## Configure the hal for use with the rp235xA +rp235xa = ["_rp235x"] +## Configure the hal for use with the rp235xB +rp235xb = ["_rp235x"] + +# hack around cortex-m peripherals being wrong when running tests. +_test = [] + +## Add a binary-info header block containing picotool-compatible metadata. +## +## Takes up a little flash space, but picotool can then report the name of your +## program and other details. +binary-info = ["rt", "dep:rp-binary-info", "rp-binary-info?/binary-info"] + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } +embassy-time-queue-driver = { version = "0.1", path = "../embassy-time-queue-driver", optional = true } +embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } +embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" } +embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } +atomic-polyfill = "1.0.1" +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +nb = "1.1.0" +cfg-if = "1.0.0" +cortex-m-rt = ">=0.6.15,<0.8" +cortex-m = "0.7.6" +critical-section = "1.2.0" +chrono = { version = "0.4", default-features = false, optional = true } +embedded-io = { version = "0.6.1" } +embedded-io-async = { version = "0.6.1" } +embedded-storage = { version = "0.3" } +embedded-storage-async = { version = "0.4.1" } +rand_core = "0.6.4" +fixed = "1.28.0" + +rp-pac = { git = "https://github.com/embassy-rs/rp-pac.git", rev = "a7f42d25517f7124ad3b4ed492dec8b0f50a0e6c", feature = ["rt"] } + +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-nb = { version = "1.0" } + +pio-proc = {version= "0.2" } +pio = {version= "0.2.1" } +rp2040-boot2 = "0.3" +document-features = "0.2.10" +sha2-const-stable = "0.1" +rp-binary-info = { version = "0.1.0", optional = true } +smart-leds = "0.4.0" + +[dev-dependencies] +embassy-executor = { version = "0.6.3", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } +static_cell = { version = "2" } diff --git a/embassy/embassy-rp/LICENSE-APACHE b/embassy/embassy-rp/LICENSE-APACHE new file mode 100644 index 0000000..48be126 --- /dev/null +++ b/embassy/embassy-rp/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) Embassy project contributors +Portions copyright (c) rp-rs organization + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/embassy/embassy-rp/LICENSE-MIT b/embassy/embassy-rp/LICENSE-MIT new file mode 100644 index 0000000..f1cfbd5 --- /dev/null +++ b/embassy/embassy-rp/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) Embassy project contributors +Portions copyright (c) rp-rs organization + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/embassy/embassy-rp/README.md b/embassy/embassy-rp/README.md new file mode 100644 index 0000000..16b1893 --- /dev/null +++ b/embassy/embassy-rp/README.md @@ -0,0 +1,27 @@ +# Embassy RP HAL + +HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. + +The embassy-rp HAL targets the Raspberry Pi RP2040 microcontroller. The HAL implements both blocking and async APIs +for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to +complete operations in low power mode and handling interrupts, so that applications can focus on more important matters. + +* [embassy-rp on crates.io](https://crates.io/crates/embassy-rp) +* [Documentation](https://docs.embassy.dev/embassy-rp/) +* [Source](https://github.com/embassy-rs/embassy/tree/main/embassy-rp) +* [Examples](https://github.com/embassy-rs/embassy/tree/main/examples/rp/src/bin) + +## `embassy-time` time driver + +If the `time-driver` feature is enabled, the HAL uses the TIMER peripheral as a global time driver for [embassy-time](https://crates.io/crates/embassy-time), with a tick rate of 1MHz. + +## Embedded-hal + +The `embassy-rp` HAL implements the traits from [embedded-hal](https://crates.io/crates/embedded-hal) (v0.2 and 1.0) and [embedded-hal-async](https://crates.io/crates/embedded-hal-async), as well as [embedded-io](https://crates.io/crates/embedded-io) and [embedded-io-async](https://crates.io/crates/embedded-io-async). + +## Interoperability + +This crate can run on any executor. + +Optionally, some features requiring [`embassy-time`](https://crates.io/crates/embassy-time) can be activated with the `time-driver` feature. If you enable it, +you must link an `embassy-time` driver in your project. diff --git a/embassy/embassy-rp/build.rs b/embassy/embassy-rp/build.rs new file mode 100644 index 0000000..a8d3876 --- /dev/null +++ b/embassy/embassy-rp/build.rs @@ -0,0 +1,39 @@ +use std::env; +use std::ffi::OsStr; +use std::fs::File; +use std::io::Write; +use std::path::{Path, PathBuf}; + +fn main() { + if env::var("CARGO_FEATURE_RP2040").is_ok() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let link_x = include_bytes!("link-rp.x.in"); + let mut f = File::create(out.join("link-rp.x")).unwrap(); + f.write_all(link_x).unwrap(); + + println!("cargo:rustc-link-search={}", out.display()); + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=link-rp.x.in"); + } + + // code below taken from https://github.com/rust-embedded/cortex-m/blob/master/cortex-m-rt/build.rs + + let mut target = env::var("TARGET").unwrap(); + + // When using a custom target JSON, `$TARGET` contains the path to that JSON file. By + // convention, these files are named after the actual target triple, eg. + // `thumbv7m-customos-elf.json`, so we extract the file stem here to allow custom target specs. + let path = Path::new(&target); + if path.extension() == Some(OsStr::new("json")) { + target = path + .file_stem() + .map_or(target.clone(), |stem| stem.to_str().unwrap().to_string()); + } + + println!("cargo::rustc-check-cfg=cfg(has_fpu)"); + if target.ends_with("-eabihf") { + println!("cargo:rustc-cfg=has_fpu"); + } +} diff --git a/embassy/embassy-rp/funcsel.txt b/embassy/embassy-rp/funcsel.txt new file mode 100644 index 0000000..4dc8939 --- /dev/null +++ b/embassy/embassy-rp/funcsel.txt @@ -0,0 +1,30 @@ +0 SPI0 RX UART0 TX I2C0 SDA PWM0 A SIO PIO0 PIO1 USB OVCUR DET +1 SPI0 CSn UART0 RX I2C0 SCL PWM0 B SIO PIO0 PIO1 USB VBUS DET +2 SPI0 SCK UART0 CTS I2C1 SDA PWM1 A SIO PIO0 PIO1 USB VBUS EN +3 SPI0 TX UART0 RTS I2C1 SCL PWM1 B SIO PIO0 PIO1 USB OVCUR DET +4 SPI0 RX UART1 TX I2C0 SDA PWM2 A SIO PIO0 PIO1 USB VBUS DET +5 SPI0 CSn UART1 RX I2C0 SCL PWM2 B SIO PIO0 PIO1 USB VBUS EN +6 SPI0 SCK UART1 CTS I2C1 SDA PWM3 A SIO PIO0 PIO1 USB OVCUR DET +7 SPI0 TX UART1 RTS I2C1 SCL PWM3 B SIO PIO0 PIO1 USB VBUS DET +8 SPI1 RX UART1 TX I2C0 SDA PWM4 A SIO PIO0 PIO1 USB VBUS EN +9 SPI1 CSn UART1 RX I2C0 SCL PWM4 B SIO PIO0 PIO1 USB OVCUR DET +10 SPI1 SCK UART1 CTS I2C1 SDA PWM5 A SIO PIO0 PIO1 USB VBUS DET +11 SPI1 TX UART1 RTS I2C1 SCL PWM5 B SIO PIO0 PIO1 USB VBUS EN +12 SPI1 RX UART0 TX I2C0 SDA PWM6 A SIO PIO0 PIO1 USB OVCUR DET +13 SPI1 CSn UART0 RX I2C0 SCL PWM6 B SIO PIO0 PIO1 USB VBUS DET +14 SPI1 SCK UART0 CTS I2C1 SDA PWM7 A SIO PIO0 PIO1 USB VBUS EN +15 SPI1 TX UART0 RTS I2C1 SCL PWM7 B SIO PIO0 PIO1 USB OVCUR DET +16 SPI0 RX UART0 TX I2C0 SDA PWM0 A SIO PIO0 PIO1 USB VBUS DET +17 SPI0 CSn UART0 RX I2C0 SCL PWM0 B SIO PIO0 PIO1 USB VBUS EN +18 SPI0 SCK UART0 CTS I2C1 SDA PWM1 A SIO PIO0 PIO1 USB OVCUR DET +19 SPI0 TX UART0 RTS I2C1 SCL PWM1 B SIO PIO0 PIO1 USB VBUS DET +20 SPI0 RX UART1 TX I2C0 SDA PWM2 A SIO PIO0 PIO1 CLOCK GPIN0 USB VBUS EN +21 SPI0 CSn UART1 RX I2C0 SCL PWM2 B SIO PIO0 PIO1 CLOCK GPOUT0 USB OVCUR DET +22 SPI0 SCK UART1 CTS I2C1 SDA PWM3 A SIO PIO0 PIO1 CLOCK GPIN1 USB VBUS DET +23 SPI0 TX UART1 RTS I2C1 SCL PWM3 B SIO PIO0 PIO1 CLOCK GPOUT1 USB VBUS EN +24 SPI1 RX UART1 TX I2C0 SDA PWM4 A SIO PIO0 PIO1 CLOCK GPOUT2 USB OVCUR DET +25 SPI1 CSn UART1 RX I2C0 SCL PWM4 B SIO PIO0 PIO1 CLOCK GPOUT3 USB VBUS DET +26 SPI1 SCK UART1 CTS I2C1 SDA PWM5 A SIO PIO0 PIO1 USB VBUS EN +27 SPI1 TX UART1 RTS I2C1 SCL PWM5 B SIO PIO0 PIO1 USB OVCUR DET +28 SPI1 RX UART0 TX I2C0 SDA PWM6 A SIO PIO0 PIO1 USB VBUS DET +29 SPI1 CSn UART0 RX I2C0 SCL PWM6 B SIO PIO0 PIO1 USB VBUS EN \ No newline at end of file diff --git a/embassy/embassy-rp/link-rp.x.in b/embassy/embassy-rp/link-rp.x.in new file mode 100644 index 0000000..1839dda --- /dev/null +++ b/embassy/embassy-rp/link-rp.x.in @@ -0,0 +1,8 @@ + +SECTIONS { + /* ### Boot loader */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} diff --git a/embassy/embassy-rp/src/adc.rs b/embassy/embassy-rp/src/adc.rs new file mode 100644 index 0000000..9582e43 --- /dev/null +++ b/embassy/embassy-rp/src/adc.rs @@ -0,0 +1,459 @@ +//! ADC driver. +use core::future::poll_fn; +use core::marker::PhantomData; +use core::mem; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::gpio::{self, AnyPin, Pull, SealedPin as GpioPin}; +use crate::interrupt::typelevel::Binding; +use crate::interrupt::InterruptExt; +use crate::pac::dma::vals::TreqSel; +use crate::peripherals::{ADC, ADC_TEMP_SENSOR}; +use crate::{dma, interrupt, pac, peripherals, Peripheral, RegExt}; + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// ADC config. +#[non_exhaustive] +#[derive(Default)] +pub struct Config {} + +enum Source<'p> { + Pin(PeripheralRef<'p, AnyPin>), + TempSensor(PeripheralRef<'p, ADC_TEMP_SENSOR>), +} + +/// ADC channel. +pub struct Channel<'p>(Source<'p>); + +impl<'p> Channel<'p> { + /// Create a new ADC channel from pin with the provided [Pull] configuration. + pub fn new_pin(pin: impl Peripheral

+ 'p, pull: Pull) -> Self { + into_ref!(pin); + pin.pad_ctrl().modify(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + // manual says: + // + // > When using an ADC input shared with a GPIO pin, the pin’s + // > digital functions must be disabled by setting IE low and OD + // > high in the pin’s pad control register + w.set_ie(false); + w.set_od(true); + w.set_pue(pull == Pull::Up); + w.set_pde(pull == Pull::Down); + }); + Self(Source::Pin(pin.map_into())) + } + + /// Create a new ADC channel for the internal temperature sensor. + pub fn new_temp_sensor(s: impl Peripheral

+ 'p) -> Self { + let r = pac::ADC; + r.cs().write_set(|w| w.set_ts_en(true)); + Self(Source::TempSensor(s.into_ref())) + } + + fn channel(&self) -> u8 { + match &self.0 { + // this requires adc pins to be sequential and matching the adc channels, + // which is the case for rp2040 + Source::Pin(p) => p._pin() - 26, + Source::TempSensor(_) => 4, + } + } +} + +impl<'p> Drop for Source<'p> { + fn drop(&mut self) { + match self { + Source::Pin(p) => { + p.pad_ctrl().modify(|w| { + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(true); + }); + } + Source::TempSensor(_) => { + pac::ADC.cs().write_clear(|w| w.set_ts_en(true)); + } + } + } +} + +/// ADC sample. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(transparent)] +pub struct Sample(u16); + +impl Sample { + /// Sample is valid. + pub fn good(&self) -> bool { + self.0 < 0x8000 + } + + /// Sample value. + pub fn value(&self) -> u16 { + self.0 & !0x8000 + } +} + +/// ADC error. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Error converting value. + ConversionFailed, +} + +/// ADC mode. +pub trait Mode {} + +/// ADC async mode. +pub struct Async; +impl Mode for Async {} + +/// ADC blocking mode. +pub struct Blocking; +impl Mode for Blocking {} + +/// ADC driver. +pub struct Adc<'d, M: Mode> { + phantom: PhantomData<(&'d ADC, M)>, +} + +impl<'d, M: Mode> Drop for Adc<'d, M> { + fn drop(&mut self) { + let r = Self::regs(); + // disable ADC. leaving it enabled comes with a ~150µA static + // current draw. the temperature sensor has already been disabled + // by the temperature-reading methods, so we don't need to touch that. + r.cs().write(|w| w.set_en(false)); + } +} + +impl<'d, M: Mode> Adc<'d, M> { + #[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 + } + + fn setup() { + 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() {} + } + + /// Sample a value from a channel in blocking mode. + pub fn blocking_read(&mut self, ch: &mut Channel) -> Result { + let r = Self::regs(); + r.cs().modify(|w| { + w.set_ainsel(ch.channel()); + w.set_start_once(true); + w.set_err(true); + }); + while !r.cs().read().ready() {} + match r.cs().read().err() { + true => Err(Error::ConversionFailed), + false => Ok(r.result().read().result()), + } + } +} + +impl<'d> Adc<'d, Async> { + /// Create ADC driver in async mode. + pub fn new( + _inner: impl Peripheral

+ 'd, + _irq: impl Binding, + _config: Config, + ) -> Self { + Self::setup(); + + // Setup IRQ + interrupt::ADC_IRQ_FIFO.unpend(); + unsafe { interrupt::ADC_IRQ_FIFO.enable() }; + + Self { phantom: PhantomData } + } + + async fn wait_for_ready() { + let r = Self::regs(); + 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; + } + + /// Sample a value from a channel until completed. + pub async fn read(&mut self, ch: &mut Channel<'_>) -> Result { + let r = Self::regs(); + r.cs().modify(|w| { + w.set_ainsel(ch.channel()); + w.set_start_once(true); + w.set_err(true); + }); + Self::wait_for_ready().await; + match r.cs().read().err() { + true => Err(Error::ConversionFailed), + false => Ok(r.result().read().result()), + } + } + + // Note for refactoring: we don't require the actual Channels here, just the channel numbers. + // The public api is responsible for asserting ownership of the actual Channels. + async fn read_many_inner( + &mut self, + channels: impl Iterator, + buf: &mut [W], + fcs_err: bool, + div: u16, + dma: impl Peripheral

, + ) -> Result<(), Error> { + #[cfg(feature = "rp2040")] + let mut rrobin = 0_u8; + #[cfg(feature = "_rp235x")] + let mut rrobin = 0_u16; + for c in channels { + rrobin |= 1 << c; + } + let first_ch = rrobin.trailing_zeros() as u8; + if rrobin.count_ones() == 1 { + rrobin = 0; + } + + let r = Self::regs(); + // clear previous errors and set channel + r.cs().modify(|w| { + w.set_ainsel(first_ch); + w.set_rrobin(rrobin); + w.set_err_sticky(true); // clear previous errors + w.set_start_many(false); + }); + // wait for previous conversions and drain fifo. an earlier batch read may have + // been cancelled, leaving the adc running. + while !r.cs().read().ready() {} + while !r.fcs().read().empty() { + r.fifo().read(); + } + + // set up fifo for dma + r.fcs().write(|w| { + w.set_thresh(1); + w.set_dreq_en(true); + w.set_shift(mem::size_of::() == 1); + w.set_en(true); + w.set_err(fcs_err); + }); + + // reset dma config on drop, regardless of whether it was a future being cancelled + // or the method returning normally. + struct ResetDmaConfig; + impl Drop for ResetDmaConfig { + fn drop(&mut self) { + pac::ADC.cs().write_clear(|w| w.set_start_many(true)); + while !pac::ADC.cs().read().ready() {} + pac::ADC.fcs().write_clear(|w| { + w.set_dreq_en(true); + w.set_shift(true); + w.set_en(true); + }); + } + } + let auto_reset = ResetDmaConfig; + + let dma = unsafe { dma::read(dma, r.fifo().as_ptr() as *const W, buf as *mut [W], TreqSel::ADC) }; + // start conversions and wait for dma to finish. we can't report errors early + // because there's no interrupt to signal them, and inspecting every element + // of the fifo is too costly to do here. + r.div().write_set(|w| w.set_int(div)); + r.cs().write_set(|w| w.set_start_many(true)); + dma.await; + mem::drop(auto_reset); + // we can't report errors before the conversions have ended since no interrupt + // exists to report them early, and since they're exceedingly rare we probably don't + // want to anyway. + match r.cs().read().err_sticky() { + false => Ok(()), + true => Err(Error::ConversionFailed), + } + } + + /// Sample multiple values from multiple channels using DMA. + /// Samples are stored in an interleaved fashion inside the buffer. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate * num_channels - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 + #[inline] + pub async fn read_many_multichannel( + &mut self, + ch: &mut [Channel<'_>], + buf: &mut [S], + div: u16, + dma: impl Peripheral

, + ) -> Result<(), Error> { + self.read_many_inner(ch.iter().map(|c| c.channel()), buf, false, div, dma) + .await + } + + /// Sample multiple values from multiple channels using DMA, with errors inlined in samples. + /// Samples are stored in an interleaved fashion inside the buffer. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate * num_channels - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 + #[inline] + pub async fn read_many_multichannel_raw( + &mut self, + ch: &mut [Channel<'_>], + buf: &mut [Sample], + div: u16, + dma: impl Peripheral

, + ) { + // errors are reported in individual samples + let _ = self + .read_many_inner( + ch.iter().map(|c| c.channel()), + unsafe { mem::transmute::<_, &mut [u16]>(buf) }, + true, + div, + dma, + ) + .await; + } + + /// Sample multiple values from a channel using DMA. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 + #[inline] + pub async fn read_many( + &mut self, + ch: &mut Channel<'_>, + buf: &mut [S], + div: u16, + dma: impl Peripheral

, + ) -> Result<(), Error> { + self.read_many_inner([ch.channel()].into_iter(), buf, false, div, dma) + .await + } + + /// Sample multiple values from a channel using DMA, with errors inlined in samples. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 + #[inline] + pub async fn read_many_raw( + &mut self, + ch: &mut Channel<'_>, + buf: &mut [Sample], + div: u16, + dma: impl Peripheral

, + ) { + // errors are reported in individual samples + let _ = self + .read_many_inner( + [ch.channel()].into_iter(), + unsafe { mem::transmute::<_, &mut [u16]>(buf) }, + true, + div, + dma, + ) + .await; + } +} + +impl<'d> Adc<'d, Blocking> { + /// Create ADC driver in blocking mode. + pub fn new_blocking(_inner: impl Peripheral

+ 'd, _config: Config) -> Self { + Self::setup(); + + Self { phantom: PhantomData } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _empty: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = Adc::::regs(); + r.inte().write(|w| w.set_fifo(false)); + WAKER.wake(); + } +} + +trait SealedAdcSample: crate::dma::Word {} +trait SealedAdcChannel {} + +/// ADC sample. +#[allow(private_bounds)] +pub trait AdcSample: SealedAdcSample {} + +impl SealedAdcSample for u16 {} +impl AdcSample for u16 {} + +impl SealedAdcSample for u8 {} +impl AdcSample for u8 {} + +/// ADC channel. +#[allow(private_bounds)] +pub trait AdcChannel: SealedAdcChannel {} +/// ADC pin. +pub trait AdcPin: AdcChannel + gpio::Pin {} + +macro_rules! impl_pin { + ($pin:ident, $channel:expr) => { + impl SealedAdcChannel for peripherals::$pin {} + impl AdcChannel for peripherals::$pin {} + impl AdcPin for peripherals::$pin {} + }; +} + +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] +impl_pin!(PIN_26, 0); +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] +impl_pin!(PIN_27, 1); +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] +impl_pin!(PIN_28, 2); +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] +impl_pin!(PIN_29, 3); + +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, 0); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, 1); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, 2); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, 3); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, 4); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, 5); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, 6); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, 7); + +impl SealedAdcChannel for peripherals::ADC_TEMP_SENSOR {} +impl AdcChannel for peripherals::ADC_TEMP_SENSOR {} diff --git a/embassy/embassy-rp/src/block.rs b/embassy/embassy-rp/src/block.rs new file mode 100644 index 0000000..a3e1ad9 --- /dev/null +++ b/embassy/embassy-rp/src/block.rs @@ -0,0 +1,1079 @@ +//! Support for the RP235x Boot ROM's "Block" structures +//! +//! Blocks contain pointers, to form Block Loops. +//! +//! The `IMAGE_DEF` Block (here the `ImageDef` type) tells the ROM how to boot a +//! firmware image. The `PARTITION_TABLE` Block (here the `PartitionTable` type) +//! tells the ROM how to divide the flash space up into partitions. + +// Credit: Taken from https://github.com/thejpster/rp-hal-rp2350-public (also licensed Apache 2.0 + MIT). +// Copyright (c) rp-rs organization + +// These all have a 1 byte size + +/// An item ID for encoding a Vector Table address +pub const ITEM_1BS_VECTOR_TABLE: u8 = 0x03; + +/// An item ID for encoding a Rolling Window Delta +pub const ITEM_1BS_ROLLING_WINDOW_DELTA: u8 = 0x05; + +/// An item ID for encoding a Signature +pub const ITEM_1BS_SIGNATURE: u8 = 0x09; + +/// An item ID for encoding a Salt +pub const ITEM_1BS_SALT: u8 = 0x0c; + +/// An item ID for encoding an Image Type +pub const ITEM_1BS_IMAGE_TYPE: u8 = 0x42; + +/// An item ID for encoding the image's Entry Point +pub const ITEM_1BS_ENTRY_POINT: u8 = 0x44; + +/// An item ID for encoding the definition of a Hash +pub const ITEM_2BS_HASH_DEF: u8 = 0x47; + +/// An item ID for encoding a Version +pub const ITEM_1BS_VERSION: u8 = 0x48; + +/// An item ID for encoding a Hash +pub const ITEM_1BS_HASH_VALUE: u8 = 0x4b; + +// These all have a 2-byte size + +/// An item ID for encoding a Load Map +pub const ITEM_2BS_LOAD_MAP: u8 = 0x06; + +/// An item ID for encoding a Partition Table +pub const ITEM_2BS_PARTITION_TABLE: u8 = 0x0a; + +/// An item ID for encoding a placeholder entry that is ignored +/// +/// Allows a Block to not be empty. +pub const ITEM_2BS_IGNORED: u8 = 0xfe; + +/// An item ID for encoding the special last item in a Block +/// +/// It records how long the Block is. +pub const ITEM_2BS_LAST: u8 = 0xff; + +// Options for ITEM_1BS_IMAGE_TYPE + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as invalid +pub const IMAGE_TYPE_INVALID: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as an executable +pub const IMAGE_TYPE_EXE: u16 = 0x0001; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as data +pub const IMAGE_TYPE_DATA: u16 = 0x0002; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as unspecified +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as Non Secure +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_NS: u16 = 0x0010; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as Non Secure +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_S: u16 = 0x0020; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU type as Arm +pub const IMAGE_TYPE_EXE_CPU_ARM: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU type as RISC-V +pub const IMAGE_TYPE_EXE_CPU_RISCV: u16 = 0x0100; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU as an RP2040 +pub const IMAGE_TYPE_EXE_CHIP_RP2040: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU as an RP2350 +pub const IMAGE_TYPE_EXE_CHIP_RP2350: u16 = 0x1000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the image as Try Before You Buy. +/// +/// This means the image must be marked as 'Bought' with the ROM before the +/// watchdog times out the trial period, otherwise it is erased and the previous +/// image will be booted. +pub const IMAGE_TYPE_TBYB: u16 = 0x8000; + +/// This is the magic Block Start value. +/// +/// The Pico-SDK calls it `PICOBIN_BLOCK_MARKER_START` +const BLOCK_MARKER_START: u32 = 0xffffded3; + +/// This is the magic Block END value. +/// +/// The Pico-SDK calls it `PICOBIN_BLOCK_MARKER_END` +const BLOCK_MARKER_END: u32 = 0xab123579; + +/// An Image Definition has one item in it - an [`ITEM_1BS_IMAGE_TYPE`] +pub type ImageDef = Block<1>; + +/// A Block as understood by the Boot ROM. +/// +/// This could be an Image Definition, or a Partition Table, or maybe some other +/// kind of block. +/// +/// It contains within the special start and end markers the Boot ROM is looking +/// for. +#[derive(Debug)] +#[repr(C)] +pub struct Block { + marker_start: u32, + items: [u32; N], + length: u32, + offset: *const u32, + marker_end: u32, +} + +unsafe impl Sync for Block {} + +impl Block { + /// Construct a new Binary Block, with the given items. + /// + /// The length, and the Start and End markers are added automatically. The + /// Block Loop pointer initially points to itself. + pub const fn new(items: [u32; N]) -> Block { + Block { + marker_start: BLOCK_MARKER_START, + items, + length: item_last(N as u16), + // offset from this block to next block in loop. By default + // we form a Block Loop with a single Block in it. + offset: core::ptr::null(), + marker_end: BLOCK_MARKER_END, + } + } + + /// Change the Block Loop offset value. + /// + /// This method isn't that useful because you can't evaluate the difference + /// between two pointers in a const context as the addresses aren't assigned + /// until long after the const evaluator has run. + /// + /// If you think you need this method, you might want to set a unique random + /// value here and swap it for the real offset as a post-processing step. + pub const fn with_offset(self, offset: *const u32) -> Block { + Block { offset, ..self } + } +} + +impl Block<0> { + /// Construct an empty block. + pub const fn empty() -> Block<0> { + Block::new([]) + } + + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<1> { + Block::new([word]) + } +} + +impl Block<1> { + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<2> { + Block::new([self.items[0], word]) + } +} + +impl Block<2> { + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<3> { + Block::new([self.items[0], self.items[1], word]) + } +} + +impl ImageDef { + /// Construct a new IMAGE_DEF Block, for an EXE with the given security and + /// architecture. + pub const fn arch_exe(security: Security, architecture: Architecture) -> Self { + Self::new([item_image_type_exe(security, architecture)]) + } + + /// Construct a new IMAGE_DEF Block, for an EXE with the given security. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn exe(security: Security) -> Self { + if cfg!(all(target_arch = "riscv32", target_os = "none")) { + Self::arch_exe(security, Architecture::Riscv) + } else { + Self::arch_exe(security, Architecture::Arm) + } + } + + /// Construct a new IMAGE_DEF Block, for a Non-Secure EXE. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn non_secure_exe() -> Self { + Self::exe(Security::NonSecure) + } + + /// Construct a new IMAGE_DEF Block, for a Secure EXE. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn secure_exe() -> Self { + Self::exe(Security::Secure) + } +} + +/// We make our partition table this fixed size. +pub const PARTITION_TABLE_MAX_ITEMS: usize = 128; + +/// Describes a unpartitioned space +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct UnpartitionedSpace { + permissions_and_location: u32, + permissions_and_flags: u32, +} + +impl UnpartitionedSpace { + /// Create a new unpartitioned space. + /// + /// It defaults to no permissions. + pub const fn new() -> Self { + Self { + permissions_and_location: 0, + permissions_and_flags: 0, + } + } + + /// Create a new unpartition space from run-time values. + /// + /// Get these from the ROM function `get_partition_table_info` with an argument of `PT_INFO`. + pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self { + Self { + permissions_and_location, + permissions_and_flags, + } + } + + /// Add a permission + pub const fn with_permission(self, permission: Permission) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | permission as u32, + permissions_and_location: self.permissions_and_location | permission as u32, + } + } + + /// Set a flag + pub const fn with_flag(self, flag: UnpartitionedFlag) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | flag as u32, + ..self + } + } + + /// Get the partition start and end + /// + /// The offsets are in 4 KiB sectors, inclusive. + pub fn get_first_last_sectors(&self) -> (u16, u16) { + ( + (self.permissions_and_location & 0x0000_1FFF) as u16, + ((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16, + ) + } + + /// Get the partition start and end + /// + /// The offsets are in bytes, inclusive. + pub fn get_first_last_bytes(&self) -> (u32, u32) { + let (first, last) = self.get_first_last_sectors(); + (u32::from(first) * 4096, (u32::from(last) * 4096) + 4095) + } + + /// Check if it has a permission + pub fn has_permission(&self, permission: Permission) -> bool { + let mask = permission as u32; + (self.permissions_and_flags & mask) != 0 + } + + /// Check if the partition has a flag set + pub fn has_flag(&self, flag: UnpartitionedFlag) -> bool { + let mask = flag as u32; + (self.permissions_and_flags & mask) != 0 + } +} + +impl core::fmt::Display for UnpartitionedSpace { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let (first, last) = self.get_first_last_bytes(); + write!( + f, + "{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}", + first, + last, + if self.has_permission(Permission::SecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::SecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::BootRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::BootWrite) { + 'W' + } else { + '_' + } + ) + } +} + +/// Describes a Partition +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Partition { + permissions_and_location: u32, + permissions_and_flags: u32, + id: Option, + extra_families: [u32; 4], + extra_families_len: usize, + name: [u8; 128], +} + +impl Partition { + const FLAGS_HAS_ID: u32 = 0b1; + const FLAGS_LINK_TYPE_A_PARTITION: u32 = 0b01 << 1; + const FLAGS_LINK_TYPE_OWNER: u32 = 0b10 << 1; + const FLAGS_LINK_MASK: u32 = 0b111111 << 1; + const FLAGS_HAS_NAME: u32 = 0b1 << 12; + const FLAGS_HAS_EXTRA_FAMILIES_SHIFT: u8 = 7; + const FLAGS_HAS_EXTRA_FAMILIES_MASK: u32 = 0b11 << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT; + + /// Create a new partition, with the given start and end sectors. + /// + /// It defaults to no permissions. + pub const fn new(first_sector: u16, last_sector: u16) -> Self { + // 0x2000 sectors of 4 KiB is 32 MiB, which is the total XIP area + core::assert!(first_sector < 0x2000); + core::assert!(last_sector < 0x2000); + core::assert!(first_sector <= last_sector); + Self { + permissions_and_location: (last_sector as u32) << 13 | first_sector as u32, + permissions_and_flags: 0, + id: None, + extra_families: [0; 4], + extra_families_len: 0, + name: [0; 128], + } + } + + /// Create a new partition from run-time values. + /// + /// Get these from the ROM function `get_partition_table_info` with an argument of `PARTITION_LOCATION_AND_FLAGS`. + pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self { + Self { + permissions_and_location, + permissions_and_flags, + id: None, + extra_families: [0; 4], + extra_families_len: 0, + name: [0; 128], + } + } + + /// Add a permission + pub const fn with_permission(self, permission: Permission) -> Self { + Self { + permissions_and_location: self.permissions_and_location | permission as u32, + permissions_and_flags: self.permissions_and_flags | permission as u32, + ..self + } + } + + /// Set the name of the partition + pub const fn with_name(self, name: &str) -> Self { + let mut new_name = [0u8; 128]; + let name = name.as_bytes(); + let mut idx = 0; + new_name[0] = name.len() as u8; + while idx < name.len() { + new_name[idx + 1] = name[idx]; + idx += 1; + } + Self { + name: new_name, + permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_NAME, + ..self + } + } + + /// Set the extra families for the partition. + /// + /// You can supply up to four. + pub const fn with_extra_families(self, extra_families: &[u32]) -> Self { + core::assert!(extra_families.len() <= 4); + let mut new_extra_families = [0u32; 4]; + let mut idx = 0; + while idx < extra_families.len() { + new_extra_families[idx] = extra_families[idx]; + idx += 1; + } + Self { + extra_families: new_extra_families, + extra_families_len: extra_families.len(), + permissions_and_flags: (self.permissions_and_flags & !Self::FLAGS_HAS_EXTRA_FAMILIES_MASK) + | (extra_families.len() as u32) << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT, + ..self + } + } + + /// Set the ID + pub const fn with_id(self, id: u64) -> Self { + Self { + id: Some(id), + permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_ID, + ..self + } + } + + /// Add a link + pub const fn with_link(self, link: Link) -> Self { + let mut new_flags = self.permissions_and_flags & !Self::FLAGS_LINK_MASK; + match link { + Link::Nothing => {} + Link::ToA { partition_idx } => { + core::assert!(partition_idx < 16); + new_flags |= Self::FLAGS_LINK_TYPE_A_PARTITION; + new_flags |= (partition_idx as u32) << 3; + } + Link::ToOwner { partition_idx } => { + core::assert!(partition_idx < 16); + new_flags |= Self::FLAGS_LINK_TYPE_OWNER; + new_flags |= (partition_idx as u32) << 3; + } + } + Self { + permissions_and_flags: new_flags, + ..self + } + } + + /// Set a flag + pub const fn with_flag(self, flag: PartitionFlag) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | flag as u32, + ..self + } + } + + /// Get the partition start and end + /// + /// The offsets are in 4 KiB sectors, inclusive. + pub fn get_first_last_sectors(&self) -> (u16, u16) { + ( + (self.permissions_and_location & 0x0000_1FFF) as u16, + ((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16, + ) + } + + /// Get the partition start and end + /// + /// The offsets are in bytes, inclusive. + pub fn get_first_last_bytes(&self) -> (u32, u32) { + let (first, last) = self.get_first_last_sectors(); + (u32::from(first) * 4096, (u32::from(last) * 4096) + 4095) + } + + /// Check if it has a permission + pub fn has_permission(&self, permission: Permission) -> bool { + let mask = permission as u32; + (self.permissions_and_flags & mask) != 0 + } + + /// Get which extra families are allowed in this partition + pub fn get_extra_families(&self) -> &[u32] { + &self.extra_families[0..self.extra_families_len] + } + + /// Get the name of the partition + /// + /// Returns `None` if there's no name, or the name is not valid UTF-8. + pub fn get_name(&self) -> Option<&str> { + let len = self.name[0] as usize; + if len == 0 { + None + } else { + core::str::from_utf8(&self.name[1..=len]).ok() + } + } + + /// Get the ID + pub fn get_id(&self) -> Option { + self.id + } + + /// Check if this partition is linked + pub fn get_link(&self) -> Link { + if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_A_PARTITION) != 0 { + let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8; + Link::ToA { partition_idx } + } else if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_OWNER) != 0 { + let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8; + Link::ToOwner { partition_idx } + } else { + Link::Nothing + } + } + + /// Check if the partition has a flag set + pub fn has_flag(&self, flag: PartitionFlag) -> bool { + let mask = flag as u32; + (self.permissions_and_flags & mask) != 0 + } +} + +impl core::fmt::Display for Partition { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let (first, last) = self.get_first_last_bytes(); + write!( + f, + "{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}", + first, + last, + if self.has_permission(Permission::SecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::SecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::BootRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::BootWrite) { + 'W' + } else { + '_' + } + ) + } +} + +/// Describes a partition table. +/// +/// Don't store this as a static - make sure you convert it to a block. +#[derive(Clone)] +pub struct PartitionTableBlock { + /// This must look like a block, including the 1 word header and the 3 word footer. + contents: [u32; PARTITION_TABLE_MAX_ITEMS], + /// This value doesn't include the 1 word header or the 3 word footer + num_items: usize, +} + +impl PartitionTableBlock { + /// Create an empty Block, big enough for a partition table. + /// + /// At a minimum you need to call [`Self::add_partition_item`]. + pub const fn new() -> PartitionTableBlock { + let mut contents = [0; PARTITION_TABLE_MAX_ITEMS]; + contents[0] = BLOCK_MARKER_START; + contents[1] = item_last(0); + contents[2] = 0; + contents[3] = BLOCK_MARKER_END; + PartitionTableBlock { contents, num_items: 0 } + } + + /// Add a partition to the partition table + pub const fn add_partition_item(self, unpartitioned: UnpartitionedSpace, partitions: &[Partition]) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. add item header space (we fill this in later) + let header_idx = idx; + new_table.contents[idx] = 0; + idx += 1; + + // 2. unpartitioned space flags + // + // (the location of unpartition space is not recorded here - it is + // inferred because the unpartitioned space is where the partitions are + // not) + new_table.contents[idx] = unpartitioned.permissions_and_flags; + idx += 1; + + // 3. partition info + + let mut partition_no = 0; + while partition_no < partitions.len() { + // a. permissions_and_location (4K units) + new_table.contents[idx] = partitions[partition_no].permissions_and_location; + idx += 1; + + // b. permissions_and_flags + new_table.contents[idx] = partitions[partition_no].permissions_and_flags; + idx += 1; + + // c. ID + if let Some(id) = partitions[partition_no].id { + new_table.contents[idx] = id as u32; + new_table.contents[idx + 1] = (id >> 32) as u32; + idx += 2; + } + + // d. Extra Families + let mut extra_families_idx = 0; + while extra_families_idx < partitions[partition_no].extra_families_len { + new_table.contents[idx] = partitions[partition_no].extra_families[extra_families_idx]; + idx += 1; + extra_families_idx += 1; + } + + // e. Name + let mut name_idx = 0; + while name_idx < partitions[partition_no].name[0] as usize { + let name_chunk = [ + partitions[partition_no].name[name_idx], + partitions[partition_no].name[name_idx + 1], + partitions[partition_no].name[name_idx + 2], + partitions[partition_no].name[name_idx + 3], + ]; + new_table.contents[idx] = u32::from_le_bytes(name_chunk); + name_idx += 4; + idx += 1; + } + + partition_no += 1; + } + + let len = idx - header_idx; + new_table.contents[header_idx] = item_generic_2bs(partitions.len() as u8, len as u16, ITEM_2BS_PARTITION_TABLE); + + // 7. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } + + /// Add a version number to the partition table + pub const fn with_version(self, major: u16, minor: u16) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. add item + new_table.contents[idx] = item_generic_2bs(0, 2, ITEM_1BS_VERSION); + idx += 1; + new_table.contents[idx] = (major as u32) << 16 | minor as u32; + idx += 1; + + // 2. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } + + /// Add a a SHA256 hash of the Block + /// + /// Adds a `HASH_DEF` covering all the previous items in the Block, and a + /// `HASH_VALUE` with a SHA-256 hash of them. + pub const fn with_sha256(self) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. HASH_DEF says what is hashed + new_table.contents[idx] = item_generic_2bs(1, 2, ITEM_2BS_HASH_DEF); + idx += 1; + // we're hashing all the previous contents - including this line. + new_table.contents[idx] = (idx + 1) as u32; + idx += 1; + + // calculate hash over prior contents + let input = unsafe { core::slice::from_raw_parts(new_table.contents.as_ptr() as *const u8, idx * 4) }; + let hash: [u8; 32] = sha2_const_stable::Sha256::new().update(input).finalize(); + + // 2. HASH_VALUE contains the hash + new_table.contents[idx] = item_generic_2bs(0, 9, ITEM_1BS_HASH_VALUE); + idx += 1; + + let mut hash_idx = 0; + while hash_idx < hash.len() { + new_table.contents[idx] = u32::from_le_bytes([ + hash[hash_idx], + hash[hash_idx + 1], + hash[hash_idx + 2], + hash[hash_idx + 3], + ]); + idx += 1; + hash_idx += 4; + } + + // 3. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } +} + +impl Default for PartitionTableBlock { + fn default() -> Self { + Self::new() + } +} + +/// Flags that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum PartitionFlag { + NotBootableArm = 1 << 9, + NotBootableRiscv = 1 << 10, + Uf2DownloadAbNonBootableOwnerAffinity = 1 << 11, + Uf2DownloadNoReboot = 1 << 13, + AcceptsDefaultFamilyRp2040 = 1 << 14, + AcceptsDefaultFamilyData = 1 << 16, + AcceptsDefaultFamilyRp2350ArmS = 1 << 17, + AcceptsDefaultFamilyRp2350Riscv = 1 << 18, + AcceptsDefaultFamilyRp2350ArmNs = 1 << 19, +} + +/// Flags that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum UnpartitionedFlag { + Uf2DownloadNoReboot = 1 << 13, + AcceptsDefaultFamilyRp2040 = 1 << 14, + AcceptsDefaultFamilyAbsolute = 1 << 15, + AcceptsDefaultFamilyData = 1 << 16, + AcceptsDefaultFamilyRp2350ArmS = 1 << 17, + AcceptsDefaultFamilyRp2350Riscv = 1 << 18, + AcceptsDefaultFamilyRp2350ArmNs = 1 << 19, +} + +/// Kinds of linked partition +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Link { + /// Not linked to anything + Nothing, + /// This is a B partition - link to our A partition. + ToA { + /// The index of our matching A partition. + partition_idx: u8, + }, + /// Link to the partition that owns this one. + ToOwner { + /// The idx of our owner + partition_idx: u8, + }, +} + +/// Permissions that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum Permission { + /// Can be read in Secure Mode + /// + /// Corresponds to `PERMISSION_S_R_BITS` in the Pico SDK + SecureRead = 1 << 26, + /// Can be written in Secure Mode + /// + /// Corresponds to `PERMISSION_S_W_BITS` in the Pico SDK + SecureWrite = 1 << 27, + /// Can be read in Non-Secure Mode + /// + /// Corresponds to `PERMISSION_NS_R_BITS` in the Pico SDK + NonSecureRead = 1 << 28, + /// Can be written in Non-Secure Mode + /// + /// Corresponds to `PERMISSION_NS_W_BITS` in the Pico SDK + NonSecureWrite = 1 << 29, + /// Can be read in Non-Secure Bootloader mode + /// + /// Corresponds to `PERMISSION_NSBOOT_R_BITS` in the Pico SDK + BootRead = 1 << 30, + /// Can be written in Non-Secure Bootloader mode + /// + /// Corresponds to `PERMISSION_NSBOOT_W_BITS` in the Pico SDK + BootWrite = 1 << 31, +} + +impl Permission { + /// Is this permission bit set this in this bitmask? + pub const fn is_in(self, mask: u32) -> bool { + (mask & (self as u32)) != 0 + } +} + +/// The supported RP2350 CPU architectures +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Architecture { + /// Core is in Arm Cortex-M33 mode + Arm, + /// Core is in RISC-V / Hazard3 mode + Riscv, +} + +/// The kinds of Secure Boot we support +#[derive(Debug, Copy, Clone)] +pub enum Security { + /// Security mode not given + Unspecified, + /// Start in Non-Secure mode + NonSecure, + /// Start in Secure mode + Secure, +} + +/// Make an item containing a tag, 1 byte length and two extra bytes. +/// +/// The `command` arg should contain `1BS` +pub const fn item_generic_1bs(value: u16, length: u8, command: u8) -> u32 { + ((value as u32) << 16) | ((length as u32) << 8) | (command as u32) +} + +/// Make an item containing a tag, 2 byte length and one extra byte. +/// +/// The `command` arg should contain `2BS` +pub const fn item_generic_2bs(value: u8, length: u16, command: u8) -> u32 { + ((value as u32) << 24) | ((length as u32) << 8) | (command as u32) +} + +/// Create Image Type item, of type IGNORED. +pub const fn item_ignored() -> u32 { + item_generic_2bs(0, 1, ITEM_2BS_IGNORED) +} + +/// Create Image Type item, of type INVALID. +pub const fn item_image_type_invalid() -> u32 { + let value = IMAGE_TYPE_INVALID; + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create Image Type item, of type DATA. +pub const fn item_image_type_data() -> u32 { + let value = IMAGE_TYPE_DATA; + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create Image Type item, of type EXE. +pub const fn item_image_type_exe(security: Security, arch: Architecture) -> u32 { + let mut value = IMAGE_TYPE_EXE | IMAGE_TYPE_EXE_CHIP_RP2350; + + match arch { + Architecture::Arm => { + value |= IMAGE_TYPE_EXE_CPU_ARM; + } + Architecture::Riscv => { + value |= IMAGE_TYPE_EXE_CPU_RISCV; + } + } + + match security { + Security::Unspecified => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED, + Security::NonSecure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_NS, + Security::Secure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_S, + } + + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create a Block Last item. +pub const fn item_last(length: u16) -> u32 { + item_generic_2bs(0, length, ITEM_2BS_LAST) +} + +/// Create a Vector Table item. +/// +/// This is only allowed on Arm systems. +pub const fn item_vector_table(table_ptr: u32) -> [u32; 2] { + [item_generic_1bs(0, 2, ITEM_1BS_VECTOR_TABLE), table_ptr] +} + +/// Create an Entry Point item. +pub const fn item_entry_point(entry_point: u32, initial_sp: u32) -> [u32; 3] { + [item_generic_1bs(0, 3, ITEM_1BS_ENTRY_POINT), entry_point, initial_sp] +} + +/// Create an Rolling Window item. +/// +/// The delta is the number of bytes into the image that 0x10000000 should +/// be mapped. +pub const fn item_rolling_window(delta: u32) -> [u32; 2] { + [item_generic_1bs(0, 3, ITEM_1BS_ROLLING_WINDOW_DELTA), delta] +} + +#[cfg(test)] +mod test { + use super::*; + + /// I used this JSON, with `picotool partition create`: + /// + /// ```json + /// { + /// "version": [1, 0], + /// "unpartitioned": { + /// "families": ["absolute"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// } + /// }, + /// "partitions": [ + /// { + /// "name": "A", + /// "id": 0, + /// "size": "2044K", + /// "families": ["rp2350-arm-s", "rp2350-riscv"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// } + /// }, + /// { + /// "name": "B", + /// "id": 1, + /// "size": "2044K", + /// "families": ["rp2350-arm-s", "rp2350-riscv"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// }, + /// "link": ["a", 0] + /// } + /// ] + /// } + /// ``` + #[test] + fn make_hashed_partition_table() { + let table = PartitionTableBlock::new() + .add_partition_item( + UnpartitionedSpace::new() + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_flag(UnpartitionedFlag::AcceptsDefaultFamilyAbsolute), + &[ + Partition::new(2, 512) + .with_id(0) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv) + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_name("A"), + Partition::new(513, 1023) + .with_id(1) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv) + .with_link(Link::ToA { partition_idx: 0 }) + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_name("B"), + ], + ) + .with_version(1, 0) + .with_sha256(); + let expected = &[ + 0xffffded3, // start + 0x02000c0a, // Item = PARTITION_TABLE + 0xfc008000, // Unpartitioned Space - permissions_and_flags + 0xfc400002, // Partition 0 - permissions_and_location (512 * 4096, 2 * 4096) + 0xfc061001, // permissions_and_flags HAS_ID | HAS_NAME | ARM-S | RISC-V + 0x00000000, // ID + 0x00000000, // ID + 0x00004101, // Name ("A") + 0xfc7fe201, // Partition 1 - permissions_and_location (1023 * 4096, 513 * 4096) + 0xfc061003, // permissions_and_flags LINKA(0) | HAS_ID | HAS_NAME | ARM-S | RISC-V + 0x00000001, // ID + 0x00000000, // ID + 0x00004201, // Name ("B") + 0x00000248, // Item = Version + 0x00010000, // 0, 1 + 0x01000247, // HASH_DEF with 2 words, and SHA256 hash + 0x00000011, // 17 words hashed + 0x0000094b, // HASH_VALUE with 9 words + 0x1945cdad, // Hash word 0 + 0x6b5f9773, // Hash word 1 + 0xe2bf39bd, // Hash word 2 + 0xb243e599, // Hash word 3 + 0xab2f0e9a, // Hash word 4 + 0x4d5d6d0b, // Hash word 5 + 0xf973050f, // Hash word 6 + 0x5ab6dadb, // Hash word 7 + 0x000019ff, // Last Item + 0x00000000, // Block Loop Next Offset + 0xab123579, // End + ]; + core::assert_eq!( + &table.contents[..29], + expected, + "{:#010x?}\n != \n{:#010x?}", + &table.contents[0..29], + expected, + ); + } +} diff --git a/embassy/embassy-rp/src/bootsel.rs b/embassy/embassy-rp/src/bootsel.rs new file mode 100644 index 0000000..d24ce7b --- /dev/null +++ b/embassy/embassy-rp/src/bootsel.rs @@ -0,0 +1,83 @@ +//! Boot Select button +//! +//! The RP2040 rom supports a BOOTSEL button that is used to enter the USB bootloader +//! if held during reset. To avoid wasting GPIO pins, the button is multiplexed onto +//! the CS pin of the QSPI flash, but that makes it somewhat expensive and complicated +//! to utilize outside of the rom's bootloader. +//! +//! This module provides functionality to poll BOOTSEL from an embassy application. + +use crate::flash::in_ram; + +impl crate::peripherals::BOOTSEL { + /// Polls the BOOTSEL button. Returns true if the button is pressed. + /// + /// Polling isn't cheap, as this function waits for core 1 to finish it's current + /// task and for any DMAs from flash to complete + pub fn is_pressed(&mut self) -> bool { + let mut cs_status = Default::default(); + + unsafe { in_ram(|| cs_status = ram_helpers::read_cs_status()) }.expect("Must be called from Core 0"); + + // bootsel is active low, so invert + !cs_status.infrompad() + } +} + +mod ram_helpers { + use rp_pac::io::regs::GpioStatus; + + /// Temporally reconfigures the CS gpio and returns the GpioStatus. + + /// This function runs from RAM so it can disable flash XIP. + /// + /// # Safety + /// + /// The caller must ensure flash is idle and will remain idle. + /// This function must live in ram. It uses inline asm to avoid any + /// potential calls to ABI functions that might be in flash. + #[inline(never)] + #[link_section = ".data.ram_func"] + #[cfg(target_arch = "arm")] + pub unsafe fn read_cs_status() -> GpioStatus { + let result: u32; + + // Magic value, used as both OEOVER::DISABLE and delay loop counter + let magic = 0x2000; + + core::arch::asm!( + ".equiv GPIO_STATUS, 0x0", + ".equiv GPIO_CTRL, 0x4", + + "ldr {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]", + + // The BOOTSEL pulls the flash's CS line low though a 1K resistor. + // this is weak enough to avoid disrupting normal operation. + // But, if we disable CS's output drive and allow it to float... + "str {val}, [{cs_gpio}, $GPIO_CTRL]", + + // ...then wait for the state to settle... + "2:", // ~4000 cycle delay loop + "subs {val}, #8", + "bne 2b", + + // ...we can read the current state of bootsel + "ldr {val}, [{cs_gpio}, $GPIO_STATUS]", + + // Finally, restore CS to normal operation so XIP can continue + "str {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]", + + cs_gpio = in(reg) rp_pac::IO_QSPI.gpio(1).as_ptr(), + orig_ctrl = out(reg) _, + val = inout(reg) magic => result, + options(nostack), + ); + + core::mem::transmute(result) + } + + #[cfg(not(target_arch = "arm"))] + pub unsafe fn read_cs_status() -> GpioStatus { + unimplemented!() + } +} diff --git a/embassy/embassy-rp/src/clocks.rs b/embassy/embassy-rp/src/clocks.rs new file mode 100644 index 0000000..e82beb0 --- /dev/null +++ b/embassy/embassy-rp/src/clocks.rs @@ -0,0 +1,1191 @@ +//! Clock configuration for the RP2040 + +#[cfg(feature = "rp2040")] +use core::arch::asm; +use core::marker::PhantomData; +#[cfg(feature = "rp2040")] +use core::sync::atomic::AtomicU16; +use core::sync::atomic::{AtomicU32, Ordering}; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use pac::clocks::vals::*; + +use crate::gpio::{AnyPin, SealedPin}; +#[cfg(feature = "rp2040")] +use crate::pac::common::{Reg, RW}; +use crate::{pac, reset, Peripheral}; + +// NOTE: all gpin handling is commented out for future reference. +// gpin is not usually safe to use during the boot init() call, so it won't +// be very useful until we have runtime clock reconfiguration. once this +// happens we can resurrect the commented-out gpin bits. +struct Clocks { + xosc: AtomicU32, + sys: AtomicU32, + reference: AtomicU32, + pll_sys: AtomicU32, + pll_usb: AtomicU32, + usb: AtomicU32, + adc: AtomicU32, + // gpin0: AtomicU32, + // gpin1: AtomicU32, + rosc: AtomicU32, + peri: AtomicU32, + #[cfg(feature = "rp2040")] + rtc: AtomicU16, +} + +static CLOCKS: Clocks = Clocks { + xosc: AtomicU32::new(0), + sys: AtomicU32::new(0), + reference: AtomicU32::new(0), + pll_sys: AtomicU32::new(0), + pll_usb: AtomicU32::new(0), + usb: AtomicU32::new(0), + adc: AtomicU32::new(0), + // gpin0: AtomicU32::new(0), + // gpin1: AtomicU32::new(0), + rosc: AtomicU32::new(0), + peri: AtomicU32::new(0), + #[cfg(feature = "rp2040")] + rtc: AtomicU16::new(0), +}; + +/// Peripheral clock sources. +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PeriClkSrc { + /// SYS. + Sys = ClkPeriCtrlAuxsrc::CLK_SYS as _, + /// PLL SYS. + PllSys = ClkPeriCtrlAuxsrc::CLKSRC_PLL_SYS as _, + /// PLL USB. + PllUsb = ClkPeriCtrlAuxsrc::CLKSRC_PLL_USB as _, + /// ROSC. + Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH as _, + /// XOSC. + Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC as _, + // Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +/// CLock configuration. +#[non_exhaustive] +pub struct ClockConfig { + /// Ring oscillator configuration. + pub rosc: Option, + /// External oscillator configuration. + pub xosc: Option, + /// Reference clock configuration. + pub ref_clk: RefClkConfig, + /// System clock configuration. + pub sys_clk: SysClkConfig, + /// Peripheral clock source configuration. + pub peri_clk_src: Option, + /// USB clock configuration. + pub usb_clk: Option, + /// ADC clock configuration. + pub adc_clk: Option, + /// RTC clock configuration. + #[cfg(feature = "rp2040")] + pub rtc_clk: Option, + // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, + // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, +} + +impl ClockConfig { + /// Clock configuration derived from external crystal. + pub fn crystal(crystal_hz: u32) -> Self { + Self { + rosc: Some(RoscConfig { + hz: 6_500_000, + range: RoscRange::Medium, + drive_strength: [0; 8], + div: 16, + }), + xosc: Some(XoscConfig { + hz: crystal_hz, + sys_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 125, + #[cfg(feature = "rp2040")] + post_div1: 6, + #[cfg(feature = "_rp235x")] + post_div1: 5, + post_div2: 2, + }), + usb_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + }), + delay_multiplier: 128, + }), + ref_clk: RefClkConfig { + src: RefClkSrc::Xosc, + div: 1, + }, + sys_clk: SysClkConfig { + src: SysClkSrc::PllSys, + div_int: 1, + div_frac: 0, + }, + peri_clk_src: Some(PeriClkSrc::Sys), + // CLK USB = PLL USB (48MHz) / 1 = 48MHz + usb_clk: Some(UsbClkConfig { + src: UsbClkSrc::PllUsb, + div: 1, + phase: 0, + }), + // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz + adc_clk: Some(AdcClkConfig { + src: AdcClkSrc::PllUsb, + div: 1, + phase: 0, + }), + // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz + #[cfg(feature = "rp2040")] + rtc_clk: Some(RtcClkConfig { + src: RtcClkSrc::PllUsb, + div_int: 1024, + div_frac: 0, + phase: 0, + }), + // gpin0: None, + // gpin1: None, + } + } + + /// Clock configuration from internal oscillator. + pub fn rosc() -> Self { + Self { + rosc: Some(RoscConfig { + hz: 140_000_000, + range: RoscRange::High, + drive_strength: [0; 8], + div: 1, + }), + xosc: None, + ref_clk: RefClkConfig { + src: RefClkSrc::Rosc, + div: 1, + }, + sys_clk: SysClkConfig { + src: SysClkSrc::Rosc, + div_int: 1, + div_frac: 0, + }, + peri_clk_src: Some(PeriClkSrc::Rosc), + usb_clk: None, + // CLK ADC = ROSC (140MHz) / 3 ≅ 48MHz + adc_clk: Some(AdcClkConfig { + src: AdcClkSrc::Rosc, + div: 3, + phase: 0, + }), + // CLK RTC = ROSC (140MHz) / 2986.667969 ≅ 46875Hz + #[cfg(feature = "rp2040")] + rtc_clk: Some(RtcClkConfig { + src: RtcClkSrc::Rosc, + div_int: 2986, + div_frac: 171, + phase: 0, + }), + // gpin0: None, + // gpin1: None, + } + } + + // pub fn bind_gpin(&mut self, gpin: Gpin<'static, P>, hz: u32) { + // match P::NR { + // 0 => self.gpin0 = Some((hz, gpin.map_into())), + // 1 => self.gpin1 = Some((hz, gpin.map_into())), + // _ => unreachable!(), + // } + // // pin is now provisionally bound. if the config is applied it must be forgotten, + // // or Gpin::drop will deconfigure the clock input. + // } +} + +/// ROSC freq range. +#[repr(u16)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RoscRange { + /// Low range. + Low = pac::rosc::vals::FreqRange::LOW.0, + /// Medium range (1.33x low) + Medium = pac::rosc::vals::FreqRange::MEDIUM.0, + /// High range (2x low) + High = pac::rosc::vals::FreqRange::HIGH.0, + /// Too high. Should not be used. + TooHigh = pac::rosc::vals::FreqRange::TOOHIGH.0, +} + +/// On-chip ring oscillator configuration. +pub struct RoscConfig { + /// Final frequency of the oscillator, after the divider has been applied. + /// The oscillator has a nominal frequency of 6.5MHz at medium range with + /// divider 16 and all drive strengths set to 0, other values should be + /// measured in situ. + pub hz: u32, + /// Oscillator range. + pub range: RoscRange, + /// Drive strength for oscillator. + pub drive_strength: [u8; 8], + /// Output divider. + pub div: u16, +} + +/// Crystal oscillator configuration. +pub struct XoscConfig { + /// Final frequency of the oscillator. + pub hz: u32, + /// Configuring PLL for the system clock. + pub sys_pll: Option, + /// Configuring PLL for the USB clock. + pub usb_pll: Option, + /// Multiplier for the startup delay. + pub delay_multiplier: u32, +} + +/// PLL configuration. +pub struct PllConfig { + /// Reference divisor. + pub refdiv: u8, + /// Feedback divisor. + pub fbdiv: u16, + /// Output divisor 1. + pub post_div1: u8, + /// Output divisor 2. + pub post_div2: u8, +} + +/// Reference clock config. +pub struct RefClkConfig { + /// Reference clock source. + pub src: RefClkSrc, + /// Reference clock divider. + pub div: u8, +} + +/// Reference clock source. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RefClkSrc { + /// XOSC. + Xosc, + /// ROSC. + Rosc, + /// PLL USB. + PllUsb, + // Gpin0, + // Gpin1, +} + +/// SYS clock source. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SysClkSrc { + /// REF. + Ref, + /// PLL SYS. + PllSys, + /// PLL USB. + PllUsb, + /// ROSC. + Rosc, + /// XOSC. + Xosc, + // Gpin0, + // Gpin1, +} + +/// SYS clock config. +pub struct SysClkConfig { + /// SYS clock source. + pub src: SysClkSrc, + /// SYS clock divider. + #[cfg(feature = "rp2040")] + pub div_int: u32, + /// SYS clock fraction. + #[cfg(feature = "rp2040")] + pub div_frac: u8, + /// SYS clock divider. + #[cfg(feature = "_rp235x")] + pub div_int: u16, + /// SYS clock fraction. + #[cfg(feature = "_rp235x")] + pub div_frac: u16, +} + +/// USB clock source. +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum UsbClkSrc { + /// PLL USB. + PllUsb = ClkUsbCtrlAuxsrc::CLKSRC_PLL_USB as _, + /// PLL SYS. + PllSys = ClkUsbCtrlAuxsrc::CLKSRC_PLL_SYS as _, + /// ROSC. + Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH as _, + /// XOSC. + Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC as _, + // Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +/// USB clock config. +pub struct UsbClkConfig { + /// USB clock source. + pub src: UsbClkSrc, + /// USB clock divider. + pub div: u8, + /// USB clock phase. + pub phase: u8, +} + +/// ADC clock source. +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum AdcClkSrc { + /// PLL USB. + PllUsb = ClkAdcCtrlAuxsrc::CLKSRC_PLL_USB as _, + /// PLL SYS. + PllSys = ClkAdcCtrlAuxsrc::CLKSRC_PLL_SYS as _, + /// ROSC. + Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH as _, + /// XOSC. + Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC as _, + // Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +/// ADC clock config. +pub struct AdcClkConfig { + /// ADC clock source. + pub src: AdcClkSrc, + /// ADC clock divider. + pub div: u8, + /// ADC clock phase. + pub phase: u8, +} + +/// RTC clock source. +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg(feature = "rp2040")] +pub enum RtcClkSrc { + /// PLL USB. + PllUsb = ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB as _, + /// PLL SYS. + PllSys = ClkRtcCtrlAuxsrc::CLKSRC_PLL_SYS as _, + /// ROSC. + Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH as _, + /// XOSC. + Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC as _, + // Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +/// RTC clock config. +#[cfg(feature = "rp2040")] +pub struct RtcClkConfig { + /// RTC clock source. + pub src: RtcClkSrc, + /// RTC clock divider. + pub div_int: u32, + /// RTC clock divider fraction. + pub div_frac: u8, + /// RTC clock phase. + pub phase: u8, +} + +/// safety: must be called exactly once at bootup +pub(crate) unsafe fn init(config: ClockConfig) { + // Reset everything except: + // - QSPI (we're using it to run this code!) + // - PLLs (it may be suicide if that's what's clocking us) + // - USB, SYSCFG (breaks usb-to-swd on core1) + // - RTC (else there would be no more time...) + let mut peris = reset::ALL_PERIPHERALS; + peris.set_io_qspi(false); + // peris.set_io_bank0(false); // might be suicide if we're clocked from gpin + peris.set_pads_qspi(false); + peris.set_pll_sys(false); + peris.set_pll_usb(false); + peris.set_usbctrl(false); + peris.set_syscfg(false); + //peris.set_rtc(false); + reset::reset(peris); + + // Disable resus that may be enabled from previous software + let c = pac::CLOCKS; + c.clk_sys_resus_ctrl() + .write_value(pac::clocks::regs::ClkSysResusCtrl(0)); + + // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. + c.clk_sys_ctrl().modify(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); + #[cfg(feature = "rp2040")] + while c.clk_sys_selected().read() != 1 {} + #[cfg(feature = "_rp235x")] + while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1) {} + c.clk_ref_ctrl().modify(|w| w.set_src(ClkRefCtrlSrc::ROSC_CLKSRC_PH)); + #[cfg(feature = "rp2040")] + while c.clk_ref_selected().read() != 1 {} + #[cfg(feature = "_rp235x")] + while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1) {} + + // Reset the PLLs + let mut peris = reset::Peripherals(0); + peris.set_pll_sys(true); + peris.set_pll_usb(true); + reset::reset(peris); + reset::unreset_wait(peris); + + // let gpin0_freq = config.gpin0.map_or(0, |p| { + // core::mem::forget(p.1); + // p.0 + // }); + // CLOCKS.gpin0.store(gpin0_freq, Ordering::Relaxed); + // let gpin1_freq = config.gpin1.map_or(0, |p| { + // core::mem::forget(p.1); + // p.0 + // }); + // CLOCKS.gpin1.store(gpin1_freq, Ordering::Relaxed); + + let rosc_freq = match config.rosc { + Some(config) => configure_rosc(config), + None => 0, + }; + CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); + + let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { + Some(config) => { + // start XOSC + // datasheet mentions support for clock inputs into XIN, but doesn't go into + // how this is achieved. pico-sdk doesn't support this at all. + start_xosc(config.hz, config.delay_multiplier); + + let pll_sys_freq = match config.sys_pll { + Some(sys_pll_config) => configure_pll(pac::PLL_SYS, config.hz, sys_pll_config), + None => 0, + }; + let pll_usb_freq = match config.usb_pll { + Some(usb_pll_config) => configure_pll(pac::PLL_USB, config.hz, usb_pll_config), + None => 0, + }; + + (config.hz, pll_sys_freq, pll_usb_freq) + } + None => (0, 0, 0), + }; + CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); + CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); + CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); + + let (ref_src, ref_aux, clk_ref_freq) = { + use {ClkRefCtrlAuxsrc as Aux, ClkRefCtrlSrc as Src}; + let div = config.ref_clk.div as u32; + assert!(div >= 1 && div <= 4); + match config.ref_clk.src { + RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div), + RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div), + RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div), + // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div), + // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div), + } + }; + assert!(clk_ref_freq != 0); + CLOCKS.reference.store(clk_ref_freq, Ordering::Relaxed); + c.clk_ref_ctrl().write(|w| { + w.set_src(ref_src); + w.set_auxsrc(ref_aux); + }); + #[cfg(feature = "rp2040")] + while c.clk_ref_selected().read() != (1 << ref_src as u32) {} + #[cfg(feature = "_rp235x")] + while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ref_src as u32) {} + c.clk_ref_div().write(|w| { + w.set_int(config.ref_clk.div); + }); + + // Configure tick generation on the 2040. + #[cfg(feature = "rp2040")] + pac::WATCHDOG.tick().write(|w| { + w.set_cycles((clk_ref_freq / 1_000_000) as u16); + w.set_enable(true); + }); + // Configure tick generator on the 2350 + #[cfg(feature = "_rp235x")] + { + pac::TICKS.timer0_cycles().write(|w| w.0 = clk_ref_freq / 1_000_000); + pac::TICKS.timer0_ctrl().write(|w| w.set_enable(true)); + } + + let (sys_src, sys_aux, clk_sys_freq) = { + use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; + let (src, aux, freq) = match config.sys_clk.src { + SysClkSrc::Ref => (Src::CLK_REF, Aux::CLKSRC_PLL_SYS, clk_ref_freq), + SysClkSrc::PllSys => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_SYS, pll_sys_freq), + SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq), + SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), + SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), + // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), + // SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq), + }; + let div = config.sys_clk.div_int as u64 * 256 + config.sys_clk.div_frac as u64; + (src, aux, ((freq as u64 * 256) / div) as u32) + }; + assert!(clk_sys_freq != 0); + CLOCKS.sys.store(clk_sys_freq, Ordering::Relaxed); + if sys_src != ClkSysCtrlSrc::CLK_REF { + c.clk_sys_ctrl().write(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); + #[cfg(feature = "rp2040")] + while c.clk_sys_selected().read() != (1 << ClkSysCtrlSrc::CLK_REF as u32) {} + #[cfg(feature = "_rp235x")] + while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << ClkSysCtrlSrc::CLK_REF as u32) {} + } + c.clk_sys_ctrl().write(|w| { + w.set_auxsrc(sys_aux); + w.set_src(sys_src); + }); + + #[cfg(feature = "rp2040")] + while c.clk_sys_selected().read() != (1 << sys_src as u32) {} + #[cfg(feature = "_rp235x")] + while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << sys_src as u32) {} + + c.clk_sys_div().write(|w| { + w.set_int(config.sys_clk.div_int); + w.set_frac(config.sys_clk.div_frac); + }); + + let mut peris = reset::ALL_PERIPHERALS; + + if let Some(src) = config.peri_clk_src { + c.clk_peri_ctrl().write(|w| { + w.set_enable(true); + w.set_auxsrc(ClkPeriCtrlAuxsrc::from_bits(src as _)); + }); + let peri_freq = match src { + PeriClkSrc::Sys => clk_sys_freq, + PeriClkSrc::PllSys => pll_sys_freq, + PeriClkSrc::PllUsb => pll_usb_freq, + PeriClkSrc::Rosc => rosc_freq, + PeriClkSrc::Xosc => xosc_freq, + // PeriClkSrc::Gpin0 => gpin0_freq, + // PeriClkSrc::Gpin1 => gpin1_freq, + }; + assert!(peri_freq != 0); + CLOCKS.peri.store(peri_freq, Ordering::Relaxed); + } else { + peris.set_spi0(false); + peris.set_spi1(false); + peris.set_uart0(false); + peris.set_uart1(false); + CLOCKS.peri.store(0, Ordering::Relaxed); + } + + if let Some(conf) = config.usb_clk { + c.clk_usb_div().write(|w| w.set_int(conf.div)); + c.clk_usb_ctrl().write(|w| { + w.set_phase(conf.phase); + w.set_enable(true); + w.set_auxsrc(ClkUsbCtrlAuxsrc::from_bits(conf.src as _)); + }); + let usb_freq = match conf.src { + UsbClkSrc::PllUsb => pll_usb_freq, + UsbClkSrc::PllSys => pll_sys_freq, + UsbClkSrc::Rosc => rosc_freq, + UsbClkSrc::Xosc => xosc_freq, + // UsbClkSrc::Gpin0 => gpin0_freq, + // UsbClkSrc::Gpin1 => gpin1_freq, + }; + assert!(usb_freq != 0); + assert!(conf.div >= 1 && conf.div <= 4); + CLOCKS.usb.store(usb_freq / conf.div as u32, Ordering::Relaxed); + } else { + peris.set_usbctrl(false); + CLOCKS.usb.store(0, Ordering::Relaxed); + } + + if let Some(conf) = config.adc_clk { + c.clk_adc_div().write(|w| w.set_int(conf.div)); + c.clk_adc_ctrl().write(|w| { + w.set_phase(conf.phase); + w.set_enable(true); + w.set_auxsrc(ClkAdcCtrlAuxsrc::from_bits(conf.src as _)); + }); + let adc_in_freq = match conf.src { + AdcClkSrc::PllUsb => pll_usb_freq, + AdcClkSrc::PllSys => pll_sys_freq, + AdcClkSrc::Rosc => rosc_freq, + AdcClkSrc::Xosc => xosc_freq, + // AdcClkSrc::Gpin0 => gpin0_freq, + // AdcClkSrc::Gpin1 => gpin1_freq, + }; + assert!(adc_in_freq != 0); + assert!(conf.div >= 1 && conf.div <= 4); + CLOCKS.adc.store(adc_in_freq / conf.div as u32, Ordering::Relaxed); + } else { + peris.set_adc(false); + CLOCKS.adc.store(0, Ordering::Relaxed); + } + + // rp2040 specific clocks + #[cfg(feature = "rp2040")] + if let Some(conf) = config.rtc_clk { + c.clk_rtc_div().write(|w| { + w.set_int(conf.div_int); + w.set_frac(conf.div_frac); + }); + c.clk_rtc_ctrl().write(|w| { + w.set_phase(conf.phase); + w.set_enable(true); + w.set_auxsrc(ClkRtcCtrlAuxsrc::from_bits(conf.src as _)); + }); + let rtc_in_freq = match conf.src { + RtcClkSrc::PllUsb => pll_usb_freq, + RtcClkSrc::PllSys => pll_sys_freq, + RtcClkSrc::Rosc => rosc_freq, + RtcClkSrc::Xosc => xosc_freq, + // RtcClkSrc::Gpin0 => gpin0_freq, + // RtcClkSrc::Gpin1 => gpin1_freq, + }; + assert!(rtc_in_freq != 0); + assert!(config.sys_clk.div_int <= 0x1000000); + CLOCKS.rtc.store( + ((rtc_in_freq as u64 * 256) / (conf.div_int as u64 * 256 + conf.div_frac as u64)) as u16, + Ordering::Relaxed, + ); + } else { + peris.set_rtc(false); + CLOCKS.rtc.store(0, Ordering::Relaxed); + } + + // rp235x specific clocks + #[cfg(feature = "_rp235x")] + { + // TODO hstx clock + peris.set_hstx(false); + } + + // Peripheral clocks should now all be running + reset::unreset_wait(peris); +} + +fn configure_rosc(config: RoscConfig) -> u32 { + let p = pac::ROSC; + + p.freqa().write(|w| { + w.set_passwd(pac::rosc::vals::Passwd::PASS); + w.set_ds0(config.drive_strength[0]); + w.set_ds1(config.drive_strength[1]); + w.set_ds2(config.drive_strength[2]); + w.set_ds3(config.drive_strength[3]); + }); + + p.freqb().write(|w| { + w.set_passwd(pac::rosc::vals::Passwd::PASS); + w.set_ds4(config.drive_strength[4]); + w.set_ds5(config.drive_strength[5]); + w.set_ds6(config.drive_strength[6]); + w.set_ds7(config.drive_strength[7]); + }); + + p.div().write(|w| { + w.set_div(pac::rosc::vals::Div(config.div + pac::rosc::vals::Div::PASS.0)); + }); + + p.ctrl().write(|w| { + w.set_enable(pac::rosc::vals::Enable::ENABLE); + w.set_freq_range(pac::rosc::vals::FreqRange(config.range as u16)); + }); + + config.hz +} + +/// ROSC clock frequency. +pub fn rosc_freq() -> u32 { + CLOCKS.rosc.load(Ordering::Relaxed) +} + +/// XOSC clock frequency. +pub fn xosc_freq() -> u32 { + CLOCKS.xosc.load(Ordering::Relaxed) +} + +// pub fn gpin0_freq() -> u32 { +// CLOCKS.gpin0.load(Ordering::Relaxed) +// } +// pub fn gpin1_freq() -> u32 { +// CLOCKS.gpin1.load(Ordering::Relaxed) +// } + +/// PLL SYS clock frequency. +pub fn pll_sys_freq() -> u32 { + CLOCKS.pll_sys.load(Ordering::Relaxed) +} + +/// PLL USB clock frequency. +pub fn pll_usb_freq() -> u32 { + CLOCKS.pll_usb.load(Ordering::Relaxed) +} + +/// SYS clock frequency. +pub fn clk_sys_freq() -> u32 { + CLOCKS.sys.load(Ordering::Relaxed) +} + +/// REF clock frequency. +pub fn clk_ref_freq() -> u32 { + CLOCKS.reference.load(Ordering::Relaxed) +} + +/// Peripheral clock frequency. +pub fn clk_peri_freq() -> u32 { + CLOCKS.peri.load(Ordering::Relaxed) +} + +/// USB clock frequency. +pub fn clk_usb_freq() -> u32 { + CLOCKS.usb.load(Ordering::Relaxed) +} + +/// ADC clock frequency. +pub fn clk_adc_freq() -> u32 { + CLOCKS.adc.load(Ordering::Relaxed) +} + +/// RTC clock frequency. +#[cfg(feature = "rp2040")] +pub fn clk_rtc_freq() -> u16 { + CLOCKS.rtc.load(Ordering::Relaxed) +} + +fn start_xosc(crystal_hz: u32, delay_multiplier: u32) { + let startup_delay = (((crystal_hz / 1000) * delay_multiplier) + 128) / 256; + pac::XOSC.startup().write(|w| w.set_delay(startup_delay as u16)); + pac::XOSC.ctrl().write(|w| { + w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ); + w.set_enable(pac::xosc::vals::Enable::ENABLE); + }); + while !pac::XOSC.status().read().stable() {} +} + +#[inline(always)] +fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { + let ref_freq = input_freq / config.refdiv as u32; + assert!(config.fbdiv >= 16 && config.fbdiv <= 320); + assert!(config.post_div1 >= 1 && config.post_div1 <= 7); + assert!(config.post_div2 >= 1 && config.post_div2 <= 7); + assert!(config.refdiv >= 1 && config.refdiv <= 63); + assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000); + let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); + + // Load VCO-related dividers before starting VCO + p.cs().write(|w| w.set_refdiv(config.refdiv as _)); + p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); + + // Turn on PLL + let pwr = p.pwr().write(|w| { + w.set_dsmpd(true); // "nothing is achieved by setting this low" + w.set_pd(false); + w.set_vcopd(false); + w.set_postdivpd(true); + *w + }); + + // Wait for PLL to lock + while !p.cs().read().lock() {} + + // Set post-dividers + p.prim().write(|w| { + w.set_postdiv1(config.post_div1); + w.set_postdiv2(config.post_div2); + }); + + // Turn on post divider + p.pwr().write(|w| { + *w = pwr; + w.set_postdivpd(false); + }); + + vco_freq / ((config.post_div1 * config.post_div2) as u32) +} + +/// General purpose input clock pin. +pub trait GpinPin: crate::gpio::Pin { + /// Pin number. + const NR: usize; +} + +macro_rules! impl_gpinpin { + ($name:ident, $pin_num:expr, $gpin_num:expr) => { + impl GpinPin for crate::peripherals::$name { + const NR: usize = $gpin_num; + } + }; +} + +impl_gpinpin!(PIN_20, 20, 0); +impl_gpinpin!(PIN_22, 22, 1); + +/// General purpose clock input driver. +pub struct Gpin<'d, T: GpinPin> { + gpin: PeripheralRef<'d, AnyPin>, + _phantom: PhantomData, +} + +impl<'d, T: GpinPin> Gpin<'d, T> { + /// Create new gpin driver. + pub fn new(gpin: impl Peripheral

+ 'd) -> Self { + into_ref!(gpin); + + gpin.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + #[cfg(feature = "_rp235x")] + gpin.pad_ctrl().write(|w| { + w.set_iso(false); + }); + + Gpin { + gpin: gpin.map_into(), + _phantom: PhantomData, + } + } + + // fn map_into(self) -> Gpin<'d, AnyPin> { + // unsafe { core::mem::transmute(self) } + // } +} + +impl<'d, T: GpinPin> Drop for Gpin<'d, T> { + fn drop(&mut self) { + self.gpin.pad_ctrl().write(|_| {}); + self.gpin + .gpio() + .ctrl() + .write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _)); + } +} + +/// General purpose clock output pin. +pub trait GpoutPin: crate::gpio::Pin { + /// Pin number. + fn number(&self) -> usize; +} + +macro_rules! impl_gpoutpin { + ($name:ident, $gpout_num:expr) => { + impl GpoutPin for crate::peripherals::$name { + fn number(&self) -> usize { + $gpout_num + } + } + }; +} + +impl_gpoutpin!(PIN_21, 0); +impl_gpoutpin!(PIN_23, 1); +impl_gpoutpin!(PIN_24, 2); +impl_gpoutpin!(PIN_25, 3); + +/// Gpout clock source. +#[repr(u8)] +pub enum GpoutSrc { + /// Sys PLL. + PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS as _, + // Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 as _ , + /// USB PLL. + PllUsb = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB as _, + /// ROSC. + Rosc = ClkGpoutCtrlAuxsrc::ROSC_CLKSRC as _, + /// XOSC. + Xosc = ClkGpoutCtrlAuxsrc::XOSC_CLKSRC as _, + /// SYS. + Sys = ClkGpoutCtrlAuxsrc::CLK_SYS as _, + /// USB. + Usb = ClkGpoutCtrlAuxsrc::CLK_USB as _, + /// ADC. + Adc = ClkGpoutCtrlAuxsrc::CLK_ADC as _, + /// RTC. + #[cfg(feature = "rp2040")] + Rtc = ClkGpoutCtrlAuxsrc::CLK_RTC as _, + /// REF. + Ref = ClkGpoutCtrlAuxsrc::CLK_REF as _, +} + +/// General purpose clock output driver. +pub struct Gpout<'d, T: GpoutPin> { + gpout: PeripheralRef<'d, T>, +} + +impl<'d, T: GpoutPin> Gpout<'d, T> { + /// Create new general purpose clock output. + pub fn new(gpout: impl Peripheral

+ 'd) -> Self { + into_ref!(gpout); + + gpout.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + #[cfg(feature = "_rp235x")] + gpout.pad_ctrl().write(|w| { + w.set_iso(false); + }); + + Self { gpout } + } + + /// Set clock divider. + #[cfg(feature = "rp2040")] + pub fn set_div(&self, int: u32, frac: u8) { + let c = pac::CLOCKS; + c.clk_gpout_div(self.gpout.number()).write(|w| { + w.set_int(int); + w.set_frac(frac); + }); + } + + /// Set clock divider. + #[cfg(feature = "_rp235x")] + pub fn set_div(&self, int: u16, frac: u16) { + let c = pac::CLOCKS; + c.clk_gpout_div(self.gpout.number()).write(|w| { + w.set_int(int); + w.set_frac(frac); + }); + } + + /// Set clock source. + pub fn set_src(&self, src: GpoutSrc) { + let c = pac::CLOCKS; + c.clk_gpout_ctrl(self.gpout.number()).modify(|w| { + w.set_auxsrc(ClkGpoutCtrlAuxsrc::from_bits(src as _)); + }); + } + + /// Enable clock. + pub fn enable(&self) { + let c = pac::CLOCKS; + c.clk_gpout_ctrl(self.gpout.number()).modify(|w| { + w.set_enable(true); + }); + } + + /// Disable clock. + pub fn disable(&self) { + let c = pac::CLOCKS; + c.clk_gpout_ctrl(self.gpout.number()).modify(|w| { + w.set_enable(false); + }); + } + + /// Clock frequency. + pub fn get_freq(&self) -> u32 { + let c = pac::CLOCKS; + let src = c.clk_gpout_ctrl(self.gpout.number()).read().auxsrc(); + + let base = match src { + ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS => pll_sys_freq(), + // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 => gpin0_freq(), + // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 => gpin1_freq(), + ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB => pll_usb_freq(), + ClkGpoutCtrlAuxsrc::ROSC_CLKSRC => rosc_freq(), + ClkGpoutCtrlAuxsrc::XOSC_CLKSRC => xosc_freq(), + ClkGpoutCtrlAuxsrc::CLK_SYS => clk_sys_freq(), + ClkGpoutCtrlAuxsrc::CLK_USB => clk_usb_freq(), + ClkGpoutCtrlAuxsrc::CLK_ADC => clk_adc_freq(), + //ClkGpoutCtrlAuxsrc::CLK_RTC => clk_rtc_freq() as _, + ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(), + _ => unreachable!(), + }; + + let div = c.clk_gpout_div(self.gpout.number()).read(); + let int = if div.int() == 0 { 0xFFFF } else { div.int() } as u64; + let frac = div.frac() as u64; + + ((base as u64 * 256) / (int * 256 + frac)) as u32 + } +} + +impl<'d, T: GpoutPin> Drop for Gpout<'d, T> { + fn drop(&mut self) { + self.disable(); + self.gpout.pad_ctrl().write(|_| {}); + self.gpout + .gpio() + .ctrl() + .write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _)); + } +} + +/// 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 |= 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) + } +} +/// Enter the `DORMANT` sleep state. This will stop *all* internal clocks +/// and can only be exited through resets, dormant-wake GPIO interrupts, +/// and RTC interrupts. If RTC is clocked from an internal clock source +/// it will be stopped and not function as a wakeup source. +#[cfg(all(target_arch = "arm", feature = "rp2040"))] +pub fn dormant_sleep() { + struct Set(Reg, T, F); + + impl Drop for Set { + fn drop(&mut self) { + self.0.write_value(self.1); + self.2(); + } + } + + fn set_with_post_restore After>( + reg: Reg, + f: F, + ) -> Set { + reg.modify(|w| { + let old = *w; + let after = f(w); + Set(reg, old, after) + }) + } + + fn set(reg: Reg, f: F) -> Set { + set_with_post_restore(reg, |r| { + f(r); + || () + }) + } + + // disable all clocks that are not vital in preparation for disabling clock sources. + // we'll keep gpout and rtc clocks untouched, gpout because we don't care about them + // and rtc because it's a possible wakeup source. if clk_rtc is not configured for + // gpin we'll never wake from rtc, but that's what the user asked for then. + let _stop_adc = set(pac::CLOCKS.clk_adc_ctrl(), |w| w.set_enable(false)); + let _stop_usb = set(pac::CLOCKS.clk_usb_ctrl(), |w| w.set_enable(false)); + let _stop_peri = set(pac::CLOCKS.clk_peri_ctrl(), |w| w.set_enable(false)); + // set up rosc. we could ask the user to tell us which clock source to wake from like + // the C SDK does, but that seems rather unfriendly. we *may* disturb rtc by changing + // rosc configuration if it's currently the rtc clock source, so we'll configure rosc + // to the slowest frequency to minimize that impact. + let _configure_rosc = ( + set(pac::ROSC.ctrl(), |w| { + w.set_enable(pac::rosc::vals::Enable::ENABLE); + w.set_freq_range(pac::rosc::vals::FreqRange::LOW); + }), + // div=32 + set(pac::ROSC.div(), |w| w.set_div(pac::rosc::vals::Div(0xaa0))), + ); + while !pac::ROSC.status().read().stable() {} + // switch over to rosc as the system clock source. this will change clock sources for + // watchdog and timer clocks, but timers won't be a concern and the watchdog won't + // speed up by enough to worry about (unless it's clocked from gpin, which we don't + // support anyway). + let _switch_clk_ref = set(pac::CLOCKS.clk_ref_ctrl(), |w| { + w.set_src(pac::clocks::vals::ClkRefCtrlSrc::ROSC_CLKSRC_PH); + }); + let _switch_clk_sys = set(pac::CLOCKS.clk_sys_ctrl(), |w| { + w.set_src(pac::clocks::vals::ClkSysCtrlSrc::CLK_REF); + }); + // oscillator dormancy does not power down plls, we have to do that ourselves. we'll + // restore them to their prior glory when woken though since the system may be clocked + // from either (and usb/adc will probably need the USB PLL anyway) + let _stop_pll_sys = set_with_post_restore(pac::PLL_SYS.pwr(), |w| { + let wake = !w.pd() && !w.vcopd(); + w.set_pd(true); + w.set_vcopd(true); + move || while wake && !pac::PLL_SYS.cs().read().lock() {} + }); + let _stop_pll_usb = set_with_post_restore(pac::PLL_USB.pwr(), |w| { + let wake = !w.pd() && !w.vcopd(); + w.set_pd(true); + w.set_vcopd(true); + move || while wake && !pac::PLL_USB.cs().read().lock() {} + }); + // dormancy only stops the oscillator we're telling to go dormant, the other remains + // running. nothing can use xosc at this point any more. not doing this costs an 200µA. + let _stop_xosc = set_with_post_restore(pac::XOSC.ctrl(), |w| { + let wake = w.enable() == pac::xosc::vals::Enable::ENABLE; + if wake { + w.set_enable(pac::xosc::vals::Enable::DISABLE); + } + move || while wake && !pac::XOSC.status().read().stable() {} + }); + let _power_down_xip_cache = set(pac::XIP_CTRL.ctrl(), |w| w.set_power_down(true)); + + // only power down memory if we're running from XIP (or ROM? how?). + // powering down memory otherwise would require a lot of exacting checks that + // are better done by the user in a local copy of this function. + // powering down memories saves ~100µA, so it's well worth doing. + unsafe { + let is_in_flash = { + // we can't rely on the address of this function as rust sees it since linker + // magic or even boot2 may place it into ram. + let pc: usize; + asm!( + "mov {pc}, pc", + pc = out (reg) pc + ); + pc < 0x20000000 + }; + if is_in_flash { + // we will be powering down memories, so we must be *absolutely* + // certain that we're running entirely from XIP and registers until + // memories are powered back up again. accessing memory that's powered + // down may corrupt memory contents (see section 2.11.4 of the manual). + // additionally a 20ns wait time is needed after powering up memories + // again. rosc is likely to run at only a few MHz at most, so the + // inter-instruction delay alone will be enough to satisfy this bound. + asm!( + "ldr {old_mem}, [{mempowerdown}]", + "str {power_down_mems}, [{mempowerdown}]", + "str {coma}, [{dormant}]", + "str {old_mem}, [{mempowerdown}]", + old_mem = out (reg) _, + mempowerdown = in (reg) pac::SYSCFG.mempowerdown().as_ptr(), + power_down_mems = in (reg) 0b11111111, + dormant = in (reg) pac::ROSC.dormant().as_ptr(), + coma = in (reg) 0x636f6d61, + ); + } else { + pac::ROSC.dormant().write_value(rp_pac::rosc::regs::Dormant(0x636f6d61)); + } + } +} diff --git a/embassy/embassy-rp/src/critical_section_impl.rs b/embassy/embassy-rp/src/critical_section_impl.rs new file mode 100644 index 0000000..d233e6f --- /dev/null +++ b/embassy/embassy-rp/src/critical_section_impl.rs @@ -0,0 +1,137 @@ +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 { + let lock = pac::SIO.spinlock(N).read(); + if lock > 0 { + Some(Self(core::marker::PhantomData)) + } else { + None + } + } + + /// Clear a locked spin-lock. + /// + /// # Safety + /// + /// Only call this function if you hold the spin-lock. + pub unsafe fn release() { + // Write (any value): release the lock + pac::SIO.spinlock(N).write_value(1); + } +} + +impl Drop for Spinlock +where + Spinlock: SpinlockValid, +{ + fn drop(&mut self) { + // This is safe because we own the object, and hence hold the lock. + unsafe { Self::release() } + } +} + +pub(crate) type Spinlock31 = Spinlock<31>; +pub trait SpinlockValid {} +impl SpinlockValid for Spinlock<31> {} diff --git a/embassy/embassy-rp/src/dma.rs b/embassy/embassy-rp/src/dma.rs new file mode 100644 index 0000000..2edcfdf --- /dev/null +++ b/embassy/embassy-rp/src/dma.rs @@ -0,0 +1,315 @@ +//! Direct Memory Access (DMA) +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{impl_peripheral, into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use pac::dma::vals::DataSize; + +use crate::interrupt::InterruptExt; +use crate::pac::dma::vals; +use crate::{interrupt, pac, peripherals}; + +#[cfg(feature = "rt")] +#[interrupt] +fn DMA_IRQ_0() { + let ints0 = pac::DMA.ints(0).read(); + for channel in 0..CHANNEL_COUNT { + let ctrl_trig = pac::DMA.ch(channel).ctrl_trig().read(); + if ctrl_trig.ahb_error() { + panic!("DMA: error on DMA_0 channel {}", channel); + } + + if ints0 & (1 << channel) == (1 << channel) { + CHANNEL_WAKERS[channel].wake(); + } + } + pac::DMA.ints(0).write_value(ints0); +} + +pub(crate) unsafe fn init() { + interrupt::DMA_IRQ_0.disable(); + interrupt::DMA_IRQ_0.set_priority(interrupt::Priority::P3); + + pac::DMA.inte(0).write_value(0xFFFF); + + interrupt::DMA_IRQ_0.enable(); +} + +/// DMA read. +/// +/// SAFETY: Slice must point to a valid location reachable by DMA. +pub unsafe fn read<'a, C: Channel, W: Word>( + ch: impl Peripheral

+ 'a, + from: *const W, + to: *mut [W], + dreq: vals::TreqSel, +) -> Transfer<'a, C> { + copy_inner( + ch, + from as *const u32, + to as *mut W as *mut u32, + to.len(), + W::size(), + false, + true, + dreq, + ) +} + +/// DMA write. +/// +/// SAFETY: Slice must point to a valid location reachable by DMA. +pub unsafe fn write<'a, C: Channel, W: Word>( + ch: impl Peripheral

+ 'a, + from: *const [W], + to: *mut W, + dreq: vals::TreqSel, +) -> Transfer<'a, C> { + copy_inner( + ch, + from as *const W as *const u32, + to as *mut u32, + from.len(), + W::size(), + true, + false, + dreq, + ) +} + +// static mut so that this is allocated in RAM. +static mut DUMMY: u32 = 0; + +/// DMA repeated write. +/// +/// SAFETY: Slice must point to a valid location reachable by DMA. +pub unsafe fn write_repeated<'a, C: Channel, W: Word>( + ch: impl Peripheral

+ 'a, + to: *mut W, + len: usize, + dreq: vals::TreqSel, +) -> Transfer<'a, C> { + copy_inner( + ch, + core::ptr::addr_of_mut!(DUMMY) as *const u32, + to as *mut u32, + len, + W::size(), + false, + false, + dreq, + ) +} + +/// DMA copy between slices. +/// +/// SAFETY: Slices must point to locations reachable by DMA. +pub unsafe fn copy<'a, C: Channel, W: Word>( + ch: impl Peripheral

+ 'a, + from: &[W], + to: &mut [W], +) -> Transfer<'a, C> { + let from_len = from.len(); + let to_len = to.len(); + assert_eq!(from_len, to_len); + copy_inner( + ch, + from.as_ptr() as *const u32, + to.as_mut_ptr() as *mut u32, + from_len, + W::size(), + true, + true, + vals::TreqSel::PERMANENT, + ) +} + +fn copy_inner<'a, C: Channel>( + ch: impl Peripheral

+ 'a, + from: *const u32, + to: *mut u32, + len: usize, + data_size: DataSize, + incr_read: bool, + incr_write: bool, + dreq: vals::TreqSel, +) -> Transfer<'a, C> { + into_ref!(ch); + + let p = ch.regs(); + + p.read_addr().write_value(from as u32); + p.write_addr().write_value(to as u32); + #[cfg(feature = "rp2040")] + p.trans_count().write(|w| { + *w = len as u32; + }); + #[cfg(feature = "_rp235x")] + p.trans_count().write(|w| { + w.set_mode(0.into()); + w.set_count(len as u32); + }); + + compiler_fence(Ordering::SeqCst); + + p.ctrl_trig().write(|w| { + w.set_treq_sel(dreq); + w.set_data_size(data_size); + w.set_incr_read(incr_read); + w.set_incr_write(incr_write); + w.set_chain_to(ch.number()); + w.set_en(true); + }); + + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) +} + +/// DMA transfer driver. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a, C: Channel> { + channel: PeripheralRef<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub(crate) fn new(channel: impl Peripheral

+ 'a) -> Self { + into_ref!(channel); + + Self { channel } + } +} + +impl<'a, C: Channel> Drop for Transfer<'a, C> { + fn drop(&mut self) { + let p = self.channel.regs(); + pac::DMA + .chan_abort() + .modify(|m| m.set_chan_abort(1 << self.channel.number())); + while p.ctrl_trig().read().busy() {} + } +} + +impl<'a, C: Channel> Unpin for Transfer<'a, C> {} +impl<'a, C: Channel> Future for Transfer<'a, C> { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We need to register/re-register the waker for each poll because any + // calls to wake will deregister the waker. + CHANNEL_WAKERS[self.channel.number() as usize].register(cx.waker()); + + if self.channel.regs().ctrl_trig().read().busy() { + Poll::Pending + } else { + Poll::Ready(()) + } + } +} + +#[cfg(feature = "rp2040")] +pub(crate) const CHANNEL_COUNT: usize = 12; +#[cfg(feature = "_rp235x")] +pub(crate) const CHANNEL_COUNT: usize = 16; +static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT]; + +trait SealedChannel {} +trait SealedWord {} + +/// DMA channel interface. +#[allow(private_bounds)] +pub trait Channel: Peripheral

+ SealedChannel + Into + Sized + 'static { + /// Channel number. + fn number(&self) -> u8; + + /// Channel registry block. + fn regs(&self) -> pac::dma::Channel { + pac::DMA.ch(self.number() as _) + } + + /// Convert into type-erased [AnyChannel]. + fn degrade(self) -> AnyChannel { + AnyChannel { number: self.number() } + } +} + +/// DMA word. +#[allow(private_bounds)] +pub trait Word: SealedWord { + /// Word size. + fn size() -> vals::DataSize; +} + +impl SealedWord for u8 {} +impl Word for u8 { + fn size() -> vals::DataSize { + vals::DataSize::SIZE_BYTE + } +} + +impl SealedWord for u16 {} +impl Word for u16 { + fn size() -> vals::DataSize { + vals::DataSize::SIZE_HALFWORD + } +} + +impl SealedWord for u32 {} +impl Word for u32 { + fn size() -> vals::DataSize { + vals::DataSize::SIZE_WORD + } +} + +/// Type erased DMA channel. +pub struct AnyChannel { + number: u8, +} + +impl_peripheral!(AnyChannel); + +impl SealedChannel for AnyChannel {} +impl Channel for AnyChannel { + fn number(&self) -> u8 { + self.number + } +} + +macro_rules! channel { + ($name:ident, $num:expr) => { + impl SealedChannel for peripherals::$name {} + impl Channel for peripherals::$name { + fn number(&self) -> u8 { + $num + } + } + + impl From for crate::dma::AnyChannel { + fn from(val: peripherals::$name) -> Self { + crate::dma::Channel::degrade(val) + } + } + }; +} + +channel!(DMA_CH0, 0); +channel!(DMA_CH1, 1); +channel!(DMA_CH2, 2); +channel!(DMA_CH3, 3); +channel!(DMA_CH4, 4); +channel!(DMA_CH5, 5); +channel!(DMA_CH6, 6); +channel!(DMA_CH7, 7); +channel!(DMA_CH8, 8); +channel!(DMA_CH9, 9); +channel!(DMA_CH10, 10); +channel!(DMA_CH11, 11); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH12, 12); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH13, 13); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH14, 14); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH15, 15); diff --git a/embassy/embassy-rp/src/flash.rs b/embassy/embassy-rp/src/flash.rs new file mode 100644 index 0000000..fbc8b35 --- /dev/null +++ b/embassy/embassy-rp/src/flash.rs @@ -0,0 +1,989 @@ +//! Flash driver. +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embedded_storage::nor_flash::{ + check_erase, check_read, check_write, ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, + ReadNorFlash, +}; + +use crate::dma::{AnyChannel, Channel, Transfer}; +use crate::pac; +use crate::peripherals::FLASH; + +/// Flash base address. +pub const FLASH_BASE: *const u32 = 0x10000000 as _; + +/// Address for xip setup function set up by the 235x bootrom. +#[cfg(feature = "_rp235x")] +pub const BOOTROM_BASE: *const u32 = 0x400e0000 as _; + +/// If running from RAM, we might have no boot2. Use bootrom `flash_enter_cmd_xip` instead. +// TODO: when run-from-ram is set, completely skip the "pause cores and jumpp to RAM" dance. +pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram") | cfg!(feature = "_rp235x"); + +// **NOTE**: +// +// These limitations are currently enforced because of using the +// RP2040 boot-rom flash functions, that are optimized for flash compatibility +// rather than performance. +/// Flash page size. +pub const PAGE_SIZE: usize = 256; +/// Flash write size. +pub const WRITE_SIZE: usize = 1; +/// Flash read size. +pub const READ_SIZE: usize = 1; +/// Flash erase size. +pub const ERASE_SIZE: usize = 4096; +/// Flash DMA read size. +pub const ASYNC_READ_SIZE: usize = 4; + +/// Error type for NVMC operations. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Operation using a location not in flash. + OutOfBounds, + /// Unaligned operation or using unaligned buffers. + Unaligned, + /// Accessed from the wrong core. + InvalidCore, + /// Other error + 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, + } + } +} + +/// Future that waits for completion of a background read +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct BackgroundRead<'a, 'd, T: Instance, const FLASH_SIZE: usize> { + flash: PhantomData<&'a mut Flash<'d, T, Async, FLASH_SIZE>>, + transfer: Transfer<'a, AnyChannel>, +} + +impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Future for BackgroundRead<'a, 'd, T, FLASH_SIZE> { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.transfer).poll(cx) + } +} + +impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Drop for BackgroundRead<'a, 'd, T, FLASH_SIZE> { + fn drop(&mut self) { + if pac::XIP_CTRL.stream_ctr().read().0 == 0 { + return; + } + pac::XIP_CTRL + .stream_ctr() + .write_value(pac::xip_ctrl::regs::StreamCtr(0)); + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + // Errata RP2040-E8: Perform an uncached read to make sure there's not a transfer in + // flight that might effect an address written to start a new transfer. This stalls + // until after any transfer is complete, so the address will not change anymore. + #[cfg(feature = "rp2040")] + const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x13000000 as *const _; + #[cfg(feature = "_rp235x")] + const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x14000000 as *const _; + unsafe { + core::ptr::read_volatile(XIP_NOCACHE_NOALLOC_BASE); + } + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + } +} + +/// Flash driver. +pub struct Flash<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> { + dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SIZE> { + /// Blocking read. + /// + /// The offset and buffer must be aligned. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + trace!( + "Reading from 0x{:x} to 0x{:x}", + FLASH_BASE as u32 + offset, + FLASH_BASE as u32 + offset + bytes.len() as u32 + ); + 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(()) + } + + /// Flash capacity. + pub fn capacity(&self) -> usize { + FLASH_SIZE + } + + /// Blocking erase. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + pub fn blocking_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 { in_ram(|| ram_helpers::flash_range_erase(from, len))? }; + + Ok(()) + } + + /// Blocking write. + /// + /// The offset and buffer must be aligned. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + pub fn blocking_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 { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } + } + + 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 { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data))? } + } 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 { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf))? } + 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 { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } + } + + Ok(()) + } + + /// Read SPI flash unique ID + #[cfg(feature = "rp2040")] + pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> { + unsafe { in_ram(|| ram_helpers::flash_unique_id(uid))? }; + Ok(()) + } + + /// Read SPI flash JEDEC ID + #[cfg(feature = "rp2040")] + pub fn blocking_jedec_id(&mut self) -> Result { + let mut jedec = None; + unsafe { + in_ram(|| { + jedec.replace(ram_helpers::flash_jedec_id()); + })?; + }; + Ok(jedec.unwrap()) + } +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Blocking, FLASH_SIZE> { + /// Create a new flash driver in blocking mode. + pub fn new_blocking(_flash: impl Peripheral

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

+ 'd, dma: impl Peripheral

+ 'd) -> Self { + into_ref!(dma); + Self { + dma: Some(dma.map_into()), + phantom: PhantomData, + } + } + + /// Start a background read operation. + /// + /// The offset and buffer must be aligned. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + pub fn background_read<'a>( + &'a mut self, + offset: u32, + data: &'a mut [u32], + ) -> Result, Error> { + trace!( + "Reading in background from 0x{:x} to 0x{:x}", + FLASH_BASE as u32 + offset, + FLASH_BASE as u32 + offset + (data.len() * 4) as u32 + ); + // Can't use check_read because we need to enforce 4-byte alignment + let offset = offset as usize; + let length = data.len() * 4; + if length > self.capacity() || offset > self.capacity() - length { + return Err(Error::OutOfBounds); + } + if offset % 4 != 0 { + return Err(Error::Unaligned); + } + + while !pac::XIP_CTRL.stat().read().fifo_empty() { + pac::XIP_CTRL.stream_fifo().read(); + } + + pac::XIP_CTRL + .stream_addr() + .write_value(pac::xip_ctrl::regs::StreamAddr(FLASH_BASE as u32 + offset as u32)); + pac::XIP_CTRL + .stream_ctr() + .write_value(pac::xip_ctrl::regs::StreamCtr(data.len() as u32)); + + // Use the XIP AUX bus port, rather than the FIFO register access (e.x. + // pac::XIP_CTRL.stream_fifo().as_ptr()) to avoid DMA stalling on + // general XIP access. + #[cfg(feature = "rp2040")] + const XIP_AUX_BASE: *const u32 = 0x50400000 as *const _; + #[cfg(feature = "_rp235x")] + const XIP_AUX_BASE: *const u32 = 0x50500000 as *const _; + let transfer = unsafe { + crate::dma::read( + self.dma.as_mut().unwrap(), + XIP_AUX_BASE, + data, + pac::dma::vals::TreqSel::XIP_STREAM, + ) + }; + + Ok(BackgroundRead { + flash: PhantomData, + transfer, + }) + } + + /// Async read. + /// + /// The offset and buffer must be aligned. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + pub async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + use core::mem::MaybeUninit; + + // Checked early to simplify address validity checks + if bytes.len() % 4 != 0 { + return Err(Error::Unaligned); + } + + // If the destination address is already aligned, then we can just DMA directly + if (bytes.as_ptr() as u32) % 4 == 0 { + // Safety: alignment and size have been checked for compatibility + let buf: &mut [u32] = + unsafe { core::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut u32, bytes.len() / 4) }; + self.background_read(offset, buf)?.await; + return Ok(()); + } + + // Destination address is unaligned, so use an intermediate buffer + const REALIGN_CHUNK: usize = PAGE_SIZE; + // Safety: MaybeUninit requires no initialization + let mut buf: [MaybeUninit; REALIGN_CHUNK / 4] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut chunk_offset: usize = 0; + while chunk_offset < bytes.len() { + let chunk_size = (bytes.len() - chunk_offset).min(REALIGN_CHUNK); + let buf = &mut buf[..(chunk_size / 4)]; + + // Safety: this is written to completely by DMA before any reads + let buf = unsafe { &mut *(buf as *mut [MaybeUninit] as *mut [u32]) }; + self.background_read(offset + chunk_offset as u32, buf)?.await; + + // Safety: [u8] has more relaxed alignment and size requirements than [u32], so this is just aliasing + let buf = unsafe { core::slice::from_raw_parts(buf.as_ptr() as *const _, buf.len() * 4) }; + bytes[chunk_offset..(chunk_offset + chunk_size)].copy_from_slice(&buf[..chunk_size]); + + chunk_offset += chunk_size; + } + + Ok(()) + } +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> ErrorType for Flash<'d, T, M, FLASH_SIZE> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> ReadNorFlash for Flash<'d, T, M, FLASH_SIZE> { + const READ_SIZE: usize = READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(offset, bytes) + } + + fn capacity(&self) -> usize { + self.capacity() + } +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> MultiwriteNorFlash for Flash<'d, T, M, FLASH_SIZE> {} + +impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash::MultiwriteNorFlash + for Flash<'d, T, Async, FLASH_SIZE> +{ +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> NorFlash for Flash<'d, T, M, 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.blocking_erase(from, to) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(offset, bytes) + } +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash::ReadNorFlash + for Flash<'d, T, Async, FLASH_SIZE> +{ + const READ_SIZE: usize = ASYNC_READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes).await + } + + fn capacity(&self) -> usize { + self.capacity() + } +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash::NorFlash + for Flash<'d, T, Async, FLASH_SIZE> +{ + const WRITE_SIZE: usize = WRITE_SIZE; + + const ERASE_SIZE: usize = ERASE_SIZE; + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.blocking_erase(from, to) + } + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(offset, bytes) + } +} + +#[allow(dead_code)] +mod ram_helpers { + use super::*; + 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) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256); + flash_function_pointers_with_boot2(true, false, &boot2) + } else { + flash_function_pointers(true, false) + }; + + 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]) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, (boot2).as_mut_ptr() as *mut u8, 256); + flash_function_pointers_with_boot2(true, true, &boot2) + } else { + flash_function_pointers(true, true) + }; + + 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]) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256); + flash_function_pointers_with_boot2(false, true, &boot2) + } else { + flash_function_pointers(false, true) + }; + + 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"] + #[cfg(feature = "rp2040")] + unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { + #[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 2f", + "blx r4", // flash_range_erase(addr, len, 1 << 31, 0) + "2:", + + "mov r0, r8", // r0 = addr + "mov r1, r9", // r0 = data + "mov r2, r10", // r2 = len + "ldr r4, [{ptrs}, #12]", + "cmp r4, #0", + "beq 2f", + "blx r4", // flash_range_program(addr, data, len); + "2:", + + "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"), + ); + } + + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// Length of data must be a multiple of 4096 + /// addr must be aligned to 4096 + #[inline(never)] + #[link_section = ".data.ram_func"] + #[cfg(feature = "_rp235x")] + unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { + let data = data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null()); + ((*ptrs).connect_internal_flash)(); + ((*ptrs).flash_exit_xip)(); + if (*ptrs).flash_range_erase.is_some() { + ((*ptrs).flash_range_erase.unwrap())(addr, len as usize, 1 << 31, 0); + } + if (*ptrs).flash_range_program.is_some() { + ((*ptrs).flash_range_program.unwrap())(addr, data as *const _, len as usize); + } + ((*ptrs).flash_flush_cache)(); + ((*ptrs).flash_enter_cmd_xip)(); + } + + #[repr(C)] + struct FlashCommand { + cmd_addr: *const u8, + cmd_addr_len: u32, + dummy_len: u32, + data: *mut u8, + data_len: u32, + } + + /// Return SPI flash unique ID + /// + /// Not all SPI flashes implement this command, so check the JEDEC + /// ID before relying on it. The Winbond parts commonly seen on + /// RP2040 devboards (JEDEC=0xEF7015) support an 8-byte unique ID; + /// https://forums.raspberrypi.com/viewtopic.php?t=331949 suggests + /// that LCSC (Zetta) parts have a 16-byte unique ID (which is + /// *not* unique in just its first 8 bytes), + /// JEDEC=0xBA6015. Macronix and Spansion parts do not have a + /// unique ID. + /// + /// The returned bytes are relatively predictable and should be + /// salted and hashed before use if that is an issue (e.g. for MAC + /// addresses). + /// + /// # 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 + /// + /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[cfg(feature = "rp2040")] + pub unsafe fn flash_unique_id(out: &mut [u8]) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, false, &boot2) + } else { + flash_function_pointers(false, false) + }; + + // 4B - read unique ID + let cmd = [0x4B]; + read_flash(&cmd[..], 4, out, &ptrs as *const FlashFunctionPointers); + } + + /// Return SPI flash JEDEC ID + /// + /// This is the three-byte manufacturer-and-model identifier + /// commonly used to check before using manufacturer-specific SPI + /// flash features, e.g. 0xEF7015 for Winbond W25Q16JV. + /// + /// # 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 + /// + /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[cfg(feature = "rp2040")] + pub unsafe fn flash_jedec_id() -> u32 { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, false, &boot2) + } else { + flash_function_pointers(false, false) + }; + + let mut id = [0u8; 4]; + // 9F - read JEDEC ID + let cmd = [0x9F]; + read_flash(&cmd[..], 0, &mut id[1..4], &ptrs as *const FlashFunctionPointers); + u32::from_be_bytes(id) + } + + #[cfg(feature = "rp2040")] + unsafe fn read_flash(cmd_addr: &[u8], dummy_len: u32, out: &mut [u8], ptrs: *const FlashFunctionPointers) { + read_flash_inner( + FlashCommand { + cmd_addr: cmd_addr.as_ptr(), + cmd_addr_len: cmd_addr.len() as u32, + dummy_len, + data: out.as_mut_ptr(), + data_len: out.len() as u32, + }, + ptrs, + ); + } + + /// Issue a generic SPI flash read command + /// + /// # Arguments + /// + /// * `cmd` - `FlashCommand` structure + /// * `ptrs` - Flash function pointers as per `write_flash_inner` + /// + /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[inline(never)] + #[link_section = ".data.ram_func"] + #[cfg(feature = "rp2040")] + unsafe fn read_flash_inner(cmd: FlashCommand, ptrs: *const FlashFunctionPointers) { + #[cfg(target_arch = "arm")] + core::arch::asm!( + "mov r10, r0", // cmd + "mov r5, r1", // ptrs + + "ldr r4, [r5, #0]", + "blx r4", // connect_internal_flash() + + "ldr r4, [r5, #4]", + "blx r4", // flash_exit_xip() + + + "movs r4, #0x18", + "lsls r4, r4, #24", // 0x18000000, SSI, RP2040 datasheet 4.10.13 + + // Disable, write 0 to SSIENR + "movs r0, #0", + "str r0, [r4, #8]", // SSIENR + + // Write ctrlr0 + "movs r0, #0x3", + "lsls r0, r0, #8", // TMOD=0x300 + "ldr r1, [r4, #0]", // CTRLR0 + "orrs r1, r0", + "str r1, [r4, #0]", + + // Write ctrlr1 with len-1 + "mov r3, r10", // cmd + "ldr r0, [r3, #8]", // dummy_len + "ldr r1, [r3, #16]", // data_len + "add r0, r1", + "subs r0, #1", + "str r0, [r4, #0x04]", // CTRLR1 + + // Enable, write 1 to ssienr + "movs r0, #1", + "str r0, [r4, #8]", // SSIENR + + // Write cmd/addr phase to DR + "mov r2, r4", + "adds r2, 0x60", // &DR + "ldr r0, [r3, #0]", // cmd_addr + "ldr r1, [r3, #4]", // cmd_addr_len + "3:", + "ldrb r3, [r0]", + "strb r3, [r2]", // DR + "adds r0, #1", + "subs r1, #1", + "bne 3b", + + // Skip any dummy cycles + "mov r3, r10", // cmd + "ldr r1, [r3, #8]", // dummy_len + "cmp r1, #0", + "beq 9f", + "4:", + "ldr r3, [r4, #0x28]", // SR + "movs r2, #0x8", + "tst r3, r2", // SR.RFNE + "beq 4b", + + "mov r2, r4", + "adds r2, 0x60", // &DR + "ldrb r3, [r2]", // DR + "subs r1, #1", + "bne 4b", + + // Read RX fifo + "9:", + "mov r2, r10", // cmd + "ldr r0, [r2, #12]", // data + "ldr r1, [r2, #16]", // data_len + + "2:", + "ldr r3, [r4, #0x28]", // SR + "movs r2, #0x8", + "tst r3, r2", // SR.RFNE + "beq 2b", + + "mov r2, r4", + "adds r2, 0x60", // &DR + "ldrb r3, [r2]", // DR + "strb r3, [r0]", + "adds r0, #1", + "subs r1, #1", + "bne 2b", + + // Disable, write 0 to ssienr + "movs r0, #0", + "str r0, [r4, #8]", // SSIENR + + // Write 0 to CTRLR1 (returning to its default value) + // + // flash_enter_cmd_xip does NOT do this, and everything goes + // wrong unless we do it here + "str r0, [r4, #4]", // CTRLR1 + + "ldr r4, [r5, #20]", + "blx r4", // flash_enter_cmd_xip(); + + in("r0") &cmd as *const FlashCommand, + in("r1") ptrs, + out("r2") _, + out("r3") _, + out("r4") _, + out("r5") _, + // Registers r8-r10 are used to store values + // from r0-r2 in registers not clobbered by + // function calls. + // The values can't be passed in using r8-r10 directly + // due to https://github.com/rust-lang/rust/issues/99071 + out("r10") _, + clobber_abi("C"), + ); + } +} + +/// Make sure to uphold the contract points with rp2040-flash. +/// - interrupts must be disabled +/// - DMA must not access flash memory +pub(crate) unsafe fn in_ram(operation: impl FnOnce()) -> Result<(), Error> { + // Make sure we're running on CORE0 + let core_id: u32 = 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(|_| { + // Wait for all DMA channels in flash to finish before ram operation + const SRAM_LOWER: u32 = 0x2000_0000; + for n in 0..crate::dma::CHANNEL_COUNT { + let ch = crate::pac::DMA.ch(n); + while ch.read_addr().read() < SRAM_LOWER && ch.ctrl_trig().read().busy() {} + } + // Wait for completion of any background reads + while pac::XIP_CTRL.stream_ctr().read().0 > 0 {} + + // Run our flash operation in RAM + operation(); + }); + + // Resume CORE1 execution + crate::multicore::resume_core1(); + Ok(()) +} + +trait SealedInstance {} +trait SealedMode {} + +/// Flash instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance {} +/// Flash mode. +#[allow(private_bounds)] +pub trait Mode: SealedMode {} + +impl SealedInstance for FLASH {} +impl Instance for FLASH {} + +macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; +} + +/// Flash blocking mode. +pub struct Blocking; +/// Flash async mode. +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); diff --git a/embassy/embassy-rp/src/float/add_sub.rs b/embassy/embassy-rp/src/float/add_sub.rs new file mode 100644 index 0000000..673544c --- /dev/null +++ b/embassy/embassy-rp/src/float/add_sub.rs @@ -0,0 +1,92 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/add_sub.rs + +use super::{Float, Int}; +use crate::rom_data; + +trait ROMAdd { + fn rom_add(self, b: Self) -> Self; +} + +impl ROMAdd for f32 { + fn rom_add(self, b: Self) -> Self { + rom_data::float_funcs::fadd(self, b) + } +} + +impl ROMAdd for f64 { + fn rom_add(self, b: Self) -> Self { + rom_data::double_funcs::dadd(self, b) + } +} + +fn add(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_not_finite() { + let class_a = a.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK); + let class_b = b.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK); + + if class_a == F::Int::ZERO && class_b == F::Int::ZERO { + // inf + inf = inf + return a; + } + if class_a == F::SIGN_MASK && class_b == F::SIGN_MASK { + // -inf + (-inf) = -inf + return a; + } + + // Sign mismatch, or either is NaN already + return F::NAN; + } + + // [-]inf/NaN + X = [-]inf/NaN + return a; + } + + if b.is_not_finite() { + // X + [-]inf/NaN = [-]inf/NaN + return b; + } + + a.rom_add(b) +} + +intrinsics! { + #[alias = __addsf3vfp] + #[aeabi = __aeabi_fadd] + extern "C" fn __addsf3(a: f32, b: f32) -> f32 { + add(a, b) + } + + #[bootrom_v2] + #[alias = __adddf3vfp] + #[aeabi = __aeabi_dadd] + extern "C" fn __adddf3(a: f64, b: f64) -> f64 { + add(a, b) + } + + // The ROM just implements subtraction the same way, so just do it here + // and save the work of implementing more complicated NaN/inf handling. + + #[alias = __subsf3vfp] + #[aeabi = __aeabi_fsub] + extern "C" fn __subsf3(a: f32, b: f32) -> f32 { + add(a, -b) + } + + #[bootrom_v2] + #[alias = __subdf3vfp] + #[aeabi = __aeabi_dsub] + extern "C" fn __subdf3(a: f64, b: f64) -> f64 { + add(a, -b) + } + + extern "aapcs" fn __aeabi_frsub(a: f32, b: f32) -> f32 { + add(b, -a) + } + + #[bootrom_v2] + extern "aapcs" fn __aeabi_drsub(a: f64, b: f64) -> f64 { + add(b, -a) + } +} diff --git a/embassy/embassy-rp/src/float/cmp.rs b/embassy/embassy-rp/src/float/cmp.rs new file mode 100644 index 0000000..e540e39 --- /dev/null +++ b/embassy/embassy-rp/src/float/cmp.rs @@ -0,0 +1,201 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/cmp.rs + +use super::Float; +use crate::rom_data; + +trait ROMCmp { + fn rom_cmp(self, b: Self) -> i32; +} + +impl ROMCmp for f32 { + fn rom_cmp(self, b: Self) -> i32 { + rom_data::float_funcs::fcmp(self, b) + } +} + +impl ROMCmp for f64 { + fn rom_cmp(self, b: Self) -> i32 { + rom_data::double_funcs::dcmp(self, b) + } +} + +fn le_abi(a: F, b: F) -> i32 { + if a.is_nan() || b.is_nan() { + 1 + } else { + a.rom_cmp(b) + } +} + +fn ge_abi(a: F, b: F) -> i32 { + if a.is_nan() || b.is_nan() { + -1 + } else { + a.rom_cmp(b) + } +} + +intrinsics! { + #[slower_than_default] + #[bootrom_v2] + #[alias = __eqsf2, __ltsf2, __nesf2] + extern "C" fn __lesf2(a: f32, b: f32) -> i32 { + le_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __eqdf2, __ltdf2, __nedf2] + extern "C" fn __ledf2(a: f64, b: f64) -> i32 { + le_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __gtsf2] + extern "C" fn __gesf2(a: f32, b: f32) -> i32 { + ge_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __gtdf2] + extern "C" fn __gedf2(a: f64, b: f64) -> i32 { + ge_abi(a, b) + } + + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmple(a: f32, b: f32) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpge(a: f32, b: f32) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpeq(a: f32, b: f32) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmplt(a: f32, b: f32) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpgt(a: f32, b: f32) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmple(a: f64, b: f64) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpge(a: f64, b: f64) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpeq(a: f64, b: f64) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmplt(a: f64, b: f64) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpgt(a: f64, b: f64) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gesf2vfp(a: f32, b: f32) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gedf2vfp(a: f64, b: f64) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gtsf2vfp(a: f32, b: f32) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gtdf2vfp(a: f64, b: f64) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ltsf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ltdf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __lesf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ledf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __nesf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) != 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __nedf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) != 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __eqsf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __eqdf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) == 0) as i32 + } +} diff --git a/embassy/embassy-rp/src/float/conv.rs b/embassy/embassy-rp/src/float/conv.rs new file mode 100644 index 0000000..021826e --- /dev/null +++ b/embassy/embassy-rp/src/float/conv.rs @@ -0,0 +1,157 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/conv.rs + +use super::Float; +use crate::rom_data; + +// Some of these are also not connected in the Pico SDK. This is probably +// because the ROM version actually does a fixed point conversion, just with +// the fractional width set to zero. + +intrinsics! { + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_i2f] + extern "C" fn __floatsisf(i: i32) -> f32 { + rom_data::float_funcs::int_to_float(i) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_i2d] + extern "C" fn __floatsidf(i: i32) -> f64 { + rom_data::double_funcs::int_to_double(i) + } + + // Questionable gain + #[aeabi = __aeabi_l2f] + extern "C" fn __floatdisf(i: i64) -> f32 { + rom_data::float_funcs::int64_to_float(i) + } + + #[bootrom_v2] + #[aeabi = __aeabi_l2d] + extern "C" fn __floatdidf(i: i64) -> f64 { + rom_data::double_funcs::int64_to_double(i) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_ui2f] + extern "C" fn __floatunsisf(i: u32) -> f32 { + rom_data::float_funcs::uint_to_float(i) + } + + // Questionable gain + #[bootrom_v2] + #[aeabi = __aeabi_ui2d] + extern "C" fn __floatunsidf(i: u32) -> f64 { + rom_data::double_funcs::uint_to_double(i) + } + + // Questionable gain + #[bootrom_v2] + #[aeabi = __aeabi_ul2f] + extern "C" fn __floatundisf(i: u64) -> f32 { + rom_data::float_funcs::uint64_to_float(i) + } + + #[bootrom_v2] + #[aeabi = __aeabi_ul2d] + extern "C" fn __floatundidf(i: u64) -> f64 { + rom_data::double_funcs::uint64_to_double(i) + } + + + // The Pico SDK does some optimization here (e.x. fast paths for zero and + // one), but we can just directly connect it. + #[aeabi = __aeabi_f2iz] + extern "C" fn __fixsfsi(f: f32) -> i32 { + rom_data::float_funcs::float_to_int(f) + } + + #[bootrom_v2] + #[aeabi = __aeabi_f2lz] + extern "C" fn __fixsfdi(f: f32) -> i64 { + rom_data::float_funcs::float_to_int64(f) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2iz] + extern "C" fn __fixdfsi(f: f64) -> i32 { + rom_data::double_funcs::double_to_int(f) + } + + // Like with the 32 bit version, there's optimization that we just + // skip. + #[bootrom_v2] + #[aeabi = __aeabi_d2lz] + extern "C" fn __fixdfdi(f: f64) -> i64 { + rom_data::double_funcs::double_to_int64(f) + } + + #[slower_than_default] + #[aeabi = __aeabi_f2uiz] + extern "C" fn __fixunssfsi(f: f32) -> u32 { + rom_data::float_funcs::float_to_uint(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_f2ulz] + extern "C" fn __fixunssfdi(f: f32) -> u64 { + rom_data::float_funcs::float_to_uint64(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2uiz] + extern "C" fn __fixunsdfsi(f: f64) -> u32 { + rom_data::double_funcs::double_to_uint(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2ulz] + extern "C" fn __fixunsdfdi(f: f64) -> u64 { + rom_data::double_funcs::double_to_uint64(f) + } + + #[bootrom_v2] + #[alias = __extendsfdf2vfp] + #[aeabi = __aeabi_f2d] + extern "C" fn __extendsfdf2(f: f32) -> f64 { + if f.is_not_finite() { + return f64::from_repr( + // Not finite + f64::EXPONENT_MASK | + // Preserve NaN or inf + ((f.repr() & f32::SIGNIFICAND_MASK) as u64) | + // Preserve sign + ((f.repr() & f32::SIGN_MASK) as u64) << (f64::BITS-f32::BITS) + ); + } + rom_data::float_funcs::float_to_double(f) + } + + #[bootrom_v2] + #[alias = __truncdfsf2vfp] + #[aeabi = __aeabi_d2f] + extern "C" fn __truncdfsf2(f: f64) -> f32 { + if f.is_not_finite() { + let mut repr: u32 = + // Not finite + f32::EXPONENT_MASK | + // Preserve sign + ((f.repr() & f64::SIGN_MASK) >> (f64::BITS-f32::BITS)) as u32; + // Set NaN + if (f.repr() & f64::SIGNIFICAND_MASK) != 0 { + repr |= 1; + } + return f32::from_repr(repr); + } + rom_data::double_funcs::double_to_float(f) + } +} diff --git a/embassy/embassy-rp/src/float/div.rs b/embassy/embassy-rp/src/float/div.rs new file mode 100644 index 0000000..aff0dcb --- /dev/null +++ b/embassy/embassy-rp/src/float/div.rs @@ -0,0 +1,139 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/conv.rs + +use super::Float; +use crate::rom_data; + +// Make sure this stays as a separate call, because when it's inlined the +// compiler will move the save of the registers used to contain the divider +// state into the function prologue. That save and restore (push/pop) takes +// longer than the actual division, so doing it in the common case where +// they are not required wastes a lot of time. +#[inline(never)] +#[cold] +fn save_divider_and_call(f: F) -> R +where + F: FnOnce() -> R, +{ + let sio = rp_pac::SIO; + + // Since we can't save the signed-ness of the calculation, we have to make + // sure that there's at least an 8 cycle delay before we read the result. + // The Pico SDK ensures this by using a 6 cycle push and two 1 cycle reads. + // Since we can't be sure the Rust implementation will optimize to the same, + // just use an explicit wait. + while !sio.div().csr().read().ready() {} + + // Read the quotient last, since that's what clears the dirty flag + let dividend = sio.div().udividend().read(); + let divisor = sio.div().udivisor().read(); + let remainder = sio.div().remainder().read(); + let quotient = sio.div().quotient().read(); + + // If we get interrupted here (before a write sets the DIRTY flag) its fine, since + // we have the full state, so the interruptor doesn't have to restore it. Once the + // write happens and the DIRTY flag is set, the interruptor becomes responsible for + // restoring our state. + let result = f(); + + // If we are interrupted here, then the interruptor will start an incorrect calculation + // using a wrong divisor, but we'll restore the divisor and result ourselves correctly. + // This sets DIRTY, so any interruptor will save the state. + sio.div().udividend().write_value(dividend); + // If we are interrupted here, the the interruptor may start the calculation using + // incorrectly signed inputs, but we'll restore the result ourselves. + // This sets DIRTY, so any interruptor will save the state. + sio.div().udivisor().write_value(divisor); + // If we are interrupted here, the interruptor will have restored everything but the + // quotient may be wrongly signed. If the calculation started by the above writes is + // still ongoing it is stopped, so it won't replace the result we're restoring. + // DIRTY and READY set, but only DIRTY matters to make the interruptor save the state. + sio.div().remainder().write_value(remainder); + // State fully restored after the quotient write. This sets both DIRTY and READY, so + // whatever we may have interrupted can read the result. + sio.div().quotient().write_value(quotient); + + result +} + +fn save_divider(f: F) -> R +where + F: FnOnce() -> R, +{ + let sio = rp_pac::SIO; + if !sio.div().csr().read().dirty() { + // Not dirty, so nothing is waiting for the calculation. So we can just + // issue it directly without a save/restore. + f() + } else { + save_divider_and_call(f) + } +} + +trait ROMDiv { + fn rom_div(self, b: Self) -> Self; +} + +impl ROMDiv for f32 { + fn rom_div(self, b: Self) -> Self { + // ROM implementation uses the hardware divider, so we have to save it + save_divider(|| rom_data::float_funcs::fdiv(self, b)) + } +} + +impl ROMDiv for f64 { + fn rom_div(self, b: Self) -> Self { + // ROM implementation uses the hardware divider, so we have to save it + save_divider(|| rom_data::double_funcs::ddiv(self, b)) + } +} + +fn div(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_not_finite() { + // inf/NaN / inf/NaN = NaN + return F::NAN; + } + + if b.is_zero() { + // inf/NaN / 0 = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // [+/-]inf/NaN / (-X) = [-/+]inf/NaN + a.negate() + } else { + // [-]inf/NaN / X = [-]inf/NaN + a + }; + } + + if b.is_nan() { + // X / NaN = NaN + return b; + } + + // ROM handles X / 0 = [-]inf and X / [-]inf = [-]0, so we only + // need to catch 0 / 0 + if b.is_zero() && a.is_zero() { + return F::NAN; + } + + a.rom_div(b) +} + +intrinsics! { + #[alias = __divsf3vfp] + #[aeabi = __aeabi_fdiv] + extern "C" fn __divsf3(a: f32, b: f32) -> f32 { + div(a, b) + } + + #[bootrom_v2] + #[alias = __divdf3vfp] + #[aeabi = __aeabi_ddiv] + extern "C" fn __divdf3(a: f64, b: f64) -> f64 { + div(a, b) + } +} diff --git a/embassy/embassy-rp/src/float/functions.rs b/embassy/embassy-rp/src/float/functions.rs new file mode 100644 index 0000000..de29ce3 --- /dev/null +++ b/embassy/embassy-rp/src/float/functions.rs @@ -0,0 +1,239 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/functions.rs + +use crate::float::{Float, Int}; +use crate::rom_data; + +trait ROMFunctions { + fn sqrt(self) -> Self; + fn ln(self) -> Self; + fn exp(self) -> Self; + fn sin(self) -> Self; + fn cos(self) -> Self; + fn tan(self) -> Self; + fn atan2(self, y: Self) -> Self; + + fn to_trig_range(self) -> Self; +} + +impl ROMFunctions for f32 { + fn sqrt(self) -> Self { + rom_data::float_funcs::fsqrt(self) + } + + fn ln(self) -> Self { + rom_data::float_funcs::fln(self) + } + + fn exp(self) -> Self { + rom_data::float_funcs::fexp(self) + } + + fn sin(self) -> Self { + rom_data::float_funcs::fsin(self) + } + + fn cos(self) -> Self { + rom_data::float_funcs::fcos(self) + } + + fn tan(self) -> Self { + rom_data::float_funcs::ftan(self) + } + + fn atan2(self, y: Self) -> Self { + rom_data::float_funcs::fatan2(self, y) + } + + fn to_trig_range(self) -> Self { + // -128 < X < 128, logic from the Pico SDK + let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS; + if exponent < 134 { + self + } else { + self % (core::f32::consts::PI * 2.0) + } + } +} + +impl ROMFunctions for f64 { + fn sqrt(self) -> Self { + rom_data::double_funcs::dsqrt(self) + } + + fn ln(self) -> Self { + rom_data::double_funcs::dln(self) + } + + fn exp(self) -> Self { + rom_data::double_funcs::dexp(self) + } + + fn sin(self) -> Self { + rom_data::double_funcs::dsin(self) + } + + fn cos(self) -> Self { + rom_data::double_funcs::dcos(self) + } + fn tan(self) -> Self { + rom_data::double_funcs::dtan(self) + } + + fn atan2(self, y: Self) -> Self { + rom_data::double_funcs::datan2(self, y) + } + + fn to_trig_range(self) -> Self { + // -1024 < X < 1024, logic from the Pico SDK + let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS; + if exponent < 1033 { + self + } else { + self % (core::f64::consts::PI * 2.0) + } + } +} + +fn is_negative_nonzero_or_nan(f: F) -> bool { + let repr = f.repr(); + if (repr & F::SIGN_MASK) != F::Int::ZERO { + // Negative, so anything other than exactly zero + return (repr & (!F::SIGN_MASK)) != F::Int::ZERO; + } + // NaN + (repr & (F::EXPONENT_MASK | F::SIGNIFICAND_MASK)) > F::EXPONENT_MASK +} + +fn sqrt(f: F) -> F { + if is_negative_nonzero_or_nan(f) { + F::NAN + } else { + f.sqrt() + } +} + +fn ln(f: F) -> F { + if is_negative_nonzero_or_nan(f) { + F::NAN + } else { + f.ln() + } +} + +fn exp(f: F) -> F { + if f.is_nan() { + F::NAN + } else { + f.exp() + } +} + +fn sin(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().sin() + } +} + +fn cos(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().cos() + } +} + +fn tan(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().tan() + } +} + +fn atan2(x: F, y: F) -> F { + if x.is_nan() || y.is_nan() { + F::NAN + } else { + x.to_trig_range().atan2(y) + } +} + +// Name collisions +mod intrinsics { + intrinsics! { + extern "C" fn sqrtf(f: f32) -> f32 { + super::sqrt(f) + } + + #[bootrom_v2] + extern "C" fn sqrt(f: f64) -> f64 { + super::sqrt(f) + } + + extern "C" fn logf(f: f32) -> f32 { + super::ln(f) + } + + #[bootrom_v2] + extern "C" fn log(f: f64) -> f64 { + super::ln(f) + } + + extern "C" fn expf(f: f32) -> f32 { + super::exp(f) + } + + #[bootrom_v2] + extern "C" fn exp(f: f64) -> f64 { + super::exp(f) + } + + #[slower_than_default] + extern "C" fn sinf(f: f32) -> f32 { + super::sin(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn sin(f: f64) -> f64 { + super::sin(f) + } + + #[slower_than_default] + extern "C" fn cosf(f: f32) -> f32 { + super::cos(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn cos(f: f64) -> f64 { + super::cos(f) + } + + #[slower_than_default] + extern "C" fn tanf(f: f32) -> f32 { + super::tan(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn tan(f: f64) -> f64 { + super::tan(f) + } + + // Questionable gain + #[bootrom_v2] + extern "C" fn atan2f(a: f32, b: f32) -> f32 { + super::atan2(a, b) + } + + // Questionable gain + #[bootrom_v2] + extern "C" fn atan2(a: f64, b: f64) -> f64 { + super::atan2(a, b) + } + } +} diff --git a/embassy/embassy-rp/src/float/mod.rs b/embassy/embassy-rp/src/float/mod.rs new file mode 100644 index 0000000..3ad6f1c --- /dev/null +++ b/embassy/embassy-rp/src/float/mod.rs @@ -0,0 +1,150 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/mod.rs + +use core::ops; + +// Borrowed and simplified from compiler-builtins so we can use bit ops +// on floating point without macro soup. +pub(crate) trait Int: + Copy + + core::fmt::Debug + + PartialEq + + PartialOrd + + ops::AddAssign + + ops::SubAssign + + ops::BitAndAssign + + ops::BitOrAssign + + ops::BitXorAssign + + ops::ShlAssign + + ops::ShrAssign + + ops::Add + + ops::Sub + + ops::Div + + ops::Shl + + ops::Shr + + ops::BitOr + + ops::BitXor + + ops::BitAnd + + ops::Not +{ + const ZERO: Self; +} + +macro_rules! int_impl { + ($ty:ty) => { + impl Int for $ty { + const ZERO: Self = 0; + } + }; +} + +int_impl!(u32); +int_impl!(u64); + +pub(crate) trait Float: + Copy + + core::fmt::Debug + + PartialEq + + PartialOrd + + ops::AddAssign + + ops::MulAssign + + ops::Add + + ops::Sub + + ops::Div + + ops::Rem +{ + /// A uint of the same with as the float + type Int: Int; + + /// NaN representation for the float + const NAN: Self; + + /// The bitwidth of the float type + const BITS: u32; + + /// The bitwidth of the significand + const SIGNIFICAND_BITS: u32; + + /// A mask for the sign bit + const SIGN_MASK: Self::Int; + + /// A mask for the significand + const SIGNIFICAND_MASK: Self::Int; + + /// A mask for the exponent + const EXPONENT_MASK: Self::Int; + + /// Returns `self` transmuted to `Self::Int` + fn repr(self) -> Self::Int; + + /// Returns a `Self::Int` transmuted back to `Self` + fn from_repr(a: Self::Int) -> Self; + + /// Return a sign swapped `self` + fn negate(self) -> Self; + + /// Returns true if `self` is either NaN or infinity + fn is_not_finite(self) -> bool { + (self.repr() & Self::EXPONENT_MASK) == Self::EXPONENT_MASK + } + + /// Returns true if `self` is infinity + #[allow(unused)] + fn is_infinity(self) -> bool { + (self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) == Self::EXPONENT_MASK + } + + /// Returns true if `self is NaN + fn is_nan(self) -> bool { + (self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) > Self::EXPONENT_MASK + } + + /// Returns true if `self` is negative + fn is_sign_negative(self) -> bool { + (self.repr() & Self::SIGN_MASK) != Self::Int::ZERO + } + + /// Returns true if `self` is zero (either sign) + fn is_zero(self) -> bool { + (self.repr() & (Self::SIGNIFICAND_MASK | Self::EXPONENT_MASK)) == Self::Int::ZERO + } +} + +macro_rules! float_impl { + ($ty:ident, $ity:ident, $bits:expr, $significand_bits:expr) => { + impl Float for $ty { + type Int = $ity; + + const NAN: Self = <$ty>::NAN; + + const BITS: u32 = $bits; + const SIGNIFICAND_BITS: u32 = $significand_bits; + + const SIGN_MASK: Self::Int = 1 << (Self::BITS - 1); + const SIGNIFICAND_MASK: Self::Int = (1 << Self::SIGNIFICAND_BITS) - 1; + const EXPONENT_MASK: Self::Int = !(Self::SIGN_MASK | Self::SIGNIFICAND_MASK); + + fn repr(self) -> Self::Int { + self.to_bits() + } + + fn from_repr(a: Self::Int) -> Self { + Self::from_bits(a) + } + + fn negate(self) -> Self { + -self + } + } + }; +} + +float_impl!(f32, u32, 32, 23); +float_impl!(f64, u64, 64, 52); + +mod add_sub; +mod cmp; +mod conv; +mod div; +mod functions; +mod mul; diff --git a/embassy/embassy-rp/src/float/mul.rs b/embassy/embassy-rp/src/float/mul.rs new file mode 100644 index 0000000..ceb0210 --- /dev/null +++ b/embassy/embassy-rp/src/float/mul.rs @@ -0,0 +1,70 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/mul.rs + +use super::Float; +use crate::rom_data; + +trait ROMMul { + fn rom_mul(self, b: Self) -> Self; +} + +impl ROMMul for f32 { + fn rom_mul(self, b: Self) -> Self { + rom_data::float_funcs::fmul(self, b) + } +} + +impl ROMMul for f64 { + fn rom_mul(self, b: Self) -> Self { + rom_data::double_funcs::dmul(self, b) + } +} + +fn mul(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_zero() { + // [-]inf/NaN * 0 = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // [+/-]inf/NaN * (-X) = [-/+]inf/NaN + a.negate() + } else { + // [-]inf/NaN * X = [-]inf/NaN + a + }; + } + + if b.is_not_finite() { + if a.is_zero() { + // 0 * [-]inf/NaN = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // (-X) * [+/-]inf/NaN = [-/+]inf/NaN + b.negate() + } else { + // X * [-]inf/NaN = [-]inf/NaN + b + }; + } + + a.rom_mul(b) +} + +intrinsics! { + #[alias = __mulsf3vfp] + #[aeabi = __aeabi_fmul] + extern "C" fn __mulsf3(a: f32, b: f32) -> f32 { + mul(a, b) + } + + #[bootrom_v2] + #[alias = __muldf3vfp] + #[aeabi = __aeabi_dmul] + extern "C" fn __muldf3(a: f64, b: f64) -> f64 { + mul(a, b) + } +} diff --git a/embassy/embassy-rp/src/fmt.rs b/embassy/embassy-rp/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-rp/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-rp/src/gpio.rs b/embassy/embassy-rp/src/gpio.rs new file mode 100644 index 0000000..2031928 --- /dev/null +++ b/embassy/embassy-rp/src/gpio.rs @@ -0,0 +1,1379 @@ +//! GPIO driver. +#![macro_use] +use core::convert::Infallible; +use core::future::Future; +use core::pin::Pin as FuturePin; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::interrupt::InterruptExt; +use crate::pac::common::{Reg, RW}; +use crate::pac::SIO; +use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; + +#[cfg(any(feature = "rp2040", feature = "rp235xa"))] +pub(crate) const BANK0_PIN_COUNT: usize = 30; +#[cfg(feature = "rp235xb")] +pub(crate) const BANK0_PIN_COUNT: usize = 48; + +static BANK0_WAKERS: [AtomicWaker; BANK0_PIN_COUNT] = [const { AtomicWaker::new() }; BANK0_PIN_COUNT]; +#[cfg(feature = "qspi-as-gpio")] +const QSPI_PIN_COUNT: usize = 6; +#[cfg(feature = "qspi-as-gpio")] +static QSPI_WAKERS: [AtomicWaker; QSPI_PIN_COUNT] = [const { AtomicWaker::new() }; QSPI_PIN_COUNT]; + +/// Represents a digital input or output level. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Level { + /// Logical low. + Low, + /// Logical high. + High, +} + +impl From for Level { + fn from(val: bool) -> Self { + match val { + true => Self::High, + false => Self::Low, + } + } +} + +impl From for bool { + fn from(level: Level) -> bool { + match level { + Level::Low => false, + Level::High => true, + } + } +} + +/// Represents a pull setting for an input. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Pull { + /// No pull. + None, + /// Internal pull-up resistor. + Up, + /// Internal pull-down resistor. + Down, +} + +/// Drive strength of an output +#[derive(Debug, Eq, PartialEq)] +pub enum Drive { + /// 2 mA drive. + _2mA, + /// 4 mA drive. + _4mA, + /// 8 mA drive. + _8mA, + /// 1 2mA drive. + _12mA, +} +/// Slew rate of an output +#[derive(Debug, Eq, PartialEq)] +pub enum SlewRate { + /// Fast slew rate. + Fast, + /// Slow slew rate. + Slow, +} + +/// A GPIO bank with up to 32 pins. +#[derive(Debug, Eq, PartialEq)] +pub enum Bank { + /// Bank 0. + Bank0 = 0, + /// QSPI. + #[cfg(feature = "qspi-as-gpio")] + Qspi = 1, +} + +/// Dormant mode config. +#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DormantWakeConfig { + /// Wake on edge high. + pub edge_high: bool, + /// Wake on edge low. + pub edge_low: bool, + /// Wake on level high. + pub level_high: bool, + /// Wake on level low. + pub level_low: bool, +} + +/// GPIO input driver. +pub struct Input<'d> { + pin: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, pull: Pull) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_input(); + pin.set_pull(pull); + Self { pin } + } + + /// Set the pin's Schmitt trigger. + #[inline] + pub fn set_schmitt(&mut self, enable: bool) { + self.pin.set_schmitt(enable) + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Returns current pin level + #[inline] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await; + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await; + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await; + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await; + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await; + } + + /// Configure dormant wake. + #[inline] + pub fn dormant_wake(&mut self, cfg: DormantWakeConfig) -> DormantWake<'_> { + self.pin.dormant_wake(cfg) + } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.set_pad_isolation(isolate) + } +} + +/// Interrupt trigger levels. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InterruptTrigger { + /// Trigger on pin low. + LevelLow, + /// Trigger on pin high. + LevelHigh, + /// Trigger on high to low transition. + EdgeLow, + /// Trigger on low to high transition. + EdgeHigh, + /// Trigger on any transition. + AnyEdge, +} + +pub(crate) unsafe fn init() { + interrupt::IO_IRQ_BANK0.disable(); + interrupt::IO_IRQ_BANK0.set_priority(interrupt::Priority::P3); + interrupt::IO_IRQ_BANK0.enable(); + + #[cfg(feature = "qspi-as-gpio")] + { + interrupt::IO_IRQ_QSPI.disable(); + interrupt::IO_IRQ_QSPI.set_priority(interrupt::Priority::P3); + interrupt::IO_IRQ_QSPI.enable(); + } +} + +#[cfg(feature = "rt")] +fn irq_handler(bank: pac::io::Io, wakers: &[AtomicWaker; N]) { + let cpu = SIO.cpuid().read() as usize; + // There are two sets of interrupt registers, one for cpu0 and one for cpu1 + // and here we are selecting the set that belongs to the currently executing + // cpu. + let proc_intx: pac::io::Int = bank.int_proc(cpu); + for pin in 0..N { + // There are 4 raw interrupt status registers, PROCx_INTS0, PROCx_INTS1, + // PROCx_INTS2, and PROCx_INTS3, and we are selecting the one that the + // current pin belongs to. + let intsx = proc_intx.ints(pin / 8); + // The status register is divided into groups of four, one group for + // each pin. Each group consists of four trigger levels LEVEL_LOW, + // LEVEL_HIGH, EDGE_LOW, and EDGE_HIGH for each pin. + let pin_group = pin % 8; + let event = (intsx.read().0 >> (pin_group * 4)) & 0xf; + + // no more than one event can be awaited per pin at any given time, so + // we can just clear all interrupt enables for that pin without having + // to check which event was signalled. + if event != 0 { + proc_intx.inte(pin / 8).write_clear(|w| { + w.set_edge_high(pin_group, true); + w.set_edge_low(pin_group, true); + w.set_level_high(pin_group, true); + w.set_level_low(pin_group, true); + }); + wakers[pin].wake(); + } + } +} + +#[cfg(feature = "rt")] +#[interrupt] +fn IO_IRQ_BANK0() { + irq_handler(pac::IO_BANK0, &BANK0_WAKERS); +} + +#[cfg(all(feature = "rt", feature = "qspi-as-gpio"))] +#[interrupt] +fn IO_IRQ_QSPI() { + irq_handler(pac::IO_QSPI, &QSPI_WAKERS); +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct InputFuture<'d> { + pin: PeripheralRef<'d, AnyPin>, +} + +impl<'d> InputFuture<'d> { + fn new(pin: PeripheralRef<'d, AnyPin>, level: InterruptTrigger) -> Self { + let pin_group = (pin.pin() % 8) as usize; + // first, clear the INTR register bits. without this INTR will still + // contain reports of previous edges, causing the IRQ to fire early + // on stale state. clearing these means that we can only detect edges + // that occur *after* the clear happened, but since both this and the + // alternative are fundamentally racy it's probably fine. + // (the alternative being checking the current level and waiting for + // its inverse, but that requires reading the current level and thus + // missing anything that happened before the level was read.) + pin.io().intr(pin.pin() as usize / 8).write(|w| { + w.set_edge_high(pin_group, true); + w.set_edge_low(pin_group, true); + }); + + // Each INTR register is divided into 8 groups, one group for each + // pin, and each group consists of LEVEL_LOW, LEVEL_HIGH, EDGE_LOW, + // and EGDE_HIGH. + pin.int_proc() + .inte((pin.pin() / 8) as usize) + .write_set(|w| match level { + InterruptTrigger::LevelHigh => { + w.set_level_high(pin_group, true); + } + InterruptTrigger::LevelLow => { + w.set_level_low(pin_group, true); + } + InterruptTrigger::EdgeHigh => { + w.set_edge_high(pin_group, true); + } + InterruptTrigger::EdgeLow => { + w.set_edge_low(pin_group, true); + } + InterruptTrigger::AnyEdge => { + w.set_edge_high(pin_group, true); + w.set_edge_low(pin_group, true); + } + }); + + Self { pin } + } +} + +impl<'d> Future for InputFuture<'d> { + type Output = (); + + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We need to register/re-register the waker for each poll because any + // calls to wake will deregister the waker. + let waker = match self.pin.bank() { + Bank::Bank0 => &BANK0_WAKERS[self.pin.pin() as usize], + #[cfg(feature = "qspi-as-gpio")] + Bank::Qspi => &QSPI_WAKERS[self.pin.pin() as usize], + }; + waker.register(cx.waker()); + + // self.int_proc() will get the register offset for the current cpu, + // then we want to access the interrupt enable register for our + // pin (there are 4 of these PROC0_INTE0, PROC0_INTE1, PROC0_INTE2, and + // PROC0_INTE3 per cpu). + let inte: pac::io::regs::Int = self.pin.int_proc().inte((self.pin.pin() / 8) as usize).read(); + // The register is divided into groups of four, one group for + // each pin. Each group consists of four trigger levels LEVEL_LOW, + // LEVEL_HIGH, EDGE_LOW, and EDGE_HIGH for each pin. + let pin_group = (self.pin.pin() % 8) as usize; + + // since the interrupt handler clears all INTE flags we'll check that + // all have been cleared and unconditionally return Ready(()) if so. + // we don't need further handshaking since only a single event wait + // is possible for any given pin at any given time. + if !inte.edge_high(pin_group) + && !inte.edge_low(pin_group) + && !inte.level_high(pin_group) + && !inte.level_low(pin_group) + { + return Poll::Ready(()); + } + Poll::Pending + } +} + +/// GPIO output driver. +pub struct Output<'d> { + pin: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create GPIO output driver for a [Pin] with the provided [Level]. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + match initial_output { + Level::High => pin.set_high(), + Level::Low => pin.set_low(), + } + + pin.set_as_output(); + Self { pin } + } + + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + self.pin.set_drive_strength(strength) + } + + /// Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.set_slew_rate(slew_rate) + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high() + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low() + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level) + } + + /// Is the output pin set as high? + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Is the output pin set as low? + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } + + /// What level output is set to + #[inline] + pub fn get_output_level(&self) -> Level { + self.pin.get_output_level() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle() + } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.set_pad_isolation(isolate) + } +} + +/// GPIO output open-drain. +pub struct OutputOpenDrain<'d> { + pin: Flex<'d>, +} + +impl<'d> OutputOpenDrain<'d> { + /// Create GPIO output driver for a [Pin] in open drain mode with the provided [Level]. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + pin.set_low(); + match initial_output { + Level::High => pin.set_as_input(), + Level::Low => pin.set_as_output(), + } + Self { pin } + } + + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + self.pin.set_drive_strength(strength) + } + + /// Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.set_slew_rate(slew_rate) + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + // For Open Drain High, disable the output pin. + self.pin.set_as_input() + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + // For Open Drain Low, enable the output pin. + self.pin.set_as_output() + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + match level { + Level::Low => self.set_low(), + Level::High => self.set_high(), + } + } + + /// Is the output level high? + #[inline] + pub fn is_set_high(&self) -> bool { + !self.is_set_low() + } + + /// Is the output level low? + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_as_output() + } + + /// What level output is set to + #[inline] + pub fn get_output_level(&self) -> Level { + self.is_set_high().into() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle_set_as_output() + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Returns current pin level + #[inline] + pub fn get_level(&self) -> Level { + self.is_high().into() + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await; + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await; + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await; + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await; + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await; + } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.set_pad_isolation(isolate) + } +} + +/// GPIO flexible pin. +/// +/// This pin can be either an input or output pin. The output level register bit will remain +/// set while not in output mode, so the pin's level will be 'remembered' when it is not in output +/// mode. +pub struct Flex<'d> { + pin: PeripheralRef<'d, AnyPin>, +} + +impl<'d> Flex<'d> { + /// Wrap the pin in a `Flex`. + /// + /// The pin remains disconnected. The initial output level is unspecified, but can be changed + /// before the pin is put into output mode. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd) -> Self { + into_ref!(pin); + + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + + pin.gpio().ctrl().write(|w| { + #[cfg(feature = "rp2040")] + w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIO_0 as _); + #[cfg(feature = "_rp235x")] + w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIOB_PROC_0 as _); + }); + + Self { pin: pin.map_into() } + } + + #[inline] + fn bit(&self) -> u32 { + 1 << (self.pin.pin() % 32) + } + + /// Set the pin's pull. + #[inline] + pub fn set_pull(&mut self, pull: Pull) { + self.pin.pad_ctrl().modify(|w| { + w.set_ie(true); + 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) { + 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) { + self.pin.pad_ctrl().modify(|w| { + w.set_slewfast(slew_rate == SlewRate::Fast); + }); + } + + /// Set the pin's Schmitt trigger. + #[inline] + pub fn set_schmitt(&mut self, enable: bool) { + self.pin.pad_ctrl().modify(|w| { + w.set_schmitt(enable); + }); + } + + /// Put the pin into input mode. + /// + /// The pull setting is left unchanged. + #[inline] + pub fn set_as_input(&mut self) { + self.pin.sio_oe().value_clr().write_value(self.bit()) + } + + /// Put the pin into output mode. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + #[inline] + pub fn set_as_output(&mut self) { + self.pin.sio_oe().value_set().write_value(self.bit()) + } + + /// Set as output pin. + #[inline] + fn is_set_as_output(&self) -> bool { + (self.pin.sio_oe().value().read() & self.bit()) != 0 + } + + /// Toggle output pin. + #[inline] + pub fn toggle_set_as_output(&mut self) { + self.pin.sio_oe().value_xor().write_value(self.bit()) + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + !self.is_low() + } + /// Get whether the pin input level is low. + + #[inline] + pub fn is_low(&self) -> bool { + self.pin.sio_in().read() & self.bit() == 0 + } + + /// Returns current pin level + #[inline] + pub fn get_level(&self) -> Level { + self.is_high().into() + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.sio_out().value_set().write_value(self.bit()) + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.sio_out().value_clr().write_value(self.bit()) + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + match level { + Level::Low => self.set_low(), + Level::High => self.set_high(), + } + } + + /// Is the output level high? + #[inline] + pub fn is_set_high(&self) -> bool { + !self.is_set_low() + } + + /// Is the output level low? + #[inline] + pub fn is_set_low(&self) -> bool { + (self.pin.sio_out().value().read() & self.bit()) == 0 + } + + /// What level output is set to + #[inline] + pub fn get_output_level(&self) -> Level { + self.is_set_high().into() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.sio_out().value_xor().write_value(self.bit()) + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptTrigger::LevelHigh).await; + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptTrigger::LevelLow).await; + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptTrigger::EdgeHigh).await; + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptTrigger::EdgeLow).await; + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptTrigger::AnyEdge).await; + } + + /// Configure dormant wake. + #[inline] + pub fn dormant_wake(&mut self, cfg: DormantWakeConfig) -> DormantWake<'_> { + let idx = self.pin._pin() as usize; + self.pin.io().intr(idx / 8).write(|w| { + w.set_edge_high(idx % 8, cfg.edge_high); + w.set_edge_low(idx % 8, cfg.edge_low); + }); + self.pin.io().int_dormant_wake().inte(idx / 8).write_set(|w| { + w.set_edge_high(idx % 8, cfg.edge_high); + w.set_edge_low(idx % 8, cfg.edge_low); + w.set_level_high(idx % 8, cfg.level_high); + w.set_level_low(idx % 8, cfg.level_low); + }); + DormantWake { + pin: self.pin.reborrow(), + cfg, + } + } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.pad_ctrl().modify(|w| { + w.set_iso(isolate); + }); + } +} + +impl<'d> Drop for Flex<'d> { + #[inline] + fn drop(&mut self) { + let idx = self.pin._pin() as usize; + self.pin.pad_ctrl().write(|_| {}); + self.pin.gpio().ctrl().write(|w| { + w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _); + }); + self.pin.io().int_dormant_wake().inte(idx / 8).write_clear(|w| { + w.set_edge_high(idx % 8, true); + w.set_edge_low(idx % 8, true); + w.set_level_high(idx % 8, true); + w.set_level_low(idx % 8, true); + }); + } +} + +/// Dormant wake driver. +pub struct DormantWake<'w> { + pin: PeripheralRef<'w, AnyPin>, + cfg: DormantWakeConfig, +} + +impl<'w> Drop for DormantWake<'w> { + fn drop(&mut self) { + let idx = self.pin._pin() as usize; + self.pin.io().intr(idx / 8).write(|w| { + w.set_edge_high(idx % 8, self.cfg.edge_high); + w.set_edge_low(idx % 8, self.cfg.edge_low); + }); + self.pin.io().int_dormant_wake().inte(idx / 8).write_clear(|w| { + w.set_edge_high(idx % 8, true); + w.set_edge_low(idx % 8, true); + w.set_level_high(idx % 8, true); + w.set_level_low(idx % 8, true); + }); + } +} + +pub(crate) trait SealedPin: Sized { + fn pin_bank(&self) -> u8; + + #[inline] + fn _pin(&self) -> u8 { + self.pin_bank() & 0x7f + } + + #[inline] + fn _bank(&self) -> Bank { + match self.pin_bank() >> 7 { + #[cfg(feature = "qspi-as-gpio")] + 1 => Bank::Qspi, + _ => Bank::Bank0, + } + } + + fn io(&self) -> pac::io::Io { + match self._bank() { + Bank::Bank0 => crate::pac::IO_BANK0, + #[cfg(feature = "qspi-as-gpio")] + Bank::Qspi => crate::pac::IO_QSPI, + } + } + + fn gpio(&self) -> pac::io::Gpio { + self.io().gpio(self._pin() as _) + } + + fn pad_ctrl(&self) -> Reg { + let block = match self._bank() { + Bank::Bank0 => crate::pac::PADS_BANK0, + #[cfg(feature = "qspi-as-gpio")] + Bank::Qspi => crate::pac::PADS_QSPI, + }; + block.gpio(self._pin() as _) + } + + fn sio_out(&self) -> pac::sio::Gpio { + if cfg!(feature = "rp2040") { + SIO.gpio_out(self._bank() as _) + } else { + SIO.gpio_out((self._pin() / 32) as _) + } + } + + fn sio_oe(&self) -> pac::sio::Gpio { + if cfg!(feature = "rp2040") { + SIO.gpio_oe(self._bank() as _) + } else { + SIO.gpio_oe((self._pin() / 32) as _) + } + } + + fn sio_in(&self) -> Reg { + if cfg!(feature = "rp2040") { + SIO.gpio_in(self._bank() as _) + } else { + SIO.gpio_in((self._pin() / 32) as _) + } + } + + fn int_proc(&self) -> pac::io::Int { + let proc = SIO.cpuid().read(); + self.io().int_proc(proc as _) + } +} + +/// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an [AnyPin]. +#[allow(private_bounds)] +pub trait Pin: Peripheral

+ Into + SealedPin + Sized + 'static { + /// Degrade to a generic pin struct + fn degrade(self) -> AnyPin { + AnyPin { + pin_bank: self.pin_bank(), + } + } + + /// Returns the pin number within a bank + #[inline] + fn pin(&self) -> u8 { + self._pin() + } + + /// Returns the bank of this pin + #[inline] + fn bank(&self) -> Bank { + self._bank() + } +} + +/// Type-erased GPIO pin +pub struct AnyPin { + pin_bank: u8, +} + +impl AnyPin { + /// Unsafely create a new type-erased pin. + /// + /// # Safety + /// + /// You must ensure that you’re only using one instance of this type at a time. + pub unsafe fn steal(pin_bank: u8) -> Self { + Self { pin_bank } + } +} + +impl_peripheral!(AnyPin); + +impl Pin for AnyPin {} +impl SealedPin for AnyPin { + fn pin_bank(&self) -> u8 { + self.pin_bank + } +} + +// ========================== + +macro_rules! impl_pin { + ($name:ident, $bank:expr, $pin_num:expr) => { + impl Pin for peripherals::$name {} + impl SealedPin for peripherals::$name { + #[inline] + fn pin_bank(&self) -> u8 { + ($bank as u8) * 128 + $pin_num + } + } + + impl From for crate::gpio::AnyPin { + fn from(val: peripherals::$name) -> Self { + crate::gpio::Pin::degrade(val) + } + } + }; +} + +impl_pin!(PIN_0, Bank::Bank0, 0); +impl_pin!(PIN_1, Bank::Bank0, 1); +impl_pin!(PIN_2, Bank::Bank0, 2); +impl_pin!(PIN_3, Bank::Bank0, 3); +impl_pin!(PIN_4, Bank::Bank0, 4); +impl_pin!(PIN_5, Bank::Bank0, 5); +impl_pin!(PIN_6, Bank::Bank0, 6); +impl_pin!(PIN_7, Bank::Bank0, 7); +impl_pin!(PIN_8, Bank::Bank0, 8); +impl_pin!(PIN_9, Bank::Bank0, 9); +impl_pin!(PIN_10, Bank::Bank0, 10); +impl_pin!(PIN_11, Bank::Bank0, 11); +impl_pin!(PIN_12, Bank::Bank0, 12); +impl_pin!(PIN_13, Bank::Bank0, 13); +impl_pin!(PIN_14, Bank::Bank0, 14); +impl_pin!(PIN_15, Bank::Bank0, 15); +impl_pin!(PIN_16, Bank::Bank0, 16); +impl_pin!(PIN_17, Bank::Bank0, 17); +impl_pin!(PIN_18, Bank::Bank0, 18); +impl_pin!(PIN_19, Bank::Bank0, 19); +impl_pin!(PIN_20, Bank::Bank0, 20); +impl_pin!(PIN_21, Bank::Bank0, 21); +impl_pin!(PIN_22, Bank::Bank0, 22); +impl_pin!(PIN_23, Bank::Bank0, 23); +impl_pin!(PIN_24, Bank::Bank0, 24); +impl_pin!(PIN_25, Bank::Bank0, 25); +impl_pin!(PIN_26, Bank::Bank0, 26); +impl_pin!(PIN_27, Bank::Bank0, 27); +impl_pin!(PIN_28, Bank::Bank0, 28); +impl_pin!(PIN_29, Bank::Bank0, 29); + +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, Bank::Bank0, 30); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, Bank::Bank0, 31); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, Bank::Bank0, 32); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, Bank::Bank0, 33); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, Bank::Bank0, 34); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, Bank::Bank0, 35); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, Bank::Bank0, 36); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, Bank::Bank0, 37); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, Bank::Bank0, 38); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, Bank::Bank0, 39); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, Bank::Bank0, 40); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, Bank::Bank0, 41); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, Bank::Bank0, 42); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, Bank::Bank0, 43); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, Bank::Bank0, 44); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, Bank::Bank0, 45); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, Bank::Bank0, 46); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, Bank::Bank0, 47); + +// TODO rp235x bank1 as gpio support +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SCLK, Bank::Qspi, 0); +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SS, Bank::Qspi, 1); +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SD0, Bank::Qspi, 2); +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SD1, Bank::Qspi, 3); +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SD2, Bank::Qspi, 4); +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SD3, Bank::Qspi, 5); + +// ==================== + +mod eh02 { + use super::*; + + impl<'d> embedded_hal_02::digital::v2::InputPin for Input<'d> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::OutputPin for Output<'d> { + type Error = Infallible; + + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Output<'d> { + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'d> { + type Error = Infallible; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + Ok(self.toggle()) + } + } + + impl<'d> embedded_hal_02::digital::v2::InputPin for OutputOpenDrain<'d> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::OutputPin for OutputOpenDrain<'d> { + type Error = Infallible; + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for OutputOpenDrain<'d> { + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for OutputOpenDrain<'d> { + type Error = Infallible; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + Ok(self.toggle()) + } + } + + impl<'d> embedded_hal_02::digital::v2::InputPin for Flex<'d> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::OutputPin for Flex<'d> { + type Error = Infallible; + + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'d> { + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'d> { + type Error = Infallible; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + Ok(self.toggle()) + } + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Input<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::InputPin for Input<'d> { + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Output<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::OutputPin for Output<'d> { + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } +} + +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Output<'d> { + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for OutputOpenDrain<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::OutputPin for OutputOpenDrain<'d> { + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } +} + +impl<'d> embedded_hal_1::digital::StatefulOutputPin for OutputOpenDrain<'d> { + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +impl<'d> embedded_hal_1::digital::InputPin for OutputOpenDrain<'d> { + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Flex<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::InputPin for Flex<'d> { + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_1::digital::OutputPin for Flex<'d> { + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } +} + +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Flex<'d> { + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +impl<'d> embedded_hal_async::digital::Wait for Flex<'d> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +impl<'d> embedded_hal_async::digital::Wait for Input<'d> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +impl<'d> embedded_hal_async::digital::Wait for OutputOpenDrain<'d> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} diff --git a/embassy/embassy-rp/src/i2c.rs b/embassy/embassy-rp/src/i2c.rs new file mode 100644 index 0000000..3277821 --- /dev/null +++ b/embassy/embassy-rp/src/i2c.rs @@ -0,0 +1,924 @@ +//! I2C driver. +use core::future; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use pac::i2c; + +use crate::gpio::AnyPin; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::{interrupt, pac, peripherals, Peripheral}; + +/// I2C error abort reason +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AbortReason { + /// A bus operation was not acknowledged, e.g. due to the addressed device + /// not being available on the bus or the device not being ready to process + /// requests at the moment + NoAcknowledge, + /// The arbitration was lost, e.g. electrical problems with the clock signal + ArbitrationLoss, + /// Transmit ended with data still in fifo + TxNotEmpty(u16), + /// Other reason. + Other(u32), +} + +/// I2C error +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// I2C abort with error + Abort(AbortReason), + /// User passed in a read buffer that was 0 length + InvalidReadBufferLength, + /// User passed in a write buffer that was 0 length + InvalidWriteBufferLength, + /// Target i2c address is out of range + AddressOutOfRange(u16), + /// Target i2c address is reserved + AddressReserved(u16), +} + +/// I2C Config error +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError { + /// Max i2c speed is 1MHz + FrequencyTooHigh, + /// The sys clock is too slow to support given frequency + ClockTooSlow, + /// The sys clock is too fast to support given frequency + ClockTooFast, +} + +/// I2C config. +#[non_exhaustive] +#[derive(Copy, Clone)] +pub struct Config { + /// Frequency. + pub frequency: u32, +} + +impl Default for Config { + fn default() -> Self { + Self { frequency: 100_000 } + } +} + +/// Size of I2C FIFO. +pub const FIFO_SIZE: u8 = 16; + +/// I2C driver. +pub struct I2c<'d, T: Instance, M: Mode> { + phantom: PhantomData<(&'d mut T, M)>, +} + +impl<'d, T: Instance> I2c<'d, T, Blocking> { + /// Create a new driver instance in blocking mode. + pub fn new_blocking( + peri: impl Peripheral

+ 'd, + scl: impl Peripheral

> + 'd, + sda: impl Peripheral

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

+ 'd, + scl: impl Peripheral

> + 'd, + sda: impl Peripheral

> + 'd, + _irq: impl Binding>, + config: Config, + ) -> Self { + into_ref!(scl, sda); + + let i2c = Self::new_inner(peri, scl.map_into(), sda.map_into(), config); + + let r = T::regs(); + + // mask everything initially + r.ic_intr_mask().write_value(i2c::regs::IcIntrMask(0)); + T::Interrupt::unpend(); + unsafe { T::Interrupt::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 + } + + 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; + + 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| { + // 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 = 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(); + + 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| { + // 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| { + // 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| { + p.ic_intr_mask().modify(|w| { + w.set_m_stop_det(true); + w.set_m_tx_abrt(true); + }); + }, + ) + .await; + p.ic_clr_stop_det().read(); + + had_abort.and(had_abort2) + } else { + had_abort + } + } + + /// Read from address into buffer asynchronously. + pub async fn read_async(&mut self, addr: impl Into, buffer: &mut [u8]) -> Result<(), Error> { + Self::setup(addr.into())?; + self.read_async_internal(buffer, true, true).await + } + + /// Write to address from buffer asynchronously. + pub async fn write_async( + &mut self, + addr: impl Into, + bytes: impl IntoIterator, + ) -> Result<(), Error> { + Self::setup(addr.into())?; + self.write_async_internal(bytes, true).await + } + + /// Write to address from bytes and read from address into buffer asynchronously. + pub async fn write_read_async( + &mut self, + addr: impl Into, + bytes: impl IntoIterator, + buffer: &mut [u8], + ) -> Result<(), Error> { + Self::setup(addr.into())?; + self.write_async_internal(bytes, false).await?; + self.read_async_internal(buffer, true, true).await + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + // Mask interrupts and wake any task waiting for this interrupt + unsafe fn on_interrupt() { + let i2c = T::regs(); + i2c.ic_intr_mask().write_value(pac::i2c::regs::IcIntrMask::default()); + + T::waker().wake(); + } +} + +pub(crate) fn set_up_i2c_pin(pin: &P) +where + P: core::ops::Deref, + T: crate::gpio::Pin, +{ + pin.gpio().ctrl().write(|w| w.set_funcsel(3)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(true); + w.set_pde(false); + }); +} + +impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { + fn new_inner( + _peri: impl Peripheral

+ 'd, + scl: PeripheralRef<'d, AnyPin>, + sda: PeripheralRef<'d, AnyPin>, + config: Config, + ) -> Self { + into_ref!(_peri); + + let reset = T::reset(); + crate::reset::reset(reset); + crate::reset::unreset_wait(reset); + + // Configure SCL & SDA pins + set_up_i2c_pin(&scl); + set_up_i2c_pin(&sda); + + let mut me = Self { phantom: PhantomData }; + + if let Err(e) = me.set_config_inner(&config) { + panic!("Error configuring i2c: {:?}", e); + } + + me + } + + fn set_config_inner(&mut self, config: &Config) -> Result<(), ConfigError> { + if config.frequency > 1_000_000 { + return Err(ConfigError::FrequencyTooHigh); + } + + let p = T::regs(); + + p.ic_enable().write(|w| w.set_enable(false)); + + // Configure baudrate + + // There are some subtleties to I2C timing which we are completely + // ignoring here See: + // https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_i2c/i2c.c#L69 + let clk_base = crate::clocks::clk_peri_freq(); + + let period = (clk_base + config.frequency / 2) / config.frequency; + let lcnt = period * 3 / 5; // spend 3/5 (60%) of the period low + let hcnt = period - lcnt; // and 2/5 (40%) of the period high + + // Check for out-of-range divisors: + if hcnt > 0xffff || lcnt > 0xffff { + return Err(ConfigError::ClockTooFast); + } + if hcnt < 8 || lcnt < 8 { + return Err(ConfigError::ClockTooSlow); + } + + // Per I2C-bus specification a device in standard or fast mode must + // internally provide a hold time of at least 300ns for the SDA + // signal to bridge the undefined region of the falling edge of SCL. + // A smaller hold time of 120ns is used for fast mode plus. + let sda_tx_hold_count = if config.frequency < 1_000_000 { + // sda_tx_hold_count = clk_base [cycles/s] * 300ns * (1s / + // 1e9ns) Reduce 300/1e9 to 3/1e7 to avoid numbers that don't + // fit in uint. Add 1 to avoid division truncation. + ((clk_base * 3) / 10_000_000) + 1 + } else { + // fast mode plus requires a clk_base > 32MHz + if clk_base <= 32_000_000 { + return Err(ConfigError::ClockTooSlow); + } + + // sda_tx_hold_count = clk_base [cycles/s] * 120ns * (1s / + // 1e9ns) Reduce 120/1e9 to 3/25e6 to avoid numbers that don't + // fit in uint. Add 1 to avoid division truncation. + ((clk_base * 3) / 25_000_000) + 1 + }; + + if sda_tx_hold_count > lcnt - 2 { + return Err(ConfigError::ClockTooSlow); + } + + p.ic_fs_scl_hcnt().write(|w| w.set_ic_fs_scl_hcnt(hcnt as u16)); + p.ic_fs_scl_lcnt().write(|w| w.set_ic_fs_scl_lcnt(lcnt as u16)); + p.ic_fs_spklen() + .write(|w| w.set_ic_fs_spklen(if lcnt < 16 { 1 } else { (lcnt / 16) as u8 })); + p.ic_sda_hold() + .modify(|w| w.set_ic_sda_tx_hold(sda_tx_hold_count as u16)); + + p.ic_enable().write(|w| w.set_enable(true)); + + Ok(()) + } + + fn setup(addr: u16) -> Result<(), Error> { + if addr >= 0x80 { + return Err(Error::AddressOutOfRange(addr)); + } + + if i2c_reserved_addr(addr) { + return Err(Error::AddressReserved(addr)); + } + + let p = T::regs(); + p.ic_enable().write(|w| w.set_enable(false)); + p.ic_tar().write(|w| w.set_ic_tar(addr)); + p.ic_enable().write(|w| w.set_enable(true)); + Ok(()) + } + + #[inline] + fn tx_fifo_full() -> bool { + Self::tx_fifo_capacity() == 0 + } + + #[inline] + fn tx_fifo_capacity() -> u8 { + let p = T::regs(); + FIFO_SIZE - p.ic_txflr().read().txflr() + } + + #[inline] + fn rx_fifo_len() -> u8 { + let p = T::regs(); + p.ic_rxflr().read().rxflr() + } + + fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> { + let p = T::regs(); + let abort_reason = p.ic_tx_abrt_source().read(); + if abort_reason.0 != 0 { + // Note clearing the abort flag also clears the reason, and this + // instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + p.ic_clr_tx_abrt().read(); + + let reason = if abort_reason.abrt_7b_addr_noack() + | abort_reason.abrt_10addr1_noack() + | abort_reason.abrt_10addr2_noack() + { + AbortReason::NoAcknowledge + } else if abort_reason.arb_lost() { + AbortReason::ArbitrationLoss + } else { + AbortReason::Other(abort_reason.0) + }; + + Err(Error::Abort(reason)) + } else { + Ok(()) + } + } + + fn read_blocking_internal(&mut self, read: &mut [u8], restart: bool, send_stop: bool) -> Result<(), Error> { + if read.is_empty() { + return Err(Error::InvalidReadBufferLength); + } + + let p = T::regs(); + let lastindex = read.len() - 1; + for (i, byte) in read.iter_mut().enumerate() { + let first = i == 0; + let last = i == lastindex; + + // wait until there is space in the FIFO to write the next byte + while Self::tx_fifo_full() {} + + p.ic_data_cmd().write(|w| { + w.set_restart(restart && first); + w.set_stop(send_stop && last); + + w.set_cmd(true); + }); + + while Self::rx_fifo_len() == 0 { + self.read_and_clear_abort_reason()?; + } + + *byte = p.ic_data_cmd().read().dat(); + } + + Ok(()) + } + + fn write_blocking_internal(&mut self, write: &[u8], send_stop: bool) -> Result<(), Error> { + if write.is_empty() { + return Err(Error::InvalidWriteBufferLength); + } + + let p = T::regs(); + + for (i, byte) in write.iter().enumerate() { + let last = i == write.len() - 1; + + p.ic_data_cmd().write(|w| { + w.set_stop(send_stop && last); + w.set_dat(*byte); + }); + + // Wait until the transmission of the address/data from the + // internal shift register has completed. For this to function + // correctly, the TX_EMPTY_CTRL flag in IC_CON must be set. The + // TX_EMPTY_CTRL flag was set in i2c_init. + while !p.ic_raw_intr_stat().read().tx_empty() {} + + let abort_reason = self.read_and_clear_abort_reason(); + + if abort_reason.is_err() || (send_stop && last) { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occurred. + + while !p.ic_raw_intr_stat().read().stop_det() {} + + p.ic_clr_stop_det().read().clr_stop_det(); + } + + // Note the hardware issues a STOP automatically on an abort + // condition. Note also the hardware clears RX FIFO as well as + // TX on abort, ecause we set hwparam + // IC_AVOID_RX_FIFO_FLUSH_ON_TX_ABRT to 0. + abort_reason?; + } + Ok(()) + } + + // ========================= + // Blocking public API + // ========================= + + /// Read from address into buffer blocking caller until done. + pub fn blocking_read(&mut self, address: impl Into, read: &mut [u8]) -> Result<(), Error> { + Self::setup(address.into())?; + self.read_blocking_internal(read, true, true) + // Automatic Stop + } + + /// Write to address from buffer blocking caller until done. + pub fn blocking_write(&mut self, address: impl Into, write: &[u8]) -> Result<(), Error> { + Self::setup(address.into())?; + self.write_blocking_internal(write, true) + } + + /// Write to address from bytes and read from address into buffer blocking caller until done. + pub fn blocking_write_read(&mut self, address: impl Into, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + Self::setup(address.into())?; + self.write_blocking_internal(write, false)?; + self.read_blocking_internal(read, true, true) + // Automatic Stop + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, T, M> { + type Error = Error; + + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, buffer) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, T, M> { + type Error = Error; + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, bytes) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T, M> { + type Error = Error; + + fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, bytes, buffer) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, T, M> { + type Error = Error; + + fn exec( + &mut self, + address: u8, + operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + Self::setup(address.into())?; + for i in 0..operations.len() { + let last = i == operations.len() - 1; + match &mut operations[i] { + embedded_hal_02::blocking::i2c::Operation::Read(buf) => { + self.read_blocking_internal(buf, false, last)? + } + embedded_hal_02::blocking::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?, + } + } + Ok(()) + } +} + +impl embedded_hal_1::i2c::Error for Error { + fn kind(&self) -> embedded_hal_1::i2c::ErrorKind { + match *self { + Self::Abort(AbortReason::ArbitrationLoss) => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss, + Self::Abort(AbortReason::NoAcknowledge) => { + embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) + } + Self::Abort(AbortReason::TxNotEmpty(_)) => embedded_hal_1::i2c::ErrorKind::Other, + Self::Abort(AbortReason::Other(_)) => embedded_hal_1::i2c::ErrorKind::Other, + Self::InvalidReadBufferLength => embedded_hal_1::i2c::ErrorKind::Other, + Self::InvalidWriteBufferLength => embedded_hal_1::i2c::ErrorKind::Other, + Self::AddressOutOfRange(_) => embedded_hal_1::i2c::ErrorKind::Other, + Self::AddressReserved(_) => embedded_hal_1::i2c::ErrorKind::Other, + } + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> { + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, read) + } + + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, write) + } + + fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, write, read) + } + + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + Self::setup(address.into())?; + for i in 0..operations.len() { + let last = i == operations.len() - 1; + match &mut operations[i] { + embedded_hal_1::i2c::Operation::Read(buf) => self.read_blocking_internal(buf, false, last)?, + embedded_hal_1::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?, + } + } + Ok(()) + } +} + +impl<'d, A, T> embedded_hal_async::i2c::I2c for I2c<'d, T, Async> +where + A: embedded_hal_async::i2c::AddressMode + Into + 'static, + T: Instance + 'd, +{ + async fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), Self::Error> { + self.read_async(address, read).await + } + + async fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> { + self.write_async(address, write.iter().copied()).await + } + + async fn write_read(&mut self, address: A, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.write_read_async(address, write.iter().copied(), read).await + } + + async fn transaction( + &mut self, + address: A, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + use embedded_hal_1::i2c::Operation; + + let addr: u16 = address.into(); + + if !operations.is_empty() { + Self::setup(addr)?; + } + let mut iterator = operations.iter_mut(); + + while let Some(op) = iterator.next() { + let last = iterator.len() == 0; + + match op { + Operation::Read(buffer) => { + self.read_async_internal(buffer, false, last).await?; + } + Operation::Write(buffer) => { + self.write_async_internal(buffer.iter().cloned(), last).await?; + } + } + } + Ok(()) + } +} + +impl<'d, T: Instance, M: Mode> embassy_embedded_hal::SetConfig for I2c<'d, T, M> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config_inner(config) + } +} + +/// Check if address is reserved. +pub fn i2c_reserved_addr(addr: u16) -> bool { + ((addr & 0x78) == 0 || (addr & 0x78) == 0x78) && addr != 0 +} + +pub(crate) trait SealedInstance { + fn regs() -> crate::pac::i2c::I2c; + fn reset() -> crate::pac::resets::regs::Peripherals; + fn waker() -> &'static AtomicWaker; +} + +trait SealedMode {} + +/// Driver mode. +#[allow(private_bounds)] +pub trait Mode: SealedMode {} + +macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; +} + +/// Blocking mode. +pub struct Blocking; +/// Async mode. +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); + +/// I2C instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_instance { + ($type:ident, $irq:ident, $reset:ident) => { + impl SealedInstance for peripherals::$type { + #[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 { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +impl_instance!(I2C0, I2C0_IRQ, set_i2c0); +impl_instance!(I2C1, I2C1_IRQ, set_i2c1); + +/// SDA pin. +pub trait SdaPin: crate::gpio::Pin {} +/// SCL pin. +pub trait SclPin: crate::gpio::Pin {} + +macro_rules! impl_pin { + ($pin:ident, $instance:ident, $function:ident) => { + impl $function for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, I2C0, SdaPin); +impl_pin!(PIN_1, I2C0, SclPin); +impl_pin!(PIN_2, I2C1, SdaPin); +impl_pin!(PIN_3, I2C1, SclPin); +impl_pin!(PIN_4, I2C0, SdaPin); +impl_pin!(PIN_5, I2C0, SclPin); +impl_pin!(PIN_6, I2C1, SdaPin); +impl_pin!(PIN_7, I2C1, SclPin); +impl_pin!(PIN_8, I2C0, SdaPin); +impl_pin!(PIN_9, I2C0, SclPin); +impl_pin!(PIN_10, I2C1, SdaPin); +impl_pin!(PIN_11, I2C1, SclPin); +impl_pin!(PIN_12, I2C0, SdaPin); +impl_pin!(PIN_13, I2C0, SclPin); +impl_pin!(PIN_14, I2C1, SdaPin); +impl_pin!(PIN_15, I2C1, SclPin); +impl_pin!(PIN_16, I2C0, SdaPin); +impl_pin!(PIN_17, I2C0, SclPin); +impl_pin!(PIN_18, I2C1, SdaPin); +impl_pin!(PIN_19, I2C1, SclPin); +impl_pin!(PIN_20, I2C0, SdaPin); +impl_pin!(PIN_21, I2C0, SclPin); +impl_pin!(PIN_22, I2C1, SdaPin); +impl_pin!(PIN_23, I2C1, SclPin); +impl_pin!(PIN_24, I2C0, SdaPin); +impl_pin!(PIN_25, I2C0, SclPin); +impl_pin!(PIN_26, I2C1, SdaPin); +impl_pin!(PIN_27, I2C1, SclPin); +impl_pin!(PIN_28, I2C0, SdaPin); +impl_pin!(PIN_29, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, I2C1, SclPin); diff --git a/embassy/embassy-rp/src/i2c_slave.rs b/embassy/embassy-rp/src/i2c_slave.rs new file mode 100644 index 0000000..c46a55d --- /dev/null +++ b/embassy/embassy-rp/src/i2c_slave.rs @@ -0,0 +1,398 @@ +//! I2C slave driver. +use core::future; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::into_ref; +use pac::i2c; + +use crate::i2c::{ + i2c_reserved_addr, set_up_i2c_pin, AbortReason, Instance, InterruptHandler, SclPin, SdaPin, FIFO_SIZE, +}; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::{pac, Peripheral}; + +/// I2C error +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// I2C abort with error + Abort(AbortReason), + /// User passed in a response buffer that was 0 length + InvalidResponseBufferLength, + /// The response buffer length was too short to contain the message + /// + /// The length parameter will always be the length of the buffer, and is + /// provided as a convenience for matching alongside `Command::Write`. + PartialWrite(usize), + /// The response buffer length was too short to contain the message + /// + /// The length parameter will always be the length of the buffer, and is + /// provided as a convenience for matching alongside `Command::GeneralCall`. + PartialGeneralCall(usize), +} + +/// Received command +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Command { + /// General Call + GeneralCall(usize), + /// Read + Read, + /// Write+read + WriteRead(usize), + /// Write + Write(usize), +} + +/// Possible responses to responding to a read +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ReadStatus { + /// Transaction Complete, controller naked our last byte + Done, + /// Transaction Incomplete, controller trying to read more bytes than were provided + NeedMoreBytes, + /// Transaction Complere, but controller stopped reading bytes before we ran out + LeftoverBytes(u16), +} + +/// Slave Configuration +#[non_exhaustive] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + /// Target Address + pub addr: u16, + /// Control if the peripheral should ack to and report general calls. + pub general_call: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + addr: 0x55, + general_call: true, + } + } +} + +/// I2CSlave driver. +pub struct I2cSlave<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + pending_byte: Option, + config: Config, +} + +impl<'d, T: Instance> I2cSlave<'d, T> { + /// Create a new instance. + pub fn new( + _peri: impl Peripheral

+ 'd, + scl: impl Peripheral

> + 'd, + sda: impl Peripheral

> + 'd, + _irq: impl Binding>, + config: Config, + ) -> Self { + into_ref!(_peri, scl, sda); + + assert!(!i2c_reserved_addr(config.addr)); + assert!(config.addr != 0); + + // Configure SCL & SDA pins + set_up_i2c_pin(&scl); + set_up_i2c_pin(&sda); + + let mut ret = Self { + phantom: PhantomData, + pending_byte: None, + config, + }; + + ret.reset(); + + ret + } + + /// Reset the i2c peripheral. If you cancel a respond_to_read, you may stall the bus. + /// You can recover the bus by calling this function, but doing so will almost certainly cause + /// an i/o error in the master. + pub fn reset(&mut self) { + let p = T::regs(); + + let reset = T::reset(); + crate::reset::reset(reset); + crate::reset::unreset_wait(reset); + + p.ic_enable().write(|w| w.set_enable(false)); + + p.ic_sar().write(|w| w.set_ic_sar(self.config.addr)); + p.ic_con().modify(|w| { + w.set_master_mode(false); + w.set_ic_slave_disable(false); + w.set_tx_empty_ctrl(true); + w.set_rx_fifo_full_hld_ctrl(true); + + // This typically makes no sense for a slave, but it is used to + // tune spike suppression, according to the datasheet. + w.set_speed(pac::i2c::vals::Speed::FAST); + + // Generate stop interrupts for general calls + // This also causes stop interrupts for other devices on the bus but those will not be + // propagated up to the application. + w.set_stop_det_ifaddressed(!self.config.general_call); + }); + p.ic_ack_general_call() + .write(|w| w.set_ack_gen_call(self.config.general_call)); + + // Set FIFO watermarks to 1 to make things simpler. This is encoded + // by a register value of 0. Rx watermark should never change, but Tx watermark will be + // adjusted in operation. + p.ic_tx_tl().write(|w| w.set_tx_tl(0)); + p.ic_rx_tl().write(|w| w.set_rx_tl(0)); + + // Clear interrupts + p.ic_clr_intr().read(); + + // Enable I2C block + p.ic_enable().write(|w| w.set_enable(true)); + + // mask everything initially + p.ic_intr_mask().write_value(i2c::regs::IcIntrMask(0)); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + } + + /// 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). + #[inline(always)] + 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 + } + + #[inline(always)] + fn drain_fifo(&mut self, buffer: &mut [u8], offset: &mut usize) { + let p = T::regs(); + + if let Some(pending) = self.pending_byte.take() { + buffer[*offset] = pending; + *offset += 1; + } + + for b in &mut buffer[*offset..] { + if !p.ic_status().read().rfne() { + break; + } + + let dat = p.ic_data_cmd().read(); + if *offset != 0 && dat.first_data_byte() { + // The RP2040 state machine will keep placing bytes into the + // FIFO, even if they are part of a subsequent write transaction. + // + // Unfortunately merely reading ic_data_cmd will consume that + // byte, the first byte of the next transaction, so we need + // to store it elsewhere + self.pending_byte = Some(dat.dat()); + break; + } + + *b = dat.dat(); + *offset += 1; + } + } + + /// Wait asynchronously for commands from an I2C master. + /// `buffer` is provided in case master does a 'write', 'write read', or 'general call' and is unused for 'read'. + pub async fn listen(&mut self, buffer: &mut [u8]) -> Result { + let p = T::regs(); + + // set rx fifo watermark to 1 byte + p.ic_rx_tl().write(|w| w.set_rx_tl(0)); + + let mut len = 0; + self.wait_on( + |me| { + let stat = p.ic_raw_intr_stat().read(); + trace!("ls:{:013b} len:{}", stat.0, len); + + if p.ic_rxflr().read().rxflr() > 0 || me.pending_byte.is_some() { + me.drain_fifo(buffer, &mut len); + // we're recieving data, set rx fifo watermark to 12 bytes (3/4 full) to reduce interrupt noise + p.ic_rx_tl().write(|w| w.set_rx_tl(11)); + } + + if buffer.len() == len { + if stat.gen_call() { + return Poll::Ready(Err(Error::PartialGeneralCall(buffer.len()))); + } else { + return Poll::Ready(Err(Error::PartialWrite(buffer.len()))); + } + } + trace!("len:{}, pend:{:?}", len, me.pending_byte); + if me.pending_byte.is_some() { + warn!("pending") + } + + if stat.restart_det() && stat.rd_req() { + p.ic_clr_restart_det().read(); + Poll::Ready(Ok(Command::WriteRead(len))) + } else if stat.gen_call() && stat.stop_det() && len > 0 { + p.ic_clr_gen_call().read(); + p.ic_clr_stop_det().read(); + Poll::Ready(Ok(Command::GeneralCall(len))) + } else if stat.stop_det() && len > 0 { + p.ic_clr_stop_det().read(); + Poll::Ready(Ok(Command::Write(len))) + } else if stat.rd_req() { + p.ic_clr_stop_det().read(); + p.ic_clr_restart_det().read(); + p.ic_clr_gen_call().read(); + Poll::Ready(Ok(Command::Read)) + } else if stat.stop_det() { + // clear stuck stop bit + // This can happen if the SDA/SCL pullups are enabled after calling this func + p.ic_clr_stop_det().read(); + Poll::Pending + } else { + Poll::Pending + } + }, + |_me| { + p.ic_intr_mask().write(|w| { + w.set_m_stop_det(true); + w.set_m_restart_det(true); + w.set_m_gen_call(true); + w.set_m_rd_req(true); + w.set_m_rx_full(true); + }); + }, + ) + .await + } + + /// Respond to an I2C master READ command, asynchronously. + pub async fn respond_to_read(&mut self, buffer: &[u8]) -> Result { + let p = T::regs(); + + if buffer.is_empty() { + return Err(Error::InvalidResponseBufferLength); + } + + let mut chunks = buffer.chunks(FIFO_SIZE as usize); + + self.wait_on( + |me| { + let stat = p.ic_raw_intr_stat().read(); + trace!("rs:{:013b}", stat.0); + + if stat.tx_abrt() { + if let Err(abort_reason) = me.read_and_clear_abort_reason() { + if let Error::Abort(AbortReason::TxNotEmpty(bytes)) = abort_reason { + p.ic_clr_intr().read(); + return Poll::Ready(Ok(ReadStatus::LeftoverBytes(bytes))); + } else { + return Poll::Ready(Err(abort_reason)); + } + } + } + + if let Some(chunk) = chunks.next() { + for byte in chunk { + p.ic_clr_rd_req().read(); + p.ic_data_cmd().write(|w| w.set_dat(*byte)); + } + + Poll::Pending + } else if stat.rx_done() { + p.ic_clr_rx_done().read(); + Poll::Ready(Ok(ReadStatus::Done)) + } else if stat.rd_req() && stat.tx_empty() { + Poll::Ready(Ok(ReadStatus::NeedMoreBytes)) + } else { + Poll::Pending + } + }, + |_me| { + p.ic_intr_mask().write(|w| { + w.set_m_rx_done(true); + w.set_m_tx_empty(true); + w.set_m_tx_abrt(true); + }) + }, + ) + .await + } + + /// Respond to reads with the fill byte until the controller stops asking + pub async fn respond_till_stop(&mut self, fill: u8) -> Result<(), Error> { + // Send fill bytes a full fifo at a time, to reduce interrupt noise. + // This does mean we'll almost certainly abort the write, but since these are fill bytes, + // we don't care. + let buff = [fill; FIFO_SIZE as usize]; + loop { + match self.respond_to_read(&buff).await { + Ok(ReadStatus::NeedMoreBytes) => (), + Ok(ReadStatus::LeftoverBytes(_)) => break Ok(()), + Ok(_) => break Ok(()), + Err(e) => break Err(e), + } + } + } + + /// Respond to a master read, then fill any remaining read bytes with `fill` + pub async fn respond_and_fill(&mut self, buffer: &[u8], fill: u8) -> Result { + let resp_stat = self.respond_to_read(buffer).await?; + + if resp_stat == ReadStatus::NeedMoreBytes { + self.respond_till_stop(fill).await?; + Ok(ReadStatus::Done) + } else { + Ok(resp_stat) + } + } + + #[inline(always)] + fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> { + let p = T::regs(); + let abort_reason = p.ic_tx_abrt_source().read(); + + if abort_reason.0 != 0 { + // Note clearing the abort flag also clears the reason, and this + // instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + p.ic_clr_tx_abrt().read(); + + let reason = if abort_reason.abrt_7b_addr_noack() + | abort_reason.abrt_10addr1_noack() + | abort_reason.abrt_10addr2_noack() + { + AbortReason::NoAcknowledge + } else if abort_reason.arb_lost() { + AbortReason::ArbitrationLoss + } else if abort_reason.tx_flush_cnt() > 0 { + AbortReason::TxNotEmpty(abort_reason.tx_flush_cnt()) + } else { + AbortReason::Other(abort_reason.0) + }; + + Err(Error::Abort(reason)) + } else { + Ok(()) + } + } +} diff --git a/embassy/embassy-rp/src/intrinsics.rs b/embassy/embassy-rp/src/intrinsics.rs new file mode 100644 index 0000000..5b9c127 --- /dev/null +++ b/embassy/embassy-rp/src/intrinsics.rs @@ -0,0 +1,477 @@ +#![macro_use] + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/intrinsics.rs + +/// Generate a series of aliases for an intrinsic function. +macro_rules! intrinsics_aliases { + ( + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + ) => {}; + ( + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + ) => {}; + + ( + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + $alias:ident + $($rest:ident)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + intrinsics! { + extern $abi fn $alias( $($argname: $ty),* ) -> $ret { + $name($($argname),*) + } + } + + intrinsics_aliases! { + extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($rest)* + } + }; + + ( + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + $alias:ident + $($rest:ident)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + intrinsics! { + unsafe extern $abi fn $alias( $($argname: $ty),* ) -> $ret { + $name($($argname),*) + } + } + + intrinsics_aliases! { + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($rest)* + } + }; +} + +/// The macro used to define overridden intrinsics. +/// +/// This is heavily inspired by the macro used by compiler-builtins. The idea +/// is to abstract anything special that needs to be done to override an +/// intrinsic function. Intrinsic generation is disabled for non-ARM targets +/// so things like CI and docs generation do not have problems. Additionally +/// they can be disabled by disabling the crate feature `intrinsics` for +/// testing or comparing performance. +/// +/// Like the compiler-builtins macro, it accepts a series of functions that +/// looks like normal Rust code: +/// +/// ```rust,ignore +/// intrinsics! { +/// extern "C" fn foo(a: i32) -> u32 { +/// // ... +/// } +/// #[nonstandard_attribute] +/// extern "C" fn bar(a: i32) -> u32 { +/// // ... +/// } +/// } +/// ``` +/// +/// Each function can also be decorated with nonstandard attributes to control +/// additional behaviour: +/// +/// * `slower_than_default` - indicates that the override is slower than the +/// default implementation. Currently this just disables the override +/// entirely. +/// * `bootrom_v2` - indicates that the override is only available +/// on a V2 bootrom or higher. Only enabled when the feature +/// `rom-v2-intrinsics` is set. +/// * `alias` - accepts a list of names to alias the intrinsic to. +/// * `aeabi` - accepts a list of ARM EABI names to alias to. +/// +macro_rules! intrinsics { + () => {}; + + ( + #[slower_than_default] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + // Not exported, but defined so the actual implementation is + // considered used + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; + + ( + #[bootrom_v2] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(feature = "rom-v2-intrinsics"))] + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(feature = "rom-v2-intrinsics")] + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics!($($rest)*); + }; + + ( + #[alias = $($alias:ident),*] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + #[alias = $($alias:ident),*] + $(#[$($attr:tt)*])* + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + #[aeabi = $($alias:ident),*] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + extern "aapcs" fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + mod $name { + #[no_mangle] + $(#[$($attr)*])* + pub extern $abi fn $name( $($argname: $ty),* ) -> $ret { + super::$name($($argname),*) + } + } + + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(all(target_arch = "arm", feature = "intrinsics")))] + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; + + ( + $(#[$($attr:tt)*])* + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + $(#[$($attr)*])* + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + mod $name { + #[no_mangle] + $(#[$($attr)*])* + pub unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + super::$name($($argname),*) + } + } + + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(all(target_arch = "arm", feature = "intrinsics")))] + #[allow(dead_code)] + unsafe fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; +} + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/sio.rs + +// This takes advantage of how AAPCS defines a 64-bit return on 32-bit registers +// by packing it into r0[0:31] and r1[32:63]. So all we need to do is put +// the remainder in the high order 32 bits of a 64 bit result. We can also +// alias the division operators to these for a similar reason r0 is the +// result either way and r1 a scratch register, so the caller can't assume it +// retains the argument value. +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + ".macro hwdivider_head", + "ldr r2, =(0xd0000000)", // SIO_BASE + // Check the DIRTY state of the divider by shifting it into the C + // status bit. + "ldr r3, [r2, #0x078]", // DIV_CSR + "lsrs r3, #2", // DIRTY = 1, so shift 2 down + // We only need to save the state when DIRTY, otherwise we can just do the + // division directly. + "bcs 2f", + "1:", + // Do the actual division now, we're either not DIRTY, or we've saved the + // state and branched back here so it's safe now. + ".endm", + ".macro hwdivider_tail", + // 8 cycle delay to wait for the result. Each branch takes two cycles + // and fits into a 2-byte Thumb instruction, so this is smaller than + // 8 NOPs. + "b 3f", + "3: b 3f", + "3: b 3f", + "3: b 3f", + "3:", + // Read the quotient last, since that's what clears the dirty flag. + "ldr r1, [r2, #0x074]", // DIV_REMAINDER + "ldr r0, [r2, #0x070]", // DIV_QUOTIENT + // Either return to the caller or back to the state restore. + "bx lr", + "2:", + // Since we can't save the signed-ness of the calculation, we have to make + // sure that there's at least an 8 cycle delay before we read the result. + // The push takes 5 cycles, and we've already spent at least 7 checking + // the DIRTY state to get here. + "push {{r4-r6, lr}}", + // Read the quotient last, since that's what clears the dirty flag. + "ldr r3, [r2, #0x060]", // DIV_UDIVIDEND + "ldr r4, [r2, #0x064]", // DIV_UDIVISOR + "ldr r5, [r2, #0x074]", // DIV_REMAINDER + "ldr r6, [r2, #0x070]", // DIV_QUOTIENT + // If we get interrupted here (before a write sets the DIRTY flag) it's + // fine, since we have the full state, so the interruptor doesn't have to + // restore it. Once the write happens and the DIRTY flag is set, the + // interruptor becomes responsible for restoring our state. + "bl 1b", + // If we are interrupted here, then the interruptor will start an incorrect + // calculation using a wrong divisor, but we'll restore the divisor and + // result ourselves correctly. This sets DIRTY, so any interruptor will + // save the state. + "str r3, [r2, #0x060]", // DIV_UDIVIDEND + // If we are interrupted here, the the interruptor may start the + // calculation using incorrectly signed inputs, but we'll restore the + // result ourselves. This sets DIRTY, so any interruptor will save the + // state. + "str r4, [r2, #0x064]", // DIV_UDIVISOR + // If we are interrupted here, the interruptor will have restored + // everything but the quotient may be wrongly signed. If the calculation + // started by the above writes is still ongoing it is stopped, so it won't + // replace the result we're restoring. DIRTY and READY set, but only + // DIRTY matters to make the interruptor save the state. + "str r5, [r2, #0x074]", // DIV_REMAINDER + // State fully restored after the quotient write. This sets both DIRTY + // and READY, so whatever we may have interrupted can read the result. + "str r6, [r2, #0x070]", // DIV_QUOTIENT + "pop {{r4-r6, pc}}", + ".endm", +); + +macro_rules! division_function { + ( + $name:ident $($intrinsic:ident)* ( $argty:ty ) { + $($begin:literal),+ + } + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + core::arch::global_asm!( + // Mangle the name slightly, since this is a global symbol. + concat!(".section .text._erphal_", stringify!($name)), + concat!(".global _erphal_", stringify!($name)), + concat!(".type _erphal_", stringify!($name), ", %function"), + ".align 2", + concat!("_erphal_", stringify!($name), ":"), + $( + concat!(".global ", stringify!($intrinsic)), + concat!(".type ", stringify!($intrinsic), ", %function"), + concat!(stringify!($intrinsic), ":"), + )* + + "hwdivider_head", + $($begin),+ , + "hwdivider_tail", + ); + + #[cfg(all(target_arch = "arm", not(feature = "intrinsics")))] + core::arch::global_asm!( + // Mangle the name slightly, since this is a global symbol. + concat!(".section .text._erphal_", stringify!($name)), + concat!(".global _erphal_", stringify!($name)), + concat!(".type _erphal_", stringify!($name), ", %function"), + ".align 2", + concat!("_erphal_", stringify!($name), ":"), + + "hwdivider_head", + $($begin),+ , + "hwdivider_tail", + ); + + #[cfg(target_arch = "arm")] + extern "aapcs" { + // Connect a local name to global symbol above through FFI. + #[link_name = concat!("_erphal_", stringify!($name)) ] + fn $name(n: $argty, d: $argty) -> u64; + } + + #[cfg(not(target_arch = "arm"))] + #[allow(unused_variables)] + unsafe fn $name(n: $argty, d: $argty) -> u64 { 0 } + }; +} + +division_function! { + unsigned_divmod __aeabi_uidivmod __aeabi_uidiv ( u32 ) { + "str r0, [r2, #0x060]", // DIV_UDIVIDEND + "str r1, [r2, #0x064]" // DIV_UDIVISOR + } +} + +division_function! { + signed_divmod __aeabi_idivmod __aeabi_idiv ( i32 ) { + "str r0, [r2, #0x068]", // DIV_SDIVIDEND + "str r1, [r2, #0x06c]" // DIV_SDIVISOR + } +} + +fn divider_unsigned(n: u32, d: u32) -> DivResult { + let packed = unsafe { unsigned_divmod(n, d) }; + DivResult { + quotient: packed as u32, + remainder: (packed >> 32) as u32, + } +} + +fn divider_signed(n: i32, d: i32) -> DivResult { + let packed = unsafe { signed_divmod(n, d) }; + // Double casts to avoid sign extension + DivResult { + quotient: packed as u32 as i32, + remainder: (packed >> 32) as u32 as i32, + } +} + +/// Result of divide/modulo operation +struct DivResult { + /// The quotient of divide/modulo operation + pub quotient: T, + /// The remainder of divide/modulo operation + pub remainder: T, +} + +intrinsics! { + extern "C" fn __udivsi3(n: u32, d: u32) -> u32 { + divider_unsigned(n, d).quotient + } + + extern "C" fn __umodsi3(n: u32, d: u32) -> u32 { + divider_unsigned(n, d).remainder + } + + extern "C" fn __udivmodsi4(n: u32, d: u32, rem: Option<&mut u32>) -> u32 { + let quo_rem = divider_unsigned(n, d); + if let Some(rem) = rem { + *rem = quo_rem.remainder; + } + quo_rem.quotient + } + + extern "C" fn __divsi3(n: i32, d: i32) -> i32 { + divider_signed(n, d).quotient + } + + extern "C" fn __modsi3(n: i32, d: i32) -> i32 { + divider_signed(n, d).remainder + } + + extern "C" fn __divmodsi4(n: i32, d: i32, rem: &mut i32) -> i32 { + let quo_rem = divider_signed(n, d); + *rem = quo_rem.remainder; + quo_rem.quotient + } +} diff --git a/embassy/embassy-rp/src/lib.rs b/embassy/embassy-rp/src/lib.rs new file mode 100644 index 0000000..f0893b5 --- /dev/null +++ b/embassy/embassy-rp/src/lib.rs @@ -0,0 +1,700 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +#[cfg(feature = "binary-info")] +pub use rp_binary_info as binary_info; + +#[cfg(feature = "critical-section-impl")] +mod critical_section_impl; + +#[cfg(feature = "rp2040")] +mod intrinsics; + +pub mod adc; +#[cfg(feature = "_rp235x")] +pub mod block; +#[cfg(feature = "rp2040")] +pub mod bootsel; +pub mod clocks; +pub mod dma; +pub mod flash; +#[cfg(feature = "rp2040")] +mod float; +pub mod gpio; +pub mod i2c; +pub mod i2c_slave; +pub mod multicore; +#[cfg(feature = "_rp235x")] +pub mod otp; +pub mod pio_programs; +pub mod pwm; +mod reset; +pub mod rom_data; +#[cfg(feature = "rp2040")] +pub mod rtc; +pub mod spi; +#[cfg(feature = "time-driver")] +pub mod time_driver; +#[cfg(feature = "_rp235x")] +pub mod trng; +pub mod uart; +pub mod usb; +pub mod watchdog; + +// PIO +pub mod pio; +pub(crate) mod relocate; + +// Reexports +pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +#[cfg(feature = "unstable-pac")] +pub use rp_pac as pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use rp_pac as pac; + +#[cfg(feature = "rt")] +pub use crate::pac::NVIC_PRIO_BITS; + +#[cfg(feature = "rp2040")] +embassy_hal_internal::interrupt_mod!( + TIMER_IRQ_0, + TIMER_IRQ_1, + TIMER_IRQ_2, + TIMER_IRQ_3, + PWM_IRQ_WRAP, + USBCTRL_IRQ, + XIP_IRQ, + PIO0_IRQ_0, + PIO0_IRQ_1, + PIO1_IRQ_0, + PIO1_IRQ_1, + DMA_IRQ_0, + DMA_IRQ_1, + IO_IRQ_BANK0, + IO_IRQ_QSPI, + SIO_IRQ_PROC0, + SIO_IRQ_PROC1, + CLOCKS_IRQ, + SPI0_IRQ, + SPI1_IRQ, + UART0_IRQ, + UART1_IRQ, + ADC_IRQ_FIFO, + I2C0_IRQ, + I2C1_IRQ, + RTC_IRQ, + SWI_IRQ_0, + SWI_IRQ_1, + SWI_IRQ_2, + SWI_IRQ_3, + SWI_IRQ_4, + SWI_IRQ_5, +); + +#[cfg(feature = "_rp235x")] +embassy_hal_internal::interrupt_mod!( + TIMER0_IRQ_0, + TIMER0_IRQ_1, + TIMER0_IRQ_2, + TIMER0_IRQ_3, + TIMER1_IRQ_0, + TIMER1_IRQ_1, + TIMER1_IRQ_2, + TIMER1_IRQ_3, + PWM_IRQ_WRAP_0, + PWM_IRQ_WRAP_1, + DMA_IRQ_0, + DMA_IRQ_1, + USBCTRL_IRQ, + PIO0_IRQ_0, + PIO0_IRQ_1, + PIO1_IRQ_0, + PIO1_IRQ_1, + PIO2_IRQ_0, + PIO2_IRQ_1, + IO_IRQ_BANK0, + IO_IRQ_BANK0_NS, + IO_IRQ_QSPI, + IO_IRQ_QSPI_NS, + SIO_IRQ_FIFO, + SIO_IRQ_BELL, + SIO_IRQ_FIFO_NS, + SIO_IRQ_BELL_NS, + CLOCKS_IRQ, + SPI0_IRQ, + SPI1_IRQ, + UART0_IRQ, + UART1_IRQ, + ADC_IRQ_FIFO, + I2C0_IRQ, + I2C1_IRQ, + TRNG_IRQ, + PLL_SYS_IRQ, + PLL_USB_IRQ, + SWI_IRQ_0, + SWI_IRQ_1, + SWI_IRQ_2, + SWI_IRQ_3, + SWI_IRQ_4, + SWI_IRQ_5, +); + +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +/// +/// Example of how to bind one interrupt: +/// +/// ```rust,ignore +/// use embassy_rp::{bind_interrupts, usb, peripherals}; +/// +/// bind_interrupts!(struct Irqs { +/// USBCTRL_IRQ => usb::InterruptHandler; +/// }); +/// ``` +/// +// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + )* + } + + $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@inner + $( + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + ); + )* + }; + (@inner $($t:tt)*) => { + $($t)* + } +} + +#[cfg(feature = "rp2040")] +embassy_hal_internal::peripherals! { + PIN_0, + PIN_1, + PIN_2, + PIN_3, + PIN_4, + PIN_5, + PIN_6, + PIN_7, + PIN_8, + PIN_9, + PIN_10, + PIN_11, + PIN_12, + PIN_13, + PIN_14, + PIN_15, + PIN_16, + PIN_17, + PIN_18, + PIN_19, + PIN_20, + PIN_21, + PIN_22, + PIN_23, + PIN_24, + PIN_25, + PIN_26, + PIN_27, + PIN_28, + PIN_29, + PIN_QSPI_SCLK, + PIN_QSPI_SS, + PIN_QSPI_SD0, + PIN_QSPI_SD1, + PIN_QSPI_SD2, + PIN_QSPI_SD3, + + UART0, + UART1, + + SPI0, + SPI1, + + I2C0, + I2C1, + + DMA_CH0, + DMA_CH1, + DMA_CH2, + DMA_CH3, + DMA_CH4, + DMA_CH5, + DMA_CH6, + DMA_CH7, + DMA_CH8, + DMA_CH9, + DMA_CH10, + DMA_CH11, + + PWM_SLICE0, + PWM_SLICE1, + PWM_SLICE2, + PWM_SLICE3, + PWM_SLICE4, + PWM_SLICE5, + PWM_SLICE6, + PWM_SLICE7, + + USB, + + RTC, + + FLASH, + + ADC, + ADC_TEMP_SENSOR, + + CORE1, + + PIO0, + PIO1, + + WATCHDOG, + BOOTSEL, +} + +#[cfg(feature = "_rp235x")] +embassy_hal_internal::peripherals! { + PIN_0, + PIN_1, + PIN_2, + PIN_3, + PIN_4, + PIN_5, + PIN_6, + PIN_7, + PIN_8, + PIN_9, + PIN_10, + PIN_11, + PIN_12, + PIN_13, + PIN_14, + PIN_15, + PIN_16, + PIN_17, + PIN_18, + PIN_19, + PIN_20, + PIN_21, + PIN_22, + PIN_23, + PIN_24, + PIN_25, + PIN_26, + PIN_27, + PIN_28, + PIN_29, + #[cfg(feature = "rp235xb")] + PIN_30, + #[cfg(feature = "rp235xb")] + PIN_31, + #[cfg(feature = "rp235xb")] + PIN_32, + #[cfg(feature = "rp235xb")] + PIN_33, + #[cfg(feature = "rp235xb")] + PIN_34, + #[cfg(feature = "rp235xb")] + PIN_35, + #[cfg(feature = "rp235xb")] + PIN_36, + #[cfg(feature = "rp235xb")] + PIN_37, + #[cfg(feature = "rp235xb")] + PIN_38, + #[cfg(feature = "rp235xb")] + PIN_39, + #[cfg(feature = "rp235xb")] + PIN_40, + #[cfg(feature = "rp235xb")] + PIN_41, + #[cfg(feature = "rp235xb")] + PIN_42, + #[cfg(feature = "rp235xb")] + PIN_43, + #[cfg(feature = "rp235xb")] + PIN_44, + #[cfg(feature = "rp235xb")] + PIN_45, + #[cfg(feature = "rp235xb")] + PIN_46, + #[cfg(feature = "rp235xb")] + PIN_47, + PIN_QSPI_SCLK, + PIN_QSPI_SS, + PIN_QSPI_SD0, + PIN_QSPI_SD1, + PIN_QSPI_SD2, + PIN_QSPI_SD3, + + UART0, + UART1, + + SPI0, + SPI1, + + I2C0, + I2C1, + + DMA_CH0, + DMA_CH1, + DMA_CH2, + DMA_CH3, + DMA_CH4, + DMA_CH5, + DMA_CH6, + DMA_CH7, + DMA_CH8, + DMA_CH9, + DMA_CH10, + DMA_CH11, + DMA_CH12, + DMA_CH13, + DMA_CH14, + DMA_CH15, + + PWM_SLICE0, + PWM_SLICE1, + PWM_SLICE2, + PWM_SLICE3, + PWM_SLICE4, + PWM_SLICE5, + PWM_SLICE6, + PWM_SLICE7, + PWM_SLICE8, + PWM_SLICE9, + PWM_SLICE10, + PWM_SLICE11, + + USB, + + RTC, + + FLASH, + + ADC, + ADC_TEMP_SENSOR, + + CORE1, + + PIO0, + PIO1, + PIO2, + + WATCHDOG, + BOOTSEL, + + TRNG +} + +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] +macro_rules! select_bootloader { + ( $( $feature:literal => $loader:ident, )+ default => $default:ident ) => { + $( + #[cfg(feature = $feature)] + #[link_section = ".boot2"] + #[used] + static BOOT2: [u8; 256] = rp2040_boot2::$loader; + )* + + #[cfg(not(any( $( feature = $feature),* )))] + #[link_section = ".boot2"] + #[used] + static BOOT2: [u8; 256] = rp2040_boot2::$default; + } +} + +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] +select_bootloader! { + "boot2-at25sf128a" => BOOT_LOADER_AT25SF128A, + "boot2-gd25q64cs" => BOOT_LOADER_GD25Q64CS, + "boot2-generic-03h" => BOOT_LOADER_GENERIC_03H, + "boot2-is25lp080" => BOOT_LOADER_IS25LP080, + "boot2-ram-memcpy" => BOOT_LOADER_RAM_MEMCPY, + "boot2-w25q080" => BOOT_LOADER_W25Q080, + "boot2-w25x10cl" => BOOT_LOADER_W25X10CL, + default => BOOT_LOADER_W25Q080 +} + +/// Installs a stack guard for the CORE0 stack in MPU region 0. +/// Will fail if the MPU is already configured. This function requires +/// a `_stack_end` symbol to be defined by the linker script, and expects +/// `_stack_end` to be located at the lowest address (largest depth) of +/// the stack. +/// +/// This method can *only* set up stack guards on the currently +/// executing core. Stack guards for CORE1 are set up automatically, +/// only CORE0 should ever use this. +/// +/// # Usage +/// +/// ```no_run +/// use embassy_rp::install_core0_stack_guard; +/// use embassy_executor::{Executor, Spawner}; +/// +/// #[embassy_executor::main] +/// async fn main(_spawner: Spawner) { +/// // set up by the linker as follows: +/// // +/// // MEMORY { +/// // STACK0: ORIGIN = 0x20040000, LENGTH = 4K +/// // } +/// // +/// // _stack_end = ORIGIN(STACK0); +/// // _stack_start = _stack_end + LENGTH(STACK0); +/// // +/// install_core0_stack_guard().expect("MPU already configured"); +/// let p = embassy_rp::init(Default::default()); +/// +/// // ... +/// } +/// ``` +pub fn install_core0_stack_guard() -> Result<(), ()> { + extern "C" { + static mut _stack_end: usize; + } + unsafe { install_stack_guard(core::ptr::addr_of_mut!(_stack_end)) } +} + +#[cfg(all(feature = "rp2040", not(feature = "_test")))] +#[inline(always)] +unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { + let core = unsafe { cortex_m::Peripherals::steal() }; + + // Fail if MPU is already configured + if core.MPU.ctrl.read() != 0 { + return Err(()); + } + + // 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) | (1 << 4)); // set address and update RNR + 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 + ); + } + Ok(()) +} + +#[cfg(all(feature = "_rp235x", not(feature = "_test")))] +#[inline(always)] +unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { + let core = unsafe { cortex_m::Peripherals::steal() }; + + // Fail if MPU is already configured + if core.MPU.ctrl.read() != 0 { + return Err(()); + } + + unsafe { + core.MPU.ctrl.write(5); // enable mpu with background default map + core.MPU.rbar.write(stack_bottom as u32 & !0xff); // set address + core.MPU.rlar.write(1); // enable region + } + Ok(()) +} + +// This is to hack around cortex_m defaulting to ARMv7 when building tests, +// so the compile fails when we try to use ARMv8 peripherals. +#[cfg(feature = "_test")] +#[inline(always)] +unsafe fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> { + Ok(()) +} + +/// HAL configuration for RP. +pub mod config { + use crate::clocks::ClockConfig; + + /// HAL configuration passed when initializing. + #[non_exhaustive] + pub struct Config { + /// Clock configuration. + pub clocks: ClockConfig, + } + + impl Default for Config { + fn default() -> Self { + Self { + clocks: ClockConfig::crystal(12_000_000), + } + } + } + + impl Config { + /// Create a new configuration with the provided clock config. + pub fn new(clocks: ClockConfig) -> Self { + Self { clocks } + } + } +} + +/// Initialize the `embassy-rp` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once at startup, otherwise it panics. +pub fn init(config: config::Config) -> Peripherals { + // Do this first, so that it panics if user is calling `init` a second time + // before doing anything important. + let peripherals = Peripherals::take(); + + unsafe { + clocks::init(config.clocks); + #[cfg(feature = "time-driver")] + time_driver::init(); + dma::init(); + gpio::init(); + } + + peripherals +} + +#[cfg(all(feature = "rt", feature = "rp2040"))] +#[cortex_m_rt::pre_init] +unsafe fn pre_init() { + // SIO does not get reset when core0 is reset with either `scb::sys_reset()` or with SWD. + // Since we're using SIO spinlock 31 for the critical-section impl, this causes random + // hangs if we reset in the middle of a CS, because the next boot sees the spinlock + // as locked and waits forever. + // + // See https://github.com/embassy-rs/embassy/issues/1736 + // and https://github.com/rp-rs/rp-hal/issues/292 + // and https://matrix.to/#/!vhKMWjizPZBgKeknOo:matrix.org/$VfOkQgyf1PjmaXZbtycFzrCje1RorAXd8BQFHTl4d5M + // + // According to Raspberry Pi, this is considered Working As Intended, and not an errata, + // even though this behavior is different from every other ARM chip (sys_reset usually resets + // the *system* as its name implies, not just the current core). + // + // To fix this, reset SIO on boot. We must do this in pre_init because it's unsound to do it + // in `embassy_rp::init`, since the user could've acquired a CS by then. pre_init is guaranteed + // to run before any user code. + // + // A similar thing could happen with PROC1. It is unclear whether it's possible for PROC1 + // to stay unreset through a PROC0 reset, so we reset it anyway just in case. + // + // Important info from PSM logic (from Luke Wren in above Matrix thread) + // + // The logic is, each PSM stage is reset if either of the following is true: + // - The previous stage is in reset and FRCE_ON is false + // - FRCE_OFF is true + // + // The PSM order is SIO -> PROC0 -> PROC1. + // So, we have to force-on PROC0 to prevent it from getting reset when resetting SIO. + pac::PSM.frce_on().write_and_wait(|w| { + w.set_proc0(true); + }); + // Then reset SIO and PROC1. + pac::PSM.frce_off().write_and_wait(|w| { + w.set_sio(true); + w.set_proc1(true); + }); + // clear force_off first, force_on second. The other way around would reset PROC0. + pac::PSM.frce_off().write_and_wait(|_| {}); + pac::PSM.frce_on().write_and_wait(|_| {}); +} + +/// Extension trait for PAC regs, adding atomic xor/bitset/bitclear writes. +#[allow(unused)] +trait RegExt { + #[allow(unused)] + fn write_xor(&self, f: impl FnOnce(&mut T) -> R) -> R; + fn write_set(&self, f: impl FnOnce(&mut T) -> R) -> R; + fn write_clear(&self, f: impl FnOnce(&mut T) -> R) -> R; + fn write_and_wait(&self, f: impl FnOnce(&mut T) -> R) -> R + where + T: PartialEq; +} + +impl RegExt for pac::common::Reg { + fn write_xor(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x1000) as *mut T; + ptr.write_volatile(val); + } + res + } + + fn write_set(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x2000) as *mut T; + ptr.write_volatile(val); + } + res + } + + fn write_clear(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x3000) as *mut T; + ptr.write_volatile(val); + } + res + } + + fn write_and_wait(&self, f: impl FnOnce(&mut T) -> R) -> R + where + T: PartialEq, + { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + self.as_ptr().write_volatile(val); + while self.as_ptr().read_volatile() != val {} + } + res + } +} diff --git a/embassy/embassy-rp/src/multicore.rs b/embassy/embassy-rp/src/multicore.rs new file mode 100644 index 0000000..ea0a29a --- /dev/null +++ b/embassy/embassy-rp/src/multicore.rs @@ -0,0 +1,344 @@ +//! 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 +//! use embassy_rp::multicore::Stack; +//! use static_cell::StaticCell; +//! use embassy_executor::Executor; +//! +//! static mut CORE1_STACK: Stack<4096> = Stack::new(); +//! static EXECUTOR0: StaticCell = StaticCell::new(); +//! static EXECUTOR1: StaticCell = StaticCell::new(); +//! +//! # // workaround weird error: `main` function not found in crate `rust_out` +//! # let _ = (); +//! +//! #[embassy_executor::task] +//! async fn core0_task() { +//! // ... +//! } +//! +//! #[embassy_executor::task] +//! async fn core1_task() { +//! // ... +//! } +//! +//! #[cortex_m_rt::entry] +//! fn main() -> ! { +//! let p = embassy_rp::init(Default::default()); +//! +//! embassy_rp::multicore::spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { +//! let executor1 = EXECUTOR1.init(Executor::new()); +//! executor1.run(|spawner| spawner.spawn(core1_task()).unwrap()); +//! }); +//! +//! let executor0 = EXECUTOR0.init(Executor::new()); +//! executor0.run(|spawner| spawner.spawn(core0_task()).unwrap()) +//! } +//! ``` + +use core::mem::ManuallyDrop; +use core::sync::atomic::{compiler_fence, AtomicBool, Ordering}; + +use crate::interrupt::InterruptExt; +use crate::peripherals::CORE1; +use crate::{gpio, install_stack_guard, interrupt, pac}; + +const PAUSE_TOKEN: u32 = 0xDEADBEEF; +const RESUME_TOKEN: u32 = !0xDEADBEEF; +static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); + +#[inline(always)] +unsafe fn core1_setup(stack_bottom: *mut usize) { + if install_stack_guard(stack_bottom).is_err() { + // currently only happens if the MPU was already set up, which + // would indicate that the core is already in use from outside + // embassy, somehow. trap if so since we can't deal with that. + cortex_m::asm::udf(); + } + unsafe { + gpio::init(); + } +} + +/// 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] } + } +} + +#[cfg(all(feature = "rt", feature = "rp2040"))] +#[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); + } + } +} + +#[cfg(all(feature = "rt", feature = "_rp235x"))] +#[interrupt] +#[link_section = ".data.ram_func"] +unsafe fn SIO_IRQ_FIFO() { + let sio = pac::SIO; + // Clear IRQ + sio.fifo().st().write(|w| w.set_wof(false)); + + while sio.fifo().st().read().vld() { + // Pause CORE1 execution and disable interrupts + if fifo_read_wfe() == PAUSE_TOKEN { + cortex_m::interrupt::disable(); + // Signal to CORE0 that execution is paused + fifo_write(PAUSE_TOKEN); + // Wait for `resume` signal from CORE0 + while fifo_read_wfe() != RESUME_TOKEN { + cortex_m::asm::nop(); + } + cortex_m::interrupt::enable(); + // Signal to CORE0 that execution is resumed + fifo_write(RESUME_TOKEN); + } + } +} + +/// Spawn a function on this core +pub fn spawn_core1(_core1: CORE1, stack: &'static mut Stack, entry: F) +where + 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, + ) -> ! { + unsafe { core1_setup(stack_bottom) }; + + let entry = unsafe { ManuallyDrop::take(&mut *entry) }; + + // make sure the preceding read doesn't get reordered past the following fifo write + compiler_fence(Ordering::SeqCst); + + // 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. + #[cfg(feature = "rp2040")] + unsafe { + interrupt::SIO_IRQ_PROC1.enable() + }; + #[cfg(feature = "_rp235x")] + unsafe { + interrupt::SIO_IRQ_FIFO.enable() + }; + + // Enable FPU + #[cfg(all(feature = "_rp235x", has_fpu))] + unsafe { + let p = cortex_m::Peripherals::steal(); + p.SCB.cpacr.modify(|cpacr| cpacr | (3 << 20) | (3 << 22)); + } + + entry() + } + + // Reset the core + 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)); + + // The ARM AAPCS ABI requires 8-byte stack alignment. + // #[align] on `struct Stack` ensures the bottom is aligned, but the top could still be + // unaligned if the user chooses a stack size that's not multiple of 8. + // So, we round down to the next multiple of 8. + let stack_words = stack.mem.len() / 8 * 2; + let mem = unsafe { core::slice::from_raw_parts_mut(stack.mem.as_mut_ptr() as *mut usize, stack_words) }; + + // 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) { + 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 { + 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)] +#[allow(unused)] +fn fifo_read_wfe() -> u32 { + 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() { + 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/embassy-rp/src/otp.rs b/embassy/embassy-rp/src/otp.rs new file mode 100644 index 0000000..6091f71 --- /dev/null +++ b/embassy/embassy-rp/src/otp.rs @@ -0,0 +1,175 @@ +//! Interface to the RP2350's One Time Programmable Memory + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs + +use crate::rom_data::otp_access; + +/// The ways in which we can fail to access OTP +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The user passed an invalid index to a function. + InvalidIndex, + /// The hardware refused to let us read this word, probably due to + /// read or write lock set earlier in the boot process. + InvalidPermissions, + /// Modification is impossible based on current state; e.g. + /// attempted to clear an OTP bit. + UnsupportedModification, + /// Value being written is bigger than 24 bits allowed for raw writes. + Overflow, + /// An unexpected failure that contains the exact return code + UnexpectedFailure(i32), +} + +/// OTP read address, using automatic Error Correction. +/// +/// A 32-bit read returns the ECC-corrected data for two neighbouring rows, or +/// all-ones on permission failure. Only the first 8 KiB is populated. +pub const OTP_DATA_BASE: *const u32 = 0x4013_0000 as *const u32; + +/// OTP read address, without using any automatic Error Correction. +/// +/// A 32-bit read returns 24-bits of raw data from the OTP word. +pub const OTP_DATA_RAW_BASE: *const u32 = 0x4013_4000 as *const u32; + +/// How many pages in OTP (post error-correction) +pub const NUM_PAGES: usize = 64; + +/// How many rows in one page in OTP (post error-correction) +pub const NUM_ROWS_PER_PAGE: usize = 64; + +/// How many rows in OTP (post error-correction) +pub const NUM_ROWS: usize = NUM_PAGES * NUM_ROWS_PER_PAGE; + +/// 24bit mask for raw writes +pub const RAW_WRITE_BIT_MASK: u32 = 0x00FF_FFFF; + +/// Read one ECC protected word from the OTP +pub fn read_ecc_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // First do a raw read to check permissions + let _ = read_raw_word(row)?; + // One 32-bit read gets us two rows + let offset = row >> 1; + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_BASE.add(offset).read() }; + if (row & 1) == 0 { + Ok(value as u16) + } else { + Ok((value >> 16) as u16) + } +} + +/// Read one raw word from the OTP +/// +/// You get the 24-bit raw value in the lower part of the 32-bit result. +pub fn read_raw_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // One 32-bit read gets us one row + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_RAW_BASE.add(row).read() }; + if value == 0xFFFF_FFFF { + Err(Error::InvalidPermissions) + } else { + Ok(value) + } +} +/// Write one raw word to the OTP +/// +/// 24 bit value will be written to the OTP +pub fn write_raw_word(row: usize, data: u32) -> Result<(), Error> { + if data > RAW_WRITE_BIT_MASK { + return Err(Error::Overflow); + } + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + let row_with_write_bit = row | 0x00010000; + // # Safety + // + // We checked this row was in range already. + let result = unsafe { otp_access(data.to_le_bytes().as_mut_ptr(), 4, row_with_write_bit as u32) }; + if result == 0 { + Ok(()) + } else { + // 5.4.3. API Function Return Codes + let error = match result { + -4 => Error::InvalidPermissions, + -18 => Error::UnsupportedModification, + _ => Error::UnexpectedFailure(result), + }; + Err(error) + } +} + +/// Write one raw word to the OTP with ECC +/// +/// 16 bit value will be written + ECC +pub fn write_ecc_word(row: usize, data: u16) -> Result<(), Error> { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + let row_with_write_and_ecc_bit = row | 0x00030000; + + // # Safety + // + // We checked this row was in range already. + + let result = unsafe { otp_access(data.to_le_bytes().as_mut_ptr(), 2, row_with_write_and_ecc_bit as u32) }; + if result == 0 { + Ok(()) + } else { + // 5.4.3. API Function Return Codes + // 5.4.3. API Function Return Codes + let error = match result { + -4 => Error::InvalidPermissions, + -18 => Error::UnsupportedModification, + _ => Error::UnexpectedFailure(result), + }; + Err(error) + } +} + +/// Get the random 64bit chipid from rows 0x0-0x3. +pub fn get_chipid() -> Result { + let w0 = read_ecc_word(0x000)?.to_be_bytes(); + let w1 = read_ecc_word(0x001)?.to_be_bytes(); + let w2 = read_ecc_word(0x002)?.to_be_bytes(); + let w3 = read_ecc_word(0x003)?.to_be_bytes(); + + Ok(u64::from_be_bytes([ + w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1], + ])) +} + +/// Get the 128bit private random number from rows 0x4-0xb. +/// +/// This ID is not exposed through the USB PICOBOOT GET_INFO command +/// or the ROM get_sys_info() API. However note that the USB PICOBOOT OTP +/// access point can read the entirety of page 0, so this value is not +/// meaningfully private unless the USB PICOBOOT interface is disabled via the +//// DISABLE_BOOTSEL_USB_PICOBOOT_IFC flag in BOOT_FLAGS0 +pub fn get_private_random_number() -> Result { + let w0 = read_ecc_word(0x004)?.to_be_bytes(); + let w1 = read_ecc_word(0x005)?.to_be_bytes(); + let w2 = read_ecc_word(0x006)?.to_be_bytes(); + let w3 = read_ecc_word(0x007)?.to_be_bytes(); + let w4 = read_ecc_word(0x008)?.to_be_bytes(); + let w5 = read_ecc_word(0x009)?.to_be_bytes(); + let w6 = read_ecc_word(0x00a)?.to_be_bytes(); + let w7 = read_ecc_word(0x00b)?.to_be_bytes(); + + Ok(u128::from_be_bytes([ + w7[0], w7[1], w6[0], w6[1], w5[0], w5[1], w4[0], w4[1], w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1], + ])) +} diff --git a/embassy/embassy-rp/src/pio/instr.rs b/embassy/embassy-rp/src/pio/instr.rs new file mode 100644 index 0000000..9a44088 --- /dev/null +++ b/embassy/embassy-rp/src/pio/instr.rs @@ -0,0 +1,101 @@ +//! Instructions controlling the PIO. +use pio::{InSource, InstructionOperands, JmpCondition, OutDestination, SetDestination}; + +use crate::pio::{Instance, StateMachine}; + +/// Set value of scratch register X. +pub unsafe fn set_x(sm: &mut StateMachine, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::X, + bit_count: 32, + } + .encode(); + sm.tx().push(value); + sm.exec_instr(OUT); +} + +/// Get value of scratch register X. +pub unsafe fn get_x(sm: &mut StateMachine) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::X, + bit_count: 32, + } + .encode(); + sm.exec_instr(IN); + sm.rx().pull() +} + +/// Set value of scratch register Y. +pub unsafe fn set_y(sm: &mut StateMachine, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::Y, + bit_count: 32, + } + .encode(); + sm.tx().push(value); + sm.exec_instr(OUT); +} + +/// Get value of scratch register Y. +pub unsafe fn get_y(sm: &mut StateMachine) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::Y, + bit_count: 32, + } + .encode(); + sm.exec_instr(IN); + + sm.rx().pull() +} + +/// Set instruction for pindir destination. +pub unsafe fn set_pindir(sm: &mut StateMachine, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINDIRS, + data, + } + .encode(); + sm.exec_instr(set); +} + +/// Set instruction for pin destination. +pub unsafe fn set_pin(sm: &mut StateMachine, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINS, + data, + } + .encode(); + sm.exec_instr(set); +} + +/// Out instruction for pin destination. +pub unsafe fn set_out_pin(sm: &mut StateMachine, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINS, + bit_count: 32, + } + .encode(); + sm.tx().push(data); + sm.exec_instr(OUT); +} + +/// Out instruction for pindir destination. +pub unsafe fn set_out_pindir(sm: &mut StateMachine, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINDIRS, + bit_count: 32, + } + .encode(); + sm.tx().push(data); + sm.exec_instr(OUT); +} + +/// Jump instruction to address. +pub unsafe fn exec_jmp(sm: &mut StateMachine, to_addr: u8) { + let jmp: u16 = InstructionOperands::JMP { + address: to_addr, + condition: JmpCondition::Always, + } + .encode(); + sm.exec_instr(jmp); +} diff --git a/embassy/embassy-rp/src/pio/mod.rs b/embassy/embassy-rp/src/pio/mod.rs new file mode 100644 index 0000000..e3c2502 --- /dev/null +++ b/embassy/embassy-rp/src/pio/mod.rs @@ -0,0 +1,1377 @@ +//! PIO driver. +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 atomic_polyfill::{AtomicU64, AtomicU8}; +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use fixed::types::extra::U8; +use fixed::FixedU32; +use pio::{Program, SideSet, Wrap}; + +use crate::dma::{Channel, Transfer, Word}; +use crate::gpio::{self, AnyPin, Drive, Level, Pull, SealedPin, SlewRate}; +use crate::interrupt::typelevel::{Binding, Handler, Interrupt}; +use crate::relocate::RelocatedProgram; +use crate::{pac, peripherals, RegExt}; + +pub mod instr; + +/// Wakers for interrupts and FIFOs. +pub struct Wakers([AtomicWaker; 12]); + +impl Wakers { + #[inline(always)] + fn fifo_in(&self) -> &[AtomicWaker] { + &self.0[0..4] + } + #[inline(always)] + fn fifo_out(&self) -> &[AtomicWaker] { + &self.0[4..8] + } + #[inline(always)] + fn irq(&self) -> &[AtomicWaker] { + &self.0[8..12] + } +} + +/// FIFO config. +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum FifoJoin { + /// Both TX and RX fifo is enabled + #[default] + Duplex, + /// Rx fifo twice as deep. TX fifo disabled + RxOnly, + /// Tx fifo twice as deep. RX fifo disabled + TxOnly, +} + +/// Shift direction. +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum ShiftDirection { + #[default] + Right = 1, + Left = 0, +} + +/// Pin direction. +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum Direction { + In = 0, + Out = 1, +} + +/// Which fifo level to use in status check. +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum StatusSource { + #[default] + /// All-ones if TX FIFO level < N, otherwise all-zeroes. + TxFifoLevel = 0, + /// All-ones if RX FIFO level < N, otherwise all-zeroes. + RxFifoLevel = 1, +} + +const RXNEMPTY_MASK: u32 = 1 << 0; +const TXNFULL_MASK: u32 = 1 << 4; +const SMIRQ_MASK: u32 = 1 << 8; + +/// Interrupt handler for PIO. +pub struct InterruptHandler { + _pio: PhantomData, +} + +impl Handler for InterruptHandler { + unsafe fn on_interrupt() { + let ints = PIO::PIO.irqs(0).ints().read().0; + for bit in 0..12 { + if ints & (1 << bit) != 0 { + PIO::wakers().0[bit].wake(); + } + } + PIO::PIO.irqs(0).inte().write_clear(|m| m.0 = ints); + } +} + +/// Future that waits for TX-FIFO to become writable +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct FifoOutFuture<'a, 'd, PIO: Instance, const SM: usize> { + sm_tx: &'a mut StateMachineTx<'d, PIO, SM>, + value: u32, +} + +impl<'a, 'd, PIO: Instance, const SM: usize> FifoOutFuture<'a, 'd, PIO, SM> { + /// Create a new future waiting for TX-FIFO to become writable. + pub fn new(sm: &'a mut StateMachineTx<'d, PIO, SM>, value: u32) -> Self { + FifoOutFuture { sm_tx: sm, value } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoOutFuture<'a, '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_tx.try_push(value) { + Poll::Ready(()) + } else { + PIO::wakers().fifo_out()[SM].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = TXNFULL_MASK << SM; + }); + // debug!("Pending"); + Poll::Pending + } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Drop for FifoOutFuture<'a, 'd, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = TXNFULL_MASK << SM; + }); + } +} + +/// Future that waits for RX-FIFO to become readable. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct FifoInFuture<'a, 'd, PIO: Instance, const SM: usize> { + sm_rx: &'a mut StateMachineRx<'d, PIO, SM>, +} + +impl<'a, 'd, PIO: Instance, const SM: usize> FifoInFuture<'a, 'd, PIO, SM> { + /// Create future that waits for RX-FIFO to become readable. + pub fn new(sm: &'a mut StateMachineRx<'d, PIO, SM>) -> Self { + FifoInFuture { sm_rx: sm } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoInFuture<'a, '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_rx.try_pull() { + Poll::Ready(v) + } else { + PIO::wakers().fifo_in()[SM].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = RXNEMPTY_MASK << SM; + }); + //debug!("Pending"); + Poll::Pending + } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Drop for FifoInFuture<'a, 'd, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = RXNEMPTY_MASK << SM; + }); + } +} + +/// Future that waits for IRQ +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct IrqFuture<'a, 'd, PIO: Instance> { + pio: PhantomData<&'a mut Irq<'d, PIO, 0>>, + irq_no: u8, +} + +impl<'a, 'd, PIO: Instance> Future for IrqFuture<'a, 'd, PIO> { + 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 PIO::PIO.irq().read().0 & (1 << self.irq_no) != 0 { + PIO::PIO.irq().write(|m| m.0 = 1 << self.irq_no); + return Poll::Ready(()); + } + + PIO::wakers().irq()[self.irq_no as usize].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = SMIRQ_MASK << self.irq_no; + }); + Poll::Pending + } +} + +impl<'a, 'd, PIO: Instance> Drop for IrqFuture<'a, 'd, PIO> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = SMIRQ_MASK << self.irq_no; + }); + } +} + +/// Type representing a PIO pin. +pub struct Pin<'l, PIO: Instance> { + pin: PeripheralRef<'l, AnyPin>, + pio: PhantomData, +} + +impl<'l, PIO: Instance> Pin<'l, PIO> { + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + self.pin.pad_ctrl().modify(|w| { + w.set_drive(match strength { + Drive::_2mA => pac::pads::vals::Drive::_2MA, + Drive::_4mA => pac::pads::vals::Drive::_4MA, + Drive::_8mA => pac::pads::vals::Drive::_8MA, + Drive::_12mA => pac::pads::vals::Drive::_12MA, + }); + }); + } + + /// Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.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) { + self.pin.pad_ctrl().modify(|w| { + w.set_pue(pull == Pull::Up); + w.set_pde(pull == Pull::Down); + }); + } + + /// Set the pin's schmitt trigger. + #[inline] + pub fn set_schmitt(&mut self, enable: bool) { + self.pin.pad_ctrl().modify(|w| { + w.set_schmitt(enable); + }); + } + + /// Set the pin's input sync bypass. + pub fn set_input_sync_bypass(&mut self, bypass: bool) { + let mask = 1 << self.pin(); + if bypass { + PIO::PIO.input_sync_bypass().write_set(|w| *w = mask); + } else { + PIO::PIO.input_sync_bypass().write_clear(|w| *w = mask); + } + } + + /// Get the underlying pin number. + pub fn pin(&self) -> u8 { + self.pin._pin() + } +} + +/// Type representing a state machine RX FIFO. +pub struct StateMachineRx<'d, PIO: Instance, const SM: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> { + /// Check if RX FIFO is empty. + pub fn empty(&self) -> bool { + PIO::PIO.fstat().read().rxempty() & (1u8 << SM) != 0 + } + + /// Check if RX FIFO is full. + pub fn full(&self) -> bool { + PIO::PIO.fstat().read().rxfull() & (1u8 << SM) != 0 + } + + /// Check RX FIFO level. + pub fn level(&self) -> u8 { + (PIO::PIO.flevel().read().0 >> (SM * 8 + 4)) as u8 & 0x0f + } + + /// Check if state machine has stalled on full RX FIFO. + pub fn stalled(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().rxstall() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_rxstall(1 << SM)); + } + ret + } + + /// Check if RX FIFO underflow (i.e. read-on-empty by the system) has occurred. + pub fn underflowed(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().rxunder() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_rxunder(1 << SM)); + } + ret + } + + /// Pull data from RX FIFO. + /// + /// This function doesn't check if there is data available to be read. + /// If the rx FIFO is empty, an undefined value is returned. If you only + /// want to pull if data is available, use `try_pull` instead. + pub fn pull(&mut self) -> u32 { + PIO::PIO.rxf(SM).read() + } + + /// Attempt pulling data from RX FIFO. + pub fn try_pull(&mut self) -> Option { + if self.empty() { + return None; + } + Some(self.pull()) + } + + /// Wait for RX FIFO readable. + pub fn wait_pull<'a>(&'a mut self) -> FifoInFuture<'a, 'd, PIO, SM> { + FifoInFuture::new(self) + } + + /// Prepare DMA transfer from RX FIFO. + pub fn dma_pull<'a, C: Channel, W: Word>( + &'a mut self, + ch: PeripheralRef<'a, C>, + data: &'a mut [W], + ) -> Transfer<'a, C> { + let pio_no = PIO::PIO_NO; + let p = ch.regs(); + p.write_addr().write_value(data.as_ptr() as u32); + p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32); + #[cfg(feature = "rp2040")] + p.trans_count().write(|w| *w = data.len() as u32); + #[cfg(feature = "_rp235x")] + p.trans_count().write(|w| w.set_count(data.len() as u32)); + compiler_fence(Ordering::SeqCst); + p.ctrl_trig().write(|w| { + // Set RX DREQ for this statemachine + w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8 + 4)); + w.set_data_size(W::size()); + w.set_chain_to(ch.number()); + w.set_incr_read(false); + w.set_incr_write(true); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) + } +} + +/// Type representing a state machine TX FIFO. +pub struct StateMachineTx<'d, PIO: Instance, const SM: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { + /// Check if TX FIFO is empty. + pub fn empty(&self) -> bool { + PIO::PIO.fstat().read().txempty() & (1u8 << SM) != 0 + } + + /// Check if TX FIFO is full. + pub fn full(&self) -> bool { + PIO::PIO.fstat().read().txfull() & (1u8 << SM) != 0 + } + + /// Check TX FIFO level. + pub fn level(&self) -> u8 { + (PIO::PIO.flevel().read().0 >> (SM * 8)) as u8 & 0x0f + } + + /// Check state machine has stalled on empty TX FIFO. + pub fn stalled(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().txstall() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_txstall(1 << SM)); + } + ret + } + + /// Check if FIFO overflowed. + pub fn overflowed(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().txover() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_txover(1 << SM)); + } + ret + } + + /// Force push data to TX FIFO. + pub fn push(&mut self, v: u32) { + PIO::PIO.txf(SM).write_value(v); + } + + /// Attempt to push data to TX FIFO. + pub fn try_push(&mut self, v: u32) -> bool { + if self.full() { + return false; + } + self.push(v); + true + } + + /// Wait until FIFO is ready for writing. + pub fn wait_push<'a>(&'a mut self, value: u32) -> FifoOutFuture<'a, 'd, PIO, SM> { + FifoOutFuture::new(self, value) + } + + /// Prepare a DMA transfer to TX FIFO. + pub fn dma_push<'a, C: Channel, W: Word>(&'a mut self, ch: PeripheralRef<'a, C>, data: &'a [W]) -> Transfer<'a, C> { + let pio_no = PIO::PIO_NO; + let p = ch.regs(); + p.read_addr().write_value(data.as_ptr() as u32); + p.write_addr().write_value(PIO::PIO.txf(SM).as_ptr() as u32); + #[cfg(feature = "rp2040")] + p.trans_count().write(|w| *w = data.len() as u32); + #[cfg(feature = "_rp235x")] + p.trans_count().write(|w| w.set_count(data.len() as u32)); + compiler_fence(Ordering::SeqCst); + p.ctrl_trig().write(|w| { + // Set TX DREQ for this statemachine + w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8)); + w.set_data_size(W::size()); + w.set_chain_to(ch.number()); + w.set_incr_read(true); + w.set_incr_write(false); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) + } +} + +/// A type representing a single PIO state machine. +pub struct StateMachine<'d, PIO: Instance, const SM: usize> { + rx: StateMachineRx<'d, PIO, SM>, + tx: StateMachineTx<'d, PIO, SM>, +} + +impl<'d, PIO: Instance, const SM: usize> Drop for StateMachine<'d, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.ctrl().write_clear(|w| w.set_sm_enable(1 << SM)); + on_pio_drop::(); + } +} + +fn assert_consecutive(pins: &[&Pin]) { + for (p1, p2) in pins.iter().zip(pins.iter().skip(1)) { + // purposely does not allow wrap-around because we can't claim pins 30 and 31. + assert!(p1.pin() + 1 == p2.pin(), "pins must be consecutive"); + } +} + +/// PIO Execution config. +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct ExecConfig { + /// If true, the MSB of the Delay/Side-set instruction field is used as side-set enable, rather than a side-set data bit. + pub side_en: bool, + /// If true, side-set data is asserted to pin directions, instead of pin values. + pub side_pindir: bool, + /// Pin to trigger jump. + pub jmp_pin: u8, + /// After reaching this address, execution is wrapped to wrap_bottom. + pub wrap_top: u8, + /// After reaching wrap_top, execution is wrapped to this address. + pub wrap_bottom: u8, +} + +/// PIO shift register config for input or output. +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ShiftConfig { + /// Number of bits shifted out of OSR before autopull. + pub threshold: u8, + /// Shift direction. + pub direction: ShiftDirection, + /// For output: Pull automatically output shift register is emptied. + /// For input: Push automatically when the input shift register is filled. + pub auto_fill: bool, +} + +/// PIO pin config. +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PinConfig { + /// The number of MSBs of the Delay/Side-set instruction field which are used for side-set. + pub sideset_count: u8, + /// The number of pins asserted by a SET. In the range 0 to 5 inclusive. + pub set_count: u8, + /// The number of pins asserted by an OUT PINS, OUT PINDIRS or MOV PINS instruction. In the range 0 to 32 inclusive. + pub out_count: u8, + /// The pin which is mapped to the least-significant bit of a state machine's IN data bus. + pub in_base: u8, + /// The lowest-numbered pin that will be affected by a side-set operation. + pub sideset_base: u8, + /// The lowest-numbered pin that will be affected by a SET PINS or SET PINDIRS instruction. + pub set_base: u8, + /// The lowest-numbered pin that will be affected by an OUT PINS, OUT PINDIRS or MOV PINS instruction. + pub out_base: u8, +} + +/// Comparison level or IRQ index for the MOV x, STATUS instruction. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(feature = "_rp235x")] +pub enum StatusN { + /// IRQ flag in this PIO block + This(u8), + /// IRQ flag in the next lower PIO block + Lower(u8), + /// IRQ flag in the next higher PIO block + Higher(u8), +} + +#[cfg(feature = "_rp235x")] +impl Default for StatusN { + fn default() -> Self { + Self::This(0) + } +} + +#[cfg(feature = "_rp235x")] +impl Into for StatusN { + fn into(self) -> crate::pac::pio::vals::ExecctrlStatusN { + let x = match self { + StatusN::This(n) => n, + StatusN::Lower(n) => n + 0x08, + StatusN::Higher(n) => n + 0x10, + }; + + crate::pac::pio::vals::ExecctrlStatusN(x) + } +} + +/// PIO config. +#[derive(Clone, Copy, Debug)] +pub struct Config<'d, PIO: Instance> { + /// Clock divisor register for state machines. + pub clock_divider: FixedU32, + /// Which data bit to use for inline OUT enable. + pub out_en_sel: u8, + /// Use a bit of OUT data as an auxiliary write enable When used in conjunction with OUT_STICKY. + pub inline_out_en: bool, + /// Continuously assert the most recent OUT/SET to the pins. + pub out_sticky: bool, + /// Which source to use for checking status. + pub status_sel: StatusSource, + /// Status comparison level. + #[cfg(feature = "rp2040")] + pub status_n: u8, + // This cfg probably shouldn't be required, but the SVD for the 2040 doesn't have the enum + #[cfg(feature = "_rp235x")] + /// Status comparison level. + pub status_n: StatusN, + exec: ExecConfig, + origin: Option, + /// Configure FIFO allocation. + pub fifo_join: FifoJoin, + /// Input shifting config. + pub shift_in: ShiftConfig, + /// Output shifting config. + pub shift_out: ShiftConfig, + // PINCTRL + pins: PinConfig, + in_count: u8, + _pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Default for Config<'d, PIO> { + fn default() -> Self { + Self { + clock_divider: 1u8.into(), + out_en_sel: Default::default(), + inline_out_en: Default::default(), + out_sticky: Default::default(), + status_sel: Default::default(), + status_n: Default::default(), + exec: Default::default(), + origin: Default::default(), + fifo_join: Default::default(), + shift_in: Default::default(), + shift_out: Default::default(), + pins: Default::default(), + in_count: Default::default(), + _pio: Default::default(), + } + } +} + +impl<'d, PIO: Instance> Config<'d, PIO> { + /// Get execution configuration. + pub fn get_exec(&self) -> ExecConfig { + self.exec + } + + /// Update execution configuration. + pub unsafe fn set_exec(&mut self, e: ExecConfig) { + self.exec = e; + } + + /// Get pin configuration. + pub fn get_pins(&self) -> PinConfig { + self.pins + } + + /// Update pin configuration. + pub unsafe fn set_pins(&mut self, p: PinConfig) { + self.pins = p; + } + + /// Configures this state machine to use the given program, including jumping to the origin + /// of the program. The state machine is not started. + /// + /// `side_set` sets the range of pins affected by side-sets. The range must be consecutive. + /// Side-set pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn use_program(&mut self, prog: &LoadedProgram<'d, PIO>, side_set: &[&Pin<'d, PIO>]) { + assert!((prog.side_set.bits() - prog.side_set.optional() as u8) as usize == side_set.len()); + assert_consecutive(side_set); + self.exec.side_en = prog.side_set.optional(); + self.exec.side_pindir = prog.side_set.pindirs(); + self.exec.wrap_bottom = prog.wrap.target; + self.exec.wrap_top = prog.wrap.source; + self.pins.sideset_count = prog.side_set.bits(); + self.pins.sideset_base = side_set.first().map_or(0, |p| p.pin()); + self.origin = Some(prog.origin); + } + + /// Set pin used to signal jump. + pub fn set_jmp_pin(&mut self, pin: &Pin<'d, PIO>) { + self.exec.jmp_pin = pin.pin(); + } + + /// Sets the range of pins affected by SET instructions. The range must be consecutive. + /// Set pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_set_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert!(pins.len() <= 5); + assert_consecutive(pins); + self.pins.set_base = pins.first().map_or(0, |p| p.pin()); + self.pins.set_count = pins.len() as u8; + } + + /// Sets the range of pins affected by OUT instructions. The range must be consecutive. + /// Out pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_out_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert_consecutive(pins); + self.pins.out_base = pins.first().map_or(0, |p| p.pin()); + self.pins.out_count = pins.len() as u8; + } + + /// Sets the range of pins used by IN instructions. The range must be consecutive. + /// In pins must configured as inputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_in_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert_consecutive(pins); + self.pins.in_base = pins.first().map_or(0, |p| p.pin()); + self.in_count = pins.len() as u8; + } +} + +impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { + /// Set the config for a given PIO state machine. + pub fn set_config(&mut self, config: &Config<'d, PIO>) { + // sm expects 0 for 65536, truncation makes that happen + assert!(config.clock_divider <= 65536, "clkdiv must be <= 65536"); + assert!(config.clock_divider >= 1, "clkdiv must be >= 1"); + assert!(config.out_en_sel < 32, "out_en_sel must be < 32"); + //assert!(config.status_n < 32, "status_n must be < 32"); + // sm expects 0 for 32, truncation makes that happen + assert!(config.shift_in.threshold <= 32, "shift_in.threshold must be <= 32"); + assert!(config.shift_out.threshold <= 32, "shift_out.threshold must be <= 32"); + let sm = Self::this_sm(); + sm.clkdiv().write(|w| w.0 = config.clock_divider.to_bits() << 8); + sm.execctrl().write(|w| { + w.set_side_en(config.exec.side_en); + w.set_side_pindir(config.exec.side_pindir); + w.set_jmp_pin(config.exec.jmp_pin); + w.set_out_en_sel(config.out_en_sel); + w.set_inline_out_en(config.inline_out_en); + w.set_out_sticky(config.out_sticky); + w.set_wrap_top(config.exec.wrap_top); + w.set_wrap_bottom(config.exec.wrap_bottom); + #[cfg(feature = "_rp235x")] + w.set_status_sel(match config.status_sel { + StatusSource::TxFifoLevel => pac::pio::vals::ExecctrlStatusSel::TXLEVEL, + StatusSource::RxFifoLevel => pac::pio::vals::ExecctrlStatusSel::RXLEVEL, + }); + #[cfg(feature = "rp2040")] + w.set_status_sel(match config.status_sel { + StatusSource::TxFifoLevel => pac::pio::vals::SmExecctrlStatusSel::TXLEVEL, + StatusSource::RxFifoLevel => pac::pio::vals::SmExecctrlStatusSel::RXLEVEL, + }); + w.set_status_n(config.status_n.into()); + }); + sm.shiftctrl().write(|w| { + w.set_fjoin_rx(config.fifo_join == FifoJoin::RxOnly); + w.set_fjoin_tx(config.fifo_join == FifoJoin::TxOnly); + w.set_pull_thresh(config.shift_out.threshold); + w.set_push_thresh(config.shift_in.threshold); + w.set_out_shiftdir(config.shift_out.direction == ShiftDirection::Right); + w.set_in_shiftdir(config.shift_in.direction == ShiftDirection::Right); + w.set_autopull(config.shift_out.auto_fill); + w.set_autopush(config.shift_in.auto_fill); + }); + + #[cfg(feature = "rp2040")] + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base); + w.set_sideset_base(config.pins.sideset_base); + w.set_set_base(config.pins.set_base); + w.set_out_base(config.pins.out_base); + }); + + #[cfg(feature = "_rp235x")] + { + let mut low_ok = true; + let mut high_ok = true; + + let in_pins = config.pins.in_base..config.pins.in_base + config.in_count; + let side_pins = config.pins.sideset_base..config.pins.sideset_base + config.pins.sideset_count; + let set_pins = config.pins.set_base..config.pins.set_base + config.pins.set_count; + let out_pins = config.pins.out_base..config.pins.out_base + config.pins.out_count; + + for pin_range in [in_pins, side_pins, set_pins, out_pins] { + for pin in pin_range { + low_ok &= pin < 32; + high_ok &= pin >= 16; + } + } + + if !low_ok && !high_ok { + panic!( + "All pins must either be <32 or >=16, in:{:?}-{:?}, side:{:?}-{:?}, set:{:?}-{:?}, out:{:?}-{:?}", + config.pins.in_base, + config.pins.in_base + config.in_count - 1, + config.pins.sideset_base, + config.pins.sideset_base + config.pins.sideset_count - 1, + config.pins.set_base, + config.pins.set_base + config.pins.set_count - 1, + config.pins.out_base, + config.pins.out_base + config.pins.out_count - 1, + ) + } + let shift = if low_ok { 0 } else { 16 }; + + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base.checked_sub(shift).unwrap_or_default()); + w.set_sideset_base(config.pins.sideset_base.checked_sub(shift).unwrap_or_default()); + w.set_set_base(config.pins.set_base.checked_sub(shift).unwrap_or_default()); + w.set_out_base(config.pins.out_base.checked_sub(shift).unwrap_or_default()); + }); + + PIO::PIO.gpiobase().write(|w| w.set_gpiobase(shift == 16)); + } + + if let Some(origin) = config.origin { + unsafe { instr::exec_jmp(self, origin) } + } + } + + /// Set the clock divider for this state machine. + pub fn set_clock_divider(&mut self, clock_divider: FixedU32) { + let sm = Self::this_sm(); + sm.clkdiv().write(|w| w.0 = clock_divider.to_bits() << 8); + } + + #[inline(always)] + fn this_sm() -> crate::pac::pio::StateMachine { + PIO::PIO.sm(SM) + } + + /// Restart this state machine. + pub fn restart(&mut self) { + let mask = 1u8 << SM; + PIO::PIO.ctrl().write_set(|w| w.set_sm_restart(mask)); + } + + /// Enable state machine. + pub fn set_enable(&mut self, enable: bool) { + let mask = 1u8 << SM; + if enable { + PIO::PIO.ctrl().write_set(|w| w.set_sm_enable(mask)); + } else { + PIO::PIO.ctrl().write_clear(|w| w.set_sm_enable(mask)); + } + } + + /// Check if state machine is enabled. + pub fn is_enabled(&self) -> bool { + PIO::PIO.ctrl().read().sm_enable() & (1u8 << SM) != 0 + } + + /// Restart a state machine's clock divider from an initial phase of 0. + pub fn clkdiv_restart(&mut self) { + let mask = 1u8 << SM; + PIO::PIO.ctrl().write_set(|w| w.set_clkdiv_restart(mask)); + } + + fn with_paused(&mut self, f: impl FnOnce(&mut Self)) { + let enabled = self.is_enabled(); + self.set_enable(false); + let pincfg = Self::this_sm().pinctrl().read(); + let execcfg = Self::this_sm().execctrl().read(); + Self::this_sm().execctrl().write_clear(|w| w.set_out_sticky(true)); + f(self); + Self::this_sm().pinctrl().write_value(pincfg); + Self::this_sm().execctrl().write_value(execcfg); + self.set_enable(enabled); + } + + /// Sets pin directions. This pauses the current state machine to run `SET` commands + /// and temporarily unsets the `OUT_STICKY` bit. + pub fn set_pin_dirs(&mut self, dir: Direction, pins: &[&Pin<'d, PIO>]) { + self.with_paused(|sm| { + for pin in pins { + Self::this_sm().pinctrl().write(|w| { + w.set_set_base(pin.pin()); + w.set_set_count(1); + }); + // SET PINDIRS, (dir) + unsafe { sm.exec_instr(0b111_00000_100_00000 | dir as u16) }; + } + }); + } + + /// Sets pin output values. This pauses the current state machine to run + /// `SET` commands and temporarily unsets the `OUT_STICKY` bit. + pub fn set_pins(&mut self, level: Level, pins: &[&Pin<'d, PIO>]) { + self.with_paused(|sm| { + for pin in pins { + Self::this_sm().pinctrl().write(|w| { + w.set_set_base(pin.pin()); + w.set_set_count(1); + }); + // SET PINS, (dir) + unsafe { sm.exec_instr(0b11100_000_000_00000 | level as u16) }; + } + }); + } + + /// Flush FIFOs for state machine. + pub fn clear_fifos(&mut self) { + // Toggle FJOIN_RX to flush FIFOs + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.modify(|w| { + w.set_fjoin_rx(!w.fjoin_rx()); + }); + shiftctrl.modify(|w| { + w.set_fjoin_rx(!w.fjoin_rx()); + }); + } + + /// Instruct state machine to execute a given instructions + /// + /// SAFETY: The state machine must be in a state where executing + /// an arbitrary instruction does not crash it. + pub unsafe fn exec_instr(&mut self, instr: u16) { + Self::this_sm().instr().write(|w| w.set_instr(instr)); + } + + /// Return a read handle for reading state machine outputs. + pub fn rx(&mut self) -> &mut StateMachineRx<'d, PIO, SM> { + &mut self.rx + } + + /// Return a handle for writing to inputs. + pub fn tx(&mut self) -> &mut StateMachineTx<'d, PIO, SM> { + &mut self.tx + } + + /// Return both read and write handles for the state machine. + pub fn rx_tx(&mut self) -> (&mut StateMachineRx<'d, PIO, SM>, &mut StateMachineTx<'d, PIO, SM>) { + (&mut self.rx, &mut self.tx) + } +} + +/// PIO handle. +pub struct Common<'d, PIO: Instance> { + instructions_used: u32, + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Drop for Common<'d, PIO> { + fn drop(&mut self) { + on_pio_drop::(); + } +} + +/// Memory of PIO instance. +pub struct InstanceMemory<'d, PIO: Instance> { + used_mask: u32, + pio: PhantomData<&'d mut PIO>, +} + +/// A loaded PIO program. +pub struct LoadedProgram<'d, PIO: Instance> { + /// Memory used by program. + pub used_memory: InstanceMemory<'d, PIO>, + /// Program origin for loading. + pub origin: u8, + /// Wrap controls what to do once program is done executing. + pub wrap: Wrap, + /// Data for 'side' set instruction parameters. + pub side_set: SideSet, +} + +/// Errors loading a PIO program. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LoadError { + /// Insufficient consecutive free instruction space to load program. + InsufficientSpace, + /// Loading the program would overwrite an instruction address already + /// used by another program. + AddressInUse(usize), +} + +impl<'d, PIO: Instance> Common<'d, PIO> { + /// Load a PIO program. This will automatically relocate the program to + /// an available chunk of free instruction memory if the program origin + /// was not explicitly specified, otherwise it will attempt to load the + /// program only at its origin. + pub fn load_program(&mut self, prog: &Program) -> LoadedProgram<'d, PIO> { + match self.try_load_program(prog) { + Ok(r) => r, + Err(e) => panic!("Failed to load PIO program: {:?}", e), + } + } + + /// Load a PIO program. This will automatically relocate the program to + /// an available chunk of free instruction memory if the program origin + /// was not explicitly specified, otherwise it will attempt to load the + /// program only at its origin. + pub fn try_load_program( + &mut self, + prog: &Program, + ) -> Result, LoadError> { + match prog.origin { + Some(origin) => self.try_load_program_at(prog, origin).map_err(LoadError::AddressInUse), + None => { + // naively search for free space, allowing wraparound since + // PIO does support that. with only 32 instruction slots it + // doesn't make much sense to do anything more fancy. + let mut origin = 0; + while origin < 32 { + match self.try_load_program_at(prog, origin as _) { + Ok(r) => return Ok(r), + Err(a) => origin = a + 1, + } + } + Err(LoadError::InsufficientSpace) + } + } + } + + fn try_load_program_at( + &mut self, + prog: &Program, + origin: u8, + ) -> Result, usize> { + let prog = RelocatedProgram::new_with_origin(prog, origin); + let used_memory = self.try_write_instr(prog.origin() as _, prog.code())?; + Ok(LoadedProgram { + used_memory, + origin: prog.origin(), + wrap: prog.wrap(), + side_set: prog.side_set(), + }) + } + + fn try_write_instr(&mut self, start: usize, instrs: I) -> Result, usize> + where + I: Iterator, + { + let mut used_mask = 0; + for (i, instr) in instrs.enumerate() { + // wrapping around the end of program memory is valid, let's make use of that. + let addr = (i + start) % 32; + let mask = 1 << addr; + if (self.instructions_used | used_mask) & mask != 0 { + return Err(addr); + } + PIO::PIO.instr_mem(addr).write(|w| { + w.set_instr_mem(instr); + }); + used_mask |= mask; + } + self.instructions_used |= used_mask; + Ok(InstanceMemory { + used_mask, + pio: PhantomData, + }) + } + + /// Free instruction memory. This is always possible but unsafe if any + /// state machine is still using this bit of memory. + pub unsafe fn free_instr(&mut self, instrs: InstanceMemory) { + self.instructions_used &= !instrs.used_mask; + } + + /// Bypass flipflop synchronizer on GPIO inputs. + pub fn set_input_sync_bypass<'a>(&'a mut self, bypass: u32, mask: u32) { + // this can interfere with per-pin bypass functions. splitting the + // modification is going to be fine since nothing that relies on + // it can reasonably run before we finish. + PIO::PIO.input_sync_bypass().write_set(|w| *w = mask & bypass); + PIO::PIO.input_sync_bypass().write_clear(|w| *w = mask & !bypass); + } + + /// Get bypass configuration. + pub fn get_input_sync_bypass(&self) -> u32 { + PIO::PIO.input_sync_bypass().read() + } + + /// Register a pin for PIO usage. Pins will be released from the PIO block + /// (i.e., have their `FUNCSEL` reset to `NULL`) when the [`Common`] *and* + /// all [`StateMachine`]s for this block have been dropped. **Other members + /// of [`Pio`] do not keep pin registrations alive.** + pub fn make_pio_pin(&mut self, pin: impl Peripheral

+ 'd) -> Pin<'d, PIO> { + into_ref!(pin); + pin.gpio().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + // TODO rp235x errata E9 recommends to not enable IE if we're not + // going to use input. Maybe add an API for the user to enable/disable this? + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); + // we can be relaxed about this because we're &mut here and nothing is cached + PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed); + Pin { + pin: pin.into_ref().map_into(), + pio: PhantomData::default(), + } + } + + /// Apply changes to all state machines in a batch. + pub fn apply_sm_batch(&mut self, f: impl FnOnce(&mut PioBatch<'d, PIO>)) { + let mut batch = PioBatch { + clkdiv_restart: 0, + sm_restart: 0, + sm_enable_mask: 0, + sm_enable: 0, + _pio: PhantomData, + }; + f(&mut batch); + PIO::PIO.ctrl().modify(|w| { + w.set_clkdiv_restart(batch.clkdiv_restart); + w.set_sm_restart(batch.sm_restart); + w.set_sm_enable((w.sm_enable() & !batch.sm_enable_mask) | batch.sm_enable); + }); + } +} + +/// Represents multiple state machines in a single type. +pub struct PioBatch<'a, PIO: Instance> { + clkdiv_restart: u8, + sm_restart: u8, + sm_enable_mask: u8, + sm_enable: u8, + _pio: PhantomData<&'a PIO>, +} + +impl<'a, PIO: Instance> PioBatch<'a, PIO> { + /// Restart a state machine's clock divider from an initial phase of 0. + pub fn restart(&mut self, _sm: &mut StateMachine<'a, PIO, SM>) { + self.clkdiv_restart |= 1 << SM; + } + + /// Enable a specific state machine. + pub fn set_enable(&mut self, _sm: &mut StateMachine<'a, PIO, SM>, enable: bool) { + self.sm_enable_mask |= 1 << SM; + self.sm_enable |= (enable as u8) << SM; + } +} + +/// Type representing a PIO interrupt. +pub struct Irq<'d, PIO: Instance, const N: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const N: usize> Irq<'d, PIO, N> { + /// Wait for an IRQ to fire. + pub fn wait<'a>(&'a mut self) -> IrqFuture<'a, 'd, PIO> { + IrqFuture { + pio: PhantomData, + irq_no: N as u8, + } + } +} + +/// Interrupt flags for a PIO instance. +#[derive(Clone)] +pub struct IrqFlags<'d, PIO: Instance> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> IrqFlags<'d, PIO> { + /// Check if interrupt fired. + pub fn check(&self, irq_no: u8) -> bool { + assert!(irq_no < 8); + self.check_any(1 << irq_no) + } + + /// Check if any of the interrupts in the bitmap fired. + pub fn check_any(&self, irqs: u8) -> bool { + PIO::PIO.irq().read().irq() & irqs != 0 + } + + /// Check if all interrupts have fired. + pub fn check_all(&self, irqs: u8) -> bool { + PIO::PIO.irq().read().irq() & irqs == irqs + } + + /// Clear interrupt for interrupt number. + pub fn clear(&self, irq_no: usize) { + assert!(irq_no < 8); + self.clear_all(1 << irq_no); + } + + /// Clear all interrupts set in the bitmap. + pub fn clear_all(&self, irqs: u8) { + PIO::PIO.irq().write(|w| w.set_irq(irqs)) + } + + /// Fire a given interrupt. + pub fn set(&self, irq_no: usize) { + assert!(irq_no < 8); + self.set_all(1 << irq_no); + } + + /// Fire all interrupts. + pub fn set_all(&self, irqs: u8) { + PIO::PIO.irq_force().write(|w| w.set_irq_force(irqs)) + } +} + +/// An instance of the PIO driver. +pub struct Pio<'d, PIO: Instance> { + /// PIO handle. + pub common: Common<'d, PIO>, + /// PIO IRQ flags. + pub irq_flags: IrqFlags<'d, PIO>, + /// IRQ0 configuration. + pub irq0: Irq<'d, PIO, 0>, + /// IRQ1 configuration. + pub irq1: Irq<'d, PIO, 1>, + /// IRQ2 configuration. + pub irq2: Irq<'d, PIO, 2>, + /// IRQ3 configuration. + pub irq3: Irq<'d, PIO, 3>, + /// State machine 0 handle. + pub sm0: StateMachine<'d, PIO, 0>, + /// State machine 1 handle. + pub sm1: StateMachine<'d, PIO, 1>, + /// State machine 2 handle. + pub sm2: StateMachine<'d, PIO, 2>, + /// State machine 3 handle. + pub sm3: StateMachine<'d, PIO, 3>, + _pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Pio<'d, PIO> { + /// Create a new instance of a PIO peripheral. + pub fn new(_pio: impl Peripheral

+ 'd, _irq: impl Binding>) -> Self { + PIO::state().users.store(5, Ordering::Release); + PIO::state().used_pins.store(0, Ordering::Release); + PIO::Interrupt::unpend(); + unsafe { PIO::Interrupt::enable() }; + Self { + common: Common { + instructions_used: 0, + pio: PhantomData, + }, + irq_flags: IrqFlags { pio: PhantomData }, + irq0: Irq { pio: PhantomData }, + irq1: Irq { pio: PhantomData }, + irq2: Irq { pio: PhantomData }, + irq3: Irq { pio: PhantomData }, + sm0: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm1: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm2: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm3: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + _pio: PhantomData, + } + } +} + +/// Representation of the PIO state keeping a record of which pins are assigned to +/// each PIO. +// make_pio_pin notionally takes ownership of the pin it is given, but the wrapped pin +// cannot be treated as an owned resource since dropping it would have to deconfigure +// the pin, breaking running state machines in the process. pins are also shared +// between all state machines, which makes ownership even messier to track any +// other way. +pub struct State { + users: AtomicU8, + used_pins: AtomicU64, +} + +fn on_pio_drop() { + let state = PIO::state(); + if state.users.fetch_sub(1, Ordering::AcqRel) == 1 { + let used_pins = state.used_pins.load(Ordering::Relaxed); + let null = pac::io::vals::Gpio0ctrlFuncsel::NULL as _; + for i in 0..crate::gpio::BANK0_PIN_COUNT { + if used_pins & (1 << i) != 0 { + pac::IO_BANK0.gpio(i).ctrl().write(|w| w.set_funcsel(null)); + } + } + } +} + +trait SealedInstance { + const PIO_NO: u8; + const PIO: &'static crate::pac::pio::Pio; + const FUNCSEL: crate::pac::io::vals::Gpio0ctrlFuncsel; + + #[inline] + fn wakers() -> &'static Wakers { + static WAKERS: Wakers = Wakers([const { AtomicWaker::new() }; 12]); + &WAKERS + } + + #[inline] + fn state() -> &'static State { + static STATE: State = State { + users: AtomicU8::new(0), + used_pins: AtomicU64::new(0), + }; + + &STATE + } +} + +/// PIO instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + Sized + Unpin { + /// Interrupt for this peripheral. + type Interrupt: crate::interrupt::typelevel::Interrupt; +} + +macro_rules! impl_pio { + ($name:ident, $pio:expr, $pac:ident, $funcsel:ident, $irq:ident) => { + impl SealedInstance for peripherals::$name { + const PIO_NO: u8 = $pio; + const PIO: &'static pac::pio::Pio = &pac::$pac; + const FUNCSEL: pac::io::vals::Gpio0ctrlFuncsel = pac::io::vals::Gpio0ctrlFuncsel::$funcsel; + } + impl Instance for peripherals::$name { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +impl_pio!(PIO0, 0, PIO0, PIO0_0, PIO0_IRQ_0); +impl_pio!(PIO1, 1, PIO1, PIO1_0, PIO1_IRQ_0); +#[cfg(feature = "_rp235x")] +impl_pio!(PIO2, 2, PIO2, PIO2_0, PIO2_IRQ_0); + +/// PIO pin. +pub trait PioPin: gpio::Pin {} + +macro_rules! impl_pio_pin { + ($( $pin:ident, )*) => { + $( + impl PioPin for peripherals::$pin {} + )* + }; +} + +impl_pio_pin! { + PIN_0, + PIN_1, + PIN_2, + PIN_3, + PIN_4, + PIN_5, + PIN_6, + PIN_7, + PIN_8, + PIN_9, + PIN_10, + PIN_11, + PIN_12, + PIN_13, + PIN_14, + PIN_15, + PIN_16, + PIN_17, + PIN_18, + PIN_19, + PIN_20, + PIN_21, + PIN_22, + PIN_23, + PIN_24, + PIN_25, + PIN_26, + PIN_27, + PIN_28, + PIN_29, +} + +#[cfg(feature = "rp235xb")] +impl_pio_pin! { + PIN_30, + PIN_31, + PIN_32, + PIN_33, + PIN_34, + PIN_35, + PIN_36, + PIN_37, + PIN_38, + PIN_39, + PIN_40, + PIN_41, + PIN_42, + PIN_43, + PIN_44, + PIN_45, + PIN_46, + PIN_47, +} diff --git a/embassy/embassy-rp/src/pio_programs/hd44780.rs b/embassy/embassy-rp/src/pio_programs/hd44780.rs new file mode 100644 index 0000000..9bbf44f --- /dev/null +++ b/embassy/embassy-rp/src/pio_programs/hd44780.rs @@ -0,0 +1,203 @@ +//! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +use crate::dma::{AnyChannel, Channel}; +use crate::pio::{ + Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, + StateMachine, +}; +use crate::{into_ref, Peripheral, PeripheralRef}; + +/// This struct represents a HD44780 program that takes command words ( <0:4>) +pub struct PioHD44780CommandWordProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioHD44780CommandWordProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + r#" + .side_set 1 opt + .origin 20 + + loop: + out x, 24 + delay: + jmp x--, delay + out pins, 4 side 1 + out null, 4 side 0 + jmp !osre, loop + irq 0 + "#, + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// This struct represents a HD44780 program that takes command sequences ( , data...) +pub struct PioHD44780CommandSequenceProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioHD44780CommandSequenceProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + // many side sets are only there to free up a delay bit! + let prg = pio_proc::pio_asm!( + r#" + .origin 27 + .side_set 1 + + .wrap_target + pull side 0 + out x 1 side 0 ; !rs + out y 7 side 0 ; #data - 1 + + ; rs/rw to e: >= 60ns + ; e high time: >= 500ns + ; e low time: >= 500ns + ; read data valid after e falling: ~5ns + ; write data hold after e falling: ~10ns + + loop: + pull side 0 + jmp !x data side 0 + command: + set pins 0b00 side 0 + jmp shift side 0 + data: + set pins 0b01 side 0 + shift: + out pins 4 side 1 [9] + nop side 0 [9] + out pins 4 side 1 [9] + mov osr null side 0 [7] + out pindirs 4 side 0 + set pins 0b10 side 0 + busy: + nop side 1 [9] + jmp pin more side 0 [9] + mov osr ~osr side 1 [9] + nop side 0 [4] + out pindirs 4 side 0 + jmp y-- loop side 0 + .wrap + more: + nop side 1 [9] + jmp busy side 0 [9] + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed HD44780 driver +pub struct PioHD44780<'l, P: Instance, const S: usize> { + dma: PeripheralRef<'l, AnyChannel>, + sm: StateMachine<'l, P, S>, + + buf: [u8; 40], +} + +impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> { + /// Configure the given state machine to first init, then write data to, a HD44780 display. + pub async fn new( + common: &mut Common<'l, P>, + mut sm: StateMachine<'l, P, S>, + mut irq: Irq<'l, P, S>, + dma: impl Peripheral

+ 'l, + rs: impl PioPin, + rw: impl PioPin, + e: impl PioPin, + db4: impl PioPin, + db5: impl PioPin, + db6: impl PioPin, + db7: impl PioPin, + word_prg: &PioHD44780CommandWordProgram<'l, P>, + seq_prg: &PioHD44780CommandSequenceProgram<'l, P>, + ) -> PioHD44780<'l, P, S> { + into_ref!(dma); + + let rs = common.make_pio_pin(rs); + let rw = common.make_pio_pin(rw); + let e = common.make_pio_pin(e); + let db4 = common.make_pio_pin(db4); + let db5 = common.make_pio_pin(db5); + let db6 = common.make_pio_pin(db6); + let db7 = common.make_pio_pin(db7); + + sm.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); + + let mut cfg = Config::default(); + cfg.use_program(&word_prg.prg, &[&e]); + cfg.clock_divider = 125u8.into(); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Left, + threshold: 32, + }; + cfg.fifo_join = FifoJoin::TxOnly; + sm.set_config(&cfg); + + sm.set_enable(true); + // init to 8 bit thrice + sm.tx().push((50000 << 8) | 0x30); + sm.tx().push((5000 << 8) | 0x30); + sm.tx().push((200 << 8) | 0x30); + // init 4 bit + sm.tx().push((200 << 8) | 0x20); + // set font and lines + sm.tx().push((50 << 8) | 0x20); + sm.tx().push(0b1100_0000); + + irq.wait().await; + sm.set_enable(false); + + let mut cfg = Config::default(); + cfg.use_program(&seq_prg.prg, &[&e]); + cfg.clock_divider = 8u8.into(); // ~64ns/insn + cfg.set_jmp_pin(&db7); + cfg.set_set_pins(&[&rs, &rw]); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out.direction = ShiftDirection::Left; + cfg.fifo_join = FifoJoin::TxOnly; + sm.set_config(&cfg); + + sm.set_enable(true); + + // display on and cursor on and blinking, reset display + sm.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; + + Self { + dma: dma.map_into(), + sm, + buf: [0x20; 40], + } + } + + /// Write a line to the display + pub async fn add_line(&mut self, s: &[u8]) { + // move cursor to 0:0, prepare 16 characters + self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); + // move line 2 up + self.buf.copy_within(22..38, 3); + // move cursor to 1:0, prepare 16 characters + self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); + // file line 2 with spaces + self.buf[22..38].fill(0x20); + // copy input line + let len = s.len().min(16); + self.buf[22..22 + len].copy_from_slice(&s[0..len]); + // set cursor to 1:15 + self.buf[38..].copy_from_slice(&[0x80, 0xcf]); + + self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; + } +} diff --git a/embassy/embassy-rp/src/pio_programs/i2s.rs b/embassy/embassy-rp/src/pio_programs/i2s.rs new file mode 100644 index 0000000..87fb2e1 --- /dev/null +++ b/embassy/embassy-rp/src/pio_programs/i2s.rs @@ -0,0 +1,95 @@ +//! Pio backed I2s output + +use fixed::traits::ToFixed; + +use crate::dma::{AnyChannel, Channel, Transfer}; +use crate::pio::{ + Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use crate::{into_ref, Peripheral, PeripheralRef}; + +/// This struct represents an i2s output driver program +pub struct PioI2sOutProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioI2sOutProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + ".side_set 2", + " set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock + "left_data:", + " out pins, 1 side 0b00", + " jmp x-- left_data side 0b01", + " out pins 1 side 0b10", + " set x, 14 side 0b11", + "right_data:", + " out pins 1 side 0b10", + " jmp x-- right_data side 0b11", + " out pins 1 side 0b00", + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed I2s output driver +pub struct PioI2sOut<'a, P: Instance, const S: usize> { + dma: PeripheralRef<'a, AnyChannel>, + sm: StateMachine<'a, P, S>, +} + +impl<'a, P: Instance, const S: usize> PioI2sOut<'a, P, S> { + /// Configure a state machine to output I2s + pub fn new( + common: &mut Common<'a, P>, + mut sm: StateMachine<'a, P, S>, + dma: impl Peripheral

+ 'a, + data_pin: impl PioPin, + bit_clock_pin: impl PioPin, + lr_clock_pin: impl PioPin, + sample_rate: u32, + bit_depth: u32, + channels: u32, + program: &PioI2sOutProgram<'a, P>, + ) -> Self { + into_ref!(dma); + + let data_pin = common.make_pio_pin(data_pin); + let bit_clock_pin = common.make_pio_pin(bit_clock_pin); + let left_right_clock_pin = common.make_pio_pin(lr_clock_pin); + + let cfg = { + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]); + cfg.set_out_pins(&[&data_pin]); + let clock_frequency = sample_rate * bit_depth * channels; + cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / clock_frequency as f64 / 2.).to_fixed(); + cfg.shift_out = ShiftConfig { + threshold: 32, + direction: ShiftDirection::Left, + auto_fill: true, + }; + // join fifos to have twice the time to start the next dma transfer + cfg.fifo_join = FifoJoin::TxOnly; + cfg + }; + sm.set_config(&cfg); + sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]); + + sm.set_enable(true); + + Self { + dma: dma.map_into(), + sm, + } + } + + /// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer. + pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> { + self.sm.tx().dma_push(self.dma.reborrow(), buff) + } +} diff --git a/embassy/embassy-rp/src/pio_programs/mod.rs b/embassy/embassy-rp/src/pio_programs/mod.rs new file mode 100644 index 0000000..7453782 --- /dev/null +++ b/embassy/embassy-rp/src/pio_programs/mod.rs @@ -0,0 +1,10 @@ +//! Pre-built pio programs for common interfaces + +pub mod hd44780; +pub mod i2s; +pub mod onewire; +pub mod pwm; +pub mod rotary_encoder; +pub mod stepper; +pub mod uart; +pub mod ws2812; diff --git a/embassy/embassy-rp/src/pio_programs/onewire.rs b/embassy/embassy-rp/src/pio_programs/onewire.rs new file mode 100644 index 0000000..f3bc5fc --- /dev/null +++ b/embassy/embassy-rp/src/pio_programs/onewire.rs @@ -0,0 +1,109 @@ +//! OneWire pio driver + +use crate::pio::{self, Common, Config, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine}; + +/// This struct represents an onewire driver program +pub struct PioOneWireProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + r#" + .wrap_target + again: + pull block + mov x, osr + jmp !x, read + write: + set pindirs, 1 + set pins, 0 + loop1: + jmp x--,loop1 + set pindirs, 0 [31] + wait 1 pin 0 [31] + pull block + mov x, osr + bytes1: + pull block + set y, 7 + set pindirs, 1 + bit1: + set pins, 0 [1] + out pins,1 [31] + set pins, 1 [20] + jmp y--,bit1 + jmp x--,bytes1 + set pindirs, 0 [31] + jmp again + read: + pull block + mov x, osr + bytes2: + set y, 7 + bit2: + set pindirs, 1 + set pins, 0 [1] + set pindirs, 0 [5] + in pins,1 [10] + jmp y--,bit2 + jmp x--,bytes2 + .wrap + "#, + ); + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed OneWire driver +pub struct PioOneWire<'d, PIO: pio::Instance, const SM: usize> { + sm: StateMachine<'d, PIO, SM>, +} + +impl<'d, PIO: pio::Instance, const SM: usize> PioOneWire<'d, PIO, SM> { + /// Create a new instance the driver + pub fn new( + common: &mut Common<'d, PIO>, + mut sm: StateMachine<'d, PIO, SM>, + pin: impl PioPin, + program: &PioOneWireProgram<'d, PIO>, + ) -> Self { + let pin = common.make_pio_pin(pin); + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[]); + cfg.set_out_pins(&[&pin]); + cfg.set_in_pins(&[&pin]); + cfg.set_set_pins(&[&pin]); + cfg.shift_in = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Right, + threshold: 8, + }; + cfg.clock_divider = 255_u8.into(); + sm.set_config(&cfg); + sm.set_enable(true); + Self { sm } + } + + /// Write bytes over the wire + pub async fn write_bytes(&mut self, bytes: &[u8]) { + self.sm.tx().wait_push(250).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes { + self.sm.tx().wait_push(*b as u32).await; + } + } + + /// Read bytes from the wire + pub async fn read_bytes(&mut self, bytes: &mut [u8]) { + self.sm.tx().wait_push(0).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes.iter_mut() { + *b = (self.sm.rx().wait_pull().await >> 24) as u8; + } + } +} diff --git a/embassy/embassy-rp/src/pio_programs/pwm.rs b/embassy/embassy-rp/src/pio_programs/pwm.rs new file mode 100644 index 0000000..c650238 --- /dev/null +++ b/embassy/embassy-rp/src/pio_programs/pwm.rs @@ -0,0 +1,121 @@ +//! PIO backed PWM driver + +use core::time::Duration; + +use pio::InstructionOperands; + +use crate::clocks; +use crate::gpio::Level; +use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine}; + +/// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time +fn to_pio_cycles(duration: Duration) -> u32 { + (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow +} + +/// This struct represents a PWM program loaded into pio instruction memory. +pub struct PioPwmProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + ".side_set 1 opt" + "pull noblock side 0" + "mov x, osr" + "mov y, isr" + "countloop:" + "jmp x!=y noset" + "jmp skip side 1" + "noset:" + "nop" + "skip:" + "jmp y-- countloop" + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed PWM output +pub struct PioPwm<'d, T: Instance, const SM: usize> { + sm: StateMachine<'d, T, SM>, + pin: Pin<'d, T>, +} + +impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { + /// Configure a state machine as a PWM output + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + pin: impl PioPin, + program: &PioPwmProgram<'d, T>, + ) -> Self { + let pin = pio.make_pio_pin(pin); + sm.set_pins(Level::High, &[&pin]); + sm.set_pin_dirs(Direction::Out, &[&pin]); + + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&pin]); + + sm.set_config(&cfg); + + Self { sm, pin } + } + + /// Enable's the PIO program, continuing the wave generation from the PIO program. + pub fn start(&mut self) { + self.sm.set_enable(true); + } + + /// Stops the PIO program, ceasing all signals from the PIN that were generated via PIO. + pub fn stop(&mut self) { + self.sm.set_enable(false); + } + + /// Sets the pwm period, which is the length of time for each pio wave until reset. + pub fn set_period(&mut self, duration: Duration) { + let is_enabled = self.sm.is_enabled(); + while !self.sm.tx().empty() {} // Make sure that the queue is empty + self.sm.set_enable(false); + self.sm.tx().push(to_pio_cycles(duration)); + unsafe { + self.sm.exec_instr( + InstructionOperands::PULL { + if_empty: false, + block: false, + } + .encode(), + ); + self.sm.exec_instr( + InstructionOperands::OUT { + destination: ::pio::OutDestination::ISR, + bit_count: 32, + } + .encode(), + ); + }; + if is_enabled { + self.sm.set_enable(true) // Enable if previously enabled + } + } + + /// Set the number of pio cycles to set the wave on high to. + pub fn set_level(&mut self, level: u32) { + self.sm.tx().push(level); + } + + /// Set the pulse width high time + pub fn write(&mut self, duration: Duration) { + self.set_level(to_pio_cycles(duration)); + } + + /// Return the state machine and pin. + pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) { + (self.sm, self.pin) + } +} diff --git a/embassy/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy/embassy-rp/src/pio_programs/rotary_encoder.rs new file mode 100644 index 0000000..86423fd --- /dev/null +++ b/embassy/embassy-rp/src/pio_programs/rotary_encoder.rs @@ -0,0 +1,73 @@ +//! PIO backed quadrature encoder + +use fixed::traits::ToFixed; + +use crate::gpio::Pull; +use crate::pio::{self, Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine}; + +/// This struct represents an Encoder program loaded into pio instruction memory. +pub struct PioEncoderProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioEncoderProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio Backed quadrature encoder reader +pub struct PioEncoder<'d, T: Instance, const SM: usize> { + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { + /// Configure a state machine with the loaded [PioEncoderProgram] + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + pin_a: impl PioPin, + pin_b: impl PioPin, + program: &PioEncoderProgram<'d, T>, + ) -> Self { + let mut pin_a = pio.make_pio_pin(pin_a); + let mut pin_b = pio.make_pio_pin(pin_b); + pin_a.set_pull(Pull::Up); + pin_b.set_pull(Pull::Up); + sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]); + + let mut cfg = Config::default(); + cfg.set_in_pins(&[&pin_a, &pin_b]); + cfg.fifo_join = FifoJoin::RxOnly; + cfg.shift_in.direction = ShiftDirection::Left; + cfg.clock_divider = 10_000.to_fixed(); + cfg.use_program(&program.prg, &[]); + sm.set_config(&cfg); + sm.set_enable(true); + Self { sm } + } + + /// Read a single count from the encoder + pub async fn read(&mut self) -> Direction { + loop { + match self.sm.rx().wait_pull().await { + 0 => return Direction::CounterClockwise, + 1 => return Direction::Clockwise, + _ => {} + } + } + } +} + +/// Encoder Count Direction +pub enum Direction { + /// Encoder turned clockwise + Clockwise, + /// Encoder turned counter clockwise + CounterClockwise, +} diff --git a/embassy/embassy-rp/src/pio_programs/stepper.rs b/embassy/embassy-rp/src/pio_programs/stepper.rs new file mode 100644 index 0000000..0d58c75 --- /dev/null +++ b/embassy/embassy-rp/src/pio_programs/stepper.rs @@ -0,0 +1,147 @@ +//! Pio Stepper Driver for 5-wire steppers + +use core::mem::{self, MaybeUninit}; + +use fixed::traits::ToFixed; +use fixed::types::extra::U8; +use fixed::FixedU32; + +use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; + +/// This struct represents a Stepper driver program loaded into pio instruction memory. +pub struct PioStepperProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioStepperProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + "pull block", + "mov x, osr", + "pull block", + "mov y, osr", + "jmp !x end", + "loop:", + "jmp !osre step", + "mov osr, y", + "step:", + "out pins, 4 [31]" + "jmp x-- loop", + "end:", + "irq 0 rel" + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed Stepper driver +pub struct PioStepper<'d, T: Instance, const SM: usize> { + irq: Irq<'d, T, SM>, + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { + /// Configure a state machine to drive a stepper + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + irq: Irq<'d, T, SM>, + pin0: impl PioPin, + pin1: impl PioPin, + pin2: impl PioPin, + pin3: impl PioPin, + program: &PioStepperProgram<'d, T>, + ) -> Self { + let pin0 = pio.make_pio_pin(pin0); + let pin1 = pio.make_pio_pin(pin1); + let pin2 = pio.make_pio_pin(pin2); + let pin3 = pio.make_pio_pin(pin3); + sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); + let mut cfg = Config::default(); + cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); + cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); + cfg.use_program(&program.prg, &[]); + sm.set_config(&cfg); + sm.set_enable(true); + Self { irq, sm } + } + + /// Set pulse frequency + pub fn set_frequency(&mut self, freq: u32) { + let clock_divider: FixedU32 = (125_000_000 / (freq * 136)).to_fixed(); + assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); + assert!(clock_divider >= 1, "clkdiv must be >= 1"); + self.sm.set_clock_divider(clock_divider); + self.sm.clkdiv_restart(); + } + + /// Full step, one phase + pub async fn step(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await + } else { + self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await + } + } + + /// Full step, two phase + pub async fn step2(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await + } else { + self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await + } + } + + /// Half step + pub async fn step_half(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await + } else { + self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await + } + } + + async fn run(&mut self, steps: i32, pattern: u32) { + self.sm.tx().wait_push(steps as u32).await; + self.sm.tx().wait_push(pattern).await; + let drop = OnDrop::new(|| { + self.sm.clear_fifos(); + unsafe { + self.sm.exec_instr( + pio::InstructionOperands::JMP { + address: 0, + condition: pio::JmpCondition::Always, + } + .encode(), + ); + } + }); + self.irq.wait().await; + drop.defuse(); + } +} + +struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + pub fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } + + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} diff --git a/embassy/embassy-rp/src/pio_programs/uart.rs b/embassy/embassy-rp/src/pio_programs/uart.rs new file mode 100644 index 0000000..c643f10 --- /dev/null +++ b/embassy/embassy-rp/src/pio_programs/uart.rs @@ -0,0 +1,185 @@ +//! Pio backed uart drivers + +use core::convert::Infallible; + +use embedded_io_async::{ErrorType, Read, Write}; +use fixed::traits::ToFixed; + +use crate::clocks::clk_sys_freq; +use crate::gpio::Level; +use crate::pio::{ + Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, +}; + +/// This struct represents a uart tx program loaded into pio instruction memory. +pub struct PioUartTxProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioUartTxProgram<'a, PIO> { + /// Load the uart tx program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + r#" + .side_set 1 opt + + ; An 8n1 UART transmit program. + ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin. + + pull side 1 [7] ; Assert stop bit, or stall with line in idle state + set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks + bitloop: ; This loop will run 8 times (8n1 UART) + out pins, 1 ; Shift 1 bit from OSR to the first OUT pin + jmp x-- bitloop [6] ; Each loop iteration is 8 cycles. + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// PIO backed Uart transmitter +pub struct PioUartTx<'a, PIO: Instance, const SM: usize> { + sm_tx: StateMachine<'a, PIO, SM>, +} + +impl<'a, PIO: Instance, const SM: usize> PioUartTx<'a, PIO, SM> { + /// Configure a pio state machine to use the loaded tx program. + pub fn new( + baud: u32, + common: &mut Common<'a, PIO>, + mut sm_tx: StateMachine<'a, PIO, SM>, + tx_pin: impl PioPin, + program: &PioUartTxProgram<'a, PIO>, + ) -> Self { + let tx_pin = common.make_pio_pin(tx_pin); + sm_tx.set_pins(Level::High, &[&tx_pin]); + sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]); + + let mut cfg = Config::default(); + + cfg.set_out_pins(&[&tx_pin]); + cfg.use_program(&program.prg, &[&tx_pin]); + cfg.shift_out.auto_fill = false; + cfg.shift_out.direction = ShiftDirection::Right; + cfg.fifo_join = FifoJoin::TxOnly; + cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed(); + sm_tx.set_config(&cfg); + sm_tx.set_enable(true); + + Self { sm_tx } + } + + /// Write a single u8 + pub async fn write_u8(&mut self, data: u8) { + self.sm_tx.tx().wait_push(data as u32).await; + } +} + +impl ErrorType for PioUartTx<'_, PIO, SM> { + type Error = Infallible; +} + +impl Write for PioUartTx<'_, PIO, SM> { + async fn write(&mut self, buf: &[u8]) -> Result { + for byte in buf { + self.write_u8(*byte).await; + } + Ok(buf.len()) + } +} + +/// This struct represents a Uart Rx program loaded into pio instruction memory. +pub struct PioUartRxProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioUartRxProgram<'a, PIO> { + /// Load the uart rx program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio_proc::pio_asm!( + r#" + ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and + ; break conditions more gracefully. + ; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX. + + start: + wait 0 pin 0 ; Stall until start bit is asserted + set x, 7 [10] ; Preload bit counter, then delay until halfway through + rx_bitloop: ; the first data bit (12 cycles incl wait, set). + in pins, 1 ; Shift data bit into ISR + jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles + jmp pin good_rx_stop ; Check stop bit (should be high) + + irq 4 rel ; Either a framing error or a break. Set a sticky flag, + wait 1 pin 0 ; and wait for line to return to idle state. + jmp start ; Don't push data if we didn't see good framing. + + good_rx_stop: ; No delay before returning to start; a little slack is + in null 24 + push ; important in case the TX clock is slightly too fast. + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// PIO backed Uart reciever +pub struct PioUartRx<'a, PIO: Instance, const SM: usize> { + sm_rx: StateMachine<'a, PIO, SM>, +} + +impl<'a, PIO: Instance, const SM: usize> PioUartRx<'a, PIO, SM> { + /// Configure a pio state machine to use the loaded rx program. + pub fn new( + baud: u32, + common: &mut Common<'a, PIO>, + mut sm_rx: StateMachine<'a, PIO, SM>, + rx_pin: impl PioPin, + program: &PioUartRxProgram<'a, PIO>, + ) -> Self { + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[]); + + let rx_pin = common.make_pio_pin(rx_pin); + sm_rx.set_pins(Level::High, &[&rx_pin]); + cfg.set_in_pins(&[&rx_pin]); + cfg.set_jmp_pin(&rx_pin); + sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]); + + cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed(); + cfg.shift_in.auto_fill = false; + cfg.shift_in.direction = ShiftDirection::Right; + cfg.shift_in.threshold = 32; + cfg.fifo_join = FifoJoin::RxOnly; + sm_rx.set_config(&cfg); + sm_rx.set_enable(true); + + Self { sm_rx } + } + + /// Wait for a single u8 + pub async fn read_u8(&mut self) -> u8 { + self.sm_rx.rx().wait_pull().await as u8 + } +} + +impl ErrorType for PioUartRx<'_, PIO, SM> { + type Error = Infallible; +} + +impl Read for PioUartRx<'_, PIO, SM> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + let mut i = 0; + while i < buf.len() { + buf[i] = self.read_u8().await; + i += 1; + } + Ok(i) + } +} diff --git a/embassy/embassy-rp/src/pio_programs/ws2812.rs b/embassy/embassy-rp/src/pio_programs/ws2812.rs new file mode 100644 index 0000000..875f020 --- /dev/null +++ b/embassy/embassy-rp/src/pio_programs/ws2812.rs @@ -0,0 +1,118 @@ +//! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +use embassy_time::Timer; +use fixed::types::U24F8; +use smart_leds::RGB8; + +use crate::clocks::clk_sys_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::pio::{ + Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use crate::{into_ref, Peripheral, PeripheralRef}; + +const T1: u8 = 2; // start bit +const T2: u8 = 5; // data bit +const T3: u8 = 3; // stop bit +const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; + +/// This struct represents a ws2812 program loaded into pio instruction memory. +pub struct PioWs2812Program<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> { + /// Load the ws2812 program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let side_set = pio::SideSet::new(false, 1, false); + let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); + + let mut wrap_target = a.label(); + let mut wrap_source = a.label(); + let mut do_zero = a.label(); + a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); + a.bind(&mut wrap_target); + // Do stop bit + a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); + // Do start bit + a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); + // Do data bit = 1 + a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); + a.bind(&mut do_zero); + // Do data bit = 0 + a.nop_with_delay_and_side_set(T2 - 1, 0); + a.bind(&mut wrap_source); + + let prg = a.assemble_with_wrap(wrap_source, wrap_target); + let prg = common.load_program(&prg); + + Self { prg } + } +} + +/// Pio backed ws2812 driver +/// Const N is the number of ws2812 leds attached to this pin +pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> { + dma: PeripheralRef<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> { + /// Configure a pio state machine to use the loaded ws2812 program. + pub fn new( + pio: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: impl Peripheral

+ 'd, + pin: impl PioPin, + program: &PioWs2812Program<'d, P>, + ) -> Self { + into_ref!(dma); + + // Setup sm0 + let mut cfg = Config::default(); + + // Pin config + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + + cfg.use_program(&program.prg, &[&out_pin]); + + // Clock config, measured in kHz to avoid overflows + let clock_freq = U24F8::from_num(clk_sys_freq() / 1000); + let ws2812_freq = U24F8::from_num(800); + let bit_freq = ws2812_freq * CYCLES_PER_BIT; + cfg.clock_divider = clock_freq / bit_freq; + + // FIFO config + cfg.fifo_join = FifoJoin::TxOnly; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 24, + direction: ShiftDirection::Left, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + Self { + dma: dma.map_into(), + sm, + } + } + + /// Write a buffer of [smart_leds::RGB8] to the ws2812 string + pub async fn write(&mut self, colors: &[RGB8; N]) { + // Precompute the word bytes from the colors + let mut words = [0u32; N]; + for i in 0..N { + let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); + words[i] = word; + } + + // DMA transfer + self.sm.tx().dma_push(self.dma.reborrow(), &words).await; + + Timer::after_micros(55).await; + } +} diff --git a/embassy/embassy-rp/src/pwm.rs b/embassy/embassy-rp/src/pwm.rs new file mode 100644 index 0000000..4fb8ade --- /dev/null +++ b/embassy/embassy-rp/src/pwm.rs @@ -0,0 +1,611 @@ +//! Pulse Width Modulation (PWM) + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +pub use embedded_hal_1::pwm::SetDutyCycle; +use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType}; +use fixed::traits::ToFixed; +use fixed::FixedU16; +use pac::pwm::regs::{ChDiv, Intr}; +use pac::pwm::vals::Divmode; + +use crate::gpio::{AnyPin, Pin as GpioPin, Pull, SealedPin as _}; +use crate::{pac, peripherals, RegExt}; + +/// The configuration of a PWM slice. +/// Note the period in clock cycles of a slice can be computed as: +/// `(top + 1) * (phase_correct ? 1 : 2) * divider` +#[non_exhaustive] +#[derive(Clone)] +pub struct Config { + /// Inverts the PWM output signal on channel A. + pub invert_a: bool, + /// Inverts the PWM output signal on channel B. + pub invert_b: bool, + /// Enables phase-correct mode for PWM operation. + /// In phase-correct mode, the PWM signal is generated in such a way that + /// the pulse is always centered regardless of the duty cycle. + /// The output frequency is halved when phase-correct mode is enabled. + pub phase_correct: bool, + /// Enables the PWM slice, allowing it to generate an output. + pub enable: bool, + /// A fractional clock divider, represented as a fixed-point number with + /// 8 integer bits and 4 fractional bits. It allows precise control over + /// the PWM output frequency by gating the PWM counter increment. + /// A higher value will result in a slower output frequency. + pub divider: fixed::FixedU16, + /// The output on channel A goes high when `compare_a` is higher than the + /// counter. A compare of 0 will produce an always low output, while a + /// compare of `top + 1` will produce an always high output. + pub compare_a: u16, + /// The output on channel B goes high when `compare_b` is higher than the + /// counter. A compare of 0 will produce an always low output, while a + /// compare of `top + 1` will produce an always high output. + pub compare_b: u16, + /// The point at which the counter wraps, representing the maximum possible + /// period. The counter will either wrap to 0 or reverse depending on the + /// setting of `phase_correct`. + pub top: u16, +} + +impl Default for Config { + fn default() -> Self { + Self { + invert_a: false, + invert_b: false, + phase_correct: false, + enable: true, // differs from reset value + divider: 1.to_fixed(), + compare_a: 0, + compare_b: 0, + top: 0xffff, + } + } +} + +/// PWM input mode. +pub enum InputMode { + /// Level mode. + Level, + /// Rising edge mode. + RisingEdge, + /// Falling edge mode. + FallingEdge, +} + +impl From for Divmode { + fn from(value: InputMode) -> Self { + match value { + InputMode::Level => Divmode::LEVEL, + InputMode::RisingEdge => Divmode::RISE, + InputMode::FallingEdge => Divmode::FALL, + } + } +} + +/// PWM error. +#[derive(Debug)] +pub enum PwmError { + /// Invalid Duty Cycle. + InvalidDutyCycle, +} + +impl Error for PwmError { + fn kind(&self) -> ErrorKind { + match self { + PwmError::InvalidDutyCycle => ErrorKind::Other, + } + } +} + +/// PWM driver. +pub struct Pwm<'d> { + pin_a: Option>, + pin_b: Option>, + slice: usize, +} + +impl<'d> ErrorType for Pwm<'d> { + type Error = PwmError; +} + +impl<'d> SetDutyCycle for Pwm<'d> { + fn max_duty_cycle(&self) -> u16 { + pac::PWM.ch(self.slice).top().read().top() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + let max_duty = self.max_duty_cycle(); + if duty > max_duty { + return Err(PwmError::InvalidDutyCycle); + } + + let p = pac::PWM.ch(self.slice); + p.cc().modify(|w| { + w.set_a(duty); + w.set_b(duty); + }); + Ok(()) + } +} + +impl<'d> Pwm<'d> { + fn new_inner( + slice: usize, + a: Option>, + b: Option>, + b_pull: Pull, + config: Config, + divmode: Divmode, + ) -> Self { + let p = pac::PWM.ch(slice); + p.csr().modify(|w| { + w.set_divmode(divmode); + w.set_en(false); + }); + p.ctr().write(|w| w.0 = 0); + Self::configure(p, &config); + + if let Some(pin) = &a { + pin.gpio().ctrl().write(|w| w.set_funcsel(4)); + #[cfg(feature = "_rp235x")] + pin.pad_ctrl().modify(|w| { + w.set_iso(false); + }); + } + if let Some(pin) = &b { + pin.gpio().ctrl().write(|w| w.set_funcsel(4)); + pin.pad_ctrl().modify(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_pue(b_pull == Pull::Up); + w.set_pde(b_pull == Pull::Down); + }); + } + Self { + // inner: p.into(), + pin_a: a, + pin_b: b, + slice, + } + } + + /// Create PWM driver without any configured pins. + #[inline] + pub fn new_free(slice: impl Peripheral

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

+ 'd, + a: impl Peripheral

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

+ 'd, + b: impl Peripheral

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

+ 'd, + a: impl Peripheral

> + 'd, + b: impl Peripheral

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

+ 'd, + b: impl Peripheral

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

+ 'd, + a: impl Peripheral

> + 'd, + b: impl Peripheral

> + 'd, + b_pull: Pull, + mode: InputMode, + config: Config, + ) -> Self { + into_ref!(slice, a, b); + Self::new_inner( + slice.number(), + Some(a.map_into()), + Some(b.map_into()), + b_pull, + config, + mode.into(), + ) + } + + /// Set the PWM config. + pub fn set_config(&mut self, config: &Config) { + Self::configure(pac::PWM.ch(self.slice), config); + } + + fn configure(p: pac::pwm::Channel, config: &Config) { + if config.divider > FixedU16::::from_bits(0xFFF) { + panic!("Requested divider is too large"); + } + + p.div().write_value(ChDiv(config.divider.to_bits() as u32)); + p.cc().write(|w| { + w.set_a(config.compare_a); + w.set_b(config.compare_b); + }); + p.top().write(|w| w.set_top(config.top)); + p.csr().modify(|w| { + w.set_a_inv(config.invert_a); + w.set_b_inv(config.invert_b); + w.set_ph_correct(config.phase_correct); + w.set_en(config.enable); + }); + } + + /// Advances a slice's output phase by one count while it is running + /// by inserting a pulse into the clock enable. The counter + /// will not count faster than once per cycle. + #[inline] + pub fn phase_advance(&mut self) { + let p = pac::PWM.ch(self.slice); + p.csr().write_set(|w| w.set_ph_adv(true)); + while p.csr().read().ph_adv() {} + } + + /// Retards a slice's output phase by one count while it is running + /// by deleting a pulse from the clock enable. The counter will not + /// count backward when clock enable is permanently low. + #[inline] + pub fn phase_retard(&mut self) { + let p = pac::PWM.ch(self.slice); + p.csr().write_set(|w| w.set_ph_ret(true)); + while p.csr().read().ph_ret() {} + } + + /// Read PWM counter. + #[inline] + pub fn counter(&self) -> u16 { + pac::PWM.ch(self.slice).ctr().read().ctr() + } + + /// Write PWM counter. + #[inline] + pub fn set_counter(&self, ctr: u16) { + pac::PWM.ch(self.slice).ctr().write(|w| w.set_ctr(ctr)) + } + + /// Wait for channel interrupt. + #[inline] + pub fn wait_for_wrap(&mut self) { + while !self.wrapped() {} + self.clear_wrapped(); + } + + /// Check if interrupt for channel is set. + #[inline] + pub fn wrapped(&mut self) -> bool { + pac::PWM.intr().read().0 & self.bit() != 0 + } + + #[inline] + /// Clear interrupt flag. + pub fn clear_wrapped(&mut self) { + pac::PWM.intr().write_value(Intr(self.bit() as _)); + } + + #[inline] + fn bit(&self) -> u32 { + 1 << self.slice as usize + } + + /// Splits the PWM driver into separate `PwmOutput` instances for channels A and B. + #[inline] + pub fn split(mut self) -> (Option>, Option>) { + ( + self.pin_a + .take() + .map(|pin| PwmOutput::new(PwmChannelPin::A(pin), self.slice.clone(), true)), + self.pin_b + .take() + .map(|pin| PwmOutput::new(PwmChannelPin::B(pin), self.slice.clone(), true)), + ) + } + /// Splits the PWM driver by reference to allow for separate duty cycle control + /// of each channel (A and B) without taking ownership of the PWM instance. + #[inline] + pub fn split_by_ref(&mut self) -> (Option>, Option>) { + ( + self.pin_a + .as_mut() + .map(|pin| PwmOutput::new(PwmChannelPin::A(pin.reborrow()), self.slice.clone(), false)), + self.pin_b + .as_mut() + .map(|pin| PwmOutput::new(PwmChannelPin::B(pin.reborrow()), self.slice.clone(), false)), + ) + } +} + +enum PwmChannelPin<'d> { + A(PeripheralRef<'d, AnyPin>), + B(PeripheralRef<'d, AnyPin>), +} + +/// Single channel of Pwm driver. +pub struct PwmOutput<'d> { + //pin that can be ether ChannelAPin or ChannelBPin + channel_pin: PwmChannelPin<'d>, + slice: usize, + is_owned: bool, +} + +impl<'d> PwmOutput<'d> { + fn new(channel_pin: PwmChannelPin<'d>, slice: usize, is_owned: bool) -> Self { + Self { + channel_pin, + slice, + is_owned, + } + } +} + +impl<'d> Drop for PwmOutput<'d> { + fn drop(&mut self) { + if self.is_owned { + let p = pac::PWM.ch(self.slice); + match &self.channel_pin { + PwmChannelPin::A(pin) => { + p.cc().modify(|w| { + w.set_a(0); + }); + + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + //Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); + } + PwmChannelPin::B(pin) => { + p.cc().modify(|w| { + w.set_b(0); + }); + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + //Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); + } + } + } + } +} + +impl<'d> ErrorType for PwmOutput<'d> { + type Error = PwmError; +} + +impl<'d> SetDutyCycle for PwmOutput<'d> { + fn max_duty_cycle(&self) -> u16 { + pac::PWM.ch(self.slice).top().read().top() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + let max_duty = self.max_duty_cycle(); + if duty > max_duty { + return Err(PwmError::InvalidDutyCycle); + } + + let p = pac::PWM.ch(self.slice); + match self.channel_pin { + PwmChannelPin::A(_) => { + p.cc().modify(|w| { + w.set_a(duty); + }); + } + PwmChannelPin::B(_) => { + p.cc().modify(|w| { + w.set_b(duty); + }); + } + } + + Ok(()) + } +} + +/// Batch representation of PWM slices. +pub struct PwmBatch(u32); + +impl PwmBatch { + #[inline] + /// Enable a PWM slice in this batch. + pub fn enable(&mut self, pwm: &Pwm<'_>) { + self.0 |= pwm.bit(); + } + + #[inline] + /// Enable slices in this batch in a PWM. + pub fn set_enabled(enabled: bool, batch: impl FnOnce(&mut PwmBatch)) { + let mut en = PwmBatch(0); + batch(&mut en); + if enabled { + pac::PWM.en().write_set(|w| w.0 = en.0); + } else { + pac::PWM.en().write_clear(|w| w.0 = en.0); + } + } +} + +impl<'d> Drop for Pwm<'d> { + fn drop(&mut self) { + pac::PWM.ch(self.slice).csr().write_clear(|w| w.set_en(false)); + if let Some(pin) = &self.pin_a { + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + } + if let Some(pin) = &self.pin_b { + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + } + } +} + +trait SealedSlice {} + +/// PWM Slice. +#[allow(private_bounds)] +pub trait Slice: Peripheral

+ SealedSlice + Sized + 'static { + /// Slice number. + fn number(&self) -> usize; +} + +macro_rules! slice { + ($name:ident, $num:expr) => { + impl SealedSlice for peripherals::$name {} + impl Slice for peripherals::$name { + fn number(&self) -> usize { + $num + } + } + }; +} + +slice!(PWM_SLICE0, 0); +slice!(PWM_SLICE1, 1); +slice!(PWM_SLICE2, 2); +slice!(PWM_SLICE3, 3); +slice!(PWM_SLICE4, 4); +slice!(PWM_SLICE5, 5); +slice!(PWM_SLICE6, 6); +slice!(PWM_SLICE7, 7); + +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE8, 8); +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE9, 9); +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE10, 10); +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE11, 11); + +/// PWM Channel A. +pub trait ChannelAPin: GpioPin {} +/// PWM Channel B. +pub trait ChannelBPin: GpioPin {} + +macro_rules! impl_pin { + ($pin:ident, $channel:ident, $kind:ident) => { + impl $kind for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, PWM_SLICE0, ChannelAPin); +impl_pin!(PIN_1, PWM_SLICE0, ChannelBPin); +impl_pin!(PIN_2, PWM_SLICE1, ChannelAPin); +impl_pin!(PIN_3, PWM_SLICE1, ChannelBPin); +impl_pin!(PIN_4, PWM_SLICE2, ChannelAPin); +impl_pin!(PIN_5, PWM_SLICE2, ChannelBPin); +impl_pin!(PIN_6, PWM_SLICE3, ChannelAPin); +impl_pin!(PIN_7, PWM_SLICE3, ChannelBPin); +impl_pin!(PIN_8, PWM_SLICE4, ChannelAPin); +impl_pin!(PIN_9, PWM_SLICE4, ChannelBPin); +impl_pin!(PIN_10, PWM_SLICE5, ChannelAPin); +impl_pin!(PIN_11, PWM_SLICE5, ChannelBPin); +impl_pin!(PIN_12, PWM_SLICE6, ChannelAPin); +impl_pin!(PIN_13, PWM_SLICE6, ChannelBPin); +impl_pin!(PIN_14, PWM_SLICE7, ChannelAPin); +impl_pin!(PIN_15, PWM_SLICE7, ChannelBPin); +impl_pin!(PIN_16, PWM_SLICE0, ChannelAPin); +impl_pin!(PIN_17, PWM_SLICE0, ChannelBPin); +impl_pin!(PIN_18, PWM_SLICE1, ChannelAPin); +impl_pin!(PIN_19, PWM_SLICE1, ChannelBPin); +impl_pin!(PIN_20, PWM_SLICE2, ChannelAPin); +impl_pin!(PIN_21, PWM_SLICE2, ChannelBPin); +impl_pin!(PIN_22, PWM_SLICE3, ChannelAPin); +impl_pin!(PIN_23, PWM_SLICE3, ChannelBPin); +impl_pin!(PIN_24, PWM_SLICE4, ChannelAPin); +impl_pin!(PIN_25, PWM_SLICE4, ChannelBPin); +impl_pin!(PIN_26, PWM_SLICE5, ChannelAPin); +impl_pin!(PIN_27, PWM_SLICE5, ChannelBPin); +impl_pin!(PIN_28, PWM_SLICE6, ChannelAPin); +impl_pin!(PIN_29, PWM_SLICE6, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, PWM_SLICE7, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, PWM_SLICE7, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, PWM_SLICE8, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, PWM_SLICE8, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, PWM_SLICE9, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, PWM_SLICE9, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, PWM_SLICE10, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, PWM_SLICE10, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, PWM_SLICE11, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, PWM_SLICE11, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, PWM_SLICE8, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, PWM_SLICE8, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, PWM_SLICE9, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, PWM_SLICE9, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, PWM_SLICE10, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, PWM_SLICE10, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, PWM_SLICE11, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, PWM_SLICE11, ChannelBPin); diff --git a/embassy/embassy-rp/src/relocate.rs b/embassy/embassy-rp/src/relocate.rs new file mode 100644 index 0000000..3448781 --- /dev/null +++ b/embassy/embassy-rp/src/relocate.rs @@ -0,0 +1,66 @@ +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().map(|&instr| { + 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.wrapping_add(self.offset) % 32; + 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_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.wrapping_add(origin) % 32, + target: wrap.target.wrapping_add(origin) % 32, + } + } + + pub fn side_set(&self) -> SideSet { + self.program.side_set + } + + pub fn origin(&self) -> u8 { + self.origin + } +} diff --git a/embassy/embassy-rp/src/reset.rs b/embassy/embassy-rp/src/reset.rs new file mode 100644 index 0000000..4b9e424 --- /dev/null +++ b/embassy/embassy-rp/src/reset.rs @@ -0,0 +1,15 @@ +pub use pac::resets::regs::Peripherals; + +use crate::pac; + +pub const ALL_PERIPHERALS: Peripherals = Peripherals(0x01ff_ffff); + +pub(crate) fn reset(peris: Peripherals) { + pac::RESETS.reset().write_value(peris); +} + +pub(crate) fn unreset_wait(peris: Peripherals) { + // TODO use the "atomic clear" register version + pac::RESETS.reset().modify(|v| *v = Peripherals(v.0 & !peris.0)); + while ((!pac::RESETS.reset_done().read().0) & peris.0) != 0 {} +} diff --git a/embassy/embassy-rp/src/rom_data/mod.rs b/embassy/embassy-rp/src/rom_data/mod.rs new file mode 100644 index 0000000..e5fcf8e --- /dev/null +++ b/embassy/embassy-rp/src/rom_data/mod.rs @@ -0,0 +1,33 @@ +#![cfg_attr( + feature = "rp2040", + doc = r" +//! Functions and data from the RPI Bootrom. +//! +//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1: +//! +//! > The Bootrom contains a number of public functions that provide useful +//! > RP2040 functionality that might be needed in the absence of any other code +//! > on the device, as well as highly optimized versions of certain key +//! > functionality that would otherwise have to take up space in most user +//! > binaries. +" +)] +#![cfg_attr( + feature = "_rp235x", + doc = r" +//! Functions and data from the RPI Bootrom. +//! +//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +//! RP2350 datasheet: +//! +//! > Whilst some ROM space is dedicated to the implementation of the boot +//! > sequence and USB/UART boot interfaces, the bootrom also contains public +//! > functions that provide useful RP2350 functionality that may be useful for +//! > any code or runtime running on the device +" +)] + +#[cfg_attr(feature = "rp2040", path = "rp2040.rs")] +#[cfg_attr(feature = "_rp235x", path = "rp235x.rs")] +mod inner; +pub use inner::*; diff --git a/embassy/embassy-rp/src/rom_data/rp2040.rs b/embassy/embassy-rp/src/rom_data/rp2040.rs new file mode 100644 index 0000000..5a74edd --- /dev/null +++ b/embassy/embassy-rp/src/rom_data/rp2040.rs @@ -0,0 +1,756 @@ +//! Functions and data from the RPI Bootrom. +//! +//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1: +//! +//! > The Bootrom contains a number of public functions that provide useful +//! > RP2040 functionality that might be needed in the absence of any other code +//! > on the device, as well as highly optimized versions of certain key +//! > functionality that would otherwise have to take up space in most user +//! > binaries. + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/rom_data.rs + +/// A bootrom function table code. +pub type RomFnTableCode = [u8; 2]; + +/// This function searches for (table) +type RomTableLookupFn = unsafe extern "C" fn(*const u16, u32) -> T; + +/// The following addresses are described at `2.8.2. Bootrom Contents` +/// Pointer to the lookup table function supplied by the rom. +const ROM_TABLE_LOOKUP_PTR: *const u16 = 0x0000_0018 as _; + +/// Pointer to helper functions lookup table. +const FUNC_TABLE: *const u16 = 0x0000_0014 as _; + +/// Pointer to the public data lookup table. +const DATA_TABLE: *const u16 = 0x0000_0016 as _; + +/// Address of the version number of the ROM. +const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; + +/// Retrive rom content from a table using a code. +fn rom_table_lookup(table: *const u16, tag: RomFnTableCode) -> T { + unsafe { + let rom_table_lookup_ptr: *const u32 = rom_hword_as_ptr(ROM_TABLE_LOOKUP_PTR); + let rom_table_lookup: RomTableLookupFn = core::mem::transmute(rom_table_lookup_ptr); + rom_table_lookup(rom_hword_as_ptr(table) as *const u16, u16::from_le_bytes(tag) as u32) + } +} + +/// To save space, the ROM likes to store memory pointers (which are 32-bit on +/// the Cortex-M0+) using only the bottom 16-bits. The assumption is that the +/// values they point at live in the first 64 KiB of ROM, and the ROM is mapped +/// to address `0x0000_0000` and so 16-bits are always sufficient. +/// +/// This functions grabs a 16-bit value from ROM and expands it out to a full 32-bit pointer. +unsafe fn rom_hword_as_ptr(rom_address: *const u16) -> *const u32 { + let ptr: u16 = *rom_address; + ptr as *const u32 +} + +macro_rules! declare_rom_function { + ( + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + declare_rom_function!{ + __internal , + $(#[$outer])* + fn $name( $($argname: $ty),* ) -> $ret + $lookup + } + }; + + ( + $(#[$outer:meta])* + unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + declare_rom_function!{ + __internal unsafe , + $(#[$outer])* + fn $name( $($argname: $ty),* ) -> $ret + $lookup + } + }; + + ( + __internal + $( $maybe_unsafe:ident )? , + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + #[cfg(not(feature = "rom-func-cache"))] + pub(crate) fn outer_call() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: *const u32 = $lookup; + unsafe { + let func : $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret + = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret { + outer_call() + } + + #[cfg(feature = "rom-func-cache")] + // unlike rp2040-hal we store a full word, containing the full function pointer. + // rp2040-hal saves two bytes by storing only the rom offset, at the cost of + // having to do an indirection and an atomic operation on every rom call. + static mut CACHE: $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret + = trampoline; + + #[cfg(feature = "rom-func-cache")] + $( $maybe_unsafe )? extern "C" fn trampoline( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{compiler_fence, Ordering}; + + let p: *const u32 = $lookup; + #[allow(unused_unsafe)] + unsafe { + CACHE = core::mem::transmute(p); + compiler_fence(Ordering::Release); + CACHE($($argname),*) + } + } + + #[cfg(feature = "rom-func-cache")] + pub(crate) fn outer_call() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{compiler_fence, Ordering}; + + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + // + // We easily get away with using only compiler fences here + // because RP2040 SRAM is not cached. If it were we'd need + // to make sure updates propagate quickly, or just take the + // hit and let each core resolve every function once. + compiler_fence(Ordering::Acquire); + unsafe { + CACHE + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{compiler_fence, Ordering}; + + // We can't just return the trampoline here because we need + // the actual resolved function address (e.x. flash operations + // can't reference a trampoline which itself is in flash). We + // can still utilize the cache, but we have to make sure it has + // been resolved already. Like the normal call path, we + // don't need anything stronger than fences because the + // final value always resolves to the same thing and SRAM + // itself is not cached. + compiler_fence(Ordering::Acquire); + #[allow(unused_unsafe)] + unsafe { + // ROM is 16kB in size at 0x0, so anything outside is cached + if CACHE as u32 >> 14 != 0 { + let p: *const u32 = $lookup; + CACHE = core::mem::transmute(p); + compiler_fence(Ordering::Release); + } + CACHE + } + } + } + + $(#[$outer])* + pub $( $maybe_unsafe )? extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::outer_call()($($argname),*) + } + }; +} + +macro_rules! rom_functions { + () => {}; + + ( + $(#[$outer:meta])* + $c:literal fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty; + + $($rest:tt)* + ) => { + declare_rom_function! { + $(#[$outer])* + fn $name( $($argname: $ty),* ) -> $ret { + $crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c) + } + } + + rom_functions!($($rest)*); + }; + + ( + $(#[$outer:meta])* + $c:literal unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty; + + $($rest:tt)* + ) => { + declare_rom_function! { + $(#[$outer])* + unsafe fn $name( $($argname: $ty),* ) -> $ret { + $crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c) + } + } + + rom_functions!($($rest)*); + }; +} + +rom_functions! { + /// Return a count of the number of 1 bits in value. + b"P3" fn popcount32(value: u32) -> u32; + + /// Return the bits of value in the reverse order. + b"R3" fn reverse32(value: u32) -> u32; + + /// Return the number of consecutive high order 0 bits of value. If value is zero, returns 32. + b"L3" fn clz32(value: u32) -> u32; + + /// Return the number of consecutive low order 0 bits of value. If value is zero, returns 32. + b"T3" fn ctz32(value: u32) -> u32; + + /// Resets the RP2040 and uses the watchdog facility to re-start in BOOTSEL mode: + /// * gpio_activity_pin_mask is provided to enable an 'activity light' via GPIO attached LED + /// for the USB Mass Storage Device: + /// * 0 No pins are used as per cold boot. + /// * Otherwise a single bit set indicating which GPIO pin should be set to output and + /// raised whenever there is mass storage activity from the host. + /// * disable_interface_mask may be used to control the exposed USB interfaces: + /// * 0 To enable both interfaces (as per cold boot). + /// * 1 To disable the USB Mass Storage Interface. + /// * 2 to Disable the USB PICOBOOT Interface. + b"UB" fn reset_to_usb_boot(gpio_activity_pin_mask: u32, disable_interface_mask: u32) -> (); + + /// Sets n bytes start at ptr to the value c and returns ptr + b"MS" unsafe fn memset(ptr: *mut u8, c: u8, n: u32) -> *mut u8; + + /// Sets n bytes start at ptr to the value c and returns ptr. + /// + /// Note this is a slightly more efficient variant of _memset that may only + /// be used if ptr is word aligned. + // Note the datasheet does not match the actual ROM for the code here, see + // https://github.com/raspberrypi/pico-feedback/issues/217 + b"S4" unsafe fn memset4(ptr: *mut u32, c: u8, n: u32) -> *mut u32; + + /// Copies n bytes starting at src to dest and returns dest. The results are undefined if the + /// regions overlap. + b"MC" unsafe fn memcpy(dest: *mut u8, src: *const u8, n: u32) -> *mut u8; + + /// Copies n bytes starting at src to dest and returns dest. The results are undefined if the + /// regions overlap. + /// + /// Note this is a slightly more efficient variant of _memcpy that may only be + /// used if dest and src are word aligned. + b"C4" unsafe fn memcpy44(dest: *mut u32, src: *const u32, n: u32) -> *mut u8; + + /// Restore all QSPI pad controls to their default state, and connect the SSI to the QSPI pads. + b"IF" unsafe fn connect_internal_flash() -> (); + + /// First set up the SSI for serial-mode operations, then issue the fixed XIP exit sequence. + /// + /// Note that the bootrom code uses the IO forcing logic to drive the CS pin, which must be + /// cleared before returning the SSI to XIP mode (e.g. by a call to _flash_flush_cache). This + /// function configures the SSI with a fixed SCK clock divisor of /6. + b"EX" unsafe fn flash_exit_xip() -> (); + + /// Erase a count bytes, starting at addr (offset from start of flash). Optionally, pass a + /// block erase command e.g. D8h block erase, and the size of the block erased by this + /// command — this function will use the larger block erase where possible, for much higher + /// erase speed. addr must be aligned to a 4096-byte sector, and count must be a multiple of + /// 4096 bytes. + b"RE" unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> (); + + /// Program data to a range of flash addresses starting at `addr` (and + /// offset from the start of flash) and `count` bytes in size. The value + /// `addr` must be aligned to a 256-byte boundary, and `count` must be a + /// multiple of 256. + b"RP" unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> (); + + /// Flush and enable the XIP cache. Also clears the IO forcing on QSPI CSn, so that the SSI can + /// drive the flashchip select as normal. + b"FC" unsafe fn flash_flush_cache() -> (); + + /// Configure the SSI to generate a standard 03h serial read command, with 24 address bits, + /// upon each XIP access. This is a very slow XIP configuration, but is very widely supported. + /// The debugger calls this function after performing a flash erase/programming operation, so + /// that the freshly-programmed code and data is visible to the debug host, without having to + /// know exactly what kind of flash device is connected. + b"CX" unsafe fn flash_enter_cmd_xip() -> (); + + /// This is the method that is entered by core 1 on reset to wait to be launched by core 0. + /// There are few cases where you should call this method (resetting core 1 is much better). + /// This method does not return and should only ever be called on core 1. + b"WV" unsafe fn wait_for_vector() -> !; +} + +// Various C intrinsics in the ROM +intrinsics! { + #[alias = __popcountdi2] + extern "C" fn __popcountsi2(x: u32) -> u32 { + popcount32(x) + } + + #[alias = __clzdi2] + extern "C" fn __clzsi2(x: u32) -> u32 { + clz32(x) + } + + #[alias = __ctzdi2] + extern "C" fn __ctzsi2(x: u32) -> u32 { + ctz32(x) + } + + // __rbit is only unofficial, but it show up in the ARM documentation, + // so may as well hook it up. + #[alias = __rbitl] + extern "C" fn __rbit(x: u32) -> u32 { + reverse32(x) + } + + unsafe extern "aapcs" fn __aeabi_memset(dest: *mut u8, n: usize, c: i32) -> () { + // Different argument order + memset(dest, c as u8, n as u32); + } + + #[alias = __aeabi_memset8] + unsafe extern "aapcs" fn __aeabi_memset4(dest: *mut u8, n: usize, c: i32) -> () { + // Different argument order + memset4(dest as *mut u32, c as u8, n as u32); + } + + unsafe extern "aapcs" fn __aeabi_memclr(dest: *mut u8, n: usize) -> () { + memset(dest, 0, n as u32); + } + + #[alias = __aeabi_memclr8] + unsafe extern "aapcs" fn __aeabi_memclr4(dest: *mut u8, n: usize) -> () { + memset4(dest as *mut u32, 0, n as u32); + } + + unsafe extern "aapcs" fn __aeabi_memcpy(dest: *mut u8, src: *const u8, n: usize) -> () { + memcpy(dest, src, n as u32); + } + + #[alias = __aeabi_memcpy8] + unsafe extern "aapcs" fn __aeabi_memcpy4(dest: *mut u8, src: *const u8, n: usize) -> () { + memcpy44(dest as *mut u32, src as *const u32, n as u32); + } +} + +unsafe fn convert_str(s: *const u8) -> &'static str { + let mut end = s; + while *end != 0 { + end = end.add(1); + } + let s = core::slice::from_raw_parts(s, end.offset_from(s) as usize); + core::str::from_utf8_unchecked(s) +} + +/// The version number of the rom. +pub fn rom_version_number() -> u8 { + unsafe { *VERSION_NUMBER } +} + +/// The Raspberry Pi Trading Ltd copyright string. +pub fn copyright_string() -> &'static str { + let s: *const u8 = rom_table_lookup(DATA_TABLE, *b"CR"); + unsafe { convert_str(s) } +} + +/// The 8 most significant hex digits of the Bootrom git revision. +pub fn git_revision() -> u32 { + let s: *const u32 = rom_table_lookup(DATA_TABLE, *b"GR"); + unsafe { *s } +} + +/// The start address of the floating point library code and data. +/// +/// This and fplib_end along with the individual function pointers in +/// soft_float_table can be used to copy the floating point implementation into +/// RAM if desired. +pub fn fplib_start() -> *const u8 { + rom_table_lookup(DATA_TABLE, *b"FS") +} + +/// See Table 180 in the RP2040 datasheet for the contents of this table. +#[cfg_attr(feature = "rom-func-cache", inline(never))] +pub fn soft_float_table() -> *const usize { + rom_table_lookup(DATA_TABLE, *b"SF") +} + +/// The end address of the floating point library code and data. +pub fn fplib_end() -> *const u8 { + rom_table_lookup(DATA_TABLE, *b"FE") +} + +/// This entry is only present in the V2 bootrom. See Table 182 in the RP2040 datasheet for the contents of this table. +#[cfg_attr(feature = "rom-func-cache", inline(never))] +pub fn soft_double_table() -> *const usize { + if rom_version_number() < 2 { + panic!( + "Double precision operations require V2 bootrom (found: V{})", + rom_version_number() + ); + } + rom_table_lookup(DATA_TABLE, *b"SD") +} + +/// ROM functions using single-precision arithmetic (i.e. 'f32' in Rust terms) +pub mod float_funcs { + + macro_rules! make_functions { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + let table: *const usize = $crate::rom_data::soft_float_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + make_functions! { + /// Calculates `a + b` + 0x00 fadd(a: f32, b: f32) -> f32; + /// Calculates `a - b` + 0x04 fsub(a: f32, b: f32) -> f32; + /// Calculates `a * b` + 0x08 fmul(a: f32, b: f32) -> f32; + /// Calculates `a / b` + 0x0c fdiv(a: f32, b: f32) -> f32; + + // 0x10 and 0x14 are deprecated + + /// Calculates `sqrt(v)` (or return -Infinity if v is negative) + 0x18 fsqrt(v: f32) -> f32; + /// Converts an f32 to a signed integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `-0x80000000` to `0x7FFFFFFF` + 0x1c float_to_int(v: f32) -> i32; + /// Converts an f32 to an signed fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x20 float_to_fix(v: f32, n: i32) -> i32; + /// Converts an f32 to an unsigned integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `0x00000000` to `0xFFFFFFFF` + 0x24 float_to_uint(v: f32) -> u32; + /// Converts an f32 to an unsigned fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x28 float_to_ufix(v: f32, n: i32) -> u32; + /// Converts a signed integer to the nearest + /// f32 value, rounding to even on tie + 0x2c int_to_float(v: i32) -> f32; + /// Converts a signed fixed point integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x30 fix_to_float(v: i32, n: i32) -> f32; + /// Converts an unsigned integer to the nearest + /// f32 value, rounding to even on tie + 0x34 uint_to_float(v: u32) -> f32; + /// Converts an unsigned fixed point integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x38 ufix_to_float(v: u32, n: i32) -> f32; + /// Calculates the cosine of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x3c fcos(angle: f32) -> f32; + /// Calculates the sine of `angle`. The value of + /// `angle` is in radians, and must be in the range `-1024` to `1024` + 0x40 fsin(angle: f32) -> f32; + /// Calculates the tangent of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x44 ftan(angle: f32) -> f32; + + // 0x48 is deprecated + + /// Calculates the exponential value of `v`, + /// i.e. `e ** v` + 0x4c fexp(v: f32) -> f32; + /// Calculates the natural logarithm of `v`. If `v <= 0` return -Infinity + 0x50 fln(v: f32) -> f32; + } + + macro_rules! make_functions_v2 { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + if $crate::rom_data::rom_version_number() < 2 { + panic!( + "Floating point function requires V2 bootrom (found: V{})", + $crate::rom_data::rom_version_number() + ); + } + let table: *const usize = $crate::rom_data::soft_float_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + // These are only on BootROM v2 or higher + make_functions_v2! { + /// Compares two floating point numbers, returning: + /// • 0 if a == b + /// • -1 if a < b + /// • 1 if a > b + 0x54 fcmp(a: f32, b: f32) -> i32; + /// Computes the arc tangent of `y/x` using the + /// signs of arguments to determine the correct quadrant + 0x58 fatan2(y: f32, x: f32) -> f32; + /// Converts a signed 64-bit integer to the + /// nearest f32 value, rounding to even on tie + 0x5c int64_to_float(v: i64) -> f32; + /// Converts a signed fixed point 64-bit integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x60 fix64_to_float(v: i64, n: i32) -> f32; + /// Converts an unsigned 64-bit integer to the + /// nearest f32 value, rounding to even on tie + 0x64 uint64_to_float(v: u64) -> f32; + /// Converts an unsigned fixed point 64-bit + /// integer representation to the nearest f32 value, rounding to even on + /// tie. `n` specifies the position of the binary point in fixed point, so + /// `f = nearest(v/(2^n))` + 0x68 ufix64_to_float(v: u64, n: i32) -> f32; + /// Convert an f32 to a signed 64-bit integer, rounding towards -Infinity, + /// and clamping the result to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x6c float_to_int64(v: f32) -> i64; + /// Converts an f32 to a signed fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation - e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x70 float_to_fix64(v: f32, n: i32) -> f32; + /// Converts an f32 to an unsigned 64-bit + /// integer, rounding towards -Infinity, and clamping the result to lie + /// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF` + 0x74 float_to_uint64(v: f32) -> u64; + /// Converts an f32 to an unsigned fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation, e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `0x0000000000000000` to + /// `0xFFFFFFFFFFFFFFFF` + 0x78 float_to_ufix64(v: f32, n: i32) -> u64; + /// Converts an f32 to an f64. + 0x7c float_to_double(v: f32) -> f64; + } +} + +/// Functions using double-precision arithmetic (i.e. 'f64' in Rust terms) +pub mod double_funcs { + + macro_rules! make_double_funcs { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + let table: *const usize = $crate::rom_data::soft_double_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + make_double_funcs! { + /// Calculates `a + b` + 0x00 dadd(a: f64, b: f64) -> f64; + /// Calculates `a - b` + 0x04 dsub(a: f64, b: f64) -> f64; + /// Calculates `a * b` + 0x08 dmul(a: f64, b: f64) -> f64; + /// Calculates `a / b` + 0x0c ddiv(a: f64, b: f64) -> f64; + + // 0x10 and 0x14 are deprecated + + /// Calculates `sqrt(v)` (or return -Infinity if v is negative) + 0x18 dsqrt(v: f64) -> f64; + /// Converts an f64 to a signed integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `-0x80000000` to `0x7FFFFFFF` + 0x1c double_to_int(v: f64) -> i32; + /// Converts an f64 to an signed fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x20 double_to_fix(v: f64, n: i32) -> i32; + /// Converts an f64 to an unsigned integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `0x00000000` to `0xFFFFFFFF` + 0x24 double_to_uint(v: f64) -> u32; + /// Converts an f64 to an unsigned fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x28 double_to_ufix(v: f64, n: i32) -> u32; + /// Converts a signed integer to the nearest + /// double value, rounding to even on tie + 0x2c int_to_double(v: i32) -> f64; + /// Converts a signed fixed point integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x30 fix_to_double(v: i32, n: i32) -> f64; + /// Converts an unsigned integer to the nearest + /// double value, rounding to even on tie + 0x34 uint_to_double(v: u32) -> f64; + /// Converts an unsigned fixed point integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so f = + /// nearest(v/(2^n)) + 0x38 ufix_to_double(v: u32, n: i32) -> f64; + /// Calculates the cosine of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x3c dcos(angle: f64) -> f64; + /// Calculates the sine of `angle`. The value of + /// `angle` is in radians, and must be in the range `-1024` to `1024` + 0x40 dsin(angle: f64) -> f64; + /// Calculates the tangent of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x44 dtan(angle: f64) -> f64; + + // 0x48 is deprecated + + /// Calculates the exponential value of `v`, + /// i.e. `e ** v` + 0x4c dexp(v: f64) -> f64; + /// Calculates the natural logarithm of v. If v <= 0 return -Infinity + 0x50 dln(v: f64) -> f64; + + // These are only on BootROM v2 or higher + + /// Compares two floating point numbers, returning: + /// • 0 if a == b + /// • -1 if a < b + /// • 1 if a > b + 0x54 dcmp(a: f64, b: f64) -> i32; + /// Computes the arc tangent of `y/x` using the + /// signs of arguments to determine the correct quadrant + 0x58 datan2(y: f64, x: f64) -> f64; + /// Converts a signed 64-bit integer to the + /// nearest double value, rounding to even on tie + 0x5c int64_to_double(v: i64) -> f64; + /// Converts a signed fixed point 64-bit integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x60 fix64_to_doubl(v: i64, n: i32) -> f64; + /// Converts an unsigned 64-bit integer to the + /// nearest double value, rounding to even on tie + 0x64 uint64_to_double(v: u64) -> f64; + /// Converts an unsigned fixed point 64-bit + /// integer representation to the nearest double value, rounding to even on + /// tie. `n` specifies the position of the binary point in fixed point, so + /// `f = nearest(v/(2^n))` + 0x68 ufix64_to_double(v: u64, n: i32) -> f64; + /// Convert an f64 to a signed 64-bit integer, rounding towards -Infinity, + /// and clamping the result to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x6c double_to_int64(v: f64) -> i64; + /// Converts an f64 to a signed fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation - e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x70 double_to_fix64(v: f64, n: i32) -> i64; + /// Converts an f64 to an unsigned 64-bit + /// integer, rounding towards -Infinity, and clamping the result to lie + /// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF` + 0x74 double_to_uint64(v: f64) -> u64; + /// Converts an f64 to an unsigned fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation, e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `0x0000000000000000` to + /// `0xFFFFFFFFFFFFFFFF` + 0x78 double_to_ufix64(v: f64, n: i32) -> u64; + /// Converts an f64 to an f32 + 0x7c double_to_float(v: f64) -> f32; + } +} diff --git a/embassy/embassy-rp/src/rom_data/rp235x.rs b/embassy/embassy-rp/src/rom_data/rp235x.rs new file mode 100644 index 0000000..b16fee8 --- /dev/null +++ b/embassy/embassy-rp/src/rom_data/rp235x.rs @@ -0,0 +1,752 @@ +//! Functions and data from the RPI Bootrom. +//! +//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +//! RP2350 datasheet: +//! +//! > Whilst some ROM space is dedicated to the implementation of the boot +//! > sequence and USB/UART boot interfaces, the bootrom also contains public +//! > functions that provide useful RP2350 functionality that may be useful for +//! > any code or runtime running on the device + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs + +/// A bootrom function table code. +pub type RomFnTableCode = [u8; 2]; + +/// This function searches for the tag which matches the mask. +type RomTableLookupFn = unsafe extern "C" fn(code: u32, mask: u32) -> usize; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_0016 as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_0018 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A2: *const u16 = ROM_TABLE_LOOKUP_A2; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A1: *const u32 = ROM_TABLE_LOOKUP_A1; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_7DFA as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A2: *const u16 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A1: *const u32 = 0x0000_7DF4 as _; + +/// Address of the version number of the ROM. +const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; + +#[allow(unused)] +mod rt_flags { + pub const FUNC_RISCV: u32 = 0x0001; + pub const FUNC_RISCV_FAR: u32 = 0x0003; + pub const FUNC_ARM_SEC: u32 = 0x0004; + // reserved for 32-bit pointer: 0x0008 + pub const FUNC_ARM_NONSEC: u32 = 0x0010; + // reserved for 32-bit pointer: 0x0020 + pub const DATA: u32 = 0x0040; + // reserved for 32-bit pointer: 0x0080 + #[cfg(all(target_arch = "arm", target_os = "none"))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_ARM_SEC; + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_RISCV; +} + +/// Retrieve rom content from a table using a code. +pub fn rom_table_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_TABLE_LOOKUP_A1.read() as usize + } else { + ROM_TABLE_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +/// Retrieve rom data content from a table using a code. +pub fn rom_data_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_DATA_LOOKUP_A1.read() as usize + } else { + ROM_DATA_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +macro_rules! declare_rom_function { + ( + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + pub extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; + + ( + $(#[$outer:meta])* + unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + /// # Safety + /// + /// This is a low-level C function. It may be difficult to call safely from + /// Rust. If in doubt, check the rp235x datasheet for details and do your own + /// safety evaluation. + pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; +} + +// **************** 5.5.7 Low-level Flash Commands **************** + +declare_rom_function! { + /// Restore all QSPI pad controls to their default state, and connect the + /// QMI peripheral to the QSPI pads. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn connect_internal_flash() -> () { + crate::rom_data::rom_table_lookup(*b"IF", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Initialise the QMI for serial operations (direct mode) + /// + /// Also initialise a basic XIP mode, where the QMI will perform 03h serial + /// read commands at low speed (CLKDIV=12) in response to XIP reads. + /// + /// Then, issue a sequence to the QSPI device on chip select 0, designed to + /// return it from continuous read mode ("XIP mode") and/or QPI mode to a + /// state where it will accept serial commands. This is necessary after + /// system reset to restore the QSPI device to a known state, because + /// resetting RP2350 does not reset attached QSPI devices. It is also + /// necessary when user code, having already performed some + /// continuous-read-mode or QPI-mode accesses, wishes to return the QSPI + /// device to a state where it will accept the serial erase and programming + /// commands issued by the bootrom’s flash access functions. + /// + /// If a GPIO for the secondary chip select is configured via FLASH_DEVINFO, + /// then the XIP exit sequence is also issued to chip select 1. + /// + /// The QSPI device should be accessible for XIP reads after calling this + /// function; the name flash_exit_xip refers to returning the QSPI device + /// from its XIP state to a serial command state. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_exit_xip() -> () { + crate::rom_data::rom_table_lookup(*b"EX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Erase count bytes, starting at addr (offset from start of flash). + /// + /// Optionally, pass a block erase command e.g. D8h block erase, and the + /// size of the block erased by this command — this function will use the + /// larger block erase where possible, for much higher erase speed. addr + /// must be aligned to a 4096-byte sector, and count must be a multiple of + /// 4096 bytes. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API, which can be achieved by calling connect_internal_flash() followed + /// by flash_exit_xip(). After the erase, the flash cache should be flushed + /// via flash_flush_cache() to ensure the modified flash data is visible to + /// cached XIP accesses. + /// + /// Finally, the original XIP mode should be restored by copying the saved + /// XIP setup function from bootram into SRAM, and executing it: the bootrom + /// provides a default function which restores the flash mode/clkdiv + /// discovered during flash scanning, and user programs can override this + /// with their own XIP setup function. + /// + /// For the duration of the erase operation, QMI is in direct mode (Section + /// 12.14.5) and attempting to access XIP from DMA, the debugger or the + /// other core will return a bus fault. XIP becomes accessible again once + /// the function returns. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> () { + crate::rom_data::rom_table_lookup(*b"RE", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Program data to a range of flash storage addresses starting at addr + /// (offset from the start of flash) and count bytes in size. + /// + /// `addr` must be aligned to a 256-byte boundary, and count must be a + /// multiple of 256. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API — see notes on flash_range_erase(). + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> () { + crate::rom_data::rom_table_lookup(*b"RP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Flush the entire XIP cache, by issuing an invalidate by set/way + /// maintenance operation to every cache line (Section 4.4.1). + /// + /// This ensures that flash program/erase operations are visible to + /// subsequent cached XIP reads. + /// + /// Note that this unpins pinned cache lines, which may interfere with + /// cache-as-SRAM use of the XIP cache. + /// + /// No other operations are performed. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_flush_cache() -> () { + crate::rom_data::rom_table_lookup(*b"FC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure the QMI to generate a standard 03h serial read command, with + /// 24 address bits, upon each XIP access. + /// + /// This is a slow XIP configuration, but is widely supported. CLKDIV is set + /// to 12. The debugger may call this function to ensure that flash is + /// readable following a program/erase operation. + /// + /// Note that the same setup is performed by flash_exit_xip(), and the + /// RP2350 flash program/erase functions do not leave XIP in an inaccessible + /// state, so calls to this function are largely redundant. It is provided + /// for compatibility with RP2040. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_enter_cmd_xip() -> () { + crate::rom_data::rom_table_lookup(*b"CX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure QMI for one of a small menu of XIP read modes supported by the + /// bootrom. This mode is configured for both memory windows (both chip + /// selects), and the clock divisor is also applied to direct mode. + /// + /// The available modes are: + /// + /// * 0: `03h` serial read: serial address, serial data, no wait cycles + /// * 1: `0Bh` serial read: serial address, serial data, 8 wait cycles + /// * 2: `BBh` dual-IO read: dual address, dual data, 4 wait cycles + /// (including MODE bits, which are driven to 0) + /// * 3: `EBh` quad-IO read: quad address, quad data, 6 wait cycles + /// (including MODE bits, which are driven to 0) + /// + /// The XIP write command/format are not configured by this function. When + /// booting from flash, the bootrom tries each of these modes in turn, from + /// 3 down to 0. The first mode that is found to work is remembered, and a + /// default XIP setup function is written into bootram that calls this + /// function (flash_select_xip_read_mode) with the parameters discovered + /// during flash scanning. This can be called at any time to restore the + /// flash parameters discovered during flash boot. + /// + /// All XIP modes configured by the bootrom have an 8-bit serial command + /// prefix, so that the flash can remain in a serial command state, meaning + /// XIP accesses can be mixed more freely with program/erase serial + /// operations. This has a performance penalty, so users can perform their + /// own flash setup after flash boot using continuous read mode or QPI mode + /// to avoid or alleviate the command prefix cost. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_select_xip_read_mode(bootrom_xip_mode: u8, clkdiv: u8) -> () { + crate::rom_data::rom_table_lookup(*b"XM", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Restore the QMI address translation registers, ATRANS0 through ATRANS7, + /// to their reset state. This makes the runtime- to-storage address map an + /// identity map, i.e. the mapped and unmapped address are equal, and the + /// entire space is fully mapped. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_reset_address_trans() -> () { + crate::rom_data::rom_table_lookup(*b"RA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** High-level Flash Commands **************** + +declare_rom_function! { + /// Applies the address translation currently configured by QMI address + /// translation registers, ATRANS0 through ATRANS7. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Translating an address outside of the XIP runtime address window, or + /// beyond the bounds of an ATRANSx_SIZE field, returns + /// BOOTROM_ERROR_INVALID_ADDRESS, which is not a valid flash storage + /// address. Otherwise, return the storage address which QMI would access + /// when presented with the runtime address addr. This is effectively a + /// virtual-to-physical address translation for QMI. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_runtime_to_storage_addr(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_runtime_to_storage_addr()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_runtime_to_storage_addr_ns(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Perform a flash read, erase, or program operation. + /// + /// Erase operations must be sector-aligned (4096 bytes) and sector- + /// multiple-sized, and program operations must be page-aligned (256 bytes) + /// and page-multiple-sized; misaligned erase and program operations will + /// return BOOTROM_ERROR_BAD_ALIGNMENT. The operation — erase, read, program + /// — is selected by the CFLASH_OP_BITS bitfield of the flags argument. + /// + /// See datasheet section 5.5.8.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_op(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_op()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_op_ns(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Security Related Functions **************** + +declare_rom_function! { + /// Allow or disallow the specific NS API (note all NS APIs default to + /// disabled). + /// + /// See datasheet section 5.5.9.1 for more details. + /// + /// Supported architectures: ARM-S + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn set_ns_api_permission(ns_api_num: u32, allowed: u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"SP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC) + } +} + +declare_rom_function! { + /// Utility method that can be used by secure ARM code to validate a buffer + /// passed to it from Non-secure code. + /// + /// See datasheet section 5.5.9.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn validate_ns_buffer() -> () { + crate::rom_data::rom_table_lookup(*b"VB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Miscellaneous Functions **************** + +declare_rom_function! { + /// Resets the RP2350 and uses the watchdog facility to restart. + /// + /// See datasheet section 5.5.10.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + fn reboot(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [reboot()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + fn reboot_ns(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Resets internal bootrom state. + /// + /// See datasheet section 5.5.10.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn bootrom_state_reset(flags: u32) -> () { + crate::rom_data::rom_table_lookup(*b"SR", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Set a boot ROM callback. + /// + /// The only supported callback_number is 0 which sets the callback used for + /// the secure_call API. + /// + /// See datasheet section 5.5.10.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn set_rom_callback(callback_number: i32, callback_fn: *const ()) -> i32 { + crate::rom_data::rom_table_lookup(*b"RC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** System Information Functions **************** + +declare_rom_function! { + /// Fills a buffer with various system information. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_sys_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_sys_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_sys_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Fills a buffer with information from the partition table. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_partition_table_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_partition_table_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_partition_table_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Loads the current partition table from flash, if present. + /// + /// See datasheet section 5.5.11.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn load_partition_table(workarea_base: *mut u8, workarea_size: usize, force_reload: bool) -> i32 { + crate::rom_data::rom_table_lookup(*b"LP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Writes data from a buffer into OTP, or reads data from OTP into a buffer. + /// + /// See datasheet section 5.5.11.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn otp_access(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [otp_access()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn otp_access_ns(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Boot Related Functions **************** + +declare_rom_function! { + /// Determines which of the partitions has the "better" IMAGE_DEF. In the + /// case of executable images, this is the one that would be booted. + /// + /// See datasheet section 5.5.12.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn pick_ab_parition(workarea_base: *mut u8, workarea_size: usize, partition_a_num: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"AB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Searches a memory region for a launchable image, and executes it if + /// possible. + /// + /// See datasheet section 5.5.12.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn chain_image(workarea_base: *mut u8, workarea_size: usize, region_base: i32, region_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"CI", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Perform an "explicit" buy of an executable launched via an IMAGE_DEF + /// which was "explicit buy" flagged. + /// + /// See datasheet section 5.5.12.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn explicit_buy(buffer: *mut u8, buffer_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"EB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Not yet documented. + /// + /// See datasheet section 5.5.12.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_uf2_target_partition(workarea_base: *mut u8, workarea_size: usize, family_id: u32, partition_out: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GU", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Returns: The index of the B partition of partition A if a partition + /// table is present and loaded, and there is a partition A with a B + /// partition; otherwise returns BOOTROM_ERROR_NOT_FOUND. + /// + /// See datasheet section 5.5.12.5 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_b_partition(partition_a: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Non-secure-specific Functions **************** + +// NB: The "secure_call" function should be here, but it doesn't have a fixed +// function signature as it is designed to let you bounce into any secure +// function from non-secure mode. + +// **************** RISC-V Functions **************** + +declare_rom_function! { + /// Set stack for RISC-V bootrom functions to use. + /// + /// See datasheet section 5.5.14.1 for more details. + /// + /// Supported architectures: RISC-V + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn set_bootrom_stack(base_size: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"SS", crate::rom_data::inner::rt_flags::FUNC_RISCV) + } +} + +/// The version number of the rom. +pub fn rom_version_number() -> u8 { + unsafe { *VERSION_NUMBER } +} + +/// The 8 most significant hex digits of the Bootrom git revision. +pub fn git_revision() -> u32 { + let ptr = rom_data_lookup(*b"GR", rt_flags::DATA) as *const u32; + unsafe { ptr.read() } +} + +/// A pointer to the resident partition table info. +/// +/// The resident partition table is the subset of the full partition table that +/// is kept in memory, and used for flash permissions. +pub fn partition_table_pointer() -> *const u32 { + let ptr = rom_data_lookup(*b"PT", rt_flags::DATA) as *const *const u32; + unsafe { ptr.read() } +} + +/// Determine if we are in secure mode +/// +/// Returns `true` if we are in secure mode and `false` if we are in non-secure +/// mode. +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub fn is_secure_mode() -> bool { + // Look at the start of ROM, which is always readable + #[allow(clippy::zero_ptr)] + let rom_base: *mut u32 = 0x0000_0000 as *mut u32; + // Use the 'tt' instruction to check the permissions for that address + let tt = cortex_m::asm::tt(rom_base); + // Is the secure bit set? => secure mode + (tt & (1 << 22)) != 0 +} + +/// Determine if we are in secure mode +/// +/// Always returns `false` on RISC-V as it is impossible to determine if +/// you are in Machine Mode or User Mode by design. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub fn is_secure_mode() -> bool { + false +} diff --git a/embassy/embassy-rp/src/rtc/datetime_chrono.rs b/embassy/embassy-rp/src/rtc/datetime_chrono.rs new file mode 100644 index 0000000..2818e46 --- /dev/null +++ b/embassy/embassy-rp/src/rtc/datetime_chrono.rs @@ -0,0 +1,62 @@ +use chrono::{Datelike, Timelike}; + +use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1}; + +/// Alias for [`chrono::NaiveDateTime`] +pub type DateTime = chrono::NaiveDateTime; +/// Alias for [`chrono::Weekday`] +pub type DayOfWeek = chrono::Weekday; + +/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs. +/// +/// [`DateTimeFilter`]: struct.DateTimeFilter.html +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] has an invalid year. The year must be between 0 and 4095. + InvalidYear, + /// The [DateTime] contains an invalid date. + InvalidDate, + /// The [DateTime] contains an invalid time. + InvalidTime, +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw.num_days_from_sunday() as u8 +} + +pub(crate) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year() < 0 || dt.year() > 4095 { + // rp2040 can't hold these years + Err(Error::InvalidYear) + } else { + // The rest of the chrono date is assumed to be valid + Ok(()) + } +} + +pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) { + w.set_year(dt.year() as u16); + w.set_month(dt.month() as u8); + w.set_day(dt.day() as u8); +} + +pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) { + w.set_dotw(dt.weekday().num_days_from_sunday() as u8); + w.set_hour(dt.hour() as u8); + w.set_min(dt.minute() as u8); + w.set_sec(dt.second() as u8); +} + +pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result { + let year = rtc_1.year() as i32; + let month = rtc_1.month() as u32; + let day = rtc_1.day() as u32; + + let hour = rtc_0.hour() as u32; + let minute = rtc_0.min() as u32; + let second = rtc_0.sec() as u32; + + let date = chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or(Error::InvalidDate)?; + let time = chrono::NaiveTime::from_hms_opt(hour, minute, second).ok_or(Error::InvalidTime)?; + Ok(DateTime::new(date, time)) +} diff --git a/embassy/embassy-rp/src/rtc/datetime_no_deps.rs b/embassy/embassy-rp/src/rtc/datetime_no_deps.rs new file mode 100644 index 0000000..5de00e6 --- /dev/null +++ b/embassy/embassy-rp/src/rtc/datetime_no_deps.rs @@ -0,0 +1,128 @@ +use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1}; + +/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs. +/// +/// [`DateTimeFilter`]: struct.DateTimeFilter.html +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] contains an invalid year value. Must be between `0..=4095`. + InvalidYear, + /// The [DateTime] contains an invalid month value. Must be between `1..=12`. + InvalidMonth, + /// The [DateTime] contains an invalid day value. Must be between `1..=31`. + InvalidDay, + /// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday. + InvalidDayOfWeek( + /// The value of the DayOfWeek that was given. + u8, + ), + /// The [DateTime] contains an invalid hour value. Must be between `0..=23`. + InvalidHour, + /// The [DateTime] contains an invalid minute value. Must be between `0..=59`. + InvalidMinute, + /// The [DateTime] contains an invalid second value. Must be between `0..=59`. + InvalidSecond, +} + +/// Structure containing date and time information +#[derive(Clone, Debug)] +pub struct DateTime { + /// 0..4095 + pub year: u16, + /// 1..12, 1 is January + pub month: u8, + /// 1..28,29,30,31 depending on month + pub day: u8, + /// + pub day_of_week: DayOfWeek, + /// 0..23 + pub hour: u8, + /// 0..59 + pub minute: u8, + /// 0..59 + pub second: u8, +} + +/// A day of the week +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[allow(missing_docs)] +pub enum DayOfWeek { + Sunday = 0, + Monday = 1, + Tuesday = 2, + Wednesday = 3, + Thursday = 4, + Friday = 5, + Saturday = 6, +} + +fn day_of_week_from_u8(v: u8) -> Result { + Ok(match v { + 0 => DayOfWeek::Sunday, + 1 => DayOfWeek::Monday, + 2 => DayOfWeek::Tuesday, + 3 => DayOfWeek::Wednesday, + 4 => DayOfWeek::Thursday, + 5 => DayOfWeek::Friday, + 6 => DayOfWeek::Saturday, + x => return Err(Error::InvalidDayOfWeek(x)), + }) +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw as u8 +} + +pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year > 4095 { + Err(Error::InvalidYear) + } else if dt.month < 1 || dt.month > 12 { + Err(Error::InvalidMonth) + } else if dt.day < 1 || dt.day > 31 { + Err(Error::InvalidDay) + } else if dt.hour > 23 { + Err(Error::InvalidHour) + } else if dt.minute > 59 { + Err(Error::InvalidMinute) + } else if dt.second > 59 { + Err(Error::InvalidSecond) + } else { + Ok(()) + } +} + +pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) { + w.set_year(dt.year); + w.set_month(dt.month); + w.set_day(dt.day); +} + +pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) { + w.set_dotw(dt.day_of_week as u8); + w.set_hour(dt.hour); + w.set_min(dt.minute); + w.set_sec(dt.second); +} + +pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result { + let year = rtc_1.year(); + let month = rtc_1.month(); + let day = rtc_1.day(); + + let day_of_week = rtc_0.dotw(); + let hour = rtc_0.hour(); + let minute = rtc_0.min(); + let second = rtc_0.sec(); + + let day_of_week = day_of_week_from_u8(day_of_week)?; + Ok(DateTime { + year, + month, + day, + day_of_week, + hour, + minute, + second, + }) +} diff --git a/embassy/embassy-rp/src/rtc/filter.rs b/embassy/embassy-rp/src/rtc/filter.rs new file mode 100644 index 0000000..d4a3bab --- /dev/null +++ b/embassy/embassy-rp/src/rtc/filter.rs @@ -0,0 +1,100 @@ +use super::DayOfWeek; +use crate::pac::rtc::regs::{IrqSetup0, IrqSetup1}; + +/// A filter used for [`RealTimeClock::schedule_alarm`]. +/// +/// [`RealTimeClock::schedule_alarm`]: struct.RealTimeClock.html#method.schedule_alarm +#[derive(Default)] +pub struct DateTimeFilter { + /// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value. + pub year: Option, + /// The month that this alarm should trigger on, `None` if the RTC alarm should not trigger on a month value. + pub month: Option, + /// The day that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day value. + pub day: Option, + /// The day of week that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day of week value. + pub day_of_week: Option, + /// The hour that this alarm should trigger on, `None` if the RTC alarm should not trigger on a hour value. + pub hour: Option, + /// The minute that this alarm should trigger on, `None` if the RTC alarm should not trigger on a minute value. + pub minute: Option, + /// The second that this alarm should trigger on, `None` if the RTC alarm should not trigger on a second value. + pub second: Option, +} + +impl DateTimeFilter { + /// Set a filter on the given year + pub fn year(mut self, year: u16) -> Self { + self.year = Some(year); + self + } + /// Set a filter on the given month + pub fn month(mut self, month: u8) -> Self { + self.month = Some(month); + self + } + /// Set a filter on the given day + pub fn day(mut self, day: u8) -> Self { + self.day = Some(day); + self + } + /// Set a filter on the given day of the week + pub fn day_of_week(mut self, day_of_week: DayOfWeek) -> Self { + self.day_of_week = Some(day_of_week); + self + } + /// Set a filter on the given hour + pub fn hour(mut self, hour: u8) -> Self { + self.hour = Some(hour); + self + } + /// Set a filter on the given minute + pub fn minute(mut self, minute: u8) -> Self { + self.minute = Some(minute); + self + } + /// Set a filter on the given second + pub fn second(mut self, second: u8) -> Self { + self.second = Some(second); + self + } +} + +// register helper functions +impl DateTimeFilter { + pub(super) fn write_setup_0(&self, w: &mut IrqSetup0) { + if let Some(year) = self.year { + w.set_year_ena(true); + + w.set_year(year); + } + if let Some(month) = self.month { + w.set_month_ena(true); + w.set_month(month); + } + if let Some(day) = self.day { + w.set_day_ena(true); + w.set_day(day); + } + } + pub(super) fn write_setup_1(&self, w: &mut IrqSetup1) { + if let Some(day_of_week) = self.day_of_week { + w.set_dotw_ena(true); + let bits = super::datetime::day_of_week_to_u8(day_of_week); + + w.set_dotw(bits); + } + if let Some(hour) = self.hour { + w.set_hour_ena(true); + w.set_hour(hour); + } + if let Some(minute) = self.minute { + w.set_min_ena(true); + w.set_min(minute); + } + if let Some(second) = self.second { + w.set_sec_ena(true); + w.set_sec(second); + } + } +} diff --git a/embassy/embassy-rp/src/rtc/mod.rs b/embassy/embassy-rp/src/rtc/mod.rs new file mode 100644 index 0000000..2ce7ac6 --- /dev/null +++ b/embassy/embassy-rp/src/rtc/mod.rs @@ -0,0 +1,204 @@ +//! RTC driver. +mod filter; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; + +pub use self::filter::DateTimeFilter; + +#[cfg_attr(feature = "chrono", path = "datetime_chrono.rs")] +#[cfg_attr(not(feature = "chrono"), path = "datetime_no_deps.rs")] +mod datetime; + +pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; +use crate::clocks::clk_rtc_freq; + +/// A reference to the real time clock of the system +pub struct Rtc<'d, T: Instance> { + inner: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Rtc<'d, T> { + /// Create a new instance of the real time clock, with the given date as an initial value. + /// + /// # Errors + /// + /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. + pub fn new(inner: impl Peripheral

+ 'd) -> Self { + into_ref!(inner); + + // Set the RTC divider + inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1)); + + Self { inner } + } + + /// Enable or disable the leap year check. The rp2040 chip will always add a Feb 29th on every year that is divisable by 4, but this may be incorrect (e.g. on century years). This function allows you to disable this check. + /// + /// Leap year checking is enabled by default. + pub fn set_leap_year_check(&mut self, leap_year_check_enabled: bool) { + self.inner.regs().ctrl().modify(|w| { + w.set_force_notleapyear(!leap_year_check_enabled); + }); + } + + /// Set the time from internal format + pub fn restore(&mut self, ymd: rp_pac::rtc::regs::Rtc1, hms: rp_pac::rtc::regs::Rtc0) { + // disable RTC while we configure it + self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false)); + while self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + + self.inner.regs().setup_0().write(|w| { + *w = rp_pac::rtc::regs::Setup0(ymd.0); + }); + self.inner.regs().setup_1().write(|w| { + *w = rp_pac::rtc::regs::Setup1(hms.0); + }); + + // Load the new datetime and re-enable RTC + self.inner.regs().ctrl().write(|w| w.set_load(true)); + self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true)); + while !self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + } + + /// Get the time in internal format + pub fn save(&mut self) -> (rp_pac::rtc::regs::Rtc1, rp_pac::rtc::regs::Rtc0) { + let rtc_0: rp_pac::rtc::regs::Rtc0 = self.inner.regs().rtc_0().read(); + let rtc_1 = self.inner.regs().rtc_1().read(); + (rtc_1, rtc_0) + } + + /// Checks to see if this Rtc is running + pub fn is_running(&self) -> bool { + self.inner.regs().ctrl().read().rtc_active() + } + + /// Set the datetime to a new value. + /// + /// # Errors + /// + /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. + pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> { + self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?; + + // disable RTC while we configure it + self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false)); + while self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + + self.inner.regs().setup_0().write(|w| { + self::datetime::write_setup_0(&t, w); + }); + self.inner.regs().setup_1().write(|w| { + self::datetime::write_setup_1(&t, w); + }); + + // Load the new datetime and re-enable RTC + self.inner.regs().ctrl().write(|w| w.set_load(true)); + self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true)); + while !self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + Ok(()) + } + + /// Return the current datetime. + /// + /// # Errors + /// + /// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`]. + pub fn now(&self) -> Result { + if !self.is_running() { + return Err(RtcError::NotRunning); + } + + let rtc_0 = self.inner.regs().rtc_0().read(); + let rtc_1 = self.inner.regs().rtc_1().read(); + + self::datetime::datetime_from_registers(rtc_0, rtc_1).map_err(RtcError::InvalidDateTime) + } + + /// Disable the alarm that was scheduled with [`schedule_alarm`]. + /// + /// [`schedule_alarm`]: #method.schedule_alarm + pub fn disable_alarm(&mut self) { + self.inner.regs().irq_setup_0().modify(|s| s.set_match_ena(false)); + + while self.inner.regs().irq_setup_0().read().match_active() { + core::hint::spin_loop(); + } + } + + /// Schedule an alarm. The `filter` determines at which point in time this alarm is set. + /// + /// Keep in mind that the filter only triggers on the specified time. If you want to schedule this alarm every minute, you have to call: + /// ```no_run + /// # #[cfg(feature = "chrono")] + /// # fn main() { } + /// # #[cfg(not(feature = "chrono"))] + /// # fn main() { + /// # use embassy_rp::rtc::{Rtc, DateTimeFilter}; + /// # let mut real_time_clock: Rtc = unsafe { core::mem::zeroed() }; + /// let now = real_time_clock.now().unwrap(); + /// real_time_clock.schedule_alarm( + /// DateTimeFilter::default() + /// .minute(if now.minute == 59 { 0 } else { now.minute + 1 }) + /// ); + /// # } + /// ``` + pub fn schedule_alarm(&mut self, filter: DateTimeFilter) { + self.disable_alarm(); + + self.inner.regs().irq_setup_0().write(|w| { + filter.write_setup_0(w); + }); + self.inner.regs().irq_setup_1().write(|w| { + 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() { + core::hint::spin_loop(); + } + } + + /// Clear the interrupt. This should be called every time the `RTC_IRQ` interrupt is triggered, + /// or the next [`schedule_alarm`] will never fire. + /// + /// [`schedule_alarm`]: #method.schedule_alarm + pub fn clear_interrupt(&mut self) { + self.disable_alarm(); + } +} + +/// Errors that can occur on methods on [Rtc] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RtcError { + /// An invalid DateTime was given or stored on the hardware. + InvalidDateTime(DateTimeError), + + /// The RTC clock is not running + NotRunning, +} + +trait SealedInstance { + fn regs(&self) -> crate::pac::rtc::Rtc; +} + +/// RTC peripheral instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance {} + +impl SealedInstance for crate::peripherals::RTC { + fn regs(&self) -> crate::pac::rtc::Rtc { + crate::pac::RTC + } +} +impl Instance for crate::peripherals::RTC {} diff --git a/embassy/embassy-rp/src/spi.rs b/embassy/embassy-rp/src/spi.rs new file mode 100644 index 0000000..c48b5c5 --- /dev/null +++ b/embassy/embassy-rp/src/spi.rs @@ -0,0 +1,728 @@ +//! Serial Peripheral Interface +use core::marker::PhantomData; + +use embassy_embedded_hal::SetConfig; +use embassy_futures::join::join; +use embassy_hal_internal::{into_ref, PeripheralRef}; +pub use embedded_hal_02::spi::{Phase, Polarity}; + +use crate::dma::{AnyChannel, Channel}; +use crate::gpio::{AnyPin, Pin as GpioPin, SealedPin as _}; +use crate::{pac, peripherals, Peripheral}; + +/// SPI errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + // No errors for now +} + +/// SPI configuration. +#[non_exhaustive] +#[derive(Clone)] +pub struct Config { + /// Frequency. + pub frequency: u32, + /// Phase. + pub phase: Phase, + /// Polarity. + pub polarity: Polarity, +} + +impl Default for Config { + fn default() -> Self { + Self { + frequency: 1_000_000, + phase: Phase::CaptureOnFirstTransition, + polarity: Polarity::IdleLow, + } + } +} + +/// SPI driver. +pub struct Spi<'d, T: Instance, M: Mode> { + inner: PeripheralRef<'d, T>, + tx_dma: Option>, + rx_dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +fn div_roundup(a: u32, b: u32) -> u32 { + (a + b - 1) / b +} + +fn calc_prescs(freq: u32) -> (u8, u8) { + let clk_peri = crate::clocks::clk_peri_freq(); + + // final SPI frequency: spi_freq = clk_peri / presc / postdiv + // presc must be in 2..=254, and must be even + // postdiv must be in 1..=256 + + // divide extra by 2, so we get rid of the "presc must be even" requirement + let ratio = div_roundup(clk_peri, freq * 2); + if ratio > 127 * 256 { + panic!("Requested too low SPI frequency"); + } + + let presc = div_roundup(ratio, 256); + let postdiv = if presc == 1 { ratio } else { div_roundup(ratio, presc) }; + + ((presc * 2) as u8, (postdiv - 1) as u8) +} + +impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { + fn new_inner( + inner: impl Peripheral

+ 'd, + clk: Option>, + mosi: Option>, + miso: Option>, + cs: Option>, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + into_ref!(inner); + + Self::apply_config(&inner, &config); + + let p = inner.regs(); + + // Always enable DREQ signals -- harmless if DMA is not listening + p.dmacr().write(|reg| { + reg.set_rxdmae(true); + reg.set_txdmae(true); + }); + + // finally, enable. + p.cr1().write(|w| w.set_sse(true)); + + if let Some(pin) = &clk { + pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); + } + if let Some(pin) = &mosi { + pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); + } + if let Some(pin) = &miso { + pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); + } + if let Some(pin) = &cs { + pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); + } + Self { + inner, + tx_dma, + rx_dma, + phantom: PhantomData, + } + } + + /// Private function to apply SPI configuration (phase, polarity, frequency) settings. + /// + /// Driver should be disabled before making changes and reenabled after the modifications + /// are applied. + fn apply_config(inner: &PeripheralRef<'d, T>, config: &Config) { + let p = inner.regs(); + let (presc, postdiv) = calc_prescs(config.frequency); + + p.cpsr().write(|w| w.set_cpsdvsr(presc)); + p.cr0().write(|w| { + w.set_dss(0b0111); // 8bit + w.set_spo(config.polarity == Polarity::IdleHigh); + w.set_sph(config.phase == Phase::CaptureOnSecondTransition); + w.set_scr(postdiv); + }); + } + + /// Write data to SPI blocking execution until done. + pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> { + let p = self.inner.regs(); + for &b in data { + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(b as _)); + while !p.sr().read().rne() {} + let _ = p.dr().read(); + } + self.flush()?; + Ok(()) + } + + /// Transfer data in place to SPI blocking execution until done. + pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> { + let p = self.inner.regs(); + for b in data { + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(*b as _)); + while !p.sr().read().rne() {} + *b = p.dr().read().data() as u8; + } + self.flush()?; + Ok(()) + } + + /// Read data from SPI blocking execution until done. + pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> { + let p = self.inner.regs(); + for b in data { + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(0)); + while !p.sr().read().rne() {} + *b = p.dr().read().data() as u8; + } + self.flush()?; + Ok(()) + } + + /// Transfer data to SPI blocking execution until done. + pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + let p = self.inner.regs(); + let len = read.len().max(write.len()); + for i in 0..len { + let wb = write.get(i).copied().unwrap_or(0); + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(wb as _)); + while !p.sr().read().rne() {} + let rb = p.dr().read().data() as u8; + if let Some(r) = read.get_mut(i) { + *r = rb; + } + } + self.flush()?; + Ok(()) + } + + /// Block execution until SPI is done. + pub fn flush(&mut self) -> Result<(), Error> { + let p = self.inner.regs(); + while p.sr().read().bsy() {} + Ok(()) + } + + /// Set SPI frequency. + pub fn set_frequency(&mut self, freq: u32) { + let (presc, postdiv) = calc_prescs(freq); + let p = self.inner.regs(); + // disable + p.cr1().write(|w| w.set_sse(false)); + + // change stuff + p.cpsr().write(|w| w.set_cpsdvsr(presc)); + p.cr0().modify(|w| { + w.set_scr(postdiv); + }); + + // enable + p.cr1().write(|w| w.set_sse(true)); + } + + /// Set SPI config. + pub fn set_config(&mut self, config: &Config) { + let p = self.inner.regs(); + + // disable + p.cr1().write(|w| w.set_sse(false)); + + // change stuff + Self::apply_config(&self.inner, config); + + // enable + p.cr1().write(|w| w.set_sse(true)); + } +} + +impl<'d, T: Instance> Spi<'d, T, Blocking> { + /// Create an SPI driver in blocking mode. + pub fn new_blocking( + inner: impl Peripheral

+ 'd, + clk: impl Peripheral

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

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

+ 'd> + 'd, + config: Config, + ) -> Self { + into_ref!(clk, mosi, miso); + Self::new_inner( + inner, + Some(clk.map_into()), + Some(mosi.map_into()), + Some(miso.map_into()), + None, + None, + None, + config, + ) + } + + /// Create an SPI driver in blocking mode supporting writes only. + pub fn new_blocking_txonly( + inner: impl Peripheral

+ 'd, + clk: impl Peripheral

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

+ 'd> + 'd, + config: Config, + ) -> Self { + into_ref!(clk, mosi); + Self::new_inner( + inner, + Some(clk.map_into()), + Some(mosi.map_into()), + None, + None, + None, + None, + config, + ) + } + + /// Create an SPI driver in blocking mode supporting reads only. + pub fn new_blocking_rxonly( + inner: impl Peripheral

+ 'd, + clk: impl Peripheral

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

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

+ 'd, + clk: impl Peripheral

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

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

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

+ 'd, + rx_dma: impl Peripheral

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

+ 'd, + clk: impl Peripheral

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

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

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx_dma, clk, mosi); + Self::new_inner( + inner, + Some(clk.map_into()), + Some(mosi.map_into()), + None, + None, + Some(tx_dma.map_into()), + None, + config, + ) + } + + /// Create an SPI driver in async mode supporting DMA read operations only. + pub fn new_rxonly( + inner: impl Peripheral

+ 'd, + clk: impl Peripheral

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

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

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx_dma, rx_dma, clk, miso); + Self::new_inner( + inner, + Some(clk.map_into()), + None, + Some(miso.map_into()), + None, + Some(tx_dma.map_into()), + Some(rx_dma.map_into()), + config, + ) + } + + /// Write data to SPI using DMA. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let tx_ch = self.tx_dma.as_mut().unwrap(); + let tx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::write(tx_ch, buffer, self.inner.regs().dr().as_ptr() as *mut _, T::TX_DREQ) + }; + tx_transfer.await; + + let p = self.inner.regs(); + while p.sr().read().bsy() {} + + // clear RX FIFO contents to prevent stale reads + while p.sr().read().rne() { + let _: u16 = p.dr().read().data(); + } + // clear RX overrun interrupt + p.icr().write(|w| w.set_roric(true)); + + Ok(()) + } + + /// Read data from SPI using DMA. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + // Start RX first. Transfer starts when TX starts, if RX + // is not started yet we might lose bytes. + let rx_ch = self.rx_dma.as_mut().unwrap(); + let rx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read(rx_ch, self.inner.regs().dr().as_ptr() as *const _, buffer, T::RX_DREQ) + }; + + let tx_ch = self.tx_dma.as_mut().unwrap(); + let tx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::write_repeated( + tx_ch, + self.inner.regs().dr().as_ptr() as *mut u8, + buffer.len(), + T::TX_DREQ, + ) + }; + join(tx_transfer, rx_transfer).await; + Ok(()) + } + + /// Transfer data to SPI using DMA. + pub async fn transfer(&mut self, rx_buffer: &mut [u8], tx_buffer: &[u8]) -> Result<(), Error> { + self.transfer_inner(rx_buffer, tx_buffer).await + } + + /// Transfer data in place to SPI using DMA. + pub async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.transfer_inner(words, words).await + } + + async fn transfer_inner(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { + // Start RX first. Transfer starts when TX starts, if RX + // is not started yet we might lose bytes. + let rx_ch = self.rx_dma.as_mut().unwrap(); + let rx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read(rx_ch, self.inner.regs().dr().as_ptr() as *const _, rx, T::RX_DREQ) + }; + + let mut tx_ch = self.tx_dma.as_mut().unwrap(); + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + let tx_transfer = async { + let p = self.inner.regs(); + unsafe { + crate::dma::write(&mut tx_ch, tx, p.dr().as_ptr() as *mut _, T::TX_DREQ).await; + + if rx.len() > tx.len() { + let write_bytes_len = rx.len() - tx.len(); + // write dummy data + // this will disable incrementation of the buffers + crate::dma::write_repeated(tx_ch, p.dr().as_ptr() as *mut u8, write_bytes_len, T::TX_DREQ).await + } + } + }; + join(tx_transfer, rx_transfer).await; + + // if tx > rx we should clear any overflow of the FIFO SPI buffer + if tx.len() > rx.len() { + let p = self.inner.regs(); + while p.sr().read().bsy() {} + + // clear RX FIFO contents to prevent stale reads + while p.sr().read().rne() { + let _: u16 = p.dr().read().data(); + } + // clear RX overrun interrupt + p.icr().write(|w| w.set_roric(true)); + } + + Ok(()) + } +} + +trait SealedMode {} + +trait SealedInstance { + const TX_DREQ: pac::dma::vals::TreqSel; + const RX_DREQ: pac::dma::vals::TreqSel; + + fn regs(&self) -> pac::spi::Spi; +} + +/// Mode. +#[allow(private_bounds)] +pub trait Mode: SealedMode {} + +/// SPI instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance {} + +macro_rules! impl_instance { + ($type:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { + impl SealedInstance for peripherals::$type { + const TX_DREQ: pac::dma::vals::TreqSel = $tx_dreq; + const RX_DREQ: pac::dma::vals::TreqSel = $rx_dreq; + + fn regs(&self) -> pac::spi::Spi { + pac::$type + } + } + impl Instance for peripherals::$type {} + }; +} + +impl_instance!( + SPI0, + Spi0, + pac::dma::vals::TreqSel::SPI0_TX, + pac::dma::vals::TreqSel::SPI0_RX +); +impl_instance!( + SPI1, + Spi1, + pac::dma::vals::TreqSel::SPI1_TX, + pac::dma::vals::TreqSel::SPI1_RX +); + +/// CLK pin. +pub trait ClkPin: GpioPin {} +/// CS pin. +pub trait CsPin: GpioPin {} +/// MOSI pin. +pub trait MosiPin: GpioPin {} +/// MISO pin. +pub trait MisoPin: GpioPin {} + +macro_rules! impl_pin { + ($pin:ident, $instance:ident, $function:ident) => { + impl $function for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, SPI0, MisoPin); +impl_pin!(PIN_1, SPI0, CsPin); +impl_pin!(PIN_2, SPI0, ClkPin); +impl_pin!(PIN_3, SPI0, MosiPin); +impl_pin!(PIN_4, SPI0, MisoPin); +impl_pin!(PIN_5, SPI0, CsPin); +impl_pin!(PIN_6, SPI0, ClkPin); +impl_pin!(PIN_7, SPI0, MosiPin); +impl_pin!(PIN_8, SPI1, MisoPin); +impl_pin!(PIN_9, SPI1, CsPin); +impl_pin!(PIN_10, SPI1, ClkPin); +impl_pin!(PIN_11, SPI1, MosiPin); +impl_pin!(PIN_12, SPI1, MisoPin); +impl_pin!(PIN_13, SPI1, CsPin); +impl_pin!(PIN_14, SPI1, ClkPin); +impl_pin!(PIN_15, SPI1, MosiPin); +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); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, SPI1, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, SPI1, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, SPI0, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, SPI0, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, SPI0, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, SPI0, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, SPI0, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, SPI0, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, SPI0, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, SPI0, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, SPI1, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, SPI1, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, SPI1, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, SPI1, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, SPI1, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, SPI1, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, SPI1, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, SPI1, MosiPin); + +macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; +} + +/// Blocking mode. +pub struct Blocking; +/// Async mode. +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); + +// ==================== + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::spi::Transfer for Spi<'d, T, M> { + type Error = Error; + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + self.blocking_transfer_in_place(words)?; + Ok(words) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::spi::Write for Spi<'d, T, M> { + type Error = Error; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(words) + } +} + +impl embedded_hal_1::spi::Error for Error { + fn kind(&self) -> embedded_hal_1::spi::ErrorKind { + match *self {} + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::ErrorType for Spi<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::SpiBus for Spi<'d, T, M> { + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_transfer(words, &[]) + } + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(words) + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.blocking_transfer(read, write) + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_transfer_in_place(words) + } +} + +impl<'d, T: Instance> embedded_hal_async::spi::SpiBus for Spi<'d, T, Async> { + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write(words).await + } + + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read(words).await + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.transfer(read, write).await + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place(words).await + } +} + +impl<'d, T: Instance, M: Mode> SetConfig for Spi<'d, T, M> { + type Config = Config; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { + self.set_config(config); + + Ok(()) + } +} diff --git a/embassy/embassy-rp/src/time_driver.rs b/embassy/embassy-rp/src/time_driver.rs new file mode 100644 index 0000000..a0eaec1 --- /dev/null +++ b/embassy/embassy-rp/src/time_driver.rs @@ -0,0 +1,144 @@ +//! Timer driver. +use core::cell::{Cell, RefCell}; + +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time_driver::Driver; +use embassy_time_queue_driver::Queue; +#[cfg(feature = "rp2040")] +use pac::TIMER; +#[cfg(feature = "_rp235x")] +use pac::TIMER0 as TIMER; + +use crate::interrupt::InterruptExt; +use crate::{interrupt, pac}; + +struct AlarmState { + timestamp: Cell, +} +unsafe impl Send for AlarmState {} + +struct TimerDriver { + alarms: Mutex, + queue: Mutex>, +} + +embassy_time_driver::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver{ + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState { + timestamp: Cell::new(0), + }), + queue: Mutex::new(RefCell::new(Queue::new())) +}); + +impl Driver for TimerDriver { + fn now(&self) -> u64 { + loop { + let hi = TIMER.timerawh().read(); + let lo = TIMER.timerawl().read(); + let hi2 = TIMER.timerawh().read(); + if hi == hi2 { + return (hi as u64) << 32 | (lo as u64); + } + } + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} + +impl TimerDriver { + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let n = 0; + let alarm = &self.alarms.borrow(cs); + alarm.timestamp.set(timestamp); + + // Arm it. + // Note that we're not checking the high bits at all. This means the irq may fire early + // if the alarm is more than 72 minutes (2^32 us) in the future. This is OK, since on irq fire + // it is checked if the alarm time has passed. + TIMER.alarm(n).write_value(timestamp as u32); + + let now = self.now(); + if timestamp <= now { + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + TIMER.armed().write(|w| w.set_armed(1 << n)); + + alarm.timestamp.set(u64::MAX); + + false + } else { + true + } + } + + fn check_alarm(&self) { + let n = 0; + critical_section::with(|cs| { + let alarm = &self.alarms.borrow(cs); + let timestamp = alarm.timestamp.get(); + if timestamp <= self.now() { + self.trigger_alarm(cs) + } else { + // Not elapsed, arm it again. + // This can happen if it was set more than 2^32 us in the future. + TIMER.alarm(n).write_value(timestamp as u32); + } + }); + + // clear the irq + TIMER.intr().write(|w| w.set_alarm(n, true)); + } + + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + } + } +} + +/// safety: must be called exactly once at bootup +pub unsafe fn init() { + // init alarms + critical_section::with(|cs| { + let alarm = DRIVER.alarms.borrow(cs); + alarm.timestamp.set(u64::MAX); + }); + + // enable irq + TIMER.inte().write(|w| { + w.set_alarm(0, true); + }); + #[cfg(feature = "rp2040")] + { + interrupt::TIMER_IRQ_0.enable(); + } + #[cfg(feature = "_rp235x")] + { + interrupt::TIMER0_IRQ_0.enable(); + } +} + +#[cfg(all(feature = "rt", feature = "rp2040"))] +#[interrupt] +fn TIMER_IRQ_0() { + DRIVER.check_alarm() +} + +#[cfg(all(feature = "rt", feature = "_rp235x"))] +#[interrupt] +fn TIMER0_IRQ_0() { + DRIVER.check_alarm() +} diff --git a/embassy/embassy-rp/src/trng.rs b/embassy/embassy-rp/src/trng.rs new file mode 100644 index 0000000..9f2f33c --- /dev/null +++ b/embassy/embassy-rp/src/trng.rs @@ -0,0 +1,405 @@ +//! True Random Number Generator (TRNG) driver. + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::Not; +use core::task::Poll; + +use embassy_hal_internal::Peripheral; +use embassy_sync::waitqueue::AtomicWaker; +use rand_core::Error; + +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::peripherals::TRNG; +use crate::{interrupt, pac}; + +trait SealedInstance { + fn regs() -> pac::trng::Trng; + fn waker() -> &'static AtomicWaker; +} + +/// TRNG peripheral instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance { + /// Interrupt for this peripheral. + type Interrupt: Interrupt; +} + +impl SealedInstance for TRNG { + fn regs() -> rp_pac::trng::Trng { + pac::TRNG + } + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } +} + +impl Instance for TRNG { + type Interrupt = interrupt::typelevel::TRNG_IRQ; +} + +#[derive(Copy, Clone, Debug)] +#[allow(missing_docs)] +/// TRNG ROSC Inverter chain length options. +pub enum InverterChainLength { + None = 0, + One, + Two, + Three, + Four, +} + +impl From for u8 { + fn from(value: InverterChainLength) -> Self { + value as u8 + } +} + +/// Configuration for the TRNG. +/// +/// - Three built in entropy checks +/// - ROSC frequency controlled by selecting one of ROSC chain lengths +/// - Sample period in terms of system clock ticks +/// +/// +/// Default configuration is based on the following from documentation: +/// +/// ---- +/// +/// RP2350 Datasheet 12.12.2 +/// +/// ... +/// +/// When configuring the TRNG block, consider the following principles: +/// • As average generation time increases, result quality increases and failed entropy checks decrease. +/// • A low sample count decreases average generation time, but increases the chance of NIST test-failing results and +/// failed entropy checks. +/// For acceptable results with an average generation time of about 2 milliseconds, use ROSC chain length settings of 0 or +/// 1 and sample count settings of 20-25. +/// +/// --- +/// +/// Note, Pico SDK and Bootrom don't use any of the entropy checks and sample the ROSC directly +/// by setting the sample period to 0. Random data collected this way is then passed through +/// either hardware accelerated SHA256 (Bootrom) or xoroshiro128** (version 1.0!). +#[non_exhaustive] +#[derive(Copy, Clone, Debug)] +pub struct Config { + /// Bypass TRNG autocorrelation test + pub disable_autocorrelation_test: bool, + /// Bypass CRNGT test + pub disable_crngt_test: bool, + /// When set, the Von-Neuman balancer is bypassed (including the + /// 32 consecutive bits test) + pub disable_von_neumann_balancer: bool, + /// Sets the number of rng_clk cycles between two consecutive + /// ring oscillator samples. + /// Note: If the von Neumann decorrelator is bypassed, the minimum value for + /// sample counter must not be less than seventeen + pub sample_count: u32, + /// Selects the number of inverters (out of four possible + /// selections) in the ring oscillator (the entropy source). Higher values select + /// longer inverter chain lengths. + pub inverter_chain_length: InverterChainLength, +} + +impl Default for Config { + fn default() -> Self { + Config { + disable_autocorrelation_test: true, + disable_crngt_test: true, + disable_von_neumann_balancer: true, + sample_count: 25, + inverter_chain_length: InverterChainLength::One, + } + } +} + +/// True Random Number Generator Driver for RP2350 +/// +/// This driver provides async and blocking options. +/// +/// See [Config] for configuration details. +/// +/// Usage example: +/// ```no_run +/// use embassy_executor::Spawner; +/// use embassy_rp::trng::Trng; +/// use embassy_rp::peripherals::TRNG; +/// use embassy_rp::bind_interrupts; +/// +/// bind_interrupts!(struct Irqs { +/// TRNG_IRQ => embassy_rp::trng::InterruptHandler; +/// }); +/// +/// #[embassy_executor::main] +/// async fn main(spawner: Spawner) { +/// let peripherals = embassy_rp::init(Default::default()); +/// let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default()); +/// +/// let mut randomness = [0u8; 58]; +/// loop { +/// trng.fill_bytes(&mut randomness).await; +/// assert_ne!(randomness, [0u8; 58]); +/// } +///} +/// ``` +pub struct Trng<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, +} + +/// 12.12.1. Overview +/// On request, the TRNG block generates a block of 192 entropy bits generated by automatically processing a series of +/// periodic samples from the TRNG block’s internal Ring Oscillator (ROSC). +const TRNG_BLOCK_SIZE_BITS: usize = 192; +const TRNG_BLOCK_SIZE_BYTES: usize = TRNG_BLOCK_SIZE_BITS / 8; + +impl<'d, T: Instance> Trng<'d, T> { + /// Create a new TRNG driver. + pub fn new( + _trng: impl Peripheral

+ 'd, + _irq: impl Binding> + 'd, + config: Config, + ) -> Self { + let regs = T::regs(); + + regs.rng_imr().write(|w| w.set_ehr_valid_int_mask(false)); + + let trng_config_register = regs.trng_config(); + trng_config_register.write(|w| { + w.set_rnd_src_sel(config.inverter_chain_length.clone().into()); + }); + + let sample_count_register = regs.sample_cnt1(); + sample_count_register.write(|w| { + *w = config.sample_count; + }); + + let debug_control_register = regs.trng_debug_control(); + debug_control_register.write(|w| { + w.set_auto_correlate_bypass(config.disable_autocorrelation_test); + w.set_trng_crngt_bypass(config.disable_crngt_test); + w.set_vnc_bypass(config.disable_von_neumann_balancer) + }); + + Trng { phantom: PhantomData } + } + + fn start_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + // Enable TRNG ROSC + source_enable_register.write(|w| w.set_rnd_src_en(true)); + } + + fn stop_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + source_enable_register.write(|w| w.set_rnd_src_en(false)); + let reset_bits_counter_register = regs.rst_bits_counter(); + reset_bits_counter_register.write(|w| w.set_rst_bits_counter(true)); + } + + fn enable_irq(&self) { + unsafe { T::Interrupt::enable() } + } + + fn disable_irq(&self) { + T::Interrupt::disable(); + } + + fn blocking_wait_for_successful_generation(&self) { + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let mut success = false; + while success.not() { + while trng_busy_register.read().trng_busy() {} + if trng_valid_register.read().ehr_valid().not() { + if regs.rng_isr().read().autocorr_err() { + regs.trng_sw_reset().write(|w| w.set_trng_sw_reset(true)); + } else { + panic!("RNG not busy, but ehr is not valid!") + } + } else { + success = true + } + } + } + + fn read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + let regs = T::regs(); + let ehr_data_regs = [ + regs.ehr_data0(), + regs.ehr_data1(), + regs.ehr_data2(), + regs.ehr_data3(), + regs.ehr_data4(), + regs.ehr_data5(), + ]; + + for (i, reg) in ehr_data_regs.iter().enumerate() { + buffer[i * 4..i * 4 + 4].copy_from_slice(®.read().to_ne_bytes()); + } + } + + fn blocking_read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + self.blocking_wait_for_successful_generation(); + self.read_ehr_registers_into_array(buffer); + } + + /// Fill the buffer with random bytes, async version. + pub async fn fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + + self.start_rng(); + self.enable_irq(); + + let mut bytes_transferred = 0usize; + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let waker = T::waker(); + + let destination_length = destination.len(); + + poll_fn(|context| { + waker.register(context.waker()); + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + if trng_busy_register.read().trng_busy() { + Poll::Pending + } else { + if trng_valid_register.read().ehr_valid().not() { + panic!("RNG not busy, but ehr is not valid!") + } + self.read_ehr_registers_into_array(&mut buffer); + let remaining = destination_length - bytes_transferred; + if remaining > TRNG_BLOCK_SIZE_BYTES { + destination[bytes_transferred..bytes_transferred + TRNG_BLOCK_SIZE_BYTES] + .copy_from_slice(&buffer); + bytes_transferred += TRNG_BLOCK_SIZE_BYTES + } else { + destination[bytes_transferred..bytes_transferred + remaining] + .copy_from_slice(&buffer[0..remaining]); + bytes_transferred += remaining + } + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + Poll::Pending + } + } + } + }) + .await + } + + /// Fill the buffer with random bytes, blocking version. + pub fn blocking_fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + self.start_rng(); + + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + for chunk in destination.chunks_mut(TRNG_BLOCK_SIZE_BYTES) { + self.blocking_wait_for_successful_generation(); + self.blocking_read_ehr_registers_into_array(&mut buffer); + chunk.copy_from_slice(&buffer[..chunk.len()]) + } + self.stop_rng() + } + + /// Return a random u32, blocking. + pub fn blocking_next_u32(&mut self) -> u32 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = regs.ehr_data5().read(); + self.stop_rng(); + result + } + + /// Return a random u64, blocking. + pub fn blocking_next_u64(&mut self) -> u64 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + + let low = regs.ehr_data4().read() as u64; + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = (regs.ehr_data5().read() as u64) << 32 | low; + self.stop_rng(); + result + } +} + +impl<'d, T: Instance> rand_core::RngCore for Trng<'d, T> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + self.blocking_fill_bytes(dest); + Ok(()) + } +} +/// TRNG interrupt handler. +pub struct InterruptHandler { + _trng: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + let isr = regs.rng_isr().read(); + // Clear ehr bit + regs.rng_icr().write(|w| { + w.set_ehr_valid(true); + }); + if isr.ehr_valid() { + T::waker().wake(); + } else { + // 12.12.5. List of Registers + // ... + // TRNG: RNG_ISR Register + // ... + // AUTOCORR_ERR: 1 indicates Autocorrelation test failed four times in a row. + // When set, RNG ceases functioning until next reset + if isr.autocorr_err() { + warn!("TRNG Autocorrect error! Resetting TRNG"); + regs.trng_sw_reset().write(|w| { + w.set_trng_sw_reset(true); + }); + } + } + } +} diff --git a/embassy/embassy-rp/src/uart/buffered.rs b/embassy/embassy-rp/src/uart/buffered.rs new file mode 100644 index 0000000..152a432 --- /dev/null +++ b/embassy/embassy-rp/src/uart/buffered.rs @@ -0,0 +1,839 @@ +//! Buffered UART driver. +use core::future::Future; +use core::slice; + +use atomic_polyfill::AtomicU8; +use embassy_hal_internal::atomic_ring_buffer::RingBuffer; + +use super::*; + +pub struct State { + tx_waker: AtomicWaker, + tx_buf: RingBuffer, + rx_waker: AtomicWaker, + rx_buf: RingBuffer, + rx_error: AtomicU8, +} + +// these must match bits 8..11 in UARTDR +const RXE_OVERRUN: u8 = 8; +const RXE_BREAK: u8 = 4; +const RXE_PARITY: u8 = 2; +const RXE_FRAMING: u8 = 1; + +impl State { + pub const fn new() -> Self { + Self { + rx_buf: RingBuffer::new(), + tx_buf: RingBuffer::new(), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + rx_error: AtomicU8::new(0), + } + } +} + +/// Buffered UART driver. +pub struct BufferedUart<'d, T: Instance> { + pub(crate) rx: BufferedUartRx<'d, T>, + pub(crate) tx: BufferedUartTx<'d, T>, +} + +/// Buffered UART RX handle. +pub struct BufferedUartRx<'d, T: Instance> { + pub(crate) phantom: PhantomData<&'d mut T>, +} + +/// Buffered UART TX handle. +pub struct BufferedUartTx<'d, T: Instance> { + pub(crate) phantom: PhantomData<&'d mut T>, +} + +pub(crate) fn init_buffers<'d, T: Instance + 'd>( + _irq: impl Binding>, + tx_buffer: Option<&'d mut [u8]>, + rx_buffer: Option<&'d mut [u8]>, +) { + let state = T::buffered_state(); + + if let Some(tx_buffer) = tx_buffer { + let len = tx_buffer.len(); + unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + } + + if let Some(rx_buffer) = rx_buffer { + 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(); + regs.uartimsc().write(|w| { + w.set_rxim(true); + w.set_rtim(true); + w.set_txim(true); + }); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; +} + +impl<'d, T: Instance> BufferedUart<'d, T> { + /// Create a buffered UART instance. + pub fn new( + _uart: impl Peripheral

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

> + 'd, + rx: impl Peripheral

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

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

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(tx, rx, cts, rts); + + super::Uart::<'d, T, Async>::init( + Some(tx.map_into()), + Some(rx.map_into()), + Some(rts.map_into()), + Some(cts.map_into()), + config, + ); + init_buffers::(irq, Some(tx_buffer), Some(rx_buffer)); + + Self { + rx: BufferedUartRx { phantom: PhantomData }, + tx: BufferedUartTx { phantom: PhantomData }, + } + } + + /// Write to UART TX buffer blocking execution until done. + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result { + self.tx.blocking_write(buffer) + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + self.tx.blocking_flush() + } + + /// Read from UART RX buffer blocking execution until done. + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result { + self.rx.blocking_read(buffer) + } + + /// Check if UART is busy transmitting. + pub fn busy(&self) -> bool { + self.tx.busy() + } + + /// Wait until TX is empty and send break condition. + pub async fn send_break(&mut self, bits: u32) { + self.tx.send_break(bits).await + } + + /// sets baudrate on runtime + pub fn set_baudrate(&mut self, baudrate: u32) { + super::Uart::<'d, T, Async>::set_baudrate_inner(baudrate); + } + + /// Split into separate RX and TX handles. + pub fn split(self) -> (BufferedUartTx<'d, T>, BufferedUartRx<'d, T>) { + (self.tx, self.rx) + } + + /// Split the Uart into a transmitter and receiver by mutable reference, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split_ref(&mut self) -> (&mut BufferedUartTx<'d, T>, &mut BufferedUartRx<'d, T>) { + (&mut self.tx, &mut self.rx) + } +} + +impl<'d, T: Instance> BufferedUartRx<'d, T> { + /// Create a new buffered UART RX. + pub fn new( + _uart: impl Peripheral

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

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

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

> + 'd, + rts: impl Peripheral

> + 'd, + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(rx, rts); + + super::Uart::<'d, T, Async>::init(None, Some(rx.map_into()), Some(rts.map_into()), None, config); + init_buffers::(irq, None, Some(rx_buffer)); + + Self { phantom: PhantomData } + } + + fn read<'a>(buf: &'a mut [u8]) -> impl Future> + 'a + where + T: 'd, + { + poll_fn(move |cx| { + if let Poll::Ready(r) = Self::try_read(buf) { + return Poll::Ready(r); + } + T::buffered_state().rx_waker.register(cx.waker()); + Poll::Pending + }) + } + + fn get_rx_error() -> Option { + let errs = T::buffered_state().rx_error.swap(0, Ordering::Relaxed); + if errs & RXE_OVERRUN != 0 { + Some(Error::Overrun) + } else if errs & RXE_BREAK != 0 { + Some(Error::Break) + } else if errs & RXE_PARITY != 0 { + Some(Error::Parity) + } else if errs & RXE_FRAMING != 0 { + Some(Error::Framing) + } else { + None + } + } + + fn try_read(buf: &mut [u8]) -> Poll> + where + T: 'd, + { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let state = T::buffered_state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let n = rx_reader.pop(|data| { + let n = data.len().min(buf.len()); + buf[..n].copy_from_slice(&data[..n]); + n + }); + + let result = if n == 0 { + match Self::get_rx_error() { + None => return Poll::Pending, + Some(e) => Err(e), + } + } else { + Ok(n) + }; + + // (Re-)Enable the interrupt to receive more data in case it was + // disabled because the buffer was full or errors were detected. + let regs = T::regs(); + regs.uartimsc().write_set(|w| { + w.set_rxim(true); + w.set_rtim(true); + }); + + Poll::Ready(result) + } + + /// Read from UART RX buffer blocking execution until done. + pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result { + loop { + match Self::try_read(buf) { + Poll::Ready(res) => return res, + Poll::Pending => continue, + } + } + } + + fn fill_buf<'a>() -> impl Future> + where + T: 'd, + { + poll_fn(move |cx| { + let state = T::buffered_state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let (p, n) = rx_reader.pop_buf(); + let result = if n == 0 { + match Self::get_rx_error() { + None => { + state.rx_waker.register(cx.waker()); + return Poll::Pending; + } + Some(e) => Err(e), + } + } else { + let buf = unsafe { slice::from_raw_parts(p, n) }; + Ok(buf) + }; + + Poll::Ready(result) + }) + } + + fn consume(amt: usize) { + let state = T::buffered_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 or errors were detected. + let regs = T::regs(); + regs.uartimsc().write_set(|w| { + w.set_rxim(true); + w.set_rtim(true); + }); + } + + /// we are ready to read if there is data in the buffer + fn read_ready() -> Result { + let state = T::buffered_state(); + Ok(!state.rx_buf.is_empty()) + } +} + +impl<'d, T: Instance> BufferedUartTx<'d, T> { + /// Create a new buffered UART TX. + pub fn new( + _uart: impl Peripheral

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

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

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

> + 'd, + cts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(tx, cts); + + super::Uart::<'d, T, Async>::init(Some(tx.map_into()), None, None, Some(cts.map_into()), config); + init_buffers::(irq, Some(tx_buffer), None); + + Self { phantom: PhantomData } + } + + fn write(buf: &[u8]) -> impl Future> + '_ { + poll_fn(move |cx| { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let state = T::buffered_state(); + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let n = tx_writer.push(|data| { + let n = data.len().min(buf.len()); + 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. + T::Interrupt::pend(); + Poll::Ready(Ok(n)) + }) + } + + fn flush() -> impl Future> { + poll_fn(move |cx| { + let state = T::buffered_state(); + if !state.tx_buf.is_empty() { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + } + + /// Write to UART TX buffer blocking execution until done. + pub fn blocking_write(&mut self, buf: &[u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + loop { + let state = T::buffered_state(); + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let n = tx_writer.push(|data| { + let n = data.len().min(buf.len()); + data[..n].copy_from_slice(&buf[..n]); + n + }); + + if n != 0 { + // 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. + T::Interrupt::pend(); + return Ok(n); + } + } + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + loop { + let state = T::buffered_state(); + if state.tx_buf.is_empty() { + return Ok(()); + } + } + } + + /// Check if UART is busy. + pub fn busy(&self) -> bool { + T::regs().uartfr().read().busy() + } + + /// Assert a break condition after waiting for the transmit buffers to empty, + /// for the specified number of bit times. This condition must be asserted + /// for at least two frame times to be effective, `bits` will adjusted + /// according to frame size, parity, and stop bit settings to ensure this. + /// + /// This method may block for a long amount of time since it has to wait + /// for the transmit fifo to empty, which may take a while on slow links. + pub async fn send_break(&mut self, bits: u32) { + let regs = T::regs(); + let bits = bits.max({ + let lcr = regs.uartlcr_h().read(); + let width = lcr.wlen() as u32 + 5; + let parity = lcr.pen() as u32; + let stops = 1 + lcr.stp2() as u32; + 2 * (1 + width + parity + stops) + }); + let divx64 = (((regs.uartibrd().read().baud_divint() as u32) << 6) + + regs.uartfbrd().read().baud_divfrac() as u32) as u64; + let div_clk = clk_peri_freq() as u64 * 64; + let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk; + + Self::flush().await.unwrap(); + while self.busy() {} + regs.uartlcr_h().write_set(|w| w.set_brk(true)); + Timer::after_micros(wait_usecs).await; + regs.uartlcr_h().write_clear(|w| w.set_brk(true)); + } +} + +impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> { + fn drop(&mut self) { + let state = T::buffered_state(); + unsafe { state.rx_buf.deinit() } + + // TX is inactive if the buffer is not available. + // We can now unregister the interrupt handler + if !state.tx_buf.is_available() { + T::Interrupt::disable(); + } + } +} + +impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> { + fn drop(&mut self) { + let state = T::buffered_state(); + unsafe { state.tx_buf.deinit() } + + // RX is inactive if the buffer is not available. + // We can now unregister the interrupt handler + if !state.rx_buf.is_available() { + T::Interrupt::disable(); + } + } +} + +/// Interrupt handler. +pub struct BufferedInterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for BufferedInterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + if r.uartdmacr().read().rxdmae() { + return; + } + + let s = T::buffered_state(); + + // 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()); + }); + + // 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 + if s.rx_buf.is_available() { + let mut rx_writer = unsafe { s.rx_buf.writer() }; + let rx_buf = rx_writer.push_slice(); + let mut n_read = 0; + let mut error = false; + for rx_byte in rx_buf { + if r.uartfr().read().rxfe() { + break; + } + let dr = r.uartdr().read(); + if (dr.0 >> 8) != 0 { + s.rx_error.fetch_or((dr.0 >> 8) as u8, Ordering::Relaxed); + error = true; + // only fill the buffer with valid characters. the current character is fine + // if the error is an overrun, but if we add it to the buffer we'll report + // the overrun one character too late. drop it instead and pretend we were + // a bit slower at draining the rx fifo than we actually were. + // this is consistent with blocking uart error reporting. + break; + } + *rx_byte = dr.data(); + n_read += 1; + } + if n_read > 0 { + rx_writer.push_done(n_read); + s.rx_waker.wake(); + } else if error { + s.rx_waker.wake(); + } + // Disable any further RX interrupts when the buffer becomes full or + // errors have occurred. This lets us buffer additional errors in the + // fifo without needing more error storage locations, and most applications + // will want to do a full reset of their uart state anyway once an error + // has happened. + if s.rx_buf.is_full() || error { + r.uartimsc().write_clear(|w| { + w.set_rxim(true); + w.set_rtim(true); + }); + } + } + + // TX + if s.tx_buf.is_available() { + let mut tx_reader = unsafe { 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; + } + r.uartdr().write(|w| w.set_data(*tx_byte)); + n_written += 1; + } + 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. + } + } +} + +impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} + +impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUart<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartRx<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartTx<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUart<'d, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + BufferedUartRx::<'d, T>::read(buf).await + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUartRx<'d, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Self::read(buf).await + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUart<'d, T> { + fn read_ready(&mut self) -> Result { + BufferedUartRx::<'d, T>::read_ready() + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUartRx<'d, T> { + fn read_ready(&mut self) -> Result { + Self::read_ready() + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUart<'d, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + BufferedUartRx::<'d, T>::fill_buf().await + } + + fn consume(&mut self, amt: usize) { + BufferedUartRx::<'d, T>::consume(amt) + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUartRx<'d, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Self::fill_buf().await + } + + fn consume(&mut self, amt: usize) { + Self::consume(amt) + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUart<'d, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + BufferedUartTx::<'d, T>::write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + BufferedUartTx::<'d, T>::flush().await + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUartTx<'d, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + Self::write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Self::flush().await + } +} + +impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUart<'d, T> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.blocking_read(buf) + } +} + +impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUartRx<'d, T> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.blocking_read(buf) + } +} + +impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUart<'d, T> { + fn write(&mut self, buf: &[u8]) -> Result { + self.tx.blocking_write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.blocking_flush() + } +} + +impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUartTx<'d, T> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUartRx<'d, T> { + type Error = Error; + + fn read(&mut self) -> Result> { + let r = T::regs(); + if r.uartfr().read().rxfe() { + return Err(nb::Error::WouldBlock); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + Err(nb::Error::Other(Error::Overrun)) + } else if dr.be() { + Err(nb::Error::Other(Error::Break)) + } else if dr.pe() { + Err(nb::Error::Other(Error::Parity)) + } else if dr.fe() { + Err(nb::Error::Other(Error::Framing)) + } else { + Ok(dr.data()) + } + } +} + +impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUartTx<'d, T> { + type Error = Error; + + fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { + while !buffer.is_empty() { + match self.blocking_write(buffer) { + Ok(0) => panic!("zero-length write."), + Ok(n) => buffer = &buffer[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUart<'d, T> { + type Error = Error; + + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUart<'d, T> { + type Error = Error; + + fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { + while !buffer.is_empty() { + match self.blocking_write(buffer) { + Ok(0) => panic!("zero-length write."), + Ok(n) => buffer = &buffer[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartRx<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartTx<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUart<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUartRx<'d, T> { + fn read(&mut self) -> nb::Result { + embedded_hal_02::serial::Read::read(self) + } +} + +impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUart<'d, T> { + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUart<'d, T> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} diff --git a/embassy/embassy-rp/src/uart/mod.rs b/embassy/embassy-rp/src/uart/mod.rs new file mode 100644 index 0000000..8d12aee --- /dev/null +++ b/embassy/embassy-rp/src/uart/mod.rs @@ -0,0 +1,1509 @@ +//! UART driver. +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use atomic_polyfill::{AtomicU16, Ordering}; +use embassy_futures::select::{select, Either}; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_time::{Delay, Timer}; +use pac::uart::regs::Uartris; + +use crate::clocks::clk_peri_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::gpio::{AnyPin, SealedPin}; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::pac::io::vals::{Inover, Outover}; +use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; + +mod buffered; +pub use buffered::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, BufferedUartTx}; + +/// Word length. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum DataBits { + /// 5 bits. + DataBits5, + /// 6 bits. + DataBits6, + /// 7 bits. + DataBits7, + /// 8 bits. + DataBits8, +} + +impl DataBits { + fn bits(&self) -> u8 { + match self { + Self::DataBits5 => 0b00, + Self::DataBits6 => 0b01, + Self::DataBits7 => 0b10, + Self::DataBits8 => 0b11, + } + } +} + +/// Parity bit. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Parity { + /// No parity. + ParityNone, + /// Even parity. + ParityEven, + /// Odd parity. + ParityOdd, +} + +/// Stop bits. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum StopBits { + #[doc = "1 stop bit"] + STOP1, + #[doc = "2 stop bits"] + STOP2, +} + +/// UART config. +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Config { + /// Baud rate. + pub baudrate: u32, + /// Word length. + pub data_bits: DataBits, + /// Stop bits. + pub stop_bits: StopBits, + /// Parity bit. + pub parity: Parity, + /// Invert the tx pin output + pub invert_tx: bool, + /// Invert the rx pin input + pub invert_rx: bool, + /// Invert the rts pin + pub invert_rts: bool, + /// Invert the cts pin + pub invert_cts: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + baudrate: 115200, + data_bits: DataBits::DataBits8, + stop_bits: StopBits::STOP1, + parity: Parity::ParityNone, + invert_rx: false, + invert_tx: false, + invert_rts: false, + invert_cts: false, + } + } +} + +/// Serial error +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Triggered when the FIFO (or shift-register) is overflowed. + Overrun, + /// Triggered when a break is received + Break, + /// Triggered when there is a parity mismatch between what's received and + /// our settings. + Parity, + /// Triggered when the received character didn't have a valid stop bit. + Framing, +} + +/// Read To Break error +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ReadToBreakError { + /// Read this many bytes, but never received a line break. + MissingBreak(usize), + /// Other, standard issue with the serial request + Other(Error), +} + +/// Internal DMA state of UART RX. +pub struct DmaState { + rx_err_waker: AtomicWaker, + rx_errs: AtomicU16, +} + +/// UART driver. +pub struct Uart<'d, T: Instance, M: Mode> { + tx: UartTx<'d, T, M>, + rx: UartRx<'d, T, M>, +} + +/// UART TX driver. +pub struct UartTx<'d, T: Instance, M: Mode> { + tx_dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +/// UART RX driver. +pub struct UartRx<'d, T: Instance, M: Mode> { + rx_dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { + /// Create a new DMA-enabled UART which can only send data + pub fn new( + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + 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, + } + } + + /// Transmit the provided buffer blocking execution until done. + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let r = T::regs(); + for &b in buffer { + while r.uartfr().read().txff() {} + r.uartdr().write(|w| w.set_data(b)); + } + Ok(()) + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + let r = T::regs(); + while !r.uartfr().read().txfe() {} + Ok(()) + } + + /// Check if UART is busy transmitting. + pub fn busy(&self) -> bool { + T::regs().uartfr().read().busy() + } + + /// Assert a break condition after waiting for the transmit buffers to empty, + /// for the specified number of bit times. This condition must be asserted + /// for at least two frame times to be effective, `bits` will adjusted + /// according to frame size, parity, and stop bit settings to ensure this. + /// + /// This method may block for a long amount of time since it has to wait + /// for the transmit fifo to empty, which may take a while on slow links. + pub async fn send_break(&mut self, bits: u32) { + let regs = T::regs(); + let bits = bits.max({ + let lcr = regs.uartlcr_h().read(); + let width = lcr.wlen() as u32 + 5; + let parity = lcr.pen() as u32; + let stops = 1 + lcr.stp2() as u32; + 2 * (1 + width + parity + stops) + }); + let divx64 = (((regs.uartibrd().read().baud_divint() as u32) << 6) + + regs.uartfbrd().read().baud_divfrac() as u32) as u64; + let div_clk = clk_peri_freq() as u64 * 64; + let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk; + + self.blocking_flush().unwrap(); + while self.busy() {} + regs.uartlcr_h().write_set(|w| w.set_brk(true)); + Timer::after_micros(wait_usecs).await; + regs.uartlcr_h().write_clear(|w| w.set_brk(true)); + } +} + +impl<'d, T: Instance> UartTx<'d, T, Blocking> { + /// Create a new UART TX instance for blocking mode operations. + pub fn new_blocking( + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(tx); + Uart::::init(Some(tx.map_into()), None, None, None, config); + Self::new_inner(None) + } + + /// Convert this uart TX instance into a buffered uart using the provided + /// irq and transmit buffer. + pub fn into_buffered( + self, + irq: impl Binding>, + tx_buffer: &'d mut [u8], + ) -> BufferedUartTx<'d, T> { + buffered::init_buffers::(irq, Some(tx_buffer), None); + + BufferedUartTx { phantom: PhantomData } + } +} + +impl<'d, T: Instance> UartTx<'d, T, Async> { + /// Write to UART TX from the provided buffer using DMA. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let ch = self.tx_dma.as_mut().unwrap(); + let transfer = unsafe { + T::regs().uartdmacr().write_set(|reg| { + reg.set_txdmae(true); + }); + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::write(ch, buffer, T::regs().uartdr().as_ptr() as *mut _, T::TX_DREQ.into()) + }; + transfer.await; + Ok(()) + } +} + +impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { + /// Create a new DMA-enabled UART which can only receive data + pub fn new( + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + _irq: impl Binding>, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(rx, rx_dma); + Uart::::init(None, Some(rx.map_into()), None, None, config); + Self::new_inner(true, Some(rx_dma.map_into())) + } + + fn new_inner(has_irq: bool, rx_dma: Option>) -> Self { + debug_assert_eq!(has_irq, rx_dma.is_some()); + if has_irq { + // disable all error interrupts initially + T::regs().uartimsc().write(|w| w.0 = 0); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + } + Self { + rx_dma, + phantom: PhantomData, + } + } + + /// Read from UART RX blocking execution until done. + pub fn blocking_read(&mut self, mut buffer: &mut [u8]) -> Result<(), Error> { + while !buffer.is_empty() { + let received = self.drain_fifo(buffer).map_err(|(_i, e)| e)?; + buffer = &mut buffer[received..]; + } + Ok(()) + } + + /// Returns Ok(len) if no errors occurred. Returns Err((len, err)) if an error was + /// encountered. in both cases, `len` is the number of *good* bytes copied into + /// `buffer`. + fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result { + let r = T::regs(); + for (i, b) in buffer.iter_mut().enumerate() { + if r.uartfr().read().rxfe() { + return Ok(i); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + return Err((i, Error::Overrun)); + } else if dr.be() { + return Err((i, Error::Break)); + } else if dr.pe() { + return Err((i, Error::Parity)); + } else if dr.fe() { + return Err((i, Error::Framing)); + } else { + *b = dr.data(); + } + } + Ok(buffer.len()) + } +} + +impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> { + fn drop(&mut self) { + if self.rx_dma.is_some() { + T::Interrupt::disable(); + // clear dma flags. irq handlers use these to disambiguate among themselves. + T::regs().uartdmacr().write_clear(|reg| { + reg.set_rxdmae(true); + reg.set_txdmae(true); + reg.set_dmaonerr(true); + }); + } + } +} + +impl<'d, T: Instance> UartRx<'d, T, Blocking> { + /// Create a new UART RX instance for blocking mode operations. + pub fn new_blocking( + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(rx); + Uart::::init(None, Some(rx.map_into()), None, None, config); + Self::new_inner(false, None) + } + + /// Convert this uart RX instance into a buffered uart using the provided + /// irq and receive buffer. + pub fn into_buffered( + self, + irq: impl Binding>, + rx_buffer: &'d mut [u8], + ) -> BufferedUartRx<'d, T> { + buffered::init_buffers::(irq, None, Some(rx_buffer)); + + BufferedUartRx { phantom: PhantomData } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let uart = T::regs(); + if !uart.uartdmacr().read().rxdmae() { + return; + } + + let state = T::dma_state(); + let errs = uart.uartris().read(); + state.rx_errs.store(errs.0 as u16, Ordering::Relaxed); + state.rx_err_waker.wake(); + // disable the error interrupts instead of clearing the flags. clearing the + // flags would allow the dma transfer to continue, potentially signaling + // completion before we can check for errors that happened *during* the transfer. + uart.uartimsc().write_clear(|w| w.0 = errs.0); + } +} + +impl<'d, T: Instance> UartRx<'d, T, Async> { + /// Read from UART RX into the provided buffer. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + // clear error flags before we drain the fifo. errors that have accumulated + // in the flags will also be present in the fifo. + T::dma_state().rx_errs.store(0, Ordering::Relaxed); + T::regs().uarticr().write(|w| { + w.set_oeic(true); + w.set_beic(true); + w.set_peic(true); + w.set_feic(true); + }); + + // then drain the fifo. we need to read at most 32 bytes. errors that apply + // to fifo bytes will be reported directly. + let buffer = match { + let limit = buffer.len().min(32); + self.drain_fifo(&mut buffer[0..limit]) + } { + Ok(len) if len < buffer.len() => &mut buffer[len..], + Ok(_) => return Ok(()), + Err((_i, e)) => return Err(e), + }; + + // start a dma transfer. if errors have happened in the interim some error + // interrupt flags will have been raised, and those will be picked up immediately + // by the interrupt handler. + let ch = self.rx_dma.as_mut().unwrap(); + T::regs().uartimsc().write_set(|w| { + w.set_oeim(true); + w.set_beim(true); + w.set_peim(true); + w.set_feim(true); + }); + T::regs().uartdmacr().write_set(|reg| { + reg.set_rxdmae(true); + reg.set_dmaonerr(true); + }); + let transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read(ch, T::regs().uartdr().as_ptr() as *const _, buffer, T::RX_DREQ.into()) + }; + + // wait for either the transfer to complete or an error to happen. + let transfer_result = select( + transfer, + poll_fn(|cx| { + T::dma_state().rx_err_waker.register(cx.waker()); + match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { + 0 => Poll::Pending, + e => Poll::Ready(Uartris(e as u32)), + } + }), + ) + .await; + + let errors = match transfer_result { + Either::First(()) => { + // We're here because the DMA finished, BUT if an error occurred on the LAST + // byte, then we may still need to grab the error state! + Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) + } + Either::Second(e) => { + // We're here because we errored, which means this is the error that + // was problematic. + e + } + }; + + // If we got no error, just return at this point + if errors.0 == 0 { + return Ok(()); + } + + // If we DID get an error, we need to figure out which one it was. + if errors.oeris() { + return Err(Error::Overrun); + } else if errors.beris() { + return Err(Error::Break); + } else if errors.peris() { + return Err(Error::Parity); + } else if errors.feris() { + return Err(Error::Framing); + } + unreachable!("unrecognized rx error"); + } + + /// Read from the UART, waiting for a line break. + /// + /// We read until one of the following occurs: + /// + /// * We read `buffer.len()` bytes without a line break + /// * returns `Err(ReadToBreakError::MissingBreak(buffer.len()))` + /// * We read `n` bytes then a line break occurs + /// * returns `Ok(n)` + /// * We encounter some error OTHER than a line break + /// * returns `Err(ReadToBreakError::Other(error))` + /// + /// **NOTE**: you MUST provide a buffer one byte larger than your largest expected + /// message to reliably detect the framing on one single call to `read_to_break()`. + /// + /// * If you expect a message of 20 bytes + line break, and provide a 20-byte buffer: + /// * The first call to `read_to_break()` will return `Err(ReadToBreakError::MissingBreak(20))` + /// * The next call to `read_to_break()` will immediately return `Ok(0)`, from the "stale" line break + /// * If you expect a message of 20 bytes + line break, and provide a 21-byte buffer: + /// * The first call to `read_to_break()` will return `Ok(20)`. + /// * The next call to `read_to_break()` will work as expected + pub async fn read_to_break(&mut self, buffer: &mut [u8]) -> Result { + self.read_to_break_with_count(buffer, 0).await + } + + /// Read from the UART, waiting for a line break as soon as at least `min_count` bytes have been read. + /// + /// We read until one of the following occurs: + /// + /// * We read `buffer.len()` bytes without a line break + /// * returns `Err(ReadToBreakError::MissingBreak(buffer.len()))` + /// * We read `n > min_count` bytes then a line break occurs + /// * returns `Ok(n)` + /// * We encounter some error OTHER than a line break + /// * returns `Err(ReadToBreakError::Other(error))` + /// + /// If a line break occurs before `min_count` bytes have been read, the break will be ignored and the read will continue + /// + /// **NOTE**: you MUST provide a buffer one byte larger than your largest expected + /// message to reliably detect the framing on one single call to `read_to_break()`. + /// + /// * If you expect a message of 20 bytes + line break, and provide a 20-byte buffer: + /// * The first call to `read_to_break()` will return `Err(ReadToBreakError::MissingBreak(20))` + /// * The next call to `read_to_break()` will immediately return `Ok(0)`, from the "stale" line break + /// * If you expect a message of 20 bytes + line break, and provide a 21-byte buffer: + /// * The first call to `read_to_break()` will return `Ok(20)`. + /// * The next call to `read_to_break()` will work as expected + pub async fn read_to_break_with_count( + &mut self, + buffer: &mut [u8], + min_count: usize, + ) -> Result { + // clear error flags before we drain the fifo. errors that have accumulated + // in the flags will also be present in the fifo. + T::dma_state().rx_errs.store(0, Ordering::Relaxed); + T::regs().uarticr().write(|w| { + w.set_oeic(true); + w.set_beic(true); + w.set_peic(true); + w.set_feic(true); + }); + + // then drain the fifo. we need to read at most 32 bytes. errors that apply + // to fifo bytes will be reported directly. + let mut sbuffer = match { + let limit = buffer.len().min(32); + self.drain_fifo(&mut buffer[0..limit]) + } { + // Drained fifo, still some room left! + Ok(len) if len < buffer.len() => &mut buffer[len..], + // Drained (some/all of the fifo), no room left + Ok(len) => return Err(ReadToBreakError::MissingBreak(len)), + // We got a break WHILE draining the FIFO, return what we did get before the break + Err((len, Error::Break)) => { + if len < min_count && len < buffer.len() { + &mut buffer[len..] + } else { + return Ok(len); + } + } + // Some other error, just return the error + Err((_i, e)) => return Err(ReadToBreakError::Other(e)), + }; + + // start a dma transfer. if errors have happened in the interim some error + // interrupt flags will have been raised, and those will be picked up immediately + // by the interrupt handler. + let mut ch = self.rx_dma.as_mut().unwrap(); + T::regs().uartimsc().write_set(|w| { + w.set_oeim(true); + w.set_beim(true); + w.set_peim(true); + w.set_feim(true); + }); + T::regs().uartdmacr().write_set(|reg| { + reg.set_rxdmae(true); + reg.set_dmaonerr(true); + }); + + loop { + let transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read( + &mut ch, + T::regs().uartdr().as_ptr() as *const _, + sbuffer, + T::RX_DREQ.into(), + ) + }; + + // wait for either the transfer to complete or an error to happen. + let transfer_result = select( + transfer, + poll_fn(|cx| { + T::dma_state().rx_err_waker.register(cx.waker()); + match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { + 0 => Poll::Pending, + e => Poll::Ready(Uartris(e as u32)), + } + }), + ) + .await; + + // Figure out our error state + let errors = match transfer_result { + Either::First(()) => { + // We're here because the DMA finished, BUT if an error occurred on the LAST + // byte, then we may still need to grab the error state! + Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) + } + Either::Second(e) => { + // We're here because we errored, which means this is the error that + // was problematic. + e + } + }; + + if errors.0 == 0 { + // No errors? That means we filled the buffer without a line break. + // For THIS function, that's a problem. + return Err(ReadToBreakError::MissingBreak(buffer.len())); + } else if errors.beris() { + // We got a Line Break! By this point, we've finished/aborted the DMA + // transaction, which means that we need to figure out where it left off + // by looking at the write_addr. + // + // First, we do a sanity check to make sure the write value is within the + // range of DMA we just did. + let sval = buffer.as_ptr() as usize; + let eval = sval + buffer.len(); + + // This is the address where the DMA would write to next + let next_addr = ch.regs().write_addr().read() as usize; + + // If we DON'T end up inside the range, something has gone really wrong. + // Note that it's okay that `eval` is one past the end of the slice, as + // this is where the write pointer will end up at the end of a full + // transfer. + if (next_addr < sval) || (next_addr > eval) { + unreachable!("UART DMA reported invalid `write_addr`"); + } + + if (next_addr - sval) < min_count { + sbuffer = &mut buffer[(next_addr - sval)..]; + continue; + } + + let regs = T::regs(); + let all_full = next_addr == eval; + + // NOTE: This is off label usage of RSR! See the issue below for + // why I am not checking if there is an "extra" FIFO byte, and why + // I am checking RSR directly (it seems to report the status of the LAST + // POPPED value, rather than the NEXT TO POP value like the datasheet + // suggests!) + // + // issue: https://github.com/raspberrypi/pico-feedback/issues/367 + let last_was_break = regs.uartrsr().read().be(); + + return match (all_full, last_was_break) { + (true, true) | (false, _) => { + // We got less than the full amount + a break, or the full amount + // and the last byte was a break. Subtract the break off by adding one to sval. + Ok(next_addr.saturating_sub(1 + sval)) + } + (true, false) => { + // We finished the whole DMA, and the last DMA'd byte was NOT a break + // character. This is an error. + // + // NOTE: we COULD potentially return Ok(buffer.len()) here, since we + // know a line break occured at SOME POINT after the DMA completed. + // + // However, we have no way of knowing if there was extra data BEFORE + // that line break, so instead return an Err to signal to the caller + // that there are "leftovers", and they'll catch the actual line break + // on the next call. + // + // Doing it like this also avoids racyness: now whether you finished + // the full read BEFORE the line break occurred or AFTER the line break + // occurs, you still get `MissingBreak(buffer.len())` instead of sometimes + // getting `Ok(buffer.len())` if you were "late enough" to observe the + // line break. + Err(ReadToBreakError::MissingBreak(buffer.len())) + } + }; + } else if errors.oeris() { + return Err(ReadToBreakError::Other(Error::Overrun)); + } else if errors.peris() { + return Err(ReadToBreakError::Other(Error::Parity)); + } else if errors.feris() { + return Err(ReadToBreakError::Other(Error::Framing)); + } + unreachable!("unrecognized rx error"); + } + } +} + +impl<'d, T: Instance> Uart<'d, T, Blocking> { + /// Create a new UART without hardware flow control + pub fn new_blocking( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

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

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

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

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + _irq: impl Binding>, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

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

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + _irq: impl Binding>, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

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

+ 'd, + mut tx: PeripheralRef<'d, AnyPin>, + mut rx: PeripheralRef<'d, AnyPin>, + mut rts: Option>, + mut cts: Option>, + has_irq: bool, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + 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(has_irq, rx_dma), + } + } + + fn init( + tx: Option>, + rx: Option>, + rts: Option>, + cts: Option>, + config: Config, + ) { + let r = T::regs(); + if let Some(pin) = &tx { + let funcsel = { + let pin_number = ((pin.gpio().as_ptr() as u32) & 0x1FF) / 8; + if (pin_number % 4) == 0 { + 2 + } else { + 11 + } + }; + pin.gpio().ctrl().write(|w| { + w.set_funcsel(funcsel); + w.set_outover(if config.invert_tx { + Outover::INVERT + } else { + Outover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + if let Some(pin) = &rx { + let funcsel = { + let pin_number = ((pin.gpio().as_ptr() as u32) & 0x1FF) / 8; + if ((pin_number - 1) % 4) == 0 { + 2 + } else { + 11 + } + }; + pin.gpio().ctrl().write(|w| { + w.set_funcsel(funcsel); + w.set_inover(if config.invert_rx { + Inover::INVERT + } else { + Inover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + if let Some(pin) = &cts { + pin.gpio().ctrl().write(|w| { + w.set_funcsel(2); + w.set_inover(if config.invert_cts { + Inover::INVERT + } else { + Inover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + if let Some(pin) = &rts { + pin.gpio().ctrl().write(|w| { + w.set_funcsel(2); + w.set_outover(if config.invert_rts { + Outover::INVERT + } else { + Outover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + + Self::set_baudrate_inner(config.baudrate); + + let (pen, eps) = match config.parity { + Parity::ParityNone => (false, false), + Parity::ParityOdd => (true, false), + Parity::ParityEven => (true, true), + }; + + r.uartlcr_h().write(|w| { + w.set_wlen(config.data_bits.bits()); + w.set_stp2(config.stop_bits == StopBits::STOP2); + w.set_pen(pen); + w.set_eps(eps); + w.set_fen(true); + }); + + r.uartifls().write(|w| { + w.set_rxiflsel(0b000); + w.set_txiflsel(0b000); + }); + + r.uartcr().write(|w| { + w.set_uarten(true); + w.set_rxe(true); + w.set_txe(true); + w.set_ctsen(cts.is_some()); + w.set_rtsen(rts.is_some()); + }); + } + + fn lcr_modify(f: impl FnOnce(&mut crate::pac::uart::regs::UartlcrH) -> R) -> R { + let r = T::regs(); + + // Notes from PL011 reference manual: + // + // - Before writing the LCR, if the UART is enabled it needs to be + // disabled and any current TX + RX activity has to be completed + // + // - There is a BUSY flag which waits for the current TX char, but this is + // OR'd with TX FIFO !FULL, so not usable when FIFOs are enabled and + // potentially nonempty + // + // - FIFOs can't be set to disabled whilst a character is in progress + // (else "FIFO integrity is not guaranteed") + // + // Combination of these means there is no general way to halt and poll for + // end of TX character, if FIFOs may be enabled. Either way, there is no + // way to poll for end of RX character. + // + // So, insert a 15 Baud period delay before changing the settings. + // 15 Baud is comfortably higher than start + max data + parity + stop. + // Anything else would require API changes to permit a non-enabled UART + // state after init() where settings can be changed safely. + let clk_base = crate::clocks::clk_peri_freq(); + + let cr = r.uartcr().read(); + if cr.uarten() { + r.uartcr().modify(|w| { + w.set_uarten(false); + w.set_txe(false); + w.set_rxe(false); + }); + + // Note: Maximise precision here. Show working, the compiler will mop this up. + // Create a 16.6 fixed-point fractional division ratio; then scale to 32-bits. + let mut brdiv_ratio = 64 * r.uartibrd().read().0 + r.uartfbrd().read().0; + brdiv_ratio <<= 10; + // 3662 is ~(15 * 244.14) where 244.14 is 16e6 / 2^16 + let scaled_freq = clk_base / 3662; + let wait_time_us = brdiv_ratio / scaled_freq; + embedded_hal_1::delay::DelayNs::delay_us(&mut Delay, wait_time_us); + } + + let res = r.uartlcr_h().modify(f); + + r.uartcr().write_value(cr); + + res + } + + /// sets baudrate on runtime + pub fn set_baudrate(&mut self, baudrate: u32) { + Self::set_baudrate_inner(baudrate); + } + + fn set_baudrate_inner(baudrate: u32) { + let r = T::regs(); + + let clk_base = crate::clocks::clk_peri_freq(); + + let baud_rate_div = (8 * clk_base) / baudrate; + let mut baud_ibrd = baud_rate_div >> 7; + let mut baud_fbrd = ((baud_rate_div & 0x7f) + 1) / 2; + + if baud_ibrd == 0 { + baud_ibrd = 1; + baud_fbrd = 0; + } else if baud_ibrd >= 65535 { + baud_ibrd = 65535; + baud_fbrd = 0; + } + + // Load PL011's baud divisor registers + r.uartibrd().write_value(pac::uart::regs::Uartibrd(baud_ibrd)); + r.uartfbrd().write_value(pac::uart::regs::Uartfbrd(baud_fbrd)); + + Self::lcr_modify(|_| {}); + } +} + +impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { + /// Transmit the provided buffer blocking execution until done. + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.blocking_write(buffer) + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + self.tx.blocking_flush() + } + + /// Read from UART RX blocking execution until done. + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.blocking_read(buffer) + } + + /// Check if UART is busy transmitting. + pub fn busy(&self) -> bool { + self.tx.busy() + } + + /// Wait until TX is empty and send break condition. + pub async fn send_break(&mut self, bits: u32) { + self.tx.send_break(bits).await + } + + /// Split the Uart into a transmitter and receiver, which is particularly + /// useful when having two tasks correlating to transmitting and receiving. + pub fn split(self) -> (UartTx<'d, T, M>, UartRx<'d, T, M>) { + (self.tx, self.rx) + } + + /// Split the Uart into a transmitter and receiver by mutable reference, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split_ref(&mut self) -> (&mut UartTx<'d, T, M>, &mut UartRx<'d, T, M>) { + (&mut self.tx, &mut self.rx) + } +} + +impl<'d, T: Instance> Uart<'d, T, Async> { + /// Write to UART TX from the provided buffer. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.write(buffer).await + } + + /// Read from UART RX into the provided buffer. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.read(buffer).await + } + + /// Read until the buffer is full or a line break occurs. + /// + /// See [`UartRx::read_to_break()`] for more details + pub async fn read_to_break<'a>(&mut self, buf: &'a mut [u8]) -> Result { + self.rx.read_to_break(buf).await + } + + /// Read until the buffer is full or a line break occurs after at least `min_count` bytes have been read. + /// + /// See [`UartRx::read_to_break_with_count()`] for more details + pub async fn read_to_break_with_count<'a>( + &mut self, + buf: &'a mut [u8], + min_count: usize, + ) -> Result { + self.rx.read_to_break_with_count(buf, min_count).await + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, T, M> { + type Error = Error; + fn read(&mut self) -> Result> { + let r = T::regs(); + if r.uartfr().read().rxfe() { + return Err(nb::Error::WouldBlock); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + Err(nb::Error::Other(Error::Overrun)) + } else if dr.be() { + Err(nb::Error::Other(Error::Break)) + } else if dr.pe() { + Err(nb::Error::Other(Error::Parity)) + } else if dr.fe() { + Err(nb::Error::Other(Error::Framing)) + } else { + Ok(dr.data()) + } + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, T, M> { + type Error = Error; + + fn write(&mut self, word: u8) -> Result<(), nb::Error> { + let r = T::regs(); + if r.uartfr().read().txff() { + return Err(nb::Error::WouldBlock); + } + + r.uartdr().write(|w| w.set_data(word)); + Ok(()) + } + + fn flush(&mut self) -> Result<(), nb::Error> { + let r = T::regs(); + if !r.uartfr().read().txfe() { + return Err(nb::Error::WouldBlock); + } + Ok(()) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, T, M> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for Uart<'d, T, M> { + type Error = Error; + + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for Uart<'d, T, M> { + type Error = Error; + + fn write(&mut self, word: u8) -> Result<(), nb::Error> { + embedded_hal_02::serial::Write::write(&mut self.tx, word) + } + + fn flush(&mut self) -> Result<(), nb::Error> { + embedded_hal_02::serial::Write::flush(&mut self.tx) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, T, M> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { + match *self { + Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, + Self::Break => embedded_hal_nb::serial::ErrorKind::Other, + Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, + Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, + } + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, T, M> { + fn read(&mut self) -> nb::Result { + let r = T::regs(); + if r.uartfr().read().rxfe() { + return Err(nb::Error::WouldBlock); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + Err(nb::Error::Other(Error::Overrun)) + } else if dr.be() { + Err(nb::Error::Other(Error::Break)) + } else if dr.pe() { + Err(nb::Error::Other(Error::Parity)) + } else if dr.fe() { + Err(nb::Error::Other(Error::Framing)) + } else { + Ok(dr.data()) + } + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, T, M> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +impl<'d, T: Instance> embedded_io::ErrorType for UartTx<'d, T, Blocking> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io::Write for UartTx<'d, T, Blocking> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf).map(|_| buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, T, M> { + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, T, M> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +impl<'d, T: Instance> embedded_io::ErrorType for Uart<'d, T, Blocking> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io::Write for Uart<'d, T, Blocking> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf).map(|_| buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +trait SealedMode {} + +trait SealedInstance { + const TX_DREQ: pac::dma::vals::TreqSel; + const RX_DREQ: pac::dma::vals::TreqSel; + + fn regs() -> pac::uart::Uart; + + fn buffered_state() -> &'static buffered::State; + + fn dma_state() -> &'static DmaState; +} + +/// UART mode. +#[allow(private_bounds)] +pub trait Mode: SealedMode {} + +macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; +} + +/// Blocking mode. +pub struct Blocking; +/// Async mode. +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); + +/// UART instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance { + /// Interrupt for this instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_instance { + ($inst:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { + impl SealedInstance for peripherals::$inst { + const TX_DREQ: pac::dma::vals::TreqSel = $tx_dreq; + const RX_DREQ: pac::dma::vals::TreqSel = $rx_dreq; + + fn regs() -> pac::uart::Uart { + pac::$inst + } + + fn buffered_state() -> &'static buffered::State { + static STATE: buffered::State = buffered::State::new(); + &STATE + } + + fn dma_state() -> &'static DmaState { + static STATE: DmaState = DmaState { + rx_err_waker: AtomicWaker::new(), + rx_errs: AtomicU16::new(0), + }; + &STATE + } + } + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +impl_instance!( + UART0, + UART0_IRQ, + pac::dma::vals::TreqSel::UART0_TX, + pac::dma::vals::TreqSel::UART0_RX +); +impl_instance!( + UART1, + UART1_IRQ, + pac::dma::vals::TreqSel::UART1_TX, + pac::dma::vals::TreqSel::UART1_RX +); + +/// Trait for TX pins. +pub trait TxPin: crate::gpio::Pin {} +/// Trait for RX pins. +pub trait RxPin: crate::gpio::Pin {} +/// Trait for Clear To Send (CTS) pins. +pub trait CtsPin: crate::gpio::Pin {} +/// Trait for Request To Send (RTS) pins. +pub trait RtsPin: crate::gpio::Pin {} + +macro_rules! impl_pin { + ($pin:ident, $instance:ident, $function:ident) => { + impl $function for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, UART0, TxPin); +impl_pin!(PIN_1, UART0, RxPin); +impl_pin!(PIN_2, UART0, CtsPin); +impl_pin!(PIN_3, UART0, RtsPin); +impl_pin!(PIN_4, UART1, TxPin); +impl_pin!(PIN_5, UART1, RxPin); +impl_pin!(PIN_6, UART1, CtsPin); +impl_pin!(PIN_7, UART1, RtsPin); +impl_pin!(PIN_8, UART1, TxPin); +impl_pin!(PIN_9, UART1, RxPin); +impl_pin!(PIN_10, UART1, CtsPin); +impl_pin!(PIN_11, UART1, RtsPin); +impl_pin!(PIN_12, UART0, TxPin); +impl_pin!(PIN_13, UART0, RxPin); +impl_pin!(PIN_14, UART0, CtsPin); +impl_pin!(PIN_15, UART0, RtsPin); +impl_pin!(PIN_16, UART0, TxPin); +impl_pin!(PIN_17, UART0, RxPin); +impl_pin!(PIN_18, UART0, CtsPin); +impl_pin!(PIN_19, UART0, RtsPin); +impl_pin!(PIN_20, UART1, TxPin); +impl_pin!(PIN_21, UART1, RxPin); +impl_pin!(PIN_22, UART1, CtsPin); +impl_pin!(PIN_23, UART1, RtsPin); +impl_pin!(PIN_24, UART1, TxPin); +impl_pin!(PIN_25, UART1, RxPin); +impl_pin!(PIN_26, UART1, CtsPin); +impl_pin!(PIN_27, UART1, RtsPin); +impl_pin!(PIN_28, UART0, TxPin); +impl_pin!(PIN_29, UART0, RxPin); + +// Additional functions added by all 2350s +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_2, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_3, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_6, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_7, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_10, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_11, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_14, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_15, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_18, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_19, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_22, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_23, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_26, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_27, UART1, RxPin); + +// Additional pins added by larger 2350 packages. +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, UART0, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, UART0, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, UART1, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, UART1, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, UART1, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, UART1, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, UART0, RtsPin); + +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, UART0, RxPin); diff --git a/embassy/embassy-rp/src/usb.rs b/embassy/embassy-rp/src/usb.rs new file mode 100644 index 0000000..26cb90d --- /dev/null +++ b/embassy/embassy-rp/src/usb.rs @@ -0,0 +1,827 @@ +//! USB driver. +use core::future::poll_fn; +use core::marker::PhantomData; +use core::slice; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb_driver as driver; +use embassy_usb_driver::{ + Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointInfo, EndpointType, Event, Unsupported, +}; + +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; + +trait SealedInstance { + fn regs() -> crate::pac::usb::Usb; + fn dpram() -> crate::pac::usb_dpram::UsbDpram; +} + +/// USB peripheral instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + 'static { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +impl crate::usb::SealedInstance for peripherals::USB { + fn regs() -> pac::usb::Usb { + pac::USB + } + fn dpram() -> crate::pac::usb_dpram::UsbDpram { + pac::USB_DPRAM + } +} + +impl crate::usb::Instance for peripherals::USB { + type Interrupt = crate::interrupt::typelevel::USBCTRL_IRQ; +} + +const EP_COUNT: usize = 16; +const EP_MEMORY_SIZE: usize = 4096; +const EP_MEMORY: *mut u8 = pac::USB_DPRAM.as_ptr() as *mut u8; + +static BUS_WAKER: AtomicWaker = AtomicWaker::new(); +static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT]; +static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT]; + +struct EndpointBuffer { + addr: u16, + len: u16, + _phantom: PhantomData, +} + +impl EndpointBuffer { + const fn new(addr: u16, len: u16) -> Self { + Self { + addr, + len, + _phantom: PhantomData, + } + } + + fn read(&mut self, buf: &mut [u8]) { + assert!(buf.len() <= self.len as usize); + compiler_fence(Ordering::SeqCst); + let mem = unsafe { slice::from_raw_parts(EP_MEMORY.add(self.addr as _), buf.len()) }; + buf.copy_from_slice(mem); + compiler_fence(Ordering::SeqCst); + } + + fn write(&mut self, buf: &[u8]) { + assert!(buf.len() <= self.len as usize); + compiler_fence(Ordering::SeqCst); + let mem = unsafe { slice::from_raw_parts_mut(EP_MEMORY.add(self.addr as _), buf.len()) }; + mem.copy_from_slice(buf); + compiler_fence(Ordering::SeqCst); + } +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct EndpointData { + ep_type: EndpointType, // only valid if used + max_packet_size: u16, + used: bool, +} + +impl EndpointData { + const fn new() -> Self { + Self { + ep_type: EndpointType::Bulk, + max_packet_size: 0, + used: false, + } + } +} + +/// RP2040 USB driver handle. +pub struct Driver<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + ep_in: [EndpointData; EP_COUNT], + ep_out: [EndpointData; EP_COUNT], + ep_mem_free: u16, // first free address in EP mem, in bytes. +} + +impl<'d, T: Instance> Driver<'d, T> { + /// Create a new USB driver. + pub fn new(_usb: impl Peripheral

+ 'd, _irq: impl Binding>) -> Self { + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let regs = T::regs(); + unsafe { + // zero fill regs + let p = regs.as_ptr() as *mut u32; + for i in 0..0x9c / 4 { + p.add(i).write_volatile(0) + } + + // zero fill epmem + let p = EP_MEMORY as *mut u32; + for i in 0..0x100 / 4 { + p.add(i).write_volatile(0) + } + } + + regs.usb_muxing().write(|w| { + w.set_to_phy(true); + w.set_softcon(true); + }); + regs.usb_pwr().write(|w| { + w.set_vbus_detect(true); + w.set_vbus_detect_override_en(true); + }); + regs.main_ctrl().write(|w| { + w.set_controller_en(true); + }); + + // Initialize the bus so that it signals that power is available + BUS_WAKER.wake(); + + Self { + phantom: PhantomData, + ep_in: [EndpointData::new(); EP_COUNT], + ep_out: [EndpointData::new(); EP_COUNT], + ep_mem_free: 0x180, // data buffer region + } + } + + fn alloc_endpoint( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result, driver::EndpointAllocError> { + trace!( + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", + ep_type, + max_packet_size, + interval_ms, + D::dir() + ); + + let alloc = match D::dir() { + Direction::Out => &mut self.ep_out, + Direction::In => &mut self.ep_in, + }; + + let index = alloc.iter_mut().enumerate().find(|(i, ep)| { + if *i == 0 { + return false; // reserved for control pipe + } + !ep.used + }); + + let (index, ep) = index.ok_or(EndpointAllocError)?; + assert!(!ep.used); + + // 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 = (max_packet_size + 63) / 64 * 64; + + let addr = self.ep_mem_free; + if addr + len > EP_MEMORY_SIZE as u16 { + warn!("Endpoint memory full"); + return Err(EndpointAllocError); + } + self.ep_mem_free += len; + + let buf = EndpointBuffer { + addr, + len, + _phantom: PhantomData, + }; + + trace!(" index={} addr={} len={}", index, buf.addr, buf.len); + + ep.ep_type = ep_type; + ep.used = true; + ep.max_packet_size = max_packet_size; + + let ep_type_reg = match ep_type { + EndpointType::Bulk => pac::usb_dpram::vals::EpControlEndpointType::BULK, + EndpointType::Control => pac::usb_dpram::vals::EpControlEndpointType::CONTROL, + EndpointType::Interrupt => pac::usb_dpram::vals::EpControlEndpointType::INTERRUPT, + EndpointType::Isochronous => pac::usb_dpram::vals::EpControlEndpointType::ISOCHRONOUS, + }; + + match D::dir() { + Direction::Out => T::dpram().ep_out_control(index - 1).write(|w| { + w.set_enable(false); + w.set_buffer_address(addr); + w.set_interrupt_per_buff(true); + w.set_endpoint_type(ep_type_reg); + }), + Direction::In => T::dpram().ep_in_control(index - 1).write(|w| { + w.set_enable(false); + w.set_buffer_address(addr); + w.set_interrupt_per_buff(true); + w.set_endpoint_type(ep_type_reg); + }), + } + + Ok(Endpoint { + _phantom: PhantomData, + info: EndpointInfo { + addr: EndpointAddress::from_parts(index, D::dir()), + ep_type, + max_packet_size, + interval_ms, + }, + buf, + }) + } +} + +/// USB interrupt handler. +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + //let x = regs.istr().read().0; + //trace!("USB IRQ: {:08x}", x); + + let ints = regs.ints().read(); + + if ints.bus_reset() { + regs.inte().write_clear(|w| w.set_bus_reset(true)); + BUS_WAKER.wake(); + } + if ints.dev_resume_from_host() { + regs.inte().write_clear(|w| w.set_dev_resume_from_host(true)); + BUS_WAKER.wake(); + } + if ints.dev_suspend() { + regs.inte().write_clear(|w| w.set_dev_suspend(true)); + BUS_WAKER.wake(); + } + if ints.setup_req() { + regs.inte().write_clear(|w| w.set_setup_req(true)); + EP_OUT_WAKERS[0].wake(); + } + + if ints.buff_status() { + let s = regs.buff_status().read(); + regs.buff_status().write_value(s); + + for i in 0..EP_COUNT { + if s.ep_in(i) { + EP_IN_WAKERS[i].wake(); + } + if s.ep_out(i) { + EP_OUT_WAKERS[i].wake(); + } + } + } + } +} + +impl<'d, T: Instance> 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(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + let regs = T::regs(); + regs.inte().write(|w| { + w.set_bus_reset(true); + w.set_buff_status(true); + w.set_dev_resume_from_host(true); + w.set_dev_suspend(true); + w.set_setup_req(true); + }); + regs.int_ep_ctrl().write(|w| { + w.set_int_ep_active(0xFFFE); // all EPs + }); + regs.sie_ctrl().write(|w| { + w.set_ep0_int_1buf(true); + w.set_pullup_en(true); + }); + + trace!("enabled"); + + ( + Bus { + phantom: PhantomData, + inited: false, + ep_out: self.ep_out, + }, + ControlPipe { + _phantom: PhantomData, + max_packet_size: control_max_packet_size, + }, + ) + } +} + +/// Type representing the RP USB bus. +pub struct Bus<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + ep_out: [EndpointData; EP_COUNT], + inited: bool, +} + +impl<'d, T: Instance> driver::Bus for Bus<'d, T> { + async fn poll(&mut self) -> Event { + poll_fn(move |cx| { + BUS_WAKER.register(cx.waker()); + + // TODO: implement VBUS detection. + if !self.inited { + self.inited = true; + return Poll::Ready(Event::PowerDetected); + } + + let regs = T::regs(); + let siestatus = regs.sie_status().read(); + let intrstatus = regs.intr().read(); + + if siestatus.resume() || intrstatus.dev_resume_from_host() { + regs.sie_status().write(|w| w.set_resume(true)); + return Poll::Ready(Event::Resume); + } + + if siestatus.bus_reset() { + regs.sie_status().write(|w| { + w.set_bus_reset(true); + w.set_setup_rec(true); + }); + regs.buff_status().write(|w| w.0 = 0xFFFF_FFFF); + regs.addr_endp().write(|w| w.set_address(0)); + + for i in 1..EP_COUNT { + T::dpram().ep_in_control(i - 1).modify(|w| w.set_enable(false)); + T::dpram().ep_out_control(i - 1).modify(|w| w.set_enable(false)); + } + + for w in &EP_IN_WAKERS { + w.wake() + } + for w in &EP_OUT_WAKERS { + w.wake() + } + return Poll::Ready(Event::Reset); + } + + if siestatus.suspended() && intrstatus.dev_suspend() { + regs.sie_status().write(|w| w.set_suspended(true)); + return Poll::Ready(Event::Suspend); + } + + // no pending event. Reenable all irqs. + regs.inte().write_set(|w| { + w.set_bus_reset(true); + w.set_dev_resume_from_host(true); + w.set_dev_suspend(true); + }); + Poll::Pending + }) + .await + } + + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { + let n = ep_addr.index(); + + if n == 0 { + T::regs().ep_stall_arm().modify(|w| { + if ep_addr.is_in() { + w.set_ep0_in(stalled); + } else { + w.set_ep0_out(stalled); + } + }); + } + + let ctrl = if ep_addr.is_in() { + T::dpram().ep_in_buffer_control(n) + } else { + T::dpram().ep_out_buffer_control(n) + }; + + ctrl.modify(|w| w.set_stall(stalled)); + + let wakers = if ep_addr.is_in() { &EP_IN_WAKERS } else { &EP_OUT_WAKERS }; + wakers[n].wake(); + } + + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + let n = ep_addr.index(); + + let ctrl = if ep_addr.is_in() { + T::dpram().ep_in_buffer_control(n) + } else { + T::dpram().ep_out_buffer_control(n) + }; + + ctrl.read().stall() + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + trace!("set_enabled {:?} {}", ep_addr, enabled); + if ep_addr.index() == 0 { + return; + } + + let n = ep_addr.index(); + match ep_addr.direction() { + Direction::In => { + T::dpram().ep_in_control(n - 1).modify(|w| w.set_enable(enabled)); + T::dpram().ep_in_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, true); // first packet is DATA0, but PID is flipped before + }); + EP_IN_WAKERS[n].wake(); + } + Direction::Out => { + T::dpram().ep_out_control(n - 1).modify(|w| w.set_enable(enabled)); + + T::dpram().ep_out_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, false); + w.set_length(0, self.ep_out[n].max_packet_size); + }); + cortex_m::asm::delay(12); + T::dpram().ep_out_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, false); + w.set_length(0, self.ep_out[n].max_packet_size); + w.set_available(0, true); + }); + EP_OUT_WAKERS[n].wake(); + } + } + } + + async fn enable(&mut self) {} + + async fn disable(&mut self) {} + + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } +} + +trait Dir { + fn dir() -> Direction; +} + +/// Type for In direction. +pub enum In {} +impl Dir for In { + fn dir() -> Direction { + Direction::In + } +} + +/// Type for Out direction. +pub enum Out {} +impl Dir for Out { + fn dir() -> Direction { + Direction::Out + } +} + +/// Endpoint for RP USB driver. +pub struct Endpoint<'d, T: Instance, D> { + _phantom: PhantomData<(&'d mut T, D)>, + info: EndpointInfo, + buf: EndpointBuffer, +} + +impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + 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 = 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"); + } +} + +impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, Out> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + 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 = 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> { + 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| { + 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); + } + self.buf.read(&mut buf[..rx_len]); + + trace!("READ OK, rx_len = {}", rx_len); + + 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> { + 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| { + 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); + + 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(()) + } +} + +/// Control pipe for RP USB driver. +pub struct ControlPipe<'d, T: Instance> { + _phantom: PhantomData<&'d mut T>, + max_packet_size: u16, +} + +impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { + fn max_packet_size(&self) -> usize { + 64 + } + + async fn setup(&mut self) -> [u8; 8] { + loop { + trace!("SETUP read waiting"); + let regs = T::regs(); + regs.inte().write_set(|w| w.set_setup_req(true)); + + poll_fn(|cx| { + 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(); + 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 { + 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| { + 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); + + 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| { + 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. + 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); + 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 bufcontrol.read().available(0) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + } + + async fn reject(&mut self) { + trace!("control: reject"); + + let regs = T::regs(); + 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); + regs.addr_endp().write(|w| w.set_address(addr)) + } +} diff --git a/embassy/embassy-rp/src/watchdog.rs b/embassy/embassy-rp/src/watchdog.rs new file mode 100644 index 0000000..edd48e0 --- /dev/null +++ b/embassy/embassy-rp/src/watchdog.rs @@ -0,0 +1,143 @@ +//! 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. + #[cfg(feature = "rp2040")] + pub fn enable_tick_generation(&mut self, cycles: u8) { + 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) { + let watchdog = pac::WATCHDOG; + watchdog.ctrl().modify(|w| { + w.set_pause_dbg0(pause); + w.set_pause_dbg1(pause); + w.set_pause_jtag(pause); + }) + } + + fn load_counter(&self, counter: u32) { + let watchdog = pac::WATCHDOG; + watchdog.load().write_value(pac::watchdog::regs::Load(counter)); + } + + fn enable(&self, bit: bool) { + let watchdog = pac::WATCHDOG; + watchdog.ctrl().modify(|w| w.set_enable(bit)) + } + + // Configure which hardware will be reset by the watchdog + // (everything except ROSC, XOSC) + 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(); + if delay_us > (MAX_PERIOD / 2) as u64 { + panic!("Period cannot exceed {} microseconds", MAX_PERIOD / 2); + } + let delay_us = delay_us as u32; + + // Due to a logic error, the watchdog decrements by 2 and + // the load value must be compensated; see RP2040-E1 + self.load_value = delay_us * 2; + + self.enable(false); + self.configure_wdog_reset_triggers(); + self.load_counter(self.load_value); + self.enable(true); + } + + /// Trigger a system reset + pub fn trigger_reset(&mut self) { + self.configure_wdog_reset_triggers(); + self.pause_on_debug(false); + self.enable(true); + let watchdog = pac::WATCHDOG; + watchdog.ctrl().write(|w| { + w.set_trigger(true); + }) + } + + /// Store data in scratch register + pub fn set_scratch(&mut self, index: usize, value: u32) { + let watchdog = pac::WATCHDOG; + match index { + 0 => watchdog.scratch0().write(|w| *w = value), + 1 => watchdog.scratch1().write(|w| *w = value), + 2 => watchdog.scratch2().write(|w| *w = value), + 3 => watchdog.scratch3().write(|w| *w = value), + 4 => watchdog.scratch4().write(|w| *w = value), + 5 => watchdog.scratch5().write(|w| *w = value), + 6 => watchdog.scratch6().write(|w| *w = value), + 7 => watchdog.scratch7().write(|w| *w = value), + _ => panic!("Invalid watchdog scratch index"), + } + } + + /// Read data from scratch register + pub fn get_scratch(&mut self, index: usize) -> u32 { + let watchdog = pac::WATCHDOG; + match index { + 0 => watchdog.scratch0().read(), + 1 => watchdog.scratch1().read(), + 2 => watchdog.scratch2().read(), + 3 => watchdog.scratch3().read(), + 4 => watchdog.scratch4().read(), + 5 => watchdog.scratch5().read(), + 6 => watchdog.scratch6().read(), + 7 => watchdog.scratch7().read(), + _ => panic!("Invalid watchdog scratch index"), + } + } +} diff --git a/embassy/embassy-stm32-wpan/Cargo.toml b/embassy/embassy-stm32-wpan/Cargo.toml new file mode 100644 index 0000000..7dd1bc6 --- /dev/null +++ b/embassy/embassy-stm32-wpan/Cargo.toml @@ -0,0 +1,66 @@ +[package] +name = "embassy-stm32-wpan" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Async STM32 WPAN stack for embedded devices in Rust." +keywords = ["embedded", "async", "stm32", "ble", "wpan"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-stm32-wpan" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-stm32-wpan-v$VERSION/embassy-stm32-wpan/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-stm32-wpan/src/" +target = "thumbv7em-none-eabihf" +features = ["stm32wb55rg"] + +[package.metadata.docs.rs] +features = ["stm32wb55rg"] + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" } +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal" } +embassy-embedded-hal = { version = "0.2.0", path = "../embassy-embedded-hal" } +embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver", optional=true } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.17", optional = true } + +cortex-m = "0.7.6" +heapless = "0.8" +aligned = "0.4.1" + +bit_field = "0.10.2" +stm32-device-signature = { version = "0.3.3", features = ["stm32wb5x"] } +stm32wb-hci = { version = "0.17.0", optional = true } +futures-util = { version = "0.3.30", default-features = false } +bitflags = { version = "2.3.3", optional = true } + +[features] +defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-internal/defmt", "stm32wb-hci?/defmt"] + +ble = ["dep:stm32wb-hci"] +mac = ["dep:bitflags", "dep:embassy-net-driver" ] + +extended = [] + +stm32wb10cc = [ "embassy-stm32/stm32wb10cc" ] +stm32wb15cc = [ "embassy-stm32/stm32wb15cc" ] +stm32wb30ce = [ "embassy-stm32/stm32wb30ce" ] +stm32wb35cc = [ "embassy-stm32/stm32wb35cc" ] +stm32wb35ce = [ "embassy-stm32/stm32wb35ce" ] +stm32wb50cg = [ "embassy-stm32/stm32wb50cg" ] +stm32wb55cc = [ "embassy-stm32/stm32wb55cc" ] +stm32wb55ce = [ "embassy-stm32/stm32wb55ce" ] +stm32wb55cg = [ "embassy-stm32/stm32wb55cg" ] +stm32wb55rc = [ "embassy-stm32/stm32wb55rc" ] +stm32wb55re = [ "embassy-stm32/stm32wb55re" ] +stm32wb55rg = [ "embassy-stm32/stm32wb55rg" ] +stm32wb55vc = [ "embassy-stm32/stm32wb55vc" ] +stm32wb55ve = [ "embassy-stm32/stm32wb55ve" ] +stm32wb55vg = [ "embassy-stm32/stm32wb55vg" ] +stm32wb55vy = [ "embassy-stm32/stm32wb55vy" ] diff --git a/embassy/embassy-stm32-wpan/README.md b/embassy/embassy-stm32-wpan/README.md new file mode 100644 index 0000000..b1a2cec --- /dev/null +++ b/embassy/embassy-stm32-wpan/README.md @@ -0,0 +1,13 @@ +# embassy-stm32-wpan + +Async WPAN (short range wireless) on STM32WB families. + +## Features + +- Rust interface to the WPAN stack running on the STM32WB co-processor . +- Controller trait implementation for the [stm32wb-hci](https://crates.io/crates/stm32wb-hci) crate. +- Embassy-net driver implementation for 802.15.4 MAC. + +## Examples + +See the [stm32wb examples](https://github.com/embassy-rs/embassy/tree/main/examples/stm32wb). diff --git a/embassy/embassy-stm32-wpan/build.rs b/embassy/embassy-stm32-wpan/build.rs new file mode 100644 index 0000000..7ab458b --- /dev/null +++ b/embassy/embassy-stm32-wpan/build.rs @@ -0,0 +1,58 @@ +use std::path::PathBuf; +use std::{env, fs}; + +fn main() { + match env::vars() + .map(|(a, _)| a) + .filter(|x| x.starts_with("CARGO_FEATURE_STM32")) + .get_one() + { + Ok(_) => {} + Err(GetOneError::None) => panic!("No stm32xx Cargo feature enabled"), + Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"), + } + + let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + + // ======== + // stm32wb tl_mbox link sections + + let out_file = out_dir.join("tl_mbox.x").to_string_lossy().to_string(); + let in_file; + if env::var_os("CARGO_FEATURE_EXTENDED").is_some() { + if env::vars() + .map(|(a, _)| a) + .any(|x| x.starts_with("CARGO_FEATURE_STM32WB1")) + { + in_file = "tl_mbox_extended_wb1.x.in"; + } else { + in_file = "tl_mbox_extended_wbx5.x.in"; + } + } else { + in_file = "tl_mbox.x.in"; + } + fs::write(out_file, fs::read_to_string(in_file).unwrap()).unwrap(); + println!("cargo:rustc-link-search={}", out_dir.display()); + println!("cargo:rerun-if-changed={}", in_file); +} + +enum GetOneError { + None, + Multiple, +} + +trait IteratorExt: Iterator { + fn get_one(self) -> Result; +} + +impl IteratorExt for T { + fn get_one(mut self) -> Result { + match self.next() { + None => Err(GetOneError::None), + Some(res) => match self.next() { + Some(_) => Err(GetOneError::Multiple), + None => Ok(res), + }, + } + } +} diff --git a/embassy/embassy-stm32-wpan/src/channels.rs b/embassy/embassy-stm32-wpan/src/channels.rs new file mode 100644 index 0000000..9a2be1c --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/channels.rs @@ -0,0 +1,96 @@ +//! CPU1 CPU2 +//! | (SYSTEM) | +//! |----HW_IPCC_SYSTEM_CMD_RSP_CHANNEL-------------->| +//! | | +//! |<---HW_IPCC_SYSTEM_EVENT_CHANNEL-----------------| +//! | | +//! | (ZIGBEE) | +//! |----HW_IPCC_ZIGBEE_CMD_APPLI_CHANNEL------------>| +//! | | +//! |----HW_IPCC_ZIGBEE_CMD_CLI_CHANNEL-------------->| +//! | | +//! |<---HW_IPCC_ZIGBEE_APPLI_NOTIF_ACK_CHANNEL-------| +//! | | +//! |<---HW_IPCC_ZIGBEE_CLI_NOTIF_ACK_CHANNEL---------| +//! | | +//! | (THREAD) | +//! |----HW_IPCC_THREAD_OT_CMD_RSP_CHANNEL----------->| +//! | | +//! |----HW_IPCC_THREAD_CLI_CMD_CHANNEL-------------->| +//! | | +//! |<---HW_IPCC_THREAD_NOTIFICATION_ACK_CHANNEL------| +//! | | +//! |<---HW_IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL--| +//! | | +//! | (BLE) | +//! |----HW_IPCC_BLE_CMD_CHANNEL--------------------->| +//! | | +//! |----HW_IPCC_HCI_ACL_DATA_CHANNEL---------------->| +//! | | +//! |<---HW_IPCC_BLE_EVENT_CHANNEL--------------------| +//! | | +//! | (BLE LLD) | +//! |----HW_IPCC_BLE_LLD_CMD_CHANNEL----------------->| +//! | | +//! |<---HW_IPCC_BLE_LLD_RSP_CHANNEL------------------| +//! | | +//! |<---HW_IPCC_BLE_LLD_M0_CMD_CHANNEL---------------| +//! | | +//! | (MAC) | +//! |----HW_IPCC_MAC_802_15_4_CMD_RSP_CHANNEL-------->| +//! | | +//! |<---HW_IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL| +//! | | +//! | (BUFFER) | +//! |----HW_IPCC_MM_RELEASE_BUFFER_CHANNE------------>| +//! | | +//! | (TRACE) | +//! |<----HW_IPCC_TRACES_CHANNEL----------------------| +//! | | +//! + +pub mod cpu1 { + use embassy_stm32::ipcc::IpccChannel; + + pub const IPCC_BLE_CMD_CHANNEL: IpccChannel = IpccChannel::Channel1; + pub const IPCC_SYSTEM_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel2; + pub const IPCC_THREAD_OT_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_ZIGBEE_CMD_APPLI_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_MAC_802_15_4_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_MM_RELEASE_BUFFER_CHANNEL: IpccChannel = IpccChannel::Channel4; + pub const IPCC_THREAD_CLI_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_LLDTESTS_CLI_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLD_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; + pub const IPCC_HCI_ACL_DATA_CHANNEL: IpccChannel = IpccChannel::Channel6; +} + +pub mod cpu2 { + use embassy_stm32::ipcc::IpccChannel; + + pub const IPCC_BLE_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel1; + pub const IPCC_SYSTEM_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel2; + pub const IPCC_THREAD_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_ZIGBEE_APPLI_NOTIF_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_LDDTESTS_M0_CMD_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLDÇM0_CMD_CHANNEL: IpccChannel = IpccChannel::Channel3; + pub const IPCC_TRACES_CHANNEL: IpccChannel = IpccChannel::Channel4; + pub const IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_LLDTESTS_CLI_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLD_CLI_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_ZIGBEE_M0_REQUEST_CHANNEL: IpccChannel = IpccChannel::Channel5; +} diff --git a/embassy/embassy-stm32-wpan/src/cmd.rs b/embassy/embassy-stm32-wpan/src/cmd.rs new file mode 100644 index 0000000..9283573 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/cmd.rs @@ -0,0 +1,104 @@ +use core::ptr; + +use crate::consts::TlPacketType; +use crate::PacketHeader; + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct Cmd { + pub cmd_code: u16, + pub payload_len: u8, + pub payload: [u8; 255], +} + +impl Default for Cmd { + fn default() -> Self { + Self { + cmd_code: 0, + payload_len: 0, + payload: [0u8; 255], + } + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct CmdSerial { + pub ty: u8, + pub cmd: Cmd, +} + +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct CmdSerialStub { + pub ty: u8, + pub cmd_code: u16, + pub payload_len: u8, +} + +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct CmdPacket { + pub header: PacketHeader, + pub cmdserial: CmdSerial, +} + +impl CmdPacket { + pub unsafe fn write_into(cmd_buf: *mut CmdPacket, packet_type: TlPacketType, cmd_code: u16, payload: &[u8]) { + let p_cmd_serial = &mut (*cmd_buf).cmdserial as *mut _ as *mut CmdSerialStub; + let p_payload = &mut (*cmd_buf).cmdserial.cmd.payload as *mut _; + + ptr::write_volatile( + p_cmd_serial, + CmdSerialStub { + ty: packet_type as u8, + cmd_code, + payload_len: payload.len() as u8, + }, + ); + + ptr::copy_nonoverlapping(payload as *const _ as *const u8, p_payload, payload.len()); + } +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct AclDataSerial { + pub ty: u8, + pub handle: u16, + pub length: u16, + pub acl_data: [u8; 1], +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct AclDataSerialStub { + pub ty: u8, + pub handle: u16, + pub length: u16, +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct AclDataPacket { + pub header: PacketHeader, + pub acl_data_serial: AclDataSerial, +} + +impl AclDataPacket { + pub unsafe fn write_into(cmd_buf: *mut AclDataPacket, packet_type: TlPacketType, handle: u16, payload: &[u8]) { + let p_cmd_serial = &mut (*cmd_buf).acl_data_serial as *mut _ as *mut AclDataSerialStub; + let p_payload = &mut (*cmd_buf).acl_data_serial.acl_data as *mut _; + + ptr::write_volatile( + p_cmd_serial, + AclDataSerialStub { + ty: packet_type as u8, + handle: handle, + length: payload.len() as u16, + }, + ); + + ptr::copy_nonoverlapping(payload as *const _ as *const u8, p_payload, payload.len()); + } +} diff --git a/embassy/embassy-stm32-wpan/src/consts.rs b/embassy/embassy-stm32-wpan/src/consts.rs new file mode 100644 index 0000000..6aaef1d --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/consts.rs @@ -0,0 +1,94 @@ +use crate::evt::CsEvt; +use crate::PacketHeader; + +#[derive(Debug)] +#[repr(C)] +pub enum TlPacketType { + MacCmd = 0x00, + + BleCmd = 0x01, + AclData = 0x02, + BleEvt = 0x04, + + OtCmd = 0x08, + OtRsp = 0x09, + CliCmd = 0x0A, + OtNot = 0x0C, + OtAck = 0x0D, + CliNot = 0x0E, + CliAck = 0x0F, + + SysCmd = 0x10, + SysRsp = 0x11, + SysEvt = 0x12, + + LocCmd = 0x20, + LocRsp = 0x21, + + TracesApp = 0x40, + TracesWl = 0x41, +} + +impl TryFrom for TlPacketType { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0x01 => Ok(TlPacketType::BleCmd), + 0x02 => Ok(TlPacketType::AclData), + 0x04 => Ok(TlPacketType::BleEvt), + 0x08 => Ok(TlPacketType::OtCmd), + 0x09 => Ok(TlPacketType::OtRsp), + 0x0A => Ok(TlPacketType::CliCmd), + 0x0C => Ok(TlPacketType::OtNot), + 0x0D => Ok(TlPacketType::OtAck), + 0x0E => Ok(TlPacketType::CliNot), + 0x0F => Ok(TlPacketType::CliAck), + 0x10 => Ok(TlPacketType::SysCmd), + 0x11 => Ok(TlPacketType::SysRsp), + 0x12 => Ok(TlPacketType::SysEvt), + 0x20 => Ok(TlPacketType::LocCmd), + 0x21 => Ok(TlPacketType::LocRsp), + 0x40 => Ok(TlPacketType::TracesApp), + 0x41 => Ok(TlPacketType::TracesWl), + + _ => Err(()), + } + } +} + +pub const TL_PACKET_HEADER_SIZE: usize = core::mem::size_of::(); +pub const TL_EVT_HEADER_SIZE: usize = 3; +pub const TL_CS_EVT_SIZE: usize = core::mem::size_of::(); + +/** + * Queue length of BLE Event + * This parameter defines the number of asynchronous events that can be stored in the HCI layer before + * being reported to the application. When a command is sent to the BLE core coprocessor, the HCI layer + * is waiting for the event with the Num_HCI_Command_Packets set to 1. The receive queue shall be large + * enough to store all asynchronous events received in between. + * When CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE is set to 27, this allow to store three 255 bytes long asynchronous events + * between the HCI command and its event. + * This parameter depends on the value given to CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE. When the queue size is to small, + * the system may hang if the queue is full with asynchronous events and the HCI layer is still waiting + * for a CC/CS event, In that case, the notification TL_BLE_HCI_ToNot() is called to indicate + * to the application a HCI command did not receive its command event within 30s (Default HCI Timeout). + */ +pub const CFG_TL_BLE_EVT_QUEUE_LENGTH: usize = 5; +pub const CFG_TL_BLE_MOST_EVENT_PAYLOAD_SIZE: usize = 255; +pub const TL_BLE_EVENT_FRAME_SIZE: usize = TL_EVT_HEADER_SIZE + CFG_TL_BLE_MOST_EVENT_PAYLOAD_SIZE; + +pub const POOL_SIZE: usize = CFG_TL_BLE_EVT_QUEUE_LENGTH * 4 * divc(TL_PACKET_HEADER_SIZE + TL_BLE_EVENT_FRAME_SIZE, 4); +pub const C_SIZE_CMD_STRING: usize = 256; + +pub const fn divc(x: usize, y: usize) -> usize { + (x + y - 1) / y +} + +pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE; +#[allow(dead_code)] +pub const TL_BLE_EVT_CS_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_BLE_EVT_CS_PACKET_SIZE; + +pub const TL_BLEEVT_CC_OPCODE: u8 = 0x0E; +pub const TL_BLEEVT_CS_OPCODE: u8 = 0x0F; +pub const TL_BLEEVT_VS_OPCODE: u8 = 0xFF; diff --git a/embassy/embassy-stm32-wpan/src/evt.rs b/embassy/embassy-stm32-wpan/src/evt.rs new file mode 100644 index 0000000..c652841 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/evt.rs @@ -0,0 +1,151 @@ +use core::marker::PhantomData; +use core::{ptr, slice}; + +use super::PacketHeader; +use crate::consts::TL_EVT_HEADER_SIZE; + +/** + * The payload of `Evt` for a command status event + */ +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct CsEvt { + pub status: u8, + pub num_cmd: u8, + pub cmd_code: u16, +} + +/** + * The payload of `Evt` for a command complete event + */ +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct CcEvt { + pub num_cmd: u8, + pub cmd_code: u16, + pub payload: [u8; 1], +} + +impl CcEvt { + pub fn write(&self, buf: &mut [u8]) { + unsafe { + let len = core::mem::size_of::(); + assert!(buf.len() >= len); + + let self_ptr: *const CcEvt = self; + let self_buf_ptr: *const u8 = self_ptr.cast(); + + core::ptr::copy(self_buf_ptr, buf.as_mut_ptr(), len); + } + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct AsynchEvt { + sub_evt_code: u16, + payload: [u8; 1], +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct Evt { + pub evt_code: u8, + pub payload_len: u8, + pub payload: [u8; 255], +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct EvtSerial { + pub kind: u8, + pub evt: Evt, +} + +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct EvtStub { + pub kind: u8, + pub evt_code: u8, +} + +/// This format shall be used for all events (asynchronous and command response) reported +/// by the CPU2 except for the command response of a system command where the header is not there +/// and the format to be used shall be `EvtSerial`. +/// +/// ### Note: +/// Be careful that the asynchronous events reported by the CPU2 on the system channel do +/// include the header and shall use `EvtPacket` format. Only the command response format on the +/// system channel is different. +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct EvtPacket { + pub header: PacketHeader, + pub evt_serial: EvtSerial, +} + +impl EvtPacket { + pub fn kind(&self) -> u8 { + self.evt_serial.kind + } + + pub fn evt(&self) -> &Evt { + &self.evt_serial.evt + } +} + +pub trait MemoryManager { + unsafe fn drop_event_packet(evt: *mut EvtPacket); +} + +/// smart pointer to the [`EvtPacket`] that will dispose of [`EvtPacket`] buffer automatically +/// on [`Drop`] +#[derive(Debug)] +pub struct EvtBox { + ptr: *mut EvtPacket, + mm: PhantomData, +} + +unsafe impl Send for EvtBox {} +impl EvtBox { + pub(super) fn new(ptr: *mut EvtPacket) -> Self { + Self { ptr, mm: PhantomData } + } + + /// Returns information about the event + pub fn stub(&self) -> EvtStub { + unsafe { + let p_evt_stub = &(*self.ptr).evt_serial as *const _ as *const EvtStub; + + ptr::read_volatile(p_evt_stub) + } + } + + pub fn payload<'a>(&'a self) -> &'a [u8] { + unsafe { + let p_payload_len = &(*self.ptr).evt_serial.evt.payload_len as *const u8; + let p_payload = &(*self.ptr).evt_serial.evt.payload as *const u8; + + let payload_len = ptr::read_volatile(p_payload_len); + + slice::from_raw_parts(p_payload, payload_len as usize) + } + } + + pub fn serial<'a>(&'a self) -> &'a [u8] { + unsafe { + let evt_serial: *const EvtSerial = &(*self.ptr).evt_serial; + let evt_serial_buf: *const u8 = evt_serial.cast(); + + let len = (*evt_serial).evt.payload_len as usize + TL_EVT_HEADER_SIZE; + + slice::from_raw_parts(evt_serial_buf, len) + } + } +} + +impl Drop for EvtBox { + fn drop(&mut self) { + unsafe { T::drop_event_packet(self.ptr) }; + } +} diff --git a/embassy/embassy-stm32-wpan/src/fmt.rs b/embassy/embassy-stm32-wpan/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-stm32-wpan/src/lhci.rs b/embassy/embassy-stm32-wpan/src/lhci.rs new file mode 100644 index 0000000..89f204f --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/lhci.rs @@ -0,0 +1,112 @@ +use core::ptr; + +use crate::cmd::CmdPacket; +use crate::consts::{TlPacketType, TL_EVT_HEADER_SIZE}; +use crate::evt::{CcEvt, EvtPacket, EvtSerial}; +use crate::tables::{DeviceInfoTable, RssInfoTable, SafeBootInfoTable, WirelessFwInfoTable, TL_DEVICE_INFO_TABLE}; + +const TL_BLEEVT_CC_OPCODE: u8 = 0x0e; +const LHCI_OPCODE_C1_DEVICE_INF: u16 = 0xfd62; + +const PACKAGE_DATA_PTR: *const u8 = 0x1FFF_7500 as _; +const UID64_PTR: *const u32 = 0x1FFF_7580 as _; + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct LhciC1DeviceInformationCcrp { + pub status: u8, + pub rev_id: u16, + pub dev_code_id: u16, + pub package_type: u8, + pub device_type_id: u8, + pub st_company_id: u32, + pub uid64: u32, + + pub uid96_0: u32, + pub uid96_1: u32, + pub uid96_2: u32, + + pub safe_boot_info_table: SafeBootInfoTable, + pub rss_info_table: RssInfoTable, + pub wireless_fw_info_table: WirelessFwInfoTable, + + pub app_fw_inf: u32, +} + +impl Default for LhciC1DeviceInformationCcrp { + fn default() -> Self { + let DeviceInfoTable { + safe_boot_info_table, + rss_info_table, + wireless_fw_info_table, + } = unsafe { ptr::read_volatile(TL_DEVICE_INFO_TABLE.as_ptr()) }; + + let device_id = stm32_device_signature::device_id(); + let uid96_0 = (device_id[3] as u32) << 24 + | (device_id[2] as u32) << 16 + | (device_id[1] as u32) << 8 + | device_id[0] as u32; + let uid96_1 = (device_id[7] as u32) << 24 + | (device_id[6] as u32) << 16 + | (device_id[5] as u32) << 8 + | device_id[4] as u32; + let uid96_2 = (device_id[11] as u32) << 24 + | (device_id[10] as u32) << 16 + | (device_id[9] as u32) << 8 + | device_id[8] as u32; + + let package_type = unsafe { *PACKAGE_DATA_PTR }; + let uid64 = unsafe { *UID64_PTR }; + let st_company_id = unsafe { *UID64_PTR.offset(1) } >> 8 & 0x00FF_FFFF; + let device_type_id = (unsafe { *UID64_PTR.offset(1) } & 0x000000FF) as u8; + + LhciC1DeviceInformationCcrp { + status: 0, + rev_id: 0, + dev_code_id: 0, + package_type, + device_type_id, + st_company_id, + uid64, + uid96_0, + uid96_1, + uid96_2, + safe_boot_info_table, + rss_info_table, + wireless_fw_info_table, + app_fw_inf: (1 << 8), // 0.0.1 + } + } +} + +impl LhciC1DeviceInformationCcrp { + pub fn new() -> Self { + Self::default() + } + + pub fn write(&self, cmd_packet: &mut CmdPacket) { + let self_size = core::mem::size_of::(); + + unsafe { + let cmd_packet_ptr: *mut CmdPacket = cmd_packet; + let evet_packet_ptr: *mut EvtPacket = cmd_packet_ptr.cast(); + + let evt_serial: *mut EvtSerial = &mut (*evet_packet_ptr).evt_serial; + let evt_payload = (*evt_serial).evt.payload.as_mut_ptr(); + let evt_cc: *mut CcEvt = evt_payload.cast(); + let evt_cc_payload_buf = (*evt_cc).payload.as_mut_ptr(); + + (*evt_serial).kind = TlPacketType::LocRsp as u8; + (*evt_serial).evt.evt_code = TL_BLEEVT_CC_OPCODE; + (*evt_serial).evt.payload_len = TL_EVT_HEADER_SIZE as u8 + self_size as u8; + + (*evt_cc).cmd_code = LHCI_OPCODE_C1_DEVICE_INF; + (*evt_cc).num_cmd = 1; + + let self_ptr: *const LhciC1DeviceInformationCcrp = self; + let self_buf = self_ptr.cast(); + + ptr::copy(self_buf, evt_cc_payload_buf, self_size); + } + } +} diff --git a/embassy/embassy-stm32-wpan/src/lib.rs b/embassy/embassy-stm32-wpan/src/lib.rs new file mode 100644 index 0000000..fb34d4b --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/lib.rs @@ -0,0 +1,155 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +// #![warn(missing_docs)] +#![allow(static_mut_refs)] // TODO: Fix + +// This must go FIRST so that all the other modules see its macros. +mod fmt; + +use core::mem::MaybeUninit; +use core::sync::atomic::{compiler_fence, Ordering}; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_stm32::interrupt; +use embassy_stm32::ipcc::{Config, Ipcc, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::peripherals::IPCC; +use sub::mm::MemoryManager; +use sub::sys::Sys; +use tables::*; +use unsafe_linked_list::LinkedListNode; + +pub mod channels; +pub mod cmd; +pub mod consts; +pub mod evt; +pub mod lhci; +pub mod shci; +pub mod sub; +pub mod tables; +pub mod unsafe_linked_list; + +#[cfg(feature = "mac")] +pub mod mac; + +#[cfg(feature = "ble")] +pub use crate::sub::ble::hci; + +type PacketHeader = LinkedListNode; + +pub struct TlMbox<'d> { + _ipcc: PeripheralRef<'d, IPCC>, + + pub sys_subsystem: Sys, + pub mm_subsystem: MemoryManager, + #[cfg(feature = "ble")] + pub ble_subsystem: sub::ble::Ble, + #[cfg(feature = "mac")] + pub mac_subsystem: sub::mac::Mac, +} + +impl<'d> TlMbox<'d> { + pub fn init( + ipcc: impl Peripheral

+ 'd, + _irqs: impl interrupt::typelevel::Binding + + interrupt::typelevel::Binding, + config: Config, + ) -> Self { + into_ref!(ipcc); + + unsafe { + TL_REF_TABLE.as_mut_ptr().write_volatile(RefTable { + device_info_table: TL_DEVICE_INFO_TABLE.as_ptr(), + ble_table: TL_BLE_TABLE.as_ptr(), + thread_table: TL_THREAD_TABLE.as_ptr(), + sys_table: TL_SYS_TABLE.as_ptr(), + mem_manager_table: TL_MEM_MANAGER_TABLE.as_ptr(), + traces_table: TL_TRACES_TABLE.as_ptr(), + mac_802_15_4_table: TL_MAC_802_15_4_TABLE.as_ptr(), + zigbee_table: TL_ZIGBEE_TABLE.as_ptr(), + lld_tests_table: TL_LLD_TESTS_TABLE.as_ptr(), + ble_lld_table: TL_BLE_LLD_TABLE.as_ptr(), + }); + + TL_SYS_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_DEVICE_INFO_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_BLE_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_THREAD_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_MEM_MANAGER_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + + TL_TRACES_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_MAC_802_15_4_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_ZIGBEE_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_LLD_TESTS_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_BLE_LLD_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + + EVT_POOL + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + SYS_SPARE_EVT_BUF + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + CS_BUFFER + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + + #[cfg(feature = "ble")] + { + BLE_SPARE_EVT_BUF + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + + BLE_CMD_BUFFER + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + HCI_ACL_DATA_BUFFER + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + } + + #[cfg(feature = "mac")] + { + MAC_802_15_4_CMD_BUFFER + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + MAC_802_15_4_NOTIF_RSP_EVT_BUFFER + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + } + } + + compiler_fence(Ordering::SeqCst); + + Ipcc::enable(config); + + Self { + _ipcc: ipcc, + sys_subsystem: sub::sys::Sys::new(), + #[cfg(feature = "ble")] + ble_subsystem: sub::ble::Ble::new(), + #[cfg(feature = "mac")] + mac_subsystem: sub::mac::Mac::new(), + mm_subsystem: sub::mm::MemoryManager::new(), + } + } +} diff --git a/embassy/embassy-stm32-wpan/src/mac/commands.rs b/embassy/embassy-stm32-wpan/src/mac/commands.rs new file mode 100644 index 0000000..82b9d27 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/commands.rs @@ -0,0 +1,478 @@ +#![allow(unused)] + +use core::{mem, slice}; + +use super::opcodes::OpcodeM4ToM0; +use super::typedefs::{ + AddressMode, Capabilities, DisassociationReason, GtsCharacteristics, KeyIdMode, MacAddress, MacChannel, MacStatus, + PanId, PibId, ScanType, SecurityLevel, +}; + +pub trait MacCommand: Sized { + const OPCODE: OpcodeM4ToM0; + + fn payload<'a>(&'a self) -> &'a [u8] { + unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::()) } + } +} + +/// MLME ASSOCIATE Request used to request an association +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AssociateRequest { + /// the logical channel on which to attempt association + pub channel_number: MacChannel, + /// the channel page on which to attempt association + pub channel_page: u8, + /// coordinator addressing mode + pub coord_addr_mode: AddressMode, + /// operational capabilities of the associating device + pub capability_information: Capabilities, + /// the identifier of the PAN with which to associate + pub coord_pan_id: PanId, + /// the security level to be used + pub security_level: SecurityLevel, + /// the mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// the originator of the key to be used + pub key_source: [u8; 8], + /// Coordinator address + pub coord_address: MacAddress, + /// the index of the key to be used + pub key_index: u8, +} + +impl MacCommand for AssociateRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeAssociateReq; +} + +/// MLME DISASSOCIATE Request sed to request a disassociation +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DisassociateRequest { + /// device addressing mode used + pub device_addr_mode: AddressMode, + /// the identifier of the PAN of the device + pub device_pan_id: PanId, + /// the reason for the disassociation + pub disassociation_reason: DisassociationReason, + /// device address + pub device_address: MacAddress, + /// `true` if the disassociation notification command is to be sent indirectly + pub tx_indirect: bool, + /// the security level to be used + pub security_level: SecurityLevel, + /// the mode to be used to indetify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// the originator of the key to be used + pub key_source: [u8; 8], +} + +impl MacCommand for DisassociateRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeDisassociateReq; +} + +/// MLME GET Request used to request a PIB value +#[repr(C)] +#[derive(Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GetRequest { + /// the name of the PIB attribute to read + pub pib_attribute: PibId, + + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 3], +} + +impl MacCommand for GetRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeGetReq; +} + +/// MLME GTS Request used to request and maintain GTSs +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GtsRequest { + /// the characteristics of the GTS + pub characteristics: GtsCharacteristics, + /// the security level to be used + pub security_level: SecurityLevel, + /// the mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// the originator of the key to be used + pub key_source: [u8; 8], +} + +impl MacCommand for GtsRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeGetReq; +} + +#[repr(C)] +#[derive(Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ResetRequest { + /// MAC PIB attributes are set to their default values or not during reset + pub set_default_pib: bool, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 3], +} + +impl MacCommand for ResetRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeResetReq; +} + +/// MLME RX ENABLE Request used to request that the receiver is either enabled +/// for a finite period of time or disabled +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RxEnableRequest { + /// the request operation can be deferred or not + pub defer_permit: bool, + /// configure the transceiver to RX with ranging for a value of + /// RANGING_ON or to not enable ranging for RANGING_OFF + pub ranging_rx_control: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 2], + /// number of symbols measured before the receiver is to be enabled or disabled + pub rx_on_time: [u8; 4], + /// number of symbols for which the receiver is to be enabled + pub rx_on_duration: [u8; 4], +} + +impl MacCommand for RxEnableRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeRxEnableReq; +} + +/// MLME SCAN Request used to initiate a channel scan over a given list of channels +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ScanRequest { + /// the type of scan to be performed + pub scan_type: ScanType, + /// the time spent on scanning each channel + pub scan_duration: u8, + /// channel page on which to perform the scan + pub channel_page: u8, + /// security level to be used + pub security_level: SecurityLevel, + /// indicate which channels are to be scanned + pub scan_channels: [u8; 4], + /// originator the key to be used + pub key_source: [u8; 8], + /// mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// index of the key to be used + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 2], +} + +impl MacCommand for ScanRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeScanReq; +} + +/// MLME SET Request used to attempt to write the given value to the indicated PIB attribute +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SetRequest { + /// the pointer to the value of the PIB attribute to set + pub pib_attribute_ptr: *const u8, + /// the name of the PIB attribute to set + pub pib_attribute: PibId, +} + +impl MacCommand for SetRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeSetReq; +} + +/// MLME START Request used by the FFDs to intiate a new PAN or to begin using a new superframe +/// configuration +#[repr(C)] +#[derive(Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct StartRequest { + /// PAN indentifier to used by the device + pub pan_id: PanId, + /// logical channel on which to begin + pub channel_number: MacChannel, + /// channel page on which to begin + pub channel_page: u8, + /// time at which to begin transmitting beacons + pub start_time: [u8; 4], + /// indicated how often the beacon is to be transmitted + pub beacon_order: u8, + /// length of the active portion of the superframe + pub superframe_order: u8, + /// indicated wheter the device is a PAN coordinator or not + pub pan_coordinator: bool, + /// indicates if the receiver of the beaconing device is disabled or not + pub battery_life_extension: bool, + /// indicated if the coordinator realignment command is to be trasmitted + pub coord_realignment: u8, + /// indicated if the coordinator realignment command is to be trasmitted + pub coord_realign_security_level: SecurityLevel, + /// index of the key to be used + pub coord_realign_key_id_index: u8, + /// originator of the key to be used + pub coord_realign_key_source: [u8; 8], + /// security level to be used for beacon frames + pub beacon_security_level: SecurityLevel, + /// mode used to identify the key to be used + pub beacon_key_id_mode: KeyIdMode, + /// index of the key to be used + pub beacon_key_index: u8, + /// originator of the key to be used + pub beacon_key_source: [u8; 8], +} + +impl MacCommand for StartRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeStartReq; +} + +/// MLME SYNC Request used to synchronize with the coordinator by acquiring and, if +/// specified, tracking its beacons +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SyncRequest { + /// the channel number on which to attempt coordinator synchronization + pub channel_number: MacChannel, + /// the channel page on which to attempt coordinator synchronization + pub channel_page: u8, + /// `true` if the MLME is to synchronize with the next beacon and attempts + /// to track all future beacons. + /// + /// `false` if the MLME is to synchronize with only the next beacon + pub track_beacon: bool, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 1], +} + +impl MacCommand for SyncRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeSyncReq; +} + +/// MLME POLL Request propmts the device to request data from the coordinator +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PollRequest { + /// addressing mode of the coordinator + pub coord_addr_mode: AddressMode, + /// security level to be used + pub security_level: SecurityLevel, + /// mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// index of the key to be used + pub key_index: u8, + /// coordinator address + pub coord_address: MacAddress, + /// originator of the key to be used + pub key_source: [u8; 8], + /// PAN identifier of the coordinator + pub coord_pan_id: PanId, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 2], +} + +impl MacCommand for PollRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmePollReq; +} + +/// MLME DPS Request allows the next higher layer to request that the PHY utilize a +/// given pair of preamble codes for a single use pending expiration of the DPSIndexDuration +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpsRequest { + /// the index value for the transmitter + tx_dps_index: u8, + /// the index value of the receiver + rx_dps_index: u8, + /// the number of symbols for which the transmitter and receiver will utilize the + /// respective DPS indices + dps_index_duration: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 1], +} + +impl MacCommand for DpsRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeDpsReq; +} + +/// MLME SOUNDING request primitive which is used by the next higher layer to request that +/// the PHY respond with channel sounding information +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SoundingRequest { + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 4], +} + +impl MacCommand for SoundingRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeSoundingReq; +} + +/// MLME CALIBRATE request primitive which used to obtain the results of a ranging +/// calibration request from an RDEV +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CalibrateRequest { + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 4], +} + +impl MacCommand for CalibrateRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeCalibrateReq; +} + +/// MCPS DATA Request used for MAC data related requests from the application +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DataRequest { + /// the handle assocated with the MSDU to be transmitted + pub msdu_ptr: *const u8, + /// source addressing mode used + pub src_addr_mode: AddressMode, + /// destination addressing mode used + pub dst_addr_mode: AddressMode, + /// destination PAN Id + pub dst_pan_id: PanId, + /// destination address + pub dst_address: MacAddress, + /// the number of octets contained in the MSDU + pub msdu_length: u8, + /// the handle assocated with the MSDU to be transmitted + pub msdu_handle: u8, + /// the ACK transmittion options for the MSDU + pub ack_tx: u8, + /// `true` if a GTS is to be used for transmission + /// + /// `false` indicates that the CAP will be used + pub gts_tx: bool, + /// the pending bit transmission options for the MSDU + pub indirect_tx: u8, + /// the security level to be used + pub security_level: SecurityLevel, + /// the mode used to indentify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// the originator of the key to be used + pub key_source: [u8; 8], + /// 2011 - the pulse repitition value + pub uwbprf: u8, + /// 2011 - the ranging configuration + pub ranging: u8, + /// 2011 - the preamble symbol repititions + pub uwb_preamble_symbol_repetitions: u8, + /// 2011 - indicates the data rate + pub datrate: u8, +} + +impl DataRequest { + pub fn set_buffer<'a>(&'a mut self, buf: &'a [u8]) -> &'a mut Self { + self.msdu_ptr = buf as *const _ as *const u8; + self.msdu_length = buf.len() as u8; + + self + } +} + +impl Default for DataRequest { + fn default() -> Self { + Self { + msdu_ptr: 0 as *const u8, + src_addr_mode: AddressMode::NoAddress, + dst_addr_mode: AddressMode::NoAddress, + dst_pan_id: PanId([0, 0]), + dst_address: MacAddress { short: [0, 0] }, + msdu_length: 0, + msdu_handle: 0, + ack_tx: 0, + gts_tx: false, + indirect_tx: 0, + security_level: SecurityLevel::Unsecure, + key_id_mode: KeyIdMode::Implicite, + key_index: 0, + key_source: [0u8; 8], + uwbprf: 0, + ranging: 0, + uwb_preamble_symbol_repetitions: 0, + datrate: 0, + } + } +} + +impl MacCommand for DataRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::McpsDataReq; +} + +/// for MCPS PURGE Request used to purge an MSDU from the transaction queue +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PurgeRequest { + /// the handle associated with the MSDU to be purged from the transaction + /// queue + pub msdu_handle: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 3], +} + +impl MacCommand for PurgeRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::McpsPurgeReq; +} + +/// MLME ASSOCIATE Response used to initiate a response to an MLME-ASSOCIATE.indication +#[repr(C)] +#[derive(Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AssociateResponse { + /// extended address of the device requesting association + pub device_address: [u8; 8], + /// 16-bitshort device address allocated by the coordinator on successful + /// association + pub assoc_short_address: [u8; 2], + /// status of the association attempt + pub status: MacStatus, + /// security level to be used + pub security_level: SecurityLevel, + /// the originator of the key to be used + pub key_source: [u8; 8], + /// the mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 2], +} + +impl MacCommand for AssociateResponse { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeAssociateRes; +} + +/// MLME ORPHAN Response used to respond to the MLME ORPHAN Indication +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OrphanResponse { + /// extended address of the orphaned device + pub orphan_address: [u8; 8], + /// short address allocated to the orphaned device + pub short_address: [u8; 2], + /// if the orphaned device is associated with coordinator or not + pub associated_member: bool, + /// security level to be used + pub security_level: SecurityLevel, + /// the originator of the key to be used + pub key_source: [u8; 8], + /// the mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 2], +} + +impl MacCommand for OrphanResponse { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeOrphanRes; +} diff --git a/embassy/embassy-stm32-wpan/src/mac/consts.rs b/embassy/embassy-stm32-wpan/src/mac/consts.rs new file mode 100644 index 0000000..56903d9 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/consts.rs @@ -0,0 +1,4 @@ +pub const MAX_PAN_DESC_SUPPORTED: usize = 6; +pub const MAX_SOUNDING_LIST_SUPPORTED: usize = 6; +pub const MAX_PENDING_ADDRESS: usize = 7; +pub const MAX_ED_SCAN_RESULTS_SUPPORTED: usize = 16; diff --git a/embassy/embassy-stm32-wpan/src/mac/control.rs b/embassy/embassy-stm32-wpan/src/mac/control.rs new file mode 100644 index 0000000..e8d2f9f --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/control.rs @@ -0,0 +1,95 @@ +use core::future::Future; +use core::task; +use core::task::Poll; + +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::mutex::MutexGuard; +use embassy_sync::signal::Signal; +use futures_util::FutureExt; + +use super::commands::MacCommand; +use super::event::MacEvent; +use super::typedefs::MacError; +use crate::mac::runner::Runner; + +pub struct Control<'a> { + runner: &'a Runner<'a>, +} + +impl<'a> Control<'a> { + pub(crate) fn new(runner: &'a Runner<'a>) -> Self { + Self { runner: runner } + } + + pub async fn send_command(&self, cmd: &T) -> Result<(), MacError> + where + T: MacCommand, + { + let _wm = self.runner.write_mutex.lock().await; + + self.runner.mac_subsystem.send_command(cmd).await + } + + pub async fn send_command_and_get_response(&self, cmd: &T) -> Result, MacError> + where + T: MacCommand, + { + let rm = self.runner.read_mutex.lock().await; + let _wm = self.runner.write_mutex.lock().await; + let token = EventToken::new(self.runner, rm); + + self.runner.mac_subsystem.send_command(cmd).await?; + + Ok(token) + } +} + +pub struct EventToken<'a> { + runner: &'a Runner<'a>, + _mutex_guard: MutexGuard<'a, CriticalSectionRawMutex, ()>, +} + +impl<'a> EventToken<'a> { + pub(crate) fn new(runner: &'a Runner<'a>, mutex_guard: MutexGuard<'a, CriticalSectionRawMutex, ()>) -> Self { + // Enable event receiving + runner.rx_event_channel.lock(|s| { + *s.borrow_mut() = Some(Signal::new()); + }); + + Self { + runner: runner, + _mutex_guard: mutex_guard, + } + } +} + +impl<'a> Future for EventToken<'a> { + type Output = MacEvent<'a>; + + fn poll(self: core::pin::Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + self.get_mut().runner.rx_event_channel.lock(|s| { + let signal = s.borrow_mut(); + let signal = match &*signal { + Some(s) => s, + _ => unreachable!(), + }; + + let result = match signal.wait().poll_unpin(cx) { + Poll::Ready(mac_event) => Poll::Ready(mac_event), + Poll::Pending => Poll::Pending, + }; + + result + }) + } +} + +impl<'a> Drop for EventToken<'a> { + fn drop(&mut self) { + // Disable event receiving + // This will also drop the contained event, if it exists, and will free up receiving the next event + self.runner.rx_event_channel.lock(|s| { + *s.borrow_mut() = None; + }); + } +} diff --git a/embassy/embassy-stm32-wpan/src/mac/driver.rs b/embassy/embassy-stm32-wpan/src/mac/driver.rs new file mode 100644 index 0000000..41cca09 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/driver.rs @@ -0,0 +1,120 @@ +#![deny(unused_must_use)] + +use core::task::Context; + +use embassy_net_driver::{Capabilities, HardwareAddress, LinkState}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; + +use crate::mac::event::MacEvent; +use crate::mac::runner::Runner; +use crate::mac::MTU; + +pub struct Driver<'d> { + runner: &'d Runner<'d>, +} + +impl<'d> Driver<'d> { + pub(crate) fn new(runner: &'d Runner<'d>) -> Self { + Self { runner: runner } + } +} + +impl<'d> embassy_net_driver::Driver for Driver<'d> { + // type RxToken<'a> = RxToken<'a, 'd> where Self: 'a; + // type TxToken<'a> = TxToken<'a, 'd> where Self: 'a; + type RxToken<'a> + = RxToken<'d> + where + Self: 'a; + type TxToken<'a> + = TxToken<'d> + where + Self: 'a; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + if self.runner.rx_channel.poll_ready_to_receive(cx).is_ready() + && self.runner.tx_buf_channel.poll_ready_to_receive(cx).is_ready() + { + Some(( + RxToken { + rx: &self.runner.rx_channel, + }, + TxToken { + tx: &self.runner.tx_channel, + tx_buf: &self.runner.tx_buf_channel, + }, + )) + } else { + None + } + } + + fn transmit(&mut self, cx: &mut Context) -> Option> { + if self.runner.tx_buf_channel.poll_ready_to_receive(cx).is_ready() { + Some(TxToken { + tx: &self.runner.tx_channel, + tx_buf: &self.runner.tx_buf_channel, + }) + } 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 { + LinkState::Down + } + + fn hardware_address(&self) -> HardwareAddress { + // self.mac_addr + HardwareAddress::Ieee802154([0; 8]) + } +} + +pub struct RxToken<'d> { + rx: &'d Channel, 1>, +} + +impl<'d> embassy_net_driver::RxToken for RxToken<'d> { + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // Only valid data events should be put into the queue + + let data_event = match self.rx.try_receive().unwrap() { + MacEvent::McpsDataInd(data_event) => data_event, + _ => unreachable!(), + }; + + f(&mut data_event.payload()) + } +} + +pub struct TxToken<'d> { + tx: &'d Channel, + tx_buf: &'d Channel, +} + +impl<'d> embassy_net_driver::TxToken for TxToken<'d> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // Only valid tx buffers should be put into the queue + let buf = self.tx_buf.try_receive().unwrap(); + let r = f(&mut buf[..len]); + + // The tx channel should always be of equal capacity to the tx_buf channel + self.tx.try_send((buf, len)).unwrap(); + + r + } +} diff --git a/embassy/embassy-stm32-wpan/src/mac/event.rs b/embassy/embassy-stm32-wpan/src/mac/event.rs new file mode 100644 index 0000000..9ca4f5a --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/event.rs @@ -0,0 +1,153 @@ +use core::{mem, ptr}; + +use super::indications::{ + AssociateIndication, BeaconNotifyIndication, CommStatusIndication, DataIndication, DisassociateIndication, + DpsIndication, GtsIndication, OrphanIndication, PollIndication, SyncLossIndication, +}; +use super::responses::{ + AssociateConfirm, CalibrateConfirm, DataConfirm, DisassociateConfirm, DpsConfirm, GetConfirm, GtsConfirm, + PollConfirm, PurgeConfirm, ResetConfirm, RxEnableConfirm, ScanConfirm, SetConfirm, SoundingConfirm, StartConfirm, +}; +use crate::evt::{EvtBox, MemoryManager}; +use crate::mac::opcodes::OpcodeM0ToM4; +use crate::sub::mac::{self, Mac}; + +pub(crate) trait ParseableMacEvent: Sized { + fn from_buffer<'a>(buf: &'a [u8]) -> Result<&'a Self, ()> { + if buf.len() < mem::size_of::() { + Err(()) + } else { + Ok(unsafe { &*(buf as *const _ as *const Self) }) + } + } +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug)] +pub enum MacEvent<'a> { + MlmeAssociateCnf(&'a AssociateConfirm), + MlmeDisassociateCnf(&'a DisassociateConfirm), + MlmeGetCnf(&'a GetConfirm), + MlmeGtsCnf(&'a GtsConfirm), + MlmeResetCnf(&'a ResetConfirm), + MlmeRxEnableCnf(&'a RxEnableConfirm), + MlmeScanCnf(&'a ScanConfirm), + MlmeSetCnf(&'a SetConfirm), + MlmeStartCnf(&'a StartConfirm), + MlmePollCnf(&'a PollConfirm), + MlmeDpsCnf(&'a DpsConfirm), + MlmeSoundingCnf(&'a SoundingConfirm), + MlmeCalibrateCnf(&'a CalibrateConfirm), + McpsDataCnf(&'a DataConfirm), + McpsPurgeCnf(&'a PurgeConfirm), + MlmeAssociateInd(&'a AssociateIndication), + MlmeDisassociateInd(&'a DisassociateIndication), + MlmeBeaconNotifyInd(&'a BeaconNotifyIndication), + MlmeCommStatusInd(&'a CommStatusIndication), + MlmeGtsInd(&'a GtsIndication), + MlmeOrphanInd(&'a OrphanIndication), + MlmeSyncLossInd(&'a SyncLossIndication), + MlmeDpsInd(&'a DpsIndication), + McpsDataInd(&'a DataIndication), + MlmePollInd(&'a PollIndication), +} + +impl<'a> MacEvent<'a> { + pub(crate) fn new(event_box: EvtBox) -> Result { + let payload = event_box.payload(); + let opcode = u16::from_le_bytes(payload[0..2].try_into().unwrap()); + + let opcode = OpcodeM0ToM4::try_from(opcode)?; + let buf = &payload[2..]; + + // To avoid re-parsing the opcode, we store the result of the parse + // this requires use of unsafe because rust cannot assume that a reference will become + // invalid when the underlying result is moved. However, because we refer to a "heap" + // allocation, the underlying reference will not move until the struct is dropped. + + let mac_event = match opcode { + OpcodeM0ToM4::MlmeAssociateCnf => { + MacEvent::MlmeAssociateCnf(unsafe { &*(AssociateConfirm::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeDisassociateCnf => { + MacEvent::MlmeDisassociateCnf(unsafe { &*(DisassociateConfirm::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeGetCnf => MacEvent::MlmeGetCnf(unsafe { &*(GetConfirm::from_buffer(buf)? as *const _) }), + OpcodeM0ToM4::MlmeGtsCnf => MacEvent::MlmeGtsCnf(unsafe { &*(GtsConfirm::from_buffer(buf)? as *const _) }), + OpcodeM0ToM4::MlmeResetCnf => { + MacEvent::MlmeResetCnf(unsafe { &*(ResetConfirm::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeRxEnableCnf => { + MacEvent::MlmeRxEnableCnf(unsafe { &*(RxEnableConfirm::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeScanCnf => { + MacEvent::MlmeScanCnf(unsafe { &*(ScanConfirm::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeSetCnf => MacEvent::MlmeSetCnf(unsafe { &*(SetConfirm::from_buffer(buf)? as *const _) }), + OpcodeM0ToM4::MlmeStartCnf => { + MacEvent::MlmeStartCnf(unsafe { &*(StartConfirm::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmePollCnf => { + MacEvent::MlmePollCnf(unsafe { &*(PollConfirm::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeDpsCnf => MacEvent::MlmeDpsCnf(unsafe { &*(DpsConfirm::from_buffer(buf)? as *const _) }), + OpcodeM0ToM4::MlmeSoundingCnf => { + MacEvent::MlmeSoundingCnf(unsafe { &*(SoundingConfirm::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeCalibrateCnf => { + MacEvent::MlmeCalibrateCnf(unsafe { &*(CalibrateConfirm::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::McpsDataCnf => { + MacEvent::McpsDataCnf(unsafe { &*(DataConfirm::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::McpsPurgeCnf => { + MacEvent::McpsPurgeCnf(unsafe { &*(PurgeConfirm::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeAssociateInd => { + MacEvent::MlmeAssociateInd(unsafe { &*(AssociateIndication::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeDisassociateInd => { + MacEvent::MlmeDisassociateInd(unsafe { &*(DisassociateIndication::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeBeaconNotifyInd => { + MacEvent::MlmeBeaconNotifyInd(unsafe { &*(BeaconNotifyIndication::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeCommStatusInd => { + MacEvent::MlmeCommStatusInd(unsafe { &*(CommStatusIndication::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeGtsInd => { + MacEvent::MlmeGtsInd(unsafe { &*(GtsIndication::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeOrphanInd => { + MacEvent::MlmeOrphanInd(unsafe { &*(OrphanIndication::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeSyncLossInd => { + MacEvent::MlmeSyncLossInd(unsafe { &*(SyncLossIndication::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmeDpsInd => { + MacEvent::MlmeDpsInd(unsafe { &*(DpsIndication::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::McpsDataInd => { + MacEvent::McpsDataInd(unsafe { &*(DataIndication::from_buffer(buf)? as *const _) }) + } + OpcodeM0ToM4::MlmePollInd => { + MacEvent::MlmePollInd(unsafe { &*(PollIndication::from_buffer(buf)? as *const _) }) + } + }; + + // Forget the event box so that drop isn't called + // We want to handle the lifetime ourselves + + mem::forget(event_box); + + Ok(mac_event) + } +} + +unsafe impl<'a> Send for MacEvent<'a> {} + +impl<'a> Drop for MacEvent<'a> { + fn drop(&mut self) { + unsafe { mac::Mac::drop_event_packet(ptr::null_mut()) }; + } +} diff --git a/embassy/embassy-stm32-wpan/src/mac/indications.rs b/embassy/embassy-stm32-wpan/src/mac/indications.rs new file mode 100644 index 0000000..c0b86d7 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/indications.rs @@ -0,0 +1,265 @@ +use core::slice; + +use super::consts::MAX_PENDING_ADDRESS; +use super::event::ParseableMacEvent; +use super::typedefs::{ + AddressMode, Capabilities, DisassociationReason, KeyIdMode, MacAddress, MacChannel, MacStatus, PanDescriptor, + PanId, SecurityLevel, +}; + +/// MLME ASSOCIATE Indication which will be used by the MAC +/// to indicate the reception of an association request command +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AssociateIndication { + /// Extended address of the device requesting association + pub device_address: [u8; 8], + /// Operational capabilities of the device requesting association + pub capability_information: Capabilities, + /// Security level purportedly used by the received MAC command frame + pub security_level: SecurityLevel, + /// The mode used to identify the key used by the originator of frame + pub key_id_mode: KeyIdMode, + /// Index of the key used by the originator of the received frame + pub key_index: u8, + /// The originator of the key used by the originator of the received frame + pub key_source: [u8; 8], +} + +impl ParseableMacEvent for AssociateIndication {} + +/// MLME DISASSOCIATE indication which will be used to send +/// disassociation indication to the application. +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DisassociateIndication { + /// Extended address of the device requesting association + pub device_address: [u8; 8], + /// The reason for the disassociation + pub disassociation_reason: DisassociationReason, + /// The security level to be used + pub security_level: SecurityLevel, + /// The mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// The index of the key to be used + pub key_index: u8, + /// The originator of the key to be used + pub key_source: [u8; 8], +} + +impl ParseableMacEvent for DisassociateIndication {} + +/// MLME BEACON NOTIIFY Indication which is used to send parameters contained +/// within a beacon frame received by the MAC to the application +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BeaconNotifyIndication { + /// he set of octets comprising the beacon payload to be transferred + /// from the MAC sublayer entity to the next higher layer + pub sdu_ptr: *const u8, + /// The PAN Descriptor for the received beacon + pub pan_descriptor: PanDescriptor, + /// The list of addresses of the devices + pub addr_list: [MacAddress; MAX_PENDING_ADDRESS], + /// Beacon Sequence Number + pub bsn: u8, + /// The beacon pending address specification + pub pend_addr_spec: u8, + /// Number of octets contained in the beacon payload of the beacon frame + pub sdu_length: u8, +} + +impl ParseableMacEvent for BeaconNotifyIndication {} + +/// MLME COMM STATUS Indication which is used by the MAC to indicate a communications status +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CommStatusIndication { + /// The 16-bit PAN identifier of the device from which the frame + /// was received or to which the frame was being sent + pub pan_id: PanId, + /// Source addressing mode + pub src_addr_mode: AddressMode, + /// Destination addressing mode + pub dst_addr_mode: AddressMode, + /// Source address + pub src_address: MacAddress, + /// Destination address + pub dst_address: MacAddress, + /// The communications status + pub status: MacStatus, + /// Security level to be used + pub security_level: SecurityLevel, + /// Mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// Index of the key to be used + pub key_index: u8, + /// Originator of the key to be used + pub key_source: [u8; 8], +} + +impl ParseableMacEvent for CommStatusIndication {} + +/// MLME GTS Indication indicates that a GTS has been allocated or that a +/// previously allocated GTS has been deallocated +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GtsIndication { + /// The short address of the device that has been allocated or deallocated a GTS + pub device_address: [u8; 2], + /// The characteristics of the GTS + pub gts_characteristics: u8, + /// Security level to be used + pub security_level: SecurityLevel, + /// Mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// Index of the key to be used + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 2], + /// Originator of the key to be used + pub key_source: [u8; 8], +} + +impl ParseableMacEvent for GtsIndication {} + +/// MLME ORPHAN Indication which is used by the coordinator to notify the +/// application of the presence of an orphaned device +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OrphanIndication { + /// Extended address of the orphaned device + pub orphan_address: [u8; 8], + /// Originator of the key used by the originator of the received frame + pub key_source: [u8; 8], + /// Security level purportedly used by the received MAC command frame + pub security_level: SecurityLevel, + /// Mode used to identify the key used by originator of received frame + pub key_id_mode: KeyIdMode, + /// Index of the key used by the originator of the received frame + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 1], +} + +impl ParseableMacEvent for OrphanIndication {} + +/// MLME SYNC LOSS Indication which is used by the MAC to indicate the loss +/// of synchronization with the coordinator +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SyncLossIndication { + /// The PAN identifier with which the device lost synchronization or to which it was realigned + pub pan_id: PanId, + /// The reason that synchronization was lost + pub loss_reason: u8, + /// The logical channel on which the device lost synchronization or to whi + pub channel_number: MacChannel, + /// The channel page on which the device lost synchronization or to which + pub channel_page: u8, + /// The security level used by the received MAC frame + pub security_level: SecurityLevel, + /// Mode used to identify the key used by originator of received frame + pub key_id_mode: KeyIdMode, + /// Index of the key used by the originator of the received frame + pub key_index: u8, + /// Originator of the key used by the originator of the received frame + pub key_source: [u8; 8], +} + +impl ParseableMacEvent for SyncLossIndication {} + +/// MLME DPS Indication which indicates the expiration of the DPSIndexDuration +/// and the resetting of the DPS values in the PHY +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpsIndication { + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 4], +} + +impl ParseableMacEvent for DpsIndication {} + +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DataIndication { + /// Pointer to the set of octets forming the MSDU being indicated + pub msdu_ptr: *const u8, + /// Source addressing mode used + pub src_addr_mode: AddressMode, + /// Source PAN ID + pub src_pan_id: PanId, + /// Source address + pub src_address: MacAddress, + /// Destination addressing mode used + pub dst_addr_mode: AddressMode, + /// Destination PAN ID + pub dst_pan_id: PanId, + /// Destination address + pub dst_address: MacAddress, + /// The number of octets contained in the MSDU being indicated + pub msdu_length: u8, + /// QI value measured during reception of the MPDU + pub mpdu_link_quality: u8, + /// The data sequence number of the received data frame + pub dsn: u8, + /// The time, in symbols, at which the data were received + pub time_stamp: [u8; 4], + /// The security level purportedly used by the received data frame + security_level: SecurityLevel, + /// Mode used to identify the key used by originator of received frame + key_id_mode: KeyIdMode, + /// The originator of the key + pub key_source: [u8; 8], + /// The index of the key + pub key_index: u8, + /// he pulse repetition value of the received PPDU + pub uwbprf: u8, + /// The preamble symbol repetitions of the UWB PHY frame + pub uwn_preamble_symbol_repetitions: u8, + /// Indicates the data rate + pub datrate: u8, + /// time units corresponding to an RMARKER at the antenna at the end of a ranging exchange, + pub ranging_received: u8, + pub ranging_counter_start: u32, + pub ranging_counter_stop: u32, + /// ime units in a message exchange over which the tracking offset was measured + pub ranging_tracking_interval: u32, + /// time units slipped or advanced by the radio tracking system + pub ranging_offset: u32, + /// The FoM characterizing the ranging measurement + pub ranging_fom: u8, + /// The Received Signal Strength Indicator measured + pub rssi: u8, +} + +impl ParseableMacEvent for DataIndication {} + +impl DataIndication { + pub fn payload<'a>(&'a self) -> &'a mut [u8] { + unsafe { slice::from_raw_parts_mut(self.msdu_ptr as *mut _, self.msdu_length as usize) } + } +} + +/// MLME POLL Indication which will be used for indicating the Data Request +/// reception to upper layer as defined in Zigbee r22 - D.8.2 +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PollIndication { + /// addressing mode used + pub addr_mode: AddressMode, + /// Poll requester address + pub request_address: MacAddress, +} + +impl ParseableMacEvent for PollIndication {} diff --git a/embassy/embassy-stm32-wpan/src/mac/macros.rs b/embassy/embassy-stm32-wpan/src/mac/macros.rs new file mode 100644 index 0000000..1a988a7 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/macros.rs @@ -0,0 +1,32 @@ +#[macro_export] +macro_rules! numeric_enum { + (#[repr($repr:ident)] + $(#$attrs:tt)* $vis:vis enum $name:ident { + $($(#$enum_attrs:tt)* $enum:ident = $constant:expr),* $(,)? + } ) => { + #[repr($repr)] + $(#$attrs)* + $vis enum $name { + $($(#$enum_attrs)* $enum = $constant),* + } + + impl ::core::convert::TryFrom<$repr> for $name { + type Error = (); + + fn try_from(value: $repr) -> ::core::result::Result { + match value { + $($constant => Ok( $name :: $enum ),)* + _ => Err(()) + } + } + } + + impl ::core::convert::From<$name> for $repr { + fn from(value: $name) -> $repr { + match value { + $($name :: $enum => $constant,)* + } + } + } + } +} diff --git a/embassy/embassy-stm32-wpan/src/mac/mod.rs b/embassy/embassy-stm32-wpan/src/mac/mod.rs new file mode 100644 index 0000000..c847a5c --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/mod.rs @@ -0,0 +1,21 @@ +pub mod commands; +mod consts; +pub mod control; +mod driver; +pub mod event; +pub mod indications; +mod macros; +mod opcodes; +pub mod responses; +pub mod runner; +pub mod typedefs; + +pub use crate::mac::control::Control; +use crate::mac::driver::Driver; +pub use crate::mac::runner::Runner; + +const MTU: usize = 127; + +pub async fn new<'a>(runner: &'a Runner<'a>) -> (Control<'a>, Driver<'a>) { + (Control::new(runner), Driver::new(runner)) +} diff --git a/embassy/embassy-stm32-wpan/src/mac/opcodes.rs b/embassy/embassy-stm32-wpan/src/mac/opcodes.rs new file mode 100644 index 0000000..fd70118 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/opcodes.rs @@ -0,0 +1,92 @@ +const ST_VENDOR_OGF: u16 = 0x3F; +const MAC_802_15_4_CMD_OPCODE_OFFSET: u16 = 0x280; + +const fn opcode(ocf: u16) -> isize { + ((ST_VENDOR_OGF << 9) | (MAC_802_15_4_CMD_OPCODE_OFFSET + ocf)) as isize +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum OpcodeM4ToM0 { + MlmeAssociateReq = opcode(0x00), + MlmeAssociateRes = opcode(0x01), + MlmeDisassociateReq = opcode(0x02), + MlmeGetReq = opcode(0x03), + MlmeGtsReq = opcode(0x04), + MlmeOrphanRes = opcode(0x05), + MlmeResetReq = opcode(0x06), + MlmeRxEnableReq = opcode(0x07), + MlmeScanReq = opcode(0x08), + MlmeSetReq = opcode(0x09), + MlmeStartReq = opcode(0x0A), + MlmeSyncReq = opcode(0x0B), + MlmePollReq = opcode(0x0C), + MlmeDpsReq = opcode(0x0D), + MlmeSoundingReq = opcode(0x0E), + MlmeCalibrateReq = opcode(0x0F), + McpsDataReq = opcode(0x10), + McpsPurgeReq = opcode(0x11), +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum OpcodeM0ToM4 { + MlmeAssociateCnf = 0x00, + MlmeDisassociateCnf, + MlmeGetCnf, + MlmeGtsCnf, + MlmeResetCnf, + MlmeRxEnableCnf, + MlmeScanCnf, + MlmeSetCnf, + MlmeStartCnf, + MlmePollCnf, + MlmeDpsCnf, + MlmeSoundingCnf, + MlmeCalibrateCnf, + McpsDataCnf, + McpsPurgeCnf, + MlmeAssociateInd, + MlmeDisassociateInd, + MlmeBeaconNotifyInd, + MlmeCommStatusInd, + MlmeGtsInd, + MlmeOrphanInd, + MlmeSyncLossInd, + MlmeDpsInd, + McpsDataInd, + MlmePollInd, +} + +impl TryFrom for OpcodeM0ToM4 { + type Error = (); + + fn try_from(value: u16) -> Result { + match value { + 0 => Ok(Self::MlmeAssociateCnf), + 1 => Ok(Self::MlmeDisassociateCnf), + 2 => Ok(Self::MlmeGetCnf), + 3 => Ok(Self::MlmeGtsCnf), + 4 => Ok(Self::MlmeResetCnf), + 5 => Ok(Self::MlmeRxEnableCnf), + 6 => Ok(Self::MlmeScanCnf), + 7 => Ok(Self::MlmeSetCnf), + 8 => Ok(Self::MlmeStartCnf), + 9 => Ok(Self::MlmePollCnf), + 10 => Ok(Self::MlmeDpsCnf), + 11 => Ok(Self::MlmeSoundingCnf), + 12 => Ok(Self::MlmeCalibrateCnf), + 13 => Ok(Self::McpsDataCnf), + 14 => Ok(Self::McpsPurgeCnf), + 15 => Ok(Self::MlmeAssociateInd), + 16 => Ok(Self::MlmeDisassociateInd), + 17 => Ok(Self::MlmeBeaconNotifyInd), + 18 => Ok(Self::MlmeCommStatusInd), + 19 => Ok(Self::MlmeGtsInd), + 20 => Ok(Self::MlmeOrphanInd), + 21 => Ok(Self::MlmeSyncLossInd), + 22 => Ok(Self::MlmeDpsInd), + 23 => Ok(Self::McpsDataInd), + 24 => Ok(Self::MlmePollInd), + _ => Err(()), + } + } +} diff --git a/embassy/embassy-stm32-wpan/src/mac/responses.rs b/embassy/embassy-stm32-wpan/src/mac/responses.rs new file mode 100644 index 0000000..544fdaa --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/responses.rs @@ -0,0 +1,273 @@ +use super::consts::{MAX_ED_SCAN_RESULTS_SUPPORTED, MAX_PAN_DESC_SUPPORTED, MAX_SOUNDING_LIST_SUPPORTED}; +use super::event::ParseableMacEvent; +use super::typedefs::{ + AddressMode, AssociationStatus, KeyIdMode, MacAddress, MacStatus, PanDescriptor, PanId, PibId, ScanType, + SecurityLevel, +}; + +/// MLME ASSOCIATE Confirm used to inform of the initiating device whether +/// its request to associate was successful or unsuccessful +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AssociateConfirm { + /// short address allocated by the coordinator on successful association + pub assoc_short_address: [u8; 2], + /// status of the association request + pub status: AssociationStatus, + /// security level to be used + pub security_level: SecurityLevel, + /// the originator of the key to be used + pub key_source: [u8; 8], + /// the mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 2], +} + +impl ParseableMacEvent for AssociateConfirm {} + +/// MLME DISASSOCIATE Confirm used to send disassociation Confirmation to the application. +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DisassociateConfirm { + /// status of the disassociation attempt + pub status: MacStatus, + /// device addressing mode used + pub device_addr_mode: AddressMode, + /// the identifier of the PAN of the device + pub device_pan_id: PanId, + /// device address + pub device_address: MacAddress, +} + +impl ParseableMacEvent for DisassociateConfirm {} + +/// MLME GET Confirm which requests information about a given PIB attribute +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GetConfirm { + /// The pointer to the value of the PIB attribute attempted to read + pub pib_attribute_value_ptr: *const u8, + /// Status of the GET attempt + pub status: MacStatus, + /// The name of the PIB attribute attempted to read + pub pib_attribute: PibId, + /// The lenght of the PIB attribute Value return + pub pib_attribute_value_len: u8, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 1], +} + +impl ParseableMacEvent for GetConfirm {} + +/// MLME GTS Confirm which eports the results of a request to allocate a new GTS +/// or to deallocate an existing GTS +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GtsConfirm { + /// The characteristics of the GTS + pub gts_characteristics: u8, + /// The status of the GTS reques + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 2], +} + +impl ParseableMacEvent for GtsConfirm {} + +/// MLME RESET Confirm which is used to report the results of the reset operation +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ResetConfirm { + /// The result of the reset operation + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for ResetConfirm {} + +/// MLME RX ENABLE Confirm which is used to report the results of the attempt +/// to enable or disable the receiver +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RxEnableConfirm { + /// Result of the request to enable or disable the receiver + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for RxEnableConfirm {} + +/// MLME SCAN Confirm which is used to report the result of the channel scan request +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ScanConfirm { + /// Status of the scan request + pub status: MacStatus, + /// The type of scan performed + pub scan_type: ScanType, + /// Channel page on which the scan was performed + pub channel_page: u8, + /// Channels given in the request which were not scanned + pub unscanned_channels: [u8; 4], + /// Number of elements returned in the appropriate result lists + pub result_list_size: u8, + /// List of energy measurements + pub energy_detect_list: [u8; MAX_ED_SCAN_RESULTS_SUPPORTED], + /// List of PAN descriptors + pub pan_descriptor_list: [PanDescriptor; MAX_PAN_DESC_SUPPORTED], + /// Categorization of energy detected in channel + pub detected_category: u8, + /// For UWB PHYs, the list of energy measurements taken + pub uwb_energy_detect_list: [u8; MAX_ED_SCAN_RESULTS_SUPPORTED], +} + +impl ParseableMacEvent for ScanConfirm {} + +/// MLME SET Confirm which reports the result of an attempt to write a value to a PIB attribute +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SetConfirm { + /// The result of the set operation + pub status: MacStatus, + /// The name of the PIB attribute that was written + pub pin_attribute: PibId, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 2], +} + +impl ParseableMacEvent for SetConfirm {} + +/// MLME START Confirm which is used to report the results of the attempt to +/// start using a new superframe configuration +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct StartConfirm { + /// Result of the attempt to start using an updated superframe configuration + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for StartConfirm {} + +/// MLME POLL Confirm which is used to report the result of a request to poll the coordinator for data +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PollConfirm { + /// The status of the data request + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for PollConfirm {} + +/// MLME DPS Confirm which reports the results of the attempt to enable or disable the DPS +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpsConfirm { + /// The status of the DPS request + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for DpsConfirm {} + +/// MLME SOUNDING Confirm which reports the result of a request to the PHY to provide +/// channel sounding information +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SoundingConfirm { + /// Results of the sounding measurement + pub sounding_list: [u8; MAX_SOUNDING_LIST_SUPPORTED], + + status: u8, +} + +impl ParseableMacEvent for SoundingConfirm {} + +/// MLME CALIBRATE Confirm which reports the result of a request to the PHY +/// to provide internal propagation path information +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CalibrateConfirm { + /// The status of the attempt to return sounding data + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], + /// A count of the propagation time from the ranging counter + /// to the transmit antenna + pub cal_tx_rmaker_offset: u32, + /// A count of the propagation time from the receive antenna + /// to the ranging counter + pub cal_rx_rmaker_offset: u32, +} + +impl ParseableMacEvent for CalibrateConfirm {} + +/// MCPS DATA Confirm which will be used for reporting the results of +/// MAC data related requests from the application +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DataConfirm { + /// The handle associated with the MSDU being confirmed + pub msdu_handle: u8, + /// The time, in symbols, at which the data were transmitted + pub time_stamp: [u8; 4], + /// ranging status + pub ranging_received: u8, + /// The status of the last MSDU transmission + pub status: MacStatus, + /// time units corresponding to an RMARKER at the antenna at + /// the beginning of a ranging exchange + pub ranging_counter_start: u32, + /// time units corresponding to an RMARKER at the antenna + /// at the end of a ranging exchange + pub ranging_counter_stop: u32, + /// time units in a message exchange over which the tracking offset was measured + pub ranging_tracking_interval: u32, + /// time units slipped or advanced by the radio tracking system + pub ranging_offset: u32, + /// The FoM characterizing the ranging measurement + pub ranging_fom: u8, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for DataConfirm {} + +/// MCPS PURGE Confirm which will be used by the MAC to notify the application of +/// the status of its request to purge an MSDU from the transaction queue +#[repr(C)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PurgeConfirm { + /// Handle associated with the MSDU requested to be purged from the transaction queue + pub msdu_handle: u8, + /// The status of the request + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 2], +} + +impl ParseableMacEvent for PurgeConfirm {} diff --git a/embassy/embassy-stm32-wpan/src/mac/runner.rs b/embassy/embassy-stm32-wpan/src/mac/runner.rs new file mode 100644 index 0000000..d3099b6 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/runner.rs @@ -0,0 +1,109 @@ +use core::cell::RefCell; + +use embassy_futures::join; +use embassy_sync::blocking_mutex; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; +use embassy_sync::channel::Channel; +use embassy_sync::mutex::Mutex; +use embassy_sync::signal::Signal; + +use crate::mac::commands::DataRequest; +use crate::mac::event::MacEvent; +use crate::mac::typedefs::{AddressMode, MacAddress, PanId, SecurityLevel}; +use crate::mac::MTU; +use crate::sub::mac::Mac; + +type ZeroCopyPubSub = blocking_mutex::Mutex>>>; + +pub struct Runner<'a> { + pub(crate) mac_subsystem: Mac, + // rx event backpressure is already provided through the MacEvent drop mechanism + // therefore, we don't need to worry about overwriting events + pub(crate) rx_event_channel: ZeroCopyPubSub>, + pub(crate) read_mutex: Mutex, + pub(crate) write_mutex: Mutex, + pub(crate) rx_channel: Channel, 1>, + pub(crate) tx_channel: Channel, + pub(crate) tx_buf_channel: Channel, +} + +impl<'a> Runner<'a> { + pub fn new(mac: Mac, tx_buf_queue: [&'a mut [u8; MTU]; 5]) -> Self { + let this = Self { + mac_subsystem: mac, + rx_event_channel: blocking_mutex::Mutex::new(RefCell::new(None)), + read_mutex: Mutex::new(()), + write_mutex: Mutex::new(()), + rx_channel: Channel::new(), + tx_channel: Channel::new(), + tx_buf_channel: Channel::new(), + }; + + for buf in tx_buf_queue { + this.tx_buf_channel.try_send(buf).unwrap(); + } + + this + } + + pub async fn run(&'a self) -> ! { + join::join( + async { + loop { + if let Ok(mac_event) = self.mac_subsystem.read().await { + match mac_event { + MacEvent::McpsDataInd(_) => { + self.rx_channel.send(mac_event).await; + } + _ => { + self.rx_event_channel.lock(|s| { + match &*s.borrow() { + Some(signal) => { + signal.signal(mac_event); + } + None => {} + }; + }); + } + } + } + } + }, + async { + let mut msdu_handle = 0x02; + + loop { + let (buf, len) = self.tx_channel.receive().await; + let _wm = self.write_mutex.lock().await; + + // The mutex should be dropped on the next loop iteration + self.mac_subsystem + .send_command( + DataRequest { + src_addr_mode: AddressMode::Short, + dst_addr_mode: AddressMode::Short, + dst_pan_id: PanId([0x1A, 0xAA]), + dst_address: MacAddress::BROADCAST, + msdu_handle: msdu_handle, + ack_tx: 0x00, + gts_tx: false, + security_level: SecurityLevel::Unsecure, + ..Default::default() + } + .set_buffer(&buf[..len]), + ) + .await + .unwrap(); + + msdu_handle = msdu_handle.wrapping_add(1); + + // The tx channel should always be of equal capacity to the tx_buf channel + self.tx_buf_channel.try_send(buf).unwrap(); + } + }, + ) + .await; + + loop {} + } +} diff --git a/embassy/embassy-stm32-wpan/src/mac/typedefs.rs b/embassy/embassy-stm32-wpan/src/mac/typedefs.rs new file mode 100644 index 0000000..0552b8e --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/mac/typedefs.rs @@ -0,0 +1,381 @@ +use core::fmt::Debug; + +use crate::numeric_enum; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MacError { + Error = 0x01, + NotImplemented = 0x02, + NotSupported = 0x03, + HardwareNotSupported = 0x04, + Undefined = 0x05, +} + +impl From for MacError { + fn from(value: u8) -> Self { + match value { + 0x01 => Self::Error, + 0x02 => Self::NotImplemented, + 0x03 => Self::NotSupported, + 0x04 => Self::HardwareNotSupported, + 0x05 => Self::Undefined, + _ => Self::Undefined, + } + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Debug, Default)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum MacStatus { + #[default] + Success = 0x00, + Failure = 0xFF + } +} + +numeric_enum! { + #[repr(u8)] + /// this enum contains all the MAC PIB Ids + #[derive(Default, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum PibId { + // PHY + #[default] + CurrentChannel = 0x00, + ChannelsSupported = 0x01, + TransmitPower = 0x02, + CCAMode = 0x03, + CurrentPage = 0x04, + MaxFrameDuration = 0x05, + SHRDuration = 0x06, + SymbolsPerOctet = 0x07, + + // MAC + AckWaitDuration = 0x40, + AssociationPermit = 0x41, + AutoRequest = 0x42, + BeaconPayload = 0x45, + BeaconPayloadLength = 0x46, + BeaconOrder = 0x47, + Bsn = 0x49, + CoordExtendedAdddress = 0x4A, + CoordShortAddress = 0x4B, + Dsn = 0x4C, + MaxFrameTotalWaitTime = 0x58, + MaxFrameRetries = 0x59, + PanId = 0x50, + ResponseWaitTime = 0x5A, + RxOnWhenIdle = 0x52, + SecurityEnabled = 0x5D, + ShortAddress = 0x53, + SuperframeOrder = 0x54, + TimestampSupported = 0x5C, + TransactionPersistenceTime = 0x55, + MaxBe = 0x57, + LifsPeriod = 0x5E, + SifsPeriod = 0x5F, + MaxCsmaBackoffs = 0x4E, + MinBe = 0x4F, + PanCoordinator = 0x10, + AssocPanCoordinator = 0x11, + ExtendedAddress = 0x6F, + AclEntryDescriptor = 0x70, + AclEntryDescriptorSize = 0x71, + DefaultSecurity = 0x72, + DefaultSecurityMaterialLength = 0x73, + DefaultSecurityMaterial = 0x74, + DefaultSecuritySuite = 0x75, + SecurityMode = 0x76, + CurrentAclEntries = 0x80, + DefaultSecurityExtendedAddress = 0x81, + AssociatedPanCoordinator = 0x56, + PromiscuousMode = 0x51, + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Default, Clone, Copy, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum AddressMode { + #[default] + NoAddress = 0x00, + Reserved = 0x01, + Short = 0x02, + Extended = 0x03, +} +} + +#[derive(Clone, Copy)] +pub union MacAddress { + pub short: [u8; 2], + pub extended: [u8; 8], +} + +impl Debug for MacAddress { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + unsafe { + write!( + fmt, + "MacAddress {{ short: {:?}, extended: {:?} }}", + self.short, self.extended + ) + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for MacAddress { + fn format(&self, fmt: defmt::Formatter) { + unsafe { + defmt::write!( + fmt, + "MacAddress {{ short: {}, extended: {} }}", + self.short, + self.extended + ) + } + } +} + +impl Default for MacAddress { + fn default() -> Self { + Self { short: [0, 0] } + } +} + +impl MacAddress { + pub const BROADCAST: Self = Self { short: [0xFF, 0xFF] }; +} + +impl TryFrom<&[u8]> for MacAddress { + type Error = (); + + fn try_from(buf: &[u8]) -> Result { + const SIZE: usize = 8; + if buf.len() < SIZE { + return Err(()); + } + + Ok(Self { + extended: [buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]], + }) + } +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GtsCharacteristics { + pub fields: u8, +} + +/// MAC PAN Descriptor which contains the network details of the device from +/// which the beacon is received +#[derive(Default, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PanDescriptor { + /// PAN identifier of the coordinator + pub coord_pan_id: PanId, + /// Coordinator addressing mode + pub coord_addr_mode: AddressMode, + /// The current logical channel occupied by the network + pub logical_channel: MacChannel, + /// Coordinator address + pub coord_addr: MacAddress, + /// The current channel page occupied by the network + pub channel_page: u8, + /// PAN coordinator is accepting GTS requests or not + pub gts_permit: bool, + /// Superframe specification as specified in the received beacon frame + pub superframe_spec: [u8; 2], + /// The time at which the beacon frame was received, in symbols + pub time_stamp: [u8; 4], + /// The LQI at which the network beacon was received + pub link_quality: u8, + /// Security level purportedly used by the received beacon frame + pub security_level: u8, +} + +impl TryFrom<&[u8]> for PanDescriptor { + type Error = (); + + fn try_from(buf: &[u8]) -> Result { + const SIZE: usize = 22; + if buf.len() < SIZE { + return Err(()); + } + + let coord_addr_mode = AddressMode::try_from(buf[2])?; + let coord_addr = match coord_addr_mode { + AddressMode::NoAddress => MacAddress { short: [0, 0] }, + AddressMode::Reserved => MacAddress { short: [0, 0] }, + AddressMode::Short => MacAddress { + short: [buf[4], buf[5]], + }, + AddressMode::Extended => MacAddress { + extended: [buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]], + }, + }; + + Ok(Self { + coord_pan_id: PanId([buf[0], buf[1]]), + coord_addr_mode, + logical_channel: MacChannel::try_from(buf[3])?, + coord_addr, + channel_page: buf[12], + gts_permit: buf[13] != 0, + superframe_spec: [buf[14], buf[15]], + time_stamp: [buf[16], buf[17], buf[18], buf[19]], + link_quality: buf[20], + security_level: buf[21], + // 2 byte stuffing + }) + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Default, Clone, Copy, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + /// Building wireless applications with STM32WB series MCUs - Application note 13.10.3 + pub enum MacChannel { + Channel11 = 0x0B, + Channel12 = 0x0C, + Channel13 = 0x0D, + Channel14 = 0x0E, + Channel15 = 0x0F, + #[default] + Channel16 = 0x10, + Channel17 = 0x11, + Channel18 = 0x12, + Channel19 = 0x13, + Channel20 = 0x14, + Channel21 = 0x15, + Channel22 = 0x16, + Channel23 = 0x17, + Channel24 = 0x18, + Channel25 = 0x19, + Channel26 = 0x1A, + } +} + +#[cfg(not(feature = "defmt"))] +bitflags::bitflags! { + pub struct Capabilities: u8 { + /// 1 if the device is capabaleof becoming a PAN coordinator + const IS_COORDINATOR_CAPABLE = 0b00000001; + /// 1 if the device is an FFD, 0 if it is an RFD + const IS_FFD = 0b00000010; + /// 1 if the device is receiving power from mains, 0 if it is battery-powered + const IS_MAINS_POWERED = 0b00000100; + /// 1 if the device does not disable its receiver to conserver power during idle periods + const RECEIVER_ON_WHEN_IDLE = 0b00001000; + // 0b00010000 reserved + // 0b00100000 reserved + /// 1 if the device is capable of sending and receiving secured MAC frames + const IS_SECURE = 0b01000000; + /// 1 if the device wishes the coordinator to allocate a short address as a result of the association + const ALLOCATE_ADDRESS = 0b10000000; + } +} + +#[cfg(feature = "defmt")] +defmt::bitflags! { + pub struct Capabilities: u8 { + /// 1 if the device is capabaleof becoming a PAN coordinator + const IS_COORDINATOR_CAPABLE = 0b00000001; + /// 1 if the device is an FFD, 0 if it is an RFD + const IS_FFD = 0b00000010; + /// 1 if the device is receiving power from mains, 0 if it is battery-powered + const IS_MAINS_POWERED = 0b00000100; + /// 1 if the device does not disable its receiver to conserver power during idle periods + const RECEIVER_ON_WHEN_IDLE = 0b00001000; + // 0b00010000 reserved + // 0b00100000 reserved + /// 1 if the device is capable of sending and receiving secured MAC frames + const IS_SECURE = 0b01000000; + /// 1 if the device wishes the coordinator to allocate a short address as a result of the association + const ALLOCATE_ADDRESS = 0b10000000; + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Default, Clone, Copy, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum KeyIdMode { + #[default] + /// the key is determined implicitly from the originator and recipient(s) of the frame + Implicite = 0x00, + /// the key is determined explicitly using a 1 bytes key source and a 1 byte key index + Explicite1Byte = 0x01, + /// the key is determined explicitly using a 4 bytes key source and a 1 byte key index + Explicite4Byte = 0x02, + /// the key is determined explicitly using a 8 bytes key source and a 1 byte key index + Explicite8Byte = 0x03, + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum AssociationStatus { + /// Association successful + Success = 0x00, + /// PAN at capacity + PanAtCapacity = 0x01, + /// PAN access denied + PanAccessDenied = 0x02 + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Clone, Copy, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum DisassociationReason { + /// The coordinator wishes the device to leave the PAN. + CoordRequested = 0x01, + /// The device wishes to leave the PAN. + DeviceRequested = 0x02, + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Default, Clone, Copy, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum SecurityLevel { + /// MAC Unsecured Mode Security + #[default] + Unsecure = 0x00, + /// MAC ACL Mode Security + AclMode = 0x01, + /// MAC Secured Mode Security + Secured = 0x02, + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ScanType { + EdScan = 0x00, + Active = 0x01, + Passive = 0x02, + Orphan = 0x03 + } +} + +/// newtype for Pan Id +#[derive(Default, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PanId(pub [u8; 2]); + +impl PanId { + pub const BROADCAST: Self = Self([0xFF, 0xFF]); +} diff --git a/embassy/embassy-stm32-wpan/src/shci.rs b/embassy/embassy-stm32-wpan/src/shci.rs new file mode 100644 index 0000000..30d6897 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/shci.rs @@ -0,0 +1,375 @@ +use core::{mem, slice}; + +use crate::consts::{TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE}; + +const SHCI_OGF: u16 = 0x3F; + +const fn opcode(ogf: u16, ocf: u16) -> isize { + ((ogf << 10) + ocf) as isize +} + +#[allow(dead_code)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SchiCommandStatus { + ShciSuccess = 0x00, + ShciUnknownCmd = 0x01, + ShciMemoryCapacityExceededErrCode = 0x07, + ShciErrUnsupportedFeature = 0x11, + ShciErrInvalidHciCmdParams = 0x12, + ShciErrInvalidParams = 0x42, /* only used for release < v1.13.0 */ + ShciErrInvalidParamsV2 = 0x92, /* available for release >= v1.13.0 */ + ShciFusCmdNotSupported = 0xFF, +} + +impl TryFrom for SchiCommandStatus { + type Error = (); + + fn try_from(v: u8) -> Result { + match v { + x if x == SchiCommandStatus::ShciSuccess as u8 => Ok(SchiCommandStatus::ShciSuccess), + x if x == SchiCommandStatus::ShciUnknownCmd as u8 => Ok(SchiCommandStatus::ShciUnknownCmd), + x if x == SchiCommandStatus::ShciMemoryCapacityExceededErrCode as u8 => { + Ok(SchiCommandStatus::ShciMemoryCapacityExceededErrCode) + } + x if x == SchiCommandStatus::ShciErrUnsupportedFeature as u8 => { + Ok(SchiCommandStatus::ShciErrUnsupportedFeature) + } + x if x == SchiCommandStatus::ShciErrInvalidHciCmdParams as u8 => { + Ok(SchiCommandStatus::ShciErrInvalidHciCmdParams) + } + x if x == SchiCommandStatus::ShciErrInvalidParams as u8 => Ok(SchiCommandStatus::ShciErrInvalidParams), /* only used for release < v1.13.0 */ + x if x == SchiCommandStatus::ShciErrInvalidParamsV2 as u8 => Ok(SchiCommandStatus::ShciErrInvalidParamsV2), /* available for release >= v1.13.0 */ + x if x == SchiCommandStatus::ShciFusCmdNotSupported as u8 => Ok(SchiCommandStatus::ShciFusCmdNotSupported), + _ => Err(()), + } + } +} + +#[allow(dead_code)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ShciOpcode { + // 0x50 reserved + // 0x51 reserved + FusGetState = opcode(SHCI_OGF, 0x52), + // 0x53 reserved + FusFirmwareUpgrade = opcode(SHCI_OGF, 0x54), + FusFirmwareDelete = opcode(SHCI_OGF, 0x55), + FusUpdateAuthKey = opcode(SHCI_OGF, 0x56), + FusLockAuthKey = opcode(SHCI_OGF, 0x57), + FusStoreUserKey = opcode(SHCI_OGF, 0x58), + FusLoadUserKey = opcode(SHCI_OGF, 0x59), + FusStartWirelessStack = opcode(SHCI_OGF, 0x5a), + // 0x5b reserved + // 0x5c reserved + FusLockUserKey = opcode(SHCI_OGF, 0x5d), + FusUnloadUserKey = opcode(SHCI_OGF, 0x5e), + FusActivateAntirollback = opcode(SHCI_OGF, 0x5f), + // 0x60 reserved + // 0x61 reserved + // 0x62 reserved + // 0x63 reserved + // 0x64 reserved + // 0x65 reserved + BleInit = opcode(SHCI_OGF, 0x66), + ThreadInit = opcode(SHCI_OGF, 0x67), + DebugInit = opcode(SHCI_OGF, 0x68), + FlashEraseActivity = opcode(SHCI_OGF, 0x69), + ConcurrentSetMode = opcode(SHCI_OGF, 0x6a), + FlashStoreData = opcode(SHCI_OGF, 0x6b), + FlashEraseData = opcode(SHCI_OGF, 0x6c), + RadioAllowLowPower = opcode(SHCI_OGF, 0x6d), + Mac802_15_4Init = opcode(SHCI_OGF, 0x6e), + ReInit = opcode(SHCI_OGF, 0x6f), + ZigbeeInit = opcode(SHCI_OGF, 0x70), + LldTestsInit = opcode(SHCI_OGF, 0x71), + ExtraConfig = opcode(SHCI_OGF, 0x72), + SetFlashActivityControl = opcode(SHCI_OGF, 0x73), + BleLldInit = opcode(SHCI_OGF, 0x74), + Config = opcode(SHCI_OGF, 0x75), + ConcurrentGetNextBleEvtTime = opcode(SHCI_OGF, 0x76), + ConcurrentEnableNext802_15_4EvtNotification = opcode(SHCI_OGF, 0x77), + Mac802_15_4DeInit = opcode(SHCI_OGF, 0x78), +} + +pub const SHCI_C2_CONFIG_EVTMASK1_BIT0_ERROR_NOTIF_ENABLE: u8 = 1 << 0; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE: u8 = 1 << 1; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT2_THREAD_NVM_RAM_UPDATE_ENABLE: u8 = 1 << 2; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT3_NVM_START_WRITE_ENABLE: u8 = 1 << 3; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT4_NVM_END_WRITE_ENABLE: u8 = 1 << 4; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT5_NVM_START_ERASE_ENABLE: u8 = 1 << 5; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT6_NVM_END_ERASE_ENABLE: u8 = 1 << 6; + +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct ShciConfigParam { + pub payload_cmd_size: u8, + pub config: u8, + pub event_mask: u8, + pub spare: u8, + pub ble_nvm_ram_address: u32, + pub thread_nvm_ram_address: u32, + pub revision_id: u16, + pub device_id: u16, +} + +impl ShciConfigParam { + pub fn payload<'a>(&'a self) -> &'a [u8] { + unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::()) } + } +} + +impl Default for ShciConfigParam { + fn default() -> Self { + Self { + payload_cmd_size: (mem::size_of::() - 1) as u8, + config: 0, + event_mask: SHCI_C2_CONFIG_EVTMASK1_BIT0_ERROR_NOTIF_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT2_THREAD_NVM_RAM_UPDATE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT3_NVM_START_WRITE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT4_NVM_END_WRITE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT5_NVM_START_ERASE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT6_NVM_END_ERASE_ENABLE, + spare: 0, + ble_nvm_ram_address: 0, + thread_nvm_ram_address: 0, + revision_id: 0, + device_id: 0, + } + } +} + +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct ShciBleInitCmdParam { + /// NOT USED - shall be set to 0 + pub p_ble_buffer_address: u32, + /// NOT USED - shall be set to 0 + pub ble_buffer_size: u32, + /// Maximum number of attribute records related to all the required characteristics (excluding the services) + /// that can be stored in the GATT database, for the specific BLE user application. + /// For each characteristic, the number of attribute records goes from two to five depending on the characteristic properties: + /// - minimum of two (one for declaration and one for the value) + /// - add one more record for each additional property: notify or indicate, broadcast, extended property. + /// The total calculated value must be increased by 9, due to the records related to the standard attribute profile and + /// GAP service characteristics, and automatically added when initializing GATT and GAP layers + /// - Min value: + 9 + /// - Max value: depending on the GATT database defined by user application + pub num_attr_record: u16, + /// Defines the maximum number of services that can be stored in the GATT database. Note that the GAP and GATT services + /// are automatically added at initialization so this parameter must be the number of user services increased by two. + /// - Min value: + 2 + /// - Max value: depending GATT database defined by user application + pub num_attr_serv: u16, + /// NOTE: This parameter is ignored by the CPU2 when the parameter "Options" is set to "LL_only" ( see Options description in that structure ) + /// + /// Size of the storage area for the attribute values. + /// Each characteristic contributes to the attrValueArrSize value as follows: + /// - Characteristic value length plus: + /// + 5 bytes if characteristic UUID is 16 bits + /// + 19 bytes if characteristic UUID is 128 bits + /// + 2 bytes if characteristic has a server configuration descriptor + /// + 2 bytes * NumOfLinks if the characteristic has a client configuration descriptor + /// + 2 bytes if the characteristic has extended properties + /// Each descriptor contributes to the attrValueArrSize value as follows: + /// - Descriptor length + pub attr_value_arr_size: u16, + /// Maximum number of BLE links supported + /// - Min value: 1 + /// - Max value: 8 + pub num_of_links: u8, + /// Disable/enable the extended packet length BLE 5.0 feature + /// - Disable: 0 + /// - Enable: 1 + pub extended_packet_length_enable: u8, + /// NOTE: This parameter is ignored by the CPU2 when the parameter "Options" is set to "LL_only" ( see Options description in that structure ) + /// + /// Maximum number of supported "prepare write request" + /// - Min value: given by the macro DEFAULT_PREP_WRITE_LIST_SIZE + /// - Max value: a value higher than the minimum required can be specified, but it is not recommended + pub prepare_write_list_size: u8, + /// NOTE: This parameter is overwritten by the CPU2 with an hardcoded optimal value when the parameter "Options" is set to "LL_only" + /// ( see Options description in that structure ) + /// + /// Number of allocated memory blocks for the BLE stack + /// - Min value: given by the macro MBLOCKS_CALC + /// - Max value: a higher value can improve data throughput performance, but uses more memory + pub block_count: u8, + /// NOTE: This parameter is ignored by the CPU2 when the parameter "Options" is set to "LL_only" ( see Options description in that structure ) + /// + /// Maximum ATT MTU size supported + /// - Min value: 23 + /// - Max value: 512 + pub att_mtu: u16, + /// The sleep clock accuracy (ppm value) that used in BLE connected slave mode to calculate the window widening + /// (in combination with the sleep clock accuracy sent by master in CONNECT_REQ PDU), + /// refer to BLE 5.0 specifications - Vol 6 - Part B - chap 4.5.7 and 4.2.2 + /// - Min value: 0 + /// - Max value: 500 (worst possible admitted by specification) + pub slave_sca: u16, + /// The sleep clock accuracy handled in master mode. It is used to determine the connection and advertising events timing. + /// It is transmitted to the slave in CONNEC_REQ PDU used by the slave to calculate the window widening, + /// see SlaveSca and Bluetooth Core Specification v5.0 Vol 6 - Part B - chap 4.5.7 and 4.2.2 + /// Possible values: + /// - 251 ppm to 500 ppm: 0 + /// - 151 ppm to 250 ppm: 1 + /// - 101 ppm to 150 ppm: 2 + /// - 76 ppm to 100 ppm: 3 + /// - 51 ppm to 75 ppm: 4 + /// - 31 ppm to 50 ppm: 5 + /// - 21 ppm to 30 ppm: 6 + /// - 0 ppm to 20 ppm: 7 + pub master_sca: u8, + /// Some information for Low speed clock mapped in bits field + /// - bit 0: + /// - 1: Calibration for the RF system wakeup clock source + /// - 0: No calibration for the RF system wakeup clock source + /// - bit 1: + /// - 1: STM32W5M Module device + /// - 0: Other devices as STM32WBxx SOC, STM32WB1M module + /// - bit 2: + /// - 1: HSE/1024 Clock config + /// - 0: LSE Clock config + pub ls_source: u8, + /// This parameter determines the maximum duration of a slave connection event. When this duration is reached the slave closes + /// the current connections event (whatever is the CE_length parameter specified by the master in HCI_CREATE_CONNECTION HCI command), + /// expressed in units of 625/256 µs (~2.44 µs) + /// - Min value: 0 (if 0 is specified, the master and slave perform only a single TX-RX exchange per connection event) + /// - Max value: 1638400 (4000 ms). A higher value can be specified (max 0xFFFFFFFF) but results in a maximum connection time + /// of 4000 ms as specified. In this case the parameter is not applied, and the predicted CE length calculated on slave is not shortened + pub max_conn_event_length: u32, + /// Startup time of the high speed (16 or 32 MHz) crystal oscillator in units of 625/256 µs (~2.44 µs). + /// - Min value: 0 + /// - Max value: 820 (~2 ms). A higher value can be specified, but the value that implemented in stack is forced to ~2 ms + pub hs_startup_time: u16, + /// Viterbi implementation in BLE LL reception. + /// - 0: Enable + /// - 1: Disable + pub viterbi_enable: u8, + /// - bit 0: + /// - 1: LL only + /// - 0: LL + host + /// - bit 1: + /// - 1: no service change desc. + /// - 0: with service change desc. + /// - bit 2: + /// - 1: device name Read-Only + /// - 0: device name R/W + /// - bit 3: + /// - 1: extended advertizing supported + /// - 0: extended advertizing not supported + /// - bit 4: + /// - 1: CS Algo #2 supported + /// - 0: CS Algo #2 not supported + /// - bit 5: + /// - 1: Reduced GATT database in NVM + /// - 0: Full GATT database in NVM + /// - bit 6: + /// - 1: GATT caching is used + /// - 0: GATT caching is not used + /// - bit 7: + /// - 1: LE Power Class 1 + /// - 0: LE Power Classe 2-3 + /// - other bits: complete with Options_extension flag + pub options: u8, + /// Reserved for future use - shall be set to 0 + pub hw_version: u8, + // /** + // * Maximum number of connection-oriented channels in initiator mode. + // * Range: 0 .. 64 + // */ + // pub max_coc_initiator_nbr: u8, + // + // /** + // * Minimum transmit power in dBm supported by the Controller. + // * Range: -127 .. 20 + // */ + // pub min_tx_power: i8, + // + // /** + // * Maximum transmit power in dBm supported by the Controller. + // * Range: -127 .. 20 + // */ + // pub max_tx_power: i8, + // + // /** + // * RX model configuration + // * - bit 0: 1: agc_rssi model improved vs RF blockers 0: Legacy agc_rssi model + // * - other bits: reserved ( shall be set to 0) + // */ + // pub rx_model_config: u8, + // + // /* Maximum number of advertising sets. + // * Range: 1 .. 8 with limitation: + // * This parameter is linked to max_adv_data_len such as both compliant with allocated Total memory computed with BLE_EXT_ADV_BUFFER_SIZE based + // * on Max Extended advertising configuration supported. + // * This parameter is considered by the CPU2 when Options has SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV flag set + // */ + // pub max_adv_set_nbr: u8, + // + // /* Maximum advertising data length (in bytes) + // * Range: 31 .. 1650 with limitation: + // * This parameter is linked to max_adv_set_nbr such as both compliant with allocated Total memory computed with BLE_EXT_ADV_BUFFER_SIZE based + // * on Max Extended advertising configuration supported. + // * This parameter is considered by the CPU2 when Options has SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV flag set + // */ + // pub max_adv_data_len: u16, + // + // /* RF TX Path Compensation Value (16-bit signed integer). Units: 0.1 dB. + // * Range: -1280 .. 1280 + // */ + // pub tx_path_compens: i16, + // + // /* RF RX Path Compensation Value (16-bit signed integer). Units: 0.1 dB. + // * Range: -1280 .. 1280 + // */ + // pub rx_path_compens: i16, + // + // /* BLE core specification version (8-bit unsigned integer). + // * values as: 11(5.2), 12(5.3) + // */ + // pub ble_core_version: u8, + // + // /** + // * Options flags extension + // * - bit 0: 1: appearance Writable 0: appearance Read-Only + // * - bit 1: 1: Enhanced ATT supported 0: Enhanced ATT not supported + // * - other bits: reserved ( shall be set to 0) + // */ + // pub options_extension: u8, +} + +impl ShciBleInitCmdParam { + pub fn payload<'a>(&'a self) -> &'a [u8] { + unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::()) } + } +} + +impl Default for ShciBleInitCmdParam { + fn default() -> Self { + Self { + p_ble_buffer_address: 0, + ble_buffer_size: 0, + num_attr_record: 68, + num_attr_serv: 8, + attr_value_arr_size: 1344, + num_of_links: 2, + extended_packet_length_enable: 1, + prepare_write_list_size: 0x3A, + block_count: 0x79, + att_mtu: 156, + slave_sca: 500, + master_sca: 0, + ls_source: 1, + max_conn_event_length: 0xFFFFFFFF, + hs_startup_time: 0x148, + viterbi_enable: 1, + options: 0, + hw_version: 0, + } + } +} + +pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE; +#[allow(dead_code)] // Not used currently but reserved +const TL_BLE_EVT_CS_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_BLE_EVT_CS_PACKET_SIZE; diff --git a/embassy/embassy-stm32-wpan/src/sub/ble.rs b/embassy/embassy-stm32-wpan/src/sub/ble.rs new file mode 100644 index 0000000..c5f2334 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/sub/ble.rs @@ -0,0 +1,95 @@ +use core::ptr; + +use embassy_stm32::ipcc::Ipcc; +use hci::Opcode; + +use crate::cmd::CmdPacket; +use crate::consts::{TlPacketType, TL_BLEEVT_CC_OPCODE, TL_BLEEVT_CS_OPCODE}; +use crate::evt::{EvtBox, EvtPacket, EvtStub}; +use crate::sub::mm; +use crate::tables::{BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE}; +use crate::unsafe_linked_list::LinkedListNode; +use crate::{channels, evt}; + +pub struct Ble { + _private: (), +} + +impl Ble { + pub(crate) fn new() -> Self { + unsafe { + LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr()); + + TL_BLE_TABLE.as_mut_ptr().write_volatile(BleTable { + pcmd_buffer: BLE_CMD_BUFFER.as_mut_ptr().cast(), + pcs_buffer: CS_BUFFER.as_ptr().cast(), + pevt_queue: EVT_QUEUE.as_ptr().cast(), + phci_acl_data_buffer: HCI_ACL_DATA_BUFFER.as_mut_ptr().cast(), + }); + } + + Self { _private: () } + } + /// `HW_IPCC_BLE_EvtNot` + pub async fn tl_read(&self) -> EvtBox { + Ipcc::receive(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, || unsafe { + if let Some(node_ptr) = LinkedListNode::remove_head(EVT_QUEUE.as_mut_ptr()) { + Some(EvtBox::new(node_ptr.cast())) + } else { + None + } + }) + .await + } + + /// `TL_BLE_SendCmd` + pub async fn tl_write(&self, opcode: u16, payload: &[u8]) { + Ipcc::send(channels::cpu1::IPCC_BLE_CMD_CHANNEL, || unsafe { + CmdPacket::write_into(BLE_CMD_BUFFER.as_mut_ptr(), TlPacketType::BleCmd, opcode, payload); + }) + .await; + } + + /// `TL_BLE_SendAclData` + pub async fn acl_write(&self, handle: u16, payload: &[u8]) { + Ipcc::send(channels::cpu1::IPCC_HCI_ACL_DATA_CHANNEL, || unsafe { + CmdPacket::write_into( + HCI_ACL_DATA_BUFFER.as_mut_ptr() as *mut _, + TlPacketType::AclData, + handle, + payload, + ); + }) + .await; + } +} + +impl evt::MemoryManager for Ble { + /// SAFETY: passing a pointer to something other than a managed event packet is UB + unsafe fn drop_event_packet(evt: *mut EvtPacket) { + let stub = unsafe { + let p_evt_stub = &(*evt).evt_serial as *const _ as *const EvtStub; + + ptr::read_volatile(p_evt_stub) + }; + + if !(stub.evt_code == TL_BLEEVT_CS_OPCODE || stub.evt_code == TL_BLEEVT_CC_OPCODE) { + mm::MemoryManager::drop_event_packet(evt); + } + } +} + +pub extern crate stm32wb_hci as hci; + +impl hci::Controller for Ble { + async fn controller_write(&mut self, opcode: Opcode, payload: &[u8]) { + self.tl_write(opcode.0, payload).await; + } + + async fn controller_read_into(&self, buf: &mut [u8]) { + let evt_box = self.tl_read().await; + let evt_serial = evt_box.serial(); + + buf[..evt_serial.len()].copy_from_slice(evt_serial); + } +} diff --git a/embassy/embassy-stm32-wpan/src/sub/mac.rs b/embassy/embassy-stm32-wpan/src/sub/mac.rs new file mode 100644 index 0000000..baf4da9 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/sub/mac.rs @@ -0,0 +1,124 @@ +use core::future::poll_fn; +use core::ptr; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_futures::poll_once; +use embassy_stm32::ipcc::Ipcc; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::cmd::CmdPacket; +use crate::consts::TlPacketType; +use crate::evt::{EvtBox, EvtPacket}; +use crate::mac::commands::MacCommand; +use crate::mac::event::MacEvent; +use crate::mac::typedefs::MacError; +use crate::tables::{MAC_802_15_4_CMD_BUFFER, MAC_802_15_4_NOTIF_RSP_EVT_BUFFER}; +use crate::{channels, evt}; + +static MAC_WAKER: AtomicWaker = AtomicWaker::new(); +static MAC_EVT_OUT: AtomicBool = AtomicBool::new(false); + +pub struct Mac { + _private: (), +} + +impl Mac { + pub(crate) fn new() -> Self { + Self { _private: () } + } + + /// `HW_IPCC_MAC_802_15_4_EvtNot` + /// + /// This function will stall if the previous `EvtBox` has not been dropped + pub async fn tl_read(&self) -> EvtBox { + // Wait for the last event box to be dropped + poll_fn(|cx| { + MAC_WAKER.register(cx.waker()); + if MAC_EVT_OUT.load(Ordering::SeqCst) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + + // Return a new event box + Ipcc::receive(channels::cpu2::IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL, || unsafe { + // The closure is not async, therefore the closure must execute to completion (cannot be dropped) + // Therefore, the event box is guaranteed to be cleaned up if it's not leaked + MAC_EVT_OUT.store(true, Ordering::SeqCst); + + Some(EvtBox::new(MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr() as *mut _)) + }) + .await + } + + /// `HW_IPCC_MAC_802_15_4_CmdEvtNot` + pub async fn tl_write_and_get_response(&self, opcode: u16, payload: &[u8]) -> u8 { + self.tl_write(opcode, payload).await; + Ipcc::flush(channels::cpu1::IPCC_MAC_802_15_4_CMD_RSP_CHANNEL).await; + + unsafe { + let p_event_packet = MAC_802_15_4_CMD_BUFFER.as_ptr() as *const EvtPacket; + let p_mac_rsp_evt = &((*p_event_packet).evt_serial.evt.payload) as *const u8; + + ptr::read_volatile(p_mac_rsp_evt) + } + } + + /// `TL_MAC_802_15_4_SendCmd` + pub async fn tl_write(&self, opcode: u16, payload: &[u8]) { + Ipcc::send(channels::cpu1::IPCC_MAC_802_15_4_CMD_RSP_CHANNEL, || unsafe { + CmdPacket::write_into( + MAC_802_15_4_CMD_BUFFER.as_mut_ptr(), + TlPacketType::MacCmd, + opcode, + payload, + ); + }) + .await; + } + + pub async fn send_command(&self, cmd: &T) -> Result<(), MacError> + where + T: MacCommand, + { + let response = self.tl_write_and_get_response(T::OPCODE as u16, cmd.payload()).await; + + if response == 0x00 { + Ok(()) + } else { + Err(MacError::from(response)) + } + } + + pub async fn read(&self) -> Result, ()> { + MacEvent::new(self.tl_read().await) + } +} + +impl evt::MemoryManager for Mac { + /// SAFETY: passing a pointer to something other than a managed event packet is UB + unsafe fn drop_event_packet(_: *mut EvtPacket) { + trace!("mac drop event"); + + // Write the ack + CmdPacket::write_into( + MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr() as *mut _, + TlPacketType::OtAck, + 0, + &[], + ); + + // Clear the rx flag + let _ = poll_once(Ipcc::receive::<()>( + channels::cpu2::IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL, + || None, + )); + + // Allow a new read call + MAC_EVT_OUT.store(false, Ordering::SeqCst); + MAC_WAKER.wake(); + } +} diff --git a/embassy/embassy-stm32-wpan/src/sub/mm.rs b/embassy/embassy-stm32-wpan/src/sub/mm.rs new file mode 100644 index 0000000..4e4d2f8 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/sub/mm.rs @@ -0,0 +1,83 @@ +//! Memory manager routines +use core::future::poll_fn; +use core::mem::MaybeUninit; +use core::task::Poll; + +use aligned::{Aligned, A4}; +use cortex_m::interrupt; +use embassy_stm32::ipcc::Ipcc; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::consts::POOL_SIZE; +use crate::evt::EvtPacket; +#[cfg(feature = "ble")] +use crate::tables::BLE_SPARE_EVT_BUF; +use crate::tables::{MemManagerTable, EVT_POOL, FREE_BUF_QUEUE, SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE}; +use crate::unsafe_linked_list::LinkedListNode; +use crate::{channels, evt}; + +static MM_WAKER: AtomicWaker = AtomicWaker::new(); +static mut LOCAL_FREE_BUF_QUEUE: Aligned> = Aligned(MaybeUninit::uninit()); + +pub struct MemoryManager { + _private: (), +} + +impl MemoryManager { + pub(crate) fn new() -> Self { + unsafe { + LinkedListNode::init_head(FREE_BUF_QUEUE.as_mut_ptr()); + LinkedListNode::init_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr()); + + TL_MEM_MANAGER_TABLE.as_mut_ptr().write_volatile(MemManagerTable { + #[cfg(feature = "ble")] + spare_ble_buffer: BLE_SPARE_EVT_BUF.as_ptr().cast(), + #[cfg(not(feature = "ble"))] + spare_ble_buffer: core::ptr::null(), + spare_sys_buffer: SYS_SPARE_EVT_BUF.as_ptr().cast(), + blepool: EVT_POOL.as_ptr().cast(), + blepoolsize: POOL_SIZE as u32, + pevt_free_buffer_queue: FREE_BUF_QUEUE.as_mut_ptr(), + traces_evt_pool: core::ptr::null(), + tracespoolsize: 0, + }); + } + + Self { _private: () } + } + + pub async fn run_queue(&self) { + loop { + poll_fn(|cx| unsafe { + MM_WAKER.register(cx.waker()); + if LinkedListNode::is_empty(LOCAL_FREE_BUF_QUEUE.as_mut_ptr()) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + + Ipcc::send(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, || { + interrupt::free(|_| unsafe { + // CS required while moving nodes + while let Some(node_ptr) = LinkedListNode::remove_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr()) { + LinkedListNode::insert_head(FREE_BUF_QUEUE.as_mut_ptr(), node_ptr); + } + }) + }) + .await; + } + } +} + +impl evt::MemoryManager for MemoryManager { + /// SAFETY: passing a pointer to something other than a managed event packet is UB + unsafe fn drop_event_packet(evt: *mut EvtPacket) { + interrupt::free(|_| unsafe { + LinkedListNode::insert_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), evt as *mut _); + }); + + MM_WAKER.wake(); + } +} diff --git a/embassy/embassy-stm32-wpan/src/sub/mod.rs b/embassy/embassy-stm32-wpan/src/sub/mod.rs new file mode 100644 index 0000000..bee3dbd --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/sub/mod.rs @@ -0,0 +1,6 @@ +#[cfg(feature = "ble")] +pub mod ble; +#[cfg(feature = "mac")] +pub mod mac; +pub mod mm; +pub mod sys; diff --git a/embassy/embassy-stm32-wpan/src/sub/sys.rs b/embassy/embassy-stm32-wpan/src/sub/sys.rs new file mode 100644 index 0000000..bd2ea3f --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/sub/sys.rs @@ -0,0 +1,105 @@ +use core::ptr; + +use crate::cmd::CmdPacket; +use crate::consts::TlPacketType; +use crate::evt::{CcEvt, EvtBox, EvtPacket}; +#[allow(unused_imports)] +use crate::shci::{SchiCommandStatus, ShciBleInitCmdParam, ShciOpcode}; +use crate::sub::mm; +use crate::tables::{SysTable, WirelessFwInfoTable}; +use crate::unsafe_linked_list::LinkedListNode; +use crate::{channels, Ipcc, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_DEVICE_INFO_TABLE, TL_SYS_TABLE}; + +pub struct Sys { + _private: (), +} + +impl Sys { + /// TL_Sys_Init + pub(crate) fn new() -> Self { + unsafe { + LinkedListNode::init_head(SYSTEM_EVT_QUEUE.as_mut_ptr()); + + TL_SYS_TABLE.as_mut_ptr().write_volatile(SysTable { + pcmd_buffer: SYS_CMD_BUF.as_mut_ptr(), + sys_queue: SYSTEM_EVT_QUEUE.as_ptr(), + }); + } + + Self { _private: () } + } + + /// Returns CPU2 wireless firmware information (if present). + pub fn wireless_fw_info(&self) -> Option { + let info = unsafe { TL_DEVICE_INFO_TABLE.as_mut_ptr().read_volatile().wireless_fw_info_table }; + + // Zero version indicates that CPU2 wasn't active and didn't fill the information table + if info.version != 0 { + Some(info) + } else { + None + } + } + + pub async fn write(&self, opcode: ShciOpcode, payload: &[u8]) { + Ipcc::send(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, || unsafe { + CmdPacket::write_into(SYS_CMD_BUF.as_mut_ptr(), TlPacketType::SysCmd, opcode as u16, payload); + }) + .await; + } + + /// `HW_IPCC_SYS_CmdEvtNot` + pub async fn write_and_get_response(&self, opcode: ShciOpcode, payload: &[u8]) -> Result { + self.write(opcode, payload).await; + Ipcc::flush(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL).await; + + unsafe { + let p_event_packet = SYS_CMD_BUF.as_ptr() as *const EvtPacket; + let p_command_event = &((*p_event_packet).evt_serial.evt.payload) as *const _ as *const CcEvt; + let p_payload = &((*p_command_event).payload) as *const u8; + + ptr::read_volatile(p_payload).try_into() + } + } + + #[cfg(feature = "mac")] + pub async fn shci_c2_mac_802_15_4_init(&self) -> Result { + use crate::tables::{ + Mac802_15_4Table, TracesTable, MAC_802_15_4_CMD_BUFFER, MAC_802_15_4_NOTIF_RSP_EVT_BUFFER, + TL_MAC_802_15_4_TABLE, TL_TRACES_TABLE, TRACES_EVT_QUEUE, + }; + + unsafe { + LinkedListNode::init_head(TRACES_EVT_QUEUE.as_mut_ptr() as *mut _); + + TL_TRACES_TABLE.as_mut_ptr().write_volatile(TracesTable { + traces_queue: TRACES_EVT_QUEUE.as_ptr() as *const _, + }); + + TL_MAC_802_15_4_TABLE.as_mut_ptr().write_volatile(Mac802_15_4Table { + p_cmdrsp_buffer: MAC_802_15_4_CMD_BUFFER.as_mut_ptr().cast(), + p_notack_buffer: MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr().cast(), + evt_queue: core::ptr::null_mut(), + }); + }; + + self.write_and_get_response(ShciOpcode::Mac802_15_4Init, &[]).await + } + + #[cfg(feature = "ble")] + pub async fn shci_c2_ble_init(&self, param: ShciBleInitCmdParam) -> Result { + self.write_and_get_response(ShciOpcode::BleInit, param.payload()).await + } + + /// `HW_IPCC_SYS_EvtNot` + pub async fn read(&self) -> EvtBox { + Ipcc::receive(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, || unsafe { + if let Some(node_ptr) = LinkedListNode::remove_head(SYSTEM_EVT_QUEUE.as_mut_ptr()) { + Some(EvtBox::new(node_ptr.cast())) + } else { + None + } + }) + .await + } +} diff --git a/embassy/embassy-stm32-wpan/src/tables.rs b/embassy/embassy-stm32-wpan/src/tables.rs new file mode 100644 index 0000000..f2c2505 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/tables.rs @@ -0,0 +1,276 @@ +use core::mem::MaybeUninit; + +use aligned::{Aligned, A4}; +use bit_field::BitField; + +use crate::cmd::{AclDataPacket, CmdPacket}; +#[cfg(feature = "mac")] +use crate::consts::C_SIZE_CMD_STRING; +use crate::consts::{POOL_SIZE, TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE}; +use crate::unsafe_linked_list::LinkedListNode; + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct SafeBootInfoTable { + version: u32, +} + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct RssInfoTable { + pub version: u32, + pub memory_size: u32, + pub rss_info: u32, +} + +/** + * Version + * [0:3] = Build - 0: Untracked - 15:Released - x: Tracked version + * [4:7] = branch - 0: Mass Market - x: ... + * [8:15] = Subversion + * [16:23] = Version minor + * [24:31] = Version major + * + * Memory Size + * [0:7] = Flash ( Number of 4k sector) + * [8:15] = Reserved ( Shall be set to 0 - may be used as flash extension ) + * [16:23] = SRAM2b ( Number of 1k sector) + * [24:31] = SRAM2a ( Number of 1k sector) + */ +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct WirelessFwInfoTable { + pub version: u32, + pub memory_size: u32, + pub thread_info: u32, + pub ble_info: u32, +} + +impl WirelessFwInfoTable { + pub fn version_major(&self) -> u8 { + let version = self.version; + (version.get_bits(24..31) & 0xff) as u8 + } + + pub fn version_minor(&self) -> u8 { + let version = self.version; + (version.clone().get_bits(16..23) & 0xff) as u8 + } + + pub fn subversion(&self) -> u8 { + let version = self.version; + (version.clone().get_bits(8..15) & 0xff) as u8 + } + + /// Size of FLASH, expressed in number of 4K sectors. + pub fn flash_size(&self) -> u8 { + let memory_size = self.memory_size; + (memory_size.clone().get_bits(0..7) & 0xff) as u8 + } + + /// Size of SRAM2a, expressed in number of 1K sectors. + pub fn sram2a_size(&self) -> u8 { + let memory_size = self.memory_size; + (memory_size.clone().get_bits(24..31) & 0xff) as u8 + } + + /// Size of SRAM2b, expressed in number of 1K sectors. + pub fn sram2b_size(&self) -> u8 { + let memory_size = self.memory_size; + (memory_size.clone().get_bits(16..23) & 0xff) as u8 + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct DeviceInfoTable { + pub safe_boot_info_table: SafeBootInfoTable, + pub rss_info_table: RssInfoTable, + pub wireless_fw_info_table: WirelessFwInfoTable, +} + +#[derive(Debug)] +#[repr(C)] +pub struct BleTable { + pub pcmd_buffer: *mut CmdPacket, + pub pcs_buffer: *const u8, + pub pevt_queue: *const u8, + pub phci_acl_data_buffer: *mut AclDataPacket, +} + +#[derive(Debug)] +#[repr(C)] +pub struct ThreadTable { + pub nostack_buffer: *const u8, + pub clicmdrsp_buffer: *const u8, + pub otcmdrsp_buffer: *const u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct LldTestsTable { + pub clicmdrsp_buffer: *const u8, + pub m0cmd_buffer: *const u8, +} + +// TODO: use later +#[derive(Debug)] +#[repr(C)] +pub struct BleLldTable { + pub cmdrsp_buffer: *const u8, + pub m0cmd_buffer: *const u8, +} + +// TODO: use later +#[derive(Debug)] +#[repr(C)] +pub struct ZigbeeTable { + pub notif_m0_to_m4_buffer: *const u8, + pub appli_cmd_m4_to_m0_bufer: *const u8, + pub request_m0_to_m4_buffer: *const u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct SysTable { + pub pcmd_buffer: *mut CmdPacket, + pub sys_queue: *const LinkedListNode, +} + +#[derive(Debug)] +#[repr(C)] +pub struct MemManagerTable { + pub spare_ble_buffer: *const u8, + pub spare_sys_buffer: *const u8, + + pub blepool: *const u8, + pub blepoolsize: u32, + + pub pevt_free_buffer_queue: *mut LinkedListNode, + + pub traces_evt_pool: *const u8, + pub tracespoolsize: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct TracesTable { + pub traces_queue: *const u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Mac802_15_4Table { + pub p_cmdrsp_buffer: *const u8, + pub p_notack_buffer: *const u8, + pub evt_queue: *const u8, +} + +/// Reference table. Contains pointers to all other tables. +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct RefTable { + pub device_info_table: *const DeviceInfoTable, + pub ble_table: *const BleTable, + pub thread_table: *const ThreadTable, + pub sys_table: *const SysTable, + pub mem_manager_table: *const MemManagerTable, + pub traces_table: *const TracesTable, + pub mac_802_15_4_table: *const Mac802_15_4Table, + pub zigbee_table: *const ZigbeeTable, + pub lld_tests_table: *const LldTestsTable, + pub ble_lld_table: *const BleLldTable, +} + +// --------------------- ref table --------------------- +#[link_section = "TL_REF_TABLE"] +pub static mut TL_REF_TABLE: MaybeUninit = MaybeUninit::uninit(); + +#[link_section = "MB_MEM1"] +pub static mut TL_DEVICE_INFO_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_BLE_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_THREAD_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_LLD_TESTS_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_BLE_LLD_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_SYS_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_MEM_MANAGER_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_TRACES_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_MAC_802_15_4_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_ZIGBEE_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +// --------------------- tables --------------------- +#[link_section = "MB_MEM1"] +pub static mut FREE_BUF_QUEUE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[allow(dead_code)] +#[link_section = "MB_MEM1"] +pub static mut TRACES_EVT_QUEUE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM2"] +pub static mut CS_BUFFER: Aligned> = + Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM2"] +pub static mut EVT_QUEUE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM2"] +pub static mut SYSTEM_EVT_QUEUE: Aligned> = Aligned(MaybeUninit::uninit()); + +// --------------------- app tables --------------------- +#[cfg(feature = "mac")] +#[link_section = "MB_MEM2"] +pub static mut MAC_802_15_4_CMD_BUFFER: Aligned> = Aligned(MaybeUninit::uninit()); + +#[cfg(feature = "mac")] +#[link_section = "MB_MEM2"] +pub static mut MAC_802_15_4_NOTIF_RSP_EVT_BUFFER: MaybeUninit< + Aligned, +> = MaybeUninit::uninit(); + +#[link_section = "MB_MEM2"] +pub static mut EVT_POOL: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM2"] +pub static mut SYS_CMD_BUF: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM2"] +pub static mut SYS_SPARE_EVT_BUF: Aligned> = + Aligned(MaybeUninit::uninit()); + +#[cfg(feature = "mac")] +#[link_section = "MB_MEM2"] +pub static mut MAC_802_15_4_CNFINDNOT: Aligned> = + Aligned(MaybeUninit::uninit()); + +#[cfg(feature = "ble")] +#[link_section = "MB_MEM1"] +pub static mut BLE_CMD_BUFFER: Aligned> = Aligned(MaybeUninit::uninit()); + +#[cfg(feature = "ble")] +#[link_section = "MB_MEM2"] +pub static mut BLE_SPARE_EVT_BUF: Aligned> = + Aligned(MaybeUninit::uninit()); + +#[cfg(feature = "ble")] +#[link_section = "MB_MEM2"] +// fuck these "magic" numbers from ST ---v---v +pub static mut HCI_ACL_DATA_BUFFER: Aligned> = + Aligned(MaybeUninit::uninit()); diff --git a/embassy/embassy-stm32-wpan/src/unsafe_linked_list.rs b/embassy/embassy-stm32-wpan/src/unsafe_linked_list.rs new file mode 100644 index 0000000..d8bc297 --- /dev/null +++ b/embassy/embassy-stm32-wpan/src/unsafe_linked_list.rs @@ -0,0 +1,257 @@ +//! Unsafe linked list. +//! Translated from ST's C by `c2rust` tool. + +#![allow( + dead_code, + mutable_transmutes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused_assignments, + unused_mut +)] + +use core::ptr; + +use cortex_m::interrupt; + +#[derive(Copy, Clone)] +#[repr(C, packed(4))] +pub struct LinkedListNode { + pub next: *mut LinkedListNode, + pub prev: *mut LinkedListNode, +} + +impl Default for LinkedListNode { + fn default() -> Self { + LinkedListNode { + next: core::ptr::null_mut(), + prev: core::ptr::null_mut(), + } + } +} + +impl LinkedListNode { + pub unsafe fn init_head(mut p_list_head: *mut LinkedListNode) { + ptr::write_volatile( + p_list_head, + LinkedListNode { + next: p_list_head, + prev: p_list_head, + }, + ); + } + + pub unsafe fn is_empty(mut p_list_head: *mut LinkedListNode) -> bool { + interrupt::free(|_| ptr::read_volatile(p_list_head).next == p_list_head) + } + + /// Insert `node` after `list_head` and before the next node + pub unsafe fn insert_head(mut p_list_head: *mut LinkedListNode, mut p_node: *mut LinkedListNode) { + interrupt::free(|_| { + let mut list_head = ptr::read_volatile(p_list_head); + if p_list_head != list_head.next { + let mut node_next = ptr::read_volatile(list_head.next); + let node = LinkedListNode { + next: list_head.next, + prev: p_list_head, + }; + + list_head.next = p_node; + node_next.prev = p_node; + + // All nodes must be written because they will all be seen by another core + ptr::write_volatile(p_node, node); + ptr::write_volatile(node.next, node_next); + ptr::write_volatile(p_list_head, list_head); + } else { + let node = LinkedListNode { + next: list_head.next, + prev: p_list_head, + }; + + list_head.next = p_node; + list_head.prev = p_node; + + // All nodes must be written because they will all be seen by another core + ptr::write_volatile(p_node, node); + ptr::write_volatile(p_list_head, list_head); + } + }); + } + + /// Insert `node` before `list_tail` and after the second-to-last node + pub unsafe fn insert_tail(mut p_list_tail: *mut LinkedListNode, mut p_node: *mut LinkedListNode) { + interrupt::free(|_| { + let mut list_tail = ptr::read_volatile(p_list_tail); + if p_list_tail != list_tail.prev { + let mut node_prev = ptr::read_volatile(list_tail.prev); + let node = LinkedListNode { + next: p_list_tail, + prev: list_tail.prev, + }; + + list_tail.prev = p_node; + node_prev.next = p_node; + + // All nodes must be written because they will all be seen by another core + ptr::write_volatile(p_node, node); + ptr::write_volatile(node.prev, node_prev); + ptr::write_volatile(p_list_tail, list_tail); + } else { + let node = LinkedListNode { + next: p_list_tail, + prev: list_tail.prev, + }; + + list_tail.prev = p_node; + list_tail.next = p_node; + + // All nodes must be written because they will all be seen by another core + ptr::write_volatile(p_node, node); + ptr::write_volatile(p_list_tail, list_tail); + } + }); + } + + /// Remove `node` from the linked list + pub unsafe fn remove_node(mut p_node: *mut LinkedListNode) { + interrupt::free(|_| { + // trace!("remove node: {:x}", p_node); + // apparently linked list nodes are not always aligned. + // if more hardfaults occur, more of these may need to be converted to unaligned. + let node = ptr::read_unaligned(p_node); + // trace!("remove node: prev/next {:x}/{:x}", node.prev, node.next); + + if node.next != node.prev { + let mut node_next = ptr::read_volatile(node.next); + let mut node_prev = ptr::read_volatile(node.prev); + + node_prev.next = node.next; + node_next.prev = node.prev; + + ptr::write_volatile(node.next, node_next); + ptr::write_volatile(node.prev, node_prev); + } else { + let mut node_next = ptr::read_volatile(node.next); + + node_next.next = node.next; + node_next.prev = node.prev; + + ptr::write_volatile(node.next, node_next); + } + }); + } + + /// Remove `list_head` and return a pointer to the `node`. + pub unsafe fn remove_head(mut p_list_head: *mut LinkedListNode) -> Option<*mut LinkedListNode> { + interrupt::free(|_| { + let list_head = ptr::read_volatile(p_list_head); + + if list_head.next == p_list_head { + None + } else { + // Allowed because a removed node is not seen by another core + let p_node = list_head.next; + Self::remove_node(p_node); + + Some(p_node) + } + }) + } + + /// Remove `list_tail` and return a pointer to the `node`. + pub unsafe fn remove_tail(mut p_list_tail: *mut LinkedListNode) -> Option<*mut LinkedListNode> { + interrupt::free(|_| { + let list_tail = ptr::read_volatile(p_list_tail); + + if list_tail.prev == p_list_tail { + None + } else { + // Allowed because a removed node is not seen by another core + let p_node = list_tail.prev; + Self::remove_node(p_node); + + Some(p_node) + } + }) + } + + pub unsafe fn insert_node_after(mut node: *mut LinkedListNode, mut ref_node: *mut LinkedListNode) { + interrupt::free(|_| { + (*node).next = (*ref_node).next; + (*node).prev = ref_node; + (*ref_node).next = node; + (*(*node).next).prev = node; + }); + + todo!("this function has not been converted to volatile semantics"); + } + + pub unsafe fn insert_node_before(mut node: *mut LinkedListNode, mut ref_node: *mut LinkedListNode) { + interrupt::free(|_| { + (*node).next = ref_node; + (*node).prev = (*ref_node).prev; + (*ref_node).prev = node; + (*(*node).prev).next = node; + }); + + todo!("this function has not been converted to volatile semantics"); + } + + pub unsafe fn get_size(mut list_head: *mut LinkedListNode) -> usize { + interrupt::free(|_| { + let mut size = 0; + let mut temp: *mut LinkedListNode = core::ptr::null_mut::(); + + temp = (*list_head).next; + while temp != list_head { + size += 1; + temp = (*temp).next + } + + size + }); + + todo!("this function has not been converted to volatile semantics"); + } + + pub unsafe fn get_next_node(mut p_ref_node: *mut LinkedListNode) -> *mut LinkedListNode { + interrupt::free(|_| { + let ref_node = ptr::read_volatile(p_ref_node); + + // Allowed because a removed node is not seen by another core + ref_node.next + }) + } + + pub unsafe fn get_prev_node(mut p_ref_node: *mut LinkedListNode) -> *mut LinkedListNode { + interrupt::free(|_| { + let ref_node = ptr::read_volatile(p_ref_node); + + // Allowed because a removed node is not seen by another core + ref_node.prev + }) + } +} + +#[allow(dead_code)] +unsafe fn debug_linked_list(mut p_node: *mut LinkedListNode) { + info!("iterating list from node: {:x}", p_node); + let mut p_current_node = p_node; + let mut i = 0; + loop { + let current_node = ptr::read_volatile(p_current_node); + info!( + "node (prev, current, next): {:x}, {:x}, {:x}", + current_node.prev, p_current_node, current_node.next + ); + + i += 1; + if i > 10 || current_node.next == p_node { + break; + } + + p_current_node = current_node.next; + } +} diff --git a/embassy/embassy-stm32-wpan/tl_mbox.x.in b/embassy/embassy-stm32-wpan/tl_mbox.x.in new file mode 100644 index 0000000..b6eecb4 --- /dev/null +++ b/embassy/embassy-stm32-wpan/tl_mbox.x.in @@ -0,0 +1,15 @@ +MEMORY +{ + RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K +} + +/* + * Scatter the mailbox interface memory sections in shared memory + */ +SECTIONS +{ + TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED + + MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM_SHARED + MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM_SHARED +} diff --git a/embassy/embassy-stm32-wpan/tl_mbox_extended_wb1.x.in b/embassy/embassy-stm32-wpan/tl_mbox_extended_wb1.x.in new file mode 100644 index 0000000..4cffdad --- /dev/null +++ b/embassy/embassy-stm32-wpan/tl_mbox_extended_wb1.x.in @@ -0,0 +1,16 @@ +MEMORY +{ + RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 4K + RAMB_SHARED (xrw) : ORIGIN = 0x20030028, LENGTH = 4K +} + +/* + * Scatter the mailbox interface memory sections in shared memory + */ +SECTIONS +{ + TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED + + MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAMB_SHARED + MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAMB_SHARED +} diff --git a/embassy/embassy-stm32-wpan/tl_mbox_extended_wbx5.x.in b/embassy/embassy-stm32-wpan/tl_mbox_extended_wbx5.x.in new file mode 100644 index 0000000..281d637 --- /dev/null +++ b/embassy/embassy-stm32-wpan/tl_mbox_extended_wbx5.x.in @@ -0,0 +1,16 @@ +MEMORY +{ + RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 2K + RAMB_SHARED (xrw) : ORIGIN = 0x20038000, LENGTH = 10K +} + +/* + * Scatter the mailbox interface memory sections in shared memory + */ +SECTIONS +{ + TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED + + MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAMB_SHARED + MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAMB_SHARED +} diff --git a/embassy/embassy-stm32/Cargo.toml b/embassy/embassy-stm32/Cargo.toml new file mode 100644 index 0000000..47e9e8b --- /dev/null +++ b/embassy/embassy-stm32/Cargo.toml @@ -0,0 +1,1642 @@ +[package] +name = "embassy-stm32" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Embassy Hardware Abstraction Layer (HAL) for ST STM32 series microcontrollers" +keywords = ["embedded", "async", "stm32", "hal", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-stm32" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-stm32-v$VERSION/embassy-stm32/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-stm32/src/" + +features = ["defmt", "unstable-pac", "exti", "time-driver-any", "time"] +flavors = [ + { regex_feature = "stm32f0.*", target = "thumbv6m-none-eabi" }, + { regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" }, + { regex_feature = "stm32f2.*", target = "thumbv7m-none-eabi" }, + { regex_feature = "stm32f3.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi", features = ["low-power"] }, + { regex_feature = "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", features = ["low-power"] }, + { regex_feature = "stm32h5.*", target = "thumbv8m.main-none-eabihf", features = ["low-power"] }, + { regex_feature = "stm32h7.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32l0.*", target = "thumbv6m-none-eabi", features = ["low-power"] }, + { regex_feature = "stm32l1.*", target = "thumbv7m-none-eabi" }, + { regex_feature = "stm32l4.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32l5.*", target = "thumbv8m.main-none-eabihf", features = ["low-power"] }, + { regex_feature = "stm32u0.*", target = "thumbv6m-none-eabi" }, + { regex_feature = "stm32u5.*", target = "thumbv8m.main-none-eabihf" }, + { regex_feature = "stm32wb.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32wba.*", target = "thumbv8m.main-none-eabihf" }, + { regex_feature = "stm32wl.*", target = "thumbv7em-none-eabi" }, +] + +[package.metadata.docs.rs] +features = ["defmt", "unstable-pac", "exti", "time-driver-any", "time", "stm32h755zi-cm7"] +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } +embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } +embassy-time-queue-driver = { version = "0.1", path = "../embassy-time-queue-driver", optional = true } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] } +embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal", default-features = false } +embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } +embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } +embassy-usb-synopsys-otg = {version = "0.2.0", path = "../embassy-usb-synopsys-otg" } +embassy-executor = { version = "0.6.3", path = "../embassy-executor", optional = true } + +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-nb = { version = "1.0" } +embedded-can = "0.4" + +embedded-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } + + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +cortex-m-rt = ">=0.6.15,<0.8" +cortex-m = "0.7.6" +futures-util = { version = "0.3.30", default-features = false } +rand_core = "0.6.3" +sdio-host = "0.5.0" +critical-section = "1.1" +#stm32-metapac = { version = "15" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ddb0e7abab14bf3e1399875767b8834442382988" } + +vcell = "0.1.3" +nb = "1.0.0" +stm32-fmc = "0.3.0" +cfg-if = "1.0.0" +embedded-io = { version = "0.6.0" } +embedded-io-async = { version = "0.6.1" } +chrono = { version = "^0.4", default-features = false, optional = true } +bit_field = "0.10.2" +document-features = "0.2.7" + +static_assertions = { version = "1.1" } +volatile-register = { version = "0.2.1" } +bitflags = "2.4.2" + +block-device-driver = { version = "0.2" } +aligned = "0.4.1" + +[dev-dependencies] +critical-section = { version = "1.1", features = ["std"] } +proptest = "1.5.0" +proptest-state-machine = "0.3.0" + +[build-dependencies] +proc-macro2 = "1.0.36" +quote = "1.0.15" + +#stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ddb0e7abab14bf3e1399875767b8834442382988", default-features = false, features = ["metadata"] } + +[features] +default = ["rt"] + +## Enable `stm32-metapac`'s `rt` feature +rt = ["stm32-metapac/rt"] + +## Use [`defmt`](https://docs.rs/defmt/latest/defmt/) for logging +defmt = [ + "dep:defmt", + "embassy-sync/defmt", + "embassy-embedded-hal/defmt", + "embassy-hal-internal/defmt", + "embedded-io-async/defmt-03", + "embassy-usb-driver/defmt", + "embassy-net-driver/defmt", + "embassy-time?/defmt", + "embassy-usb-synopsys-otg/defmt", +] + +exti = [] +low-power = [ "dep:embassy-executor", "embassy-executor?/arch-cortex-m", "time" ] +low-power-debug-with-sleep = [] + +## Automatically generate `memory.x` file using [`stm32-metapac`](https://docs.rs/stm32-metapac/) +memory-x = ["stm32-metapac/memory-x"] + +## Use secure registers when TrustZone is enabled +trustzone-secure = [] + +## Re-export stm32-metapac at `embassy_stm32::pac`. +## This is unstable because semver-minor (non-breaking) releases of embassy-stm32 may major-bump (breaking) the stm32-metapac version. +## If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC. +## There are no plans to make this stable. +unstable-pac = [] + +## Enable this feature to disable the overclocking check. +## DO NOT ENABLE THIS FEATURE UNLESS YOU KNOW WHAT YOU'RE DOING. +unchecked-overclocking = [] + +#! ## Time + +## Enables additional driver features that depend on embassy-time +time = ["dep:embassy-time", "embassy-embedded-hal/time"] + +# Features starting with `_` are for internal use only. They're not intended +# to be enabled by other crates, and are not covered by semver guarantees. +_time-driver = ["dep:embassy-time-driver", "time", "dep:embassy-time-queue-driver"] + +## Use any time driver +time-driver-any = ["_time-driver"] +## Use TIM1 as time driver +time-driver-tim1 = ["_time-driver"] +## Use TIM2 as time driver +time-driver-tim2 = ["_time-driver"] +## Use TIM3 as time driver +time-driver-tim3 = ["_time-driver"] +## Use TIM4 as time driver +time-driver-tim4 = ["_time-driver"] +## Use TIM5 as time driver +time-driver-tim5 = ["_time-driver"] +## Use TIM8 as time driver +time-driver-tim8 = ["_time-driver"] +## Use TIM9 as time driver +time-driver-tim9 = ["_time-driver"] +## Use TIM12 as time driver +time-driver-tim12 = ["_time-driver"] +## Use TIM15 as time driver +time-driver-tim15 = ["_time-driver"] +## Use TIM20 as time driver +time-driver-tim20 = ["_time-driver"] +## Use TIM21 as time driver +time-driver-tim21 = ["_time-driver"] +## Use TIM22 as time driver +time-driver-tim22 = ["_time-driver"] +## Use TIM23 as time driver +time-driver-tim23 = ["_time-driver"] +## Use TIM24 as time driver +time-driver-tim24 = ["_time-driver"] + + +#! ## Analog Switch Pins (Pxy_C) on STM32H7 series +#! Get `PXY` and `PXY_C` singletons. Digital impls are on `PXY`, Analog impls are on `PXY_C` +#! If disabled, you get only the `PXY` singleton. It has both digital and analog impls. + +## Split PA0 +split-pa0 = ["_split-pins-enabled"] +## Split PA1 +split-pa1 = ["_split-pins-enabled"] +## Split PC2 +split-pc2 = ["_split-pins-enabled"] +## Split PC3 +split-pc3 = ["_split-pins-enabled"] + +## internal use only +_split-pins-enabled = [] + +## internal use only +_dual-core = [] + +#! ## Chip-selection features +#! Select your chip by specifying the model as a feature, e.g. `stm32c011d6`. +#! Check the `Cargo.toml` for the latest list of supported chips. +#! +#! **Important:** Do not forget to adapt the target chip in your toolchain, +#! e.g. in `.cargo/config.toml`. + +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" ] +stm32f030f4 = [ "stm32-metapac/stm32f030f4" ] +stm32f030k6 = [ "stm32-metapac/stm32f030k6" ] +stm32f030r8 = [ "stm32-metapac/stm32f030r8" ] +stm32f030rc = [ "stm32-metapac/stm32f030rc" ] +stm32f031c4 = [ "stm32-metapac/stm32f031c4" ] +stm32f031c6 = [ "stm32-metapac/stm32f031c6" ] +stm32f031e6 = [ "stm32-metapac/stm32f031e6" ] +stm32f031f4 = [ "stm32-metapac/stm32f031f4" ] +stm32f031f6 = [ "stm32-metapac/stm32f031f6" ] +stm32f031g4 = [ "stm32-metapac/stm32f031g4" ] +stm32f031g6 = [ "stm32-metapac/stm32f031g6" ] +stm32f031k4 = [ "stm32-metapac/stm32f031k4" ] +stm32f031k6 = [ "stm32-metapac/stm32f031k6" ] +stm32f038c6 = [ "stm32-metapac/stm32f038c6" ] +stm32f038e6 = [ "stm32-metapac/stm32f038e6" ] +stm32f038f6 = [ "stm32-metapac/stm32f038f6" ] +stm32f038g6 = [ "stm32-metapac/stm32f038g6" ] +stm32f038k6 = [ "stm32-metapac/stm32f038k6" ] +stm32f042c4 = [ "stm32-metapac/stm32f042c4" ] +stm32f042c6 = [ "stm32-metapac/stm32f042c6" ] +stm32f042f4 = [ "stm32-metapac/stm32f042f4" ] +stm32f042f6 = [ "stm32-metapac/stm32f042f6" ] +stm32f042g4 = [ "stm32-metapac/stm32f042g4" ] +stm32f042g6 = [ "stm32-metapac/stm32f042g6" ] +stm32f042k4 = [ "stm32-metapac/stm32f042k4" ] +stm32f042k6 = [ "stm32-metapac/stm32f042k6" ] +stm32f042t6 = [ "stm32-metapac/stm32f042t6" ] +stm32f048c6 = [ "stm32-metapac/stm32f048c6" ] +stm32f048g6 = [ "stm32-metapac/stm32f048g6" ] +stm32f048t6 = [ "stm32-metapac/stm32f048t6" ] +stm32f051c4 = [ "stm32-metapac/stm32f051c4" ] +stm32f051c6 = [ "stm32-metapac/stm32f051c6" ] +stm32f051c8 = [ "stm32-metapac/stm32f051c8" ] +stm32f051k4 = [ "stm32-metapac/stm32f051k4" ] +stm32f051k6 = [ "stm32-metapac/stm32f051k6" ] +stm32f051k8 = [ "stm32-metapac/stm32f051k8" ] +stm32f051r4 = [ "stm32-metapac/stm32f051r4" ] +stm32f051r6 = [ "stm32-metapac/stm32f051r6" ] +stm32f051r8 = [ "stm32-metapac/stm32f051r8" ] +stm32f051t8 = [ "stm32-metapac/stm32f051t8" ] +stm32f058c8 = [ "stm32-metapac/stm32f058c8" ] +stm32f058r8 = [ "stm32-metapac/stm32f058r8" ] +stm32f058t8 = [ "stm32-metapac/stm32f058t8" ] +stm32f070c6 = [ "stm32-metapac/stm32f070c6" ] +stm32f070cb = [ "stm32-metapac/stm32f070cb" ] +stm32f070f6 = [ "stm32-metapac/stm32f070f6" ] +stm32f070rb = [ "stm32-metapac/stm32f070rb" ] +stm32f071c8 = [ "stm32-metapac/stm32f071c8" ] +stm32f071cb = [ "stm32-metapac/stm32f071cb" ] +stm32f071rb = [ "stm32-metapac/stm32f071rb" ] +stm32f071v8 = [ "stm32-metapac/stm32f071v8" ] +stm32f071vb = [ "stm32-metapac/stm32f071vb" ] +stm32f072c8 = [ "stm32-metapac/stm32f072c8" ] +stm32f072cb = [ "stm32-metapac/stm32f072cb" ] +stm32f072r8 = [ "stm32-metapac/stm32f072r8" ] +stm32f072rb = [ "stm32-metapac/stm32f072rb" ] +stm32f072v8 = [ "stm32-metapac/stm32f072v8" ] +stm32f072vb = [ "stm32-metapac/stm32f072vb" ] +stm32f078cb = [ "stm32-metapac/stm32f078cb" ] +stm32f078rb = [ "stm32-metapac/stm32f078rb" ] +stm32f078vb = [ "stm32-metapac/stm32f078vb" ] +stm32f091cb = [ "stm32-metapac/stm32f091cb" ] +stm32f091cc = [ "stm32-metapac/stm32f091cc" ] +stm32f091rb = [ "stm32-metapac/stm32f091rb" ] +stm32f091rc = [ "stm32-metapac/stm32f091rc" ] +stm32f091vb = [ "stm32-metapac/stm32f091vb" ] +stm32f091vc = [ "stm32-metapac/stm32f091vc" ] +stm32f098cc = [ "stm32-metapac/stm32f098cc" ] +stm32f098rc = [ "stm32-metapac/stm32f098rc" ] +stm32f098vc = [ "stm32-metapac/stm32f098vc" ] +stm32f100c4 = [ "stm32-metapac/stm32f100c4" ] +stm32f100c6 = [ "stm32-metapac/stm32f100c6" ] +stm32f100c8 = [ "stm32-metapac/stm32f100c8" ] +stm32f100cb = [ "stm32-metapac/stm32f100cb" ] +stm32f100r4 = [ "stm32-metapac/stm32f100r4" ] +stm32f100r6 = [ "stm32-metapac/stm32f100r6" ] +stm32f100r8 = [ "stm32-metapac/stm32f100r8" ] +stm32f100rb = [ "stm32-metapac/stm32f100rb" ] +stm32f100rc = [ "stm32-metapac/stm32f100rc" ] +stm32f100rd = [ "stm32-metapac/stm32f100rd" ] +stm32f100re = [ "stm32-metapac/stm32f100re" ] +stm32f100v8 = [ "stm32-metapac/stm32f100v8" ] +stm32f100vb = [ "stm32-metapac/stm32f100vb" ] +stm32f100vc = [ "stm32-metapac/stm32f100vc" ] +stm32f100vd = [ "stm32-metapac/stm32f100vd" ] +stm32f100ve = [ "stm32-metapac/stm32f100ve" ] +stm32f100zc = [ "stm32-metapac/stm32f100zc" ] +stm32f100zd = [ "stm32-metapac/stm32f100zd" ] +stm32f100ze = [ "stm32-metapac/stm32f100ze" ] +stm32f101c4 = [ "stm32-metapac/stm32f101c4" ] +stm32f101c6 = [ "stm32-metapac/stm32f101c6" ] +stm32f101c8 = [ "stm32-metapac/stm32f101c8" ] +stm32f101cb = [ "stm32-metapac/stm32f101cb" ] +stm32f101r4 = [ "stm32-metapac/stm32f101r4" ] +stm32f101r6 = [ "stm32-metapac/stm32f101r6" ] +stm32f101r8 = [ "stm32-metapac/stm32f101r8" ] +stm32f101rb = [ "stm32-metapac/stm32f101rb" ] +stm32f101rc = [ "stm32-metapac/stm32f101rc" ] +stm32f101rd = [ "stm32-metapac/stm32f101rd" ] +stm32f101re = [ "stm32-metapac/stm32f101re" ] +stm32f101rf = [ "stm32-metapac/stm32f101rf" ] +stm32f101rg = [ "stm32-metapac/stm32f101rg" ] +stm32f101t4 = [ "stm32-metapac/stm32f101t4" ] +stm32f101t6 = [ "stm32-metapac/stm32f101t6" ] +stm32f101t8 = [ "stm32-metapac/stm32f101t8" ] +stm32f101tb = [ "stm32-metapac/stm32f101tb" ] +stm32f101v8 = [ "stm32-metapac/stm32f101v8" ] +stm32f101vb = [ "stm32-metapac/stm32f101vb" ] +stm32f101vc = [ "stm32-metapac/stm32f101vc" ] +stm32f101vd = [ "stm32-metapac/stm32f101vd" ] +stm32f101ve = [ "stm32-metapac/stm32f101ve" ] +stm32f101vf = [ "stm32-metapac/stm32f101vf" ] +stm32f101vg = [ "stm32-metapac/stm32f101vg" ] +stm32f101zc = [ "stm32-metapac/stm32f101zc" ] +stm32f101zd = [ "stm32-metapac/stm32f101zd" ] +stm32f101ze = [ "stm32-metapac/stm32f101ze" ] +stm32f101zf = [ "stm32-metapac/stm32f101zf" ] +stm32f101zg = [ "stm32-metapac/stm32f101zg" ] +stm32f102c4 = [ "stm32-metapac/stm32f102c4" ] +stm32f102c6 = [ "stm32-metapac/stm32f102c6" ] +stm32f102c8 = [ "stm32-metapac/stm32f102c8" ] +stm32f102cb = [ "stm32-metapac/stm32f102cb" ] +stm32f102r4 = [ "stm32-metapac/stm32f102r4" ] +stm32f102r6 = [ "stm32-metapac/stm32f102r6" ] +stm32f102r8 = [ "stm32-metapac/stm32f102r8" ] +stm32f102rb = [ "stm32-metapac/stm32f102rb" ] +stm32f103c4 = [ "stm32-metapac/stm32f103c4" ] +stm32f103c6 = [ "stm32-metapac/stm32f103c6" ] +stm32f103c8 = [ "stm32-metapac/stm32f103c8" ] +stm32f103cb = [ "stm32-metapac/stm32f103cb" ] +stm32f103r4 = [ "stm32-metapac/stm32f103r4" ] +stm32f103r6 = [ "stm32-metapac/stm32f103r6" ] +stm32f103r8 = [ "stm32-metapac/stm32f103r8" ] +stm32f103rb = [ "stm32-metapac/stm32f103rb" ] +stm32f103rc = [ "stm32-metapac/stm32f103rc" ] +stm32f103rd = [ "stm32-metapac/stm32f103rd" ] +stm32f103re = [ "stm32-metapac/stm32f103re" ] +stm32f103rf = [ "stm32-metapac/stm32f103rf" ] +stm32f103rg = [ "stm32-metapac/stm32f103rg" ] +stm32f103t4 = [ "stm32-metapac/stm32f103t4" ] +stm32f103t6 = [ "stm32-metapac/stm32f103t6" ] +stm32f103t8 = [ "stm32-metapac/stm32f103t8" ] +stm32f103tb = [ "stm32-metapac/stm32f103tb" ] +stm32f103v8 = [ "stm32-metapac/stm32f103v8" ] +stm32f103vb = [ "stm32-metapac/stm32f103vb" ] +stm32f103vc = [ "stm32-metapac/stm32f103vc" ] +stm32f103vd = [ "stm32-metapac/stm32f103vd" ] +stm32f103ve = [ "stm32-metapac/stm32f103ve" ] +stm32f103vf = [ "stm32-metapac/stm32f103vf" ] +stm32f103vg = [ "stm32-metapac/stm32f103vg" ] +stm32f103zc = [ "stm32-metapac/stm32f103zc" ] +stm32f103zd = [ "stm32-metapac/stm32f103zd" ] +stm32f103ze = [ "stm32-metapac/stm32f103ze" ] +stm32f103zf = [ "stm32-metapac/stm32f103zf" ] +stm32f103zg = [ "stm32-metapac/stm32f103zg" ] +stm32f105r8 = [ "stm32-metapac/stm32f105r8" ] +stm32f105rb = [ "stm32-metapac/stm32f105rb" ] +stm32f105rc = [ "stm32-metapac/stm32f105rc" ] +stm32f105v8 = [ "stm32-metapac/stm32f105v8" ] +stm32f105vb = [ "stm32-metapac/stm32f105vb" ] +stm32f105vc = [ "stm32-metapac/stm32f105vc" ] +stm32f107rb = [ "stm32-metapac/stm32f107rb" ] +stm32f107rc = [ "stm32-metapac/stm32f107rc" ] +stm32f107vb = [ "stm32-metapac/stm32f107vb" ] +stm32f107vc = [ "stm32-metapac/stm32f107vc" ] +stm32f205rb = [ "stm32-metapac/stm32f205rb" ] +stm32f205rc = [ "stm32-metapac/stm32f205rc" ] +stm32f205re = [ "stm32-metapac/stm32f205re" ] +stm32f205rf = [ "stm32-metapac/stm32f205rf" ] +stm32f205rg = [ "stm32-metapac/stm32f205rg" ] +stm32f205vb = [ "stm32-metapac/stm32f205vb" ] +stm32f205vc = [ "stm32-metapac/stm32f205vc" ] +stm32f205ve = [ "stm32-metapac/stm32f205ve" ] +stm32f205vf = [ "stm32-metapac/stm32f205vf" ] +stm32f205vg = [ "stm32-metapac/stm32f205vg" ] +stm32f205zc = [ "stm32-metapac/stm32f205zc" ] +stm32f205ze = [ "stm32-metapac/stm32f205ze" ] +stm32f205zf = [ "stm32-metapac/stm32f205zf" ] +stm32f205zg = [ "stm32-metapac/stm32f205zg" ] +stm32f207ic = [ "stm32-metapac/stm32f207ic" ] +stm32f207ie = [ "stm32-metapac/stm32f207ie" ] +stm32f207if = [ "stm32-metapac/stm32f207if" ] +stm32f207ig = [ "stm32-metapac/stm32f207ig" ] +stm32f207vc = [ "stm32-metapac/stm32f207vc" ] +stm32f207ve = [ "stm32-metapac/stm32f207ve" ] +stm32f207vf = [ "stm32-metapac/stm32f207vf" ] +stm32f207vg = [ "stm32-metapac/stm32f207vg" ] +stm32f207zc = [ "stm32-metapac/stm32f207zc" ] +stm32f207ze = [ "stm32-metapac/stm32f207ze" ] +stm32f207zf = [ "stm32-metapac/stm32f207zf" ] +stm32f207zg = [ "stm32-metapac/stm32f207zg" ] +stm32f215re = [ "stm32-metapac/stm32f215re" ] +stm32f215rg = [ "stm32-metapac/stm32f215rg" ] +stm32f215ve = [ "stm32-metapac/stm32f215ve" ] +stm32f215vg = [ "stm32-metapac/stm32f215vg" ] +stm32f215ze = [ "stm32-metapac/stm32f215ze" ] +stm32f215zg = [ "stm32-metapac/stm32f215zg" ] +stm32f217ie = [ "stm32-metapac/stm32f217ie" ] +stm32f217ig = [ "stm32-metapac/stm32f217ig" ] +stm32f217ve = [ "stm32-metapac/stm32f217ve" ] +stm32f217vg = [ "stm32-metapac/stm32f217vg" ] +stm32f217ze = [ "stm32-metapac/stm32f217ze" ] +stm32f217zg = [ "stm32-metapac/stm32f217zg" ] +stm32f301c6 = [ "stm32-metapac/stm32f301c6" ] +stm32f301c8 = [ "stm32-metapac/stm32f301c8" ] +stm32f301k6 = [ "stm32-metapac/stm32f301k6" ] +stm32f301k8 = [ "stm32-metapac/stm32f301k8" ] +stm32f301r6 = [ "stm32-metapac/stm32f301r6" ] +stm32f301r8 = [ "stm32-metapac/stm32f301r8" ] +stm32f302c6 = [ "stm32-metapac/stm32f302c6" ] +stm32f302c8 = [ "stm32-metapac/stm32f302c8" ] +stm32f302cb = [ "stm32-metapac/stm32f302cb" ] +stm32f302cc = [ "stm32-metapac/stm32f302cc" ] +stm32f302k6 = [ "stm32-metapac/stm32f302k6" ] +stm32f302k8 = [ "stm32-metapac/stm32f302k8" ] +stm32f302r6 = [ "stm32-metapac/stm32f302r6" ] +stm32f302r8 = [ "stm32-metapac/stm32f302r8" ] +stm32f302rb = [ "stm32-metapac/stm32f302rb" ] +stm32f302rc = [ "stm32-metapac/stm32f302rc" ] +stm32f302rd = [ "stm32-metapac/stm32f302rd" ] +stm32f302re = [ "stm32-metapac/stm32f302re" ] +stm32f302vb = [ "stm32-metapac/stm32f302vb" ] +stm32f302vc = [ "stm32-metapac/stm32f302vc" ] +stm32f302vd = [ "stm32-metapac/stm32f302vd" ] +stm32f302ve = [ "stm32-metapac/stm32f302ve" ] +stm32f302zd = [ "stm32-metapac/stm32f302zd" ] +stm32f302ze = [ "stm32-metapac/stm32f302ze" ] +stm32f303c6 = [ "stm32-metapac/stm32f303c6" ] +stm32f303c8 = [ "stm32-metapac/stm32f303c8" ] +stm32f303cb = [ "stm32-metapac/stm32f303cb" ] +stm32f303cc = [ "stm32-metapac/stm32f303cc" ] +stm32f303k6 = [ "stm32-metapac/stm32f303k6" ] +stm32f303k8 = [ "stm32-metapac/stm32f303k8" ] +stm32f303r6 = [ "stm32-metapac/stm32f303r6" ] +stm32f303r8 = [ "stm32-metapac/stm32f303r8" ] +stm32f303rb = [ "stm32-metapac/stm32f303rb" ] +stm32f303rc = [ "stm32-metapac/stm32f303rc" ] +stm32f303rd = [ "stm32-metapac/stm32f303rd" ] +stm32f303re = [ "stm32-metapac/stm32f303re" ] +stm32f303vb = [ "stm32-metapac/stm32f303vb" ] +stm32f303vc = [ "stm32-metapac/stm32f303vc" ] +stm32f303vd = [ "stm32-metapac/stm32f303vd" ] +stm32f303ve = [ "stm32-metapac/stm32f303ve" ] +stm32f303zd = [ "stm32-metapac/stm32f303zd" ] +stm32f303ze = [ "stm32-metapac/stm32f303ze" ] +stm32f318c8 = [ "stm32-metapac/stm32f318c8" ] +stm32f318k8 = [ "stm32-metapac/stm32f318k8" ] +stm32f328c8 = [ "stm32-metapac/stm32f328c8" ] +stm32f334c4 = [ "stm32-metapac/stm32f334c4" ] +stm32f334c6 = [ "stm32-metapac/stm32f334c6" ] +stm32f334c8 = [ "stm32-metapac/stm32f334c8" ] +stm32f334k4 = [ "stm32-metapac/stm32f334k4" ] +stm32f334k6 = [ "stm32-metapac/stm32f334k6" ] +stm32f334k8 = [ "stm32-metapac/stm32f334k8" ] +stm32f334r6 = [ "stm32-metapac/stm32f334r6" ] +stm32f334r8 = [ "stm32-metapac/stm32f334r8" ] +stm32f358cc = [ "stm32-metapac/stm32f358cc" ] +stm32f358rc = [ "stm32-metapac/stm32f358rc" ] +stm32f358vc = [ "stm32-metapac/stm32f358vc" ] +stm32f373c8 = [ "stm32-metapac/stm32f373c8" ] +stm32f373cb = [ "stm32-metapac/stm32f373cb" ] +stm32f373cc = [ "stm32-metapac/stm32f373cc" ] +stm32f373r8 = [ "stm32-metapac/stm32f373r8" ] +stm32f373rb = [ "stm32-metapac/stm32f373rb" ] +stm32f373rc = [ "stm32-metapac/stm32f373rc" ] +stm32f373v8 = [ "stm32-metapac/stm32f373v8" ] +stm32f373vb = [ "stm32-metapac/stm32f373vb" ] +stm32f373vc = [ "stm32-metapac/stm32f373vc" ] +stm32f378cc = [ "stm32-metapac/stm32f378cc" ] +stm32f378rc = [ "stm32-metapac/stm32f378rc" ] +stm32f378vc = [ "stm32-metapac/stm32f378vc" ] +stm32f398ve = [ "stm32-metapac/stm32f398ve" ] +stm32f401cb = [ "stm32-metapac/stm32f401cb" ] +stm32f401cc = [ "stm32-metapac/stm32f401cc" ] +stm32f401cd = [ "stm32-metapac/stm32f401cd" ] +stm32f401ce = [ "stm32-metapac/stm32f401ce" ] +stm32f401rb = [ "stm32-metapac/stm32f401rb" ] +stm32f401rc = [ "stm32-metapac/stm32f401rc" ] +stm32f401rd = [ "stm32-metapac/stm32f401rd" ] +stm32f401re = [ "stm32-metapac/stm32f401re" ] +stm32f401vb = [ "stm32-metapac/stm32f401vb" ] +stm32f401vc = [ "stm32-metapac/stm32f401vc" ] +stm32f401vd = [ "stm32-metapac/stm32f401vd" ] +stm32f401ve = [ "stm32-metapac/stm32f401ve" ] +stm32f405oe = [ "stm32-metapac/stm32f405oe" ] +stm32f405og = [ "stm32-metapac/stm32f405og" ] +stm32f405rg = [ "stm32-metapac/stm32f405rg" ] +stm32f405vg = [ "stm32-metapac/stm32f405vg" ] +stm32f405zg = [ "stm32-metapac/stm32f405zg" ] +stm32f407ie = [ "stm32-metapac/stm32f407ie" ] +stm32f407ig = [ "stm32-metapac/stm32f407ig" ] +stm32f407ve = [ "stm32-metapac/stm32f407ve" ] +stm32f407vg = [ "stm32-metapac/stm32f407vg" ] +stm32f407ze = [ "stm32-metapac/stm32f407ze" ] +stm32f407zg = [ "stm32-metapac/stm32f407zg" ] +stm32f410c8 = [ "stm32-metapac/stm32f410c8" ] +stm32f410cb = [ "stm32-metapac/stm32f410cb" ] +stm32f410r8 = [ "stm32-metapac/stm32f410r8" ] +stm32f410rb = [ "stm32-metapac/stm32f410rb" ] +stm32f410t8 = [ "stm32-metapac/stm32f410t8" ] +stm32f410tb = [ "stm32-metapac/stm32f410tb" ] +stm32f411cc = [ "stm32-metapac/stm32f411cc" ] +stm32f411ce = [ "stm32-metapac/stm32f411ce" ] +stm32f411rc = [ "stm32-metapac/stm32f411rc" ] +stm32f411re = [ "stm32-metapac/stm32f411re" ] +stm32f411vc = [ "stm32-metapac/stm32f411vc" ] +stm32f411ve = [ "stm32-metapac/stm32f411ve" ] +stm32f412ce = [ "stm32-metapac/stm32f412ce" ] +stm32f412cg = [ "stm32-metapac/stm32f412cg" ] +stm32f412re = [ "stm32-metapac/stm32f412re" ] +stm32f412rg = [ "stm32-metapac/stm32f412rg" ] +stm32f412ve = [ "stm32-metapac/stm32f412ve" ] +stm32f412vg = [ "stm32-metapac/stm32f412vg" ] +stm32f412ze = [ "stm32-metapac/stm32f412ze" ] +stm32f412zg = [ "stm32-metapac/stm32f412zg" ] +stm32f413cg = [ "stm32-metapac/stm32f413cg" ] +stm32f413ch = [ "stm32-metapac/stm32f413ch" ] +stm32f413mg = [ "stm32-metapac/stm32f413mg" ] +stm32f413mh = [ "stm32-metapac/stm32f413mh" ] +stm32f413rg = [ "stm32-metapac/stm32f413rg" ] +stm32f413rh = [ "stm32-metapac/stm32f413rh" ] +stm32f413vg = [ "stm32-metapac/stm32f413vg" ] +stm32f413vh = [ "stm32-metapac/stm32f413vh" ] +stm32f413zg = [ "stm32-metapac/stm32f413zg" ] +stm32f413zh = [ "stm32-metapac/stm32f413zh" ] +stm32f415og = [ "stm32-metapac/stm32f415og" ] +stm32f415rg = [ "stm32-metapac/stm32f415rg" ] +stm32f415vg = [ "stm32-metapac/stm32f415vg" ] +stm32f415zg = [ "stm32-metapac/stm32f415zg" ] +stm32f417ie = [ "stm32-metapac/stm32f417ie" ] +stm32f417ig = [ "stm32-metapac/stm32f417ig" ] +stm32f417ve = [ "stm32-metapac/stm32f417ve" ] +stm32f417vg = [ "stm32-metapac/stm32f417vg" ] +stm32f417ze = [ "stm32-metapac/stm32f417ze" ] +stm32f417zg = [ "stm32-metapac/stm32f417zg" ] +stm32f423ch = [ "stm32-metapac/stm32f423ch" ] +stm32f423mh = [ "stm32-metapac/stm32f423mh" ] +stm32f423rh = [ "stm32-metapac/stm32f423rh" ] +stm32f423vh = [ "stm32-metapac/stm32f423vh" ] +stm32f423zh = [ "stm32-metapac/stm32f423zh" ] +stm32f427ag = [ "stm32-metapac/stm32f427ag" ] +stm32f427ai = [ "stm32-metapac/stm32f427ai" ] +stm32f427ig = [ "stm32-metapac/stm32f427ig" ] +stm32f427ii = [ "stm32-metapac/stm32f427ii" ] +stm32f427vg = [ "stm32-metapac/stm32f427vg" ] +stm32f427vi = [ "stm32-metapac/stm32f427vi" ] +stm32f427zg = [ "stm32-metapac/stm32f427zg" ] +stm32f427zi = [ "stm32-metapac/stm32f427zi" ] +stm32f429ag = [ "stm32-metapac/stm32f429ag" ] +stm32f429ai = [ "stm32-metapac/stm32f429ai" ] +stm32f429be = [ "stm32-metapac/stm32f429be" ] +stm32f429bg = [ "stm32-metapac/stm32f429bg" ] +stm32f429bi = [ "stm32-metapac/stm32f429bi" ] +stm32f429ie = [ "stm32-metapac/stm32f429ie" ] +stm32f429ig = [ "stm32-metapac/stm32f429ig" ] +stm32f429ii = [ "stm32-metapac/stm32f429ii" ] +stm32f429ne = [ "stm32-metapac/stm32f429ne" ] +stm32f429ng = [ "stm32-metapac/stm32f429ng" ] +stm32f429ni = [ "stm32-metapac/stm32f429ni" ] +stm32f429ve = [ "stm32-metapac/stm32f429ve" ] +stm32f429vg = [ "stm32-metapac/stm32f429vg" ] +stm32f429vi = [ "stm32-metapac/stm32f429vi" ] +stm32f429ze = [ "stm32-metapac/stm32f429ze" ] +stm32f429zg = [ "stm32-metapac/stm32f429zg" ] +stm32f429zi = [ "stm32-metapac/stm32f429zi" ] +stm32f437ai = [ "stm32-metapac/stm32f437ai" ] +stm32f437ig = [ "stm32-metapac/stm32f437ig" ] +stm32f437ii = [ "stm32-metapac/stm32f437ii" ] +stm32f437vg = [ "stm32-metapac/stm32f437vg" ] +stm32f437vi = [ "stm32-metapac/stm32f437vi" ] +stm32f437zg = [ "stm32-metapac/stm32f437zg" ] +stm32f437zi = [ "stm32-metapac/stm32f437zi" ] +stm32f439ai = [ "stm32-metapac/stm32f439ai" ] +stm32f439bg = [ "stm32-metapac/stm32f439bg" ] +stm32f439bi = [ "stm32-metapac/stm32f439bi" ] +stm32f439ig = [ "stm32-metapac/stm32f439ig" ] +stm32f439ii = [ "stm32-metapac/stm32f439ii" ] +stm32f439ng = [ "stm32-metapac/stm32f439ng" ] +stm32f439ni = [ "stm32-metapac/stm32f439ni" ] +stm32f439vg = [ "stm32-metapac/stm32f439vg" ] +stm32f439vi = [ "stm32-metapac/stm32f439vi" ] +stm32f439zg = [ "stm32-metapac/stm32f439zg" ] +stm32f439zi = [ "stm32-metapac/stm32f439zi" ] +stm32f446mc = [ "stm32-metapac/stm32f446mc" ] +stm32f446me = [ "stm32-metapac/stm32f446me" ] +stm32f446rc = [ "stm32-metapac/stm32f446rc" ] +stm32f446re = [ "stm32-metapac/stm32f446re" ] +stm32f446vc = [ "stm32-metapac/stm32f446vc" ] +stm32f446ve = [ "stm32-metapac/stm32f446ve" ] +stm32f446zc = [ "stm32-metapac/stm32f446zc" ] +stm32f446ze = [ "stm32-metapac/stm32f446ze" ] +stm32f469ae = [ "stm32-metapac/stm32f469ae" ] +stm32f469ag = [ "stm32-metapac/stm32f469ag" ] +stm32f469ai = [ "stm32-metapac/stm32f469ai" ] +stm32f469be = [ "stm32-metapac/stm32f469be" ] +stm32f469bg = [ "stm32-metapac/stm32f469bg" ] +stm32f469bi = [ "stm32-metapac/stm32f469bi" ] +stm32f469ie = [ "stm32-metapac/stm32f469ie" ] +stm32f469ig = [ "stm32-metapac/stm32f469ig" ] +stm32f469ii = [ "stm32-metapac/stm32f469ii" ] +stm32f469ne = [ "stm32-metapac/stm32f469ne" ] +stm32f469ng = [ "stm32-metapac/stm32f469ng" ] +stm32f469ni = [ "stm32-metapac/stm32f469ni" ] +stm32f469ve = [ "stm32-metapac/stm32f469ve" ] +stm32f469vg = [ "stm32-metapac/stm32f469vg" ] +stm32f469vi = [ "stm32-metapac/stm32f469vi" ] +stm32f469ze = [ "stm32-metapac/stm32f469ze" ] +stm32f469zg = [ "stm32-metapac/stm32f469zg" ] +stm32f469zi = [ "stm32-metapac/stm32f469zi" ] +stm32f479ag = [ "stm32-metapac/stm32f479ag" ] +stm32f479ai = [ "stm32-metapac/stm32f479ai" ] +stm32f479bg = [ "stm32-metapac/stm32f479bg" ] +stm32f479bi = [ "stm32-metapac/stm32f479bi" ] +stm32f479ig = [ "stm32-metapac/stm32f479ig" ] +stm32f479ii = [ "stm32-metapac/stm32f479ii" ] +stm32f479ng = [ "stm32-metapac/stm32f479ng" ] +stm32f479ni = [ "stm32-metapac/stm32f479ni" ] +stm32f479vg = [ "stm32-metapac/stm32f479vg" ] +stm32f479vi = [ "stm32-metapac/stm32f479vi" ] +stm32f479zg = [ "stm32-metapac/stm32f479zg" ] +stm32f479zi = [ "stm32-metapac/stm32f479zi" ] +stm32f722ic = [ "stm32-metapac/stm32f722ic" ] +stm32f722ie = [ "stm32-metapac/stm32f722ie" ] +stm32f722rc = [ "stm32-metapac/stm32f722rc" ] +stm32f722re = [ "stm32-metapac/stm32f722re" ] +stm32f722vc = [ "stm32-metapac/stm32f722vc" ] +stm32f722ve = [ "stm32-metapac/stm32f722ve" ] +stm32f722zc = [ "stm32-metapac/stm32f722zc" ] +stm32f722ze = [ "stm32-metapac/stm32f722ze" ] +stm32f723ic = [ "stm32-metapac/stm32f723ic" ] +stm32f723ie = [ "stm32-metapac/stm32f723ie" ] +stm32f723vc = [ "stm32-metapac/stm32f723vc" ] +stm32f723ve = [ "stm32-metapac/stm32f723ve" ] +stm32f723zc = [ "stm32-metapac/stm32f723zc" ] +stm32f723ze = [ "stm32-metapac/stm32f723ze" ] +stm32f730i8 = [ "stm32-metapac/stm32f730i8" ] +stm32f730r8 = [ "stm32-metapac/stm32f730r8" ] +stm32f730v8 = [ "stm32-metapac/stm32f730v8" ] +stm32f730z8 = [ "stm32-metapac/stm32f730z8" ] +stm32f732ie = [ "stm32-metapac/stm32f732ie" ] +stm32f732re = [ "stm32-metapac/stm32f732re" ] +stm32f732ve = [ "stm32-metapac/stm32f732ve" ] +stm32f732ze = [ "stm32-metapac/stm32f732ze" ] +stm32f733ie = [ "stm32-metapac/stm32f733ie" ] +stm32f733ve = [ "stm32-metapac/stm32f733ve" ] +stm32f733ze = [ "stm32-metapac/stm32f733ze" ] +stm32f745ie = [ "stm32-metapac/stm32f745ie" ] +stm32f745ig = [ "stm32-metapac/stm32f745ig" ] +stm32f745ve = [ "stm32-metapac/stm32f745ve" ] +stm32f745vg = [ "stm32-metapac/stm32f745vg" ] +stm32f745ze = [ "stm32-metapac/stm32f745ze" ] +stm32f745zg = [ "stm32-metapac/stm32f745zg" ] +stm32f746be = [ "stm32-metapac/stm32f746be" ] +stm32f746bg = [ "stm32-metapac/stm32f746bg" ] +stm32f746ie = [ "stm32-metapac/stm32f746ie" ] +stm32f746ig = [ "stm32-metapac/stm32f746ig" ] +stm32f746ne = [ "stm32-metapac/stm32f746ne" ] +stm32f746ng = [ "stm32-metapac/stm32f746ng" ] +stm32f746ve = [ "stm32-metapac/stm32f746ve" ] +stm32f746vg = [ "stm32-metapac/stm32f746vg" ] +stm32f746ze = [ "stm32-metapac/stm32f746ze" ] +stm32f746zg = [ "stm32-metapac/stm32f746zg" ] +stm32f750n8 = [ "stm32-metapac/stm32f750n8" ] +stm32f750v8 = [ "stm32-metapac/stm32f750v8" ] +stm32f750z8 = [ "stm32-metapac/stm32f750z8" ] +stm32f756bg = [ "stm32-metapac/stm32f756bg" ] +stm32f756ig = [ "stm32-metapac/stm32f756ig" ] +stm32f756ng = [ "stm32-metapac/stm32f756ng" ] +stm32f756vg = [ "stm32-metapac/stm32f756vg" ] +stm32f756zg = [ "stm32-metapac/stm32f756zg" ] +stm32f765bg = [ "stm32-metapac/stm32f765bg" ] +stm32f765bi = [ "stm32-metapac/stm32f765bi" ] +stm32f765ig = [ "stm32-metapac/stm32f765ig" ] +stm32f765ii = [ "stm32-metapac/stm32f765ii" ] +stm32f765ng = [ "stm32-metapac/stm32f765ng" ] +stm32f765ni = [ "stm32-metapac/stm32f765ni" ] +stm32f765vg = [ "stm32-metapac/stm32f765vg" ] +stm32f765vi = [ "stm32-metapac/stm32f765vi" ] +stm32f765zg = [ "stm32-metapac/stm32f765zg" ] +stm32f765zi = [ "stm32-metapac/stm32f765zi" ] +stm32f767bg = [ "stm32-metapac/stm32f767bg" ] +stm32f767bi = [ "stm32-metapac/stm32f767bi" ] +stm32f767ig = [ "stm32-metapac/stm32f767ig" ] +stm32f767ii = [ "stm32-metapac/stm32f767ii" ] +stm32f767ng = [ "stm32-metapac/stm32f767ng" ] +stm32f767ni = [ "stm32-metapac/stm32f767ni" ] +stm32f767vg = [ "stm32-metapac/stm32f767vg" ] +stm32f767vi = [ "stm32-metapac/stm32f767vi" ] +stm32f767zg = [ "stm32-metapac/stm32f767zg" ] +stm32f767zi = [ "stm32-metapac/stm32f767zi" ] +stm32f768ai = [ "stm32-metapac/stm32f768ai" ] +stm32f769ag = [ "stm32-metapac/stm32f769ag" ] +stm32f769ai = [ "stm32-metapac/stm32f769ai" ] +stm32f769bg = [ "stm32-metapac/stm32f769bg" ] +stm32f769bi = [ "stm32-metapac/stm32f769bi" ] +stm32f769ig = [ "stm32-metapac/stm32f769ig" ] +stm32f769ii = [ "stm32-metapac/stm32f769ii" ] +stm32f769ng = [ "stm32-metapac/stm32f769ng" ] +stm32f769ni = [ "stm32-metapac/stm32f769ni" ] +stm32f777bi = [ "stm32-metapac/stm32f777bi" ] +stm32f777ii = [ "stm32-metapac/stm32f777ii" ] +stm32f777ni = [ "stm32-metapac/stm32f777ni" ] +stm32f777vi = [ "stm32-metapac/stm32f777vi" ] +stm32f777zi = [ "stm32-metapac/stm32f777zi" ] +stm32f778ai = [ "stm32-metapac/stm32f778ai" ] +stm32f779ai = [ "stm32-metapac/stm32f779ai" ] +stm32f779bi = [ "stm32-metapac/stm32f779bi" ] +stm32f779ii = [ "stm32-metapac/stm32f779ii" ] +stm32f779ni = [ "stm32-metapac/stm32f779ni" ] +stm32g030c6 = [ "stm32-metapac/stm32g030c6" ] +stm32g030c8 = [ "stm32-metapac/stm32g030c8" ] +stm32g030f6 = [ "stm32-metapac/stm32g030f6" ] +stm32g030j6 = [ "stm32-metapac/stm32g030j6" ] +stm32g030k6 = [ "stm32-metapac/stm32g030k6" ] +stm32g030k8 = [ "stm32-metapac/stm32g030k8" ] +stm32g031c4 = [ "stm32-metapac/stm32g031c4" ] +stm32g031c6 = [ "stm32-metapac/stm32g031c6" ] +stm32g031c8 = [ "stm32-metapac/stm32g031c8" ] +stm32g031f4 = [ "stm32-metapac/stm32g031f4" ] +stm32g031f6 = [ "stm32-metapac/stm32g031f6" ] +stm32g031f8 = [ "stm32-metapac/stm32g031f8" ] +stm32g031g4 = [ "stm32-metapac/stm32g031g4" ] +stm32g031g6 = [ "stm32-metapac/stm32g031g6" ] +stm32g031g8 = [ "stm32-metapac/stm32g031g8" ] +stm32g031j4 = [ "stm32-metapac/stm32g031j4" ] +stm32g031j6 = [ "stm32-metapac/stm32g031j6" ] +stm32g031k4 = [ "stm32-metapac/stm32g031k4" ] +stm32g031k6 = [ "stm32-metapac/stm32g031k6" ] +stm32g031k8 = [ "stm32-metapac/stm32g031k8" ] +stm32g031y8 = [ "stm32-metapac/stm32g031y8" ] +stm32g041c6 = [ "stm32-metapac/stm32g041c6" ] +stm32g041c8 = [ "stm32-metapac/stm32g041c8" ] +stm32g041f6 = [ "stm32-metapac/stm32g041f6" ] +stm32g041f8 = [ "stm32-metapac/stm32g041f8" ] +stm32g041g6 = [ "stm32-metapac/stm32g041g6" ] +stm32g041g8 = [ "stm32-metapac/stm32g041g8" ] +stm32g041j6 = [ "stm32-metapac/stm32g041j6" ] +stm32g041k6 = [ "stm32-metapac/stm32g041k6" ] +stm32g041k8 = [ "stm32-metapac/stm32g041k8" ] +stm32g041y8 = [ "stm32-metapac/stm32g041y8" ] +stm32g050c6 = [ "stm32-metapac/stm32g050c6" ] +stm32g050c8 = [ "stm32-metapac/stm32g050c8" ] +stm32g050f6 = [ "stm32-metapac/stm32g050f6" ] +stm32g050k6 = [ "stm32-metapac/stm32g050k6" ] +stm32g050k8 = [ "stm32-metapac/stm32g050k8" ] +stm32g051c6 = [ "stm32-metapac/stm32g051c6" ] +stm32g051c8 = [ "stm32-metapac/stm32g051c8" ] +stm32g051f6 = [ "stm32-metapac/stm32g051f6" ] +stm32g051f8 = [ "stm32-metapac/stm32g051f8" ] +stm32g051g6 = [ "stm32-metapac/stm32g051g6" ] +stm32g051g8 = [ "stm32-metapac/stm32g051g8" ] +stm32g051k6 = [ "stm32-metapac/stm32g051k6" ] +stm32g051k8 = [ "stm32-metapac/stm32g051k8" ] +stm32g061c6 = [ "stm32-metapac/stm32g061c6" ] +stm32g061c8 = [ "stm32-metapac/stm32g061c8" ] +stm32g061f6 = [ "stm32-metapac/stm32g061f6" ] +stm32g061f8 = [ "stm32-metapac/stm32g061f8" ] +stm32g061g6 = [ "stm32-metapac/stm32g061g6" ] +stm32g061g8 = [ "stm32-metapac/stm32g061g8" ] +stm32g061k6 = [ "stm32-metapac/stm32g061k6" ] +stm32g061k8 = [ "stm32-metapac/stm32g061k8" ] +stm32g070cb = [ "stm32-metapac/stm32g070cb" ] +stm32g070kb = [ "stm32-metapac/stm32g070kb" ] +stm32g070rb = [ "stm32-metapac/stm32g070rb" ] +stm32g071c6 = [ "stm32-metapac/stm32g071c6" ] +stm32g071c8 = [ "stm32-metapac/stm32g071c8" ] +stm32g071cb = [ "stm32-metapac/stm32g071cb" ] +stm32g071eb = [ "stm32-metapac/stm32g071eb" ] +stm32g071g6 = [ "stm32-metapac/stm32g071g6" ] +stm32g071g8 = [ "stm32-metapac/stm32g071g8" ] +stm32g071gb = [ "stm32-metapac/stm32g071gb" ] +stm32g071k6 = [ "stm32-metapac/stm32g071k6" ] +stm32g071k8 = [ "stm32-metapac/stm32g071k8" ] +stm32g071kb = [ "stm32-metapac/stm32g071kb" ] +stm32g071r6 = [ "stm32-metapac/stm32g071r6" ] +stm32g071r8 = [ "stm32-metapac/stm32g071r8" ] +stm32g071rb = [ "stm32-metapac/stm32g071rb" ] +stm32g081cb = [ "stm32-metapac/stm32g081cb" ] +stm32g081eb = [ "stm32-metapac/stm32g081eb" ] +stm32g081gb = [ "stm32-metapac/stm32g081gb" ] +stm32g081kb = [ "stm32-metapac/stm32g081kb" ] +stm32g081rb = [ "stm32-metapac/stm32g081rb" ] +stm32g0b0ce = [ "stm32-metapac/stm32g0b0ce" ] +stm32g0b0ke = [ "stm32-metapac/stm32g0b0ke" ] +stm32g0b0re = [ "stm32-metapac/stm32g0b0re" ] +stm32g0b0ve = [ "stm32-metapac/stm32g0b0ve" ] +stm32g0b1cb = [ "stm32-metapac/stm32g0b1cb" ] +stm32g0b1cc = [ "stm32-metapac/stm32g0b1cc" ] +stm32g0b1ce = [ "stm32-metapac/stm32g0b1ce" ] +stm32g0b1kb = [ "stm32-metapac/stm32g0b1kb" ] +stm32g0b1kc = [ "stm32-metapac/stm32g0b1kc" ] +stm32g0b1ke = [ "stm32-metapac/stm32g0b1ke" ] +stm32g0b1mb = [ "stm32-metapac/stm32g0b1mb" ] +stm32g0b1mc = [ "stm32-metapac/stm32g0b1mc" ] +stm32g0b1me = [ "stm32-metapac/stm32g0b1me" ] +stm32g0b1ne = [ "stm32-metapac/stm32g0b1ne" ] +stm32g0b1rb = [ "stm32-metapac/stm32g0b1rb" ] +stm32g0b1rc = [ "stm32-metapac/stm32g0b1rc" ] +stm32g0b1re = [ "stm32-metapac/stm32g0b1re" ] +stm32g0b1vb = [ "stm32-metapac/stm32g0b1vb" ] +stm32g0b1vc = [ "stm32-metapac/stm32g0b1vc" ] +stm32g0b1ve = [ "stm32-metapac/stm32g0b1ve" ] +stm32g0c1cc = [ "stm32-metapac/stm32g0c1cc" ] +stm32g0c1ce = [ "stm32-metapac/stm32g0c1ce" ] +stm32g0c1kc = [ "stm32-metapac/stm32g0c1kc" ] +stm32g0c1ke = [ "stm32-metapac/stm32g0c1ke" ] +stm32g0c1mc = [ "stm32-metapac/stm32g0c1mc" ] +stm32g0c1me = [ "stm32-metapac/stm32g0c1me" ] +stm32g0c1ne = [ "stm32-metapac/stm32g0c1ne" ] +stm32g0c1rc = [ "stm32-metapac/stm32g0c1rc" ] +stm32g0c1re = [ "stm32-metapac/stm32g0c1re" ] +stm32g0c1vc = [ "stm32-metapac/stm32g0c1vc" ] +stm32g0c1ve = [ "stm32-metapac/stm32g0c1ve" ] +stm32g431c6 = [ "stm32-metapac/stm32g431c6" ] +stm32g431c8 = [ "stm32-metapac/stm32g431c8" ] +stm32g431cb = [ "stm32-metapac/stm32g431cb" ] +stm32g431k6 = [ "stm32-metapac/stm32g431k6" ] +stm32g431k8 = [ "stm32-metapac/stm32g431k8" ] +stm32g431kb = [ "stm32-metapac/stm32g431kb" ] +stm32g431m6 = [ "stm32-metapac/stm32g431m6" ] +stm32g431m8 = [ "stm32-metapac/stm32g431m8" ] +stm32g431mb = [ "stm32-metapac/stm32g431mb" ] +stm32g431r6 = [ "stm32-metapac/stm32g431r6" ] +stm32g431r8 = [ "stm32-metapac/stm32g431r8" ] +stm32g431rb = [ "stm32-metapac/stm32g431rb" ] +stm32g431v6 = [ "stm32-metapac/stm32g431v6" ] +stm32g431v8 = [ "stm32-metapac/stm32g431v8" ] +stm32g431vb = [ "stm32-metapac/stm32g431vb" ] +stm32g441cb = [ "stm32-metapac/stm32g441cb" ] +stm32g441kb = [ "stm32-metapac/stm32g441kb" ] +stm32g441mb = [ "stm32-metapac/stm32g441mb" ] +stm32g441rb = [ "stm32-metapac/stm32g441rb" ] +stm32g441vb = [ "stm32-metapac/stm32g441vb" ] +stm32g471cc = [ "stm32-metapac/stm32g471cc" ] +stm32g471ce = [ "stm32-metapac/stm32g471ce" ] +stm32g471mc = [ "stm32-metapac/stm32g471mc" ] +stm32g471me = [ "stm32-metapac/stm32g471me" ] +stm32g471qc = [ "stm32-metapac/stm32g471qc" ] +stm32g471qe = [ "stm32-metapac/stm32g471qe" ] +stm32g471rc = [ "stm32-metapac/stm32g471rc" ] +stm32g471re = [ "stm32-metapac/stm32g471re" ] +stm32g471vc = [ "stm32-metapac/stm32g471vc" ] +stm32g471ve = [ "stm32-metapac/stm32g471ve" ] +stm32g473cb = [ "stm32-metapac/stm32g473cb" ] +stm32g473cc = [ "stm32-metapac/stm32g473cc" ] +stm32g473ce = [ "stm32-metapac/stm32g473ce" ] +stm32g473mb = [ "stm32-metapac/stm32g473mb" ] +stm32g473mc = [ "stm32-metapac/stm32g473mc" ] +stm32g473me = [ "stm32-metapac/stm32g473me" ] +stm32g473pb = [ "stm32-metapac/stm32g473pb" ] +stm32g473pc = [ "stm32-metapac/stm32g473pc" ] +stm32g473pe = [ "stm32-metapac/stm32g473pe" ] +stm32g473qb = [ "stm32-metapac/stm32g473qb" ] +stm32g473qc = [ "stm32-metapac/stm32g473qc" ] +stm32g473qe = [ "stm32-metapac/stm32g473qe" ] +stm32g473rb = [ "stm32-metapac/stm32g473rb" ] +stm32g473rc = [ "stm32-metapac/stm32g473rc" ] +stm32g473re = [ "stm32-metapac/stm32g473re" ] +stm32g473vb = [ "stm32-metapac/stm32g473vb" ] +stm32g473vc = [ "stm32-metapac/stm32g473vc" ] +stm32g473ve = [ "stm32-metapac/stm32g473ve" ] +stm32g474cb = [ "stm32-metapac/stm32g474cb" ] +stm32g474cc = [ "stm32-metapac/stm32g474cc" ] +stm32g474ce = [ "stm32-metapac/stm32g474ce" ] +stm32g474mb = [ "stm32-metapac/stm32g474mb" ] +stm32g474mc = [ "stm32-metapac/stm32g474mc" ] +stm32g474me = [ "stm32-metapac/stm32g474me" ] +stm32g474pb = [ "stm32-metapac/stm32g474pb" ] +stm32g474pc = [ "stm32-metapac/stm32g474pc" ] +stm32g474pe = [ "stm32-metapac/stm32g474pe" ] +stm32g474qb = [ "stm32-metapac/stm32g474qb" ] +stm32g474qc = [ "stm32-metapac/stm32g474qc" ] +stm32g474qe = [ "stm32-metapac/stm32g474qe" ] +stm32g474rb = [ "stm32-metapac/stm32g474rb" ] +stm32g474rc = [ "stm32-metapac/stm32g474rc" ] +stm32g474re = [ "stm32-metapac/stm32g474re" ] +stm32g474vb = [ "stm32-metapac/stm32g474vb" ] +stm32g474vc = [ "stm32-metapac/stm32g474vc" ] +stm32g474ve = [ "stm32-metapac/stm32g474ve" ] +stm32g483ce = [ "stm32-metapac/stm32g483ce" ] +stm32g483me = [ "stm32-metapac/stm32g483me" ] +stm32g483pe = [ "stm32-metapac/stm32g483pe" ] +stm32g483qe = [ "stm32-metapac/stm32g483qe" ] +stm32g483re = [ "stm32-metapac/stm32g483re" ] +stm32g483ve = [ "stm32-metapac/stm32g483ve" ] +stm32g484ce = [ "stm32-metapac/stm32g484ce" ] +stm32g484me = [ "stm32-metapac/stm32g484me" ] +stm32g484pe = [ "stm32-metapac/stm32g484pe" ] +stm32g484qe = [ "stm32-metapac/stm32g484qe" ] +stm32g484re = [ "stm32-metapac/stm32g484re" ] +stm32g484ve = [ "stm32-metapac/stm32g484ve" ] +stm32g491cc = [ "stm32-metapac/stm32g491cc" ] +stm32g491ce = [ "stm32-metapac/stm32g491ce" ] +stm32g491kc = [ "stm32-metapac/stm32g491kc" ] +stm32g491ke = [ "stm32-metapac/stm32g491ke" ] +stm32g491mc = [ "stm32-metapac/stm32g491mc" ] +stm32g491me = [ "stm32-metapac/stm32g491me" ] +stm32g491rc = [ "stm32-metapac/stm32g491rc" ] +stm32g491re = [ "stm32-metapac/stm32g491re" ] +stm32g491vc = [ "stm32-metapac/stm32g491vc" ] +stm32g491ve = [ "stm32-metapac/stm32g491ve" ] +stm32g4a1ce = [ "stm32-metapac/stm32g4a1ce" ] +stm32g4a1ke = [ "stm32-metapac/stm32g4a1ke" ] +stm32g4a1me = [ "stm32-metapac/stm32g4a1me" ] +stm32g4a1re = [ "stm32-metapac/stm32g4a1re" ] +stm32g4a1ve = [ "stm32-metapac/stm32g4a1ve" ] +stm32h503cb = [ "stm32-metapac/stm32h503cb" ] +stm32h503eb = [ "stm32-metapac/stm32h503eb" ] +stm32h503kb = [ "stm32-metapac/stm32h503kb" ] +stm32h503rb = [ "stm32-metapac/stm32h503rb" ] +stm32h523cc = [ "stm32-metapac/stm32h523cc" ] +stm32h523ce = [ "stm32-metapac/stm32h523ce" ] +stm32h523he = [ "stm32-metapac/stm32h523he" ] +stm32h523rc = [ "stm32-metapac/stm32h523rc" ] +stm32h523re = [ "stm32-metapac/stm32h523re" ] +stm32h523vc = [ "stm32-metapac/stm32h523vc" ] +stm32h523ve = [ "stm32-metapac/stm32h523ve" ] +stm32h523zc = [ "stm32-metapac/stm32h523zc" ] +stm32h523ze = [ "stm32-metapac/stm32h523ze" ] +stm32h533ce = [ "stm32-metapac/stm32h533ce" ] +stm32h533he = [ "stm32-metapac/stm32h533he" ] +stm32h533re = [ "stm32-metapac/stm32h533re" ] +stm32h533ve = [ "stm32-metapac/stm32h533ve" ] +stm32h533ze = [ "stm32-metapac/stm32h533ze" ] +stm32h562ag = [ "stm32-metapac/stm32h562ag" ] +stm32h562ai = [ "stm32-metapac/stm32h562ai" ] +stm32h562ig = [ "stm32-metapac/stm32h562ig" ] +stm32h562ii = [ "stm32-metapac/stm32h562ii" ] +stm32h562rg = [ "stm32-metapac/stm32h562rg" ] +stm32h562ri = [ "stm32-metapac/stm32h562ri" ] +stm32h562vg = [ "stm32-metapac/stm32h562vg" ] +stm32h562vi = [ "stm32-metapac/stm32h562vi" ] +stm32h562zg = [ "stm32-metapac/stm32h562zg" ] +stm32h562zi = [ "stm32-metapac/stm32h562zi" ] +stm32h563ag = [ "stm32-metapac/stm32h563ag" ] +stm32h563ai = [ "stm32-metapac/stm32h563ai" ] +stm32h563ig = [ "stm32-metapac/stm32h563ig" ] +stm32h563ii = [ "stm32-metapac/stm32h563ii" ] +stm32h563mi = [ "stm32-metapac/stm32h563mi" ] +stm32h563rg = [ "stm32-metapac/stm32h563rg" ] +stm32h563ri = [ "stm32-metapac/stm32h563ri" ] +stm32h563vg = [ "stm32-metapac/stm32h563vg" ] +stm32h563vi = [ "stm32-metapac/stm32h563vi" ] +stm32h563zg = [ "stm32-metapac/stm32h563zg" ] +stm32h563zi = [ "stm32-metapac/stm32h563zi" ] +stm32h573ai = [ "stm32-metapac/stm32h573ai" ] +stm32h573ii = [ "stm32-metapac/stm32h573ii" ] +stm32h573mi = [ "stm32-metapac/stm32h573mi" ] +stm32h573ri = [ "stm32-metapac/stm32h573ri" ] +stm32h573vi = [ "stm32-metapac/stm32h573vi" ] +stm32h573zi = [ "stm32-metapac/stm32h573zi" ] +stm32h723ve = [ "stm32-metapac/stm32h723ve" ] +stm32h723vg = [ "stm32-metapac/stm32h723vg" ] +stm32h723ze = [ "stm32-metapac/stm32h723ze" ] +stm32h723zg = [ "stm32-metapac/stm32h723zg" ] +stm32h725ae = [ "stm32-metapac/stm32h725ae" ] +stm32h725ag = [ "stm32-metapac/stm32h725ag" ] +stm32h725ie = [ "stm32-metapac/stm32h725ie" ] +stm32h725ig = [ "stm32-metapac/stm32h725ig" ] +stm32h725re = [ "stm32-metapac/stm32h725re" ] +stm32h725rg = [ "stm32-metapac/stm32h725rg" ] +stm32h725ve = [ "stm32-metapac/stm32h725ve" ] +stm32h725vg = [ "stm32-metapac/stm32h725vg" ] +stm32h725ze = [ "stm32-metapac/stm32h725ze" ] +stm32h725zg = [ "stm32-metapac/stm32h725zg" ] +stm32h730ab = [ "stm32-metapac/stm32h730ab" ] +stm32h730ib = [ "stm32-metapac/stm32h730ib" ] +stm32h730vb = [ "stm32-metapac/stm32h730vb" ] +stm32h730zb = [ "stm32-metapac/stm32h730zb" ] +stm32h733vg = [ "stm32-metapac/stm32h733vg" ] +stm32h733zg = [ "stm32-metapac/stm32h733zg" ] +stm32h735ag = [ "stm32-metapac/stm32h735ag" ] +stm32h735ig = [ "stm32-metapac/stm32h735ig" ] +stm32h735rg = [ "stm32-metapac/stm32h735rg" ] +stm32h735vg = [ "stm32-metapac/stm32h735vg" ] +stm32h735zg = [ "stm32-metapac/stm32h735zg" ] +stm32h742ag = [ "stm32-metapac/stm32h742ag" ] +stm32h742ai = [ "stm32-metapac/stm32h742ai" ] +stm32h742bg = [ "stm32-metapac/stm32h742bg" ] +stm32h742bi = [ "stm32-metapac/stm32h742bi" ] +stm32h742ig = [ "stm32-metapac/stm32h742ig" ] +stm32h742ii = [ "stm32-metapac/stm32h742ii" ] +stm32h742vg = [ "stm32-metapac/stm32h742vg" ] +stm32h742vi = [ "stm32-metapac/stm32h742vi" ] +stm32h742xg = [ "stm32-metapac/stm32h742xg" ] +stm32h742xi = [ "stm32-metapac/stm32h742xi" ] +stm32h742zg = [ "stm32-metapac/stm32h742zg" ] +stm32h742zi = [ "stm32-metapac/stm32h742zi" ] +stm32h743ag = [ "stm32-metapac/stm32h743ag" ] +stm32h743ai = [ "stm32-metapac/stm32h743ai" ] +stm32h743bg = [ "stm32-metapac/stm32h743bg" ] +stm32h743bi = [ "stm32-metapac/stm32h743bi" ] +stm32h743ig = [ "stm32-metapac/stm32h743ig" ] +stm32h743ii = [ "stm32-metapac/stm32h743ii" ] +stm32h743vg = [ "stm32-metapac/stm32h743vg" ] +stm32h743vi = [ "stm32-metapac/stm32h743vi" ] +stm32h743xg = [ "stm32-metapac/stm32h743xg" ] +stm32h743xi = [ "stm32-metapac/stm32h743xi" ] +stm32h743zg = [ "stm32-metapac/stm32h743zg" ] +stm32h743zi = [ "stm32-metapac/stm32h743zi" ] +stm32h745bg-cm7 = [ "stm32-metapac/stm32h745bg-cm7", "_dual-core" ] +stm32h745bg-cm4 = [ "stm32-metapac/stm32h745bg-cm4", "_dual-core" ] +stm32h745bi-cm7 = [ "stm32-metapac/stm32h745bi-cm7", "_dual-core" ] +stm32h745bi-cm4 = [ "stm32-metapac/stm32h745bi-cm4", "_dual-core" ] +stm32h745ig-cm7 = [ "stm32-metapac/stm32h745ig-cm7", "_dual-core" ] +stm32h745ig-cm4 = [ "stm32-metapac/stm32h745ig-cm4", "_dual-core" ] +stm32h745ii-cm7 = [ "stm32-metapac/stm32h745ii-cm7", "_dual-core" ] +stm32h745ii-cm4 = [ "stm32-metapac/stm32h745ii-cm4", "_dual-core" ] +stm32h745xg-cm7 = [ "stm32-metapac/stm32h745xg-cm7", "_dual-core" ] +stm32h745xg-cm4 = [ "stm32-metapac/stm32h745xg-cm4", "_dual-core" ] +stm32h745xi-cm7 = [ "stm32-metapac/stm32h745xi-cm7", "_dual-core" ] +stm32h745xi-cm4 = [ "stm32-metapac/stm32h745xi-cm4", "_dual-core" ] +stm32h745zg-cm7 = [ "stm32-metapac/stm32h745zg-cm7", "_dual-core" ] +stm32h745zg-cm4 = [ "stm32-metapac/stm32h745zg-cm4", "_dual-core" ] +stm32h745zi-cm7 = [ "stm32-metapac/stm32h745zi-cm7", "_dual-core" ] +stm32h745zi-cm4 = [ "stm32-metapac/stm32h745zi-cm4", "_dual-core" ] +stm32h747ag-cm7 = [ "stm32-metapac/stm32h747ag-cm7", "_dual-core" ] +stm32h747ag-cm4 = [ "stm32-metapac/stm32h747ag-cm4", "_dual-core" ] +stm32h747ai-cm7 = [ "stm32-metapac/stm32h747ai-cm7", "_dual-core" ] +stm32h747ai-cm4 = [ "stm32-metapac/stm32h747ai-cm4", "_dual-core" ] +stm32h747bg-cm7 = [ "stm32-metapac/stm32h747bg-cm7", "_dual-core" ] +stm32h747bg-cm4 = [ "stm32-metapac/stm32h747bg-cm4", "_dual-core" ] +stm32h747bi-cm7 = [ "stm32-metapac/stm32h747bi-cm7", "_dual-core" ] +stm32h747bi-cm4 = [ "stm32-metapac/stm32h747bi-cm4", "_dual-core" ] +stm32h747ig-cm7 = [ "stm32-metapac/stm32h747ig-cm7", "_dual-core" ] +stm32h747ig-cm4 = [ "stm32-metapac/stm32h747ig-cm4", "_dual-core" ] +stm32h747ii-cm7 = [ "stm32-metapac/stm32h747ii-cm7", "_dual-core" ] +stm32h747ii-cm4 = [ "stm32-metapac/stm32h747ii-cm4", "_dual-core" ] +stm32h747xg-cm7 = [ "stm32-metapac/stm32h747xg-cm7", "_dual-core" ] +stm32h747xg-cm4 = [ "stm32-metapac/stm32h747xg-cm4", "_dual-core" ] +stm32h747xi-cm7 = [ "stm32-metapac/stm32h747xi-cm7", "_dual-core" ] +stm32h747xi-cm4 = [ "stm32-metapac/stm32h747xi-cm4", "_dual-core" ] +stm32h747zi-cm7 = [ "stm32-metapac/stm32h747zi-cm7", "_dual-core" ] +stm32h747zi-cm4 = [ "stm32-metapac/stm32h747zi-cm4", "_dual-core" ] +stm32h750ib = [ "stm32-metapac/stm32h750ib" ] +stm32h750vb = [ "stm32-metapac/stm32h750vb" ] +stm32h750xb = [ "stm32-metapac/stm32h750xb" ] +stm32h750zb = [ "stm32-metapac/stm32h750zb" ] +stm32h753ai = [ "stm32-metapac/stm32h753ai" ] +stm32h753bi = [ "stm32-metapac/stm32h753bi" ] +stm32h753ii = [ "stm32-metapac/stm32h753ii" ] +stm32h753vi = [ "stm32-metapac/stm32h753vi" ] +stm32h753xi = [ "stm32-metapac/stm32h753xi" ] +stm32h753zi = [ "stm32-metapac/stm32h753zi" ] +stm32h755bi-cm7 = [ "stm32-metapac/stm32h755bi-cm7", "_dual-core" ] +stm32h755bi-cm4 = [ "stm32-metapac/stm32h755bi-cm4", "_dual-core" ] +stm32h755ii-cm7 = [ "stm32-metapac/stm32h755ii-cm7", "_dual-core" ] +stm32h755ii-cm4 = [ "stm32-metapac/stm32h755ii-cm4", "_dual-core" ] +stm32h755xi-cm7 = [ "stm32-metapac/stm32h755xi-cm7", "_dual-core" ] +stm32h755xi-cm4 = [ "stm32-metapac/stm32h755xi-cm4", "_dual-core" ] +stm32h755zi-cm7 = [ "stm32-metapac/stm32h755zi-cm7", "_dual-core" ] +stm32h755zi-cm4 = [ "stm32-metapac/stm32h755zi-cm4", "_dual-core" ] +stm32h757ai-cm7 = [ "stm32-metapac/stm32h757ai-cm7", "_dual-core" ] +stm32h757ai-cm4 = [ "stm32-metapac/stm32h757ai-cm4", "_dual-core" ] +stm32h757bi-cm7 = [ "stm32-metapac/stm32h757bi-cm7", "_dual-core" ] +stm32h757bi-cm4 = [ "stm32-metapac/stm32h757bi-cm4", "_dual-core" ] +stm32h757ii-cm7 = [ "stm32-metapac/stm32h757ii-cm7", "_dual-core" ] +stm32h757ii-cm4 = [ "stm32-metapac/stm32h757ii-cm4", "_dual-core" ] +stm32h757xi-cm7 = [ "stm32-metapac/stm32h757xi-cm7", "_dual-core" ] +stm32h757xi-cm4 = [ "stm32-metapac/stm32h757xi-cm4", "_dual-core" ] +stm32h757zi-cm7 = [ "stm32-metapac/stm32h757zi-cm7", "_dual-core" ] +stm32h757zi-cm4 = [ "stm32-metapac/stm32h757zi-cm4", "_dual-core" ] +stm32h7a3ag = [ "stm32-metapac/stm32h7a3ag" ] +stm32h7a3ai = [ "stm32-metapac/stm32h7a3ai" ] +stm32h7a3ig = [ "stm32-metapac/stm32h7a3ig" ] +stm32h7a3ii = [ "stm32-metapac/stm32h7a3ii" ] +stm32h7a3lg = [ "stm32-metapac/stm32h7a3lg" ] +stm32h7a3li = [ "stm32-metapac/stm32h7a3li" ] +stm32h7a3ng = [ "stm32-metapac/stm32h7a3ng" ] +stm32h7a3ni = [ "stm32-metapac/stm32h7a3ni" ] +stm32h7a3qi = [ "stm32-metapac/stm32h7a3qi" ] +stm32h7a3rg = [ "stm32-metapac/stm32h7a3rg" ] +stm32h7a3ri = [ "stm32-metapac/stm32h7a3ri" ] +stm32h7a3vg = [ "stm32-metapac/stm32h7a3vg" ] +stm32h7a3vi = [ "stm32-metapac/stm32h7a3vi" ] +stm32h7a3zg = [ "stm32-metapac/stm32h7a3zg" ] +stm32h7a3zi = [ "stm32-metapac/stm32h7a3zi" ] +stm32h7b0ab = [ "stm32-metapac/stm32h7b0ab" ] +stm32h7b0ib = [ "stm32-metapac/stm32h7b0ib" ] +stm32h7b0rb = [ "stm32-metapac/stm32h7b0rb" ] +stm32h7b0vb = [ "stm32-metapac/stm32h7b0vb" ] +stm32h7b0zb = [ "stm32-metapac/stm32h7b0zb" ] +stm32h7b3ai = [ "stm32-metapac/stm32h7b3ai" ] +stm32h7b3ii = [ "stm32-metapac/stm32h7b3ii" ] +stm32h7b3li = [ "stm32-metapac/stm32h7b3li" ] +stm32h7b3ni = [ "stm32-metapac/stm32h7b3ni" ] +stm32h7b3qi = [ "stm32-metapac/stm32h7b3qi" ] +stm32h7b3ri = [ "stm32-metapac/stm32h7b3ri" ] +stm32h7b3vi = [ "stm32-metapac/stm32h7b3vi" ] +stm32h7b3zi = [ "stm32-metapac/stm32h7b3zi" ] +stm32h7r3a8 = [ "stm32-metapac/stm32h7r3a8" ] +stm32h7r3i8 = [ "stm32-metapac/stm32h7r3i8" ] +stm32h7r3l8 = [ "stm32-metapac/stm32h7r3l8" ] +stm32h7r3r8 = [ "stm32-metapac/stm32h7r3r8" ] +stm32h7r3v8 = [ "stm32-metapac/stm32h7r3v8" ] +stm32h7r3z8 = [ "stm32-metapac/stm32h7r3z8" ] +stm32h7r7a8 = [ "stm32-metapac/stm32h7r7a8" ] +stm32h7r7i8 = [ "stm32-metapac/stm32h7r7i8" ] +stm32h7r7l8 = [ "stm32-metapac/stm32h7r7l8" ] +stm32h7r7z8 = [ "stm32-metapac/stm32h7r7z8" ] +stm32h7s3a8 = [ "stm32-metapac/stm32h7s3a8" ] +stm32h7s3i8 = [ "stm32-metapac/stm32h7s3i8" ] +stm32h7s3l8 = [ "stm32-metapac/stm32h7s3l8" ] +stm32h7s3r8 = [ "stm32-metapac/stm32h7s3r8" ] +stm32h7s3v8 = [ "stm32-metapac/stm32h7s3v8" ] +stm32h7s3z8 = [ "stm32-metapac/stm32h7s3z8" ] +stm32h7s7a8 = [ "stm32-metapac/stm32h7s7a8" ] +stm32h7s7i8 = [ "stm32-metapac/stm32h7s7i8" ] +stm32h7s7l8 = [ "stm32-metapac/stm32h7s7l8" ] +stm32h7s7z8 = [ "stm32-metapac/stm32h7s7z8" ] +stm32l010c6 = [ "stm32-metapac/stm32l010c6" ] +stm32l010f4 = [ "stm32-metapac/stm32l010f4" ] +stm32l010k4 = [ "stm32-metapac/stm32l010k4" ] +stm32l010k8 = [ "stm32-metapac/stm32l010k8" ] +stm32l010r8 = [ "stm32-metapac/stm32l010r8" ] +stm32l010rb = [ "stm32-metapac/stm32l010rb" ] +stm32l011d3 = [ "stm32-metapac/stm32l011d3" ] +stm32l011d4 = [ "stm32-metapac/stm32l011d4" ] +stm32l011e3 = [ "stm32-metapac/stm32l011e3" ] +stm32l011e4 = [ "stm32-metapac/stm32l011e4" ] +stm32l011f3 = [ "stm32-metapac/stm32l011f3" ] +stm32l011f4 = [ "stm32-metapac/stm32l011f4" ] +stm32l011g3 = [ "stm32-metapac/stm32l011g3" ] +stm32l011g4 = [ "stm32-metapac/stm32l011g4" ] +stm32l011k3 = [ "stm32-metapac/stm32l011k3" ] +stm32l011k4 = [ "stm32-metapac/stm32l011k4" ] +stm32l021d4 = [ "stm32-metapac/stm32l021d4" ] +stm32l021f4 = [ "stm32-metapac/stm32l021f4" ] +stm32l021g4 = [ "stm32-metapac/stm32l021g4" ] +stm32l021k4 = [ "stm32-metapac/stm32l021k4" ] +stm32l031c4 = [ "stm32-metapac/stm32l031c4" ] +stm32l031c6 = [ "stm32-metapac/stm32l031c6" ] +stm32l031e4 = [ "stm32-metapac/stm32l031e4" ] +stm32l031e6 = [ "stm32-metapac/stm32l031e6" ] +stm32l031f4 = [ "stm32-metapac/stm32l031f4" ] +stm32l031f6 = [ "stm32-metapac/stm32l031f6" ] +stm32l031g4 = [ "stm32-metapac/stm32l031g4" ] +stm32l031g6 = [ "stm32-metapac/stm32l031g6" ] +stm32l031k4 = [ "stm32-metapac/stm32l031k4" ] +stm32l031k6 = [ "stm32-metapac/stm32l031k6" ] +stm32l041c4 = [ "stm32-metapac/stm32l041c4" ] +stm32l041c6 = [ "stm32-metapac/stm32l041c6" ] +stm32l041e6 = [ "stm32-metapac/stm32l041e6" ] +stm32l041f6 = [ "stm32-metapac/stm32l041f6" ] +stm32l041g6 = [ "stm32-metapac/stm32l041g6" ] +stm32l041k6 = [ "stm32-metapac/stm32l041k6" ] +stm32l051c6 = [ "stm32-metapac/stm32l051c6" ] +stm32l051c8 = [ "stm32-metapac/stm32l051c8" ] +stm32l051k6 = [ "stm32-metapac/stm32l051k6" ] +stm32l051k8 = [ "stm32-metapac/stm32l051k8" ] +stm32l051r6 = [ "stm32-metapac/stm32l051r6" ] +stm32l051r8 = [ "stm32-metapac/stm32l051r8" ] +stm32l051t6 = [ "stm32-metapac/stm32l051t6" ] +stm32l051t8 = [ "stm32-metapac/stm32l051t8" ] +stm32l052c6 = [ "stm32-metapac/stm32l052c6" ] +stm32l052c8 = [ "stm32-metapac/stm32l052c8" ] +stm32l052k6 = [ "stm32-metapac/stm32l052k6" ] +stm32l052k8 = [ "stm32-metapac/stm32l052k8" ] +stm32l052r6 = [ "stm32-metapac/stm32l052r6" ] +stm32l052r8 = [ "stm32-metapac/stm32l052r8" ] +stm32l052t6 = [ "stm32-metapac/stm32l052t6" ] +stm32l052t8 = [ "stm32-metapac/stm32l052t8" ] +stm32l053c6 = [ "stm32-metapac/stm32l053c6" ] +stm32l053c8 = [ "stm32-metapac/stm32l053c8" ] +stm32l053r6 = [ "stm32-metapac/stm32l053r6" ] +stm32l053r8 = [ "stm32-metapac/stm32l053r8" ] +stm32l062c8 = [ "stm32-metapac/stm32l062c8" ] +stm32l062k8 = [ "stm32-metapac/stm32l062k8" ] +stm32l063c8 = [ "stm32-metapac/stm32l063c8" ] +stm32l063r8 = [ "stm32-metapac/stm32l063r8" ] +stm32l071c8 = [ "stm32-metapac/stm32l071c8" ] +stm32l071cb = [ "stm32-metapac/stm32l071cb" ] +stm32l071cz = [ "stm32-metapac/stm32l071cz" ] +stm32l071k8 = [ "stm32-metapac/stm32l071k8" ] +stm32l071kb = [ "stm32-metapac/stm32l071kb" ] +stm32l071kz = [ "stm32-metapac/stm32l071kz" ] +stm32l071rb = [ "stm32-metapac/stm32l071rb" ] +stm32l071rz = [ "stm32-metapac/stm32l071rz" ] +stm32l071v8 = [ "stm32-metapac/stm32l071v8" ] +stm32l071vb = [ "stm32-metapac/stm32l071vb" ] +stm32l071vz = [ "stm32-metapac/stm32l071vz" ] +stm32l072cb = [ "stm32-metapac/stm32l072cb" ] +stm32l072cz = [ "stm32-metapac/stm32l072cz" ] +stm32l072kb = [ "stm32-metapac/stm32l072kb" ] +stm32l072kz = [ "stm32-metapac/stm32l072kz" ] +stm32l072rb = [ "stm32-metapac/stm32l072rb" ] +stm32l072rz = [ "stm32-metapac/stm32l072rz" ] +stm32l072v8 = [ "stm32-metapac/stm32l072v8" ] +stm32l072vb = [ "stm32-metapac/stm32l072vb" ] +stm32l072vz = [ "stm32-metapac/stm32l072vz" ] +stm32l073cb = [ "stm32-metapac/stm32l073cb" ] +stm32l073cz = [ "stm32-metapac/stm32l073cz" ] +stm32l073rb = [ "stm32-metapac/stm32l073rb" ] +stm32l073rz = [ "stm32-metapac/stm32l073rz" ] +stm32l073v8 = [ "stm32-metapac/stm32l073v8" ] +stm32l073vb = [ "stm32-metapac/stm32l073vb" ] +stm32l073vz = [ "stm32-metapac/stm32l073vz" ] +stm32l081cb = [ "stm32-metapac/stm32l081cb" ] +stm32l081cz = [ "stm32-metapac/stm32l081cz" ] +stm32l081kz = [ "stm32-metapac/stm32l081kz" ] +stm32l082cz = [ "stm32-metapac/stm32l082cz" ] +stm32l082kb = [ "stm32-metapac/stm32l082kb" ] +stm32l082kz = [ "stm32-metapac/stm32l082kz" ] +stm32l083cb = [ "stm32-metapac/stm32l083cb" ] +stm32l083cz = [ "stm32-metapac/stm32l083cz" ] +stm32l083rb = [ "stm32-metapac/stm32l083rb" ] +stm32l083rz = [ "stm32-metapac/stm32l083rz" ] +stm32l083v8 = [ "stm32-metapac/stm32l083v8" ] +stm32l083vb = [ "stm32-metapac/stm32l083vb" ] +stm32l083vz = [ "stm32-metapac/stm32l083vz" ] +stm32l100c6 = [ "stm32-metapac/stm32l100c6" ] +stm32l100c6-a = [ "stm32-metapac/stm32l100c6-a" ] +stm32l100r8 = [ "stm32-metapac/stm32l100r8" ] +stm32l100r8-a = [ "stm32-metapac/stm32l100r8-a" ] +stm32l100rb = [ "stm32-metapac/stm32l100rb" ] +stm32l100rb-a = [ "stm32-metapac/stm32l100rb-a" ] +stm32l100rc = [ "stm32-metapac/stm32l100rc" ] +stm32l151c6 = [ "stm32-metapac/stm32l151c6" ] +stm32l151c6-a = [ "stm32-metapac/stm32l151c6-a" ] +stm32l151c8 = [ "stm32-metapac/stm32l151c8" ] +stm32l151c8-a = [ "stm32-metapac/stm32l151c8-a" ] +stm32l151cb = [ "stm32-metapac/stm32l151cb" ] +stm32l151cb-a = [ "stm32-metapac/stm32l151cb-a" ] +stm32l151cc = [ "stm32-metapac/stm32l151cc" ] +stm32l151qc = [ "stm32-metapac/stm32l151qc" ] +stm32l151qd = [ "stm32-metapac/stm32l151qd" ] +stm32l151qe = [ "stm32-metapac/stm32l151qe" ] +stm32l151r6 = [ "stm32-metapac/stm32l151r6" ] +stm32l151r6-a = [ "stm32-metapac/stm32l151r6-a" ] +stm32l151r8 = [ "stm32-metapac/stm32l151r8" ] +stm32l151r8-a = [ "stm32-metapac/stm32l151r8-a" ] +stm32l151rb = [ "stm32-metapac/stm32l151rb" ] +stm32l151rb-a = [ "stm32-metapac/stm32l151rb-a" ] +stm32l151rc = [ "stm32-metapac/stm32l151rc" ] +stm32l151rc-a = [ "stm32-metapac/stm32l151rc-a" ] +stm32l151rd = [ "stm32-metapac/stm32l151rd" ] +stm32l151re = [ "stm32-metapac/stm32l151re" ] +stm32l151uc = [ "stm32-metapac/stm32l151uc" ] +stm32l151v8 = [ "stm32-metapac/stm32l151v8" ] +stm32l151v8-a = [ "stm32-metapac/stm32l151v8-a" ] +stm32l151vb = [ "stm32-metapac/stm32l151vb" ] +stm32l151vb-a = [ "stm32-metapac/stm32l151vb-a" ] +stm32l151vc = [ "stm32-metapac/stm32l151vc" ] +stm32l151vc-a = [ "stm32-metapac/stm32l151vc-a" ] +stm32l151vd = [ "stm32-metapac/stm32l151vd" ] +stm32l151vd-x = [ "stm32-metapac/stm32l151vd-x" ] +stm32l151ve = [ "stm32-metapac/stm32l151ve" ] +stm32l151zc = [ "stm32-metapac/stm32l151zc" ] +stm32l151zd = [ "stm32-metapac/stm32l151zd" ] +stm32l151ze = [ "stm32-metapac/stm32l151ze" ] +stm32l152c6 = [ "stm32-metapac/stm32l152c6" ] +stm32l152c6-a = [ "stm32-metapac/stm32l152c6-a" ] +stm32l152c8 = [ "stm32-metapac/stm32l152c8" ] +stm32l152c8-a = [ "stm32-metapac/stm32l152c8-a" ] +stm32l152cb = [ "stm32-metapac/stm32l152cb" ] +stm32l152cb-a = [ "stm32-metapac/stm32l152cb-a" ] +stm32l152cc = [ "stm32-metapac/stm32l152cc" ] +stm32l152qc = [ "stm32-metapac/stm32l152qc" ] +stm32l152qd = [ "stm32-metapac/stm32l152qd" ] +stm32l152qe = [ "stm32-metapac/stm32l152qe" ] +stm32l152r6 = [ "stm32-metapac/stm32l152r6" ] +stm32l152r6-a = [ "stm32-metapac/stm32l152r6-a" ] +stm32l152r8 = [ "stm32-metapac/stm32l152r8" ] +stm32l152r8-a = [ "stm32-metapac/stm32l152r8-a" ] +stm32l152rb = [ "stm32-metapac/stm32l152rb" ] +stm32l152rb-a = [ "stm32-metapac/stm32l152rb-a" ] +stm32l152rc = [ "stm32-metapac/stm32l152rc" ] +stm32l152rc-a = [ "stm32-metapac/stm32l152rc-a" ] +stm32l152rd = [ "stm32-metapac/stm32l152rd" ] +stm32l152re = [ "stm32-metapac/stm32l152re" ] +stm32l152uc = [ "stm32-metapac/stm32l152uc" ] +stm32l152v8 = [ "stm32-metapac/stm32l152v8" ] +stm32l152v8-a = [ "stm32-metapac/stm32l152v8-a" ] +stm32l152vb = [ "stm32-metapac/stm32l152vb" ] +stm32l152vb-a = [ "stm32-metapac/stm32l152vb-a" ] +stm32l152vc = [ "stm32-metapac/stm32l152vc" ] +stm32l152vc-a = [ "stm32-metapac/stm32l152vc-a" ] +stm32l152vd = [ "stm32-metapac/stm32l152vd" ] +stm32l152vd-x = [ "stm32-metapac/stm32l152vd-x" ] +stm32l152ve = [ "stm32-metapac/stm32l152ve" ] +stm32l152zc = [ "stm32-metapac/stm32l152zc" ] +stm32l152zd = [ "stm32-metapac/stm32l152zd" ] +stm32l152ze = [ "stm32-metapac/stm32l152ze" ] +stm32l162qc = [ "stm32-metapac/stm32l162qc" ] +stm32l162qd = [ "stm32-metapac/stm32l162qd" ] +stm32l162rc = [ "stm32-metapac/stm32l162rc" ] +stm32l162rc-a = [ "stm32-metapac/stm32l162rc-a" ] +stm32l162rd = [ "stm32-metapac/stm32l162rd" ] +stm32l162re = [ "stm32-metapac/stm32l162re" ] +stm32l162vc = [ "stm32-metapac/stm32l162vc" ] +stm32l162vc-a = [ "stm32-metapac/stm32l162vc-a" ] +stm32l162vd = [ "stm32-metapac/stm32l162vd" ] +stm32l162vd-x = [ "stm32-metapac/stm32l162vd-x" ] +stm32l162ve = [ "stm32-metapac/stm32l162ve" ] +stm32l162zc = [ "stm32-metapac/stm32l162zc" ] +stm32l162zd = [ "stm32-metapac/stm32l162zd" ] +stm32l162ze = [ "stm32-metapac/stm32l162ze" ] +stm32l412c8 = [ "stm32-metapac/stm32l412c8" ] +stm32l412cb = [ "stm32-metapac/stm32l412cb" ] +stm32l412k8 = [ "stm32-metapac/stm32l412k8" ] +stm32l412kb = [ "stm32-metapac/stm32l412kb" ] +stm32l412r8 = [ "stm32-metapac/stm32l412r8" ] +stm32l412rb = [ "stm32-metapac/stm32l412rb" ] +stm32l412t8 = [ "stm32-metapac/stm32l412t8" ] +stm32l412tb = [ "stm32-metapac/stm32l412tb" ] +stm32l422cb = [ "stm32-metapac/stm32l422cb" ] +stm32l422kb = [ "stm32-metapac/stm32l422kb" ] +stm32l422rb = [ "stm32-metapac/stm32l422rb" ] +stm32l422tb = [ "stm32-metapac/stm32l422tb" ] +stm32l431cb = [ "stm32-metapac/stm32l431cb" ] +stm32l431cc = [ "stm32-metapac/stm32l431cc" ] +stm32l431kb = [ "stm32-metapac/stm32l431kb" ] +stm32l431kc = [ "stm32-metapac/stm32l431kc" ] +stm32l431rb = [ "stm32-metapac/stm32l431rb" ] +stm32l431rc = [ "stm32-metapac/stm32l431rc" ] +stm32l431vc = [ "stm32-metapac/stm32l431vc" ] +stm32l432kb = [ "stm32-metapac/stm32l432kb" ] +stm32l432kc = [ "stm32-metapac/stm32l432kc" ] +stm32l433cb = [ "stm32-metapac/stm32l433cb" ] +stm32l433cc = [ "stm32-metapac/stm32l433cc" ] +stm32l433rb = [ "stm32-metapac/stm32l433rb" ] +stm32l433rc = [ "stm32-metapac/stm32l433rc" ] +stm32l433vc = [ "stm32-metapac/stm32l433vc" ] +stm32l442kc = [ "stm32-metapac/stm32l442kc" ] +stm32l443cc = [ "stm32-metapac/stm32l443cc" ] +stm32l443rc = [ "stm32-metapac/stm32l443rc" ] +stm32l443vc = [ "stm32-metapac/stm32l443vc" ] +stm32l451cc = [ "stm32-metapac/stm32l451cc" ] +stm32l451ce = [ "stm32-metapac/stm32l451ce" ] +stm32l451rc = [ "stm32-metapac/stm32l451rc" ] +stm32l451re = [ "stm32-metapac/stm32l451re" ] +stm32l451vc = [ "stm32-metapac/stm32l451vc" ] +stm32l451ve = [ "stm32-metapac/stm32l451ve" ] +stm32l452cc = [ "stm32-metapac/stm32l452cc" ] +stm32l452ce = [ "stm32-metapac/stm32l452ce" ] +stm32l452rc = [ "stm32-metapac/stm32l452rc" ] +stm32l452re = [ "stm32-metapac/stm32l452re" ] +stm32l452vc = [ "stm32-metapac/stm32l452vc" ] +stm32l452ve = [ "stm32-metapac/stm32l452ve" ] +stm32l462ce = [ "stm32-metapac/stm32l462ce" ] +stm32l462re = [ "stm32-metapac/stm32l462re" ] +stm32l462ve = [ "stm32-metapac/stm32l462ve" ] +stm32l471qe = [ "stm32-metapac/stm32l471qe" ] +stm32l471qg = [ "stm32-metapac/stm32l471qg" ] +stm32l471re = [ "stm32-metapac/stm32l471re" ] +stm32l471rg = [ "stm32-metapac/stm32l471rg" ] +stm32l471ve = [ "stm32-metapac/stm32l471ve" ] +stm32l471vg = [ "stm32-metapac/stm32l471vg" ] +stm32l471ze = [ "stm32-metapac/stm32l471ze" ] +stm32l471zg = [ "stm32-metapac/stm32l471zg" ] +stm32l475rc = [ "stm32-metapac/stm32l475rc" ] +stm32l475re = [ "stm32-metapac/stm32l475re" ] +stm32l475rg = [ "stm32-metapac/stm32l475rg" ] +stm32l475vc = [ "stm32-metapac/stm32l475vc" ] +stm32l475ve = [ "stm32-metapac/stm32l475ve" ] +stm32l475vg = [ "stm32-metapac/stm32l475vg" ] +stm32l476je = [ "stm32-metapac/stm32l476je" ] +stm32l476jg = [ "stm32-metapac/stm32l476jg" ] +stm32l476me = [ "stm32-metapac/stm32l476me" ] +stm32l476mg = [ "stm32-metapac/stm32l476mg" ] +stm32l476qe = [ "stm32-metapac/stm32l476qe" ] +stm32l476qg = [ "stm32-metapac/stm32l476qg" ] +stm32l476rc = [ "stm32-metapac/stm32l476rc" ] +stm32l476re = [ "stm32-metapac/stm32l476re" ] +stm32l476rg = [ "stm32-metapac/stm32l476rg" ] +stm32l476vc = [ "stm32-metapac/stm32l476vc" ] +stm32l476ve = [ "stm32-metapac/stm32l476ve" ] +stm32l476vg = [ "stm32-metapac/stm32l476vg" ] +stm32l476ze = [ "stm32-metapac/stm32l476ze" ] +stm32l476zg = [ "stm32-metapac/stm32l476zg" ] +stm32l486jg = [ "stm32-metapac/stm32l486jg" ] +stm32l486qg = [ "stm32-metapac/stm32l486qg" ] +stm32l486rg = [ "stm32-metapac/stm32l486rg" ] +stm32l486vg = [ "stm32-metapac/stm32l486vg" ] +stm32l486zg = [ "stm32-metapac/stm32l486zg" ] +stm32l496ae = [ "stm32-metapac/stm32l496ae" ] +stm32l496ag = [ "stm32-metapac/stm32l496ag" ] +stm32l496qe = [ "stm32-metapac/stm32l496qe" ] +stm32l496qg = [ "stm32-metapac/stm32l496qg" ] +stm32l496re = [ "stm32-metapac/stm32l496re" ] +stm32l496rg = [ "stm32-metapac/stm32l496rg" ] +stm32l496ve = [ "stm32-metapac/stm32l496ve" ] +stm32l496vg = [ "stm32-metapac/stm32l496vg" ] +stm32l496wg = [ "stm32-metapac/stm32l496wg" ] +stm32l496ze = [ "stm32-metapac/stm32l496ze" ] +stm32l496zg = [ "stm32-metapac/stm32l496zg" ] +stm32l4a6ag = [ "stm32-metapac/stm32l4a6ag" ] +stm32l4a6qg = [ "stm32-metapac/stm32l4a6qg" ] +stm32l4a6rg = [ "stm32-metapac/stm32l4a6rg" ] +stm32l4a6vg = [ "stm32-metapac/stm32l4a6vg" ] +stm32l4a6zg = [ "stm32-metapac/stm32l4a6zg" ] +stm32l4p5ae = [ "stm32-metapac/stm32l4p5ae" ] +stm32l4p5ag = [ "stm32-metapac/stm32l4p5ag" ] +stm32l4p5ce = [ "stm32-metapac/stm32l4p5ce" ] +stm32l4p5cg = [ "stm32-metapac/stm32l4p5cg" ] +stm32l4p5qe = [ "stm32-metapac/stm32l4p5qe" ] +stm32l4p5qg = [ "stm32-metapac/stm32l4p5qg" ] +stm32l4p5re = [ "stm32-metapac/stm32l4p5re" ] +stm32l4p5rg = [ "stm32-metapac/stm32l4p5rg" ] +stm32l4p5ve = [ "stm32-metapac/stm32l4p5ve" ] +stm32l4p5vg = [ "stm32-metapac/stm32l4p5vg" ] +stm32l4p5ze = [ "stm32-metapac/stm32l4p5ze" ] +stm32l4p5zg = [ "stm32-metapac/stm32l4p5zg" ] +stm32l4q5ag = [ "stm32-metapac/stm32l4q5ag" ] +stm32l4q5cg = [ "stm32-metapac/stm32l4q5cg" ] +stm32l4q5qg = [ "stm32-metapac/stm32l4q5qg" ] +stm32l4q5rg = [ "stm32-metapac/stm32l4q5rg" ] +stm32l4q5vg = [ "stm32-metapac/stm32l4q5vg" ] +stm32l4q5zg = [ "stm32-metapac/stm32l4q5zg" ] +stm32l4r5ag = [ "stm32-metapac/stm32l4r5ag" ] +stm32l4r5ai = [ "stm32-metapac/stm32l4r5ai" ] +stm32l4r5qg = [ "stm32-metapac/stm32l4r5qg" ] +stm32l4r5qi = [ "stm32-metapac/stm32l4r5qi" ] +stm32l4r5vg = [ "stm32-metapac/stm32l4r5vg" ] +stm32l4r5vi = [ "stm32-metapac/stm32l4r5vi" ] +stm32l4r5zg = [ "stm32-metapac/stm32l4r5zg" ] +stm32l4r5zi = [ "stm32-metapac/stm32l4r5zi" ] +stm32l4r7ai = [ "stm32-metapac/stm32l4r7ai" ] +stm32l4r7vi = [ "stm32-metapac/stm32l4r7vi" ] +stm32l4r7zi = [ "stm32-metapac/stm32l4r7zi" ] +stm32l4r9ag = [ "stm32-metapac/stm32l4r9ag" ] +stm32l4r9ai = [ "stm32-metapac/stm32l4r9ai" ] +stm32l4r9vg = [ "stm32-metapac/stm32l4r9vg" ] +stm32l4r9vi = [ "stm32-metapac/stm32l4r9vi" ] +stm32l4r9zg = [ "stm32-metapac/stm32l4r9zg" ] +stm32l4r9zi = [ "stm32-metapac/stm32l4r9zi" ] +stm32l4s5ai = [ "stm32-metapac/stm32l4s5ai" ] +stm32l4s5qi = [ "stm32-metapac/stm32l4s5qi" ] +stm32l4s5vi = [ "stm32-metapac/stm32l4s5vi" ] +stm32l4s5zi = [ "stm32-metapac/stm32l4s5zi" ] +stm32l4s7ai = [ "stm32-metapac/stm32l4s7ai" ] +stm32l4s7vi = [ "stm32-metapac/stm32l4s7vi" ] +stm32l4s7zi = [ "stm32-metapac/stm32l4s7zi" ] +stm32l4s9ai = [ "stm32-metapac/stm32l4s9ai" ] +stm32l4s9vi = [ "stm32-metapac/stm32l4s9vi" ] +stm32l4s9zi = [ "stm32-metapac/stm32l4s9zi" ] +stm32l552cc = [ "stm32-metapac/stm32l552cc" ] +stm32l552ce = [ "stm32-metapac/stm32l552ce" ] +stm32l552me = [ "stm32-metapac/stm32l552me" ] +stm32l552qc = [ "stm32-metapac/stm32l552qc" ] +stm32l552qe = [ "stm32-metapac/stm32l552qe" ] +stm32l552rc = [ "stm32-metapac/stm32l552rc" ] +stm32l552re = [ "stm32-metapac/stm32l552re" ] +stm32l552vc = [ "stm32-metapac/stm32l552vc" ] +stm32l552ve = [ "stm32-metapac/stm32l552ve" ] +stm32l552zc = [ "stm32-metapac/stm32l552zc" ] +stm32l552ze = [ "stm32-metapac/stm32l552ze" ] +stm32l562ce = [ "stm32-metapac/stm32l562ce" ] +stm32l562me = [ "stm32-metapac/stm32l562me" ] +stm32l562qe = [ "stm32-metapac/stm32l562qe" ] +stm32l562re = [ "stm32-metapac/stm32l562re" ] +stm32l562ve = [ "stm32-metapac/stm32l562ve" ] +stm32l562ze = [ "stm32-metapac/stm32l562ze" ] +stm32u031c6 = [ "stm32-metapac/stm32u031c6" ] +stm32u031c8 = [ "stm32-metapac/stm32u031c8" ] +stm32u031f4 = [ "stm32-metapac/stm32u031f4" ] +stm32u031f6 = [ "stm32-metapac/stm32u031f6" ] +stm32u031f8 = [ "stm32-metapac/stm32u031f8" ] +stm32u031g6 = [ "stm32-metapac/stm32u031g6" ] +stm32u031g8 = [ "stm32-metapac/stm32u031g8" ] +stm32u031k4 = [ "stm32-metapac/stm32u031k4" ] +stm32u031k6 = [ "stm32-metapac/stm32u031k6" ] +stm32u031k8 = [ "stm32-metapac/stm32u031k8" ] +stm32u031r6 = [ "stm32-metapac/stm32u031r6" ] +stm32u031r8 = [ "stm32-metapac/stm32u031r8" ] +stm32u073c8 = [ "stm32-metapac/stm32u073c8" ] +stm32u073cb = [ "stm32-metapac/stm32u073cb" ] +stm32u073cc = [ "stm32-metapac/stm32u073cc" ] +stm32u073h8 = [ "stm32-metapac/stm32u073h8" ] +stm32u073hb = [ "stm32-metapac/stm32u073hb" ] +stm32u073hc = [ "stm32-metapac/stm32u073hc" ] +stm32u073k8 = [ "stm32-metapac/stm32u073k8" ] +stm32u073kb = [ "stm32-metapac/stm32u073kb" ] +stm32u073kc = [ "stm32-metapac/stm32u073kc" ] +stm32u073m8 = [ "stm32-metapac/stm32u073m8" ] +stm32u073mb = [ "stm32-metapac/stm32u073mb" ] +stm32u073mc = [ "stm32-metapac/stm32u073mc" ] +stm32u073r8 = [ "stm32-metapac/stm32u073r8" ] +stm32u073rb = [ "stm32-metapac/stm32u073rb" ] +stm32u073rc = [ "stm32-metapac/stm32u073rc" ] +stm32u083cc = [ "stm32-metapac/stm32u083cc" ] +stm32u083hc = [ "stm32-metapac/stm32u083hc" ] +stm32u083kc = [ "stm32-metapac/stm32u083kc" ] +stm32u083mc = [ "stm32-metapac/stm32u083mc" ] +stm32u083rc = [ "stm32-metapac/stm32u083rc" ] +stm32u535cb = [ "stm32-metapac/stm32u535cb" ] +stm32u535cc = [ "stm32-metapac/stm32u535cc" ] +stm32u535ce = [ "stm32-metapac/stm32u535ce" ] +stm32u535je = [ "stm32-metapac/stm32u535je" ] +stm32u535nc = [ "stm32-metapac/stm32u535nc" ] +stm32u535ne = [ "stm32-metapac/stm32u535ne" ] +stm32u535rb = [ "stm32-metapac/stm32u535rb" ] +stm32u535rc = [ "stm32-metapac/stm32u535rc" ] +stm32u535re = [ "stm32-metapac/stm32u535re" ] +stm32u535vc = [ "stm32-metapac/stm32u535vc" ] +stm32u535ve = [ "stm32-metapac/stm32u535ve" ] +stm32u545ce = [ "stm32-metapac/stm32u545ce" ] +stm32u545je = [ "stm32-metapac/stm32u545je" ] +stm32u545ne = [ "stm32-metapac/stm32u545ne" ] +stm32u545re = [ "stm32-metapac/stm32u545re" ] +stm32u545ve = [ "stm32-metapac/stm32u545ve" ] +stm32u575ag = [ "stm32-metapac/stm32u575ag" ] +stm32u575ai = [ "stm32-metapac/stm32u575ai" ] +stm32u575cg = [ "stm32-metapac/stm32u575cg" ] +stm32u575ci = [ "stm32-metapac/stm32u575ci" ] +stm32u575og = [ "stm32-metapac/stm32u575og" ] +stm32u575oi = [ "stm32-metapac/stm32u575oi" ] +stm32u575qg = [ "stm32-metapac/stm32u575qg" ] +stm32u575qi = [ "stm32-metapac/stm32u575qi" ] +stm32u575rg = [ "stm32-metapac/stm32u575rg" ] +stm32u575ri = [ "stm32-metapac/stm32u575ri" ] +stm32u575vg = [ "stm32-metapac/stm32u575vg" ] +stm32u575vi = [ "stm32-metapac/stm32u575vi" ] +stm32u575zg = [ "stm32-metapac/stm32u575zg" ] +stm32u575zi = [ "stm32-metapac/stm32u575zi" ] +stm32u585ai = [ "stm32-metapac/stm32u585ai" ] +stm32u585ci = [ "stm32-metapac/stm32u585ci" ] +stm32u585oi = [ "stm32-metapac/stm32u585oi" ] +stm32u585qi = [ "stm32-metapac/stm32u585qi" ] +stm32u585ri = [ "stm32-metapac/stm32u585ri" ] +stm32u585vi = [ "stm32-metapac/stm32u585vi" ] +stm32u585zi = [ "stm32-metapac/stm32u585zi" ] +stm32u595ai = [ "stm32-metapac/stm32u595ai" ] +stm32u595aj = [ "stm32-metapac/stm32u595aj" ] +stm32u595qi = [ "stm32-metapac/stm32u595qi" ] +stm32u595qj = [ "stm32-metapac/stm32u595qj" ] +stm32u595ri = [ "stm32-metapac/stm32u595ri" ] +stm32u595rj = [ "stm32-metapac/stm32u595rj" ] +stm32u595vi = [ "stm32-metapac/stm32u595vi" ] +stm32u595vj = [ "stm32-metapac/stm32u595vj" ] +stm32u595zi = [ "stm32-metapac/stm32u595zi" ] +stm32u595zj = [ "stm32-metapac/stm32u595zj" ] +stm32u599bj = [ "stm32-metapac/stm32u599bj" ] +stm32u599ni = [ "stm32-metapac/stm32u599ni" ] +stm32u599nj = [ "stm32-metapac/stm32u599nj" ] +stm32u599vi = [ "stm32-metapac/stm32u599vi" ] +stm32u599vj = [ "stm32-metapac/stm32u599vj" ] +stm32u599zi = [ "stm32-metapac/stm32u599zi" ] +stm32u599zj = [ "stm32-metapac/stm32u599zj" ] +stm32u5a5aj = [ "stm32-metapac/stm32u5a5aj" ] +stm32u5a5qi = [ "stm32-metapac/stm32u5a5qi" ] +stm32u5a5qj = [ "stm32-metapac/stm32u5a5qj" ] +stm32u5a5rj = [ "stm32-metapac/stm32u5a5rj" ] +stm32u5a5vj = [ "stm32-metapac/stm32u5a5vj" ] +stm32u5a5zj = [ "stm32-metapac/stm32u5a5zj" ] +stm32u5a9bj = [ "stm32-metapac/stm32u5a9bj" ] +stm32u5a9nj = [ "stm32-metapac/stm32u5a9nj" ] +stm32u5a9vj = [ "stm32-metapac/stm32u5a9vj" ] +stm32u5a9zj = [ "stm32-metapac/stm32u5a9zj" ] +stm32u5f7vi = [ "stm32-metapac/stm32u5f7vi" ] +stm32u5f7vj = [ "stm32-metapac/stm32u5f7vj" ] +stm32u5f9bj = [ "stm32-metapac/stm32u5f9bj" ] +stm32u5f9nj = [ "stm32-metapac/stm32u5f9nj" ] +stm32u5f9vi = [ "stm32-metapac/stm32u5f9vi" ] +stm32u5f9vj = [ "stm32-metapac/stm32u5f9vj" ] +stm32u5f9zi = [ "stm32-metapac/stm32u5f9zi" ] +stm32u5f9zj = [ "stm32-metapac/stm32u5f9zj" ] +stm32u5g7vj = [ "stm32-metapac/stm32u5g7vj" ] +stm32u5g9bj = [ "stm32-metapac/stm32u5g9bj" ] +stm32u5g9nj = [ "stm32-metapac/stm32u5g9nj" ] +stm32u5g9vj = [ "stm32-metapac/stm32u5g9vj" ] +stm32u5g9zj = [ "stm32-metapac/stm32u5g9zj" ] +stm32wb10cc = [ "stm32-metapac/stm32wb10cc" ] +stm32wb15cc = [ "stm32-metapac/stm32wb15cc" ] +stm32wb30ce = [ "stm32-metapac/stm32wb30ce" ] +stm32wb35cc = [ "stm32-metapac/stm32wb35cc" ] +stm32wb35ce = [ "stm32-metapac/stm32wb35ce" ] +stm32wb50cg = [ "stm32-metapac/stm32wb50cg" ] +stm32wb55cc = [ "stm32-metapac/stm32wb55cc" ] +stm32wb55ce = [ "stm32-metapac/stm32wb55ce" ] +stm32wb55cg = [ "stm32-metapac/stm32wb55cg" ] +stm32wb55rc = [ "stm32-metapac/stm32wb55rc" ] +stm32wb55re = [ "stm32-metapac/stm32wb55re" ] +stm32wb55rg = [ "stm32-metapac/stm32wb55rg" ] +stm32wb55vc = [ "stm32-metapac/stm32wb55vc" ] +stm32wb55ve = [ "stm32-metapac/stm32wb55ve" ] +stm32wb55vg = [ "stm32-metapac/stm32wb55vg" ] +stm32wb55vy = [ "stm32-metapac/stm32wb55vy" ] +stm32wba50ke = [ "stm32-metapac/stm32wba50ke" ] +stm32wba50kg = [ "stm32-metapac/stm32wba50kg" ] +stm32wba52ce = [ "stm32-metapac/stm32wba52ce" ] +stm32wba52cg = [ "stm32-metapac/stm32wba52cg" ] +stm32wba52ke = [ "stm32-metapac/stm32wba52ke" ] +stm32wba52kg = [ "stm32-metapac/stm32wba52kg" ] +stm32wba54ce = [ "stm32-metapac/stm32wba54ce" ] +stm32wba54cg = [ "stm32-metapac/stm32wba54cg" ] +stm32wba54ke = [ "stm32-metapac/stm32wba54ke" ] +stm32wba54kg = [ "stm32-metapac/stm32wba54kg" ] +stm32wba55ce = [ "stm32-metapac/stm32wba55ce" ] +stm32wba55cg = [ "stm32-metapac/stm32wba55cg" ] +stm32wba55he = [ "stm32-metapac/stm32wba55he" ] +stm32wba55hg = [ "stm32-metapac/stm32wba55hg" ] +stm32wba55ue = [ "stm32-metapac/stm32wba55ue" ] +stm32wba55ug = [ "stm32-metapac/stm32wba55ug" ] +stm32wl54cc-cm4 = [ "stm32-metapac/stm32wl54cc-cm4", "_dual-core" ] +stm32wl54cc-cm0p = [ "stm32-metapac/stm32wl54cc-cm0p", "_dual-core" ] +stm32wl54jc-cm4 = [ "stm32-metapac/stm32wl54jc-cm4", "_dual-core" ] +stm32wl54jc-cm0p = [ "stm32-metapac/stm32wl54jc-cm0p", "_dual-core" ] +stm32wl55cc-cm4 = [ "stm32-metapac/stm32wl55cc-cm4", "_dual-core" ] +stm32wl55cc-cm0p = [ "stm32-metapac/stm32wl55cc-cm0p", "_dual-core" ] +stm32wl55jc-cm4 = [ "stm32-metapac/stm32wl55jc-cm4", "_dual-core" ] +stm32wl55jc-cm0p = [ "stm32-metapac/stm32wl55jc-cm0p", "_dual-core" ] +stm32wle4c8 = [ "stm32-metapac/stm32wle4c8" ] +stm32wle4cb = [ "stm32-metapac/stm32wle4cb" ] +stm32wle4cc = [ "stm32-metapac/stm32wle4cc" ] +stm32wle4j8 = [ "stm32-metapac/stm32wle4j8" ] +stm32wle4jb = [ "stm32-metapac/stm32wle4jb" ] +stm32wle4jc = [ "stm32-metapac/stm32wle4jc" ] +stm32wle5c8 = [ "stm32-metapac/stm32wle5c8" ] +stm32wle5cb = [ "stm32-metapac/stm32wle5cb" ] +stm32wle5cc = [ "stm32-metapac/stm32wle5cc" ] +stm32wle5j8 = [ "stm32-metapac/stm32wle5j8" ] +stm32wle5jb = [ "stm32-metapac/stm32wle5jb" ] +stm32wle5jc = [ "stm32-metapac/stm32wle5jc" ] diff --git a/embassy/embassy-stm32/README.md b/embassy/embassy-stm32/README.md new file mode 100644 index 0000000..445366f --- /dev/null +++ b/embassy/embassy-stm32/README.md @@ -0,0 +1,38 @@ +# Embassy STM32 HAL + +The embassy-stm32 HAL aims to provide a safe, idiomatic hardware abstraction layer for all STM32 families. The HAL implements both blocking and async APIs for many peripherals. Where appropriate, traits from both blocking and asynchronous versions of [embedded-hal](https://docs.rs/embedded-hal/latest/embedded_hal/) v0.2 and v1.0 are implemented, as well as serial traits from embedded-io\[-async]. + +* [embassy-stm32 on crates.io](https://crates.io/crates/embassy-stm32) +* [Documentation](https://docs.embassy.dev/embassy-stm32/) (**Important:** use docs.embassy.dev rather than docs.rs to see the specific docs for the chip you’re using!) +* [Source](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32) +* [Examples](https://github.com/embassy-rs/embassy/tree/main/examples) + +## embassy-stm32 supports all STM32 chip families + +STM32 microcontrollers come in many families and flavors, and supporting all of them is a big undertaking. Embassy takes advantage of the fact that the STM32 peripheral versions are shared across chip families. For example, instead of re-implementing the SPI 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. + +In practice, this works as follows: + +1. You tell the compiler which chip you’re using with a feature flag +1. The stm32-metapac module generates register types for that chip at compile time, based on data from the stm32-data module +1. The embassy-stm32 HAL picks the correct implementation each peripheral based on automatically-generated feature flags, and applies any other tweaks which are required for the HAL to work on that chip + +Be aware that, while embassy-stm32 strives to consistently support all peripherals across all chips, this approach can lead to slightly different APIs and capabilities being available on different families. Check the [documentation](https://docs.embassy.dev/embassy-stm32/) for the specific chip you’re using to confirm exactly what’s available. + +## Embedded-hal + +The `embassy-stm32` HAL implements the traits from [embedded-hal](https://crates.io/crates/embedded-hal) (v0.2 and 1.0) and [embedded-hal-async](https://crates.io/crates/embedded-hal-async), as well as [embedded-io](https://crates.io/crates/embedded-io) and [embedded-io-async](https://crates.io/crates/embedded-io-async). + +## `embassy-time` time driver +If a `time-driver-*` feature is enabled, embassy-stm32 provides a time driver for use with [embassy-time](https://docs.embassy.dev/embassy-time/). You can pick which hardware timer is used for this internally via the `time-driver-tim*` features, or let embassy pick with `time-driver-any`. + +embassy-time has a default tick rate of 1MHz, which is fast enough to cause problems with the 16-bit timers currently supported by the embassy-stm32 time driver (specifically, if a critical section delays an IRQ by more than 32ms). To avoid this, it’s recommended to pick a lower tick rate. 32.768kHz is a reasonable default for many purposes. + +## Interoperability + +This crate can run on any executor. + +Optionally, some features requiring [`embassy-time`](https://crates.io/crates/embassy-time) can be activated with the `time` feature. If you enable it, +you must link an `embassy-time` driver in your project. + +The `low-power` feature integrates specifically with `embassy-executor`, it can't be used on other executors for now. diff --git a/embassy/embassy-stm32/build.rs b/embassy/embassy-stm32/build.rs new file mode 100644 index 0000000..f92d8b8 --- /dev/null +++ b/embassy/embassy-stm32/build.rs @@ -0,0 +1,1854 @@ +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::fmt::Write as _; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::{env, fs}; + +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; +use stm32_metapac::metadata::ir::BitOffset; +use stm32_metapac::metadata::{ + MemoryRegionKind, PeripheralRccKernelClock, PeripheralRccRegister, PeripheralRegisters, StopMode, ALL_CHIPS, + ALL_PERIPHERAL_VERSIONS, METADATA, +}; + +#[path = "./build_common.rs"] +mod common; + +fn main() { + let mut cfgs = common::CfgSet::new(); + common::set_target_cfgs(&mut cfgs); + + let chip_name = match env::vars() + .map(|(a, _)| a) + .filter(|x| x.starts_with("CARGO_FEATURE_STM32")) + .get_one() + { + Ok(x) => x, + Err(GetOneError::None) => panic!("No stm32xx Cargo feature enabled"), + Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"), + } + .strip_prefix("CARGO_FEATURE_") + .unwrap() + .to_ascii_lowercase(); + + eprintln!("chip: {chip_name}"); + + for p in METADATA.peripherals { + if let Some(r) = &p.registers { + cfgs.enable(r.kind); + cfgs.enable(format!("{}_{}", r.kind, r.version)); + } + } + + for &(kind, versions) in ALL_PERIPHERAL_VERSIONS.iter() { + cfgs.declare(kind); + for &version in versions.iter() { + cfgs.declare(format!("{}_{}", kind, version)); + } + } + + // ======== + // Generate singletons + + let mut singletons: Vec = Vec::new(); + for p in METADATA.peripherals { + if let Some(r) = &p.registers { + if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" || r.kind == "otg" || r.kind == "octospi" { + // TODO: should we emit this for all peripherals? if so, we will need a list of all + // possible peripherals across all chips, so that we can declare the configs + // (replacing the hard-coded list of `peri_*` cfgs below) + cfgs.enable(format!("peri_{}", p.name.to_ascii_lowercase())); + } + + match r.kind { + // Generate singletons per pin, not per port + "gpio" => { + let port_letter = p.name.strip_prefix("GPIO").unwrap(); + for pin_num in 0..16 { + singletons.push(format!("P{}{}", port_letter, pin_num)); + } + } + + // No singleton for these, the HAL handles them specially. + "exti" => {} + + // We *shouldn't* have singletons for these, but the HAL currently requires + // singletons, for using with RccPeripheral to enable/disable clocks to them. + "rcc" => { + for pin in p.pins { + if pin.signal.starts_with("MCO") { + let name = pin.signal.replace('_', "").to_string(); + if !singletons.contains(&name) { + cfgs.enable(name.to_ascii_lowercase()); + singletons.push(name); + } + } + } + singletons.push(p.name.to_string()); + } + //"dbgmcu" => {} + //"syscfg" => {} + //"dma" => {} + //"bdma" => {} + //"dmamux" => {} + + // For other peripherals, one singleton per peri + _ => singletons.push(p.name.to_string()), + } + } + } + + cfgs.declare_all(&[ + "peri_adc1_common", + "peri_adc3_common", + "peri_adc12_common", + "peri_adc34_common", + "peri_sai1", + "peri_sai2", + "peri_sai3", + "peri_sai4", + "peri_ucpd1", + "peri_ucpd2", + "peri_usb_otg_fs", + "peri_usb_otg_hs", + "peri_octospi2", + ]); + cfgs.declare_all(&["mco", "mco1", "mco2"]); + + // One singleton per EXTI line + for pin_num in 0..16 { + singletons.push(format!("EXTI{}", pin_num)); + } + + // One singleton per DMA channel + for c in METADATA.dma_channels { + singletons.push(c.name.to_string()); + } + + let mut pin_set = std::collections::HashSet::new(); + for p in METADATA.peripherals { + for pin in p.pins { + pin_set.insert(pin.pin); + } + } + + struct SplitFeature { + feature_name: String, + pin_name_with_c: String, + #[cfg(feature = "_split-pins-enabled")] + pin_name_without_c: String, + } + + // Extra analog switch pins available on most H7 chips + let split_features: Vec = vec![ + #[cfg(feature = "split-pa0")] + SplitFeature { + feature_name: "split-pa0".to_string(), + pin_name_with_c: "PA0_C".to_string(), + pin_name_without_c: "PA0".to_string(), + }, + #[cfg(feature = "split-pa1")] + SplitFeature { + feature_name: "split-pa1".to_string(), + pin_name_with_c: "PA1_C".to_string(), + pin_name_without_c: "PA1".to_string(), + }, + #[cfg(feature = "split-pc2")] + SplitFeature { + feature_name: "split-pc2".to_string(), + pin_name_with_c: "PC2_C".to_string(), + pin_name_without_c: "PC2".to_string(), + }, + #[cfg(feature = "split-pc3")] + SplitFeature { + feature_name: "split-pc3".to_string(), + pin_name_with_c: "PC3_C".to_string(), + pin_name_without_c: "PC3".to_string(), + }, + ]; + + for split_feature in &split_features { + if pin_set.contains(split_feature.pin_name_with_c.as_str()) { + singletons.push(split_feature.pin_name_with_c.clone()); + } else { + panic!( + "'{}' feature invalid for this chip! No pin '{}' found.\n + Found pins: {:#?}", + split_feature.feature_name, split_feature.pin_name_with_c, pin_set + ) + } + } + + // ======== + // Handle time-driver-XXXX features. + + let time_driver = match env::vars() + .map(|(a, _)| a) + .filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_")) + .get_one() + { + Ok(x) => Some( + x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_") + .unwrap() + .to_ascii_lowercase(), + ), + Err(GetOneError::None) => None, + Err(GetOneError::Multiple) => panic!("Multiple time-driver-xxx Cargo features enabled"), + }; + + let time_driver_singleton = match time_driver.as_ref().map(|x| x.as_ref()) { + None => "", + Some("tim1") => "TIM1", + Some("tim2") => "TIM2", + Some("tim3") => "TIM3", + Some("tim4") => "TIM4", + Some("tim5") => "TIM5", + Some("tim8") => "TIM8", + Some("tim9") => "TIM9", + Some("tim12") => "TIM12", + Some("tim15") => "TIM15", + Some("tim20") => "TIM20", + Some("tim21") => "TIM21", + Some("tim22") => "TIM22", + Some("tim23") => "TIM23", + Some("tim24") => "TIM24", + Some("any") => { + // Order of TIM candidators: + // 1. 2CH -> 2CH_CMP -> GP16 -> GP32 -> ADV + // 2. In same catagory: larger TIM number first + [ + "TIM22", "TIM21", "TIM12", "TIM9", // 2CH + "TIM15", // 2CH_CMP + "TIM19", "TIM4", "TIM3", // GP16 + "TIM24", "TIM23", "TIM5", "TIM2", // GP32 + "TIM20", "TIM8", "TIM1", //ADV + ] + .iter() + .find(|tim| singletons.contains(&tim.to_string())).expect("time-driver-any requested, but the chip doesn't have TIM1, TIM2, TIM3, TIM4, TIM5, TIM8, TIM9, TIM12, TIM15, TIM20, TIM21, TIM22, TIM23 or TIM24.") + } + _ => panic!("unknown time_driver {:?}", time_driver), + }; + + if !time_driver_singleton.is_empty() { + cfgs.enable(format!("time_driver_{}", time_driver_singleton.to_lowercase())); + } + for tim in [ + "tim1", "tim2", "tim3", "tim4", "tim5", "tim8", "tim9", "tim12", "tim15", "tim20", "tim21", "tim22", "tim23", + "tim24", + ] { + cfgs.declare(format!("time_driver_{}", tim)); + } + + // ======== + // Write singletons + + let mut g = TokenStream::new(); + + let singleton_tokens: Vec<_> = singletons.iter().map(|s| format_ident!("{}", s)).collect(); + + g.extend(quote! { + embassy_hal_internal::peripherals_definition!(#(#singleton_tokens),*); + }); + + let singleton_tokens: Vec<_> = singletons + .iter() + .filter(|s| *s != &time_driver_singleton.to_string()) + .map(|s| format_ident!("{}", s)) + .collect(); + + g.extend(quote! { + embassy_hal_internal::peripherals_struct!(#(#singleton_tokens),*); + }); + + // ======== + // Generate interrupt declarations + + let mut irqs = Vec::new(); + for irq in METADATA.interrupts { + irqs.push(format_ident!("{}", irq.name)); + } + + g.extend(quote! { + embassy_hal_internal::interrupt_mod!( + #( + #irqs, + )* + ); + }); + + // ======== + // Generate FLASH regions + let mut flash_regions = TokenStream::new(); + let flash_memory_regions: Vec<_> = METADATA + .memory + .iter() + .filter(|x| x.kind == MemoryRegionKind::Flash && x.settings.is_some()) + .collect(); + for region in flash_memory_regions.iter() { + let region_name = format_ident!("{}", get_flash_region_name(region.name)); + let bank_variant = format_ident!( + "{}", + if region.name.starts_with("BANK_1") { + "Bank1" + } else if region.name.starts_with("BANK_2") { + "Bank2" + } else { + continue; + } + ); + let base = region.address; + let size = region.size; + let settings = region.settings.as_ref().unwrap(); + let erase_size = settings.erase_size; + let write_size = settings.write_size; + let erase_value = settings.erase_value; + + flash_regions.extend(quote! { + pub const #region_name: crate::flash::FlashRegion = crate::flash::FlashRegion { + bank: crate::flash::FlashBank::#bank_variant, + base: #base, + size: #size, + erase_size: #erase_size, + write_size: #write_size, + erase_value: #erase_value, + _ensure_internal: (), + }; + }); + + let region_type = format_ident!("{}", get_flash_region_type_name(region.name)); + flash_regions.extend(quote! { + #[cfg(flash)] + pub struct #region_type<'d, MODE = crate::flash::Async>(pub &'static crate::flash::FlashRegion, pub(crate) embassy_hal_internal::PeripheralRef<'d, crate::peripherals::FLASH>, pub(crate) core::marker::PhantomData); + }); + } + + let (fields, (inits, region_names)): (Vec, (Vec, Vec)) = flash_memory_regions + .iter() + .map(|f| { + let region_name = get_flash_region_name(f.name); + let field_name = format_ident!("{}", region_name.to_lowercase()); + let field_type = format_ident!("{}", get_flash_region_type_name(f.name)); + let field = quote! { + pub #field_name: #field_type<'d, MODE> + }; + let region_name = format_ident!("{}", region_name); + let init = quote! { + #field_name: #field_type(&#region_name, unsafe { p.clone_unchecked()}, core::marker::PhantomData) + }; + + (field, (init, region_name)) + }) + .unzip(); + + let regions_len = flash_memory_regions.len(); + flash_regions.extend(quote! { + #[cfg(flash)] + pub struct FlashLayout<'d, MODE = crate::flash::Async> { + #(#fields),*, + _mode: core::marker::PhantomData, + } + + #[cfg(flash)] + impl<'d, MODE> FlashLayout<'d, MODE> { + pub(crate) fn new(p: embassy_hal_internal::PeripheralRef<'d, crate::peripherals::FLASH>) -> Self { + Self { + #(#inits),*, + _mode: core::marker::PhantomData, + } + } + } + + pub const FLASH_REGIONS: [&crate::flash::FlashRegion; #regions_len] = [ + #(&#region_names),* + ]; + }); + + let max_erase_size = flash_memory_regions + .iter() + .map(|region| region.settings.as_ref().unwrap().erase_size) + .max() + .unwrap(); + + g.extend(quote! { pub const MAX_ERASE_SIZE: usize = #max_erase_size as usize; }); + + g.extend(quote! { pub mod flash_regions { #flash_regions } }); + + // ======== + // Extract the rcc registers + + let rcc_registers = METADATA + .peripherals + .iter() + .filter_map(|p| p.registers.as_ref()) + .find(|r| r.kind == "rcc") + .unwrap(); + let rcc_block = rcc_registers.ir.blocks.iter().find(|b| b.name == "Rcc").unwrap(); + + // ======== + // Generate RccPeripheral impls + + // count how many times each xxENR field is used, to enable refcounting if used more than once. + let mut rcc_field_count: HashMap<_, usize> = HashMap::new(); + for p in METADATA.peripherals { + if let Some(rcc) = &p.rcc { + let en = rcc.enable.as_ref().unwrap(); + *rcc_field_count.entry((en.register, en.field)).or_insert(0) += 1; + } + } + + struct ClockGen<'a> { + rcc_registers: &'a PeripheralRegisters, + chained_muxes: HashMap<&'a str, &'a PeripheralRccRegister>, + + clock_names: BTreeSet, + muxes: BTreeSet<(Ident, Ident, Ident)>, + } + + let mut clock_gen = ClockGen { + rcc_registers, + chained_muxes: HashMap::new(), + + clock_names: BTreeSet::new(), + muxes: BTreeSet::new(), + }; + if chip_name.starts_with("stm32h5") { + clock_gen.chained_muxes.insert( + "PER", + &PeripheralRccRegister { + register: "CCIPR5", + field: "PERSEL", + }, + ); + } + + if chip_name.starts_with("stm32h7r") || chip_name.starts_with("stm32h7s") { + clock_gen.chained_muxes.insert( + "PER", + &PeripheralRccRegister { + register: "AHBPERCKSELR", + field: "PERSEL", + }, + ); + } else if chip_name.starts_with("stm32h7") { + clock_gen.chained_muxes.insert( + "PER", + &PeripheralRccRegister { + register: "D1CCIPR", + field: "PERSEL", + }, + ); + } + if chip_name.starts_with("stm32u5") { + clock_gen.chained_muxes.insert( + "ICLK", + &PeripheralRccRegister { + register: "CCIPR1", + field: "ICLKSEL", + }, + ); + } + if chip_name.starts_with("stm32wb") && !chip_name.starts_with("stm32wba") { + clock_gen.chained_muxes.insert( + "CLK48", + &PeripheralRccRegister { + register: "CCIPR", + field: "CLK48SEL", + }, + ); + } + if chip_name.starts_with("stm32f7") { + clock_gen.chained_muxes.insert( + "CLK48", + &PeripheralRccRegister { + register: "DCKCFGR2", + field: "CLK48SEL", + }, + ); + } + if chip_name.starts_with("stm32f4") && !chip_name.starts_with("stm32f410") { + clock_gen.chained_muxes.insert( + "CLK48", + &PeripheralRccRegister { + register: "DCKCFGR", + field: "CLK48SEL", + }, + ); + } + + impl<'a> ClockGen<'a> { + fn gen_clock(&mut self, peripheral: &str, name: &str) -> TokenStream { + let clock_name = format_ident!("{}", name.to_ascii_lowercase()); + self.clock_names.insert(name.to_ascii_lowercase()); + quote!(unsafe { + unwrap!( + crate::rcc::get_freqs().#clock_name.to_hertz(), + "peripheral '{}' is configured to use the '{}' clock, which is not running. \ + Either enable it in 'config.rcc' or change 'config.rcc.mux' to use another clock", + #peripheral, + #name + ) + }) + } + + fn gen_mux(&mut self, peripheral: &str, mux: &PeripheralRccRegister) -> TokenStream { + let ir = &self.rcc_registers.ir; + let fieldset_name = mux.register.to_ascii_lowercase(); + let fieldset = ir + .fieldsets + .iter() + .find(|i| i.name.eq_ignore_ascii_case(&fieldset_name)) + .unwrap(); + let field_name = mux.field.to_ascii_lowercase(); + let field = fieldset.fields.iter().find(|i| i.name == field_name).unwrap(); + let enum_name = field.enumm.unwrap(); + let enumm = ir.enums.iter().find(|i| i.name == enum_name).unwrap(); + + let fieldset_name = format_ident!("{}", fieldset_name); + let field_name = format_ident!("{}", field_name); + let enum_name = format_ident!("{}", enum_name); + + self.muxes + .insert((fieldset_name.clone(), field_name.clone(), enum_name.clone())); + + let mut match_arms = TokenStream::new(); + + for v in enumm.variants.iter().filter(|v| v.name != "DISABLE") { + let variant_name = format_ident!("{}", v.name); + let expr = if let Some(mux) = self.chained_muxes.get(&v.name) { + self.gen_mux(peripheral, mux) + } else { + self.gen_clock(peripheral, v.name) + }; + match_arms.extend(quote! { + crate::pac::rcc::vals::#enum_name::#variant_name => #expr, + }); + } + + quote! { + match crate::pac::RCC.#fieldset_name().read().#field_name() { + #match_arms + #[allow(unreachable_patterns)] + _ => unreachable!(), + } + } + } + } + + let mut refcount_idxs = HashMap::new(); + + for p in METADATA.peripherals { + if !singletons.contains(&p.name.to_string()) { + continue; + } + + if let Some(rcc) = &p.rcc { + let rst_reg = rcc.reset.as_ref(); + let en_reg = rcc.enable.as_ref().unwrap(); + let pname = format_ident!("{}", p.name); + + let get_offset_and_bit = |reg: &PeripheralRccRegister| -> TokenStream { + let reg_offset = rcc_block + .items + .iter() + .find(|i| i.name.eq_ignore_ascii_case(reg.register)) + .unwrap() + .byte_offset; + let reg_offset: u8 = (reg_offset / 4).try_into().unwrap(); + + let bit_offset = &rcc_registers + .ir + .fieldsets + .iter() + .find(|i| i.name.eq_ignore_ascii_case(reg.register)) + .unwrap() + .fields + .iter() + .find(|i| i.name.eq_ignore_ascii_case(reg.field)) + .unwrap() + .bit_offset; + let BitOffset::Regular(bit_offset) = bit_offset else { + panic!("cursed bit offset") + }; + let bit_offset: u8 = bit_offset.offset.try_into().unwrap(); + + quote! { (#reg_offset, #bit_offset) } + }; + + let reset_offset_and_bit = match rst_reg { + Some(rst_reg) => { + let reset_offset_and_bit = get_offset_and_bit(rst_reg); + quote! { Some(#reset_offset_and_bit) } + } + None => quote! { None }, + }; + let enable_offset_and_bit = get_offset_and_bit(en_reg); + + let needs_refcount = *rcc_field_count.get(&(en_reg.register, en_reg.field)).unwrap() > 1; + let refcount_idx = if needs_refcount { + let next_refcount_idx = refcount_idxs.len() as u8; + let refcount_idx = *refcount_idxs + .entry((en_reg.register, en_reg.field)) + .or_insert(next_refcount_idx); + quote! { Some(#refcount_idx) } + } else { + quote! { None } + }; + + let clock_frequency = match &rcc.kernel_clock { + PeripheralRccKernelClock::Mux(mux) => clock_gen.gen_mux(p.name, mux), + PeripheralRccKernelClock::Clock(clock) => clock_gen.gen_clock(p.name, clock), + }; + + // A refcount leak can result if the same field is shared by peripherals with different stop modes + // This condition should be checked in stm32-data + let stop_mode = match rcc.stop_mode { + StopMode::Standby => quote! { crate::rcc::StopMode::Standby }, + StopMode::Stop2 => quote! { crate::rcc::StopMode::Stop2 }, + StopMode::Stop1 => quote! { crate::rcc::StopMode::Stop1 }, + }; + + g.extend(quote! { + impl crate::rcc::SealedRccPeripheral for peripherals::#pname { + fn frequency() -> crate::time::Hertz { + #clock_frequency + } + + const RCC_INFO: crate::rcc::RccInfo = unsafe { + crate::rcc::RccInfo::new( + #reset_offset_and_bit, + #enable_offset_and_bit, + #refcount_idx, + #[cfg(feature = "low-power")] + #stop_mode, + ) + }; + } + + impl crate::rcc::RccPeripheral for peripherals::#pname {} + }); + } + } + + g.extend({ + let refcounts_len = refcount_idxs.len(); + let refcount_zeros: TokenStream = refcount_idxs.iter().map(|_| quote! { 0u8, }).collect(); + quote! { + pub(crate) static mut REFCOUNTS: [u8; #refcounts_len] = [#refcount_zeros]; + } + }); + + let struct_fields: Vec<_> = clock_gen + .muxes + .iter() + .map(|(_fieldset, fieldname, enum_name)| { + quote! { + pub #fieldname: #enum_name + } + }) + .collect(); + + let mut inits = TokenStream::new(); + for fieldset in clock_gen + .muxes + .iter() + .map(|(f, _, _)| f) + .collect::>() + .into_iter() + { + let setters: Vec<_> = clock_gen + .muxes + .iter() + .filter(|(f, _, _)| f == fieldset) + .map(|(_, fieldname, _)| { + let setter = format_ident!("set_{}", fieldname); + quote! { + w.#setter(self.#fieldname); + } + }) + .collect(); + + inits.extend(quote! { + crate::pac::RCC.#fieldset().modify(|w| { + #(#setters)* + }); + }) + } + + let enum_names: BTreeSet<_> = clock_gen.muxes.iter().map(|(_, _, enum_name)| enum_name).collect(); + + g.extend(quote! { + pub mod mux { + #(pub use crate::pac::rcc::vals::#enum_names as #enum_names; )* + + #[derive(Clone, Copy)] + #[non_exhaustive] + pub struct ClockMux { + #( #struct_fields, )* + } + + impl ClockMux { + pub(crate) const fn default() -> Self { + // safety: zero value is valid for all PAC enums. + unsafe { ::core::mem::zeroed() } + } + } + + impl Default for ClockMux { + fn default() -> Self { + Self::default() + } + } + + impl ClockMux { + pub(crate) fn init(&self) { + #inits + } + } + } + }); + + // Generate RCC + clock_gen.clock_names.insert("sys".to_string()); + clock_gen.clock_names.insert("rtc".to_string()); + let clock_idents: Vec<_> = clock_gen.clock_names.iter().map(|n| format_ident!("{}", n)).collect(); + g.extend(quote! { + #[derive(Clone, Copy, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[repr(C)] + pub struct Clocks { + #( + pub #clock_idents: crate::time::MaybeHertz, + )* + } + }); + + let clocks_macro = quote!( + macro_rules! set_clocks { + ($($(#[$m:meta])* $k:ident: $v:expr,)*) => { + { + #[allow(unused)] + struct Temp { + $($(#[$m])* $k: Option,)* + } + let all = Temp { + $($(#[$m])* $k: $v,)* + }; + crate::rcc::set_freqs(crate::rcc::Clocks { + #( #clock_idents: all.#clock_idents.into(), )* + }); + } + }; + } + ); + + // ======== + // Generate fns to enable GPIO, DMA in RCC + + for kind in ["dma", "bdma", "dmamux", "gpdma", "gpio"] { + let mut gg = TokenStream::new(); + + for p in METADATA.peripherals { + if p.registers.is_some() && p.registers.as_ref().unwrap().kind == kind { + if let Some(rcc) = &p.rcc { + let en = rcc.enable.as_ref().unwrap(); + let en_reg = format_ident!("{}", en.register.to_ascii_lowercase()); + let set_en_field = format_ident!("set_{}", en.field.to_ascii_lowercase()); + + gg.extend(quote! { + crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(true)); + }) + } + } + } + + let fname = format_ident!("init_{}", kind); + g.extend(quote! { + pub unsafe fn #fname(){ + #gg + } + }) + } + + // ======== + // Generate pin_trait_impl! + + #[rustfmt::skip] + let signals: HashMap<_, _> = [ + // (kind, signal) => trait + (("ucpd", "CC1"), quote!(crate::ucpd::Cc1Pin)), + (("ucpd", "CC2"), quote!(crate::ucpd::Cc2Pin)), + (("usart", "TX"), quote!(crate::usart::TxPin)), + (("usart", "RX"), quote!(crate::usart::RxPin)), + (("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)), + (("sai", "SCK_A"), quote!(crate::sai::SckPin)), + (("sai", "SCK_B"), quote!(crate::sai::SckPin)), + (("sai", "FS_A"), quote!(crate::sai::FsPin)), + (("sai", "FS_B"), quote!(crate::sai::FsPin)), + (("sai", "SD_A"), quote!(crate::sai::SdPin)), + (("sai", "SD_B"), quote!(crate::sai::SdPin)), + (("sai", "MCLK_A"), quote!(crate::sai::MclkPin)), + (("sai", "MCLK_B"), quote!(crate::sai::MclkPin)), + (("sai", "WS"), quote!(crate::sai::WsPin)), + (("spi", "SCK"), quote!(crate::spi::SckPin)), + (("spi", "MOSI"), quote!(crate::spi::MosiPin)), + (("spi", "MISO"), quote!(crate::spi::MisoPin)), + (("spi", "NSS"), quote!(crate::spi::CsPin)), + (("spi", "I2S_MCK"), quote!(crate::spi::MckPin)), + (("spi", "I2S_CK"), quote!(crate::spi::CkPin)), + (("spi", "I2S_WS"), quote!(crate::spi::WsPin)), + (("i2c", "SDA"), quote!(crate::i2c::SdaPin)), + (("i2c", "SCL"), quote!(crate::i2c::SclPin)), + (("rcc", "MCO_1"), quote!(crate::rcc::McoPin)), + (("rcc", "MCO_2"), quote!(crate::rcc::McoPin)), + (("rcc", "MCO"), quote!(crate::rcc::McoPin)), + (("dcmi", "D0"), quote!(crate::dcmi::D0Pin)), + (("dcmi", "D1"), quote!(crate::dcmi::D1Pin)), + (("dcmi", "D2"), quote!(crate::dcmi::D2Pin)), + (("dcmi", "D3"), quote!(crate::dcmi::D3Pin)), + (("dcmi", "D4"), quote!(crate::dcmi::D4Pin)), + (("dcmi", "D5"), quote!(crate::dcmi::D5Pin)), + (("dcmi", "D6"), quote!(crate::dcmi::D6Pin)), + (("dcmi", "D7"), quote!(crate::dcmi::D7Pin)), + (("dcmi", "D8"), quote!(crate::dcmi::D8Pin)), + (("dcmi", "D9"), quote!(crate::dcmi::D9Pin)), + (("dcmi", "D10"), quote!(crate::dcmi::D10Pin)), + (("dcmi", "D11"), quote!(crate::dcmi::D11Pin)), + (("dcmi", "D12"), quote!(crate::dcmi::D12Pin)), + (("dcmi", "D13"), quote!(crate::dcmi::D13Pin)), + (("dcmi", "HSYNC"), quote!(crate::dcmi::HSyncPin)), + (("dcmi", "VSYNC"), quote!(crate::dcmi::VSyncPin)), + (("dcmi", "PIXCLK"), quote!(crate::dcmi::PixClkPin)), + (("dsihost", "TE"), quote!(crate::dsihost::TePin)), + (("ltdc", "CLK"), quote!(crate::ltdc::ClkPin)), + (("ltdc", "HSYNC"), quote!(crate::ltdc::HsyncPin)), + (("ltdc", "VSYNC"), quote!(crate::ltdc::VsyncPin)), + (("ltdc", "DE"), quote!(crate::ltdc::DePin)), + (("ltdc", "R0"), quote!(crate::ltdc::R0Pin)), + (("ltdc", "R1"), quote!(crate::ltdc::R1Pin)), + (("ltdc", "R2"), quote!(crate::ltdc::R2Pin)), + (("ltdc", "R3"), quote!(crate::ltdc::R3Pin)), + (("ltdc", "R4"), quote!(crate::ltdc::R4Pin)), + (("ltdc", "R5"), quote!(crate::ltdc::R5Pin)), + (("ltdc", "R6"), quote!(crate::ltdc::R6Pin)), + (("ltdc", "R7"), quote!(crate::ltdc::R7Pin)), + (("ltdc", "G0"), quote!(crate::ltdc::G0Pin)), + (("ltdc", "G1"), quote!(crate::ltdc::G1Pin)), + (("ltdc", "G2"), quote!(crate::ltdc::G2Pin)), + (("ltdc", "G3"), quote!(crate::ltdc::G3Pin)), + (("ltdc", "G4"), quote!(crate::ltdc::G4Pin)), + (("ltdc", "G5"), quote!(crate::ltdc::G5Pin)), + (("ltdc", "G6"), quote!(crate::ltdc::G6Pin)), + (("ltdc", "G7"), quote!(crate::ltdc::G7Pin)), + (("ltdc", "B0"), quote!(crate::ltdc::B0Pin)), + (("ltdc", "B1"), quote!(crate::ltdc::B1Pin)), + (("ltdc", "B2"), quote!(crate::ltdc::B2Pin)), + (("ltdc", "B3"), quote!(crate::ltdc::B3Pin)), + (("ltdc", "B4"), quote!(crate::ltdc::B4Pin)), + (("ltdc", "B5"), quote!(crate::ltdc::B5Pin)), + (("ltdc", "B6"), quote!(crate::ltdc::B6Pin)), + (("ltdc", "B7"), quote!(crate::ltdc::B7Pin)), + (("usb", "DP"), quote!(crate::usb::DpPin)), + (("usb", "DM"), quote!(crate::usb::DmPin)), + (("otg", "DP"), quote!(crate::usb::DpPin)), + (("otg", "DM"), quote!(crate::usb::DmPin)), + (("otg", "ULPI_CK"), quote!(crate::usb::UlpiClkPin)), + (("otg", "ULPI_DIR"), quote!(crate::usb::UlpiDirPin)), + (("otg", "ULPI_NXT"), quote!(crate::usb::UlpiNxtPin)), + (("otg", "ULPI_STP"), quote!(crate::usb::UlpiStpPin)), + (("otg", "ULPI_D0"), quote!(crate::usb::UlpiD0Pin)), + (("otg", "ULPI_D1"), quote!(crate::usb::UlpiD1Pin)), + (("otg", "ULPI_D2"), quote!(crate::usb::UlpiD2Pin)), + (("otg", "ULPI_D3"), quote!(crate::usb::UlpiD3Pin)), + (("otg", "ULPI_D4"), quote!(crate::usb::UlpiD4Pin)), + (("otg", "ULPI_D5"), quote!(crate::usb::UlpiD5Pin)), + (("otg", "ULPI_D6"), quote!(crate::usb::UlpiD6Pin)), + (("otg", "ULPI_D7"), quote!(crate::usb::UlpiD7Pin)), + (("can", "TX"), quote!(crate::can::TxPin)), + (("can", "RX"), quote!(crate::can::RxPin)), + (("eth", "REF_CLK"), quote!(crate::eth::RefClkPin)), + (("eth", "RX_CLK"), quote!(crate::eth::RXClkPin)), + (("eth", "TX_CLK"), quote!(crate::eth::TXClkPin)), + (("eth", "MDIO"), quote!(crate::eth::MDIOPin)), + (("eth", "MDC"), quote!(crate::eth::MDCPin)), + (("eth", "CRS_DV"), quote!(crate::eth::CRSPin)), + (("eth", "RX_DV"), quote!(crate::eth::RXDVPin)), + (("eth", "RXD0"), quote!(crate::eth::RXD0Pin)), + (("eth", "RXD1"), quote!(crate::eth::RXD1Pin)), + (("eth", "RXD2"), quote!(crate::eth::RXD2Pin)), + (("eth", "RXD3"), quote!(crate::eth::RXD3Pin)), + (("eth", "TXD0"), quote!(crate::eth::TXD0Pin)), + (("eth", "TXD1"), quote!(crate::eth::TXD1Pin)), + (("eth", "TXD2"), quote!(crate::eth::TXD2Pin)), + (("eth", "TXD3"), quote!(crate::eth::TXD3Pin)), + (("eth", "TX_EN"), quote!(crate::eth::TXEnPin)), + (("fmc", "A0"), quote!(crate::fmc::A0Pin)), + (("fmc", "A1"), quote!(crate::fmc::A1Pin)), + (("fmc", "A2"), quote!(crate::fmc::A2Pin)), + (("fmc", "A3"), quote!(crate::fmc::A3Pin)), + (("fmc", "A4"), quote!(crate::fmc::A4Pin)), + (("fmc", "A5"), quote!(crate::fmc::A5Pin)), + (("fmc", "A6"), quote!(crate::fmc::A6Pin)), + (("fmc", "A7"), quote!(crate::fmc::A7Pin)), + (("fmc", "A8"), quote!(crate::fmc::A8Pin)), + (("fmc", "A9"), quote!(crate::fmc::A9Pin)), + (("fmc", "A10"), quote!(crate::fmc::A10Pin)), + (("fmc", "A11"), quote!(crate::fmc::A11Pin)), + (("fmc", "A12"), quote!(crate::fmc::A12Pin)), + (("fmc", "A13"), quote!(crate::fmc::A13Pin)), + (("fmc", "A14"), quote!(crate::fmc::A14Pin)), + (("fmc", "A15"), quote!(crate::fmc::A15Pin)), + (("fmc", "A16"), quote!(crate::fmc::A16Pin)), + (("fmc", "A17"), quote!(crate::fmc::A17Pin)), + (("fmc", "A18"), quote!(crate::fmc::A18Pin)), + (("fmc", "A19"), quote!(crate::fmc::A19Pin)), + (("fmc", "A20"), quote!(crate::fmc::A20Pin)), + (("fmc", "A21"), quote!(crate::fmc::A21Pin)), + (("fmc", "A22"), quote!(crate::fmc::A22Pin)), + (("fmc", "A23"), quote!(crate::fmc::A23Pin)), + (("fmc", "A24"), quote!(crate::fmc::A24Pin)), + (("fmc", "A25"), quote!(crate::fmc::A25Pin)), + (("fmc", "D0"), quote!(crate::fmc::D0Pin)), + (("fmc", "D1"), quote!(crate::fmc::D1Pin)), + (("fmc", "D2"), quote!(crate::fmc::D2Pin)), + (("fmc", "D3"), quote!(crate::fmc::D3Pin)), + (("fmc", "D4"), quote!(crate::fmc::D4Pin)), + (("fmc", "D5"), quote!(crate::fmc::D5Pin)), + (("fmc", "D6"), quote!(crate::fmc::D6Pin)), + (("fmc", "D7"), quote!(crate::fmc::D7Pin)), + (("fmc", "D8"), quote!(crate::fmc::D8Pin)), + (("fmc", "D9"), quote!(crate::fmc::D9Pin)), + (("fmc", "D10"), quote!(crate::fmc::D10Pin)), + (("fmc", "D11"), quote!(crate::fmc::D11Pin)), + (("fmc", "D12"), quote!(crate::fmc::D12Pin)), + (("fmc", "D13"), quote!(crate::fmc::D13Pin)), + (("fmc", "D14"), quote!(crate::fmc::D14Pin)), + (("fmc", "D15"), quote!(crate::fmc::D15Pin)), + (("fmc", "D16"), quote!(crate::fmc::D16Pin)), + (("fmc", "D17"), quote!(crate::fmc::D17Pin)), + (("fmc", "D18"), quote!(crate::fmc::D18Pin)), + (("fmc", "D19"), quote!(crate::fmc::D19Pin)), + (("fmc", "D20"), quote!(crate::fmc::D20Pin)), + (("fmc", "D21"), quote!(crate::fmc::D21Pin)), + (("fmc", "D22"), quote!(crate::fmc::D22Pin)), + (("fmc", "D23"), quote!(crate::fmc::D23Pin)), + (("fmc", "D24"), quote!(crate::fmc::D24Pin)), + (("fmc", "D25"), quote!(crate::fmc::D25Pin)), + (("fmc", "D26"), quote!(crate::fmc::D26Pin)), + (("fmc", "D27"), quote!(crate::fmc::D27Pin)), + (("fmc", "D28"), quote!(crate::fmc::D28Pin)), + (("fmc", "D29"), quote!(crate::fmc::D29Pin)), + (("fmc", "D30"), quote!(crate::fmc::D30Pin)), + (("fmc", "D31"), quote!(crate::fmc::D31Pin)), + (("fmc", "DA0"), quote!(crate::fmc::DA0Pin)), + (("fmc", "DA1"), quote!(crate::fmc::DA1Pin)), + (("fmc", "DA2"), quote!(crate::fmc::DA2Pin)), + (("fmc", "DA3"), quote!(crate::fmc::DA3Pin)), + (("fmc", "DA4"), quote!(crate::fmc::DA4Pin)), + (("fmc", "DA5"), quote!(crate::fmc::DA5Pin)), + (("fmc", "DA6"), quote!(crate::fmc::DA6Pin)), + (("fmc", "DA7"), quote!(crate::fmc::DA7Pin)), + (("fmc", "DA8"), quote!(crate::fmc::DA8Pin)), + (("fmc", "DA9"), quote!(crate::fmc::DA9Pin)), + (("fmc", "DA10"), quote!(crate::fmc::DA10Pin)), + (("fmc", "DA11"), quote!(crate::fmc::DA11Pin)), + (("fmc", "DA12"), quote!(crate::fmc::DA12Pin)), + (("fmc", "DA13"), quote!(crate::fmc::DA13Pin)), + (("fmc", "DA14"), quote!(crate::fmc::DA14Pin)), + (("fmc", "DA15"), quote!(crate::fmc::DA15Pin)), + (("fmc", "SDNWE"), quote!(crate::fmc::SDNWEPin)), + (("fmc", "SDNCAS"), quote!(crate::fmc::SDNCASPin)), + (("fmc", "SDNRAS"), quote!(crate::fmc::SDNRASPin)), + (("fmc", "SDNE0"), quote!(crate::fmc::SDNE0Pin)), + (("fmc", "SDNE1"), quote!(crate::fmc::SDNE1Pin)), + (("fmc", "SDCKE0"), quote!(crate::fmc::SDCKE0Pin)), + (("fmc", "SDCKE1"), quote!(crate::fmc::SDCKE1Pin)), + (("fmc", "SDCLK"), quote!(crate::fmc::SDCLKPin)), + (("fmc", "NBL0"), quote!(crate::fmc::NBL0Pin)), + (("fmc", "NBL1"), quote!(crate::fmc::NBL1Pin)), + (("fmc", "NBL2"), quote!(crate::fmc::NBL2Pin)), + (("fmc", "NBL3"), quote!(crate::fmc::NBL3Pin)), + (("fmc", "INT"), quote!(crate::fmc::INTPin)), + (("fmc", "NL"), quote!(crate::fmc::NLPin)), + (("fmc", "NWAIT"), quote!(crate::fmc::NWaitPin)), + (("fmc", "NE1"), quote!(crate::fmc::NE1Pin)), + (("fmc", "NE2"), quote!(crate::fmc::NE2Pin)), + (("fmc", "NE3"), quote!(crate::fmc::NE3Pin)), + (("fmc", "NE4"), quote!(crate::fmc::NE4Pin)), + (("fmc", "NCE"), quote!(crate::fmc::NCEPin)), + (("fmc", "NOE"), quote!(crate::fmc::NOEPin)), + (("fmc", "NWE"), quote!(crate::fmc::NWEPin)), + (("fmc", "CLK"), quote!(crate::fmc::ClkPin)), + (("fmc", "BA0"), quote!(crate::fmc::BA0Pin)), + (("fmc", "BA1"), quote!(crate::fmc::BA1Pin)), + (("timer", "CH1"), quote!(crate::timer::Channel1Pin)), + (("timer", "CH1N"), quote!(crate::timer::Channel1ComplementaryPin)), + (("timer", "CH2"), quote!(crate::timer::Channel2Pin)), + (("timer", "CH2N"), quote!(crate::timer::Channel2ComplementaryPin)), + (("timer", "CH3"), quote!(crate::timer::Channel3Pin)), + (("timer", "CH3N"), quote!(crate::timer::Channel3ComplementaryPin)), + (("timer", "CH4"), quote!(crate::timer::Channel4Pin)), + (("timer", "CH4N"), quote!(crate::timer::Channel4ComplementaryPin)), + (("timer", "ETR"), quote!(crate::timer::ExternalTriggerPin)), + (("timer", "BKIN"), quote!(crate::timer::BreakInputPin)), + (("timer", "BKIN_COMP1"), quote!(crate::timer::BreakInputComparator1Pin)), + (("timer", "BKIN_COMP2"), quote!(crate::timer::BreakInputComparator2Pin)), + (("timer", "BKIN2"), quote!(crate::timer::BreakInput2Pin)), + (("timer", "BKIN2_COMP1"), quote!(crate::timer::BreakInput2Comparator1Pin)), + (("timer", "BKIN2_COMP2"), quote!(crate::timer::BreakInput2Comparator2Pin)), + (("hrtim", "CHA1"), quote!(crate::hrtim::ChannelAPin)), + (("hrtim", "CHA2"), quote!(crate::hrtim::ChannelAComplementaryPin)), + (("hrtim", "CHB1"), quote!(crate::hrtim::ChannelBPin)), + (("hrtim", "CHB2"), quote!(crate::hrtim::ChannelBComplementaryPin)), + (("hrtim", "CHC1"), quote!(crate::hrtim::ChannelCPin)), + (("hrtim", "CHC2"), quote!(crate::hrtim::ChannelCComplementaryPin)), + (("hrtim", "CHD1"), quote!(crate::hrtim::ChannelDPin)), + (("hrtim", "CHD2"), quote!(crate::hrtim::ChannelDComplementaryPin)), + (("hrtim", "CHE1"), quote!(crate::hrtim::ChannelEPin)), + (("hrtim", "CHE2"), quote!(crate::hrtim::ChannelEComplementaryPin)), + (("hrtim", "CHF1"), quote!(crate::hrtim::ChannelFPin)), + (("hrtim", "CHF2"), quote!(crate::hrtim::ChannelFComplementaryPin)), + (("lptim", "CH1"), quote!(crate::lptim::Channel1Pin)), + (("lptim", "CH2"), quote!(crate::lptim::Channel2Pin)), + (("lptim", "OUT"), quote!(crate::lptim::OutputPin)), + (("sdmmc", "CK"), quote!(crate::sdmmc::CkPin)), + (("sdmmc", "CMD"), quote!(crate::sdmmc::CmdPin)), + (("sdmmc", "D0"), quote!(crate::sdmmc::D0Pin)), + (("sdmmc", "D1"), quote!(crate::sdmmc::D1Pin)), + (("sdmmc", "D2"), quote!(crate::sdmmc::D2Pin)), + (("sdmmc", "D3"), quote!(crate::sdmmc::D3Pin)), + (("sdmmc", "D4"), quote!(crate::sdmmc::D4Pin)), + (("sdmmc", "D5"), quote!(crate::sdmmc::D5Pin)), + (("sdmmc", "D6"), quote!(crate::sdmmc::D6Pin)), + (("sdmmc", "D6"), quote!(crate::sdmmc::D7Pin)), + (("sdmmc", "D8"), quote!(crate::sdmmc::D8Pin)), + (("quadspi", "BK1_IO0"), quote!(crate::qspi::BK1D0Pin)), + (("quadspi", "BK1_IO1"), quote!(crate::qspi::BK1D1Pin)), + (("quadspi", "BK1_IO2"), quote!(crate::qspi::BK1D2Pin)), + (("quadspi", "BK1_IO3"), quote!(crate::qspi::BK1D3Pin)), + (("quadspi", "BK1_NCS"), quote!(crate::qspi::BK1NSSPin)), + (("quadspi", "BK2_IO0"), quote!(crate::qspi::BK2D0Pin)), + (("quadspi", "BK2_IO1"), quote!(crate::qspi::BK2D1Pin)), + (("quadspi", "BK2_IO2"), quote!(crate::qspi::BK2D2Pin)), + (("quadspi", "BK2_IO3"), quote!(crate::qspi::BK2D3Pin)), + (("quadspi", "BK2_NCS"), quote!(crate::qspi::BK2NSSPin)), + (("quadspi", "CLK"), quote!(crate::qspi::SckPin)), + (("octospi", "IO0"), quote!(crate::ospi::D0Pin)), + (("octospi", "IO1"), quote!(crate::ospi::D1Pin)), + (("octospi", "IO2"), quote!(crate::ospi::D2Pin)), + (("octospi", "IO3"), quote!(crate::ospi::D3Pin)), + (("octospi", "IO4"), quote!(crate::ospi::D4Pin)), + (("octospi", "IO5"), quote!(crate::ospi::D5Pin)), + (("octospi", "IO6"), quote!(crate::ospi::D6Pin)), + (("octospi", "IO7"), quote!(crate::ospi::D7Pin)), + (("octospi", "DQS"), quote!(crate::ospi::DQSPin)), + (("octospi", "NCS"), quote!(crate::ospi::NSSPin)), + (("octospi", "CLK"), quote!(crate::ospi::SckPin)), + (("octospi", "NCLK"), quote!(crate::ospi::NckPin)), + (("octospim", "P1_IO0"), quote!(crate::ospi::D0Pin)), + (("octospim", "P1_IO1"), quote!(crate::ospi::D1Pin)), + (("octospim", "P1_IO2"), quote!(crate::ospi::D2Pin)), + (("octospim", "P1_IO3"), quote!(crate::ospi::D3Pin)), + (("octospim", "P1_IO4"), quote!(crate::ospi::D4Pin)), + (("octospim", "P1_IO5"), quote!(crate::ospi::D5Pin)), + (("octospim", "P1_IO6"), quote!(crate::ospi::D6Pin)), + (("octospim", "P1_IO7"), quote!(crate::ospi::D7Pin)), + (("octospim", "P1_DQS"), quote!(crate::ospi::DQSPin)), + (("octospim", "P1_NCS"), quote!(crate::ospi::NSSPin)), + (("octospim", "P1_CLK"), quote!(crate::ospi::SckPin)), + (("octospim", "P1_NCLK"), quote!(crate::ospi::NckPin)), + (("octospim", "P2_IO0"), quote!(crate::ospi::D0Pin)), + (("octospim", "P2_IO1"), quote!(crate::ospi::D1Pin)), + (("octospim", "P2_IO2"), quote!(crate::ospi::D2Pin)), + (("octospim", "P2_IO3"), quote!(crate::ospi::D3Pin)), + (("octospim", "P2_IO4"), quote!(crate::ospi::D4Pin)), + (("octospim", "P2_IO5"), quote!(crate::ospi::D5Pin)), + (("octospim", "P2_IO6"), quote!(crate::ospi::D6Pin)), + (("octospim", "P2_IO7"), quote!(crate::ospi::D7Pin)), + (("octospim", "P2_DQS"), quote!(crate::ospi::DQSPin)), + (("octospim", "P2_NCS"), quote!(crate::ospi::NSSPin)), + (("octospim", "P2_CLK"), quote!(crate::ospi::SckPin)), + (("octospim", "P2_NCLK"), quote!(crate::ospi::NckPin)), + (("tsc", "G1_IO1"), quote!(crate::tsc::G1IO1Pin)), + (("tsc", "G1_IO2"), quote!(crate::tsc::G1IO2Pin)), + (("tsc", "G1_IO3"), quote!(crate::tsc::G1IO3Pin)), + (("tsc", "G1_IO4"), quote!(crate::tsc::G1IO4Pin)), + (("tsc", "G2_IO1"), quote!(crate::tsc::G2IO1Pin)), + (("tsc", "G2_IO2"), quote!(crate::tsc::G2IO2Pin)), + (("tsc", "G2_IO3"), quote!(crate::tsc::G2IO3Pin)), + (("tsc", "G2_IO4"), quote!(crate::tsc::G2IO4Pin)), + (("tsc", "G3_IO1"), quote!(crate::tsc::G3IO1Pin)), + (("tsc", "G3_IO2"), quote!(crate::tsc::G3IO2Pin)), + (("tsc", "G3_IO3"), quote!(crate::tsc::G3IO3Pin)), + (("tsc", "G3_IO4"), quote!(crate::tsc::G3IO4Pin)), + (("tsc", "G4_IO1"), quote!(crate::tsc::G4IO1Pin)), + (("tsc", "G4_IO2"), quote!(crate::tsc::G4IO2Pin)), + (("tsc", "G4_IO3"), quote!(crate::tsc::G4IO3Pin)), + (("tsc", "G4_IO4"), quote!(crate::tsc::G4IO4Pin)), + (("tsc", "G5_IO1"), quote!(crate::tsc::G5IO1Pin)), + (("tsc", "G5_IO2"), quote!(crate::tsc::G5IO2Pin)), + (("tsc", "G5_IO3"), quote!(crate::tsc::G5IO3Pin)), + (("tsc", "G5_IO4"), quote!(crate::tsc::G5IO4Pin)), + (("tsc", "G6_IO1"), quote!(crate::tsc::G6IO1Pin)), + (("tsc", "G6_IO2"), quote!(crate::tsc::G6IO2Pin)), + (("tsc", "G6_IO3"), quote!(crate::tsc::G6IO3Pin)), + (("tsc", "G6_IO4"), quote!(crate::tsc::G6IO4Pin)), + (("tsc", "G7_IO1"), quote!(crate::tsc::G7IO1Pin)), + (("tsc", "G7_IO2"), quote!(crate::tsc::G7IO2Pin)), + (("tsc", "G7_IO3"), quote!(crate::tsc::G7IO3Pin)), + (("tsc", "G7_IO4"), quote!(crate::tsc::G7IO4Pin)), + (("tsc", "G8_IO1"), quote!(crate::tsc::G8IO1Pin)), + (("tsc", "G8_IO2"), quote!(crate::tsc::G8IO2Pin)), + (("tsc", "G8_IO3"), quote!(crate::tsc::G8IO3Pin)), + (("tsc", "G8_IO4"), quote!(crate::tsc::G8IO4Pin)), + ].into(); + + for p in METADATA.peripherals { + if let Some(regs) = &p.registers { + for pin in p.pins { + let key = (regs.kind, pin.signal); + if let Some(tr) = signals.get(&key) { + let mut peri = format_ident!("{}", p.name); + + let pin_name = { + // If we encounter a _C pin but the split_feature for this pin is not enabled, skip it + if pin.pin.ends_with("_C") && !split_features.iter().any(|x| x.pin_name_with_c == pin.pin) { + continue; + } + + format_ident!("{}", pin.pin) + }; + + let af = pin.af.unwrap_or(0); + + // MCO is special + if pin.signal.starts_with("MCO") { + peri = format_ident!("{}", pin.signal.replace('_', "")); + } + + // OCTOSPIM is special + if p.name == "OCTOSPIM" { + // Some chips have OCTOSPIM but not OCTOSPI2. + if METADATA.peripherals.iter().any(|p| p.name == "OCTOSPI2") { + peri = format_ident!("{}", "OCTOSPI2"); + g.extend(quote! { + pin_trait_impl!(#tr, #peri, #pin_name, #af); + }); + } + peri = format_ident!("{}", "OCTOSPI1"); + } + + g.extend(quote! { + pin_trait_impl!(#tr, #peri, #pin_name, #af); + }) + } + + // ADC is special + if regs.kind == "adc" { + if p.rcc.is_none() { + continue; + } + + let peri = format_ident!("{}", p.name); + let pin_name = { + // If we encounter a _C pin but the split_feature for this pin is not enabled, skip it + if pin.pin.ends_with("_C") && !split_features.iter().any(|x| x.pin_name_with_c == pin.pin) { + continue; + } + format_ident!("{}", pin.pin) + }; + + // H7 has differential voltage measurements + let ch: Option = if pin.signal.starts_with("INP") { + Some(pin.signal.strip_prefix("INP").unwrap().parse().unwrap()) + } else if pin.signal.starts_with("INN") { + // TODO handle in the future when embassy supports differential measurements + None + } else if pin.signal.starts_with("IN") && pin.signal.ends_with('b') { + // we number STM32L1 ADC bank 1 as 0..=31, bank 2 as 32..=63 + let signal = pin.signal.strip_prefix("IN").unwrap().strip_suffix('b').unwrap(); + Some(32u8 + signal.parse::().unwrap()) + } else if pin.signal.starts_with("IN") { + Some(pin.signal.strip_prefix("IN").unwrap().parse().unwrap()) + } else { + None + }; + if let Some(ch) = ch { + g.extend(quote! { + impl_adc_pin!( #peri, #pin_name, #ch); + }) + } + } + + if regs.kind == "opamp" { + if pin.signal.starts_with("VP") { + // Impl NonInvertingPin for the VP* signals (VP0, VP1, VP2, etc) + let peri = format_ident!("{}", p.name); + let pin_name = format_ident!("{}", pin.pin); + let ch: u8 = pin.signal.strip_prefix("VP").unwrap().parse().unwrap(); + + g.extend(quote! { + impl_opamp_vp_pin!( #peri, #pin_name, #ch); + }) + } else if pin.signal == "VOUT" { + // Impl OutputPin for the VOUT pin + let peri = format_ident!("{}", p.name); + let pin_name = format_ident!("{}", pin.pin); + g.extend(quote! { + impl_opamp_vout_pin!( #peri, #pin_name ); + }) + } + } + + // DAC is special + if regs.kind == "dac" { + let peri = format_ident!("{}", p.name); + let pin_name = format_ident!("{}", pin.pin); + let ch: u8 = pin.signal.strip_prefix("OUT").unwrap().parse().unwrap(); + + g.extend(quote! { + impl_dac_pin!( #peri, #pin_name, #ch); + }) + } + + if regs.kind == "spdifrx" { + let peri = format_ident!("{}", p.name); + let pin_name = format_ident!("{}", pin.pin); + let af = pin.af.unwrap_or(0); + let sel: u8 = pin.signal.strip_prefix("IN").unwrap().parse().unwrap(); + + g.extend(quote! { + impl_spdifrx_pin!( #peri, #pin_name, #af, #sel); + }) + } + } + } + } + + // ======== + // Generate dma_trait_impl! + + let signals: HashMap<_, _> = [ + // (kind, signal) => trait + (("adc", "ADC"), quote!(crate::adc::RxDma)), + (("adc", "ADC1"), quote!(crate::adc::RxDma)), + (("adc", "ADC2"), quote!(crate::adc::RxDma)), + (("adc", "ADC3"), quote!(crate::adc::RxDma)), + (("adc", "ADC4"), quote!(crate::adc::RxDma)), + (("ucpd", "RX"), quote!(crate::ucpd::RxDma)), + (("ucpd", "TX"), quote!(crate::ucpd::TxDma)), + (("usart", "RX"), quote!(crate::usart::RxDma)), + (("usart", "TX"), quote!(crate::usart::TxDma)), + (("lpuart", "RX"), quote!(crate::usart::RxDma)), + (("lpuart", "TX"), quote!(crate::usart::TxDma)), + (("sai", "A"), quote!(crate::sai::Dma)), + (("sai", "B"), quote!(crate::sai::Dma)), + (("spi", "RX"), quote!(crate::spi::RxDma)), + (("spi", "TX"), quote!(crate::spi::TxDma)), + (("spdifrx", "RX"), quote!(crate::spdifrx::Dma)), + (("i2c", "RX"), quote!(crate::i2c::RxDma)), + (("i2c", "TX"), quote!(crate::i2c::TxDma)), + (("dcmi", "DCMI"), quote!(crate::dcmi::FrameDma)), + (("dcmi", "PSSI"), quote!(crate::dcmi::FrameDma)), + // SDMMCv1 uses the same channel for both directions, so just implement for RX + (("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)), + (("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)), + (("octospi", "OCTOSPI1"), quote!(crate::ospi::OctoDma)), + (("dac", "CH1"), quote!(crate::dac::DacDma1)), + (("dac", "CH2"), quote!(crate::dac::DacDma2)), + (("timer", "UP"), quote!(crate::timer::UpDma)), + (("hash", "IN"), quote!(crate::hash::Dma)), + (("cryp", "IN"), quote!(crate::cryp::DmaIn)), + (("cryp", "OUT"), quote!(crate::cryp::DmaOut)), + (("timer", "CH1"), quote!(crate::timer::Ch1Dma)), + (("timer", "CH2"), quote!(crate::timer::Ch2Dma)), + (("timer", "CH3"), quote!(crate::timer::Ch3Dma)), + (("timer", "CH4"), quote!(crate::timer::Ch4Dma)), + (("cordic", "WRITE"), quote!(crate::cordic::WriteDma)), // FIXME: stm32u5a crash on Cordic driver + (("cordic", "READ"), quote!(crate::cordic::ReadDma)), // FIXME: stm32u5a crash on Cordic driver + ] + .into(); + + for p in METADATA.peripherals { + if let Some(regs) = &p.registers { + // FIXME: stm32u5a crash on Cordic driver + if chip_name.starts_with("stm32u5a") && regs.kind == "cordic" { + continue; + } + + let mut dupe = HashSet::new(); + for ch in p.dma_channels { + if let Some(tr) = signals.get(&(regs.kind, ch.signal)) { + let peri = format_ident!("{}", p.name); + + let channels = if let Some(channel) = &ch.channel { + // Chip with DMA/BDMA, without DMAMUX + vec![*channel] + } else if let Some(dmamux) = &ch.dmamux { + // Chip with DMAMUX + METADATA + .dma_channels + .iter() + .filter(|ch| ch.dmamux == Some(*dmamux)) + .map(|ch| ch.name) + .collect() + } else if let Some(dma) = &ch.dma { + // Chip with GPDMA + METADATA + .dma_channels + .iter() + .filter(|ch| ch.dma == *dma) + .map(|ch| ch.name) + .collect() + } else { + unreachable!(); + }; + + for channel in channels { + // Some chips have multiple request numbers for the same (peri, signal, channel) combos. + // Ignore the dupes, picking the first one. Otherwise this causes conflicting trait impls + let key = (ch.signal, channel.to_string()); + if !dupe.insert(key) { + continue; + } + + let request = if let Some(request) = ch.request { + let request = request as u8; + quote!(#request) + } else { + quote!(()) + }; + + let channel = format_ident!("{}", channel); + g.extend(quote! { + dma_trait_impl!(#tr, #peri, #channel, #request); + }); + } + } + } + } + } + + // ======== + // Generate Div/Mul impls for RCC prescalers/dividers/multipliers. + for e in rcc_registers.ir.enums { + fn is_rcc_name(e: &str) -> bool { + match e { + "Pllp" | "Pllq" | "Pllr" | "Pllm" | "Plln" => true, + "Timpre" | "Pllrclkpre" => false, + e if e.ends_with("pre") || e.ends_with("pres") || e.ends_with("div") || e.ends_with("mul") => true, + _ => false, + } + } + + #[derive(Copy, Clone, Debug)] + struct Frac { + num: u32, + denom: u32, + } + + impl Frac { + fn simplify(self) -> Self { + let d = gcd(self.num, self.denom); + Self { + num: self.num / d, + denom: self.denom / d, + } + } + } + + fn gcd(a: u32, b: u32) -> u32 { + if b == 0 { + return a; + } + gcd(b, a % b) + } + + fn parse_num(n: &str) -> Result { + for prefix in ["DIV", "MUL"] { + if let Some(n) = n.strip_prefix(prefix) { + let exponent = n.find('_').map(|e| n.len() - 1 - e).unwrap_or(0) as u32; + let mantissa = n.replace('_', "").parse().map_err(|_| ())?; + let f = Frac { + num: mantissa, + denom: 10u32.pow(exponent), + }; + return Ok(f.simplify()); + } + } + Err(()) + } + + if is_rcc_name(e.name) { + let enum_name = format_ident!("{}", e.name); + let mut muls = Vec::new(); + let mut divs = Vec::new(); + for v in e.variants { + let Ok(val) = parse_num(v.name) else { + panic!("could not parse mul/div. enum={} variant={}", e.name, v.name) + }; + let variant_name = format_ident!("{}", v.name); + let variant = quote!(crate::pac::rcc::vals::#enum_name::#variant_name); + let num = val.num; + let denom = val.denom; + muls.push(quote!(#variant => self * #num / #denom,)); + divs.push(quote!(#variant => self * #denom / #num,)); + } + + g.extend(quote! { + impl core::ops::Div for crate::time::Hertz { + type Output = crate::time::Hertz; + fn div(self, rhs: crate::pac::rcc::vals::#enum_name) -> Self::Output { + match rhs { + #(#divs)* + #[allow(unreachable_patterns)] + _ => unreachable!(), + } + } + } + impl core::ops::Mul for crate::time::Hertz { + type Output = crate::time::Hertz; + fn mul(self, rhs: crate::pac::rcc::vals::#enum_name) -> Self::Output { + match rhs { + #(#muls)* + #[allow(unreachable_patterns)] + _ => unreachable!(), + } + } + } + }); + } + } + + // ======== + // Write peripheral_interrupts module. + let mut mt = TokenStream::new(); + for p in METADATA.peripherals { + let mut pt = TokenStream::new(); + + for irq in p.interrupts { + let iname = format_ident!("{}", irq.interrupt); + let sname = format_ident!("{}", irq.signal); + pt.extend(quote!(pub type #sname = crate::interrupt::typelevel::#iname;)); + } + + let pname = format_ident!("{}", p.name); + mt.extend(quote!(pub mod #pname { #pt })); + } + g.extend(quote!(#[allow(non_camel_case_types)] pub mod peripheral_interrupts { #mt })); + + // ======== + // Write foreach_foo! macrotables + + let mut flash_regions_table: Vec> = Vec::new(); + let mut interrupts_table: Vec> = Vec::new(); + let mut peripherals_table: Vec> = Vec::new(); + let mut pins_table: Vec> = Vec::new(); + let mut adc_table: Vec> = Vec::new(); + + for m in METADATA + .memory + .iter() + .filter(|m| m.kind == MemoryRegionKind::Flash && m.settings.is_some()) + { + let settings = m.settings.as_ref().unwrap(); + let row = vec![ + get_flash_region_type_name(m.name), + settings.write_size.to_string(), + settings.erase_size.to_string(), + ]; + flash_regions_table.push(row); + } + + let gpio_base = METADATA.peripherals.iter().find(|p| p.name == "GPIOA").unwrap().address as u32; + let gpio_stride = 0x400; + + for p in METADATA.peripherals { + if let Some(regs) = &p.registers { + if regs.kind == "gpio" { + let port_letter = p.name.chars().nth(4).unwrap(); + assert_eq!(0, (p.address as u32 - gpio_base) % gpio_stride); + let port_num = (p.address as u32 - gpio_base) / gpio_stride; + + for pin_num in 0u32..16 { + let pin_name = format!("P{}{}", port_letter, pin_num); + + pins_table.push(vec![ + pin_name.clone(), + p.name.to_string(), + port_num.to_string(), + pin_num.to_string(), + format!("EXTI{}", pin_num), + ]); + + // If we have the split pins, we need to do a little extra work: + // Add the "_C" variant to the table. The solution is not optimal, though. + // Adding them only when the corresponding GPIOx also appears. + // This should avoid unintended side-effects as much as possible. + #[cfg(feature = "_split-pins-enabled")] + for split_feature in &split_features { + if split_feature.pin_name_without_c == pin_name { + pins_table.push(vec![ + split_feature.pin_name_with_c.to_string(), + p.name.to_string(), + port_num.to_string(), + pin_num.to_string(), + format!("EXTI{}", pin_num), + ]); + } + } + } + } + + if regs.kind == "adc" { + let adc_num = p.name.strip_prefix("ADC").unwrap(); + let mut adc_common = None; + for p2 in METADATA.peripherals { + if let Some(common_nums) = p2.name.strip_prefix("ADC").and_then(|s| s.strip_suffix("_COMMON")) { + if common_nums.contains(adc_num) { + adc_common = Some(p2); + } + } + } + let adc_common = adc_common.map(|p| p.name).unwrap_or("none"); + let row = vec![p.name.to_string(), adc_common.to_string(), "adc".to_string()]; + adc_table.push(row); + } + + for irq in p.interrupts { + let row = vec![ + p.name.to_string(), + regs.kind.to_string(), + regs.block.to_string(), + irq.signal.to_string(), + irq.interrupt.to_ascii_uppercase(), + ]; + interrupts_table.push(row) + } + + let row = vec![regs.kind.to_string(), p.name.to_string()]; + peripherals_table.push(row); + } + } + + let mut dmas = TokenStream::new(); + let has_dmamux = METADATA + .peripherals + .iter() + .flat_map(|p| &p.registers) + .any(|p| p.kind == "dmamux"); + + let mut dma_irqs: BTreeMap<&str, Vec> = BTreeMap::new(); + + for p in METADATA.peripherals { + if let Some(r) = &p.registers { + if r.kind == "dma" || r.kind == "bdma" || r.kind == "gpdma" || r.kind == "lpdma" { + for irq in p.interrupts { + let ch_name = format!("{}_{}", p.name, irq.signal); + let ch = METADATA.dma_channels.iter().find(|c| c.name == ch_name).unwrap(); + + // Some H7 chips have BDMA1 hardcoded for DFSDM, ie no DMAMUX. It's unsupported, skip it. + if has_dmamux && ch.dmamux.is_none() { + continue; + } + + dma_irqs.entry(irq.interrupt).or_default().push(ch_name); + } + } + } + } + + #[cfg(feature = "_dual-core")] + let mut dma_ch_to_irq: BTreeMap<&str, Vec> = BTreeMap::new(); + + #[cfg(feature = "_dual-core")] + for (irq, channels) in &dma_irqs { + for channel in channels { + dma_ch_to_irq.entry(channel).or_default().push(irq.to_string()); + } + } + + for (ch_idx, ch) in METADATA.dma_channels.iter().enumerate() { + // Some H7 chips have BDMA1 hardcoded for DFSDM, ie no DMAMUX. It's unsupported, skip it. + if has_dmamux && ch.dmamux.is_none() { + continue; + } + + let name = format_ident!("{}", ch.name); + let idx = ch_idx as u8; + #[cfg(feature = "_dual-core")] + let irq = { + let irq_name = if let Some(x) = &dma_ch_to_irq.get(ch.name) { + format_ident!("{}", x.get(0).unwrap()) + } else { + panic!("failed to find dma interrupt") + }; + quote!(crate::pac::Interrupt::#irq_name) + }; + + g.extend(quote!(dma_channel_impl!(#name, #idx);)); + + let dma = format_ident!("{}", ch.dma); + let ch_num = ch.channel as usize; + + let dma_peri = METADATA.peripherals.iter().find(|p| p.name == ch.dma).unwrap(); + let bi = dma_peri.registers.as_ref().unwrap(); + + let dma_info = match bi.kind { + "dma" => quote!(crate::dma::DmaInfo::Dma(crate::pac::#dma)), + "bdma" => quote!(crate::dma::DmaInfo::Bdma(crate::pac::#dma)), + "gpdma" => quote!(crate::pac::#dma), + "lpdma" => quote!(unsafe { crate::pac::gpdma::Gpdma::from_ptr(crate::pac::#dma.as_ptr())}), + _ => panic!("bad dma channel kind {}", bi.kind), + }; + + let dmamux = match &ch.dmamux { + Some(dmamux) => { + let dmamux = format_ident!("{}", dmamux); + let num = ch.dmamux_channel.unwrap() as usize; + quote! { + dmamux: crate::dma::DmamuxInfo { + mux: crate::pac::#dmamux, + num: #num, + }, + } + } + None => quote!(), + }; + + #[cfg(not(feature = "_dual-core"))] + dmas.extend(quote! { + crate::dma::ChannelInfo { + dma: #dma_info, + num: #ch_num, + #dmamux + }, + }); + #[cfg(feature = "_dual-core")] + dmas.extend(quote! { + crate::dma::ChannelInfo { + dma: #dma_info, + num: #ch_num, + irq: #irq, + #dmamux + }, + }); + } + + // ======== + // Generate DMA IRQs. + + let dma_irqs: TokenStream = dma_irqs + .iter() + .map(|(irq, channels)| { + let irq = format_ident!("{}", irq); + + let channels = channels.iter().map(|c| format_ident!("{}", c)); + + quote! { + #[cfg(feature = "rt")] + #[crate::interrupt] + unsafe fn #irq () { + #( + ::on_irq(); + )* + } + } + }) + .collect(); + + g.extend(dma_irqs); + + g.extend(quote! { + pub(crate) const DMA_CHANNELS: &[crate::dma::ChannelInfo] = &[#dmas]; + }); + + for irq in METADATA.interrupts { + let name = irq.name.to_ascii_uppercase(); + interrupts_table.push(vec![name.clone()]); + if name.contains("EXTI") { + interrupts_table.push(vec!["EXTI".to_string(), name.clone()]); + } + } + + let mut m = clocks_macro.to_string(); + + // DO NOT ADD more macros like these. + // These turned to be a bad idea! + // Instead, make build.rs generate the final code. + make_table(&mut m, "foreach_flash_region", &flash_regions_table); + make_table(&mut m, "foreach_interrupt", &interrupts_table); + make_table(&mut m, "foreach_peripheral", &peripherals_table); + make_table(&mut m, "foreach_pin", &pins_table); + make_table(&mut m, "foreach_adc", &adc_table); + + let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("_macros.rs").to_string_lossy().to_string(); + fs::write(&out_file, m).unwrap(); + rustfmt(&out_file); + + // ======== + // Write generated.rs + + let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); + fs::write(&out_file, g.to_string()).unwrap(); + rustfmt(&out_file); + + // ======== + // Configs for multicore and for targeting groups of chips + + fn get_chip_cfgs(chip_name: &str) -> Vec { + let mut cfgs = Vec::new(); + + // Multicore + + let mut s = chip_name.split('_'); + let mut chip_name: String = s.next().unwrap().to_string(); + let core_name = if let Some(c) = s.next() { + if !c.starts_with("CM") { + chip_name.push('_'); + chip_name.push_str(c); + None + } else { + Some(c) + } + } else { + None + }; + + if let Some(core) = core_name { + cfgs.push(format!("{}_{}", &chip_name[..chip_name.len() - 2], core)); + } + + // Configs for targeting groups of chips + if &chip_name[..8] == "stm32wba" { + cfgs.push(chip_name[..8].to_owned()); // stm32wba + cfgs.push(chip_name[..10].to_owned()); // stm32wba52 + cfgs.push(format!("package_{}", &chip_name[10..11])); + cfgs.push(format!("flashsize_{}", &chip_name[11..12])); + } else { + if &chip_name[..8] == "stm32h7r" || &chip_name[..8] == "stm32h7s" { + cfgs.push("stm32h7rs".to_owned()); + } else { + cfgs.push(chip_name[..7].to_owned()); // stm32f4 + } + cfgs.push(chip_name[..9].to_owned()); // stm32f429 + cfgs.push(format!("{}x", &chip_name[..8])); // stm32f42x + cfgs.push(format!("{}x{}", &chip_name[..7], &chip_name[8..9])); // stm32f4x9 + cfgs.push(format!("package_{}", &chip_name[9..10])); + cfgs.push(format!("flashsize_{}", &chip_name[10..11])); + } + + // Mark the L4+ chips as they have many differences to regular L4. + if &chip_name[..7] == "stm32l4" { + if "pqrs".contains(&chip_name[7..8]) { + cfgs.push("stm32l4_plus".to_owned()); + } else { + cfgs.push("stm32l4_nonplus".to_owned()); + } + } + + cfgs + } + + cfgs.enable_all(&get_chip_cfgs(&chip_name)); + for &chip_name in ALL_CHIPS.iter() { + cfgs.declare_all(&get_chip_cfgs(&chip_name.to_ascii_lowercase())); + } + + println!("cargo:rerun-if-changed=build.rs"); +} + +enum GetOneError { + None, + Multiple, +} + +trait IteratorExt: Iterator { + fn get_one(self) -> Result; +} + +impl IteratorExt for T { + fn get_one(mut self) -> Result { + match self.next() { + None => Err(GetOneError::None), + Some(res) => match self.next() { + Some(_) => Err(GetOneError::Multiple), + None => Ok(res), + }, + } + } +} + +fn make_table(out: &mut String, name: &str, data: &Vec>) { + write!( + out, + "#[allow(unused)] +macro_rules! {} {{ + ($($pat:tt => $code:tt;)*) => {{ + macro_rules! __{}_inner {{ + $(($pat) => $code;)* + ($_:tt) => {{}} + }} +", + name, name + ) + .unwrap(); + + for row in data { + writeln!(out, " __{}_inner!(({}));", name, row.join(",")).unwrap(); + } + + write!( + out, + " }}; +}}" + ) + .unwrap(); +} + +fn get_flash_region_name(name: &str) -> String { + let name = name.replace("BANK_", "BANK").replace("REGION_", "REGION"); + if name.contains("REGION") { + name + } else { + name + "_REGION" + } +} + +fn get_flash_region_type_name(name: &str) -> String { + get_flash_region_name(name) + .replace("BANK", "Bank") + .replace("REGION", "Region") + .replace('_', "") +} + +/// rustfmt a given path. +/// Failures are logged to stderr and ignored. +fn rustfmt(path: impl AsRef) { + let path = path.as_ref(); + match Command::new("rustfmt").args([path]).output() { + Err(e) => { + eprintln!("failed to exec rustfmt {:?}: {:?}", path, e); + } + Ok(out) => { + if !out.status.success() { + eprintln!("rustfmt {:?} failed:", path); + eprintln!("=== STDOUT:"); + std::io::stderr().write_all(&out.stdout).unwrap(); + eprintln!("=== STDERR:"); + std::io::stderr().write_all(&out.stderr).unwrap(); + } + } + } +} diff --git a/embassy/embassy-stm32/build_common.rs b/embassy/embassy-stm32/build_common.rs new file mode 100644 index 0000000..4f24e6d --- /dev/null +++ b/embassy/embassy-stm32/build_common.rs @@ -0,0 +1,94 @@ +// NOTE: this file is copy-pasted between several Embassy crates, because there is no +// straightforward way to share this code: +// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path = +// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate +// reside in the crate's directory, +// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because +// symlinks don't work on Windows. + +use std::collections::HashSet; +use std::env; + +/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring +/// them (`cargo:rust-check-cfg=cfg(X)`). +#[derive(Debug)] +pub struct CfgSet { + enabled: HashSet, + declared: HashSet, +} + +impl CfgSet { + pub fn new() -> Self { + Self { + enabled: HashSet::new(), + declared: HashSet::new(), + } + } + + /// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation. + /// + /// All configs that can potentially be enabled should be unconditionally declared using + /// [`Self::declare()`]. + pub fn enable(&mut self, cfg: impl AsRef) { + if self.enabled.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-cfg={}", cfg.as_ref()); + } + } + + pub fn enable_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.enable(cfg.as_ref()); + } + } + + /// Declare a valid config for conditional compilation, without enabling it. + /// + /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. + pub fn declare(&mut self, cfg: impl AsRef) { + if self.declared.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); + } + } + + pub fn declare_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.declare(cfg.as_ref()); + } + } + + pub fn set(&mut self, cfg: impl Into, enable: bool) { + let cfg = cfg.into(); + if enable { + self.enable(cfg.clone()); + } + self.declare(cfg); + } +} + +/// Sets configs that describe the target platform. +pub fn set_target_cfgs(cfgs: &mut CfgSet) { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + cfgs.enable_all(&["cortex_m", "armv6m"]); + } else if target.starts_with("thumbv7m-") { + cfgs.enable_all(&["cortex_m", "armv7m"]); + } else if target.starts_with("thumbv7em-") { + cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]); + } else if target.starts_with("thumbv8m.base") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]); + } else if target.starts_with("thumbv8m.main") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]); + } + cfgs.declare_all(&[ + "cortex_m", + "armv6m", + "armv7m", + "armv7em", + "armv8m", + "armv8m_base", + "armv8m_main", + ]); + + cfgs.set("has_fpu", target.ends_with("-eabihf")); +} diff --git a/embassy/embassy-stm32/src/adc/f1.rs b/embassy/embassy-stm32/src/adc/f1.rs new file mode 100644 index 0000000..b37ec26 --- /dev/null +++ b/embassy/embassy-stm32/src/adc/f1.rs @@ -0,0 +1,174 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::into_ref; + +use super::blocking_delay_us; +use crate::adc::{Adc, AdcChannel, Instance, SampleTime}; +use crate::time::Hertz; +use crate::{interrupt, rcc, Peripheral}; + +pub const VDDA_CALIB_MV: u32 = 3300; +pub const ADC_MAX: u32 = (1 << 12) - 1; +// No calibration data for F103, voltage should be 1.2v +pub const VREF_INT: u32 = 1200; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + if T::regs().sr().read().eoc() { + T::regs().cr1().modify(|w| w.set_eocie(false)); + } else { + return; + } + + T::state().waker.wake(); + } +} + +pub struct Vref; +impl AdcChannel for Vref {} +impl super::SealedAdcChannel for Vref { + fn channel(&self) -> u8 { + 17 + } +} + +pub struct Temperature; +impl AdcChannel for Temperature {} +impl super::SealedAdcChannel for Temperature { + fn channel(&self) -> u8 { + 16 + } +} + +impl<'d, T: Instance> Adc<'d, T> { + pub fn new(adc: impl Peripheral

+ 'd) -> Self { + into_ref!(adc); + rcc::enable_and_reset::(); + T::regs().cr2().modify(|reg| reg.set_adon(true)); + + // 11.4: Before starting a calibration, the ADC must have been in power-on state (ADON bit = ‘1’) + // for at least two ADC clock cycles. + blocking_delay_us((1_000_000 * 2) / Self::freq().0 + 1); + + // Reset calibration + T::regs().cr2().modify(|reg| reg.set_rstcal(true)); + while T::regs().cr2().read().rstcal() { + // spin + } + + // Calibrate + T::regs().cr2().modify(|reg| reg.set_cal(true)); + while T::regs().cr2().read().cal() { + // spin + } + + // One cycle after calibration + blocking_delay_us((1_000_000 * 1) / Self::freq().0 + 1); + + Self { + adc, + sample_time: SampleTime::from_bits(0), + } + } + + fn freq() -> Hertz { + T::frequency() + } + + pub fn sample_time_for_us(&self, us: u32) -> SampleTime { + match us * Self::freq().0 / 1_000_000 { + 0..=1 => SampleTime::CYCLES1_5, + 2..=7 => SampleTime::CYCLES7_5, + 8..=13 => SampleTime::CYCLES13_5, + 14..=28 => SampleTime::CYCLES28_5, + 29..=41 => SampleTime::CYCLES41_5, + 42..=55 => SampleTime::CYCLES55_5, + 56..=71 => SampleTime::CYCLES71_5, + _ => SampleTime::CYCLES239_5, + } + } + + pub fn enable_vref(&self) -> Vref { + T::regs().cr2().modify(|reg| { + reg.set_tsvrefe(true); + }); + Vref {} + } + + pub fn enable_temperature(&self) -> Temperature { + T::regs().cr2().modify(|reg| { + reg.set_tsvrefe(true); + }); + Temperature {} + } + + pub fn set_sample_time(&mut self, sample_time: SampleTime) { + self.sample_time = sample_time; + } + + /// Perform a single conversion. + async fn convert(&mut self) -> u16 { + T::regs().cr2().modify(|reg| { + reg.set_adon(true); + reg.set_swstart(true); + }); + T::regs().cr1().modify(|w| w.set_eocie(true)); + + poll_fn(|cx| { + T::state().waker.register(cx.waker()); + + if !T::regs().cr2().read().swstart() && T::regs().sr().read().eoc() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + T::regs().dr().read().0 as u16 + } + + pub async fn read(&mut self, channel: &mut impl AdcChannel) -> u16 { + Self::set_channel_sample_time(channel.channel(), self.sample_time); + T::regs().cr1().modify(|reg| { + reg.set_scan(false); + reg.set_discen(false); + }); + T::regs().sqr1().modify(|reg| reg.set_l(0)); + + T::regs().cr2().modify(|reg| { + reg.set_cont(false); + reg.set_exttrig(true); + reg.set_swstart(false); + reg.set_extsel(7); // SWSTART + }); + + // Configure the channel to sample + T::regs().sqr3().write(|reg| reg.set_sq(0, channel.channel())); + self.convert().await + } + + 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)); + } else { + T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); + } + } +} + +impl<'d, T: Instance> Drop for Adc<'d, T> { + fn drop(&mut self) { + T::regs().cr2().modify(|reg| reg.set_adon(false)); + + rcc::disable::(); + } +} diff --git a/embassy/embassy-stm32/src/adc/f3.rs b/embassy/embassy-stm32/src/adc/f3.rs new file mode 100644 index 0000000..0ebeb8a --- /dev/null +++ b/embassy/embassy-stm32/src/adc/f3.rs @@ -0,0 +1,193 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::into_ref; + +use super::blocking_delay_us; +use crate::adc::{Adc, AdcChannel, Instance, SampleTime}; +use crate::interrupt::typelevel::Interrupt; +use crate::time::Hertz; +use crate::{interrupt, rcc, Peripheral}; + +pub const VDDA_CALIB_MV: u32 = 3300; +pub const ADC_MAX: u32 = (1 << 12) - 1; +pub const VREF_INT: u32 = 1230; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + if T::regs().isr().read().eoc() { + T::regs().ier().modify(|w| w.set_eocie(false)); + } else { + return; + } + + T::state().waker.wake(); + } +} + +pub struct Vref; +impl AdcChannel for Vref {} +impl super::SealedAdcChannel for Vref { + fn channel(&self) -> u8 { + 18 + } +} + +impl Vref { + /// The value that vref would be if vdda was at 3300mv + pub fn value(&self) -> u16 { + crate::pac::VREFINTCAL.data().read() + } +} + +pub struct Temperature; +impl AdcChannel for Temperature {} +impl super::SealedAdcChannel for Temperature { + fn channel(&self) -> u8 { + 16 + } +} + +impl<'d, T: Instance> Adc<'d, T> { + pub fn new( + adc: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + use crate::pac::adc::vals; + + into_ref!(adc); + + rcc::enable_and_reset::(); + + // Enable the adc regulator + T::regs().cr().modify(|w| w.set_advregen(vals::Advregen::INTERMEDIATE)); + T::regs().cr().modify(|w| w.set_advregen(vals::Advregen::ENABLED)); + + // Wait for the regulator to stabilize + blocking_delay_us(10); + + assert!(!T::regs().cr().read().aden()); + + // Begin calibration + T::regs().cr().modify(|w| w.set_adcaldif(false)); + T::regs().cr().modify(|w| w.set_adcal(true)); + + while T::regs().cr().read().adcal() {} + + // Wait more than 4 clock cycles after adcal is cleared (RM0364 p. 223). + blocking_delay_us((1_000_000 * 4) / Self::freq().0 + 1); + + // Enable the adc + T::regs().cr().modify(|w| w.set_aden(true)); + + // Wait until the adc is ready + while !T::regs().isr().read().adrdy() {} + + T::Interrupt::unpend(); + unsafe { + T::Interrupt::enable(); + } + + Self { + adc, + sample_time: SampleTime::from_bits(0), + } + } + + fn freq() -> Hertz { + ::frequency() + } + + pub fn sample_time_for_us(&self, us: u32) -> SampleTime { + match us * Self::freq().0 / 1_000_000 { + 0..=1 => SampleTime::CYCLES1_5, + 2..=4 => SampleTime::CYCLES4_5, + 5..=7 => SampleTime::CYCLES7_5, + 8..=19 => SampleTime::CYCLES19_5, + 20..=61 => SampleTime::CYCLES61_5, + 62..=181 => SampleTime::CYCLES181_5, + _ => SampleTime::CYCLES601_5, + } + } + + pub fn enable_vref(&self) -> Vref { + T::common_regs().ccr().modify(|w| w.set_vrefen(true)); + + Vref {} + } + + pub fn enable_temperature(&self) -> Temperature { + T::common_regs().ccr().modify(|w| w.set_tsen(true)); + + Temperature {} + } + + pub fn set_sample_time(&mut self, sample_time: SampleTime) { + self.sample_time = sample_time; + } + + /// Perform a single conversion. + async fn convert(&mut self) -> u16 { + T::regs().isr().write(|_| {}); + T::regs().ier().modify(|w| w.set_eocie(true)); + T::regs().cr().modify(|w| w.set_adstart(true)); + + poll_fn(|cx| { + T::state().waker.register(cx.waker()); + + if T::regs().isr().read().eoc() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + T::regs().isr().write(|_| {}); + + T::regs().dr().read().rdata() + } + + pub async fn read(&mut self, channel: &mut impl AdcChannel) -> u16 { + Self::set_channel_sample_time(channel.channel(), self.sample_time); + + // Configure the channel to sample + T::regs().sqr1().write(|w| w.set_sq(0, channel.channel())); + self.convert().await + } + + 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)); + } else { + T::regs().smpr2().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); + } + } +} + +impl<'d, T: Instance> Drop for Adc<'d, T> { + fn drop(&mut self) { + use crate::pac::adc::vals; + + T::regs().cr().modify(|w| w.set_adstp(true)); + + while T::regs().cr().read().adstp() {} + + T::regs().cr().modify(|w| w.set_addis(true)); + + while T::regs().cr().read().aden() {} + + // Disable the adc regulator + T::regs().cr().modify(|w| w.set_advregen(vals::Advregen::INTERMEDIATE)); + T::regs().cr().modify(|w| w.set_advregen(vals::Advregen::DISABLED)); + + rcc::disable::(); + } +} diff --git a/embassy/embassy-stm32/src/adc/f3_v1_1.rs b/embassy/embassy-stm32/src/adc/f3_v1_1.rs new file mode 100644 index 0000000..291a386 --- /dev/null +++ b/embassy/embassy-stm32/src/adc/f3_v1_1.rs @@ -0,0 +1,408 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_futures::yield_now; +use embassy_hal_internal::into_ref; +use embassy_time::Instant; + +use super::Resolution; +use crate::adc::{Adc, AdcChannel, Instance, SampleTime}; +use crate::interrupt::typelevel::Interrupt; +use crate::time::Hertz; +use crate::{interrupt, rcc, Peripheral}; + +const ADC_FREQ: Hertz = crate::rcc::HSI_FREQ; + +pub const VDDA_CALIB_MV: u32 = 3300; +pub const ADC_MAX: u32 = (1 << 12) - 1; +pub const VREF_INT: u32 = 1230; + +pub enum AdcPowerMode { + AlwaysOn, + DelayOff, + IdleOff, + DelayIdleOff, +} + +pub enum Prescaler { + Div1, + Div2, + Div3, + Div4, +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + if T::regs().sr().read().eoc() { + T::regs().cr1().modify(|w| w.set_eocie(false)); + } else { + return; + } + + T::state().waker.wake(); + } +} + +fn update_vref(op: i8) { + static VREF_STATUS: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0); + + if op > 0 { + if VREF_STATUS.fetch_add(1, core::sync::atomic::Ordering::SeqCst) == 0 { + T::regs().ccr().modify(|w| w.set_tsvrefe(true)); + } + } else { + if VREF_STATUS.fetch_sub(1, core::sync::atomic::Ordering::SeqCst) == 1 { + T::regs().ccr().modify(|w| w.set_tsvrefe(false)); + } + } +} + +pub struct Vref(core::marker::PhantomData); +impl AdcChannel for Vref {} +impl super::SealedAdcChannel for Vref { + fn channel(&self) -> u8 { + 17 + } +} + +impl Vref { + /// The value that vref would be if vdda was at 3000mv + pub fn calibrated_value(&self) -> u16 { + crate::pac::VREFINTCAL.data().read() + } + + pub async fn calibrate(&mut self, adc: &mut Adc<'_, T>) -> Calibration { + let vref_val = adc.read(self).await; + Calibration { + vref_cal: self.calibrated_value(), + vref_val, + } + } +} + +pub struct Calibration { + vref_cal: u16, + vref_val: u16, +} + +impl Calibration { + /// The millivolts that the calibration value was measured at + pub const CALIBRATION_UV: u32 = 3_000_000; + + /// Returns the measured VddA in microvolts (uV) + pub fn vdda_uv(&self) -> u32 { + (Self::CALIBRATION_UV * self.vref_cal as u32) / self.vref_val as u32 + } + + /// Returns the measured VddA as an f32 + pub fn vdda_f32(&self) -> f32 { + (Self::CALIBRATION_UV as f32 / 1_000.0) * (self.vref_cal as f32 / self.vref_val as f32) + } + + /// Returns a calibrated voltage value as in microvolts (uV) + pub fn cal_uv(&self, raw: u16, resolution: super::Resolution) -> u32 { + (self.vdda_uv() / super::resolution_to_max_count(resolution)) * raw as u32 + } + + /// Returns a calibrated voltage value as an f32 + pub fn cal_f32(&self, raw: u16, resolution: super::Resolution) -> f32 { + raw as f32 * self.vdda_f32() / super::resolution_to_max_count(resolution) as f32 + } +} + +impl Drop for Vref { + fn drop(&mut self) { + update_vref::(-1) + } +} + +pub struct Temperature(core::marker::PhantomData); +impl AdcChannel for Temperature {} +impl super::SealedAdcChannel for Temperature { + fn channel(&self) -> u8 { + 16 + } +} + +impl Drop for Temperature { + fn drop(&mut self) { + update_vref::(-1) + } +} + +impl<'d, T: Instance> Adc<'d, T> { + pub fn new( + adc: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + into_ref!(adc); + + rcc::enable_and_reset::(); + + //let r = T::regs(); + //r.cr2().write(|w| w.set_align(true)); + + T::Interrupt::unpend(); + unsafe { + T::Interrupt::enable(); + } + + Self { adc } + } + + fn freq() -> Hertz { + let div = T::regs().ccr().read().adcpre() + 1; + ADC_FREQ / div as u32 + } + + pub async fn set_resolution(&mut self, res: Resolution) { + let was_on = Self::is_on(); + if was_on { + self.stop_adc().await; + } + + T::regs().cr1().modify(|w| w.set_res(res.into())); + + if was_on { + self.start_adc().await; + } + } + + pub fn resolution(&self) -> Resolution { + T::regs().cr1().read().res() + } + + pub fn enable_vref(&self) -> Vref { + update_vref::(1); + + Vref(core::marker::PhantomData) + } + + pub fn enable_temperature(&self) -> Temperature { + T::regs().ccr().modify(|w| w.set_tsvrefe(true)); + + Temperature::(core::marker::PhantomData) + } + + /// Perform a single conversion. + async fn convert(&mut self) -> u16 { + let was_on = Self::is_on(); + + if !was_on { + self.start_adc().await; + } + + self.wait_sample_ready().await; + + T::regs().sr().write(|_| {}); + T::regs().cr1().modify(|w| { + w.set_eocie(true); + w.set_scan(false); + }); + T::regs().cr2().modify(|w| { + w.set_swstart(true); + w.set_cont(false); + }); // swstart cleared by HW + + let res = poll_fn(|cx| { + T::state().waker.register(cx.waker()); + + if T::regs().sr().read().eoc() { + let res = T::regs().dr().read().rdata(); + Poll::Ready(res) + } else { + Poll::Pending + } + }) + .await; + + if !was_on { + self.stop_adc().await; + } + + res + } + + #[inline(always)] + fn is_on() -> bool { + T::regs().sr().read().adons() || T::regs().cr2().read().adon() + } + + pub async fn start_adc(&self) { + //defmt::trace!("Turn ADC on"); + T::regs().cr2().modify(|w| w.set_adon(true)); + //defmt::trace!("Waiting for ADC to turn on"); + + let mut t = Instant::now(); + + while !T::regs().sr().read().adons() { + yield_now().await; + if t.elapsed() > embassy_time::Duration::from_millis(1000) { + t = Instant::now(); + //defmt::trace!("ADC still not on"); + } + } + + //defmt::trace!("ADC on"); + } + + pub async fn stop_adc(&self) { + if T::regs().cr2().read().adon() { + //defmt::trace!("ADC should be on, wait for it to start"); + while !T::regs().csr().read().adons1() { + yield_now().await; + } + } + + //defmt::trace!("Turn ADC off"); + + T::regs().cr2().modify(|w| w.set_adon(false)); + + //defmt::trace!("Waiting for ADC to turn off"); + + while T::regs().csr().read().adons1() { + yield_now().await; + } + } + + pub async fn read(&mut self, channel: &mut impl AdcChannel) -> u16 { + self.set_sample_sequence(&[channel.channel()]).await; + self.convert().await + } + + async fn wait_sample_ready(&self) { + //trace!("Waiting for sample channel to be ready"); + while T::regs().sr().read().rcnr() { + yield_now().await; + } + } + + pub async fn set_sample_time(&mut self, channel: &mut impl AdcChannel, sample_time: SampleTime) { + if Self::get_channel_sample_time(channel.channel()) != sample_time { + self.stop_adc().await; + unsafe { + Self::set_channel_sample_time(channel.channel(), sample_time); + } + self.start_adc().await; + } + } + + pub fn get_sample_time(&self, channel: &impl AdcChannel) -> SampleTime { + Self::get_channel_sample_time(channel.channel()) + } + + /// Sets the channel sample time + /// + /// ## SAFETY: + /// - ADON == 0 i.e ADC must not be enabled when this is called. + unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + let sample_time = sample_time.into(); + + match ch { + 0..=9 => T::regs().smpr3().modify(|reg| reg.set_smp(ch as _, sample_time)), + 10..=19 => T::regs() + .smpr2() + .modify(|reg| reg.set_smp(ch as usize - 10, sample_time)), + 20..=29 => T::regs() + .smpr1() + .modify(|reg| reg.set_smp(ch as usize - 20, sample_time)), + 30..=31 => T::regs() + .smpr0() + .modify(|reg| reg.set_smp(ch as usize - 30, sample_time)), + _ => panic!("Invalid channel to sample"), + } + } + + fn get_channel_sample_time(ch: u8) -> SampleTime { + match ch { + 0..=9 => T::regs().smpr3().read().smp(ch as _), + 10..=19 => T::regs().smpr2().read().smp(ch as usize - 10), + 20..=29 => T::regs().smpr1().read().smp(ch as usize - 20), + 30..=31 => T::regs().smpr0().read().smp(ch as usize - 30), + _ => panic!("Invalid channel to sample"), + } + .into() + } + + /// Sets the sequence to sample the ADC. Must be less than 28 elements. + async fn set_sample_sequence(&self, sequence: &[u8]) { + assert!(sequence.len() <= 28); + let mut iter = sequence.iter(); + T::regs().sqr1().modify(|w| w.set_l((sequence.len() - 1) as _)); + for (idx, ch) in iter.by_ref().take(6).enumerate() { + T::regs().sqr5().modify(|w| w.set_sq(idx, *ch)); + } + for (idx, ch) in iter.by_ref().take(6).enumerate() { + T::regs().sqr4().modify(|w| w.set_sq(idx, *ch)); + } + for (idx, ch) in iter.by_ref().take(6).enumerate() { + T::regs().sqr3().modify(|w| w.set_sq(idx, *ch)); + } + for (idx, ch) in iter.by_ref().take(6).enumerate() { + T::regs().sqr2().modify(|w| w.set_sq(idx, *ch)); + } + for (idx, ch) in iter.by_ref().take(4).enumerate() { + T::regs().sqr1().modify(|w| w.set_sq(idx, *ch)); + } + } + + fn get_res_clks(res: Resolution) -> u32 { + match res { + Resolution::BITS12 => 12, + Resolution::BITS10 => 11, + Resolution::BITS8 => 9, + Resolution::BITS6 => 7, + } + } + + fn get_sample_time_clks(sample_time: SampleTime) -> u32 { + match sample_time { + SampleTime::CYCLES4 => 4, + SampleTime::CYCLES9 => 9, + SampleTime::CYCLES16 => 16, + SampleTime::CYCLES24 => 24, + SampleTime::CYCLES48 => 48, + SampleTime::CYCLES96 => 96, + SampleTime::CYCLES192 => 192, + SampleTime::CYCLES384 => 384, + } + } + + pub fn sample_time_for_us(&self, us: u32) -> SampleTime { + let res_clks = Self::get_res_clks(self.resolution()); + let us_clks = us * Self::freq().0 / 1_000_000; + let clks = us_clks.saturating_sub(res_clks); + match clks { + 0..=4 => SampleTime::CYCLES4, + 5..=9 => SampleTime::CYCLES9, + 10..=16 => SampleTime::CYCLES16, + 17..=24 => SampleTime::CYCLES24, + 25..=48 => SampleTime::CYCLES48, + 49..=96 => SampleTime::CYCLES96, + 97..=192 => SampleTime::CYCLES192, + 193.. => SampleTime::CYCLES384, + } + } + + pub fn us_for_cfg(&self, res: Resolution, sample_time: SampleTime) -> u32 { + let res_clks = Self::get_res_clks(res); + let sample_clks = Self::get_sample_time_clks(sample_time); + (res_clks + sample_clks) * 1_000_000 / Self::freq().0 + } +} + +impl<'d, T: Instance> Drop for Adc<'d, T> { + fn drop(&mut self) { + while !T::regs().sr().read().adons() {} + + T::regs().cr2().modify(|w| w.set_adon(false)); + + rcc::disable::(); + } +} diff --git a/embassy/embassy-stm32/src/adc/g4.rs b/embassy/embassy-stm32/src/adc/g4.rs new file mode 100644 index 0000000..872cf3f --- /dev/null +++ b/embassy/embassy-stm32/src/adc/g4.rs @@ -0,0 +1,510 @@ +#[allow(unused)] +#[cfg(stm32h7)] +use pac::adc::vals::{Adcaldif, Difsel, Exten}; +#[allow(unused)] +#[cfg(stm32g4)] +use pac::adc::vals::{Adcaldif, Difsel, Exten, Rovsm, Trovs}; +use pac::adccommon::vals::Presc; +use stm32_metapac::adc::vals::{Adstp, Dmacfg, Dmaen}; + +use super::{blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime}; +use crate::adc::SealedAdcChannel; +use crate::dma::Transfer; +use crate::time::Hertz; +use crate::{pac, rcc, Peripheral}; + +/// Default VREF voltage used for sample conversion to millivolts. +pub const VREF_DEFAULT_MV: u32 = 3300; +/// VREF voltage used for factory calibration of VREFINTCAL register. +pub const VREF_CALIB_MV: u32 = 3300; + +/// Max single ADC operation clock frequency +#[cfg(stm32g4)] +const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(60); +#[cfg(stm32h7)] +const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(50); + +#[cfg(stm32g4)] +const VREF_CHANNEL: u8 = 18; +#[cfg(stm32g4)] +const TEMP_CHANNEL: u8 = 16; + +#[cfg(stm32h7)] +const VREF_CHANNEL: u8 = 19; +#[cfg(stm32h7)] +const TEMP_CHANNEL: u8 = 18; + +// TODO this should be 14 for H7a/b/35 +const VBAT_CHANNEL: u8 = 17; + +// NOTE: Vrefint/Temperature/Vbat are not available on all ADCs, this currently cannot be modeled with stm32-data, so these are available from the software on all ADCs +/// Internal voltage reference channel. +pub struct VrefInt; +impl AdcChannel for VrefInt {} +impl super::SealedAdcChannel for VrefInt { + fn channel(&self) -> u8 { + VREF_CHANNEL + } +} + +/// Internal temperature channel. +pub struct Temperature; +impl AdcChannel for Temperature {} +impl super::SealedAdcChannel for Temperature { + fn channel(&self) -> u8 { + TEMP_CHANNEL + } +} + +/// Internal battery voltage channel. +pub struct Vbat; +impl AdcChannel for Vbat {} +impl super::SealedAdcChannel for Vbat { + fn channel(&self) -> u8 { + VBAT_CHANNEL + } +} + +// NOTE (unused): The prescaler enum closely copies the hardware capabilities, +// but high prescaling doesn't make a lot of sense in the current implementation and is ommited. +#[allow(unused)] +enum Prescaler { + NotDivided, + DividedBy2, + DividedBy4, + DividedBy6, + DividedBy8, + DividedBy10, + DividedBy12, + DividedBy16, + DividedBy32, + DividedBy64, + DividedBy128, + DividedBy256, +} + +impl Prescaler { + fn from_ker_ck(frequency: Hertz) -> Self { + let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; + match raw_prescaler { + 0 => Self::NotDivided, + 1 => Self::DividedBy2, + 2..=3 => Self::DividedBy4, + 4..=5 => Self::DividedBy6, + 6..=7 => Self::DividedBy8, + 8..=9 => Self::DividedBy10, + 10..=11 => Self::DividedBy12, + _ => unimplemented!(), + } + } + + fn divisor(&self) -> u32 { + match self { + Prescaler::NotDivided => 1, + Prescaler::DividedBy2 => 2, + Prescaler::DividedBy4 => 4, + Prescaler::DividedBy6 => 6, + Prescaler::DividedBy8 => 8, + Prescaler::DividedBy10 => 10, + Prescaler::DividedBy12 => 12, + Prescaler::DividedBy16 => 16, + Prescaler::DividedBy32 => 32, + Prescaler::DividedBy64 => 64, + Prescaler::DividedBy128 => 128, + Prescaler::DividedBy256 => 256, + } + } + + fn presc(&self) -> Presc { + match self { + Prescaler::NotDivided => Presc::DIV1, + Prescaler::DividedBy2 => Presc::DIV2, + Prescaler::DividedBy4 => Presc::DIV4, + Prescaler::DividedBy6 => Presc::DIV6, + Prescaler::DividedBy8 => Presc::DIV8, + Prescaler::DividedBy10 => Presc::DIV10, + Prescaler::DividedBy12 => Presc::DIV12, + Prescaler::DividedBy16 => Presc::DIV16, + Prescaler::DividedBy32 => Presc::DIV32, + Prescaler::DividedBy64 => Presc::DIV64, + Prescaler::DividedBy128 => Presc::DIV128, + Prescaler::DividedBy256 => Presc::DIV256, + } + } +} + +impl<'d, T: Instance> Adc<'d, T> { + /// Create a new ADC driver. + pub fn new(adc: impl Peripheral

+ 'd) -> Self { + embassy_hal_internal::into_ref!(adc); + rcc::enable_and_reset::(); + + let prescaler = Prescaler::from_ker_ck(T::frequency()); + + T::common_regs().ccr().modify(|w| w.set_presc(prescaler.presc())); + + let frequency = Hertz(T::frequency().0 / prescaler.divisor()); + info!("ADC frequency set to {} Hz", frequency.0); + + if frequency > MAX_ADC_CLK_FREQ { + panic!("Maximal allowed frequency for the ADC is {} MHz and it varies with different packages, refer to ST docs for more information.", MAX_ADC_CLK_FREQ.0 / 1_000_000 ); + } + + let mut s = Self { + adc, + sample_time: SampleTime::from_bits(0), + }; + s.power_up(); + s.configure_differential_inputs(); + + s.calibrate(); + blocking_delay_us(1); + + s.enable(); + s.configure(); + + s + } + + fn power_up(&mut self) { + T::regs().cr().modify(|reg| { + reg.set_deeppwd(false); + reg.set_advregen(true); + }); + + blocking_delay_us(10); + } + + fn configure_differential_inputs(&mut self) { + T::regs().difsel().modify(|w| { + for n in 0..18 { + w.set_difsel(n, Difsel::SINGLEENDED); + } + }); + } + + fn calibrate(&mut self) { + T::regs().cr().modify(|w| { + w.set_adcaldif(Adcaldif::SINGLEENDED); + }); + + T::regs().cr().modify(|w| w.set_adcal(true)); + + while T::regs().cr().read().adcal() {} + } + + fn enable(&mut self) { + // Make sure bits are off + while T::regs().cr().read().addis() { + // spin + } + + if !T::regs().cr().read().aden() { + // Enable ADC + T::regs().isr().modify(|reg| { + reg.set_adrdy(true); + }); + T::regs().cr().modify(|reg| { + reg.set_aden(true); + }); + + while !T::regs().isr().read().adrdy() { + // spin + } + } + } + + fn configure(&mut self) { + // single conversion mode, software trigger + T::regs().cfgr().modify(|w| { + w.set_cont(false); + w.set_exten(Exten::DISABLED); + }); + } + + /// Enable reading the voltage reference internal channel. + pub fn enable_vrefint(&self) -> VrefInt { + T::common_regs().ccr().modify(|reg| { + reg.set_vrefen(true); + }); + + VrefInt {} + } + + /// Enable reading the temperature internal channel. + pub fn enable_temperature(&self) -> Temperature { + T::common_regs().ccr().modify(|reg| { + reg.set_vsenseen(true); + }); + + Temperature {} + } + + /// Enable reading the vbat internal channel. + pub fn enable_vbat(&self) -> Vbat { + T::common_regs().ccr().modify(|reg| { + reg.set_vbaten(true); + }); + + Vbat {} + } + + /// Enable differential channel. + /// Caution: + /// : When configuring the channel “i” in differential input mode, its negative input voltage VINN[i] + /// is connected to another channel. As a consequence, this channel is no longer usable in + /// single-ended mode or in differential mode and must never be configured to be converted. + /// Some channels are shared between ADC1/ADC2/ADC3/ADC4/ADC5: this can make the + /// channel on the other ADC unusable. The only exception is when ADC master and the slave + /// operate in interleaved mode. + #[cfg(stm32g4)] + pub fn set_differential_channel(&mut self, ch: usize, enable: bool) { + T::regs().cr().modify(|w| w.set_aden(false)); // disable adc + T::regs().difsel().modify(|w| { + w.set_difsel( + ch, + if enable { + Difsel::DIFFERENTIAL + } else { + Difsel::SINGLEENDED + }, + ); + }); + T::regs().cr().modify(|w| w.set_aden(true)); + } + + #[cfg(stm32g4)] + pub fn set_differential(&mut self, channel: &mut impl AdcChannel, enable: bool) { + self.set_differential_channel(channel.channel() as usize, enable); + } + + /// Set oversampling shift. + #[cfg(stm32g4)] + pub fn set_oversampling_shift(&mut self, shift: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovss(shift)); + } + + /// Set oversampling ratio. + #[cfg(stm32g4)] + pub fn set_oversampling_ratio(&mut self, ratio: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovsr(ratio)); + } + + /// Enable oversampling in regular mode. + #[cfg(stm32g4)] + pub fn enable_regular_oversampling_mode(&mut self, mode: Rovsm, trig_mode: Trovs, enable: bool) { + T::regs().cfgr2().modify(|reg| reg.set_trovs(trig_mode)); + T::regs().cfgr2().modify(|reg| reg.set_rovsm(mode)); + T::regs().cfgr2().modify(|reg| reg.set_rovse(enable)); + } + + // Reads that are not implemented as INJECTED in "blocking_read" + // #[cfg(stm32g4)] + // pub fn enalble_injected_oversampling_mode(&mut self, enable: bool) { + // T::regs().cfgr2().modify(|reg| reg.set_jovse(enable)); + // } + + // #[cfg(stm32g4)] + // pub fn enable_oversampling_regular_injected_mode(&mut self, enable: bool) { + // // the regularoversampling mode is forced to resumed mode (ROVSM bit ignored), + // T::regs().cfgr2().modify(|reg| reg.set_rovse(enable)); + // T::regs().cfgr2().modify(|reg| reg.set_jovse(enable)); + // } + + /// Set the ADC sample time. + pub fn set_sample_time(&mut self, sample_time: SampleTime) { + self.sample_time = sample_time; + } + + /// Set the ADC resolution. + pub fn set_resolution(&mut self, resolution: Resolution) { + T::regs().cfgr().modify(|reg| reg.set_res(resolution.into())); + } + + /// Perform a single conversion. + fn convert(&mut self) -> u16 { + T::regs().isr().modify(|reg| { + reg.set_eos(true); + reg.set_eoc(true); + }); + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + while !T::regs().isr().read().eos() { + // spin + } + + T::regs().dr().read().0 as u16 + } + + /// Read an ADC pin. + pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { + channel.setup(); + + self.read_channel(channel) + } + + /// Read one or multiple ADC channels using DMA. + /// + /// `sequence` iterator and `readings` must have the same length. + /// + /// Example + /// ```rust,ignore + /// use embassy_stm32::adc::{Adc, AdcChannel} + /// + /// let mut adc = Adc::new(p.ADC1); + /// let mut adc_pin0 = p.PA0.degrade_adc(); + /// let mut adc_pin1 = p.PA1.degrade_adc(); + /// let mut measurements = [0u16; 2]; + /// + /// adc.read_async( + /// p.DMA1_CH2, + /// [ + /// (&mut *adc_pin0, SampleTime::CYCLES160_5), + /// (&mut *adc_pin1, SampleTime::CYCLES160_5), + /// ] + /// .into_iter(), + /// &mut measurements, + /// ) + /// .await; + /// defmt::info!("measurements: {}", measurements); + /// ``` + pub async fn read( + &mut self, + rx_dma: &mut impl RxDma, + sequence: impl ExactSizeIterator, SampleTime)>, + readings: &mut [u16], + ) { + assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); + assert!( + sequence.len() == readings.len(), + "Sequence length must be equal to readings length" + ); + assert!( + sequence.len() <= 16, + "Asynchronous read sequence cannot be more than 16 in length" + ); + + // Ensure no conversions are ongoing and ADC is enabled. + Self::cancel_conversions(); + self.enable(); + + // Set sequence length + T::regs().sqr1().modify(|w| { + w.set_l(sequence.len() as u8 - 1); + }); + + // Configure channels and ranks + for (_i, (channel, sample_time)) in sequence.enumerate() { + Self::configure_channel(channel, sample_time); + + match _i { + 0..=3 => { + T::regs().sqr1().modify(|w| { + w.set_sq(_i, channel.channel()); + }); + } + 4..=8 => { + T::regs().sqr2().modify(|w| { + w.set_sq(_i - 4, channel.channel()); + }); + } + 9..=13 => { + T::regs().sqr3().modify(|w| { + w.set_sq(_i - 9, channel.channel()); + }); + } + 14..=15 => { + T::regs().sqr4().modify(|w| { + w.set_sq(_i - 14, channel.channel()); + }); + } + _ => unreachable!(), + } + } + + // Set continuous mode with oneshot dma. + // Clear overrun flag before starting transfer. + T::regs().isr().modify(|reg| { + reg.set_ovr(true); + }); + + T::regs().cfgr().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + reg.set_dmacfg(Dmacfg::ONESHOT); + reg.set_dmaen(Dmaen::ENABLE); + }); + + let request = rx_dma.request(); + let transfer = unsafe { + Transfer::new_read( + rx_dma, + request, + T::regs().dr().as_ptr() as *mut u16, + readings, + Default::default(), + ) + }; + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + // Wait for conversion sequence to finish. + transfer.await; + + // Ensure conversions are finished. + Self::cancel_conversions(); + + // Reset configuration. + T::regs().cfgr().modify(|reg| { + reg.set_cont(false); + }); + } + + fn configure_channel(channel: &mut impl AdcChannel, sample_time: SampleTime) { + // Configure channel + Self::set_channel_sample_time(channel.channel(), sample_time); + } + + fn read_channel(&mut self, channel: &mut impl AdcChannel) -> u16 { + Self::configure_channel(channel, self.sample_time); + #[cfg(stm32h7)] + { + T::regs().cfgr2().modify(|w| w.set_lshift(0)); + T::regs() + .pcsel() + .write(|w| w.set_pcsel(channel.channel() as _, Pcsel::PRESELECTED)); + } + + T::regs().sqr1().write(|reg| { + reg.set_sq(0, channel.channel()); + reg.set_l(0); + }); + + self.convert() + } + + fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + let sample_time = sample_time.into(); + if ch <= 9 { + T::regs().smpr().modify(|reg| reg.set_smp(ch as _, sample_time)); + } else { + T::regs().smpr2().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); + } + } + + fn cancel_conversions() { + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(Adstp::STOP); + }); + while T::regs().cr().read().adstart() {} + } + } +} diff --git a/embassy/embassy-stm32/src/adc/mod.rs b/embassy/embassy-stm32/src/adc/mod.rs new file mode 100644 index 0000000..4ab82c1 --- /dev/null +++ b/embassy/embassy-stm32/src/adc/mod.rs @@ -0,0 +1,224 @@ +//! Analog to Digital Converter (ADC) + +#![macro_use] +#![allow(missing_docs)] // TODO +#![cfg_attr(adc_f3_v2, allow(unused))] + +#[cfg(not(any(adc_f3_v2, adc_u5)))] +#[cfg_attr(adc_f1, path = "f1.rs")] +#[cfg_attr(adc_f3, path = "f3.rs")] +#[cfg_attr(adc_f3_v1_1, path = "f3_v1_1.rs")] +#[cfg_attr(adc_v1, path = "v1.rs")] +#[cfg_attr(adc_l0, path = "v1.rs")] +#[cfg_attr(adc_v2, path = "v2.rs")] +#[cfg_attr(any(adc_v3, adc_g0, adc_h5, adc_u0), path = "v3.rs")] +#[cfg_attr(adc_v4, path = "v4.rs")] +#[cfg_attr(adc_g4, path = "g4.rs")] +mod _version; + +use core::marker::PhantomData; + +#[allow(unused)] +#[cfg(not(any(adc_f3_v2, adc_u5)))] +pub use _version::*; +#[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] +use embassy_sync::waitqueue::AtomicWaker; + +#[cfg(not(any(adc_u5)))] +pub use crate::pac::adc::vals; +#[cfg(not(any(adc_f1, adc_f3_v2, adc_u5)))] +pub use crate::pac::adc::vals::Res as Resolution; +#[cfg(not(any(adc_u5)))] +pub use crate::pac::adc::vals::SampleTime; +use crate::peripherals; + +dma_trait!(RxDma, Instance); + +/// Analog to Digital driver. +pub struct Adc<'d, T: Instance> { + #[allow(unused)] + adc: crate::PeripheralRef<'d, T>, + #[cfg(not(any(adc_f3_v2, adc_f3_v1_1, adc_u5)))] + sample_time: SampleTime, +} + +#[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] +pub struct State { + pub waker: AtomicWaker, +} + +#[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] +impl State { + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +trait SealedInstance { + #[allow(unused)] + fn regs() -> crate::pac::adc::Adc; + #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0, adc_u5)))] + #[allow(unused)] + fn common_regs() -> crate::pac::adccommon::AdcCommon; + #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] + fn state() -> &'static State; +} + +pub(crate) trait SealedAdcChannel { + #[cfg(any(adc_v1, adc_l0, adc_v2, adc_g4, adc_v4))] + fn setup(&mut self) {} + + #[allow(unused)] + fn channel(&self) -> u8; +} + +/// Performs a busy-wait delay for a specified number of microseconds. +#[allow(unused)] +pub(crate) fn blocking_delay_us(us: u32) { + #[cfg(feature = "time")] + embassy_time::block_for(embassy_time::Duration::from_micros(us as u64)); + #[cfg(not(feature = "time"))] + { + let freq = unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 as u64; + let us = us as u64; + let cycles = freq * us / 1_000_000; + cortex_m::asm::delay(cycles as u32); + } +} + +/// ADC instance. +#[cfg(not(any( + adc_f1, + adc_v1, + adc_l0, + adc_v2, + adc_v3, + adc_v4, + adc_g4, + adc_f3, + adc_f3_v1_1, + adc_g0, + adc_u0, + adc_h5 +)))] +#[allow(private_bounds)] +pub trait Instance: SealedInstance + crate::Peripheral

{ + type Interrupt: crate::interrupt::typelevel::Interrupt; +} +/// ADC instance. +#[cfg(any( + adc_f1, + adc_v1, + adc_l0, + adc_v2, + adc_v3, + adc_v4, + adc_g4, + adc_f3, + adc_f3_v1_1, + adc_g0, + adc_u0, + adc_h5 +))] +#[allow(private_bounds)] +pub trait Instance: SealedInstance + crate::Peripheral

+ crate::rcc::RccPeripheral { + type Interrupt: crate::interrupt::typelevel::Interrupt; +} + +/// ADC channel. +#[allow(private_bounds)] +pub trait AdcChannel: SealedAdcChannel + Sized { + #[allow(unused_mut)] + fn degrade_adc(mut self) -> AnyAdcChannel { + #[cfg(any(adc_v1, adc_l0, adc_v2, adc_g4, adc_v4))] + self.setup(); + + AnyAdcChannel { + channel: self.channel(), + _phantom: PhantomData, + } + } +} + +/// A type-erased channel for a given ADC instance. +/// +/// This is useful in scenarios where you need the ADC channels to have the same type, such as +/// storing them in an array. +pub struct AnyAdcChannel { + channel: u8, + _phantom: PhantomData, +} + +impl AdcChannel for AnyAdcChannel {} +impl SealedAdcChannel for AnyAdcChannel { + fn channel(&self) -> u8 { + self.channel + } +} + +foreach_adc!( + ($inst:ident, $common_inst:ident, $clock:ident) => { + impl crate::adc::SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::adc::Adc { + crate::pac::$inst + } + + #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0, adc_u5)))] + fn common_regs() -> crate::pac::adccommon::AdcCommon { + return crate::pac::$common_inst + } + + #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl crate::adc::Instance for peripherals::$inst { + type Interrupt = crate::_generated::peripheral_interrupts::$inst::GLOBAL; + } + }; +); + +macro_rules! impl_adc_pin { + ($inst:ident, $pin:ident, $ch:expr) => { + impl crate::adc::AdcChannel for crate::peripherals::$pin {} + impl crate::adc::SealedAdcChannel for crate::peripherals::$pin { + #[cfg(any(adc_v1, adc_l0, adc_v2, adc_g4, adc_v4))] + fn setup(&mut self) { + ::set_as_analog(self); + } + + fn channel(&self) -> u8 { + $ch + } + } + }; +} + +/// Get the maximum reading value for this resolution. +/// +/// This is `2**n - 1`. +#[cfg(not(any(adc_f1, adc_f3_v2, adc_u5)))] +pub const fn resolution_to_max_count(res: Resolution) -> u32 { + match res { + #[cfg(adc_v4)] + Resolution::BITS16 => (1 << 16) - 1, + #[cfg(adc_v4)] + Resolution::BITS14 => (1 << 14) - 1, + #[cfg(adc_v4)] + Resolution::BITS14V => (1 << 14) - 1, + #[cfg(adc_v4)] + Resolution::BITS12V => (1 << 12) - 1, + Resolution::BITS12 => (1 << 12) - 1, + Resolution::BITS10 => (1 << 10) - 1, + Resolution::BITS8 => (1 << 8) - 1, + #[cfg(any(adc_v1, adc_v2, adc_v3, adc_l0, adc_g0, adc_f3, adc_f3_v1_1, adc_h5))] + Resolution::BITS6 => (1 << 6) - 1, + #[allow(unreachable_patterns)] + _ => core::unreachable!(), + } +} diff --git a/embassy/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy/embassy-stm32/src/adc/ringbuffered_v2.rs new file mode 100644 index 0000000..3f0c1a5 --- /dev/null +++ b/embassy/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -0,0 +1,438 @@ +use core::marker::PhantomData; +use core::mem; +use core::sync::atomic::{compiler_fence, Ordering}; + +use embassy_hal_internal::{into_ref, Peripheral}; +use stm32_metapac::adc::vals::SampleTime; + +use crate::adc::{Adc, AdcChannel, Instance, RxDma}; +use crate::dma::{Priority, ReadableRingBuffer, TransferOptions}; +use crate::pac::adc::vals; +use crate::rcc; + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OverrunError; + +fn clear_interrupt_flags(r: crate::pac::adc::Adc) { + r.sr().modify(|regs| { + regs.set_eoc(false); + regs.set_ovr(false); + }); +} + +#[derive(PartialOrd, PartialEq, Debug, Clone, Copy)] +pub enum Sequence { + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Ten, + Eleven, + Twelve, + Thirteen, + Fourteen, + Fifteen, + Sixteen, +} + +impl From for u8 { + fn from(s: Sequence) -> u8 { + match s { + Sequence::One => 0, + Sequence::Two => 1, + Sequence::Three => 2, + Sequence::Four => 3, + Sequence::Five => 4, + Sequence::Six => 5, + Sequence::Seven => 6, + Sequence::Eight => 7, + Sequence::Nine => 8, + Sequence::Ten => 9, + Sequence::Eleven => 10, + Sequence::Twelve => 11, + Sequence::Thirteen => 12, + Sequence::Fourteen => 13, + Sequence::Fifteen => 14, + Sequence::Sixteen => 15, + } + } +} + +impl From for Sequence { + fn from(val: u8) -> Self { + match val { + 0 => Sequence::One, + 1 => Sequence::Two, + 2 => Sequence::Three, + 3 => Sequence::Four, + 4 => Sequence::Five, + 5 => Sequence::Six, + 6 => Sequence::Seven, + 7 => Sequence::Eight, + 8 => Sequence::Nine, + 9 => Sequence::Ten, + 10 => Sequence::Eleven, + 11 => Sequence::Twelve, + 12 => Sequence::Thirteen, + 13 => Sequence::Fourteen, + 14 => Sequence::Fifteen, + 15 => Sequence::Sixteen, + _ => panic!("Invalid sequence number"), + } + } +} + +pub struct RingBufferedAdc<'d, T: Instance> { + _phantom: PhantomData, + ring_buf: ReadableRingBuffer<'d, u16>, +} + +impl<'d, T: Instance> Adc<'d, T> { + /// Configures the ADC to use a DMA ring buffer for continuous data acquisition. + /// + /// The `dma_buf` should be large enough to prevent DMA buffer overrun. + /// The length of the `dma_buf` should be a multiple of the ADC channel count. + /// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements. + /// + /// `read` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. + /// It is critical to call `read` frequently to prevent DMA buffer overrun. + /// + /// [`read`]: #method.read + pub fn into_ring_buffered( + self, + dma: impl Peripheral

> + 'd, + dma_buf: &'d mut [u16], + ) -> RingBufferedAdc<'d, T> { + assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); + into_ref!(dma); + + let opts: crate::dma::TransferOptions = TransferOptions { + half_transfer_ir: true, + priority: Priority::VeryHigh, + ..Default::default() + }; + + // Safety: we forget the struct before this function returns. + let rx_src = T::regs().dr().as_ptr() as *mut u16; + let request = dma.request(); + + let ring_buf = unsafe { ReadableRingBuffer::new(dma, request, rx_src, dma_buf, opts) }; + + // Don't disable the clock + mem::forget(self); + + RingBufferedAdc { + _phantom: PhantomData, + ring_buf, + } + } +} + +impl<'d, T: Instance> RingBufferedAdc<'d, T> { + fn is_on() -> bool { + T::regs().cr2().read().adon() + } + + fn stop_adc() { + T::regs().cr2().modify(|reg| { + reg.set_adon(false); + }); + } + + fn start_adc() { + T::regs().cr2().modify(|reg| { + reg.set_adon(true); + }); + } + + /// Sets the channel sample time + /// + /// ## SAFETY: + /// - ADON == 0 i.e ADC must not be enabled when this is called. + unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + if ch <= 9 { + 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)); + } + } + + fn set_channels_sample_time(&mut self, ch: &[u8], sample_time: SampleTime) { + let ch_iter = ch.iter(); + for idx in ch_iter { + unsafe { + Self::set_channel_sample_time(*idx, sample_time); + } + } + } + + pub fn set_sample_sequence( + &mut self, + sequence: Sequence, + channel: &mut impl AdcChannel, + sample_time: SampleTime, + ) { + let was_on = Self::is_on(); + if !was_on { + Self::start_adc(); + } + + // Check the sequence is long enough + T::regs().sqr1().modify(|r| { + let prev: Sequence = r.l().into(); + if prev < sequence { + let new_l: Sequence = sequence; + trace!("Setting sequence length from {:?} to {:?}", prev as u8, new_l as u8); + r.set_l(sequence.into()) + } else { + r.set_l(prev.into()) + } + }); + + // Set this GPIO as an analog input. + channel.setup(); + + // Set the channel in the right sequence field. + match sequence { + Sequence::One => T::regs().sqr3().modify(|w| w.set_sq(0, channel.channel())), + Sequence::Two => T::regs().sqr3().modify(|w| w.set_sq(1, channel.channel())), + Sequence::Three => T::regs().sqr3().modify(|w| w.set_sq(2, channel.channel())), + Sequence::Four => T::regs().sqr3().modify(|w| w.set_sq(3, channel.channel())), + Sequence::Five => T::regs().sqr3().modify(|w| w.set_sq(4, channel.channel())), + Sequence::Six => T::regs().sqr3().modify(|w| w.set_sq(5, channel.channel())), + Sequence::Seven => T::regs().sqr2().modify(|w| w.set_sq(6, channel.channel())), + Sequence::Eight => T::regs().sqr2().modify(|w| w.set_sq(7, channel.channel())), + Sequence::Nine => T::regs().sqr2().modify(|w| w.set_sq(8, channel.channel())), + Sequence::Ten => T::regs().sqr2().modify(|w| w.set_sq(9, channel.channel())), + Sequence::Eleven => T::regs().sqr2().modify(|w| w.set_sq(10, channel.channel())), + Sequence::Twelve => T::regs().sqr2().modify(|w| w.set_sq(11, channel.channel())), + Sequence::Thirteen => T::regs().sqr1().modify(|w| w.set_sq(12, channel.channel())), + Sequence::Fourteen => T::regs().sqr1().modify(|w| w.set_sq(13, channel.channel())), + Sequence::Fifteen => T::regs().sqr1().modify(|w| w.set_sq(14, channel.channel())), + Sequence::Sixteen => T::regs().sqr1().modify(|w| w.set_sq(15, channel.channel())), + }; + + if !was_on { + Self::stop_adc(); + } + + self.set_channels_sample_time(&[channel.channel()], sample_time); + + Self::start_adc(); + } + + /// Turns on ADC if it is not already turned on and starts continuous DMA transfer. + pub fn start(&mut self) -> Result<(), OverrunError> { + self.setup_adc(); + self.ring_buf.clear(); + + Ok(()) + } + + fn stop(&mut self, err: OverrunError) -> Result { + self.teardown_adc(); + Err(err) + } + + /// Stops DMA transfer. + /// It does not turn off ADC. + /// Calling `start` restarts continuous DMA transfer. + /// + /// [`start`]: #method.start + pub fn teardown_adc(&mut self) { + // Stop the DMA transfer + self.ring_buf.request_pause(); + + let r = T::regs(); + + // Stop ADC + r.cr2().modify(|reg| { + // Stop ADC + reg.set_swstart(false); + // Stop DMA + reg.set_dma(false); + }); + + r.cr1().modify(|w| { + // Disable interrupt for end of conversion + w.set_eocie(false); + // Disable interrupt for overrun + w.set_ovrie(false); + }); + + clear_interrupt_flags(r); + + compiler_fence(Ordering::SeqCst); + } + + fn setup_adc(&mut self) { + compiler_fence(Ordering::SeqCst); + + self.ring_buf.start(); + + let r = T::regs(); + + // Enable ADC + let was_on = Self::is_on(); + if !was_on { + r.cr2().modify(|reg| { + reg.set_adon(false); + reg.set_swstart(false); + }); + } + + // Clear all interrupts + r.sr().modify(|regs| { + regs.set_eoc(false); + regs.set_ovr(false); + regs.set_strt(false); + }); + + r.cr1().modify(|w| { + // Enable interrupt for end of conversion + w.set_eocie(true); + // Enable interrupt for overrun + w.set_ovrie(true); + // Scanning converisons of multiple channels + w.set_scan(true); + // Continuous conversion mode + w.set_discen(false); + }); + + r.cr2().modify(|w| { + // Enable DMA mode + w.set_dma(true); + // Enable continuous conversions + w.set_cont(true); + // DMA requests are issues as long as DMA=1 and data are converted. + w.set_dds(vals::Dds::CONTINUOUS); + // EOC flag is set at the end of each conversion. + w.set_eocs(vals::Eocs::EACHCONVERSION); + }); + + // Begin ADC conversions + T::regs().cr2().modify(|reg| { + reg.set_adon(true); + reg.set_swstart(true); + }); + + super::blocking_delay_us(3); + } + + /// Read bytes that are readily available in the ring buffer. + /// If no bytes are currently available in the buffer the call waits until the some + /// bytes are available (at least one byte and at most half the buffer size) + /// + /// Background receive is started if `start()` has not been previously called. + /// + /// Receive in the background is terminated if an error is returned. + /// It must then manually be started again by calling `start()` or by re-calling `read()`. + pub fn blocking_read(&mut self, buf: &mut [u16; N]) -> Result { + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr2().read().dma() { + self.start()?; + } + + // Clear overrun flag if set. + if r.sr().read().ovr() { + return self.stop(OverrunError); + } + + loop { + match self.ring_buf.read(buf) { + Ok((0, _)) => {} + Ok((len, _)) => { + return Ok(len); + } + Err(_) => { + return self.stop(OverrunError); + } + } + } + } + + /// Reads measurements from the DMA ring buffer. + /// + /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. + /// The length of the `measurements` array should be exactly half of the DMA buffer length. Because interrupts are only generated if half or full DMA transfer completes. + /// + /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `set_sample_sequence`. + /// There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled. + /// For example if 3 channels are sampled `measurements` contain: `[sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3..]`. + /// + /// If an error is returned, it indicates a DMA overrun, and the process must be restarted by calling `start` or `read` again. + /// + /// By default, the ADC fills the DMA buffer as quickly as possible. To control the sample rate, call `teardown_adc` after each readout, and then start the DMA again at the desired interval. + /// Note that even if using `teardown_adc` to control the sample rate, with each call to `read`, measurements equivalent to half the size of the DMA buffer are still collected. + /// + /// Example: + /// ```rust,ignore + /// const DMA_BUF_LEN: usize = 120; + /// let adc_dma_buf = [0u16; DMA_BUF_LEN]; + /// let mut adc: RingBufferedAdc = adc.into_ring_buffered(p.DMA2_CH0, adc_dma_buf); + /// + /// adc.set_sample_sequence(Sequence::One, &mut p.PA0, SampleTime::CYCLES112); + /// adc.set_sample_sequence(Sequence::Two, &mut p.PA1, SampleTime::CYCLES112); + /// adc.set_sample_sequence(Sequence::Three, &mut p.PA2, SampleTime::CYCLES112); + /// + /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; + /// loop { + /// match adc.read(&mut measurements).await { + /// Ok(_) => { + /// defmt::info!("adc1: {}", measurements); + /// // Only needed to manually control sample rate. + /// adc.teardown_adc(); + /// } + /// Err(e) => { + /// defmt::warn!("Error: {:?}", e); + /// // DMA overrun, next call to `read` restarts ADC. + /// } + /// } + /// + /// // Manually control sample rate. + /// Timer::after_millis(100).await; + /// } + /// ``` + /// + /// + /// [`set_sample_sequence`]: #method.set_sample_sequence + /// [`teardown_adc`]: #method.teardown_adc + /// [`start`]: #method.start + pub async fn read(&mut self, measurements: &mut [u16; N]) -> Result { + assert_eq!( + self.ring_buf.capacity() / 2, + N, + "Buffer size must be half the size of the ring buffer" + ); + + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr2().read().dma() { + self.start()?; + } + + // Clear overrun flag if set. + if r.sr().read().ovr() { + return self.stop(OverrunError); + } + match self.ring_buf.read_exact(measurements).await { + Ok(len) => Ok(len), + Err(_) => self.stop(OverrunError), + } + } +} + +impl Drop for RingBufferedAdc<'_, T> { + fn drop(&mut self) { + self.teardown_adc(); + rcc::disable::(); + } +} diff --git a/embassy/embassy-stm32/src/adc/v1.rs b/embassy/embassy-stm32/src/adc/v1.rs new file mode 100644 index 0000000..9bec2e1 --- /dev/null +++ b/embassy/embassy-stm32/src/adc/v1.rs @@ -0,0 +1,204 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::into_ref; +#[cfg(adc_l0)] +use stm32_metapac::adc::vals::Ckmode; + +use super::blocking_delay_us; +use crate::adc::{Adc, AdcChannel, Instance, Resolution, SampleTime}; +use crate::interrupt::typelevel::Interrupt; +use crate::peripherals::ADC1; +use crate::{interrupt, rcc, Peripheral}; + +pub const VDDA_CALIB_MV: u32 = 3300; +pub const VREF_INT: u32 = 1230; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + if T::regs().isr().read().eoc() { + T::regs().ier().modify(|w| w.set_eocie(false)); + } else { + return; + } + + T::state().waker.wake(); + } +} + +#[cfg(not(adc_l0))] +pub struct Vbat; + +#[cfg(not(adc_l0))] +impl AdcChannel for Vbat {} + +#[cfg(not(adc_l0))] +impl super::SealedAdcChannel for Vbat { + fn channel(&self) -> u8 { + 18 + } +} + +pub struct Vref; +impl AdcChannel for Vref {} +impl super::SealedAdcChannel for Vref { + fn channel(&self) -> u8 { + 17 + } +} + +pub struct Temperature; +impl AdcChannel for Temperature {} +impl super::SealedAdcChannel for Temperature { + fn channel(&self) -> u8 { + 16 + } +} + +impl<'d, T: Instance> Adc<'d, T> { + pub fn new( + adc: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + into_ref!(adc); + rcc::enable_and_reset::(); + + // Delay 1μs when using HSI14 as the ADC clock. + // + // Table 57. ADC characteristics + // tstab = 14 * 1/fadc + blocking_delay_us(1); + + // set default PCKL/2 on L0s because HSI is disabled in the default clock config + #[cfg(adc_l0)] + T::regs().cfgr2().modify(|reg| reg.set_ckmode(Ckmode::PCLK_DIV2)); + + // A.7.1 ADC calibration code example + T::regs().cfgr1().modify(|reg| reg.set_dmaen(false)); + T::regs().cr().modify(|reg| reg.set_adcal(true)); + + #[cfg(adc_l0)] + while !T::regs().isr().read().eocal() {} + + #[cfg(not(adc_l0))] + while T::regs().cr().read().adcal() {} + + // A.7.2 ADC enable sequence code example + if T::regs().isr().read().adrdy() { + T::regs().isr().modify(|reg| reg.set_adrdy(true)); + } + T::regs().cr().modify(|reg| reg.set_aden(true)); + while !T::regs().isr().read().adrdy() { + // ES0233, 2.4.3 ADEN bit cannot be set immediately after the ADC calibration + // Workaround: When the ADC calibration is complete (ADCAL = 0), keep setting the + // ADEN bit until the ADRDY flag goes high. + T::regs().cr().modify(|reg| reg.set_aden(true)); + } + + T::Interrupt::unpend(); + unsafe { + T::Interrupt::enable(); + } + + Self { + adc, + sample_time: SampleTime::from_bits(0), + } + } + + #[cfg(not(adc_l0))] + pub fn enable_vbat(&self) -> Vbat { + // SMP must be ≥ 56 ADC clock cycles when using HSI14. + // + // 6.3.20 Vbat monitoring characteristics + // ts_vbat ≥ 4μs + T::regs().ccr().modify(|reg| reg.set_vbaten(true)); + Vbat + } + + pub fn enable_vref(&self) -> Vref { + // Table 28. Embedded internal reference voltage + // tstart = 10μs + T::regs().ccr().modify(|reg| reg.set_vrefen(true)); + blocking_delay_us(10); + Vref + } + + pub fn enable_temperature(&self) -> Temperature { + // SMP must be ≥ 56 ADC clock cycles when using HSI14. + // + // 6.3.19 Temperature sensor characteristics + // tstart ≤ 10μs + // ts_temp ≥ 4μs + T::regs().ccr().modify(|reg| reg.set_tsen(true)); + blocking_delay_us(10); + Temperature + } + + pub fn set_sample_time(&mut self, sample_time: SampleTime) { + self.sample_time = sample_time; + } + + pub fn set_resolution(&mut self, resolution: Resolution) { + T::regs().cfgr1().modify(|reg| reg.set_res(resolution.into())); + } + + #[cfg(adc_l0)] + pub fn set_ckmode(&mut self, ckmode: Ckmode) { + // set ADC clock mode + T::regs().cfgr2().modify(|reg| reg.set_ckmode(ckmode)); + } + + pub async fn read(&mut self, channel: &mut impl AdcChannel) -> u16 { + let ch_num = channel.channel(); + channel.setup(); + + // A.7.5 Single conversion sequence code example - Software trigger + T::regs().chselr().write(|reg| reg.set_chselx(ch_num as usize, true)); + + self.convert().await + } + + async fn convert(&mut self) -> u16 { + T::regs().isr().modify(|reg| { + reg.set_eoc(true); + reg.set_eosmp(true); + }); + + T::regs().smpr().modify(|reg| reg.set_smp(self.sample_time.into())); + T::regs().ier().modify(|w| w.set_eocie(true)); + T::regs().cr().modify(|reg| reg.set_adstart(true)); + + poll_fn(|cx| { + T::state().waker.register(cx.waker()); + + if T::regs().isr().read().eoc() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + T::regs().dr().read().data() + } +} + +impl<'d, T: Instance> Drop for Adc<'d, T> { + fn drop(&mut self) { + // A.7.3 ADC disable code example + T::regs().cr().modify(|reg| reg.set_adstp(true)); + while T::regs().cr().read().adstp() {} + + T::regs().cr().modify(|reg| reg.set_addis(true)); + while T::regs().cr().read().aden() {} + + rcc::disable::(); + } +} diff --git a/embassy/embassy-stm32/src/adc/v2.rs b/embassy/embassy-stm32/src/adc/v2.rs new file mode 100644 index 0000000..842a5ee --- /dev/null +++ b/embassy/embassy-stm32/src/adc/v2.rs @@ -0,0 +1,214 @@ +use embassy_hal_internal::into_ref; + +use super::blocking_delay_us; +use crate::adc::{Adc, AdcChannel, Instance, Resolution, SampleTime}; +use crate::peripherals::ADC1; +use crate::time::Hertz; +use crate::{rcc, Peripheral}; + +mod ringbuffered_v2; +pub use ringbuffered_v2::{RingBufferedAdc, Sequence}; + +/// Default VREF voltage used for sample conversion to millivolts. +pub const VREF_DEFAULT_MV: u32 = 3300; +/// VREF voltage used for factory calibration of VREFINTCAL register. +pub const VREF_CALIB_MV: u32 = 3300; + +pub struct VrefInt; +impl AdcChannel for VrefInt {} +impl super::SealedAdcChannel for VrefInt { + fn channel(&self) -> u8 { + 17 + } +} + +impl VrefInt { + /// Time needed for internal voltage reference to stabilize + pub fn start_time_us() -> u32 { + 10 + } +} + +pub struct Temperature; +impl AdcChannel for Temperature {} +impl super::SealedAdcChannel for Temperature { + fn channel(&self) -> u8 { + cfg_if::cfg_if! { + if #[cfg(any(stm32f2, stm32f40x, stm32f41x))] { + 16 + } else { + 18 + } + } + } +} + +impl Temperature { + /// Time needed for temperature sensor readings to stabilize + pub fn start_time_us() -> u32 { + 10 + } +} + +pub struct Vbat; +impl AdcChannel for Vbat {} +impl super::SealedAdcChannel for Vbat { + fn channel(&self) -> u8 { + 18 + } +} + +enum Prescaler { + Div2, + Div4, + Div6, + Div8, +} + +impl Prescaler { + fn from_pclk2(freq: Hertz) -> Self { + // Datasheet for F2 specifies min frequency 0.6 MHz, and max 30 MHz (with VDDA 2.4-3.6V). + #[cfg(stm32f2)] + const MAX_FREQUENCY: Hertz = Hertz(30_000_000); + // Datasheet for both F4 and F7 specifies min frequency 0.6 MHz, typ freq. 30 MHz and max 36 MHz. + #[cfg(not(stm32f2))] + const MAX_FREQUENCY: Hertz = Hertz(36_000_000); + let raw_div = freq.0 / MAX_FREQUENCY.0; + match raw_div { + 0..=1 => Self::Div2, + 2..=3 => Self::Div4, + 4..=5 => Self::Div6, + 6..=7 => Self::Div8, + _ => panic!("Selected PCLK2 frequency is too high for ADC with largest possible prescaler."), + } + } + + fn adcpre(&self) -> crate::pac::adccommon::vals::Adcpre { + match self { + Prescaler::Div2 => crate::pac::adccommon::vals::Adcpre::DIV2, + Prescaler::Div4 => crate::pac::adccommon::vals::Adcpre::DIV4, + Prescaler::Div6 => crate::pac::adccommon::vals::Adcpre::DIV6, + Prescaler::Div8 => crate::pac::adccommon::vals::Adcpre::DIV8, + } + } +} + +impl<'d, T> Adc<'d, T> +where + T: Instance, +{ + pub fn new(adc: impl Peripheral

+ 'd) -> Self { + into_ref!(adc); + rcc::enable_and_reset::(); + + let presc = Prescaler::from_pclk2(T::frequency()); + T::common_regs().ccr().modify(|w| w.set_adcpre(presc.adcpre())); + T::regs().cr2().modify(|reg| { + reg.set_adon(true); + }); + + blocking_delay_us(3); + + Self { + adc, + sample_time: SampleTime::from_bits(0), + } + } + + pub fn set_sample_time(&mut self, sample_time: SampleTime) { + self.sample_time = sample_time; + } + + pub fn set_resolution(&mut self, resolution: Resolution) { + T::regs().cr1().modify(|reg| reg.set_res(resolution.into())); + } + + /// Enables internal voltage reference and returns [VrefInt], which can be used in + /// [Adc::read_internal()] to perform conversion. + pub fn enable_vrefint(&self) -> VrefInt { + T::common_regs().ccr().modify(|reg| { + reg.set_tsvrefe(true); + }); + + VrefInt {} + } + + /// Enables internal temperature sensor and returns [Temperature], which can be used in + /// [Adc::read_internal()] to perform conversion. + /// + /// On STM32F42 and STM32F43 this can not be used together with [Vbat]. If both are enabled, + /// temperature sensor will return vbat value. + pub fn enable_temperature(&self) -> Temperature { + T::common_regs().ccr().modify(|reg| { + reg.set_tsvrefe(true); + }); + + Temperature {} + } + + /// Enables vbat input and returns [Vbat], which can be used in + /// [Adc::read_internal()] to perform conversion. + pub fn enable_vbat(&self) -> Vbat { + T::common_regs().ccr().modify(|reg| { + reg.set_vbate(true); + }); + + Vbat {} + } + + /// Perform a single conversion. + fn convert(&mut self) -> u16 { + // clear end of conversion flag + T::regs().sr().modify(|reg| { + reg.set_eoc(false); + }); + + // Start conversion + T::regs().cr2().modify(|reg| { + reg.set_swstart(true); + }); + + while T::regs().sr().read().strt() == false { + // spin //wait for actual start + } + while T::regs().sr().read().eoc() == false { + // spin //wait for finish + } + + T::regs().dr().read().0 as u16 + } + + pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { + channel.setup(); + + // Configure ADC + let channel = channel.channel(); + + // Select channel + T::regs().sqr3().write(|reg| reg.set_sq(0, channel)); + + // Configure channel + Self::set_channel_sample_time(channel, self.sample_time); + + self.convert() + } + + 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)); + } else { + T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); + } + } +} + +impl<'d, T: Instance> Drop for Adc<'d, T> { + fn drop(&mut self) { + T::regs().cr2().modify(|reg| { + reg.set_adon(false); + }); + + rcc::disable::(); + } +} diff --git a/embassy/embassy-stm32/src/adc/v3.rs b/embassy/embassy-stm32/src/adc/v3.rs new file mode 100644 index 0000000..9441e42 --- /dev/null +++ b/embassy/embassy-stm32/src/adc/v3.rs @@ -0,0 +1,499 @@ +use cfg_if::cfg_if; +use embassy_hal_internal::into_ref; +use pac::adc::vals::Dmacfg; + +use super::{ + blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, +}; +use crate::dma::Transfer; +use crate::{pac, rcc, Peripheral}; + +/// Default VREF voltage used for sample conversion to millivolts. +pub const VREF_DEFAULT_MV: u32 = 3300; +/// VREF voltage used for factory calibration of VREFINTCAL register. +pub const VREF_CALIB_MV: u32 = 3000; + +pub struct VrefInt; +impl AdcChannel for VrefInt {} +impl SealedAdcChannel for VrefInt { + fn channel(&self) -> u8 { + cfg_if! { + if #[cfg(adc_g0)] { + let val = 13; + } else if #[cfg(adc_h5)] { + let val = 17; + } else if #[cfg(adc_u0)] { + let val = 12; + } else { + let val = 0; + } + } + val + } +} + +pub struct Temperature; +impl AdcChannel for Temperature {} +impl SealedAdcChannel for Temperature { + fn channel(&self) -> u8 { + cfg_if! { + if #[cfg(adc_g0)] { + let val = 12; + } else if #[cfg(adc_h5)] { + let val = 16; + } else if #[cfg(adc_u0)] { + let val = 11; + } else { + let val = 17; + } + } + val + } +} + +pub struct Vbat; +impl AdcChannel for Vbat {} +impl SealedAdcChannel for Vbat { + fn channel(&self) -> u8 { + cfg_if! { + if #[cfg(adc_g0)] { + let val = 14; + } else if #[cfg(adc_h5)] { + let val = 2; + } else if #[cfg(adc_h5)] { + let val = 13; + } else { + let val = 18; + } + } + val + } +} + +cfg_if! { + if #[cfg(adc_h5)] { + pub struct VddCore; + impl AdcChannel for VddCore {} + impl super::SealedAdcChannel for VddCore { + fn channel(&self) -> u8 { + 6 + } + } + } +} + +cfg_if! { + if #[cfg(adc_u0)] { + pub struct DacOut; + impl AdcChannel for DacOut {} + impl super::SealedAdcChannel for DacOut { + fn channel(&self) -> u8 { + 19 + } + } + } +} + +impl<'d, T: Instance> Adc<'d, T> { + pub fn new(adc: impl Peripheral

+ 'd) -> Self { + into_ref!(adc); + rcc::enable_and_reset::(); + T::regs().cr().modify(|reg| { + #[cfg(not(any(adc_g0, adc_u0)))] + reg.set_deeppwd(false); + reg.set_advregen(true); + }); + + // If this is false then each ADC_CHSELR bit enables an input channel. + #[cfg(any(adc_g0, adc_u0))] + T::regs().cfgr1().modify(|reg| { + reg.set_chselrmod(false); + }); + + blocking_delay_us(20); + + T::regs().cr().modify(|reg| { + reg.set_adcal(true); + }); + + while T::regs().cr().read().adcal() { + // spin + } + + blocking_delay_us(1); + + Self { + adc, + sample_time: SampleTime::from_bits(0), + } + } + + // Enable ADC only when it is not already running. + fn enable(&mut self) { + // Make sure bits are off + while T::regs().cr().read().addis() { + // spin + } + + if !T::regs().cr().read().aden() { + // Enable ADC + T::regs().isr().modify(|reg| { + reg.set_adrdy(true); + }); + T::regs().cr().modify(|reg| { + reg.set_aden(true); + }); + + while !T::regs().isr().read().adrdy() { + // spin + } + } + } + + pub fn enable_vrefint(&self) -> VrefInt { + #[cfg(not(any(adc_g0, adc_u0)))] + T::common_regs().ccr().modify(|reg| { + reg.set_vrefen(true); + }); + #[cfg(any(adc_g0, adc_u0))] + T::regs().ccr().modify(|reg| { + reg.set_vrefen(true); + }); + + // "Table 24. Embedded internal voltage reference" states that it takes a maximum of 12 us + // to stabilize the internal voltage reference. + blocking_delay_us(15); + + VrefInt {} + } + + pub fn enable_temperature(&self) -> Temperature { + cfg_if! { + if #[cfg(any(adc_g0, adc_u0))] { + T::regs().ccr().modify(|reg| { + reg.set_tsen(true); + }); + } else if #[cfg(adc_h5)] { + T::common_regs().ccr().modify(|reg| { + reg.set_tsen(true); + }); + } else { + T::common_regs().ccr().modify(|reg| { + reg.set_ch17sel(true); + }); + } + } + + Temperature {} + } + + pub fn enable_vbat(&self) -> Vbat { + cfg_if! { + if #[cfg(any(adc_g0, adc_u0))] { + T::regs().ccr().modify(|reg| { + reg.set_vbaten(true); + }); + } else if #[cfg(adc_h5)] { + T::common_regs().ccr().modify(|reg| { + reg.set_vbaten(true); + }); + } else { + T::common_regs().ccr().modify(|reg| { + reg.set_ch18sel(true); + }); + } + } + + Vbat {} + } + + /// Set the ADC sample time. + pub fn set_sample_time(&mut self, sample_time: SampleTime) { + self.sample_time = sample_time; + } + + /// Get the ADC sample time. + pub fn sample_time(&self) -> SampleTime { + self.sample_time + } + + /// Set the ADC resolution. + pub fn set_resolution(&mut self, resolution: Resolution) { + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().cfgr().modify(|reg| reg.set_res(resolution.into())); + #[cfg(any(adc_g0, adc_u0))] + T::regs().cfgr1().modify(|reg| reg.set_res(resolution.into())); + } + + /* + /// Convert a raw sample from the `Temperature` to deg C + pub fn to_degrees_centigrade(sample: u16) -> f32 { + (130.0 - 30.0) / (VtempCal130::get().read() as f32 - VtempCal30::get().read() as f32) + * (sample as f32 - VtempCal30::get().read() as f32) + + 30.0 + } + */ + + /// Perform a single conversion. + fn convert(&mut self) -> u16 { + T::regs().isr().modify(|reg| { + reg.set_eos(true); + reg.set_eoc(true); + }); + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + while !T::regs().isr().read().eos() { + // spin + } + + T::regs().dr().read().0 as u16 + } + + /// Read an ADC channel. + pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { + self.read_channel(channel) + } + + /// Read one or multiple ADC channels using DMA. + /// + /// `sequence` iterator and `readings` must have the same length. + /// + /// Example + /// ```rust,ignore + /// use embassy_stm32::adc::{Adc, AdcChannel} + /// + /// let mut adc = Adc::new(p.ADC1); + /// let mut adc_pin0 = p.PA0.degrade_adc(); + /// let mut adc_pin1 = p.PA1.degrade_adc(); + /// let mut measurements = [0u16; 2]; + /// + /// adc.read_async( + /// p.DMA1_CH2, + /// [ + /// (&mut *adc_pin0, SampleTime::CYCLES160_5), + /// (&mut *adc_pin1, SampleTime::CYCLES160_5), + /// ] + /// .into_iter(), + /// &mut measurements, + /// ) + /// .await; + /// defmt::info!("measurements: {}", measurements); + /// ``` + pub async fn read( + &mut self, + rx_dma: &mut impl RxDma, + sequence: impl ExactSizeIterator, SampleTime)>, + readings: &mut [u16], + ) { + assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); + assert!( + sequence.len() == readings.len(), + "Sequence length must be equal to readings length" + ); + assert!( + sequence.len() <= 16, + "Asynchronous read sequence cannot be more than 16 in length" + ); + + // Ensure no conversions are ongoing and ADC is enabled. + Self::cancel_conversions(); + self.enable(); + + // Set sequence length + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().sqr1().modify(|w| { + w.set_l(sequence.len() as u8 - 1); + }); + + #[cfg(any(adc_g0, adc_u0))] + let mut channel_mask = 0; + + // Configure channels and ranks + for (_i, (channel, sample_time)) in sequence.enumerate() { + Self::configure_channel(channel, sample_time); + + // Each channel is sampled according to sequence + #[cfg(not(any(adc_g0, adc_u0)))] + match _i { + 0..=3 => { + T::regs().sqr1().modify(|w| { + w.set_sq(_i, channel.channel()); + }); + } + 4..=8 => { + T::regs().sqr2().modify(|w| { + w.set_sq(_i - 4, channel.channel()); + }); + } + 9..=13 => { + T::regs().sqr3().modify(|w| { + w.set_sq(_i - 9, channel.channel()); + }); + } + 14..=15 => { + T::regs().sqr4().modify(|w| { + w.set_sq(_i - 14, channel.channel()); + }); + } + _ => unreachable!(), + } + + #[cfg(any(adc_g0, adc_u0))] + { + channel_mask |= 1 << channel.channel(); + } + } + + // On G0 and U0 enabled channels are sampled from 0 to last channel. + // It is possible to add up to 8 sequences if CHSELRMOD = 1. + // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used. + #[cfg(any(adc_g0, adc_u0))] + T::regs().chselr().modify(|reg| { + reg.set_chsel(channel_mask); + }); + + // Set continuous mode with oneshot dma. + // Clear overrun flag before starting transfer. + T::regs().isr().modify(|reg| { + reg.set_ovr(true); + }); + + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().cfgr().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + reg.set_dmacfg(Dmacfg::ONESHOT); + reg.set_dmaen(true); + }); + #[cfg(any(adc_g0, adc_u0))] + T::regs().cfgr1().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + reg.set_dmacfg(Dmacfg::ONESHOT); + reg.set_dmaen(true); + }); + + let request = rx_dma.request(); + let transfer = unsafe { + Transfer::new_read( + rx_dma, + request, + T::regs().dr().as_ptr() as *mut u16, + readings, + Default::default(), + ) + }; + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + // Wait for conversion sequence to finish. + transfer.await; + + // Ensure conversions are finished. + Self::cancel_conversions(); + + // Reset configuration. + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().cfgr().modify(|reg| { + reg.set_cont(false); + }); + #[cfg(any(adc_g0, adc_u0))] + T::regs().cfgr1().modify(|reg| { + reg.set_cont(false); + }); + } + + fn configure_channel(channel: &mut impl AdcChannel, sample_time: SampleTime) { + // RM0492, RM0481, etc. + // "This option bit must be set to 1 when ADCx_INP0 or ADCx_INN1 channel is selected." + #[cfg(adc_h5)] + if channel.channel() == 0 { + T::regs().or().modify(|reg| reg.set_op0(true)); + } + + // Configure channel + Self::set_channel_sample_time(channel.channel(), sample_time); + } + + fn read_channel(&mut self, channel: &mut impl AdcChannel) -> u16 { + self.enable(); + Self::configure_channel(channel, self.sample_time); + + // Select channel + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().sqr1().write(|reg| reg.set_sq(0, channel.channel())); + #[cfg(any(adc_g0, adc_u0))] + T::regs().chselr().write(|reg| reg.set_chsel(1 << channel.channel())); + + // Some models are affected by an erratum: + // If we perform conversions slower than 1 kHz, the first read ADC value can be + // corrupted, so we discard it and measure again. + // + // STM32L471xx: Section 2.7.3 + // STM32G4: Section 2.7.3 + #[cfg(any(rcc_l4, rcc_g4))] + let _ = self.convert(); + let val = self.convert(); + + T::regs().cr().modify(|reg| reg.set_addis(true)); + + // RM0492, RM0481, etc. + // "This option bit must be set to 1 when ADCx_INP0 or ADCx_INN1 channel is selected." + #[cfg(adc_h5)] + if channel.channel() == 0 { + T::regs().or().modify(|reg| reg.set_op0(false)); + } + + val + } + + #[cfg(any(adc_g0, adc_u0))] + pub fn set_oversampling_shift(&mut self, shift: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovss(shift)); + } + + #[cfg(any(adc_g0, adc_u0))] + pub fn set_oversampling_ratio(&mut self, ratio: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovsr(ratio)); + } + + #[cfg(any(adc_g0, adc_u0))] + pub fn oversampling_enable(&mut self, enable: bool) { + T::regs().cfgr2().modify(|reg| reg.set_ovse(enable)); + } + + fn set_channel_sample_time(_ch: u8, sample_time: SampleTime) { + cfg_if! { + if #[cfg(any(adc_g0, adc_u0))] { + // On G0 and U6 all channels use the same sampling time. + T::regs().smpr().modify(|reg| reg.set_smp1(sample_time.into())); + } else if #[cfg(adc_h5)] { + match _ch { + 0..=9 => T::regs().smpr1().modify(|w| w.set_smp(_ch as usize % 10, sample_time.into())), + _ => T::regs().smpr2().modify(|w| w.set_smp(_ch as usize % 10, sample_time.into())), + } + } else { + let sample_time = sample_time.into(); + T::regs() + .smpr(_ch as usize / 10) + .modify(|reg| reg.set_smp(_ch as usize % 10, sample_time)); + } + } + } + + fn cancel_conversions() { + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(true); + }); + while T::regs().cr().read().adstart() {} + } + } +} diff --git a/embassy/embassy-stm32/src/adc/v4.rs b/embassy/embassy-stm32/src/adc/v4.rs new file mode 100644 index 0000000..63b5b58 --- /dev/null +++ b/embassy/embassy-stm32/src/adc/v4.rs @@ -0,0 +1,486 @@ +#[allow(unused)] +use pac::adc::vals::{Adcaldif, Adstp, Boost, Difsel, Dmngt, Exten, Pcsel}; +use pac::adccommon::vals::Presc; + +use super::{ + blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, +}; +use crate::dma::Transfer; +use crate::time::Hertz; +use crate::{pac, rcc, Peripheral}; + +/// Default VREF voltage used for sample conversion to millivolts. +pub const VREF_DEFAULT_MV: u32 = 3300; +/// VREF voltage used for factory calibration of VREFINTCAL register. +pub const VREF_CALIB_MV: u32 = 3300; + +/// Max single ADC operation clock frequency +#[cfg(stm32g4)] +const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(60); +#[cfg(stm32h7)] +const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(50); + +#[cfg(stm32g4)] +const VREF_CHANNEL: u8 = 18; +#[cfg(stm32g4)] +const TEMP_CHANNEL: u8 = 16; + +#[cfg(stm32h7)] +const VREF_CHANNEL: u8 = 19; +#[cfg(stm32h7)] +const TEMP_CHANNEL: u8 = 18; + +// TODO this should be 14 for H7a/b/35 +const VBAT_CHANNEL: u8 = 17; + +// NOTE: Vrefint/Temperature/Vbat are not available on all ADCs, this currently cannot be modeled with stm32-data, so these are available from the software on all ADCs +/// Internal voltage reference channel. +pub struct VrefInt; +impl AdcChannel for VrefInt {} +impl SealedAdcChannel for VrefInt { + fn channel(&self) -> u8 { + VREF_CHANNEL + } +} + +/// Internal temperature channel. +pub struct Temperature; +impl AdcChannel for Temperature {} +impl SealedAdcChannel for Temperature { + fn channel(&self) -> u8 { + TEMP_CHANNEL + } +} + +/// Internal battery voltage channel. +pub struct Vbat; +impl AdcChannel for Vbat {} +impl SealedAdcChannel for Vbat { + fn channel(&self) -> u8 { + VBAT_CHANNEL + } +} + +// NOTE (unused): The prescaler enum closely copies the hardware capabilities, +// but high prescaling doesn't make a lot of sense in the current implementation and is ommited. +#[allow(unused)] +enum Prescaler { + NotDivided, + DividedBy2, + DividedBy4, + DividedBy6, + DividedBy8, + DividedBy10, + DividedBy12, + DividedBy16, + DividedBy32, + DividedBy64, + DividedBy128, + DividedBy256, +} + +impl Prescaler { + fn from_ker_ck(frequency: Hertz) -> Self { + let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; + match raw_prescaler { + 0 => Self::NotDivided, + 1 => Self::DividedBy2, + 2..=3 => Self::DividedBy4, + 4..=5 => Self::DividedBy6, + 6..=7 => Self::DividedBy8, + 8..=9 => Self::DividedBy10, + 10..=11 => Self::DividedBy12, + _ => unimplemented!(), + } + } + + fn divisor(&self) -> u32 { + match self { + Prescaler::NotDivided => 1, + Prescaler::DividedBy2 => 2, + Prescaler::DividedBy4 => 4, + Prescaler::DividedBy6 => 6, + Prescaler::DividedBy8 => 8, + Prescaler::DividedBy10 => 10, + Prescaler::DividedBy12 => 12, + Prescaler::DividedBy16 => 16, + Prescaler::DividedBy32 => 32, + Prescaler::DividedBy64 => 64, + Prescaler::DividedBy128 => 128, + Prescaler::DividedBy256 => 256, + } + } + + fn presc(&self) -> Presc { + match self { + Prescaler::NotDivided => Presc::DIV1, + Prescaler::DividedBy2 => Presc::DIV2, + Prescaler::DividedBy4 => Presc::DIV4, + Prescaler::DividedBy6 => Presc::DIV6, + Prescaler::DividedBy8 => Presc::DIV8, + Prescaler::DividedBy10 => Presc::DIV10, + Prescaler::DividedBy12 => Presc::DIV12, + Prescaler::DividedBy16 => Presc::DIV16, + Prescaler::DividedBy32 => Presc::DIV32, + Prescaler::DividedBy64 => Presc::DIV64, + Prescaler::DividedBy128 => Presc::DIV128, + Prescaler::DividedBy256 => Presc::DIV256, + } + } +} + +/// Number of samples used for averaging. +pub enum Averaging { + Disabled, + Samples2, + Samples4, + Samples8, + Samples16, + Samples32, + Samples64, + Samples128, + Samples256, + Samples512, + Samples1024, +} + +impl<'d, T: Instance> Adc<'d, T> { + /// Create a new ADC driver. + pub fn new(adc: impl Peripheral

+ 'd) -> Self { + embassy_hal_internal::into_ref!(adc); + rcc::enable_and_reset::(); + + let prescaler = Prescaler::from_ker_ck(T::frequency()); + + T::common_regs().ccr().modify(|w| w.set_presc(prescaler.presc())); + + let frequency = Hertz(T::frequency().0 / prescaler.divisor()); + info!("ADC frequency set to {} Hz", frequency.0); + + if frequency > MAX_ADC_CLK_FREQ { + panic!("Maximal allowed frequency for the ADC is {} MHz and it varies with different packages, refer to ST docs for more information.", MAX_ADC_CLK_FREQ.0 / 1_000_000 ); + } + + #[cfg(stm32h7)] + { + let boost = if frequency < Hertz::khz(6_250) { + Boost::LT6_25 + } else if frequency < Hertz::khz(12_500) { + Boost::LT12_5 + } else if frequency < Hertz::mhz(25) { + Boost::LT25 + } else { + Boost::LT50 + }; + T::regs().cr().modify(|w| w.set_boost(boost)); + } + let mut s = Self { + adc, + sample_time: SampleTime::from_bits(0), + }; + s.power_up(); + s.configure_differential_inputs(); + + s.calibrate(); + blocking_delay_us(1); + + s.enable(); + s.configure(); + + s + } + + fn power_up(&mut self) { + T::regs().cr().modify(|reg| { + reg.set_deeppwd(false); + reg.set_advregen(true); + }); + + blocking_delay_us(10); + } + + fn configure_differential_inputs(&mut self) { + T::regs().difsel().modify(|w| { + for n in 0..20 { + w.set_difsel(n, Difsel::SINGLEENDED); + } + }); + } + + fn calibrate(&mut self) { + T::regs().cr().modify(|w| { + w.set_adcaldif(Adcaldif::SINGLEENDED); + w.set_adcallin(true); + }); + + T::regs().cr().modify(|w| w.set_adcal(true)); + + while T::regs().cr().read().adcal() {} + } + + fn enable(&mut self) { + T::regs().isr().write(|w| w.set_adrdy(true)); + T::regs().cr().modify(|w| w.set_aden(true)); + while !T::regs().isr().read().adrdy() {} + T::regs().isr().write(|w| w.set_adrdy(true)); + } + + fn configure(&mut self) { + // single conversion mode, software trigger + T::regs().cfgr().modify(|w| { + w.set_cont(false); + w.set_exten(Exten::DISABLED); + }); + } + + /// Enable reading the voltage reference internal channel. + pub fn enable_vrefint(&self) -> VrefInt { + T::common_regs().ccr().modify(|reg| { + reg.set_vrefen(true); + }); + + VrefInt {} + } + + /// Enable reading the temperature internal channel. + pub fn enable_temperature(&self) -> Temperature { + T::common_regs().ccr().modify(|reg| { + reg.set_vsenseen(true); + }); + + Temperature {} + } + + /// Enable reading the vbat internal channel. + pub fn enable_vbat(&self) -> Vbat { + T::common_regs().ccr().modify(|reg| { + reg.set_vbaten(true); + }); + + Vbat {} + } + + /// Set the ADC sample time. + pub fn set_sample_time(&mut self, sample_time: SampleTime) { + self.sample_time = sample_time; + } + + /// Get the ADC sample time. + pub fn sample_time(&self) -> SampleTime { + self.sample_time + } + + /// Set the ADC resolution. + pub fn set_resolution(&mut self, resolution: Resolution) { + T::regs().cfgr().modify(|reg| reg.set_res(resolution.into())); + } + + /// Set hardware averaging. + pub fn set_averaging(&mut self, averaging: Averaging) { + let (enable, samples, right_shift) = match averaging { + Averaging::Disabled => (false, 0, 0), + Averaging::Samples2 => (true, 1, 1), + Averaging::Samples4 => (true, 3, 2), + Averaging::Samples8 => (true, 7, 3), + Averaging::Samples16 => (true, 15, 4), + Averaging::Samples32 => (true, 31, 5), + Averaging::Samples64 => (true, 63, 6), + Averaging::Samples128 => (true, 127, 7), + Averaging::Samples256 => (true, 255, 8), + Averaging::Samples512 => (true, 511, 9), + Averaging::Samples1024 => (true, 1023, 10), + }; + + T::regs().cfgr2().modify(|reg| { + reg.set_rovse(enable); + reg.set_osvr(samples); + reg.set_ovss(right_shift); + }) + } + + /// Perform a single conversion. + fn convert(&mut self) -> u16 { + T::regs().isr().modify(|reg| { + reg.set_eos(true); + reg.set_eoc(true); + }); + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + while !T::regs().isr().read().eos() { + // spin + } + + T::regs().dr().read().0 as u16 + } + + /// Read an ADC channel. + pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { + self.read_channel(channel) + } + + /// Read one or multiple ADC channels using DMA. + /// + /// `sequence` iterator and `readings` must have the same length. + /// + /// Example + /// ```rust,ignore + /// use embassy_stm32::adc::{Adc, AdcChannel} + /// + /// let mut adc = Adc::new(p.ADC1); + /// let mut adc_pin0 = p.PA0.degrade_adc(); + /// let mut adc_pin2 = p.PA2.degrade_adc(); + /// let mut measurements = [0u16; 2]; + /// + /// adc.read_async( + /// p.DMA2_CH0, + /// [ + /// (&mut *adc_pin0, SampleTime::CYCLES112), + /// (&mut *adc_pin2, SampleTime::CYCLES112), + /// ] + /// .into_iter(), + /// &mut measurements, + /// ) + /// .await; + /// defmt::info!("measurements: {}", measurements); + /// ``` + pub async fn read( + &mut self, + rx_dma: &mut impl RxDma, + sequence: impl ExactSizeIterator, SampleTime)>, + readings: &mut [u16], + ) { + assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); + assert!( + sequence.len() == readings.len(), + "Sequence length must be equal to readings length" + ); + assert!( + sequence.len() <= 16, + "Asynchronous read sequence cannot be more than 16 in length" + ); + + // Ensure no conversions are ongoing + Self::cancel_conversions(); + + // Set sequence length + T::regs().sqr1().modify(|w| { + w.set_l(sequence.len() as u8 - 1); + }); + + // Configure channels and ranks + for (i, (channel, sample_time)) in sequence.enumerate() { + Self::configure_channel(channel, sample_time); + match i { + 0..=3 => { + T::regs().sqr1().modify(|w| { + w.set_sq(i, channel.channel()); + }); + } + 4..=8 => { + T::regs().sqr2().modify(|w| { + w.set_sq(i - 4, channel.channel()); + }); + } + 9..=13 => { + T::regs().sqr3().modify(|w| { + w.set_sq(i - 9, channel.channel()); + }); + } + 14..=15 => { + T::regs().sqr4().modify(|w| { + w.set_sq(i - 14, channel.channel()); + }); + } + _ => unreachable!(), + } + } + + // Set continuous mode with oneshot dma. + // Clear overrun flag before starting transfer. + + T::regs().isr().modify(|reg| { + reg.set_ovr(true); + }); + T::regs().cfgr().modify(|reg| { + reg.set_cont(true); + reg.set_dmngt(Dmngt::DMA_ONESHOT); + }); + + let request = rx_dma.request(); + let transfer = unsafe { + Transfer::new_read( + rx_dma, + request, + T::regs().dr().as_ptr() as *mut u16, + readings, + Default::default(), + ) + }; + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + // Wait for conversion sequence to finish. + transfer.await; + + // Ensure conversions are finished. + Self::cancel_conversions(); + + // Reset configuration. + T::regs().cfgr().modify(|reg| { + reg.set_cont(false); + reg.set_dmngt(Dmngt::from_bits(0)); + }); + } + + fn configure_channel(channel: &mut impl AdcChannel, sample_time: SampleTime) { + channel.setup(); + + let channel = channel.channel(); + + Self::set_channel_sample_time(channel, sample_time); + + #[cfg(stm32h7)] + { + T::regs().cfgr2().modify(|w| w.set_lshift(0)); + T::regs() + .pcsel() + .modify(|w| w.set_pcsel(channel as _, Pcsel::PRESELECTED)); + } + } + + fn read_channel(&mut self, channel: &mut impl AdcChannel) -> u16 { + Self::configure_channel(channel, self.sample_time); + + T::regs().sqr1().modify(|reg| { + reg.set_sq(0, channel.channel()); + reg.set_l(0); + }); + + self.convert() + } + + 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)); + } else { + T::regs().smpr(1).modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); + } + } + + fn cancel_conversions() { + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(Adstp::STOP); + }); + while T::regs().cr().read().adstart() {} + } + } +} diff --git a/embassy/embassy-stm32/src/can/bxcan/filter.rs b/embassy/embassy-stm32/src/can/bxcan/filter.rs new file mode 100644 index 0000000..167c6c5 --- /dev/null +++ b/embassy/embassy-stm32/src/can/bxcan/filter.rs @@ -0,0 +1,479 @@ +//! Filter bank API. + +use core::marker::PhantomData; + +use super::{ExtendedId, Fifo, Id, StandardId}; + +const F32_RTR: u32 = 0b010; // set the RTR bit to match remote frames +const F32_IDE: u32 = 0b100; // set the IDE bit to match extended identifiers +const F16_RTR: u16 = 0b10000; +const F16_IDE: u16 = 0b01000; + +/// A 16-bit filter list entry. +/// +/// This can match data and remote frames using standard IDs. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ListEntry16(u16); + +/// A 32-bit filter list entry. +/// +/// This can match data and remote frames using extended or standard IDs. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ListEntry32(u32); + +/// A 16-bit identifier mask. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Mask16 { + id: u16, + mask: u16, +} + +/// A 32-bit identifier mask. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Mask32 { + id: u32, + mask: u32, +} + +impl ListEntry16 { + /// Creates a filter list entry that accepts data frames with the given standard ID. + /// + /// This entry will *not* accept remote frames with the same ID. + pub fn data_frames_with_id(id: StandardId) -> Self { + Self(id.as_raw() << 5) + } + + /// Creates a filter list entry that accepts remote frames with the given standard ID. + pub fn remote_frames_with_id(id: StandardId) -> Self { + Self(id.as_raw() << 5 | F16_RTR) + } +} + +impl ListEntry32 { + /// Creates a filter list entry that accepts data frames with the given ID. + /// + /// This entry will *not* accept remote frames with the same ID. + /// + /// The filter will only accept *either* standard *or* extended frames, depending on `id`. + pub fn data_frames_with_id(id: impl Into) -> Self { + match id.into() { + Id::Standard(id) => Self(u32::from(id.as_raw()) << 21), + Id::Extended(id) => Self(id.as_raw() << 3 | F32_IDE), + } + } + + /// Creates a filter list entry that accepts remote frames with the given ID. + pub fn remote_frames_with_id(id: impl Into) -> Self { + match id.into() { + Id::Standard(id) => Self(u32::from(id.as_raw()) << 21 | F32_RTR), + Id::Extended(id) => Self(id.as_raw() << 3 | F32_IDE | F32_RTR), + } + } +} + +impl Mask16 { + /// Creates a 16-bit identifier mask that accepts all frames. + /// + /// This will accept both standard and extended data and remote frames with any ID. + pub fn accept_all() -> Self { + Self { id: 0, mask: 0 } + } + + /// Creates a 16-bit identifier mask that accepts all frames with the given standard + /// ID and mask combination. + /// + /// Filter logic: `frame_accepted = (incoming_id & mask) == (id & mask)` + /// + /// A mask of all all ones (`0x7FF`) matches an exact ID, a mask of 0 matches all IDs. + /// + /// Both data and remote frames with `id` will be accepted. Any extended frames will be + /// rejected. + pub fn frames_with_std_id(id: StandardId, mask: StandardId) -> Self { + Self { + id: id.as_raw() << 5, + mask: mask.as_raw() << 5 | F16_IDE, // also require IDE = 0 + } + } + + /// Make the filter accept data frames only. + pub fn data_frames_only(&mut self) -> &mut Self { + self.id &= !F16_RTR; // RTR = 0 + self.mask |= F16_RTR; + self + } + + /// Make the filter accept remote frames only. + pub fn remote_frames_only(&mut self) -> &mut Self { + self.id |= F16_RTR; // RTR = 1 + self.mask |= F16_RTR; + self + } +} + +impl Mask32 { + /// Creates a 32-bit identifier mask that accepts all frames. + /// + /// This will accept both standard and extended data and remote frames with any ID. + pub fn accept_all() -> Self { + Self { id: 0, mask: 0 } + } + + /// Creates a 32-bit identifier mask that accepts all frames with the given extended + /// ID and mask combination. + /// + /// Filter logic: `frame_accepted = (incoming_id & mask) == (id & mask)` + /// + /// A mask of all all ones (`0x1FFF_FFFF`) matches an exact ID, a mask of 0 matches all IDs. + /// + /// Both data and remote frames with `id` will be accepted. Standard frames will be rejected. + pub fn frames_with_ext_id(id: ExtendedId, mask: ExtendedId) -> Self { + Self { + id: id.as_raw() << 3 | F32_IDE, + mask: mask.as_raw() << 3 | F32_IDE, // also require IDE = 1 + } + } + + /// Creates a 32-bit identifier mask that accepts all frames with the given standard + /// ID and mask combination. + /// + /// Filter logic: `frame_accepted = (incoming_id & mask) == (id & mask)` + /// + /// A mask of all all ones (`0x7FF`) matches the exact ID, a mask of 0 matches all IDs. + /// + /// Both data and remote frames with `id` will be accepted. Extended frames will be rejected. + pub fn frames_with_std_id(id: StandardId, mask: StandardId) -> Self { + Self { + id: u32::from(id.as_raw()) << 21, + mask: u32::from(mask.as_raw()) << 21 | F32_IDE, // also require IDE = 0 + } + } + + /// Make the filter accept data frames only. + pub fn data_frames_only(&mut self) -> &mut Self { + self.id &= !F32_RTR; // RTR = 0 + self.mask |= F32_RTR; + self + } + + /// Make the filter accept remote frames only. + pub fn remote_frames_only(&mut self) -> &mut Self { + self.id |= F32_RTR; // RTR = 1 + self.mask |= F32_RTR; + self + } +} + +/// The configuration of a filter bank. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BankConfig { + /// Specify up to 4 exact standard CAN ID's. + List16([ListEntry16; 4]), + /// Specify up to 2 exact standard or extended CAN ID's. + List32([ListEntry32; 2]), + /// Specify up to 2 standard ID's with masks. + Mask16([Mask16; 2]), + /// Specify a single extended ID with mask. + Mask32(Mask32), +} + +impl From<[ListEntry16; 4]> for BankConfig { + #[inline] + fn from(entries: [ListEntry16; 4]) -> Self { + Self::List16(entries) + } +} + +impl From<[ListEntry32; 2]> for BankConfig { + #[inline] + fn from(entries: [ListEntry32; 2]) -> Self { + Self::List32(entries) + } +} + +impl From<[Mask16; 2]> for BankConfig { + #[inline] + fn from(entries: [Mask16; 2]) -> Self { + Self::Mask16(entries) + } +} + +impl From for BankConfig { + #[inline] + fn from(filter: Mask32) -> Self { + Self::Mask32(filter) + } +} + +/// Interface to the filter banks of a CAN peripheral. +pub struct MasterFilters<'a> { + /// Number of assigned filter banks. + /// + /// On chips with splittable filter banks, this value can be dynamic. + bank_count: u8, + _phantom: PhantomData<&'a ()>, + info: &'static crate::can::Info, +} + +// NOTE: This type mutably borrows the CAN instance and has unique access to the registers while it +// exists. +impl MasterFilters<'_> { + pub(crate) unsafe fn new(info: &'static crate::can::Info) -> Self { + // Enable initialization mode. + info.regs.0.fmr().modify(|reg| reg.set_finit(true)); + + // Read the filter split value. + let bank_count = info.regs.0.fmr().read().can2sb(); + + // (Reset value of CAN2SB is 0x0E, 14, which, in devices with 14 filter banks, assigns all + // of them to the master peripheral, and in devices with 28, assigns them 50/50 to + // master/slave instances) + + Self { + bank_count, + _phantom: PhantomData, + info, + } + } + + fn banks_imm(&self) -> FilterBanks { + FilterBanks { + start_idx: 0, + bank_count: self.bank_count, + info: self.info, + } + } + + /// Returns the number of filter banks currently assigned to this instance. + /// + /// Chips with splittable filter banks may start out with some banks assigned to the master + /// instance and some assigned to the slave instance. + pub fn num_banks(&self) -> u8 { + self.bank_count + } + + /// Disables all enabled filter banks. + /// + /// This causes all incoming frames to be disposed. + pub fn clear(&mut self) -> &mut Self { + self.banks_imm().clear(); + self + } + + /// Disables a filter bank. + /// + /// If `index` is out of bounds, this will panic. + pub fn disable_bank(&mut self, index: u8) -> &mut Self { + self.banks_imm().disable(index); + self + } + + /// Configures a filter bank according to `config` and enables it. + /// + /// Each filter bank is associated with one of the two RX FIFOs, configured by the [`Fifo`] + /// passed to this function. In the event that both FIFOs are configured to accept an incoming + /// frame, the accepting filter bank with the lowest index wins. The FIFO state is ignored, so + /// if the FIFO is full, it will overflow, even if the other FIFO is also configured to accept + /// the frame. + /// + /// # Parameters + /// + /// - `index`: the filter index. + /// - `fifo`: the receive FIFO the filter should pass accepted messages to. + /// - `config`: the filter configuration. + pub fn enable_bank(&mut self, index: u8, fifo: Fifo, config: impl Into) -> &mut Self { + self.banks_imm().enable(index, fifo, config.into()); + self + } +} + +impl MasterFilters<'_> { + /// Sets the index at which the filter banks owned by the slave peripheral start. + pub fn set_split(&mut self, split_index: u8) -> &mut Self { + assert!(split_index <= self.info.num_filter_banks); + self.info.regs.0.fmr().modify(|reg| reg.set_can2sb(split_index)); + self.bank_count = split_index; + self + } + + /// Accesses the filters assigned to the slave peripheral. + pub fn slave_filters(&mut self) -> SlaveFilters<'_> { + // NB: This mutably borrows `self`, so it has full access to the filter bank registers. + SlaveFilters { + start_idx: self.bank_count, + bank_count: self.info.num_filter_banks - self.bank_count, + _phantom: PhantomData, + info: self.info, + } + } +} + +impl Drop for MasterFilters<'_> { + #[inline] + fn drop(&mut self) { + // Leave initialization mode. + self.info.regs.0.fmr().modify(|regs| regs.set_finit(false)); + } +} + +/// Interface to the filter banks assigned to a slave peripheral. +pub struct SlaveFilters<'a> { + start_idx: u8, + bank_count: u8, + _phantom: PhantomData<&'a ()>, + info: &'static crate::can::Info, +} + +impl SlaveFilters<'_> { + fn banks_imm(&self) -> FilterBanks { + FilterBanks { + start_idx: self.start_idx, + bank_count: self.bank_count, + info: self.info, + } + } + + /// Returns the number of filter banks currently assigned to this instance. + /// + /// Chips with splittable filter banks may start out with some banks assigned to the master + /// instance and some assigned to the slave instance. + pub fn num_banks(&self) -> u8 { + self.bank_count + } + + /// Disables all enabled filter banks. + /// + /// This causes all incoming frames to be disposed. + pub fn clear(&mut self) -> &mut Self { + self.banks_imm().clear(); + self + } + + /// Disables a filter bank. + /// + /// If `index` is out of bounds, this will panic. + pub fn disable_bank(&mut self, index: u8) -> &mut Self { + self.banks_imm().disable(index); + self + } + + /// Configures a filter bank according to `config` and enables it. + /// + /// # Parameters + /// + /// - `index`: the filter index. + /// - `fifo`: the receive FIFO the filter should pass accepted messages to. + /// - `config`: the filter configuration. + pub fn enable_bank(&mut self, index: u8, fifo: Fifo, config: impl Into) -> &mut Self { + self.banks_imm().enable(index, fifo, config.into()); + self + } +} + +struct FilterBanks { + start_idx: u8, + bank_count: u8, + info: &'static crate::can::Info, +} + +impl FilterBanks { + fn clear(&mut self) { + let mask = filter_bitmask(self.start_idx, self.bank_count); + + self.info.regs.0.fa1r().modify(|reg| { + for i in 0..28usize { + if (0x01u32 << i) & mask != 0 { + reg.set_fact(i, false); + } + } + }); + } + + fn assert_bank_index(&self, index: u8) { + assert!((self.start_idx..self.start_idx + self.bank_count).contains(&index)); + } + + fn disable(&mut self, index: u8) { + self.assert_bank_index(index); + self.info + .regs + .0 + .fa1r() + .modify(|reg| reg.set_fact(index as usize, false)) + } + + fn enable(&mut self, index: u8, fifo: Fifo, config: BankConfig) { + self.assert_bank_index(index); + + // Configure mode. + let mode = matches!(config, BankConfig::List16(_) | BankConfig::List32(_)); + self.info.regs.0.fm1r().modify(|reg| reg.set_fbm(index as usize, mode)); + + // Configure scale. + let scale = matches!(config, BankConfig::List32(_) | BankConfig::Mask32(_)); + self.info.regs.0.fs1r().modify(|reg| reg.set_fsc(index as usize, scale)); + + // Configure filter register. + let (fxr1, fxr2); + match config { + BankConfig::List16([a, b, c, d]) => { + fxr1 = (u32::from(b.0) << 16) | u32::from(a.0); + fxr2 = (u32::from(d.0) << 16) | u32::from(c.0); + } + BankConfig::List32([a, b]) => { + fxr1 = a.0; + fxr2 = b.0; + } + BankConfig::Mask16([a, b]) => { + fxr1 = (u32::from(a.mask) << 16) | u32::from(a.id); + fxr2 = (u32::from(b.mask) << 16) | u32::from(b.id); + } + BankConfig::Mask32(a) => { + fxr1 = a.id; + fxr2 = a.mask; + } + }; + let bank = self.info.regs.0.fb(index as usize); + bank.fr1().write(|w| w.0 = fxr1); + bank.fr2().write(|w| w.0 = fxr2); + + // Assign to the right FIFO + self.info.regs.0.ffa1r().modify(|reg| { + reg.set_ffa( + index as usize, + match fifo { + Fifo::Fifo0 => false, + Fifo::Fifo1 => true, + }, + ) + }); + + // Set active. + self.info.regs.0.fa1r().modify(|reg| reg.set_fact(index as usize, true)) + } +} + +/// Computes a bitmask for per-filter-bank registers that only includes filters in the given range. +fn filter_bitmask(start_idx: u8, bank_count: u8) -> u32 { + let count_mask = (1 << bank_count) - 1; // `bank_count` 1-bits + count_mask << start_idx +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_filter_bitmask() { + assert_eq!(filter_bitmask(0, 1), 0x1); + assert_eq!(filter_bitmask(1, 1), 0b10); + assert_eq!(filter_bitmask(0, 4), 0xf); + assert_eq!(filter_bitmask(1, 3), 0xe); + assert_eq!(filter_bitmask(8, 1), 0x100); + assert_eq!(filter_bitmask(8, 4), 0xf00); + } +} diff --git a/embassy/embassy-stm32/src/can/bxcan/mod.rs b/embassy/embassy-stm32/src/can/bxcan/mod.rs new file mode 100644 index 0000000..19f1cea --- /dev/null +++ b/embassy/embassy-stm32/src/can/bxcan/mod.rs @@ -0,0 +1,1196 @@ +pub mod filter; +mod registers; + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::interrupt::InterruptExt; +use embassy_hal_internal::into_ref; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_sync::waitqueue::AtomicWaker; +pub use embedded_can::{ExtendedId, Id, StandardId}; + +use self::filter::MasterFilters; +use self::registers::{Registers, RxFifo}; +pub use super::common::{BufferedCanReceiver, BufferedCanSender}; +use super::frame::{Envelope, Frame}; +use super::util; +use crate::can::enums::{BusError, TryReadError}; +use crate::gpio::{AfType, OutputType, Pull, Speed}; +use crate::interrupt::typelevel::Interrupt; +use crate::rcc::{self, RccPeripheral}; +use crate::{interrupt, peripherals, Peripheral}; + +/// Interrupt handler. +pub struct TxInterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for TxInterruptHandler { + unsafe fn on_interrupt() { + T::regs().tsr().write(|v| { + v.set_rqcp(0, true); + v.set_rqcp(1, true); + v.set_rqcp(2, true); + }); + T::state().tx_mode.on_interrupt::(); + } +} + +/// RX0 interrupt handler. +pub struct Rx0InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for Rx0InterruptHandler { + unsafe fn on_interrupt() { + T::state().rx_mode.on_interrupt::(RxFifo::Fifo0); + } +} + +/// RX1 interrupt handler. +pub struct Rx1InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for Rx1InterruptHandler { + unsafe fn on_interrupt() { + T::state().rx_mode.on_interrupt::(RxFifo::Fifo1); + } +} + +/// SCE interrupt handler. +pub struct SceInterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for SceInterruptHandler { + unsafe fn on_interrupt() { + let msr = T::regs().msr(); + let msr_val = msr.read(); + + if msr_val.slaki() { + msr.modify(|m| m.set_slaki(true)); + T::state().err_waker.wake(); + } else if msr_val.erri() { + // Disable the interrupt, but don't acknowledge the error, so that it can be + // forwarded off the bus message consumer. If we don't provide some way for + // downstream code to determine that it has already provided this bus error instance + // to the bus message consumer, we are doomed to re-provide a single error instance for + // an indefinite amount of time. + let ier = T::regs().ier(); + ier.modify(|i| i.set_errie(false)); + + T::state().err_waker.wake(); + } + } +} + +/// Configuration proxy returned by [`Can::modify_config`]. +pub struct CanConfig<'a> { + phantom: PhantomData<&'a ()>, + info: &'static Info, + periph_clock: crate::time::Hertz, +} + +impl CanConfig<'_> { + /// Configures the bit timings. + /// + /// You can use to calculate the `btr` parameter. Enter + /// parameters as follows: + /// + /// - *Clock Rate*: The input clock speed to the CAN peripheral (*not* the CPU clock speed). + /// This is the clock rate of the peripheral bus the CAN peripheral is attached to (eg. APB1). + /// - *Sample Point*: Should normally be left at the default value of 87.5%. + /// - *SJW*: Should normally be left at the default value of 1. + /// + /// Then copy the `CAN_BUS_TIME` register value from the table and pass it as the `btr` + /// parameter to this method. + pub fn set_bit_timing(self, bt: crate::can::util::NominalBitTiming) -> Self { + self.info.regs.set_bit_timing(bt); + self + } + + /// Configure the CAN bit rate. + /// + /// This is a helper that internally calls `set_bit_timing()`[Self::set_bit_timing]. + pub fn set_bitrate(self, bitrate: u32) -> Self { + let bit_timing = util::calc_can_timings(self.periph_clock, bitrate).unwrap(); + self.set_bit_timing(bit_timing) + } + + /// Enables or disables loopback mode: Internally connects the TX and RX + /// signals together. + pub fn set_loopback(self, enabled: bool) -> Self { + self.info.regs.set_loopback(enabled); + self + } + + /// Enables or disables silent mode: Disconnects the TX signal from the pin. + pub fn set_silent(self, enabled: bool) -> Self { + self.info.regs.set_silent(enabled); + self + } + + /// Enables or disables automatic retransmission of frames. + /// + /// If this is enabled, the CAN peripheral will automatically try to retransmit each frame + /// until it can be sent. Otherwise, it will try only once to send each frame. + /// + /// Automatic retransmission is enabled by default. + pub fn set_automatic_retransmit(self, enabled: bool) -> Self { + self.info.regs.set_automatic_retransmit(enabled); + self + } +} + +impl Drop for CanConfig<'_> { + #[inline] + fn drop(&mut self) { + self.info.regs.leave_init_mode(); + } +} + +/// CAN driver +pub struct Can<'d> { + phantom: PhantomData<&'d ()>, + info: &'static Info, + state: &'static State, + periph_clock: crate::time::Hertz, +} + +/// Error returned by `try_write` +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TryWriteError { + /// All transmit mailboxes are full + Full, +} + +impl<'d> Can<'d> { + /// Creates a new Bxcan instance, keeping the peripheral in sleep mode. + /// You must call [Can::enable_non_blocking] to use the peripheral. + pub fn new( + _peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + _irqs: impl interrupt::typelevel::Binding> + + interrupt::typelevel::Binding> + + interrupt::typelevel::Binding> + + interrupt::typelevel::Binding> + + 'd, + ) -> Self { + into_ref!(_peri, rx, tx); + let info = T::info(); + let regs = &T::info().regs; + + rx.set_as_af(rx.af_num(), AfType::input(Pull::None)); + tx.set_as_af(tx.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + + rcc::enable_and_reset::(); + + { + regs.0.ier().write(|w| { + w.set_errie(true); + w.set_fmpie(0, true); + w.set_fmpie(1, true); + w.set_tmeie(true); + w.set_bofie(true); + w.set_epvie(true); + w.set_ewgie(true); + w.set_lecie(true); + }); + + regs.0.mcr().write(|w| { + // Enable timestamps on rx messages + + w.set_ttcm(true); + }); + } + + unsafe { + info.tx_interrupt.unpend(); + info.tx_interrupt.enable(); + info.rx0_interrupt.unpend(); + info.rx0_interrupt.enable(); + info.rx1_interrupt.unpend(); + info.rx1_interrupt.enable(); + info.sce_interrupt.unpend(); + info.sce_interrupt.enable(); + } + + rx.set_as_af(rx.af_num(), AfType::input(Pull::None)); + tx.set_as_af(tx.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + + Registers(T::regs()).leave_init_mode(); + + Self { + phantom: PhantomData, + info: T::info(), + state: T::state(), + periph_clock: T::frequency(), + } + } + + /// Set CAN bit rate. + pub fn set_bitrate(&mut self, bitrate: u32) { + let bit_timing = util::calc_can_timings(self.periph_clock, bitrate).unwrap(); + self.modify_config().set_bit_timing(bit_timing); + } + + /// Configure bit timings and silent/loop-back mode. + /// + /// Calling this method will enter initialization mode. You must enable the peripheral + /// again afterwards with [`enable`](Self::enable). + pub fn modify_config(&mut self) -> CanConfig<'_> { + self.info.regs.enter_init_mode(); + + CanConfig { + phantom: self.phantom, + info: self.info, + periph_clock: self.periph_clock, + } + } + + /// Enables the peripheral and synchronizes with the bus. + /// + /// This will wait for 11 consecutive recessive bits (bus idle state). + /// Contrary to enable method from bxcan library, this will not freeze the executor while waiting. + pub async fn enable(&mut self) { + while self.info.regs.enable_non_blocking().is_err() { + // SCE interrupt is only generated for entering sleep mode, but not leaving. + // Yield to allow other tasks to execute while can bus is initializing. + embassy_futures::yield_now().await; + } + } + + /// Enables or disables the peripheral from automatically wakeup when a SOF is detected on the bus + /// while the peripheral is in sleep mode + pub fn set_automatic_wakeup(&mut self, enabled: bool) { + self.info.regs.set_automatic_wakeup(enabled); + } + + /// Manually wake the peripheral from sleep mode. + /// + /// Waking the peripheral manually does not trigger a wake-up interrupt. + /// This will wait until the peripheral has acknowledged it has awoken from sleep mode + pub fn wakeup(&mut self) { + self.info.regs.wakeup() + } + + /// Check if the peripheral is currently in sleep mode + pub fn is_sleeping(&self) -> bool { + self.info.regs.0.msr().read().slak() + } + + /// Put the peripheral in sleep mode + /// + /// When the peripherial is in sleep mode, messages can still be queued for transmission + /// and any previously received messages can be read from the receive FIFOs, however + /// no messages will be transmitted and no additional messages will be received. + /// + /// If the peripheral has automatic wakeup enabled, when a Start-of-Frame is detected + /// the peripheral will automatically wake and receive the incoming message. + pub async fn sleep(&mut self) { + self.info.regs.0.ier().modify(|i| i.set_slkie(true)); + self.info.regs.0.mcr().modify(|m| m.set_sleep(true)); + + poll_fn(|cx| { + self.state.err_waker.register(cx.waker()); + if self.is_sleeping() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + self.info.regs.0.ier().modify(|i| i.set_slkie(false)); + } + + /// Enable FIFO scheduling of outgoing frames. + /// + /// If this is enabled, frames will be transmitted in the order that they are passed to + /// [`write()`][Self::write] or [`try_write()`][Self::try_write()]. + /// + /// If this is disabled, frames are transmitted in order of priority. + /// + /// FIFO scheduling is disabled by default. + pub fn set_tx_fifo_scheduling(&mut self, enabled: bool) { + self.info.regs.set_tx_fifo_scheduling(enabled) + } + + /// Checks if FIFO scheduling of outgoing frames is enabled. + pub fn tx_fifo_scheduling_enabled(&self) -> bool { + self.info.regs.tx_fifo_scheduling_enabled() + } + + /// Queues the message to be sent. + /// + /// If the TX queue is full, this will wait until there is space, therefore exerting backpressure. + pub async fn write(&mut self, frame: &Frame) -> TransmitStatus { + self.split().0.write(frame).await + } + + /// Attempts to transmit a frame without blocking. + /// + /// Returns [Err(TryWriteError::Full)] if the frame can not be queued for transmission now. + /// + /// If FIFO scheduling is enabled, any empty mailbox will be used. + /// + /// Otherwise, the frame will only be accepted if there is no frame with the same priority already queued. + /// This is done to work around a hardware limitation that could lead to out-of-order delivery + /// of frames with the same priority. + pub fn try_write(&mut self, frame: &Frame) -> Result { + self.split().0.try_write(frame) + } + + /// Waits for a specific transmit mailbox to become empty + pub async fn flush(&self, mb: Mailbox) { + CanTx { + _phantom: PhantomData, + info: self.info, + state: self.state, + } + .flush_inner(mb) + .await; + } + + /// Waits until any of the transmit mailboxes become empty + /// + /// Note that [`Self::try_write()`] may fail with [`TryWriteError::Full`], + /// even after the future returned by this function completes. + /// This will happen if FIFO scheduling of outgoing frames is not enabled, + /// and a frame with equal priority is already queued for transmission. + pub async fn flush_any(&self) { + CanTx { + _phantom: PhantomData, + info: self.info, + state: self.state, + } + .flush_any_inner() + .await + } + + /// Waits until all of the transmit mailboxes become empty + pub async fn flush_all(&self) { + CanTx { + _phantom: PhantomData, + info: self.info, + state: self.state, + } + .flush_all_inner() + .await + } + + /// Attempts to abort the sending of a frame that is pending in a mailbox. + /// + /// If there is no frame in the provided mailbox, or its transmission succeeds before it can be + /// aborted, this function has no effect and returns `false`. + /// + /// If there is a frame in the provided mailbox, and it is canceled successfully, this function + /// returns `true`. + pub fn abort(&mut self, mailbox: Mailbox) -> bool { + self.info.regs.abort(mailbox) + } + + /// Returns `true` if no frame is pending for transmission. + pub fn is_transmitter_idle(&self) -> bool { + self.info.regs.is_idle() + } + + /// Read a CAN frame. + /// + /// If no CAN frame is in the RX buffer, this will wait until there is one. + /// + /// Returns a tuple of the time the message was received and the message frame + pub async fn read(&mut self) -> Result { + self.state.rx_mode.read(self.info, self.state).await + } + + /// Attempts to read a CAN frame without blocking. + /// + /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. + pub fn try_read(&mut self) -> Result { + self.state.rx_mode.try_read(self.info) + } + + /// Waits while receive queue is empty. + pub async fn wait_not_empty(&mut self) { + self.state.rx_mode.wait_not_empty(self.info, self.state).await + } + + /// Split the CAN driver into transmit and receive halves. + /// + /// Useful for doing separate transmit/receive tasks. + pub fn split<'c>(&'c mut self) -> (CanTx<'d>, CanRx<'d>) { + ( + CanTx { + _phantom: PhantomData, + info: self.info, + state: self.state, + }, + CanRx { + _phantom: PhantomData, + info: self.info, + state: self.state, + }, + ) + } + + /// Return a buffered instance of driver. User must supply Buffers + pub fn buffered<'c, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize>( + &'c mut self, + txb: &'static mut TxBuf, + rxb: &'static mut RxBuf, + ) -> BufferedCan<'d, TX_BUF_SIZE, RX_BUF_SIZE> { + let (tx, rx) = self.split(); + BufferedCan { + tx: tx.buffered(txb), + rx: rx.buffered(rxb), + } + } +} + +impl<'d> Can<'d> { + /// Accesses the filter banks owned by this CAN peripheral. + /// + /// To modify filters of a slave peripheral, `modify_filters` has to be called on the master + /// peripheral instead. + pub fn modify_filters(&mut self) -> MasterFilters<'_> { + unsafe { MasterFilters::new(self.info) } + } +} + +/// Buffered CAN driver. +pub struct BufferedCan<'d, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> { + tx: BufferedCanTx<'d, TX_BUF_SIZE>, + rx: BufferedCanRx<'d, RX_BUF_SIZE>, +} + +impl<'d, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCan<'d, TX_BUF_SIZE, RX_BUF_SIZE> { + /// Async write frame to TX buffer. + pub async fn write(&mut self, frame: &Frame) { + self.tx.write(frame).await + } + + /// Returns a sender that can be used for sending CAN frames. + pub fn writer(&self) -> BufferedCanSender { + self.tx.writer() + } + + /// Async read frame from RX buffer. + pub async fn read(&mut self) -> Result { + self.rx.read().await + } + + /// Attempts to read a CAN frame without blocking. + /// + /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. + pub fn try_read(&mut self) -> Result { + self.rx.try_read() + } + + /// Waits while receive queue is empty. + pub async fn wait_not_empty(&mut self) { + self.rx.wait_not_empty().await + } + + /// Returns a receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver. + pub fn reader(&self) -> BufferedCanReceiver { + self.rx.reader() + } +} + +/// CAN driver, transmit half. +pub struct CanTx<'d> { + _phantom: PhantomData<&'d ()>, + info: &'static Info, + state: &'static State, +} + +impl<'d> CanTx<'d> { + /// Queues the message to be sent. + /// + /// If the TX queue is full, this will wait until there is space, therefore exerting backpressure. + pub async fn write(&mut self, frame: &Frame) -> TransmitStatus { + poll_fn(|cx| { + self.state.tx_mode.register(cx.waker()); + if let Ok(status) = self.info.regs.transmit(frame) { + return Poll::Ready(status); + } + + Poll::Pending + }) + .await + } + + /// Attempts to transmit a frame without blocking. + /// + /// Returns [Err(TryWriteError::Full)] if the frame can not be queued for transmission now. + /// + /// If FIFO scheduling is enabled, any empty mailbox will be used. + /// + /// Otherwise, the frame will only be accepted if there is no frame with the same priority already queued. + /// This is done to work around a hardware limitation that could lead to out-of-order delivery + /// of frames with the same priority. + pub fn try_write(&mut self, frame: &Frame) -> Result { + self.info.regs.transmit(frame).map_err(|_| TryWriteError::Full) + } + + async fn flush_inner(&self, mb: Mailbox) { + poll_fn(|cx| { + self.state.tx_mode.register(cx.waker()); + if self.info.regs.0.tsr().read().tme(mb.index()) { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + } + + /// Waits for a specific transmit mailbox to become empty + pub async fn flush(&self, mb: Mailbox) { + self.flush_inner(mb).await + } + + async fn flush_any_inner(&self) { + poll_fn(|cx| { + self.state.tx_mode.register(cx.waker()); + + let tsr = self.info.regs.0.tsr().read(); + if tsr.tme(Mailbox::Mailbox0.index()) + || tsr.tme(Mailbox::Mailbox1.index()) + || tsr.tme(Mailbox::Mailbox2.index()) + { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + } + + /// Waits until any of the transmit mailboxes become empty + /// + /// Note that [`Self::try_write()`] may fail with [`TryWriteError::Full`], + /// even after the future returned by this function completes. + /// This will happen if FIFO scheduling of outgoing frames is not enabled, + /// and a frame with equal priority is already queued for transmission. + pub async fn flush_any(&self) { + self.flush_any_inner().await + } + + async fn flush_all_inner(&self) { + poll_fn(|cx| { + self.state.tx_mode.register(cx.waker()); + + let tsr = self.info.regs.0.tsr().read(); + if tsr.tme(Mailbox::Mailbox0.index()) + && tsr.tme(Mailbox::Mailbox1.index()) + && tsr.tme(Mailbox::Mailbox2.index()) + { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + } + + /// Waits until all of the transmit mailboxes become empty + pub async fn flush_all(&self) { + self.flush_all_inner().await + } + + /// Attempts to abort the sending of a frame that is pending in a mailbox. + /// + /// If there is no frame in the provided mailbox, or its transmission succeeds before it can be + /// aborted, this function has no effect and returns `false`. + /// + /// If there is a frame in the provided mailbox, and it is canceled successfully, this function + /// returns `true`. + pub fn abort(&mut self, mailbox: Mailbox) -> bool { + self.info.regs.abort(mailbox) + } + + /// Returns `true` if no frame is pending for transmission. + pub fn is_idle(&self) -> bool { + self.info.regs.is_idle() + } + + /// Return a buffered instance of driver. User must supply Buffers + pub fn buffered( + self, + txb: &'static mut TxBuf, + ) -> BufferedCanTx<'d, TX_BUF_SIZE> { + BufferedCanTx::new(self.info, self.state, self, txb) + } +} + +/// User supplied buffer for TX buffering +pub type TxBuf = Channel; + +/// Buffered CAN driver, transmit half. +pub struct BufferedCanTx<'d, const TX_BUF_SIZE: usize> { + info: &'static Info, + state: &'static State, + _tx: CanTx<'d>, + tx_buf: &'static TxBuf, +} + +impl<'d, const TX_BUF_SIZE: usize> BufferedCanTx<'d, TX_BUF_SIZE> { + fn new(info: &'static Info, state: &'static State, _tx: CanTx<'d>, tx_buf: &'static TxBuf) -> Self { + Self { + info, + state, + _tx, + tx_buf, + } + .setup() + } + + fn setup(self) -> Self { + // We don't want interrupts being processed while we change modes. + critical_section::with(|_| { + let tx_inner = super::common::ClassicBufferedTxInner { + tx_receiver: self.tx_buf.receiver().into(), + }; + let state = self.state as *const State; + unsafe { + let mut_state = state as *mut State; + (*mut_state).tx_mode = TxMode::Buffered(tx_inner); + } + }); + self + } + + /// Async write frame to TX buffer. + pub async fn write(&mut self, frame: &Frame) { + self.tx_buf.send(*frame).await; + let waker = self.info.tx_waker; + waker(); // Wake for Tx + } + + /// Returns a sender that can be used for sending CAN frames. + pub fn writer(&self) -> BufferedCanSender { + BufferedCanSender { + tx_buf: self.tx_buf.sender().into(), + waker: self.info.tx_waker, + } + } +} + +impl<'d, const TX_BUF_SIZE: usize> Drop for BufferedCanTx<'d, TX_BUF_SIZE> { + fn drop(&mut self) { + critical_section::with(|_| { + let state = self.state as *const State; + unsafe { + let mut_state = state as *mut State; + (*mut_state).tx_mode = TxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + } + }); + } +} + +/// CAN driver, receive half. +#[allow(dead_code)] +pub struct CanRx<'d> { + _phantom: PhantomData<&'d ()>, + info: &'static Info, + state: &'static State, +} + +impl<'d> CanRx<'d> { + /// Read a CAN frame. + /// + /// If no CAN frame is in the RX buffer, this will wait until there is one. + /// + /// Returns a tuple of the time the message was received and the message frame + pub async fn read(&mut self) -> Result { + self.state.rx_mode.read(self.info, self.state).await + } + + /// Attempts to read a CAN frame without blocking. + /// + /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. + pub fn try_read(&mut self) -> Result { + self.state.rx_mode.try_read(self.info) + } + + /// Waits while receive queue is empty. + pub async fn wait_not_empty(&mut self) { + self.state.rx_mode.wait_not_empty(self.info, self.state).await + } + + /// Return a buffered instance of driver. User must supply Buffers + pub fn buffered( + self, + rxb: &'static mut RxBuf, + ) -> BufferedCanRx<'d, RX_BUF_SIZE> { + BufferedCanRx::new(self.info, self.state, self, rxb) + } +} + +/// User supplied buffer for RX Buffering +pub type RxBuf = Channel, BUF_SIZE>; + +/// CAN driver, receive half in Buffered mode. +pub struct BufferedCanRx<'d, const RX_BUF_SIZE: usize> { + info: &'static Info, + state: &'static State, + _rx: CanRx<'d>, + rx_buf: &'static RxBuf, +} + +impl<'d, const RX_BUF_SIZE: usize> BufferedCanRx<'d, RX_BUF_SIZE> { + fn new(info: &'static Info, state: &'static State, _rx: CanRx<'d>, rx_buf: &'static RxBuf) -> Self { + BufferedCanRx { + info, + state, + _rx, + rx_buf, + } + .setup() + } + + fn setup(self) -> Self { + // We don't want interrupts being processed while we change modes. + critical_section::with(|_| { + let rx_inner = super::common::ClassicBufferedRxInner { + rx_sender: self.rx_buf.sender().into(), + }; + let state = self.state as *const State; + unsafe { + let mut_state = state as *mut State; + (*mut_state).rx_mode = RxMode::Buffered(rx_inner); + } + }); + self + } + + /// Async read frame from RX buffer. + pub async fn read(&mut self) -> Result { + self.rx_buf.receive().await + } + + /// Attempts to read a CAN frame without blocking. + /// + /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. + pub fn try_read(&mut self) -> Result { + match &self.state.rx_mode { + RxMode::Buffered(_) => { + if let Ok(result) = self.rx_buf.try_receive() { + match result { + Ok(envelope) => Ok(envelope), + Err(e) => Err(TryReadError::BusError(e)), + } + } else { + if let Some(err) = self.info.regs.curr_error() { + return Err(TryReadError::BusError(err)); + } else { + Err(TryReadError::Empty) + } + } + } + _ => { + panic!("Bad Mode") + } + } + } + + /// Waits while receive queue is empty. + pub async fn wait_not_empty(&mut self) { + poll_fn(|cx| self.rx_buf.poll_ready_to_receive(cx)).await + } + + /// Returns a receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver. + pub fn reader(&self) -> BufferedCanReceiver { + self.rx_buf.receiver().into() + } +} + +impl<'d, const RX_BUF_SIZE: usize> Drop for BufferedCanRx<'d, RX_BUF_SIZE> { + fn drop(&mut self) { + critical_section::with(|_| { + let state = self.state as *const State; + unsafe { + let mut_state = state as *mut State; + (*mut_state).rx_mode = RxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + } + }); + } +} + +impl Drop for Can<'_> { + fn drop(&mut self) { + // Cannot call `free()` because it moves the instance. + // Manually reset the peripheral. + self.info.regs.0.mcr().write(|w| w.set_reset(true)); + self.info.regs.enter_init_mode(); + self.info.regs.leave_init_mode(); + //rcc::disable::(); + } +} + +/// Identifies one of the two receive FIFOs. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Fifo { + /// First receive FIFO + Fifo0 = 0, + /// Second receive FIFO + Fifo1 = 1, +} + +/// Identifies one of the three transmit mailboxes. +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Mailbox { + /// Transmit mailbox 0 + Mailbox0 = 0, + /// Transmit mailbox 1 + Mailbox1 = 1, + /// Transmit mailbox 2 + Mailbox2 = 2, +} + +/// Contains information about a frame enqueued for transmission via [`Can::transmit`] or +/// [`Tx::transmit`]. +pub struct TransmitStatus { + dequeued_frame: Option, + mailbox: Mailbox, +} + +impl TransmitStatus { + /// Returns the lower-priority frame that was dequeued to make space for the new frame. + #[inline] + pub fn dequeued_frame(&self) -> Option<&Frame> { + self.dequeued_frame.as_ref() + } + + /// Returns the [`Mailbox`] the frame was enqueued in. + #[inline] + pub fn mailbox(&self) -> Mailbox { + self.mailbox + } +} + +pub(crate) enum RxMode { + NonBuffered(AtomicWaker), + Buffered(super::common::ClassicBufferedRxInner), +} + +impl RxMode { + pub fn on_interrupt(&self, fifo: RxFifo) { + match self { + Self::NonBuffered(waker) => { + // Disable interrupts until read + let fifo_idx = match fifo { + RxFifo::Fifo0 => 0usize, + RxFifo::Fifo1 => 1usize, + }; + T::regs().ier().modify(|w| { + w.set_fmpie(fifo_idx, false); + }); + waker.wake(); + } + Self::Buffered(buf) => { + loop { + match Registers(T::regs()).receive_fifo(fifo) { + Some(envelope) => { + // NOTE: consensus was reached that if rx_queue is full, packets should be dropped + let _ = buf.rx_sender.try_send(Ok(envelope)); + } + None => return, + }; + } + } + } + } + + pub(crate) async fn read(&self, info: &Info, state: &State) -> Result { + match self { + Self::NonBuffered(waker) => { + poll_fn(|cx| { + state.err_waker.register(cx.waker()); + waker.register(cx.waker()); + match self.try_read(info) { + Ok(result) => Poll::Ready(Ok(result)), + Err(TryReadError::Empty) => Poll::Pending, + Err(TryReadError::BusError(be)) => Poll::Ready(Err(be)), + } + }) + .await + } + _ => { + panic!("Bad Mode") + } + } + } + pub(crate) fn try_read(&self, info: &Info) -> Result { + match self { + Self::NonBuffered(_) => { + let registers = &info.regs; + if let Some(msg) = registers.receive_fifo(RxFifo::Fifo0) { + registers.0.ier().modify(|w| { + w.set_fmpie(0, true); + }); + Ok(msg) + } else if let Some(msg) = registers.receive_fifo(RxFifo::Fifo1) { + registers.0.ier().modify(|w| { + w.set_fmpie(1, true); + }); + Ok(msg) + } else if let Some(err) = registers.curr_error() { + Err(TryReadError::BusError(err)) + } else { + registers.0.ier().modify(|w| { + w.set_fmpie(0, true); + w.set_fmpie(1, true); + }); + Err(TryReadError::Empty) + } + } + _ => { + panic!("Bad Mode") + } + } + } + pub(crate) async fn wait_not_empty(&self, info: &Info, state: &State) { + match &state.rx_mode { + Self::NonBuffered(waker) => { + poll_fn(|cx| { + waker.register(cx.waker()); + if info.regs.receive_frame_available() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } + _ => { + panic!("Bad Mode") + } + } + } +} + +pub(crate) enum TxMode { + NonBuffered(AtomicWaker), + Buffered(super::common::ClassicBufferedTxInner), +} + +impl TxMode { + pub fn buffer_free(&self) -> bool { + let tsr = T::regs().tsr().read(); + tsr.tme(Mailbox::Mailbox0.index()) || tsr.tme(Mailbox::Mailbox1.index()) || tsr.tme(Mailbox::Mailbox2.index()) + } + pub fn on_interrupt(&self) { + match &T::state().tx_mode { + TxMode::NonBuffered(waker) => waker.wake(), + TxMode::Buffered(buf) => { + while self.buffer_free::() { + match buf.tx_receiver.try_receive() { + Ok(frame) => { + _ = Registers(T::regs()).transmit(&frame); + } + Err(_) => { + break; + } + } + } + } + } + } + + fn register(&self, arg: &core::task::Waker) { + match self { + TxMode::NonBuffered(waker) => { + waker.register(arg); + } + _ => { + panic!("Bad mode"); + } + } + } +} + +pub(crate) struct State { + pub(crate) rx_mode: RxMode, + pub(crate) tx_mode: TxMode, + pub err_waker: AtomicWaker, +} + +impl State { + pub const fn new() -> Self { + Self { + rx_mode: RxMode::NonBuffered(AtomicWaker::new()), + tx_mode: TxMode::NonBuffered(AtomicWaker::new()), + err_waker: AtomicWaker::new(), + } + } +} + +pub(crate) struct Info { + regs: Registers, + tx_interrupt: crate::interrupt::Interrupt, + rx0_interrupt: crate::interrupt::Interrupt, + rx1_interrupt: crate::interrupt::Interrupt, + sce_interrupt: crate::interrupt::Interrupt, + tx_waker: fn(), + + /// The total number of filter banks available to the instance. + /// + /// This is usually either 14 or 28, and should be specified in the chip's reference manual or datasheet. + num_filter_banks: u8, +} + +trait SealedInstance { + fn info() -> &'static Info; + fn regs() -> crate::pac::can::Can; + fn state() -> &'static State; + unsafe fn mut_state() -> &'static mut State; +} + +/// CAN instance trait. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral + 'static { + /// TX interrupt for this instance. + type TXInterrupt: crate::interrupt::typelevel::Interrupt; + /// RX0 interrupt for this instance. + type RX0Interrupt: crate::interrupt::typelevel::Interrupt; + /// RX1 interrupt for this instance. + type RX1Interrupt: crate::interrupt::typelevel::Interrupt; + /// SCE interrupt for this instance. + type SCEInterrupt: crate::interrupt::typelevel::Interrupt; +} + +/// A bxCAN instance that owns filter banks. +/// +/// In master-slave-instance setups, only the master instance owns the filter banks, and needs to +/// split some of them off for use by the slave instance. In that case, the master instance should +/// implement [`FilterOwner`] and [`MasterInstance`], while the slave instance should only implement +/// [`Instance`]. +/// +/// In single-instance configurations, the instance owns all filter banks and they can not be split +/// off. In that case, the instance should implement [`Instance`] and [`FilterOwner`]. +/// +/// # Safety +/// +/// This trait must only be implemented if the instance does, in fact, own its associated filter +/// banks, and `NUM_FILTER_BANKS` must be correct. +pub unsafe trait FilterOwner: Instance { + /// The total number of filter banks available to the instance. + /// + /// This is usually either 14 or 28, and should be specified in the chip's reference manual or datasheet. + const NUM_FILTER_BANKS: u8; +} + +/// A bxCAN master instance that shares filter banks with a slave instance. +/// +/// In master-slave-instance setups, this trait should be implemented for the master instance. +/// +/// # Safety +/// +/// This trait must only be implemented when there is actually an associated slave instance. +pub unsafe trait MasterInstance: FilterOwner {} + +foreach_peripheral!( + (can, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + + fn info() -> &'static Info { + static INFO: Info = Info { + regs: Registers(crate::pac::$inst), + tx_interrupt: crate::_generated::peripheral_interrupts::$inst::TX::IRQ, + rx0_interrupt: crate::_generated::peripheral_interrupts::$inst::RX0::IRQ, + rx1_interrupt: crate::_generated::peripheral_interrupts::$inst::RX1::IRQ, + sce_interrupt: crate::_generated::peripheral_interrupts::$inst::SCE::IRQ, + tx_waker: crate::_generated::peripheral_interrupts::$inst::TX::pend, + num_filter_banks: peripherals::$inst::NUM_FILTER_BANKS, + }; + &INFO + } + fn regs() -> crate::pac::can::Can { + crate::pac::$inst + } + + unsafe fn mut_state() -> & 'static mut State { + static mut STATE: State = State::new(); + &mut *core::ptr::addr_of_mut!(STATE) + } + fn state() -> &'static State { + unsafe { peripherals::$inst::mut_state() } + } + } + + impl Instance for peripherals::$inst { + type TXInterrupt = crate::_generated::peripheral_interrupts::$inst::TX; + type RX0Interrupt = crate::_generated::peripheral_interrupts::$inst::RX0; + type RX1Interrupt = crate::_generated::peripheral_interrupts::$inst::RX1; + type SCEInterrupt = crate::_generated::peripheral_interrupts::$inst::SCE; + } + }; +); + +foreach_peripheral!( + (can, CAN) => { + unsafe impl FilterOwner for peripherals::CAN { + const NUM_FILTER_BANKS: u8 = 14; + } + }; + // CAN1 and CAN2 is a combination of master and slave instance. + // CAN1 owns the filter bank and needs to be enabled in order + // for CAN2 to receive messages. + (can, CAN1) => { + cfg_if::cfg_if! { + if #[cfg(all( + any(stm32l4, stm32f72x, stm32f73x), + not(any(stm32l49x, stm32l4ax)) + ))] { + // Most L4 devices and some F7 devices use the name "CAN1" + // even if there is no "CAN2" peripheral. + unsafe impl FilterOwner for peripherals::CAN1 { + const NUM_FILTER_BANKS: u8 = 14; + } + } else { + unsafe impl FilterOwner for peripherals::CAN1 { + const NUM_FILTER_BANKS: u8 = 28; + } + unsafe impl MasterInstance for peripherals::CAN1 {} + } + } + }; + (can, CAN2) => { + unsafe impl FilterOwner for peripherals::CAN2 { + const NUM_FILTER_BANKS: u8 = 0; + } + }; + (can, CAN3) => { + unsafe impl FilterOwner for peripherals::CAN3 { + const NUM_FILTER_BANKS: u8 = 14; + } + }; +); + +pin_trait!(RxPin, Instance); +pin_trait!(TxPin, Instance); + +trait Index { + fn index(&self) -> usize; +} + +impl Index for Mailbox { + fn index(&self) -> usize { + match self { + Mailbox::Mailbox0 => 0, + Mailbox::Mailbox1 => 1, + Mailbox::Mailbox2 => 2, + } + } +} diff --git a/embassy/embassy-stm32/src/can/bxcan/registers.rs b/embassy/embassy-stm32/src/can/bxcan/registers.rs new file mode 100644 index 0000000..9798a05 --- /dev/null +++ b/embassy/embassy-stm32/src/can/bxcan/registers.rs @@ -0,0 +1,560 @@ +use core::cmp::Ordering; +use core::convert::Infallible; + +pub use embedded_can::{ExtendedId, Id, StandardId}; +use stm32_metapac::can::vals::{Lec, Rtr}; + +use super::{Mailbox, TransmitStatus}; +use crate::can::enums::BusError; +use crate::can::frame::{Envelope, Frame, Header}; + +pub(crate) struct Registers(pub crate::pac::can::Can); + +impl Registers { + pub fn enter_init_mode(&self) { + self.0.mcr().modify(|reg| { + reg.set_sleep(false); + reg.set_inrq(true); + }); + loop { + let msr = self.0.msr().read(); + if !msr.slak() && msr.inak() { + break; + } + } + } + + // Leaves initialization mode, enters sleep mode. + pub fn leave_init_mode(&self) { + self.0.mcr().modify(|reg| { + reg.set_sleep(true); + reg.set_inrq(false); + }); + loop { + let msr = self.0.msr().read(); + if msr.slak() && !msr.inak() { + break; + } + } + } + + pub fn set_bit_timing(&self, bt: crate::can::util::NominalBitTiming) { + let prescaler = u16::from(bt.prescaler) & 0x1FF; + let seg1 = u8::from(bt.seg1); + let seg2 = u8::from(bt.seg2) & 0x7F; + let sync_jump_width = u8::from(bt.sync_jump_width) & 0x7F; + self.0.btr().modify(|reg| { + reg.set_brp(prescaler - 1); + reg.set_ts(0, seg1 - 1); + reg.set_ts(1, seg2 - 1); + reg.set_sjw(sync_jump_width - 1); + }); + } + + /// Enables or disables silent mode: Disconnects the TX signal from the pin. + pub fn set_silent(&self, enabled: bool) { + let mode = match enabled { + false => stm32_metapac::can::vals::Silm::NORMAL, + true => stm32_metapac::can::vals::Silm::SILENT, + }; + self.0.btr().modify(|reg| reg.set_silm(mode)); + } + + /// Enables or disables automatic retransmission of messages. + /// + /// If this is enabled, the CAN peripheral will automatically try to retransmit each frame + /// until it can be sent. Otherwise, it will try only once to send each frame. + /// + /// Automatic retransmission is enabled by default. + pub fn set_automatic_retransmit(&self, enabled: bool) { + self.0.mcr().modify(|reg| reg.set_nart(enabled)); + } + + /// Enables or disables loopback mode: Internally connects the TX and RX + /// signals together. + pub fn set_loopback(&self, enabled: bool) { + self.0.btr().modify(|reg| reg.set_lbkm(enabled)); + } + + /// Configures the automatic wake-up feature. + /// + /// This is turned off by default. + /// + /// When turned on, an incoming frame will cause the peripheral to wake up from sleep and + /// receive the frame. If enabled, [`Interrupt::Wakeup`] will also be triggered by the incoming + /// frame. + #[allow(dead_code)] + pub fn set_automatic_wakeup(&self, enabled: bool) { + self.0.mcr().modify(|reg| reg.set_awum(enabled)); + } + + /// Leaves initialization mode and enables the peripheral (non-blocking version). + /// + /// Usually, it is recommended to call [`CanConfig::enable`] instead. This method is only needed + /// if you want non-blocking initialization. + /// + /// If this returns [`WouldBlock`][nb::Error::WouldBlock], the peripheral will enable itself + /// in the background. The peripheral is enabled and ready to use when this method returns + /// successfully. + pub fn enable_non_blocking(&self) -> nb::Result<(), Infallible> { + let msr = self.0.msr().read(); + if msr.slak() { + self.0.mcr().modify(|reg| { + reg.set_abom(true); + reg.set_sleep(false); + }); + Err(nb::Error::WouldBlock) + } else { + Ok(()) + } + } + + /// Puts the peripheral in a sleep mode to save power. + /// + /// While in sleep mode, an incoming CAN frame will trigger [`Interrupt::Wakeup`] if enabled. + #[allow(dead_code)] + pub fn sleep(&mut self) { + self.0.mcr().modify(|reg| { + reg.set_sleep(true); + reg.set_inrq(false); + }); + loop { + let msr = self.0.msr().read(); + if msr.slak() && !msr.inak() { + break; + } + } + } + + /// Wakes up from sleep mode. + /// + /// Note that this will not trigger [`Interrupt::Wakeup`], only reception of an incoming CAN + /// frame will cause that interrupt. + #[allow(dead_code)] + pub fn wakeup(&self) { + self.0.mcr().modify(|reg| { + reg.set_sleep(false); + reg.set_inrq(false); + }); + loop { + let msr = self.0.msr().read(); + if !msr.slak() && !msr.inak() { + break; + } + } + } + + pub fn curr_error(&self) -> Option { + if !self.0.msr().read().erri() { + // This ensures that once a single error instance has + // been acknowledged and forwared to the bus message consumer + // we don't continue to re-forward the same error occurrance for an + // in-definite amount of time. + return None; + } + + // Since we have not already acknowledge the error, and the interrupt was + // disabled in the ISR, we will acknowledge the current error and re-enable the interrupt + // so futher errors are captured + self.0.msr().modify(|m| m.set_erri(true)); + self.0.ier().modify(|i| i.set_errie(true)); + + let err = self.0.esr().read(); + if err.boff() { + return Some(BusError::BusOff); + } else if err.epvf() { + return Some(BusError::BusPassive); + } else if err.ewgf() { + return Some(BusError::BusWarning); + } else if err.lec() != Lec::NOERROR { + return Some(match err.lec() { + Lec::STUFF => BusError::Stuff, + Lec::FORM => BusError::Form, + Lec::ACK => BusError::Acknowledge, + Lec::BITRECESSIVE => BusError::BitRecessive, + Lec::BITDOMINANT => BusError::BitDominant, + Lec::CRC => BusError::Crc, + Lec::CUSTOM => BusError::Software, + Lec::NOERROR => unreachable!(), + }); + } + None + } + + /// Enables or disables FIFO scheduling of outgoing mailboxes. + /// + /// If this is enabled, mailboxes are scheduled based on the time when the transmit request bit of the mailbox was set. + /// + /// If this is disabled, mailboxes are scheduled based on the priority of the frame in the mailbox. + pub fn set_tx_fifo_scheduling(&self, enabled: bool) { + self.0.mcr().modify(|w| w.set_txfp(enabled)) + } + + /// Checks if FIFO scheduling of outgoing mailboxes is enabled. + pub fn tx_fifo_scheduling_enabled(&self) -> bool { + self.0.mcr().read().txfp() + } + + /// Puts a CAN frame in a transmit mailbox for transmission on the bus. + /// + /// The behavior of this function depends on wheter or not FIFO scheduling is enabled. + /// See [`Self::set_tx_fifo_scheduling()`] and [`Self::tx_fifo_scheduling_enabled()`]. + /// + /// # Priority based scheduling + /// + /// If FIFO scheduling is disabled, frames are transmitted to the bus based on their + /// priority (see [`FramePriority`]). Transmit order is preserved for frames with identical + /// priority. + /// + /// If all transmit mailboxes are full, and `frame` has a higher priority than the + /// lowest-priority message in the transmit mailboxes, transmission of the enqueued frame is + /// cancelled and `frame` is enqueued instead. The frame that was replaced is returned as + /// [`TransmitStatus::dequeued_frame`]. + /// + /// # FIFO scheduling + /// + /// If FIFO scheduling is enabled, frames are transmitted in the order that they are passed to this function. + /// + /// If all transmit mailboxes are full, this function returns [`nb::Error::WouldBlock`]. + pub fn transmit(&self, frame: &Frame) -> nb::Result { + // Check if FIFO scheduling is enabled. + let fifo_scheduling = self.0.mcr().read().txfp(); + + // Get the index of the next free mailbox or the one with the lowest priority. + let tsr = self.0.tsr().read(); + let idx = tsr.code() as usize; + + let frame_is_pending = !tsr.tme(0) || !tsr.tme(1) || !tsr.tme(2); + let all_frames_are_pending = !tsr.tme(0) && !tsr.tme(1) && !tsr.tme(2); + + let pending_frame; + if fifo_scheduling && all_frames_are_pending { + // FIFO scheduling is enabled and all mailboxes are full. + // We will not drop a lower priority frame, we just report WouldBlock. + return Err(nb::Error::WouldBlock); + } else if !fifo_scheduling && frame_is_pending { + // Priority scheduling is enabled and alteast one mailbox is full. + // + // In this mode, the peripheral transmits high priority frames first. + // Frames with identical priority should be transmitted in FIFO order, + // but the controller schedules pending frames of same priority based on the + // mailbox index. As a workaround check all pending mailboxes and only accept + // frames with a different priority. + self.check_priority(0, frame.id().into())?; + self.check_priority(1, frame.id().into())?; + self.check_priority(2, frame.id().into())?; + + if all_frames_are_pending { + // No free mailbox is available. This can only happen when three frames with + // ascending priority (descending IDs) were requested for transmission and all + // of them are blocked by bus traffic with even higher priority. + // To prevent a priority inversion abort and replace the lowest priority frame. + pending_frame = self.read_pending_mailbox(idx); + } else { + // There was a free mailbox. + pending_frame = None; + } + } else { + // Either we have FIFO scheduling and at-least one free mailbox, + // or we have priority scheduling and all mailboxes are free. + // No further checks are needed. + pending_frame = None + } + + self.write_mailbox(idx, frame); + + let mailbox = match idx { + 0 => Mailbox::Mailbox0, + 1 => Mailbox::Mailbox1, + 2 => Mailbox::Mailbox2, + _ => unreachable!(), + }; + Ok(TransmitStatus { + dequeued_frame: pending_frame, + mailbox, + }) + } + + /// Returns `Ok` when the mailbox is free or if it contains pending frame with a + /// different priority from the identifier `id`. + fn check_priority(&self, idx: usize, id: IdReg) -> nb::Result<(), Infallible> { + // Read the pending frame's id to check its priority. + assert!(idx < 3); + let tir = &self.0.tx(idx).tir().read(); + + // Check the priority by comparing the identifiers. But first make sure the + // frame has not finished the transmission (`TXRQ` == 0) in the meantime. + if tir.txrq() && id == IdReg::from_register(tir.0) { + // There's a mailbox whose priority is equal to the priority of the new frame. + return Err(nb::Error::WouldBlock); + } + + Ok(()) + } + + fn write_mailbox(&self, idx: usize, frame: &Frame) { + debug_assert!(idx < 3); + + let mb = self.0.tx(idx); + mb.tdtr().write(|w| w.set_dlc(frame.header().len() as u8)); + + mb.tdlr() + .write(|w| w.0 = u32::from_ne_bytes(unwrap!(frame.data()[0..4].try_into()))); + mb.tdhr() + .write(|w| w.0 = u32::from_ne_bytes(unwrap!(frame.data()[4..8].try_into()))); + let id: IdReg = frame.id().into(); + mb.tir().write(|w| { + w.0 = id.0; + w.set_txrq(true); + if frame.header().rtr() { + w.set_rtr(Rtr::REMOTE); + } + }); + } + + fn read_pending_mailbox(&self, idx: usize) -> Option { + if self.abort_by_index(idx) { + debug_assert!(idx < 3); + + let mb = self.0.tx(idx); + + let id = IdReg(mb.tir().read().0); + let mut data = [0xff; 8]; + data[0..4].copy_from_slice(&mb.tdlr().read().0.to_ne_bytes()); + data[4..8].copy_from_slice(&mb.tdhr().read().0.to_ne_bytes()); + let len = mb.tdtr().read().dlc(); + + Some(unwrap!(Frame::new(Header::new(id.id(), len, id.rtr()), &data))) + } else { + // Abort request failed because the frame was already sent (or being sent) on + // the bus. All mailboxes are now free. This can happen for small prescaler + // values (e.g. 1MBit/s bit timing with a source clock of 8MHz) or when an ISR + // has preempted the execution. + None + } + } + + /// Tries to abort a pending frame. Returns `true` when aborted. + fn abort_by_index(&self, idx: usize) -> bool { + self.0.tsr().write(|reg| reg.set_abrq(idx, true)); + + // Wait for the abort request to be finished. + loop { + let tsr = self.0.tsr().read(); + if false == tsr.abrq(idx) { + break tsr.txok(idx) == false; + } + } + } + + /// Attempts to abort the sending of a frame that is pending in a mailbox. + /// + /// If there is no frame in the provided mailbox, or its transmission succeeds before it can be + /// aborted, this function has no effect and returns `false`. + /// + /// If there is a frame in the provided mailbox, and it is canceled successfully, this function + /// returns `true`. + pub fn abort(&self, mailbox: Mailbox) -> bool { + // If the mailbox is empty, the value of TXOKx depends on what happened with the previous + // frame in that mailbox. Only call abort_by_index() if the mailbox is not empty. + let tsr = self.0.tsr().read(); + let mailbox_empty = match mailbox { + Mailbox::Mailbox0 => tsr.tme(0), + Mailbox::Mailbox1 => tsr.tme(1), + Mailbox::Mailbox2 => tsr.tme(2), + }; + if mailbox_empty { + false + } else { + self.abort_by_index(mailbox as usize) + } + } + + /// Returns `true` if no frame is pending for transmission. + pub fn is_idle(&self) -> bool { + let tsr = self.0.tsr().read(); + tsr.tme(0) && tsr.tme(1) && tsr.tme(2) + } + + pub fn receive_frame_available(&self) -> bool { + if self.0.rfr(0).read().fmp() != 0 { + true + } else if self.0.rfr(1).read().fmp() != 0 { + true + } else { + false + } + } + + pub fn receive_fifo(&self, fifo: RxFifo) -> Option { + // Generate timestamp as early as possible + #[cfg(feature = "time")] + let ts = embassy_time::Instant::now(); + + use crate::pac::can::vals::Ide; + + let fifo_idx = match fifo { + RxFifo::Fifo0 => 0usize, + RxFifo::Fifo1 => 1usize, + }; + let rfr = self.0.rfr(fifo_idx); + let fifo = self.0.rx(fifo_idx); + + // If there are no pending messages, there is nothing to do + if rfr.read().fmp() == 0 { + return None; + } + + let rir = fifo.rir().read(); + let id: embedded_can::Id = if rir.ide() == Ide::STANDARD { + unwrap!(embedded_can::StandardId::new(rir.stid())).into() + } else { + let stid = (rir.stid() & 0x7FF) as u32; + let exid = rir.exid() & 0x3FFFF; + let id = (stid << 18) | (exid); + unwrap!(embedded_can::ExtendedId::new(id)).into() + }; + let rdtr = fifo.rdtr().read(); + let data_len = rdtr.dlc(); + let rtr = rir.rtr() == stm32_metapac::can::vals::Rtr::REMOTE; + + #[cfg(not(feature = "time"))] + let ts = rdtr.time(); + + let mut data: [u8; 8] = [0; 8]; + data[0..4].copy_from_slice(&fifo.rdlr().read().0.to_ne_bytes()); + data[4..8].copy_from_slice(&fifo.rdhr().read().0.to_ne_bytes()); + + let frame = unwrap!(Frame::new(Header::new(id, data_len, rtr), &data)); + let envelope = Envelope { ts, frame }; + + rfr.modify(|v| v.set_rfom(true)); + + Some(envelope) + } +} + +/// Identifier of a CAN message. +/// +/// Can be either a standard identifier (11bit, Range: 0..0x3FF) or a +/// extendended identifier (29bit , Range: 0..0x1FFFFFFF). +/// +/// The `Ord` trait can be used to determine the frame’s priority this ID +/// belongs to. +/// Lower identifier values have a higher priority. Additionally standard frames +/// have a higher priority than extended frames and data frames have a higher +/// priority than remote frames. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct IdReg(u32); + +impl IdReg { + const STANDARD_SHIFT: u32 = 21; + + const EXTENDED_SHIFT: u32 = 3; + + const IDE_MASK: u32 = 0x0000_0004; + + const RTR_MASK: u32 = 0x0000_0002; + + /// Creates a new standard identifier (11bit, Range: 0..0x7FF) + /// + /// Panics for IDs outside the allowed range. + fn new_standard(id: StandardId) -> Self { + Self(u32::from(id.as_raw()) << Self::STANDARD_SHIFT) + } + + /// Creates a new extendended identifier (29bit , Range: 0..0x1FFFFFFF). + /// + /// Panics for IDs outside the allowed range. + fn new_extended(id: ExtendedId) -> IdReg { + Self(id.as_raw() << Self::EXTENDED_SHIFT | Self::IDE_MASK) + } + + fn from_register(reg: u32) -> IdReg { + Self(reg & 0xFFFF_FFFE) + } + + /// Returns the identifier. + fn to_id(self) -> Id { + if self.is_extended() { + Id::Extended(unsafe { ExtendedId::new_unchecked(self.0 >> Self::EXTENDED_SHIFT) }) + } else { + Id::Standard(unsafe { StandardId::new_unchecked((self.0 >> Self::STANDARD_SHIFT) as u16) }) + } + } + + /// Returns the identifier. + fn id(self) -> embedded_can::Id { + if self.is_extended() { + unwrap!(embedded_can::ExtendedId::new(self.0 >> Self::EXTENDED_SHIFT)).into() + } else { + unwrap!(embedded_can::StandardId::new((self.0 >> Self::STANDARD_SHIFT) as u16)).into() + } + } + + /// Returns `true` if the identifier is an extended identifier. + fn is_extended(self) -> bool { + self.0 & Self::IDE_MASK != 0 + } + + /// Returns `true` if the identifer is part of a remote frame (RTR bit set). + fn rtr(self) -> bool { + self.0 & Self::RTR_MASK != 0 + } +} + +impl From<&embedded_can::Id> for IdReg { + fn from(eid: &embedded_can::Id) -> Self { + match eid { + embedded_can::Id::Standard(id) => IdReg::new_standard(StandardId::new(id.as_raw()).unwrap()), + embedded_can::Id::Extended(id) => IdReg::new_extended(ExtendedId::new(id.as_raw()).unwrap()), + } + } +} + +impl From for embedded_can::Id { + fn from(idr: IdReg) -> Self { + idr.id() + } +} + +/// `IdReg` is ordered by priority. +impl Ord for IdReg { + fn cmp(&self, other: &Self) -> Ordering { + // When the IDs match, data frames have priority over remote frames. + let rtr = self.rtr().cmp(&other.rtr()).reverse(); + + let id_a = self.to_id(); + let id_b = other.to_id(); + match (id_a, id_b) { + (Id::Standard(a), Id::Standard(b)) => { + // Lower IDs have priority over higher IDs. + a.as_raw().cmp(&b.as_raw()).reverse().then(rtr) + } + (Id::Extended(a), Id::Extended(b)) => a.as_raw().cmp(&b.as_raw()).reverse().then(rtr), + (Id::Standard(a), Id::Extended(b)) => { + // Standard frames have priority over extended frames if their Base IDs match. + a.as_raw() + .cmp(&b.standard_id().as_raw()) + .reverse() + .then(Ordering::Greater) + } + (Id::Extended(a), Id::Standard(b)) => { + a.standard_id().as_raw().cmp(&b.as_raw()).reverse().then(Ordering::Less) + } + } + } +} + +impl PartialOrd for IdReg { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) enum RxFifo { + Fifo0, + Fifo1, +} diff --git a/embassy/embassy-stm32/src/can/common.rs b/embassy/embassy-stm32/src/can/common.rs new file mode 100644 index 0000000..a54b54f --- /dev/null +++ b/embassy/embassy-stm32/src/can/common.rs @@ -0,0 +1,52 @@ +use embassy_sync::channel::{DynamicReceiver, DynamicSender}; + +use super::enums::*; +use super::frame::*; + +pub(crate) struct ClassicBufferedRxInner { + pub rx_sender: DynamicSender<'static, Result>, +} +pub(crate) struct ClassicBufferedTxInner { + pub tx_receiver: DynamicReceiver<'static, Frame>, +} + +#[cfg(any(can_fdcan_v1, can_fdcan_h7))] + +pub(crate) struct FdBufferedRxInner { + pub rx_sender: DynamicSender<'static, Result>, +} + +#[cfg(any(can_fdcan_v1, can_fdcan_h7))] +pub(crate) struct FdBufferedTxInner { + pub tx_receiver: DynamicReceiver<'static, FdFrame>, +} + +/// Sender that can be used for sending CAN frames. +#[derive(Copy, Clone)] +pub struct BufferedCanSender { + pub(crate) tx_buf: embassy_sync::channel::DynamicSender<'static, Frame>, + pub(crate) waker: fn(), +} + +impl BufferedCanSender { + /// Async write frame to TX buffer. + pub fn try_write(&mut self, frame: Frame) -> Result<(), embassy_sync::channel::TrySendError> { + self.tx_buf.try_send(frame)?; + (self.waker)(); + Ok(()) + } + + /// Async write frame to TX buffer. + pub async fn write(&mut self, frame: Frame) { + self.tx_buf.send(frame).await; + (self.waker)(); + } + + /// Allows a poll_fn to poll until the channel is ready to write + pub fn poll_ready_to_send(&self, cx: &mut core::task::Context<'_>) -> core::task::Poll<()> { + self.tx_buf.poll_ready_to_send(cx) + } +} + +/// Receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver. +pub type BufferedCanReceiver = embassy_sync::channel::DynamicReceiver<'static, Result>; diff --git a/embassy/embassy-stm32/src/can/enums.rs b/embassy/embassy-stm32/src/can/enums.rs new file mode 100644 index 0000000..a5cca42 --- /dev/null +++ b/embassy/embassy-stm32/src/can/enums.rs @@ -0,0 +1,70 @@ +//! Enums shared between CAN controller types. + +/// Bus error +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BusError { + /// Bit stuffing error - more than 5 equal bits + Stuff, + /// Form error - A fixed format part of a received message has wrong format + Form, + /// The message transmitted by the FDCAN was not acknowledged by another node. + Acknowledge, + /// Bit0Error: During the transmission of a message the device wanted to send a dominant level + /// but the monitored bus value was recessive. + BitRecessive, + /// Bit1Error: During the transmission of a message the device wanted to send a recessive level + /// but the monitored bus value was dominant. + BitDominant, + /// The CRC check sum of a received message was incorrect. The CRC of an + /// incoming message does not match with the CRC calculated from the received data. + Crc, + /// A software error occured + Software, + /// The FDCAN is in Bus_Off state. + BusOff, + /// The FDCAN is in the Error_Passive state. + BusPassive, + /// At least one of error counter has reached the Error_Warning limit of 96. + BusWarning, +} + +/// Bus error modes. +/// +/// Contrary to the `BusError` enum which also includes last-seen acute protocol +/// errors, this enum includes only the mutually exclusive bus error modes. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BusErrorMode { + /// Error active mode (default). Controller will transmit an active error + /// frame upon protocol error. + ErrorActive, + /// Error passive mode. An error counter exceeded 127. Controller will + /// transmit a passive error frame upon protocol error. + ErrorPassive, + /// Bus off mode. The transmit error counter exceeded 255. Controller is not + /// participating in bus traffic. + BusOff, +} + +/// Frame Create Errors +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FrameCreateError { + /// Data in header does not match supplied. + NotEnoughData, + /// Invalid data length not 0-8 for Classic packet or valid for FD. + InvalidDataLength, + /// Invalid ID. + InvalidCanId, +} + +/// Error returned by `try_read` +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TryReadError { + /// Bus error + BusError(BusError), + /// Receive buffer is empty + Empty, +} diff --git a/embassy/embassy-stm32/src/can/fd/config.rs b/embassy/embassy-stm32/src/can/fd/config.rs new file mode 100644 index 0000000..68161ca --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/config.rs @@ -0,0 +1,475 @@ +//! Configuration for FDCAN Module +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +use core::num::{NonZeroU16, NonZeroU8}; + +/// Configures the bit timings. +/// +/// You can use to calculate the `btr` parameter. Enter +/// parameters as follows: +/// +/// - *Clock Rate*: The input clock speed to the CAN peripheral (*not* the CPU clock speed). +/// This is the clock rate of the peripheral bus the CAN peripheral is attached to (eg. APB1). +/// - *Sample Point*: Should normally be left at the default value of 87.5%. +/// - *SJW*: Should normally be left at the default value of 1. +/// +/// Then copy the `CAN_BUS_TIME` register value from the table and pass it as the `btr` +/// parameter to this method. +#[derive(Clone, Copy, Debug)] +pub struct NominalBitTiming { + /// Value by which the oscillator frequency is divided for generating the bit time quanta. The bit + /// time is built up from a multiple of this quanta. Valid values are 1 to 512. + pub prescaler: NonZeroU16, + /// Valid values are 1 to 128. + pub seg1: NonZeroU8, + /// Valid values are 1 to 255. + pub seg2: NonZeroU8, + /// Valid values are 1 to 128. + pub sync_jump_width: NonZeroU8, +} +impl NominalBitTiming { + #[inline] + pub(crate) fn nbrp(&self) -> u16 { + u16::from(self.prescaler) & 0x1FF + } + #[inline] + pub(crate) fn ntseg1(&self) -> u8 { + u8::from(self.seg1) + } + #[inline] + pub(crate) fn ntseg2(&self) -> u8 { + u8::from(self.seg2) & 0x7F + } + #[inline] + pub(crate) fn nsjw(&self) -> u8 { + u8::from(self.sync_jump_width) & 0x7F + } +} + +impl Default for NominalBitTiming { + #[inline] + fn default() -> Self { + // Kernel Clock 8MHz, Bit rate: 500kbit/s. Corresponds to a NBTP + // register value of 0x0600_0A03 + Self { + prescaler: NonZeroU16::new(1).unwrap(), + seg1: NonZeroU8::new(11).unwrap(), + seg2: NonZeroU8::new(4).unwrap(), + sync_jump_width: NonZeroU8::new(4).unwrap(), + } + } +} + +/// Configures the data bit timings for the FdCan Variable Bitrates. +/// This is not used when frame_transmit is set to anything other than AllowFdCanAndBRS. +#[derive(Clone, Copy, Debug)] +pub struct DataBitTiming { + /// Tranceiver Delay Compensation + pub transceiver_delay_compensation: bool, + /// The value by which the oscillator frequency is divided to generate the bit time quanta. The bit + /// time is built up from a multiple of this quanta. Valid values for the Baud Rate Prescaler are 1 + /// to 31. + pub prescaler: NonZeroU16, + /// Valid values are 1 to 31. + pub seg1: NonZeroU8, + /// Valid values are 1 to 15. + pub seg2: NonZeroU8, + /// Must always be smaller than DTSEG2, valid values are 1 to 15. + pub sync_jump_width: NonZeroU8, +} +impl DataBitTiming { + // #[inline] + // fn tdc(&self) -> u8 { + // let tsd = self.transceiver_delay_compensation as u8; + // //TODO: stm32g4 does not export the TDC field + // todo!() + // } + #[inline] + pub(crate) fn dbrp(&self) -> u8 { + (u16::from(self.prescaler) & 0x001F) as u8 + } + #[inline] + pub(crate) fn dtseg1(&self) -> u8 { + u8::from(self.seg1) & 0x1F + } + #[inline] + pub(crate) fn dtseg2(&self) -> u8 { + u8::from(self.seg2) & 0x0F + } + #[inline] + pub(crate) fn dsjw(&self) -> u8 { + u8::from(self.sync_jump_width) & 0x0F + } +} + +impl Default for DataBitTiming { + #[inline] + fn default() -> Self { + // Kernel Clock 8MHz, Bit rate: 500kbit/s. Corresponds to a DBTP + // register value of 0x0000_0A33 + Self { + transceiver_delay_compensation: false, + prescaler: NonZeroU16::new(1).unwrap(), + seg1: NonZeroU8::new(11).unwrap(), + seg2: NonZeroU8::new(4).unwrap(), + sync_jump_width: NonZeroU8::new(4).unwrap(), + } + } +} + +/// Configures which modes to use +/// Individual headers can contain a desire to be send via FdCan +/// or use Bit rate switching. But if this general setting does not allow +/// that, only classic CAN is used instead. +#[derive(Clone, Copy, Debug)] +pub enum FrameTransmissionConfig { + /// Only allow Classic CAN message Frames + ClassicCanOnly, + /// Allow (non-brs) FdCAN Message Frames + AllowFdCan, + /// Allow FdCAN Message Frames and allow Bit Rate Switching + AllowFdCanAndBRS, +} + +/// +#[derive(Clone, Copy, Debug)] +pub enum ClockDivider { + /// Divide by 1 + _1 = 0b0000, + /// Divide by 2 + _2 = 0b0001, + /// Divide by 4 + _4 = 0b0010, + /// Divide by 6 + _6 = 0b0011, + /// Divide by 8 + _8 = 0b0100, + /// Divide by 10 + _10 = 0b0101, + /// Divide by 12 + _12 = 0b0110, + /// Divide by 14 + _14 = 0b0111, + /// Divide by 16 + _16 = 0b1000, + /// Divide by 18 + _18 = 0b1001, + /// Divide by 20 + _20 = 0b1010, + /// Divide by 22 + _22 = 0b1011, + /// Divide by 24 + _24 = 0b1100, + /// Divide by 26 + _26 = 0b1101, + /// Divide by 28 + _28 = 0b1110, + /// Divide by 30 + _30 = 0b1111, +} + +/// Prescaler of the Timestamp counter +#[derive(Clone, Copy, Debug)] +pub enum TimestampPrescaler { + /// 1 + _1 = 1, + /// 2 + _2 = 2, + /// 3 + _3 = 3, + /// 4 + _4 = 4, + /// 5 + _5 = 5, + /// 6 + _6 = 6, + /// 7 + _7 = 7, + /// 8 + _8 = 8, + /// 9 + _9 = 9, + /// 10 + _10 = 10, + /// 11 + _11 = 11, + /// 12 + _12 = 12, + /// 13 + _13 = 13, + /// 14 + _14 = 14, + /// 15 + _15 = 15, + /// 16 + _16 = 16, +} + +/// Selects the source of the Timestamp counter +#[derive(Clone, Copy, Debug)] +pub enum TimestampSource { + /// The Timestamp counter is disabled + None, + /// Using the FdCan input clock as the Timstamp counter's source, + /// and using a specific prescaler + Prescaler(TimestampPrescaler), + /// Using TIM3 as a source + FromTIM3, +} + +/// How to handle frames in the global filter +#[derive(Clone, Copy, Debug)] +pub enum NonMatchingFilter { + /// Frames will go to Fifo0 when they do no match any specific filter + IntoRxFifo0 = 0b00, + /// Frames will go to Fifo1 when they do no match any specific filter + IntoRxFifo1 = 0b01, + /// Frames will be rejected when they do not match any specific filter + Reject = 0b11, +} + +/// How to handle frames which do not match a specific filter +#[derive(Clone, Copy, Debug)] +pub struct GlobalFilter { + /// How to handle non-matching standard frames + pub handle_standard_frames: NonMatchingFilter, + + /// How to handle non-matching extended frames + pub handle_extended_frames: NonMatchingFilter, + + /// How to handle remote standard frames + pub reject_remote_standard_frames: bool, + + /// How to handle remote extended frames + pub reject_remote_extended_frames: bool, +} +impl GlobalFilter { + /// Reject all non-matching and remote frames + pub const fn reject_all() -> Self { + Self { + handle_standard_frames: NonMatchingFilter::Reject, + handle_extended_frames: NonMatchingFilter::Reject, + reject_remote_standard_frames: true, + reject_remote_extended_frames: true, + } + } + + /// How to handle non-matching standard frames + pub const fn set_handle_standard_frames(mut self, filter: NonMatchingFilter) -> Self { + self.handle_standard_frames = filter; + self + } + /// How to handle non-matching exteded frames + pub const fn set_handle_extended_frames(mut self, filter: NonMatchingFilter) -> Self { + self.handle_extended_frames = filter; + self + } + /// How to handle remote standard frames + pub const fn set_reject_remote_standard_frames(mut self, filter: bool) -> Self { + self.reject_remote_standard_frames = filter; + self + } + /// How to handle remote extended frames + pub const fn set_reject_remote_extended_frames(mut self, filter: bool) -> Self { + self.reject_remote_extended_frames = filter; + self + } +} +impl Default for GlobalFilter { + #[inline] + fn default() -> Self { + Self { + handle_standard_frames: NonMatchingFilter::IntoRxFifo0, + handle_extended_frames: NonMatchingFilter::IntoRxFifo0, + reject_remote_standard_frames: false, + reject_remote_extended_frames: false, + } + } +} + +/// TX buffer operation mode +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum TxBufferMode { + /// TX FIFO operation - In this mode CAN frames are trasmitted strictly in write order. + Fifo, + /// TX priority queue operation - In this mode CAN frames are transmitted according to CAN priority. + Priority, +} + +impl From for crate::pac::can::vals::Tfqm { + fn from(value: TxBufferMode) -> Self { + match value { + TxBufferMode::Priority => Self::QUEUE, + TxBufferMode::Fifo => Self::FIFO, + } + } +} + +impl From for TxBufferMode { + fn from(value: crate::pac::can::vals::Tfqm) -> Self { + match value { + crate::pac::can::vals::Tfqm::QUEUE => Self::Priority, + crate::pac::can::vals::Tfqm::FIFO => Self::Fifo, + } + } +} + +/// FdCan Config Struct +#[derive(Clone, Copy, Debug)] +pub struct FdCanConfig { + /// Nominal Bit Timings + pub nbtr: NominalBitTiming, + /// (Variable) Data Bit Timings + pub dbtr: DataBitTiming, + /// Enables or disables automatic retransmission of messages + /// + /// If this is enabled, the CAN peripheral will automatically try to retransmit each frame + /// util it can be sent. Otherwise, it will try only once to send each frame. + /// + /// Automatic retransmission is enabled by default. + pub automatic_retransmit: bool, + /// Enabled or disables the pausing between transmissions + /// + /// This feature looses up burst transmissions coming from a single node and it protects against + /// "babbling idiot" scenarios where the application program erroneously requests too many + /// transmissions. + pub transmit_pause: bool, + /// Enabled or disables the pausing between transmissions + /// + /// This feature looses up burst transmissions coming from a single node and it protects against + /// "babbling idiot" scenarios where the application program erroneously requests too many + /// transmissions. + pub frame_transmit: FrameTransmissionConfig, + /// Non Isoe Mode + /// If this is set, the FDCAN uses the CAN FD frame format as specified by the Bosch CAN + /// FD Specification V1.0. + pub non_iso_mode: bool, + /// Edge Filtering: Two consecutive dominant tq required to detect an edge for hard synchronization + pub edge_filtering: bool, + /// Enables protocol exception handling + pub protocol_exception_handling: bool, + /// Sets the general clock divider for this FdCAN instance + pub clock_divider: ClockDivider, + /// Sets the timestamp source + pub timestamp_source: TimestampSource, + /// Configures the Global Filter + pub global_filter: GlobalFilter, + /// TX buffer mode (FIFO or priority queue) + pub tx_buffer_mode: TxBufferMode, +} + +impl FdCanConfig { + /// Configures the bit timings. + #[inline] + pub const fn set_nominal_bit_timing(mut self, btr: NominalBitTiming) -> Self { + self.nbtr = btr; + self + } + + /// Configures the bit timings. + #[inline] + pub const fn set_data_bit_timing(mut self, btr: DataBitTiming) -> Self { + self.dbtr = btr; + self + } + + /// Enables or disables automatic retransmission of messages + /// + /// If this is enabled, the CAN peripheral will automatically try to retransmit each frame + /// util it can be sent. Otherwise, it will try only once to send each frame. + /// + /// Automatic retransmission is enabled by default. + #[inline] + pub const fn set_automatic_retransmit(mut self, enabled: bool) -> Self { + self.automatic_retransmit = enabled; + self + } + + /// Enabled or disables the pausing between transmissions + /// + /// This feature looses up burst transmissions coming from a single node and it protects against + /// "babbling idiot" scenarios where the application program erroneously requests too many + /// transmissions. + #[inline] + pub const fn set_transmit_pause(mut self, enabled: bool) -> Self { + self.transmit_pause = enabled; + self + } + + /// If this is set, the FDCAN uses the CAN FD frame format as specified by the Bosch CAN + /// FD Specification V1.0. + #[inline] + pub const fn set_non_iso_mode(mut self, enabled: bool) -> Self { + self.non_iso_mode = enabled; + self + } + + /// Two consecutive dominant tq required to detect an edge for hard synchronization + #[inline] + pub const fn set_edge_filtering(mut self, enabled: bool) -> Self { + self.edge_filtering = enabled; + self + } + + /// Sets the allowed transmission types for messages. + #[inline] + pub const fn set_frame_transmit(mut self, fts: FrameTransmissionConfig) -> Self { + self.frame_transmit = fts; + self + } + + /// Enables protocol exception handling + #[inline] + pub const fn set_protocol_exception_handling(mut self, peh: bool) -> Self { + self.protocol_exception_handling = peh; + self + } + + /// Sets the general clock divider for this FdCAN instance + #[inline] + pub const fn set_clock_divider(mut self, div: ClockDivider) -> Self { + self.clock_divider = div; + self + } + + /// Sets the timestamp source + #[inline] + pub const fn set_timestamp_source(mut self, tss: TimestampSource) -> Self { + self.timestamp_source = tss; + self + } + + /// Sets the global filter settings + #[inline] + pub const fn set_global_filter(mut self, filter: GlobalFilter) -> Self { + self.global_filter = filter; + self + } + + /// Sets the TX buffer mode (FIFO or priority queue) + #[inline] + pub const fn set_tx_buffer_mode(mut self, txbm: TxBufferMode) -> Self { + self.tx_buffer_mode = txbm; + self + } +} + +impl Default for FdCanConfig { + #[inline] + fn default() -> Self { + Self { + nbtr: NominalBitTiming::default(), + dbtr: DataBitTiming::default(), + automatic_retransmit: true, + transmit_pause: false, + frame_transmit: FrameTransmissionConfig::ClassicCanOnly, + non_iso_mode: false, + edge_filtering: false, + protocol_exception_handling: true, + clock_divider: ClockDivider::_1, + timestamp_source: TimestampSource::None, + global_filter: GlobalFilter::default(), + tx_buffer_mode: TxBufferMode::Priority, + } + } +} diff --git a/embassy/embassy-stm32/src/can/fd/filter.rs b/embassy/embassy-stm32/src/can/fd/filter.rs new file mode 100644 index 0000000..2023a2e --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/filter.rs @@ -0,0 +1,379 @@ +//! Definition of Filter structs for FDCAN Module +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +use embedded_can::{ExtendedId, StandardId}; + +use crate::can::fd::message_ram; +pub use crate::can::fd::message_ram::{EXTENDED_FILTER_MAX, STANDARD_FILTER_MAX}; + +/// A Standard Filter +pub type StandardFilter = Filter; +/// An Extended Filter +pub type ExtendedFilter = Filter; + +impl Default for StandardFilter { + fn default() -> Self { + StandardFilter::disable() + } +} +impl Default for ExtendedFilter { + fn default() -> Self { + ExtendedFilter::disable() + } +} + +impl StandardFilter { + /// Accept all messages in FIFO 0 + pub fn accept_all_into_fifo0() -> StandardFilter { + StandardFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::StoreInFifo0, + } + } + + /// Accept all messages in FIFO 1 + pub fn accept_all_into_fifo1() -> StandardFilter { + StandardFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::StoreInFifo1, + } + } + + /// Reject all messages + pub fn reject_all() -> StandardFilter { + StandardFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::Reject, + } + } + + /// Disable the filter + pub fn disable() -> StandardFilter { + StandardFilter { + filter: FilterType::Disabled, + action: Action::Disable, + } + } +} + +impl ExtendedFilter { + /// Accept all messages in FIFO 0 + pub fn accept_all_into_fifo0() -> ExtendedFilter { + ExtendedFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::StoreInFifo0, + } + } + + /// Accept all messages in FIFO 1 + pub fn accept_all_into_fifo1() -> ExtendedFilter { + ExtendedFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::StoreInFifo1, + } + } + + /// Reject all messages + pub fn reject_all() -> ExtendedFilter { + ExtendedFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::Reject, + } + } + + /// Disable the filter + pub fn disable() -> ExtendedFilter { + ExtendedFilter { + filter: FilterType::Disabled, + action: Action::Disable, + } + } +} + +/// Filter Type +#[derive(Clone, Copy, Debug)] +pub enum FilterType +where + ID: Copy + Clone + core::fmt::Debug, + UNIT: Copy + Clone + core::fmt::Debug, +{ + /// Match with a range between two messages + Range { + /// First Id of the range + from: ID, + /// Last Id of the range + to: ID, + }, + /// Match with a bitmask + BitMask { + /// Filter of the bitmask + filter: UNIT, + /// Mask of the bitmask + mask: UNIT, + }, + /// Match with a single ID + DedicatedSingle(ID), + /// Match with one of two ID's + DedicatedDual(ID, ID), + /// Filter is disabled + Disabled, +} +impl From> for message_ram::enums::FilterType +where + ID: Copy + Clone + core::fmt::Debug, + UNIT: Copy + Clone + core::fmt::Debug, +{ + fn from(f: FilterType) -> Self { + match f { + FilterType::Range { to: _, from: _ } => Self::RangeFilter, + FilterType::BitMask { filter: _, mask: _ } => Self::ClassicFilter, + FilterType::DedicatedSingle(_) => Self::DualIdFilter, + FilterType::DedicatedDual(_, _) => Self::DualIdFilter, + FilterType::Disabled => Self::FilterDisabled, + } + } +} + +/// Filter Action +#[derive(Clone, Copy, Debug)] +pub enum Action { + /// No Action + Disable = 0b000, + /// Store an matching message in FIFO 0 + StoreInFifo0 = 0b001, + /// Store an matching message in FIFO 1 + StoreInFifo1 = 0b010, + /// Reject an matching message + Reject = 0b011, + /// Flag a matching message (But not store?!?) + FlagHighPrio = 0b100, + /// Flag a matching message as a High Priority message and store it in FIFO 0 + FlagHighPrioAndStoreInFifo0 = 0b101, + /// Flag a matching message as a High Priority message and store it in FIFO 1 + FlagHighPrioAndStoreInFifo1 = 0b110, +} +impl From for message_ram::enums::FilterElementConfig { + fn from(a: Action) -> Self { + match a { + Action::Disable => Self::DisableFilterElement, + Action::StoreInFifo0 => Self::StoreInFifo0, + Action::StoreInFifo1 => Self::StoreInFifo1, + Action::Reject => Self::Reject, + Action::FlagHighPrio => Self::SetPriority, + Action::FlagHighPrioAndStoreInFifo0 => Self::SetPriorityAndStoreInFifo0, + Action::FlagHighPrioAndStoreInFifo1 => Self::SetPriorityAndStoreInFifo1, + } + } +} + +/// Filter +#[derive(Clone, Copy, Debug)] +pub struct Filter +where + ID: Copy + Clone + core::fmt::Debug, + UNIT: Copy + Clone + core::fmt::Debug, +{ + /// How to match an incoming message + pub filter: FilterType, + /// What to do with a matching message + pub action: Action, +} + +/// Standard Filter Slot +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum StandardFilterSlot { + /// 0 + _0 = 0, + /// 1 + _1 = 1, + /// 2 + _2 = 2, + /// 3 + _3 = 3, + /// 4 + _4 = 4, + /// 5 + _5 = 5, + /// 6 + _6 = 6, + /// 7 + _7 = 7, + /// 8 + _8 = 8, + /// 9 + _9 = 9, + /// 10 + _10 = 10, + /// 11 + _11 = 11, + /// 12 + _12 = 12, + /// 13 + _13 = 13, + /// 14 + _14 = 14, + /// 15 + _15 = 15, + /// 16 + _16 = 16, + /// 17 + _17 = 17, + /// 18 + _18 = 18, + /// 19 + _19 = 19, + /// 20 + _20 = 20, + /// 21 + _21 = 21, + /// 22 + _22 = 22, + /// 23 + _23 = 23, + /// 24 + _24 = 24, + /// 25 + _25 = 25, + /// 26 + _26 = 26, + /// 27 + _27 = 27, +} +impl From for StandardFilterSlot { + fn from(u: u8) -> Self { + match u { + 0 => StandardFilterSlot::_0, + 1 => StandardFilterSlot::_1, + 2 => StandardFilterSlot::_2, + 3 => StandardFilterSlot::_3, + 4 => StandardFilterSlot::_4, + 5 => StandardFilterSlot::_5, + 6 => StandardFilterSlot::_6, + 7 => StandardFilterSlot::_7, + 8 => StandardFilterSlot::_8, + 9 => StandardFilterSlot::_9, + 10 => StandardFilterSlot::_10, + 11 => StandardFilterSlot::_11, + 12 => StandardFilterSlot::_12, + 13 => StandardFilterSlot::_13, + 14 => StandardFilterSlot::_14, + 15 => StandardFilterSlot::_15, + 16 => StandardFilterSlot::_16, + 17 => StandardFilterSlot::_17, + 18 => StandardFilterSlot::_18, + 19 => StandardFilterSlot::_19, + 20 => StandardFilterSlot::_20, + 21 => StandardFilterSlot::_21, + 22 => StandardFilterSlot::_22, + 23 => StandardFilterSlot::_23, + 24 => StandardFilterSlot::_24, + 25 => StandardFilterSlot::_25, + 26 => StandardFilterSlot::_26, + 27 => StandardFilterSlot::_27, + _ => panic!("Standard Filter Slot Too High!"), + } + } +} + +/// Extended Filter Slot +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ExtendedFilterSlot { + /// 0 + _0 = 0, + /// 1 + _1 = 1, + /// 2 + _2 = 2, + /// 3 + _3 = 3, + /// 4 + _4 = 4, + /// 5 + _5 = 5, + /// 6 + _6 = 6, + /// 7 + _7 = 7, +} +impl From for ExtendedFilterSlot { + fn from(u: u8) -> Self { + match u { + 0 => ExtendedFilterSlot::_0, + 1 => ExtendedFilterSlot::_1, + 2 => ExtendedFilterSlot::_2, + 3 => ExtendedFilterSlot::_3, + 4 => ExtendedFilterSlot::_4, + 5 => ExtendedFilterSlot::_5, + 6 => ExtendedFilterSlot::_6, + 7 => ExtendedFilterSlot::_7, + _ => panic!("Extended Filter Slot Too High!"), // Should be unreachable + } + } +} + +/// Enum over both Standard and Extended Filter ID's +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum FilterId { + /// Standard Filter Slots + Standard(StandardFilterSlot), + /// Extended Filter Slots + Extended(ExtendedFilterSlot), +} + +pub(crate) trait ActivateFilter +where + ID: Copy + Clone + core::fmt::Debug, + UNIT: Copy + Clone + core::fmt::Debug, +{ + fn activate(&mut self, f: Filter); + // fn read(&self) -> Filter; +} + +impl ActivateFilter for message_ram::StandardFilter { + fn activate(&mut self, f: Filter) { + let sft = f.filter.into(); + + let (sfid1, sfid2) = match f.filter { + FilterType::Range { to, from } => (to.as_raw(), from.as_raw()), + FilterType::DedicatedSingle(id) => (id.as_raw(), id.as_raw()), + FilterType::DedicatedDual(id1, id2) => (id1.as_raw(), id2.as_raw()), + FilterType::BitMask { filter, mask } => (filter, mask), + FilterType::Disabled => (0x0, 0x0), + }; + let sfec = f.action.into(); + self.write(|w| { + unsafe { w.sfid1().bits(sfid1).sfid2().bits(sfid2) } + .sft() + .set_filter_type(sft) + .sfec() + .set_filter_element_config(sfec) + }); + } + // fn read(&self) -> Filter { + // todo!() + // } +} +impl ActivateFilter for message_ram::ExtendedFilter { + fn activate(&mut self, f: Filter) { + let eft = f.filter.into(); + + let (efid1, efid2) = match f.filter { + FilterType::Range { to, from } => (to.as_raw(), from.as_raw()), + FilterType::DedicatedSingle(id) => (id.as_raw(), id.as_raw()), + FilterType::DedicatedDual(id1, id2) => (id1.as_raw(), id2.as_raw()), + FilterType::BitMask { filter, mask } => (filter, mask), + FilterType::Disabled => (0x0, 0x0), + }; + let efec = f.action.into(); + self.write(|w| { + unsafe { w.efid1().bits(efid1).efid2().bits(efid2) } + .eft() + .set_filter_type(eft) + .efec() + .set_filter_element_config(efec) + }); + } + // fn read(&self) -> Filter { + // todo!() + // } +} diff --git a/embassy/embassy-stm32/src/can/fd/message_ram/common.rs b/embassy/embassy-stm32/src/can/fd/message_ram/common.rs new file mode 100644 index 0000000..108c1a4 --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/message_ram/common.rs @@ -0,0 +1,134 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::enums::{ + BitRateSwitching, ErrorStateIndicator, FilterElementConfig, FilterType, FrameFormat, IdType, + RemoteTransmissionRequest, +}; +use super::generic; + +#[doc = "Reader of field `ID`"] +pub type ID_R = generic::R; + +#[doc = "Reader of field `RTR`"] +pub type RTR_R = generic::R; +impl RTR_R { + pub fn rtr(&self) -> RemoteTransmissionRequest { + match self.bits { + false => RemoteTransmissionRequest::TransmitDataFrame, + true => RemoteTransmissionRequest::TransmitRemoteFrame, + } + } + pub fn is_transmit_remote_frame(&self) -> bool { + *self == RemoteTransmissionRequest::TransmitRemoteFrame + } + pub fn is_transmit_data_frame(&self) -> bool { + *self == RemoteTransmissionRequest::TransmitDataFrame + } +} + +#[doc = "Reader of field `XTD`"] +pub type XTD_R = generic::R; +impl XTD_R { + pub fn id_type(&self) -> IdType { + match self.bits() { + false => IdType::StandardId, + true => IdType::ExtendedId, + } + } + pub fn is_standard_id(&self) -> bool { + *self == IdType::StandardId + } + pub fn is_exteded_id(&self) -> bool { + *self == IdType::ExtendedId + } +} + +#[doc = "Reader of field `ESI`"] +pub type ESI_R = generic::R; +impl ESI_R { + pub fn error_state(&self) -> ErrorStateIndicator { + match self.bits() { + false => ErrorStateIndicator::ErrorActive, + true => ErrorStateIndicator::ErrorPassive, + } + } + pub fn is_error_active(&self) -> bool { + *self == ErrorStateIndicator::ErrorActive + } + pub fn is_error_passive(&self) -> bool { + *self == ErrorStateIndicator::ErrorPassive + } +} + +#[doc = "Reader of field `DLC`"] +pub type DLC_R = generic::R; + +#[doc = "Reader of field `BRS`"] +pub type BRS_R = generic::R; +impl BRS_R { + pub fn bit_rate_switching(&self) -> BitRateSwitching { + match self.bits() { + true => BitRateSwitching::WithBRS, + false => BitRateSwitching::WithoutBRS, + } + } + pub fn is_with_brs(&self) -> bool { + *self == BitRateSwitching::WithBRS + } + pub fn is_without_brs(&self) -> bool { + *self == BitRateSwitching::WithoutBRS + } +} + +#[doc = "Reader of field `FDF`"] +pub type FDF_R = generic::R; +impl FDF_R { + pub fn frame_format(&self) -> FrameFormat { + match self.bits() { + false => FrameFormat::Classic, + true => FrameFormat::Fdcan, + } + } + pub fn is_classic_format(&self) -> bool { + *self == FrameFormat::Classic + } + pub fn is_fdcan_format(&self) -> bool { + *self == FrameFormat::Fdcan + } +} + +#[doc = "Reader of field `(X|S)FT`"] +pub type ESFT_R = generic::R; +impl ESFT_R { + #[doc = r"Gets the Filtertype"] + #[inline(always)] + pub fn to_filter_type(&self) -> FilterType { + match self.bits() { + 0b00 => FilterType::RangeFilter, + 0b01 => FilterType::DualIdFilter, + 0b10 => FilterType::ClassicFilter, + 0b11 => FilterType::FilterDisabled, + _ => unreachable!(), + } + } +} + +#[doc = "Reader of field `(E|S)FEC`"] +pub type ESFEC_R = generic::R; +impl ESFEC_R { + pub fn to_filter_element_config(&self) -> FilterElementConfig { + match self.bits() { + 0b000 => FilterElementConfig::DisableFilterElement, + 0b001 => FilterElementConfig::StoreInFifo0, + 0b010 => FilterElementConfig::StoreInFifo1, + 0b011 => FilterElementConfig::Reject, + 0b100 => FilterElementConfig::SetPriority, + 0b101 => FilterElementConfig::SetPriorityAndStoreInFifo0, + 0b110 => FilterElementConfig::SetPriorityAndStoreInFifo1, + _ => unimplemented!(), + } + } +} diff --git a/embassy/embassy-stm32/src/can/fd/message_ram/enums.rs b/embassy/embassy-stm32/src/can/fd/message_ram/enums.rs new file mode 100644 index 0000000..0ec5e0f --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/message_ram/enums.rs @@ -0,0 +1,233 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +/// Datalength is the message length generalised over +/// the Standard (Classic) and FDCAN message types + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum DataLength { + Classic(u8), + Fdcan(u8), +} +impl DataLength { + /// Creates a DataLength type + /// + /// Uses the byte length and Type of frame as input + pub fn new(len: u8, ff: FrameFormat) -> DataLength { + match ff { + FrameFormat::Classic => match len { + 0..=8 => DataLength::Classic(len), + _ => panic!("DataLength > 8"), + }, + FrameFormat::Fdcan => match len { + 0..=64 => DataLength::Fdcan(len), + _ => panic!("DataLength > 64"), + }, + } + } + /// Specialised function to create classic frames + pub fn new_classic(len: u8) -> DataLength { + Self::new(len, FrameFormat::Classic) + } + /// Specialised function to create FDCAN frames + pub fn new_fdcan(len: u8) -> DataLength { + Self::new(len, FrameFormat::Fdcan) + } + + /// returns the length in bytes + pub fn len(&self) -> u8 { + match self { + DataLength::Classic(l) | DataLength::Fdcan(l) => *l, + } + } + + pub(crate) fn dlc(&self) -> u8 { + match self { + DataLength::Classic(l) => *l, + // See RM0433 Rev 7 Table 475. DLC coding + DataLength::Fdcan(l) => match l { + 0..=8 => *l, + 9..=12 => 9, + 13..=16 => 10, + 17..=20 => 11, + 21..=24 => 12, + 25..=32 => 13, + 33..=48 => 14, + 49..=64 => 15, + _ => panic!("DataLength > 64"), + }, + } + } +} +impl From for FrameFormat { + fn from(dl: DataLength) -> FrameFormat { + match dl { + DataLength::Classic(_) => FrameFormat::Classic, + DataLength::Fdcan(_) => FrameFormat::Fdcan, + } + } +} + +/// Wheter or not to generate an Tx Event +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Event { + /// Do not generate an Tx Event + NoEvent, + /// Generate an Tx Event with a specified ID + Event(u8), +} + +impl From for EventControl { + fn from(e: Event) -> Self { + match e { + Event::NoEvent => EventControl::DoNotStore, + Event::Event(_) => EventControl::Store, + } + } +} + +impl From> for Event { + fn from(mm: Option) -> Self { + match mm { + None => Event::NoEvent, + Some(mm) => Event::Event(mm), + } + } +} + +impl From for Option { + fn from(e: Event) -> Option { + match e { + Event::NoEvent => None, + Event::Event(mm) => Some(mm), + } + } +} + +/// TODO +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ErrorStateIndicator { + /// TODO + ErrorActive = 0, + /// TODO + ErrorPassive = 1, +} +impl From for bool { + #[inline(always)] + fn from(e: ErrorStateIndicator) -> Self { + e as u8 != 0 + } +} + +/// Type of frame, standard (classic) or FdCAN +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FrameFormat { + Classic = 0, + Fdcan = 1, +} +impl From for bool { + #[inline(always)] + fn from(e: FrameFormat) -> Self { + e as u8 != 0 + } +} + +/// Type of Id, Standard or Extended +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum IdType { + /// Standard ID + StandardId = 0, + /// Extended ID + ExtendedId = 1, +} +impl From for bool { + #[inline(always)] + fn from(e: IdType) -> Self { + e as u8 != 0 + } +} + +/// Whether the frame contains data or requests data +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum RemoteTransmissionRequest { + /// Frame contains data + TransmitDataFrame = 0, + /// frame does not contain data + TransmitRemoteFrame = 1, +} +impl From for bool { + #[inline(always)] + fn from(e: RemoteTransmissionRequest) -> Self { + e as u8 != 0 + } +} + +/// Whether BitRateSwitching should be or was enabled +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum BitRateSwitching { + /// disable bit rate switching + WithoutBRS = 0, + /// enable bit rate switching + WithBRS = 1, +} +impl From for bool { + #[inline(always)] + fn from(e: BitRateSwitching) -> Self { + e as u8 != 0 + } +} + +/// Whether to store transmit Events +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum EventControl { + /// do not store an tx event + DoNotStore, + /// store transmit events + Store, +} +impl From for bool { + #[inline(always)] + fn from(e: EventControl) -> Self { + e as u8 != 0 + } +} + +/// If an received message matched any filters +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FilterFrameMatch { + /// This did match filter + DidMatch(u8), + /// This received frame did not match any specific filters + DidNotMatch, +} + +/// Type of filter to be used +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FilterType { + /// Filter uses the range between two id's + RangeFilter = 0b00, + /// The filter matches on two specific id's (or one ID checked twice) + DualIdFilter = 0b01, + /// Filter is using a bitmask + ClassicFilter = 0b10, + /// Filter is disabled + FilterDisabled = 0b11, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FilterElementConfig { + /// Filter is disabled + DisableFilterElement = 0b000, + /// Store a matching message in FIFO 0 + StoreInFifo0 = 0b001, + /// Store a matching message in FIFO 1 + StoreInFifo1 = 0b010, + /// Reject a matching message + Reject = 0b011, + /// Flag that a priority message has been received, *But do note store!*?? + SetPriority = 0b100, + /// Flag and store message in FIFO 0 + SetPriorityAndStoreInFifo0 = 0b101, + /// Flag and store message in FIFO 1 + SetPriorityAndStoreInFifo1 = 0b110, + //_Unused = 0b111, +} diff --git a/embassy/embassy-stm32/src/can/fd/message_ram/extended_filter.rs b/embassy/embassy-stm32/src/can/fd/message_ram/extended_filter.rs new file mode 100644 index 0000000..453e905 --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/message_ram/extended_filter.rs @@ -0,0 +1,136 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::common::{ESFEC_R, ESFT_R}; +use super::enums::{FilterElementConfig, FilterType}; +use super::generic; + +#[doc = "Reader of register ExtendedFilter"] +pub(crate) type R = generic::R; +#[doc = "Writer for register ExtendedFilter"] +pub(crate) type W = generic::W; +#[doc = "Register ExtendedFilter `reset()`'s"] +impl generic::ResetValue for super::ExtendedFilter { + type Type = super::ExtendedFilterType; + #[inline(always)] + fn reset_value() -> Self::Type { + // Sets filter element to Disabled + [0x0, 0x0] + } +} + +#[doc = "Reader of field `EFID2`"] +pub(crate) type EFID2_R = generic::R; +#[doc = "Write proxy for field `EFID2`"] +pub(crate) struct EFID2_W<'a> { + w: &'a mut W, +} +impl<'a> EFID2_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u32) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x1FFFFFFF)) | ((value as u32) & 0x1FFFFFFF); + self.w + } +} + +#[doc = "Reader of field `EFID1`"] +pub(crate) type EFID1_R = generic::R; +#[doc = "Write proxy for field `EFID1`"] +pub(crate) struct EFID1_W<'a> { + w: &'a mut W, +} +impl<'a> EFID1_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u32) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x1FFFFFFF)) | ((value as u32) & 0x1FFFFFFF); + self.w + } +} + +#[doc = "Write proxy for field `EFEC`"] +pub(crate) struct EFEC_W<'a> { + w: &'a mut W, +} +impl<'a> EFEC_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x07 << 29)) | (((value as u32) & 0x07) << 29); + self.w + } + #[doc = r"Sets the field according to FilterElementConfig"] + #[inline(always)] + pub fn set_filter_element_config(self, fec: FilterElementConfig) -> &'a mut W { + //SAFETY: FilterElementConfig only be valid options + unsafe { self.bits(fec as u8) } + } +} + +#[doc = "Write proxy for field `EFT`"] +pub(crate) struct EFT_W<'a> { + w: &'a mut W, +} +impl<'a> EFT_W<'a> { + #[doc = r"Sets the field according the FilterType"] + #[inline(always)] + pub fn set_filter_type(self, filter: FilterType) -> &'a mut W { + //SAFETY: FilterType only be valid options + unsafe { self.bits(filter as u8) } + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x03 << 30)) | (((value as u32) & 0x03) << 30); + self.w + } +} + +impl R { + #[doc = "Byte 0 - Bits 0:28 - EFID1"] + #[inline(always)] + pub fn sfid1(&self) -> EFID1_R { + EFID1_R::new(((self.bits[0]) & 0x1FFFFFFF) as u32) + } + #[doc = "Byte 0 - Bits 29:31 - EFEC"] + #[inline(always)] + pub fn efec(&self) -> ESFEC_R { + ESFEC_R::new(((self.bits[0] >> 29) & 0x07) as u8) + } + #[doc = "Byte 1 - Bits 0:28 - EFID2"] + #[inline(always)] + pub fn sfid2(&self) -> EFID2_R { + EFID2_R::new(((self.bits[1]) & 0x1FFFFFFF) as u32) + } + #[doc = "Byte 1 - Bits 30:31 - EFT"] + #[inline(always)] + pub fn eft(&self) -> ESFT_R { + ESFT_R::new(((self.bits[1] >> 30) & 0x03) as u8) + } +} +impl W { + #[doc = "Byte 0 - Bits 0:28 - EFID1"] + #[inline(always)] + pub fn efid1(&mut self) -> EFID1_W { + EFID1_W { w: self } + } + #[doc = "Byte 0 - Bits 29:31 - EFEC"] + #[inline(always)] + pub fn efec(&mut self) -> EFEC_W { + EFEC_W { w: self } + } + #[doc = "Byte 1 - Bits 0:28 - EFID2"] + #[inline(always)] + pub fn efid2(&mut self) -> EFID2_W { + EFID2_W { w: self } + } + #[doc = "Byte 1 - Bits 30:31 - EFT"] + #[inline(always)] + pub fn eft(&mut self) -> EFT_W { + EFT_W { w: self } + } +} diff --git a/embassy/embassy-stm32/src/can/fd/message_ram/generic.rs b/embassy/embassy-stm32/src/can/fd/message_ram/generic.rs new file mode 100644 index 0000000..1a5e121 --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/message_ram/generic.rs @@ -0,0 +1,168 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +use core::marker; + +///This trait shows that register has `read` method +/// +///Registers marked with `Writable` can be also `modify`'ed +pub trait Readable {} + +///This trait shows that register has `write`, `write_with_zero` and `reset` method +/// +///Registers marked with `Readable` can be also `modify`'ed +pub trait Writable {} + +///Reset value of the register +/// +///This value is initial value for `write` method. +///It can be also directly writed to register by `reset` method. +pub trait ResetValue { + ///Register size + type Type; + ///Reset value of the register + fn reset_value() -> Self::Type; +} + +///This structure provides volatile access to register +pub struct Reg { + register: vcell::VolatileCell, + _marker: marker::PhantomData, +} + +unsafe impl Send for Reg {} + +impl Reg +where + Self: Readable, + U: Copy, +{ + ///Reads the contents of `Readable` register + /// + ///You can read the contents of a register in such way: + ///```ignore + ///let bits = periph.reg.read().bits(); + ///``` + ///or get the content of a particular field of a register. + ///```ignore + ///let reader = periph.reg.read(); + ///let bits = reader.field1().bits(); + ///let flag = reader.field2().bit_is_set(); + ///``` + #[inline(always)] + pub fn read(&self) -> R { + R { + bits: self.register.get(), + _reg: marker::PhantomData, + } + } +} + +impl Reg +where + Self: ResetValue + Writable, + U: Copy, +{ + ///Writes the reset value to `Writable` register + /// + ///Resets the register to its initial state + #[inline(always)] + pub fn reset(&self) { + self.register.set(Self::reset_value()) + } +} + +impl Reg +where + Self: ResetValue + Writable, + U: Copy, +{ + ///Writes bits to `Writable` register + /// + ///You can write raw bits into a register: + ///```ignore + ///periph.reg.write(|w| unsafe { w.bits(rawbits) }); + ///``` + ///or write only the fields you need: + ///```ignore + ///periph.reg.write(|w| w + /// .field1().bits(newfield1bits) + /// .field2().set_bit() + /// .field3().variant(VARIANT) + ///); + ///``` + ///Other fields will have reset value. + #[inline(always)] + pub fn write(&self, f: F) + where + F: FnOnce(&mut W) -> &mut W, + { + self.register.set( + f(&mut W { + bits: Self::reset_value(), + _reg: marker::PhantomData, + }) + .bits, + ); + } +} + +///Register/field reader +/// +///Result of the [`read`](Reg::read) method of a register. +///Also it can be used in the [`modify`](Reg::read) method +pub struct R { + pub(crate) bits: U, + _reg: marker::PhantomData, +} + +impl R +where + U: Copy, +{ + ///Create new instance of reader + #[inline(always)] + pub(crate) fn new(bits: U) -> Self { + Self { + bits, + _reg: marker::PhantomData, + } + } + ///Read raw bits from register/field + #[inline(always)] + pub fn bits(&self) -> U { + self.bits + } +} + +impl PartialEq for R +where + U: PartialEq, + FI: Copy + Into, +{ + #[inline(always)] + fn eq(&self, other: &FI) -> bool { + self.bits.eq(&(*other).into()) + } +} + +impl R { + ///Value of the field as raw bits + #[inline(always)] + pub fn bit(&self) -> bool { + self.bits + } + ///Returns `true` if the bit is clear (0) + #[inline(always)] + pub fn bit_is_clear(&self) -> bool { + !self.bit() + } +} + +///Register writer +/// +///Used as an argument to the closures in the [`write`](Reg::write) and [`modify`](Reg::modify) methods of the register +pub struct W { + ///Writable bits + pub(crate) bits: U, + _reg: marker::PhantomData, +} diff --git a/embassy/embassy-stm32/src/can/fd/message_ram/mod.rs b/embassy/embassy-stm32/src/can/fd/message_ram/mod.rs new file mode 100644 index 0000000..040a999 --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/message_ram/mod.rs @@ -0,0 +1,150 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +use volatile_register::RW; + +pub(crate) mod common; +pub(crate) mod enums; +pub(crate) mod generic; + +/// Number of Receive Fifos configured by this module +pub const RX_FIFOS_MAX: u8 = 2; +/// Number of Receive Messages per RxFifo configured by this module +pub const RX_FIFO_MAX: u8 = 3; +/// Number of Transmit Messages configured by this module +pub const TX_FIFO_MAX: u8 = 3; +/// Number of Transmit Events configured by this module +pub const TX_EVENT_MAX: u8 = 3; +/// Number of Standard Filters configured by this module +pub const STANDARD_FILTER_MAX: u8 = 28; +/// Number of Extended Filters configured by this module +pub const EXTENDED_FILTER_MAX: u8 = 8; + +/// MessageRam Overlay +#[repr(C)] +pub struct RegisterBlock { + pub(crate) filters: Filters, + pub(crate) receive: [Receive; RX_FIFOS_MAX as usize], + pub(crate) transmit: Transmit, +} +impl RegisterBlock { + pub fn reset(&mut self) { + self.filters.reset(); + self.receive[0].reset(); + self.receive[1].reset(); + self.transmit.reset(); + } +} + +#[repr(C)] +pub(crate) struct Filters { + pub(crate) flssa: [StandardFilter; STANDARD_FILTER_MAX as usize], + pub(crate) flesa: [ExtendedFilter; EXTENDED_FILTER_MAX as usize], +} +impl Filters { + pub fn reset(&mut self) { + for sf in &mut self.flssa { + sf.reset(); + } + for ef in &mut self.flesa { + ef.reset(); + } + } +} + +#[repr(C)] +pub(crate) struct Receive { + pub(crate) fxsa: [RxFifoElement; RX_FIFO_MAX as usize], +} +impl Receive { + pub fn reset(&mut self) { + for fe in &mut self.fxsa { + fe.reset(); + } + } +} + +#[repr(C)] +pub(crate) struct Transmit { + pub(crate) efsa: [TxEventElement; TX_EVENT_MAX as usize], + pub(crate) tbsa: [TxBufferElement; TX_FIFO_MAX as usize], +} +impl Transmit { + pub fn reset(&mut self) { + for ee in &mut self.efsa { + ee.reset(); + } + for be in &mut self.tbsa { + be.reset(); + } + } +} + +pub(crate) mod standard_filter; +pub(crate) type StandardFilterType = u32; +pub(crate) type StandardFilter = generic::Reg; +pub(crate) struct _StandardFilter; +impl generic::Readable for StandardFilter {} +impl generic::Writable for StandardFilter {} + +pub(crate) mod extended_filter; +pub(crate) type ExtendedFilterType = [u32; 2]; +pub(crate) type ExtendedFilter = generic::Reg; +pub(crate) struct _ExtendedFilter; +impl generic::Readable for ExtendedFilter {} +impl generic::Writable for ExtendedFilter {} + +pub(crate) mod txevent_element; +pub(crate) type TxEventElementType = [u32; 2]; +pub(crate) type TxEventElement = generic::Reg; +pub(crate) struct _TxEventElement; +impl generic::Readable for TxEventElement {} +impl generic::Writable for TxEventElement {} + +pub(crate) mod rxfifo_element; +#[repr(C)] +pub(crate) struct RxFifoElement { + pub(crate) header: RxFifoElementHeader, + pub(crate) data: [RW; 16], +} +impl RxFifoElement { + pub(crate) fn reset(&mut self) { + self.header.reset(); + for byte in self.data.iter_mut() { + unsafe { byte.write(0) }; + } + } +} +pub(crate) type RxFifoElementHeaderType = [u32; 2]; +pub(crate) type RxFifoElementHeader = generic::Reg; +pub(crate) struct _RxFifoElement; +impl generic::Readable for RxFifoElementHeader {} +impl generic::Writable for RxFifoElementHeader {} + +pub(crate) mod txbuffer_element; +#[repr(C)] +pub(crate) struct TxBufferElement { + pub(crate) header: TxBufferElementHeader, + pub(crate) data: [RW; 16], +} +impl TxBufferElement { + pub(crate) fn reset(&mut self) { + self.header.reset(); + for byte in self.data.iter_mut() { + unsafe { byte.write(0) }; + } + } +} +pub(crate) type TxBufferElementHeader = generic::Reg; +pub(crate) type TxBufferElementHeaderType = [u32; 2]; +pub(crate) struct _TxBufferElement; +impl generic::Readable for TxBufferElementHeader {} +impl generic::Writable for TxBufferElementHeader {} + +// Ensure the RegisterBlock is the same size as on pg 1957 of RM0440. +static_assertions::assert_eq_size!(Filters, [u32; 28 + 16]); +static_assertions::assert_eq_size!(Receive, [u32; 54]); +static_assertions::assert_eq_size!(Transmit, [u32; 6 + 54]); +static_assertions::assert_eq_size!( + RegisterBlock, + [u32; 28 /*Standard Filters*/ +16 /*Extended Filters*/ +54 /*RxFifo0*/ +54 /*RxFifo1*/ +6 /*TxEvent*/ +54 /*TxFifo */] +); diff --git a/embassy/embassy-stm32/src/can/fd/message_ram/rxfifo_element.rs b/embassy/embassy-stm32/src/can/fd/message_ram/rxfifo_element.rs new file mode 100644 index 0000000..48fc3a0 --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/message_ram/rxfifo_element.rs @@ -0,0 +1,122 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::common::{BRS_R, DLC_R, ESI_R, FDF_R, ID_R, RTR_R, XTD_R}; +use super::enums::{DataLength, FilterFrameMatch, FrameFormat}; +use super::generic; + +#[doc = "Reader of register RxFifoElement"] +pub(crate) type R = generic::R; +// #[doc = "Writer for register ExtendedFilter"] +// pub(crate) type W = generic::W; +#[doc = "Register ExtendedFilter `reset()`'s"] +impl generic::ResetValue for super::RxFifoElementHeader { + type Type = super::RxFifoElementHeaderType; + #[inline(always)] + fn reset_value() -> Self::Type { + [0x0, 0x0] + } +} + +#[doc = "Reader of field `RXTS`"] +pub(crate) type RXTS_R = generic::R; + +#[doc = "Reader of field `FIDX`"] +pub(crate) type FIDX_R = generic::R; + +pub(crate) struct _ANMF; +#[doc = "Reader of field `ANMF`"] +pub(crate) type ANMF_R = generic::R; +impl ANMF_R { + pub fn is_matching_frame(&self) -> bool { + self.bit_is_clear() + } +} + +impl R { + #[doc = "Byte 0 - Bits 0:28 - ID"] + #[inline(always)] + pub fn id(&self) -> ID_R { + ID_R::new(((self.bits[0]) & 0x1FFFFFFF) as u32) + } + #[doc = "Byte 0 - Bit 29 - RTR"] + #[inline(always)] + pub fn rtr(&self) -> RTR_R { + RTR_R::new(((self.bits[0] >> 29) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - XTD"] + #[inline(always)] + pub fn xtd(&self) -> XTD_R { + XTD_R::new(((self.bits[0] >> 30) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - ESI"] + #[inline(always)] + pub fn esi(&self) -> ESI_R { + ESI_R::new(((self.bits[0] >> 31) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 0:15 - RXTS"] + #[inline(always)] + pub fn txts(&self) -> RXTS_R { + RXTS_R::new(((self.bits[1]) & 0xFFFF) as u16) + } + #[doc = "Byte 1 - Bits 16:19 - DLC"] + #[inline(always)] + pub fn dlc(&self) -> DLC_R { + DLC_R::new(((self.bits[1] >> 16) & 0x0F) as u8) + } + #[doc = "Byte 1 - Bits 20 - BRS"] + #[inline(always)] + pub fn brs(&self) -> BRS_R { + BRS_R::new(((self.bits[1] >> 20) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 20 - FDF"] + #[inline(always)] + pub fn fdf(&self) -> FDF_R { + FDF_R::new(((self.bits[1] >> 21) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 24:30 - FIDX"] + #[inline(always)] + pub fn fidx(&self) -> FIDX_R { + FIDX_R::new(((self.bits[1] >> 24) & 0xFF) as u8) + } + #[doc = "Byte 1 - Bits 31 - ANMF"] + #[inline(always)] + pub fn anmf(&self) -> ANMF_R { + ANMF_R::new(((self.bits[1] >> 31) & 0x01) != 0) + } + pub fn to_data_length(&self) -> DataLength { + let dlc = self.dlc().bits(); + let ff = self.fdf().frame_format(); + let len = if ff == FrameFormat::Fdcan { + // See RM0433 Rev 7 Table 475. DLC coding + match dlc { + 0..=8 => dlc, + 9 => 12, + 10 => 16, + 11 => 20, + 12 => 24, + 13 => 32, + 14 => 48, + 15 => 64, + _ => panic!("DLC > 15"), + } + } else { + match dlc { + 0..=8 => dlc, + 9..=15 => 8, + _ => panic!("DLC > 15"), + } + }; + DataLength::new(len, ff) + } + pub fn to_filter_match(&self) -> FilterFrameMatch { + if self.anmf().is_matching_frame() { + FilterFrameMatch::DidMatch(self.fidx().bits()) + } else { + FilterFrameMatch::DidNotMatch + } + } +} diff --git a/embassy/embassy-stm32/src/can/fd/message_ram/standard_filter.rs b/embassy/embassy-stm32/src/can/fd/message_ram/standard_filter.rs new file mode 100644 index 0000000..3a3bbcf --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/message_ram/standard_filter.rs @@ -0,0 +1,136 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::common::{ESFEC_R, ESFT_R}; +use super::enums::{FilterElementConfig, FilterType}; +use super::generic; + +#[doc = "Reader of register StandardFilter"] +pub(crate) type R = generic::R; +#[doc = "Writer for register StandardFilter"] +pub(crate) type W = generic::W; +#[doc = "Register StandardFilter `reset()`'s with value 0xC0000"] +impl generic::ResetValue for super::StandardFilter { + type Type = super::StandardFilterType; + #[inline(always)] + fn reset_value() -> Self::Type { + // Sets filter element to Disabled + 0xC000 + } +} + +#[doc = "Reader of field `SFID2`"] +pub(crate) type SFID2_R = generic::R; +#[doc = "Write proxy for field `SFID2`"] +pub(crate) struct SFID2_W<'a> { + w: &'a mut W, +} +impl<'a> SFID2_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u16) -> &'a mut W { + self.w.bits = (self.w.bits & !(0x07ff)) | ((value as u32) & 0x07ff); + self.w + } +} + +#[doc = "Reader of field `SFID1`"] +pub(crate) type SFID1_R = generic::R; +#[doc = "Write proxy for field `SFID1`"] +pub(crate) struct SFID1_W<'a> { + w: &'a mut W, +} +impl<'a> SFID1_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u16) -> &'a mut W { + self.w.bits = (self.w.bits & !(0x07ff << 16)) | (((value as u32) & 0x07ff) << 16); + self.w + } +} + +#[doc = "Write proxy for field `SFEC`"] +pub(crate) struct SFEC_W<'a> { + w: &'a mut W, +} +impl<'a> SFEC_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits = (self.w.bits & !(0x07 << 27)) | (((value as u32) & 0x07) << 27); + self.w + } + #[doc = r"Sets the field according to FilterElementConfig"] + #[inline(always)] + pub fn set_filter_element_config(self, fec: FilterElementConfig) -> &'a mut W { + //SAFETY: FilterElementConfig only be valid options + unsafe { self.bits(fec as u8) } + } +} + +#[doc = "Write proxy for field `SFT`"] +pub(crate) struct SFT_W<'a> { + w: &'a mut W, +} +impl<'a> SFT_W<'a> { + #[doc = r"Sets the field according the FilterType"] + #[inline(always)] + pub fn set_filter_type(self, filter: FilterType) -> &'a mut W { + //SAFETY: FilterType only be valid options + unsafe { self.bits(filter as u8) } + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits = (self.w.bits & !(0x03 << 30)) | (((value as u32) & 0x03) << 30); + self.w + } +} + +impl R { + #[doc = "Bits 0:10 - SFID2"] + #[inline(always)] + pub fn sfid2(&self) -> SFID2_R { + SFID2_R::new((self.bits & 0x07ff) as u16) + } + #[doc = "Bits 16:26 - SFID1"] + #[inline(always)] + pub fn sfid1(&self) -> SFID1_R { + SFID1_R::new(((self.bits >> 16) & 0x07ff) as u16) + } + #[doc = "Bits 27:29 - SFEC"] + #[inline(always)] + pub fn sfec(&self) -> ESFEC_R { + ESFEC_R::new(((self.bits >> 27) & 0x07) as u8) + } + #[doc = "Bits 30:31 - SFT"] + #[inline(always)] + pub fn sft(&self) -> ESFT_R { + ESFT_R::new(((self.bits >> 30) & 0x03) as u8) + } +} +impl W { + #[doc = "Bits 0:10 - SFID2"] + #[inline(always)] + pub fn sfid2(&mut self) -> SFID2_W { + SFID2_W { w: self } + } + #[doc = "Bits 16:26 - SFID1"] + #[inline(always)] + pub fn sfid1(&mut self) -> SFID1_W { + SFID1_W { w: self } + } + #[doc = "Bits 27:29 - SFEC"] + #[inline(always)] + pub fn sfec(&mut self) -> SFEC_W { + SFEC_W { w: self } + } + #[doc = "Bits 30:31 - SFT"] + #[inline(always)] + pub fn sft(&mut self) -> SFT_W { + SFT_W { w: self } + } +} diff --git a/embassy/embassy-stm32/src/can/fd/message_ram/txbuffer_element.rs b/embassy/embassy-stm32/src/can/fd/message_ram/txbuffer_element.rs new file mode 100644 index 0000000..455406a --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/message_ram/txbuffer_element.rs @@ -0,0 +1,433 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::common::{BRS_R, DLC_R, ESI_R, FDF_R, ID_R, RTR_R, XTD_R}; +use super::enums::{ + BitRateSwitching, DataLength, ErrorStateIndicator, Event, EventControl, FrameFormat, IdType, + RemoteTransmissionRequest, +}; +use super::generic; + +#[doc = "Reader of register TxBufferElement"] +pub(crate) type R = generic::R; +#[doc = "Writer for register TxBufferElement"] +pub(crate) type W = generic::W; +impl generic::ResetValue for super::TxBufferElementHeader { + type Type = super::TxBufferElementHeaderType; + + #[allow(dead_code)] + #[inline(always)] + fn reset_value() -> Self::Type { + [0; 2] + } +} + +#[doc = "Write proxy for field `ESI`"] +pub(crate) struct ESI_W<'a> { + w: &'a mut W, +} +impl<'a> ESI_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_error_indicator(self, esi: ErrorStateIndicator) -> &'a mut W { + self.bit(esi as u8 != 0) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x01 << 31)) | (((value as u32) & 0x01) << 31); + self.w + } +} + +#[doc = "Write proxy for field `XTD`"] +pub(crate) struct XTD_W<'a> { + w: &'a mut W, +} +impl<'a> XTD_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_id_type(self, idt: IdType) -> &'a mut W { + self.bit(idt as u8 != 0) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x01 << 30)) | (((value as u32) & 0x01) << 30); + self.w + } +} + +#[doc = "Write proxy for field `RTR`"] +pub(crate) struct RTR_W<'a> { + w: &'a mut W, +} +impl<'a> RTR_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_rtr(self, rtr: RemoteTransmissionRequest) -> &'a mut W { + self.bit(rtr as u8 != 0) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x01 << 29)) | (((value as u32) & 0x01) << 29); + self.w + } +} + +#[doc = "Write proxy for field `ID`"] +pub(crate) struct ID_W<'a> { + w: &'a mut W, +} +impl<'a> ID_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub unsafe fn bits(self, value: u32) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x1FFFFFFF)) | ((value as u32) & 0x1FFFFFFF); + self.w + } +} + +#[doc = "Write proxy for field `DLC`"] +pub(crate) struct DLC_W<'a> { + w: &'a mut W, +} +impl<'a> DLC_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x0F << 16)) | (((value as u32) & 0x0F) << 16); + self.w + } +} + +#[doc = "Write proxy for field `BRS`"] +pub(crate) struct BRS_W<'a> { + w: &'a mut W, +} +impl<'a> BRS_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_brs(self, brs: BitRateSwitching) -> &'a mut W { + self.bit(brs as u8 != 0) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x01 << 20)) | (((value as u32) & 0x01) << 20); + self.w + } +} + +#[doc = "Write proxy for field `FDF`"] +pub(crate) struct FDF_W<'a> { + w: &'a mut W, +} +impl<'a> FDF_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_format(self, fdf: FrameFormat) -> &'a mut W { + self.bit(fdf as u8 != 0) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x01 << 21)) | (((value as u32) & 0x01) << 21); + self.w + } +} + +#[doc = "Reader of field `EFC`"] +pub(crate) type EFC_R = generic::R; +impl EFC_R { + pub fn to_event_control(&self) -> EventControl { + match self.bit() { + false => EventControl::DoNotStore, + true => EventControl::Store, + } + } +} +#[doc = "Write proxy for field `EFC`"] +pub(crate) struct EFC_W<'a> { + w: &'a mut W, +} +impl<'a> EFC_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_event_control(self, efc: EventControl) -> &'a mut W { + self.bit(match efc { + EventControl::DoNotStore => false, + EventControl::Store => true, + }) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x01 << 23)) | (((value as u32) & 0x01) << 23); + self.w + } +} + +struct Marker(u8); +impl From for Marker { + fn from(e: Event) -> Marker { + match e { + Event::NoEvent => Marker(0), + Event::Event(mm) => Marker(mm), + } + } +} + +#[doc = "Reader of field `MM`"] +pub(crate) type MM_R = generic::R; +#[doc = "Write proxy for field `MM`"] +pub(crate) struct MM_W<'a> { + w: &'a mut W, +} +impl<'a> MM_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x7F << 24)) | (((value as u32) & 0x7F) << 24); + self.w + } + + fn set_message_marker(self, mm: Marker) -> &'a mut W { + unsafe { self.bits(mm.0) } + } +} + +impl R { + #[doc = "Byte 0 - Bits 0:28 - ID"] + #[inline(always)] + pub fn id(&self) -> ID_R { + ID_R::new(((self.bits[0]) & 0x1FFFFFFF) as u32) + } + #[doc = "Byte 0 - Bit 29 - RTR"] + #[inline(always)] + pub fn rtr(&self) -> RTR_R { + RTR_R::new(((self.bits[0] >> 29) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - XTD"] + #[inline(always)] + pub fn xtd(&self) -> XTD_R { + XTD_R::new(((self.bits[0] >> 30) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - ESI"] + #[inline(always)] + pub fn esi(&self) -> ESI_R { + ESI_R::new(((self.bits[0] >> 31) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 16:19 - DLC"] + #[inline(always)] + pub fn dlc(&self) -> DLC_R { + DLC_R::new(((self.bits[1] >> 16) & 0x0F) as u8) + } + #[doc = "Byte 1 - Bits 20 - BRS"] + #[inline(always)] + pub fn brs(&self) -> BRS_R { + BRS_R::new(((self.bits[1] >> 20) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 20 - FDF"] + #[inline(always)] + pub fn fdf(&self) -> FDF_R { + FDF_R::new(((self.bits[1] >> 21) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 23 - EFC"] + #[inline(always)] + pub fn efc(&self) -> EFC_R { + EFC_R::new(((self.bits[1] >> 23) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 24:31 - MM"] + #[inline(always)] + pub fn mm(&self) -> MM_R { + MM_R::new(((self.bits[1] >> 24) & 0xFF) as u8) + } + pub fn to_data_length(&self) -> DataLength { + let dlc = self.dlc().bits(); + let ff = self.fdf().frame_format(); + let len = if ff == FrameFormat::Fdcan { + // See RM0433 Rev 7 Table 475. DLC coding + match dlc { + 0..=8 => dlc, + 9 => 12, + 10 => 16, + 11 => 20, + 12 => 24, + 13 => 32, + 14 => 48, + 15 => 64, + _ => panic!("DLC > 15"), + } + } else { + match dlc { + 0..=8 => dlc, + 9..=15 => 8, + _ => panic!("DLC > 15"), + } + }; + DataLength::new(len, ff) + } + pub fn to_event(&self) -> Event { + let mm = self.mm().bits(); + let efc = self.efc().to_event_control(); + match efc { + EventControl::DoNotStore => Event::NoEvent, + EventControl::Store => Event::Event(mm), + } + } +} +impl W { + #[doc = "Byte 0 - Bits 0:28 - ID"] + #[inline(always)] + pub fn id(&mut self) -> ID_W { + ID_W { w: self } + } + #[doc = "Byte 0 - Bit 29 - RTR"] + #[inline(always)] + pub fn rtr(&mut self) -> RTR_W { + RTR_W { w: self } + } + #[doc = "Byte 0 - Bit 30 - XTD"] + #[inline(always)] + pub fn xtd(&mut self) -> XTD_W { + XTD_W { w: self } + } + #[doc = "Byte 0 - Bit 31 - ESI"] + #[inline(always)] + pub fn esi(&mut self) -> ESI_W { + ESI_W { w: self } + } + #[doc = "Byte 1 - Bit 16:19 - DLC"] + #[inline(always)] + pub fn dlc(&mut self) -> DLC_W { + DLC_W { w: self } + } + #[doc = "Byte 1 - Bit 20 - BRS"] + #[inline(always)] + pub fn brs(&mut self) -> BRS_W { + BRS_W { w: self } + } + #[doc = "Byte 1 - Bit 21 - FDF"] + #[inline(always)] + pub fn fdf(&mut self) -> FDF_W { + FDF_W { w: self } + } + #[doc = "Byte 1 - Bit 23 - EFC"] + #[inline(always)] + pub fn efc(&mut self) -> EFC_W { + EFC_W { w: self } + } + #[doc = "Byte 1 - Bit 24:31 - MM"] + #[inline(always)] + pub fn mm(&mut self) -> MM_W { + MM_W { w: self } + } + #[doc = "Convenience function for setting the data length and frame format"] + #[inline(always)] + pub fn set_len(&mut self, dl: impl Into) -> &mut Self { + let dl: DataLength = dl.into(); + self.fdf().set_format(dl.into()); + unsafe { self.dlc().bits(dl.dlc()) } + } + pub fn set_event(&mut self, event: Event) -> &mut Self { + self.mm().set_message_marker(event.into()); + self.efc().set_event_control(event.into()) + } +} diff --git a/embassy/embassy-stm32/src/can/fd/message_ram/txevent_element.rs b/embassy/embassy-stm32/src/can/fd/message_ram/txevent_element.rs new file mode 100644 index 0000000..817a444 --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/message_ram/txevent_element.rs @@ -0,0 +1,138 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::common::{BRS_R, DLC_R, ESI_R, RTR_R, XTD_R}; +use super::generic; + +#[doc = "Reader of register TxEventElement"] +pub(crate) type R = generic::R; +// #[doc = "Writer for register TxEventElement"] +// pub(crate) type W = generic::W; +#[doc = "Register TxEventElement `reset()`'s"] +impl generic::ResetValue for super::TxEventElement { + type Type = super::TxEventElementType; + #[inline(always)] + fn reset_value() -> Self::Type { + [0, 0] + } +} + +#[doc = "Reader of field `ID`"] +pub(crate) type ID_R = generic::R; + +#[doc = "Reader of field `TXTS`"] +pub(crate) type TXTS_R = generic::R; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum DataLengthFormat { + StandardLength = 0, + FDCANLength = 1, +} +impl From for bool { + #[inline(always)] + fn from(dlf: DataLengthFormat) -> Self { + dlf as u8 != 0 + } +} + +#[doc = "Reader of field `EDL`"] +pub(crate) type EDL_R = generic::R; +impl EDL_R { + pub fn data_length_format(&self) -> DataLengthFormat { + match self.bits() { + false => DataLengthFormat::StandardLength, + true => DataLengthFormat::FDCANLength, + } + } + pub fn is_standard_length(&self) -> bool { + *self == DataLengthFormat::StandardLength + } + pub fn is_fdcan_length(&self) -> bool { + *self == DataLengthFormat::FDCANLength + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum EventType { + //_Reserved = 0b00, + TxEvent = 0b01, + TxDespiteAbort = 0b10, + //_Reserved = 0b10, +} + +#[doc = "Reader of field `EFC`"] +pub(crate) type EFC_R = generic::R; +impl EFC_R { + pub fn event_type(&self) -> EventType { + match self.bits() { + 0b01 => EventType::TxEvent, + 0b10 => EventType::TxDespiteAbort, + _ => unimplemented!(), + } + } + pub fn is_tx_event(&self) -> bool { + self.event_type() == EventType::TxEvent + } + pub fn is_despite_abort(&self) -> bool { + self.event_type() == EventType::TxDespiteAbort + } +} + +#[doc = "Reader of field `MM`"] +pub(crate) type MM_R = generic::R; + +impl R { + #[doc = "Byte 0 - Bits 0:28 - ID"] + #[inline(always)] + pub fn id(&self) -> ID_R { + ID_R::new(((self.bits[0]) & 0x1FFFFFFF) as u32) + } + #[doc = "Byte 0 - Bit 29 - RTR"] + #[inline(always)] + pub fn rtr(&self) -> RTR_R { + RTR_R::new(((self.bits[0] >> 29) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - XTD"] + #[inline(always)] + pub fn xtd(&self) -> XTD_R { + XTD_R::new(((self.bits[0] >> 30) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - ESI"] + #[inline(always)] + pub fn esi(&self) -> ESI_R { + ESI_R::new(((self.bits[0] >> 31) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 0:15 - TXTS"] + #[inline(always)] + pub fn txts(&self) -> TXTS_R { + TXTS_R::new(((self.bits[1]) & 0xFFFF) as u16) + } + #[doc = "Byte 1 - Bits 16:19 - DLC"] + #[inline(always)] + pub fn dlc(&self) -> DLC_R { + DLC_R::new(((self.bits[1] >> 16) & 0x0F) as u8) + } + #[doc = "Byte 1 - Bits 20 - BRS"] + #[inline(always)] + pub fn brs(&self) -> BRS_R { + BRS_R::new(((self.bits[1] >> 20) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 21 - EDL"] + #[inline(always)] + pub fn edl(&self) -> EDL_R { + EDL_R::new(((self.bits[1] >> 21) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 22:23 - EFC"] + #[inline(always)] + pub fn efc(&self) -> EFC_R { + EFC_R::new(((self.bits[1] >> 22) & 0x03) as u8) + } + #[doc = "Byte 1 - Bits 24:31 - MM"] + #[inline(always)] + pub fn mm(&self) -> MM_R { + MM_R::new(((self.bits[1] >> 24) & 0xFF) as u8) + } +} diff --git a/embassy/embassy-stm32/src/can/fd/mod.rs b/embassy/embassy-stm32/src/can/fd/mod.rs new file mode 100644 index 0000000..271ca0b --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/mod.rs @@ -0,0 +1,6 @@ +//! Module containing that which is specific to fdcan hardware variant + +pub mod config; +pub mod filter; +pub(crate) mod message_ram; +pub(crate) mod peripheral; diff --git a/embassy/embassy-stm32/src/can/fd/peripheral.rs b/embassy/embassy-stm32/src/can/fd/peripheral.rs new file mode 100644 index 0000000..1c7abfc --- /dev/null +++ b/embassy/embassy-stm32/src/can/fd/peripheral.rs @@ -0,0 +1,738 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +use core::convert::Infallible; +use core::slice; + +use cfg_if::cfg_if; + +use crate::can::enums::*; +use crate::can::fd::config::*; +use crate::can::fd::message_ram::enums::*; +use crate::can::fd::message_ram::{RegisterBlock, RxFifoElement, TxBufferElement}; +use crate::can::frame::*; + +/// Loopback Mode +#[derive(Clone, Copy, Debug)] +enum LoopbackMode { + None, + Internal, + External, +} + +pub struct Registers { + pub regs: crate::pac::can::Fdcan, + pub msgram: crate::pac::fdcanram::Fdcanram, + #[allow(dead_code)] + pub msg_ram_offset: usize, +} + +impl Registers { + fn tx_buffer_element(&self, bufidx: usize) -> &mut TxBufferElement { + &mut self.msg_ram_mut().transmit.tbsa[bufidx] + } + pub fn msg_ram_mut(&self) -> &mut RegisterBlock { + #[cfg(can_fdcan_h7)] + let ptr = self.msgram.ram(self.msg_ram_offset / 4).as_ptr() as *mut RegisterBlock; + + #[cfg(not(can_fdcan_h7))] + let ptr = self.msgram.as_ptr() as *mut RegisterBlock; + + unsafe { &mut (*ptr) } + } + + fn rx_fifo_element(&self, fifonr: usize, bufnum: usize) -> &mut RxFifoElement { + &mut self.msg_ram_mut().receive[fifonr].fxsa[bufnum] + } + + pub fn read(&self, fifonr: usize) -> Option<(F, u16)> { + // Fill level - do we have a msg? + if self.regs.rxfs(fifonr).read().ffl() < 1 { + return None; + } + + let read_idx = self.regs.rxfs(fifonr).read().fgi(); + let mailbox = self.rx_fifo_element(fifonr, read_idx as usize); + + let mut buffer = [0u8; 64]; + let maybe_header = extract_frame(mailbox, &mut buffer); + + // Clear FIFO, reduces count and increments read buf + self.regs.rxfa(fifonr).modify(|w| w.set_fai(read_idx)); + + match maybe_header { + Some((header, ts)) => { + let data = &buffer[0..header.len() as usize]; + match F::from_header(header, data) { + Ok(frame) => Some((frame, ts)), + Err(_) => None, + } + } + None => None, + } + } + + pub fn put_tx_frame(&self, bufidx: usize, header: &Header, buffer: &[u8]) { + let mailbox = self.tx_buffer_element(bufidx); + mailbox.reset(); + put_tx_header(mailbox, header); + put_tx_data(mailbox, &buffer[..header.len() as usize]); + + // Set as ready to transmit + self.regs.txbar().modify(|w| w.set_ar(bufidx, true)); + } + + fn reg_to_error(value: u8) -> Option { + match value { + //0b000 => None, + 0b001 => Some(BusError::Stuff), + 0b010 => Some(BusError::Form), + 0b011 => Some(BusError::Acknowledge), + 0b100 => Some(BusError::BitRecessive), + 0b101 => Some(BusError::BitDominant), + 0b110 => Some(BusError::Crc), + //0b111 => Some(BusError::NoError), + _ => None, + } + } + + pub fn curr_error(&self) -> Option { + let err = { self.regs.psr().read() }; + if err.bo() { + return Some(BusError::BusOff); + } else if err.ep() { + return Some(BusError::BusPassive); + } else if err.ew() { + return Some(BusError::BusWarning); + } else { + cfg_if! { + if #[cfg(can_fdcan_h7)] { + let lec = err.lec(); + } else { + let lec = err.lec().to_bits(); + } + } + if let Some(err) = Self::reg_to_error(lec) { + return Some(err); + } + } + None + } + /// Returns if the tx queue is able to accept new messages without having to cancel an existing one + #[inline] + pub fn tx_queue_is_full(&self) -> bool { + self.regs.txfqs().read().tfqf() + } + + /// Returns the current TX buffer operation mode (queue or FIFO) + #[inline] + pub fn tx_queue_mode(&self) -> TxBufferMode { + self.regs.txbc().read().tfqm().into() + } + + #[inline] + pub fn has_pending_frame(&self, idx: usize) -> bool { + self.regs.txbrp().read().trp(idx) + } + + /// Returns `Ok` when the mailbox is free or if it contains pending frame with a + /// lower priority (higher ID) than the identifier `id`. + #[inline] + pub fn is_available(&self, bufidx: usize, id: &embedded_can::Id) -> bool { + if self.has_pending_frame(bufidx) { + let mailbox = self.tx_buffer_element(bufidx); + + let header_reg = mailbox.header.read(); + let old_id = make_id(header_reg.id().bits(), header_reg.xtd().bits()); + + *id > old_id + } else { + true + } + } + + /// Attempts to abort the sending of a frame that is pending in a mailbox. + /// + /// If there is no frame in the provided mailbox, or its transmission succeeds before it can be + /// aborted, this function has no effect and returns `false`. + /// + /// If there is a frame in the provided mailbox, and it is canceled successfully, this function + /// returns `true`. + #[inline] + pub fn abort(&self, bufidx: usize) -> bool { + let can = self.regs; + + // Check if there is a request pending to abort + if self.has_pending_frame(bufidx) { + // Abort Request + can.txbcr().write(|w| w.set_cr(bufidx, true)); + + // Wait for the abort request to be finished. + loop { + if can.txbcf().read().cf(bufidx) { + // Return false when a transmission has occured + break can.txbto().read().to(bufidx) == false; + } + } + } else { + false + } + } + + #[inline] + fn abort_pending_mailbox(&self, bufidx: usize) -> Option { + if self.abort(bufidx) { + let mailbox = self.tx_buffer_element(bufidx); + + let header_reg = mailbox.header.read(); + let id = make_id(header_reg.id().bits(), header_reg.xtd().bits()); + + let len = match header_reg.to_data_length() { + DataLength::Fdcan(len) => len, + DataLength::Classic(len) => len, + }; + if len as usize > ClassicData::MAX_DATA_LEN { + return None; + } + + let mut data = [0u8; 64]; + data_from_tx_buffer(&mut data, mailbox, len as usize); + + if header_reg.rtr().bit() { + F::new_remote(id, len as usize) + } else { + F::new(id, &data[0..(len as usize)]) + } + } else { + // Abort request failed because the frame was already sent (or being sent) on + // the bus. All mailboxes are now free. This can happen for small prescaler + // values (e.g. 1MBit/s bit timing with a source clock of 8MHz) or when an ISR + // has preempted the execution. + None + } + } + + pub fn write(&self, frame: &F) -> nb::Result, Infallible> { + let (idx, pending_frame) = if self.tx_queue_is_full() { + if self.tx_queue_mode() == TxBufferMode::Fifo { + // Does not make sense to cancel a pending frame when using FIFO + return Err(nb::Error::WouldBlock); + } + // If the queue is full, + // Discard the first slot with a lower priority message + let id = frame.header().id(); + if self.is_available(0, id) { + (0, self.abort_pending_mailbox(0)) + } else if self.is_available(1, id) { + (1, self.abort_pending_mailbox(1)) + } else if self.is_available(2, id) { + (2, self.abort_pending_mailbox(2)) + } else { + // For now we bail when there is no lower priority slot available + // Can this lead to priority inversion? + return Err(nb::Error::WouldBlock); + } + } else { + // Read the Write Pointer + let idx = self.regs.txfqs().read().tfqpi(); + + (idx, None) + }; + + self.put_tx_frame(idx as usize, frame.header(), frame.data()); + + Ok(pending_frame) + } + + #[inline] + fn reset_msg_ram(&self) { + self.msg_ram_mut().reset(); + } + + #[inline] + fn enter_init_mode(&self) { + self.regs.cccr().modify(|w| w.set_init(true)); + while false == self.regs.cccr().read().init() {} + self.regs.cccr().modify(|w| w.set_cce(true)); + } + + /// Enables or disables loopback mode: Internally connects the TX and RX + /// signals together. + #[inline] + fn set_loopback_mode(&self, mode: LoopbackMode) { + let (test, mon, lbck) = match mode { + LoopbackMode::None => (false, false, false), + LoopbackMode::Internal => (true, true, true), + LoopbackMode::External => (true, false, true), + }; + + self.set_test_mode(test); + self.set_bus_monitoring_mode(mon); + + self.regs.test().modify(|w| w.set_lbck(lbck)); + } + + /// Enables or disables silent mode: Disconnects the TX signal from the pin. + #[inline] + fn set_bus_monitoring_mode(&self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_mon(enabled)); + } + + #[inline] + fn set_restricted_operations(&self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_asm(enabled)); + } + + #[inline] + fn set_normal_operations(&self, _enabled: bool) { + self.set_loopback_mode(LoopbackMode::None); + } + + #[inline] + fn set_test_mode(&self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_test(enabled)); + } + + #[inline] + fn set_power_down_mode(&self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_csr(enabled)); + while self.regs.cccr().read().csa() != enabled {} + } + + /// Moves out of PoweredDownMode and into ConfigMode + #[inline] + pub fn into_config_mode(self, _config: FdCanConfig) { + self.set_power_down_mode(false); + self.enter_init_mode(); + self.reset_msg_ram(); + + // check the FDCAN core matches our expections + assert!( + self.regs.crel().read().rel() == 3, + "Expected FDCAN core major release 3" + ); + assert!( + self.regs.endn().read().etv() == 0x87654321_u32, + "Error reading endianness test value from FDCAN core" + ); + + /* + for fid in 0..crate::can::message_ram::STANDARD_FILTER_MAX { + self.set_standard_filter((fid as u8).into(), StandardFilter::disable()); + } + for fid in 0..Ecrate::can::message_ram::XTENDED_FILTER_MAX { + self.set_extended_filter(fid.into(), ExtendedFilter::disable()); + } + */ + } + + /// Applies the settings of a new FdCanConfig See [`FdCanConfig`] + #[inline] + pub fn apply_config(&self, config: FdCanConfig) { + self.set_tx_buffer_mode(config.tx_buffer_mode); + + // set standard filters list size to 28 + // set extended filters list size to 8 + // REQUIRED: we use the memory map as if these settings are set + // instead of re-calculating them. + #[cfg(not(can_fdcan_h7))] + { + self.regs.rxgfc().modify(|w| { + w.set_lss(crate::can::fd::message_ram::STANDARD_FILTER_MAX); + w.set_lse(crate::can::fd::message_ram::EXTENDED_FILTER_MAX); + }); + } + #[cfg(can_fdcan_h7)] + { + self.regs + .sidfc() + .modify(|w| w.set_lss(crate::can::fd::message_ram::STANDARD_FILTER_MAX)); + self.regs + .xidfc() + .modify(|w| w.set_lse(crate::can::fd::message_ram::EXTENDED_FILTER_MAX)); + } + + self.configure_msg_ram(); + + // Enable timestamping + #[cfg(not(can_fdcan_h7))] + self.regs + .tscc() + .write(|w| w.set_tss(stm32_metapac::can::vals::Tss::INCREMENT)); + #[cfg(can_fdcan_h7)] + self.regs.tscc().write(|w| w.set_tss(0x01)); + + // this isn't really documented in the reference manual + // but corresponding txbtie bit has to be set for the TC (TxComplete) interrupt to fire + self.regs.txbtie().write(|w| w.0 = 0xffff_ffff); + self.regs.ie().modify(|w| { + w.set_rfne(0, true); // Rx Fifo 0 New Msg + w.set_rfne(1, true); // Rx Fifo 1 New Msg + w.set_tce(true); // Tx Complete + w.set_boe(true); // Bus-Off Status Changed + }); + self.regs.ile().modify(|w| { + w.set_eint0(true); // Interrupt Line 0 + w.set_eint1(true); // Interrupt Line 1 + }); + + self.set_data_bit_timing(config.dbtr); + self.set_nominal_bit_timing(config.nbtr); + self.set_automatic_retransmit(config.automatic_retransmit); + self.set_transmit_pause(config.transmit_pause); + self.set_frame_transmit(config.frame_transmit); + //self.set_interrupt_line_config(config.interrupt_line_config); + self.set_non_iso_mode(config.non_iso_mode); + self.set_edge_filtering(config.edge_filtering); + self.set_protocol_exception_handling(config.protocol_exception_handling); + self.set_global_filter(config.global_filter); + } + + #[inline] + fn leave_init_mode(&self, config: FdCanConfig) { + self.apply_config(config); + + self.regs.cccr().modify(|w| w.set_cce(false)); + self.regs.cccr().modify(|w| w.set_init(false)); + while self.regs.cccr().read().init() == true {} + } + + /// Moves out of ConfigMode and into specified mode + #[inline] + pub fn into_mode(&self, config: FdCanConfig, mode: crate::can::_version::OperatingMode) { + match mode { + crate::can::OperatingMode::InternalLoopbackMode => self.set_loopback_mode(LoopbackMode::Internal), + crate::can::OperatingMode::ExternalLoopbackMode => self.set_loopback_mode(LoopbackMode::External), + crate::can::OperatingMode::NormalOperationMode => self.set_normal_operations(true), + crate::can::OperatingMode::RestrictedOperationMode => self.set_restricted_operations(true), + crate::can::OperatingMode::BusMonitoringMode => self.set_bus_monitoring_mode(true), + } + self.leave_init_mode(config); + } + + /// Configures the bit timings. + /// + /// You can use to calculate the `btr` parameter. Enter + /// parameters as follows: + /// + /// - *Clock Rate*: The input clock speed to the CAN peripheral (*not* the CPU clock speed). + /// This is the clock rate of the peripheral bus the CAN peripheral is attached to (eg. APB1). + /// - *Sample Point*: Should normally be left at the default value of 87.5%. + /// - *SJW*: Should normally be left at the default value of 1. + /// + /// Then copy the `CAN_BUS_TIME` register value from the table and pass it as the `btr` + /// parameter to this method. + #[inline] + pub fn set_nominal_bit_timing(&self, btr: NominalBitTiming) { + self.regs.nbtp().write(|w| { + w.set_nbrp(btr.nbrp() - 1); + w.set_ntseg1(btr.ntseg1() - 1); + w.set_ntseg2(btr.ntseg2() - 1); + w.set_nsjw(btr.nsjw() - 1); + }); + } + + /// Configures the data bit timings for the FdCan Variable Bitrates. + /// This is not used when frame_transmit is set to anything other than AllowFdCanAndBRS. + #[inline] + pub fn set_data_bit_timing(&self, btr: DataBitTiming) { + self.regs.dbtp().write(|w| { + w.set_dbrp(btr.dbrp() - 1); + w.set_dtseg1(btr.dtseg1() - 1); + w.set_dtseg2(btr.dtseg2() - 1); + w.set_dsjw(btr.dsjw() - 1); + }); + } + + /// Enables or disables automatic retransmission of messages + /// + /// If this is enabled, the CAN peripheral will automatically try to retransmit each frame + /// util it can be sent. Otherwise, it will try only once to send each frame. + /// + /// Automatic retransmission is enabled by default. + #[inline] + pub fn set_automatic_retransmit(&self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_dar(!enabled)); + } + + /// Configures the transmit pause feature. See + /// [`FdCanConfig::set_transmit_pause`] + #[inline] + pub fn set_transmit_pause(&self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_txp(!enabled)); + } + + /// Configures non-iso mode. See [`FdCanConfig::set_non_iso_mode`] + #[inline] + pub fn set_non_iso_mode(&self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_niso(enabled)); + } + + /// Configures edge filtering. See [`FdCanConfig::set_edge_filtering`] + #[inline] + pub fn set_edge_filtering(&self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_efbi(enabled)); + } + + /// Configures TX Buffer Mode + #[inline] + pub fn set_tx_buffer_mode(&self, tbm: TxBufferMode) { + self.regs.txbc().write(|w| w.set_tfqm(tbm.into())); + } + + /// Configures frame transmission mode. See + /// [`FdCanConfig::set_frame_transmit`] + #[inline] + pub fn set_frame_transmit(&self, fts: FrameTransmissionConfig) { + let (fdoe, brse) = match fts { + FrameTransmissionConfig::ClassicCanOnly => (false, false), + FrameTransmissionConfig::AllowFdCan => (true, false), + FrameTransmissionConfig::AllowFdCanAndBRS => (true, true), + }; + + self.regs.cccr().modify(|w| { + w.set_fdoe(fdoe); + #[cfg(can_fdcan_h7)] + w.set_bse(brse); + #[cfg(not(can_fdcan_h7))] + w.set_brse(brse); + }); + } + + /// Sets the protocol exception handling on/off + #[inline] + pub fn set_protocol_exception_handling(&self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_pxhd(!enabled)); + } + + /// Configures and resets the timestamp counter + #[inline] + #[allow(unused)] + pub fn set_timestamp_counter_source(&self, select: TimestampSource) { + #[cfg(can_fdcan_h7)] + let (tcp, tss) = match select { + TimestampSource::None => (0, 0), + TimestampSource::Prescaler(p) => (p as u8, 1), + TimestampSource::FromTIM3 => (0, 2), + }; + + #[cfg(not(can_fdcan_h7))] + let (tcp, tss) = match select { + TimestampSource::None => (0, stm32_metapac::can::vals::Tss::ZERO), + TimestampSource::Prescaler(p) => (p as u8, stm32_metapac::can::vals::Tss::INCREMENT), + TimestampSource::FromTIM3 => (0, stm32_metapac::can::vals::Tss::EXTERNAL), + }; + + self.regs.tscc().write(|w| { + w.set_tcp(tcp); + w.set_tss(tss); + }); + } + + #[cfg(not(can_fdcan_h7))] + /// Configures the global filter settings + #[inline] + pub fn set_global_filter(&self, filter: GlobalFilter) { + let anfs = match filter.handle_standard_frames { + crate::can::fd::config::NonMatchingFilter::IntoRxFifo0 => stm32_metapac::can::vals::Anfs::ACCEPT_FIFO_0, + crate::can::fd::config::NonMatchingFilter::IntoRxFifo1 => stm32_metapac::can::vals::Anfs::ACCEPT_FIFO_1, + crate::can::fd::config::NonMatchingFilter::Reject => stm32_metapac::can::vals::Anfs::REJECT, + }; + let anfe = match filter.handle_extended_frames { + crate::can::fd::config::NonMatchingFilter::IntoRxFifo0 => stm32_metapac::can::vals::Anfe::ACCEPT_FIFO_0, + crate::can::fd::config::NonMatchingFilter::IntoRxFifo1 => stm32_metapac::can::vals::Anfe::ACCEPT_FIFO_1, + crate::can::fd::config::NonMatchingFilter::Reject => stm32_metapac::can::vals::Anfe::REJECT, + }; + + self.regs.rxgfc().modify(|w| { + w.set_anfs(anfs); + w.set_anfe(anfe); + w.set_rrfs(filter.reject_remote_standard_frames); + w.set_rrfe(filter.reject_remote_extended_frames); + }); + } + + #[cfg(can_fdcan_h7)] + /// Configures the global filter settings + #[inline] + pub fn set_global_filter(&self, filter: GlobalFilter) { + let anfs = match filter.handle_standard_frames { + crate::can::fd::config::NonMatchingFilter::IntoRxFifo0 => 0, + crate::can::fd::config::NonMatchingFilter::IntoRxFifo1 => 1, + crate::can::fd::config::NonMatchingFilter::Reject => 2, + }; + + let anfe = match filter.handle_extended_frames { + crate::can::fd::config::NonMatchingFilter::IntoRxFifo0 => 0, + crate::can::fd::config::NonMatchingFilter::IntoRxFifo1 => 1, + crate::can::fd::config::NonMatchingFilter::Reject => 2, + }; + + self.regs.gfc().modify(|w| { + w.set_anfs(anfs); + w.set_anfe(anfe); + w.set_rrfs(filter.reject_remote_standard_frames); + w.set_rrfe(filter.reject_remote_extended_frames); + }); + } + + #[cfg(not(can_fdcan_h7))] + fn configure_msg_ram(&self) {} + + #[cfg(can_fdcan_h7)] + fn configure_msg_ram(&self) { + let r = self.regs; + + use crate::can::fd::message_ram::*; + //use fdcan::message_ram::*; + let mut offset_words = (self.msg_ram_offset / 4) as u16; + + // 11-bit filter + r.sidfc().modify(|w| w.set_flssa(offset_words)); + offset_words += STANDARD_FILTER_MAX as u16; + + // 29-bit filter + r.xidfc().modify(|w| w.set_flesa(offset_words)); + offset_words += 2 * EXTENDED_FILTER_MAX as u16; + + // Rx FIFO 0 and 1 + for i in 0..=1 { + r.rxfc(i).modify(|w| { + w.set_fsa(offset_words); + w.set_fs(RX_FIFO_MAX); + w.set_fwm(RX_FIFO_MAX); + }); + offset_words += 18 * RX_FIFO_MAX as u16; + } + + // Rx buffer - see below + // Tx event FIFO + r.txefc().modify(|w| { + w.set_efsa(offset_words); + w.set_efs(TX_EVENT_MAX); + w.set_efwm(TX_EVENT_MAX); + }); + offset_words += 2 * TX_EVENT_MAX as u16; + + // Tx buffers + r.txbc().modify(|w| { + w.set_tbsa(offset_words); + w.set_tfqs(TX_FIFO_MAX); + }); + offset_words += 18 * TX_FIFO_MAX as u16; + + // Rx Buffer - not used + r.rxbc().modify(|w| { + w.set_rbsa(offset_words); + }); + + // TX event FIFO? + // Trigger memory? + + // Set the element sizes to 16 bytes + r.rxesc().modify(|w| { + w.set_rbds(0b111); + for i in 0..=1 { + w.set_fds(i, 0b111); + } + }); + r.txesc().modify(|w| { + w.set_tbds(0b111); + }) + } +} + +fn make_id(id: u32, extended: bool) -> embedded_can::Id { + if extended { + embedded_can::Id::from(unsafe { embedded_can::ExtendedId::new_unchecked(id & 0x1FFFFFFF) }) + } else { + // A standard identifier is stored into ID[28:18]. + embedded_can::Id::from(unsafe { embedded_can::StandardId::new_unchecked(((id >> 18) & 0x000007FF) as u16) }) + } +} + +fn put_tx_header(mailbox: &mut TxBufferElement, header: &Header) { + let (id, id_type) = match header.id() { + // A standard identifier has to be written to ID[28:18]. + embedded_can::Id::Standard(id) => ((id.as_raw() as u32) << 18, IdType::StandardId), + embedded_can::Id::Extended(id) => (id.as_raw() as u32, IdType::ExtendedId), + }; + + // Use FDCAN only for DLC > 8. FDCAN users can revise this if required. + let frame_format = if header.len() > 8 || header.fdcan() { + FrameFormat::Fdcan + } else { + FrameFormat::Classic + }; + let brs = (frame_format == FrameFormat::Fdcan) && header.bit_rate_switching(); + + mailbox.header.write(|w| { + unsafe { w.id().bits(id) } + .rtr() + .bit(header.len() == 0 && header.rtr()) + .xtd() + .set_id_type(id_type) + .set_len(DataLength::new(header.len(), frame_format)) + .set_event(Event::NoEvent) + .fdf() + .set_format(frame_format) + .brs() + .bit(brs) + //esi.set_error_indicator(//TODO//) + }); +} + +fn put_tx_data(mailbox: &mut TxBufferElement, buffer: &[u8]) { + let mut lbuffer = [0_u32; 16]; + let len = buffer.len(); + let data = unsafe { slice::from_raw_parts_mut(lbuffer.as_mut_ptr() as *mut u8, len) }; + data[..len].copy_from_slice(&buffer[..len]); + let data_len = ((len) + 3) / 4; + for (register, byte) in mailbox.data.iter_mut().zip(lbuffer[..data_len].iter()) { + unsafe { register.write(*byte) }; + } +} + +fn data_from_fifo(buffer: &mut [u8], mailbox: &RxFifoElement, len: usize) { + for (i, register) in mailbox.data.iter().enumerate() { + let register_value = register.read(); + let register_bytes = unsafe { slice::from_raw_parts(®ister_value as *const u32 as *const u8, 4) }; + let num_bytes = (len) - i * 4; + if num_bytes <= 4 { + buffer[i * 4..i * 4 + num_bytes].copy_from_slice(®ister_bytes[..num_bytes]); + break; + } + buffer[i * 4..(i + 1) * 4].copy_from_slice(register_bytes); + } +} + +fn data_from_tx_buffer(buffer: &mut [u8], mailbox: &TxBufferElement, len: usize) { + for (i, register) in mailbox.data.iter().enumerate() { + let register_value = register.read(); + let register_bytes = unsafe { slice::from_raw_parts(®ister_value as *const u32 as *const u8, 4) }; + let num_bytes = (len) - i * 4; + if num_bytes <= 4 { + buffer[i * 4..i * 4 + num_bytes].copy_from_slice(®ister_bytes[..num_bytes]); + break; + } + buffer[i * 4..(i + 1) * 4].copy_from_slice(register_bytes); + } +} + +fn extract_frame(mailbox: &RxFifoElement, buffer: &mut [u8]) -> Option<(Header, u16)> { + let header_reg = mailbox.header.read(); + + let id = make_id(header_reg.id().bits(), header_reg.xtd().bits()); + let dlc = header_reg.to_data_length().len(); + let len = dlc as usize; + let timestamp = header_reg.txts().bits; + if len > buffer.len() { + return None; + } + data_from_fifo(buffer, mailbox, len); + let header = if header_reg.fdf().bits { + Header::new_fd(id, dlc, header_reg.rtr().bits(), header_reg.brs().bits()) + } else { + Header::new(id, dlc, header_reg.rtr().bits()) + }; + Some((header, timestamp)) +} diff --git a/embassy/embassy-stm32/src/can/fdcan.rs b/embassy/embassy-stm32/src/can/fdcan.rs new file mode 100644 index 0000000..c549313 --- /dev/null +++ b/embassy/embassy-stm32/src/can/fdcan.rs @@ -0,0 +1,1073 @@ +#[allow(unused_variables)] +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::interrupt::InterruptExt; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::can::fd::peripheral::Registers; +use crate::gpio::{AfType, OutputType, Pull, Speed}; +use crate::interrupt::typelevel::Interrupt; +use crate::rcc::{self, RccPeripheral}; +use crate::{interrupt, peripherals, Peripheral}; + +pub(crate) mod fd; + +use self::fd::config::*; +use self::fd::filter::*; +pub use self::fd::{config, filter}; +pub use super::common::{BufferedCanReceiver, BufferedCanSender}; +use super::enums::*; +use super::frame::*; +use super::util; + +/// Timestamp for incoming packets. Use Embassy time when enabled. +#[cfg(feature = "time")] +pub type Timestamp = embassy_time::Instant; + +/// Timestamp for incoming packets. +#[cfg(not(feature = "time"))] +pub type Timestamp = u16; + +/// Interrupt handler channel 0. +pub struct IT0InterruptHandler { + _phantom: PhantomData, +} + +// We use IT0 for everything currently +impl interrupt::typelevel::Handler for IT0InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::registers().regs; + + let ir = regs.ir().read(); + + if ir.tc() { + regs.ir().write(|w| w.set_tc(true)); + } + if ir.tefn() { + regs.ir().write(|w| w.set_tefn(true)); + } + + match &T::state().tx_mode { + TxMode::NonBuffered(waker) => waker.wake(), + TxMode::ClassicBuffered(buf) => { + if !T::registers().tx_queue_is_full() { + match buf.tx_receiver.try_receive() { + Ok(frame) => { + _ = T::registers().write(&frame); + } + Err(_) => {} + } + } + } + TxMode::FdBuffered(buf) => { + if !T::registers().tx_queue_is_full() { + match buf.tx_receiver.try_receive() { + Ok(frame) => { + _ = T::registers().write(&frame); + } + Err(_) => {} + } + } + } + } + + if ir.rfn(0) { + T::state().rx_mode.on_interrupt::(0); + } + if ir.rfn(1) { + T::state().rx_mode.on_interrupt::(1); + } + + if ir.bo() { + regs.ir().write(|w| w.set_bo(true)); + if regs.psr().read().bo() { + // Initiate bus-off recovery sequence by resetting CCCR.INIT + regs.cccr().modify(|w| w.set_init(false)); + } + } + } +} + +/// Interrupt handler channel 1. +pub struct IT1InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for IT1InterruptHandler { + unsafe fn on_interrupt() {} +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Different operating modes +pub enum OperatingMode { + //PoweredDownMode, + //ConfigMode, + /// This mode can be used for a “Hot Selftest”, meaning the FDCAN can be tested without + /// affecting a running CAN system connected to the FDCAN_TX and FDCAN_RX pins. In this + /// mode, FDCAN_RX pin is disconnected from the FDCAN and FDCAN_TX pin is held + /// recessive. + InternalLoopbackMode, + /// This mode is provided for hardware self-test. To be independent from external stimulation, + /// the FDCAN ignores acknowledge errors (recessive bit sampled in the acknowledge slot of a + /// data / remote frame) in Loop Back mode. In this mode the FDCAN performs an internal + /// feedback from its transmit output to its receive input. The actual value of the FDCAN_RX + /// input pin is disregarded by the FDCAN. The transmitted messages can be monitored at the + /// FDCAN_TX transmit pin. + ExternalLoopbackMode, + /// The normal use of the Fdcan instance after configurations + NormalOperationMode, + /// In Restricted operation mode the node is able to receive data and remote frames and to give + /// acknowledge to valid frames, but it does not send data frames, remote frames, active error + /// frames, or overload frames. In case of an error condition or overload condition, it does not + /// send dominant bits, instead it waits for the occurrence of bus idle condition to resynchronize + /// itself to the CAN communication. The error counters for transmit and receive are frozen while + /// error logging (can_errors) is active. TODO: automatically enter in this mode? + RestrictedOperationMode, + /// In Bus monitoring mode (for more details refer to ISO11898-1, 10.12 Bus monitoring), + /// the FDCAN is able to receive valid data frames and valid remote frames, but cannot start a + /// transmission. In this mode, it sends only recessive bits on the CAN bus. If the FDCAN is + /// required to send a dominant bit (ACK bit, overload flag, active error flag), the bit is + /// rerouted internally so that the FDCAN can monitor it, even if the CAN bus remains in recessive + /// state. In Bus monitoring mode the TXBRP register is held in reset state. The Bus monitoring + /// mode can be used to analyze the traffic on a CAN bus without affecting it by the transmission + /// of dominant bits. + BusMonitoringMode, + //TestMode, +} + +fn calc_ns_per_timer_tick( + info: &'static Info, + freq: crate::time::Hertz, + mode: crate::can::fd::config::FrameTransmissionConfig, +) -> u64 { + match mode { + // Use timestamp from Rx FIFO to adjust timestamp reported to user + crate::can::fd::config::FrameTransmissionConfig::ClassicCanOnly => { + let prescale: u64 = ({ info.regs.regs.nbtp().read().nbrp() } + 1) as u64 + * ({ info.regs.regs.tscc().read().tcp() } + 1) as u64; + 1_000_000_000 as u64 / (freq.0 as u64 * prescale) + } + // For VBR this is too hard because the FDCAN timer switches clock rate you need to configure to use + // timer3 instead which is too hard to do from this module. + _ => 0, + } +} + +/// FDCAN Configuration instance instance +/// Create instance of this first +pub struct CanConfigurator<'d> { + _phantom: PhantomData<&'d ()>, + config: crate::can::fd::config::FdCanConfig, + info: &'static Info, + state: &'static State, + /// Reference to internals. + properties: Properties, + periph_clock: crate::time::Hertz, +} + +impl<'d> CanConfigurator<'d> { + /// Creates a new Fdcan instance, keeping the peripheral in sleep mode. + /// You must call [Fdcan::enable_non_blocking] to use the peripheral. + pub fn new( + _peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + _irqs: impl interrupt::typelevel::Binding> + + interrupt::typelevel::Binding> + + 'd, + ) -> CanConfigurator<'d> { + into_ref!(_peri, rx, tx); + + rx.set_as_af(rx.af_num(), AfType::input(Pull::None)); + tx.set_as_af(tx.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + + rcc::enable_and_reset::(); + + let mut config = crate::can::fd::config::FdCanConfig::default(); + config.timestamp_source = TimestampSource::Prescaler(TimestampPrescaler::_1); + T::registers().into_config_mode(config); + + rx.set_as_af(rx.af_num(), AfType::input(Pull::None)); + tx.set_as_af(tx.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + + unsafe { + T::IT0Interrupt::unpend(); // Not unsafe + T::IT0Interrupt::enable(); + + T::IT1Interrupt::unpend(); // Not unsafe + T::IT1Interrupt::enable(); + } + Self { + _phantom: PhantomData, + config, + info: T::info(), + state: T::state(), + properties: Properties::new(T::info()), + periph_clock: T::frequency(), + } + } + + /// Get driver properties + pub fn properties(&self) -> &Properties { + &self.properties + } + + /// Get configuration + pub fn config(&self) -> crate::can::fd::config::FdCanConfig { + return self.config; + } + + /// Set configuration + pub fn set_config(&mut self, config: crate::can::fd::config::FdCanConfig) { + self.config = config; + } + + /// Configures the bit timings calculated from supplied bitrate. + pub fn set_bitrate(&mut self, bitrate: u32) { + let bit_timing = util::calc_can_timings(self.periph_clock, bitrate).unwrap(); + + let nbtr = crate::can::fd::config::NominalBitTiming { + sync_jump_width: bit_timing.sync_jump_width, + prescaler: bit_timing.prescaler, + seg1: bit_timing.seg1, + seg2: bit_timing.seg2, + }; + self.config = self.config.set_nominal_bit_timing(nbtr); + } + + /// Configures the bit timings for VBR data calculated from supplied bitrate. This also sets confit to allow can FD and VBR + pub fn set_fd_data_bitrate(&mut self, bitrate: u32, transceiver_delay_compensation: bool) { + let bit_timing = util::calc_can_timings(self.periph_clock, bitrate).unwrap(); + // Note, used existing calcluation for normal(non-VBR) bitrate, appears to work for 250k/1M + let nbtr = crate::can::fd::config::DataBitTiming { + transceiver_delay_compensation, + sync_jump_width: bit_timing.sync_jump_width, + prescaler: bit_timing.prescaler, + seg1: bit_timing.seg1, + seg2: bit_timing.seg2, + }; + self.config.frame_transmit = FrameTransmissionConfig::AllowFdCanAndBRS; + self.config = self.config.set_data_bit_timing(nbtr); + } + + /// Start in mode. + pub fn start(self, mode: OperatingMode) -> Can<'d> { + let ns_per_timer_tick = calc_ns_per_timer_tick(self.info, self.periph_clock, self.config.frame_transmit); + critical_section::with(|_| { + let state = self.state as *const State; + unsafe { + let mut_state = state as *mut State; + (*mut_state).ns_per_timer_tick = ns_per_timer_tick; + } + }); + self.info.regs.into_mode(self.config, mode); + Can { + _phantom: PhantomData, + config: self.config, + info: self.info, + state: self.state, + _mode: mode, + properties: Properties::new(self.info), + } + } + + /// Start, entering mode. Does same as start(mode) + pub fn into_normal_mode(self) -> Can<'d> { + self.start(OperatingMode::NormalOperationMode) + } + + /// Start, entering mode. Does same as start(mode) + pub fn into_internal_loopback_mode(self) -> Can<'d> { + self.start(OperatingMode::InternalLoopbackMode) + } + + /// Start, entering mode. Does same as start(mode) + pub fn into_external_loopback_mode(self) -> Can<'d> { + self.start(OperatingMode::ExternalLoopbackMode) + } +} + +/// FDCAN Instance +pub struct Can<'d> { + _phantom: PhantomData<&'d ()>, + config: crate::can::fd::config::FdCanConfig, + info: &'static Info, + state: &'static State, + _mode: OperatingMode, + properties: Properties, +} + +impl<'d> Can<'d> { + /// Get driver properties + pub fn properties(&self) -> &Properties { + &self.properties + } + + /// Flush one of the TX mailboxes. + pub async fn flush(&self, idx: usize) { + poll_fn(|cx| { + self.state.tx_mode.register(cx.waker()); + + if idx > 3 { + panic!("Bad mailbox"); + } + let idx = 1 << idx; + if !self.info.regs.regs.txbrp().read().trp(idx) { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + pub async fn write(&mut self, frame: &Frame) -> Option { + self.state.tx_mode.write(self.info, frame).await + } + + /// Returns the next received message frame + pub async fn read(&mut self) -> Result { + self.state.rx_mode.read_classic(self.info, self.state).await + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + pub async fn write_fd(&mut self, frame: &FdFrame) -> Option { + self.state.tx_mode.write_fd(self.info, frame).await + } + + /// Returns the next received message frame + pub async fn read_fd(&mut self) -> Result { + self.state.rx_mode.read_fd(self.info, self.state).await + } + + /// Split instance into separate portions: Tx(write), Rx(read), common properties + pub fn split(self) -> (CanTx<'d>, CanRx<'d>, Properties) { + ( + CanTx { + _phantom: PhantomData, + info: self.info, + state: self.state, + config: self.config, + _mode: self._mode, + }, + CanRx { + _phantom: PhantomData, + info: self.info, + state: self.state, + _mode: self._mode, + }, + self.properties, + ) + } + /// Join split rx and tx portions back together + pub fn join(tx: CanTx<'d>, rx: CanRx<'d>) -> Self { + Can { + _phantom: PhantomData, + config: tx.config, + info: tx.info, + state: tx.state, + _mode: rx._mode, + properties: Properties::new(tx.info), + } + } + + /// Return a buffered instance of driver without CAN FD support. User must supply Buffers + pub fn buffered( + self, + tx_buf: &'static mut TxBuf, + rxb: &'static mut RxBuf, + ) -> BufferedCan<'d, TX_BUF_SIZE, RX_BUF_SIZE> { + BufferedCan::new(self.info, self.state, self._mode, tx_buf, rxb) + } + + /// Return a buffered instance of driver with CAN FD support. User must supply Buffers + pub fn buffered_fd( + self, + tx_buf: &'static mut TxFdBuf, + rxb: &'static mut RxFdBuf, + ) -> BufferedCanFd<'d, TX_BUF_SIZE, RX_BUF_SIZE> { + BufferedCanFd::new(self.info, self.state, self._mode, tx_buf, rxb) + } +} + +/// User supplied buffer for RX Buffering +pub type RxBuf = Channel, BUF_SIZE>; + +/// User supplied buffer for TX buffering +pub type TxBuf = Channel; + +/// Buffered FDCAN Instance +pub struct BufferedCan<'d, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> { + _phantom: PhantomData<&'d ()>, + info: &'static Info, + state: &'static State, + _mode: OperatingMode, + tx_buf: &'static TxBuf, + rx_buf: &'static RxBuf, + properties: Properties, +} + +impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCan<'d, TX_BUF_SIZE, RX_BUF_SIZE> { + fn new( + info: &'static Info, + state: &'static State, + _mode: OperatingMode, + tx_buf: &'static TxBuf, + rx_buf: &'static RxBuf, + ) -> Self { + BufferedCan { + _phantom: PhantomData, + info, + state, + _mode, + tx_buf, + rx_buf, + properties: Properties::new(info), + } + .setup() + } + + /// Get driver properties + pub fn properties(&self) -> &Properties { + &self.properties + } + + fn setup(self) -> Self { + // We don't want interrupts being processed while we change modes. + critical_section::with(|_| { + let rx_inner = super::common::ClassicBufferedRxInner { + rx_sender: self.rx_buf.sender().into(), + }; + let tx_inner = super::common::ClassicBufferedTxInner { + tx_receiver: self.tx_buf.receiver().into(), + }; + let state = self.state as *const State; + unsafe { + let mut_state = state as *mut State; + (*mut_state).rx_mode = RxMode::ClassicBuffered(rx_inner); + (*mut_state).tx_mode = TxMode::ClassicBuffered(tx_inner); + } + }); + self + } + + /// Async write frame to TX buffer. + pub async fn write(&mut self, frame: Frame) { + self.tx_buf.send(frame).await; + self.info.interrupt0.pend(); // Wake for Tx + //T::IT0Interrupt::pend(); // Wake for Tx + } + + /// Async read frame from RX buffer. + pub async fn read(&mut self) -> Result { + self.rx_buf.receive().await + } + + /// Returns a sender that can be used for sending CAN frames. + pub fn writer(&self) -> BufferedCanSender { + BufferedCanSender { + tx_buf: self.tx_buf.sender().into(), + waker: self.info.tx_waker, + } + } + + /// Returns a receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver. + pub fn reader(&self) -> BufferedCanReceiver { + self.rx_buf.receiver().into() + } +} + +impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> Drop for BufferedCan<'d, TX_BUF_SIZE, RX_BUF_SIZE> { + fn drop(&mut self) { + critical_section::with(|_| { + let state = self.state as *const State; + unsafe { + let mut_state = state as *mut State; + (*mut_state).rx_mode = RxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + (*mut_state).tx_mode = TxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + } + }); + } +} + +/// User supplied buffer for RX Buffering +pub type RxFdBuf = Channel, BUF_SIZE>; + +/// User supplied buffer for TX buffering +pub type TxFdBuf = Channel; + +/// Sender that can be used for sending CAN frames. +#[derive(Copy, Clone)] +pub struct BufferedFdCanSender { + tx_buf: DynamicSender<'static, FdFrame>, + waker: fn(), +} + +impl BufferedFdCanSender { + /// Async write frame to TX buffer. + pub fn try_write(&mut self, frame: FdFrame) -> Result<(), embassy_sync::channel::TrySendError> { + self.tx_buf.try_send(frame)?; + (self.waker)(); + Ok(()) + } + + /// Async write frame to TX buffer. + pub async fn write(&mut self, frame: FdFrame) { + self.tx_buf.send(frame).await; + (self.waker)(); + } + + /// Allows a poll_fn to poll until the channel is ready to write + pub fn poll_ready_to_send(&self, cx: &mut core::task::Context<'_>) -> core::task::Poll<()> { + self.tx_buf.poll_ready_to_send(cx) + } +} + +/// Receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver. +pub type BufferedFdCanReceiver = DynamicReceiver<'static, Result>; + +/// Buffered FDCAN Instance +pub struct BufferedCanFd<'d, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> { + _phantom: PhantomData<&'d ()>, + info: &'static Info, + state: &'static State, + _mode: OperatingMode, + tx_buf: &'static TxFdBuf, + rx_buf: &'static RxFdBuf, + properties: Properties, +} + +impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCanFd<'d, TX_BUF_SIZE, RX_BUF_SIZE> { + fn new( + info: &'static Info, + state: &'static State, + _mode: OperatingMode, + tx_buf: &'static TxFdBuf, + rx_buf: &'static RxFdBuf, + ) -> Self { + BufferedCanFd { + _phantom: PhantomData, + info, + state, + _mode, + tx_buf, + rx_buf, + properties: Properties::new(info), + } + .setup() + } + + /// Get driver properties + pub fn properties(&self) -> &Properties { + &self.properties + } + + fn setup(self) -> Self { + // We don't want interrupts being processed while we change modes. + critical_section::with(|_| { + let rx_inner = super::common::FdBufferedRxInner { + rx_sender: self.rx_buf.sender().into(), + }; + let tx_inner = super::common::FdBufferedTxInner { + tx_receiver: self.tx_buf.receiver().into(), + }; + let state = self.state as *const State; + unsafe { + let mut_state = state as *mut State; + (*mut_state).rx_mode = RxMode::FdBuffered(rx_inner); + (*mut_state).tx_mode = TxMode::FdBuffered(tx_inner); + } + }); + self + } + + /// Async write frame to TX buffer. + pub async fn write(&mut self, frame: FdFrame) { + self.tx_buf.send(frame).await; + self.info.interrupt0.pend(); // Wake for Tx + //T::IT0Interrupt::pend(); // Wake for Tx + } + + /// Async read frame from RX buffer. + pub async fn read(&mut self) -> Result { + self.rx_buf.receive().await + } + + /// Returns a sender that can be used for sending CAN frames. + pub fn writer(&self) -> BufferedFdCanSender { + BufferedFdCanSender { + tx_buf: self.tx_buf.sender().into(), + waker: self.info.tx_waker, + } + } + + /// Returns a receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver. + pub fn reader(&self) -> BufferedFdCanReceiver { + self.rx_buf.receiver().into() + } +} + +impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> Drop for BufferedCanFd<'d, TX_BUF_SIZE, RX_BUF_SIZE> { + fn drop(&mut self) { + critical_section::with(|_| { + let state = self.state as *const State; + unsafe { + let mut_state = state as *mut State; + (*mut_state).rx_mode = RxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + (*mut_state).tx_mode = TxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + } + }); + } +} + +/// FDCAN Rx only Instance +pub struct CanRx<'d> { + _phantom: PhantomData<&'d ()>, + info: &'static Info, + state: &'static State, + _mode: OperatingMode, +} + +impl<'d> CanRx<'d> { + /// Returns the next received message frame + pub async fn read(&mut self) -> Result { + self.state.rx_mode.read_classic(&self.info, &self.state).await + } + + /// Returns the next received message frame + pub async fn read_fd(&mut self) -> Result { + self.state.rx_mode.read_fd(&self.info, &self.state).await + } +} + +/// FDCAN Tx only Instance +pub struct CanTx<'d> { + _phantom: PhantomData<&'d ()>, + info: &'static Info, + state: &'static State, + config: crate::can::fd::config::FdCanConfig, + _mode: OperatingMode, +} + +impl<'c, 'd> CanTx<'d> { + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + pub async fn write(&mut self, frame: &Frame) -> Option { + self.state.tx_mode.write(self.info, frame).await + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + pub async fn write_fd(&mut self, frame: &FdFrame) -> Option { + self.state.tx_mode.write_fd(self.info, frame).await + } +} + +enum RxMode { + NonBuffered(AtomicWaker), + ClassicBuffered(super::common::ClassicBufferedRxInner), + FdBuffered(super::common::FdBufferedRxInner), +} + +impl RxMode { + fn register(&self, arg: &core::task::Waker) { + match self { + RxMode::NonBuffered(waker) => waker.register(arg), + _ => { + panic!("Bad Mode") + } + } + } + + fn on_interrupt(&self, fifonr: usize) { + T::registers().regs.ir().write(|w| w.set_rfn(fifonr, true)); + match self { + RxMode::NonBuffered(waker) => { + waker.wake(); + } + RxMode::ClassicBuffered(buf) => { + if let Some(result) = self.try_read::() { + let _ = buf.rx_sender.try_send(result); + } + } + RxMode::FdBuffered(buf) => { + if let Some(result) = self.try_read_fd::() { + let _ = buf.rx_sender.try_send(result); + } + } + } + } + + //async fn read_classic(&self) -> Result { + fn try_read(&self) -> Option> { + if let Some((frame, ts)) = T::registers().read(0) { + let ts = T::calc_timestamp(T::state().ns_per_timer_tick, ts); + Some(Ok(Envelope { ts, frame })) + } else if let Some((frame, ts)) = T::registers().read(1) { + let ts = T::calc_timestamp(T::state().ns_per_timer_tick, ts); + Some(Ok(Envelope { ts, frame })) + } else if let Some(err) = T::registers().curr_error() { + // TODO: this is probably wrong + Some(Err(err)) + } else { + None + } + } + + fn try_read_fd(&self) -> Option> { + if let Some((frame, ts)) = T::registers().read(0) { + let ts = T::calc_timestamp(T::state().ns_per_timer_tick, ts); + Some(Ok(FdEnvelope { ts, frame })) + } else if let Some((frame, ts)) = T::registers().read(1) { + let ts = T::calc_timestamp(T::state().ns_per_timer_tick, ts); + Some(Ok(FdEnvelope { ts, frame })) + } else if let Some(err) = T::registers().curr_error() { + // TODO: this is probably wrong + Some(Err(err)) + } else { + None + } + } + + fn read( + &self, + info: &'static Info, + state: &'static State, + ) -> Option> { + if let Some((msg, ts)) = info.regs.read(0) { + let ts = info.calc_timestamp(state.ns_per_timer_tick, ts); + Some(Ok((msg, ts))) + } else if let Some((msg, ts)) = info.regs.read(1) { + let ts = info.calc_timestamp(state.ns_per_timer_tick, ts); + Some(Ok((msg, ts))) + } else if let Some(err) = info.regs.curr_error() { + // TODO: this is probably wrong + Some(Err(err)) + } else { + None + } + } + + async fn read_async( + &self, + info: &'static Info, + state: &'static State, + ) -> Result<(F, Timestamp), BusError> { + //let _ = self.read::(info, state); + poll_fn(move |cx| { + state.err_waker.register(cx.waker()); + self.register(cx.waker()); + match self.read::<_>(info, state) { + Some(result) => Poll::Ready(result), + None => Poll::Pending, + } + }) + .await + } + + async fn read_classic(&self, info: &'static Info, state: &'static State) -> Result { + match self.read_async::<_>(info, state).await { + Ok((frame, ts)) => Ok(Envelope { ts, frame }), + Err(e) => Err(e), + } + } + + async fn read_fd(&self, info: &'static Info, state: &'static State) -> Result { + match self.read_async::<_>(info, state).await { + Ok((frame, ts)) => Ok(FdEnvelope { ts, frame }), + Err(e) => Err(e), + } + } +} + +enum TxMode { + NonBuffered(AtomicWaker), + ClassicBuffered(super::common::ClassicBufferedTxInner), + FdBuffered(super::common::FdBufferedTxInner), +} + +impl TxMode { + fn register(&self, arg: &core::task::Waker) { + match self { + TxMode::NonBuffered(waker) => { + waker.register(arg); + } + _ => { + panic!("Bad mode"); + } + } + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + async fn write_generic(&self, info: &'static Info, frame: &F) -> Option { + poll_fn(|cx| { + self.register(cx.waker()); + + if let Ok(dropped) = info.regs.write(frame) { + return Poll::Ready(dropped); + } + + // Couldn't replace any lower priority frames. Need to wait for some mailboxes + // to clear. + Poll::Pending + }) + .await + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + async fn write(&self, info: &'static Info, frame: &Frame) -> Option { + self.write_generic::<_>(info, frame).await + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + async fn write_fd(&self, info: &'static Info, frame: &FdFrame) -> Option { + self.write_generic::<_>(info, frame).await + } +} + +/// Common driver properties, including filters and error counters +pub struct Properties { + info: &'static Info, + // phantom pointer to ensure !Sync + //instance: PhantomData<*const T>, +} + +impl Properties { + fn new(info: &'static Info) -> Self { + Self { + info, + //instance: Default::default(), + } + } + + /// Set a standard address CAN filter in the specified slot in FDCAN memory. + #[inline] + pub fn set_standard_filter(&self, slot: StandardFilterSlot, filter: StandardFilter) { + self.info.regs.msg_ram_mut().filters.flssa[slot as usize].activate(filter); + } + + /// Set the full array of standard address CAN filters in FDCAN memory. + /// Overwrites all standard address filters in memory. + pub fn set_standard_filters(&self, filters: &[StandardFilter; STANDARD_FILTER_MAX as usize]) { + for (i, f) in filters.iter().enumerate() { + self.info.regs.msg_ram_mut().filters.flssa[i].activate(*f); + } + } + + /// Set an extended address CAN filter in the specified slot in FDCAN memory. + #[inline] + pub fn set_extended_filter(&self, slot: ExtendedFilterSlot, filter: ExtendedFilter) { + self.info.regs.msg_ram_mut().filters.flesa[slot as usize].activate(filter); + } + + /// Set the full array of extended address CAN filters in FDCAN memory. + /// Overwrites all extended address filters in memory. + pub fn set_extended_filters(&self, filters: &[ExtendedFilter; EXTENDED_FILTER_MAX as usize]) { + for (i, f) in filters.iter().enumerate() { + self.info.regs.msg_ram_mut().filters.flesa[i].activate(*f); + } + } + + /// Get the CAN RX error counter + pub fn rx_error_count(&self) -> u8 { + self.info.regs.regs.ecr().read().rec() + } + + /// Get the CAN TX error counter + pub fn tx_error_count(&self) -> u8 { + self.info.regs.regs.ecr().read().tec() + } + + /// Get the current bus error mode + pub fn bus_error_mode(&self) -> BusErrorMode { + // This read will clear LEC and DLEC. This is not ideal, but protocol + // error reporting in this driver should have a big ol' FIXME on it + // anyway! + let psr = self.info.regs.regs.psr().read(); + match (psr.bo(), psr.ep()) { + (false, false) => BusErrorMode::ErrorActive, + (false, true) => BusErrorMode::ErrorPassive, + (true, _) => BusErrorMode::BusOff, + } + } +} + +struct State { + pub rx_mode: RxMode, + pub tx_mode: TxMode, + pub ns_per_timer_tick: u64, + + pub err_waker: AtomicWaker, +} + +impl State { + const fn new() -> Self { + Self { + rx_mode: RxMode::NonBuffered(AtomicWaker::new()), + tx_mode: TxMode::NonBuffered(AtomicWaker::new()), + ns_per_timer_tick: 0, + err_waker: AtomicWaker::new(), + } + } +} + +struct Info { + regs: Registers, + interrupt0: crate::interrupt::Interrupt, + _interrupt1: crate::interrupt::Interrupt, + tx_waker: fn(), +} + +impl Info { + #[cfg(feature = "time")] + fn calc_timestamp(&self, ns_per_timer_tick: u64, ts_val: u16) -> Timestamp { + let now_embassy = embassy_time::Instant::now(); + if ns_per_timer_tick == 0 { + return now_embassy; + } + let cantime = { self.regs.regs.tscv().read().tsc() }; + let delta = cantime.overflowing_sub(ts_val).0 as u64; + let ns = ns_per_timer_tick * delta as u64; + now_embassy - embassy_time::Duration::from_nanos(ns) + } + + #[cfg(not(feature = "time"))] + fn calc_timestamp(&self, _ns_per_timer_tick: u64, ts_val: u16) -> Timestamp { + ts_val + } +} + +trait SealedInstance { + const MSG_RAM_OFFSET: usize; + + fn info() -> &'static Info; + fn registers() -> crate::can::fd::peripheral::Registers; + fn state() -> &'static State; + unsafe fn mut_state() -> &'static mut State; + fn calc_timestamp(ns_per_timer_tick: u64, ts_val: u16) -> Timestamp; +} + +/// Instance trait +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral + 'static { + /// Interrupt 0 + type IT0Interrupt: crate::interrupt::typelevel::Interrupt; + /// Interrupt 1 + type IT1Interrupt: crate::interrupt::typelevel::Interrupt; +} + +/// Fdcan Instance struct +pub struct FdcanInstance<'a, T>(PeripheralRef<'a, T>); + +macro_rules! impl_fdcan { + ($inst:ident, + //$irq0:ident, $irq1:ident, + $msg_ram_inst:ident, $msg_ram_offset:literal) => { + impl SealedInstance for peripherals::$inst { + const MSG_RAM_OFFSET: usize = $msg_ram_offset; + + fn info() -> &'static Info { + static INFO: Info = Info { + regs: Registers{regs: crate::pac::$inst, msgram: crate::pac::$msg_ram_inst, msg_ram_offset: $msg_ram_offset}, + interrupt0: crate::_generated::peripheral_interrupts::$inst::IT0::IRQ, + _interrupt1: crate::_generated::peripheral_interrupts::$inst::IT1::IRQ, + tx_waker: crate::_generated::peripheral_interrupts::$inst::IT0::pend, + }; + &INFO + } + fn registers() -> Registers { + Registers{regs: crate::pac::$inst, msgram: crate::pac::$msg_ram_inst, msg_ram_offset: Self::MSG_RAM_OFFSET} + } + unsafe fn mut_state() -> &'static mut State { + static mut STATE: State = State::new(); + &mut *core::ptr::addr_of_mut!(STATE) + } + fn state() -> &'static State { + unsafe { peripherals::$inst::mut_state() } + } + + #[cfg(feature = "time")] + fn calc_timestamp(ns_per_timer_tick: u64, ts_val: u16) -> Timestamp { + let now_embassy = embassy_time::Instant::now(); + if ns_per_timer_tick == 0 { + return now_embassy; + } + let cantime = { Self::registers().regs.tscv().read().tsc() }; + let delta = cantime.overflowing_sub(ts_val).0 as u64; + let ns = ns_per_timer_tick * delta as u64; + now_embassy - embassy_time::Duration::from_nanos(ns) + } + + #[cfg(not(feature = "time"))] + fn calc_timestamp(_ns_per_timer_tick: u64, ts_val: u16) -> Timestamp { + ts_val + } + + } + + #[allow(non_snake_case)] + pub(crate) mod $inst { + + foreach_interrupt!( + ($inst,can,FDCAN,IT0,$irq:ident) => { + pub type Interrupt0 = crate::interrupt::typelevel::$irq; + }; + ($inst,can,FDCAN,IT1,$irq:ident) => { + pub type Interrupt1 = crate::interrupt::typelevel::$irq; + }; + ); + } + impl Instance for peripherals::$inst { + type IT0Interrupt = $inst::Interrupt0; + type IT1Interrupt = $inst::Interrupt1; + } + }; + + ($inst:ident, $msg_ram_inst:ident) => { + impl_fdcan!($inst, $msg_ram_inst, 0); + }; +} + +#[cfg(not(can_fdcan_h7))] +foreach_peripheral!( + (can, FDCAN) => { impl_fdcan!(FDCAN, FDCANRAM); }; + (can, FDCAN1) => { impl_fdcan!(FDCAN1, FDCANRAM1); }; + (can, FDCAN2) => { impl_fdcan!(FDCAN2, FDCANRAM2); }; + (can, FDCAN3) => { impl_fdcan!(FDCAN3, FDCANRAM3); }; +); + +#[cfg(can_fdcan_h7)] +foreach_peripheral!( + (can, FDCAN1) => { impl_fdcan!(FDCAN1, FDCANRAM, 0x0000); }; + (can, FDCAN2) => { impl_fdcan!(FDCAN2, FDCANRAM, 0x0C00); }; + (can, FDCAN3) => { impl_fdcan!(FDCAN3, FDCANRAM, 0x1800); }; +); + +pin_trait!(RxPin, Instance); +pin_trait!(TxPin, Instance); diff --git a/embassy/embassy-stm32/src/can/frame.rs b/embassy/embassy-stm32/src/can/frame.rs new file mode 100644 index 0000000..d2d1f7a --- /dev/null +++ b/embassy/embassy-stm32/src/can/frame.rs @@ -0,0 +1,463 @@ +//! Definition for CAN Frames +use bit_field::BitField; + +use crate::can::enums::FrameCreateError; + +/// Calculate proper timestamp when available. +#[cfg(feature = "time")] +pub type Timestamp = embassy_time::Instant; + +/// Raw register timestamp +#[cfg(not(feature = "time"))] +pub type Timestamp = u16; + +/// CAN Header, without meta data +#[derive(Debug, Copy, Clone)] +pub struct Header { + id: embedded_can::Id, + len: u8, + flags: u8, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Header { + fn format(&self, fmt: defmt::Formatter<'_>) { + match self.id() { + embedded_can::Id::Standard(id) => { + defmt::write!(fmt, "Can Standard ID={:x} len={}", id.as_raw(), self.len,) + } + embedded_can::Id::Extended(id) => { + defmt::write!(fmt, "Can Extended ID={:x} len={}", id.as_raw(), self.len,) + } + } + } +} + +impl Header { + const FLAG_RTR: usize = 0; // Remote + const FLAG_FDCAN: usize = 1; // FDCan vs Classic CAN + const FLAG_BRS: usize = 2; // Bit-rate switching, ignored for Classic CAN + + /// Create new CAN Header + pub fn new(id: embedded_can::Id, len: u8, rtr: bool) -> Header { + let mut flags = 0u8; + flags.set_bit(Self::FLAG_RTR, rtr); + Header { id, len, flags } + } + + /// Create new CAN FD Header + pub fn new_fd(id: embedded_can::Id, len: u8, rtr: bool, brs: bool) -> Header { + let mut flags = 0u8; + flags.set_bit(Self::FLAG_RTR, rtr); + flags.set_bit(Self::FLAG_FDCAN, true); + flags.set_bit(Self::FLAG_BRS, brs); + Header { id, len, flags } + } + + /// Return ID + pub fn id(&self) -> &embedded_can::Id { + &self.id + } + + /// Return length as u8 + pub fn len(&self) -> u8 { + self.len + } + + /// Is remote frame + pub fn rtr(&self) -> bool { + self.flags.get_bit(Self::FLAG_RTR) + } + + /// Request/is FDCAN frame + pub fn fdcan(&self) -> bool { + self.flags.get_bit(Self::FLAG_FDCAN) + } + + /// Request/is Flexible Data Rate + pub fn bit_rate_switching(&self) -> bool { + self.flags.get_bit(Self::FLAG_BRS) + } + + /// Get priority of frame + pub(crate) fn priority(&self) -> u32 { + match self.id() { + embedded_can::Id::Standard(id) => (id.as_raw() as u32) << 18, + embedded_can::Id::Extended(id) => id.as_raw(), + } + } +} + +/// Trait for FDCAN frame types, providing ability to construct from a Header +/// and to retrieve the Header from a frame +pub trait CanHeader: Sized { + /// Construct frame from header and payload + fn from_header(header: Header, data: &[u8]) -> Result; + + /// Get this frame's header struct + fn header(&self) -> &Header; +} + +/// Payload of a classic CAN data frame. +/// +/// Contains 0 to 8 Bytes of data. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ClassicData { + pub(crate) bytes: [u8; Self::MAX_DATA_LEN], +} + +impl ClassicData { + pub(crate) const MAX_DATA_LEN: usize = 8; + /// Creates a data payload from a raw byte slice. + /// + /// Returns `None` if `data` is more than 64 bytes (which is the maximum) or + /// cannot be represented with an FDCAN DLC. + pub fn new(data: &[u8]) -> Result { + if data.len() > 8 { + return Err(FrameCreateError::InvalidDataLength); + } + + let mut bytes = [0; 8]; + bytes[..data.len()].copy_from_slice(data); + + Ok(Self { bytes }) + } + + /// Raw read access to data. + pub fn raw(&self) -> &[u8] { + &self.bytes + } + + /// Checks if the length can be encoded in FDCAN DLC field. + pub const fn is_valid_len(len: usize) -> bool { + match len { + 0..=8 => true, + _ => false, + } + } + + /// Creates an empty data payload containing 0 bytes. + #[inline] + pub const fn empty() -> Self { + Self { bytes: [0; 8] } + } +} + +/// Frame with up to 8 bytes of data payload as per Classic(non-FD) CAN +/// For CAN-FD support use FdFrame +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Frame { + can_header: Header, + data: ClassicData, +} + +impl Frame { + /// Create a new CAN classic Frame + pub fn new(can_header: Header, raw_data: &[u8]) -> Result { + let data = ClassicData::new(raw_data)?; + Ok(Frame { can_header, data: data }) + } + + /// Creates a new data frame. + pub fn new_data(id: impl Into, data: &[u8]) -> Result { + let eid: embedded_can::Id = id.into(); + let header = Header::new(eid, data.len() as u8, false); + Self::new(header, data) + } + + /// Create new extended frame + pub fn new_extended(raw_id: u32, raw_data: &[u8]) -> Result { + if let Some(id) = embedded_can::ExtendedId::new(raw_id) { + Self::new(Header::new(id.into(), raw_data.len() as u8, false), raw_data) + } else { + Err(FrameCreateError::InvalidCanId) + } + } + + /// Create new standard frame + pub fn new_standard(raw_id: u16, raw_data: &[u8]) -> Result { + if let Some(id) = embedded_can::StandardId::new(raw_id) { + Self::new(Header::new(id.into(), raw_data.len() as u8, false), raw_data) + } else { + Err(FrameCreateError::InvalidCanId) + } + } + + /// Create new remote frame + pub fn new_remote(id: impl Into, len: usize) -> Result { + if len <= 8usize { + Self::new(Header::new(id.into(), len as u8, true), &[0; 8]) + } else { + Err(FrameCreateError::InvalidDataLength) + } + } + + /// Get reference to data + pub fn header(&self) -> &Header { + &self.can_header + } + + /// Return ID + pub fn id(&self) -> &embedded_can::Id { + &self.can_header.id + } + + /// Get reference to data + pub fn data(&self) -> &[u8] { + &self.data.raw() + } + + /// Get priority of frame + pub fn priority(&self) -> u32 { + self.header().priority() + } +} + +impl embedded_can::Frame for Frame { + fn new(id: impl Into, raw_data: &[u8]) -> Option { + let frameopt = Frame::new(Header::new(id.into(), raw_data.len() as u8, false), raw_data); + match frameopt { + Ok(frame) => Some(frame), + Err(_) => None, + } + } + fn new_remote(id: impl Into, len: usize) -> Option { + if len <= 8 { + let frameopt = Frame::new(Header::new(id.into(), len as u8, true), &[0; 8]); + match frameopt { + Ok(frame) => Some(frame), + Err(_) => None, + } + } else { + None + } + } + fn is_extended(&self) -> bool { + match self.can_header.id { + embedded_can::Id::Extended(_) => true, + embedded_can::Id::Standard(_) => false, + } + } + fn is_remote_frame(&self) -> bool { + self.can_header.rtr() + } + fn id(&self) -> embedded_can::Id { + self.can_header.id + } + fn dlc(&self) -> usize { + self.can_header.len as usize + } + fn data(&self) -> &[u8] { + &self.data.raw() + } +} + +impl CanHeader for Frame { + fn from_header(header: Header, data: &[u8]) -> Result { + Self::new(header, data) + } + + fn header(&self) -> &Header { + self.header() + } +} + +/// Contains CAN frame and additional metadata. +/// +/// Timestamp is available if `time` feature is enabled. +/// For CAN-FD support use FdEnvelope +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Envelope { + /// Reception time. + pub ts: Timestamp, + /// The actual CAN frame. + pub frame: Frame, +} + +impl Envelope { + /// Convert into a tuple + pub fn parts(self) -> (Frame, Timestamp) { + (self.frame, self.ts) + } +} + +/// Payload of a (FD)CAN data frame. +/// +/// Contains 0 to 64 Bytes of data. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FdData { + pub(crate) bytes: [u8; 64], +} + +impl FdData { + /// Creates a data payload from a raw byte slice. + /// + /// Returns `None` if `data` is more than 64 bytes (which is the maximum) or + /// cannot be represented with an FDCAN DLC. + pub fn new(data: &[u8]) -> Result { + if !FdData::is_valid_len(data.len()) { + return Err(FrameCreateError::InvalidDataLength); + } + + let mut bytes = [0; 64]; + bytes[..data.len()].copy_from_slice(data); + + Ok(Self { bytes }) + } + + /// Raw read access to data. + pub fn raw(&self) -> &[u8] { + &self.bytes + } + + /// Checks if the length can be encoded in FDCAN DLC field. + pub const fn is_valid_len(len: usize) -> bool { + match len { + 0..=8 => true, + 12 => true, + 16 => true, + 20 => true, + 24 => true, + 32 => true, + 48 => true, + 64 => true, + _ => false, + } + } + + /// Creates an empty data payload containing 0 bytes. + #[inline] + pub const fn empty() -> Self { + Self { bytes: [0; 64] } + } +} + +/// Frame with up to 8 bytes of data payload as per Fd CAN +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FdFrame { + can_header: Header, + data: FdData, +} + +impl FdFrame { + /// Create a new CAN classic Frame + pub fn new(can_header: Header, raw_data: &[u8]) -> Result { + let data = FdData::new(raw_data)?; + Ok(FdFrame { can_header, data }) + } + + /// Create new extended frame + pub fn new_extended(raw_id: u32, raw_data: &[u8]) -> Result { + if let Some(id) = embedded_can::ExtendedId::new(raw_id) { + Self::new(Header::new(id.into(), raw_data.len() as u8, false), raw_data) + } else { + Err(FrameCreateError::InvalidCanId) + } + } + + /// Create new standard frame + pub fn new_standard(raw_id: u16, raw_data: &[u8]) -> Result { + if let Some(id) = embedded_can::StandardId::new(raw_id) { + Self::new(Header::new(id.into(), raw_data.len() as u8, false), raw_data) + } else { + Err(FrameCreateError::InvalidCanId) + } + } + + /// Create new remote frame + pub fn new_remote(id: impl Into, len: usize) -> Result { + if len <= 8 { + Self::new(Header::new(id.into(), len as u8, true), &[0; 8]) + } else { + Err(FrameCreateError::InvalidDataLength) + } + } + + /// Get reference to data + pub fn header(&self) -> &Header { + &self.can_header + } + + /// Return ID + pub fn id(&self) -> &embedded_can::Id { + &self.can_header.id + } + + /// Get reference to data + pub fn data(&self) -> &[u8] { + &self.data.raw() + } +} + +impl embedded_can::Frame for FdFrame { + fn new(id: impl Into, raw_data: &[u8]) -> Option { + match FdFrame::new(Header::new_fd(id.into(), raw_data.len() as u8, false, true), raw_data) { + Ok(frame) => Some(frame), + Err(_) => None, + } + } + fn new_remote(id: impl Into, len: usize) -> Option { + if len <= 8 { + match FdFrame::new(Header::new_fd(id.into(), len as u8, true, true), &[0; 64]) { + Ok(frame) => Some(frame), + Err(_) => None, + } + } else { + None + } + } + fn is_extended(&self) -> bool { + match self.can_header.id { + embedded_can::Id::Extended(_) => true, + embedded_can::Id::Standard(_) => false, + } + } + fn is_remote_frame(&self) -> bool { + self.can_header.rtr() + } + fn id(&self) -> embedded_can::Id { + self.can_header.id + } + // Returns length in bytes even for CANFD packets which embedded-can does not really mention. + fn dlc(&self) -> usize { + self.can_header.len as usize + } + fn data(&self) -> &[u8] { + &self.data.raw() + } +} + +impl CanHeader for FdFrame { + fn from_header(header: Header, data: &[u8]) -> Result { + Self::new(header, data) + } + + fn header(&self) -> &Header { + self.header() + } +} + +/// Contains CAN FD frame and additional metadata. +/// +/// Timestamp is available if `time` feature is enabled. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FdEnvelope { + /// Reception time. + pub ts: Timestamp, + + /// The actual CAN frame. + pub frame: FdFrame, +} + +impl FdEnvelope { + /// Convert into a tuple + pub fn parts(self) -> (FdFrame, Timestamp) { + (self.frame, self.ts) + } +} diff --git a/embassy/embassy-stm32/src/can/mod.rs b/embassy/embassy-stm32/src/can/mod.rs new file mode 100644 index 0000000..410a6bf --- /dev/null +++ b/embassy/embassy-stm32/src/can/mod.rs @@ -0,0 +1,14 @@ +//! Controller Area Network (CAN) +#![macro_use] + +#[cfg_attr(can_bxcan, path = "bxcan/mod.rs")] +#[cfg_attr(any(can_fdcan_v1, can_fdcan_h7), path = "fdcan.rs")] +mod _version; +pub use _version::*; + +mod common; +pub mod enums; +pub mod frame; +pub mod util; + +pub use frame::Frame; diff --git a/embassy/embassy-stm32/src/can/util.rs b/embassy/embassy-stm32/src/can/util.rs new file mode 100644 index 0000000..fcdbbad --- /dev/null +++ b/embassy/embassy-stm32/src/can/util.rs @@ -0,0 +1,117 @@ +//! Utility functions shared between CAN controller types. + +use core::num::{NonZeroU16, NonZeroU8}; + +/// Shared struct to represent bit timings used by calc_can_timings. +#[derive(Clone, Copy, Debug)] +pub struct NominalBitTiming { + /// Value by which the oscillator frequency is divided for generating the bit time quanta. The bit + /// time is built up from a multiple of this quanta. Valid values are 1 to 512. + pub prescaler: NonZeroU16, + /// Valid values are 1 to 128. + pub seg1: NonZeroU8, + /// Valid values are 1 to 255. + pub seg2: NonZeroU8, + /// Valid values are 1 to 128. + pub sync_jump_width: NonZeroU8, +} + +/// Calculate nominal CAN bit timing based on CAN bitrate and periphial clock frequency +pub fn calc_can_timings(periph_clock: crate::time::Hertz, can_bitrate: u32) -> Option { + const BS1_MAX: u8 = 16; + const BS2_MAX: u8 = 8; + const MAX_SAMPLE_POINT_PERMILL: u16 = 900; + + let periph_clock = periph_clock.0; + + if can_bitrate < 1000 { + return None; + } + + // Ref. "Automatic Baudrate Detection in CANopen Networks", U. Koppe, MicroControl GmbH & Co. KG + // CAN in Automation, 2003 + // + // According to the source, optimal quanta per bit are: + // Bitrate Optimal Maximum + // 1000 kbps 8 10 + // 500 kbps 16 17 + // 250 kbps 16 17 + // 125 kbps 16 17 + let max_quanta_per_bit: u8 = if can_bitrate >= 1_000_000 { 10 } else { 17 }; + + // Computing (prescaler * BS): + // BITRATE = 1 / (PRESCALER * (1 / PCLK) * (1 + BS1 + BS2)) -- See the Reference Manual + // BITRATE = PCLK / (PRESCALER * (1 + BS1 + BS2)) -- Simplified + // let: + // BS = 1 + BS1 + BS2 -- Number of time quanta per bit + // PRESCALER_BS = PRESCALER * BS + // ==> + // PRESCALER_BS = PCLK / BITRATE + let prescaler_bs = periph_clock / can_bitrate; + + // Searching for such prescaler value so that the number of quanta per bit is highest. + let mut bs1_bs2_sum = max_quanta_per_bit - 1; + while (prescaler_bs % (1 + bs1_bs2_sum) as u32) != 0 { + if bs1_bs2_sum <= 2 { + return None; // No solution + } + bs1_bs2_sum -= 1; + } + + let prescaler = prescaler_bs / (1 + bs1_bs2_sum) as u32; + if (prescaler < 1) || (prescaler > 1024) { + return None; // No solution + } + + // Now we have a constraint: (BS1 + BS2) == bs1_bs2_sum. + // We need to find such values so that the sample point is as close as possible to the optimal value, + // which is 87.5%, which is 7/8. + // + // Solve[(1 + bs1)/(1 + bs1 + bs2) == 7/8, bs2] (* Where 7/8 is 0.875, the recommended sample point location *) + // {{bs2 -> (1 + bs1)/7}} + // + // Hence: + // bs2 = (1 + bs1) / 7 + // bs1 = (7 * bs1_bs2_sum - 1) / 8 + // + // Sample point location can be computed as follows: + // Sample point location = (1 + bs1) / (1 + bs1 + bs2) + // + // Since the optimal solution is so close to the maximum, we prepare two solutions, and then pick the best one: + // - With rounding to nearest + // - With rounding to zero + let mut bs1 = ((7 * bs1_bs2_sum - 1) + 4) / 8; // Trying rounding to nearest first + let mut bs2 = bs1_bs2_sum - bs1; + core::assert!(bs1_bs2_sum > bs1); + + let sample_point_permill = 1000 * ((1 + bs1) / (1 + bs1 + bs2)) as u16; + if sample_point_permill > MAX_SAMPLE_POINT_PERMILL { + // Nope, too far; now rounding to zero + bs1 = (7 * bs1_bs2_sum - 1) / 8; + bs2 = bs1_bs2_sum - bs1; + } + + // Check is BS1 and BS2 are in range + if (bs1 < 1) || (bs1 > BS1_MAX) || (bs2 < 1) || (bs2 > BS2_MAX) { + return None; + } + + // Check if final bitrate matches the requested + if can_bitrate != (periph_clock / (prescaler * (1 + bs1 + bs2) as u32)) { + return None; + } + + // One is recommended by DS-015, CANOpen, and DeviceNet + let sync_jump_width = core::num::NonZeroU8::new(1)?; + + let seg1 = core::num::NonZeroU8::new(bs1)?; + let seg2 = core::num::NonZeroU8::new(bs2)?; + let nz_prescaler = core::num::NonZeroU16::new(prescaler as u16)?; + + Some(NominalBitTiming { + sync_jump_width, + prescaler: nz_prescaler, + seg1, + seg2, + }) +} diff --git a/embassy/embassy-stm32/src/cordic/enums.rs b/embassy/embassy-stm32/src/cordic/enums.rs new file mode 100644 index 0000000..e8695fa --- /dev/null +++ b/embassy/embassy-stm32/src/cordic/enums.rs @@ -0,0 +1,71 @@ +/// CORDIC function +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Function { + Cos = 0, + Sin, + Phase, + Modulus, + Arctan, + Cosh, + Sinh, + Arctanh, + Ln, + Sqrt, +} + +/// CORDIC precision +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, Default)] +pub enum Precision { + Iters4 = 1, + Iters8, + Iters12, + Iters16, + Iters20, + #[default] + Iters24, // this value is recommended by Reference Manual + Iters28, + Iters32, + Iters36, + Iters40, + Iters44, + Iters48, + Iters52, + Iters56, + Iters60, +} + +/// CORDIC scale +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, Default, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Scale { + #[default] + Arg1Res1 = 0, + Arg1o2Res2, + Arg1o4Res4, + Arg1o8Res8, + Arg1o16Res16, + Arg1o32Res32, + Arg1o64Res64, + Arg1o128Res128, +} + +/// CORDIC argument/result register access count +#[allow(missing_docs)] +#[derive(Clone, Copy, Default)] +pub enum AccessCount { + #[default] + One, + Two, +} + +/// CORDIC argument/result data width +#[allow(missing_docs)] +#[derive(Clone, Copy)] +pub enum Width { + Bits32, + Bits16, +} diff --git a/embassy/embassy-stm32/src/cordic/errors.rs b/embassy/embassy-stm32/src/cordic/errors.rs new file mode 100644 index 0000000..3c70fc9 --- /dev/null +++ b/embassy/embassy-stm32/src/cordic/errors.rs @@ -0,0 +1,144 @@ +use super::{Function, Scale}; + +/// Error for [Cordic](super::Cordic) +#[derive(Debug)] +pub enum CordicError { + /// Config error + ConfigError(ConfigError), + /// Argument length is incorrect + ArgumentLengthIncorrect, + /// Result buffer length error + ResultLengthNotEnough, + /// Input value is out of range for Q1.x format + NumberOutOfRange(NumberOutOfRange), + /// Argument error + ArgError(ArgError), +} + +impl From for CordicError { + fn from(value: ConfigError) -> Self { + Self::ConfigError(value) + } +} + +impl From for CordicError { + fn from(value: NumberOutOfRange) -> Self { + Self::NumberOutOfRange(value) + } +} + +impl From for CordicError { + fn from(value: ArgError) -> Self { + Self::ArgError(value) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for CordicError { + fn format(&self, fmt: defmt::Formatter) { + use CordicError::*; + + match self { + ConfigError(e) => defmt::write!(fmt, "{}", e), + ResultLengthNotEnough => defmt::write!(fmt, "Output buffer length is not long enough"), + ArgumentLengthIncorrect => defmt::write!(fmt, "Argument length incorrect"), + NumberOutOfRange(e) => defmt::write!(fmt, "{}", e), + ArgError(e) => defmt::write!(fmt, "{}", e), + } + } +} + +/// Error during parsing [Cordic::Config](super::Config) +#[allow(dead_code)] +#[derive(Debug)] +pub struct ConfigError { + pub(super) func: Function, + pub(super) scale_range: [u8; 2], +} + +#[cfg(feature = "defmt")] +impl defmt::Format for ConfigError { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "For FUNCTION: {},", self.func); + + if self.scale_range[0] == self.scale_range[1] { + defmt::write!(fmt, " SCALE value should be {}", self.scale_range[0]) + } else { + defmt::write!( + fmt, + " SCALE value should be {} <= SCALE <= {}", + self.scale_range[0], + self.scale_range[1] + ) + } + } +} + +/// Input value is out of range for Q1.x format +#[allow(missing_docs)] +#[derive(Debug)] +pub enum NumberOutOfRange { + BelowLowerBound, + AboveUpperBound, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for NumberOutOfRange { + fn format(&self, fmt: defmt::Formatter) { + use NumberOutOfRange::*; + + match self { + BelowLowerBound => defmt::write!(fmt, "input value should be equal or greater than -1"), + AboveUpperBound => defmt::write!(fmt, "input value should be equal or less than 1"), + } + } +} + +/// Error on checking input arguments +#[allow(dead_code)] +#[derive(Debug)] +pub struct ArgError { + pub(super) func: Function, + pub(super) scale: Option, + pub(super) arg_range: [f32; 2], // only for debug display, f32 is ok + pub(super) inclusive_upper_bound: bool, + pub(super) arg_type: ArgType, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for ArgError { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "For FUNCTION: {},", self.func); + + if let Some(scale) = self.scale { + defmt::write!(fmt, " when SCALE is {},", scale); + } + + defmt::write!(fmt, " {} should be", self.arg_type); + + if self.inclusive_upper_bound { + defmt::write!( + fmt, + " {} <= {} <= {}", + self.arg_range[0], + self.arg_type, + self.arg_range[1] + ) + } else { + defmt::write!( + fmt, + " {} <= {} < {}", + self.arg_range[0], + self.arg_type, + self.arg_range[1] + ) + }; + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(super) enum ArgType { + Arg1, + Arg2, +} diff --git a/embassy/embassy-stm32/src/cordic/mod.rs b/embassy/embassy-stm32/src/cordic/mod.rs new file mode 100644 index 0000000..fb342d2 --- /dev/null +++ b/embassy/embassy-stm32/src/cordic/mod.rs @@ -0,0 +1,730 @@ +//! coordinate rotation digital computer (CORDIC) + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; + +use crate::pac::cordic::vals; +use crate::{dma, peripherals, rcc}; + +mod enums; +pub use enums::*; + +mod errors; +pub use errors::*; + +pub mod utils; + +/// CORDIC driver +pub struct Cordic<'d, T: Instance> { + peri: PeripheralRef<'d, T>, + config: Config, +} + +/// Cordic instance +trait SealedInstance { + /// Get access to CORDIC registers + fn regs() -> crate::pac::cordic::Cordic; + + /// Set Function value + fn set_func(&self, func: Function) { + Self::regs() + .csr() + .modify(|v| v.set_func(vals::Func::from_bits(func as u8))); + } + + /// Set Precision value + fn set_precision(&self, precision: Precision) { + Self::regs() + .csr() + .modify(|v| v.set_precision(vals::Precision::from_bits(precision as u8))) + } + + /// Set Scale value + fn set_scale(&self, scale: Scale) { + Self::regs() + .csr() + .modify(|v| v.set_scale(vals::Scale::from_bits(scale as u8))) + } + + /// Enable global interrupt + #[allow(unused)] + fn enable_irq(&self) { + Self::regs().csr().modify(|v| v.set_ien(true)) + } + + /// Disable global interrupt + fn disable_irq(&self) { + Self::regs().csr().modify(|v| v.set_ien(false)) + } + + /// Enable Read DMA + fn enable_read_dma(&self) { + Self::regs().csr().modify(|v| { + v.set_dmaren(true); + }) + } + + /// Disable Read DMA + fn disable_read_dma(&self) { + Self::regs().csr().modify(|v| { + v.set_dmaren(false); + }) + } + + /// Enable Write DMA + fn enable_write_dma(&self) { + Self::regs().csr().modify(|v| { + v.set_dmawen(true); + }) + } + + /// Disable Write DMA + fn disable_write_dma(&self) { + Self::regs().csr().modify(|v| { + v.set_dmawen(false); + }) + } + + /// Set NARGS value + fn set_argument_count(&self, n: AccessCount) { + Self::regs().csr().modify(|v| { + v.set_nargs(match n { + AccessCount::One => vals::Num::NUM1, + AccessCount::Two => vals::Num::NUM2, + }) + }) + } + + /// Set NRES value + fn set_result_count(&self, n: AccessCount) { + Self::regs().csr().modify(|v| { + v.set_nres(match n { + AccessCount::One => vals::Num::NUM1, + AccessCount::Two => vals::Num::NUM2, + }); + }) + } + + /// Set ARGSIZE and RESSIZE value + fn set_data_width(&self, arg: Width, res: Width) { + Self::regs().csr().modify(|v| { + v.set_argsize(match arg { + Width::Bits32 => vals::Size::BITS32, + Width::Bits16 => vals::Size::BITS16, + }); + v.set_ressize(match res { + Width::Bits32 => vals::Size::BITS32, + Width::Bits16 => vals::Size::BITS16, + }) + }) + } + + /// Read RRDY flag + fn ready_to_read(&self) -> bool { + Self::regs().csr().read().rrdy() + } + + /// Write value to WDATA + fn write_argument(&self, arg: u32) { + Self::regs().wdata().write_value(arg) + } + + /// Read value from RDATA + fn read_result(&self) -> u32 { + Self::regs().rdata().read() + } +} + +/// CORDIC instance trait +#[allow(private_bounds)] +pub trait Instance: SealedInstance + Peripheral

+ crate::rcc::RccPeripheral {} + +/// CORDIC configuration +#[derive(Debug)] +pub struct Config { + function: Function, + precision: Precision, + scale: Scale, +} + +impl Config { + /// Create a config for Cordic driver + pub fn new(function: Function, precision: Precision, scale: Scale) -> Result { + let config = Self { + function, + precision, + scale, + }; + + config.check_scale()?; + + Ok(config) + } + + fn check_scale(&self) -> Result<(), ConfigError> { + use Function::*; + + let scale_raw = self.scale as u8; + + let err_range = match self.function { + Cos | Sin | Phase | Modulus if !(0..=0).contains(&scale_raw) => Some([0, 0]), + + Arctan if !(0..=7).contains(&scale_raw) => Some([0, 7]), + + Cosh | Sinh | Arctanh if !(1..=1).contains(&scale_raw) => Some([1, 1]), + + Ln if !(1..=4).contains(&scale_raw) => Some([1, 4]), + + Sqrt if !(0..=2).contains(&scale_raw) => Some([0, 2]), + + Cos | Sin | Phase | Modulus | Arctan | Cosh | Sinh | Arctanh | Ln | Sqrt => None, + }; + + if let Some(range) = err_range { + Err(ConfigError { + func: self.function, + scale_range: range, + }) + } else { + Ok(()) + } + } +} + +// common method +impl<'d, T: Instance> Cordic<'d, T> { + /// Create a Cordic driver instance + /// + /// Note: + /// If you need a peripheral -> CORDIC -> peripheral mode, + /// you may want to set Cordic into [Mode::ZeroOverhead] mode, and add extra arguments with [Self::extra_config] + pub fn new(peri: impl Peripheral

+ 'd, config: Config) -> Self { + rcc::enable_and_reset::(); + + into_ref!(peri); + + let mut instance = Self { peri, config }; + + instance.reconfigure(); + + instance + } + + /// Set a new config for Cordic driver + pub fn set_config(&mut self, config: Config) { + self.config = config; + self.reconfigure(); + } + + /// Set extra config for data count and data width. + pub fn extra_config(&mut self, arg_cnt: AccessCount, arg_width: Width, res_width: Width) { + self.peri.set_argument_count(arg_cnt); + self.peri.set_data_width(arg_width, res_width); + } + + fn clean_rrdy_flag(&mut self) { + while self.peri.ready_to_read() { + self.peri.read_result(); + } + } + + /// Disable IRQ and DMA, clean RRDY, and set ARG2 to +1 (0x7FFFFFFF) + pub fn reconfigure(&mut self) { + // reset ARG2 to +1 + { + self.peri.disable_irq(); + self.peri.disable_read_dma(); + self.peri.disable_write_dma(); + self.clean_rrdy_flag(); + + self.peri.set_func(Function::Cos); + self.peri.set_precision(Precision::Iters4); + self.peri.set_scale(Scale::Arg1Res1); + self.peri.set_argument_count(AccessCount::Two); + self.peri.set_data_width(Width::Bits32, Width::Bits32); + self.peri.write_argument(0x0u32); + self.peri.write_argument(0x7FFFFFFFu32); + + self.clean_rrdy_flag(); + } + + self.peri.set_func(self.config.function); + self.peri.set_precision(self.config.precision); + self.peri.set_scale(self.config.scale); + + // we don't set NRES in here, but to make sure NRES is set each time user call "calc"-ish functions, + // since each "calc"-ish functions can have different ARGSIZE and RESSIZE, thus NRES should be change accordingly. + } +} + +impl<'d, T: Instance> Drop for Cordic<'d, T> { + fn drop(&mut self) { + rcc::disable::(); + } +} + +// q1.31 related +impl<'d, T: Instance> Cordic<'d, T> { + /// Run a blocking CORDIC calculation in q1.31 format + /// + /// Notice: + /// If you set `arg1_only` to `true`, please be sure ARG2 value has been set to desired value before. + /// This function won't set ARG2 to +1 before or after each round of calculation. + /// If you want to make sure ARG2 is set to +1, consider run [.reconfigure()](Self::reconfigure). + pub fn blocking_calc_32bit( + &mut self, + arg: &[u32], + res: &mut [u32], + arg1_only: bool, + res1_only: bool, + ) -> Result { + if arg.is_empty() { + return Ok(0); + } + + let res_cnt = Self::check_arg_res_length_32bit(arg.len(), res.len(), arg1_only, res1_only)?; + + self.peri + .set_argument_count(if arg1_only { AccessCount::One } else { AccessCount::Two }); + + self.peri + .set_result_count(if res1_only { AccessCount::One } else { AccessCount::Two }); + + self.peri.set_data_width(Width::Bits32, Width::Bits32); + + let mut cnt = 0; + + match arg1_only { + true => { + // To use cordic preload function, the first value is special. + // It is loaded to CORDIC WDATA register out side of loop + let first_value = arg[0]; + + // preload 1st value to CORDIC, to start the CORDIC calc + self.peri.write_argument(first_value); + + for &arg1 in &arg[1..] { + // preload arg1 (for next calc) + self.peri.write_argument(arg1); + + // then read current result out + res[cnt] = self.peri.read_result(); + cnt += 1; + if !res1_only { + res[cnt] = self.peri.read_result(); + cnt += 1; + } + } + + // read the last result + res[cnt] = self.peri.read_result(); + cnt += 1; + if !res1_only { + res[cnt] = self.peri.read_result(); + // cnt += 1; + } + } + false => { + // To use cordic preload function, the first and last value is special. + // They are load to CORDIC WDATA register out side of loop + let first_value = arg[0]; + let last_value = arg[arg.len() - 1]; + + let paired_args = &arg[1..arg.len() - 1]; + + // preload 1st value to CORDIC + self.peri.write_argument(first_value); + + for args in paired_args.chunks(2) { + let arg2 = args[0]; + let arg1 = args[1]; + + // load arg2 (for current calc) first, to start the CORDIC calc + self.peri.write_argument(arg2); + + // preload arg1 (for next calc) + self.peri.write_argument(arg1); + + // then read current result out + res[cnt] = self.peri.read_result(); + cnt += 1; + if !res1_only { + res[cnt] = self.peri.read_result(); + cnt += 1; + } + } + + // load last value to CORDIC, and finish the calculation + self.peri.write_argument(last_value); + res[cnt] = self.peri.read_result(); + cnt += 1; + if !res1_only { + res[cnt] = self.peri.read_result(); + // cnt += 1; + } + } + } + + // at this point cnt should be equal to res_cnt + + Ok(res_cnt) + } + + /// Run a async CORDIC calculation in q.1.31 format + /// + /// Notice: + /// If you set `arg1_only` to `true`, please be sure ARG2 value has been set to desired value before. + /// This function won't set ARG2 to +1 before or after each round of calculation. + /// If you want to make sure ARG2 is set to +1, consider run [.reconfigure()](Self::reconfigure). + pub async fn async_calc_32bit( + &mut self, + write_dma: impl Peripheral

>, + read_dma: impl Peripheral

>, + arg: &[u32], + res: &mut [u32], + arg1_only: bool, + res1_only: bool, + ) -> Result { + if arg.is_empty() { + return Ok(0); + } + + let res_cnt = Self::check_arg_res_length_32bit(arg.len(), res.len(), arg1_only, res1_only)?; + + let active_res_buf = &mut res[..res_cnt]; + + into_ref!(write_dma, read_dma); + + self.peri + .set_argument_count(if arg1_only { AccessCount::One } else { AccessCount::Two }); + + self.peri + .set_result_count(if res1_only { AccessCount::One } else { AccessCount::Two }); + + self.peri.set_data_width(Width::Bits32, Width::Bits32); + + let write_req = write_dma.request(); + let read_req = read_dma.request(); + + self.peri.enable_write_dma(); + self.peri.enable_read_dma(); + + let _on_drop = OnDrop::new(|| { + self.peri.disable_write_dma(); + self.peri.disable_read_dma(); + }); + + unsafe { + let write_transfer = dma::Transfer::new_write( + &mut write_dma, + write_req, + arg, + T::regs().wdata().as_ptr() as *mut _, + Default::default(), + ); + + let read_transfer = dma::Transfer::new_read( + &mut read_dma, + read_req, + T::regs().rdata().as_ptr() as *mut _, + active_res_buf, + Default::default(), + ); + + embassy_futures::join::join(write_transfer, read_transfer).await; + } + + Ok(res_cnt) + } + + fn check_arg_res_length_32bit( + arg_len: usize, + res_len: usize, + arg1_only: bool, + res1_only: bool, + ) -> Result { + if !arg1_only && arg_len % 2 != 0 { + return Err(CordicError::ArgumentLengthIncorrect); + } + + let mut minimal_res_length = arg_len; + + if !res1_only { + minimal_res_length *= 2; + } + + if !arg1_only { + minimal_res_length /= 2 + } + + if minimal_res_length > res_len { + return Err(CordicError::ResultLengthNotEnough); + } + + Ok(minimal_res_length) + } +} + +// q1.15 related +impl<'d, T: Instance> Cordic<'d, T> { + /// Run a blocking CORDIC calculation in q1.15 format + /// + /// Notice:: + /// User will take respond to merge two u16 arguments into one u32 data, and/or split one u32 data into two u16 results. + pub fn blocking_calc_16bit(&mut self, arg: &[u32], res: &mut [u32]) -> Result { + if arg.is_empty() { + return Ok(0); + } + + if arg.len() > res.len() { + return Err(CordicError::ResultLengthNotEnough); + } + + let res_cnt = arg.len(); + + // In q1.15 mode, 1 write/read to access 2 arguments/results + self.peri.set_argument_count(AccessCount::One); + self.peri.set_result_count(AccessCount::One); + + self.peri.set_data_width(Width::Bits16, Width::Bits16); + + // To use cordic preload function, the first value is special. + // It is loaded to CORDIC WDATA register out side of loop + let first_value = arg[0]; + + // preload 1st value to CORDIC, to start the CORDIC calc + self.peri.write_argument(first_value); + + let mut cnt = 0; + + for &arg_val in &arg[1..] { + // preload arg_val (for next calc) + self.peri.write_argument(arg_val); + + // then read current result out + res[cnt] = self.peri.read_result(); + cnt += 1; + } + + // read last result out + res[cnt] = self.peri.read_result(); + // cnt += 1; + + Ok(res_cnt) + } + + /// Run a async CORDIC calculation in q1.15 format + /// + /// Notice:: + /// User will take respond to merge two u16 arguments into one u32 data, and/or split one u32 data into two u16 results. + pub async fn async_calc_16bit( + &mut self, + write_dma: impl Peripheral

>, + read_dma: impl Peripheral

>, + arg: &[u32], + res: &mut [u32], + ) -> Result { + if arg.is_empty() { + return Ok(0); + } + + if arg.len() > res.len() { + return Err(CordicError::ResultLengthNotEnough); + } + + let res_cnt = arg.len(); + + let active_res_buf = &mut res[..res_cnt]; + + into_ref!(write_dma, read_dma); + + // In q1.15 mode, 1 write/read to access 2 arguments/results + self.peri.set_argument_count(AccessCount::One); + self.peri.set_result_count(AccessCount::One); + + self.peri.set_data_width(Width::Bits16, Width::Bits16); + + let write_req = write_dma.request(); + let read_req = read_dma.request(); + + self.peri.enable_write_dma(); + self.peri.enable_read_dma(); + + let _on_drop = OnDrop::new(|| { + self.peri.disable_write_dma(); + self.peri.disable_read_dma(); + }); + + unsafe { + let write_transfer = dma::Transfer::new_write( + &mut write_dma, + write_req, + arg, + T::regs().wdata().as_ptr() as *mut _, + Default::default(), + ); + + let read_transfer = dma::Transfer::new_read( + &mut read_dma, + read_req, + T::regs().rdata().as_ptr() as *mut _, + active_res_buf, + Default::default(), + ); + + embassy_futures::join::join(write_transfer, read_transfer).await; + } + + Ok(res_cnt) + } +} + +macro_rules! check_arg_value { + ($func_arg1_name:ident, $func_arg2_name:ident, $float_type:ty) => { + impl<'d, T: Instance> Cordic<'d, T> { + /// check input value ARG1, SCALE and FUNCTION are compatible with each other + pub fn $func_arg1_name(&self, arg: $float_type) -> Result<(), ArgError> { + let config = &self.config; + + use Function::*; + + struct Arg1ErrInfo { + scale: Option, + range: [f32; 2], // f32 is ok, it only used in error display + inclusive_upper_bound: bool, + } + + let err_info = match config.function { + Cos | Sin | Phase | Modulus | Arctan if !(-1.0..=1.0).contains(arg) => Some(Arg1ErrInfo { + scale: None, + range: [-1.0, 1.0], + inclusive_upper_bound: true, + }), + + Cosh | Sinh if !(-0.559..=0.559).contains(arg) => Some(Arg1ErrInfo { + scale: None, + range: [-0.559, 0.559], + inclusive_upper_bound: true, + }), + + Arctanh if !(-0.403..=0.403).contains(arg) => Some(Arg1ErrInfo { + scale: None, + range: [-0.403, 0.403], + inclusive_upper_bound: true, + }), + + Ln => match config.scale { + Scale::Arg1o2Res2 if !(0.0535..0.5).contains(arg) => Some(Arg1ErrInfo { + scale: Some(Scale::Arg1o2Res2), + range: [0.0535, 0.5], + inclusive_upper_bound: false, + }), + Scale::Arg1o4Res4 if !(0.25..0.75).contains(arg) => Some(Arg1ErrInfo { + scale: Some(Scale::Arg1o4Res4), + range: [0.25, 0.75], + inclusive_upper_bound: false, + }), + Scale::Arg1o8Res8 if !(0.375..0.875).contains(arg) => Some(Arg1ErrInfo { + scale: Some(Scale::Arg1o8Res8), + range: [0.375, 0.875], + inclusive_upper_bound: false, + }), + Scale::Arg1o16Res16 if !(0.4375..0.584).contains(arg) => Some(Arg1ErrInfo { + scale: Some(Scale::Arg1o16Res16), + range: [0.4375, 0.584], + inclusive_upper_bound: false, + }), + + Scale::Arg1o2Res2 | Scale::Arg1o4Res4 | Scale::Arg1o8Res8 | Scale::Arg1o16Res16 => None, + + _ => unreachable!(), + }, + + Sqrt => match config.scale { + Scale::Arg1Res1 if !(0.027..0.75).contains(arg) => Some(Arg1ErrInfo { + scale: Some(Scale::Arg1Res1), + range: [0.027, 0.75], + inclusive_upper_bound: false, + }), + Scale::Arg1o2Res2 if !(0.375..0.875).contains(arg) => Some(Arg1ErrInfo { + scale: Some(Scale::Arg1o2Res2), + range: [0.375, 0.875], + inclusive_upper_bound: false, + }), + Scale::Arg1o4Res4 if !(0.4375..0.584).contains(arg) => Some(Arg1ErrInfo { + scale: Some(Scale::Arg1o4Res4), + range: [0.4375, 0.584], + inclusive_upper_bound: false, + }), + Scale::Arg1Res1 | Scale::Arg1o2Res2 | Scale::Arg1o4Res4 => None, + _ => unreachable!(), + }, + + Cos | Sin | Phase | Modulus | Arctan | Cosh | Sinh | Arctanh => None, + }; + + if let Some(err) = err_info { + return Err(ArgError { + func: config.function, + scale: err.scale, + arg_range: err.range, + inclusive_upper_bound: err.inclusive_upper_bound, + arg_type: ArgType::Arg1, + }); + } + + Ok(()) + } + + /// check input value ARG2 and FUNCTION are compatible with each other + pub fn $func_arg2_name(&self, arg: $float_type) -> Result<(), ArgError> { + let config = &self.config; + + use Function::*; + + struct Arg2ErrInfo { + range: [f32; 2], // f32 is ok, it only used in error display + } + + let err_info = match config.function { + Cos | Sin if !(0.0..=1.0).contains(arg) => Some(Arg2ErrInfo { range: [0.0, 1.0] }), + + Phase | Modulus if !(-1.0..=1.0).contains(arg) => Some(Arg2ErrInfo { range: [-1.0, 1.0] }), + + Cos | Sin | Phase | Modulus | Arctan | Cosh | Sinh | Arctanh | Ln | Sqrt => None, + }; + + if let Some(err) = err_info { + return Err(ArgError { + func: config.function, + scale: None, + arg_range: err.range, + inclusive_upper_bound: true, + arg_type: ArgType::Arg2, + }); + } + + Ok(()) + } + } + }; +} + +check_arg_value!(check_f64_arg1, check_f64_arg2, &f64); +check_arg_value!(check_f32_arg1, check_f32_arg2, &f32); + +foreach_interrupt!( + ($inst:ident, cordic, $block:ident, GLOBAL, $irq:ident) => { + impl Instance for peripherals::$inst { + } + + impl SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::cordic::Cordic { + crate::pac::$inst + } + } + }; +); + +dma_trait!(WriteDma, Instance); +dma_trait!(ReadDma, Instance); diff --git a/embassy/embassy-stm32/src/cordic/utils.rs b/embassy/embassy-stm32/src/cordic/utils.rs new file mode 100644 index 0000000..008f502 --- /dev/null +++ b/embassy/embassy-stm32/src/cordic/utils.rs @@ -0,0 +1,62 @@ +//! Common math utils +use super::errors::NumberOutOfRange; + +macro_rules! floating_fixed_convert { + ($f_to_q:ident, $q_to_f:ident, $unsigned_bin_typ:ty, $signed_bin_typ:ty, $float_ty:ty, $offset:literal, $min_positive:literal) => { + /// convert float point to fixed point format + pub fn $f_to_q(value: $float_ty) -> Result<$unsigned_bin_typ, NumberOutOfRange> { + const MIN_POSITIVE: $float_ty = unsafe { core::mem::transmute($min_positive) }; + + if value < -1.0 { + return Err(NumberOutOfRange::BelowLowerBound) + } + + if value > 1.0 { + return Err(NumberOutOfRange::AboveUpperBound) + } + + + let value = if 1.0 - MIN_POSITIVE < value && value <= 1.0 { + // make a exception for value between (1.0^{-x} , 1.0] float point, + // convert it to max representable value of q1.x format + (1.0 as $float_ty) - MIN_POSITIVE + } else { + value + }; + + // It's necessary to cast the float value to signed integer, before convert it to a unsigned value. + // Since value from register is actually a "signed value", a "as" cast will keep original binary format but mark it as a unsigned value for register writing. + // see https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast + Ok((value * ((1 as $unsigned_bin_typ << $offset) as $float_ty)) as $signed_bin_typ as $unsigned_bin_typ) + } + + #[inline(always)] + /// convert fixed point to float point format + pub fn $q_to_f(value: $unsigned_bin_typ) -> $float_ty { + // It's necessary to cast the unsigned integer to signed integer, before convert it to a float value. + // Since value from register is actually a "signed value", a "as" cast will keep original binary format but mark it as a signed value. + // see https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast + (value as $signed_bin_typ as $float_ty) / ((1 as $unsigned_bin_typ << $offset) as $float_ty) + } + }; +} + +floating_fixed_convert!( + f64_to_q1_31, + q1_31_to_f64, + u32, + i32, + f64, + 31, + 0x3E00_0000_0000_0000u64 // binary form of 1f64^(-31) +); + +floating_fixed_convert!( + f32_to_q1_15, + q1_15_to_f32, + u16, + i16, + f32, + 15, + 0x3800_0000u32 // binary form of 1f32^(-15) +); diff --git a/embassy/embassy-stm32/src/crc/mod.rs b/embassy/embassy-stm32/src/crc/mod.rs new file mode 100644 index 0000000..29523b9 --- /dev/null +++ b/embassy/embassy-stm32/src/crc/mod.rs @@ -0,0 +1,7 @@ +//! Cyclic Redundancy Check (CRC) +#[cfg_attr(crc_v1, path = "v1.rs")] +#[cfg_attr(crc_v2, path = "v2v3.rs")] +#[cfg_attr(crc_v3, path = "v2v3.rs")] +mod _version; + +pub use _version::*; diff --git a/embassy/embassy-stm32/src/crc/v1.rs b/embassy/embassy-stm32/src/crc/v1.rs new file mode 100644 index 0000000..f3d13de --- /dev/null +++ b/embassy/embassy-stm32/src/crc/v1.rs @@ -0,0 +1,63 @@ +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use crate::pac::CRC as PAC_CRC; +use crate::peripherals::CRC; +use crate::{rcc, Peripheral}; + +/// CRC driver. +pub struct Crc<'d> { + _peri: PeripheralRef<'d, CRC>, +} + +impl<'d> Crc<'d> { + /// Instantiates the CRC32 peripheral and initializes it to default values. + pub fn new(peripheral: impl Peripheral

+ 'd) -> Self { + into_ref!(peripheral); + + // Note: enable and reset come from RccPeripheral. + // enable CRC clock in RCC. + rcc::enable_and_reset::(); + // Peripheral the peripheral + let mut instance = Self { _peri: peripheral }; + instance.reset(); + instance + } + + /// Resets the CRC unit to default value (0xFFFF_FFFF) + pub fn reset(&mut self) { + PAC_CRC.cr().write(|w| w.set_reset(true)); + } + + /// Feeds a word to the peripheral and returns the current CRC value + pub fn feed_word(&mut self, word: u32) -> u32 { + // write a single byte to the device, and return the result + #[cfg(not(crc_v1))] + PAC_CRC.dr32().write_value(word); + #[cfg(crc_v1)] + PAC_CRC.dr().write_value(word); + self.read() + } + + /// Feed a slice of words to the peripheral and return the result. + pub fn feed_words(&mut self, words: &[u32]) -> u32 { + for word in words { + #[cfg(not(crc_v1))] + PAC_CRC.dr32().write_value(*word); + #[cfg(crc_v1)] + PAC_CRC.dr().write_value(*word); + } + + self.read() + } + + /// Read the CRC result value. + #[cfg(not(crc_v1))] + pub fn read(&self) -> u32 { + PAC_CRC.dr32().read() + } + /// Read the CRC result value. + #[cfg(crc_v1)] + pub fn read(&self) -> u32 { + PAC_CRC.dr().read() + } +} diff --git a/embassy/embassy-stm32/src/crc/v2v3.rs b/embassy/embassy-stm32/src/crc/v2v3.rs new file mode 100644 index 0000000..09d956d --- /dev/null +++ b/embassy/embassy-stm32/src/crc/v2v3.rs @@ -0,0 +1,174 @@ +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use crate::pac::crc::vals; +use crate::pac::CRC as PAC_CRC; +use crate::peripherals::CRC; +use crate::{rcc, Peripheral}; + +/// CRC driver. +pub struct Crc<'d> { + _peripheral: PeripheralRef<'d, CRC>, + _config: Config, +} + +/// CRC configuration errlr +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError { + /// The selected polynomial is invalid. + InvalidPolynomial, +} + +/// CRC configuration +pub struct Config { + reverse_in: InputReverseConfig, + reverse_out: bool, + #[cfg(crc_v3)] + poly_size: PolySize, + crc_init_value: u32, + #[cfg(crc_v3)] + crc_poly: u32, +} + +/// Input reverse configuration. +pub enum InputReverseConfig { + /// Don't reverse anything + None, + /// Reverse bytes + Byte, + /// Reverse 16-bit halfwords. + Halfword, + /// Reverse 32-bit words. + Word, +} + +impl Config { + /// Create a new CRC config. + pub fn new( + reverse_in: InputReverseConfig, + reverse_out: bool, + #[cfg(crc_v3)] poly_size: PolySize, + crc_init_value: u32, + #[cfg(crc_v3)] crc_poly: u32, + ) -> Result { + // As Per RM0091 (DocID018940 Rev 9), Even polynomials are not supported. + #[cfg(crc_v3)] + if crc_poly % 2 == 0 { + return Err(ConfigError::InvalidPolynomial); + } + Ok(Config { + reverse_in, + reverse_out, + #[cfg(crc_v3)] + poly_size, + crc_init_value, + #[cfg(crc_v3)] + crc_poly, + }) + } +} + +/// Polynomial size +#[cfg(crc_v3)] +#[allow(missing_docs)] +pub enum PolySize { + Width7, + Width8, + Width16, + Width32, +} + +impl<'d> Crc<'d> { + /// Instantiates the CRC32 peripheral and initializes it to default values. + pub fn new(peripheral: impl Peripheral

+ 'd, config: Config) -> Self { + // Note: enable and reset come from RccPeripheral. + // reset to default values and enable CRC clock in RCC. + rcc::enable_and_reset::(); + into_ref!(peripheral); + let mut instance = Self { + _peripheral: peripheral, + _config: config, + }; + instance.reconfigure(); + instance.reset(); + instance + } + + /// Reset the CRC engine. + pub fn reset(&mut self) { + PAC_CRC.cr().modify(|w| w.set_reset(true)); + } + + /// Reconfigures the CRC peripheral. Doesn't reset. + fn reconfigure(&mut self) { + // Init CRC value + PAC_CRC.init().write_value(self._config.crc_init_value); + #[cfg(crc_v3)] + PAC_CRC.pol().write_value(self._config.crc_poly); + + // configure CR components + // (reverse I/O, polysize, poly) + PAC_CRC.cr().write(|w| { + // configure reverse output + w.set_rev_out(match self._config.reverse_out { + true => vals::RevOut::REVERSED, + false => vals::RevOut::NORMAL, + }); + // configure reverse input + w.set_rev_in(match self._config.reverse_in { + InputReverseConfig::None => vals::RevIn::NORMAL, + InputReverseConfig::Byte => vals::RevIn::BYTE, + InputReverseConfig::Halfword => vals::RevIn::HALFWORD, + InputReverseConfig::Word => vals::RevIn::WORD, + }); + // configure the polynomial. + #[cfg(crc_v3)] + w.set_polysize(match self._config.poly_size { + PolySize::Width7 => vals::Polysize::POLYSIZE7, + PolySize::Width8 => vals::Polysize::POLYSIZE8, + PolySize::Width16 => vals::Polysize::POLYSIZE16, + PolySize::Width32 => vals::Polysize::POLYSIZE32, + }); + }); + + self.reset(); + } + + /// Feeds a byte into the CRC peripheral. Returns the computed checksum. + pub fn feed_byte(&mut self, byte: u8) -> u32 { + PAC_CRC.dr8().write_value(byte); + PAC_CRC.dr32().read() + } + + /// Feeds an slice of bytes into the CRC peripheral. Returns the computed checksum. + pub fn feed_bytes(&mut self, bytes: &[u8]) -> u32 { + for byte in bytes { + PAC_CRC.dr8().write_value(*byte); + } + PAC_CRC.dr32().read() + } + /// Feeds a halfword into the CRC peripheral. Returns the computed checksum. + pub fn feed_halfword(&mut self, halfword: u16) -> u32 { + PAC_CRC.dr16().write_value(halfword); + PAC_CRC.dr32().read() + } + /// Feeds an slice of halfwords into the CRC peripheral. Returns the computed checksum. + pub fn feed_halfwords(&mut self, halfwords: &[u16]) -> u32 { + for halfword in halfwords { + PAC_CRC.dr16().write_value(*halfword); + } + PAC_CRC.dr32().read() + } + /// Feeds a words into the CRC peripheral. Returns the computed checksum. + pub fn feed_word(&mut self, word: u32) -> u32 { + PAC_CRC.dr32().write_value(word as u32); + PAC_CRC.dr32().read() + } + /// Feeds an slice of words into the CRC peripheral. Returns the computed checksum. + pub fn feed_words(&mut self, words: &[u32]) -> u32 { + for word in words { + PAC_CRC.dr32().write_value(*word as u32); + } + PAC_CRC.dr32().read() + } +} diff --git a/embassy/embassy-stm32/src/cryp/mod.rs b/embassy/embassy-stm32/src/cryp/mod.rs new file mode 100644 index 0000000..8d600c7 --- /dev/null +++ b/embassy/embassy-stm32/src/cryp/mod.rs @@ -0,0 +1,1909 @@ +//! Crypto Accelerator (CRYP) +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +use core::cmp::min; +use core::marker::PhantomData; +use core::ptr; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::dma::{NoDma, Transfer, TransferOptions}; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, pac, peripherals, rcc, Peripheral}; + +const DES_BLOCK_SIZE: usize = 8; // 64 bits +const AES_BLOCK_SIZE: usize = 16; // 128 bits + +static CRYP_WAKER: AtomicWaker = AtomicWaker::new(); + +/// CRYP interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let bits = T::regs().misr().read(); + if bits.inmis() { + T::regs().imscr().modify(|w| w.set_inim(false)); + CRYP_WAKER.wake(); + } + if bits.outmis() { + T::regs().imscr().modify(|w| w.set_outim(false)); + CRYP_WAKER.wake(); + } + } +} + +/// This trait encapsulates all cipher-specific behavior/ +pub trait Cipher<'c> { + /// Processing block size. Determined by the processor and the algorithm. + const BLOCK_SIZE: usize; + + /// Indicates whether the cipher requires the application to provide padding. + /// If `true`, no partial blocks will be accepted (a panic will occur). + const REQUIRES_PADDING: bool = false; + + /// Returns the symmetric key. + fn key(&self) -> &[u8]; + + /// Returns the initialization vector. + fn iv(&self) -> &[u8]; + + /// Sets the processor algorithm mode according to the associated cipher. + fn set_algomode(&self, p: pac::cryp::Cryp); + + /// Performs any key preparation within the processor, if necessary. + fn prepare_key(&self, _p: pac::cryp::Cryp) {} + + /// Performs any cipher-specific initialization. + fn init_phase_blocking(&self, _p: pac::cryp::Cryp, _cryp: &Cryp) {} + + /// Performs any cipher-specific initialization. + async fn init_phase(&self, _p: pac::cryp::Cryp, _cryp: &mut Cryp<'_, T, DmaIn, DmaOut>) + where + DmaIn: crate::cryp::DmaIn, + DmaOut: crate::cryp::DmaOut, + { + } + + /// Called prior to processing the last data block for cipher-specific operations. + fn pre_final(&self, _p: pac::cryp::Cryp, _dir: Direction, _padding_len: usize) -> [u32; 4] { + return [0; 4]; + } + + /// Called after processing the last data block for cipher-specific operations. + fn post_final_blocking( + &self, + _p: pac::cryp::Cryp, + _cryp: &Cryp, + _dir: Direction, + _int_data: &mut [u8; AES_BLOCK_SIZE], + _temp1: [u32; 4], + _padding_mask: [u8; 16], + ) { + } + + /// Called after processing the last data block for cipher-specific operations. + async fn post_final( + &self, + _p: pac::cryp::Cryp, + _cryp: &mut Cryp<'_, T, DmaIn, DmaOut>, + _dir: Direction, + _int_data: &mut [u8; AES_BLOCK_SIZE], + _temp1: [u32; 4], + _padding_mask: [u8; 16], + ) where + DmaIn: crate::cryp::DmaIn, + DmaOut: crate::cryp::DmaOut, + { + } + + /// Returns the AAD header block as required by the cipher. + fn get_header_block(&self) -> &[u8] { + return [0; 0].as_slice(); + } +} + +/// This trait enables restriction of ciphers to specific key sizes. +pub trait CipherSized {} + +/// This trait enables restriction of initialization vectors to sizes compatibile with a cipher mode. +pub trait IVSized {} + +/// This trait enables restriction of a header phase to authenticated ciphers only. +pub trait CipherAuthenticated { + /// Defines the authentication tag size. + const TAG_SIZE: usize = TAG_SIZE; +} + +/// TDES-ECB Cipher Mode +pub struct TdesEcb<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 0], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> TdesEcb<'c, KEY_SIZE> { + /// Constructs a new AES-ECB cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE]) -> Self { + return Self { key: key, iv: &[0; 0] }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for TdesEcb<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = DES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn set_algomode(&self, p: pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(0)); + } + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + { + p.cr().modify(|w| w.set_algomode0(0)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for TdesEcb<'c, { 112 / 8 }> {} +impl<'c> CipherSized for TdesEcb<'c, { 168 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for TdesEcb<'c, KEY_SIZE> {} + +/// TDES-CBC Cipher Mode +pub struct TdesCbc<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 8], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> TdesCbc<'c, KEY_SIZE> { + /// Constructs a new TDES-CBC cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 8]) -> Self { + return Self { key: key, iv: iv }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for TdesCbc<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = DES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn set_algomode(&self, p: pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(1)); + } + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + { + p.cr().modify(|w| w.set_algomode0(1)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for TdesCbc<'c, { 112 / 8 }> {} +impl<'c> CipherSized for TdesCbc<'c, { 168 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for TdesCbc<'c, KEY_SIZE> {} + +/// DES-ECB Cipher Mode +pub struct DesEcb<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 0], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> DesEcb<'c, KEY_SIZE> { + /// Constructs a new AES-ECB cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE]) -> Self { + return Self { key: key, iv: &[0; 0] }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for DesEcb<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = DES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn set_algomode(&self, p: pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(2)); + } + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + { + p.cr().modify(|w| w.set_algomode0(2)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for DesEcb<'c, { 56 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for DesEcb<'c, KEY_SIZE> {} + +/// DES-CBC Cipher Mode +pub struct DesCbc<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 8], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> DesCbc<'c, KEY_SIZE> { + /// Constructs a new AES-CBC cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 8]) -> Self { + return Self { key: key, iv: iv }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for DesCbc<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = DES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn set_algomode(&self, p: pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(3)); + } + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + { + p.cr().modify(|w| w.set_algomode0(3)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for DesCbc<'c, { 56 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for DesCbc<'c, KEY_SIZE> {} + +/// AES-ECB Cipher Mode +pub struct AesEcb<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 0], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> AesEcb<'c, KEY_SIZE> { + /// Constructs a new AES-ECB cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE]) -> Self { + return Self { key: key, iv: &[0; 0] }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesEcb<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn prepare_key(&self, p: pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(7)); + } + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + { + p.cr().modify(|w| w.set_algomode0(7)); + p.cr().modify(|w| w.set_algomode3(false)); + } + p.cr().modify(|w| w.set_crypen(true)); + while p.sr().read().busy() {} + } + + fn set_algomode(&self, p: pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(2)); + } + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + { + p.cr().modify(|w| w.set_algomode0(2)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for AesEcb<'c, { 128 / 8 }> {} +impl<'c> CipherSized for AesEcb<'c, { 192 / 8 }> {} +impl<'c> CipherSized for AesEcb<'c, { 256 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for AesEcb<'c, KEY_SIZE> {} + +/// AES-CBC Cipher Mode +pub struct AesCbc<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 16], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> AesCbc<'c, KEY_SIZE> { + /// Constructs a new AES-CBC cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 16]) -> Self { + return Self { key: key, iv: iv }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesCbc<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn prepare_key(&self, p: pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(7)); + } + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + { + p.cr().modify(|w| w.set_algomode0(7)); + p.cr().modify(|w| w.set_algomode3(false)); + } + p.cr().modify(|w| w.set_crypen(true)); + while p.sr().read().busy() {} + } + + fn set_algomode(&self, p: pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(5)); + } + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + { + p.cr().modify(|w| w.set_algomode0(5)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for AesCbc<'c, { 128 / 8 }> {} +impl<'c> CipherSized for AesCbc<'c, { 192 / 8 }> {} +impl<'c> CipherSized for AesCbc<'c, { 256 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for AesCbc<'c, KEY_SIZE> {} + +/// AES-CTR Cipher Mode +pub struct AesCtr<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 16], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> AesCtr<'c, KEY_SIZE> { + /// Constructs a new AES-CTR cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 16]) -> Self { + return Self { key: key, iv: iv }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesCtr<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn set_algomode(&self, p: pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(6)); + } + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + { + p.cr().modify(|w| w.set_algomode0(6)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for AesCtr<'c, { 128 / 8 }> {} +impl<'c> CipherSized for AesCtr<'c, { 192 / 8 }> {} +impl<'c> CipherSized for AesCtr<'c, { 256 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for AesCtr<'c, KEY_SIZE> {} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +///AES-GCM Cipher Mode +pub struct AesGcm<'c, const KEY_SIZE: usize> { + iv: [u8; 16], + key: &'c [u8; KEY_SIZE], +} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize> AesGcm<'c, KEY_SIZE> { + /// Constucts a new AES-GCM cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 12]) -> Self { + let mut new_gcm = Self { key: key, iv: [0; 16] }; + new_gcm.iv[..12].copy_from_slice(iv); + new_gcm.iv[15] = 2; + new_gcm + } +} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGcm<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &[u8] { + self.iv.as_slice() + } + + fn set_algomode(&self, p: pac::cryp::Cryp) { + p.cr().modify(|w| w.set_algomode0(0)); + p.cr().modify(|w| w.set_algomode3(true)); + } + + fn init_phase_blocking(&self, p: pac::cryp::Cryp, _cryp: &Cryp) { + p.cr().modify(|w| w.set_gcm_ccmph(0)); + p.cr().modify(|w| w.set_crypen(true)); + while p.cr().read().crypen() {} + } + + async fn init_phase(&self, p: pac::cryp::Cryp, _cryp: &mut Cryp<'_, T, DmaIn, DmaOut>) { + p.cr().modify(|w| w.set_gcm_ccmph(0)); + p.cr().modify(|w| w.set_crypen(true)); + while p.cr().read().crypen() {} + } + + #[cfg(cryp_v2)] + fn pre_final(&self, p: pac::cryp::Cryp, dir: Direction, _padding_len: usize) -> [u32; 4] { + //Handle special GCM partial block process. + if dir == Direction::Encrypt { + p.cr().modify(|w| w.set_crypen(false)); + p.cr().modify(|w| w.set_algomode3(false)); + p.cr().modify(|w| w.set_algomode0(6)); + let iv1r = p.csgcmccmr(7).read() - 1; + p.init(1).ivrr().write_value(iv1r); + p.cr().modify(|w| w.set_crypen(true)); + } + [0; 4] + } + + #[cfg(any(cryp_v3, cryp_v4))] + fn pre_final(&self, p: pac::cryp::Cryp, _dir: Direction, padding_len: usize) -> [u32; 4] { + //Handle special GCM partial block process. + p.cr().modify(|w| w.set_npblb(padding_len as u8)); + [0; 4] + } + + #[cfg(cryp_v2)] + fn post_final_blocking( + &self, + p: pac::cryp::Cryp, + cryp: &Cryp, + dir: Direction, + int_data: &mut [u8; AES_BLOCK_SIZE], + _temp1: [u32; 4], + padding_mask: [u8; AES_BLOCK_SIZE], + ) { + if dir == Direction::Encrypt { + //Handle special GCM partial block process. + p.cr().modify(|w| w.set_crypen(false)); + p.cr().modify(|w| w.set_algomode3(true)); + p.cr().modify(|w| w.set_algomode0(0)); + for i in 0..AES_BLOCK_SIZE { + int_data[i] = int_data[i] & padding_mask[i]; + } + p.cr().modify(|w| w.set_crypen(true)); + p.cr().modify(|w| w.set_gcm_ccmph(3)); + + cryp.write_bytes_blocking(Self::BLOCK_SIZE, int_data); + cryp.read_bytes_blocking(Self::BLOCK_SIZE, int_data); + } + } + + #[cfg(cryp_v2)] + async fn post_final( + &self, + p: pac::cryp::Cryp, + cryp: &mut Cryp<'_, T, DmaIn, DmaOut>, + dir: Direction, + int_data: &mut [u8; AES_BLOCK_SIZE], + _temp1: [u32; 4], + padding_mask: [u8; AES_BLOCK_SIZE], + ) where + DmaIn: crate::cryp::DmaIn, + DmaOut: crate::cryp::DmaOut, + { + if dir == Direction::Encrypt { + // Handle special GCM partial block process. + p.cr().modify(|w| w.set_crypen(false)); + p.cr().modify(|w| w.set_algomode3(true)); + p.cr().modify(|w| w.set_algomode0(0)); + for i in 0..AES_BLOCK_SIZE { + int_data[i] = int_data[i] & padding_mask[i]; + } + p.cr().modify(|w| w.set_crypen(true)); + p.cr().modify(|w| w.set_gcm_ccmph(3)); + + let mut out_data: [u8; AES_BLOCK_SIZE] = [0; AES_BLOCK_SIZE]; + + let read = Cryp::::read_bytes(&mut cryp.outdma, Self::BLOCK_SIZE, &mut out_data); + let write = Cryp::::write_bytes(&mut cryp.indma, Self::BLOCK_SIZE, int_data); + + embassy_futures::join::join(read, write).await; + + int_data.copy_from_slice(&out_data); + } + } +} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c> CipherSized for AesGcm<'c, { 128 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c> CipherSized for AesGcm<'c, { 192 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c> CipherSized for AesGcm<'c, { 256 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize> CipherAuthenticated<16> for AesGcm<'c, KEY_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize> IVSized for AesGcm<'c, KEY_SIZE> {} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +/// AES-GMAC Cipher Mode +pub struct AesGmac<'c, const KEY_SIZE: usize> { + iv: [u8; 16], + key: &'c [u8; KEY_SIZE], +} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize> AesGmac<'c, KEY_SIZE> { + /// Constructs a new AES-GMAC cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 12]) -> Self { + let mut new_gmac = Self { key: key, iv: [0; 16] }; + new_gmac.iv[..12].copy_from_slice(iv); + new_gmac.iv[15] = 2; + new_gmac + } +} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGmac<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &[u8] { + self.iv.as_slice() + } + + fn set_algomode(&self, p: pac::cryp::Cryp) { + p.cr().modify(|w| w.set_algomode0(0)); + p.cr().modify(|w| w.set_algomode3(true)); + } + + fn init_phase_blocking(&self, p: pac::cryp::Cryp, _cryp: &Cryp) { + p.cr().modify(|w| w.set_gcm_ccmph(0)); + p.cr().modify(|w| w.set_crypen(true)); + while p.cr().read().crypen() {} + } + + async fn init_phase(&self, p: pac::cryp::Cryp, _cryp: &mut Cryp<'_, T, DmaIn, DmaOut>) { + p.cr().modify(|w| w.set_gcm_ccmph(0)); + p.cr().modify(|w| w.set_crypen(true)); + while p.cr().read().crypen() {} + } + + #[cfg(cryp_v2)] + fn pre_final(&self, p: pac::cryp::Cryp, dir: Direction, _padding_len: usize) -> [u32; 4] { + //Handle special GCM partial block process. + if dir == Direction::Encrypt { + p.cr().modify(|w| w.set_crypen(false)); + p.cr().modify(|w| w.set_algomode3(false)); + p.cr().modify(|w| w.set_algomode0(6)); + let iv1r = p.csgcmccmr(7).read() - 1; + p.init(1).ivrr().write_value(iv1r); + p.cr().modify(|w| w.set_crypen(true)); + } + [0; 4] + } + + #[cfg(any(cryp_v3, cryp_v4))] + fn pre_final(&self, p: pac::cryp::Cryp, _dir: Direction, padding_len: usize) -> [u32; 4] { + //Handle special GCM partial block process. + p.cr().modify(|w| w.set_npblb(padding_len as u8)); + [0; 4] + } + + #[cfg(cryp_v2)] + fn post_final_blocking( + &self, + p: pac::cryp::Cryp, + cryp: &Cryp, + dir: Direction, + int_data: &mut [u8; AES_BLOCK_SIZE], + _temp1: [u32; 4], + padding_mask: [u8; AES_BLOCK_SIZE], + ) { + if dir == Direction::Encrypt { + //Handle special GCM partial block process. + p.cr().modify(|w| w.set_crypen(false)); + p.cr().modify(|w| w.set_algomode3(true)); + p.cr().modify(|w| w.set_algomode0(0)); + for i in 0..AES_BLOCK_SIZE { + int_data[i] = int_data[i] & padding_mask[i]; + } + p.cr().modify(|w| w.set_crypen(true)); + p.cr().modify(|w| w.set_gcm_ccmph(3)); + + cryp.write_bytes_blocking(Self::BLOCK_SIZE, int_data); + cryp.read_bytes_blocking(Self::BLOCK_SIZE, int_data); + } + } + + #[cfg(cryp_v2)] + async fn post_final( + &self, + p: pac::cryp::Cryp, + cryp: &mut Cryp<'_, T, DmaIn, DmaOut>, + dir: Direction, + int_data: &mut [u8; AES_BLOCK_SIZE], + _temp1: [u32; 4], + padding_mask: [u8; AES_BLOCK_SIZE], + ) where + DmaIn: crate::cryp::DmaIn, + DmaOut: crate::cryp::DmaOut, + { + if dir == Direction::Encrypt { + // Handle special GCM partial block process. + p.cr().modify(|w| w.set_crypen(false)); + p.cr().modify(|w| w.set_algomode3(true)); + p.cr().modify(|w| w.set_algomode0(0)); + for i in 0..AES_BLOCK_SIZE { + int_data[i] = int_data[i] & padding_mask[i]; + } + p.cr().modify(|w| w.set_crypen(true)); + p.cr().modify(|w| w.set_gcm_ccmph(3)); + + let mut out_data: [u8; AES_BLOCK_SIZE] = [0; AES_BLOCK_SIZE]; + + let read = Cryp::::read_bytes(&mut cryp.outdma, Self::BLOCK_SIZE, &mut out_data); + let write = Cryp::::write_bytes(&mut cryp.indma, Self::BLOCK_SIZE, int_data); + + embassy_futures::join::join(read, write).await; + } + } +} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c> CipherSized for AesGmac<'c, { 128 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c> CipherSized for AesGmac<'c, { 192 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c> CipherSized for AesGmac<'c, { 256 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize> CipherAuthenticated<16> for AesGmac<'c, KEY_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize> IVSized for AesGmac<'c, KEY_SIZE> {} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +/// AES-CCM Cipher Mode +pub struct AesCcm<'c, const KEY_SIZE: usize, const TAG_SIZE: usize, const IV_SIZE: usize> { + key: &'c [u8; KEY_SIZE], + aad_header: [u8; 6], + aad_header_len: usize, + block0: [u8; 16], + ctr: [u8; 16], +} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize, const IV_SIZE: usize> AesCcm<'c, KEY_SIZE, TAG_SIZE, IV_SIZE> { + /// Constructs a new AES-CCM cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; IV_SIZE], aad_len: usize, payload_len: usize) -> Self { + let mut aad_header: [u8; 6] = [0; 6]; + let mut aad_header_len = 0; + let mut block0: [u8; 16] = [0; 16]; + if aad_len != 0 { + if aad_len < 65280 { + aad_header[0] = (aad_len >> 8) as u8 & 0xFF; + aad_header[1] = aad_len as u8 & 0xFF; + aad_header_len = 2; + } else { + aad_header[0] = 0xFF; + aad_header[1] = 0xFE; + let aad_len_bytes: [u8; 4] = (aad_len as u32).to_be_bytes(); + aad_header[2] = aad_len_bytes[0]; + aad_header[3] = aad_len_bytes[1]; + aad_header[4] = aad_len_bytes[2]; + aad_header[5] = aad_len_bytes[3]; + aad_header_len = 6; + } + } + let total_aad_len = aad_header_len + aad_len; + let mut aad_padding_len = 16 - (total_aad_len % 16); + if aad_padding_len == 16 { + aad_padding_len = 0; + } + aad_header_len += aad_padding_len; + let total_aad_len_padded = aad_header_len + aad_len; + if total_aad_len_padded > 0 { + block0[0] = 0x40; + } + block0[0] |= ((((TAG_SIZE as u8) - 2) >> 1) & 0x07) << 3; + block0[0] |= ((15 - (iv.len() as u8)) - 1) & 0x07; + block0[1..1 + iv.len()].copy_from_slice(iv); + let payload_len_bytes: [u8; 4] = (payload_len as u32).to_be_bytes(); + if iv.len() <= 11 { + block0[12] = payload_len_bytes[0]; + } else if payload_len_bytes[0] > 0 { + panic!("Message is too large for given IV size."); + } + if iv.len() <= 12 { + block0[13] = payload_len_bytes[1]; + } else if payload_len_bytes[1] > 0 { + panic!("Message is too large for given IV size."); + } + block0[14] = payload_len_bytes[2]; + block0[15] = payload_len_bytes[3]; + let mut ctr: [u8; 16] = [0; 16]; + ctr[0] = block0[0] & 0x07; + ctr[1..1 + iv.len()].copy_from_slice(&block0[1..1 + iv.len()]); + ctr[15] = 0x01; + + return Self { + key: key, + aad_header: aad_header, + aad_header_len: aad_header_len, + block0: block0, + ctr: ctr, + }; + } +} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize, const IV_SIZE: usize> Cipher<'c> + for AesCcm<'c, KEY_SIZE, TAG_SIZE, IV_SIZE> +{ + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &[u8] { + self.ctr.as_slice() + } + + fn set_algomode(&self, p: pac::cryp::Cryp) { + p.cr().modify(|w| w.set_algomode0(1)); + p.cr().modify(|w| w.set_algomode3(true)); + } + + fn init_phase_blocking(&self, p: pac::cryp::Cryp, cryp: &Cryp) { + p.cr().modify(|w| w.set_gcm_ccmph(0)); + + cryp.write_bytes_blocking(Self::BLOCK_SIZE, &self.block0); + + p.cr().modify(|w| w.set_crypen(true)); + while p.cr().read().crypen() {} + } + + async fn init_phase(&self, p: pac::cryp::Cryp, cryp: &mut Cryp<'_, T, DmaIn, DmaOut>) + where + DmaIn: crate::cryp::DmaIn, + DmaOut: crate::cryp::DmaOut, + { + p.cr().modify(|w| w.set_gcm_ccmph(0)); + + Cryp::::write_bytes(&mut cryp.indma, Self::BLOCK_SIZE, &self.block0).await; + + p.cr().modify(|w| w.set_crypen(true)); + while p.cr().read().crypen() {} + } + + fn get_header_block(&self) -> &[u8] { + return &self.aad_header[0..self.aad_header_len]; + } + + #[cfg(cryp_v2)] + fn pre_final(&self, p: pac::cryp::Cryp, dir: Direction, _padding_len: usize) -> [u32; 4] { + //Handle special CCM partial block process. + let mut temp1 = [0; 4]; + if dir == Direction::Decrypt { + p.cr().modify(|w| w.set_crypen(false)); + let iv1temp = p.init(1).ivrr().read(); + temp1[0] = p.csgcmccmr(0).read().swap_bytes(); + temp1[1] = p.csgcmccmr(1).read().swap_bytes(); + temp1[2] = p.csgcmccmr(2).read().swap_bytes(); + temp1[3] = p.csgcmccmr(3).read().swap_bytes(); + p.init(1).ivrr().write_value(iv1temp); + p.cr().modify(|w| w.set_algomode3(false)); + p.cr().modify(|w| w.set_algomode0(6)); + p.cr().modify(|w| w.set_crypen(true)); + } + return temp1; + } + + #[cfg(any(cryp_v3, cryp_v4))] + fn pre_final(&self, p: pac::cryp::Cryp, _dir: Direction, padding_len: usize) -> [u32; 4] { + //Handle special GCM partial block process. + p.cr().modify(|w| w.set_npblb(padding_len as u8)); + [0; 4] + } + + #[cfg(cryp_v2)] + fn post_final_blocking( + &self, + p: pac::cryp::Cryp, + cryp: &Cryp, + dir: Direction, + int_data: &mut [u8; AES_BLOCK_SIZE], + temp1: [u32; 4], + padding_mask: [u8; 16], + ) { + if dir == Direction::Decrypt { + //Handle special CCM partial block process. + let mut temp2 = [0; 4]; + temp2[0] = p.csgcmccmr(0).read().swap_bytes(); + temp2[1] = p.csgcmccmr(1).read().swap_bytes(); + temp2[2] = p.csgcmccmr(2).read().swap_bytes(); + temp2[3] = p.csgcmccmr(3).read().swap_bytes(); + p.cr().modify(|w| w.set_algomode3(true)); + p.cr().modify(|w| w.set_algomode0(1)); + p.cr().modify(|w| w.set_gcm_ccmph(3)); + // Header phase + p.cr().modify(|w| w.set_gcm_ccmph(1)); + for i in 0..AES_BLOCK_SIZE { + int_data[i] = int_data[i] & padding_mask[i]; + } + let mut in_data: [u32; 4] = [0; 4]; + for i in 0..in_data.len() { + let mut int_bytes: [u8; 4] = [0; 4]; + int_bytes.copy_from_slice(&int_data[(i * 4)..(i * 4) + 4]); + let int_word = u32::from_le_bytes(int_bytes); + in_data[i] = int_word; + in_data[i] = in_data[i] ^ temp1[i] ^ temp2[i]; + } + cryp.write_words_blocking(Self::BLOCK_SIZE, &in_data); + } + } + + #[cfg(cryp_v2)] + async fn post_final( + &self, + p: pac::cryp::Cryp, + cryp: &mut Cryp<'_, T, DmaIn, DmaOut>, + dir: Direction, + int_data: &mut [u8; AES_BLOCK_SIZE], + temp1: [u32; 4], + padding_mask: [u8; 16], + ) where + DmaIn: crate::cryp::DmaIn, + DmaOut: crate::cryp::DmaOut, + { + if dir == Direction::Decrypt { + //Handle special CCM partial block process. + let mut temp2 = [0; 4]; + temp2[0] = p.csgcmccmr(0).read().swap_bytes(); + temp2[1] = p.csgcmccmr(1).read().swap_bytes(); + temp2[2] = p.csgcmccmr(2).read().swap_bytes(); + temp2[3] = p.csgcmccmr(3).read().swap_bytes(); + p.cr().modify(|w| w.set_algomode3(true)); + p.cr().modify(|w| w.set_algomode0(1)); + p.cr().modify(|w| w.set_gcm_ccmph(3)); + // Header phase + p.cr().modify(|w| w.set_gcm_ccmph(1)); + for i in 0..AES_BLOCK_SIZE { + int_data[i] = int_data[i] & padding_mask[i]; + } + let mut in_data: [u32; 4] = [0; 4]; + for i in 0..in_data.len() { + let mut int_bytes: [u8; 4] = [0; 4]; + int_bytes.copy_from_slice(&int_data[(i * 4)..(i * 4) + 4]); + let int_word = u32::from_le_bytes(int_bytes); + in_data[i] = int_word; + in_data[i] = in_data[i] ^ temp1[i] ^ temp2[i]; + } + Cryp::::write_words(&mut cryp.indma, Self::BLOCK_SIZE, &in_data).await; + } + } +} + +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const TAG_SIZE: usize, const IV_SIZE: usize> CipherSized for AesCcm<'c, { 128 / 8 }, TAG_SIZE, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const TAG_SIZE: usize, const IV_SIZE: usize> CipherSized for AesCcm<'c, { 192 / 8 }, TAG_SIZE, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const TAG_SIZE: usize, const IV_SIZE: usize> CipherSized for AesCcm<'c, { 256 / 8 }, TAG_SIZE, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<4> for AesCcm<'c, KEY_SIZE, 4, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<6> for AesCcm<'c, KEY_SIZE, 6, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<8> for AesCcm<'c, KEY_SIZE, 8, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<10> for AesCcm<'c, KEY_SIZE, 10, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<12> for AesCcm<'c, KEY_SIZE, 12, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<14> for AesCcm<'c, KEY_SIZE, 14, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<16> for AesCcm<'c, KEY_SIZE, 16, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 7> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 8> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 9> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 10> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 11> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 12> {} +#[cfg(any(cryp_v2, cryp_v3, cryp_v4))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 13> {} + +#[allow(dead_code)] +/// Holds the state information for a cipher operation. +/// Allows suspending/resuming of cipher operations. +pub struct Context<'c, C: Cipher<'c> + CipherSized> { + phantom_data: PhantomData<&'c C>, + cipher: &'c C, + dir: Direction, + last_block_processed: bool, + header_processed: bool, + aad_complete: bool, + cr: u32, + iv: [u32; 4], + csgcmccm: [u32; 8], + csgcm: [u32; 8], + header_len: u64, + payload_len: u64, + aad_buffer: [u8; 16], + aad_buffer_len: usize, +} + +/// Selects whether the crypto processor operates in encryption or decryption mode. +#[derive(PartialEq, Clone, Copy)] +pub enum Direction { + /// Encryption mode + Encrypt, + /// Decryption mode + Decrypt, +} + +/// Crypto Accelerator Driver +pub struct Cryp<'d, T: Instance, DmaIn = NoDma, DmaOut = NoDma> { + _peripheral: PeripheralRef<'d, T>, + indma: PeripheralRef<'d, DmaIn>, + outdma: PeripheralRef<'d, DmaOut>, +} + +impl<'d, T: Instance, DmaIn, DmaOut> Cryp<'d, T, DmaIn, DmaOut> { + /// Create a new CRYP driver. + pub fn new( + peri: impl Peripheral

+ 'd, + indma: impl Peripheral

+ 'd, + outdma: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + rcc::enable_and_reset::(); + into_ref!(peri, indma, outdma); + let instance = Self { + _peripheral: peri, + indma: indma, + outdma: outdma, + }; + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + instance + } + + /// Start a new encrypt or decrypt operation for the given cipher. + pub fn start_blocking<'c, C: Cipher<'c> + CipherSized + IVSized>( + &self, + cipher: &'c C, + dir: Direction, + ) -> Context<'c, C> { + let mut ctx: Context<'c, C> = Context { + dir, + last_block_processed: false, + cr: 0, + iv: [0; 4], + csgcmccm: [0; 8], + csgcm: [0; 8], + aad_complete: false, + header_len: 0, + payload_len: 0, + cipher: cipher, + phantom_data: PhantomData, + header_processed: false, + aad_buffer: [0; 16], + aad_buffer_len: 0, + }; + + T::regs().cr().modify(|w| w.set_crypen(false)); + + let key = ctx.cipher.key(); + + if key.len() == (128 / 8) { + T::regs().cr().modify(|w| w.set_keysize(0)); + } else if key.len() == (192 / 8) { + T::regs().cr().modify(|w| w.set_keysize(1)); + } else if key.len() == (256 / 8) { + T::regs().cr().modify(|w| w.set_keysize(2)); + } + + self.load_key(key); + + // Set data type to 8-bit. This will match software implementations. + T::regs().cr().modify(|w| w.set_datatype(2)); + + ctx.cipher.prepare_key(T::regs()); + + ctx.cipher.set_algomode(T::regs()); + + // Set encrypt/decrypt + if dir == Direction::Encrypt { + T::regs().cr().modify(|w| w.set_algodir(false)); + } else { + T::regs().cr().modify(|w| w.set_algodir(true)); + } + + // Load the IV into the registers. + let iv = ctx.cipher.iv(); + let mut full_iv: [u8; 16] = [0; 16]; + full_iv[0..iv.len()].copy_from_slice(iv); + let mut iv_idx = 0; + let mut iv_word: [u8; 4] = [0; 4]; + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + iv_idx += 4; + T::regs().init(0).ivlr().write_value(u32::from_be_bytes(iv_word)); + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + iv_idx += 4; + T::regs().init(0).ivrr().write_value(u32::from_be_bytes(iv_word)); + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + iv_idx += 4; + T::regs().init(1).ivlr().write_value(u32::from_be_bytes(iv_word)); + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + T::regs().init(1).ivrr().write_value(u32::from_be_bytes(iv_word)); + + // Flush in/out FIFOs + T::regs().cr().modify(|w| w.fflush()); + + ctx.cipher.init_phase_blocking(T::regs(), self); + + self.store_context(&mut ctx); + + ctx + } + + /// Start a new encrypt or decrypt operation for the given cipher. + pub async fn start<'c, C: Cipher<'c> + CipherSized + IVSized>( + &mut self, + cipher: &'c C, + dir: Direction, + ) -> Context<'c, C> + where + DmaIn: crate::cryp::DmaIn, + DmaOut: crate::cryp::DmaOut, + { + let mut ctx: Context<'c, C> = Context { + dir, + last_block_processed: false, + cr: 0, + iv: [0; 4], + csgcmccm: [0; 8], + csgcm: [0; 8], + aad_complete: false, + header_len: 0, + payload_len: 0, + cipher: cipher, + phantom_data: PhantomData, + header_processed: false, + aad_buffer: [0; 16], + aad_buffer_len: 0, + }; + + T::regs().cr().modify(|w| w.set_crypen(false)); + + let key = ctx.cipher.key(); + + if key.len() == (128 / 8) { + T::regs().cr().modify(|w| w.set_keysize(0)); + } else if key.len() == (192 / 8) { + T::regs().cr().modify(|w| w.set_keysize(1)); + } else if key.len() == (256 / 8) { + T::regs().cr().modify(|w| w.set_keysize(2)); + } + + self.load_key(key); + + // Set data type to 8-bit. This will match software implementations. + T::regs().cr().modify(|w| w.set_datatype(2)); + + ctx.cipher.prepare_key(T::regs()); + + ctx.cipher.set_algomode(T::regs()); + + // Set encrypt/decrypt + if dir == Direction::Encrypt { + T::regs().cr().modify(|w| w.set_algodir(false)); + } else { + T::regs().cr().modify(|w| w.set_algodir(true)); + } + + // Load the IV into the registers. + let iv = ctx.cipher.iv(); + let mut full_iv: [u8; 16] = [0; 16]; + full_iv[0..iv.len()].copy_from_slice(iv); + let mut iv_idx = 0; + let mut iv_word: [u8; 4] = [0; 4]; + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + iv_idx += 4; + T::regs().init(0).ivlr().write_value(u32::from_be_bytes(iv_word)); + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + iv_idx += 4; + T::regs().init(0).ivrr().write_value(u32::from_be_bytes(iv_word)); + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + iv_idx += 4; + T::regs().init(1).ivlr().write_value(u32::from_be_bytes(iv_word)); + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + T::regs().init(1).ivrr().write_value(u32::from_be_bytes(iv_word)); + + // Flush in/out FIFOs + T::regs().cr().modify(|w| w.fflush()); + + ctx.cipher.init_phase(T::regs(), self).await; + + self.store_context(&mut ctx); + + ctx + } + + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + /// Controls the header phase of cipher processing. + /// This function is only valid for authenticated ciphers including GCM, CCM, and GMAC. + /// All additional associated data (AAD) must be supplied to this function prior to starting the payload phase with `payload_blocking`. + /// The AAD must be supplied in multiples of the block size (128-bits for AES, 64-bits for DES), except when supplying the last block. + /// When supplying the last block of AAD, `last_aad_block` must be `true`. + pub fn aad_blocking< + 'c, + const TAG_SIZE: usize, + C: Cipher<'c> + CipherSized + IVSized + CipherAuthenticated, + >( + &self, + ctx: &mut Context<'c, C>, + aad: &[u8], + last_aad_block: bool, + ) { + self.load_context(ctx); + + // Perform checks for correctness. + if ctx.aad_complete { + panic!("Cannot update AAD after starting payload!") + } + + ctx.header_len += aad.len() as u64; + + // Header phase + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(1)); + T::regs().cr().modify(|w| w.set_crypen(true)); + + // First write the header B1 block if not yet written. + if !ctx.header_processed { + ctx.header_processed = true; + let header = ctx.cipher.get_header_block(); + ctx.aad_buffer[0..header.len()].copy_from_slice(header); + ctx.aad_buffer_len += header.len(); + } + + // Fill the header block to make a full block. + let len_to_copy = min(aad.len(), C::BLOCK_SIZE - ctx.aad_buffer_len); + ctx.aad_buffer[ctx.aad_buffer_len..ctx.aad_buffer_len + len_to_copy].copy_from_slice(&aad[..len_to_copy]); + ctx.aad_buffer_len += len_to_copy; + ctx.aad_buffer[ctx.aad_buffer_len..].fill(0); + let mut aad_len_remaining = aad.len() - len_to_copy; + + if ctx.aad_buffer_len < C::BLOCK_SIZE { + // The buffer isn't full and this is the last buffer, so process it as is (already padded). + if last_aad_block { + self.write_bytes_blocking(C::BLOCK_SIZE, &ctx.aad_buffer); + // Block until input FIFO is empty. + while !T::regs().sr().read().ifem() {} + + // Switch to payload phase. + ctx.aad_complete = true; + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(2)); + T::regs().cr().modify(|w| w.fflush()); + } else { + // Just return because we don't yet have a full block to process. + return; + } + } else { + // Load the full block from the buffer. + self.write_bytes_blocking(C::BLOCK_SIZE, &ctx.aad_buffer); + // Block until input FIFO is empty. + while !T::regs().sr().read().ifem() {} + } + + // Handle a partial block that is passed in. + ctx.aad_buffer_len = 0; + let leftovers = aad_len_remaining % C::BLOCK_SIZE; + ctx.aad_buffer[..leftovers].copy_from_slice(&aad[aad.len() - leftovers..aad.len()]); + ctx.aad_buffer_len += leftovers; + ctx.aad_buffer[ctx.aad_buffer_len..].fill(0); + aad_len_remaining -= leftovers; + assert_eq!(aad_len_remaining % C::BLOCK_SIZE, 0); + + // Load full data blocks into core. + let num_full_blocks = aad_len_remaining / C::BLOCK_SIZE; + let start_index = len_to_copy; + let end_index = start_index + (C::BLOCK_SIZE * num_full_blocks); + self.write_bytes_blocking(C::BLOCK_SIZE, &aad[start_index..end_index]); + + if last_aad_block { + if leftovers > 0 { + self.write_bytes_blocking(C::BLOCK_SIZE, &ctx.aad_buffer); + } + // Switch to payload phase. + ctx.aad_complete = true; + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(2)); + T::regs().cr().modify(|w| w.fflush()); + } + + self.store_context(ctx); + } + + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + /// Controls the header phase of cipher processing. + /// This function is only valid for authenticated ciphers including GCM, CCM, and GMAC. + /// All additional associated data (AAD) must be supplied to this function prior to starting the payload phase with `payload`. + /// The AAD must be supplied in multiples of the block size (128-bits for AES, 64-bits for DES), except when supplying the last block. + /// When supplying the last block of AAD, `last_aad_block` must be `true`. + pub async fn aad<'c, const TAG_SIZE: usize, C: Cipher<'c> + CipherSized + IVSized + CipherAuthenticated>( + &mut self, + ctx: &mut Context<'c, C>, + aad: &[u8], + last_aad_block: bool, + ) where + DmaIn: crate::cryp::DmaIn, + DmaOut: crate::cryp::DmaOut, + { + self.load_context(ctx); + + // Perform checks for correctness. + if ctx.aad_complete { + panic!("Cannot update AAD after starting payload!") + } + + ctx.header_len += aad.len() as u64; + + // Header phase + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(1)); + T::regs().cr().modify(|w| w.set_crypen(true)); + + // First write the header B1 block if not yet written. + if !ctx.header_processed { + ctx.header_processed = true; + let header = ctx.cipher.get_header_block(); + ctx.aad_buffer[0..header.len()].copy_from_slice(header); + ctx.aad_buffer_len += header.len(); + } + + // Fill the header block to make a full block. + let len_to_copy = min(aad.len(), C::BLOCK_SIZE - ctx.aad_buffer_len); + ctx.aad_buffer[ctx.aad_buffer_len..ctx.aad_buffer_len + len_to_copy].copy_from_slice(&aad[..len_to_copy]); + ctx.aad_buffer_len += len_to_copy; + ctx.aad_buffer[ctx.aad_buffer_len..].fill(0); + let mut aad_len_remaining = aad.len() - len_to_copy; + + if ctx.aad_buffer_len < C::BLOCK_SIZE { + // The buffer isn't full and this is the last buffer, so process it as is (already padded). + if last_aad_block { + Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &ctx.aad_buffer).await; + assert_eq!(T::regs().sr().read().ifem(), true); + + // Switch to payload phase. + ctx.aad_complete = true; + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(2)); + T::regs().cr().modify(|w| w.fflush()); + } else { + // Just return because we don't yet have a full block to process. + return; + } + } else { + // Load the full block from the buffer. + Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &ctx.aad_buffer).await; + assert_eq!(T::regs().sr().read().ifem(), true); + } + + // Handle a partial block that is passed in. + ctx.aad_buffer_len = 0; + let leftovers = aad_len_remaining % C::BLOCK_SIZE; + ctx.aad_buffer[..leftovers].copy_from_slice(&aad[aad.len() - leftovers..aad.len()]); + ctx.aad_buffer_len += leftovers; + ctx.aad_buffer[ctx.aad_buffer_len..].fill(0); + aad_len_remaining -= leftovers; + assert_eq!(aad_len_remaining % C::BLOCK_SIZE, 0); + + // Load full data blocks into core. + let num_full_blocks = aad_len_remaining / C::BLOCK_SIZE; + let start_index = len_to_copy; + let end_index = start_index + (C::BLOCK_SIZE * num_full_blocks); + Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &aad[start_index..end_index]).await; + + if last_aad_block { + if leftovers > 0 { + Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &ctx.aad_buffer).await; + assert_eq!(T::regs().sr().read().ifem(), true); + } + // Switch to payload phase. + ctx.aad_complete = true; + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(2)); + T::regs().cr().modify(|w| w.fflush()); + } + + self.store_context(ctx); + } + + /// Performs encryption/decryption on the provided context. + /// The context determines algorithm, mode, and state of the crypto accelerator. + /// When the last piece of data is supplied, `last_block` should be `true`. + /// This function panics under various mismatches of parameters. + /// Output buffer must be at least as long as the input buffer. + /// Data must be a multiple of block size (128-bits for AES, 64-bits for DES) for CBC and ECB modes. + /// Padding or ciphertext stealing must be managed by the application for these modes. + /// Data must also be a multiple of block size unless `last_block` is `true`. + pub fn payload_blocking<'c, C: Cipher<'c> + CipherSized + IVSized>( + &self, + ctx: &mut Context<'c, C>, + input: &[u8], + output: &mut [u8], + last_block: bool, + ) { + self.load_context(ctx); + + let last_block_remainder = input.len() % C::BLOCK_SIZE; + + // Perform checks for correctness. + if !ctx.aad_complete && ctx.header_len > 0 { + panic!("Additional associated data must be processed first!"); + } else if !ctx.aad_complete { + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + { + ctx.aad_complete = true; + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(2)); + T::regs().cr().modify(|w| w.fflush()); + T::regs().cr().modify(|w| w.set_crypen(true)); + } + } + if ctx.last_block_processed { + panic!("The last block has already been processed!"); + } + if input.len() > output.len() { + panic!("Output buffer length must match input length."); + } + if !last_block { + if last_block_remainder != 0 { + panic!("Input length must be a multiple of {} bytes.", C::BLOCK_SIZE); + } + } + if C::REQUIRES_PADDING { + if last_block_remainder != 0 { + panic!("Input must be a multiple of {} bytes in ECB and CBC modes. Consider padding or ciphertext stealing.", C::BLOCK_SIZE); + } + } + if last_block { + ctx.last_block_processed = true; + } + + // Load data into core, block by block. + let num_full_blocks = input.len() / C::BLOCK_SIZE; + for block in 0..num_full_blocks { + let index = block * C::BLOCK_SIZE; + // Write block in + self.write_bytes_blocking(C::BLOCK_SIZE, &input[index..index + C::BLOCK_SIZE]); + // Read block out + self.read_bytes_blocking(C::BLOCK_SIZE, &mut output[index..index + C::BLOCK_SIZE]); + } + + // Handle the final block, which is incomplete. + if last_block_remainder > 0 { + let padding_len = C::BLOCK_SIZE - last_block_remainder; + let temp1 = ctx.cipher.pre_final(T::regs(), ctx.dir, padding_len); + + let mut intermediate_data: [u8; AES_BLOCK_SIZE] = [0; AES_BLOCK_SIZE]; + let mut last_block: [u8; AES_BLOCK_SIZE] = [0; AES_BLOCK_SIZE]; + last_block[..last_block_remainder].copy_from_slice(&input[input.len() - last_block_remainder..input.len()]); + self.write_bytes_blocking(C::BLOCK_SIZE, &last_block); + self.read_bytes_blocking(C::BLOCK_SIZE, &mut intermediate_data); + + // Handle the last block depending on mode. + let output_len = output.len(); + output[output_len - last_block_remainder..output_len] + .copy_from_slice(&intermediate_data[0..last_block_remainder]); + + let mut mask: [u8; 16] = [0; 16]; + mask[..last_block_remainder].fill(0xFF); + ctx.cipher + .post_final_blocking(T::regs(), self, ctx.dir, &mut intermediate_data, temp1, mask); + } + + ctx.payload_len += input.len() as u64; + + self.store_context(ctx); + } + + /// Performs encryption/decryption on the provided context. + /// The context determines algorithm, mode, and state of the crypto accelerator. + /// When the last piece of data is supplied, `last_block` should be `true`. + /// This function panics under various mismatches of parameters. + /// Output buffer must be at least as long as the input buffer. + /// Data must be a multiple of block size (128-bits for AES, 64-bits for DES) for CBC and ECB modes. + /// Padding or ciphertext stealing must be managed by the application for these modes. + /// Data must also be a multiple of block size unless `last_block` is `true`. + pub async fn payload<'c, C: Cipher<'c> + CipherSized + IVSized>( + &mut self, + ctx: &mut Context<'c, C>, + input: &[u8], + output: &mut [u8], + last_block: bool, + ) where + DmaIn: crate::cryp::DmaIn, + DmaOut: crate::cryp::DmaOut, + { + self.load_context(ctx); + + let last_block_remainder = input.len() % C::BLOCK_SIZE; + + // Perform checks for correctness. + if !ctx.aad_complete && ctx.header_len > 0 { + panic!("Additional associated data must be processed first!"); + } else if !ctx.aad_complete { + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + { + ctx.aad_complete = true; + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(2)); + T::regs().cr().modify(|w| w.fflush()); + T::regs().cr().modify(|w| w.set_crypen(true)); + } + } + if ctx.last_block_processed { + panic!("The last block has already been processed!"); + } + if input.len() > output.len() { + panic!("Output buffer length must match input length."); + } + if !last_block { + if last_block_remainder != 0 { + panic!("Input length must be a multiple of {} bytes.", C::BLOCK_SIZE); + } + } + if C::REQUIRES_PADDING { + if last_block_remainder != 0 { + panic!("Input must be a multiple of {} bytes in ECB and CBC modes. Consider padding or ciphertext stealing.", C::BLOCK_SIZE); + } + } + if last_block { + ctx.last_block_processed = true; + } + + // Load data into core, block by block. + let num_full_blocks = input.len() / C::BLOCK_SIZE; + for block in 0..num_full_blocks { + let index = block * C::BLOCK_SIZE; + // Read block out + let read = Self::read_bytes( + &mut self.outdma, + C::BLOCK_SIZE, + &mut output[index..index + C::BLOCK_SIZE], + ); + // Write block in + let write = Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &input[index..index + C::BLOCK_SIZE]); + embassy_futures::join::join(read, write).await; + } + + // Handle the final block, which is incomplete. + if last_block_remainder > 0 { + let padding_len = C::BLOCK_SIZE - last_block_remainder; + let temp1 = ctx.cipher.pre_final(T::regs(), ctx.dir, padding_len); + + let mut intermediate_data: [u8; AES_BLOCK_SIZE] = [0; AES_BLOCK_SIZE]; + let mut last_block: [u8; AES_BLOCK_SIZE] = [0; AES_BLOCK_SIZE]; + last_block[..last_block_remainder].copy_from_slice(&input[input.len() - last_block_remainder..input.len()]); + let read = Self::read_bytes(&mut self.outdma, C::BLOCK_SIZE, &mut intermediate_data); + let write = Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &last_block); + embassy_futures::join::join(read, write).await; + + // Handle the last block depending on mode. + let output_len = output.len(); + output[output_len - last_block_remainder..output_len] + .copy_from_slice(&intermediate_data[0..last_block_remainder]); + + let mut mask: [u8; 16] = [0; 16]; + mask[..last_block_remainder].fill(0xFF); + ctx.cipher + .post_final(T::regs(), self, ctx.dir, &mut intermediate_data, temp1, mask) + .await; + } + + ctx.payload_len += input.len() as u64; + + self.store_context(ctx); + } + + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + /// Generates an authentication tag for authenticated ciphers including GCM, CCM, and GMAC. + /// Called after the all data has been encrypted/decrypted by `payload`. + pub fn finish_blocking< + 'c, + const TAG_SIZE: usize, + C: Cipher<'c> + CipherSized + IVSized + CipherAuthenticated, + >( + &self, + mut ctx: Context<'c, C>, + ) -> [u8; TAG_SIZE] { + self.load_context(&mut ctx); + + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(3)); + T::regs().cr().modify(|w| w.set_crypen(true)); + + let headerlen1: u32 = ((ctx.header_len * 8) >> 32) as u32; + let headerlen2: u32 = (ctx.header_len * 8) as u32; + let payloadlen1: u32 = ((ctx.payload_len * 8) >> 32) as u32; + let payloadlen2: u32 = (ctx.payload_len * 8) as u32; + + #[cfg(cryp_v2)] + let footer: [u32; 4] = [ + headerlen1.swap_bytes(), + headerlen2.swap_bytes(), + payloadlen1.swap_bytes(), + payloadlen2.swap_bytes(), + ]; + #[cfg(any(cryp_v3, cryp_v4))] + let footer: [u32; 4] = [headerlen1, headerlen2, payloadlen1, payloadlen2]; + + self.write_words_blocking(C::BLOCK_SIZE, &footer); + + while !T::regs().sr().read().ofne() {} + + let mut full_tag: [u8; 16] = [0; 16]; + self.read_bytes_blocking(C::BLOCK_SIZE, &mut full_tag); + let mut tag: [u8; TAG_SIZE] = [0; TAG_SIZE]; + tag.copy_from_slice(&full_tag[0..TAG_SIZE]); + + T::regs().cr().modify(|w| w.set_crypen(false)); + + tag + } + + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + // Generates an authentication tag for authenticated ciphers including GCM, CCM, and GMAC. + /// Called after the all data has been encrypted/decrypted by `payload`. + pub async fn finish< + 'c, + const TAG_SIZE: usize, + C: Cipher<'c> + CipherSized + IVSized + CipherAuthenticated, + >( + &mut self, + mut ctx: Context<'c, C>, + ) -> [u8; TAG_SIZE] + where + DmaIn: crate::cryp::DmaIn, + DmaOut: crate::cryp::DmaOut, + { + self.load_context(&mut ctx); + + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(3)); + T::regs().cr().modify(|w| w.set_crypen(true)); + + let headerlen1: u32 = ((ctx.header_len * 8) >> 32) as u32; + let headerlen2: u32 = (ctx.header_len * 8) as u32; + let payloadlen1: u32 = ((ctx.payload_len * 8) >> 32) as u32; + let payloadlen2: u32 = (ctx.payload_len * 8) as u32; + + #[cfg(cryp_v2)] + let footer: [u32; 4] = [ + headerlen1.swap_bytes(), + headerlen2.swap_bytes(), + payloadlen1.swap_bytes(), + payloadlen2.swap_bytes(), + ]; + #[cfg(any(cryp_v3, cryp_v4))] + let footer: [u32; 4] = [headerlen1, headerlen2, payloadlen1, payloadlen2]; + + let write = Self::write_words(&mut self.indma, C::BLOCK_SIZE, &footer); + + let mut full_tag: [u8; 16] = [0; 16]; + let read = Self::read_bytes(&mut self.outdma, C::BLOCK_SIZE, &mut full_tag); + + embassy_futures::join::join(read, write).await; + + let mut tag: [u8; TAG_SIZE] = [0; TAG_SIZE]; + tag.copy_from_slice(&full_tag[0..TAG_SIZE]); + + T::regs().cr().modify(|w| w.set_crypen(false)); + + tag + } + + fn load_key(&self, key: &[u8]) { + // Load the key into the registers. + let mut keyidx = 0; + let mut keyword: [u8; 4] = [0; 4]; + let keylen = key.len() * 8; + if keylen > 192 { + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(0).klr().write_value(u32::from_be_bytes(keyword)); + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(0).krr().write_value(u32::from_be_bytes(keyword)); + } + if keylen > 128 { + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(1).klr().write_value(u32::from_be_bytes(keyword)); + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(1).krr().write_value(u32::from_be_bytes(keyword)); + } + if keylen > 64 { + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(2).klr().write_value(u32::from_be_bytes(keyword)); + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(2).krr().write_value(u32::from_be_bytes(keyword)); + } + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(3).klr().write_value(u32::from_be_bytes(keyword)); + keyword = [0; 4]; + keyword[0..key.len() - keyidx].copy_from_slice(&key[keyidx..key.len()]); + T::regs().key(3).krr().write_value(u32::from_be_bytes(keyword)); + } + + fn store_context<'c, C: Cipher<'c> + CipherSized>(&self, ctx: &mut Context<'c, C>) { + // Wait for data block processing to finish. + while !T::regs().sr().read().ifem() {} + while T::regs().sr().read().ofne() {} + while T::regs().sr().read().busy() {} + + // Disable crypto processor. + T::regs().cr().modify(|w| w.set_crypen(false)); + + // Save the peripheral state. + ctx.cr = T::regs().cr().read().0; + ctx.iv[0] = T::regs().init(0).ivlr().read(); + ctx.iv[1] = T::regs().init(0).ivrr().read(); + ctx.iv[2] = T::regs().init(1).ivlr().read(); + ctx.iv[3] = T::regs().init(1).ivrr().read(); + + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + for i in 0..8 { + ctx.csgcmccm[i] = T::regs().csgcmccmr(i).read(); + ctx.csgcm[i] = T::regs().csgcmr(i).read(); + } + } + + fn load_context<'c, C: Cipher<'c> + CipherSized>(&self, ctx: &Context<'c, C>) { + // Reload state registers. + T::regs().cr().write(|w| w.0 = ctx.cr); + T::regs().init(0).ivlr().write_value(ctx.iv[0]); + T::regs().init(0).ivrr().write_value(ctx.iv[1]); + T::regs().init(1).ivlr().write_value(ctx.iv[2]); + T::regs().init(1).ivrr().write_value(ctx.iv[3]); + + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + for i in 0..8 { + T::regs().csgcmccmr(i).write_value(ctx.csgcmccm[i]); + T::regs().csgcmr(i).write_value(ctx.csgcm[i]); + } + self.load_key(ctx.cipher.key()); + + // Prepare key if applicable. + ctx.cipher.prepare_key(T::regs()); + T::regs().cr().write(|w| w.0 = ctx.cr); + + // Enable crypto processor. + T::regs().cr().modify(|w| w.set_crypen(true)); + } + + fn write_bytes_blocking(&self, block_size: usize, blocks: &[u8]) { + // Ensure input is a multiple of block size. + assert_eq!(blocks.len() % block_size, 0); + let mut index = 0; + let end_index = blocks.len(); + while index < end_index { + let mut in_word: [u8; 4] = [0; 4]; + in_word.copy_from_slice(&blocks[index..index + 4]); + T::regs().din().write_value(u32::from_ne_bytes(in_word)); + index += 4; + if index % block_size == 0 { + // Block until input FIFO is empty. + while !T::regs().sr().read().ifem() {} + } + } + } + + async fn write_bytes(dma: &mut PeripheralRef<'_, DmaIn>, block_size: usize, blocks: &[u8]) + where + DmaIn: crate::cryp::DmaIn, + { + if blocks.len() == 0 { + return; + } + // Ensure input is a multiple of block size. + assert_eq!(blocks.len() % block_size, 0); + // Configure DMA to transfer input to crypto core. + let dma_request = dma.request(); + let dst_ptr = T::regs().din().as_ptr(); + let num_words = blocks.len() / 4; + let src_ptr = ptr::slice_from_raw_parts(blocks.as_ptr().cast(), num_words); + let options = TransferOptions { + #[cfg(not(gpdma))] + priority: crate::dma::Priority::High, + ..Default::default() + }; + let dma_transfer = unsafe { Transfer::new_write_raw(dma, dma_request, src_ptr, dst_ptr, options) }; + T::regs().dmacr().modify(|w| w.set_dien(true)); + // Wait for the transfer to complete. + dma_transfer.await; + } + + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + fn write_words_blocking(&self, block_size: usize, blocks: &[u32]) { + assert_eq!((blocks.len() * 4) % block_size, 0); + let mut byte_counter: usize = 0; + for word in blocks { + T::regs().din().write_value(*word); + byte_counter += 4; + if byte_counter % block_size == 0 { + // Block until input FIFO is empty. + while !T::regs().sr().read().ifem() {} + } + } + } + + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + async fn write_words(dma: &mut PeripheralRef<'_, DmaIn>, block_size: usize, blocks: &[u32]) + where + DmaIn: crate::cryp::DmaIn, + { + if blocks.len() == 0 { + return; + } + // Ensure input is a multiple of block size. + assert_eq!((blocks.len() * 4) % block_size, 0); + // Configure DMA to transfer input to crypto core. + let dma_request = dma.request(); + let dst_ptr = T::regs().din().as_ptr(); + let num_words = blocks.len(); + let src_ptr = ptr::slice_from_raw_parts(blocks.as_ptr().cast(), num_words); + let options = TransferOptions { + #[cfg(not(gpdma))] + priority: crate::dma::Priority::High, + ..Default::default() + }; + let dma_transfer = unsafe { Transfer::new_write_raw(dma, dma_request, src_ptr, dst_ptr, options) }; + T::regs().dmacr().modify(|w| w.set_dien(true)); + // Wait for the transfer to complete. + dma_transfer.await; + } + + fn read_bytes_blocking(&self, block_size: usize, blocks: &mut [u8]) { + // Block until there is output to read. + while !T::regs().sr().read().ofne() {} + // Ensure input is a multiple of block size. + assert_eq!(blocks.len() % block_size, 0); + // Read block out + let mut index = 0; + let end_index = blocks.len(); + while index < end_index { + let out_word: u32 = T::regs().dout().read(); + blocks[index..index + 4].copy_from_slice(u32::to_ne_bytes(out_word).as_slice()); + index += 4; + } + } + + async fn read_bytes(dma: &mut PeripheralRef<'_, DmaOut>, block_size: usize, blocks: &mut [u8]) + where + DmaOut: crate::cryp::DmaOut, + { + if blocks.len() == 0 { + return; + } + // Ensure input is a multiple of block size. + assert_eq!(blocks.len() % block_size, 0); + // Configure DMA to get output from crypto core. + let dma_request = dma.request(); + let src_ptr = T::regs().dout().as_ptr(); + let num_words = blocks.len() / 4; + let dst_ptr = ptr::slice_from_raw_parts_mut(blocks.as_mut_ptr().cast(), num_words); + let options = TransferOptions { + #[cfg(not(gpdma))] + priority: crate::dma::Priority::VeryHigh, + ..Default::default() + }; + let dma_transfer = unsafe { Transfer::new_read_raw(dma, dma_request, src_ptr, dst_ptr, options) }; + T::regs().dmacr().modify(|w| w.set_doen(true)); + // Wait for the transfer to complete. + dma_transfer.await; + } +} + +trait SealedInstance { + fn regs() -> pac::cryp::Cryp; +} + +/// CRYP instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + Peripheral

+ crate::rcc::RccPeripheral + 'static + Send { + /// Interrupt for this CRYP instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +foreach_interrupt!( + ($inst:ident, cryp, CRYP, GLOBAL, $irq:ident) => { + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + + impl SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::cryp::Cryp { + crate::pac::$inst + } + } + }; +); + +dma_trait!(DmaIn, Instance); +dma_trait!(DmaOut, Instance); diff --git a/embassy/embassy-stm32/src/dac/mod.rs b/embassy/embassy-stm32/src/dac/mod.rs new file mode 100644 index 0000000..8bba5de --- /dev/null +++ b/embassy/embassy-stm32/src/dac/mod.rs @@ -0,0 +1,539 @@ +//! Digital to Analog Converter (DAC) +#![macro_use] + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use crate::dma::NoDma; +#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] +use crate::pac::dac; +use crate::rcc::{self, RccPeripheral}; +use crate::{peripherals, Peripheral}; + +mod tsel; +pub use tsel::TriggerSel; + +/// Operating mode for DAC channel +#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Mode { + /// Normal mode, channel is connected to external pin with buffer enabled. + NormalExternalBuffered, + /// Normal mode, channel is connected to external pin and internal peripherals + /// with buffer enabled. + NormalBothBuffered, + /// Normal mode, channel is connected to external pin with buffer disabled. + NormalExternalUnbuffered, + /// Normal mode, channel is connected to internal peripherals with buffer disabled. + NormalInternalUnbuffered, + /// Sample-and-hold mode, channel is connected to external pin with buffer enabled. + SampleHoldExternalBuffered, + /// Sample-and-hold mode, channel is connected to external pin and internal peripherals + /// with buffer enabled. + SampleHoldBothBuffered, + /// Sample-and-hold mode, channel is connected to external pin and internal peripherals + /// with buffer disabled. + SampleHoldBothUnbuffered, + /// Sample-and-hold mode, channel is connected to internal peripherals with buffer disabled. + SampleHoldInternalUnbuffered, +} + +#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] +impl Mode { + fn mode(&self) -> dac::vals::Mode { + match self { + Mode::NormalExternalBuffered => dac::vals::Mode::NORMAL_EXT_BUFEN, + Mode::NormalBothBuffered => dac::vals::Mode::NORMAL_EXT_INT_BUFEN, + Mode::NormalExternalUnbuffered => dac::vals::Mode::NORMAL_EXT_BUFDIS, + Mode::NormalInternalUnbuffered => dac::vals::Mode::NORMAL_INT_BUFDIS, + Mode::SampleHoldExternalBuffered => dac::vals::Mode::SAMPHOLD_EXT_BUFEN, + Mode::SampleHoldBothBuffered => dac::vals::Mode::SAMPHOLD_EXT_INT_BUFEN, + Mode::SampleHoldBothUnbuffered => dac::vals::Mode::SAMPHOLD_EXT_INT_BUFDIS, + Mode::SampleHoldInternalUnbuffered => dac::vals::Mode::SAMPHOLD_INT_BUFDIS, + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Single 8 or 12 bit value that can be output by the DAC. +/// +/// 12-bit values outside the permitted range are silently truncated. +pub enum Value { + /// 8 bit value + Bit8(u8), + /// 12 bit value stored in a u16, left-aligned + Bit12Left(u16), + /// 12 bit value stored in a u16, right-aligned + Bit12Right(u16), +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Dual 8 or 12 bit values that can be output by the DAC channels 1 and 2 simultaneously. +/// +/// 12-bit values outside the permitted range are silently truncated. +pub enum DualValue { + /// 8 bit value + Bit8(u8, u8), + /// 12 bit value stored in a u16, left-aligned + Bit12Left(u16, u16), + /// 12 bit value stored in a u16, right-aligned + Bit12Right(u16, u16), +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Array variant of [`Value`]. +pub enum ValueArray<'a> { + /// 8 bit values + Bit8(&'a [u8]), + /// 12 bit value stored in a u16, left-aligned + Bit12Left(&'a [u16]), + /// 12 bit values stored in a u16, right-aligned + Bit12Right(&'a [u16]), +} + +/// Driver for a single DAC channel. +/// +/// If you want to use both channels, either together or independently, +/// create a [`Dac`] first and use it to access each channel. +pub struct DacChannel<'d, T: Instance, const N: u8, DMA = NoDma> { + phantom: PhantomData<&'d mut T>, + #[allow(unused)] + dma: PeripheralRef<'d, DMA>, +} + +/// DAC channel 1 type alias. +pub type DacCh1<'d, T, DMA = NoDma> = DacChannel<'d, T, 1, DMA>; +/// DAC channel 2 type alias. +pub type DacCh2<'d, T, DMA = NoDma> = DacChannel<'d, T, 2, DMA>; + +impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { + const IDX: usize = (N - 1) as usize; + + /// Create a new `DacChannel` instance, consuming the underlying DAC peripheral. + /// + /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument. + /// + /// The channel is enabled on creation and begin to drive the output pin. + /// Note that some methods, such as `set_trigger()` and `set_mode()`, will + /// disable the channel; you must re-enable it with `enable()`. + /// + /// By default, triggering is disabled, but it can be enabled using + /// [`DacChannel::set_trigger()`]. + pub fn new( + _peri: impl Peripheral

+ 'd, + dma: impl Peripheral

+ 'd, + pin: impl Peripheral

+ crate::gpio::Pin> + 'd, + ) -> Self { + into_ref!(dma, pin); + pin.set_as_analog(); + rcc::enable_and_reset::(); + let mut dac = Self { + phantom: PhantomData, + dma, + }; + #[cfg(any(dac_v5, dac_v6, dac_v7))] + dac.set_hfsel(); + dac.enable(); + dac + } + + /// Create a new `DacChannel` instance where the external output pin is not used, + /// so the DAC can only be used to generate internal signals. + /// The GPIO pin is therefore available to be used for other functions. + /// + /// The channel is set to [`Mode::NormalInternalUnbuffered`] and enabled on creation. + /// Note that some methods, such as `set_trigger()` and `set_mode()`, will disable the + /// channel; you must re-enable it with `enable()`. + /// + /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument. + /// + /// By default, triggering is disabled, but it can be enabled using + /// [`DacChannel::set_trigger()`]. + #[cfg(all(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7), not(any(stm32h56x, stm32h57x))))] + pub fn new_internal(_peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd) -> Self { + into_ref!(dma); + rcc::enable_and_reset::(); + let mut dac = Self { + phantom: PhantomData, + dma, + }; + #[cfg(any(dac_v5, dac_v6, dac_v7))] + dac.set_hfsel(); + dac.set_mode(Mode::NormalInternalUnbuffered); + dac.enable(); + dac + } + + /// Enable or disable this channel. + pub fn set_enable(&mut self, on: bool) { + critical_section::with(|_| { + T::regs().cr().modify(|reg| { + reg.set_en(Self::IDX, on); + }); + }); + } + + /// Enable this channel. + pub fn enable(&mut self) { + self.set_enable(true) + } + + /// Disable this channel. + pub fn disable(&mut self) { + self.set_enable(false) + } + + /// Set the trigger source for this channel. + /// + /// This method disables the channel, so you may need to re-enable afterwards. + pub fn set_trigger(&mut self, source: TriggerSel) { + critical_section::with(|_| { + T::regs().cr().modify(|reg| { + reg.set_en(Self::IDX, false); + reg.set_tsel(Self::IDX, source as u8); + }); + }); + } + + /// Enable or disable triggering for this channel. + pub fn set_triggering(&mut self, on: bool) { + critical_section::with(|_| { + T::regs().cr().modify(|reg| { + reg.set_ten(Self::IDX, on); + }); + }); + } + + /// Software trigger this channel. + pub fn trigger(&mut self) { + T::regs().swtrigr().write(|reg| { + reg.set_swtrig(Self::IDX, true); + }); + } + + /// Set mode of this channel. + /// + /// This method disables the channel, so you may need to re-enable afterwards. + #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] + pub fn set_mode(&mut self, mode: Mode) { + critical_section::with(|_| { + T::regs().cr().modify(|reg| { + reg.set_en(Self::IDX, false); + }); + T::regs().mcr().modify(|reg| { + reg.set_mode(Self::IDX, mode.mode()); + }); + }); + } + + /// Write a new value to this channel. + /// + /// If triggering is not enabled, the new value is immediately output; otherwise, + /// it will be output after the next trigger. + pub fn set(&mut self, value: Value) { + match value { + Value::Bit8(v) => T::regs().dhr8r(Self::IDX).write(|reg| reg.set_dhr(v)), + Value::Bit12Left(v) => T::regs().dhr12l(Self::IDX).write(|reg| reg.set_dhr(v)), + Value::Bit12Right(v) => T::regs().dhr12r(Self::IDX).write(|reg| reg.set_dhr(v)), + } + } + + /// Read the current output value of the DAC. + pub fn read(&self) -> u16 { + T::regs().dor(Self::IDX).read().dor() + } + + /// Set HFSEL as appropriate for the current peripheral clock frequency. + #[cfg(dac_v5)] + fn set_hfsel(&mut self) { + if T::frequency() >= crate::time::mhz(80) { + critical_section::with(|_| { + T::regs().cr().modify(|reg| { + reg.set_hfsel(true); + }); + }); + } + } + + /// Set HFSEL as appropriate for the current peripheral clock frequency. + #[cfg(any(dac_v6, dac_v7))] + fn set_hfsel(&mut self) { + if T::frequency() >= crate::time::mhz(160) { + critical_section::with(|_| { + T::regs().mcr().modify(|reg| { + reg.set_hfsel(0b10); + }); + }); + } else if T::frequency() >= crate::time::mhz(80) { + critical_section::with(|_| { + T::regs().mcr().modify(|reg| { + reg.set_hfsel(0b01); + }); + }); + } + } +} + +macro_rules! impl_dma_methods { + ($n:literal, $trait:ident) => { + impl<'d, T: Instance, DMA> DacChannel<'d, T, $n, DMA> + where + DMA: $trait, + { + /// Write `data` to this channel via DMA. + /// + /// To prevent delays or glitches when outputing a periodic waveform, the `circular` + /// flag can be set. This configures a circular DMA transfer that continually outputs + /// `data`. Note that for performance reasons in circular mode the transfer-complete + /// interrupt is disabled. + #[cfg(not(gpdma))] + pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) { + // Enable DAC and DMA + T::regs().cr().modify(|w| { + w.set_en(Self::IDX, true); + w.set_dmaen(Self::IDX, true); + }); + + let tx_request = self.dma.request(); + let dma_channel = &mut self.dma; + + let tx_options = crate::dma::TransferOptions { + circular, + half_transfer_ir: false, + complete_transfer_ir: !circular, + ..Default::default() + }; + + // Initiate the correct type of DMA transfer depending on what data is passed + let tx_f = match data { + ValueArray::Bit8(buf) => unsafe { + crate::dma::Transfer::new_write( + dma_channel, + tx_request, + buf, + T::regs().dhr8r(Self::IDX).as_ptr() as *mut u8, + tx_options, + ) + }, + ValueArray::Bit12Left(buf) => unsafe { + crate::dma::Transfer::new_write( + dma_channel, + tx_request, + buf, + T::regs().dhr12l(Self::IDX).as_ptr() as *mut u16, + tx_options, + ) + }, + ValueArray::Bit12Right(buf) => unsafe { + crate::dma::Transfer::new_write( + dma_channel, + tx_request, + buf, + T::regs().dhr12r(Self::IDX).as_ptr() as *mut u16, + tx_options, + ) + }, + }; + + tx_f.await; + + T::regs().cr().modify(|w| { + w.set_en(Self::IDX, false); + w.set_dmaen(Self::IDX, false); + }); + } + } + }; +} + +impl_dma_methods!(1, DacDma1); +impl_dma_methods!(2, DacDma2); + +impl<'d, T: Instance, const N: u8, DMA> Drop for DacChannel<'d, T, N, DMA> { + fn drop(&mut self) { + rcc::disable::(); + } +} + +/// DAC driver. +/// +/// Use this struct when you want to use both channels, either together or independently. +/// +/// # Example +/// +/// ```ignore +/// // Pins may need to be changed for your specific device. +/// let (dac_ch1, dac_ch2) = embassy_stm32::dac::Dac::new(p.DAC1, NoDma, NoDma, p.PA4, p.PA5).split(); +/// ``` +pub struct Dac<'d, T: Instance, DMACh1 = NoDma, DMACh2 = NoDma> { + ch1: DacChannel<'d, T, 1, DMACh1>, + ch2: DacChannel<'d, T, 2, DMACh2>, +} + +impl<'d, T: Instance, DMACh1, DMACh2> Dac<'d, T, DMACh1, DMACh2> { + /// Create a new `Dac` instance, consuming the underlying DAC peripheral. + /// + /// This struct allows you to access both channels of the DAC, where available. You can either + /// call `split()` to obtain separate `DacChannel`s, or use methods on `Dac` to use + /// the two channels together. + /// + /// The channels are enabled on creation and begin to drive their output pins. + /// Note that some methods, such as `set_trigger()` and `set_mode()`, will + /// disable the channel; you must re-enable them with `enable()`. + /// + /// By default, triggering is disabled, but it can be enabled using the `set_trigger()` + /// method on the underlying channels. + pub fn new( + _peri: impl Peripheral

+ 'd, + dma_ch1: impl Peripheral

+ 'd, + dma_ch2: impl Peripheral

+ 'd, + pin_ch1: impl Peripheral

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

+ crate::gpio::Pin> + 'd, + ) -> Self { + into_ref!(dma_ch1, dma_ch2, pin_ch1, pin_ch2); + pin_ch1.set_as_analog(); + pin_ch2.set_as_analog(); + + // Enable twice to increment the DAC refcount for each channel. + rcc::enable_and_reset::(); + rcc::enable_and_reset::(); + + let mut ch1 = DacCh1 { + phantom: PhantomData, + dma: dma_ch1, + }; + #[cfg(any(dac_v5, dac_v6, dac_v7))] + ch1.set_hfsel(); + ch1.enable(); + + let mut ch2 = DacCh2 { + phantom: PhantomData, + dma: dma_ch2, + }; + #[cfg(any(dac_v5, dac_v6, dac_v7))] + ch2.set_hfsel(); + ch2.enable(); + + Self { ch1, ch2 } + } + + /// Create a new `Dac` instance where the external output pins are not used, + /// so the DAC can only be used to generate internal signals but the GPIO + /// pins remain available for other functions. + /// + /// This struct allows you to access both channels of the DAC, where available. You can either + /// call `split()` to obtain separate `DacChannel`s, or use methods on `Dac` to use the two + /// channels together. + /// + /// The channels are set to [`Mode::NormalInternalUnbuffered`] and enabled on creation. + /// Note that some methods, such as `set_trigger()` and `set_mode()`, will disable the + /// channel; you must re-enable them with `enable()`. + /// + /// By default, triggering is disabled, but it can be enabled using the `set_trigger()` + /// method on the underlying channels. + #[cfg(all(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7), not(any(stm32h56x, stm32h57x))))] + pub fn new_internal( + _peri: impl Peripheral

+ 'd, + dma_ch1: impl Peripheral

+ 'd, + dma_ch2: impl Peripheral

+ 'd, + ) -> Self { + into_ref!(dma_ch1, dma_ch2); + // Enable twice to increment the DAC refcount for each channel. + rcc::enable_and_reset::(); + rcc::enable_and_reset::(); + + let mut ch1 = DacCh1 { + phantom: PhantomData, + dma: dma_ch1, + }; + #[cfg(any(dac_v5, dac_v6, dac_v7))] + ch1.set_hfsel(); + ch1.set_mode(Mode::NormalInternalUnbuffered); + ch1.enable(); + + let mut ch2 = DacCh2 { + phantom: PhantomData, + dma: dma_ch2, + }; + #[cfg(any(dac_v5, dac_v6, dac_v7))] + ch2.set_hfsel(); + ch2.set_mode(Mode::NormalInternalUnbuffered); + ch2.enable(); + + Self { ch1, ch2 } + } + + /// Split this `Dac` into separate channels. + /// + /// You can access and move the channels around separately after splitting. + pub fn split(self) -> (DacCh1<'d, T, DMACh1>, DacCh2<'d, T, DMACh2>) { + (self.ch1, self.ch2) + } + + /// Temporarily access channel 1. + pub fn ch1(&mut self) -> &mut DacCh1<'d, T, DMACh1> { + &mut self.ch1 + } + + /// Temporarily access channel 2. + pub fn ch2(&mut self) -> &mut DacCh2<'d, T, DMACh2> { + &mut self.ch2 + } + + /// Simultaneously update channels 1 and 2 with a new value. + /// + /// If triggering is not enabled, the new values are immediately output; + /// otherwise, they will be output after the next trigger. + pub fn set(&mut self, values: DualValue) { + match values { + DualValue::Bit8(v1, v2) => T::regs().dhr8rd().write(|reg| { + reg.set_dhr(0, v1); + reg.set_dhr(1, v2); + }), + DualValue::Bit12Left(v1, v2) => T::regs().dhr12ld().write(|reg| { + reg.set_dhr(0, v1); + reg.set_dhr(1, v2); + }), + DualValue::Bit12Right(v1, v2) => T::regs().dhr12rd().write(|reg| { + reg.set_dhr(0, v1); + reg.set_dhr(1, v2); + }), + } + } +} + +trait SealedInstance { + fn regs() -> crate::pac::dac::Dac; +} + +/// DAC instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral + 'static {} +dma_trait!(DacDma1, Instance); +dma_trait!(DacDma2, Instance); + +/// Marks a pin that can be used with the DAC +pub trait DacPin: crate::gpio::Pin + 'static {} + +foreach_peripheral!( + (dac, $inst:ident) => { + impl crate::dac::SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::dac::Dac { + crate::pac::$inst + } + } + + impl crate::dac::Instance for peripherals::$inst {} + }; +); + +macro_rules! impl_dac_pin { + ($inst:ident, $pin:ident, $ch:expr) => { + impl crate::dac::DacPin for crate::peripherals::$pin {} + }; +} diff --git a/embassy/embassy-stm32/src/dac/tsel.rs b/embassy/embassy-stm32/src/dac/tsel.rs new file mode 100644 index 0000000..1877954 --- /dev/null +++ b/embassy/embassy-stm32/src/dac/tsel.rs @@ -0,0 +1,301 @@ +#![allow(missing_docs)] + +/// Trigger selection for STM32F0. +#[cfg(stm32f0)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Tim6 = 0, + Tim3 = 1, + Tim7 = 2, + Tim15 = 3, + Tim2 = 4, + Exti9 = 6, + Software = 7, +} + +/// Trigger selection for STM32F1. +#[cfg(stm32f1)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Tim6 = 0, + #[cfg(any(stm32f100, stm32f105, stm32f107))] + Tim3 = 1, + #[cfg(any(stm32f101, stm32f103))] + Tim8 = 1, + Tim7 = 2, + #[cfg(any(stm32f101, stm32f103, stm32f105, stm32f107))] + Tim5 = 3, + #[cfg(all(stm32f100, any(flashsize_4, flashsize_6, flashsize_8, flashsize_b)))] + Tim15 = 3, + #[cfg(all(stm32f100, any(flashsize_c, flashsize_d, flashsize_e)))] + /// Can be remapped to TIM15 with MISC_REMAP in AFIO_MAPR2. + Tim5Or15 = 3, + Tim2 = 4, + Tim4 = 5, + Exti9 = 6, + Software = 7, +} + +/// Trigger selection for STM32F2/F4/F7/L4, except F410 or L4+. +#[cfg(all(any(stm32f2, stm32f4, stm32f7, stm32l4_nonplus), not(stm32f410)))] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Tim6 = 0, + Tim8 = 1, + #[cfg(not(any(stm32l45x, stm32l46x)))] + Tim7 = 2, + Tim5 = 3, + Tim2 = 4, + Tim4 = 5, + Exti9 = 6, + Software = 7, +} + +/// Trigger selection for STM32F410. +#[cfg(stm32f410)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Tim5 = 3, + Exti9 = 6, + Software = 7, +} + +/// Trigger selection for STM32F301/2 and 318. +#[cfg(any(stm32f301, stm32f302, stm32f318))] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Tim6 = 0, + #[cfg(stm32f302)] + /// Requires DAC_TRIG_RMP set in SYSCFG_CFGR1. + Tim3 = 1, + Tim15 = 3, + Tim2 = 4, + #[cfg(all(stm32f302, any(flashsize_6, flashsize_8)))] + Tim4 = 5, + Exti9 = 6, + Software = 7, +} + +/// Trigger selection for STM32F303/3x8 (excluding 318 which is like 301, and 378 which is 37x). +#[cfg(any(stm32f303, stm32f328, stm32f358, stm32f398))] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Tim6 = 0, + /// * DAC1: defaults to TIM8 but can be remapped to TIM3 with DAC_TRIG_RMP in SYSCFG_CFGR1 + /// * DAC2: always TIM3 + Tim8Or3 = 1, + Tim7 = 2, + Tim15 = 3, + Tim2 = 4, + Tim4 = 5, + Exti9 = 6, + Software = 7, +} + +/// Trigger selection for STM32F37x. +#[cfg(any(stm32f373, stm32f378))] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Tim6 = 0, + Tim3 = 1, + Tim7 = 2, + /// TIM5 on DAC1, TIM18 on DAC2 + Dac1Tim5Dac2Tim18 = 3, + Tim2 = 4, + Tim4 = 5, + Exti9 = 6, + Software = 7, +} + +/// Trigger selection for STM32F334. +#[cfg(stm32f334)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Tim6 = 0, + /// Requires DAC_TRIG_RMP set in SYSCFG_CFGR1. + Tim3 = 1, + Tim7 = 2, + /// Can be remapped to HRTIM_DACTRG1 using DAC1_TRIG3_RMP in SYSCFG_CFGR3. + Tim15OrHrtimDacTrg1 = 3, + Tim2 = 4, + /// Requires DAC_TRIG5_RMP set in SYSCFG_CFGR3. + HrtimDacTrg2 = 5, +} + +/// Trigger selection for STM32L0. +#[cfg(stm32l0)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Tim6 = 0, + Tim3 = 1, + Tim3Ch3 = 2, + Tim21 = 3, + Tim2 = 4, + Tim7 = 5, + Exti9 = 6, + Software = 7, +} + +/// Trigger selection for STM32L1. +#[cfg(stm32l1)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Tim6 = 0, + Tim7 = 2, + Tim9 = 3, + Tim2 = 4, + Tim4 = 5, + Exti9 = 6, + Software = 7, +} + +/// Trigger selection for L4+, L5, U5, H7. +#[cfg(any(stm32l4_plus, stm32l5, stm32u5, stm32h7))] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Software = 0, + Tim1 = 1, + Tim2 = 2, + Tim4 = 3, + Tim5 = 4, + Tim6 = 5, + Tim7 = 6, + Tim8 = 7, + Tim15 = 8, + #[cfg(all(stm32h7, hrtim))] + Hrtim1DacTrg1 = 9, + #[cfg(all(stm32h7, hrtim))] + Hrtim1DacTrg2 = 10, + Lptim1 = 11, + #[cfg(not(stm32u5))] + Lptim2 = 12, + #[cfg(stm32u5)] + Lptim3 = 12, + Exti9 = 13, + #[cfg(any(stm32h7ax, stm32h7bx))] + /// RM0455 suggests this might be LPTIM2 on DAC1 and LPTIM3 on DAC2, + /// but it's probably wrong. Please let us know if you find out. + Lptim3 = 14, + #[cfg(any(stm32h72x, stm32h73x))] + Tim23 = 14, + #[cfg(any(stm32h72x, stm32h73x))] + Tim24 = 15, +} + +/// Trigger selection for H5. +#[cfg(stm32h5)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Software = 0, + Tim1 = 1, + Tim2 = 2, + #[cfg(any(stm32h56x, stm32h57x))] + Tim4 = 3, + #[cfg(stm32h503)] + Tim3 = 3, + #[cfg(any(stm32h56x, stm32h57x))] + Tim5 = 4, + Tim6 = 5, + Tim7 = 6, + #[cfg(any(stm32h56x, stm32h57x))] + Tim8 = 7, + #[cfg(any(stm32h56x, stm32h57x))] + Tim15 = 8, + Lptim1 = 11, + Lptim2 = 12, + Exti9 = 13, +} + +/// Trigger selection for G0. +#[cfg(stm32g0)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Software = 0, + Tim1 = 1, + Tim2 = 2, + Tim3 = 3, + Tim6 = 5, + Tim7 = 6, + Tim15 = 8, + Lptim1 = 11, + Lptim2 = 12, + Exti9 = 13, +} + +/// Trigger selection for U0. +#[cfg(stm32u0)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Software = 0, + Tim1 = 1, + Tim2 = 2, + Tim3 = 3, + Tim6 = 5, + Tim7 = 6, + Tim15 = 8, + Lptim1 = 11, + Lptim2 = 12, + Exti9 = 14, +} + +/// Trigger selection for G4. +#[cfg(stm32g4)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Software = 0, + /// * DAC1, DAC2, DAC4: TIM8 + /// * DAC3: TIM1 + Dac124Tim8Dac3Tim1 = 1, + Tim7 = 2, + Tim15 = 3, + Tim2 = 4, + Tim4 = 5, + Exti9 = 6, + Tim6 = 7, + Tim3 = 8, + HrtimDacRstTrg1 = 9, + HrtimDacRstTrg2 = 10, + HrtimDacRstTrg3 = 11, + HrtimDacRstTrg4 = 12, + HrtimDacRstTrg5 = 13, + HrtimDacRstTrg6 = 14, + /// * DAC1, DAC4: HRTIM_DAC_TRG1 + /// * DAC2: HRTIM_DAC_TRG2 + /// * DAC3: HRTIM_DAC_TRG3 + HrtimDacTrg123 = 15, +} + +/// Trigger selection for WL. +#[cfg(stm32wl)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + Software = 0, + Tim1 = 1, + Tim2 = 2, + Lptim1 = 11, + Lptim2 = 12, + Lptim3 = 13, + Exti9 = 14, +} + +impl TriggerSel { + pub fn tsel(&self) -> u8 { + *self as u8 + } +} diff --git a/embassy/embassy-stm32/src/dcmi.rs b/embassy/embassy-stm32/src/dcmi.rs new file mode 100644 index 0000000..4ba4e82 --- /dev/null +++ b/embassy/embassy-stm32/src/dcmi.rs @@ -0,0 +1,483 @@ +//! Digital Camera Interface (DCMI) +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::dma::Transfer; +use crate::gpio::{AfType, Pull}; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, rcc, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let ris = crate::pac::DCMI.ris().read(); + if ris.err_ris() { + trace!("DCMI IRQ: Error."); + crate::pac::DCMI.ier().modify(|ier| ier.set_err_ie(false)); + } + if ris.ovr_ris() { + trace!("DCMI IRQ: Overrun."); + crate::pac::DCMI.ier().modify(|ier| ier.set_ovr_ie(false)); + } + if ris.frame_ris() { + trace!("DCMI IRQ: Frame captured."); + crate::pac::DCMI.ier().modify(|ier| ier.set_frame_ie(false)); + } + STATE.waker.wake(); + } +} + +/// The level on the VSync pin when the data is not valid on the parallel interface. +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq)] +pub enum VSyncDataInvalidLevel { + Low, + High, +} + +/// The level on the VSync pin when the data is not valid on the parallel interface. +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq)] +pub enum HSyncDataInvalidLevel { + Low, + High, +} + +#[derive(Clone, Copy, PartialEq)] +#[allow(missing_docs)] +pub enum PixelClockPolarity { + RisingEdge, + FallingEdge, +} + +struct State { + waker: AtomicWaker, +} + +impl State { + const fn new() -> State { + State { + waker: AtomicWaker::new(), + } + } +} + +static STATE: State = State::new(); + +/// DCMI error. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Overrun error: the hardware generated data faster than we could read it. + Overrun, + /// Internal peripheral error. + PeripheralError, +} + +/// DCMI configuration. +#[non_exhaustive] +pub struct Config { + /// VSYNC level. + pub vsync_level: VSyncDataInvalidLevel, + /// HSYNC level. + pub hsync_level: HSyncDataInvalidLevel, + /// PIXCLK polarity. + pub pixclk_polarity: PixelClockPolarity, +} + +impl Default for Config { + fn default() -> Self { + Self { + vsync_level: VSyncDataInvalidLevel::High, + hsync_level: HSyncDataInvalidLevel::Low, + pixclk_polarity: PixelClockPolarity::RisingEdge, + } + } +} + +macro_rules! config_pins { + ($($pin:ident),*) => { + into_ref!($($pin),*); + critical_section::with(|_| { + $( + $pin.set_as_af($pin.af_num(), AfType::input(Pull::None)); + )* + }) + }; +} + +/// DCMI driver. +pub struct Dcmi<'d, T: Instance, Dma: FrameDma> { + inner: PeripheralRef<'d, T>, + dma: PeripheralRef<'d, Dma>, +} + +impl<'d, T, Dma> Dcmi<'d, T, Dma> +where + T: Instance, + Dma: FrameDma, +{ + /// Create a new DCMI driver with 8 data bits. + pub fn new_8bit( + peri: impl Peripheral

+ 'd, + dma: impl Peripheral

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

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + v_sync: impl Peripheral

> + 'd, + h_sync: impl Peripheral

> + 'd, + pixclk: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(peri, dma); + config_pins!(d0, d1, d2, d3, d4, d5, d6, d7); + config_pins!(v_sync, h_sync, pixclk); + + Self::new_inner(peri, dma, config, false, 0b00) + } + + /// Create a new DCMI driver with 10 data bits. + pub fn new_10bit( + peri: impl Peripheral

+ 'd, + dma: impl Peripheral

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

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + d8: impl Peripheral

> + 'd, + d9: impl Peripheral

> + 'd, + v_sync: impl Peripheral

> + 'd, + h_sync: impl Peripheral

> + 'd, + pixclk: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(peri, dma); + config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9); + config_pins!(v_sync, h_sync, pixclk); + + Self::new_inner(peri, dma, config, false, 0b01) + } + + /// Create a new DCMI driver with 12 data bits. + pub fn new_12bit( + peri: impl Peripheral

+ 'd, + dma: impl Peripheral

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

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + d8: impl Peripheral

> + 'd, + d9: impl Peripheral

> + 'd, + d10: impl Peripheral

> + 'd, + d11: impl Peripheral

> + 'd, + v_sync: impl Peripheral

> + 'd, + h_sync: impl Peripheral

> + 'd, + pixclk: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(peri, dma); + config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11); + config_pins!(v_sync, h_sync, pixclk); + + Self::new_inner(peri, dma, config, false, 0b10) + } + + /// Create a new DCMI driver with 14 data bits. + pub fn new_14bit( + peri: impl Peripheral

+ 'd, + dma: impl Peripheral

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

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + d8: impl Peripheral

> + 'd, + d9: impl Peripheral

> + 'd, + d10: impl Peripheral

> + 'd, + d11: impl Peripheral

> + 'd, + d12: impl Peripheral

> + 'd, + d13: impl Peripheral

> + 'd, + v_sync: impl Peripheral

> + 'd, + h_sync: impl Peripheral

> + 'd, + pixclk: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(peri, dma); + config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13); + config_pins!(v_sync, h_sync, pixclk); + + Self::new_inner(peri, dma, config, false, 0b11) + } + + /// Create a new DCMI driver with 8 data bits, with embedded synchronization. + pub fn new_es_8bit( + peri: impl Peripheral

+ 'd, + dma: impl Peripheral

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

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + pixclk: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(peri, dma); + config_pins!(d0, d1, d2, d3, d4, d5, d6, d7); + config_pins!(pixclk); + + Self::new_inner(peri, dma, config, true, 0b00) + } + + /// Create a new DCMI driver with 10 data bits, with embedded synchronization. + pub fn new_es_10bit( + peri: impl Peripheral

+ 'd, + dma: impl Peripheral

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

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + d8: impl Peripheral

> + 'd, + d9: impl Peripheral

> + 'd, + pixclk: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(peri, dma); + config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9); + config_pins!(pixclk); + + Self::new_inner(peri, dma, config, true, 0b01) + } + + /// Create a new DCMI driver with 12 data bits, with embedded synchronization. + pub fn new_es_12bit( + peri: impl Peripheral

+ 'd, + dma: impl Peripheral

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

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + d8: impl Peripheral

> + 'd, + d9: impl Peripheral

> + 'd, + d10: impl Peripheral

> + 'd, + d11: impl Peripheral

> + 'd, + pixclk: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(peri, dma); + config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11); + config_pins!(pixclk); + + Self::new_inner(peri, dma, config, true, 0b10) + } + + /// Create a new DCMI driver with 14 data bits, with embedded synchronization. + pub fn new_es_14bit( + peri: impl Peripheral

+ 'd, + dma: impl Peripheral

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

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + d8: impl Peripheral

> + 'd, + d9: impl Peripheral

> + 'd, + d10: impl Peripheral

> + 'd, + d11: impl Peripheral

> + 'd, + d12: impl Peripheral

> + 'd, + d13: impl Peripheral

> + 'd, + pixclk: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(peri, dma); + config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13); + config_pins!(pixclk); + + Self::new_inner(peri, dma, config, true, 0b11) + } + + fn new_inner( + peri: PeripheralRef<'d, T>, + dma: PeripheralRef<'d, Dma>, + config: Config, + use_embedded_synchronization: bool, + edm: u8, + ) -> Self { + rcc::enable_and_reset::(); + + peri.regs().cr().modify(|r| { + r.set_cm(true); // disable continuous mode (snapshot mode) + r.set_ess(use_embedded_synchronization); + r.set_pckpol(config.pixclk_polarity == PixelClockPolarity::RisingEdge); + r.set_vspol(config.vsync_level == VSyncDataInvalidLevel::High); + r.set_hspol(config.hsync_level == HSyncDataInvalidLevel::High); + r.set_fcrc(0x00); // capture every frame + r.set_edm(edm); // extended data mode + }); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + Self { inner: peri, dma } + } + + fn toggle(enable: bool) { + crate::pac::DCMI.cr().modify(|r| { + r.set_enable(enable); + r.set_capture(enable); + }) + } + + fn enable_irqs() { + crate::pac::DCMI.ier().modify(|r| { + r.set_err_ie(true); + r.set_ovr_ie(true); + r.set_frame_ie(true); + }); + } + + fn clear_interrupt_flags() { + crate::pac::DCMI.icr().write(|r| { + r.set_ovr_isc(true); + r.set_err_isc(true); + r.set_frame_isc(true); + }) + } + + /// This method starts the capture and finishes when both the dma transfer and DCMI finish the frame transfer. + /// The implication is that the input buffer size must be exactly the size of the captured frame. + pub async fn capture(&mut self, buffer: &mut [u32]) -> Result<(), Error> { + let r = self.inner.regs(); + let src = r.dr().as_ptr() as *mut u32; + let request = self.dma.request(); + let dma_read = unsafe { Transfer::new_read(&mut self.dma, request, src, buffer, Default::default()) }; + + Self::clear_interrupt_flags(); + Self::enable_irqs(); + + Self::toggle(true); + + let result = poll_fn(|cx| { + STATE.waker.register(cx.waker()); + + let ris = crate::pac::DCMI.ris().read(); + if ris.err_ris() { + crate::pac::DCMI.icr().write(|r| r.set_err_isc(true)); + Poll::Ready(Err(Error::PeripheralError)) + } else if ris.ovr_ris() { + crate::pac::DCMI.icr().write(|r| r.set_ovr_isc(true)); + Poll::Ready(Err(Error::Overrun)) + } else if ris.frame_ris() { + crate::pac::DCMI.icr().write(|r| r.set_frame_isc(true)); + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }); + + let (_, result) = embassy_futures::join::join(dma_read, result).await; + + Self::toggle(false); + + result + } +} + +trait SealedInstance: crate::rcc::RccPeripheral { + fn regs(&self) -> crate::pac::dcmi::Dcmi; +} + +/// DCMI instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + 'static { + /// Interrupt for this instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +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!(HSyncPin, Instance); +pin_trait!(VSyncPin, Instance); +pin_trait!(PixClkPin, Instance); + +// allow unused as U5 sources do not contain interrupt nor dma data +#[allow(unused)] +macro_rules! impl_peripheral { + ($inst:ident, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs(&self) -> crate::pac::dcmi::Dcmi { + crate::pac::$inst + } + } + + impl Instance for crate::peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +foreach_interrupt! { + ($inst:ident, dcmi, $block:ident, GLOBAL, $irq:ident) => { + impl_peripheral!($inst, $irq); + }; +} + +dma_trait!(FrameDma, Instance); diff --git a/embassy/embassy-stm32/src/dma/dma_bdma.rs b/embassy/embassy-stm32/src/dma/dma_bdma.rs new file mode 100644 index 0000000..1945c35 --- /dev/null +++ b/embassy/embassy-stm32/src/dma/dma_bdma.rs @@ -0,0 +1,1082 @@ +use core::future::{poll_fn, Future}; +use core::pin::Pin; +use core::sync::atomic::{fence, AtomicUsize, Ordering}; +use core::task::{Context, Poll, Waker}; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use super::ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer}; +use super::word::{Word, WordSize}; +use super::{AnyChannel, Channel, Dir, Request, STATE}; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, pac}; + +pub(crate) struct ChannelInfo { + pub(crate) dma: DmaInfo, + pub(crate) num: usize, + #[cfg(feature = "_dual-core")] + pub(crate) irq: pac::Interrupt, + #[cfg(dmamux)] + pub(crate) dmamux: super::DmamuxInfo, +} + +#[derive(Clone, Copy)] +pub(crate) enum DmaInfo { + #[cfg(dma)] + Dma(pac::dma::Dma), + #[cfg(bdma)] + Bdma(pac::bdma::Dma), +} + +/// DMA transfer options. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TransferOptions { + /// Peripheral burst transfer configuration + #[cfg(dma)] + pub pburst: Burst, + /// Memory burst transfer configuration + #[cfg(dma)] + pub mburst: Burst, + /// Flow control configuration + #[cfg(dma)] + pub flow_ctrl: FlowControl, + /// FIFO threshold for DMA FIFO mode. If none, direct mode is used. + #[cfg(dma)] + pub fifo_threshold: Option, + /// Request priority level + pub priority: Priority, + /// Enable circular DMA + /// + /// Note: + /// If you enable circular mode manually, you may want to build and `.await` the `Transfer` in a separate task. + /// Since DMA in circular mode need manually stop, `.await` in current task would block the task forever. + pub circular: bool, + /// Enable half transfer interrupt + pub half_transfer_ir: bool, + /// Enable transfer complete interrupt + pub complete_transfer_ir: bool, +} + +impl Default for TransferOptions { + fn default() -> Self { + Self { + #[cfg(dma)] + pburst: Burst::Single, + #[cfg(dma)] + mburst: Burst::Single, + #[cfg(dma)] + flow_ctrl: FlowControl::Dma, + #[cfg(dma)] + fifo_threshold: None, + priority: Priority::VeryHigh, + circular: false, + half_transfer_ir: false, + complete_transfer_ir: true, + } + } +} + +/// DMA request priority +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Priority { + /// Low Priority + Low, + /// Medium Priority + Medium, + /// High Priority + High, + /// Very High Priority + VeryHigh, +} + +#[cfg(dma)] +impl From for pac::dma::vals::Pl { + fn from(value: Priority) -> Self { + match value { + Priority::Low => pac::dma::vals::Pl::LOW, + Priority::Medium => pac::dma::vals::Pl::MEDIUM, + Priority::High => pac::dma::vals::Pl::HIGH, + Priority::VeryHigh => pac::dma::vals::Pl::VERYHIGH, + } + } +} + +#[cfg(bdma)] +impl From for pac::bdma::vals::Pl { + fn from(value: Priority) -> Self { + match value { + Priority::Low => pac::bdma::vals::Pl::LOW, + Priority::Medium => pac::bdma::vals::Pl::MEDIUM, + Priority::High => pac::bdma::vals::Pl::HIGH, + Priority::VeryHigh => pac::bdma::vals::Pl::VERYHIGH, + } + } +} + +#[cfg(dma)] +pub use dma_only::*; +#[cfg(dma)] +mod dma_only { + use pac::dma::vals; + + use super::*; + + impl From for vals::Size { + fn from(raw: WordSize) -> Self { + match raw { + WordSize::OneByte => Self::BITS8, + WordSize::TwoBytes => Self::BITS16, + WordSize::FourBytes => Self::BITS32, + } + } + } + + impl From

for vals::Dir { + fn from(raw: Dir) -> Self { + match raw { + Dir::MemoryToPeripheral => Self::MEMORYTOPERIPHERAL, + Dir::PeripheralToMemory => Self::PERIPHERALTOMEMORY, + } + } + } + + /// DMA transfer burst setting. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Burst { + /// Single transfer + Single, + /// Incremental burst of 4 beats + Incr4, + /// Incremental burst of 8 beats + Incr8, + /// Incremental burst of 16 beats + Incr16, + } + + impl From for vals::Burst { + fn from(burst: Burst) -> Self { + match burst { + Burst::Single => vals::Burst::SINGLE, + Burst::Incr4 => vals::Burst::INCR4, + Burst::Incr8 => vals::Burst::INCR8, + Burst::Incr16 => vals::Burst::INCR16, + } + } + } + + /// DMA flow control setting. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum FlowControl { + /// Flow control by DMA + Dma, + /// Flow control by peripheral + Peripheral, + } + + impl From for vals::Pfctrl { + fn from(flow: FlowControl) -> Self { + match flow { + FlowControl::Dma => vals::Pfctrl::DMA, + FlowControl::Peripheral => vals::Pfctrl::PERIPHERAL, + } + } + } + + /// DMA FIFO threshold. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum FifoThreshold { + /// 1/4 full FIFO + Quarter, + /// 1/2 full FIFO + Half, + /// 3/4 full FIFO + ThreeQuarters, + /// Full FIFO + Full, + } + + impl From for vals::Fth { + fn from(value: FifoThreshold) -> Self { + match value { + FifoThreshold::Quarter => vals::Fth::QUARTER, + FifoThreshold::Half => vals::Fth::HALF, + FifoThreshold::ThreeQuarters => vals::Fth::THREEQUARTERS, + FifoThreshold::Full => vals::Fth::FULL, + } + } + } +} + +#[cfg(bdma)] +mod bdma_only { + use pac::bdma::vals; + + use super::*; + + impl From for vals::Size { + fn from(raw: WordSize) -> Self { + match raw { + WordSize::OneByte => Self::BITS8, + WordSize::TwoBytes => Self::BITS16, + WordSize::FourBytes => Self::BITS32, + } + } + } + + impl From for vals::Dir { + fn from(raw: Dir) -> Self { + match raw { + Dir::MemoryToPeripheral => Self::FROMMEMORY, + Dir::PeripheralToMemory => Self::FROMPERIPHERAL, + } + } + } +} + +pub(crate) struct ChannelState { + waker: AtomicWaker, + complete_count: AtomicUsize, +} + +impl ChannelState { + pub(crate) const NEW: Self = Self { + waker: AtomicWaker::new(), + complete_count: AtomicUsize::new(0), + }; +} + +/// safety: must be called only once +pub(crate) unsafe fn init( + cs: critical_section::CriticalSection, + #[cfg(dma)] dma_priority: interrupt::Priority, + #[cfg(bdma)] bdma_priority: interrupt::Priority, +) { + foreach_interrupt! { + ($peri:ident, dma, $block:ident, $signal_name:ident, $irq:ident) => { + crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, dma_priority); + #[cfg(not(feature = "_dual-core"))] + crate::interrupt::typelevel::$irq::enable(); + }; + ($peri:ident, bdma, $block:ident, $signal_name:ident, $irq:ident) => { + crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, bdma_priority); + #[cfg(not(feature = "_dual-core"))] + crate::interrupt::typelevel::$irq::enable(); + }; + } + crate::_generated::init_dma(); + crate::_generated::init_bdma(); +} + +impl AnyChannel { + /// Safety: Must be called with a matching set of parameters for a valid dma channel + pub(crate) unsafe fn on_irq(&self) { + let info = self.info(); + let state = &STATE[self.id as usize]; + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + let cr = r.st(info.num).cr(); + let isr = r.isr(info.num / 4).read(); + + if isr.teif(info.num % 4) { + panic!("DMA: error on DMA@{:08x} channel {}", r.as_ptr() as u32, info.num); + } + + if isr.htif(info.num % 4) && cr.read().htie() { + // Acknowledge half transfer complete interrupt + r.ifcr(info.num / 4).write(|w| w.set_htif(info.num % 4, true)); + } else if isr.tcif(info.num % 4) && cr.read().tcie() { + // Acknowledge transfer complete interrupt + r.ifcr(info.num / 4).write(|w| w.set_tcif(info.num % 4, true)); + state.complete_count.fetch_add(1, Ordering::Release); + } else { + return; + } + state.waker.wake(); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + let isr = r.isr().read(); + let cr = r.ch(info.num).cr(); + + if isr.teif(info.num) { + panic!("DMA: error on BDMA@{:08x} channel {}", r.as_ptr() as u32, info.num); + } + + if isr.htif(info.num) && cr.read().htie() { + // Acknowledge half transfer complete interrupt + r.ifcr().write(|w| w.set_htif(info.num, true)); + } else if isr.tcif(info.num) && cr.read().tcie() { + // Acknowledge transfer complete interrupt + r.ifcr().write(|w| w.set_tcif(info.num, true)); + #[cfg(not(armv6m))] + state.complete_count.fetch_add(1, Ordering::Release); + #[cfg(armv6m)] + critical_section::with(|_| { + let x = state.complete_count.load(Ordering::Relaxed); + state.complete_count.store(x + 1, Ordering::Release); + }) + } else { + return; + } + + state.waker.wake(); + } + } + } + + unsafe fn configure( + &self, + _request: Request, + dir: Dir, + peri_addr: *const u32, + mem_addr: *mut u32, + mem_len: usize, + incr_mem: bool, + data_size: WordSize, + options: TransferOptions, + ) { + let info = self.info(); + #[cfg(feature = "_dual-core")] + { + use embassy_hal_internal::interrupt::InterruptExt as _; + info.irq.enable(); + } + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&info.dmamux, _request); + + assert!(mem_len > 0 && mem_len <= 0xFFFF); + + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + let ch = r.st(info.num); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::SeqCst); + + self.clear_irqs(); + + ch.par().write_value(peri_addr as u32); + ch.m0ar().write_value(mem_addr as u32); + ch.ndtr().write_value(pac::dma::regs::Ndtr(mem_len as _)); + ch.fcr().write(|w| { + if let Some(fth) = options.fifo_threshold { + // FIFO mode + w.set_dmdis(pac::dma::vals::Dmdis::DISABLED); + w.set_fth(fth.into()); + } else { + // Direct mode + w.set_dmdis(pac::dma::vals::Dmdis::ENABLED); + } + }); + ch.cr().write(|w| { + w.set_dir(dir.into()); + w.set_msize(data_size.into()); + w.set_psize(data_size.into()); + w.set_pl(options.priority.into()); + w.set_minc(incr_mem); + w.set_pinc(false); + w.set_teie(true); + w.set_htie(options.half_transfer_ir); + w.set_tcie(options.complete_transfer_ir); + w.set_circ(options.circular); + #[cfg(dma_v1)] + w.set_trbuff(true); + #[cfg(dma_v2)] + w.set_chsel(_request); + w.set_pburst(options.pburst.into()); + w.set_mburst(options.mburst.into()); + w.set_pfctrl(options.flow_ctrl.into()); + w.set_en(false); // don't start yet + }); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + #[cfg(bdma_v2)] + critical_section::with(|_| r.cselr().modify(|w| w.set_cs(info.num, _request))); + + let state: &ChannelState = &STATE[self.id as usize]; + let ch = r.ch(info.num); + + state.complete_count.store(0, Ordering::Release); + self.clear_irqs(); + + ch.par().write_value(peri_addr as u32); + ch.mar().write_value(mem_addr as u32); + ch.ndtr().write(|w| w.set_ndt(mem_len as u16)); + ch.cr().write(|w| { + w.set_psize(data_size.into()); + w.set_msize(data_size.into()); + w.set_minc(incr_mem); + w.set_dir(dir.into()); + w.set_teie(true); + w.set_tcie(options.complete_transfer_ir); + w.set_htie(options.half_transfer_ir); + w.set_circ(options.circular); + w.set_pl(options.priority.into()); + w.set_en(false); // don't start yet + }); + } + } + } + + fn start(&self) { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + let ch = r.st(info.num); + ch.cr().modify(|w| w.set_en(true)) + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + let ch = r.ch(info.num); + ch.cr().modify(|w| w.set_en(true)); + } + } + } + + fn clear_irqs(&self) { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + let isrn = info.num / 4; + let isrbit = info.num % 4; + + r.ifcr(isrn).write(|w| { + w.set_htif(isrbit, true); + w.set_tcif(isrbit, true); + w.set_teif(isrbit, true); + }); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + r.ifcr().write(|w| { + w.set_htif(info.num, true); + w.set_tcif(info.num, true); + w.set_teif(info.num, true); + }); + } + } + } + + fn request_stop(&self) { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + // Disable the channel. Keep the IEs enabled so the irqs still fire. + r.st(info.num).cr().write(|w| { + w.set_teie(true); + w.set_tcie(true); + }); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + // Disable the channel. Keep the IEs enabled so the irqs still fire. + r.ch(info.num).cr().write(|w| { + w.set_teie(true); + w.set_tcie(true); + }); + } + } + } + + fn request_pause(&self) { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + // Disable the channel without overwriting the existing configuration + r.st(info.num).cr().modify(|w| { + w.set_en(false); + }); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + // Disable the channel without overwriting the existing configuration + r.ch(info.num).cr().modify(|w| { + w.set_en(false); + }); + } + } + } + + fn is_running(&self) -> bool { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => r.st(info.num).cr().read().en(), + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + let state: &ChannelState = &STATE[self.id as usize]; + let ch = r.ch(info.num); + let en = ch.cr().read().en(); + let circular = ch.cr().read().circ(); + let tcif = state.complete_count.load(Ordering::Acquire) != 0; + en && (circular || !tcif) + } + } + } + + fn get_remaining_transfers(&self) -> u16 { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => r.st(info.num).ndtr().read().ndt(), + #[cfg(bdma)] + DmaInfo::Bdma(r) => r.ch(info.num).ndtr().read().ndt(), + } + } + + fn disable_circular_mode(&self) { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(regs) => regs.st(info.num).cr().modify(|w| { + w.set_circ(false); + }), + #[cfg(bdma)] + DmaInfo::Bdma(regs) => regs.ch(info.num).cr().modify(|w| { + w.set_circ(false); + }), + } + } + + fn poll_stop(&self) -> Poll<()> { + use core::sync::atomic::compiler_fence; + compiler_fence(Ordering::SeqCst); + + if !self.is_running() { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +/// DMA transfer. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a> { + channel: PeripheralRef<'a, AnyChannel>, +} + +impl<'a> Transfer<'a> { + /// Create a new read DMA transfer (peripheral to memory). + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: &'a mut [W], + options: TransferOptions, + ) -> Self { + Self::new_read_raw(channel, request, peri_addr, buf, options) + } + + /// Create a new read DMA transfer (peripheral to memory), using raw pointers. + pub unsafe fn new_read_raw( + channel: impl Peripheral

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

+ 'a, + request: Request, + buf: &'a [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + Self::new_write_raw(channel, request, buf, peri_addr, options) + } + + /// Create a new write DMA transfer (memory to peripheral), using raw pointers. + pub unsafe fn new_write_raw( + channel: impl Peripheral

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

+ 'a, + request: Request, + repeated: &'a W, + count: usize, + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + Self::new_inner( + channel.map_into(), + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + repeated as *const W as *mut u32, + count, + false, + W::size(), + options, + ) + } + + unsafe fn new_inner( + channel: PeripheralRef<'a, AnyChannel>, + _request: Request, + dir: Dir, + peri_addr: *const u32, + mem_addr: *mut u32, + mem_len: usize, + incr_mem: bool, + data_size: WordSize, + options: TransferOptions, + ) -> Self { + assert!(mem_len > 0 && mem_len <= 0xFFFF); + + channel.configure( + _request, dir, peri_addr, mem_addr, mem_len, incr_mem, data_size, options, + ); + channel.start(); + + Self { channel } + } + + /// Request the transfer to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_stop(&mut self) { + self.channel.request_stop() + } + + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + + /// Return whether this transfer is still running. + /// + /// If this returns `false`, it can be because either the transfer finished, or + /// it was requested to stop early with [`request_stop`](Self::request_stop). + pub fn is_running(&mut self) -> bool { + self.channel.is_running() + } + + /// Gets the total remaining transfers for the channel + /// Note: this will be zero for transfers that completed without cancellation. + pub fn get_remaining_transfers(&self) -> u16 { + self.channel.get_remaining_transfers() + } + + /// Blocking wait until the transfer finishes. + pub fn blocking_wait(mut self) { + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + + core::mem::forget(self); + } +} + +impl<'a> Drop for Transfer<'a> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + } +} + +impl<'a> Unpin for Transfer<'a> {} +impl<'a> Future for Transfer<'a> { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let state: &ChannelState = &STATE[self.channel.id as usize]; + + state.waker.register(cx.waker()); + + if self.is_running() { + Poll::Pending + } else { + Poll::Ready(()) + } + } +} + +// ============================== + +struct DmaCtrlImpl<'a>(PeripheralRef<'a, AnyChannel>); + +impl<'a> DmaCtrl for DmaCtrlImpl<'a> { + fn get_remaining_transfers(&self) -> usize { + self.0.get_remaining_transfers() as _ + } + + fn reset_complete_count(&mut self) -> usize { + let state = &STATE[self.0.id as usize]; + #[cfg(not(armv6m))] + return state.complete_count.swap(0, Ordering::AcqRel); + #[cfg(armv6m)] + return critical_section::with(|_| { + let x = state.complete_count.load(Ordering::Acquire); + state.complete_count.store(0, Ordering::Release); + x + }); + } + + fn set_waker(&mut self, waker: &Waker) { + STATE[self.0.id as usize].waker.register(waker); + } +} + +/// Ringbuffer for receiving data using DMA circular mode. +pub struct ReadableRingBuffer<'a, W: Word> { + channel: PeripheralRef<'a, AnyChannel>, + ringbuf: ReadableDmaRingBuffer<'a, W>, +} + +impl<'a, W: Word> ReadableRingBuffer<'a, W> { + /// Create a new ring buffer. + pub unsafe fn new( + channel: impl Peripheral

+ 'a, + _request: Request, + peri_addr: *mut W, + buffer: &'a mut [W], + mut options: TransferOptions, + ) -> Self { + into_ref!(channel); + let channel: PeripheralRef<'a, AnyChannel> = channel.map_into(); + + let buffer_ptr = buffer.as_mut_ptr(); + let len = buffer.len(); + let dir = Dir::PeripheralToMemory; + let data_size = W::size(); + + options.half_transfer_ir = true; + options.complete_transfer_ir = true; + options.circular = true; + + channel.configure( + _request, + dir, + peri_addr as *mut u32, + buffer_ptr as *mut u32, + len, + true, + data_size, + options, + ); + + Self { + channel, + ringbuf: ReadableDmaRingBuffer::new(buffer), + } + } + + /// Start the ring buffer operation. + /// + /// You must call this after creating it for it to work. + pub fn start(&mut self) { + self.channel.start(); + } + + /// Clear all data in the ring buffer. + pub fn clear(&mut self) { + self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow())); + } + + /// Read elements from the ring buffer + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the elements were read, then there will be some elements in the buffer remaining + /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read + /// Error is returned if the portion to be read was overwritten by the DMA controller. + pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), Error> { + self.ringbuf.read(&mut DmaCtrlImpl(self.channel.reborrow()), buf) + } + + /// Read an exact number of elements from the ringbuffer. + /// + /// Returns the remaining number of elements available for immediate reading. + /// Error is returned if the portion to be read was overwritten by the DMA controller. + /// + /// Async/Wake Behavior: + /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, + /// and when it wraps around. This means that when called with a buffer of length 'M', when this + /// ring buffer was created with a buffer of size 'N': + /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. + /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. + pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result { + self.ringbuf + .read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) + .await + } + + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?) + } + + /// The capacity of the ringbuffer + pub const fn capacity(&self) -> usize { + self.ringbuf.cap() + } + + /// Set a waker to be woken when at least one byte is received. + pub fn set_waker(&mut self, waker: &Waker) { + DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); + } + + /// Request the DMA to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_stop(&mut self) { + self.channel.request_stop() + } + + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + + /// Return whether DMA is still running. + /// + /// If this returns `false`, it can be because either the transfer finished, or + /// it was requested to stop early with [`request_stop`](Self::request_stop). + pub fn is_running(&mut self) -> bool { + self.channel.is_running() + } + + /// Stop the DMA transfer and await until the buffer is full. + /// + /// This disables the DMA transfer's circular mode so that the transfer + /// stops when the buffer is full. + /// + /// This is designed to be used with streaming input data such as the + /// I2S/SAI or ADC. + /// + /// When using the UART, you probably want `request_stop()`. + pub async fn stop(&mut self) { + self.channel.disable_circular_mode(); + //wait until cr.susp reads as true + poll_fn(|cx| { + self.set_waker(cx.waker()); + self.channel.poll_stop() + }) + .await + } +} + +impl<'a, W: Word> Drop for ReadableRingBuffer<'a, W> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + } +} + +/// Ringbuffer for writing data using DMA circular mode. +pub struct WritableRingBuffer<'a, W: Word> { + channel: PeripheralRef<'a, AnyChannel>, + ringbuf: WritableDmaRingBuffer<'a, W>, +} + +impl<'a, W: Word> WritableRingBuffer<'a, W> { + /// Create a new ring buffer. + pub unsafe fn new( + channel: impl Peripheral

+ 'a, + _request: Request, + peri_addr: *mut W, + buffer: &'a mut [W], + mut options: TransferOptions, + ) -> Self { + into_ref!(channel); + let channel: PeripheralRef<'a, AnyChannel> = channel.map_into(); + + let len = buffer.len(); + let dir = Dir::MemoryToPeripheral; + let data_size = W::size(); + let buffer_ptr = buffer.as_mut_ptr(); + + options.half_transfer_ir = true; + options.complete_transfer_ir = true; + options.circular = true; + + channel.configure( + _request, + dir, + peri_addr as *mut u32, + buffer_ptr as *mut u32, + len, + true, + data_size, + options, + ); + + Self { + channel, + ringbuf: WritableDmaRingBuffer::new(buffer), + } + } + + /// Start the ring buffer operation. + /// + /// You must call this after creating it for it to work. + pub fn start(&mut self) { + self.channel.start(); + } + + /// Clear all data in the ring buffer. + pub fn clear(&mut self) { + self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow())); + } + + /// Write elements directly to the raw buffer. + /// This can be used to fill the buffer before starting the DMA transfer. + pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { + self.ringbuf.write_immediate(buf) + } + + /// Write elements from the ring buffer + /// Return a tuple of the length written and the length remaining in the buffer + pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { + self.ringbuf.write(&mut DmaCtrlImpl(self.channel.reborrow()), buf) + } + + /// Write an exact number of elements to the ringbuffer. + pub async fn write_exact(&mut self, buffer: &[W]) -> Result { + self.ringbuf + .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) + .await + } + + /// Wait for any ring buffer write error. + pub async fn wait_write_error(&mut self) -> Result { + self.ringbuf + .wait_write_error(&mut DmaCtrlImpl(self.channel.reborrow())) + .await + } + + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?) + } + + /// The capacity of the ringbuffer + pub const fn capacity(&self) -> usize { + self.ringbuf.cap() + } + + /// Set a waker to be woken when at least one byte is received. + pub fn set_waker(&mut self, waker: &Waker) { + DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); + } + + /// Request the DMA to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_stop(&mut self) { + self.channel.request_stop() + } + + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + + /// Return whether DMA is still running. + /// + /// If this returns `false`, it can be because either the transfer finished, or + /// it was requested to stop early with [`request_stop`](Self::request_stop). + pub fn is_running(&mut self) -> bool { + self.channel.is_running() + } + + /// Stop the DMA transfer and await until the buffer is empty. + /// + /// This disables the DMA transfer's circular mode so that the transfer + /// stops when all available data has been written. + /// + /// This is designed to be used with streaming output data such as the + /// I2S/SAI or DAC. + pub async fn stop(&mut self) { + self.channel.disable_circular_mode(); + //wait until cr.susp reads as true + poll_fn(|cx| { + self.set_waker(cx.waker()); + self.channel.poll_stop() + }) + .await + } +} + +impl<'a, W: Word> Drop for WritableRingBuffer<'a, W> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + } +} diff --git a/embassy/embassy-stm32/src/dma/dmamux.rs b/embassy/embassy-stm32/src/dma/dmamux.rs new file mode 100644 index 0000000..1585b30 --- /dev/null +++ b/embassy/embassy-stm32/src/dma/dmamux.rs @@ -0,0 +1,25 @@ +#![macro_use] + +use crate::pac; + +pub(crate) struct DmamuxInfo { + pub(crate) mux: pac::dmamux::Dmamux, + pub(crate) num: usize, +} + +pub(crate) fn configure_dmamux(info: &DmamuxInfo, request: u8) { + let ch_mux_regs = info.mux.ccr(info.num); + ch_mux_regs.write(|reg| { + reg.set_nbreq(0); + reg.set_dmareq_id(request); + }); + + ch_mux_regs.modify(|reg| { + reg.set_ege(true); + }); +} + +/// safety: must be called only once +pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) { + crate::_generated::init_dmamux(); +} diff --git a/embassy/embassy-stm32/src/dma/gpdma.rs b/embassy/embassy-stm32/src/dma/gpdma.rs new file mode 100644 index 0000000..a877bb8 --- /dev/null +++ b/embassy/embassy-stm32/src/dma/gpdma.rs @@ -0,0 +1,341 @@ +#![macro_use] + +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{fence, Ordering}; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use super::word::{Word, WordSize}; +use super::{AnyChannel, Channel, Dir, Request, STATE}; +use crate::interrupt::typelevel::Interrupt; +use crate::interrupt::Priority; +use crate::pac; +use crate::pac::gpdma::vals; + +pub(crate) struct ChannelInfo { + pub(crate) dma: pac::gpdma::Gpdma, + pub(crate) num: usize, + #[cfg(feature = "_dual-core")] + pub(crate) irq: pac::Interrupt, +} + +/// GPDMA transfer options. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TransferOptions {} + +impl Default for TransferOptions { + fn default() -> Self { + Self {} + } +} + +impl From for vals::Dw { + fn from(raw: WordSize) -> Self { + match raw { + WordSize::OneByte => Self::BYTE, + WordSize::TwoBytes => Self::HALFWORD, + WordSize::FourBytes => Self::WORD, + } + } +} + +pub(crate) struct ChannelState { + waker: AtomicWaker, +} + +impl ChannelState { + pub(crate) const NEW: Self = Self { + waker: AtomicWaker::new(), + }; +} + +/// safety: must be called only once +pub(crate) unsafe fn init(cs: critical_section::CriticalSection, irq_priority: Priority) { + foreach_interrupt! { + ($peri:ident, gpdma, $block:ident, $signal_name:ident, $irq:ident) => { + crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, irq_priority); + #[cfg(not(feature = "_dual-core"))] + crate::interrupt::typelevel::$irq::enable(); + }; + } + crate::_generated::init_gpdma(); +} + +impl AnyChannel { + /// Safety: Must be called with a matching set of parameters for a valid dma channel + pub(crate) unsafe fn on_irq(&self) { + let info = self.info(); + #[cfg(feature = "_dual-core")] + { + use embassy_hal_internal::interrupt::InterruptExt as _; + info.irq.enable(); + } + + let state = &STATE[self.id as usize]; + + let ch = info.dma.ch(info.num); + let sr = ch.sr().read(); + + if sr.dtef() { + panic!( + "DMA: data transfer error on DMA@{:08x} channel {}", + info.dma.as_ptr() as u32, + info.num + ); + } + if sr.usef() { + panic!( + "DMA: user settings error on DMA@{:08x} channel {}", + info.dma.as_ptr() as u32, + info.num + ); + } + + if sr.suspf() || sr.tcf() { + // disable all xxIEs to prevent the irq from firing again. + ch.cr().write(|_| {}); + + // Wake the future. It'll look at tcf and see it's set. + state.waker.wake(); + } + } +} + +/// DMA transfer. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a> { + channel: PeripheralRef<'a, AnyChannel>, +} + +impl<'a> Transfer<'a> { + /// Create a new read DMA transfer (peripheral to memory). + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: &'a mut [W], + options: TransferOptions, + ) -> Self { + Self::new_read_raw(channel, request, peri_addr, buf, options) + } + + /// Create a new read DMA transfer (peripheral to memory), using raw pointers. + pub unsafe fn new_read_raw( + channel: impl Peripheral

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

+ 'a, + request: Request, + buf: &'a [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + Self::new_write_raw(channel, request, buf, peri_addr, options) + } + + /// Create a new write DMA transfer (memory to peripheral), using raw pointers. + pub unsafe fn new_write_raw( + channel: impl Peripheral

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

+ 'a, + request: Request, + repeated: &'a W, + count: usize, + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + Self::new_inner( + channel.map_into(), + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + repeated as *const W as *mut u32, + count, + false, + W::size(), + options, + ) + } + + unsafe fn new_inner( + channel: PeripheralRef<'a, AnyChannel>, + request: Request, + dir: Dir, + peri_addr: *const u32, + mem_addr: *mut u32, + mem_len: usize, + incr_mem: bool, + data_size: WordSize, + _options: TransferOptions, + ) -> Self { + // BNDT is specified as bytes, not as number of transfers. + let Ok(bndt) = (mem_len * data_size.bytes()).try_into() else { + panic!("DMA transfers may not be larger than 65535 bytes."); + }; + + let info = channel.info(); + let ch = info.dma.ch(info.num); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::SeqCst); + + let this = Self { channel }; + + ch.cr().write(|w| w.set_reset(true)); + ch.fcr().write(|w| w.0 = 0xFFFF_FFFF); // clear all irqs + ch.llr().write(|_| {}); // no linked list + ch.tr1().write(|w| { + w.set_sdw(data_size.into()); + w.set_ddw(data_size.into()); + w.set_sinc(dir == Dir::MemoryToPeripheral && incr_mem); + w.set_dinc(dir == Dir::PeripheralToMemory && incr_mem); + }); + ch.tr2().write(|w| { + w.set_dreq(match dir { + Dir::MemoryToPeripheral => vals::Dreq::DESTINATIONPERIPHERAL, + Dir::PeripheralToMemory => vals::Dreq::SOURCEPERIPHERAL, + }); + w.set_reqsel(request); + }); + ch.tr3().write(|_| {}); // no address offsets. + ch.br1().write(|w| w.set_bndt(bndt)); + + match dir { + Dir::MemoryToPeripheral => { + ch.sar().write_value(mem_addr as _); + ch.dar().write_value(peri_addr as _); + } + Dir::PeripheralToMemory => { + ch.sar().write_value(peri_addr as _); + ch.dar().write_value(mem_addr as _); + } + } + + ch.cr().write(|w| { + // Enable interrupts + w.set_tcie(true); + w.set_useie(true); + w.set_dteie(true); + w.set_suspie(true); + + // Start it + w.set_en(true); + }); + + this + } + + /// Request the transfer to stop. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_stop(&mut self) { + let info = self.channel.info(); + let ch = info.dma.ch(info.num); + + ch.cr().modify(|w| w.set_susp(true)) + } + + /// Return whether this transfer is still running. + /// + /// If this returns `false`, it can be because either the transfer finished, or + /// it was requested to stop early with [`request_stop`](Self::request_stop). + pub fn is_running(&mut self) -> bool { + let info = self.channel.info(); + let ch = info.dma.ch(info.num); + + let sr = ch.sr().read(); + !sr.tcf() && !sr.suspf() + } + + /// Gets the total remaining transfers for the channel + /// Note: this will be zero for transfers that completed without cancellation. + pub fn get_remaining_transfers(&self) -> u16 { + let info = self.channel.info(); + let ch = info.dma.ch(info.num); + + ch.br1().read().bndt() + } + + /// Blocking wait until the transfer finishes. + pub fn blocking_wait(mut self) { + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + + core::mem::forget(self); + } +} + +impl<'a> Drop for Transfer<'a> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + } +} + +impl<'a> Unpin for Transfer<'a> {} +impl<'a> Future for Transfer<'a> { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let state = &STATE[self.channel.id as usize]; + state.waker.register(cx.waker()); + + if self.is_running() { + Poll::Pending + } else { + Poll::Ready(()) + } + } +} diff --git a/embassy/embassy-stm32/src/dma/mod.rs b/embassy/embassy-stm32/src/dma/mod.rs new file mode 100644 index 0000000..66c4aa5 --- /dev/null +++ b/embassy/embassy-stm32/src/dma/mod.rs @@ -0,0 +1,141 @@ +//! Direct Memory Access (DMA) +#![macro_use] + +#[cfg(any(bdma, dma))] +mod dma_bdma; +#[cfg(any(bdma, dma))] +pub use dma_bdma::*; + +#[cfg(gpdma)] +pub(crate) mod gpdma; +#[cfg(gpdma)] +pub use gpdma::*; + +#[cfg(dmamux)] +mod dmamux; +#[cfg(dmamux)] +pub(crate) use dmamux::*; + +mod util; +pub(crate) use util::*; + +pub(crate) mod ringbuffer; +pub mod word; + +use embassy_hal_internal::{impl_peripheral, Peripheral}; + +use crate::interrupt; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum Dir { + MemoryToPeripheral, + PeripheralToMemory, +} + +/// DMA request type alias. (also known as DMA channel number in some chips) +#[cfg(any(dma_v2, bdma_v2, gpdma, dmamux))] +pub type Request = u8; +/// DMA request type alias. (also known as DMA channel number in some chips) +#[cfg(not(any(dma_v2, bdma_v2, gpdma, dmamux)))] +pub type Request = (); + +pub(crate) trait SealedChannel { + fn id(&self) -> u8; +} + +pub(crate) trait ChannelInterrupt { + #[cfg_attr(not(feature = "rt"), allow(unused))] + unsafe fn on_irq(); +} + +/// DMA channel. +#[allow(private_bounds)] +pub trait Channel: SealedChannel + Peripheral

+ Into + 'static { + /// Type-erase (degrade) this pin into an `AnyChannel`. + /// + /// This converts DMA channel singletons (`DMA1_CH3`, `DMA2_CH1`, ...), which + /// are all different types, into the same type. It is useful for + /// creating arrays of channels, or avoiding generics. + #[inline] + fn degrade(self) -> AnyChannel { + AnyChannel { id: self.id() } + } +} + +macro_rules! dma_channel_impl { + ($channel_peri:ident, $index:expr) => { + impl crate::dma::SealedChannel for crate::peripherals::$channel_peri { + fn id(&self) -> u8 { + $index + } + } + impl crate::dma::ChannelInterrupt for crate::peripherals::$channel_peri { + unsafe fn on_irq() { + crate::dma::AnyChannel { id: $index }.on_irq(); + } + } + + impl crate::dma::Channel for crate::peripherals::$channel_peri {} + + impl From for crate::dma::AnyChannel { + fn from(x: crate::peripherals::$channel_peri) -> Self { + crate::dma::Channel::degrade(x) + } + } + }; +} + +/// Type-erased DMA channel. +pub struct AnyChannel { + pub(crate) id: u8, +} +impl_peripheral!(AnyChannel); + +impl AnyChannel { + fn info(&self) -> &ChannelInfo { + &crate::_generated::DMA_CHANNELS[self.id as usize] + } +} + +impl SealedChannel for AnyChannel { + fn id(&self) -> u8 { + self.id + } +} +impl Channel for AnyChannel {} + +const CHANNEL_COUNT: usize = crate::_generated::DMA_CHANNELS.len(); +static STATE: [ChannelState; CHANNEL_COUNT] = [ChannelState::NEW; CHANNEL_COUNT]; + +/// "No DMA" placeholder. +/// +/// You may pass this in place of a real DMA channel when creating a driver +/// to indicate it should not use DMA. +/// +/// This often causes async functionality to not be available on the instance, +/// leaving only blocking functionality. +pub struct NoDma; + +impl_peripheral!(NoDma); + +// safety: must be called only once at startup +pub(crate) unsafe fn init( + cs: critical_section::CriticalSection, + #[cfg(bdma)] bdma_priority: interrupt::Priority, + #[cfg(dma)] dma_priority: interrupt::Priority, + #[cfg(gpdma)] gpdma_priority: interrupt::Priority, +) { + #[cfg(any(dma, bdma))] + dma_bdma::init( + cs, + #[cfg(dma)] + dma_priority, + #[cfg(bdma)] + bdma_priority, + ); + #[cfg(gpdma)] + gpdma::init(cs, gpdma_priority); + #[cfg(dmamux)] + dmamux::init(cs); +} diff --git a/embassy/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy/embassy-stm32/src/dma/ringbuffer/mod.rs new file mode 100644 index 0000000..44ea497 --- /dev/null +++ b/embassy/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -0,0 +1,333 @@ +#![cfg_attr(gpdma, allow(unused))] + +use core::future::poll_fn; +use core::task::{Poll, Waker}; + +use crate::dma::word::Word; + +pub trait DmaCtrl { + /// Get the NDTR register value, i.e. the space left in the underlying + /// buffer until the dma writer wraps. + fn get_remaining_transfers(&self) -> usize; + + /// Reset the transfer completed counter to 0 and return the value just prior to the reset. + fn reset_complete_count(&mut self) -> usize; + + /// Set the waker for a running poll_fn + fn set_waker(&mut self, waker: &Waker); +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + Overrun, + /// the newly read DMA positions don't make sense compared to the previous + /// ones. This can usually only occur due to wrong Driver implementation, if + /// the driver author (or the user using raw metapac code) directly resets + /// the channel for instance. + DmaUnsynced, +} + +#[derive(Debug, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct DmaIndex { + complete_count: usize, + pos: usize, +} + +impl DmaIndex { + fn reset(&mut self) { + self.pos = 0; + self.complete_count = 0; + } + + fn as_index(&self, cap: usize, offset: usize) -> usize { + (self.pos + offset) % cap + } + + fn dma_sync(&mut self, cap: usize, dma: &mut impl DmaCtrl) { + // Important! + // The ordering of the first two lines matters! + // If changed, the code will detect a wrong +capacity + // jump at wrap-around. + let count_diff = dma.reset_complete_count(); + let pos = cap - dma.get_remaining_transfers(); + self.pos = if pos < self.pos && count_diff == 0 { + cap - 1 + } else { + pos + }; + + self.complete_count += count_diff; + } + + fn advance(&mut self, cap: usize, steps: usize) { + let next = self.pos + steps; + self.complete_count += next / cap; + self.pos = next % cap; + } + + fn normalize(lhs: &mut DmaIndex, rhs: &mut DmaIndex) { + let min_count = lhs.complete_count.min(rhs.complete_count); + lhs.complete_count -= min_count; + rhs.complete_count -= min_count; + } + + fn diff(&self, cap: usize, rhs: &DmaIndex) -> isize { + (self.complete_count * cap + self.pos) as isize - (rhs.complete_count * cap + rhs.pos) as isize + } +} + +pub struct ReadableDmaRingBuffer<'a, W: Word> { + dma_buf: &'a mut [W], + write_index: DmaIndex, + read_index: DmaIndex, +} + +impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { + /// Construct an empty buffer. + pub fn new(dma_buf: &'a mut [W]) -> Self { + Self { + dma_buf, + write_index: Default::default(), + read_index: Default::default(), + } + } + + /// Reset the ring buffer to its initial state. + pub fn reset(&mut self, dma: &mut impl DmaCtrl) { + dma.reset_complete_count(); + self.write_index.reset(); + self.write_index.dma_sync(self.cap(), dma); + self.read_index = self.write_index; + } + + /// Get the full ringbuffer capacity. + pub const fn cap(&self) -> usize { + self.dma_buf.len() + } + + /// Get the available readable dma samples. + pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { + self.write_index.dma_sync(self.cap(), dma); + DmaIndex::normalize(&mut self.write_index, &mut self.read_index); + + let diff = self.write_index.diff(self.cap(), &self.read_index); + + if diff < 0 { + Err(Error::DmaUnsynced) + } else if diff > self.cap() as isize { + Err(Error::Overrun) + } else { + Ok(diff as usize) + } + } + + /// Read elements from the ring buffer. + /// + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the elements were read, then there will be some elements in the buffer remaining + /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read + /// Error is returned if the portion to be read was overwritten by the DMA controller, + /// in which case the rinbuffer will automatically reset itself. + pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), Error> { + self.read_raw(dma, buf).inspect_err(|_e| { + self.reset(dma); + }) + } + + /// Read an exact number of elements from the ringbuffer. + /// + /// Returns the remaining number of elements available for immediate reading. + /// Error is returned if the portion to be read was overwritten by the DMA controller. + /// + /// Async/Wake Behavior: + /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, + /// and when it wraps around. This means that when called with a buffer of length 'M', when this + /// ring buffer was created with a buffer of size 'N': + /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. + /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. + pub async fn read_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &mut [W]) -> Result { + let mut read_data = 0; + let buffer_len = buffer.len(); + + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + match self.read(dma, &mut buffer[read_data..buffer_len]) { + Ok((len, remaining)) => { + read_data += len; + if read_data == buffer_len { + Poll::Ready(Ok(remaining)) + } else { + Poll::Pending + } + } + Err(e) => Poll::Ready(Err(e)), + } + }) + .await + } + + fn read_raw(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), Error> { + let readable = self.len(dma)?.min(buf.len()); + for i in 0..readable { + buf[i] = self.read_buf(i); + } + let available = self.len(dma)?; + self.read_index.advance(self.cap(), readable); + Ok((readable, available - readable)) + } + + fn read_buf(&self, offset: usize) -> W { + unsafe { + core::ptr::read_volatile( + self.dma_buf + .as_ptr() + .offset(self.read_index.as_index(self.cap(), offset) as isize), + ) + } + } +} + +pub struct WritableDmaRingBuffer<'a, W: Word> { + dma_buf: &'a mut [W], + read_index: DmaIndex, + write_index: DmaIndex, +} + +impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { + /// Construct a ringbuffer filled with the given buffer data. + pub fn new(dma_buf: &'a mut [W]) -> Self { + let len = dma_buf.len(); + Self { + dma_buf, + read_index: Default::default(), + write_index: DmaIndex { + complete_count: 0, + pos: len, + }, + } + } + + /// Reset the ring buffer to its initial state. The buffer after the reset will be full. + pub fn reset(&mut self, dma: &mut impl DmaCtrl) { + dma.reset_complete_count(); + self.read_index.reset(); + self.read_index.dma_sync(self.cap(), dma); + self.write_index = self.read_index; + self.write_index.advance(self.cap(), self.cap()); + } + + /// Get the remaining writable dma samples. + pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { + self.read_index.dma_sync(self.cap(), dma); + DmaIndex::normalize(&mut self.read_index, &mut self.write_index); + + let diff = self.write_index.diff(self.cap(), &self.read_index); + + if diff < 0 { + Err(Error::Overrun) + } else if diff > self.cap() as isize { + Err(Error::DmaUnsynced) + } else { + Ok(self.cap().saturating_sub(diff as usize)) + } + } + + /// Get the full ringbuffer capacity. + pub const fn cap(&self) -> usize { + self.dma_buf.len() + } + + /// Append data to the ring buffer. + /// Returns a tuple of the data written and the remaining write capacity in the buffer. + /// Error is returned if the portion to be written was previously read by the DMA controller. + /// In this case, the ringbuffer will automatically reset itself, giving a full buffer worth of + /// leeway between the write index and the DMA. + pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), Error> { + self.write_raw(dma, buf).inspect_err(|_e| { + self.reset(dma); + }) + } + + /// Write elements directly to the buffer. + /// + /// Subsequent writes will overwrite the content of the buffer, so it is not useful to call this more than once. + /// Data is aligned towards the end of the buffer. + /// + /// In case of success, returns the written length, and the empty space in front of the written block. + /// Fails if the data to write exceeds the buffer capacity. + pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { + if buf.len() > self.cap() { + return Err(Error::Overrun); + } + + let start = self.cap() - buf.len(); + for (i, data) in buf.iter().enumerate() { + self.write_buf(start + i, *data) + } + let written = buf.len().min(self.cap()); + Ok((written, self.cap() - written)) + } + + /// Wait for any ring buffer write error. + pub async fn wait_write_error(&mut self, dma: &mut impl DmaCtrl) -> Result { + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + match self.len(dma) { + Ok(_) => Poll::Pending, + Err(e) => Poll::Ready(Err(e)), + } + }) + .await + } + + /// Write an exact number of elements to the ringbuffer. + pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result { + let mut written_data = 0; + let buffer_len = buffer.len(); + + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + match self.write(dma, &buffer[written_data..buffer_len]) { + Ok((len, remaining)) => { + written_data += len; + if written_data == buffer_len { + Poll::Ready(Ok(remaining)) + } else { + Poll::Pending + } + } + Err(e) => Poll::Ready(Err(e)), + } + }) + .await + } + + fn write_raw(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), Error> { + let writable = self.len(dma)?.min(buf.len()); + for i in 0..writable { + self.write_buf(i, buf[i]); + } + let available = self.len(dma)?; + self.write_index.advance(self.cap(), writable); + Ok((writable, available - writable)) + } + + fn write_buf(&mut self, offset: usize, value: W) { + unsafe { + core::ptr::write_volatile( + self.dma_buf + .as_mut_ptr() + .offset(self.write_index.as_index(self.cap(), offset) as isize), + value, + ) + } + } +} + +#[cfg(test)] +mod tests; diff --git a/embassy/embassy-stm32/src/dma/ringbuffer/tests/mod.rs b/embassy/embassy-stm32/src/dma/ringbuffer/tests/mod.rs new file mode 100644 index 0000000..6fabedb --- /dev/null +++ b/embassy/embassy-stm32/src/dma/ringbuffer/tests/mod.rs @@ -0,0 +1,90 @@ +use std::{cell, vec}; + +use super::*; + +#[allow(unused)] +#[derive(PartialEq, Debug)] +enum TestCircularTransferRequest { + ResetCompleteCount(usize), + PositionRequest(usize), +} + +#[allow(unused)] +struct TestCircularTransfer { + len: usize, + requests: cell::RefCell>, +} + +impl DmaCtrl for TestCircularTransfer { + fn get_remaining_transfers(&self) -> usize { + match self.requests.borrow_mut().pop().unwrap() { + TestCircularTransferRequest::PositionRequest(pos) => { + let len = self.len; + + assert!(len >= pos); + + len - pos + } + _ => unreachable!(), + } + } + + fn reset_complete_count(&mut self) -> usize { + match self.requests.get_mut().pop().unwrap() { + TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count, + _ => unreachable!(), + } + } + + fn set_waker(&mut self, _waker: &Waker) {} +} + +impl TestCircularTransfer { + #[allow(unused)] + pub fn new(len: usize) -> Self { + Self { + requests: cell::RefCell::new(vec![]), + len, + } + } + + #[allow(unused)] + pub fn setup(&self, mut requests: vec::Vec) { + requests.reverse(); + self.requests.replace(requests); + } +} + +const CAP: usize = 16; + +#[test] +fn dma_index_as_index_returns_index_mod_cap_by_default() { + let index = DmaIndex::default(); + assert_eq!(index.as_index(CAP, 0), 0); + assert_eq!(index.as_index(CAP, 1), 1); + assert_eq!(index.as_index(CAP, 2), 2); + assert_eq!(index.as_index(CAP, 3), 3); + assert_eq!(index.as_index(CAP, 4), 4); + assert_eq!(index.as_index(CAP, CAP), 0); + assert_eq!(index.as_index(CAP, CAP + 1), 1); +} + +#[test] +fn dma_index_advancing_increases_as_index() { + let mut index = DmaIndex::default(); + assert_eq!(index.as_index(CAP, 0), 0); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 1); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 2); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 3); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 4); + index.advance(CAP, CAP - 4); + assert_eq!(index.as_index(CAP, 0), 0); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 1); +} + +mod prop_test; diff --git a/embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs b/embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs new file mode 100644 index 0000000..661fb17 --- /dev/null +++ b/embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs @@ -0,0 +1,50 @@ +use std::task::Waker; + +use proptest::prop_oneof; +use proptest::strategy::{self, BoxedStrategy, Strategy as _}; +use proptest_state_machine::{prop_state_machine, ReferenceStateMachine, StateMachineTest}; + +use super::*; + +const CAP: usize = 128; + +#[derive(Debug, Default)] +struct DmaMock { + pos: usize, + wraps: usize, +} + +impl DmaMock { + pub fn advance(&mut self, steps: usize) { + let next = self.pos + steps; + self.pos = next % CAP; + self.wraps += next / CAP; + } +} + +impl DmaCtrl for DmaMock { + fn get_remaining_transfers(&self) -> usize { + CAP - self.pos + } + + fn reset_complete_count(&mut self) -> usize { + core::mem::replace(&mut self.wraps, 0) + } + + fn set_waker(&mut self, _waker: &Waker) {} +} + +#[derive(Debug, Clone)] +enum Status { + Available(usize), + Failed, +} + +impl Status { + pub fn new(capacity: usize) -> Self { + Self::Available(capacity) + } +} + +mod reader; +mod writer; diff --git a/embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs b/embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs new file mode 100644 index 0000000..4f3957a --- /dev/null +++ b/embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs @@ -0,0 +1,123 @@ +use core::fmt::Debug; + +use super::*; + +#[derive(Debug, Clone)] +enum ReaderTransition { + Write(usize), + Reset, + ReadUpTo(usize), +} + +struct ReaderSM; + +impl ReferenceStateMachine for ReaderSM { + type State = Status; + type Transition = ReaderTransition; + + fn init_state() -> BoxedStrategy { + strategy::Just(Status::new(0)).boxed() + } + + fn transitions(_state: &Self::State) -> BoxedStrategy { + prop_oneof![ + (1..50_usize).prop_map(ReaderTransition::Write), + (1..50_usize).prop_map(ReaderTransition::ReadUpTo), + strategy::Just(ReaderTransition::Reset), + ] + .boxed() + } + + fn apply(status: Self::State, transition: &Self::Transition) -> Self::State { + match (status, transition) { + (_, ReaderTransition::Reset) => Status::Available(0), + (Status::Available(x), ReaderTransition::Write(y)) => { + if x + y > CAP { + Status::Failed + } else { + Status::Available(x + y) + } + } + (Status::Failed, ReaderTransition::Write(_)) => Status::Failed, + (Status::Available(x), ReaderTransition::ReadUpTo(y)) => Status::Available(x.saturating_sub(*y)), + (Status::Failed, ReaderTransition::ReadUpTo(_)) => Status::Available(0), + } + } +} + +struct ReaderSut { + status: Status, + buffer: *mut [u8], + producer: DmaMock, + consumer: ReadableDmaRingBuffer<'static, u8>, +} + +impl Debug for ReaderSut { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + ::fmt(&self.producer, f) + } +} + +struct ReaderTest; + +impl StateMachineTest for ReaderTest { + type SystemUnderTest = ReaderSut; + type Reference = ReaderSM; + + fn init_test(ref_status: &::State) -> Self::SystemUnderTest { + let buffer = Box::into_raw(Box::new([0; CAP])); + ReaderSut { + status: ref_status.clone(), + buffer, + producer: DmaMock::default(), + consumer: ReadableDmaRingBuffer::new(unsafe { &mut *buffer }), + } + } + + fn teardown(state: Self::SystemUnderTest) { + unsafe { + let _ = Box::from_raw(state.buffer); + }; + } + + fn apply( + mut sut: Self::SystemUnderTest, + ref_state: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { + match transition { + ReaderTransition::Write(x) => sut.producer.advance(x), + ReaderTransition::Reset => { + sut.consumer.reset(&mut sut.producer); + } + ReaderTransition::ReadUpTo(x) => { + let status = sut.status; + let ReaderSut { + ref mut producer, + ref mut consumer, + .. + } = sut; + let mut buf = vec![0; x]; + let res = consumer.read(producer, &mut buf); + match status { + Status::Available(n) => { + let readable = x.min(n); + + assert_eq!(res.unwrap().0, readable); + } + Status::Failed => assert!(res.is_err()), + } + } + } + + ReaderSut { + status: ref_state.clone(), + ..sut + } + } +} + +prop_state_machine! { + #[test] + fn reader_state_test(sequential 1..20 => ReaderTest); +} diff --git a/embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs b/embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs new file mode 100644 index 0000000..15433c0 --- /dev/null +++ b/embassy/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs @@ -0,0 +1,122 @@ +use core::fmt::Debug; + +use super::*; + +#[derive(Debug, Clone)] +enum WriterTransition { + Read(usize), + WriteUpTo(usize), + Reset, +} + +struct WriterSM; + +impl ReferenceStateMachine for WriterSM { + type State = Status; + type Transition = WriterTransition; + + fn init_state() -> BoxedStrategy { + strategy::Just(Status::new(CAP)).boxed() + } + + fn transitions(_state: &Self::State) -> BoxedStrategy { + prop_oneof![ + (1..50_usize).prop_map(WriterTransition::Read), + (1..50_usize).prop_map(WriterTransition::WriteUpTo), + strategy::Just(WriterTransition::Reset), + ] + .boxed() + } + + fn apply(status: Self::State, transition: &Self::Transition) -> Self::State { + match (status, transition) { + (_, WriterTransition::Reset) => Status::Available(CAP), + (Status::Available(x), WriterTransition::Read(y)) => { + if x < *y { + Status::Failed + } else { + Status::Available(x - y) + } + } + (Status::Failed, WriterTransition::Read(_)) => Status::Failed, + (Status::Available(x), WriterTransition::WriteUpTo(y)) => Status::Available((x + *y).min(CAP)), + (Status::Failed, WriterTransition::WriteUpTo(_)) => Status::Available(CAP), + } + } +} + +struct WriterSut { + status: Status, + buffer: *mut [u8], + producer: WritableDmaRingBuffer<'static, u8>, + consumer: DmaMock, +} + +impl Debug for WriterSut { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + ::fmt(&self.consumer, f) + } +} + +struct WriterTest; + +impl StateMachineTest for WriterTest { + type SystemUnderTest = WriterSut; + type Reference = WriterSM; + + fn init_test(ref_status: &::State) -> Self::SystemUnderTest { + let buffer = Box::into_raw(Box::new([0; CAP])); + WriterSut { + status: ref_status.clone(), + buffer, + producer: WritableDmaRingBuffer::new(unsafe { &mut *buffer }), + consumer: DmaMock::default(), + } + } + + fn teardown(state: Self::SystemUnderTest) { + unsafe { + let _ = Box::from_raw(state.buffer); + }; + } + + fn apply( + mut sut: Self::SystemUnderTest, + ref_status: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { + match transition { + WriterTransition::Read(x) => sut.consumer.advance(x), + WriterTransition::Reset => { + sut.producer.reset(&mut sut.consumer); + } + WriterTransition::WriteUpTo(x) => { + let status = sut.status; + let WriterSut { + ref mut producer, + ref mut consumer, + .. + } = sut; + let mut buf = vec![0; x]; + let res = producer.write(consumer, &mut buf); + match status { + Status::Available(n) => { + let writable = x.min(CAP - n.min(CAP)); + assert_eq!(res.unwrap().0, writable); + } + Status::Failed => assert!(res.is_err()), + } + } + } + + WriterSut { + status: ref_status.clone(), + ..sut + } + } +} + +prop_state_machine! { + #[test] + fn writer_state_test(sequential 1..20 => WriterTest); +} diff --git a/embassy/embassy-stm32/src/dma/util.rs b/embassy/embassy-stm32/src/dma/util.rs new file mode 100644 index 0000000..5aaca57 --- /dev/null +++ b/embassy/embassy-stm32/src/dma/util.rs @@ -0,0 +1,61 @@ +use embassy_hal_internal::PeripheralRef; + +use super::word::Word; +use super::{AnyChannel, Request, Transfer, TransferOptions}; + +/// Convenience wrapper, contains a channel and a request number. +/// +/// Commonly used in peripheral drivers that own DMA channels. +pub(crate) struct ChannelAndRequest<'d> { + pub channel: PeripheralRef<'d, AnyChannel>, + pub request: Request, +} + +impl<'d> ChannelAndRequest<'d> { + pub unsafe fn read<'a, W: Word>( + &'a mut self, + peri_addr: *mut W, + buf: &'a mut [W], + options: TransferOptions, + ) -> Transfer<'a> { + Transfer::new_read(&mut self.channel, self.request, peri_addr, buf, options) + } + + pub unsafe fn read_raw<'a, W: Word>( + &'a mut self, + peri_addr: *mut W, + buf: *mut [W], + options: TransferOptions, + ) -> Transfer<'a> { + Transfer::new_read_raw(&mut self.channel, self.request, peri_addr, buf, options) + } + + pub unsafe fn write<'a, W: Word>( + &'a mut self, + buf: &'a [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Transfer<'a> { + Transfer::new_write(&mut self.channel, self.request, buf, peri_addr, options) + } + + pub unsafe fn write_raw<'a, W: Word>( + &'a mut self, + buf: *const [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Transfer<'a> { + Transfer::new_write_raw(&mut self.channel, self.request, buf, peri_addr, options) + } + + #[allow(dead_code)] + pub unsafe fn write_repeated<'a, W: Word>( + &'a mut self, + repeated: &'a W, + count: usize, + peri_addr: *mut W, + options: TransferOptions, + ) -> Transfer<'a> { + Transfer::new_write_repeated(&mut self.channel, self.request, repeated, count, peri_addr, options) + } +} diff --git a/embassy/embassy-stm32/src/dma/word.rs b/embassy/embassy-stm32/src/dma/word.rs new file mode 100644 index 0000000..fb1bde8 --- /dev/null +++ b/embassy/embassy-stm32/src/dma/word.rs @@ -0,0 +1,88 @@ +//! DMA word sizes. + +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WordSize { + OneByte, + TwoBytes, + FourBytes, +} + +impl WordSize { + /// Amount of bytes of this word size. + pub fn bytes(&self) -> usize { + match self { + Self::OneByte => 1, + Self::TwoBytes => 2, + Self::FourBytes => 4, + } + } +} + +trait SealedWord {} + +/// DMA word trait. +/// +/// This is implemented for u8, u16, u32, etc. +#[allow(private_bounds)] +pub trait Word: SealedWord + Default + Copy + 'static { + /// Word size + fn size() -> WordSize; + /// Amount of bits of this word size. + fn bits() -> usize; +} + +macro_rules! impl_word { + (_, $T:ident, $bits:literal, $size:ident) => { + impl SealedWord for $T {} + impl Word for $T { + fn bits() -> usize { + $bits + } + fn size() -> WordSize { + WordSize::$size + } + } + }; + ($T:ident, $uX:ident, $bits:literal, $size:ident) => { + #[repr(transparent)] + #[derive(Copy, Clone, Default)] + #[doc = concat!(stringify!($T), " word size")] + pub struct $T(pub $uX); + impl_word!(_, $T, $bits, $size); + }; +} + +impl_word!(U1, u8, 1, OneByte); +impl_word!(U2, u8, 2, OneByte); +impl_word!(U3, u8, 3, OneByte); +impl_word!(U4, u8, 4, OneByte); +impl_word!(U5, u8, 5, OneByte); +impl_word!(U6, u8, 6, OneByte); +impl_word!(U7, u8, 7, OneByte); +impl_word!(_, u8, 8, OneByte); +impl_word!(U9, u16, 9, TwoBytes); +impl_word!(U10, u16, 10, TwoBytes); +impl_word!(U11, u16, 11, TwoBytes); +impl_word!(U12, u16, 12, TwoBytes); +impl_word!(U13, u16, 13, TwoBytes); +impl_word!(U14, u16, 14, TwoBytes); +impl_word!(U15, u16, 15, TwoBytes); +impl_word!(_, u16, 16, TwoBytes); +impl_word!(U17, u32, 17, FourBytes); +impl_word!(U18, u32, 18, FourBytes); +impl_word!(U19, u32, 19, FourBytes); +impl_word!(U20, u32, 20, FourBytes); +impl_word!(U21, u32, 21, FourBytes); +impl_word!(U22, u32, 22, FourBytes); +impl_word!(U23, u32, 23, FourBytes); +impl_word!(U24, u32, 24, FourBytes); +impl_word!(U25, u32, 25, FourBytes); +impl_word!(U26, u32, 26, FourBytes); +impl_word!(U27, u32, 27, FourBytes); +impl_word!(U28, u32, 28, FourBytes); +impl_word!(U29, u32, 29, FourBytes); +impl_word!(U30, u32, 30, FourBytes); +impl_word!(U31, u32, 31, FourBytes); +impl_word!(_, u32, 32, FourBytes); diff --git a/embassy/embassy-stm32/src/dsihost.rs b/embassy/embassy-stm32/src/dsihost.rs new file mode 100644 index 0000000..77c3d95 --- /dev/null +++ b/embassy/embassy-stm32/src/dsihost.rs @@ -0,0 +1,429 @@ +//! DSI HOST + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +//use crate::gpio::{AnyPin, SealedPin}; +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::rcc::{self, RccPeripheral}; +use crate::{peripherals, Peripheral}; + +/// Performs a busy-wait delay for a specified number of microseconds. +pub fn blocking_delay_ms(ms: u32) { + #[cfg(feature = "time")] + embassy_time::block_for(embassy_time::Duration::from_millis(ms as u64)); + #[cfg(not(feature = "time"))] + cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 1_000 * ms); +} + +/// PacketTypes extracted from CubeMX +#[repr(u8)] +#[allow(dead_code)] +pub enum PacketType { + /// DCS short write, no parameters + DcsShortPktWriteP0, + /// DCS short write, one parameter + DcsShortPktWriteP1, + /// Generic short write, no parameters + GenShortPktWriteP0, + /// Generic short write, one parameter + GenShortPktWriteP1, + /// Generic short write, two parameters + GenShortPktWriteP2, + /// DCS long write + DcsLongPktWrite, + /// Generic long write + GenLongPktWrite, + /// DCS short read + DcsShortPktRead(u8), + /// Generic short read, no parameters + GenShortPktReadP0, + /// Generic short read, one parameter + GenShortPktReadP1(u8), + /// Generic short read, two parameters + GenShortPktReadP2(u8, u8), + /// Used to set the maximum return packet size for reading data + MaxReturnPktSize, +} + +impl From for u8 { + fn from(packet_type: PacketType) -> u8 { + match packet_type { + PacketType::DcsShortPktWriteP0 => 0x05, + PacketType::DcsShortPktWriteP1 => 0x15, + PacketType::GenShortPktWriteP0 => 0x03, + PacketType::GenShortPktWriteP1 => 0x13, + PacketType::GenShortPktWriteP2 => 0x23, + PacketType::DcsLongPktWrite => 0x39, + PacketType::GenLongPktWrite => 0x29, + PacketType::DcsShortPktRead(_) => 0x06, + PacketType::GenShortPktReadP0 => 0x04, + PacketType::GenShortPktReadP1(_) => 0x14, + PacketType::GenShortPktReadP2(_, _) => 0x24, + PacketType::MaxReturnPktSize => 0x37, + } + } +} + +/// DSIHOST driver. +pub struct DsiHost<'d, T: Instance> { + _peri: PhantomData<&'d mut T>, + _te: PeripheralRef<'d, AnyPin>, +} + +impl<'d, T: Instance> DsiHost<'d, T> { + /// Note: Full-Duplex modes are not supported at this time + pub fn new(_peri: impl Peripheral

+ 'd, te: impl Peripheral

> + 'd) -> Self { + into_ref!(te); + + rcc::enable_and_reset::(); + + // Set Tearing Enable pin according to CubeMx example + te.set_as_af(te.af_num(), AfType::output(OutputType::PushPull, Speed::Low)); + /* + T::regs().wcr().modify(|w| { + w.set_dsien(true); + }); + */ + Self { + _peri: PhantomData, + _te: te.map_into(), + } + } + + /// Get the DSIHOST hardware version. Found in the reference manual for comparison. + pub fn get_version(&self) -> u32 { + T::regs().vr().read().version() + } + + /// Set the enable bit in the control register and assert that it has been enabled + pub fn enable(&mut self) { + T::regs().cr().modify(|w| w.set_en(true)); + assert!(T::regs().cr().read().en()) + } + + /// Unset the enable bit in the control register and assert that it has been disabled + pub fn disable(&mut self) { + T::regs().cr().modify(|w| w.set_en(false)); + assert!(!T::regs().cr().read().en()) + } + + /// Set the DSI enable bit in the wrapper control register and assert that it has been enabled + pub fn enable_wrapper_dsi(&mut self) { + T::regs().wcr().modify(|w| w.set_dsien(true)); + assert!(T::regs().wcr().read().dsien()) + } + + /// Unset the DSI enable bit in the wrapper control register and assert that it has been disabled + pub fn disable_wrapper_dsi(&mut self) { + T::regs().wcr().modify(|w| w.set_dsien(false)); + assert!(!T::regs().wcr().read().dsien()) + } + + /// DCS or Generic short/long write command + pub fn write_cmd(&mut self, channel_id: u8, address: u8, data: &[u8]) -> Result<(), Error> { + assert!(data.len() > 0); + + if data.len() == 1 { + self.short_write(channel_id, PacketType::DcsShortPktWriteP1, address, data[0]) + } else { + self.long_write( + channel_id, + PacketType::DcsLongPktWrite, // FIXME: This might be a generic long packet, as well... + address, + data, + ) + } + } + + fn short_write(&mut self, channel_id: u8, packet_type: PacketType, param1: u8, param2: u8) -> Result<(), Error> { + #[cfg(feature = "defmt")] + defmt::debug!("short_write: BEGIN wait for command fifo empty"); + + // Wait for Command FIFO empty + self.wait_command_fifo_empty()?; + #[cfg(feature = "defmt")] + defmt::debug!("short_write: END wait for command fifo empty"); + + // Configure the packet to send a short DCS command with 0 or 1 parameters + // Update the DSI packet header with new information + self.config_packet_header(channel_id, packet_type, param1, param2); + + self.wait_command_fifo_empty()?; + + let status = T::regs().isr1().read().0; + if status != 0 { + error!("ISR1 after short_write(): {:b}", status); + } + Ok(()) + } + + fn config_packet_header(&mut self, channel_id: u8, packet_type: PacketType, param1: u8, param2: u8) { + T::regs().ghcr().write(|w| { + w.set_dt(packet_type.into()); + w.set_vcid(channel_id); + w.set_wclsb(param1); + w.set_wcmsb(param2); + }); + } + + /// Write long DCS or long Generic command. + /// + /// `params` is expected to contain at least 2 elements. Use [`short_write`] for a single element. + fn long_write(&mut self, channel_id: u8, packet_type: PacketType, address: u8, data: &[u8]) -> Result<(), Error> { + // Must be a long packet if we do the long write, obviously. + assert!(matches!( + packet_type, + PacketType::DcsLongPktWrite | PacketType::GenLongPktWrite + )); + + // params needs to have at least 2 elements, otherwise short_write should be used + assert!(data.len() >= 2); + + #[cfg(feature = "defmt")] + defmt::debug!("long_write: BEGIN wait for command fifo empty"); + + self.wait_command_fifo_empty()?; + + #[cfg(feature = "defmt")] + defmt::debug!("long_write: DONE wait for command fifo empty"); + + // Note: CubeMX example "NbParams" is always one LESS than params.len() + // DCS code (last element of params) must be on payload byte 1 and if we have only 2 more params, + // then they must go into data2 and data3 + T::regs().gpdr().write(|w| { + // data[2] may or may not exist. + if let Some(x) = data.get(2) { + w.set_data4(*x); + } + // data[0] and [1] have to exist if long_write is called. + w.set_data3(data[1]); + w.set_data2(data[0]); + + // DCS Code + w.set_data1(address); + }); + + self.wait_command_fifo_empty()?; + + // These steps are only necessary if more than 1x 4 bytes need to go into the FIFO + if data.len() >= 4 { + // Generate an iterator that iterates over chunks of exactly 4 bytes + let iter = data[3..data.len()].chunks_exact(4); + // Obtain remainder before consuming iter + let remainder = iter.remainder(); + + // Keep filling the buffer with remaining data + for param in iter { + self.wait_command_fifo_not_full()?; + T::regs().gpdr().write(|w| { + w.set_data4(param[3]); + w.set_data3(param[2]); + w.set_data2(param[1]); + w.set_data1(param[0]); + }); + + self.wait_command_fifo_empty().unwrap(); + } + + // If the remaining data was not devisible by 4 we get a remainder + if remainder.len() >= 1 { + self.wait_command_fifo_not_full()?; + T::regs().gpdr().write(|w| { + if let Some(x) = remainder.get(2) { + w.set_data3(*x); + } + if let Some(x) = remainder.get(1) { + w.set_data2(*x); + } + w.set_data1(remainder[0]); + }); + self.wait_command_fifo_empty().unwrap(); + } + } + // Configure the packet to send a long DCS command + self.config_packet_header( + channel_id, + packet_type, + ((data.len() + 1) & 0x00FF) as u8, // +1 to account for address byte + (((data.len() + 1) & 0xFF00) >> 8) as u8, // +1 to account for address byte + ); + + self.wait_command_fifo_empty()?; + + let status = T::regs().isr1().read().0; + if status != 0 { + error!("ISR1 after long_write(): {:b}", status); + } + Ok(()) + } + + /// Read DSI Register + pub fn read( + &mut self, + channel_id: u8, + packet_type: PacketType, + read_size: u16, + data: &mut [u8], + ) -> Result<(), Error> { + if data.len() != read_size as usize { + return Err(Error::InvalidReadSize); + } + + // Set the maximum return packet size + self.short_write( + channel_id, + PacketType::MaxReturnPktSize, + (read_size & 0xFF) as u8, + ((read_size & 0xFF00) >> 8) as u8, + )?; + + // Set the packet header according to the packet_type + use PacketType::*; + match packet_type { + DcsShortPktRead(cmd) => self.config_packet_header(channel_id, packet_type, cmd, 0), + GenShortPktReadP0 => self.config_packet_header(channel_id, packet_type, 0, 0), + GenShortPktReadP1(param1) => self.config_packet_header(channel_id, packet_type, param1, 0), + GenShortPktReadP2(param1, param2) => self.config_packet_header(channel_id, packet_type, param1, param2), + _ => return Err(Error::InvalidPacketType), + } + + self.wait_read_not_busy()?; + + // Obtain chunks of 32-bit so the entire FIFO data register can be read + for bytes in data.chunks_exact_mut(4) { + self.wait_payload_read_fifo_not_empty()?; + + // Only perform a single read on the entire register to avoid unintended side-effects + let gpdr = T::regs().gpdr().read(); + bytes[0] = gpdr.data1(); + bytes[1] = gpdr.data2(); + bytes[2] = gpdr.data3(); + bytes[3] = gpdr.data4(); + } + + // Collect the remaining chunks and read the corresponding number of bytes from the FIFO + let remainder = data.chunks_exact_mut(4).into_remainder(); + if !remainder.is_empty() { + self.wait_payload_read_fifo_not_empty()?; + // Only perform a single read on the entire register to avoid unintended side-effects + let gpdr = T::regs().gpdr().read(); + if let Some(x) = remainder.get_mut(0) { + *x = gpdr.data1() + } + if let Some(x) = remainder.get_mut(1) { + *x = gpdr.data2() + } + if let Some(x) = remainder.get_mut(2) { + *x = gpdr.data3() + } + } + + /* + // Used this to check whether there are read errors. Does not seem like it. + if !self.read_busy() { + defmt::debug!("Read not busy!"); + if self.packet_size_error() { + return Err(Error::ReadError); + } + } + */ + Ok(()) + } + + fn wait_command_fifo_empty(&self) -> Result<(), Error> { + for _ in 1..1000 { + // Wait for Command FIFO empty + if T::regs().gpsr().read().cmdfe() { + return Ok(()); + } + blocking_delay_ms(1); + } + Err(Error::FifoTimeout) + } + + fn wait_command_fifo_not_full(&self) -> Result<(), Error> { + for _ in 1..1000 { + // Wait for Command FIFO not empty + if !T::regs().gpsr().read().cmdff() { + return Ok(()); + } + blocking_delay_ms(1); + } + Err(Error::FifoTimeout) + } + + fn wait_read_not_busy(&self) -> Result<(), Error> { + for _ in 1..1000 { + // Wait for read not busy + if !self.read_busy() { + return Ok(()); + } + blocking_delay_ms(1); + } + Err(Error::ReadTimeout) + } + + fn wait_payload_read_fifo_not_empty(&self) -> Result<(), Error> { + for _ in 1..1000 { + // Wait for payload read FIFO not empty + if !T::regs().gpsr().read().prdfe() { + return Ok(()); + } + blocking_delay_ms(1); + } + Err(Error::FifoTimeout) + } + + fn _packet_size_error(&self) -> bool { + T::regs().isr1().read().pse() + } + + fn read_busy(&self) -> bool { + T::regs().gpsr().read().rcb() + } +} + +/// Possible Error Types for DSI HOST +#[non_exhaustive] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Waiting for FIFO empty flag timed out + FifoTimeout, + /// The specified `PacketType` is invalid for the selected operation + InvalidPacketType, + /// Provided read size does not match the read buffer length + InvalidReadSize, + /// Error during read + ReadError, + /// Read operation timed out + ReadTimeout, +} + +impl<'d, T: Instance> Drop for DsiHost<'d, T> { + fn drop(&mut self) {} +} + +trait SealedInstance: crate::rcc::SealedRccPeripheral { + fn regs() -> crate::pac::dsihost::Dsihost; +} + +/// DSI instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral + 'static {} + +pin_trait!(TePin, Instance); + +foreach_peripheral!( + (dsihost, $inst:ident) => { + impl crate::dsihost::SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::dsihost::Dsihost { + crate::pac::$inst + } + } + + impl crate::dsihost::Instance for peripherals::$inst {} + }; +); diff --git a/embassy/embassy-stm32/src/eth/generic_smi.rs b/embassy/embassy-stm32/src/eth/generic_smi.rs new file mode 100644 index 0000000..3b43051 --- /dev/null +++ b/embassy/embassy-stm32/src/eth/generic_smi.rs @@ -0,0 +1,119 @@ +//! Generic SMI Ethernet PHY + +use core::task::Context; + +#[cfg(feature = "time")] +use embassy_time::{Duration, Timer}; +#[cfg(feature = "time")] +use futures_util::FutureExt; + +use super::{StationManagement, PHY}; + +#[allow(dead_code)] +mod phy_consts { + pub const PHY_REG_BCR: u8 = 0x00; + pub const PHY_REG_BSR: u8 = 0x01; + pub const PHY_REG_ID1: u8 = 0x02; + pub const PHY_REG_ID2: u8 = 0x03; + pub const PHY_REG_ANTX: u8 = 0x04; + pub const PHY_REG_ANRX: u8 = 0x05; + pub const PHY_REG_ANEXP: u8 = 0x06; + pub const PHY_REG_ANNPTX: u8 = 0x07; + pub const PHY_REG_ANNPRX: u8 = 0x08; + pub const PHY_REG_CTL: u8 = 0x0D; // Ethernet PHY Register Control + pub const PHY_REG_ADDAR: u8 = 0x0E; // Ethernet PHY Address or Data + + pub const PHY_REG_WUCSR: u16 = 0x8010; + + pub const PHY_REG_BCR_COLTEST: u16 = 1 << 7; + pub const PHY_REG_BCR_FD: u16 = 1 << 8; + pub const PHY_REG_BCR_ANRST: u16 = 1 << 9; + pub const PHY_REG_BCR_ISOLATE: u16 = 1 << 10; + pub const PHY_REG_BCR_POWERDN: u16 = 1 << 11; + pub const PHY_REG_BCR_AN: u16 = 1 << 12; + pub const PHY_REG_BCR_100M: u16 = 1 << 13; + pub const PHY_REG_BCR_LOOPBACK: u16 = 1 << 14; + pub const PHY_REG_BCR_RESET: u16 = 1 << 15; + + pub const PHY_REG_BSR_JABBER: u16 = 1 << 1; + pub const PHY_REG_BSR_UP: u16 = 1 << 2; + pub const PHY_REG_BSR_FAULT: u16 = 1 << 4; + pub const PHY_REG_BSR_ANDONE: u16 = 1 << 5; +} +use self::phy_consts::*; + +/// Generic SMI Ethernet PHY implementation +pub struct GenericSMI { + phy_addr: u8, + #[cfg(feature = "time")] + poll_interval: Duration, +} + +impl GenericSMI { + /// Construct the PHY. It assumes the address `phy_addr` in the SMI communication + pub fn new(phy_addr: u8) -> Self { + Self { + phy_addr, + #[cfg(feature = "time")] + poll_interval: Duration::from_millis(500), + } + } +} + +unsafe impl PHY for GenericSMI { + fn phy_reset(&mut self, sm: &mut S) { + sm.smi_write(self.phy_addr, PHY_REG_BCR, PHY_REG_BCR_RESET); + while sm.smi_read(self.phy_addr, PHY_REG_BCR) & PHY_REG_BCR_RESET == PHY_REG_BCR_RESET {} + } + + fn phy_init(&mut self, sm: &mut S) { + // Clear WU CSR + self.smi_write_ext(sm, PHY_REG_WUCSR, 0); + + // Enable auto-negotiation + sm.smi_write( + self.phy_addr, + PHY_REG_BCR, + PHY_REG_BCR_AN | PHY_REG_BCR_ANRST | PHY_REG_BCR_100M, + ); + } + + fn poll_link(&mut self, sm: &mut S, cx: &mut Context) -> bool { + #[cfg(not(feature = "time"))] + cx.waker().wake_by_ref(); + + #[cfg(feature = "time")] + let _ = Timer::after(self.poll_interval).poll_unpin(cx); + + let bsr = sm.smi_read(self.phy_addr, PHY_REG_BSR); + + // No link without autonegotiate + if bsr & PHY_REG_BSR_ANDONE == 0 { + return false; + } + // No link if link is down + if bsr & PHY_REG_BSR_UP == 0 { + return false; + } + + // Got link + true + } +} + +/// Public functions for the PHY +impl GenericSMI { + /// Set the SMI polling interval. + #[cfg(feature = "time")] + pub fn set_poll_interval(&mut self, poll_interval: Duration) { + self.poll_interval = poll_interval + } + + // Writes a value to an extended PHY register in MMD address space + fn smi_write_ext(&mut self, sm: &mut S, reg_addr: u16, reg_data: u16) { + sm.smi_write(self.phy_addr, PHY_REG_CTL, 0x0003); // set address + sm.smi_write(self.phy_addr, PHY_REG_ADDAR, reg_addr); + sm.smi_write(self.phy_addr, PHY_REG_CTL, 0x4003); // set data + sm.smi_write(self.phy_addr, PHY_REG_ADDAR, reg_data); + } +} diff --git a/embassy/embassy-stm32/src/eth/mod.rs b/embassy/embassy-stm32/src/eth/mod.rs new file mode 100644 index 0000000..773452b --- /dev/null +++ b/embassy/embassy-stm32/src/eth/mod.rs @@ -0,0 +1,228 @@ +//! Ethernet (ETH) +#![macro_use] + +#[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; + +use core::mem::MaybeUninit; +use core::task::Context; + +use embassy_net_driver::{Capabilities, HardwareAddress, LinkState}; +use embassy_sync::waitqueue::AtomicWaker; + +pub use self::_version::{InterruptHandler, *}; +use crate::rcc::RccPeripheral; + +#[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]); + +/// Ethernet packet queue. +/// +/// This struct owns the memory used for reading and writing packets. +/// +/// `TX` is the number of packets in the transmit queue, `RX` in the receive +/// queue. A bigger queue allows the hardware to receive more packets while the +/// CPU is busy doing other things, which may increase performance (especially for RX) +/// at the cost of more RAM usage. +pub struct PacketQueue { + tx_desc: [TDes; TX], + rx_desc: [RDes; RX], + tx_buf: [Packet; TX], + rx_buf: [Packet; RX], +} + +impl PacketQueue { + /// Create a new packet queue. + pub const fn new() -> Self { + Self { + tx_desc: [const { TDes::new() }; TX], + rx_desc: [const { RDes::new() }; RX], + tx_buf: [Packet([0; TX_BUFFER_SIZE]); TX], + rx_buf: [Packet([0; RX_BUFFER_SIZE]); RX], + } + } + + /// Initialize a packet queue in-place. + /// + /// This can be helpful to avoid accidentally stack-allocating the packet queue in the stack. The + /// Rust compiler can sometimes be a bit dumb when working with large owned values: if you call `new()` + /// and then store the returned PacketQueue in its final place (like a `static`), the compiler might + /// place it temporarily on the stack then move it. Since this struct is quite big, it may result + /// in a stack overflow. + /// + /// With this function, you can create an uninitialized `static` with type `MaybeUninit>` + /// and initialize it in-place, guaranteeing no stack usage. + /// + /// After calling this function, calling `assume_init` on the MaybeUninit is guaranteed safe. + pub fn init(this: &mut MaybeUninit) { + unsafe { + this.as_mut_ptr().write_bytes(0u8, 1); + } + } +} + +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 { + if self.phy.poll_link(&mut self.station_management, cx) { + LinkState::Up + } else { + LinkState::Down + } + } + + fn hardware_address(&self) -> HardwareAddress { + HardwareAddress::Ethernet(self.mac_addr) + } +} + +/// `embassy-net` RX token. +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 + } +} + +/// `embassy-net` TX token. +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 +/// +/// # Safety +/// +/// The methods cannot move out of self +pub unsafe trait StationManagement { + /// Read a register over SMI. + fn smi_read(&mut self, phy_addr: u8, reg: u8) -> u16; + /// Write a register over SMI. + fn smi_write(&mut self, phy_addr: u8, reg: u8, val: u16); +} + +/// Traits for an Ethernet PHY +/// +/// # Safety +/// +/// The methods cannot move S +pub unsafe trait PHY { + /// Reset PHY and wait for it to come out of reset. + fn phy_reset(&mut self, sm: &mut S); + /// PHY initialisation. + fn phy_init(&mut self, sm: &mut S); + /// Poll link to see if it is up and FD with 100Mbps + fn poll_link(&mut self, sm: &mut S, cx: &mut Context) -> bool; +} + +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { + /// Directly expose the SMI interface used by the Ethernet driver. + /// + /// This can be used to for example configure special PHY registers for compliance testing. + /// + /// # Safety + /// + /// Revert any temporary PHY register changes such as to enable test modes before handing + /// the Ethernet device over to the networking stack otherwise things likely won't work. + pub unsafe fn station_management(&mut self) -> &mut impl StationManagement { + &mut self.station_management + } +} + +trait SealedInstance { + fn regs() -> crate::pac::eth::Eth; +} + +/// Ethernet instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral + Send + 'static {} + +impl SealedInstance for crate::peripherals::ETH { + fn regs() -> crate::pac::eth::Eth { + crate::pac::ETH + } +} +impl Instance for crate::peripherals::ETH {} + +pin_trait!(RXClkPin, Instance); +pin_trait!(TXClkPin, Instance); +pin_trait!(RefClkPin, Instance); +pin_trait!(MDIOPin, Instance); +pin_trait!(MDCPin, Instance); +pin_trait!(RXDVPin, Instance); +pin_trait!(CRSPin, Instance); +pin_trait!(RXD0Pin, Instance); +pin_trait!(RXD1Pin, Instance); +pin_trait!(RXD2Pin, Instance); +pin_trait!(RXD3Pin, Instance); +pin_trait!(TXD0Pin, Instance); +pin_trait!(TXD1Pin, Instance); +pin_trait!(TXD2Pin, Instance); +pin_trait!(TXD3Pin, Instance); +pin_trait!(TXEnPin, Instance); diff --git a/embassy/embassy-stm32/src/eth/v1/mod.rs b/embassy/embassy-stm32/src/eth/v1/mod.rs new file mode 100644 index 0000000..cce75ec --- /dev/null +++ b/embassy/embassy-stm32/src/eth/v1/mod.rs @@ -0,0 +1,327 @@ +// The v1c ethernet driver was ported to embassy from the awesome stm32-eth project (https://github.com/stm32-rs/stm32-eth). + +mod rx_desc; +mod tx_desc; + +use core::marker::PhantomData; +use core::sync::atomic::{fence, Ordering}; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +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::*; +#[cfg(eth_v1a)] +use crate::gpio::Pull; +use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed}; +use crate::interrupt::InterruptExt; +#[cfg(eth_v1a)] +use crate::pac::AFIO; +#[cfg(any(eth_v1b, eth_v1c))] +use crate::pac::SYSCFG; +use crate::pac::{ETH, RCC}; +use crate::rcc::SealedRccPeripheral; +use crate::{interrupt, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler {} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + WAKER.wake(); + + // TODO: Check and clear more flags + 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(); + } +} + +/// Ethernet driver. +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], + pub(crate) phy: P, + pub(crate) station_management: EthernetStationManagement, + pub(crate) mac_addr: [u8; 6], +} + +#[cfg(eth_v1a)] +macro_rules! config_in_pins { + ($($pin:ident),*) => { + critical_section::with(|_| { + $( + // TODO properly create a set_as_input function + $pin.set_as_af($pin.af_num(), AfType::input(Pull::None)); + )* + }) + } +} + +#[cfg(eth_v1a)] +macro_rules! config_af_pins { + ($($pin:ident),*) => { + critical_section::with(|_| { + $( + $pin.set_as_af($pin.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + )* + }) + }; +} + +#[cfg(any(eth_v1b, eth_v1c))] +macro_rules! config_pins { + ($($pin:ident),*) => { + critical_section::with(|_| { + $( + $pin.set_as_af($pin.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + )* + }) + }; +} + +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { + /// safety: the returned instance is not leak-safe + pub fn new( + queue: &'d mut PacketQueue, + peri: impl Peripheral

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

> + 'd, + mdio: impl Peripheral

> + 'd, + mdc: impl Peripheral

> + 'd, + crs: impl Peripheral

> + 'd, + rx_d0: impl Peripheral

> + 'd, + rx_d1: impl Peripheral

> + 'd, + tx_d0: impl Peripheral

> + 'd, + tx_d1: impl Peripheral

> + 'd, + tx_en: impl Peripheral

> + 'd, + phy: P, + mac_addr: [u8; 6], + ) -> Self { + into_ref!(peri, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + + // Enable the necessary Clocks + #[cfg(eth_v1a)] + critical_section::with(|_| { + 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)); + + RCC.ahbenr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); + }); + + #[cfg(any(eth_v1b, eth_v1c))] + critical_section::with(|_| { + RCC.ahb1enr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); + + // RMII (Reduced Media Independent Interface) + SYSCFG.pmc().modify(|w| w.set_mii_rmii_sel(true)); + }); + + #[cfg(eth_v1a)] + { + config_in_pins!(ref_clk, rx_d0, rx_d1); + config_af_pins!(mdio, mdc, tx_d0, tx_d1, tx_en); + } + + #[cfg(any(eth_v1b, eth_v1c))] + config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + + let dma = T::regs().ethernet_dma(); + let mac = T::regs().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 + + let hclk = ::frequency(); + 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, + station_management: EthernetStationManagement { + peri: PhantomData, + clock_range: clock_range, + }, + 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); + + let mac = T::regs().ethernet_mac(); + let dma = T::regs().ethernet_dma(); + + mac.maccr().modify(|w| { + w.set_re(true); + w.set_te(true); + }); + dma.dmaomr().modify(|w| { + w.set_ftf(Ftf::FLUSH); // flush transmit fifo (queue) + w.set_st(St::STARTED); // start transmitting channel + 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); + }); + + this.phy.phy_reset(&mut this.station_management); + this.phy.phy_init(&mut this.station_management); + + interrupt::ETH.unpend(); + unsafe { interrupt::ETH.enable() }; + + this + } +} + +/// Ethernet station management interface. +pub struct EthernetStationManagement { + peri: PhantomData, + clock_range: Cr, +} + +unsafe impl StationManagement for EthernetStationManagement { + fn smi_read(&mut self, phy_addr: u8, reg: u8) -> u16 { + let mac = T::regs().ethernet_mac(); + + mac.macmiiar().modify(|w| { + w.set_pa(phy_addr); + w.set_mr(reg); + w.set_mw(Mw::READ); // read operation + w.set_cr(self.clock_range); + w.set_mb(MbProgress::BUSY); // indicate that operation is in progress + }); + while mac.macmiiar().read().mb() == MbProgress::BUSY {} + mac.macmiidr().read().md() + } + + fn smi_write(&mut self, phy_addr: u8, reg: u8, val: u16) { + let mac = T::regs().ethernet_mac(); + + mac.macmiidr().write(|w| w.set_md(val)); + mac.macmiiar().modify(|w| { + w.set_pa(phy_addr); + w.set_mr(reg); + w.set_mw(Mw::WRITE); // write + w.set_cr(self.clock_range); + w.set_mb(MbProgress::BUSY); + }); + while mac.macmiiar().read().mb() == MbProgress::BUSY {} + } +} + +impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { + fn drop(&mut self) { + let dma = T::regs().ethernet_dma(); + let mac = T::regs().ethernet_mac(); + + // Disable the TX DMA and wait for any previous transmissions to be completed + dma.dmaomr().modify(|w| w.set_st(St::STOPPED)); + + // Disable MAC transmitter and receiver + mac.maccr().modify(|w| { + w.set_re(false); + w.set_te(false); + }); + + dma.dmaomr().modify(|w| w.set_sr(DmaomrSr::STOPPED)); + + critical_section::with(|_| { + for pin in self.pins.iter_mut() { + pin.set_as_disconnected(); + } + }) + } +} diff --git a/embassy/embassy-stm32/src/eth/v1/rx_desc.rs b/embassy/embassy-stm32/src/eth/v1/rx_desc.rs new file mode 100644 index 0000000..668378b --- /dev/null +++ b/embassy/embassy-stm32/src/eth/v1/rx_desc.rs @@ -0,0 +1,232 @@ +use core::sync::atomic::{compiler_fence, fence, Ordering}; + +use stm32_metapac::eth::vals::{Rpd, Rps}; +use vcell::VolatileCell; + +use crate::eth::RX_BUFFER_SIZE; +use crate::pac::ETH; + +mod rx_consts { + /// Owned by DMA engine + pub const RXDESC_0_OWN: u32 = 1 << 31; + /// First descriptor + pub const RXDESC_0_FS: u32 = 1 << 9; + /// Last descriptor + pub const RXDESC_0_LS: u32 = 1 << 8; + /// Error summary + pub const RXDESC_0_ES: u32 = 1 << 15; + /// Frame length + pub const RXDESC_0_FL_MASK: u32 = 0x3FFF; + pub const RXDESC_0_FL_SHIFT: usize = 16; + + pub const RXDESC_1_RBS_MASK: u32 = 0x0fff; + /// Second address chained + pub const RXDESC_1_RCH: u32 = 1 << 14; + /// End Of Ring + pub const RXDESC_1_RER: u32 = 1 << 15; +} + +use rx_consts::*; + +use super::Packet; + +/// Receive Descriptor representation +/// +/// * rdes0: OWN and Status +/// * rdes1: allocated buffer length +/// * rdes2: data buffer address +/// * rdes3: next descriptor address +#[repr(C)] +pub(crate) struct RDes { + rdes0: VolatileCell, + rdes1: VolatileCell, + rdes2: VolatileCell, + rdes3: VolatileCell, +} + +impl RDes { + pub const fn new() -> Self { + Self { + rdes0: VolatileCell::new(0), + rdes1: VolatileCell::new(0), + rdes2: VolatileCell::new(0), + rdes3: VolatileCell::new(0), + } + } + + /// Return true if this RDes is acceptable to us + #[inline(always)] + fn valid(&self) -> bool { + // Write-back descriptor is valid if: + // + // Contains first buffer of packet AND contains last buf of + // packet AND no errors + (self.rdes0.get() & (RXDESC_0_ES | RXDESC_0_FS | RXDESC_0_LS)) == (RXDESC_0_FS | RXDESC_0_LS) + } + + /// Return true if this RDes is not currently owned by the DMA + #[inline(always)] + 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)] + 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); + + compiler_fence(Ordering::Release); + + self.rdes0.set(self.rdes0.get() | RXDESC_0_OWN); + + // Used to flush the store buffer as fast as possible to make the buffer available for the + // DMA. + fence(Ordering::SeqCst); + } + + // points to next descriptor (RCH) + #[inline(always)] + fn set_buffer2(&self, buffer: *const u8) { + self.rdes3.set(buffer as u32); + } + + #[inline(always)] + fn set_end_of_ring(&self) { + self.rdes1.set(self.rdes1.get() | RXDESC_1_RER); + } + + #[inline(always)] + fn packet_len(&self) -> usize { + ((self.rdes0.get() >> RXDESC_0_FL_SHIFT) & RXDESC_0_FL_MASK) as usize + } + + 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); + + match next { + Some(next) => self.set_buffer2(next as *const _ as *const u8), + None => { + self.set_buffer2(0 as *const u8); + self.set_end_of_ring(); + } + } + + self.set_ready(buf); + } +} + +/// Running state of the `RxRing` +#[derive(PartialEq, Eq, Debug)] +pub enum RunningState { + Unknown, + Stopped, + Running, +} + +/// Rx ring of descriptors and packets +pub(crate) struct RDesRing<'a> { + descriptors: &'a mut [RDes], + buffers: &'a mut [Packet], + index: usize, +} + +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()); + + for (i, entry) in descriptors.iter().enumerate() { + entry.setup(descriptors.get(i + 1), buffers[i].0.as_mut_ptr()); + } + + // Register rx descriptor start + ETH.ethernet_dma() + .dmardlar() + .write(|w| w.0 = descriptors.as_ptr() as u32); + // We already have fences in `set_owned`, which is called in `setup` + + Self { + descriptors, + buffers, + index: 0, + } + } + + pub(crate) fn demand_poll(&self) { + ETH.ethernet_dma().dmarpdr().write(|w| w.set_rpd(Rpd::POLL)); + } + + /// Get current `RunningState` + fn running_state(&self) -> RunningState { + match ETH.ethernet_dma().dmasr().read().rps() { + // Reset or Stop Receive Command issued + Rps::STOPPED => RunningState::Stopped, + // Fetching receive transfer descriptor + Rps::RUNNINGFETCHING => RunningState::Running, + // Waiting for receive packet + Rps::RUNNINGWAITING => RunningState::Running, + // Receive descriptor unavailable + Rps::SUSPENDED => RunningState::Stopped, + // Closing receive descriptor + Rps::_RESERVED_5 => RunningState::Running, + // Transferring the receive packet data from receive buffer to host memory + Rps::RUNNINGWRITING => RunningState::Running, + _ => RunningState::Unknown, + } + } + + /// 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); + + // 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; + } + + // 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 + } + } +} diff --git a/embassy/embassy-stm32/src/eth/v1/tx_desc.rs b/embassy/embassy-stm32/src/eth/v1/tx_desc.rs new file mode 100644 index 0000000..1317d20 --- /dev/null +++ b/embassy/embassy-stm32/src/eth/v1/tx_desc.rs @@ -0,0 +1,171 @@ +use core::sync::atomic::{compiler_fence, fence, Ordering}; + +use vcell::VolatileCell; + +use crate::eth::TX_BUFFER_SIZE; +use crate::pac::ETH; + +/// Transmit and Receive Descriptor fields +#[allow(dead_code)] +mod tx_consts { + pub const TXDESC_0_OWN: u32 = 1 << 31; + pub const TXDESC_0_IOC: u32 = 1 << 30; + // First segment of frame + pub const TXDESC_0_FS: u32 = 1 << 28; + // Last segment of frame + pub const TXDESC_0_LS: u32 = 1 << 29; + // Transmit end of ring + pub const TXDESC_0_TER: u32 = 1 << 21; + // Second address chained + pub const TXDESC_0_TCH: u32 = 1 << 20; + // Error status + pub const TXDESC_0_ES: u32 = 1 << 15; + + // Transmit buffer size + pub const TXDESC_1_TBS_SHIFT: usize = 0; + pub const TXDESC_1_TBS_MASK: u32 = 0x0fff << TXDESC_1_TBS_SHIFT; +} +use tx_consts::*; + +use super::Packet; + +/// Transmit Descriptor representation +/// +/// * tdes0: control +/// * tdes1: buffer lengths +/// * tdes2: data buffer address +/// * tdes3: next descriptor address +#[repr(C)] +pub(crate) struct TDes { + tdes0: VolatileCell, + tdes1: VolatileCell, + tdes2: VolatileCell, + tdes3: VolatileCell, +} + +impl TDes { + pub const fn new() -> Self { + Self { + tdes0: VolatileCell::new(0), + tdes1: VolatileCell::new(0), + tdes2: VolatileCell::new(0), + tdes3: VolatileCell::new(0), + } + } + + /// Return true if this TDes is not currently owned by the DMA + fn available(&self) -> bool { + (self.tdes0.get() & TXDESC_0_OWN) == 0 + } + + /// Pass ownership to the DMA engine + fn set_owned(&mut self) { + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::Release); + + compiler_fence(Ordering::Release); + self.tdes0.set(self.tdes0.get() | TXDESC_0_OWN); + + // Used to flush the store buffer as fast as possible to make the buffer available for the + // DMA. + fence(Ordering::SeqCst); + } + + fn set_buffer1(&self, buffer: *const u8) { + self.tdes2.set(buffer as u32); + } + + 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(&self, buffer: *const u8) { + self.tdes3.set(buffer as u32); + } + + 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 + 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 { + Some(next) => self.set_buffer2(next as *const TDes as *const u8), + None => { + self.set_buffer2(0 as *const u8); + self.set_end_of_ring(); + } + } + } +} + +pub(crate) struct TDesRing<'a> { + descriptors: &'a mut [TDes], + buffers: &'a mut [Packet], + index: usize, +} + +impl<'a> TDesRing<'a> { + /// Initialise this TDesRing. Assume TDesRing is corrupt + pub(crate) fn new(descriptors: &'a mut [TDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 0); + assert!(descriptors.len() == buffers.len()); + + for (i, entry) in descriptors.iter().enumerate() { + entry.setup(descriptors.get(i + 1)); + } + + // Register txdescriptor start + ETH.ethernet_dma() + .dmatdlar() + .write(|w| w.0 = descriptors.as_ptr() as u32); + + Self { + descriptors, + buffers, + index: 0, + } + } + + pub(crate) fn len(&self) -> usize { + self.descriptors.len() + } + + /// 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 + } + } + + /// 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()); + + descriptor.set_buffer1(self.buffers[self.index].0.as_ptr()); + descriptor.set_buffer1_len(len); + + descriptor.set_owned(); + + // 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 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 + ETH.ethernet_dma().dmatpdr().modify(|w| w.0 = 1) + } +} diff --git a/embassy/embassy-stm32/src/eth/v2/descriptors.rs b/embassy/embassy-stm32/src/eth/v2/descriptors.rs new file mode 100644 index 0000000..645bfdb --- /dev/null +++ b/embassy/embassy-stm32/src/eth/v2/descriptors.rs @@ -0,0 +1,253 @@ +use core::sync::atomic::{fence, Ordering}; + +use vcell::VolatileCell; + +use crate::eth::{Packet, RX_BUFFER_SIZE, TX_BUFFER_SIZE}; +use crate::pac::ETH; + +/// Transmit and Receive Descriptor fields +#[allow(dead_code)] +mod emac_consts { + pub const EMAC_DES3_OWN: u32 = 0x8000_0000; + pub const EMAC_DES3_CTXT: u32 = 0x4000_0000; + pub const EMAC_DES3_FD: u32 = 0x2000_0000; + pub const EMAC_DES3_LD: u32 = 0x1000_0000; + pub const EMAC_DES3_ES: u32 = 0x0000_8000; + pub const EMAC_DES0_BUF1AP: u32 = 0xFFFF_FFFF; + + pub const EMAC_TDES2_IOC: u32 = 0x8000_0000; + pub const EMAC_TDES2_B1L: u32 = 0x0000_3FFF; + + pub const EMAC_RDES3_IOC: u32 = 0x4000_0000; + pub const EMAC_RDES3_PL: u32 = 0x0000_7FFF; + pub const EMAC_RDES3_BUF1V: u32 = 0x0100_0000; + pub const EMAC_RDES3_PKTLEN: u32 = 0x0000_7FFF; +} +use emac_consts::*; + +/// Transmit Descriptor representation +/// +/// * tdes0: transmit buffer address +/// * tdes1: +/// * tdes2: buffer lengths +/// * tdes3: control and payload/frame length +#[repr(C)] +pub(crate) struct TDes { + tdes0: VolatileCell, + tdes1: VolatileCell, + tdes2: VolatileCell, + tdes3: VolatileCell, +} + +impl TDes { + pub const fn new() -> Self { + Self { + tdes0: VolatileCell::new(0), + tdes1: VolatileCell::new(0), + tdes2: VolatileCell::new(0), + tdes3: VolatileCell::new(0), + } + } + + /// Return true if this TDes is not currently owned by the DMA + fn available(&self) -> bool { + self.tdes3.get() & EMAC_DES3_OWN == 0 + } +} + +pub(crate) struct TDesRing<'a> { + descriptors: &'a mut [TDes], + buffers: &'a mut [Packet], + index: usize, +} + +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()); + + for td in descriptors.iter_mut() { + *td = TDes::new(); + } + + // Initialize the pointers in the DMA engine. (There will be a memory barrier later + // before the DMA engine is enabled.) + let dma = ETH.ethernet_dma(); + 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, + } + } + + pub(crate) fn len(&self) -> usize { + self.descriptors.len() + } + + /// 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 + } + } + + /// 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(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); + + // 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); + + // signal DMA it can try again. + // See issue #2129 + ETH.ethernet_dma().dmactx_dtpr().write(|w| w.0 = &td as *const _ as u32); + + self.index = (self.index + 1) % self.descriptors.len(); + } +} + +/// Receive Descriptor representation +/// +/// * rdes0: receive buffer address +/// * rdes1: +/// * rdes2: +/// * rdes3: OWN and Status +#[repr(C)] +pub(crate) struct RDes { + rdes0: VolatileCell, + rdes1: VolatileCell, + rdes2: VolatileCell, + rdes3: VolatileCell, +} + +impl RDes { + pub const fn new() -> Self { + Self { + rdes0: VolatileCell::new(0), + rdes1: VolatileCell::new(0), + rdes2: VolatileCell::new(0), + rdes3: VolatileCell::new(0), + } + } + + /// Return true if this RDes is acceptable to us + #[inline(always)] + fn valid(&self) -> bool { + // Write-back descriptor is valid if: + // + // Contains first buffer of packet AND contains last buf of + // packet AND no errors AND not a context descriptor + self.rdes3.get() & (EMAC_DES3_FD | EMAC_DES3_LD | EMAC_DES3_ES | EMAC_DES3_CTXT) + == (EMAC_DES3_FD | EMAC_DES3_LD) + } + + /// Return true if this RDes is not currently owned by the DMA + #[inline(always)] + fn available(&self) -> bool { + self.rdes3.get() & EMAC_DES3_OWN == 0 // Owned by us + } + + #[inline(always)] + 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 +pub(crate) struct RDesRing<'a> { + descriptors: &'a mut [RDes], + buffers: &'a mut [Packet], + index: usize, +} + +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()); + + for (i, desc) in descriptors.iter_mut().enumerate() { + *desc = RDes::new(); + desc.set_ready(buffers[i].0.as_mut_ptr()); + } + + let dma = ETH.ethernet_dma(); + 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); + + Self { + descriptors, + buffers, + index: 0, + } + } + + /// 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); + + // 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; + } + + // 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.rdes3.get() & EMAC_RDES3_PKTLEN) as usize; + return Some(&mut self.buffers[self.index].0[..len]); + } + + /// Pop the packet previously returned by `available`. + pub(crate) fn pop_packet(&mut self) { + let rd = &mut self.descriptors[self.index]; + assert!(rd.available()); + + rd.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. + // See issue #2129 + ETH.ethernet_dma().dmacrx_dtpr().write(|w| w.0 = &rd as *const _ as u32); + + // Increment index. + self.index = (self.index + 1) % self.descriptors.len(); + } +} diff --git a/embassy/embassy-stm32/src/eth/v2/mod.rs b/embassy/embassy-stm32/src/eth/v2/mod.rs new file mode 100644 index 0000000..9dd7f7d --- /dev/null +++ b/embassy/embassy-stm32/src/eth/v2/mod.rs @@ -0,0 +1,372 @@ +mod descriptors; + +use core::marker::PhantomData; +use core::sync::atomic::{fence, Ordering}; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use stm32_metapac::syscfg::vals::EthSelPhy; + +pub(crate) use self::descriptors::{RDes, RDesRing, TDes, TDesRing}; +use super::*; +use crate::gpio::{AfType, AnyPin, OutputType, SealedPin as _, Speed}; +use crate::interrupt::InterruptExt; +use crate::pac::ETH; +use crate::rcc::SealedRccPeripheral; +use crate::{interrupt, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler {} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + WAKER.wake(); + + // TODO: Check and clear more flags + 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(); + } +} + +/// Ethernet driver. +pub struct Ethernet<'d, T: Instance, P: PHY> { + _peri: PeripheralRef<'d, T>, + pub(crate) tx: TDesRing<'d>, + pub(crate) rx: RDesRing<'d>, + pins: Pins<'d>, + pub(crate) phy: P, + pub(crate) station_management: EthernetStationManagement, + pub(crate) mac_addr: [u8; 6], +} + +/// Pins of ethernet driver. +enum Pins<'d> { + Rmii([PeripheralRef<'d, AnyPin>; 9]), + Mii([PeripheralRef<'d, AnyPin>; 14]), +} + +macro_rules! config_pins { + ($($pin:ident),*) => { + critical_section::with(|_| { + $( + // TODO: shouldn't some pins be configured as inputs? + $pin.set_as_af($pin.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + )* + }) + }; +} + +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { + /// Create a new RMII ethernet driver using 9 pins. + pub fn new( + queue: &'d mut PacketQueue, + peri: impl Peripheral

+ 'd, + irq: impl interrupt::typelevel::Binding + 'd, + ref_clk: impl Peripheral

> + 'd, + mdio: impl Peripheral

> + 'd, + mdc: impl Peripheral

> + 'd, + crs: impl Peripheral

> + 'd, + rx_d0: impl Peripheral

> + 'd, + rx_d1: impl Peripheral

> + 'd, + tx_d0: impl Peripheral

> + 'd, + tx_d1: impl Peripheral

> + 'd, + tx_en: impl Peripheral

> + 'd, + phy: P, + mac_addr: [u8; 6], + ) -> Self { + // Enable the necessary clocks + critical_section::with(|_| { + crate::pac::RCC.ahb1enr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); + + crate::pac::SYSCFG.pmcr().modify(|w| w.set_eth_sel_phy(EthSelPhy::RMII)); + }); + + into_ref!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + + let pins = Pins::Rmii([ + ref_clk.map_into(), + mdio.map_into(), + mdc.map_into(), + crs.map_into(), + rx_d0.map_into(), + rx_d1.map_into(), + tx_d0.map_into(), + tx_d1.map_into(), + tx_en.map_into(), + ]); + + Self::new_inner(queue, peri, irq, pins, phy, mac_addr) + } + + /// Create a new MII ethernet driver using 14 pins. + pub fn new_mii( + queue: &'d mut PacketQueue, + peri: impl Peripheral

+ 'd, + irq: impl interrupt::typelevel::Binding + 'd, + rx_clk: impl Peripheral

> + 'd, + tx_clk: impl Peripheral

> + 'd, + mdio: impl Peripheral

> + 'd, + mdc: impl Peripheral

> + 'd, + rxdv: impl Peripheral

> + 'd, + rx_d0: impl Peripheral

> + 'd, + rx_d1: impl Peripheral

> + 'd, + rx_d2: impl Peripheral

> + 'd, + rx_d3: impl Peripheral

> + 'd, + tx_d0: impl Peripheral

> + 'd, + tx_d1: impl Peripheral

> + 'd, + tx_d2: impl Peripheral

> + 'd, + tx_d3: impl Peripheral

> + 'd, + tx_en: impl Peripheral

> + 'd, + phy: P, + mac_addr: [u8; 6], + ) -> Self { + // Enable the necessary clocks + critical_section::with(|_| { + crate::pac::RCC.ahb1enr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); + + crate::pac::SYSCFG + .pmcr() + .modify(|w| w.set_eth_sel_phy(EthSelPhy::MII_GMII)); + }); + + into_ref!(rx_clk, tx_clk, mdio, mdc, rxdv, rx_d0, rx_d1, rx_d2, rx_d3, tx_d0, tx_d1, tx_d2, tx_d3, tx_en); + config_pins!(rx_clk, tx_clk, mdio, mdc, rxdv, rx_d0, rx_d1, rx_d2, rx_d3, tx_d0, tx_d1, tx_d2, tx_d3, tx_en); + + let pins = Pins::Mii([ + rx_clk.map_into(), + tx_clk.map_into(), + mdio.map_into(), + mdc.map_into(), + rxdv.map_into(), + rx_d0.map_into(), + rx_d1.map_into(), + rx_d2.map_into(), + rx_d3.map_into(), + tx_d0.map_into(), + tx_d1.map_into(), + tx_d2.map_into(), + tx_d3.map_into(), + tx_en.map_into(), + ]); + + Self::new_inner(queue, peri, irq, pins, phy, mac_addr) + } + + fn new_inner( + queue: &'d mut PacketQueue, + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, + pins: Pins<'d>, + phy: P, + mac_addr: [u8; 6], + ) -> Self { + let dma = T::regs().ethernet_dma(); + let mac = T::regs().ethernet_mac(); + let mtl = T::regs().ethernet_mtl(); + + // Reset and wait + dma.dmamr().modify(|w| w.set_swr(true)); + while dma.dmamr().read().swr() {} + + 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 + }); + + // Disable multicast filter + mac.macpfr().modify(|w| w.set_pm(true)); + + // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, + // so the LR write must happen after the HR write. + mac.maca0hr() + .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.macqtx_fcr().modify(|w| w.set_pt(0x100)); + + // 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) + }); + + // 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)); + + dma.dmactx_cr().modify(|w| w.set_txpbl(1)); // 32 ? + dma.dmacrx_cr().modify(|w| { + w.set_rxpbl(1); // 32 ? + w.set_rbsz(RX_BUFFER_SIZE as u16); + }); + + let hclk = ::frequency(); + 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") + } + }; + + let mut this = Self { + _peri: peri.into_ref(), + tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf), + rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf), + pins, + phy, + station_management: EthernetStationManagement { + peri: PhantomData, + clock_range: clock_range, + }, + mac_addr, + }; + + fence(Ordering::SeqCst); + + let mac = T::regs().ethernet_mac(); + let mtl = T::regs().ethernet_mtl(); + let dma = T::regs().ethernet_dma(); + + mac.maccr().modify(|w| { + w.set_re(true); + w.set_te(true); + }); + mtl.mtltx_qomr().modify(|w| w.set_ftq(true)); + + dma.dmactx_cr().modify(|w| w.set_st(true)); + dma.dmacrx_cr().modify(|w| w.set_sr(true)); + + // Enable interrupts + dma.dmacier().modify(|w| { + w.set_nie(true); + w.set_rie(true); + w.set_tie(true); + }); + + this.phy.phy_reset(&mut this.station_management); + this.phy.phy_init(&mut this.station_management); + + interrupt::ETH.unpend(); + unsafe { interrupt::ETH.enable() }; + + this + } +} + +/// Ethernet SMI driver. +pub struct EthernetStationManagement { + peri: PhantomData, + clock_range: u8, +} + +unsafe impl StationManagement for EthernetStationManagement { + fn smi_read(&mut self, phy_addr: u8, reg: u8) -> u16 { + let mac = T::regs().ethernet_mac(); + + mac.macmdioar().modify(|w| { + w.set_pa(phy_addr); + w.set_rda(reg); + w.set_goc(0b11); // read + w.set_cr(self.clock_range); + w.set_mb(true); + }); + while mac.macmdioar().read().mb() {} + mac.macmdiodr().read().md() + } + + fn smi_write(&mut self, phy_addr: u8, reg: u8, val: u16) { + let mac = T::regs().ethernet_mac(); + + mac.macmdiodr().write(|w| w.set_md(val)); + mac.macmdioar().modify(|w| { + w.set_pa(phy_addr); + w.set_rda(reg); + w.set_goc(0b01); // write + w.set_cr(self.clock_range); + w.set_mb(true); + }); + while mac.macmdioar().read().mb() {} + } +} + +impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { + fn drop(&mut self) { + let dma = T::regs().ethernet_dma(); + let mac = T::regs().ethernet_mac(); + let mtl = T::regs().ethernet_mtl(); + + // Disable the TX DMA and wait for any previous transmissions to be completed + dma.dmactx_cr().modify(|w| w.set_st(false)); + while { + let txqueue = mtl.mtltx_qdr().read(); + txqueue.trcsts() == 0b01 || txqueue.txqsts() + } {} + + // Disable MAC transmitter and receiver + mac.maccr().modify(|w| { + w.set_re(false); + w.set_te(false); + }); + + // Wait for previous receiver transfers to be completed and then disable the RX DMA + while { + let rxqueue = mtl.mtlrx_qdr().read(); + rxqueue.rxqsts() != 0b00 || rxqueue.prxq() != 0 + } {} + dma.dmacrx_cr().modify(|w| w.set_sr(false)); + + critical_section::with(|_| { + for pin in match self.pins { + Pins::Rmii(ref mut pins) => pins.iter_mut(), + Pins::Mii(ref mut pins) => pins.iter_mut(), + } { + pin.set_as_disconnected(); + } + }) + } +} diff --git a/embassy/embassy-stm32/src/exti.rs b/embassy/embassy-stm32/src/exti.rs new file mode 100644 index 0000000..5cff742 --- /dev/null +++ b/embassy/embassy-stm32/src/exti.rs @@ -0,0 +1,406 @@ +//! External Interrupts (EXTI) +use core::convert::Infallible; +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{impl_peripheral, into_ref}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::gpio::{AnyPin, Input, Level, Pin as GpioPin, Pull}; +use crate::pac::exti::regs::Lines; +use crate::pac::EXTI; +use crate::{interrupt, pac, peripherals, Peripheral}; + +const EXTI_COUNT: usize = 16; +static EXTI_WAKERS: [AtomicWaker; EXTI_COUNT] = [const { AtomicWaker::new() }; EXTI_COUNT]; + +#[cfg(exti_w)] +fn cpu_regs() -> pac::exti::Cpu { + EXTI.cpu(crate::pac::CORE_INDEX) +} + +#[cfg(not(exti_w))] +fn cpu_regs() -> pac::exti::Exti { + EXTI +} + +#[cfg(not(any(exti_c0, exti_g0, exti_u0, exti_l5, gpio_v1, exti_u5, exti_h5, exti_h50)))] +fn exticr_regs() -> pac::syscfg::Syscfg { + pac::SYSCFG +} +#[cfg(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50))] +fn exticr_regs() -> pac::exti::Exti { + EXTI +} +#[cfg(gpio_v1)] +fn exticr_regs() -> pac::afio::Afio { + pac::AFIO +} + +unsafe fn on_irq() { + #[cfg(not(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50)))] + let bits = EXTI.pr(0).read().0; + #[cfg(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50))] + let bits = EXTI.rpr(0).read().0 | EXTI.fpr(0).read().0; + + // We don't handle or change any EXTI lines above 16. + let bits = bits & 0x0000FFFF; + + // Mask all the channels that fired. + cpu_regs().imr(0).modify(|w| w.0 &= !bits); + + // Wake the tasks + for pin in BitIter(bits) { + EXTI_WAKERS[pin as usize].wake(); + } + + // Clear pending + #[cfg(not(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50)))] + EXTI.pr(0).write_value(Lines(bits)); + #[cfg(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50))] + { + EXTI.rpr(0).write_value(Lines(bits)); + EXTI.fpr(0).write_value(Lines(bits)); + } + + #[cfg(feature = "low-power")] + crate::low_power::on_wakeup_irq(); +} + +struct BitIter(u32); + +impl Iterator for BitIter { + type Item = u32; + + fn next(&mut self) -> Option { + match self.0.trailing_zeros() { + 32 => None, + b => { + self.0 &= !(1 << b); + Some(b) + } + } + } +} + +/// EXTI input driver. +/// +/// This driver augments a GPIO `Input` with EXTI functionality. EXTI is not +/// built into `Input` itself because it needs to take ownership of the corresponding +/// EXTI channel, which is a limited resource. +/// +/// Pins PA5, PB5, PC5... all use EXTI channel 5, so you can't use EXTI on, say, PA5 and PC5 at the same time. +pub struct ExtiInput<'d> { + pin: Input<'d>, +} + +impl<'d> Unpin for ExtiInput<'d> {} + +impl<'d> ExtiInput<'d> { + /// Create an EXTI input. + pub fn new( + pin: impl Peripheral

+ 'd, + ch: impl Peripheral

+ 'd, + pull: Pull, + ) -> Self { + into_ref!(pin, ch); + + // Needed if using AnyPin+AnyChannel. + assert_eq!(pin.pin(), ch.number()); + + Self { + pin: Input::new(pin, pull), + } + } + + /// Get whether the pin is high. + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + /// Get whether the pin is low. + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Get the pin level. + pub fn get_level(&self) -> Level { + self.pin.get_level() + } + + /// Asynchronously wait until the pin is high. + /// + /// This returns immediately if the pin is already high. + pub async fn wait_for_high(&mut self) { + let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false); + if self.is_high() { + return; + } + fut.await + } + + /// Asynchronously wait until the pin is low. + /// + /// This returns immediately if the pin is already low. + pub async fn wait_for_low(&mut self) { + let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true); + if self.is_low() { + return; + } + fut.await + } + + /// Asynchronously wait until the pin sees a rising edge. + /// + /// If the pin is already high, it will wait for it to go low then back high. + pub async fn wait_for_rising_edge(&mut self) { + ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false).await + } + + /// Asynchronously wait until the pin sees a falling edge. + /// + /// If the pin is already low, it will wait for it to go high then back low. + pub async fn wait_for_falling_edge(&mut self) { + ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true).await + } + + /// Asynchronously wait until the pin sees any edge (either rising or falling). + pub async fn wait_for_any_edge(&mut self) { + ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, true).await + } +} + +impl<'d> embedded_hal_02::digital::v2::InputPin for ExtiInput<'d> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for ExtiInput<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::InputPin for ExtiInput<'d> { + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_async::digital::Wait for ExtiInput<'d> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct ExtiInputFuture<'a> { + pin: u8, + phantom: PhantomData<&'a mut AnyPin>, +} + +impl<'a> ExtiInputFuture<'a> { + fn new(pin: u8, port: u8, rising: bool, falling: bool) -> Self { + critical_section::with(|_| { + let pin = pin as usize; + exticr_regs().exticr(pin / 4).modify(|w| w.set_exti(pin % 4, port)); + EXTI.rtsr(0).modify(|w| w.set_line(pin, rising)); + EXTI.ftsr(0).modify(|w| w.set_line(pin, falling)); + + // clear pending bit + #[cfg(not(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50)))] + EXTI.pr(0).write(|w| w.set_line(pin, true)); + #[cfg(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50))] + { + EXTI.rpr(0).write(|w| w.set_line(pin, true)); + EXTI.fpr(0).write(|w| w.set_line(pin, true)); + } + + cpu_regs().imr(0).modify(|w| w.set_line(pin, true)); + }); + + Self { + pin, + phantom: PhantomData, + } + } +} + +impl<'a> Drop for ExtiInputFuture<'a> { + fn drop(&mut self) { + critical_section::with(|_| { + let pin = self.pin as _; + cpu_regs().imr(0).modify(|w| w.set_line(pin, false)); + }); + } +} + +impl<'a> Future for ExtiInputFuture<'a> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + EXTI_WAKERS[self.pin as usize].register(cx.waker()); + + let imr = cpu_regs().imr(0).read(); + if !imr.line(self.pin as _) { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +macro_rules! foreach_exti_irq { + ($action:ident) => { + foreach_interrupt!( + (EXTI0) => { $action!(EXTI0); }; + (EXTI1) => { $action!(EXTI1); }; + (EXTI2) => { $action!(EXTI2); }; + (EXTI3) => { $action!(EXTI3); }; + (EXTI4) => { $action!(EXTI4); }; + (EXTI5) => { $action!(EXTI5); }; + (EXTI6) => { $action!(EXTI6); }; + (EXTI7) => { $action!(EXTI7); }; + (EXTI8) => { $action!(EXTI8); }; + (EXTI9) => { $action!(EXTI9); }; + (EXTI10) => { $action!(EXTI10); }; + (EXTI11) => { $action!(EXTI11); }; + (EXTI12) => { $action!(EXTI12); }; + (EXTI13) => { $action!(EXTI13); }; + (EXTI14) => { $action!(EXTI14); }; + (EXTI15) => { $action!(EXTI15); }; + + // plus the weird ones + (EXTI0_1) => { $action!( EXTI0_1 ); }; + (EXTI15_10) => { $action!(EXTI15_10); }; + (EXTI15_4) => { $action!(EXTI15_4); }; + (EXTI1_0) => { $action!(EXTI1_0); }; + (EXTI2_3) => { $action!(EXTI2_3); }; + (EXTI2_TSC) => { $action!(EXTI2_TSC); }; + (EXTI3_2) => { $action!(EXTI3_2); }; + (EXTI4_15) => { $action!(EXTI4_15); }; + (EXTI9_5) => { $action!(EXTI9_5); }; + ); + }; +} + +macro_rules! impl_irq { + ($e:ident) => { + #[allow(non_snake_case)] + #[cfg(feature = "rt")] + #[interrupt] + unsafe fn $e() { + on_irq() + } + }; +} + +foreach_exti_irq!(impl_irq); + +trait SealedChannel {} + +/// EXTI channel trait. +#[allow(private_bounds)] +pub trait Channel: SealedChannel + Sized { + /// Get the EXTI channel number. + fn number(&self) -> u8; + + /// Type-erase (degrade) this channel into an `AnyChannel`. + /// + /// This converts EXTI channel singletons (`EXTI0`, `EXTI1`, ...), which + /// are all different types, into the same type. It is useful for + /// creating arrays of channels, or avoiding generics. + fn degrade(self) -> AnyChannel { + AnyChannel { + number: self.number() as u8, + } + } +} + +/// Type-erased (degraded) EXTI channel. +/// +/// This represents ownership over any EXTI channel, known at runtime. +pub struct AnyChannel { + number: u8, +} + +impl_peripheral!(AnyChannel); +impl SealedChannel for AnyChannel {} +impl Channel for AnyChannel { + fn number(&self) -> u8 { + self.number + } +} + +macro_rules! impl_exti { + ($type:ident, $number:expr) => { + impl SealedChannel for peripherals::$type {} + impl Channel for peripherals::$type { + fn number(&self) -> u8 { + $number + } + } + }; +} + +impl_exti!(EXTI0, 0); +impl_exti!(EXTI1, 1); +impl_exti!(EXTI2, 2); +impl_exti!(EXTI3, 3); +impl_exti!(EXTI4, 4); +impl_exti!(EXTI5, 5); +impl_exti!(EXTI6, 6); +impl_exti!(EXTI7, 7); +impl_exti!(EXTI8, 8); +impl_exti!(EXTI9, 9); +impl_exti!(EXTI10, 10); +impl_exti!(EXTI11, 11); +impl_exti!(EXTI12, 12); +impl_exti!(EXTI13, 13); +impl_exti!(EXTI14, 14); +impl_exti!(EXTI15, 15); + +macro_rules! enable_irq { + ($e:ident) => { + crate::interrupt::typelevel::$e::enable(); + }; +} + +/// safety: must be called only once +pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) { + use crate::interrupt::typelevel::Interrupt; + + foreach_exti_irq!(enable_irq); +} diff --git a/embassy/embassy-stm32/src/flash/asynch.rs b/embassy/embassy-stm32/src/flash/asynch.rs new file mode 100644 index 0000000..9468ac6 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/asynch.rs @@ -0,0 +1,201 @@ +use core::marker::PhantomData; +use core::sync::atomic::{fence, Ordering}; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::into_ref; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::mutex::Mutex; + +use super::{ + blocking_read, ensure_sector_aligned, family, get_sector, Async, Error, Flash, FlashLayout, FLASH_BASE, FLASH_SIZE, + WRITE_SIZE, +}; +use crate::interrupt::InterruptExt; +use crate::peripherals::FLASH; +use crate::{interrupt, Peripheral}; + +pub(super) static REGION_ACCESS: Mutex = Mutex::new(()); + +impl<'d> Flash<'d, Async> { + /// Create a new flash driver with async capabilities. + pub fn new( + p: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, + ) -> Self { + into_ref!(p); + + crate::interrupt::FLASH.unpend(); + unsafe { crate::interrupt::FLASH.enable() }; + + Self { + inner: p, + _mode: PhantomData, + } + } + + /// Split this flash driver into one instance per flash memory region. + /// + /// See module-level documentation for details on how memory regions work. + pub fn into_regions(self) -> FlashLayout<'d, Async> { + assert!(family::is_default_layout()); + FlashLayout::new(self.inner) + } + + /// Async write. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + /// For example, to write address `0x0800_1234` you have to use offset `0x1234`. + pub async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + unsafe { write_chunked(FLASH_BASE as u32, FLASH_SIZE as u32, offset, bytes).await } + } + + /// Async erase. + /// + /// NOTE: `from` and `to` are offsets from the flash start, NOT an absolute address. + /// For example, to erase address `0x0801_0000` you have to use offset `0x1_0000`. + pub async fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + unsafe { erase_sectored(FLASH_BASE as u32, from, to).await } + } +} + +/// Interrupt handler +pub struct InterruptHandler; + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + family::on_interrupt(); + } +} + +impl embedded_storage_async::nor_flash::ReadNorFlash for Flash<'_, Async> { + const READ_SIZE: usize = super::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(offset, bytes) + } + + fn capacity(&self) -> usize { + FLASH_SIZE + } +} + +impl embedded_storage_async::nor_flash::NorFlash for Flash<'_, Async> { + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = super::MAX_ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes).await + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to).await + } +} + +pub(super) async unsafe fn write_chunked(base: u32, size: u32, offset: u32, bytes: &[u8]) -> Result<(), Error> { + if offset + bytes.len() as u32 > size { + return Err(Error::Size); + } + if offset % WRITE_SIZE as u32 != 0 || bytes.len() % WRITE_SIZE != 0 { + return Err(Error::Unaligned); + } + + let mut address = base + offset; + trace!("Writing {} bytes at 0x{:x}", bytes.len(), address); + + for chunk in bytes.chunks(WRITE_SIZE) { + family::clear_all_err(); + fence(Ordering::SeqCst); + family::unlock(); + fence(Ordering::SeqCst); + family::enable_write(); + fence(Ordering::SeqCst); + + let _on_drop = OnDrop::new(|| { + family::disable_write(); + fence(Ordering::SeqCst); + family::lock(); + }); + + family::write(address, unwrap!(chunk.try_into())).await?; + address += WRITE_SIZE as u32; + } + Ok(()) +} + +pub(super) async unsafe fn erase_sectored(base: u32, from: u32, to: u32) -> Result<(), Error> { + let start_address = base + from; + let end_address = base + to; + let regions = family::get_flash_regions(); + + ensure_sector_aligned(start_address, end_address, regions)?; + + trace!("Erasing from 0x{:x} to 0x{:x}", start_address, end_address); + + let mut address = start_address; + while address < end_address { + let sector = get_sector(address, regions); + trace!("Erasing sector: {:?}", sector); + + family::clear_all_err(); + fence(Ordering::SeqCst); + family::unlock(); + fence(Ordering::SeqCst); + + let _on_drop = OnDrop::new(|| family::lock()); + + family::erase_sector(§or).await?; + address += sector.size; + } + Ok(()) +} + +foreach_flash_region! { + ($type_name:ident, $write_size:literal, $erase_size:literal) => { + impl crate::_generated::flash_regions::$type_name<'_, Async> { + /// Async read. + /// + /// Note: reading from flash can't actually block, so this is the same as `blocking_read`. + pub async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + blocking_read(self.0.base, self.0.size, offset, bytes) + } + + /// Async write. + pub async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + let _guard = REGION_ACCESS.lock().await; + unsafe { write_chunked(self.0.base, self.0.size, offset, bytes).await } + } + + /// Async erase. + pub async fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + let _guard = REGION_ACCESS.lock().await; + unsafe { erase_sectored(self.0.base, from, to).await } + } + } + + impl embedded_storage_async::nor_flash::ReadNorFlash for crate::_generated::flash_regions::$type_name<'_, Async> { + const READ_SIZE: usize = super::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes).await + } + + fn capacity(&self) -> usize { + self.0.size as usize + } + } + + impl embedded_storage_async::nor_flash::NorFlash for crate::_generated::flash_regions::$type_name<'_, Async> { + const WRITE_SIZE: usize = $write_size; + const ERASE_SIZE: usize = $erase_size; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes).await + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to).await + } + } + }; +} diff --git a/embassy/embassy-stm32/src/flash/common.rs b/embassy/embassy-stm32/src/flash/common.rs new file mode 100644 index 0000000..8ec4bb2 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/common.rs @@ -0,0 +1,306 @@ +use core::marker::PhantomData; +use core::sync::atomic::{fence, Ordering}; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use stm32_metapac::FLASH_BASE; + +use super::{ + family, Async, Blocking, Error, FlashBank, FlashLayout, FlashRegion, FlashSector, FLASH_SIZE, MAX_ERASE_SIZE, + READ_SIZE, WRITE_SIZE, +}; +use crate::peripherals::FLASH; +use crate::Peripheral; + +/// Internal flash memory driver. +pub struct Flash<'d, MODE = Async> { + pub(crate) inner: PeripheralRef<'d, FLASH>, + pub(crate) _mode: PhantomData, +} + +impl<'d> Flash<'d, Blocking> { + /// Create a new flash driver, usable in blocking mode. + pub fn new_blocking(p: impl Peripheral

+ 'd) -> Self { + into_ref!(p); + + Self { + inner: p, + _mode: PhantomData, + } + } +} + +impl<'d, MODE> Flash<'d, MODE> { + /// Split this flash driver into one instance per flash memory region. + /// + /// See module-level documentation for details on how memory regions work. + pub fn into_blocking_regions(self) -> FlashLayout<'d, Blocking> { + assert!(family::is_default_layout()); + FlashLayout::new(self.inner) + } + + /// Blocking read. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + /// For example, to read address `0x0800_1234` you have to use offset `0x1234`. + pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + blocking_read(FLASH_BASE as u32, FLASH_SIZE as u32, offset, bytes) + } + + /// Blocking write. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + /// For example, to write address `0x0800_1234` you have to use offset `0x1234`. + pub fn blocking_write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + unsafe { + blocking_write( + FLASH_BASE as u32, + FLASH_SIZE as u32, + offset, + bytes, + write_chunk_unlocked, + ) + } + } + + /// Blocking erase. + /// + /// NOTE: `from` and `to` are offsets from the flash start, NOT an absolute address. + /// For example, to erase address `0x0801_0000` you have to use offset `0x1_0000`. + pub fn blocking_erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + unsafe { blocking_erase(FLASH_BASE as u32, from, to, erase_sector_unlocked) } + } +} + +pub(super) fn blocking_read(base: u32, size: u32, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + if offset + bytes.len() as u32 > size { + return Err(Error::Size); + } + + let start_address = base + offset; + + #[cfg(flash_f4)] + family::assert_not_corrupted_read(start_address + bytes.len() as u32); + + let flash_data = unsafe { core::slice::from_raw_parts(start_address as *const u8, bytes.len()) }; + bytes.copy_from_slice(flash_data); + Ok(()) +} + +pub(super) unsafe fn blocking_write( + base: u32, + size: u32, + offset: u32, + bytes: &[u8], + write_chunk: unsafe fn(u32, &[u8]) -> Result<(), Error>, +) -> Result<(), Error> { + if offset + bytes.len() as u32 > size { + return Err(Error::Size); + } + if offset % WRITE_SIZE as u32 != 0 || bytes.len() % WRITE_SIZE != 0 { + return Err(Error::Unaligned); + } + + let mut address = base + offset; + trace!("Writing {} bytes at 0x{:x}", bytes.len(), address); + + for chunk in bytes.chunks(WRITE_SIZE) { + write_chunk(address, chunk)?; + address += WRITE_SIZE as u32; + } + Ok(()) +} + +pub(super) unsafe fn write_chunk_unlocked(address: u32, chunk: &[u8]) -> Result<(), Error> { + family::clear_all_err(); + fence(Ordering::SeqCst); + family::unlock(); + fence(Ordering::SeqCst); + family::enable_blocking_write(); + fence(Ordering::SeqCst); + + let _on_drop = OnDrop::new(|| { + family::disable_blocking_write(); + fence(Ordering::SeqCst); + family::lock(); + }); + + family::blocking_write(address, unwrap!(chunk.try_into())) +} + +pub(super) unsafe fn write_chunk_with_critical_section(address: u32, chunk: &[u8]) -> Result<(), Error> { + critical_section::with(|_| write_chunk_unlocked(address, chunk)) +} + +pub(super) unsafe fn blocking_erase( + base: u32, + from: u32, + to: u32, + erase_sector: unsafe fn(&FlashSector) -> Result<(), Error>, +) -> Result<(), Error> { + let start_address = base + from; + let end_address = base + to; + let regions = family::get_flash_regions(); + + ensure_sector_aligned(start_address, end_address, regions)?; + + trace!("Erasing from 0x{:x} to 0x{:x}", start_address, end_address); + + let mut address = start_address; + while address < end_address { + let sector = get_sector(address, regions); + trace!("Erasing sector: {:?}", sector); + erase_sector(§or)?; + address += sector.size; + } + Ok(()) +} + +pub(super) unsafe fn erase_sector_unlocked(sector: &FlashSector) -> Result<(), Error> { + family::clear_all_err(); + fence(Ordering::SeqCst); + family::unlock(); + fence(Ordering::SeqCst); + + let _on_drop = OnDrop::new(|| family::lock()); + + family::blocking_erase_sector(sector) +} + +pub(super) unsafe fn erase_sector_with_critical_section(sector: &FlashSector) -> Result<(), Error> { + critical_section::with(|_| erase_sector_unlocked(sector)) +} + +pub(super) fn get_sector(address: u32, regions: &[&FlashRegion]) -> FlashSector { + let mut current_bank = FlashBank::Bank1; + let mut bank_offset = 0; + for region in regions { + if region.bank != current_bank { + current_bank = region.bank; + bank_offset = 0; + } + + if address >= region.base && address < region.end() { + let index_in_region = (address - region.base) / region.erase_size; + return FlashSector { + bank: region.bank, + index_in_bank: bank_offset + index_in_region as u8, + start: region.base + index_in_region * region.erase_size, + size: region.erase_size, + }; + } + + bank_offset += region.sectors(); + } + + panic!("Flash sector not found"); +} + +pub(super) fn ensure_sector_aligned( + start_address: u32, + end_address: u32, + regions: &[&FlashRegion], +) -> Result<(), Error> { + let mut address = start_address; + while address < end_address { + let sector = get_sector(address, regions); + if sector.start != address { + return Err(Error::Unaligned); + } + address += sector.size; + } + if address != end_address { + return Err(Error::Unaligned); + } + Ok(()) +} + +impl embedded_storage::nor_flash::ErrorType for Flash<'_, MODE> { + type Error = Error; +} + +impl embedded_storage::nor_flash::ReadNorFlash for Flash<'_, MODE> { + const READ_SIZE: usize = READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(offset, bytes) + } + + fn capacity(&self) -> usize { + FLASH_SIZE + } +} + +impl embedded_storage::nor_flash::NorFlash for Flash<'_, MODE> { + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = MAX_ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(offset, bytes) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.blocking_erase(from, to) + } +} + +foreach_flash_region! { + ($type_name:ident, $write_size:literal, $erase_size:literal) => { + impl crate::_generated::flash_regions::$type_name<'_, MODE> { + /// Blocking read. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + /// For example, to read address `0x0800_1234` you have to use offset `0x1234`. + pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + blocking_read(self.0.base, self.0.size, offset, bytes) + } + } + + impl crate::_generated::flash_regions::$type_name<'_, Blocking> { + /// Blocking write. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + /// For example, to write address `0x0800_1234` you have to use offset `0x1234`. + pub fn blocking_write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + unsafe { blocking_write(self.0.base, self.0.size, offset, bytes, write_chunk_with_critical_section) } + } + + /// Blocking erase. + /// + /// NOTE: `from` and `to` are offsets from the flash start, NOT an absolute address. + /// For example, to erase address `0x0801_0000` you have to use offset `0x1_0000`. + pub fn blocking_erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + unsafe { blocking_erase(self.0.base, from, to, erase_sector_with_critical_section) } + } + } + + impl embedded_storage::nor_flash::ErrorType for crate::_generated::flash_regions::$type_name<'_, MODE> { + type Error = Error; + } + + impl embedded_storage::nor_flash::ReadNorFlash for crate::_generated::flash_regions::$type_name<'_, MODE> { + const READ_SIZE: usize = READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(offset, bytes) + } + + fn capacity(&self) -> usize { + self.0.size as usize + } + } + + impl embedded_storage::nor_flash::NorFlash for crate::_generated::flash_regions::$type_name<'_, Blocking> { + const WRITE_SIZE: usize = $write_size; + const ERASE_SIZE: usize = $erase_size; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(offset, bytes) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.blocking_erase(from, to) + } + } + }; +} diff --git a/embassy/embassy-stm32/src/flash/f0.rs b/embassy/embassy-stm32/src/flash/f0.rs new file mode 100644 index 0000000..402312f --- /dev/null +++ b/embassy/embassy-stm32/src/flash/f0.rs @@ -0,0 +1,101 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write_value(0x4567_0123); + pac::FLASH.keyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 2); + + pac::FLASH.cr().write(|w| w.set_pg(true)); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for chunk in buf.chunks(2) { + write_volatile(address as *mut u16, u16::from_le_bytes(unwrap!(chunk.try_into()))); + address += chunk.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } + + wait_ready_blocking() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + pac::FLASH.cr().modify(|w| { + w.set_per(true); + }); + + pac::FLASH.ar().write(|w| w.set_far(sector.start)); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + let mut ret: Result<(), Error> = wait_ready_blocking(); + + if !pac::FLASH.sr().read().eop() { + trace!("FLASH: EOP not set"); + ret = Err(Error::Prog); + } else { + pac::FLASH.sr().write(|w| w.set_eop(true)); + } + + pac::FLASH.cr().modify(|w| w.set_per(false)); + + clear_all_err(); + if ret.is_err() { + return ret; + } + Ok(()) +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + pac::FLASH.sr().modify(|_| {}); +} + +unsafe fn wait_ready_blocking() -> Result<(), Error> { + loop { + let sr = pac::FLASH.sr().read(); + + if !sr.bsy() { + if sr.wrprt() { + return Err(Error::Protected); + } + + if sr.pgerr() { + return Err(Error::Seq); + } + + return Ok(()); + } + } +} diff --git a/embassy/embassy-stm32/src/flash/f1f3.rs b/embassy/embassy-stm32/src/flash/f1f3.rs new file mode 100644 index 0000000..ff7f810 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/f1f3.rs @@ -0,0 +1,111 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write_value(0x4567_0123); + pac::FLASH.keyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 2); + + pac::FLASH.cr().write(|w| w.set_pg(true)); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for chunk in buf.chunks(2) { + write_volatile(address as *mut u16, u16::from_le_bytes(unwrap!(chunk.try_into()))); + address += chunk.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + + wait_ready_blocking()?; + } + + Ok(()) +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + pac::FLASH.cr().modify(|w| { + w.set_per(true); + }); + + pac::FLASH.ar().write(|w| w.set_far(sector.start)); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + // Wait for at least one clock cycle before reading the + // BSY bit, because there is a one-cycle delay between + // setting the STRT bit and the BSY bit being asserted + // by hardware. See STM32F105xx, STM32F107xx device errata, + // section 2.2.8 + #[cfg(stm32f1)] + pac::FLASH.cr().read(); + + let mut ret: Result<(), Error> = wait_ready_blocking(); + + if !pac::FLASH.sr().read().eop() { + trace!("FLASH: EOP not set"); + ret = Err(Error::Prog); + } else { + pac::FLASH.sr().write(|w| w.set_eop(true)); + } + + pac::FLASH.cr().modify(|w| w.set_per(false)); + + clear_all_err(); + if ret.is_err() { + return ret; + } + Ok(()) +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + pac::FLASH.sr().modify(|_| {}); +} + +unsafe fn wait_ready_blocking() -> Result<(), Error> { + loop { + let sr = pac::FLASH.sr().read(); + + if !sr.bsy() { + if sr.wrprterr() { + return Err(Error::Protected); + } + + if sr.pgerr() { + return Err(Error::Seq); + } + + return Ok(()); + } + } +} diff --git a/embassy/embassy-stm32/src/flash/f2.rs b/embassy/embassy-stm32/src/flash/f2.rs new file mode 100644 index 0000000..cdab1fd --- /dev/null +++ b/embassy/embassy-stm32/src/flash/f2.rs @@ -0,0 +1,142 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, AtomicBool, Ordering}; + +use pac::flash::regs::Sr; + +use super::{FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +static DATA_CACHE_WAS_ENABLED: AtomicBool = AtomicBool::new(false); + +impl FlashSector { + const fn snb(&self) -> u8 { + ((self.bank as u8) << 4) + self.index_in_bank + } +} + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write_value(0x4567_0123); + pac::FLASH.keyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + save_data_cache_state(); + + pac::FLASH.cr().write(|w| { + w.set_pg(true); + w.set_psize(pac::flash::vals::Psize::PSIZE32); + }); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); + restore_data_cache_state(); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + write_start(start_address, buf); + blocking_wait_ready() +} + +unsafe fn write_start(start_address: u32, buf: &[u8; WRITE_SIZE]) { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + save_data_cache_state(); + + trace!("Blocking erasing sector number {}", sector.snb()); + + pac::FLASH.cr().modify(|w| { + w.set_ser(true); + w.set_snb(sector.snb()) + }); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + let ret: Result<(), Error> = blocking_wait_ready(); + clear_all_err(); + restore_data_cache_state(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + pac::FLASH.sr().modify(|_| {}); +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.sr().read(); + + if !sr.bsy() { + return get_result(sr); + } + } +} + +fn get_result(sr: Sr) -> Result<(), Error> { + if sr.pgserr() { + Err(Error::Seq) + } else if sr.pgperr() { + Err(Error::Parallelism) + } else if sr.pgaerr() { + Err(Error::Unaligned) + } else if sr.wrperr() { + Err(Error::Protected) + } else { + Ok(()) + } +} + +fn save_data_cache_state() { + let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2; + if dual_bank { + // Disable data cache during write/erase if there are two banks, see errata 2.2.12 + let dcen = pac::FLASH.acr().read().dcen(); + DATA_CACHE_WAS_ENABLED.store(dcen, Ordering::Relaxed); + if dcen { + pac::FLASH.acr().modify(|w| w.set_dcen(false)); + } + } +} + +fn restore_data_cache_state() { + let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2; + if dual_bank { + // Restore data cache if it was enabled + let dcen = DATA_CACHE_WAS_ENABLED.load(Ordering::Relaxed); + if dcen { + // Reset data cache before we enable it again + pac::FLASH.acr().modify(|w| w.set_dcrst(true)); + pac::FLASH.acr().modify(|w| w.set_dcrst(false)); + pac::FLASH.acr().modify(|w| w.set_dcen(true)) + } + } +} diff --git a/embassy/embassy-stm32/src/flash/f4.rs b/embassy/embassy-stm32/src/flash/f4.rs new file mode 100644 index 0000000..d0bb957 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/f4.rs @@ -0,0 +1,555 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, AtomicBool, Ordering}; + +use embassy_sync::waitqueue::AtomicWaker; +use pac::flash::regs::Sr; +use pac::FLASH_SIZE; + +use super::{FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; +#[allow(missing_docs)] // TODO +#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] +mod alt_regions { + use core::marker::PhantomData; + + use embassy_hal_internal::PeripheralRef; + use stm32_metapac::FLASH_SIZE; + + use crate::_generated::flash_regions::{BANK1_REGION1, BANK1_REGION2, BANK1_REGION3}; + use crate::flash::{asynch, Async, Bank1Region1, Bank1Region2, Blocking, Error, Flash, FlashBank, FlashRegion}; + use crate::peripherals::FLASH; + + pub const ALT_BANK1_REGION3: FlashRegion = FlashRegion { + size: 3 * BANK1_REGION3.erase_size, + ..BANK1_REGION3 + }; + pub const ALT_BANK2_REGION1: FlashRegion = FlashRegion { + bank: FlashBank::Bank2, + base: BANK1_REGION1.base + FLASH_SIZE as u32 / 2, + ..BANK1_REGION1 + }; + pub const ALT_BANK2_REGION2: FlashRegion = FlashRegion { + bank: FlashBank::Bank2, + base: BANK1_REGION2.base + FLASH_SIZE as u32 / 2, + ..BANK1_REGION2 + }; + pub const ALT_BANK2_REGION3: FlashRegion = FlashRegion { + bank: FlashBank::Bank2, + base: BANK1_REGION3.base + FLASH_SIZE as u32 / 2, + size: 3 * BANK1_REGION3.erase_size, + ..BANK1_REGION3 + }; + + pub const ALT_FLASH_REGIONS: [&FlashRegion; 6] = [ + &BANK1_REGION1, + &BANK1_REGION2, + &ALT_BANK1_REGION3, + &ALT_BANK2_REGION1, + &ALT_BANK2_REGION2, + &ALT_BANK2_REGION3, + ]; + + pub struct AltBank1Region3<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); + pub struct AltBank2Region1<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); + pub struct AltBank2Region2<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); + pub struct AltBank2Region3<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); + + pub struct AltFlashLayout<'d, MODE = Async> { + pub bank1_region1: Bank1Region1<'d, MODE>, + pub bank1_region2: Bank1Region2<'d, MODE>, + pub bank1_region3: AltBank1Region3<'d, MODE>, + pub bank2_region1: AltBank2Region1<'d, MODE>, + pub bank2_region2: AltBank2Region2<'d, MODE>, + pub bank2_region3: AltBank2Region3<'d, MODE>, + } + + impl<'d> Flash<'d> { + pub fn into_alt_regions(self) -> AltFlashLayout<'d, Async> { + assert!(!super::is_default_layout()); + + // SAFETY: We never expose the cloned peripheral references, and their instance is not public. + // Also, all async flash region operations are protected with a mutex. + let p = self.inner; + AltFlashLayout { + bank1_region1: Bank1Region1(&BANK1_REGION1, unsafe { p.clone_unchecked() }, PhantomData), + bank1_region2: Bank1Region2(&BANK1_REGION2, unsafe { p.clone_unchecked() }, PhantomData), + bank1_region3: AltBank1Region3(&ALT_BANK1_REGION3, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region1: AltBank2Region1(&ALT_BANK2_REGION1, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region2: AltBank2Region2(&ALT_BANK2_REGION2, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region3: AltBank2Region3(&ALT_BANK2_REGION3, unsafe { p.clone_unchecked() }, PhantomData), + } + } + + pub fn into_alt_blocking_regions(self) -> AltFlashLayout<'d, Blocking> { + assert!(!super::is_default_layout()); + + // SAFETY: We never expose the cloned peripheral references, and their instance is not public. + // Also, all blocking flash region operations are protected with a cs. + let p = self.inner; + AltFlashLayout { + bank1_region1: Bank1Region1(&BANK1_REGION1, unsafe { p.clone_unchecked() }, PhantomData), + bank1_region2: Bank1Region2(&BANK1_REGION2, unsafe { p.clone_unchecked() }, PhantomData), + bank1_region3: AltBank1Region3(&ALT_BANK1_REGION3, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region1: AltBank2Region1(&ALT_BANK2_REGION1, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region2: AltBank2Region2(&ALT_BANK2_REGION2, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region3: AltBank2Region3(&ALT_BANK2_REGION3, unsafe { p.clone_unchecked() }, PhantomData), + } + } + } + + macro_rules! foreach_altflash_region { + ($type_name:ident, $region:ident) => { + impl $type_name<'_, MODE> { + pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + crate::flash::common::blocking_read(self.0.base, self.0.size, offset, bytes) + } + } + + impl $type_name<'_, Async> { + pub async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + self.blocking_read(offset, bytes) + } + + pub async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + let _guard = asynch::REGION_ACCESS.lock().await; + unsafe { asynch::write_chunked(self.0.base, self.0.size, offset, bytes).await } + } + + pub async fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + let _guard = asynch::REGION_ACCESS.lock().await; + unsafe { asynch::erase_sectored(self.0.base, from, to).await } + } + } + + impl embedded_storage::nor_flash::ErrorType for $type_name<'_, MODE> { + type Error = Error; + } + + impl embedded_storage::nor_flash::ReadNorFlash for $type_name<'_, MODE> { + const READ_SIZE: usize = crate::flash::READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(offset, bytes) + } + + fn capacity(&self) -> usize { + self.0.size as usize + } + } + + impl embedded_storage_async::nor_flash::ReadNorFlash for $type_name<'_, Async> { + const READ_SIZE: usize = crate::flash::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes).await + } + + fn capacity(&self) -> usize { + self.0.size as usize + } + } + + impl embedded_storage_async::nor_flash::NorFlash for $type_name<'_, Async> { + const WRITE_SIZE: usize = $region.write_size as usize; + const ERASE_SIZE: usize = $region.erase_size as usize; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes).await + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to).await + } + } + }; + } + + foreach_altflash_region!(AltBank1Region3, ALT_BANK1_REGION3); + foreach_altflash_region!(AltBank2Region1, ALT_BANK2_REGION1); + foreach_altflash_region!(AltBank2Region2, ALT_BANK2_REGION2); + foreach_altflash_region!(AltBank2Region3, ALT_BANK2_REGION3); +} + +#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] +pub use alt_regions::*; + +static WAKER: AtomicWaker = AtomicWaker::new(); +static DATA_CACHE_WAS_ENABLED: AtomicBool = AtomicBool::new(false); + +impl FlashSector { + const fn snb(&self) -> u8 { + ((self.bank as u8) << 4) + self.index_in_bank + } +} + +#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] +pub(crate) fn is_default_layout() -> bool { + !pac::FLASH.optcr().read().db1m() +} + +#[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479)))] +pub(crate) const fn is_default_layout() -> bool { + true +} + +#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] +pub fn get_flash_regions() -> &'static [&'static FlashRegion] { + if is_default_layout() { + &FLASH_REGIONS + } else { + &ALT_FLASH_REGIONS + } +} + +#[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479)))] +pub const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn on_interrupt() { + // Clear IRQ flags + pac::FLASH.sr().write(|w| { + w.set_operr(true); + w.set_eop(true); + }); + + WAKER.wake(); +} + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write_value(0x4567_0123); + pac::FLASH.keyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_write() { + assert_eq!(0, WRITE_SIZE % 4); + save_data_cache_state(); + + pac::FLASH.cr().write(|w| { + w.set_pg(true); + w.set_psize(pac::flash::vals::Psize::PSIZE32); + w.set_eopie(true); + w.set_errie(true); + }); +} + +pub(crate) unsafe fn disable_write() { + pac::FLASH.cr().write(|w| { + w.set_pg(false); + w.set_eopie(false); + w.set_errie(false); + }); + restore_data_cache_state(); +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + save_data_cache_state(); + + pac::FLASH.cr().write(|w| { + w.set_pg(true); + w.set_psize(pac::flash::vals::Psize::PSIZE32); + }); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); + restore_data_cache_state(); +} + +pub(crate) async unsafe fn write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + write_start(start_address, buf); + wait_ready().await +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + write_start(start_address, buf); + blocking_wait_ready() +} + +unsafe fn write_start(start_address: u32, buf: &[u8; WRITE_SIZE]) { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } +} + +pub(crate) async unsafe fn erase_sector(sector: &FlashSector) -> Result<(), Error> { + save_data_cache_state(); + + trace!("Erasing sector number {}", sector.snb()); + + pac::FLASH.cr().modify(|w| { + w.set_ser(true); + w.set_snb(sector.snb()); + w.set_eopie(true); + w.set_errie(true); + }); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + let ret: Result<(), Error> = wait_ready().await; + pac::FLASH.cr().modify(|w| { + w.set_eopie(false); + w.set_errie(false); + }); + clear_all_err(); + restore_data_cache_state(); + ret +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + save_data_cache_state(); + + trace!("Blocking erasing sector number {}", sector.snb()); + + pac::FLASH.cr().modify(|w| { + w.set_ser(true); + w.set_snb(sector.snb()) + }); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + let ret: Result<(), Error> = blocking_wait_ready(); + clear_all_err(); + restore_data_cache_state(); + ret +} + +pub(crate) fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + pac::FLASH.sr().modify(|_| {}); +} + +pub(crate) async fn wait_ready() -> Result<(), Error> { + use core::future::poll_fn; + use core::task::Poll; + + poll_fn(|cx| { + WAKER.register(cx.waker()); + + let sr = pac::FLASH.sr().read(); + if !sr.bsy() { + Poll::Ready(get_result(sr)) + } else { + return Poll::Pending; + } + }) + .await +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.sr().read(); + + if !sr.bsy() { + return get_result(sr); + } + } +} + +fn get_result(sr: Sr) -> Result<(), Error> { + if sr.pgserr() { + Err(Error::Seq) + } else if sr.pgperr() { + Err(Error::Parallelism) + } else if sr.pgaerr() { + Err(Error::Unaligned) + } else if sr.wrperr() { + Err(Error::Protected) + } else { + Ok(()) + } +} + +fn save_data_cache_state() { + let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2; + if dual_bank { + // Disable data cache during write/erase if there are two banks, see errata 2.2.12 + let dcen = pac::FLASH.acr().read().dcen(); + DATA_CACHE_WAS_ENABLED.store(dcen, Ordering::Relaxed); + if dcen { + pac::FLASH.acr().modify(|w| w.set_dcen(false)); + } + } +} + +fn restore_data_cache_state() { + let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2; + if dual_bank { + // Restore data cache if it was enabled + let dcen = DATA_CACHE_WAS_ENABLED.load(Ordering::Relaxed); + if dcen { + // Reset data cache before we enable it again + pac::FLASH.acr().modify(|w| w.set_dcrst(true)); + pac::FLASH.acr().modify(|w| w.set_dcrst(false)); + pac::FLASH.acr().modify(|w| w.set_dcen(true)) + } + } +} + +pub(crate) fn assert_not_corrupted_read(end_address: u32) { + #[allow(unused)] + const REVISION_3: u16 = 0x2001; + + #[allow(unused)] + let second_bank_read = + unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2 && end_address > (FLASH_SIZE / 2) as u32; + + #[cfg(any( + feature = "stm32f427ai", + feature = "stm32f427ii", + feature = "stm32f427vi", + feature = "stm32f427zi", + feature = "stm32f429ai", + feature = "stm32f429bi", + feature = "stm32f429ii", + feature = "stm32f429ni", + feature = "stm32f429vi", + feature = "stm32f429zi", + feature = "stm32f437ai", + feature = "stm32f437ii", + feature = "stm32f437vi", + feature = "stm32f437zi", + feature = "stm32f439ai", + feature = "stm32f439bi", + feature = "stm32f439ii", + feature = "stm32f439ni", + feature = "stm32f439vi", + feature = "stm32f439zi", + ))] + if second_bank_read && pac::DBGMCU.idcode().read().rev_id() < REVISION_3 && !pa12_is_output_pull_low() { + panic!("Read corruption for stm32f42xxI and stm32f43xxI when PA12 is in use for chips below revision 3, see errata 2.2.11"); + } + + #[cfg(any( + feature = "stm32f427ag", + feature = "stm32f427ig", + feature = "stm32f427vg", + feature = "stm32f427zg", + feature = "stm32f429ag", + feature = "stm32f429bg", + feature = "stm32f429ig", + feature = "stm32f429ng", + feature = "stm32f429vg", + feature = "stm32f429zg", + feature = "stm32f437ig", + feature = "stm32f437vg", + feature = "stm32f437zg", + feature = "stm32f439bg", + feature = "stm32f439ig", + feature = "stm32f439ng", + feature = "stm32f439vg", + feature = "stm32f439zg", + ))] + if second_bank_read && pac::DBGMCU.idcode().read().rev_id() < REVISION_3 && !pa12_is_output_pull_low() { + panic!("Read corruption for stm32f42xxG and stm32f43xxG in dual bank mode when PA12 is in use for chips below revision 3, see errata 2.2.11"); + } +} + +#[allow(unused)] +fn pa12_is_output_pull_low() -> bool { + use pac::gpio::vals; + use pac::GPIOA; + const PIN: usize = 12; + GPIOA.moder().read().moder(PIN) == vals::Moder::OUTPUT + && GPIOA.pupdr().read().pupdr(PIN) == vals::Pupdr::PULLDOWN + && GPIOA.odr().read().odr(PIN) == vals::Odr::LOW +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::flash::{get_sector, FlashBank}; + + #[test] + #[cfg(stm32f429)] + fn can_get_sector() { + const SMALL_SECTOR_SIZE: u32 = 16 * 1024; + const MEDIUM_SECTOR_SIZE: u32 = 64 * 1024; + const LARGE_SECTOR_SIZE: u32 = 128 * 1024; + + let assert_sector = |snb: u8, index_in_bank: u8, start: u32, size: u32, address: u32| { + let sector = get_sector(address, &FLASH_REGIONS); + assert_eq!(snb, sector.snb()); + assert_eq!( + FlashSector { + bank: FlashBank::Bank1, + index_in_bank, + start, + size + }, + sector + ); + }; + + assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); + assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); + assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); + + assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); + assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); + + assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); + assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); + assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); + assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + + let assert_sector = |snb: u8, bank: FlashBank, index_in_bank: u8, start: u32, size: u32, address: u32| { + let sector = get_sector(address, &ALT_FLASH_REGIONS); + assert_eq!(snb, sector.snb()); + assert_eq!( + FlashSector { + bank, + index_in_bank, + start, + size + }, + sector + ) + }; + + assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); + assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); + assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); + + assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); + assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); + + assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); + assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); + assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0806_0000); + assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0807_FFFF); + + assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_0000); + assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_3FFF); + assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_C000); + assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_FFFF); + + assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_0000); + assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_FFFF); + + assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080A_0000); + assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080B_FFFF); + assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); + assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + } +} diff --git a/embassy/embassy-stm32/src/flash/f7.rs b/embassy/embassy-stm32/src/flash/f7.rs new file mode 100644 index 0000000..09ebe9d --- /dev/null +++ b/embassy/embassy-stm32/src/flash/f7.rs @@ -0,0 +1,171 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write_value(0x4567_0123); + pac::FLASH.keyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + + pac::FLASH.cr().write(|w| { + w.set_pg(true); + w.set_psize(pac::flash::vals::Psize::PSIZE32); + }); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } + + blocking_wait_ready() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + pac::FLASH.cr().modify(|w| { + w.set_ser(true); + w.set_snb(sector.index_in_bank) + }); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + let ret: Result<(), Error> = blocking_wait_ready(); + pac::FLASH.cr().modify(|w| w.set_ser(false)); + clear_all_err(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + pac::FLASH.sr().modify(|_| {}); +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.sr().read(); + + if !sr.bsy() { + if sr.erserr() { + return Err(Error::Seq); + } + + if sr.pgperr() { + return Err(Error::Parallelism); + } + + if sr.pgaerr() { + return Err(Error::Unaligned); + } + + if sr.wrperr() { + return Err(Error::Protected); + } + + return Ok(()); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::flash::{get_sector, FlashBank}; + + #[test] + #[cfg(stm32f732)] + fn can_get_sector() { + const SMALL_SECTOR_SIZE: u32 = 16 * 1024; + const MEDIUM_SECTOR_SIZE: u32 = 64 * 1024; + const LARGE_SECTOR_SIZE: u32 = 128 * 1024; + + let assert_sector = |index_in_bank: u8, start: u32, size: u32, address: u32| { + assert_eq!( + FlashSector { + bank: FlashBank::Bank1, + index_in_bank, + start, + size + }, + get_sector(address, &FLASH_REGIONS) + ) + }; + + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); + assert_sector(3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); + assert_sector(3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); + + assert_sector(4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); + assert_sector(4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); + + assert_sector(5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); + assert_sector(5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); + assert_sector(7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0806_0000); + assert_sector(7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0807_FFFF); + } + + #[test] + #[cfg(stm32f769)] + fn can_get_sector() { + const SMALL_SECTOR_SIZE: u32 = 32 * 1024; + const MEDIUM_SECTOR_SIZE: u32 = 128 * 1024; + const LARGE_SECTOR_SIZE: u32 = 256 * 1024; + + let assert_sector = |index_in_bank: u8, start: u32, size: u32, address: u32| { + assert_eq!( + FlashSector { + bank: FlashBank::Bank1, + index_in_bank, + start, + size + }, + get_sector(address, &FLASH_REGIONS) + ) + }; + + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_7FFF); + assert_sector(3, 0x0801_8000, SMALL_SECTOR_SIZE, 0x0801_8000); + assert_sector(3, 0x0801_8000, SMALL_SECTOR_SIZE, 0x0801_FFFF); + + assert_sector(4, 0x0802_0000, MEDIUM_SECTOR_SIZE, 0x0802_0000); + assert_sector(4, 0x0802_0000, MEDIUM_SECTOR_SIZE, 0x0803_FFFF); + + assert_sector(5, 0x0804_0000, LARGE_SECTOR_SIZE, 0x0804_0000); + assert_sector(5, 0x0804_0000, LARGE_SECTOR_SIZE, 0x0807_FFFF); + assert_sector(7, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080C_0000); + assert_sector(7, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + } +} diff --git a/embassy/embassy-stm32/src/flash/g.rs b/embassy/embassy-stm32/src/flash/g.rs new file mode 100644 index 0000000..01a0c60 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/g.rs @@ -0,0 +1,96 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use cortex_m::interrupt; + +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} +pub(crate) unsafe fn unlock() { + // Wait, while the memory interface is busy. + while pac::FLASH.sr().read().bsy() {} + + // Unlock flash + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write_value(0x4567_0123); + pac::FLASH.keyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + pac::FLASH.cr().write(|w| w.set_pg(true)); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } + + wait_ready_blocking() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; + while pac::FLASH.sr().read().bsy() {} + clear_all_err(); + + interrupt::free(|_| { + pac::FLASH.cr().modify(|w| { + w.set_per(true); + w.set_pnb(idx as u8); + w.set_strt(true); + }); + }); + + let ret: Result<(), Error> = wait_ready_blocking(); + pac::FLASH.cr().modify(|w| w.set_per(false)); + ret +} + +pub(crate) unsafe fn wait_ready_blocking() -> Result<(), Error> { + while pac::FLASH.sr().read().bsy() {} + + let sr = pac::FLASH.sr().read(); + + if sr.progerr() { + return Err(Error::Prog); + } + + if sr.wrperr() { + return Err(Error::Protected); + } + + if sr.pgaerr() { + return Err(Error::Unaligned); + } + + Ok(()) +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + pac::FLASH.sr().modify(|_| {}); +} diff --git a/embassy/embassy-stm32/src/flash/h5.rs b/embassy/embassy-stm32/src/flash/h5.rs new file mode 100644 index 0000000..9e131ca --- /dev/null +++ b/embassy/embassy-stm32/src/flash/h5.rs @@ -0,0 +1,177 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +// const fn is_dual_bank() -> bool { +// FLASH_REGIONS.len() >= 2 +// } + +pub(crate) fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + if !pac::FLASH.nscr().read().lock() { + pac::FLASH.nscr().modify(|r| { + r.set_lock(true); + }); + } +} + +pub(crate) unsafe fn unlock() { + // TODO: check locked first + while pac::FLASH.nssr().read().bsy() { + #[cfg(feature = "defmt")] + defmt::trace!("busy") + } + + // only unlock if locked to begin with + if pac::FLASH.nscr().read().lock() { + pac::FLASH.nskeyr().write_value(0x4567_0123); + pac::FLASH.nskeyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); +} + +pub(crate) unsafe fn disable_blocking_write() {} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + // // We cannot have the write setup sequence in begin_write as it depends on the address + // let bank = if start_address < BANK1_REGION.end() { + // pac::FLASH.bank(0) + // } else { + // pac::FLASH.bank(1) + // }; + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + clear_all_err(); + + pac::FLASH.nscr().write(|w| { + w.set_pg(true); + // w.set_psize(2); // 32 bits at once + }); + + let mut res = None; + let mut address = start_address; + // TODO: see write size + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + res = Some(blocking_wait_ready().map_err(|e| { + error!("write err"); + e + })); + pac::FLASH.nssr().modify(|w| { + if w.eop() { + w.set_eop(true); + } + }); + if unwrap!(res).is_err() { + break; + } + } + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + pac::FLASH.nscr().write(|w| w.set_pg(false)); + + unwrap!(res) +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + // pac::FLASH.wrp2r_cur().read().wrpsg() + // TODO: write protection check + if pac::FLASH.nscr().read().lock() == true { + error!("flash locked"); + } + + loop { + let sr = pac::FLASH.nssr().read(); + if !sr.bsy() && !sr.dbne() { + break; + } + } + clear_all_err(); + + pac::FLASH.nscr().modify(|r| { + // TODO: later check bank swap + r.set_bksel(match sector.bank { + crate::flash::FlashBank::Bank1 => stm32_metapac::flash::vals::NscrBksel::B_0X0, + crate::flash::FlashBank::Bank2 => stm32_metapac::flash::vals::NscrBksel::B_0X1, + }); + r.set_snb(sector.index_in_bank); + r.set_ser(true); + }); + + pac::FLASH.nscr().modify(|r| { + r.set_strt(true); + }); + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + let ret: Result<(), Error> = blocking_wait_ready().map_err(|e| { + error!("erase err"); + e + }); + + pac::FLASH.nscr().modify(|w| w.set_ser(false)); + clear_all_err(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + pac::FLASH.nssr().modify(|_| {}) +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.nssr().read(); + + if !sr.bsy() { + if sr.optchangeerr() { + error!("optchangeerr"); + return Err(Error::Prog); + } + if sr.obkwerr() { + error!("obkwerr"); + return Err(Error::Seq); + } + if sr.obkerr() { + error!("obkerr"); + return Err(Error::Seq); + } + if sr.incerr() { + error!("incerr"); + return Err(Error::Unaligned); + } + if sr.strberr() { + error!("strberr"); + return Err(Error::Parallelism); + } + if sr.wrperr() { + error!("protected"); + return Err(Error::Protected); + } + + return Ok(()); + } + } +} diff --git a/embassy/embassy-stm32/src/flash/h50.rs b/embassy/embassy-stm32/src/flash/h50.rs new file mode 100644 index 0000000..82e77d1 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/h50.rs @@ -0,0 +1,166 @@ +/// STM32H50 series flash impl. See RM0492 +use core::{ + ptr::write_volatile, + sync::atomic::{fence, Ordering}, +}; + +use cortex_m::interrupt; +use pac::flash::regs::Nssr; +use pac::flash::vals::Bksel; + +use super::{Error, FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.nscr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + while busy() {} + + if pac::FLASH.nscr().read().lock() { + pac::FLASH.nskeyr().write_value(0x4567_0123); + pac::FLASH.nskeyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + pac::FLASH.nscr().write(|w| w.set_pg(true)); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.nscr().write(|w| w.set_pg(false)); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } + + wait_ready_blocking() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + assert!(sector.index_in_bank < 8); + + while busy() {} + + interrupt::free(|_| { + pac::FLASH.nscr().modify(|w| { + // BKSEL ignores SWAP_BANK, so we must take it into account here + w.set_bksel(match (sector.bank, banks_swapped()) { + (FlashBank::Bank1, false) => Bksel::BANK1, + (FlashBank::Bank2, true) => Bksel::BANK1, + (FlashBank::Bank2, false) => Bksel::BANK2, + (FlashBank::Bank1, true) => Bksel::BANK2, + }); + w.set_snb(sector.index_in_bank); + w.set_ser(true); + w.set_strt(true); + }) + }); + + let ret = wait_ready_blocking(); + pac::FLASH.nscr().modify(|w| w.set_ser(false)); + ret +} + +pub(crate) unsafe fn wait_ready_blocking() -> Result<(), Error> { + loop { + let sr = pac::FLASH.nssr().read(); + + if !sr_busy(sr) { + if sr.wrperr() { + return Err(Error::Protected); + } + if sr.pgserr() { + return Err(Error::Seq); + } + if sr.strberr() { + // writing several times to the same byte in the write buffer + return Err(Error::Prog); + } + if sr.incerr() { + // attempting write operation before completion of previous + // write operation + return Err(Error::Seq); + } + + return Ok(()); + } + } +} + +pub(crate) unsafe fn clear_all_err() { + pac::FLASH.nsccr().modify(|w| { + w.set_clr_wrperr(true); + w.set_clr_pgserr(true); + w.set_clr_strberr(true); + w.set_clr_incerr(true); + }) +} + +/// Get the current SWAP_BANK option. +/// +/// This value is only loaded on system or power-on reset. `perform_bank_swap()` +/// will not reflect here. +pub fn banks_swapped() -> bool { + pac::FLASH.optcr().read().swap_bank() +} + +/// Logical, persistent swap of flash banks 1 and 2. +/// +/// This allows the application to write a new firmware blob into bank 2, then +/// swap the banks and perform a reset, loading the new firmware. +/// +/// Swap does not take effect until system or power-on reset. +/// +/// PLEASE READ THE REFERENCE MANUAL - there are nuances to this feature. For +/// instance, erase commands and interrupt enables which take a flash bank as a +/// parameter ignore the swap! +pub fn perform_bank_swap() { + while busy() {} + + unsafe { + clear_all_err(); + } + + // unlock OPTLOCK + pac::FLASH.optkeyr().write(|w| *w = 0x0819_2A3B); + pac::FLASH.optkeyr().write(|w| *w = 0x4C5D_6E7F); + while pac::FLASH.optcr().read().optlock() {} + + // toggle SWAP_BANK option + pac::FLASH.optsr_prg().modify(|w| w.set_swap_bank(!banks_swapped())); + + // load option bytes + pac::FLASH.optcr().modify(|w| w.set_optstrt(true)); + while pac::FLASH.optcr().read().optstrt() {} + + // re-lock OPTLOCK + pac::FLASH.optcr().modify(|w| w.set_optlock(true)); +} + +fn sr_busy(sr: Nssr) -> bool { + // Note: RM0492 sometimes incorrectly refers to WBNE as NSWBNE + sr.bsy() || sr.dbne() || sr.wbne() +} + +fn busy() -> bool { + let sr = pac::FLASH.nssr().read(); + sr_busy(sr) +} diff --git a/embassy/embassy-stm32/src/flash/h7.rs b/embassy/embassy-stm32/src/flash/h7.rs new file mode 100644 index 0000000..2549153 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/h7.rs @@ -0,0 +1,165 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use super::{FlashRegion, FlashSector, BANK1_REGION, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +const fn is_dual_bank() -> bool { + FLASH_REGIONS.len() >= 2 +} + +pub(crate) fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.bank(0).cr().modify(|w| w.set_lock(true)); + if is_dual_bank() { + pac::FLASH.bank(1).cr().modify(|w| w.set_lock(true)); + } +} + +pub(crate) unsafe fn unlock() { + if pac::FLASH.bank(0).cr().read().lock() { + pac::FLASH.bank(0).keyr().write_value(0x4567_0123); + pac::FLASH.bank(0).keyr().write_value(0xCDEF_89AB); + } + if is_dual_bank() { + if pac::FLASH.bank(1).cr().read().lock() { + pac::FLASH.bank(1).keyr().write_value(0x4567_0123); + pac::FLASH.bank(1).keyr().write_value(0xCDEF_89AB); + } + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); +} + +pub(crate) unsafe fn disable_blocking_write() {} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + // We cannot have the write setup sequence in begin_write as it depends on the address + let bank = if start_address < BANK1_REGION.end() { + pac::FLASH.bank(0) + } else { + pac::FLASH.bank(1) + }; + bank.cr().write(|w| { + w.set_pg(true); + #[cfg(flash_h7)] + w.set_psize(2); // 32 bits at once + }); + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + let mut res = None; + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + res = Some(blocking_wait_ready(bank)); + bank.sr().modify(|w| { + if w.eop() { + w.set_eop(true); + } + }); + if unwrap!(res).is_err() { + break; + } + } + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + bank.cr().write(|w| w.set_pg(false)); + + unwrap!(res) +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + let bank = pac::FLASH.bank(sector.bank as usize); + bank.cr().modify(|w| { + w.set_ser(true); + #[cfg(flash_h7)] + w.set_snb(sector.index_in_bank); + #[cfg(flash_h7ab)] + w.set_ssn(sector.index_in_bank); + }); + + bank.cr().modify(|w| { + w.set_start(true); + }); + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + let ret: Result<(), Error> = blocking_wait_ready(bank); + bank.cr().modify(|w| w.set_ser(false)); + bank_clear_all_err(bank); + ret +} + +pub(crate) unsafe fn clear_all_err() { + bank_clear_all_err(pac::FLASH.bank(0)); + bank_clear_all_err(pac::FLASH.bank(1)); +} + +unsafe fn bank_clear_all_err(bank: pac::flash::Bank) { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + bank.sr().modify(|_| {}); +} + +unsafe fn blocking_wait_ready(bank: pac::flash::Bank) -> Result<(), Error> { + loop { + let sr = bank.sr().read(); + + if !sr.bsy() && !sr.qw() { + if sr.wrperr() { + return Err(Error::Protected); + } + if sr.pgserr() { + error!("pgserr"); + return Err(Error::Seq); + } + if sr.incerr() { + // writing to a different address when programming 256 bit word was not finished + error!("incerr"); + return Err(Error::Seq); + } + if sr.crcrderr() { + error!("crcrderr"); + return Err(Error::Seq); + } + if sr.operr() { + return Err(Error::Prog); + } + if sr.sneccerr1() { + // single ECC error + return Err(Error::Prog); + } + if sr.dbeccerr() { + // double ECC error + return Err(Error::Prog); + } + if sr.rdperr() { + return Err(Error::Protected); + } + if sr.rdserr() { + return Err(Error::Protected); + } + + return Ok(()); + } + } +} diff --git a/embassy/embassy-stm32/src/flash/l.rs b/embassy/embassy-stm32/src/flash/l.rs new file mode 100644 index 0000000..ea00bf4 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/l.rs @@ -0,0 +1,240 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + #[cfg(any(flash_wl, flash_wb, flash_l4))] + pac::FLASH.cr().modify(|w| w.set_lock(true)); + + #[cfg(any(flash_l0))] + pac::FLASH.pecr().modify(|w| { + w.set_optlock(true); + w.set_prglock(true); + w.set_pelock(true); + }); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().modify(|w| w.set_nslock(true)); +} + +pub(crate) unsafe fn unlock() { + #[cfg(any(flash_wl, flash_wb, flash_l4))] + { + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write_value(0x4567_0123); + pac::FLASH.keyr().write_value(0xCDEF_89AB); + } + } + + #[cfg(any(flash_l0, flash_l1))] + { + if pac::FLASH.pecr().read().pelock() { + pac::FLASH.pekeyr().write_value(0x89AB_CDEF); + pac::FLASH.pekeyr().write_value(0x0203_0405); + } + + if pac::FLASH.pecr().read().prglock() { + pac::FLASH.prgkeyr().write_value(0x8C9D_AEBF); + pac::FLASH.prgkeyr().write_value(0x1314_1516); + } + } + + #[cfg(any(flash_l5))] + { + if pac::FLASH.nscr().read().nslock() { + pac::FLASH.nskeyr().write_value(0x4567_0123); + pac::FLASH.nskeyr().write_value(0xCDEF_89AB); + } + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + pac::FLASH.cr().write(|w| w.set_pg(true)); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().write(|w| w.set_nspg(true)); +} + +pub(crate) unsafe fn disable_blocking_write() { + #[cfg(any(flash_wl, flash_wb, flash_l4))] + pac::FLASH.cr().write(|w| w.set_pg(false)); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().write(|w| w.set_nspg(false)); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } + + wait_ready_blocking() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + #[cfg(any(flash_l0, flash_l1))] + { + pac::FLASH.pecr().modify(|w| { + w.set_erase(true); + w.set_prog(true); + }); + + write_volatile(sector.start as *mut u32, 0xFFFFFFFF); + } + + #[cfg(any(flash_wl, flash_wb, flash_l4, flash_l5))] + { + let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; + + #[cfg(flash_l4)] + let (idx, bank) = if idx > 255 { (idx - 256, true) } else { (idx, false) }; + + #[cfg(flash_l5)] + let (idx, bank) = if pac::FLASH.optr().read().dbank() { + if idx > 255 { + (idx - 256, Some(true)) + } else { + (idx, Some(false)) + } + } else { + (idx, None) + }; + + #[cfg(not(flash_l5))] + pac::FLASH.cr().modify(|w| { + w.set_per(true); + w.set_pnb(idx as u8); + #[cfg(any(flash_wl, flash_wb))] + w.set_strt(true); + #[cfg(any(flash_l4))] + w.set_start(true); + #[cfg(any(flash_l4))] + w.set_bker(bank); + }); + + #[cfg(flash_l5)] + pac::FLASH.nscr().modify(|w| { + w.set_nsper(true); + w.set_nspnb(idx as u8); + if let Some(bank) = bank { + w.set_nsbker(bank); + } + w.set_nsstrt(true); + }); + } + + let ret: Result<(), Error> = wait_ready_blocking(); + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + pac::FLASH.cr().modify(|w| w.set_per(false)); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().modify(|w| w.set_nsper(false)); + + #[cfg(any(flash_l0, flash_l1))] + pac::FLASH.pecr().modify(|w| { + w.set_erase(false); + w.set_prog(false); + }); + + clear_all_err(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + #[cfg(not(flash_l5))] + pac::FLASH.sr().modify(|_| {}); + + #[cfg(flash_l5)] + pac::FLASH.nssr().modify(|_| {}); +} + +unsafe fn wait_ready_blocking() -> Result<(), Error> { + loop { + #[cfg(not(flash_l5))] + { + let sr = pac::FLASH.sr().read(); + + if !sr.bsy() { + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.progerr() { + return Err(Error::Prog); + } + + if sr.wrperr() { + return Err(Error::Protected); + } + + if sr.pgaerr() { + return Err(Error::Unaligned); + } + + if sr.sizerr() { + return Err(Error::Size); + } + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.miserr() { + return Err(Error::Miss); + } + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.pgserr() { + return Err(Error::Seq); + } + + return Ok(()); + } + } + + #[cfg(flash_l5)] + { + let nssr = pac::FLASH.nssr().read(); + + if !nssr.nsbsy() { + if nssr.nsprogerr() { + return Err(Error::Prog); + } + + if nssr.nswrperr() { + return Err(Error::Protected); + } + + if nssr.nspgaerr() { + return Err(Error::Unaligned); + } + + if nssr.nssizerr() { + return Err(Error::Size); + } + + if nssr.nspgserr() { + return Err(Error::Seq); + } + + return Ok(()); + } + } + } +} diff --git a/embassy/embassy-stm32/src/flash/mod.rs b/embassy/embassy-stm32/src/flash/mod.rs new file mode 100644 index 0000000..88fe6a2 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/mod.rs @@ -0,0 +1,144 @@ +//! Flash memory (FLASH) +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; + +#[cfg(flash_f4)] +mod asynch; +#[cfg(flash)] +mod common; + +#[cfg(flash_f4)] +pub use asynch::InterruptHandler; +#[cfg(flash)] +pub use common::*; + +pub use crate::_generated::flash_regions::*; +pub use crate::_generated::MAX_ERASE_SIZE; +pub use crate::pac::{FLASH_BASE, FLASH_SIZE, WRITE_SIZE}; + +/// Get whether the default flash layout is being used. +/// +/// In some chips, dual-bank is not default. This will then return `false` +/// when dual-bank is enabled. +pub fn is_default_layout() -> bool { + family::is_default_layout() +} + +/// Get all flash regions. +pub fn get_flash_regions() -> &'static [&'static FlashRegion] { + family::get_flash_regions() +} + +/// Read size (always 1) +pub const READ_SIZE: usize = 1; + +/// Blocking flash mode typestate. +pub enum Blocking {} +/// Async flash mode typestate. +pub enum Async {} + +/// Flash memory region +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FlashRegion { + /// Bank number. + pub bank: FlashBank, + /// Absolute base address. + pub base: u32, + /// Size in bytes. + pub size: u32, + /// Erase size (sector size). + pub erase_size: u32, + /// Minimum write size. + pub write_size: u32, + /// Erase value (usually `0xFF`, but is `0x00` in some chips) + pub erase_value: u8, + pub(crate) _ensure_internal: (), +} + +impl FlashRegion { + /// Absolute end address. + pub const fn end(&self) -> u32 { + self.base + self.size + } + + /// Number of sectors in the region. + pub const fn sectors(&self) -> u8 { + (self.size / self.erase_size) as u8 + } +} + +/// Flash sector. +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FlashSector { + /// Bank number. + pub bank: FlashBank, + /// Sector number within the bank. + pub index_in_bank: u8, + /// Absolute start address. + pub start: u32, + /// Size in bytes. + pub size: u32, +} + +/// Flash bank. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FlashBank { + /// Bank 1 + Bank1 = 0, + /// Bank 2 + Bank2 = 1, +} + +#[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb), path = "l.rs")] +#[cfg_attr(flash_f0, path = "f0.rs")] +#[cfg_attr(any(flash_f1, flash_f3), path = "f1f3.rs")] +#[cfg_attr(flash_f2, path = "f2.rs")] +#[cfg_attr(flash_f4, path = "f4.rs")] +#[cfg_attr(flash_f7, path = "f7.rs")] +#[cfg_attr(any(flash_g0, flash_g4c2, flash_g4c3, flash_g4c4), path = "g.rs")] +#[cfg_attr(flash_h7, path = "h7.rs")] +#[cfg_attr(flash_h7ab, path = "h7.rs")] +#[cfg_attr(flash_u5, path = "u5.rs")] +#[cfg_attr(flash_h5, path = "h5.rs")] +#[cfg_attr(flash_h50, path = "h50.rs")] +#[cfg_attr(flash_u0, path = "u0.rs")] +#[cfg_attr( + not(any( + flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb, flash_f0, flash_f1, flash_f2, flash_f3, flash_f4, + flash_f7, flash_g0, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0, + flash_h5, + )), + path = "other.rs" +)] +mod family; + +#[allow(unused_imports)] +pub use family::*; + +/// Flash error +/// +/// See STM32 Reference Manual for your chip for details. +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + Prog, + Size, + Miss, + Seq, + Protected, + Unaligned, + Parallelism, +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + match self { + Self::Size => NorFlashErrorKind::OutOfBounds, + Self::Unaligned => NorFlashErrorKind::NotAligned, + _ => NorFlashErrorKind::Other, + } + } +} diff --git a/embassy/embassy-stm32/src/flash/other.rs b/embassy/embassy-stm32/src/flash/other.rs new file mode 100644 index 0000000..20f84a7 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/other.rs @@ -0,0 +1,33 @@ +#![allow(unused)] + +use super::{Error, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + unimplemented!(); +} +pub(crate) unsafe fn unlock() { + unimplemented!(); +} +pub(crate) unsafe fn enable_blocking_write() { + unimplemented!(); +} +pub(crate) unsafe fn disable_blocking_write() { + unimplemented!(); +} +pub(crate) unsafe fn blocking_write(_start_address: u32, _buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + unimplemented!(); +} +pub(crate) unsafe fn blocking_erase_sector(_sector: &FlashSector) -> Result<(), Error> { + unimplemented!(); +} +pub(crate) unsafe fn clear_all_err() { + unimplemented!(); +} diff --git a/embassy/embassy-stm32/src/flash/u0.rs b/embassy/embassy-stm32/src/flash/u0.rs new file mode 100644 index 0000000..bfdbd15 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/u0.rs @@ -0,0 +1,96 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use cortex_m::interrupt; + +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} +pub(crate) unsafe fn unlock() { + // Wait, while the memory interface is busy. + while pac::FLASH.sr().read().bsy1() {} + + // Unlock flash + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write(|w| w.set_key(0x4567_0123)); + pac::FLASH.keyr().write(|w| w.set_key(0xCDEF_89AB)); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + pac::FLASH.cr().write(|w| w.set_pg(true)); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } + + wait_ready_blocking() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; + while pac::FLASH.sr().read().bsy1() {} + clear_all_err(); + + interrupt::free(|_| { + pac::FLASH.cr().modify(|w| { + w.set_per(true); + w.set_pnb(idx as u8); + w.set_strt(true); + }); + }); + + let ret: Result<(), Error> = wait_ready_blocking(); + pac::FLASH.cr().modify(|w| w.set_per(false)); + ret +} + +pub(crate) unsafe fn wait_ready_blocking() -> Result<(), Error> { + while pac::FLASH.sr().read().bsy1() {} + + let sr = pac::FLASH.sr().read(); + + if sr.progerr() { + return Err(Error::Prog); + } + + if sr.wrperr() { + return Err(Error::Protected); + } + + if sr.pgaerr() { + return Err(Error::Unaligned); + } + + Ok(()) +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + pac::FLASH.sr().modify(|_| {}); +} diff --git a/embassy/embassy-stm32/src/flash/u5.rs b/embassy/embassy-stm32/src/flash/u5.rs new file mode 100644 index 0000000..e5af4f1 --- /dev/null +++ b/embassy/embassy-stm32/src/flash/u5.rs @@ -0,0 +1,153 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use super::{FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + #[cfg(feature = "trustzone-secure")] + pac::FLASH.seccr().modify(|w| w.set_lock(true)); + #[cfg(not(feature = "trustzone-secure"))] + pac::FLASH.nscr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + #[cfg(feature = "trustzone-secure")] + if pac::FLASH.seccr().read().lock() { + pac::FLASH.seckeyr().write_value(0x4567_0123); + pac::FLASH.seckeyr().write_value(0xCDEF_89AB); + } + #[cfg(not(feature = "trustzone-secure"))] + if pac::FLASH.nscr().read().lock() { + pac::FLASH.nskeyr().write_value(0x4567_0123); + pac::FLASH.nskeyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + + #[cfg(feature = "trustzone-secure")] + pac::FLASH.seccr().write(|w| { + w.set_pg(pac::flash::vals::SeccrPg::B_0X1); + }); + #[cfg(not(feature = "trustzone-secure"))] + pac::FLASH.nscr().write(|w| { + w.set_pg(pac::flash::vals::NscrPg::B_0X1); + }); +} + +pub(crate) unsafe fn disable_blocking_write() { + #[cfg(feature = "trustzone-secure")] + pac::FLASH.seccr().write(|w| w.set_pg(pac::flash::vals::SeccrPg::B_0X0)); + #[cfg(not(feature = "trustzone-secure"))] + pac::FLASH.nscr().write(|w| w.set_pg(pac::flash::vals::NscrPg::B_0X0)); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } + + blocking_wait_ready() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + #[cfg(feature = "trustzone-secure")] + pac::FLASH.seccr().modify(|w| { + w.set_per(pac::flash::vals::SeccrPer::B_0X1); + w.set_pnb(sector.index_in_bank); + // TODO: add check for bank swap + w.set_bker(match sector.bank { + FlashBank::Bank1 => pac::flash::vals::SeccrBker::B_0X0, + FlashBank::Bank2 => pac::flash::vals::SeccrBker::B_0X1, + }); + }); + #[cfg(not(feature = "trustzone-secure"))] + pac::FLASH.nscr().modify(|w| { + w.set_per(pac::flash::vals::NscrPer::B_0X1); + w.set_pnb(sector.index_in_bank); + // TODO: add check for bank swap + w.set_bker(match sector.bank { + FlashBank::Bank1 => pac::flash::vals::NscrBker::B_0X0, + FlashBank::Bank2 => pac::flash::vals::NscrBker::B_0X1, + }); + }); + + #[cfg(feature = "trustzone-secure")] + pac::FLASH.seccr().modify(|w| { + w.set_strt(true); + }); + #[cfg(not(feature = "trustzone-secure"))] + pac::FLASH.nscr().modify(|w| { + w.set_strt(true); + }); + + let ret: Result<(), Error> = blocking_wait_ready(); + #[cfg(feature = "trustzone-secure")] + pac::FLASH + .seccr() + .modify(|w| w.set_per(pac::flash::vals::SeccrPer::B_0X0)); + #[cfg(not(feature = "trustzone-secure"))] + pac::FLASH + .nscr() + .modify(|w| w.set_per(pac::flash::vals::NscrPer::B_0X0)); + clear_all_err(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + #[cfg(feature = "trustzone-secure")] + pac::FLASH.secsr().modify(|_| {}); + #[cfg(not(feature = "trustzone-secure"))] + pac::FLASH.nssr().modify(|_| {}); +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + #[cfg(feature = "trustzone-secure")] + let sr = pac::FLASH.secsr().read(); + #[cfg(not(feature = "trustzone-secure"))] + let sr = pac::FLASH.nssr().read(); + + if !sr.bsy() { + if sr.pgserr() { + return Err(Error::Seq); + } + + if sr.sizerr() { + return Err(Error::Size); + } + + if sr.pgaerr() { + return Err(Error::Unaligned); + } + + if sr.wrperr() { + return Err(Error::Protected); + } + + if sr.progerr() { + return Err(Error::Prog); + } + + return Ok(()); + } + } +} diff --git a/embassy/embassy-stm32/src/fmc.rs b/embassy/embassy-stm32/src/fmc.rs new file mode 100644 index 0000000..83b49a3 --- /dev/null +++ b/embassy/embassy-stm32/src/fmc.rs @@ -0,0 +1,368 @@ +//! Flexible Memory Controller (FMC) / Flexible Static Memory Controller (FSMC) +use core::marker::PhantomData; + +use embassy_hal_internal::into_ref; + +use crate::gpio::{AfType, OutputType, Pull, Speed}; +use crate::{rcc, Peripheral}; + +/// FMC driver +pub struct Fmc<'d, T: Instance> { + peri: PhantomData<&'d mut T>, +} + +unsafe impl<'d, T> Send for Fmc<'d, T> where T: Instance {} + +impl<'d, T> Fmc<'d, T> +where + T: Instance, +{ + /// Create a raw FMC instance. + /// + /// **Note:** This is currently used to provide access to some basic FMC functions + /// for manual configuration for memory types that stm32-fmc does not support. + pub fn new_raw(_instance: impl Peripheral

+ 'd) -> Self { + Self { peri: PhantomData } + } + + /// Enable the FMC peripheral and reset it. + pub fn enable(&mut self) { + rcc::enable_and_reset::(); + } + + /// Enable the memory controller on applicable chips. + pub fn memory_controller_enable(&mut self) { + // 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, fmc_v4)))] + T::REGS.bcr1().modify(|r| r.set_fmcen(true)); + #[cfg(any(fmc_v4))] + T::REGS.nor_psram().bcr1().modify(|r| r.set_fmcen(true)); + } + + /// Get the kernel clock currently in use for this FMC instance. + pub fn source_clock_hz(&self) -> u32 { + ::frequency().0 + } +} + +unsafe impl<'d, T> stm32_fmc::FmcPeripheral for Fmc<'d, T> +where + T: Instance, +{ + const REGISTERS: *const () = T::REGS.as_ptr() as *const _; + + fn enable(&mut self) { + rcc::enable_and_reset::(); + } + + fn memory_controller_enable(&mut self) { + // 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, fmc_v4)))] + T::REGS.bcr1().modify(|r| r.set_fmcen(true)); + #[cfg(any(fmc_v4))] + T::REGS.nor_psram().bcr1().modify(|r| r.set_fmcen(true)); + } + + fn source_clock_hz(&self) -> u32 { + ::frequency().0 + } +} + +macro_rules! config_pins { + ($($pin:ident),*) => { + into_ref!($($pin),*); + $( + $pin.set_as_af($pin.af_num(), AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up)); + )* + }; +} + +macro_rules! fmc_sdram_constructor { + ($name:ident: ( + bank: $bank:expr, + addr: [$(($addr_pin_name:ident: $addr_signal:ident)),*], + ba: [$(($ba_pin_name:ident: $ba_signal:ident)),*], + d: [$(($d_pin_name:ident: $d_signal:ident)),*], + nbl: [$(($nbl_pin_name:ident: $nbl_signal:ident)),*], + ctrl: [$(($ctrl_pin_name:ident: $ctrl_signal:ident)),*] + )) => { + /// Create a new FMC instance. + pub fn $name( + _instance: impl Peripheral

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

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

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

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

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

> + 'd),*, + chip: CHIP + ) -> stm32_fmc::Sdram, CHIP> { + + critical_section::with(|_| { + config_pins!( + $($addr_pin_name),*, + $($ba_pin_name),*, + $($d_pin_name),*, + $($nbl_pin_name),*, + $($ctrl_pin_name),* + ); + }); + + let fmc = Self { peri: PhantomData }; + stm32_fmc::Sdram::new_unchecked( + fmc, + $bank, + chip, + ) + } + }; +} + +impl<'d, T: Instance> Fmc<'d, T> { + fmc_sdram_constructor!(sdram_a12bits_d16bits_4banks_bank1: ( + bank: stm32_fmc::SdramTargetBank::Bank1, + 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: SDCKE0Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE0Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); + + fmc_sdram_constructor!(sdram_a12bits_d32bits_4banks_bank1: ( + bank: stm32_fmc::SdramTargetBank::Bank1, + 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), + (d16: D16Pin), (d17: D17Pin), (d18: D18Pin), (d19: D19Pin), (d20: D20Pin), (d21: D21Pin), (d22: D22Pin), (d23: D23Pin), + (d24: D24Pin), (d25: D25Pin), (d26: D26Pin), (d27: D27Pin), (d28: D28Pin), (d29: D29Pin), (d30: D30Pin), (d31: D31Pin) + ], + nbl: [ + (nbl0: NBL0Pin), (nbl1: NBL1Pin), (nbl2: NBL2Pin), (nbl3: NBL3Pin) + ], + ctrl: [ + (sdcke: SDCKE0Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE0Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); + + fmc_sdram_constructor!(sdram_a13bits_d32bits_4banks_bank1: ( + bank: stm32_fmc::SdramTargetBank::Bank1, + 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), (a12: A12Pin) + ], + 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), + (d16: D16Pin), (d17: D17Pin), (d18: D18Pin), (d19: D19Pin), (d20: D20Pin), (d21: D21Pin), (d22: D22Pin), (d23: D23Pin), + (d24: D24Pin), (d25: D25Pin), (d26: D26Pin), (d27: D27Pin), (d28: D28Pin), (d29: D29Pin), (d30: D30Pin), (d31: D31Pin) + ], + nbl: [ + (nbl0: NBL0Pin), (nbl1: NBL1Pin), (nbl2: NBL2Pin), (nbl3: NBL3Pin) + ], + ctrl: [ + (sdcke: SDCKE0Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE0Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); + + 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: [ + (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), + (d16: D16Pin), (d17: D17Pin), (d18: D18Pin), (d19: D19Pin), (d20: D20Pin), (d21: D21Pin), (d22: D22Pin), (d23: D23Pin), + (d24: D24Pin), (d25: D25Pin), (d26: D26Pin), (d27: D27Pin), (d28: D28Pin), (d29: D29Pin), (d30: D30Pin), (d31: D31Pin) + ], + nbl: [ + (nbl0: NBL0Pin), (nbl1: NBL1Pin), (nbl2: NBL2Pin), (nbl3: NBL3Pin) + ], + ctrl: [ + (sdcke: SDCKE1Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE1Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); + + fmc_sdram_constructor!(sdram_a13bits_d32bits_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), (a12: A12Pin) + ], + 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), + (d16: D16Pin), (d17: D17Pin), (d18: D18Pin), (d19: D19Pin), (d20: D20Pin), (d21: D21Pin), (d22: D22Pin), (d23: D23Pin), + (d24: D24Pin), (d25: D25Pin), (d26: D26Pin), (d27: D27Pin), (d28: D28Pin), (d29: D29Pin), (d30: D30Pin), (d31: D31Pin) + ], + nbl: [ + (nbl0: NBL0Pin), (nbl1: NBL1Pin), (nbl2: NBL2Pin), (nbl3: NBL3Pin) + ], + ctrl: [ + (sdcke: SDCKE1Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE1Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); +} + +trait SealedInstance: crate::rcc::RccPeripheral { + const REGS: crate::pac::fmc::Fmc; +} + +/// FMC instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + 'static {} + +foreach_peripheral!( + (fmc, $inst:ident) => { + impl crate::fmc::SealedInstance for crate::peripherals::$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/embassy-stm32/src/fmt.rs b/embassy/embassy-stm32/src/fmt.rs new file mode 100644 index 0000000..b6ae24e --- /dev/null +++ b/embassy/embassy-stm32/src/fmt.rs @@ -0,0 +1,292 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! rcc_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "unchecked-overclocking"))] + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + #[cfg(feature = "unchecked-overclocking")] + { + #[cfg(feature = "log")] + ::log::warn!("`rcc_assert!` skipped: `unchecked-overclocking` feature is enabled."); + #[cfg(feature = "defmt")] + ::defmt::warn!("`rcc_assert!` skipped: `unchecked-overclocking` feature is enabled."); + } + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-stm32/src/gpio.rs b/embassy/embassy-stm32/src/gpio.rs new file mode 100644 index 0000000..87519f5 --- /dev/null +++ b/embassy/embassy-stm32/src/gpio.rs @@ -0,0 +1,1147 @@ +//! General-purpose Input/Output (GPIO) + +#![macro_use] +use core::convert::Infallible; + +use critical_section::CriticalSection; +use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; + +use crate::pac::gpio::{self, vals}; +use crate::{pac, peripherals, Peripheral}; + +/// GPIO flexible pin. +/// +/// This pin can either be a disconnected, input, or output pin, or both. The level register bit will remain +/// set while not in output mode, so the pin's level will be 'remembered' when it is not in output +/// mode. +pub struct Flex<'d> { + pub(crate) pin: PeripheralRef<'d, AnyPin>, +} + +impl<'d> Flex<'d> { + /// Wrap the pin in a `Flex`. + /// + /// The pin remains disconnected. The initial output level is unspecified, but can be changed + /// before the pin is put into output mode. + /// + #[inline] + pub fn new(pin: impl Peripheral

+ 'd) -> Self { + into_ref!(pin); + // Pin will be in disconnected state. + Self { pin: pin.map_into() } + } + + /// Put the pin into input mode. + /// + /// The internal weak pull-up and pull-down resistors will be enabled according to `pull`. + #[inline(never)] + pub fn set_as_input(&mut self, pull: Pull) { + critical_section::with(|_| { + let r = self.pin.block(); + let n = self.pin.pin() as usize; + #[cfg(gpio_v1)] + { + let cnf = match pull { + Pull::Up => { + r.bsrr().write(|w| w.set_bs(n, true)); + vals::CnfIn::PULL + } + Pull::Down => { + r.bsrr().write(|w| w.set_br(n, true)); + vals::CnfIn::PULL + } + Pull::None => vals::CnfIn::FLOATING, + }; + + r.cr(n / 8).modify(|w| { + w.set_mode(n % 8, vals::Mode::INPUT); + w.set_cnf_in(n % 8, cnf); + }); + } + #[cfg(gpio_v2)] + { + r.pupdr().modify(|w| w.set_pupdr(n, pull.to_pupdr())); + r.otyper().modify(|w| w.set_ot(n, vals::Ot::PUSHPULL)); + r.moder().modify(|w| w.set_moder(n, vals::Moder::INPUT)); + } + }); + } + + /// Put the pin into push-pull output mode. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + /// + /// The internal weak pull-up and pull-down resistors will be disabled. + #[inline(never)] + pub fn set_as_output(&mut self, speed: Speed) { + critical_section::with(|_| { + let r = self.pin.block(); + let n = self.pin.pin() as usize; + #[cfg(gpio_v1)] + { + r.cr(n / 8).modify(|w| { + w.set_mode(n % 8, speed.to_mode()); + w.set_cnf_out(n % 8, vals::CnfOut::PUSHPULL); + }); + } + #[cfg(gpio_v2)] + { + r.pupdr().modify(|w| w.set_pupdr(n, vals::Pupdr::FLOATING)); + r.otyper().modify(|w| w.set_ot(n, vals::Ot::PUSHPULL)); + r.ospeedr().modify(|w| w.set_ospeedr(n, speed.to_ospeedr())); + r.moder().modify(|w| w.set_moder(n, vals::Moder::OUTPUT)); + } + }); + } + + /// Put the pin into input + open-drain output mode. + /// + /// The hardware will drive the line low if you set it to low, and will leave it floating if you set + /// it to high, in which case you can read the input to figure out whether another device + /// is driving the line low. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + /// + /// The internal weak pull-up and pull-down resistors will be disabled. + #[inline(never)] + pub fn set_as_input_output(&mut self, speed: Speed) { + #[cfg(gpio_v1)] + critical_section::with(|_| { + let r = self.pin.block(); + let n = self.pin.pin() as usize; + r.cr(n / 8).modify(|w| w.set_mode(n % 8, speed.to_mode())); + r.cr(n / 8).modify(|w| w.set_cnf_out(n % 8, vals::CnfOut::OPENDRAIN)); + }); + + #[cfg(gpio_v2)] + self.set_as_input_output_pull(speed, Pull::None); + } + + /// Put the pin into input + open-drain output mode with internal pullup or pulldown. + /// + /// This works like [`Self::set_as_input_output()`], but it also allows to enable the internal + /// weak pull-up or pull-down resistors. + #[inline(never)] + #[cfg(gpio_v2)] + pub fn set_as_input_output_pull(&mut self, speed: Speed, pull: Pull) { + critical_section::with(|_| { + let r = self.pin.block(); + let n = self.pin.pin() as usize; + r.pupdr().modify(|w| w.set_pupdr(n, pull.to_pupdr())); + r.otyper().modify(|w| w.set_ot(n, vals::Ot::OPENDRAIN)); + r.ospeedr().modify(|w| w.set_ospeedr(n, speed.to_ospeedr())); + r.moder().modify(|w| w.set_moder(n, vals::Moder::OUTPUT)); + }); + } + + /// Put the pin into analog mode + /// + /// This mode is used by ADC and COMP but usually there is no need to set this manually + /// as the mode change is handled by the driver. + #[inline] + pub fn set_as_analog(&mut self) { + // TODO: does this also need a critical section, like other methods? + self.pin.set_as_analog(); + } + + /// Put the pin into AF mode, unchecked. + /// + /// This puts the pin into the AF mode, with the requested number and AF type. This is + /// completely unchecked, it can attach the pin to literally any peripheral, so use with care. + #[inline] + pub fn set_as_af_unchecked(&mut self, af_num: u8, af_type: AfType) { + critical_section::with(|_| { + self.pin.set_as_af(af_num, af_type); + }); + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + !self.is_low() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + let state = self.pin.block().idr().read().idr(self.pin.pin() as _); + state == vals::Idr::LOW + } + + /// Get the current pin input level. + #[inline] + pub fn get_level(&self) -> Level { + self.is_high().into() + } + + /// Get whether the output level is set to high. + #[inline] + pub fn is_set_high(&self) -> bool { + !self.is_set_low() + } + + /// Get whether the output level is set to low. + #[inline] + pub fn is_set_low(&self) -> bool { + let state = self.pin.block().odr().read().odr(self.pin.pin() as _); + state == vals::Odr::LOW + } + + /// Get the current output level. + #[inline] + pub fn get_output_level(&self) -> Level { + self.is_set_high().into() + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high(); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low(); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + match level { + Level::Low => self.pin.set_low(), + Level::High => self.pin.set_high(), + } + } + + /// Toggle the output level. + #[inline] + pub fn toggle(&mut self) { + if self.is_set_low() { + self.set_high() + } else { + self.set_low() + } + } +} + +impl<'d> Drop for Flex<'d> { + #[inline] + fn drop(&mut self) { + critical_section::with(|_| { + self.pin.set_as_disconnected(); + }); + } +} + +/// Pull setting for an input. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pull { + /// No pull + None, + /// Pull up + Up, + /// Pull down + Down, +} + +impl Pull { + #[cfg(gpio_v2)] + const fn to_pupdr(self) -> vals::Pupdr { + match self { + Pull::None => vals::Pupdr::FLOATING, + Pull::Up => vals::Pupdr::PULLUP, + Pull::Down => vals::Pupdr::PULLDOWN, + } + } +} + +/// Speed setting for an output. +/// +/// These vary depending on the chip, check the reference manual and datasheet ("I/O port +/// characteristics") for details. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Speed { + #[cfg_attr(gpio_v1, doc = "Output speed OUTPUT2MHZ")] + #[cfg_attr(gpio_v2, doc = "Output speed 00")] + Low, + #[cfg_attr(gpio_v1, doc = "Output speed OUTPUT10MHZ")] + #[cfg_attr(gpio_v2, doc = "Output speed 01")] + Medium, + #[cfg_attr(gpio_v2, doc = "Output speed 10")] + #[cfg(not(any(gpio_v1, syscfg_f0)))] + High, + #[cfg_attr(gpio_v1, doc = "Output speed OUTPUT50MHZ")] + #[cfg_attr(gpio_v2, doc = "Output speed 11")] + VeryHigh, +} + +impl Speed { + #[cfg(gpio_v1)] + const fn to_mode(self) -> vals::Mode { + match self { + Speed::Low => vals::Mode::OUTPUT2MHZ, + Speed::Medium => vals::Mode::OUTPUT10MHZ, + Speed::VeryHigh => vals::Mode::OUTPUT50MHZ, + } + } + + #[cfg(gpio_v2)] + const fn to_ospeedr(self: Speed) -> vals::Ospeedr { + match self { + Speed::Low => vals::Ospeedr::LOWSPEED, + Speed::Medium => vals::Ospeedr::MEDIUMSPEED, + #[cfg(not(syscfg_f0))] + Speed::High => vals::Ospeedr::HIGHSPEED, + Speed::VeryHigh => vals::Ospeedr::VERYHIGHSPEED, + } + } +} + +/// GPIO input driver. +pub struct Input<'d> { + pub(crate) pin: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, pull: Pull) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_input(pull); + Self { pin } + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Get the current pin input level. + #[inline] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } +} + +/// Digital input or output level. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Level { + /// Low + Low, + /// High + High, +} + +impl From for Level { + fn from(val: bool) -> Self { + match val { + true => Self::High, + false => Self::Low, + } + } +} + +impl From for bool { + fn from(level: Level) -> bool { + match level { + Level::Low => false, + Level::High => true, + } + } +} + +/// GPIO output driver. +/// +/// Note that pins will **return to their floating state** when `Output` is dropped. +/// If pins should retain their state indefinitely, either keep ownership of the +/// `Output`, or pass it to [`core::mem::forget`]. +pub struct Output<'d> { + pub(crate) pin: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create GPIO output driver for a [Pin] with the provided [Level] and [Speed] configuration. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, initial_output: Level, speed: Speed) -> Self { + let mut pin = Flex::new(pin); + match initial_output { + Level::High => pin.set_high(), + Level::Low => pin.set_low(), + } + pin.set_as_output(speed); + Self { pin } + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high(); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low(); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level) + } + + /// Is the output pin set as high? + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Is the output pin set as low? + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } + + /// What level output is set to + #[inline] + pub fn get_output_level(&self) -> Level { + self.pin.get_output_level() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle(); + } +} + +/// GPIO output open-drain driver. +/// +/// Note that pins will **return to their floating state** when `OutputOpenDrain` is dropped. +/// If pins should retain their state indefinitely, either keep ownership of the +/// `OutputOpenDrain`, or pass it to [`core::mem::forget`]. +pub struct OutputOpenDrain<'d> { + pub(crate) pin: Flex<'d>, +} + +impl<'d> OutputOpenDrain<'d> { + /// Create a new GPIO open drain output driver for a [Pin] with the provided [Level] and [Speed]. + #[inline] + pub fn new(pin: impl Peripheral

+ 'd, initial_output: Level, speed: Speed) -> Self { + let mut pin = Flex::new(pin); + match initial_output { + Level::High => pin.set_high(), + Level::Low => pin.set_low(), + } + pin.set_as_input_output(speed); + Self { pin } + } + + /// Create a new GPIO open drain output driver for a [Pin] with the provided [Level], [Speed] + /// and [Pull]. + #[inline] + #[cfg(gpio_v2)] + pub fn new_pull(pin: impl Peripheral

+ 'd, initial_output: Level, speed: Speed, pull: Pull) -> Self { + let mut pin = Flex::new(pin); + match initial_output { + Level::High => pin.set_high(), + Level::Low => pin.set_low(), + } + pin.set_as_input_output_pull(speed, pull); + Self { pin } + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + !self.pin.is_low() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Get the current pin input level. + #[inline] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high(); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low(); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level); + } + + /// Get whether the output level is set to high. + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Get whether the output level is set to low. + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } + + /// Get the current output level. + #[inline] + pub fn get_output_level(&self) -> Level { + self.pin.get_output_level() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle() + } +} + +/// GPIO output type +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum OutputType { + /// Drive the pin both high or low. + PushPull, + /// Drive the pin low, or don't drive it at all if the output level is high. + OpenDrain, +} + +impl OutputType { + #[cfg(gpio_v1)] + const fn to_cnf_out(self) -> vals::CnfOut { + match self { + OutputType::PushPull => vals::CnfOut::ALTPUSHPULL, + OutputType::OpenDrain => vals::CnfOut::ALTOPENDRAIN, + } + } + + #[cfg(gpio_v2)] + const fn to_ot(self) -> vals::Ot { + match self { + OutputType::PushPull => vals::Ot::PUSHPULL, + OutputType::OpenDrain => vals::Ot::OPENDRAIN, + } + } +} + +/// Alternate function type settings. +#[derive(Copy, Clone)] +#[cfg(gpio_v1)] +pub struct AfType { + mode: vals::Mode, + cnf: u8, + pull: Pull, +} + +#[cfg(gpio_v1)] +impl AfType { + /// Input with optional pullup or pulldown. + pub const fn input(pull: Pull) -> Self { + let cnf_in = match pull { + Pull::Up | Pull::Down => vals::CnfIn::PULL, + Pull::None => vals::CnfIn::FLOATING, + }; + Self { + mode: vals::Mode::INPUT, + cnf: cnf_in.to_bits(), + pull, + } + } + + /// Output with output type and speed and no pull-up or pull-down. + pub const fn output(output_type: OutputType, speed: Speed) -> Self { + Self { + mode: speed.to_mode(), + cnf: output_type.to_cnf_out().to_bits(), + pull: Pull::None, + } + } +} + +#[inline(never)] +#[cfg(gpio_v1)] +fn set_as_af(pin_port: u8, _af_num: u8, af_type: AfType) { + let pin = unsafe { AnyPin::steal(pin_port) }; + let r = pin.block(); + let n = pin._pin() as usize; + + r.cr(n / 8).modify(|w| { + w.set_mode(n % 8, af_type.mode); + // note that we are writing the CNF field, which is exposed as both `cnf_in` and `cnf_out` + // in the PAC. the choice of `cnf_in` instead of `cnf_out` in this code is arbitrary and + // does not affect the result. + w.set_cnf_in(n % 8, vals::CnfIn::from_bits(af_type.cnf)); + }); + + match af_type.pull { + Pull::Up => r.bsrr().write(|w| w.set_bs(n, true)), + Pull::Down => r.bsrr().write(|w| w.set_br(n, true)), + Pull::None => {} + } +} + +/// Alternate function type settings. +#[derive(Copy, Clone)] +#[cfg(gpio_v2)] +pub struct AfType { + pupdr: vals::Pupdr, + ot: vals::Ot, + ospeedr: vals::Ospeedr, +} + +#[cfg(gpio_v2)] +impl AfType { + /// Input with optional pullup or pulldown. + pub const fn input(pull: Pull) -> Self { + Self { + pupdr: pull.to_pupdr(), + ot: vals::Ot::PUSHPULL, + ospeedr: vals::Ospeedr::LOWSPEED, + } + } + + /// Output with output type and speed and no pull-up or pull-down. + pub const fn output(output_type: OutputType, speed: Speed) -> Self { + Self::output_pull(output_type, speed, Pull::None) + } + + /// Output with output type, speed and pull-up or pull-down; + pub const fn output_pull(output_type: OutputType, speed: Speed, pull: Pull) -> Self { + Self { + pupdr: pull.to_pupdr(), + ot: output_type.to_ot(), + ospeedr: speed.to_ospeedr(), + } + } +} + +#[inline(never)] +#[cfg(gpio_v2)] +fn set_as_af(pin_port: u8, af_num: u8, af_type: AfType) { + let pin = unsafe { AnyPin::steal(pin_port) }; + let r = pin.block(); + let n = pin._pin() as usize; + + r.afr(n / 8).modify(|w| w.set_afr(n % 8, af_num)); + r.pupdr().modify(|w| w.set_pupdr(n, af_type.pupdr)); + r.otyper().modify(|w| w.set_ot(n, af_type.ot)); + r.ospeedr().modify(|w| w.set_ospeedr(n, af_type.ospeedr)); + r.moder().modify(|w| w.set_moder(n, vals::Moder::ALTERNATE)); +} + +#[inline(never)] +fn set_as_analog(pin_port: u8) { + let pin = unsafe { AnyPin::steal(pin_port) }; + let r = pin.block(); + let n = pin._pin() as usize; + + #[cfg(gpio_v1)] + r.cr(n / 8).modify(|w| { + w.set_mode(n % 8, vals::Mode::INPUT); + w.set_cnf_in(n % 8, vals::CnfIn::ANALOG); + }); + + #[cfg(gpio_v2)] + r.moder().modify(|w| w.set_moder(n, vals::Moder::ANALOG)); +} + +#[inline(never)] +fn get_pull(pin_port: u8) -> Pull { + let pin = unsafe { AnyPin::steal(pin_port) }; + let r = pin.block(); + let n = pin._pin() as usize; + + #[cfg(gpio_v1)] + return match r.cr(n / 8).read().mode(n % 8) { + vals::Mode::INPUT => match r.cr(n / 8).read().cnf_in(n % 8) { + vals::CnfIn::PULL => match r.odr().read().odr(n) { + vals::Odr::LOW => Pull::Down, + vals::Odr::HIGH => Pull::Up, + }, + _ => Pull::None, + }, + _ => Pull::None, + }; + + #[cfg(gpio_v2)] + return match r.pupdr().read().pupdr(n) { + vals::Pupdr::FLOATING => Pull::None, + vals::Pupdr::PULLDOWN => Pull::Down, + vals::Pupdr::PULLUP => Pull::Up, + vals::Pupdr::_RESERVED_3 => Pull::None, + }; +} + +pub(crate) trait SealedPin { + fn pin_port(&self) -> u8; + + #[inline] + fn _pin(&self) -> u8 { + self.pin_port() % 16 + } + + #[inline] + fn _port(&self) -> u8 { + self.pin_port() / 16 + } + + #[inline] + fn block(&self) -> gpio::Gpio { + pac::GPIO(self._port() as _) + } + + /// Set the output as high. + #[inline] + fn set_high(&self) { + let n = self._pin() as _; + self.block().bsrr().write(|w| w.set_bs(n, true)); + } + + /// Set the output as low. + #[inline] + fn set_low(&self) { + let n = self._pin() as _; + self.block().bsrr().write(|w| w.set_br(n, true)); + } + + #[inline] + fn set_as_af(&self, af_num: u8, af_type: AfType) { + set_as_af(self.pin_port(), af_num, af_type) + } + + #[inline] + fn set_as_analog(&self) { + set_as_analog(self.pin_port()); + } + + /// Set the pin as "disconnected", ie doing nothing and consuming the lowest + /// amount of power possible. + /// + /// This is currently the same as [`Self::set_as_analog()`] but is semantically different + /// really. Drivers should `set_as_disconnected()` pins when dropped. + /// + /// Note that this also disables the internal weak pull-up and pull-down resistors. + #[inline] + fn set_as_disconnected(&self) { + self.set_as_analog(); + } + + /// Get the pull-up configuration. + #[inline] + fn pull(&self) -> Pull { + critical_section::with(|_| get_pull(self.pin_port())) + } +} + +/// GPIO pin trait. +#[allow(private_bounds)] +pub trait Pin: Peripheral

+ Into + SealedPin + Sized + 'static { + /// EXTI channel assigned to this pin. + /// + /// For example, PC4 uses EXTI4. + #[cfg(feature = "exti")] + type ExtiChannel: crate::exti::Channel; + + /// Number of the pin within the port (0..31) + #[inline] + fn pin(&self) -> u8 { + self._pin() + } + + /// Port of the pin + #[inline] + fn port(&self) -> u8 { + self._port() + } + + /// Type-erase (degrade) this pin into an `AnyPin`. + /// + /// This converts pin singletons (`PA5`, `PB6`, ...), which + /// are all different types, into the same type. It is useful for + /// creating arrays of pins, or avoiding generics. + #[inline] + fn degrade(self) -> AnyPin { + AnyPin { + pin_port: self.pin_port(), + } + } +} + +/// Type-erased GPIO pin +pub struct AnyPin { + pin_port: u8, +} + +impl AnyPin { + /// Unsafely create an `AnyPin` from a pin+port number. + /// + /// `pin_port` is `port_num * 16 + pin_num`, where `port_num` is 0 for port `A`, 1 for port `B`, etc... + #[inline] + pub unsafe fn steal(pin_port: u8) -> Self { + Self { pin_port } + } + + #[inline] + fn _port(&self) -> u8 { + self.pin_port / 16 + } + + /// Get the GPIO register block for this pin. + #[cfg(feature = "unstable-pac")] + #[inline] + pub fn block(&self) -> gpio::Gpio { + pac::GPIO(self._port() as _) + } +} + +impl_peripheral!(AnyPin); +impl Pin for AnyPin { + #[cfg(feature = "exti")] + type ExtiChannel = crate::exti::AnyChannel; +} +impl SealedPin for AnyPin { + #[inline] + fn pin_port(&self) -> u8 { + self.pin_port + } +} + +// ==================== + +foreach_pin!( + ($pin_name:ident, $port_name:ident, $port_num:expr, $pin_num:expr, $exti_ch:ident) => { + impl Pin for peripherals::$pin_name { + #[cfg(feature = "exti")] + type ExtiChannel = peripherals::$exti_ch; + } + impl SealedPin for peripherals::$pin_name { + #[inline] + fn pin_port(&self) -> u8 { + $port_num * 16 + $pin_num + } + } + + impl From for AnyPin { + fn from(x: peripherals::$pin_name) -> Self { + x.degrade() + } + } + }; +); + +pub(crate) unsafe fn init(_cs: CriticalSection) { + #[cfg(afio)] + crate::rcc::enable_and_reset_with_cs::(_cs); + + crate::_generated::init_gpio(); +} + +impl<'d> embedded_hal_02::digital::v2::InputPin for Input<'d> { + type Error = Infallible; + + #[inline] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl<'d> embedded_hal_02::digital::v2::OutputPin for Output<'d> { + type Error = Infallible; + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Output<'d> { + #[inline] + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + /// Is the output pin set as low? + #[inline] + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } +} + +impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'d> { + type Error = Infallible; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } +} + +impl<'d> embedded_hal_02::digital::v2::InputPin for OutputOpenDrain<'d> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl<'d> embedded_hal_02::digital::v2::OutputPin for OutputOpenDrain<'d> { + type Error = Infallible; + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for OutputOpenDrain<'d> { + #[inline] + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + /// Is the output pin set as low? + #[inline] + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } +} + +impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for OutputOpenDrain<'d> { + type Error = Infallible; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } +} + +impl<'d> embedded_hal_02::digital::v2::InputPin for Flex<'d> { + type Error = Infallible; + + #[inline] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl<'d> embedded_hal_02::digital::v2::OutputPin for Flex<'d> { + type Error = Infallible; + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'d> { + #[inline] + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + /// Is the output pin set as low? + #[inline] + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } +} + +impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'d> { + type Error = Infallible; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Input<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::InputPin for Input<'d> { + #[inline] + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + #[inline] + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Output<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::OutputPin for Output<'d> { + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } +} + +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Output<'d> { + #[inline] + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + /// Is the output pin set as low? + #[inline] + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for OutputOpenDrain<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::InputPin for OutputOpenDrain<'d> { + #[inline] + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + #[inline] + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_1::digital::OutputPin for OutputOpenDrain<'d> { + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } +} + +impl<'d> embedded_hal_1::digital::StatefulOutputPin for OutputOpenDrain<'d> { + #[inline] + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + /// Is the output pin set as low? + #[inline] + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +impl<'d> embedded_hal_1::digital::InputPin for Flex<'d> { + #[inline] + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + #[inline] + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_1::digital::OutputPin for Flex<'d> { + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Flex<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Flex<'d> { + #[inline] + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + /// Is the output pin set as low? + #[inline] + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} diff --git a/embassy/embassy-stm32/src/hash/mod.rs b/embassy/embassy-stm32/src/hash/mod.rs new file mode 100644 index 0000000..4d4a8ec --- /dev/null +++ b/embassy/embassy-stm32/src/hash/mod.rs @@ -0,0 +1,588 @@ +//! Hash generator (HASH) +use core::cmp::min; +#[cfg(hash_v2)] +use core::future::poll_fn; +use core::marker::PhantomData; +#[cfg(hash_v2)] +use core::ptr; +#[cfg(hash_v2)] +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use stm32_metapac::hash::regs::*; + +use crate::dma::NoDma; +#[cfg(hash_v2)] +use crate::dma::Transfer; +use crate::interrupt::typelevel::Interrupt; +use crate::peripherals::HASH; +use crate::{interrupt, pac, peripherals, rcc, Peripheral}; + +#[cfg(hash_v1)] +const NUM_CONTEXT_REGS: usize = 51; +#[cfg(hash_v3)] +const NUM_CONTEXT_REGS: usize = 103; +#[cfg(any(hash_v2, hash_v4))] +const NUM_CONTEXT_REGS: usize = 54; + +const HASH_BUFFER_LEN: usize = 132; +const DIGEST_BLOCK_SIZE: usize = 128; + +static HASH_WAKER: AtomicWaker = AtomicWaker::new(); + +/// HASH interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let bits = T::regs().sr().read(); + if bits.dinis() { + T::regs().imr().modify(|reg| reg.set_dinie(false)); + HASH_WAKER.wake(); + } + if bits.dcis() { + T::regs().imr().modify(|reg| reg.set_dcie(false)); + HASH_WAKER.wake(); + } + } +} + +///Hash algorithm selection +#[derive(Clone, Copy, PartialEq)] +pub enum Algorithm { + /// SHA-1 Algorithm + SHA1 = 0, + + #[cfg(any(hash_v1, hash_v2, hash_v4))] + /// MD5 Algorithm + MD5 = 1, + + /// SHA-224 Algorithm + SHA224 = 2, + + /// SHA-256 Algorithm + SHA256 = 3, + + #[cfg(hash_v3)] + /// SHA-384 Algorithm + SHA384 = 12, + + #[cfg(hash_v3)] + /// SHA-512/224 Algorithm + SHA512_224 = 13, + + #[cfg(hash_v3)] + /// SHA-512/256 Algorithm + SHA512_256 = 14, + + #[cfg(hash_v3)] + /// SHA-256 Algorithm + SHA512 = 15, +} + +/// Input data width selection +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum DataType { + ///32-bit data, no data is swapped. + Width32 = 0, + ///16-bit data, each half-word is swapped. + Width16 = 1, + ///8-bit data, all bytes are swapped. + Width8 = 2, + ///1-bit data, all bits are swapped. + Width1 = 3, +} + +/// Stores the state of the HASH peripheral for suspending/resuming +/// digest calculation. +pub struct Context<'c> { + first_word_sent: bool, + key_sent: bool, + buffer: [u8; HASH_BUFFER_LEN], + buflen: usize, + algo: Algorithm, + format: DataType, + imr: u32, + str: u32, + cr: u32, + csr: [u32; NUM_CONTEXT_REGS], + key: HmacKey<'c>, +} + +type HmacKey<'k> = Option<&'k [u8]>; + +/// HASH driver. +pub struct Hash<'d, T: Instance, D = NoDma> { + _peripheral: PeripheralRef<'d, T>, + #[allow(dead_code)] + dma: PeripheralRef<'d, D>, +} + +impl<'d, T: Instance, D> Hash<'d, T, D> { + /// Instantiates, resets, and enables the HASH peripheral. + pub fn new( + peripheral: impl Peripheral

+ 'd, + dma: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + rcc::enable_and_reset::(); + into_ref!(peripheral, dma); + let instance = Self { + _peripheral: peripheral, + dma: dma, + }; + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + instance + } + + /// Starts computation of a new hash and returns the saved peripheral state. + pub fn start<'c>(&mut self, algorithm: Algorithm, format: DataType, key: HmacKey<'c>) -> Context<'c> { + // Define a context for this new computation. + let mut ctx = Context { + first_word_sent: false, + key_sent: false, + buffer: [0; HASH_BUFFER_LEN], + buflen: 0, + algo: algorithm, + format: format, + imr: 0, + str: 0, + cr: 0, + csr: [0; NUM_CONTEXT_REGS], + key, + }; + + // Set the data type in the peripheral. + T::regs().cr().modify(|w| w.set_datatype(ctx.format as u8)); + + // Select the algorithm. + #[cfg(hash_v1)] + if ctx.algo == Algorithm::MD5 { + T::regs().cr().modify(|w| w.set_algo(true)); + } + + #[cfg(hash_v2)] + { + // Select the algorithm. + let mut algo0 = false; + let mut algo1 = false; + if ctx.algo == Algorithm::MD5 || ctx.algo == Algorithm::SHA256 { + algo0 = true; + } + if ctx.algo == Algorithm::SHA224 || ctx.algo == Algorithm::SHA256 { + algo1 = true; + } + T::regs().cr().modify(|w| w.set_algo0(algo0)); + T::regs().cr().modify(|w| w.set_algo1(algo1)); + } + + #[cfg(any(hash_v3, hash_v4))] + T::regs().cr().modify(|w| w.set_algo(ctx.algo as u8)); + + // Configure HMAC mode if a key is provided. + if let Some(key) = ctx.key { + T::regs().cr().modify(|w| w.set_mode(true)); + if key.len() > 64 { + T::regs().cr().modify(|w| w.set_lkey(true)); + } + } + + T::regs().cr().modify(|w| w.set_init(true)); + + // Store and return the state of the peripheral. + self.store_context(&mut ctx); + ctx + } + + /// Restores the peripheral state using the given context, + /// then updates the state with the provided data. + /// Peripheral state is saved upon return. + pub fn update_blocking<'c>(&mut self, ctx: &mut Context<'c>, input: &[u8]) { + // Restore the peripheral state. + self.load_context(&ctx); + + // Load the HMAC key if provided. + if !ctx.key_sent { + if let Some(key) = ctx.key { + self.accumulate_blocking(key); + T::regs().str().write(|w| w.set_dcal(true)); + // Block waiting for digest. + while !T::regs().sr().read().dinis() {} + } + ctx.key_sent = true; + } + + let mut data_waiting = input.len() + ctx.buflen; + if data_waiting < DIGEST_BLOCK_SIZE || (data_waiting < ctx.buffer.len() && !ctx.first_word_sent) { + // There isn't enough data to digest a block, so append it to the buffer. + ctx.buffer[ctx.buflen..ctx.buflen + input.len()].copy_from_slice(input); + ctx.buflen += input.len(); + self.store_context(ctx); + return; + } + + let mut ilen_remaining = input.len(); + let mut input_start = 0; + + // Handle first block. + if !ctx.first_word_sent { + let empty_len = ctx.buffer.len() - ctx.buflen; + let copy_len = min(empty_len, ilen_remaining); + // Fill the buffer. + if copy_len > 0 { + ctx.buffer[ctx.buflen..ctx.buflen + copy_len].copy_from_slice(&input[0..copy_len]); + ctx.buflen += copy_len; + ilen_remaining -= copy_len; + input_start += copy_len; + } + self.accumulate_blocking(ctx.buffer.as_slice()); + data_waiting -= ctx.buflen; + ctx.buflen = 0; + ctx.first_word_sent = true; + } + + if data_waiting < DIGEST_BLOCK_SIZE { + // There isn't enough data remaining to process another block, so store it. + ctx.buffer[0..ilen_remaining].copy_from_slice(&input[input_start..input_start + ilen_remaining]); + ctx.buflen += ilen_remaining; + } else { + // First ingest the data in the buffer. + let empty_len = DIGEST_BLOCK_SIZE - ctx.buflen; + if empty_len > 0 { + let copy_len = min(empty_len, ilen_remaining); + ctx.buffer[ctx.buflen..ctx.buflen + copy_len] + .copy_from_slice(&input[input_start..input_start + copy_len]); + ctx.buflen += copy_len; + ilen_remaining -= copy_len; + input_start += copy_len; + } + self.accumulate_blocking(&ctx.buffer[0..DIGEST_BLOCK_SIZE]); + ctx.buflen = 0; + + // Move any extra data to the now-empty buffer. + let leftovers = ilen_remaining % 64; + if leftovers > 0 { + ctx.buffer[0..leftovers].copy_from_slice(&input[input.len() - leftovers..input.len()]); + ctx.buflen += leftovers; + ilen_remaining -= leftovers; + } + + // Hash the remaining data. + self.accumulate_blocking(&input[input_start..input_start + ilen_remaining]); + } + + // Save the peripheral context. + self.store_context(ctx); + } + + /// Restores the peripheral state using the given context, + /// then updates the state with the provided data. + /// Peripheral state is saved upon return. + #[cfg(hash_v2)] + pub async fn update<'c>(&mut self, ctx: &mut Context<'c>, input: &[u8]) + where + D: crate::hash::Dma, + { + // Restore the peripheral state. + self.load_context(&ctx); + + // Load the HMAC key if provided. + if !ctx.key_sent { + if let Some(key) = ctx.key { + self.accumulate(key).await; + } + ctx.key_sent = true; + } + + let data_waiting = input.len() + ctx.buflen; + if data_waiting < DIGEST_BLOCK_SIZE { + // There isn't enough data to digest a block, so append it to the buffer. + ctx.buffer[ctx.buflen..ctx.buflen + input.len()].copy_from_slice(input); + ctx.buflen += input.len(); + self.store_context(ctx); + return; + } + + // Enable multiple DMA transfers. + T::regs().cr().modify(|w| w.set_mdmat(true)); + + let mut ilen_remaining = input.len(); + let mut input_start = 0; + + // First ingest the data in the buffer. + let empty_len = DIGEST_BLOCK_SIZE - ctx.buflen; + if empty_len > 0 { + let copy_len = min(empty_len, ilen_remaining); + ctx.buffer[ctx.buflen..ctx.buflen + copy_len].copy_from_slice(&input[input_start..input_start + copy_len]); + ctx.buflen += copy_len; + ilen_remaining -= copy_len; + input_start += copy_len; + } + self.accumulate(&ctx.buffer[..DIGEST_BLOCK_SIZE]).await; + ctx.buflen = 0; + + // Move any extra data to the now-empty buffer. + let leftovers = ilen_remaining % DIGEST_BLOCK_SIZE; + if leftovers > 0 { + assert!(ilen_remaining >= leftovers); + ctx.buffer[0..leftovers].copy_from_slice(&input[input.len() - leftovers..input.len()]); + ctx.buflen += leftovers; + ilen_remaining -= leftovers; + } else { + ctx.buffer + .copy_from_slice(&input[input.len() - DIGEST_BLOCK_SIZE..input.len()]); + ctx.buflen += DIGEST_BLOCK_SIZE; + ilen_remaining -= DIGEST_BLOCK_SIZE; + } + + // Hash the remaining data. + self.accumulate(&input[input_start..input_start + ilen_remaining]).await; + + // Save the peripheral context. + self.store_context(ctx); + } + + /// Computes a digest for the given context. + /// The digest buffer must be large enough to accomodate a digest for the selected algorithm. + /// The largest returned digest size is 128 bytes for SHA-512. + /// Panics if the supplied digest buffer is too short. + pub fn finish_blocking<'c>(&mut self, mut ctx: Context<'c>, digest: &mut [u8]) -> usize { + // Restore the peripheral state. + self.load_context(&ctx); + + // Hash the leftover bytes, if any. + self.accumulate_blocking(&ctx.buffer[0..ctx.buflen]); + ctx.buflen = 0; + + //Start the digest calculation. + T::regs().str().write(|w| w.set_dcal(true)); + + // Load the HMAC key if provided. + if let Some(key) = ctx.key { + while !T::regs().sr().read().dinis() {} + self.accumulate_blocking(key); + T::regs().str().write(|w| w.set_dcal(true)); + } + + // Block until digest computation is complete. + while !T::regs().sr().read().dcis() {} + + // Return the digest. + let digest_words = match ctx.algo { + Algorithm::SHA1 => 5, + #[cfg(any(hash_v1, hash_v2, hash_v4))] + Algorithm::MD5 => 4, + Algorithm::SHA224 => 7, + Algorithm::SHA256 => 8, + #[cfg(hash_v3)] + Algorithm::SHA384 => 12, + #[cfg(hash_v3)] + Algorithm::SHA512_224 => 7, + #[cfg(hash_v3)] + Algorithm::SHA512_256 => 8, + #[cfg(hash_v3)] + Algorithm::SHA512 => 16, + }; + + let digest_len_bytes = digest_words * 4; + // Panics if the supplied digest buffer is too short. + if digest.len() < digest_len_bytes { + panic!("Digest buffer must be at least {} bytes long.", digest_words * 4); + } + + let mut i = 0; + while i < digest_words { + let word = T::regs().hr(i).read(); + digest[(i * 4)..((i * 4) + 4)].copy_from_slice(word.to_be_bytes().as_slice()); + i += 1; + } + digest_len_bytes + } + + /// Computes a digest for the given context. + /// The digest buffer must be large enough to accomodate a digest for the selected algorithm. + /// The largest returned digest size is 128 bytes for SHA-512. + /// Panics if the supplied digest buffer is too short. + #[cfg(hash_v2)] + pub async fn finish<'c>(&mut self, mut ctx: Context<'c>, digest: &mut [u8]) -> usize + where + D: crate::hash::Dma, + { + // Restore the peripheral state. + self.load_context(&ctx); + + // Must be cleared prior to the last DMA transfer. + T::regs().cr().modify(|w| w.set_mdmat(false)); + + // Hash the leftover bytes, if any. + self.accumulate(&ctx.buffer[0..ctx.buflen]).await; + ctx.buflen = 0; + + // Load the HMAC key if provided. + if let Some(key) = ctx.key { + self.accumulate(key).await; + } + + // Wait for completion. + poll_fn(|cx| { + // Check if already done. + let bits = T::regs().sr().read(); + if bits.dcis() { + return Poll::Ready(()); + } + // Register waker, then enable interrupts. + HASH_WAKER.register(cx.waker()); + T::regs().imr().modify(|reg| reg.set_dcie(true)); + // Check for completion. + let bits = T::regs().sr().read(); + if bits.dcis() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + // Return the digest. + let digest_words = match ctx.algo { + Algorithm::SHA1 => 5, + #[cfg(any(hash_v1, hash_v2, hash_v4))] + Algorithm::MD5 => 4, + Algorithm::SHA224 => 7, + Algorithm::SHA256 => 8, + #[cfg(hash_v3)] + Algorithm::SHA384 => 12, + #[cfg(hash_v3)] + Algorithm::SHA512_224 => 7, + #[cfg(hash_v3)] + Algorithm::SHA512_256 => 8, + #[cfg(hash_v3)] + Algorithm::SHA512 => 16, + }; + + let digest_len_bytes = digest_words * 4; + // Panics if the supplied digest buffer is too short. + if digest.len() < digest_len_bytes { + panic!("Digest buffer must be at least {} bytes long.", digest_words * 4); + } + + let mut i = 0; + while i < digest_words { + let word = T::regs().hr(i).read(); + digest[(i * 4)..((i * 4) + 4)].copy_from_slice(word.to_be_bytes().as_slice()); + i += 1; + } + digest_len_bytes + } + + /// Push data into the hash core. + fn accumulate_blocking(&mut self, input: &[u8]) { + // Set the number of valid bits. + let num_valid_bits: u8 = (8 * (input.len() % 4)) as u8; + T::regs().str().modify(|w| w.set_nblw(num_valid_bits)); + + let mut i = 0; + while i < input.len() { + let mut word: [u8; 4] = [0; 4]; + let copy_idx = min(i + 4, input.len()); + word[0..copy_idx - i].copy_from_slice(&input[i..copy_idx]); + T::regs().din().write_value(u32::from_ne_bytes(word)); + i += 4; + } + } + + /// Push data into the hash core. + #[cfg(hash_v2)] + async fn accumulate(&mut self, input: &[u8]) + where + D: crate::hash::Dma, + { + // Ignore an input length of 0. + if input.len() == 0 { + return; + } + + // Set the number of valid bits. + let num_valid_bits: u8 = (8 * (input.len() % 4)) as u8; + T::regs().str().modify(|w| w.set_nblw(num_valid_bits)); + + // Configure DMA to transfer input to hash core. + let dma_request = self.dma.request(); + let dst_ptr = T::regs().din().as_ptr(); + let mut num_words = input.len() / 4; + if input.len() % 4 > 0 { + num_words += 1; + } + let src_ptr = ptr::slice_from_raw_parts(input.as_ptr().cast(), num_words); + let dma_transfer = + unsafe { Transfer::new_write_raw(&mut self.dma, dma_request, src_ptr, dst_ptr, Default::default()) }; + T::regs().cr().modify(|w| w.set_dmae(true)); + + // Wait for the transfer to complete. + dma_transfer.await; + } + + /// Save the peripheral state to a context. + fn store_context<'c>(&mut self, ctx: &mut Context<'c>) { + // Block waiting for data in ready. + while !T::regs().sr().read().dinis() {} + + // Store peripheral context. + ctx.imr = T::regs().imr().read().0; + ctx.str = T::regs().str().read().0; + ctx.cr = T::regs().cr().read().0; + let mut i = 0; + while i < NUM_CONTEXT_REGS { + ctx.csr[i] = T::regs().csr(i).read(); + i += 1; + } + } + + /// Restore the peripheral state from a context. + fn load_context(&mut self, ctx: &Context) { + // Restore the peripheral state from the context. + T::regs().imr().write_value(Imr { 0: ctx.imr }); + T::regs().str().write_value(Str { 0: ctx.str }); + T::regs().cr().write_value(Cr { 0: ctx.cr }); + T::regs().cr().modify(|w| w.set_init(true)); + let mut i = 0; + while i < NUM_CONTEXT_REGS { + T::regs().csr(i).write_value(ctx.csr[i]); + i += 1; + } + } +} + +trait SealedInstance { + fn regs() -> pac::hash::Hash; +} + +/// HASH instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + Peripheral

+ crate::rcc::RccPeripheral + 'static + Send { + /// Interrupt for this HASH instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +foreach_interrupt!( + ($inst:ident, hash, HASH, GLOBAL, $irq:ident) => { + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + + impl SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::hash::Hash { + crate::pac::$inst + } + } + }; +); + +dma_trait!(Dma, Instance); diff --git a/embassy/embassy-stm32/src/hrtim/mod.rs b/embassy/embassy-stm32/src/hrtim/mod.rs new file mode 100644 index 0000000..d9b7c16 --- /dev/null +++ b/embassy/embassy-stm32/src/hrtim/mod.rs @@ -0,0 +1,444 @@ +//! High Resolution Timer (HRTIM) + +mod traits; + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +pub use traits::Instance; + +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::time::Hertz; +use crate::{rcc, Peripheral}; + +/// HRTIM burst controller instance. +pub struct BurstController { + phantom: PhantomData, +} + +/// HRTIM master instance. +pub struct Master { + phantom: PhantomData, +} + +/// HRTIM channel A instance. +pub struct ChA { + phantom: PhantomData, +} + +/// HRTIM channel B instance. +pub struct ChB { + phantom: PhantomData, +} + +/// HRTIM channel C instance. +pub struct ChC { + phantom: PhantomData, +} + +/// HRTIM channel D instance. +pub struct ChD { + phantom: PhantomData, +} + +/// HRTIM channel E instance. +pub struct ChE { + phantom: PhantomData, +} + +/// HRTIM channel F instance. +#[cfg(hrtim_v2)] +pub struct ChF { + phantom: PhantomData, +} + +trait SealedAdvancedChannel { + fn raw() -> usize; +} + +/// Advanced channel instance trait. +#[allow(private_bounds)] +pub trait AdvancedChannel: SealedAdvancedChannel {} + +/// HRTIM PWM pin. +pub struct PwmPin<'d, T, C> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +/// HRTIM complementary PWM pin. +pub struct ComplementaryPwmPin<'d, T, C> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! advanced_channel_impl { + ($new_chx:ident, $channel:tt, $ch_num:expr, $pin_trait:ident, $complementary_pin_trait:ident) => { + impl<'d, T: Instance> PwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] + pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af( + pin.af_num(), + AfType::output(OutputType::PushPull, Speed::VeryHigh), + ); + }); + PwmPin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + + impl<'d, T: Instance> ComplementaryPwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance.")] + pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af( + pin.af_num(), + AfType::output(OutputType::PushPull, Speed::VeryHigh), + ); + }); + ComplementaryPwmPin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + + impl SealedAdvancedChannel for $channel { + fn raw() -> usize { + $ch_num + } + } + impl AdvancedChannel for $channel {} + }; +} + +advanced_channel_impl!(new_cha, ChA, 0, ChannelAPin, ChannelAComplementaryPin); +advanced_channel_impl!(new_chb, ChB, 1, ChannelBPin, ChannelBComplementaryPin); +advanced_channel_impl!(new_chc, ChC, 2, ChannelCPin, ChannelCComplementaryPin); +advanced_channel_impl!(new_chd, ChD, 3, ChannelDPin, ChannelDComplementaryPin); +advanced_channel_impl!(new_che, ChE, 4, ChannelEPin, ChannelEComplementaryPin); +#[cfg(hrtim_v2)] +advanced_channel_impl!(new_chf, ChF, 5, ChannelFPin, ChannelFComplementaryPin); + +/// Struct used to divide a high resolution timer into multiple channels +pub struct AdvancedPwm<'d, T: Instance> { + _inner: PeripheralRef<'d, T>, + /// Master instance. + pub master: Master, + /// Burst controller. + pub burst_controller: BurstController, + /// Channel A. + pub ch_a: ChA, + /// Channel B. + pub ch_b: ChB, + /// Channel C. + pub ch_c: ChC, + /// Channel D. + pub ch_d: ChD, + /// Channel E. + pub ch_e: ChE, + /// Channel F. + #[cfg(hrtim_v2)] + pub ch_f: ChF, +} + +impl<'d, T: Instance> AdvancedPwm<'d, T> { + /// Create a new HRTIM driver. + /// + /// This splits the HRTIM into its constituent parts, which you can then use individually. + pub fn new( + tim: impl Peripheral

+ 'd, + _cha: Option>>, + _chan: Option>>, + _chb: Option>>, + _chbn: Option>>, + _chc: Option>>, + _chcn: Option>>, + _chd: Option>>, + _chdn: Option>>, + _che: Option>>, + _chen: Option>>, + #[cfg(hrtim_v2)] _chf: Option>>, + #[cfg(hrtim_v2)] _chfn: Option>>, + ) -> Self { + Self::new_inner(tim) + } + + fn new_inner(tim: impl Peripheral

+ 'd) -> Self { + into_ref!(tim); + + rcc::enable_and_reset::(); + + #[cfg(stm32f334)] + if crate::pac::RCC.cfgr3().read().hrtim1sw() == crate::pac::rcc::vals::Timsw::PLL1_P { + // Enable and and stabilize the DLL + T::regs().dllcr().modify(|w| { + w.set_cal(true); + }); + + trace!("hrtim: wait for dll calibration"); + while !T::regs().isr().read().dllrdy() {} + + trace!("hrtim: dll calibration complete"); + + // Enable periodic calibration + // Cal must be disabled before we can enable it + T::regs().dllcr().modify(|w| { + w.set_cal(false); + }); + + T::regs().dllcr().modify(|w| { + w.set_calen(true); + w.set_calrte(11); + }); + } + + Self { + _inner: tim, + master: Master { phantom: PhantomData }, + burst_controller: BurstController { phantom: PhantomData }, + ch_a: ChA { phantom: PhantomData }, + ch_b: ChB { phantom: PhantomData }, + ch_c: ChC { phantom: PhantomData }, + ch_d: ChD { phantom: PhantomData }, + ch_e: ChE { phantom: PhantomData }, + #[cfg(hrtim_v2)] + ch_f: ChF { phantom: PhantomData }, + } + } +} + +/// Fixed-frequency bridge converter driver. +/// +/// Our implementation of the bridge converter uses a single channel and three compare registers, +/// allowing implementation of a synchronous buck or boost converter in continuous or discontinuous +/// conduction mode. +/// +/// It is important to remember that in synchronous topologies, energy can flow in reverse during +/// light loading conditions, and that the low-side switch must be active for a short time to drive +/// a bootstrapped high-side switch. +pub struct BridgeConverter> { + timer: PhantomData, + channel: PhantomData, + dead_time: u16, + primary_duty: u16, + min_secondary_duty: u16, + max_secondary_duty: u16, +} + +impl> BridgeConverter { + /// Create a new HRTIM bridge converter driver. + pub fn new(_channel: C, frequency: Hertz) -> Self { + T::set_channel_frequency(C::raw(), frequency); + + // Always enable preload + T::regs().tim(C::raw()).cr().modify(|w| { + w.set_preen(true); + w.set_repu(true); + w.set_cont(true); + }); + + // Enable timer outputs + T::regs().oenr().modify(|w| { + w.set_t1oen(C::raw(), true); + w.set_t2oen(C::raw(), true); + }); + + // The dead-time generation unit cannot be used because it forces the other output + // to be completely complementary to the first output, which restricts certain waveforms + // Therefore, software-implemented dead time must be used when setting the duty cycles + + // Set output 1 to active on a period event + T::regs().tim(C::raw()).setr(0).modify(|w| w.set_per(true)); + + // Set output 1 to inactive on a compare 1 event + T::regs().tim(C::raw()).rstr(0).modify(|w| w.set_cmp(0, true)); + + // Set output 2 to active on a compare 2 event + T::regs().tim(C::raw()).setr(1).modify(|w| w.set_cmp(1, true)); + + // Set output 2 to inactive on a compare 3 event + T::regs().tim(C::raw()).rstr(1).modify(|w| w.set_cmp(2, true)); + + Self { + timer: PhantomData, + channel: PhantomData, + dead_time: 0, + primary_duty: 0, + min_secondary_duty: 0, + max_secondary_duty: 0, + } + } + + /// Start HRTIM. + pub fn start(&mut self) { + T::regs().mcr().modify(|w| w.set_tcen(C::raw(), true)); + } + + /// Stop HRTIM. + pub fn stop(&mut self) { + T::regs().mcr().modify(|w| w.set_tcen(C::raw(), false)); + } + + /// Enable burst mode. + pub fn enable_burst_mode(&mut self) { + T::regs().tim(C::raw()).outr().modify(|w| { + // Enable Burst Mode + w.set_idlem(0, true); + w.set_idlem(1, true); + + // Set output to active during the burst + w.set_idles(0, true); + w.set_idles(1, true); + }) + } + + /// Disable burst mode. + pub fn disable_burst_mode(&mut self) { + T::regs().tim(C::raw()).outr().modify(|w| { + // Disable Burst Mode + w.set_idlem(0, false); + w.set_idlem(1, false); + }) + } + + fn update_primary_duty_or_dead_time(&mut self) { + self.min_secondary_duty = self.primary_duty + self.dead_time; + + T::regs().tim(C::raw()).cmp(0).modify(|w| w.set_cmp(self.primary_duty)); + T::regs() + .tim(C::raw()) + .cmp(1) + .modify(|w| w.set_cmp(self.min_secondary_duty)); + } + + /// Set the dead time as a proportion of the maximum compare value + pub fn set_dead_time(&mut self, dead_time: u16) { + self.dead_time = dead_time; + self.max_secondary_duty = self.get_max_compare_value() - dead_time; + self.update_primary_duty_or_dead_time(); + } + + /// Get the maximum compare value of a duty cycle + pub fn get_max_compare_value(&mut self) -> u16 { + T::regs().tim(C::raw()).per().read().per() + } + + /// The primary duty is the period in which the primary switch is active + /// + /// In the case of a buck converter, this is the high-side switch + /// In the case of a boost converter, this is the low-side switch + pub fn set_primary_duty(&mut self, primary_duty: u16) { + self.primary_duty = primary_duty; + self.update_primary_duty_or_dead_time(); + } + + /// The secondary duty is the period in any switch is active + /// + /// If less than or equal to the primary duty, the secondary switch will be active for one tick + /// If a fully complementary output is desired, the secondary duty can be set to the max compare + pub fn set_secondary_duty(&mut self, secondary_duty: u16) { + let secondary_duty = if secondary_duty > self.max_secondary_duty { + self.max_secondary_duty + } else if secondary_duty <= self.min_secondary_duty { + self.min_secondary_duty + 1 + } else { + secondary_duty + }; + + T::regs().tim(C::raw()).cmp(2).modify(|w| w.set_cmp(secondary_duty)); + } +} + +/// Variable-frequency resonant converter driver. +/// +/// This implementation of a resonsant converter is appropriate for a half or full bridge, +/// but does not include secondary rectification, which is appropriate for applications +/// with a low-voltage on the secondary side. +pub struct ResonantConverter> { + timer: PhantomData, + channel: PhantomData, + min_period: u16, + max_period: u16, +} + +impl> ResonantConverter { + /// Create a new variable-frequency resonant converter driver. + pub fn new(_channel: C, min_frequency: Hertz, max_frequency: Hertz) -> Self { + T::set_channel_frequency(C::raw(), min_frequency); + + // Always enable preload + T::regs().tim(C::raw()).cr().modify(|w| { + w.set_preen(true); + w.set_repu(true); + + w.set_cont(true); + w.set_half(true); + }); + + // Enable timer outputs + T::regs().oenr().modify(|w| { + w.set_t1oen(C::raw(), true); + w.set_t2oen(C::raw(), true); + }); + + // Dead-time generator can be used in this case because the primary fets + // of a resonant converter are always complementary + T::regs().tim(C::raw()).outr().modify(|w| w.set_dten(true)); + + let max_period = T::regs().tim(C::raw()).per().read().per(); + let min_period = max_period * (min_frequency.0 / max_frequency.0) as u16; + + Self { + timer: PhantomData, + channel: PhantomData, + min_period: min_period, + max_period: max_period, + } + } + + /// Set the dead time as a proportion of the maximum compare value + pub fn set_dead_time(&mut self, value: u16) { + T::set_channel_dead_time(C::raw(), value); + } + + /// Set the timer period. + pub fn set_period(&mut self, period: u16) { + assert!(period < self.max_period); + assert!(period > self.min_period); + + T::regs().tim(C::raw()).per().modify(|w| w.set_per(period)); + } + + /// Get the minimum compare value of a duty cycle + pub fn get_min_period(&mut self) -> u16 { + self.min_period + } + + /// Get the maximum compare value of a duty cycle + pub fn get_max_period(&mut self) -> u16 { + self.max_period + } +} + +pin_trait!(ChannelAPin, Instance); +pin_trait!(ChannelAComplementaryPin, Instance); +pin_trait!(ChannelBPin, Instance); +pin_trait!(ChannelBComplementaryPin, Instance); +pin_trait!(ChannelCPin, Instance); +pin_trait!(ChannelCComplementaryPin, Instance); +pin_trait!(ChannelDPin, Instance); +pin_trait!(ChannelDComplementaryPin, Instance); +pin_trait!(ChannelEPin, Instance); +pin_trait!(ChannelEComplementaryPin, Instance); +#[cfg(hrtim_v2)] +pin_trait!(ChannelFPin, Instance); +#[cfg(hrtim_v2)] +pin_trait!(ChannelFComplementaryPin, Instance); diff --git a/embassy/embassy-stm32/src/hrtim/traits.rs b/embassy/embassy-stm32/src/hrtim/traits.rs new file mode 100644 index 0000000..75f9971 --- /dev/null +++ b/embassy/embassy-stm32/src/hrtim/traits.rs @@ -0,0 +1,170 @@ +use crate::rcc::RccPeripheral; +use crate::time::Hertz; + +#[repr(u8)] +#[derive(Clone, Copy)] +pub(crate) enum Prescaler { + Div1 = 1, + Div2 = 2, + Div4 = 4, + Div8 = 8, + Div16 = 16, + Div32 = 32, + Div64 = 64, + Div128 = 128, +} + +impl From for u8 { + fn from(val: Prescaler) -> Self { + match val { + Prescaler::Div1 => 0b000, + Prescaler::Div2 => 0b001, + Prescaler::Div4 => 0b010, + Prescaler::Div8 => 0b011, + Prescaler::Div16 => 0b100, + Prescaler::Div32 => 0b101, + Prescaler::Div64 => 0b110, + Prescaler::Div128 => 0b111, + } + } +} + +impl From for Prescaler { + fn from(val: u8) -> Self { + match val { + 0b000 => Prescaler::Div1, + 0b001 => Prescaler::Div2, + 0b010 => Prescaler::Div4, + 0b011 => Prescaler::Div8, + 0b100 => Prescaler::Div16, + 0b101 => Prescaler::Div32, + 0b110 => Prescaler::Div64, + 0b111 => Prescaler::Div128, + _ => unreachable!(), + } + } +} + +impl Prescaler { + pub fn compute_min_high_res(val: u32) -> Self { + *[ + Prescaler::Div1, + Prescaler::Div2, + Prescaler::Div4, + Prescaler::Div8, + Prescaler::Div16, + Prescaler::Div32, + Prescaler::Div64, + Prescaler::Div128, + ] + .iter() + .skip_while(|psc| **psc as u32 <= val) + .next() + .unwrap() + } + + pub fn compute_min_low_res(val: u32) -> Self { + *[Prescaler::Div32, Prescaler::Div64, Prescaler::Div128] + .iter() + .skip_while(|psc| **psc as u32 <= val) + .next() + .unwrap() + } +} + +pub(crate) trait SealedInstance: RccPeripheral { + fn regs() -> crate::pac::hrtim::Hrtim; + + #[allow(unused)] + fn set_master_frequency(frequency: Hertz) { + let f = frequency.0; + + // TODO: wire up HRTIM to the RCC mux infra. + //#[cfg(stm32f334)] + //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(Self::frequency()).0; + //#[cfg(not(stm32f334))] + let timer_f = Self::frequency().0; + + let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); + let psc = if Self::regs().isr().read().dllrdy() { + Prescaler::compute_min_high_res(psc_min) + } else { + Prescaler::compute_min_low_res(psc_min) + }; + + let timer_f = 32 * (timer_f / psc as u32); + let per: u16 = (timer_f / f) as u16; + + let regs = Self::regs(); + + regs.mcr().modify(|w| w.set_ckpsc(psc.into())); + regs.mper().modify(|w| w.set_mper(per)); + } + + fn set_channel_frequency(channel: usize, frequency: Hertz) { + let f = frequency.0; + + // TODO: wire up HRTIM to the RCC mux infra. + //#[cfg(stm32f334)] + //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(Self::frequency()).0; + //#[cfg(not(stm32f334))] + let timer_f = Self::frequency().0; + + let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); + let psc = if Self::regs().isr().read().dllrdy() { + Prescaler::compute_min_high_res(psc_min) + } else { + Prescaler::compute_min_low_res(psc_min) + }; + + let timer_f = 32 * (timer_f / psc as u32); + let per: u16 = (timer_f / f) as u16; + + let regs = Self::regs(); + + regs.tim(channel).cr().modify(|w| w.set_ckpsc(psc.into())); + regs.tim(channel).per().modify(|w| w.set_per(per)); + } + + /// Set the dead time as a proportion of max_duty + fn set_channel_dead_time(channel: usize, dead_time: u16) { + let regs = Self::regs(); + + let channel_psc: Prescaler = regs.tim(channel).cr().read().ckpsc().into(); + + // The dead-time base clock runs 4 times slower than the hrtim base clock + // u9::MAX = 511 + let psc_min = (channel_psc as u32 * dead_time as u32) / (4 * 511); + let psc = if Self::regs().isr().read().dllrdy() { + Prescaler::compute_min_high_res(psc_min) + } else { + Prescaler::compute_min_low_res(psc_min) + }; + + let dt_val = (psc as u32 * dead_time as u32) / (4 * channel_psc as u32); + + regs.tim(channel).dt().modify(|w| { + w.set_dtprsc(psc.into()); + w.set_dtf(dt_val as u16); + w.set_dtr(dt_val as u16); + }); + } +} + +/// HRTIM instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + 'static {} + +foreach_interrupt! { + ($inst:ident, hrtim, HRTIM, MASTER, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::hrtim::Hrtim { + crate::pac::$inst + } + } + + impl Instance for crate::peripherals::$inst { + + } + }; +} diff --git a/embassy/embassy-stm32/src/hsem/mod.rs b/embassy/embassy-stm32/src/hsem/mod.rs new file mode 100644 index 0000000..06ab7a9 --- /dev/null +++ b/embassy/embassy-stm32/src/hsem/mod.rs @@ -0,0 +1,187 @@ +//! Hardware Semaphore (HSEM) + +// TODO: This code works for all HSEM implemenations except for the STM32WBA52/4/5xx MCUs. +// Those MCUs have a different HSEM implementation (Secure semaphore lock support, +// Privileged / unprivileged semaphore lock support, Semaphore lock protection via semaphore attribute), +// which is not yet supported by this code. +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use crate::rcc::RccPeripheral; +use crate::{pac, Peripheral}; + +/// HSEM error. +#[derive(Debug)] +pub enum HsemError { + /// Locking the semaphore failed. + LockFailed, +} + +/// CPU core. +/// The enum values are identical to the bus master IDs / core Ids defined for each +/// chip family (i.e. stm32h747 see rm0399 table 95) +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[repr(u8)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CoreId { + #[cfg(any(stm32h745, stm32h747, stm32h755, stm32h757))] + /// Cortex-M7, core 1. + Core0 = 0x3, + + #[cfg(any(stm32h745, stm32h747, stm32h755, stm32h757))] + /// Cortex-M4, core 2. + Core1 = 0x1, + + #[cfg(not(any(stm32h745, stm32h747, stm32h755, stm32h757)))] + /// Cortex-M4, core 1 + Core0 = 0x4, + + #[cfg(any(stm32wb, stm32wl))] + /// Cortex-M0+, core 2. + Core1 = 0x8, +} + +/// Get the current core id +/// This code assume that it is only executed on a Cortex-M M0+, M4 or M7 core. +#[inline(always)] +pub fn get_current_coreid() -> CoreId { + let cpuid = unsafe { cortex_m::peripheral::CPUID::PTR.read_volatile().base.read() }; + match cpuid & 0x000000F0 { + #[cfg(any(stm32wb, stm32wl))] + 0x0 => CoreId::Core1, + + #[cfg(not(any(stm32h745, stm32h747, stm32h755, stm32h757)))] + 0x4 => CoreId::Core0, + + #[cfg(any(stm32h745, stm32h747, stm32h755, stm32h757))] + 0x4 => CoreId::Core1, + + #[cfg(any(stm32h745, stm32h747, stm32h755, stm32h757))] + 0x7 => CoreId::Core0, + _ => panic!("Unknown Cortex-M core"), + } +} + +/// Translates the core ID to an index into the interrupt registers. +#[inline(always)] +fn core_id_to_index(core: CoreId) -> usize { + match core { + CoreId::Core0 => 0, + #[cfg(any(stm32h745, stm32h747, stm32h755, stm32h757, stm32wb, stm32wl))] + CoreId::Core1 => 1, + } +} + +/// HSEM driver +pub struct HardwareSemaphore<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> HardwareSemaphore<'d, T> { + /// Creates a new HardwareSemaphore instance. + pub fn new(peripheral: impl Peripheral

+ 'd) -> Self { + into_ref!(peripheral); + HardwareSemaphore { _peri: peripheral } + } + + /// Locks the semaphore. + /// The 2-step lock procedure consists in a write to lock the semaphore, followed by a read to + /// check if the lock has been successful, carried out from the HSEM_Rx register. + pub fn two_step_lock(&mut self, sem_id: u8, process_id: u8) -> Result<(), HsemError> { + T::regs().r(sem_id as usize).write(|w| { + w.set_procid(process_id); + w.set_coreid(get_current_coreid() as u8); + w.set_lock(true); + }); + let reg = T::regs().r(sem_id as usize).read(); + match ( + reg.lock(), + reg.coreid() == get_current_coreid() as u8, + reg.procid() == process_id, + ) { + (true, true, true) => Ok(()), + _ => Err(HsemError::LockFailed), + } + } + + /// Locks the semaphore. + /// The 1-step procedure consists in a read to lock and check the semaphore in a single step, + /// carried out from the HSEM_RLRx register. + pub fn one_step_lock(&mut self, sem_id: u8) -> Result<(), HsemError> { + let reg = T::regs().rlr(sem_id as usize).read(); + match (reg.lock(), reg.coreid() == get_current_coreid() as u8, reg.procid()) { + (false, true, 0) => Ok(()), + _ => Err(HsemError::LockFailed), + } + } + + /// Unlocks the semaphore. + /// Unlocking a semaphore is a protected process, to prevent accidental clearing by a AHB bus + /// core ID or by a process not having the semaphore lock right. + pub fn unlock(&mut self, sem_id: u8, process_id: u8) { + T::regs().r(sem_id as usize).write(|w| { + w.set_procid(process_id); + w.set_coreid(get_current_coreid() as u8); + w.set_lock(false); + }); + } + + /// Unlocks all semaphores. + /// All semaphores locked by a COREID can be unlocked at once by using the HSEM_CR + /// register. Write COREID and correct KEY value in HSEM_CR. All locked semaphores with a + /// matching COREID are unlocked, and may generate an interrupt when enabled. + pub fn unlock_all(&mut self, key: u16, core_id: u8) { + T::regs().cr().write(|w| { + w.set_key(key); + w.set_coreid(core_id); + }); + } + + /// Checks if the semaphore is locked. + pub fn is_semaphore_locked(&self, sem_id: u8) -> bool { + T::regs().r(sem_id as usize).read().lock() + } + + /// Sets the clear (unlock) key + pub fn set_clear_key(&mut self, key: u16) { + T::regs().keyr().modify(|w| w.set_key(key)); + } + + /// Gets the clear (unlock) key + pub fn get_clear_key(&mut self) -> u16 { + T::regs().keyr().read().key() + } + + /// Sets the interrupt enable bit for the semaphore. + pub fn enable_interrupt(&mut self, core_id: CoreId, sem_x: usize, enable: bool) { + T::regs() + .ier(core_id_to_index(core_id)) + .modify(|w| w.set_ise(sem_x, enable)); + } + + /// Gets the interrupt flag for the semaphore. + pub fn is_interrupt_active(&mut self, core_id: CoreId, sem_x: usize) -> bool { + T::regs().isr(core_id_to_index(core_id)).read().isf(sem_x) + } + + /// Clears the interrupt flag for the semaphore. + pub fn clear_interrupt(&mut self, core_id: CoreId, sem_x: usize) { + T::regs() + .icr(core_id_to_index(core_id)) + .write(|w| w.set_isc(sem_x, false)); + } +} + +trait SealedInstance { + fn regs() -> pac::hsem::Hsem; +} + +/// HSEM instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral + Send + 'static {} + +impl SealedInstance for crate::peripherals::HSEM { + fn regs() -> crate::pac::hsem::Hsem { + crate::pac::HSEM + } +} +impl Instance for crate::peripherals::HSEM {} diff --git a/embassy/embassy-stm32/src/i2c/mod.rs b/embassy/embassy-stm32/src/i2c/mod.rs new file mode 100644 index 0000000..1fc91f1 --- /dev/null +++ b/embassy/embassy-stm32/src/i2c/mod.rs @@ -0,0 +1,554 @@ +//! Inter-Integrated-Circuit (I2C) +#![macro_use] + +#[cfg_attr(i2c_v1, path = "v1.rs")] +#[cfg_attr(any(i2c_v2, i2c_v3), path = "v2.rs")] +mod _version; + +use core::future::Future; +use core::iter; +use core::marker::PhantomData; + +use embassy_hal_internal::{Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +#[cfg(feature = "time")] +use embassy_time::{Duration, Instant}; + +use crate::dma::ChannelAndRequest; +#[cfg(gpio_v2)] +use crate::gpio::Pull; +use crate::gpio::{AfType, AnyPin, OutputType, SealedPin as _, Speed}; +use crate::interrupt::typelevel::Interrupt; +use crate::mode::{Async, Blocking, Mode}; +use crate::rcc::{RccInfo, SealedRccPeripheral}; +use crate::time::Hertz; +use crate::{interrupt, peripherals}; + +/// I2C error. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Bus error + Bus, + /// Arbitration lost + Arbitration, + /// ACK not received (either to the address or to a data byte) + Nack, + /// Timeout + Timeout, + /// CRC error + Crc, + /// Overrun error + Overrun, + /// Zero-length transfers are not allowed. + ZeroLengthTransfer, +} + +/// I2C config +#[non_exhaustive] +#[derive(Copy, Clone)] +pub struct Config { + /// Enable internal pullup on SDA. + /// + /// Using external pullup resistors is recommended for I2C. If you do + /// have external pullups you should not enable this. + #[cfg(gpio_v2)] + pub sda_pullup: bool, + /// Enable internal pullup on SCL. + /// + /// Using external pullup resistors is recommended for I2C. If you do + /// have external pullups you should not enable this. + #[cfg(gpio_v2)] + pub scl_pullup: bool, + /// Timeout. + #[cfg(feature = "time")] + pub timeout: embassy_time::Duration, +} + +impl Default for Config { + fn default() -> Self { + Self { + #[cfg(gpio_v2)] + sda_pullup: false, + #[cfg(gpio_v2)] + scl_pullup: false, + #[cfg(feature = "time")] + timeout: embassy_time::Duration::from_millis(1000), + } + } +} + +impl Config { + fn scl_af(&self) -> AfType { + #[cfg(gpio_v1)] + return AfType::output(OutputType::OpenDrain, Speed::Medium); + #[cfg(gpio_v2)] + return AfType::output_pull( + OutputType::OpenDrain, + Speed::Medium, + match self.scl_pullup { + true => Pull::Up, + false => Pull::None, + }, + ); + } + + fn sda_af(&self) -> AfType { + #[cfg(gpio_v1)] + return AfType::output(OutputType::OpenDrain, Speed::Medium); + #[cfg(gpio_v2)] + return AfType::output_pull( + OutputType::OpenDrain, + Speed::Medium, + match self.sda_pullup { + true => Pull::Up, + false => Pull::None, + }, + ); + } +} + +/// I2C driver. +pub struct I2c<'d, M: Mode> { + info: &'static Info, + state: &'static State, + kernel_clock: Hertz, + scl: Option>, + sda: Option>, + tx_dma: Option>, + rx_dma: Option>, + #[cfg(feature = "time")] + timeout: Duration, + _phantom: PhantomData, +} + +impl<'d> I2c<'d, Async> { + /// Create a new I2C driver. + pub fn new( + peri: impl Peripheral

+ 'd, + scl: impl Peripheral

> + 'd, + sda: impl Peripheral

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

> + 'd, + rx_dma: impl Peripheral

> + 'd, + freq: Hertz, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(scl, config.scl_af()), + new_pin!(sda, config.sda_af()), + new_dma!(tx_dma), + new_dma!(rx_dma), + freq, + config, + ) + } +} + +impl<'d> I2c<'d, Blocking> { + /// Create a new blocking I2C driver. + pub fn new_blocking( + peri: impl Peripheral

+ 'd, + scl: impl Peripheral

> + 'd, + sda: impl Peripheral

> + 'd, + freq: Hertz, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(scl, config.scl_af()), + new_pin!(sda, config.sda_af()), + None, + None, + freq, + config, + ) + } +} + +impl<'d, M: Mode> I2c<'d, M> { + /// Create a new I2C driver. + fn new_inner( + _peri: impl Peripheral

+ 'd, + scl: Option>, + sda: Option>, + tx_dma: Option>, + rx_dma: Option>, + freq: Hertz, + config: Config, + ) -> Self { + unsafe { T::EventInterrupt::enable() }; + unsafe { T::ErrorInterrupt::enable() }; + + let mut this = Self { + info: T::info(), + state: T::state(), + kernel_clock: T::frequency(), + scl, + sda, + tx_dma, + rx_dma, + #[cfg(feature = "time")] + timeout: config.timeout, + _phantom: PhantomData, + }; + this.enable_and_init(freq, config); + this + } + + fn enable_and_init(&mut self, freq: Hertz, config: Config) { + self.info.rcc.enable_and_reset(); + self.init(freq, config); + } + + fn timeout(&self) -> Timeout { + Timeout { + #[cfg(feature = "time")] + deadline: Instant::now() + self.timeout, + } + } +} + +impl<'d, M: Mode> Drop for I2c<'d, M> { + fn drop(&mut self) { + self.scl.as_ref().map(|x| x.set_as_disconnected()); + self.sda.as_ref().map(|x| x.set_as_disconnected()); + + self.info.rcc.disable() + } +} + +#[derive(Copy, Clone)] +struct Timeout { + #[cfg(feature = "time")] + deadline: Instant, +} + +#[allow(dead_code)] +impl Timeout { + #[inline] + fn check(self) -> Result<(), Error> { + #[cfg(feature = "time")] + if Instant::now() > self.deadline { + return Err(Error::Timeout); + } + + Ok(()) + } + + #[inline] + fn with(self, fut: impl Future>) -> impl Future> { + #[cfg(feature = "time")] + { + use futures_util::FutureExt; + + embassy_futures::select::select(embassy_time::Timer::at(self.deadline), fut).map(|r| match r { + embassy_futures::select::Either::First(_) => Err(Error::Timeout), + embassy_futures::select::Either::Second(r) => r, + }) + } + + #[cfg(not(feature = "time"))] + fut + } +} + +struct State { + #[allow(unused)] + waker: AtomicWaker, +} + +impl State { + const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +struct Info { + regs: crate::pac::i2c::I2c, + rcc: RccInfo, +} + +peri_trait!( + irqs: [EventInterrupt, ErrorInterrupt], +); + +pin_trait!(SclPin, Instance); +pin_trait!(SdaPin, Instance); +dma_trait!(RxDma, Instance); +dma_trait!(TxDma, Instance); + +/// Event interrupt handler. +pub struct EventInterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for EventInterruptHandler { + unsafe fn on_interrupt() { + _version::on_interrupt::() + } +} + +/// Error interrupt handler. +pub struct ErrorInterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for ErrorInterruptHandler { + unsafe fn on_interrupt() { + _version::on_interrupt::() + } +} + +foreach_peripheral!( + (i2c, $inst:ident) => { + #[allow(private_interfaces)] + impl SealedInstance for peripherals::$inst { + fn info() -> &'static Info { + static INFO: Info = Info{ + regs: crate::pac::$inst, + rcc: crate::peripherals::$inst::RCC_INFO, + }; + &INFO + } + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for peripherals::$inst { + type EventInterrupt = crate::_generated::peripheral_interrupts::$inst::EV; + type ErrorInterrupt = crate::_generated::peripheral_interrupts::$inst::ER; + } + }; +); + +impl<'d, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, M> { + type Error = Error; + + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, buffer) + } +} + +impl<'d, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, M> { + type Error = Error; + + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, write) + } +} + +impl<'d, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, M> { + type Error = Error; + + fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, write, read) + } +} + +impl embedded_hal_1::i2c::Error for Error { + fn kind(&self) -> embedded_hal_1::i2c::ErrorKind { + match *self { + Self::Bus => embedded_hal_1::i2c::ErrorKind::Bus, + Self::Arbitration => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss, + Self::Nack => { + embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Unknown) + } + Self::Timeout => embedded_hal_1::i2c::ErrorKind::Other, + Self::Crc => embedded_hal_1::i2c::ErrorKind::Other, + Self::Overrun => embedded_hal_1::i2c::ErrorKind::Overrun, + Self::ZeroLengthTransfer => embedded_hal_1::i2c::ErrorKind::Other, + } + } +} + +impl<'d, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, M> { + type Error = Error; +} + +impl<'d, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, M> { + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, read) + } + + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, write) + } + + fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, write, read) + } + + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + self.blocking_transaction(address, operations) + } +} + +impl<'d> embedded_hal_async::i2c::I2c for I2c<'d, Async> { + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.read(address, read).await + } + + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.write(address, write).await + } + + async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.write_read(address, write, read).await + } + + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + self.transaction(address, operations).await + } +} + +/// Frame type in I2C transaction. +/// +/// This tells each method what kind of framing to use, to generate a (repeated) start condition (ST +/// or SR), and/or a stop condition (SP). For read operations, this also controls whether to send an +/// ACK or NACK after the last byte received. +/// +/// For write operations, the following options are identical because they differ only in the (N)ACK +/// treatment relevant for read operations: +/// +/// - `FirstFrame` and `FirstAndNextFrame` +/// - `NextFrame` and `LastFrameNoStop` +/// +/// Abbreviations used below: +/// +/// - `ST` = start condition +/// - `SR` = repeated start condition +/// - `SP` = stop condition +/// - `ACK`/`NACK` = last byte in read operation +#[derive(Copy, Clone)] +#[allow(dead_code)] +enum FrameOptions { + /// `[ST/SR]+[NACK]+[SP]` First frame (of this type) in transaction and also last frame overall. + FirstAndLastFrame, + /// `[ST/SR]+[NACK]` First frame of this type in transaction, last frame in a read operation but + /// not the last frame overall. + FirstFrame, + /// `[ST/SR]+[ACK]` First frame of this type in transaction, neither last frame overall nor last + /// frame in a read operation. + FirstAndNextFrame, + /// `[ACK]` Middle frame in a read operation (neither first nor last). + NextFrame, + /// `[NACK]+[SP]` Last frame overall in this transaction but not the first frame. + LastFrame, + /// `[NACK]` Last frame in a read operation but not last frame overall in this transaction. + LastFrameNoStop, +} + +#[allow(dead_code)] +impl FrameOptions { + /// Sends start or repeated start condition before transfer. + fn send_start(self) -> bool { + match self { + Self::FirstAndLastFrame | Self::FirstFrame | Self::FirstAndNextFrame => true, + Self::NextFrame | Self::LastFrame | Self::LastFrameNoStop => false, + } + } + + /// Sends stop condition after transfer. + fn send_stop(self) -> bool { + match self { + Self::FirstAndLastFrame | Self::LastFrame => true, + Self::FirstFrame | Self::FirstAndNextFrame | Self::NextFrame | Self::LastFrameNoStop => false, + } + } + + /// Sends NACK after last byte received, indicating end of read operation. + fn send_nack(self) -> bool { + match self { + Self::FirstAndLastFrame | Self::FirstFrame | Self::LastFrame | Self::LastFrameNoStop => true, + Self::FirstAndNextFrame | Self::NextFrame => false, + } + } +} + +/// Iterates over operations in transaction. +/// +/// Returns necessary frame options for each operation to uphold the [transaction contract] and have +/// the right start/stop/(N)ACK conditions on the wire. +/// +/// [transaction contract]: embedded_hal_1::i2c::I2c::transaction +#[allow(dead_code)] +fn operation_frames<'a, 'b: 'a>( + operations: &'a mut [embedded_hal_1::i2c::Operation<'b>], +) -> Result, FrameOptions)>, Error> { + use embedded_hal_1::i2c::Operation::{Read, Write}; + + // Check empty read buffer before starting transaction. Otherwise, we would risk halting with an + // error in the middle of the transaction. + // + // In principle, we could allow empty read frames within consecutive read operations, as long as + // at least one byte remains in the final (merged) read operation, but that makes the logic more + // complicated and error-prone. + if operations.iter().any(|op| match op { + Read(read) => read.is_empty(), + Write(_) => false, + }) { + return Err(Error::Overrun); + } + + let mut operations = operations.iter_mut().peekable(); + + let mut next_first_frame = true; + + Ok(iter::from_fn(move || { + let Some(op) = operations.next() else { + return None; + }; + + // Is `op` first frame of its type? + let first_frame = next_first_frame; + let next_op = operations.peek(); + + // Get appropriate frame options as combination of the following properties: + // + // - For each first operation of its type, generate a (repeated) start condition. + // - For the last operation overall in the entire transaction, generate a stop condition. + // - For read operations, check the next operation: if it is also a read operation, we merge + // these and send ACK for all bytes in the current operation; send NACK only for the final + // read operation's last byte (before write or end of entire transaction) to indicate last + // byte read and release the bus for transmission of the bus master's next byte (or stop). + // + // We check the third property unconditionally, i.e. even for write opeartions. This is okay + // because the resulting frame options are identical for write operations. + let frame = match (first_frame, next_op) { + (true, None) => FrameOptions::FirstAndLastFrame, + (true, Some(Read(_))) => FrameOptions::FirstAndNextFrame, + (true, Some(Write(_))) => FrameOptions::FirstFrame, + // + (false, None) => FrameOptions::LastFrame, + (false, Some(Read(_))) => FrameOptions::NextFrame, + (false, Some(Write(_))) => FrameOptions::LastFrameNoStop, + }; + + // Pre-calculate if `next_op` is the first operation of its type. We do this here and not at + // the beginning of the loop because we hand out `op` as iterator value and cannot access it + // anymore in the next iteration. + next_first_frame = match (&op, next_op) { + (_, None) => false, + (Read(_), Some(Write(_))) | (Write(_), Some(Read(_))) => true, + (Read(_), Some(Read(_))) | (Write(_), Some(Write(_))) => false, + }; + + Some((op, frame)) + })) +} diff --git a/embassy/embassy-stm32/src/i2c/v1.rs b/embassy/embassy-stm32/src/i2c/v1.rs new file mode 100644 index 0000000..28026f8 --- /dev/null +++ b/embassy/embassy-stm32/src/i2c/v1.rs @@ -0,0 +1,822 @@ +//! # I2Cv1 +//! +//! This implementation is used for STM32F1, STM32F2, STM32F4, and STM32L1 devices. +//! +//! All other devices (as of 2023-12-28) use [`v2`](super::v2) instead. + +use core::future::poll_fn; +use core::task::Poll; + +use embassy_embedded_hal::SetConfig; +use embassy_futures::select::{select, Either}; +use embassy_hal_internal::drop::OnDrop; +use embedded_hal_1::i2c::Operation; + +use super::*; +use crate::mode::Mode as PeriMode; +use crate::pac::i2c; + +// /!\ /!\ +// /!\ Implementation note! /!\ +// /!\ /!\ +// +// It's somewhat unclear whether using interrupts here in a *strictly* one-shot style is actually +// what we want! If you are looking in this file because you are doing async I2C and your code is +// just totally hanging (sometimes), maybe swing by this issue: +// . +// +// There's some more details there, and we might have a fix for you. But please let us know if you +// hit a case like this! +pub unsafe fn on_interrupt() { + let regs = T::info().regs; + // i2c v2 only woke the task on transfer complete interrupts. v1 uses interrupts for a bunch of + // other stuff, so we wake the task on every interrupt. + T::state().waker.wake(); + critical_section::with(|_| { + // Clear event interrupt flag. + regs.cr2().modify(|w| { + w.set_itevten(false); + w.set_iterren(false); + }); + }); +} + +impl<'d, M: PeriMode> I2c<'d, M> { + pub(crate) fn init(&mut self, freq: Hertz, _config: Config) { + self.info.regs.cr1().modify(|reg| { + reg.set_pe(false); + //reg.set_anfoff(false); + }); + + // Errata: "Start cannot be generated after a misplaced Stop" + // + // > If a master generates a misplaced Stop on the bus (bus error) + // > while the microcontroller I2C peripheral attempts to switch to + // > Master mode by setting the START bit, the Start condition is + // > not properly generated. + // + // This also can occur with falsely detected STOP events, for example + // if the SDA line is shorted to low. + // + // The workaround for this is to trigger the SWRST line AFTER power is + // enabled, AFTER PE is disabled and BEFORE making any other configuration. + // + // It COULD be possible to apply this workaround at runtime, instead of + // only on initialization, however this would require detecting the timeout + // or BUSY lockup condition, and re-configuring the peripheral after reset. + // + // This presents as an ~infinite hang on read or write, as the START condition + // is never generated, meaning the start event is never generated. + self.info.regs.cr1().modify(|reg| { + reg.set_swrst(true); + }); + self.info.regs.cr1().modify(|reg| { + reg.set_swrst(false); + }); + + let timings = Timings::new(self.kernel_clock, freq); + + self.info.regs.cr2().modify(|reg| { + reg.set_freq(timings.freq); + }); + self.info.regs.ccr().modify(|reg| { + reg.set_f_s(timings.mode.f_s()); + reg.set_duty(timings.duty.duty()); + reg.set_ccr(timings.ccr); + }); + self.info.regs.trise().modify(|reg| { + reg.set_trise(timings.trise); + }); + + self.info.regs.cr1().modify(|reg| { + reg.set_pe(true); + }); + } + + fn check_and_clear_error_flags(info: &'static Info) -> Result { + // Note that flags should only be cleared once they have been registered. If flags are + // cleared otherwise, there may be an inherent race condition and flags may be missed. + let sr1 = info.regs.sr1().read(); + + if sr1.timeout() { + info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_timeout(false); + }); + return Err(Error::Timeout); + } + + if sr1.pecerr() { + info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_pecerr(false); + }); + return Err(Error::Crc); + } + + if sr1.ovr() { + info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_ovr(false); + }); + return Err(Error::Overrun); + } + + if sr1.af() { + info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_af(false); + }); + return Err(Error::Nack); + } + + if sr1.arlo() { + info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_arlo(false); + }); + return Err(Error::Arbitration); + } + + // The errata indicates that BERR may be incorrectly detected. It recommends ignoring and + // clearing the BERR bit instead. + if sr1.berr() { + info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_berr(false); + }); + } + + Ok(sr1) + } + + fn write_bytes(&mut self, addr: u8, bytes: &[u8], timeout: Timeout, frame: FrameOptions) -> Result<(), Error> { + if frame.send_start() { + // Send a START condition + + self.info.regs.cr1().modify(|reg| { + reg.set_start(true); + }); + + // Wait until START condition was generated + while !Self::check_and_clear_error_flags(self.info)?.start() { + timeout.check()?; + } + + // Check if we were the ones to generate START + if self.info.regs.cr1().read().start() || !self.info.regs.sr2().read().msl() { + return Err(Error::Arbitration); + } + + // Set up current address we're trying to talk to + self.info.regs.dr().write(|reg| reg.set_dr(addr << 1)); + + // 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(self.info)?.addr() { + timeout.check()?; + } + + // Clear condition by reading SR2 + let _ = self.info.regs.sr2().read(); + } + + // Send bytes + for c in bytes { + self.send_byte(*c, timeout)?; + } + + if frame.send_stop() { + // Send a STOP condition + self.info.regs.cr1().modify(|reg| reg.set_stop(true)); + } + + // Fallthrough is success + Ok(()) + } + + fn send_byte(&self, byte: u8, timeout: Timeout) -> 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(self.info)?.txe() + } { + timeout.check()?; + } + + // Push out a byte of data + self.info.regs.dr().write(|reg| reg.set_dr(byte)); + + // Wait until byte is transferred + while { + // Check for any potential error conditions. + !Self::check_and_clear_error_flags(self.info)?.btf() + } { + timeout.check()?; + } + + Ok(()) + } + + fn recv_byte(&self, timeout: Timeout) -> Result { + while { + // Check for any potential error conditions. + Self::check_and_clear_error_flags(self.info)?; + + !self.info.regs.sr1().read().rxne() + } { + timeout.check()?; + } + + let value = self.info.regs.dr().read().dr(); + Ok(value) + } + + fn blocking_read_timeout( + &mut self, + addr: u8, + buffer: &mut [u8], + timeout: Timeout, + frame: FrameOptions, + ) -> Result<(), Error> { + let Some((last, buffer)) = buffer.split_last_mut() else { + return Err(Error::Overrun); + }; + + if frame.send_start() { + // Send a START condition and set ACK bit + self.info.regs.cr1().modify(|reg| { + reg.set_start(true); + reg.set_ack(true); + }); + + // Wait until START condition was generated + while !Self::check_and_clear_error_flags(self.info)?.start() { + timeout.check()?; + } + + // Check if we were the ones to generate START + if self.info.regs.cr1().read().start() || !self.info.regs.sr2().read().msl() { + return Err(Error::Arbitration); + } + + // Set up current address we're trying to talk to + self.info.regs.dr().write(|reg| reg.set_dr((addr << 1) + 1)); + + // Wait until address was sent + // Wait for the address to be acknowledged + while !Self::check_and_clear_error_flags(self.info)?.addr() { + timeout.check()?; + } + + // Clear condition by reading SR2 + let _ = self.info.regs.sr2().read(); + } + + // Receive bytes into buffer + for c in buffer { + *c = self.recv_byte(timeout)?; + } + + // Prepare to send NACK then STOP after next byte + self.info.regs.cr1().modify(|reg| { + if frame.send_nack() { + reg.set_ack(false); + } + if frame.send_stop() { + reg.set_stop(true); + } + }); + + // Receive last byte + *last = self.recv_byte(timeout)?; + + // Fallthrough is success + Ok(()) + } + + /// Blocking read. + pub fn blocking_read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Error> { + self.blocking_read_timeout(addr, read, self.timeout(), FrameOptions::FirstAndLastFrame) + } + + /// Blocking write. + pub fn blocking_write(&mut self, addr: u8, write: &[u8]) -> Result<(), Error> { + self.write_bytes(addr, write, self.timeout(), FrameOptions::FirstAndLastFrame)?; + + // Fallthrough is success + Ok(()) + } + + /// Blocking write, restart, read. + pub fn blocking_write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + // Check empty read buffer before starting transaction. Otherwise, we would not generate the + // stop condition below. + if read.is_empty() { + return Err(Error::Overrun); + } + + let timeout = self.timeout(); + + self.write_bytes(addr, write, timeout, FrameOptions::FirstFrame)?; + self.blocking_read_timeout(addr, read, timeout, FrameOptions::FirstAndLastFrame)?; + + Ok(()) + } + + /// Blocking transaction with operations. + /// + /// Consecutive operations of same type are merged. See [transaction contract] for details. + /// + /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction + pub fn blocking_transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { + let timeout = self.timeout(); + + for (op, frame) in operation_frames(operations)? { + match op { + Operation::Read(read) => self.blocking_read_timeout(addr, read, timeout, frame)?, + Operation::Write(write) => self.write_bytes(addr, write, timeout, frame)?, + } + } + + Ok(()) + } + + // Async + + #[inline] // pretty sure this should always be inlined + fn enable_interrupts(info: &'static Info) -> () { + info.regs.cr2().modify(|w| { + w.set_iterren(true); + w.set_itevten(true); + }); + } +} + +impl<'d> I2c<'d, Async> { + async fn write_frame(&mut self, address: u8, write: &[u8], frame: FrameOptions) -> Result<(), Error> { + self.info.regs.cr2().modify(|w| { + // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for + // reception. + w.set_itbufen(false); + // DMA mode can be enabled for transmission by setting the DMAEN bit in the I2C_CR2 + // register. + w.set_dmaen(true); + // Sending NACK is not necessary (nor possible) for write transfer. + w.set_last(false); + }); + + // Sentinel to disable transfer when an error occurs or future is canceled. + // TODO: Generate STOP condition on cancel? + let on_drop = OnDrop::new(|| { + self.info.regs.cr2().modify(|w| { + w.set_dmaen(false); + w.set_iterren(false); + w.set_itevten(false); + }) + }); + + if frame.send_start() { + // Send a START condition + self.info.regs.cr1().modify(|reg| { + reg.set_start(true); + }); + + // Wait until START condition was generated + poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(self.info) { + Err(e) => Poll::Ready(Err(e)), + Ok(sr1) => { + if sr1.start() { + Poll::Ready(Ok(())) + } else { + // When pending, (re-)enable interrupts to wake us up. + Self::enable_interrupts(self.info); + Poll::Pending + } + } + } + }) + .await?; + + // Check if we were the ones to generate START + if self.info.regs.cr1().read().start() || !self.info.regs.sr2().read().msl() { + return Err(Error::Arbitration); + } + + // Set up current address we're trying to talk to + self.info.regs.dr().write(|reg| reg.set_dr(address << 1)); + + // Wait for the address to be acknowledged + poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(self.info) { + Err(e) => Poll::Ready(Err(e)), + Ok(sr1) => { + if sr1.addr() { + Poll::Ready(Ok(())) + } else { + // When pending, (re-)enable interrupts to wake us up. + Self::enable_interrupts(self.info); + Poll::Pending + } + } + } + }) + .await?; + + // Clear condition by reading SR2 + self.info.regs.sr2().read(); + } + + let dma_transfer = unsafe { + // Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved to + // this address from the memory after each TxE event. + let dst = self.info.regs.dr().as_ptr() as *mut u8; + + self.tx_dma.as_mut().unwrap().write(write, dst, Default::default()) + }; + + // Wait for bytes to be sent, or an error to occur. + let poll_error = poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(self.info) { + Err(e) => Poll::Ready(Err::<(), Error>(e)), + Ok(_) => { + // When pending, (re-)enable interrupts to wake us up. + Self::enable_interrupts(self.info); + Poll::Pending + } + } + }); + + // Wait for either the DMA transfer to successfully finish, or an I2C error to occur. + match select(dma_transfer, poll_error).await { + Either::Second(Err(e)) => Err(e), + _ => Ok(()), + }?; + + self.info.regs.cr2().modify(|w| { + w.set_dmaen(false); + }); + + if frame.send_stop() { + // The I2C transfer itself will take longer than the DMA transfer, so wait for that to finish too. + + // 18.3.8 “Master transmitter: In the interrupt routine after the EOT interrupt, disable DMA + // requests then wait for a BTF event before programming the Stop condition.” + poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(self.info) { + Err(e) => Poll::Ready(Err(e)), + Ok(sr1) => { + if sr1.btf() { + Poll::Ready(Ok(())) + } else { + // When pending, (re-)enable interrupts to wake us up. + Self::enable_interrupts(self.info); + Poll::Pending + } + } + } + }) + .await?; + + self.info.regs.cr1().modify(|w| { + w.set_stop(true); + }); + } + + drop(on_drop); + + // Fallthrough is success + Ok(()) + } + + /// Write. + pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { + self.write_frame(address, write, FrameOptions::FirstAndLastFrame) + .await?; + + Ok(()) + } + + /// Read. + pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { + self.read_frame(address, buffer, FrameOptions::FirstAndLastFrame) + .await?; + + Ok(()) + } + + async fn read_frame(&mut self, address: u8, buffer: &mut [u8], frame: FrameOptions) -> Result<(), Error> { + if buffer.is_empty() { + return Err(Error::Overrun); + } + + // Some branches below depend on whether the buffer contains only a single byte. + let single_byte = buffer.len() == 1; + + self.info.regs.cr2().modify(|w| { + // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for + // reception. + w.set_itbufen(false); + // DMA mode can be enabled for transmission by setting the DMAEN bit in the I2C_CR2 + // register. + w.set_dmaen(true); + // If, in the I2C_CR2 register, the LAST bit is set, I2C automatically sends a NACK + // after the next byte following EOT_1. The user can generate a Stop condition in + // the DMA Transfer Complete interrupt routine if enabled. + w.set_last(frame.send_nack() && !single_byte); + }); + + // Sentinel to disable transfer when an error occurs or future is canceled. + // TODO: Generate STOP condition on cancel? + let on_drop = OnDrop::new(|| { + self.info.regs.cr2().modify(|w| { + w.set_dmaen(false); + w.set_iterren(false); + w.set_itevten(false); + }) + }); + + if frame.send_start() { + // Send a START condition and set ACK bit + self.info.regs.cr1().modify(|reg| { + reg.set_start(true); + reg.set_ack(true); + }); + + // Wait until START condition was generated + poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(self.info) { + Err(e) => Poll::Ready(Err(e)), + Ok(sr1) => { + if sr1.start() { + Poll::Ready(Ok(())) + } else { + // When pending, (re-)enable interrupts to wake us up. + Self::enable_interrupts(self.info); + Poll::Pending + } + } + } + }) + .await?; + + // Check if we were the ones to generate START + if self.info.regs.cr1().read().start() || !self.info.regs.sr2().read().msl() { + return Err(Error::Arbitration); + } + + // Set up current address we're trying to talk to + self.info.regs.dr().write(|reg| reg.set_dr((address << 1) + 1)); + + // Wait for the address to be acknowledged + poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(self.info) { + Err(e) => Poll::Ready(Err(e)), + Ok(sr1) => { + if sr1.addr() { + Poll::Ready(Ok(())) + } else { + // When pending, (re-)enable interrupts to wake us up. + Self::enable_interrupts(self.info); + Poll::Pending + } + } + } + }) + .await?; + + // 18.3.8: When a single byte must be received: the NACK must be programmed during EV6 + // event, i.e. program ACK=0 when ADDR=1, before clearing ADDR flag. + if frame.send_nack() && single_byte { + self.info.regs.cr1().modify(|w| { + w.set_ack(false); + }); + } + + // Clear condition by reading SR2 + self.info.regs.sr2().read(); + } else { + // Before starting reception of single byte (but without START condition, i.e. in case + // of continued frame), program NACK to emit at end of this byte. + if frame.send_nack() && single_byte { + self.info.regs.cr1().modify(|w| { + w.set_ack(false); + }); + } + } + + // 18.3.8: When a single byte must be received: [snip] Then the user can program the STOP + // condition either after clearing ADDR flag, or in the DMA Transfer Complete interrupt + // routine. + if frame.send_stop() && single_byte { + self.info.regs.cr1().modify(|w| { + w.set_stop(true); + }); + } + + let dma_transfer = unsafe { + // Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved + // from this address from the memory after each RxE event. + let src = self.info.regs.dr().as_ptr() as *mut u8; + + self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default()) + }; + + // Wait for bytes to be received, or an error to occur. + let poll_error = poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(self.info) { + Err(e) => Poll::Ready(Err::<(), Error>(e)), + _ => { + // When pending, (re-)enable interrupts to wake us up. + Self::enable_interrupts(self.info); + Poll::Pending + } + } + }); + + match select(dma_transfer, poll_error).await { + Either::Second(Err(e)) => Err(e), + _ => Ok(()), + }?; + + self.info.regs.cr2().modify(|w| { + w.set_dmaen(false); + }); + + if frame.send_stop() && !single_byte { + self.info.regs.cr1().modify(|w| { + w.set_stop(true); + }); + } + + drop(on_drop); + + // Fallthrough is success + Ok(()) + } + + /// Write, restart, read. + pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + // Check empty read buffer before starting transaction. Otherwise, we would not generate the + // stop condition below. + if read.is_empty() { + return Err(Error::Overrun); + } + + self.write_frame(address, write, FrameOptions::FirstFrame).await?; + self.read_frame(address, read, FrameOptions::FirstAndLastFrame).await + } + + /// Transaction with operations. + /// + /// Consecutive operations of same type are merged. See [transaction contract] for details. + /// + /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction + pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { + for (op, frame) in operation_frames(operations)? { + match op { + Operation::Read(read) => self.read_frame(addr, read, frame).await?, + Operation::Write(write) => self.write_frame(addr, write, frame).await?, + } + } + + Ok(()) + } +} + +enum Mode { + Fast, + Standard, +} + +impl Mode { + fn f_s(&self) -> i2c::vals::FS { + match self { + Mode::Fast => i2c::vals::FS::FAST, + Mode::Standard => i2c::vals::FS::STANDARD, + } + } +} + +enum Duty { + Duty2_1, + Duty16_9, +} + +impl Duty { + fn duty(&self) -> i2c::vals::Duty { + match self { + Duty::Duty2_1 => i2c::vals::Duty::DUTY2_1, + Duty::Duty16_9 => i2c::vals::Duty::DUTY16_9, + } + } +} + +struct Timings { + freq: u8, + mode: Mode, + trise: u8, + ccr: u16, + duty: Duty, +} + +impl Timings { + fn new(i2cclk: Hertz, speed: Hertz) -> Self { + // Calculate settings for I2C speed modes + let speed = speed.0; + let clock = i2cclk.0; + let freq = clock / 1_000_000; + assert!((2..=50).contains(&freq)); + + // Configure bus frequency into I2C peripheral + let trise = if speed <= 100_000 { + freq + 1 + } else { + (freq * 300) / 1000 + 1 + }; + + let mut ccr; + let duty; + let mode; + + // I2C clock control calculation + if speed <= 100_000 { + duty = Duty::Duty2_1; + mode = Mode::Standard; + ccr = { + let ccr = clock / (speed * 2); + if ccr < 4 { + 4 + } else { + ccr + } + }; + } else { + const DUTYCYCLE: u8 = 0; + mode = Mode::Fast; + if DUTYCYCLE == 0 { + duty = Duty::Duty2_1; + ccr = clock / (speed * 3); + ccr = if ccr < 1 { 1 } else { ccr }; + + // Set clock to fast mode with appropriate parameters for selected speed (2:1 duty cycle) + } else { + duty = Duty::Duty16_9; + ccr = clock / (speed * 25); + ccr = if ccr < 1 { 1 } else { ccr }; + + // Set clock to fast mode with appropriate parameters for selected speed (16:9 duty cycle) + } + } + + Self { + freq: freq as u8, + trise: trise as u8, + ccr: ccr as u16, + duty, + mode, + //prescale: presc_reg, + //scll, + //sclh, + //sdadel, + //scldel, + } + } +} + +impl<'d, M: PeriMode> SetConfig for I2c<'d, M> { + type Config = Hertz; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { + let timings = Timings::new(self.kernel_clock, *config); + self.info.regs.cr2().modify(|reg| { + reg.set_freq(timings.freq); + }); + self.info.regs.ccr().modify(|reg| { + reg.set_f_s(timings.mode.f_s()); + reg.set_duty(timings.duty.duty()); + reg.set_ccr(timings.ccr); + }); + self.info.regs.trise().modify(|reg| { + reg.set_trise(timings.trise); + }); + + Ok(()) + } +} diff --git a/embassy/embassy-stm32/src/i2c/v2.rs b/embassy/embassy-stm32/src/i2c/v2.rs new file mode 100644 index 0000000..8c8df79 --- /dev/null +++ b/embassy/embassy-stm32/src/i2c/v2.rs @@ -0,0 +1,818 @@ +use core::cmp; +use core::future::poll_fn; +use core::task::Poll; + +use embassy_embedded_hal::SetConfig; +use embassy_hal_internal::drop::OnDrop; +use embedded_hal_1::i2c::Operation; + +use super::*; +use crate::pac::i2c; + +pub(crate) unsafe fn on_interrupt() { + let regs = T::info().regs; + let isr = regs.isr().read(); + + if isr.tcr() || isr.tc() { + T::state().waker.wake(); + } + // The flag can only be cleared by writting to nbytes, we won't do that here, so disable + // the interrupt + critical_section::with(|_| { + regs.cr1().modify(|w| w.set_tcie(false)); + }); +} + +impl<'d, M: Mode> I2c<'d, M> { + pub(crate) fn init(&mut self, freq: Hertz, _config: Config) { + self.info.regs.cr1().modify(|reg| { + reg.set_pe(false); + reg.set_anfoff(false); + }); + + let timings = Timings::new(self.kernel_clock, freq.into()); + + self.info.regs.timingr().write(|reg| { + reg.set_presc(timings.prescale); + reg.set_scll(timings.scll); + reg.set_sclh(timings.sclh); + reg.set_sdadel(timings.sdadel); + reg.set_scldel(timings.scldel); + }); + + self.info.regs.cr1().modify(|reg| { + reg.set_pe(true); + }); + } + + fn master_stop(&mut self) { + self.info.regs.cr2().write(|w| w.set_stop(true)); + } + + fn master_read( + info: &'static Info, + address: u8, + length: usize, + stop: Stop, + reload: bool, + restart: bool, + timeout: Timeout, + ) -> 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 info.regs.cr2().read().start() { + timeout.check()?; + } + } + + // Set START and prepare to receive bytes into + // `buffer`. The START bit can be set even if the bus + // is BUSY or I2C is in slave mode. + + let reload = if reload { + i2c::vals::Reload::NOTCOMPLETED + } else { + i2c::vals::Reload::COMPLETED + }; + + info.regs.cr2().modify(|w| { + w.set_sadd((address << 1 | 0) as u16); + w.set_add10(i2c::vals::Addmode::BIT7); + w.set_dir(i2c::vals::Dir::READ); + w.set_nbytes(length as u8); + w.set_start(true); + w.set_autoend(stop.autoend()); + w.set_reload(reload); + }); + + Ok(()) + } + + fn master_write( + info: &'static Info, + address: u8, + length: usize, + stop: Stop, + reload: bool, + timeout: Timeout, + ) -> 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 info.regs.cr2().read().start() { + timeout.check()?; + } + + // Wait for the bus to be free + while info.regs.isr().read().busy() { + timeout.check()?; + } + + let reload = if reload { + i2c::vals::Reload::NOTCOMPLETED + } else { + i2c::vals::Reload::COMPLETED + }; + + // Set START and prepare to send `bytes`. The + // START bit can be set even if the bus is BUSY or + // I2C is in slave mode. + info.regs.cr2().modify(|w| { + w.set_sadd((address << 1 | 0) as u16); + w.set_add10(i2c::vals::Addmode::BIT7); + w.set_dir(i2c::vals::Dir::WRITE); + w.set_nbytes(length as u8); + w.set_start(true); + w.set_autoend(stop.autoend()); + w.set_reload(reload); + }); + + Ok(()) + } + + fn master_continue(info: &'static Info, length: usize, reload: bool, timeout: Timeout) -> Result<(), Error> { + assert!(length < 256 && length > 0); + + while !info.regs.isr().read().tcr() { + timeout.check()?; + } + + let reload = if reload { + i2c::vals::Reload::NOTCOMPLETED + } else { + i2c::vals::Reload::COMPLETED + }; + + info.regs.cr2().modify(|w| { + w.set_nbytes(length as u8); + w.set_reload(reload); + }); + + Ok(()) + } + + fn flush_txdr(&self) { + if self.info.regs.isr().read().txis() { + self.info.regs.txdr().write(|w| w.set_txdata(0)); + } + if !self.info.regs.isr().read().txe() { + self.info.regs.isr().modify(|w| w.set_txe(true)) + } + } + + fn wait_txe(&self, timeout: Timeout) -> Result<(), Error> { + loop { + let isr = self.info.regs.isr().read(); + if isr.txe() { + return Ok(()); + } else if isr.berr() { + self.info.regs.icr().write(|reg| reg.set_berrcf(true)); + return Err(Error::Bus); + } else if isr.arlo() { + self.info.regs.icr().write(|reg| reg.set_arlocf(true)); + return Err(Error::Arbitration); + } else if isr.nackf() { + self.info.regs.icr().write(|reg| reg.set_nackcf(true)); + self.flush_txdr(); + return Err(Error::Nack); + } + + timeout.check()?; + } + } + + fn wait_rxne(&self, timeout: Timeout) -> Result<(), Error> { + loop { + let isr = self.info.regs.isr().read(); + if isr.rxne() { + return Ok(()); + } else if isr.berr() { + self.info.regs.icr().write(|reg| reg.set_berrcf(true)); + return Err(Error::Bus); + } else if isr.arlo() { + self.info.regs.icr().write(|reg| reg.set_arlocf(true)); + return Err(Error::Arbitration); + } else if isr.nackf() { + self.info.regs.icr().write(|reg| reg.set_nackcf(true)); + self.flush_txdr(); + return Err(Error::Nack); + } + + timeout.check()?; + } + } + + fn wait_tc(&self, timeout: Timeout) -> Result<(), Error> { + loop { + let isr = self.info.regs.isr().read(); + if isr.tc() { + return Ok(()); + } else if isr.berr() { + self.info.regs.icr().write(|reg| reg.set_berrcf(true)); + return Err(Error::Bus); + } else if isr.arlo() { + self.info.regs.icr().write(|reg| reg.set_arlocf(true)); + return Err(Error::Arbitration); + } else if isr.nackf() { + self.info.regs.icr().write(|reg| reg.set_nackcf(true)); + self.flush_txdr(); + return Err(Error::Nack); + } + + timeout.check()?; + } + } + + fn read_internal(&mut self, address: u8, read: &mut [u8], restart: bool, timeout: Timeout) -> Result<(), Error> { + let completed_chunks = read.len() / 255; + let total_chunks = if completed_chunks * 255 == read.len() { + completed_chunks + } else { + completed_chunks + 1 + }; + let last_chunk_idx = total_chunks.saturating_sub(1); + + Self::master_read( + self.info, + address, + read.len().min(255), + Stop::Automatic, + last_chunk_idx != 0, + restart, + timeout, + )?; + + for (number, chunk) in read.chunks_mut(255).enumerate() { + if number != 0 { + Self::master_continue(self.info, chunk.len(), number != last_chunk_idx, timeout)?; + } + + for byte in chunk { + // Wait until we have received something + self.wait_rxne(timeout)?; + + *byte = self.info.regs.rxdr().read().rxdata(); + } + } + Ok(()) + } + + fn write_internal(&mut self, address: u8, write: &[u8], send_stop: bool, timeout: Timeout) -> Result<(), Error> { + let completed_chunks = write.len() / 255; + let total_chunks = if completed_chunks * 255 == write.len() { + completed_chunks + } else { + completed_chunks + 1 + }; + let last_chunk_idx = total_chunks.saturating_sub(1); + + // I2C start + // + // ST SAD+W + if let Err(err) = Self::master_write( + self.info, + address, + write.len().min(255), + Stop::Software, + last_chunk_idx != 0, + timeout, + ) { + if send_stop { + self.master_stop(); + } + return Err(err); + } + + for (number, chunk) in write.chunks(255).enumerate() { + if number != 0 { + Self::master_continue(self.info, chunk.len(), number != last_chunk_idx, timeout)?; + } + + for byte in chunk { + // Wait until we are allowed to send data + // (START has been ACKed or last byte when + // through) + if let Err(err) = self.wait_txe(timeout) { + if send_stop { + self.master_stop(); + } + return Err(err); + } + + self.info.regs.txdr().write(|w| w.set_txdata(*byte)); + } + } + // Wait until the write finishes + let result = self.wait_tc(timeout); + if send_stop { + self.master_stop(); + } + result + } + + // ========================= + // Blocking public API + + /// Blocking read. + pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Error> { + self.read_internal(address, read, false, self.timeout()) + // Automatic Stop + } + + /// Blocking write. + pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { + self.write_internal(address, write, true, self.timeout()) + } + + /// Blocking write, restart, read. + pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + let timeout = self.timeout(); + self.write_internal(address, write, false, timeout)?; + self.read_internal(address, read, true, timeout) + // Automatic Stop + } + + /// Blocking transaction with operations. + /// + /// Consecutive operations of same type are merged. See [transaction contract] for details. + /// + /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction + pub fn blocking_transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { + let _ = addr; + let _ = operations; + todo!() + } + + /// Blocking write multiple buffers. + /// + /// The buffers are concatenated in a single write transaction. + pub fn blocking_write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error> { + if write.is_empty() { + return Err(Error::ZeroLengthTransfer); + } + + let timeout = self.timeout(); + + let first_length = write[0].len(); + let last_slice_index = write.len() - 1; + + if let Err(err) = Self::master_write( + self.info, + address, + first_length.min(255), + Stop::Software, + (first_length > 255) || (last_slice_index != 0), + timeout, + ) { + self.master_stop(); + return Err(err); + } + + for (idx, slice) in write.iter().enumerate() { + let slice_len = slice.len(); + let completed_chunks = slice_len / 255; + let total_chunks = if completed_chunks * 255 == slice_len { + completed_chunks + } else { + completed_chunks + 1 + }; + let last_chunk_idx = total_chunks.saturating_sub(1); + + if idx != 0 { + if let Err(err) = Self::master_continue( + self.info, + slice_len.min(255), + (idx != last_slice_index) || (slice_len > 255), + timeout, + ) { + self.master_stop(); + return Err(err); + } + } + + for (number, chunk) in slice.chunks(255).enumerate() { + if number != 0 { + if let Err(err) = Self::master_continue( + self.info, + chunk.len(), + (number != last_chunk_idx) || (idx != last_slice_index), + timeout, + ) { + self.master_stop(); + return Err(err); + } + } + + for byte in chunk { + // Wait until we are allowed to send data + // (START has been ACKed or last byte when + // through) + if let Err(err) = self.wait_txe(timeout) { + self.master_stop(); + return Err(err); + } + + // Put byte on the wire + //self.i2c.txdr.write(|w| w.txdata().bits(*byte)); + self.info.regs.txdr().write(|w| w.set_txdata(*byte)); + } + } + } + // Wait until the write finishes + let result = self.wait_tc(timeout); + self.master_stop(); + result + } +} + +impl<'d> I2c<'d, Async> { + async fn write_dma_internal( + &mut self, + address: u8, + write: &[u8], + first_slice: bool, + last_slice: bool, + timeout: Timeout, + ) -> Result<(), Error> { + let total_len = write.len(); + + let dma_transfer = unsafe { + let regs = self.info.regs; + regs.cr1().modify(|w| { + w.set_txdmaen(true); + if first_slice { + w.set_tcie(true); + } + }); + let dst = regs.txdr().as_ptr() as *mut u8; + + self.tx_dma.as_mut().unwrap().write(write, dst, Default::default()) + }; + + let mut remaining_len = total_len; + + let on_drop = OnDrop::new(|| { + let regs = self.info.regs; + regs.cr1().modify(|w| { + if last_slice { + w.set_txdmaen(false); + } + w.set_tcie(false); + }) + }); + + poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + let isr = self.info.regs.isr().read(); + if remaining_len == total_len { + if first_slice { + Self::master_write( + self.info, + address, + total_len.min(255), + Stop::Software, + (total_len > 255) || !last_slice, + timeout, + )?; + } else { + Self::master_continue(self.info, total_len.min(255), (total_len > 255) || !last_slice, timeout)?; + self.info.regs.cr1().modify(|w| w.set_tcie(true)); + } + } else if !(isr.tcr() || isr.tc()) { + // poll_fn was woken without an interrupt present + return Poll::Pending; + } else if remaining_len == 0 { + return Poll::Ready(Ok(())); + } else { + let last_piece = (remaining_len <= 255) && last_slice; + + if let Err(e) = Self::master_continue(self.info, remaining_len.min(255), !last_piece, timeout) { + return Poll::Ready(Err(e)); + } + self.info.regs.cr1().modify(|w| w.set_tcie(true)); + } + + remaining_len = remaining_len.saturating_sub(255); + Poll::Pending + }) + .await?; + + dma_transfer.await; + + if last_slice { + // This should be done already + self.wait_tc(timeout)?; + self.master_stop(); + } + + drop(on_drop); + + Ok(()) + } + + async fn read_dma_internal( + &mut self, + address: u8, + buffer: &mut [u8], + restart: bool, + timeout: Timeout, + ) -> Result<(), Error> { + let total_len = buffer.len(); + + let dma_transfer = unsafe { + let regs = self.info.regs; + regs.cr1().modify(|w| { + w.set_rxdmaen(true); + w.set_tcie(true); + }); + let src = regs.rxdr().as_ptr() as *mut u8; + + self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default()) + }; + + let mut remaining_len = total_len; + + let on_drop = OnDrop::new(|| { + let regs = self.info.regs; + regs.cr1().modify(|w| { + w.set_rxdmaen(false); + w.set_tcie(false); + }) + }); + + poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + let isr = self.info.regs.isr().read(); + if remaining_len == total_len { + Self::master_read( + self.info, + address, + total_len.min(255), + Stop::Software, + total_len > 255, + restart, + timeout, + )?; + } else if !(isr.tcr() || isr.tc()) { + // poll_fn was woken without an interrupt present + return Poll::Pending; + } else if remaining_len == 0 { + return Poll::Ready(Ok(())); + } else { + let last_piece = remaining_len <= 255; + + if let Err(e) = Self::master_continue(self.info, remaining_len.min(255), !last_piece, timeout) { + return Poll::Ready(Err(e)); + } + self.info.regs.cr1().modify(|w| w.set_tcie(true)); + } + + remaining_len = remaining_len.saturating_sub(255); + Poll::Pending + }) + .await?; + + dma_transfer.await; + + // This should be done already + self.wait_tc(timeout)?; + self.master_stop(); + + drop(on_drop); + + Ok(()) + } + + // ========================= + // Async public API + + /// Write. + pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { + let timeout = self.timeout(); + if write.is_empty() { + self.write_internal(address, write, true, timeout) + } else { + timeout + .with(self.write_dma_internal(address, write, true, true, timeout)) + .await + } + } + + /// Write multiple buffers. + /// + /// The buffers are concatenated in a single write transaction. + pub async fn write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error> { + let timeout = self.timeout(); + + if write.is_empty() { + return Err(Error::ZeroLengthTransfer); + } + let mut iter = write.iter(); + + let mut first = true; + let mut current = iter.next(); + while let Some(c) = current { + let next = iter.next(); + let is_last = next.is_none(); + + let fut = self.write_dma_internal(address, c, first, is_last, timeout); + timeout.with(fut).await?; + first = false; + current = next; + } + Ok(()) + } + + /// Read. + pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { + let timeout = self.timeout(); + + if buffer.is_empty() { + self.read_internal(address, buffer, false, timeout) + } else { + let fut = self.read_dma_internal(address, buffer, false, timeout); + timeout.with(fut).await + } + } + + /// Write, restart, read. + pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + let timeout = self.timeout(); + + if write.is_empty() { + self.write_internal(address, write, false, timeout)?; + } else { + let fut = self.write_dma_internal(address, write, true, true, timeout); + timeout.with(fut).await?; + } + + if read.is_empty() { + self.read_internal(address, read, true, timeout)?; + } else { + let fut = self.read_dma_internal(address, read, true, timeout); + timeout.with(fut).await?; + } + + Ok(()) + } + + /// Transaction with operations. + /// + /// Consecutive operations of same type are merged. See [transaction contract] for details. + /// + /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction + pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { + let _ = addr; + let _ = operations; + todo!() + } +} + +/// I2C Stop Configuration +/// +/// Peripheral options for generating the STOP condition +#[derive(Copy, Clone, PartialEq)] +enum Stop { + /// Software end mode: Must write register to generate STOP condition + Software, + /// Automatic end mode: A STOP condition is automatically generated once the + /// configured number of bytes have been transferred + Automatic, +} + +impl Stop { + fn autoend(&self) -> i2c::vals::Autoend { + match self { + Stop::Software => i2c::vals::Autoend::SOFTWARE, + Stop::Automatic => i2c::vals::Autoend::AUTOMATIC, + } + } +} + +struct Timings { + prescale: u8, + scll: u8, + sclh: u8, + sdadel: u8, + scldel: u8, +} + +impl Timings { + fn new(i2cclk: Hertz, freq: Hertz) -> Self { + let i2cclk = i2cclk.0; + let freq = freq.0; + // Refer to RM0433 Rev 7 Figure 539 for setup and hold timing: + // + // t_I2CCLK = 1 / PCLK1 + // t_PRESC = (PRESC + 1) * t_I2CCLK + // t_SCLL = (SCLL + 1) * t_PRESC + // t_SCLH = (SCLH + 1) * t_PRESC + // + // t_SYNC1 + t_SYNC2 > 4 * t_I2CCLK + // t_SCL ~= t_SYNC1 + t_SYNC2 + t_SCLL + t_SCLH + let ratio = i2cclk / freq; + + // For the standard-mode configuration method, we must have a ratio of 4 + // or higher + assert!(ratio >= 4, "The I2C PCLK must be at least 4 times the bus frequency!"); + + let (presc_reg, scll, sclh, sdadel, scldel) = if freq > 100_000 { + // Fast-mode (Fm) or Fast-mode Plus (Fm+) + // here we pick SCLL + 1 = 2 * (SCLH + 1) + + // Prescaler, 384 ticks for sclh/scll. Round up then subtract 1 + let presc_reg = ((ratio - 1) / 384) as u8; + // ratio < 1200 by pclk 120MHz max., therefore presc < 16 + + // Actual precale value selected + let presc = (presc_reg + 1) as u32; + + let sclh = ((ratio / presc) - 3) / 3; + let scll = (2 * (sclh + 1)) - 1; + + let (sdadel, scldel) = if freq > 400_000 { + // Fast-mode Plus (Fm+) + assert!(i2cclk >= 17_000_000); // See table in datsheet + + let sdadel = i2cclk / 8_000_000 / presc; + let scldel = i2cclk / 4_000_000 / presc - 1; + + (sdadel, scldel) + } else { + // Fast-mode (Fm) + assert!(i2cclk >= 8_000_000); // See table in datsheet + + let sdadel = i2cclk / 4_000_000 / presc; + let scldel = i2cclk / 2_000_000 / presc - 1; + + (sdadel, scldel) + }; + + (presc_reg, scll as u8, sclh as u8, sdadel as u8, scldel as u8) + } else { + // Standard-mode (Sm) + // here we pick SCLL = SCLH + assert!(i2cclk >= 2_000_000); // See table in datsheet + + // Prescaler, 512 ticks for sclh/scll. Round up then + // subtract 1 + let presc = (ratio - 1) / 512; + let presc_reg = cmp::min(presc, 15) as u8; + + // Actual prescale value selected + let presc = (presc_reg + 1) as u32; + + let sclh = ((ratio / presc) - 2) / 2; + let scll = sclh; + + // Speed check + assert!(sclh < 256, "The I2C PCLK is too fast for this bus frequency!"); + + let sdadel = i2cclk / 2_000_000 / presc; + let scldel = i2cclk / 500_000 / presc - 1; + + (presc_reg, scll as u8, sclh as u8, sdadel as u8, scldel as u8) + }; + + // Sanity check + assert!(presc_reg < 16); + + // Keep values within reasonable limits for fast per_ck + let sdadel = cmp::max(sdadel, 2); + let scldel = cmp::max(scldel, 4); + + //(presc_reg, scll, sclh, sdadel, scldel) + Self { + prescale: presc_reg, + scll, + sclh, + sdadel, + scldel, + } + } +} + +impl<'d, M: Mode> SetConfig for I2c<'d, M> { + type Config = Hertz; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { + let timings = Timings::new(self.kernel_clock, *config); + self.info.regs.timingr().write(|reg| { + reg.set_presc(timings.prescale); + reg.set_scll(timings.scll); + reg.set_sclh(timings.sclh); + reg.set_sdadel(timings.sdadel); + reg.set_scldel(timings.scldel); + }); + + Ok(()) + } +} diff --git a/embassy/embassy-stm32/src/i2s.rs b/embassy/embassy-stm32/src/i2s.rs new file mode 100644 index 0000000..f11371f --- /dev/null +++ b/embassy/embassy-stm32/src/i2s.rs @@ -0,0 +1,715 @@ +//! Inter-IC Sound (I2S) + +use embassy_futures::join::join; +use embassy_hal_internal::into_ref; +use stm32_metapac::spi::vals; + +use crate::dma::{ringbuffer, ChannelAndRequest, ReadableRingBuffer, TransferOptions, WritableRingBuffer}; +use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed}; +use crate::mode::Async; +use crate::spi::{Config as SpiConfig, RegsExt as _, *}; +use crate::time::Hertz; +use crate::{Peripheral, PeripheralRef}; + +/// I2S mode +#[derive(Copy, Clone)] +pub enum Mode { + /// Master mode + Master, + /// Slave mode + Slave, +} + +/// I2S function +#[derive(Copy, Clone)] +#[allow(dead_code)] +enum Function { + /// Transmit audio data + Transmit, + /// Receive audio data + Receive, + #[cfg(spi_v3)] + /// Transmit and Receive audio data + FullDuplex, +} + +/// I2C standard +#[derive(Copy, Clone)] +pub enum Standard { + /// Philips + Philips, + /// Most significant bit first. + MsbFirst, + /// Least significant bit first. + LsbFirst, + /// PCM with long sync. + PcmLongSync, + /// PCM with short sync. + PcmShortSync, +} + +/// SAI error +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// `write` called on a SAI in receive mode. + NotATransmitter, + /// `read` called on a SAI in transmit mode. + NotAReceiver, + /// Overrun + Overrun, +} + +impl From for Error { + fn from(#[allow(unused)] err: ringbuffer::Error) -> Self { + #[cfg(feature = "defmt")] + { + if err == ringbuffer::Error::DmaUnsynced { + defmt::error!("Ringbuffer broken invariants detected!"); + } + } + Self::Overrun + } +} + +impl Standard { + #[cfg(any(spi_v1, spi_v3, spi_f1))] + const fn i2sstd(&self) -> vals::I2sstd { + match self { + Standard::Philips => vals::I2sstd::PHILIPS, + Standard::MsbFirst => vals::I2sstd::MSB, + Standard::LsbFirst => vals::I2sstd::LSB, + Standard::PcmLongSync => vals::I2sstd::PCM, + Standard::PcmShortSync => vals::I2sstd::PCM, + } + } + + #[cfg(any(spi_v1, spi_v3, spi_f1))] + const fn pcmsync(&self) -> vals::Pcmsync { + match self { + Standard::PcmLongSync => vals::Pcmsync::LONG, + _ => vals::Pcmsync::SHORT, + } + } +} + +/// I2S data format. +#[derive(Copy, Clone)] +pub enum Format { + /// 16 bit data length on 16 bit wide channel + Data16Channel16, + /// 16 bit data length on 32 bit wide channel + Data16Channel32, + /// 24 bit data length on 32 bit wide channel + Data24Channel32, + /// 32 bit data length on 32 bit wide channel + Data32Channel32, +} + +impl Format { + #[cfg(any(spi_v1, spi_v3, spi_f1))] + const fn datlen(&self) -> vals::Datlen { + match self { + Format::Data16Channel16 => vals::Datlen::BITS16, + Format::Data16Channel32 => vals::Datlen::BITS16, + Format::Data24Channel32 => vals::Datlen::BITS24, + Format::Data32Channel32 => vals::Datlen::BITS32, + } + } + + #[cfg(any(spi_v1, spi_v3, spi_f1))] + const fn chlen(&self) -> vals::Chlen { + match self { + Format::Data16Channel16 => vals::Chlen::BITS16, + Format::Data16Channel32 => vals::Chlen::BITS32, + Format::Data24Channel32 => vals::Chlen::BITS32, + Format::Data32Channel32 => vals::Chlen::BITS32, + } + } +} + +/// Clock polarity +#[derive(Copy, Clone)] +pub enum ClockPolarity { + /// Low on idle. + IdleLow, + /// High on idle. + IdleHigh, +} + +impl ClockPolarity { + #[cfg(any(spi_v1, spi_v3, spi_f1))] + const fn ckpol(&self) -> vals::Ckpol { + match self { + ClockPolarity::IdleHigh => vals::Ckpol::IDLEHIGH, + ClockPolarity::IdleLow => vals::Ckpol::IDLELOW, + } + } +} + +/// [`I2S`] configuration. +/// +/// - `MS`: `Master` or `Slave` +/// - `TR`: `Transmit` or `Receive` +/// - `STD`: I2S standard, eg `Philips` +/// - `FMT`: Frame Format marker, eg `Data16Channel16` +#[non_exhaustive] +#[derive(Copy, Clone)] +pub struct Config { + /// Mode + pub mode: Mode, + /// Which I2S standard to use. + pub standard: Standard, + /// Data format. + pub format: Format, + /// Clock polarity. + pub clock_polarity: ClockPolarity, + /// True to enable master clock output from this instance. + pub master_clock: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + mode: Mode::Master, + standard: Standard::Philips, + format: Format::Data16Channel16, + clock_polarity: ClockPolarity::IdleLow, + master_clock: true, + } + } +} + +/// I2S driver writer. Useful for moving write functionality across tasks. +pub struct Writer<'s, 'd, W: Word>(&'s mut WritableRingBuffer<'d, W>); + +impl<'s, 'd, W: Word> Writer<'s, 'd, W> { + /// Write data to the I2S ringbuffer. + /// This appends the data to the buffer and returns immediately. The data will be transmitted in the background. + /// If thfre’s no space in the buffer, this waits until there is. + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { + self.0.write_exact(data).await?; + Ok(()) + } + + /// Reset the ring buffer to its initial state. + /// Can be used to recover from overrun. + /// The ringbuffer will always auto-reset on Overrun in any case. + pub fn reset(&mut self) { + self.0.clear(); + } +} + +/// I2S driver reader. Useful for moving read functionality across tasks. +pub struct Reader<'s, 'd, W: Word>(&'s mut ReadableRingBuffer<'d, W>); + +impl<'s, 'd, W: Word> Reader<'s, 'd, W> { + /// Read data from the I2S ringbuffer. + /// SAI is always receiving data in the background. This function pops already-received data from the buffer. + /// If there’s less than data.len() data in the buffer, this waits until there is. + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + self.0.read_exact(data).await?; + Ok(()) + } + + /// Reset the ring buffer to its initial state. + /// Can be used to prevent overrun. + /// The ringbuffer will always auto-reset on Overrun in any case. + pub fn reset(&mut self) { + self.0.clear(); + } +} + +/// I2S driver. +pub struct I2S<'d, W: Word> { + #[allow(dead_code)] + mode: Mode, + spi: Spi<'d, Async>, + txsd: Option>, + rxsd: Option>, + ws: Option>, + ck: Option>, + mck: Option>, + tx_ring_buffer: Option>, + rx_ring_buffer: Option>, +} + +impl<'d, W: Word> I2S<'d, W> { + /// Create a transmitter driver. + pub fn new_txonly( + peri: impl Peripheral

+ 'd, + sd: impl Peripheral

> + 'd, + ws: impl Peripheral

> + 'd, + ck: impl Peripheral

> + 'd, + mck: impl Peripheral

> + 'd, + txdma: impl Peripheral

> + 'd, + txdma_buf: &'d mut [W], + freq: Hertz, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + ws, + ck, + new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_dma!(txdma).map(|d| (d, txdma_buf)), + None, + freq, + config, + Function::Transmit, + ) + } + + /// Create a transmitter driver without a master clock pin. + pub fn new_txonly_nomck( + peri: impl Peripheral

+ 'd, + sd: impl Peripheral

> + 'd, + ws: impl Peripheral

> + 'd, + ck: impl Peripheral

> + 'd, + txdma: impl Peripheral

> + 'd, + txdma_buf: &'d mut [W], + freq: Hertz, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + ws, + ck, + None, + new_dma!(txdma).map(|d| (d, txdma_buf)), + None, + freq, + config, + Function::Transmit, + ) + } + + /// Create a receiver driver. + pub fn new_rxonly( + peri: impl Peripheral

+ 'd, + sd: impl Peripheral

> + 'd, + ws: impl Peripheral

> + 'd, + ck: impl Peripheral

> + 'd, + mck: impl Peripheral

> + 'd, + rxdma: impl Peripheral

> + 'd, + rxdma_buf: &'d mut [W], + freq: Hertz, + config: Config, + ) -> Self { + Self::new_inner( + peri, + None, + new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + ws, + ck, + new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + new_dma!(rxdma).map(|d| (d, rxdma_buf)), + freq, + config, + Function::Receive, + ) + } + + #[cfg(spi_v3)] + /// Create a full duplex driver. + pub fn new_full_duplex( + peri: impl Peripheral

+ 'd, + txsd: impl Peripheral

> + 'd, + rxsd: impl Peripheral

> + 'd, + ws: impl Peripheral

> + 'd, + ck: impl Peripheral

> + 'd, + mck: impl Peripheral

> + 'd, + txdma: impl Peripheral

> + 'd, + txdma_buf: &'d mut [W], + rxdma: impl Peripheral

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

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

> + 'd, + ck: impl Peripheral

> + 'd, + mck: Option>, + txdma: Option<(ChannelAndRequest<'d>, &'d mut [W])>, + rxdma: Option<(ChannelAndRequest<'d>, &'d mut [W])>, + freq: Hertz, + config: Config, + function: Function, + ) -> Self { + into_ref!(ws, ck); + + ws.set_as_af(ws.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + ck.set_as_af(ck.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + + let spi = Spi::new_internal(peri, None, None, { + let mut config = SpiConfig::default(); + config.frequency = freq; + config + }); + + let regs = T::info().regs; + + // TODO move i2s to the new mux infra. + //#[cfg(all(rcc_f4, not(stm32f410)))] + //let pclk = unsafe { get_freqs() }.plli2s1_q.unwrap(); + //#[cfg(stm32f410)] + let pclk = T::frequency(); + + let (odd, div) = compute_baud_rate(pclk, freq, config.master_clock, config.format); + + #[cfg(any(spi_v1, spi_v3, spi_f1))] + { + #[cfg(spi_v3)] + { + regs.cr1().modify(|w| w.set_spe(false)); + + reset_incompatible_bitfields::(); + } + + use stm32_metapac::spi::vals::{I2scfg, Odd}; + + // 1. Select the I2SDIV[7:0] bits in the SPI_I2SPR/SPI_I2SCFGR register to define the serial clock baud + // rate to reach the proper audio sample frequency. The ODD bit in the + // SPI_I2SPR/SPI_I2SCFGR register also has to be defined. + + // 2. Select the CKPOL bit to define the steady level for the communication clock. Set the + // MCKOE bit in the SPI_I2SPR/SPI_I2SCFGR register if the master clock MCK needs to be provided to + // the external DAC/ADC audio component (the I2SDIV and ODD values should be + // computed depending on the state of the MCK output, for more details refer to + // Section 28.4.4: Clock generator). + + // 3. Set the I2SMOD bit in SPI_I2SCFGR to activate the I2S functionalities and choose the + // I2S standard through the I2SSTD[1:0] and PCMSYNC bits, the data length through the + // DATLEN[1:0] bits and the number of bits per channel by configuring the CHLEN bit. + // Select also the I2S master mode and direction (Transmitter or Receiver) through the + // I2SCFG[1:0] bits in the SPI_I2SCFGR register. + + // 4. If needed, select all the potential interruption sources and the DMA capabilities by + // writing the SPI_CR2 register. + + // 5. The I2SE bit in SPI_I2SCFGR register must be set. + + let clk_reg = { + #[cfg(any(spi_v1, spi_f1))] + { + regs.i2spr() + } + #[cfg(spi_v3)] + { + regs.i2scfgr() + } + }; + + clk_reg.modify(|w| { + w.set_i2sdiv(div); + w.set_odd(match odd { + true => Odd::ODD, + false => Odd::EVEN, + }); + + w.set_mckoe(config.master_clock); + }); + + regs.i2scfgr().modify(|w| { + w.set_ckpol(config.clock_polarity.ckpol()); + + w.set_i2smod(true); + + w.set_i2sstd(config.standard.i2sstd()); + w.set_pcmsync(config.standard.pcmsync()); + + w.set_datlen(config.format.datlen()); + w.set_chlen(config.format.chlen()); + + w.set_i2scfg(match (config.mode, function) { + (Mode::Master, Function::Transmit) => I2scfg::MASTERTX, + (Mode::Master, Function::Receive) => I2scfg::MASTERRX, + #[cfg(spi_v3)] + (Mode::Master, Function::FullDuplex) => I2scfg::MASTERFULLDUPLEX, + (Mode::Slave, Function::Transmit) => I2scfg::SLAVETX, + (Mode::Slave, Function::Receive) => I2scfg::SLAVERX, + #[cfg(spi_v3)] + (Mode::Slave, Function::FullDuplex) => I2scfg::SLAVEFULLDUPLEX, + }); + + #[cfg(any(spi_v1, spi_f1))] + w.set_i2se(true); + }); + + let mut opts = TransferOptions::default(); + opts.half_transfer_ir = true; + + Self { + mode: config.mode, + spi, + txsd: txsd.map(|w| w.map_into()), + rxsd: rxsd.map(|w| w.map_into()), + ws: Some(ws.map_into()), + ck: Some(ck.map_into()), + mck: mck.map(|w| w.map_into()), + tx_ring_buffer: txdma.map(|(ch, buf)| unsafe { + WritableRingBuffer::new(ch.channel, ch.request, regs.tx_ptr(), buf, opts) + }), + rx_ring_buffer: rxdma.map(|(ch, buf)| unsafe { + ReadableRingBuffer::new(ch.channel, ch.request, regs.rx_ptr(), buf, opts) + }), + } + } + } +} + +impl<'d, W: Word> Drop for I2S<'d, W> { + fn drop(&mut self) { + self.txsd.as_ref().map(|x| x.set_as_disconnected()); + self.rxsd.as_ref().map(|x| x.set_as_disconnected()); + self.ws.as_ref().map(|x| x.set_as_disconnected()); + self.ck.as_ref().map(|x| x.set_as_disconnected()); + self.mck.as_ref().map(|x| x.set_as_disconnected()); + } +} + +// Note, calculation details: +// Fs = i2s_clock / [256 * ((2 * div) + odd)] when master clock is enabled +// Fs = i2s_clock / [(channel_length * 2) * ((2 * div) + odd)]` when master clock is disabled +// channel_length is 16 or 32 +// +// can be rewritten as +// Fs = i2s_clock / (coef * division) +// where coef is a constant equal to 256, 64 or 32 depending channel length and master clock +// and where division = (2 * div) + odd +// +// Equation can be rewritten as +// division = i2s_clock/ (coef * Fs) +// +// note: division = (2 * div) + odd = (div << 1) + odd +// in other word, from bits point of view, division[8:1] = div[7:0] and division[0] = odd +fn compute_baud_rate(i2s_clock: Hertz, request_freq: Hertz, mclk: bool, data_format: Format) -> (bool, u8) { + let coef = if mclk { + 256 + } else if let Format::Data16Channel16 = data_format { + 32 + } else { + 64 + }; + + let (n, d) = (i2s_clock.0, coef * request_freq.0); + let division = (n + (d >> 1)) / d; + + if division < 4 { + (false, 2) + } else if division > 511 { + (true, 255) + } else { + ((division & 1) == 1, (division >> 1) as u8) + } +} + +#[cfg(spi_v3)] +// The STM32H7 reference manual specifies that any incompatible bitfields should be reset +// to their reset values while operating in I2S mode. +fn reset_incompatible_bitfields() { + let regs = T::info().regs; + regs.cr1().modify(|w| { + let iolock = w.iolock(); + let csusp = w.csusp(); + let spe = w.cstart(); + let cstart = w.cstart(); + w.0 = 0; + w.set_iolock(iolock); + w.set_csusp(csusp); + w.set_spe(spe); + w.set_cstart(cstart); + }); + + regs.cr2().write(|w| w.0 = 0); + + regs.cfg1().modify(|w| { + let txdmaen = w.txdmaen(); + let rxdmaen = w.rxdmaen(); + let fthlv = w.fthlv(); + w.0 = 0; + w.set_txdmaen(txdmaen); + w.set_rxdmaen(rxdmaen); + w.set_fthlv(fthlv); + }); + + regs.cfg2().modify(|w| { + let afcntr = w.afcntr(); + let lsbfirst = w.lsbfirst(); + let ioswp = w.ioswp(); + w.0 = 0; + w.set_afcntr(afcntr); + w.set_lsbfirst(lsbfirst); + w.set_ioswp(ioswp); + }); + + regs.ier().modify(|w| { + let tifreie = w.tifreie(); + let ovrie = w.ovrie(); + let udrie = w.udrie(); + let txpie = w.txpie(); + let rxpie = w.rxpie(); + + w.0 = 0; + + w.set_tifreie(tifreie); + w.set_ovrie(ovrie); + w.set_udrie(udrie); + w.set_txpie(txpie); + w.set_rxpie(rxpie); + }); + + regs.ifcr().write(|w| { + w.set_suspc(true); + w.set_tifrec(true); + w.set_ovrc(true); + w.set_udrc(true); + }); + + regs.crcpoly().write(|w| w.0 = 0x107); + regs.txcrc().write(|w| w.0 = 0); + regs.rxcrc().write(|w| w.0 = 0); + regs.udrdr().write(|w| w.0 = 0); +} diff --git a/embassy/embassy-stm32/src/ipcc.rs b/embassy/embassy-stm32/src/ipcc.rs new file mode 100644 index 0000000..20cd20d --- /dev/null +++ b/embassy/embassy-stm32/src/ipcc.rs @@ -0,0 +1,265 @@ +//! Inter-Process Communication Controller (IPCC) + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_sync::waitqueue::AtomicWaker; + +use crate::interrupt::typelevel::Interrupt; +use crate::peripherals::IPCC; +use crate::{interrupt, rcc}; + +/// Interrupt handler. +pub struct ReceiveInterruptHandler {} + +impl interrupt::typelevel::Handler for ReceiveInterruptHandler { + unsafe fn on_interrupt() { + let regs = IPCC::regs(); + + let channels = [ + IpccChannel::Channel1, + IpccChannel::Channel2, + IpccChannel::Channel3, + IpccChannel::Channel4, + IpccChannel::Channel5, + IpccChannel::Channel6, + ]; + + // Status register gives channel occupied status. For rx, use cpu1. + let sr = regs.cpu(1).sr().read(); + regs.cpu(0).mr().modify(|w| { + for channel in channels { + if sr.chf(channel as usize) { + // If bit is set to 1 then interrupt is disabled; we want to disable the interrupt + w.set_chom(channel as usize, true); + + // There shouldn't be a race because the channel is masked only if the interrupt has fired + IPCC::state().rx_waker_for(channel).wake(); + } + } + }) + } +} + +/// TX interrupt handler. +pub struct TransmitInterruptHandler {} + +impl interrupt::typelevel::Handler for TransmitInterruptHandler { + unsafe fn on_interrupt() { + let regs = IPCC::regs(); + + let channels = [ + IpccChannel::Channel1, + IpccChannel::Channel2, + IpccChannel::Channel3, + IpccChannel::Channel4, + IpccChannel::Channel5, + IpccChannel::Channel6, + ]; + + // Status register gives channel occupied status. For tx, use cpu0. + let sr = regs.cpu(0).sr().read(); + regs.cpu(0).mr().modify(|w| { + for channel in channels { + if !sr.chf(channel as usize) { + // If bit is set to 1 then interrupt is disabled; we want to disable the interrupt + w.set_chfm(channel as usize, true); + + // There shouldn't be a race because the channel is masked only if the interrupt has fired + IPCC::state().tx_waker_for(channel).wake(); + } + } + }); + } +} + +/// IPCC config. +#[non_exhaustive] +#[derive(Clone, Copy, Default)] +pub struct Config { + // TODO: add IPCC peripheral configuration, if any, here + // reserved for future use +} + +/// Channel. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub enum IpccChannel { + Channel1 = 0, + Channel2 = 1, + Channel3 = 2, + Channel4 = 3, + Channel5 = 4, + Channel6 = 5, +} + +/// IPCC driver. +pub struct Ipcc; + +impl Ipcc { + /// Enable IPCC. + pub fn enable(_config: Config) { + rcc::enable_and_reset::(); + IPCC::set_cpu2(true); + + // set RF wake-up clock = LSE + crate::pac::RCC.csr().modify(|w| w.set_rfwkpsel(0b01)); + + let regs = IPCC::regs(); + + regs.cpu(0).cr().modify(|w| { + w.set_rxoie(true); + w.set_txfie(true); + }); + + // enable interrupts + crate::interrupt::typelevel::IPCC_C1_RX::unpend(); + crate::interrupt::typelevel::IPCC_C1_TX::unpend(); + + unsafe { crate::interrupt::typelevel::IPCC_C1_RX::enable() }; + unsafe { crate::interrupt::typelevel::IPCC_C1_TX::enable() }; + } + + /// Send data to an IPCC channel. The closure is called to write the data when appropriate. + pub async fn send(channel: IpccChannel, f: impl FnOnce()) { + let regs = IPCC::regs(); + + Self::flush(channel).await; + + f(); + + compiler_fence(Ordering::SeqCst); + + trace!("ipcc: ch {}: send data", channel as u8); + regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)); + } + + /// Wait for the tx channel to become clear + pub async fn flush(channel: IpccChannel) { + let regs = IPCC::regs(); + + // This is a race, but is nice for debugging + if regs.cpu(0).sr().read().chf(channel as usize) { + trace!("ipcc: ch {}: wait for tx free", channel as u8); + } + + poll_fn(|cx| { + IPCC::state().tx_waker_for(channel).register(cx.waker()); + // If bit is set to 1 then interrupt is disabled; we want to enable the interrupt + regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, false)); + + compiler_fence(Ordering::SeqCst); + + if !regs.cpu(0).sr().read().chf(channel as usize) { + // If bit is set to 1 then interrupt is disabled; we want to disable the interrupt + regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, true)); + + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + } + + /// Receive data from an IPCC channel. The closure is called to read the data when appropriate. + pub async fn receive(channel: IpccChannel, mut f: impl FnMut() -> Option) -> R { + let regs = IPCC::regs(); + + loop { + // This is a race, but is nice for debugging + if !regs.cpu(1).sr().read().chf(channel as usize) { + trace!("ipcc: ch {}: wait for rx occupied", channel as u8); + } + + poll_fn(|cx| { + IPCC::state().rx_waker_for(channel).register(cx.waker()); + // If bit is set to 1 then interrupt is disabled; we want to enable the interrupt + regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, false)); + + compiler_fence(Ordering::SeqCst); + + if regs.cpu(1).sr().read().chf(channel as usize) { + // If bit is set to 1 then interrupt is disabled; we want to disable the interrupt + regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, true)); + + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + trace!("ipcc: ch {}: read data", channel as u8); + + match f() { + Some(ret) => return ret, + None => {} + } + + trace!("ipcc: ch {}: clear rx", channel as u8); + compiler_fence(Ordering::SeqCst); + // If the channel is clear and the read function returns none, fetch more data + regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)); + } + } +} + +impl SealedInstance for crate::peripherals::IPCC { + fn regs() -> crate::pac::ipcc::Ipcc { + crate::pac::IPCC + } + + fn set_cpu2(enabled: bool) { + crate::pac::PWR.cr4().modify(|w| w.set_c2boot(enabled)); + } + + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } +} + +struct State { + rx_wakers: [AtomicWaker; 6], + tx_wakers: [AtomicWaker; 6], +} + +impl State { + const fn new() -> Self { + Self { + rx_wakers: [const { AtomicWaker::new() }; 6], + tx_wakers: [const { AtomicWaker::new() }; 6], + } + } + + const fn rx_waker_for(&self, channel: IpccChannel) -> &AtomicWaker { + match channel { + IpccChannel::Channel1 => &self.rx_wakers[0], + IpccChannel::Channel2 => &self.rx_wakers[1], + IpccChannel::Channel3 => &self.rx_wakers[2], + IpccChannel::Channel4 => &self.rx_wakers[3], + IpccChannel::Channel5 => &self.rx_wakers[4], + IpccChannel::Channel6 => &self.rx_wakers[5], + } + } + + const fn tx_waker_for(&self, channel: IpccChannel) -> &AtomicWaker { + match channel { + IpccChannel::Channel1 => &self.tx_wakers[0], + IpccChannel::Channel2 => &self.tx_wakers[1], + IpccChannel::Channel3 => &self.tx_wakers[2], + IpccChannel::Channel4 => &self.tx_wakers[3], + IpccChannel::Channel5 => &self.tx_wakers[4], + IpccChannel::Channel6 => &self.tx_wakers[5], + } + } +} + +trait SealedInstance: crate::rcc::RccPeripheral { + fn regs() -> crate::pac::ipcc::Ipcc; + fn set_cpu2(enabled: bool); + fn state() -> &'static State; +} diff --git a/embassy/embassy-stm32/src/lib.rs b/embassy/embassy-stm32/src/lib.rs new file mode 100644 index 0000000..39f3dfd --- /dev/null +++ b/embassy/embassy-stm32/src/lib.rs @@ -0,0 +1,552 @@ +#![cfg_attr(not(test), no_std)] +#![allow(async_fn_in_trait)] +#![cfg_attr( + docsrs, + doc = "

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

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

\n\n" +)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +// This must go FIRST so that all the other modules see its macros. +mod fmt; +include!(concat!(env!("OUT_DIR"), "/_macros.rs")); + +// Utilities +mod macros; +pub mod time; +/// Operating modes for peripherals. +pub mod mode { + trait SealedMode {} + + /// Operating mode for a peripheral. + #[allow(private_bounds)] + pub trait Mode: SealedMode {} + + macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; + } + + /// Blocking mode. + pub struct Blocking; + /// Async mode. + pub struct Async; + + impl_mode!(Blocking); + impl_mode!(Async); +} + +// Always-present hardware +pub mod dma; +pub mod gpio; +pub mod rcc; +#[cfg(feature = "_time-driver")] +mod time_driver; +pub mod timer; + +// Sometimes-present hardware + +#[cfg(adc)] +pub mod adc; +#[cfg(can)] +pub mod can; +// FIXME: Cordic driver cause stm32u5a5zj crash +#[cfg(all(cordic, not(any(stm32u5a5, stm32u5a9))))] +pub mod cordic; +#[cfg(crc)] +pub mod crc; +#[cfg(cryp)] +pub mod cryp; +#[cfg(dac)] +pub mod dac; +#[cfg(dcmi)] +pub mod dcmi; +#[cfg(dsihost)] +pub mod dsihost; +#[cfg(eth)] +pub mod eth; +#[cfg(feature = "exti")] +pub mod exti; +pub mod flash; +#[cfg(fmc)] +pub mod fmc; +#[cfg(hash)] +pub mod hash; +#[cfg(hrtim)] +pub mod hrtim; +#[cfg(hsem)] +pub mod hsem; +#[cfg(i2c)] +pub mod i2c; +#[cfg(any(all(spi_v1, rcc_f4), spi_v3))] +pub mod i2s; +#[cfg(stm32wb)] +pub mod ipcc; +#[cfg(feature = "low-power")] +pub mod low_power; +#[cfg(lptim)] +pub mod lptim; +#[cfg(ltdc)] +pub mod ltdc; +#[cfg(opamp)] +pub mod opamp; +#[cfg(octospi)] +pub mod ospi; +#[cfg(quadspi)] +pub mod qspi; +#[cfg(rng)] +pub mod rng; +#[cfg(all(rtc, not(rtc_v1)))] +pub mod rtc; +#[cfg(sai)] +pub mod sai; +#[cfg(sdmmc)] +pub mod sdmmc; +#[cfg(spdifrx)] +pub mod spdifrx; +#[cfg(spi)] +pub mod spi; +#[cfg(tsc)] +pub mod tsc; +#[cfg(ucpd)] +pub mod ucpd; +#[cfg(uid)] +pub mod uid; +#[cfg(usart)] +pub mod usart; +#[cfg(any(usb, otg))] +pub mod usb; +#[cfg(iwdg)] +pub mod wdg; + +// This must go last, so that it sees all the impl_foo! macros defined earlier. +pub(crate) mod _generated { + #![allow(dead_code)] + #![allow(unused_imports)] + #![allow(non_snake_case)] + #![allow(missing_docs)] + + include!(concat!(env!("OUT_DIR"), "/_generated.rs")); +} + +pub use crate::_generated::interrupt; + +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +/// +/// Example of how to bind one interrupt: +/// +/// ```rust,ignore +/// use embassy_stm32::{bind_interrupts, usb, peripherals}; +/// +/// bind_interrupts!(struct Irqs { +/// OTG_FS => usb::InterruptHandler; +/// }); +/// ``` +/// +/// Example of how to bind multiple interrupts, and multiple handlers to each interrupt, in a single macro invocation: +/// +/// ```rust,ignore +/// use embassy_stm32::{bind_interrupts, i2c, peripherals}; +/// +/// bind_interrupts!(struct Irqs { +/// I2C1 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; +/// I2C2_3 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler, +/// i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; +/// }); +/// ``` + +// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + )* + } + + $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@inner + $( + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + ); + )* + }; + (@inner $($t:tt)*) => { + $($t)* + } +} + +// Reexports +pub use _generated::{peripherals, Peripherals}; +pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +#[cfg(feature = "unstable-pac")] +pub use stm32_metapac as pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use stm32_metapac as pac; + +use crate::interrupt::Priority; +#[cfg(feature = "rt")] +pub use crate::pac::NVIC_PRIO_BITS; + +/// `embassy-stm32` global configuration. +#[non_exhaustive] +#[derive(Clone, Copy)] +pub struct Config { + /// RCC config. + pub rcc: rcc::Config, + + /// Enable debug during sleep and stop. + /// + /// May increase power consumption. Defaults to true. + #[cfg(dbgmcu)] + pub enable_debug_during_sleep: bool, + + /// On low-power boards (eg. `stm32l4`, `stm32l5` and `stm32u5`), + /// some GPIO pins are powered by an auxiliary, independent power supply (`VDDIO2`), + /// which needs to be enabled before these pins can be used. + /// + /// May increase power consumption. Defaults to true. + #[cfg(any(stm32l4, stm32l5, stm32u5))] + pub enable_independent_io_supply: bool, + + /// BDMA interrupt priority. + /// + /// Defaults to P0 (highest). + #[cfg(bdma)] + pub bdma_interrupt_priority: Priority, + + /// DMA interrupt priority. + /// + /// Defaults to P0 (highest). + #[cfg(dma)] + pub dma_interrupt_priority: Priority, + + /// GPDMA interrupt priority. + /// + /// Defaults to P0 (highest). + #[cfg(gpdma)] + pub gpdma_interrupt_priority: Priority, + + /// Enables UCPD1 dead battery functionality. + /// + /// Defaults to false (disabled). + #[cfg(peri_ucpd1)] + pub enable_ucpd1_dead_battery: bool, + + /// Enables UCPD2 dead battery functionality. + /// + /// Defaults to false (disabled). + #[cfg(peri_ucpd2)] + pub enable_ucpd2_dead_battery: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + rcc: Default::default(), + #[cfg(dbgmcu)] + enable_debug_during_sleep: true, + #[cfg(any(stm32l4, stm32l5, stm32u5))] + enable_independent_io_supply: true, + #[cfg(bdma)] + bdma_interrupt_priority: Priority::P0, + #[cfg(dma)] + dma_interrupt_priority: Priority::P0, + #[cfg(gpdma)] + gpdma_interrupt_priority: Priority::P0, + #[cfg(peri_ucpd1)] + enable_ucpd1_dead_battery: false, + #[cfg(peri_ucpd2)] + enable_ucpd2_dead_battery: false, + } + } +} + +/// Initialize the `embassy-stm32` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once at startup, otherwise it panics. +#[cfg(not(feature = "_dual-core"))] +pub fn init(config: Config) -> Peripherals { + init_hw(config) +} + +#[cfg(feature = "_dual-core")] +mod dual_core { + use core::cell::UnsafeCell; + use core::mem::MaybeUninit; + use core::sync::atomic::{AtomicUsize, Ordering}; + + use rcc::Clocks; + + use super::*; + + /// Object containing data that embassy needs to share between cores. + /// + /// It cannot be initialized by the user. The intended use is: + /// + /// ``` + /// use core::mem::MaybeUninit; + /// use embassy_stm32::{init_secondary, SharedData}; + /// + /// #[link_section = ".ram_d3"] + /// static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + /// + /// init_secondary(&SHARED_DATA); + /// ``` + /// + /// This static must be placed in the same position for both cores. How and where this is done is left to the user. + pub struct SharedData { + init_flag: AtomicUsize, + clocks: UnsafeCell>, + config: UnsafeCell>, + } + + unsafe impl Sync for SharedData {} + + const INIT_DONE_FLAG: usize = 0xca11ab1e; + + /// Initialize the `embassy-stm32` HAL with the provided configuration. + /// This function does the actual initialization of the hardware, in contrast to [init_secondary] or [try_init_secondary]. + /// Any core can do the init, but it's important only one core does it. + /// + /// This returns the peripheral singletons that can be used for creating drivers. + /// + /// This should only be called once at startup, otherwise it panics. + /// + /// The `shared_data` is used to coordinate the init with the second core. Read the [SharedData] docs + /// for more information on its requirements. + pub fn init_primary(config: Config, shared_data: &'static MaybeUninit) -> Peripherals { + let shared_data = unsafe { shared_data.assume_init_ref() }; + + rcc::set_freqs_ptr(shared_data.clocks.get()); + let p = init_hw(config); + + unsafe { *shared_data.config.get() }.write(config); + + shared_data.init_flag.store(INIT_DONE_FLAG, Ordering::SeqCst); + + p + } + + /// Try to initialize the `embassy-stm32` HAL based on the init done by the other core using [init_primary]. + /// + /// This returns the peripheral singletons that can be used for creating drivers if the other core is done with its init. + /// If the other core is not done yet, this will return `None`. + /// + /// This should only be called once at startup, otherwise it may panic. + /// + /// The `shared_data` is used to coordinate the init with the second core. Read the [SharedData] docs + /// for more information on its requirements. + pub fn try_init_secondary(shared_data: &'static MaybeUninit) -> Option { + let shared_data = unsafe { shared_data.assume_init_ref() }; + + if shared_data.init_flag.load(Ordering::SeqCst) != INIT_DONE_FLAG { + return None; + } + + // Separate load and store to support the CM0 of the STM32WL + shared_data.init_flag.store(0, Ordering::SeqCst); + + Some(init_secondary_hw(shared_data)) + } + + /// Initialize the `embassy-stm32` HAL based on the init done by the other core using [init_primary]. + /// + /// This returns the peripheral singletons that can be used for creating drivers when the other core is done with its init. + /// If the other core is not done yet, this will spinloop wait on it. + /// + /// This should only be called once at startup, otherwise it may panic. + /// + /// The `shared_data` is used to coordinate the init with the second core. Read the [SharedData] docs + /// for more information on its requirements. + pub fn init_secondary(shared_data: &'static MaybeUninit) -> Peripherals { + loop { + if let Some(p) = try_init_secondary(shared_data) { + return p; + } + } + } + + fn init_secondary_hw(shared_data: &'static SharedData) -> Peripherals { + rcc::set_freqs_ptr(shared_data.clocks.get()); + + let config = unsafe { (*shared_data.config.get()).assume_init() }; + + // We use different timers on the different cores, so we have to still initialize one here + critical_section::with(|cs| { + unsafe { + dma::init( + cs, + #[cfg(bdma)] + config.bdma_interrupt_priority, + #[cfg(dma)] + config.dma_interrupt_priority, + #[cfg(gpdma)] + config.gpdma_interrupt_priority, + ) + } + + #[cfg(feature = "_time-driver")] + // must be after rcc init + time_driver::init(cs); + }); + + Peripherals::take() + } +} + +#[cfg(feature = "_dual-core")] +pub use dual_core::*; + +fn init_hw(config: Config) -> Peripherals { + critical_section::with(|cs| { + let p = Peripherals::take_with_cs(cs); + + #[cfg(dbgmcu)] + crate::pac::DBGMCU.cr().modify(|cr| { + #[cfg(dbgmcu_h5)] + { + cr.set_stop(config.enable_debug_during_sleep); + cr.set_standby(config.enable_debug_during_sleep); + } + #[cfg(any(dbgmcu_f0, dbgmcu_c0, dbgmcu_g0, dbgmcu_u0, dbgmcu_u5, dbgmcu_wba, dbgmcu_l5))] + { + cr.set_dbg_stop(config.enable_debug_during_sleep); + cr.set_dbg_standby(config.enable_debug_during_sleep); + } + #[cfg(any( + dbgmcu_f1, dbgmcu_f2, dbgmcu_f3, dbgmcu_f4, dbgmcu_f7, dbgmcu_g4, dbgmcu_f7, dbgmcu_l0, dbgmcu_l1, + dbgmcu_l4, dbgmcu_wb, dbgmcu_wl + ))] + { + cr.set_dbg_sleep(config.enable_debug_during_sleep); + cr.set_dbg_stop(config.enable_debug_during_sleep); + cr.set_dbg_standby(config.enable_debug_during_sleep); + } + #[cfg(dbgmcu_h7)] + { + cr.set_d1dbgcken(config.enable_debug_during_sleep); + cr.set_d3dbgcken(config.enable_debug_during_sleep); + cr.set_dbgsleep_d1(config.enable_debug_during_sleep); + cr.set_dbgstby_d1(config.enable_debug_during_sleep); + cr.set_dbgstop_d1(config.enable_debug_during_sleep); + } + }); + + #[cfg(not(any(stm32f1, stm32wb, stm32wl)))] + rcc::enable_and_reset_with_cs::(cs); + #[cfg(not(any(stm32h5, stm32h7, stm32h7rs, stm32wb, stm32wl)))] + rcc::enable_and_reset_with_cs::(cs); + #[cfg(not(any(stm32f2, stm32f4, stm32f7, stm32l0, stm32h5, stm32h7, stm32h7rs)))] + rcc::enable_and_reset_with_cs::(cs); + + // Enable the VDDIO2 power supply on chips that have it. + // Note that this requires the PWR peripheral to be enabled first. + #[cfg(any(stm32l4, stm32l5))] + { + crate::pac::PWR.cr2().modify(|w| { + // The official documentation states that we should ideally enable VDDIO2 + // through the PVME2 bit, but it looks like this isn't required, + // and CubeMX itself skips this step. + w.set_iosv(config.enable_independent_io_supply); + }); + } + #[cfg(stm32u5)] + { + crate::pac::PWR.svmcr().modify(|w| { + w.set_io2sv(config.enable_independent_io_supply); + }); + } + + // dead battery functionality is still present on these + // chips despite them not having UCPD- disable it + #[cfg(any(stm32g070, stm32g0b0))] + { + crate::pac::SYSCFG.cfgr1().modify(|w| { + w.set_ucpd1_strobe(true); + w.set_ucpd2_strobe(true); + }); + } + + unsafe { + #[cfg(ucpd)] + ucpd::init( + cs, + #[cfg(peri_ucpd1)] + config.enable_ucpd1_dead_battery, + #[cfg(peri_ucpd2)] + config.enable_ucpd2_dead_battery, + ); + + #[cfg(feature = "_split-pins-enabled")] + crate::pac::SYSCFG.pmcr().modify(|pmcr| { + #[cfg(feature = "split-pa0")] + pmcr.set_pa0so(true); + #[cfg(feature = "split-pa1")] + pmcr.set_pa1so(true); + #[cfg(feature = "split-pc2")] + pmcr.set_pc2so(true); + #[cfg(feature = "split-pc3")] + pmcr.set_pc3so(true); + }); + + gpio::init(cs); + dma::init( + cs, + #[cfg(bdma)] + config.bdma_interrupt_priority, + #[cfg(dma)] + config.dma_interrupt_priority, + #[cfg(gpdma)] + config.gpdma_interrupt_priority, + ); + #[cfg(feature = "exti")] + exti::init(cs); + + rcc::init(config.rcc); + + // must be after rcc init + #[cfg(feature = "_time-driver")] + time_driver::init(cs); + + #[cfg(feature = "low-power")] + { + crate::rcc::REFCOUNT_STOP2 = 0; + crate::rcc::REFCOUNT_STOP1 = 0; + } + } + + p + }) +} diff --git a/embassy/embassy-stm32/src/low_power.rs b/embassy/embassy-stm32/src/low_power.rs new file mode 100644 index 0000000..7734365 --- /dev/null +++ b/embassy/embassy-stm32/src/low_power.rs @@ -0,0 +1,269 @@ +//! Low-power support. +//! +//! The STM32 line of microcontrollers support various deep-sleep modes which exploit clock-gating +//! to reduce power consumption. `embassy-stm32` provides a low-power executor, [`Executor`] which +//! can use knowledge of which peripherals are currently blocked upon to transparently and safely +//! enter such low-power modes (currently, only `STOP2`) when idle. +//! +//! The executor determines which peripherals are active by their RCC state; consequently, +//! low-power states can only be entered if all peripherals have been `drop`'d. There are a few +//! exceptions to this rule: +//! +//! * `GPIO` +//! * `RTC` +//! +//! Since entering and leaving low-power modes typically incurs a significant latency, the +//! low-power executor will only attempt to enter when the next timer event is at least +//! [`time_driver::MIN_STOP_PAUSE`] in the future. +//! +//! Currently there is no macro analogous to `embassy_executor::main` for this executor; +//! consequently one must define their entrypoint manually. Moreover, you must relinquish control +//! of the `RTC` peripheral to the executor. This will typically look like +//! +//! ```rust,no_run +//! use embassy_executor::Spawner; +//! use embassy_stm32::low_power::Executor; +//! use embassy_stm32::rtc::{Rtc, RtcConfig}; +//! use static_cell::StaticCell; +//! +//! #[cortex_m_rt::entry] +//! fn main() -> ! { +//! Executor::take().run(|spawner| { +//! unwrap!(spawner.spawn(async_main(spawner))); +//! }); +//! } +//! +//! #[embassy_executor::task] +//! async fn async_main(spawner: Spawner) { +//! // initialize the platform... +//! let mut config = embassy_stm32::Config::default(); +//! // when enabled the power-consumption is much higher during stop, but debugging and RTT is working +//! config.enable_debug_during_sleep = false; +//! let p = embassy_stm32::init(config); +//! +//! // give the RTC to the executor... +//! let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); +//! static RTC: StaticCell = StaticCell::new(); +//! let rtc = RTC.init(rtc); +//! embassy_stm32::low_power::stop_with_rtc(rtc); +//! +//! // your application here... +//! } +//! ``` + +// TODO: Usage of `static mut` here is unsound. Fix then remove this `allow`.` +#![allow(static_mut_refs)] + +use core::arch::asm; +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; + +use cortex_m::peripheral::SCB; +use embassy_executor::*; + +use crate::interrupt; +use crate::time_driver::{get_driver, RtcDriver}; + +const THREAD_PENDER: usize = usize::MAX; + +use crate::rtc::Rtc; + +static mut EXECUTOR: Option = None; + +#[cfg(not(stm32u0))] +foreach_interrupt! { + (RTC, rtc, $block:ident, WKUP, $irq:ident) => { + #[interrupt] + #[allow(non_snake_case)] + unsafe fn $irq() { + EXECUTOR.as_mut().unwrap().on_wakeup_irq(); + } + }; +} + +#[cfg(stm32u0)] +foreach_interrupt! { + (RTC, rtc, $block:ident, TAMP, $irq:ident) => { + #[interrupt] + #[allow(non_snake_case)] + unsafe fn $irq() { + EXECUTOR.as_mut().unwrap().on_wakeup_irq(); + } + }; +} + +#[allow(dead_code)] +pub(crate) unsafe fn on_wakeup_irq() { + EXECUTOR.as_mut().unwrap().on_wakeup_irq(); +} + +/// Configure STOP mode with RTC. +pub fn stop_with_rtc(rtc: &'static Rtc) { + unsafe { EXECUTOR.as_mut().unwrap() }.stop_with_rtc(rtc) +} + +/// Get whether the core is ready to enter the given stop mode. +/// +/// This will return false if some peripheral driver is in use that +/// prevents entering the given stop mode. +pub fn stop_ready(stop_mode: StopMode) -> bool { + match unsafe { EXECUTOR.as_mut().unwrap() }.stop_mode() { + Some(StopMode::Stop2) => true, + Some(StopMode::Stop1) => stop_mode == StopMode::Stop1, + None => false, + } +} + +/// Available Stop modes. +#[non_exhaustive] +#[derive(PartialEq)] +pub enum StopMode { + /// STOP 1 + Stop1, + /// STOP 2 + Stop2, +} + +#[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0))] +use stm32_metapac::pwr::vals::Lpms; + +#[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0))] +impl Into for StopMode { + fn into(self) -> Lpms { + match self { + StopMode::Stop1 => Lpms::STOP1, + StopMode::Stop2 => Lpms::STOP2, + } + } +} + +/// Thread mode executor, using WFE/SEV. +/// +/// This is the simplest and most common kind of executor. It runs on +/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction +/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction +/// is executed, to make the `WFE` exit from sleep and poll the task. +/// +/// This executor allows for ultra low power consumption for chips where `WFE` +/// triggers low-power sleep without extra steps. If your chip requires extra steps, +/// you may use [`raw::Executor`] directly to program custom behavior. +pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + scb: SCB, + time_driver: &'static RtcDriver, +} + +impl Executor { + /// Create a new Executor. + pub fn take() -> &'static mut Self { + critical_section::with(|_| unsafe { + assert!(EXECUTOR.is_none()); + + EXECUTOR = Some(Self { + inner: raw::Executor::new(THREAD_PENDER as *mut ()), + not_send: PhantomData, + scb: cortex_m::Peripherals::steal().SCB, + time_driver: get_driver(), + }); + + let executor = EXECUTOR.as_mut().unwrap(); + + executor + }) + } + + unsafe fn on_wakeup_irq(&mut self) { + self.time_driver.resume_time(); + trace!("low power: resume"); + } + + pub(self) fn stop_with_rtc(&mut self, rtc: &'static Rtc) { + self.time_driver.set_rtc(rtc); + + rtc.enable_wakeup_line(); + + trace!("low power: stop with rtc configured"); + } + + fn stop_mode(&self) -> Option { + if unsafe { crate::rcc::REFCOUNT_STOP2 == 0 } && unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } { + Some(StopMode::Stop2) + } else if unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } { + Some(StopMode::Stop1) + } else { + None + } + } + + #[allow(unused_variables)] + fn configure_stop(&mut self, stop_mode: StopMode) { + #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0))] + crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); + #[cfg(stm32h5)] + crate::pac::PWR.pmcr().modify(|v| { + use crate::pac::pwr::vals; + v.set_lpms(vals::Lpms::STOP); + v.set_svos(vals::Svos::SCALE3); + }); + } + + fn configure_pwr(&mut self) { + self.scb.clear_sleepdeep(); + + compiler_fence(Ordering::SeqCst); + + let stop_mode = self.stop_mode(); + + if stop_mode.is_none() { + trace!("low power: not ready to stop"); + return; + } + + if self.time_driver.pause_time().is_err() { + trace!("low power: failed to pause time"); + return; + } + + let stop_mode = stop_mode.unwrap(); + match stop_mode { + StopMode::Stop1 => trace!("low power: stop 1"), + StopMode::Stop2 => trace!("low power: stop 2"), + } + self.configure_stop(stop_mode); + + #[cfg(not(feature = "low-power-debug-with-sleep"))] + self.scb.set_sleepdeep(); + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + let executor = unsafe { EXECUTOR.as_mut().unwrap() }; + init(executor.inner.spawner()); + + loop { + unsafe { + executor.inner.poll(); + self.configure_pwr(); + asm!("wfe"); + }; + } + } +} diff --git a/embassy/embassy-stm32/src/lptim/channel.rs b/embassy/embassy-stm32/src/lptim/channel.rs new file mode 100644 index 0000000..17fc2fb --- /dev/null +++ b/embassy/embassy-stm32/src/lptim/channel.rs @@ -0,0 +1,18 @@ +/// Timer channel. +#[derive(Clone, Copy)] +pub enum Channel { + /// Channel 1. + Ch1, + /// Channel 2. + Ch2, +} + +impl Channel { + /// Get the channel index (0..1) + pub fn index(&self) -> usize { + match self { + Channel::Ch1 => 0, + Channel::Ch2 => 1, + } + } +} diff --git a/embassy/embassy-stm32/src/lptim/mod.rs b/embassy/embassy-stm32/src/lptim/mod.rs new file mode 100644 index 0000000..1649cc5 --- /dev/null +++ b/embassy/embassy-stm32/src/lptim/mod.rs @@ -0,0 +1,48 @@ +//! Low-power timer (LPTIM) + +pub mod pwm; +pub mod timer; + +use crate::rcc::RccPeripheral; + +/// Timer channel. +#[cfg(any(lptim_v2a, lptim_v2b))] +mod channel; +#[cfg(any(lptim_v2a, lptim_v2b))] +pub use channel::Channel; + +pin_trait!(OutputPin, BasicInstance); +pin_trait!(Channel1Pin, BasicInstance); +pin_trait!(Channel2Pin, BasicInstance); + +pub(crate) trait SealedInstance: RccPeripheral { + fn regs() -> crate::pac::lptim::Lptim; +} +pub(crate) trait SealedBasicInstance: RccPeripheral {} + +/// LPTIM basic instance trait. +#[allow(private_bounds)] +pub trait BasicInstance: SealedBasicInstance + 'static {} + +/// LPTIM instance trait. +#[allow(private_bounds)] +pub trait Instance: BasicInstance + SealedInstance + 'static {} + +foreach_interrupt! { + ($inst:ident, lptim, LPTIM, GLOBAL, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::lptim::Lptim { + crate::pac::$inst + } + } + impl SealedBasicInstance for crate::peripherals::$inst { + } + impl BasicInstance for crate::peripherals::$inst {} + impl Instance for crate::peripherals::$inst {} + }; + ($inst:ident, lptim, LPTIM_BASIC, GLOBAL, $irq:ident) => { + impl SealedBasicInstance for crate::peripherals::$inst { + } + impl BasicInstance for crate::peripherals::$inst {} + }; +} diff --git a/embassy/embassy-stm32/src/lptim/pwm.rs b/embassy/embassy-stm32/src/lptim/pwm.rs new file mode 100644 index 0000000..1f43eb6 --- /dev/null +++ b/embassy/embassy-stm32/src/lptim/pwm.rs @@ -0,0 +1,168 @@ +//! PWM driver. + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::timer::Timer; +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +use super::OutputPin; +#[cfg(any(lptim_v2a, lptim_v2b))] +use super::{channel::Channel, timer::ChannelDirection, Channel1Pin, Channel2Pin}; +use super::{BasicInstance, Instance}; +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::time::Hertz; +use crate::Peripheral; + +/// Output marker type. +pub enum Output {} +/// Channel 1 marker type. +pub enum Ch1 {} +/// Channel 2 marker type. +pub enum Ch2 {} + +/// PWM pin wrapper. +/// +/// This wraps a pin to make it usable with PWM. +pub struct PwmPin<'d, T, C> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, T: BasicInstance> PwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] + pub fn $new_chx(pin: impl Peripheral

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

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

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

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

+ 'd) -> Self { + into_ref!(tim); + + rcc::enable_and_reset::(); + + Self { _tim: tim } + } + + /// Enable the timer. + pub fn enable(&self) { + T::regs().cr().modify(|w| w.set_enable(true)); + } + + /// Disable the timer. + pub fn disable(&self) { + T::regs().cr().modify(|w| w.set_enable(false)); + } + + /// Start the timer in single pulse mode. + pub fn single_mode_start(&self) { + T::regs().cr().modify(|w| w.set_sngstrt(true)); + } + + /// Start the timer in continuous mode. + pub fn continuous_mode_start(&self) { + T::regs().cr().modify(|w| w.set_cntstrt(true)); + } + + /// Set the frequency of how many times per second the timer counts up to the max value or down to 0. + pub fn set_frequency(&self, frequency: Hertz) { + let f = frequency.0; + assert!(f > 0); + + let pclk_f = T::frequency().0; + + let pclk_ticks_per_timer_period = pclk_f / f; + + let psc = Prescaler::from_ticks(pclk_ticks_per_timer_period); + let arr = psc.scale_down(pclk_ticks_per_timer_period); + + T::regs().cfgr().modify(|r| r.set_presc((&psc).into())); + T::regs().arr().modify(|r| r.set_arr(arr.into())); + } + + /// Get the timer frequency. + pub fn get_frequency(&self) -> Hertz { + let pclk_f = T::frequency(); + let arr = T::regs().arr().read().arr(); + let psc = Prescaler::from(T::regs().cfgr().read().presc()); + + pclk_f / psc.scale_up(arr) + } + + /// Get the clock frequency of the timer (before prescaler is applied). + pub fn get_clock_frequency(&self) -> Hertz { + T::frequency() + } + + /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. + pub fn get_max_compare_value(&self) -> u16 { + T::regs().arr().read().arr() + } +} + +#[cfg(any(lptim_v2a, lptim_v2b))] +impl<'d, T: Instance> Timer<'d, T> { + /// Enable/disable a channel. + pub fn enable_channel(&self, channel: Channel, enable: bool) { + T::regs().ccmr(0).modify(|w| { + w.set_cce(channel.index(), enable); + }); + } + + /// Get enable/disable state of a channel + pub fn get_channel_enable_state(&self, channel: Channel) -> bool { + T::regs().ccmr(0).read().cce(channel.index()) + } + + /// Set compare value for a channel. + pub fn set_compare_value(&self, channel: Channel, value: u16) { + T::regs().ccr(channel.index()).modify(|w| w.set_ccr(value)); + } + + /// Get compare value for a channel. + pub fn get_compare_value(&self, channel: Channel) -> u16 { + T::regs().ccr(channel.index()).read().ccr() + } + + /// Set channel direction. + #[cfg(any(lptim_v2a, lptim_v2b))] + pub fn set_channel_direction(&self, channel: Channel, direction: ChannelDirection) { + T::regs() + .ccmr(0) + .modify(|w| w.set_ccsel(channel.index(), direction.into())); + } +} + +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +impl<'d, T: Instance> Timer<'d, T> { + /// Set compare value for a channel. + pub fn set_compare_value(&self, value: u16) { + T::regs().cmp().modify(|w| w.set_cmp(value)); + } + + /// Get compare value for a channel. + pub fn get_compare_value(&self) -> u16 { + T::regs().cmp().read().cmp() + } +} diff --git a/embassy/embassy-stm32/src/lptim/timer/prescaler.rs b/embassy/embassy-stm32/src/lptim/timer/prescaler.rs new file mode 100644 index 0000000..5d2326f --- /dev/null +++ b/embassy/embassy-stm32/src/lptim/timer/prescaler.rs @@ -0,0 +1,90 @@ +//! Low-level timer driver. + +use crate::pac::lptim::vals; + +pub enum Prescaler { + Div1, + Div2, + Div4, + Div8, + Div16, + Div32, + Div64, + Div128, +} + +impl From<&Prescaler> for vals::Presc { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => vals::Presc::DIV1, + Prescaler::Div2 => vals::Presc::DIV2, + Prescaler::Div4 => vals::Presc::DIV4, + Prescaler::Div8 => vals::Presc::DIV8, + Prescaler::Div16 => vals::Presc::DIV16, + Prescaler::Div32 => vals::Presc::DIV32, + Prescaler::Div64 => vals::Presc::DIV64, + Prescaler::Div128 => vals::Presc::DIV128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: vals::Presc) -> Self { + match prescaler { + vals::Presc::DIV1 => Prescaler::Div1, + vals::Presc::DIV2 => Prescaler::Div2, + vals::Presc::DIV4 => Prescaler::Div4, + vals::Presc::DIV8 => Prescaler::Div8, + vals::Presc::DIV16 => Prescaler::Div16, + vals::Presc::DIV32 => Prescaler::Div32, + vals::Presc::DIV64 => Prescaler::Div64, + vals::Presc::DIV128 => Prescaler::Div128, + } + } +} + +impl From<&Prescaler> for u32 { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => 1, + Prescaler::Div2 => 2, + Prescaler::Div4 => 4, + Prescaler::Div8 => 8, + Prescaler::Div16 => 16, + Prescaler::Div32 => 32, + Prescaler::Div64 => 64, + Prescaler::Div128 => 128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: u32) -> Self { + match prescaler { + 1 => Prescaler::Div1, + 2 => Prescaler::Div2, + 4 => Prescaler::Div4, + 8 => Prescaler::Div8, + 16 => Prescaler::Div16, + 32 => Prescaler::Div32, + 64 => Prescaler::Div64, + 128 => Prescaler::Div128, + _ => unreachable!(), + } + } +} + +impl Prescaler { + pub fn from_ticks(ticks: u32) -> Self { + // We need to scale down to a 16-bit range + (ticks >> 16).next_power_of_two().into() + } + + pub fn scale_down(&self, ticks: u32) -> u16 { + (ticks / u32::from(self)).try_into().unwrap() + } + + pub fn scale_up(&self, ticks: u16) -> u32 { + u32::from(self) * ticks as u32 + } +} diff --git a/embassy/embassy-stm32/src/ltdc.rs b/embassy/embassy-stm32/src/ltdc.rs new file mode 100644 index 0000000..e25c4f3 --- /dev/null +++ b/embassy/embassy-stm32/src/ltdc.rs @@ -0,0 +1,578 @@ +//! LTDC - LCD-TFT Display Controller +//! See ST application note AN4861: Introduction to LCD-TFT display controller (LTDC) on STM32 MCUs for high level details +//! This module was tested against the stm32h735g-dk using the RM0468 ST reference manual for detailed register information + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use stm32_metapac::ltdc::regs::Dccr; +use stm32_metapac::ltdc::vals::{Bf1, Bf2, Cfuif, Clif, Crrif, Cterrif, Pf, Vbr}; + +use crate::gpio::{AfType, OutputType, Speed}; +use crate::interrupt::typelevel::Interrupt; +use crate::interrupt::{self}; +use crate::{peripherals, rcc, Peripheral}; + +static LTDC_WAKER: AtomicWaker = AtomicWaker::new(); + +/// LTDC error +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// FIFO underrun. Generated when a pixel is requested while the FIFO is empty + FifoUnderrun, + /// Transfer error. Generated when a bus error occurs + TransferError, +} + +/// Display configuration parameters +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct LtdcConfiguration { + /// Active width in pixels + pub active_width: u16, + /// Active height in pixels + pub active_height: u16, + + /// Horizontal back porch (in units of pixel clock period) + pub h_back_porch: u16, + /// Horizontal front porch (in units of pixel clock period) + pub h_front_porch: u16, + /// Vertical back porch (in units of horizontal scan line) + pub v_back_porch: u16, + /// Vertical front porch (in units of horizontal scan line) + pub v_front_porch: u16, + + /// Horizontal synchronization width (in units of pixel clock period) + pub h_sync: u16, + /// Vertical synchronization height (in units of horizontal scan line) + pub v_sync: u16, + + /// Horizontal synchronization polarity: `false`: active low, `true`: active high + pub h_sync_polarity: PolarityActive, + /// Vertical synchronization polarity: `false`: active low, `true`: active high + pub v_sync_polarity: PolarityActive, + /// Data enable polarity: `false`: active low, `true`: active high + pub data_enable_polarity: PolarityActive, + /// Pixel clock polarity: `false`: falling edge, `true`: rising edge + pub pixel_clock_polarity: PolarityEdge, +} + +/// Edge polarity +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PolarityEdge { + /// Falling edge + FallingEdge, + /// Rising edge + RisingEdge, +} + +/// Active polarity +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PolarityActive { + /// Active low + ActiveLow, + /// Active high + ActiveHigh, +} + +/// LTDC driver. +pub struct Ltdc<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, +} + +/// LTDC interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +/// 24 bit color +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RgbColor { + /// Red + pub red: u8, + /// Green + pub green: u8, + /// Blue + pub blue: u8, +} + +/// Layer +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct LtdcLayerConfig { + /// Layer number + pub layer: LtdcLayer, + /// Pixel format + pub pixel_format: PixelFormat, + /// Window left x in pixels + pub window_x0: u16, + /// Window right x in pixels + pub window_x1: u16, + /// Window top y in pixels + pub window_y0: u16, + /// Window bottom y in pixels + pub window_y1: u16, +} + +/// Pixel format +#[repr(u8)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PixelFormat { + /// ARGB8888 + ARGB8888 = Pf::ARGB8888 as u8, + /// RGB888 + RGB888 = Pf::RGB888 as u8, + /// RGB565 + RGB565 = Pf::RGB565 as u8, + /// ARGB1555 + ARGB1555 = Pf::ARGB1555 as u8, + /// ARGB4444 + ARGB4444 = Pf::ARGB4444 as u8, + /// L8 (8-bit luminance) + L8 = Pf::L8 as u8, + /// AL44 (4-bit alpha, 4-bit luminance + AL44 = Pf::AL44 as u8, + /// AL88 (8-bit alpha, 8-bit luminance) + AL88 = Pf::AL88 as u8, +} + +impl PixelFormat { + /// Number of bytes per pixel + pub fn bytes_per_pixel(&self) -> usize { + match self { + PixelFormat::ARGB8888 => 4, + PixelFormat::RGB888 => 3, + PixelFormat::RGB565 | PixelFormat::ARGB4444 | PixelFormat::ARGB1555 | PixelFormat::AL88 => 2, + PixelFormat::AL44 | PixelFormat::L8 => 1, + } + } +} + +/// Ltdc Blending Layer +#[repr(usize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LtdcLayer { + /// Bottom layer + Layer1 = 0, + /// Top layer + Layer2 = 1, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + cortex_m::asm::dsb(); + Ltdc::::enable_interrupts(false); + LTDC_WAKER.wake(); + } +} + +impl<'d, T: Instance> Ltdc<'d, T> { + // Create a new LTDC driver without specifying color and control pins. This is typically used if you want to drive a display though a DsiHost + /// Note: Full-Duplex modes are not supported at this time + pub fn new(peri: impl Peripheral

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

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

> + 'd, + hsync: impl Peripheral

> + 'd, + vsync: impl Peripheral

> + 'd, + b0: impl Peripheral

> + 'd, + b1: impl Peripheral

> + 'd, + b2: impl Peripheral

> + 'd, + b3: impl Peripheral

> + 'd, + b4: impl Peripheral

> + 'd, + b5: impl Peripheral

> + 'd, + b6: impl Peripheral

> + 'd, + b7: impl Peripheral

> + 'd, + g0: impl Peripheral

> + 'd, + g1: impl Peripheral

> + 'd, + g2: impl Peripheral

> + 'd, + g3: impl Peripheral

> + 'd, + g4: impl Peripheral

> + 'd, + g5: impl Peripheral

> + 'd, + g6: impl Peripheral

> + 'd, + g7: impl Peripheral

> + 'd, + r0: impl Peripheral

> + 'd, + r1: impl Peripheral

> + 'd, + r2: impl Peripheral

> + 'd, + r3: impl Peripheral

> + 'd, + r4: impl Peripheral

> + 'd, + r5: impl Peripheral

> + 'd, + r6: impl Peripheral

> + 'd, + r7: impl Peripheral

> + 'd, + ) -> Self { + Self::setup_clocks(); + into_ref!(peri); + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(hsync, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(vsync, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b0, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b1, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b2, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b3, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b4, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b5, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b6, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(b7, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g0, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g1, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g2, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g3, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g4, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g5, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g6, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(g7, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r0, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r1, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r2, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r3, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r4, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r5, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r6, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + new_pin!(r7, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + + Self { _peri: peri } + } + + /// Initialise and enable the display + pub fn init(&mut self, config: &LtdcConfiguration) { + use stm32_metapac::ltdc::vals::{Depol, Hspol, Pcpol, Vspol}; + let ltdc = T::regs(); + + // check bus access + assert!(ltdc.gcr().read().0 == 0x2220); // reset value + + // configure the HS, VS, DE and PC polarity + ltdc.gcr().modify(|w| { + w.set_hspol(match config.h_sync_polarity { + PolarityActive::ActiveHigh => Hspol::ACTIVEHIGH, + PolarityActive::ActiveLow => Hspol::ACTIVELOW, + }); + + w.set_vspol(match config.v_sync_polarity { + PolarityActive::ActiveHigh => Vspol::ACTIVEHIGH, + PolarityActive::ActiveLow => Vspol::ACTIVELOW, + }); + + w.set_depol(match config.data_enable_polarity { + PolarityActive::ActiveHigh => Depol::ACTIVEHIGH, + PolarityActive::ActiveLow => Depol::ACTIVELOW, + }); + + w.set_pcpol(match config.pixel_clock_polarity { + PolarityEdge::RisingEdge => Pcpol::RISINGEDGE, + PolarityEdge::FallingEdge => Pcpol::FALLINGEDGE, + }); + }); + + // set synchronization pulse width + ltdc.sscr().modify(|w| { + w.set_vsh(config.v_sync - 1); + w.set_hsw(config.h_sync - 1); + }); + + // set accumulated back porch + ltdc.bpcr().modify(|w| { + w.set_avbp(config.v_sync + config.v_back_porch - 1); + w.set_ahbp(config.h_sync + config.h_back_porch - 1); + }); + + // set accumulated active width + let aa_height = config.v_sync + config.v_back_porch + config.active_height - 1; + let aa_width = config.h_sync + config.h_back_porch + config.active_width - 1; + ltdc.awcr().modify(|w| { + w.set_aah(aa_height); + w.set_aaw(aa_width); + }); + + // set total width and height + let total_height: u16 = config.v_sync + config.v_back_porch + config.active_height + config.v_front_porch - 1; + let total_width: u16 = config.h_sync + config.h_back_porch + config.active_width + config.h_front_porch - 1; + ltdc.twcr().modify(|w| { + w.set_totalh(total_height); + w.set_totalw(total_width) + }); + + // set the background color value to black + ltdc.bccr().modify(|w| { + w.set_bcred(0); + w.set_bcgreen(0); + w.set_bcblue(0); + }); + + self.enable(); + } + + /// Set the enable bit in the control register and assert that it has been enabled + /// + /// This does need to be called if init has already been called + pub fn enable(&mut self) { + T::regs().gcr().modify(|w| w.set_ltdcen(true)); + assert!(T::regs().gcr().read().ltdcen()) + } + + /// Unset the enable bit in the control register and assert that it has been disabled + pub fn disable(&mut self) { + T::regs().gcr().modify(|w| w.set_ltdcen(false)); + assert!(!T::regs().gcr().read().ltdcen()) + } + + /// Initialise and enable the layer + /// + /// clut - a 256 length color look-up table applies to L8, AL44 and AL88 pixel format and will default to greyscale if `None` supplied and these pixel formats are used + pub fn init_layer(&mut self, layer_config: &LtdcLayerConfig, clut: Option<&[RgbColor]>) { + let ltdc = T::regs(); + let layer = ltdc.layer(layer_config.layer as usize); + + // 256 color look-up table for L8, AL88 and AL88 pixel formats + if let Some(clut) = clut { + assert_eq!(clut.len(), 256, "Color lookup table must be exactly 256 in length"); + for (index, color) in clut.iter().enumerate() { + layer.clutwr().write(|w| { + w.set_clutadd(index as u8); + w.set_red(color.red); + w.set_green(color.green); + w.set_blue(color.blue); + }); + } + } + + // configure the horizontal start and stop position + let h_win_start = layer_config.window_x0 + ltdc.bpcr().read().ahbp() + 1; + let h_win_stop = layer_config.window_x1 + ltdc.bpcr().read().ahbp(); + layer.whpcr().write(|w| { + w.set_whstpos(h_win_start); + w.set_whsppos(h_win_stop); + }); + + // configure the vertical start and stop position + let v_win_start = layer_config.window_y0 + ltdc.bpcr().read().avbp() + 1; + let v_win_stop = layer_config.window_y1 + ltdc.bpcr().read().avbp(); + layer.wvpcr().write(|w| { + w.set_wvstpos(v_win_start); + w.set_wvsppos(v_win_stop) + }); + + // set the pixel format + layer + .pfcr() + .write(|w| w.set_pf(Pf::from_bits(layer_config.pixel_format as u8))); + + // set the default color value to transparent black + layer.dccr().write_value(Dccr::default()); + + // set the global constant alpha value + let alpha = 0xFF; + layer.cacr().write(|w| w.set_consta(alpha)); + + // set the blending factors. + layer.bfcr().modify(|w| { + w.set_bf1(Bf1::PIXEL); + w.set_bf2(Bf2::PIXEL); + }); + + // calculate framebuffer pixel size in bytes + let bytes_per_pixel = layer_config.pixel_format.bytes_per_pixel() as u16; + let width = layer_config.window_x1 - layer_config.window_x0; + let height = layer_config.window_y1 - layer_config.window_y0; + + // framebuffer pitch and line length + layer.cfblr().modify(|w| { + w.set_cfbp(width * bytes_per_pixel); + #[cfg(not(stm32u5))] + w.set_cfbll(width * bytes_per_pixel + 7); + #[cfg(stm32u5)] + w.set_cfbll(width * bytes_per_pixel + 3); + }); + + // framebuffer line number + layer.cfblnr().modify(|w| w.set_cfblnbr(height)); + + // enable LTDC_Layer by setting LEN bit + layer.cr().modify(|w| { + if clut.is_some() { + w.set_cluten(true); + } + w.set_len(true); + }); + } + + /// Set the current buffer. The async function will return when buffer has been completely copied to the LCD screen + /// frame_buffer_addr is a pointer to memory that should not move (best to make it static) + pub async fn set_buffer(&mut self, layer: LtdcLayer, frame_buffer_addr: *const ()) -> Result<(), Error> { + let mut bits = T::regs().isr().read(); + + // if all clear + if !bits.fuif() && !bits.lif() && !bits.rrif() && !bits.terrif() { + // wait for interrupt + poll_fn(|cx| { + // quick check to avoid registration if already done. + let bits = T::regs().isr().read(); + if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() { + return Poll::Ready(()); + } + + LTDC_WAKER.register(cx.waker()); + Self::clear_interrupt_flags(); // don't poison the request with old flags + Self::enable_interrupts(true); + + // set the new frame buffer address + let layer = T::regs().layer(layer as usize); + layer.cfbar().modify(|w| w.set_cfbadd(frame_buffer_addr as u32)); + + // configure a shadow reload for the next blanking period + T::regs().srcr().write(|w| { + w.set_vbr(Vbr::RELOAD); + }); + + // need to check condition after register to avoid a race + // condition that would result in lost notifications. + let bits = T::regs().isr().read(); + if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + // re-read the status register after wait. + bits = T::regs().isr().read(); + } + + let result = if bits.fuif() { + Err(Error::FifoUnderrun) + } else if bits.terrif() { + Err(Error::TransferError) + } else if bits.lif() { + panic!("line interrupt event is disabled") + } else if bits.rrif() { + // register reload flag is expected + Ok(()) + } else { + unreachable!("all interrupt status values checked") + }; + + Self::clear_interrupt_flags(); + result + } + + fn setup_clocks() { + critical_section::with(|_cs| { + // RM says the pllsaidivr should only be changed when pllsai is off. But this could have other unintended side effects. So let's just give it a try like this. + // According to the debugger, this bit gets set, anyway. + #[cfg(stm32f7)] + crate::pac::RCC + .dckcfgr1() + .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2)); + + // It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO. + #[cfg(stm32f4)] + crate::pac::RCC + .dckcfgr() + .modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2)); + }); + + rcc::enable_and_reset::(); + } + + fn clear_interrupt_flags() { + T::regs().icr().write(|w| { + w.set_cfuif(Cfuif::CLEAR); + w.set_clif(Clif::CLEAR); + w.set_crrif(Crrif::CLEAR); + w.set_cterrif(Cterrif::CLEAR); + }); + } + + fn enable_interrupts(enable: bool) { + T::regs().ier().write(|w| { + w.set_fuie(enable); + w.set_lie(false); // we are not interested in the line interrupt enable event + w.set_rrie(enable); + w.set_terrie(enable) + }); + + // enable interrupts for LTDC peripheral + T::Interrupt::unpend(); + if enable { + unsafe { T::Interrupt::enable() }; + } else { + T::Interrupt::disable() + } + } +} + +impl<'d, T: Instance> Drop for Ltdc<'d, T> { + fn drop(&mut self) {} +} + +trait SealedInstance: crate::rcc::SealedRccPeripheral { + fn regs() -> crate::pac::ltdc::Ltdc; +} + +/// LTDC instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + Peripheral

+ crate::rcc::RccPeripheral + 'static + Send { + /// Interrupt for this LTDC instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +pin_trait!(ClkPin, Instance); +pin_trait!(HsyncPin, Instance); +pin_trait!(VsyncPin, Instance); +pin_trait!(DePin, Instance); +pin_trait!(R0Pin, Instance); +pin_trait!(R1Pin, Instance); +pin_trait!(R2Pin, Instance); +pin_trait!(R3Pin, Instance); +pin_trait!(R4Pin, Instance); +pin_trait!(R5Pin, Instance); +pin_trait!(R6Pin, Instance); +pin_trait!(R7Pin, Instance); +pin_trait!(G0Pin, Instance); +pin_trait!(G1Pin, Instance); +pin_trait!(G2Pin, Instance); +pin_trait!(G3Pin, Instance); +pin_trait!(G4Pin, Instance); +pin_trait!(G5Pin, Instance); +pin_trait!(G6Pin, Instance); +pin_trait!(G7Pin, Instance); +pin_trait!(B0Pin, Instance); +pin_trait!(B1Pin, Instance); +pin_trait!(B2Pin, Instance); +pin_trait!(B3Pin, Instance); +pin_trait!(B4Pin, Instance); +pin_trait!(B5Pin, Instance); +pin_trait!(B6Pin, Instance); +pin_trait!(B7Pin, Instance); + +foreach_interrupt!( + ($inst:ident, ltdc, LTDC, GLOBAL, $irq:ident) => { + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + + impl SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::ltdc::Ltdc { + crate::pac::$inst + } + } + }; +); diff --git a/embassy/embassy-stm32/src/macros.rs b/embassy/embassy-stm32/src/macros.rs new file mode 100644 index 0000000..ae53deb --- /dev/null +++ b/embassy/embassy-stm32/src/macros.rs @@ -0,0 +1,105 @@ +#![macro_use] + +macro_rules! peri_trait { + ( + $(irqs: [$($irq:ident),*],)? + ) => { + #[allow(private_interfaces)] + pub(crate) trait SealedInstance { + #[allow(unused)] + fn info() -> &'static Info; + #[allow(unused)] + fn state() -> &'static State; + } + + /// Peripheral instance trait. + #[allow(private_bounds)] + pub trait Instance: crate::Peripheral

+ SealedInstance + crate::rcc::RccPeripheral { + $($( + /// Interrupt for this peripheral. + type $irq: crate::interrupt::typelevel::Interrupt; + )*)? + } + }; +} + +macro_rules! peri_trait_impl { + ($instance:ident, $info:expr) => { + #[allow(private_interfaces)] + impl SealedInstance for crate::peripherals::$instance { + fn info() -> &'static Info { + static INFO: Info = $info; + &INFO + } + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + impl Instance for crate::peripherals::$instance {} + }; +} + +macro_rules! pin_trait { + ($signal:ident, $instance:path $(, $mode:path)?) => { + #[doc = concat!(stringify!($signal), " pin trait")] + pub trait $signal: crate::gpio::Pin { + #[doc = concat!("Get the AF number needed to use this pin as ", stringify!($signal))] + fn af_num(&self) -> u8; + } + }; +} + +macro_rules! pin_trait_impl { + (crate::$mod:ident::$trait:ident$(<$mode:ident>)?, $instance:ident, $pin:ident, $af:expr) => { + impl crate::$mod::$trait for crate::peripherals::$pin { + fn af_num(&self) -> u8 { + $af + } + } + }; +} + +// ==================== + +macro_rules! dma_trait { + ($signal:ident, $instance:path$(, $mode:path)?) => { + #[doc = concat!(stringify!($signal), " DMA request trait")] + pub trait $signal: crate::dma::Channel { + #[doc = concat!("Get the DMA request number needed to use this channel as", stringify!($signal))] + /// Note: in some chips, ST calls this the "channel", and calls channels "streams". + /// `embassy-stm32` always uses the "channel" and "request number" names. + fn request(&self) -> crate::dma::Request; + } + }; +} + +#[allow(unused)] +macro_rules! dma_trait_impl { + (crate::$mod:ident::$trait:ident$(<$mode:ident>)?, $instance:ident, $channel:ident, $request:expr) => { + impl crate::$mod::$trait for crate::peripherals::$channel { + fn request(&self) -> crate::dma::Request { + $request + } + } + }; +} + +macro_rules! new_dma { + ($name:ident) => {{ + let dma = $name.into_ref(); + let request = dma.request(); + Some(crate::dma::ChannelAndRequest { + channel: dma.map_into(), + request, + }) + }}; +} + +macro_rules! new_pin { + ($name:ident, $af_type:expr) => {{ + let pin = $name.into_ref(); + pin.set_as_af(pin.af_num(), $af_type); + Some(pin.map_into()) + }}; +} diff --git a/embassy/embassy-stm32/src/opamp.rs b/embassy/embassy-stm32/src/opamp.rs new file mode 100644 index 0000000..d1c53a7 --- /dev/null +++ b/embassy/embassy-stm32/src/opamp.rs @@ -0,0 +1,356 @@ +//! Operational Amplifier (OPAMP) +#![macro_use] + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use crate::pac::opamp::vals::*; +use crate::Peripheral; + +/// Gain +#[allow(missing_docs)] +#[derive(Clone, Copy)] +pub enum OpAmpGain { + Mul1, + Mul2, + Mul4, + Mul8, + Mul16, +} + +/// Speed +#[allow(missing_docs)] +#[derive(Clone, Copy)] +pub enum OpAmpSpeed { + Normal, + HighSpeed, +} + +#[cfg(opamp_g4)] +impl From for crate::pac::opamp::vals::Opahsm { + fn from(v: OpAmpSpeed) -> Self { + match v { + OpAmpSpeed::Normal => crate::pac::opamp::vals::Opahsm::NORMAL, + OpAmpSpeed::HighSpeed => crate::pac::opamp::vals::Opahsm::HIGHSPEED, + } + } +} + +/// OpAmp external outputs, wired to a GPIO pad. +/// +/// This struct can also be used as an ADC input. +pub struct OpAmpOutput<'d, T: Instance> { + _inner: &'d OpAmp<'d, T>, +} + +/// OpAmp internal outputs, wired directly to ADC inputs. +/// +/// This struct can be used as an ADC input. +#[cfg(opamp_g4)] +pub struct OpAmpInternalOutput<'d, T: Instance> { + _inner: &'d OpAmp<'d, T>, +} + +/// OpAmp driver. +pub struct OpAmp<'d, T: Instance> { + _inner: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> OpAmp<'d, T> { + /// Create a new driver instance. + /// + /// Does not enable the opamp, but does set the speed mode on some families. + pub fn new(opamp: impl Peripheral

+ 'd, #[cfg(opamp_g4)] speed: OpAmpSpeed) -> Self { + into_ref!(opamp); + + #[cfg(opamp_g4)] + T::regs().csr().modify(|w| { + w.set_opahsm(speed.into()); + }); + + Self { _inner: opamp } + } + + /// Configure the OpAmp as a buffer for the provided input pin, + /// outputting to the provided output pin, and enable the opamp. + /// + /// The input pin is configured for analogue mode but not consumed, + /// so it may subsequently be used for ADC or comparator inputs. + /// + /// The output pin is held within the returned [`OpAmpOutput`] struct, + /// preventing it being used elsewhere. The `OpAmpOutput` can then be + /// directly used as an ADC input. The opamp will be disabled when the + /// [`OpAmpOutput`] is dropped. + pub fn buffer_ext( + &mut self, + in_pin: impl Peripheral

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

+ crate::gpio::Pin>, + gain: OpAmpGain, + ) -> OpAmpOutput<'_, T> { + into_ref!(in_pin); + into_ref!(out_pin); + in_pin.set_as_analog(); + out_pin.set_as_analog(); + + // PGA_GAIN value may have different meaning in different MCU serials, use with caution. + let (vm_sel, pga_gain) = match gain { + OpAmpGain::Mul1 => (0b11, 0b00), + OpAmpGain::Mul2 => (0b10, 0b00), + OpAmpGain::Mul4 => (0b10, 0b01), + OpAmpGain::Mul8 => (0b10, 0b10), + OpAmpGain::Mul16 => (0b10, 0b11), + }; + + T::regs().csr().modify(|w| { + w.set_vp_sel(VpSel::from_bits(in_pin.channel())); + w.set_vm_sel(VmSel::from_bits(vm_sel)); + w.set_pga_gain(PgaGain::from_bits(pga_gain)); + #[cfg(opamp_g4)] + w.set_opaintoen(Opaintoen::OUTPUTPIN); + w.set_opampen(true); + }); + + OpAmpOutput { _inner: self } + } + /// Configure the OpAmp as a buffer for the DAC it is connected to, + /// outputting to the provided output pin, and enable the opamp. + /// + /// The output pin is held within the returned [`OpAmpOutput`] struct, + /// preventing it being used elsewhere. The `OpAmpOutput` can then be + /// directly used as an ADC input. The opamp will be disabled when the + /// [`OpAmpOutput`] is dropped. + #[cfg(opamp_g4)] + pub fn buffer_dac( + &mut self, + out_pin: impl Peripheral

+ crate::gpio::Pin>, + ) -> OpAmpOutput<'_, T> { + into_ref!(out_pin); + out_pin.set_as_analog(); + + T::regs().csr().modify(|w| { + use crate::pac::opamp::vals::*; + + w.set_vm_sel(VmSel::OUTPUT); + w.set_vp_sel(VpSel::DAC3_CH1); + w.set_opaintoen(Opaintoen::OUTPUTPIN); + w.set_opampen(true); + }); + + OpAmpOutput { _inner: self } + } + + /// Configure the OpAmp as a buffer for the provided input pin, + /// with the output only used internally, and enable the opamp. + /// + /// The input pin is configured for analogue mode but not consumed, + /// so it may be subsequently used for ADC or comparator inputs. + /// + /// The returned `OpAmpInternalOutput` struct may be used as an ADC input. + /// The opamp output will be disabled when it is dropped. + #[cfg(opamp_g4)] + pub fn buffer_int( + &mut self, + pin: impl Peripheral

+ crate::gpio::Pin>, + gain: OpAmpGain, + ) -> OpAmpInternalOutput<'_, T> { + into_ref!(pin); + pin.set_as_analog(); + + // PGA_GAIN value may have different meaning in different MCU serials, use with caution. + let (vm_sel, pga_gain) = match gain { + OpAmpGain::Mul1 => (0b11, 0b00), + OpAmpGain::Mul2 => (0b10, 0b00), + OpAmpGain::Mul4 => (0b10, 0b01), + OpAmpGain::Mul8 => (0b10, 0b10), + OpAmpGain::Mul16 => (0b10, 0b11), + }; + + T::regs().csr().modify(|w| { + use crate::pac::opamp::vals::*; + w.set_vp_sel(VpSel::from_bits(pin.channel())); + w.set_vm_sel(VmSel::from_bits(vm_sel)); + w.set_pga_gain(PgaGain::from_bits(pga_gain)); + w.set_opaintoen(Opaintoen::ADCCHANNEL); + w.set_opampen(true); + }); + + OpAmpInternalOutput { _inner: self } + } +} + +impl<'d, T: Instance> Drop for OpAmpOutput<'d, T> { + fn drop(&mut self) { + T::regs().csr().modify(|w| { + w.set_opampen(false); + }); + } +} + +#[cfg(opamp_g4)] +impl<'d, T: Instance> Drop for OpAmpInternalOutput<'d, T> { + fn drop(&mut self) { + T::regs().csr().modify(|w| { + w.set_opampen(false); + }); + } +} + +pub(crate) trait SealedInstance { + fn regs() -> crate::pac::opamp::Opamp; +} + +pub(crate) trait SealedNonInvertingPin { + fn channel(&self) -> u8; +} + +pub(crate) trait SealedInvertingPin { + #[allow(unused)] + fn channel(&self) -> u8; +} + +pub(crate) trait SealedOutputPin {} + +/// Opamp instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + 'static {} +/// Non-inverting pin trait. +#[allow(private_bounds)] +pub trait NonInvertingPin: SealedNonInvertingPin {} +/// Inverting pin trait. +#[allow(private_bounds)] +pub trait InvertingPin: SealedInvertingPin {} +/// Output pin trait. +#[allow(private_bounds)] +pub trait OutputPin: SealedOutputPin {} + +macro_rules! impl_opamp_external_output { + ($inst:ident, $adc:ident, $ch:expr) => { + foreach_adc!( + ($adc, $common_inst:ident, $adc_clock:ident) => { + impl<'d> crate::adc::SealedAdcChannel + for OpAmpOutput<'d, crate::peripherals::$inst> + { + fn channel(&self) -> u8 { + $ch + } + } + + impl<'d> crate::adc::AdcChannel + for OpAmpOutput<'d, crate::peripherals::$inst> + { + } + }; + ); + }; +} + +foreach_peripheral!( + (opamp, OPAMP1) => { + impl_opamp_external_output!(OPAMP1, ADC1, 3); + }; + (opamp, OPAMP2) => { + impl_opamp_external_output!(OPAMP2, ADC2, 3); + }; + (opamp, OPAMP3) => { + impl_opamp_external_output!(OPAMP3, ADC1, 12); + impl_opamp_external_output!(OPAMP3, ADC3, 1); + }; + // OPAMP4 only in STM32G4 Cat 3 devices + (opamp, OPAMP4) => { + impl_opamp_external_output!(OPAMP4, ADC1, 11); + impl_opamp_external_output!(OPAMP4, ADC4, 3); + }; + // OPAMP5 only in STM32G4 Cat 3 devices + (opamp, OPAMP5) => { + impl_opamp_external_output!(OPAMP5, ADC5, 1); + }; + // OPAMP6 only in STM32G4 Cat 3/4 devices + (opamp, OPAMP6) => { + impl_opamp_external_output!(OPAMP6, ADC1, 14); + impl_opamp_external_output!(OPAMP6, ADC2, 14); + }; +); + +#[cfg(opamp_g4)] +macro_rules! impl_opamp_internal_output { + ($inst:ident, $adc:ident, $ch:expr) => { + foreach_adc!( + ($adc, $common_inst:ident, $adc_clock:ident) => { + impl<'d> crate::adc::SealedAdcChannel + for OpAmpInternalOutput<'d, crate::peripherals::$inst> + { + fn channel(&self) -> u8 { + $ch + } + } + + impl<'d> crate::adc::AdcChannel + for OpAmpInternalOutput<'d, crate::peripherals::$inst> + { + } + }; + ); + }; +} + +#[cfg(opamp_g4)] +foreach_peripheral!( + (opamp, OPAMP1) => { + impl_opamp_internal_output!(OPAMP1, ADC1, 13); + }; + (opamp, OPAMP2) => { + impl_opamp_internal_output!(OPAMP2, ADC2, 16); + }; + (opamp, OPAMP3) => { + impl_opamp_internal_output!(OPAMP3, ADC2, 18); + // Only in Cat 3/4 devices + impl_opamp_internal_output!(OPAMP3, ADC3, 13); + }; + // OPAMP4 only in Cat 3 devices + (opamp, OPAMP4) => { + impl_opamp_internal_output!(OPAMP4, ADC5, 5); + }; + // OPAMP5 only in Cat 3 devices + (opamp, OPAMP5) => { + impl_opamp_internal_output!(OPAMP5, ADC5, 3); + }; + // OPAMP6 only in Cat 3/4 devices + (opamp, OPAMP6) => { + // Only in Cat 3 devices + impl_opamp_internal_output!(OPAMP6, ADC4, 17); + // Only in Cat 4 devices + impl_opamp_internal_output!(OPAMP6, ADC3, 17); + }; +); + +foreach_peripheral! { + (opamp, $inst:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::opamp::Opamp { + crate::pac::$inst + } + } + + impl Instance for crate::peripherals::$inst { + } + }; +} + +#[allow(unused_macros)] +macro_rules! impl_opamp_vp_pin { + ($inst:ident, $pin:ident, $ch:expr) => { + impl crate::opamp::NonInvertingPin for crate::peripherals::$pin {} + impl crate::opamp::SealedNonInvertingPin for crate::peripherals::$pin { + fn channel(&self) -> u8 { + $ch + } + } + }; +} + +#[allow(unused_macros)] +macro_rules! impl_opamp_vout_pin { + ($inst:ident, $pin:ident) => { + impl crate::opamp::OutputPin for crate::peripherals::$pin {} + impl crate::opamp::SealedOutputPin for crate::peripherals::$pin {} + }; +} diff --git a/embassy/embassy-stm32/src/ospi/enums.rs b/embassy/embassy-stm32/src/ospi/enums.rs new file mode 100644 index 0000000..4021f7c --- /dev/null +++ b/embassy/embassy-stm32/src/ospi/enums.rs @@ -0,0 +1,386 @@ +//! Enums used in Ospi configuration. + +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub(crate) enum OspiMode { + IndirectWrite, + IndirectRead, + AutoPolling, + MemoryMapped, +} + +impl Into for OspiMode { + fn into(self) -> u8 { + match self { + OspiMode::IndirectWrite => 0b00, + OspiMode::IndirectRead => 0b01, + OspiMode::AutoPolling => 0b10, + OspiMode::MemoryMapped => 0b11, + } + } +} + +/// Ospi lane width +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub enum OspiWidth { + /// None + NONE, + /// Single lane + SING, + /// Dual lanes + DUAL, + /// Quad lanes + QUAD, + /// Eight lanes + OCTO, +} + +impl Into for OspiWidth { + fn into(self) -> u8 { + match self { + OspiWidth::NONE => 0b00, + OspiWidth::SING => 0b01, + OspiWidth::DUAL => 0b10, + OspiWidth::QUAD => 0b11, + OspiWidth::OCTO => 0b100, + } + } +} + +/// Flash bank selection +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub enum FlashSelection { + /// Bank 1 + Flash1, + /// Bank 2 + Flash2, +} + +impl Into for FlashSelection { + fn into(self) -> bool { + match self { + FlashSelection::Flash1 => false, + FlashSelection::Flash2 => true, + } + } +} + +/// Wrap Size +#[allow(dead_code)] +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum WrapSize { + None, + _16Bytes, + _32Bytes, + _64Bytes, + _128Bytes, +} + +impl Into for WrapSize { + fn into(self) -> u8 { + match self { + WrapSize::None => 0x00, + WrapSize::_16Bytes => 0x02, + WrapSize::_32Bytes => 0x03, + WrapSize::_64Bytes => 0x04, + WrapSize::_128Bytes => 0x05, + } + } +} + +/// Memory Type +#[allow(missing_docs)] +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub enum MemoryType { + Micron, + Macronix, + Standard, + MacronixRam, + HyperBusMemory, + HyperBusRegister, +} + +impl Into for MemoryType { + fn into(self) -> u8 { + match self { + MemoryType::Micron => 0x00, + MemoryType::Macronix => 0x01, + MemoryType::Standard => 0x02, + MemoryType::MacronixRam => 0x03, + MemoryType::HyperBusMemory => 0x04, + MemoryType::HyperBusRegister => 0x04, + } + } +} + +/// Ospi memory size. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum MemorySize { + _1KiB, + _2KiB, + _4KiB, + _8KiB, + _16KiB, + _32KiB, + _64KiB, + _128KiB, + _256KiB, + _512KiB, + _1MiB, + _2MiB, + _4MiB, + _8MiB, + _16MiB, + _32MiB, + _64MiB, + _128MiB, + _256MiB, + _512MiB, + _1GiB, + _2GiB, + _4GiB, + Other(u8), +} + +impl Into for MemorySize { + fn into(self) -> u8 { + match self { + MemorySize::_1KiB => 9, + MemorySize::_2KiB => 10, + MemorySize::_4KiB => 11, + MemorySize::_8KiB => 12, + MemorySize::_16KiB => 13, + MemorySize::_32KiB => 14, + MemorySize::_64KiB => 15, + MemorySize::_128KiB => 16, + MemorySize::_256KiB => 17, + MemorySize::_512KiB => 18, + MemorySize::_1MiB => 19, + MemorySize::_2MiB => 20, + MemorySize::_4MiB => 21, + MemorySize::_8MiB => 22, + MemorySize::_16MiB => 23, + MemorySize::_32MiB => 24, + MemorySize::_64MiB => 25, + MemorySize::_128MiB => 26, + MemorySize::_256MiB => 27, + MemorySize::_512MiB => 28, + MemorySize::_1GiB => 29, + MemorySize::_2GiB => 30, + MemorySize::_4GiB => 31, + MemorySize::Other(val) => val, + } + } +} + +/// Ospi Address size +#[derive(Copy, Clone)] +pub enum AddressSize { + /// 8-bit address + _8Bit, + /// 16-bit address + _16Bit, + /// 24-bit address + _24bit, + /// 32-bit address + _32bit, +} + +impl Into for AddressSize { + fn into(self) -> u8 { + match self { + AddressSize::_8Bit => 0b00, + AddressSize::_16Bit => 0b01, + AddressSize::_24bit => 0b10, + AddressSize::_32bit => 0b11, + } + } +} + +/// Time the Chip Select line stays high. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum ChipSelectHighTime { + _1Cycle, + _2Cycle, + _3Cycle, + _4Cycle, + _5Cycle, + _6Cycle, + _7Cycle, + _8Cycle, +} + +impl Into for ChipSelectHighTime { + fn into(self) -> u8 { + match self { + ChipSelectHighTime::_1Cycle => 0, + ChipSelectHighTime::_2Cycle => 1, + ChipSelectHighTime::_3Cycle => 2, + ChipSelectHighTime::_4Cycle => 3, + ChipSelectHighTime::_5Cycle => 4, + ChipSelectHighTime::_6Cycle => 5, + ChipSelectHighTime::_7Cycle => 6, + ChipSelectHighTime::_8Cycle => 7, + } + } +} + +/// FIFO threshold. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum FIFOThresholdLevel { + _1Bytes, + _2Bytes, + _3Bytes, + _4Bytes, + _5Bytes, + _6Bytes, + _7Bytes, + _8Bytes, + _9Bytes, + _10Bytes, + _11Bytes, + _12Bytes, + _13Bytes, + _14Bytes, + _15Bytes, + _16Bytes, + _17Bytes, + _18Bytes, + _19Bytes, + _20Bytes, + _21Bytes, + _22Bytes, + _23Bytes, + _24Bytes, + _25Bytes, + _26Bytes, + _27Bytes, + _28Bytes, + _29Bytes, + _30Bytes, + _31Bytes, + _32Bytes, +} + +impl Into for FIFOThresholdLevel { + fn into(self) -> u8 { + match self { + FIFOThresholdLevel::_1Bytes => 0, + FIFOThresholdLevel::_2Bytes => 1, + FIFOThresholdLevel::_3Bytes => 2, + FIFOThresholdLevel::_4Bytes => 3, + FIFOThresholdLevel::_5Bytes => 4, + FIFOThresholdLevel::_6Bytes => 5, + FIFOThresholdLevel::_7Bytes => 6, + FIFOThresholdLevel::_8Bytes => 7, + FIFOThresholdLevel::_9Bytes => 8, + FIFOThresholdLevel::_10Bytes => 9, + FIFOThresholdLevel::_11Bytes => 10, + FIFOThresholdLevel::_12Bytes => 11, + FIFOThresholdLevel::_13Bytes => 12, + FIFOThresholdLevel::_14Bytes => 13, + FIFOThresholdLevel::_15Bytes => 14, + FIFOThresholdLevel::_16Bytes => 15, + FIFOThresholdLevel::_17Bytes => 16, + FIFOThresholdLevel::_18Bytes => 17, + FIFOThresholdLevel::_19Bytes => 18, + FIFOThresholdLevel::_20Bytes => 19, + FIFOThresholdLevel::_21Bytes => 20, + FIFOThresholdLevel::_22Bytes => 21, + FIFOThresholdLevel::_23Bytes => 22, + FIFOThresholdLevel::_24Bytes => 23, + FIFOThresholdLevel::_25Bytes => 24, + FIFOThresholdLevel::_26Bytes => 25, + FIFOThresholdLevel::_27Bytes => 26, + FIFOThresholdLevel::_28Bytes => 27, + FIFOThresholdLevel::_29Bytes => 28, + FIFOThresholdLevel::_30Bytes => 29, + FIFOThresholdLevel::_31Bytes => 30, + FIFOThresholdLevel::_32Bytes => 31, + } + } +} + +/// Dummy cycle count +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum DummyCycles { + _0, + _1, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + _11, + _12, + _13, + _14, + _15, + _16, + _17, + _18, + _19, + _20, + _21, + _22, + _23, + _24, + _25, + _26, + _27, + _28, + _29, + _30, + _31, +} + +impl Into for DummyCycles { + fn into(self) -> u8 { + match self { + DummyCycles::_0 => 0, + DummyCycles::_1 => 1, + DummyCycles::_2 => 2, + DummyCycles::_3 => 3, + DummyCycles::_4 => 4, + DummyCycles::_5 => 5, + DummyCycles::_6 => 6, + DummyCycles::_7 => 7, + DummyCycles::_8 => 8, + DummyCycles::_9 => 9, + DummyCycles::_10 => 10, + DummyCycles::_11 => 11, + DummyCycles::_12 => 12, + DummyCycles::_13 => 13, + DummyCycles::_14 => 14, + DummyCycles::_15 => 15, + DummyCycles::_16 => 16, + DummyCycles::_17 => 17, + DummyCycles::_18 => 18, + DummyCycles::_19 => 19, + DummyCycles::_20 => 20, + DummyCycles::_21 => 21, + DummyCycles::_22 => 22, + DummyCycles::_23 => 23, + DummyCycles::_24 => 24, + DummyCycles::_25 => 25, + DummyCycles::_26 => 26, + DummyCycles::_27 => 27, + DummyCycles::_28 => 28, + DummyCycles::_29 => 29, + DummyCycles::_30 => 30, + DummyCycles::_31 => 31, + } + } +} diff --git a/embassy/embassy-stm32/src/ospi/mod.rs b/embassy/embassy-stm32/src/ospi/mod.rs new file mode 100644 index 0000000..38217a9 --- /dev/null +++ b/embassy/embassy-stm32/src/ospi/mod.rs @@ -0,0 +1,1302 @@ +//! OCTOSPI Serial Peripheral Interface +//! + +#![macro_use] + +pub mod enums; + +use core::marker::PhantomData; + +use embassy_embedded_hal::{GetConfig, SetConfig}; +use embassy_hal_internal::{into_ref, PeripheralRef}; +pub use enums::*; +use stm32_metapac::octospi::vals::{PhaseMode, SizeInBits}; + +use crate::dma::{word, ChannelAndRequest}; +use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; +use crate::mode::{Async, Blocking, Mode as PeriMode}; +use crate::pac::octospi::{vals, Octospi as Regs}; +#[cfg(octospim_v1)] +use crate::pac::octospim::Octospim; +use crate::rcc::{self, RccPeripheral}; +use crate::{peripherals, Peripheral}; + +/// OPSI driver config. +#[derive(Clone, Copy)] +pub struct Config { + /// Fifo threshold used by the peripheral to generate the interrupt indicating data + /// or space is available in the FIFO + pub fifo_threshold: FIFOThresholdLevel, + /// Indicates the type of external device connected + pub memory_type: MemoryType, // Need to add an additional enum to provide this public interface + /// Defines the size of the external device connected to the OSPI corresponding + /// to the number of address bits required to access the device + pub device_size: MemorySize, + /// Sets the minimum number of clock cycles that the chip select signal must be held high + /// between commands + pub chip_select_high_time: ChipSelectHighTime, + /// Enables the free running clock + pub free_running_clock: bool, + /// Sets the clock level when the device is not selected + pub clock_mode: bool, + /// Indicates the wrap size corresponding to the external device configuration + pub wrap_size: WrapSize, + /// Specified the prescaler factor used for generating the external clock based + /// on the AHB clock + pub clock_prescaler: u8, + /// Allows the delay of 1/2 cycle the data sampling to account for external + /// signal delays + pub sample_shifting: bool, + /// Allows hold to 1/4 cycle the data + pub delay_hold_quarter_cycle: bool, + /// Enables the transaction boundary feature and defines the boundary to release + /// the chip select + pub chip_select_boundary: u8, + /// Enbales the delay block bypass so the sampling is not affected by the delay block + pub delay_block_bypass: bool, + /// Enables communication regulation feature. Chip select is released when the other + /// OctoSpi requests access to the bus + pub max_transfer: u8, + /// Enables the refresh feature, chip select is released every refresh + 1 clock cycles + pub refresh: u32, +} + +impl Default for Config { + fn default() -> Self { + Self { + fifo_threshold: FIFOThresholdLevel::_16Bytes, // 32 bytes FIFO, half capacity + memory_type: MemoryType::Micron, + device_size: MemorySize::Other(0), + chip_select_high_time: ChipSelectHighTime::_5Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 0, + sample_shifting: false, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, // Acceptable range 0 to 31 + delay_block_bypass: true, + max_transfer: 0, + refresh: 0, + } + } +} + +/// OSPI transfer configuration. +pub struct TransferConfig { + /// Instruction width (IMODE) + pub iwidth: OspiWidth, + /// Instruction Id + pub instruction: Option, + /// Number of Instruction Bytes + pub isize: AddressSize, + /// Instruction Double Transfer rate enable + pub idtr: bool, + + /// Address width (ADMODE) + pub adwidth: OspiWidth, + /// Device memory address + pub address: Option, + /// Number of Address Bytes + pub adsize: AddressSize, + /// Address Double Transfer rate enable + pub addtr: bool, + + /// Alternate bytes width (ABMODE) + pub abwidth: OspiWidth, + /// Alternate Bytes + pub alternate_bytes: Option, + /// Number of Alternate Bytes + pub absize: AddressSize, + /// Alternate Bytes Double Transfer rate enable + pub abdtr: bool, + + /// Data width (DMODE) + pub dwidth: OspiWidth, + /// Data buffer + pub ddtr: bool, + + /// Number of dummy cycles (DCYC) + pub dummy: DummyCycles, +} + +impl Default for TransferConfig { + fn default() -> Self { + Self { + iwidth: OspiWidth::NONE, + instruction: None, + isize: AddressSize::_8Bit, + idtr: false, + + adwidth: OspiWidth::NONE, + address: None, + adsize: AddressSize::_8Bit, + addtr: false, + + abwidth: OspiWidth::NONE, + alternate_bytes: None, + absize: AddressSize::_8Bit, + abdtr: false, + + dwidth: OspiWidth::NONE, + ddtr: false, + + dummy: DummyCycles::_0, + } + } +} + +/// Error used for Octospi implementation +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum OspiError { + /// Peripheral configuration is invalid + InvalidConfiguration, + /// Operation configuration is invalid + InvalidCommand, + /// Size zero buffer passed to instruction + EmptyBuffer, +} + +/// OSPI driver. +pub struct Ospi<'d, T: Instance, M: PeriMode> { + _peri: PeripheralRef<'d, T>, + sck: Option>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + nss: Option>, + dqs: Option>, + dma: Option>, + _phantom: PhantomData, + config: Config, + width: OspiWidth, +} + +impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { + /// Enter memory mode. + /// The Input `read_config` is used to configure the read operation in memory mode + pub fn enable_memory_mapped_mode( + &mut self, + read_config: TransferConfig, + write_config: TransferConfig, + ) -> Result<(), OspiError> { + // Use configure command to set read config + self.configure_command(&read_config, None)?; + + let reg = T::REGS; + while reg.sr().read().busy() {} + + reg.ccr().modify(|r| { + r.set_dqse(false); + r.set_sioo(true); + }); + + // Set wrting configurations, there are separate registers for write configurations in memory mapped mode + reg.wccr().modify(|w| { + w.set_imode(PhaseMode::from_bits(write_config.iwidth.into())); + w.set_idtr(write_config.idtr); + w.set_isize(SizeInBits::from_bits(write_config.isize.into())); + + w.set_admode(PhaseMode::from_bits(write_config.adwidth.into())); + w.set_addtr(write_config.idtr); + w.set_adsize(SizeInBits::from_bits(write_config.adsize.into())); + + w.set_dmode(PhaseMode::from_bits(write_config.dwidth.into())); + w.set_ddtr(write_config.ddtr); + + w.set_abmode(PhaseMode::from_bits(write_config.abwidth.into())); + w.set_dqse(true); + }); + + reg.wtcr().modify(|w| w.set_dcyc(write_config.dummy.into())); + + // Enable memory mapped mode + reg.cr().modify(|r| { + r.set_fmode(crate::ospi::vals::FunctionalMode::MEMORYMAPPED); + r.set_tcen(false); + }); + Ok(()) + } + + /// Quit from memory mapped mode + pub fn disable_memory_mapped_mode(&mut self) { + let reg = T::REGS; + + reg.cr().modify(|r| { + r.set_fmode(crate::ospi::vals::FunctionalMode::INDIRECTWRITE); + r.set_abort(true); + r.set_dmaen(false); + r.set_en(false); + }); + + // Clear transfer complete flag + reg.fcr().write(|w| w.set_ctcf(true)); + + // Re-enable ospi + reg.cr().modify(|r| { + r.set_en(true); + }); + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + sck: Option>, + nss: Option>, + dqs: Option>, + dma: Option>, + config: Config, + width: OspiWidth, + dual_quad: bool, + ) -> Self { + into_ref!(peri); + + #[cfg(octospim_v1)] + { + // RCC for octospim should be enabled before writing register + #[cfg(stm32l4)] + crate::pac::RCC.ahb2smenr().modify(|w| w.set_octospimsmen(true)); + #[cfg(stm32u5)] + crate::pac::RCC.ahb2enr1().modify(|w| w.set_octospimen(true)); + #[cfg(not(any(stm32l4, stm32u5)))] + crate::pac::RCC.ahb3enr().modify(|w| w.set_iomngren(true)); + + // Disable OctoSPI peripheral first + T::REGS.cr().modify(|w| { + w.set_en(false); + }); + + // OctoSPI IO Manager has been enabled before + T::OCTOSPIM_REGS.cr().modify(|w| { + w.set_muxen(false); + w.set_req2ack_time(0xff); + }); + + // Clear config + T::OCTOSPIM_REGS.p1cr().modify(|w| { + w.set_clksrc(false); + w.set_dqssrc(false); + w.set_ncssrc(false); + w.set_clken(false); + w.set_dqsen(false); + w.set_ncsen(false); + w.set_iolsrc(0); + w.set_iohsrc(0); + }); + + T::OCTOSPIM_REGS.p1cr().modify(|w| { + let octospi_src = if T::OCTOSPI_IDX == 1 { false } else { true }; + w.set_ncsen(true); + w.set_ncssrc(octospi_src); + w.set_clken(true); + w.set_clksrc(octospi_src); + if dqs.is_some() { + w.set_dqsen(true); + w.set_dqssrc(octospi_src); + } + + // Set OCTOSPIM IOL and IOH according to the index of OCTOSPI instance + if T::OCTOSPI_IDX == 1 { + w.set_iolen(true); + w.set_iolsrc(0); + // Enable IOH in octo and dual quad mode + if let OspiWidth::OCTO = width { + w.set_iohen(true); + w.set_iohsrc(0b01); + } else if dual_quad { + w.set_iohen(true); + w.set_iohsrc(0b00); + } else { + w.set_iohen(false); + w.set_iohsrc(0b00); + } + } else { + w.set_iolen(true); + w.set_iolsrc(0b10); + // Enable IOH in octo and dual quad mode + if let OspiWidth::OCTO = width { + w.set_iohen(true); + w.set_iohsrc(0b11); + } else if dual_quad { + w.set_iohen(true); + w.set_iohsrc(0b10); + } else { + w.set_iohen(false); + w.set_iohsrc(0b00); + } + } + }); + } + + // System configuration + rcc::enable_and_reset::(); + while T::REGS.sr().read().busy() {} + + // Device configuration + T::REGS.dcr1().modify(|w| { + w.set_devsize(config.device_size.into()); + w.set_mtyp(vals::MemType::from_bits(config.memory_type.into())); + w.set_csht(config.chip_select_high_time.into()); + w.set_dlybyp(config.delay_block_bypass); + w.set_frck(false); + w.set_ckmode(config.clock_mode); + }); + + T::REGS.dcr2().modify(|w| { + w.set_wrapsize(config.wrap_size.into()); + }); + + T::REGS.dcr3().modify(|w| { + w.set_csbound(config.chip_select_boundary); + #[cfg(octospi_v1)] + { + w.set_maxtran(config.max_transfer); + } + }); + + T::REGS.dcr4().modify(|w| { + w.set_refresh(config.refresh); + }); + + T::REGS.cr().modify(|w| { + w.set_fthres(vals::Threshold(config.fifo_threshold.into())); + }); + + // Wait for busy flag to clear + while T::REGS.sr().read().busy() {} + + T::REGS.dcr2().modify(|w| { + w.set_prescaler(config.clock_prescaler); + }); + + T::REGS.cr().modify(|w| { + w.set_dmm(dual_quad); + }); + + T::REGS.tcr().modify(|w| { + w.set_sshift(match config.sample_shifting { + true => vals::SampleShift::HALFCYCLE, + false => vals::SampleShift::NONE, + }); + w.set_dhqc(config.delay_hold_quarter_cycle); + }); + + // Enable peripheral + T::REGS.cr().modify(|w| { + w.set_en(true); + }); + + // Free running clock needs to be set after peripheral enable + if config.free_running_clock { + T::REGS.dcr1().modify(|w| { + w.set_frck(config.free_running_clock); + }); + } + + Self { + _peri: peri, + sck, + d0, + d1, + d2, + d3, + d4, + d5, + d6, + d7, + nss, + dqs, + dma, + _phantom: PhantomData, + config, + width, + } + } + + // Function to configure the peripheral for the requested command + fn configure_command(&mut self, command: &TransferConfig, data_len: Option) -> Result<(), OspiError> { + // Check that transaction doesn't use more than hardware initialized pins + if >::into(command.iwidth) > >::into(self.width) + || >::into(command.adwidth) > >::into(self.width) + || >::into(command.abwidth) > >::into(self.width) + || >::into(command.dwidth) > >::into(self.width) + { + return Err(OspiError::InvalidCommand); + } + + T::REGS.cr().modify(|w| { + w.set_fmode(0.into()); + }); + + // Configure alternate bytes + if let Some(ab) = command.alternate_bytes { + T::REGS.abr().write(|v| v.set_alternate(ab)); + T::REGS.ccr().modify(|w| { + w.set_abmode(PhaseMode::from_bits(command.abwidth.into())); + w.set_abdtr(command.abdtr); + w.set_absize(SizeInBits::from_bits(command.absize.into())); + }) + } + + // Configure dummy cycles + T::REGS.tcr().modify(|w| { + w.set_dcyc(command.dummy.into()); + }); + + // Configure data + if let Some(data_length) = data_len { + T::REGS.dlr().write(|v| { + v.set_dl((data_length - 1) as u32); + }) + } else { + T::REGS.dlr().write(|v| { + v.set_dl((0) as u32); + }) + } + + // Configure instruction/address/data modes + T::REGS.ccr().modify(|w| { + w.set_imode(PhaseMode::from_bits(command.iwidth.into())); + w.set_idtr(command.idtr); + w.set_isize(SizeInBits::from_bits(command.isize.into())); + + w.set_admode(PhaseMode::from_bits(command.adwidth.into())); + w.set_addtr(command.idtr); + w.set_adsize(SizeInBits::from_bits(command.adsize.into())); + + w.set_dmode(PhaseMode::from_bits(command.dwidth.into())); + w.set_ddtr(command.ddtr); + }); + + // Set informationrequired to initiate transaction + if let Some(instruction) = command.instruction { + if let Some(address) = command.address { + T::REGS.ir().write(|v| { + v.set_instruction(instruction); + }); + + T::REGS.ar().write(|v| { + v.set_address(address); + }); + } else { + // Double check requirements for delay hold and sample shifting + // if let None = command.data_len { + // if self.config.delay_hold_quarter_cycle && command.idtr { + // T::REGS.ccr().modify(|w| { + // w.set_ddtr(true); + // }); + // } + // } + + T::REGS.ir().write(|v| { + v.set_instruction(instruction); + }); + } + } else { + if let Some(address) = command.address { + T::REGS.ar().write(|v| { + v.set_address(address); + }); + } else { + // The only single phase transaction supported is instruction only + return Err(OspiError::InvalidCommand); + } + } + + Ok(()) + } + + /// Function used to control or configure the target device without data transfer + pub fn blocking_command(&mut self, command: &TransferConfig) -> Result<(), OspiError> { + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + // Need additional validation that command configuration doesn't have data set + self.configure_command(command, None)?; + + // Transaction initiated by setting final configuration, i.e the instruction register + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|w| { + w.set_ctcf(true); + }); + + Ok(()) + } + + /// Blocking read with byte by byte data transfer + pub fn blocking_read(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), OspiError> { + if buf.is_empty() { + return Err(OspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + // Ensure DMA is not enabled for this transaction + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + self.configure_command(&transaction, Some(buf.len()))?; + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS.cr().modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTREAD)); + if T::REGS.ccr().read().admode() == vals::PhaseMode::NONE { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + for idx in 0..buf.len() { + while !T::REGS.sr().read().tcf() && !T::REGS.sr().read().ftf() {} + buf[idx] = unsafe { (T::REGS.dr().as_ptr() as *mut W).read_volatile() }; + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|v| v.set_ctcf(true)); + + Ok(()) + } + + /// Blocking write with byte by byte data transfer + pub fn blocking_write(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), OspiError> { + if buf.is_empty() { + return Err(OspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + self.configure_command(&transaction, Some(buf.len()))?; + + T::REGS + .cr() + .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTWRITE)); + + for idx in 0..buf.len() { + while !T::REGS.sr().read().ftf() {} + unsafe { (T::REGS.dr().as_ptr() as *mut W).write_volatile(buf[idx]) }; + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|v| v.set_ctcf(true)); + + Ok(()) + } + + /// Set new bus configuration + pub fn set_config(&mut self, config: &Config) { + // Wait for busy flag to clear + while T::REGS.sr().read().busy() {} + + // Disable DMA channel while configuring the peripheral + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + // Device configuration + T::REGS.dcr1().modify(|w| { + w.set_devsize(config.device_size.into()); + w.set_mtyp(vals::MemType::from_bits(config.memory_type.into())); + w.set_csht(config.chip_select_high_time.into()); + w.set_dlybyp(config.delay_block_bypass); + w.set_frck(false); + w.set_ckmode(config.clock_mode); + }); + + T::REGS.dcr2().modify(|w| { + w.set_wrapsize(config.wrap_size.into()); + }); + + T::REGS.dcr3().modify(|w| { + w.set_csbound(config.chip_select_boundary); + #[cfg(octospi_v1)] + { + w.set_maxtran(config.max_transfer); + } + }); + + T::REGS.dcr4().modify(|w| { + w.set_refresh(config.refresh); + }); + + T::REGS.cr().modify(|w| { + w.set_fthres(vals::Threshold(config.fifo_threshold.into())); + }); + + // Wait for busy flag to clear + while T::REGS.sr().read().busy() {} + + T::REGS.dcr2().modify(|w| { + w.set_prescaler(config.clock_prescaler); + }); + + T::REGS.tcr().modify(|w| { + w.set_sshift(match config.sample_shifting { + true => vals::SampleShift::HALFCYCLE, + false => vals::SampleShift::NONE, + }); + w.set_dhqc(config.delay_hold_quarter_cycle); + }); + + // Enable peripheral + T::REGS.cr().modify(|w| { + w.set_en(true); + }); + + // Free running clock needs to be set after peripheral enable + if config.free_running_clock { + T::REGS.dcr1().modify(|w| { + w.set_frck(config.free_running_clock); + }); + } + + self.config = *config; + } + + /// Get current configuration + pub fn get_config(&self) -> Config { + self.config + } +} + +impl<'d, T: Instance> Ospi<'d, T, Blocking> { + /// Create new blocking OSPI driver for a single spi external chip + pub fn new_blocking_singlespi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::input(Pull::None)), + None, + None, + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + config, + OspiWidth::SING, + false, + ) + } + + /// Create new blocking OSPI driver for a dualspi external chip + pub fn new_blocking_dualspi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + config, + OspiWidth::DUAL, + false, + ) + } + + /// Create new blocking OSPI driver for a quadspi external chip + pub fn new_blocking_quadspi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + config, + OspiWidth::QUAD, + false, + ) + } + + /// Create new blocking OSPI driver for two quadspi external chips + pub fn new_blocking_dualquadspi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + config, + OspiWidth::QUAD, + true, + ) + } + + /// Create new blocking OSPI driver for octospi external chips + pub fn new_blocking_octospi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + config, + OspiWidth::OCTO, + false, + ) + } +} + +impl<'d, T: Instance> Ospi<'d, T, Async> { + /// Create new blocking OSPI driver for a single spi external chip + pub fn new_singlespi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::input(Pull::None)), + None, + None, + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + new_dma!(dma), + config, + OspiWidth::SING, + false, + ) + } + + /// Create new blocking OSPI driver for a dualspi external chip + pub fn new_dualspi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + new_dma!(dma), + config, + OspiWidth::DUAL, + false, + ) + } + + /// Create new blocking OSPI driver for a quadspi external chip + pub fn new_quadspi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + new_dma!(dma), + config, + OspiWidth::QUAD, + false, + ) + } + + /// Create new blocking OSPI driver for two quadspi external chips + pub fn new_dualquadspi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + new_dma!(dma), + config, + OspiWidth::QUAD, + true, + ) + } + + /// Create new blocking OSPI driver for octospi external chips + pub fn new_octospi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + new_dma!(dma), + config, + OspiWidth::OCTO, + false, + ) + } + + /// Blocking read with DMA transfer + pub fn blocking_read_dma(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), OspiError> { + if buf.is_empty() { + return Err(OspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS.cr().modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTREAD)); + if T::REGS.ccr().read().admode() == vals::PhaseMode::NONE { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.blocking_wait(); + + finish_dma(T::REGS); + + Ok(()) + } + + /// Blocking write with DMA transfer + pub fn blocking_write_dma(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), OspiError> { + if buf.is_empty() { + return Err(OspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + T::REGS + .cr() + .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTWRITE)); + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.blocking_wait(); + + finish_dma(T::REGS); + + Ok(()) + } + + /// Asynchronous read from external device + pub async fn read(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), OspiError> { + if buf.is_empty() { + return Err(OspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS.cr().modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTREAD)); + if T::REGS.ccr().read().admode() == vals::PhaseMode::NONE { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.await; + + finish_dma(T::REGS); + + Ok(()) + } + + /// Asynchronous write to external device + pub async fn write(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), OspiError> { + if buf.is_empty() { + return Err(OspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + T::REGS + .cr() + .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTWRITE)); + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.await; + + finish_dma(T::REGS); + + Ok(()) + } +} + +impl<'d, T: Instance, M: PeriMode> Drop for Ospi<'d, T, M> { + fn drop(&mut self) { + self.sck.as_ref().map(|x| x.set_as_disconnected()); + self.d0.as_ref().map(|x| x.set_as_disconnected()); + self.d1.as_ref().map(|x| x.set_as_disconnected()); + self.d2.as_ref().map(|x| x.set_as_disconnected()); + self.d3.as_ref().map(|x| x.set_as_disconnected()); + self.d4.as_ref().map(|x| x.set_as_disconnected()); + self.d5.as_ref().map(|x| x.set_as_disconnected()); + self.d6.as_ref().map(|x| x.set_as_disconnected()); + self.d7.as_ref().map(|x| x.set_as_disconnected()); + self.nss.as_ref().map(|x| x.set_as_disconnected()); + self.dqs.as_ref().map(|x| x.set_as_disconnected()); + + rcc::disable::(); + } +} + +fn finish_dma(regs: Regs) { + while !regs.sr().read().tcf() {} + regs.fcr().write(|v| v.set_ctcf(true)); + + regs.cr().modify(|w| { + w.set_dmaen(false); + }); +} + +#[cfg(octospim_v1)] +/// OctoSPI I/O manager instance trait. +pub(crate) trait SealedOctospimInstance { + const OCTOSPIM_REGS: Octospim; + const OCTOSPI_IDX: u8; +} + +/// OctoSPI instance trait. +pub(crate) trait SealedInstance { + const REGS: Regs; +} + +/// OSPI instance trait. +#[cfg(octospim_v1)] +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral + SealedOctospimInstance {} + +/// OSPI instance trait. +#[cfg(not(octospim_v1))] +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral {} + +pin_trait!(SckPin, Instance); +pin_trait!(NckPin, Instance); +pin_trait!(D0Pin, Instance); +pin_trait!(D1Pin, Instance); +pin_trait!(D2Pin, Instance); +pin_trait!(D3Pin, Instance); +pin_trait!(D4Pin, Instance); +pin_trait!(D5Pin, Instance); +pin_trait!(D6Pin, Instance); +pin_trait!(D7Pin, Instance); +pin_trait!(DQSPin, Instance); +pin_trait!(NSSPin, Instance); +dma_trait!(OctoDma, Instance); + +// Hard-coded the octospi index, for OCTOSPIM +#[cfg(octospim_v1)] +impl SealedOctospimInstance for peripherals::OCTOSPI1 { + const OCTOSPIM_REGS: Octospim = crate::pac::OCTOSPIM; + const OCTOSPI_IDX: u8 = 1; +} + +#[cfg(all(octospim_v1, peri_octospi2))] +impl SealedOctospimInstance for peripherals::OCTOSPI2 { + const OCTOSPIM_REGS: Octospim = crate::pac::OCTOSPIM; + const OCTOSPI_IDX: u8 = 2; +} + +#[cfg(octospim_v1)] +foreach_peripheral!( + (octospi, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); + +#[cfg(not(octospim_v1))] +foreach_peripheral!( + (octospi, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); + +impl<'d, T: Instance, M: PeriMode> SetConfig for Ospi<'d, T, M> { + type Config = Config; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { + self.set_config(config); + Ok(()) + } +} + +impl<'d, T: Instance, M: PeriMode> GetConfig for Ospi<'d, T, M> { + type Config = Config; + fn get_config(&self) -> Self::Config { + self.get_config() + } +} + +/// Word sizes usable for OSPI. +#[allow(private_bounds)] +pub trait Word: word::Word {} + +macro_rules! impl_word { + ($T:ty) => { + impl Word for $T {} + }; +} + +impl_word!(u8); +impl_word!(u16); +impl_word!(u32); diff --git a/embassy/embassy-stm32/src/qspi/enums.rs b/embassy/embassy-stm32/src/qspi/enums.rs new file mode 100644 index 0000000..ecade9b --- /dev/null +++ b/embassy/embassy-stm32/src/qspi/enums.rs @@ -0,0 +1,333 @@ +//! Enums used in QSPI configuration. + +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub(crate) enum QspiMode { + IndirectWrite, + IndirectRead, + AutoPolling, + MemoryMapped, +} + +impl Into for QspiMode { + fn into(self) -> u8 { + match self { + QspiMode::IndirectWrite => 0b00, + QspiMode::IndirectRead => 0b01, + QspiMode::AutoPolling => 0b10, + QspiMode::MemoryMapped => 0b11, + } + } +} + +/// QSPI lane width +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub enum QspiWidth { + /// None + NONE, + /// Single lane + SING, + /// Dual lanes + DUAL, + /// Quad lanes + QUAD, +} + +impl Into for QspiWidth { + fn into(self) -> u8 { + match self { + QspiWidth::NONE => 0b00, + QspiWidth::SING => 0b01, + QspiWidth::DUAL => 0b10, + QspiWidth::QUAD => 0b11, + } + } +} + +/// Flash bank selection +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub enum FlashSelection { + /// Bank 1 + Flash1, + /// Bank 2 + Flash2, +} + +impl Into for FlashSelection { + fn into(self) -> bool { + match self { + FlashSelection::Flash1 => false, + FlashSelection::Flash2 => true, + } + } +} + +/// QSPI memory size. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum MemorySize { + _1KiB, + _2KiB, + _4KiB, + _8KiB, + _16KiB, + _32KiB, + _64KiB, + _128KiB, + _256KiB, + _512KiB, + _1MiB, + _2MiB, + _4MiB, + _8MiB, + _16MiB, + _32MiB, + _64MiB, + _128MiB, + _256MiB, + _512MiB, + _1GiB, + _2GiB, + _4GiB, + Other(u8), +} + +impl Into for MemorySize { + fn into(self) -> u8 { + match self { + MemorySize::_1KiB => 9, + MemorySize::_2KiB => 10, + MemorySize::_4KiB => 11, + MemorySize::_8KiB => 12, + MemorySize::_16KiB => 13, + MemorySize::_32KiB => 14, + MemorySize::_64KiB => 15, + MemorySize::_128KiB => 16, + MemorySize::_256KiB => 17, + MemorySize::_512KiB => 18, + MemorySize::_1MiB => 19, + MemorySize::_2MiB => 20, + MemorySize::_4MiB => 21, + MemorySize::_8MiB => 22, + MemorySize::_16MiB => 23, + MemorySize::_32MiB => 24, + MemorySize::_64MiB => 25, + MemorySize::_128MiB => 26, + MemorySize::_256MiB => 27, + MemorySize::_512MiB => 28, + MemorySize::_1GiB => 29, + MemorySize::_2GiB => 30, + MemorySize::_4GiB => 31, + MemorySize::Other(val) => val, + } + } +} + +/// QSPI Address size +#[derive(Copy, Clone)] +pub enum AddressSize { + /// 8-bit address + _8Bit, + /// 16-bit address + _16Bit, + /// 24-bit address + _24bit, + /// 32-bit address + _32bit, +} + +impl Into for AddressSize { + fn into(self) -> u8 { + match self { + AddressSize::_8Bit => 0b00, + AddressSize::_16Bit => 0b01, + AddressSize::_24bit => 0b10, + AddressSize::_32bit => 0b11, + } + } +} + +/// Time the Chip Select line stays high. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum ChipSelectHighTime { + _1Cycle, + _2Cycle, + _3Cycle, + _4Cycle, + _5Cycle, + _6Cycle, + _7Cycle, + _8Cycle, +} + +impl Into for ChipSelectHighTime { + fn into(self) -> u8 { + match self { + ChipSelectHighTime::_1Cycle => 0, + ChipSelectHighTime::_2Cycle => 1, + ChipSelectHighTime::_3Cycle => 2, + ChipSelectHighTime::_4Cycle => 3, + ChipSelectHighTime::_5Cycle => 4, + ChipSelectHighTime::_6Cycle => 5, + ChipSelectHighTime::_7Cycle => 6, + ChipSelectHighTime::_8Cycle => 7, + } + } +} + +/// FIFO threshold. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum FIFOThresholdLevel { + _1Bytes, + _2Bytes, + _3Bytes, + _4Bytes, + _5Bytes, + _6Bytes, + _7Bytes, + _8Bytes, + _9Bytes, + _10Bytes, + _11Bytes, + _12Bytes, + _13Bytes, + _14Bytes, + _15Bytes, + _16Bytes, + _17Bytes, + _18Bytes, + _19Bytes, + _20Bytes, + _21Bytes, + _22Bytes, + _23Bytes, + _24Bytes, + _25Bytes, + _26Bytes, + _27Bytes, + _28Bytes, + _29Bytes, + _30Bytes, + _31Bytes, + _32Bytes, +} + +impl Into for FIFOThresholdLevel { + fn into(self) -> u8 { + match self { + FIFOThresholdLevel::_1Bytes => 0, + FIFOThresholdLevel::_2Bytes => 1, + FIFOThresholdLevel::_3Bytes => 2, + FIFOThresholdLevel::_4Bytes => 3, + FIFOThresholdLevel::_5Bytes => 4, + FIFOThresholdLevel::_6Bytes => 5, + FIFOThresholdLevel::_7Bytes => 6, + FIFOThresholdLevel::_8Bytes => 7, + FIFOThresholdLevel::_9Bytes => 8, + FIFOThresholdLevel::_10Bytes => 9, + FIFOThresholdLevel::_11Bytes => 10, + FIFOThresholdLevel::_12Bytes => 11, + FIFOThresholdLevel::_13Bytes => 12, + FIFOThresholdLevel::_14Bytes => 13, + FIFOThresholdLevel::_15Bytes => 14, + FIFOThresholdLevel::_16Bytes => 15, + FIFOThresholdLevel::_17Bytes => 16, + FIFOThresholdLevel::_18Bytes => 17, + FIFOThresholdLevel::_19Bytes => 18, + FIFOThresholdLevel::_20Bytes => 19, + FIFOThresholdLevel::_21Bytes => 20, + FIFOThresholdLevel::_22Bytes => 21, + FIFOThresholdLevel::_23Bytes => 22, + FIFOThresholdLevel::_24Bytes => 23, + FIFOThresholdLevel::_25Bytes => 24, + FIFOThresholdLevel::_26Bytes => 25, + FIFOThresholdLevel::_27Bytes => 26, + FIFOThresholdLevel::_28Bytes => 27, + FIFOThresholdLevel::_29Bytes => 28, + FIFOThresholdLevel::_30Bytes => 29, + FIFOThresholdLevel::_31Bytes => 30, + FIFOThresholdLevel::_32Bytes => 31, + } + } +} + +/// Dummy cycle count +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum DummyCycles { + _0, + _1, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + _11, + _12, + _13, + _14, + _15, + _16, + _17, + _18, + _19, + _20, + _21, + _22, + _23, + _24, + _25, + _26, + _27, + _28, + _29, + _30, + _31, +} + +impl Into for DummyCycles { + fn into(self) -> u8 { + match self { + DummyCycles::_0 => 0, + DummyCycles::_1 => 1, + DummyCycles::_2 => 2, + DummyCycles::_3 => 3, + DummyCycles::_4 => 4, + DummyCycles::_5 => 5, + DummyCycles::_6 => 6, + DummyCycles::_7 => 7, + DummyCycles::_8 => 8, + DummyCycles::_9 => 9, + DummyCycles::_10 => 10, + DummyCycles::_11 => 11, + DummyCycles::_12 => 12, + DummyCycles::_13 => 13, + DummyCycles::_14 => 14, + DummyCycles::_15 => 15, + DummyCycles::_16 => 16, + DummyCycles::_17 => 17, + DummyCycles::_18 => 18, + DummyCycles::_19 => 19, + DummyCycles::_20 => 20, + DummyCycles::_21 => 21, + DummyCycles::_22 => 22, + DummyCycles::_23 => 23, + DummyCycles::_24 => 24, + DummyCycles::_25 => 25, + DummyCycles::_26 => 26, + DummyCycles::_27 => 27, + DummyCycles::_28 => 28, + DummyCycles::_29 => 29, + DummyCycles::_30 => 30, + DummyCycles::_31 => 31, + } + } +} diff --git a/embassy/embassy-stm32/src/qspi/mod.rs b/embassy/embassy-stm32/src/qspi/mod.rs new file mode 100644 index 0000000..0c65d05 --- /dev/null +++ b/embassy/embassy-stm32/src/qspi/mod.rs @@ -0,0 +1,473 @@ +//! Quad Serial Peripheral Interface (QSPI) + +#![macro_use] + +pub mod enums; + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use enums::*; + +use crate::dma::ChannelAndRequest; +use crate::gpio::{AfType, AnyPin, OutputType, Pull, Speed}; +use crate::mode::{Async, Blocking, Mode as PeriMode}; +use crate::pac::quadspi::Quadspi as Regs; +use crate::rcc::{self, RccPeripheral}; +use crate::{peripherals, Peripheral}; + +/// QSPI transfer configuration. +pub struct TransferConfig { + /// Instruction width (IMODE) + pub iwidth: QspiWidth, + /// Address width (ADMODE) + pub awidth: QspiWidth, + /// Data width (DMODE) + pub dwidth: QspiWidth, + /// Instruction Id + pub instruction: u8, + /// Flash memory address + pub address: Option, + /// Number of dummy cycles (DCYC) + pub dummy: DummyCycles, +} + +impl Default for TransferConfig { + fn default() -> Self { + Self { + iwidth: QspiWidth::NONE, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::NONE, + instruction: 0, + address: None, + dummy: DummyCycles::_0, + } + } +} + +/// QSPI driver configuration. +pub struct Config { + /// Flash memory size representend as 2^[0-32], as reasonable minimum 1KiB(9) was chosen. + /// If you need other value the whose predefined use `Other` variant. + pub memory_size: MemorySize, + /// Address size (8/16/24/32-bit) + pub address_size: AddressSize, + /// Scalar factor for generating CLK [0-255] + pub prescaler: u8, + /// Number of bytes to trigger FIFO threshold flag. + pub fifo_threshold: FIFOThresholdLevel, + /// Minimum number of cycles that chip select must be high between issued commands + pub cs_high_time: ChipSelectHighTime, +} + +impl Default for Config { + fn default() -> Self { + Self { + memory_size: MemorySize::Other(0), + address_size: AddressSize::_24bit, + prescaler: 128, + fifo_threshold: FIFOThresholdLevel::_17Bytes, + cs_high_time: ChipSelectHighTime::_5Cycle, + } + } +} + +/// QSPI driver. +#[allow(dead_code)] +pub struct Qspi<'d, T: Instance, M: PeriMode> { + _peri: PeripheralRef<'d, T>, + sck: Option>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + nss: Option>, + dma: Option>, + _phantom: PhantomData, + config: Config, +} + +impl<'d, T: Instance, M: PeriMode> Qspi<'d, T, M> { + fn new_inner( + peri: impl Peripheral

+ 'd, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + sck: Option>, + nss: Option>, + dma: Option>, + config: Config, + fsel: FlashSelection, + ) -> Self { + into_ref!(peri); + + rcc::enable_and_reset::(); + + while T::REGS.sr().read().busy() {} + + #[cfg(stm32h7)] + { + use stm32_metapac::quadspi::regs::Cr; + // Apply precautionary steps according to the errata... + T::REGS.cr().write_value(Cr(0)); + while T::REGS.sr().read().busy() {} + T::REGS.cr().write_value(Cr(0xFF000001)); + T::REGS.ccr().write(|w| w.set_frcm(true)); + T::REGS.ccr().write(|w| w.set_frcm(true)); + T::REGS.cr().write_value(Cr(0)); + while T::REGS.sr().read().busy() {} + } + + T::REGS.cr().modify(|w| { + w.set_en(true); + //w.set_tcen(false); + w.set_sshift(false); + w.set_fthres(config.fifo_threshold.into()); + w.set_prescaler(config.prescaler); + w.set_fsel(fsel.into()); + }); + T::REGS.dcr().modify(|w| { + w.set_fsize(config.memory_size.into()); + w.set_csht(config.cs_high_time.into()); + w.set_ckmode(true); + }); + + Self { + _peri: peri, + sck, + d0, + d1, + d2, + d3, + nss, + dma, + _phantom: PhantomData, + config, + } + } + + /// Do a QSPI command. + pub fn blocking_command(&mut self, transaction: TransferConfig) { + #[cfg(not(stm32h7))] + T::REGS.cr().modify(|v| v.set_dmaen(false)); + self.setup_transaction(QspiMode::IndirectWrite, &transaction, None); + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().modify(|v| v.set_ctcf(true)); + } + + /// Blocking read data. + pub fn blocking_read(&mut self, buf: &mut [u8], transaction: TransferConfig) { + #[cfg(not(stm32h7))] + T::REGS.cr().modify(|v| v.set_dmaen(false)); + self.setup_transaction(QspiMode::IndirectWrite, &transaction, Some(buf.len())); + + let current_ar = T::REGS.ar().read().address(); + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::IndirectRead.into()); + }); + T::REGS.ar().write(|v| { + v.set_address(current_ar); + }); + + for b in buf { + while !T::REGS.sr().read().tcf() && !T::REGS.sr().read().ftf() {} + *b = unsafe { (T::REGS.dr().as_ptr() as *mut u8).read_volatile() }; + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().modify(|v| v.set_ctcf(true)); + } + + /// Blocking write data. + pub fn blocking_write(&mut self, buf: &[u8], transaction: TransferConfig) { + // STM32H7 does not have dmaen + #[cfg(not(stm32h7))] + T::REGS.cr().modify(|v| v.set_dmaen(false)); + + self.setup_transaction(QspiMode::IndirectWrite, &transaction, Some(buf.len())); + + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::IndirectWrite.into()); + }); + + for &b in buf { + while !T::REGS.sr().read().ftf() {} + unsafe { (T::REGS.dr().as_ptr() as *mut u8).write_volatile(b) }; + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().modify(|v| v.set_ctcf(true)); + } + + fn setup_transaction(&mut self, fmode: QspiMode, transaction: &TransferConfig, data_len: Option) { + match (transaction.address, transaction.awidth) { + (Some(_), QspiWidth::NONE) => panic!("QSPI address can't be sent with an address width of NONE"), + (Some(_), _) => {} + (None, QspiWidth::NONE) => {} + (None, _) => panic!("QSPI address is not set, so the address width should be NONE"), + } + + match (data_len, transaction.dwidth) { + (Some(0), _) => panic!("QSPI data must be at least one byte"), + (Some(_), QspiWidth::NONE) => panic!("QSPI data can't be sent with a data width of NONE"), + (Some(_), _) => {} + (None, QspiWidth::NONE) => {} + (None, _) => panic!("QSPI data is empty, so the data width should be NONE"), + } + + T::REGS.fcr().modify(|v| { + v.set_csmf(true); + v.set_ctcf(true); + v.set_ctef(true); + v.set_ctof(true); + }); + + while T::REGS.sr().read().busy() {} + + if let Some(len) = data_len { + T::REGS.dlr().write(|v| v.set_dl(len as u32 - 1)); + } + + T::REGS.ccr().write(|v| { + v.set_fmode(fmode.into()); + v.set_imode(transaction.iwidth.into()); + v.set_instruction(transaction.instruction); + v.set_admode(transaction.awidth.into()); + v.set_adsize(self.config.address_size.into()); + v.set_dmode(transaction.dwidth.into()); + v.set_abmode(QspiWidth::NONE.into()); + v.set_dcyc(transaction.dummy.into()); + }); + + if let Some(addr) = transaction.address { + T::REGS.ar().write(|v| { + v.set_address(addr); + }); + } + } +} + +impl<'d, T: Instance> Qspi<'d, T, Blocking> { + /// Create a new QSPI driver for bank 1, in blocking mode. + pub fn new_blocking_bank1( + peri: impl Peripheral

+ 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + sck: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + config, + FlashSelection::Flash1, + ) + } + + /// Create a new QSPI driver for bank 2, in blocking mode. + pub fn new_blocking_bank2( + peri: impl Peripheral

+ 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + sck: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + config, + FlashSelection::Flash2, + ) + } +} + +impl<'d, T: Instance> Qspi<'d, T, Async> { + /// Create a new QSPI driver for bank 1. + pub fn new_bank1( + peri: impl Peripheral

+ 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + sck: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + new_dma!(dma), + config, + FlashSelection::Flash1, + ) + } + + /// Create a new QSPI driver for bank 2. + pub fn new_bank2( + peri: impl Peripheral

+ 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + sck: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + new_dma!(dma), + config, + FlashSelection::Flash2, + ) + } + + /// Blocking read data, using DMA. + pub fn blocking_read_dma(&mut self, buf: &mut [u8], transaction: TransferConfig) { + let transfer = self.start_read_transfer(transaction, buf); + transfer.blocking_wait(); + } + + /// Async read data, using DMA. + pub async fn read_dma(&mut self, buf: &mut [u8], transaction: TransferConfig) { + let transfer = self.start_read_transfer(transaction, buf); + transfer.await; + } + + fn start_read_transfer<'a>( + &'a mut self, + transaction: TransferConfig, + buf: &'a mut [u8], + ) -> crate::dma::Transfer<'a> { + self.setup_transaction(QspiMode::IndirectWrite, &transaction, Some(buf.len())); + + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::IndirectRead.into()); + }); + let current_ar = T::REGS.ar().read().address(); + T::REGS.ar().write(|v| { + v.set_address(current_ar); + }); + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut u8, buf, Default::default()) + }; + + // STM32H7 does not have dmaen + #[cfg(not(stm32h7))] + T::REGS.cr().modify(|v| v.set_dmaen(true)); + transfer + } + + /// Blocking write data, using DMA. + pub fn blocking_write_dma(&mut self, buf: &[u8], transaction: TransferConfig) { + let transfer = self.start_write_transfer(transaction, buf); + transfer.blocking_wait(); + } + + /// Async write data, using DMA. + pub async fn write_dma(&mut self, buf: &[u8], transaction: TransferConfig) { + let transfer = self.start_write_transfer(transaction, buf); + transfer.await; + } + + fn start_write_transfer<'a>(&'a mut self, transaction: TransferConfig, buf: &'a [u8]) -> crate::dma::Transfer<'a> { + self.setup_transaction(QspiMode::IndirectWrite, &transaction, Some(buf.len())); + + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::IndirectWrite.into()); + }); + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(buf, T::REGS.dr().as_ptr() as *mut u8, Default::default()) + }; + + // STM32H7 does not have dmaen + #[cfg(not(stm32h7))] + T::REGS.cr().modify(|v| v.set_dmaen(true)); + transfer + } +} + +trait SealedInstance { + const REGS: Regs; +} + +/// QSPI instance trait. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral {} + +pin_trait!(SckPin, Instance); +pin_trait!(BK1D0Pin, Instance); +pin_trait!(BK1D1Pin, Instance); +pin_trait!(BK1D2Pin, Instance); +pin_trait!(BK1D3Pin, Instance); +pin_trait!(BK1NSSPin, Instance); + +pin_trait!(BK2D0Pin, Instance); +pin_trait!(BK2D1Pin, Instance); +pin_trait!(BK2D2Pin, Instance); +pin_trait!(BK2D3Pin, Instance); +pin_trait!(BK2NSSPin, Instance); + +dma_trait!(QuadDma, Instance); + +foreach_peripheral!( + (quadspi, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); diff --git a/embassy/embassy-stm32/src/rcc/bd.rs b/embassy/embassy-stm32/src/rcc/bd.rs new file mode 100644 index 0000000..7913679 --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/bd.rs @@ -0,0 +1,286 @@ +use core::sync::atomic::{compiler_fence, Ordering}; + +use crate::pac::common::{Reg, RW}; +pub use crate::pac::rcc::vals::Rtcsel as RtcClockSource; +use crate::time::Hertz; + +#[cfg(any(stm32f0, stm32f1, stm32f3))] +pub const LSI_FREQ: Hertz = Hertz(40_000); +#[cfg(not(any(stm32f0, stm32f1, stm32f3)))] +pub const LSI_FREQ: Hertz = Hertz(32_000); + +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum LseMode { + Oscillator(LseDrive), + Bypass, +} + +#[derive(Clone, Copy)] +pub struct LseConfig { + pub frequency: Hertz, + pub mode: LseMode, + /// If peripherals other than RTC/TAMP or RCC functions need the lse this bit must be set + #[cfg(any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba))] + pub peripherals_clocked: bool, +} + +#[allow(dead_code)] +#[derive(Default, Clone, Copy)] +pub enum LseDrive { + #[cfg(not(stm32h5))] // ES0565: LSE Low drive mode is not functional + Low = 0, + MediumLow = 0x01, + #[default] + MediumHigh = 0x02, + High = 0x03, +} + +// All families but these have the LSEDRV register +#[cfg(not(any(rcc_f1, rcc_f1cl, rcc_f100, rcc_f2, rcc_f4, rcc_f410, rcc_l1)))] +impl From for crate::pac::rcc::vals::Lsedrv { + fn from(value: LseDrive) -> Self { + use crate::pac::rcc::vals::Lsedrv; + + match value { + #[cfg(not(stm32h5))] // ES0565: LSE Low drive mode is not functional + LseDrive::Low => Lsedrv::LOW, + LseDrive::MediumLow => Lsedrv::MEDIUMLOW, + LseDrive::MediumHigh => Lsedrv::MEDIUMHIGH, + LseDrive::High => Lsedrv::HIGH, + } + } +} + +#[cfg(not(any(rtc_v2l0, rtc_v2l1, stm32c0)))] +type Bdcr = crate::pac::rcc::regs::Bdcr; +#[cfg(any(rtc_v2l0, rtc_v2l1))] +type Bdcr = crate::pac::rcc::regs::Csr; +#[cfg(any(stm32c0))] +type Bdcr = crate::pac::rcc::regs::Csr1; + +#[cfg(any(stm32c0))] +fn unlock() {} + +#[cfg(not(any(stm32c0)))] +fn unlock() { + #[cfg(any(stm32f0, stm32f1, stm32f2, stm32f3, stm32l0, stm32l1))] + let cr = crate::pac::PWR.cr(); + #[cfg(not(any(stm32f0, stm32f1, stm32f2, stm32f3, stm32l0, stm32l1, stm32u5, stm32h5, stm32wba)))] + let cr = crate::pac::PWR.cr1(); + #[cfg(any(stm32u5, stm32h5, stm32wba))] + let cr = crate::pac::PWR.dbpcr(); + + cr.modify(|w| w.set_dbp(true)); + while !cr.read().dbp() {} +} + +fn bdcr() -> Reg { + #[cfg(any(rtc_v2l0, rtc_v2l1))] + return crate::pac::RCC.csr(); + #[cfg(not(any(rtc_v2l0, rtc_v2l1, stm32c0)))] + return crate::pac::RCC.bdcr(); + #[cfg(any(stm32c0))] + return crate::pac::RCC.csr1(); +} + +#[derive(Clone, Copy)] +pub struct LsConfig { + pub rtc: RtcClockSource, + pub lsi: bool, + pub lse: Option, +} + +impl LsConfig { + pub const fn default_lse() -> Self { + Self { + rtc: RtcClockSource::LSE, + lse: Some(LseConfig { + frequency: Hertz(32_768), + mode: LseMode::Oscillator(LseDrive::MediumHigh), + #[cfg(any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba))] + peripherals_clocked: false, + }), + lsi: false, + } + } + + pub const fn default_lsi() -> Self { + Self { + rtc: RtcClockSource::LSI, + lsi: true, + lse: None, + } + } + + pub const fn off() -> Self { + Self { + rtc: RtcClockSource::DISABLE, + lsi: false, + lse: None, + } + } +} + +impl Default for LsConfig { + fn default() -> Self { + // on L5, just the fact that LSI is enabled makes things crash. + // TODO: investigate. + + #[cfg(not(stm32l5))] + return Self::default_lsi(); + #[cfg(stm32l5)] + return Self::off(); + } +} + +impl LsConfig { + pub(crate) fn init(&self) -> Option { + let rtc_clk = match self.rtc { + RtcClockSource::LSI => { + assert!(self.lsi); + Some(LSI_FREQ) + } + RtcClockSource::LSE => Some(self.lse.as_ref().unwrap().frequency), + RtcClockSource::DISABLE => None, + _ => todo!(), + }; + + let (lse_en, lse_byp, lse_drv) = match &self.lse { + Some(c) => match c.mode { + LseMode::Oscillator(lse_drv) => (true, false, Some(lse_drv)), + LseMode::Bypass => (true, true, None), + }, + None => (false, false, None), + }; + #[cfg(any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba))] + let lse_sysen = if let Some(lse) = self.lse { + Some(lse.peripherals_clocked) + } else { + None + }; + #[cfg(rcc_u0)] + let lse_sysen = Some(lse_en); + + _ = lse_drv; // not all chips have it. + + // Disable backup domain write protection + unlock(); + + if self.lsi { + #[cfg(any(stm32u5, stm32h5, stm32wba))] + let csr = crate::pac::RCC.bdcr(); + #[cfg(not(any(stm32u5, stm32h5, stm32wba, stm32c0)))] + let csr = crate::pac::RCC.csr(); + #[cfg(any(stm32c0))] + let csr = crate::pac::RCC.csr2(); + + #[cfg(not(any(rcc_wb, rcc_wba)))] + csr.modify(|w| w.set_lsion(true)); + + #[cfg(any(rcc_wb, rcc_wba))] + csr.modify(|w| w.set_lsi1on(true)); + + #[cfg(not(any(rcc_wb, rcc_wba)))] + while !csr.read().lsirdy() {} + + #[cfg(any(rcc_wb, rcc_wba))] + while !csr.read().lsi1rdy() {} + } + + // backup domain configuration (LSEON, RTCEN, RTCSEL) is kept across resets. + // once set, changing it requires a backup domain reset. + // first check if the configuration matches what we want. + + // check if it's already enabled and in the source we want. + let reg = bdcr().read(); + let mut ok = true; + ok &= reg.rtcsel() == self.rtc; + #[cfg(not(rcc_wba))] + { + ok &= reg.rtcen() == (self.rtc != RtcClockSource::DISABLE); + } + ok &= reg.lseon() == lse_en; + ok &= reg.lsebyp() == lse_byp; + #[cfg(any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba, rcc_u0))] + if let Some(lse_sysen) = lse_sysen { + ok &= reg.lsesysen() == lse_sysen; + } + #[cfg(not(any(rcc_f1, rcc_f1cl, rcc_f100, rcc_f2, rcc_f4, rcc_f410, rcc_l1)))] + if let Some(lse_drv) = lse_drv { + ok &= reg.lsedrv() == lse_drv.into(); + } + + // if configuration is OK, we're done. + if ok { + trace!("BDCR ok: {:08x}", bdcr().read().0); + return rtc_clk; + } + + // If not OK, reset backup domain and configure it. + #[cfg(not(any(rcc_l0, rcc_l0_v2, rcc_l1, stm32h5, stm32h7rs, stm32c0)))] + { + bdcr().modify(|w| w.set_bdrst(true)); + bdcr().modify(|w| w.set_bdrst(false)); + } + // H5 has a terrible, terrible errata: 'SRAM2 is erased when the backup domain is reset' + // pending a more sane sane way to handle this, just don't reset BD for now. + // This means the RTCSEL write below will have no effect, only if it has already been written + // after last power-on. Since it's uncommon to dynamically change RTCSEL, this is better than + // letting half our RAM go magically *poof*. + // STM32H503CB/EB/KB/RB device errata - 2.2.8 SRAM2 unduly erased upon a backup domain reset + // STM32H562xx/563xx/573xx device errata - 2.2.14 SRAM2 is erased when the backup domain is reset + //#[cfg(any(stm32h5, stm32h7rs))] + #[cfg(any(stm32h7rs))] + { + bdcr().modify(|w| w.set_vswrst(true)); + bdcr().modify(|w| w.set_vswrst(false)); + } + #[cfg(any(stm32c0, stm32l0))] + { + bdcr().modify(|w| w.set_rtcrst(true)); + bdcr().modify(|w| w.set_rtcrst(false)); + } + + if lse_en { + bdcr().modify(|w| { + #[cfg(not(any(rcc_f1, rcc_f1cl, rcc_f100, rcc_f2, rcc_f4, rcc_f410, rcc_l1)))] + if let Some(lse_drv) = lse_drv { + w.set_lsedrv(lse_drv.into()); + } + w.set_lsebyp(lse_byp); + w.set_lseon(true); + }); + + while !bdcr().read().lserdy() {} + + #[cfg(any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba, rcc_u0))] + if let Some(lse_sysen) = lse_sysen { + bdcr().modify(|w| { + w.set_lsesysen(lse_sysen); + }); + + if lse_sysen { + while !bdcr().read().lsesysrdy() {} + } + } + } + + if self.rtc != RtcClockSource::DISABLE { + bdcr().modify(|w| { + #[cfg(any(rtc_v2h7, rtc_v2l4, rtc_v2wb, rtc_v3, rtc_v3u5))] + assert!(!w.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + #[cfg(not(rcc_wba))] + w.set_rtcen(true); + w.set_rtcsel(self.rtc); + }); + } + + trace!("BDCR configured: {:08x}", bdcr().read().0); + + compiler_fence(Ordering::SeqCst); + + rtc_clk + } +} diff --git a/embassy/embassy-stm32/src/rcc/c0.rs b/embassy/embassy-stm32/src/rcc/c0.rs new file mode 100644 index 0000000..977b2e7 --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/c0.rs @@ -0,0 +1,195 @@ +use crate::pac::flash::vals::Latency; +pub use crate::pac::rcc::vals::{ + Hpre as AHBPrescaler, Hsidiv as HsiSysDiv, Hsikerdiv as HsiKerDiv, Ppre as APBPrescaler, Sw as Sysclk, +}; +use crate::pac::{FLASH, RCC}; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(48_000_000); + +/// HSE Mode +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1) + Bypass, +} + +/// HSE Configuration +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, +} + +/// HSI Configuration +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hsi { + /// Division factor for HSISYS clock. Default is 4. + pub sys_div: HsiSysDiv, + /// Division factor for HSIKER clock. Default is 3. + pub ker_div: HsiKerDiv, +} + +/// Clocks configutation +#[non_exhaustive] +#[derive(Clone, Copy)] +pub struct Config { + /// HSI Configuration + pub hsi: Option, + + /// HSE Configuration + pub hse: Option, + + /// System Clock Configuration + pub sys: Sysclk, + + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + + /// Low-Speed Clock Configuration + pub ls: super::LsConfig, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, +} + +impl Default for Config { + #[inline] + fn default() -> Config { + Config { + hsi: Some(Hsi { + sys_div: HsiSysDiv::DIV4, + ker_div: HsiKerDiv::DIV3, + }), + hse: None, + sys: Sysclk::HSISYS, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + ls: Default::default(), + mux: Default::default(), + } + } +} + +pub(crate) unsafe fn init(config: Config) { + // Turn on the HSI + match config.hsi { + None => RCC.cr().modify(|w| w.set_hsion(true)), + Some(hsi) => RCC.cr().modify(|w| { + w.set_hsidiv(hsi.sys_div); + w.set_hsikerdiv(hsi.ker_div); + w.set_hsion(true); + }), + } + while !RCC.cr().read().hsirdy() {} + + // Use the HSI clock as system clock during the actual clock setup + RCC.cfgr().modify(|w| w.set_sw(Sysclk::HSISYS)); + while RCC.cfgr().read().sws() != Sysclk::HSISYS {} + + // Configure HSI + let (hsi, hsisys, hsiker) = match config.hsi { + None => (None, None, None), + Some(hsi) => ( + Some(HSI_FREQ), + Some(HSI_FREQ / hsi.sys_div), + Some(HSI_FREQ / hsi.ker_div), + ), + }; + + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + match hse.mode { + HseMode::Bypass => rcc_assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => rcc_assert!(max::HSE_OSC.contains(&hse.freq)), + } + + RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + let sys = match config.sys { + Sysclk::HSISYS => unwrap!(hsisys), + Sysclk::HSE => unwrap!(hse), + _ => unreachable!(), + }; + + rcc_assert!(max::SYSCLK.contains(&sys)); + + // Calculate the AHB frequency (HCLK), among other things so we can calculate the correct flash read latency. + let hclk = sys / config.ahb_pre; + rcc_assert!(max::HCLK.contains(&hclk)); + + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); + rcc_assert!(max::PCLK.contains(&pclk1)); + + let latency = match hclk.0 { + ..=24_000_000 => Latency::WS0, + _ => Latency::WS1, + }; + + // Configure flash read access latency based on voltage scale and frequency + FLASH.acr().modify(|w| { + w.set_latency(latency); + }); + + // Spin until the effective flash latency is set. + while FLASH.acr().read().latency() != latency {} + + // Now that boost mode and flash read access latency are configured, set up SYSCLK + RCC.cfgr().modify(|w| { + w.set_sw(config.sys); + w.set_hpre(config.ahb_pre); + w.set_ppre(config.apb1_pre); + }); + while RCC.cfgr().read().sws() != config.sys {} + + // Disable HSI if not used + if config.hsi.is_none() { + RCC.cr().modify(|w| w.set_hsion(false)); + } + + let rtc = config.ls.init(); + + config.mux.init(); + + set_clocks!( + sys: Some(sys), + hclk1: Some(hclk), + pclk1: Some(pclk1), + pclk1_tim: Some(pclk1_tim), + hsi: hsi, + hsiker: hsiker, + hse: hse, + rtc: rtc, + + // TODO + lsi: None, + lse: None, + ); +} + +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + pub(crate) const HSE_OSC: RangeInclusive = Hertz(4_000_000)..=Hertz(48_000_000); + pub(crate) const HSE_BYP: RangeInclusive = Hertz(0)..=Hertz(48_000_000); + pub(crate) const SYSCLK: RangeInclusive = Hertz(0)..=Hertz(48_000_000); + pub(crate) const PCLK: RangeInclusive = Hertz(8)..=Hertz(48_000_000); + pub(crate) const HCLK: RangeInclusive = Hertz(0)..=Hertz(48_000_000); +} diff --git a/embassy/embassy-stm32/src/rcc/f013.rs b/embassy/embassy-stm32/src/rcc/f013.rs new file mode 100644 index 0000000..a38ca95 --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/f013.rs @@ -0,0 +1,478 @@ +use crate::pac::flash::vals::Latency; +#[cfg(stm32f1)] +pub use crate::pac::rcc::vals::Adcpre as ADCPrescaler; +#[cfg(stm32f3)] +pub use crate::pac::rcc::vals::Adcpres as AdcPllPrescaler; +use crate::pac::rcc::vals::Pllsrc; +#[cfg(stm32f1)] +pub use crate::pac::rcc::vals::Pllxtpre as PllPreDiv; +#[cfg(any(stm32f0, stm32f3))] +pub use crate::pac::rcc::vals::Prediv as PllPreDiv; +pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Pllmul as PllMul, Ppre as APBPrescaler, Sw as Sysclk}; +use crate::pac::{FLASH, RCC}; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(8_000_000); + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1) + Bypass, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum PllSource { + HSE, + HSI, + #[cfg(rcc_f0v4)] + HSI48, +} + +#[derive(Clone, Copy)] +pub struct Pll { + pub src: PllSource, + + /// PLL pre-divider. + /// + /// On some chips, this must be 2 if `src == HSI`. Init will panic if this is not the case. + pub prediv: PllPreDiv, + + /// PLL multiplication factor. + pub mul: PllMul, +} + +#[cfg(all(stm32f3, not(rcc_f37)))] +#[derive(Clone, Copy)] +pub enum AdcClockSource { + Pll(AdcPllPrescaler), + Hclk(AdcHclkPrescaler), +} + +#[cfg(all(stm32f3, not(rcc_f37)))] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum AdcHclkPrescaler { + Div1, + Div2, + Div4, +} + +#[cfg(stm32f334)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum HrtimClockSource { + BusClk, + PllClk, +} + +/// Clocks configutation +#[non_exhaustive] +#[derive(Clone, Copy)] +pub struct Config { + pub hsi: bool, + pub hse: Option, + #[cfg(crs)] + pub hsi48: Option, + pub sys: Sysclk, + + pub pll: Option, + + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + #[cfg(not(stm32f0))] + pub apb2_pre: APBPrescaler, + + #[cfg(stm32f1)] + pub adc_pre: ADCPrescaler, + + #[cfg(all(stm32f3, not(rcc_f37)))] + pub adc: AdcClockSource, + #[cfg(all(stm32f3, not(rcc_f37), any(peri_adc3_common, peri_adc34_common)))] + pub adc34: AdcClockSource, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, + + pub ls: super::LsConfig, +} + +impl Default for Config { + fn default() -> Self { + Self { + hsi: true, + hse: None, + #[cfg(crs)] + hsi48: Some(Default::default()), + sys: Sysclk::HSI, + pll: None, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + #[cfg(not(stm32f0))] + apb2_pre: APBPrescaler::DIV1, + ls: Default::default(), + + #[cfg(stm32f1)] + // ensure ADC is not out of range by default even if APB2 is maxxed out (36mhz) + adc_pre: ADCPrescaler::DIV6, + + #[cfg(all(stm32f3, not(rcc_f37)))] + adc: AdcClockSource::Hclk(AdcHclkPrescaler::Div1), + #[cfg(all(stm32f3, not(rcc_f37), any(peri_adc3_common, peri_adc34_common)))] + adc34: AdcClockSource::Hclk(AdcHclkPrescaler::Div1), + + mux: Default::default(), + } + } +} + +/// Initialize and Set the clock frequencies +pub(crate) unsafe fn init(config: Config) { + // Turn on the HSI + RCC.cr().modify(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + + // Use the HSI clock as system clock during the actual clock setup + RCC.cfgr().modify(|w| w.set_sw(Sysclk::HSI)); + while RCC.cfgr().read().sws() != Sysclk::HSI {} + + // Configure HSI + let hsi = match config.hsi { + false => None, + true => Some(HSI_FREQ), + }; + + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + match hse.mode { + HseMode::Bypass => rcc_assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => rcc_assert!(max::HSE_OSC.contains(&hse.freq)), + } + + RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + // configure HSI48 + #[cfg(crs)] + let hsi48 = config.hsi48.map(|config| super::init_hsi48(config)); + #[cfg(not(crs))] + let hsi48: Option = None; + + // Enable PLL + let pll = config.pll.map(|pll| { + let (src_val, src_freq) = match pll.src { + #[cfg(any(rcc_f0v3, rcc_f0v4, rcc_f3v3))] + PllSource::HSI => (Pllsrc::HSI_DIV_PREDIV, unwrap!(hsi)), + #[cfg(not(any(rcc_f0v3, rcc_f0v4, rcc_f3v3)))] + PllSource::HSI => { + if pll.prediv != PllPreDiv::DIV2 { + panic!("if PLL source is HSI, PLL prediv must be 2."); + } + (Pllsrc::HSI_DIV2, unwrap!(hsi)) + } + PllSource::HSE => (Pllsrc::HSE_DIV_PREDIV, unwrap!(hse)), + #[cfg(rcc_f0v4)] + PllSource::HSI48 => (Pllsrc::HSI48_DIV_PREDIV, unwrap!(hsi48)), + }; + let in_freq = src_freq / pll.prediv; + rcc_assert!(max::PLL_IN.contains(&in_freq)); + let out_freq = in_freq * pll.mul; + rcc_assert!(max::PLL_OUT.contains(&out_freq)); + + #[cfg(not(stm32f1))] + RCC.cfgr2().modify(|w| w.set_prediv(pll.prediv)); + RCC.cfgr().modify(|w| { + w.set_pllmul(pll.mul); + w.set_pllsrc(src_val); + #[cfg(stm32f1)] + w.set_pllxtpre(pll.prediv); + }); + RCC.cr().modify(|w| w.set_pllon(true)); + while !RCC.cr().read().pllrdy() {} + + out_freq + }); + + #[cfg(stm32f3)] + let pll_mul_2 = pll.map(|pll| pll * 2u32); + + #[cfg(any(rcc_f1, rcc_f1cl, stm32f3))] + let usb = match pll { + Some(Hertz(72_000_000)) => Some(crate::pac::rcc::vals::Usbpre::DIV1_5), + Some(Hertz(48_000_000)) => Some(crate::pac::rcc::vals::Usbpre::DIV1), + _ => None, + } + .map(|usbpre| { + RCC.cfgr().modify(|w| w.set_usbpre(usbpre)); + Hertz(48_000_000) + }); + + // Configure sysclk + let sys = match config.sys { + Sysclk::HSI => unwrap!(hsi), + Sysclk::HSE => unwrap!(hse), + Sysclk::PLL1_P => unwrap!(pll), + #[cfg(crs)] + Sysclk::HSI48 => unwrap!(hsi48), + #[cfg(not(crs))] + _ => unreachable!(), + }; + + let hclk = sys / config.ahb_pre; + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); + #[cfg(not(stm32f0))] + let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk, config.apb2_pre); + #[cfg(stm32f0)] + let (pclk2, pclk2_tim) = (pclk1, pclk1_tim); + + rcc_assert!(max::HCLK.contains(&hclk)); + rcc_assert!(max::PCLK1.contains(&pclk1)); + #[cfg(not(stm32f0))] + rcc_assert!(max::PCLK2.contains(&pclk2)); + + #[cfg(stm32f1)] + let adc = pclk2 / config.adc_pre; + #[cfg(stm32f1)] + rcc_assert!(max::ADC.contains(&adc)); + + // Set latency based on HCLK frquency + #[cfg(stm32f0)] + let latency = match hclk.0 { + ..=24_000_000 => Latency::WS0, + _ => Latency::WS1, + }; + #[cfg(any(stm32f1, stm32f3))] + let latency = match hclk.0 { + ..=24_000_000 => Latency::WS0, + ..=48_000_000 => Latency::WS1, + _ => Latency::WS2, + }; + FLASH.acr().modify(|w| { + w.set_latency(latency); + // RM0316: "The prefetch buffer must be kept on when using a prescaler + // different from 1 on the AHB clock.", "Half-cycle access cannot be + // used when there is a prescaler different from 1 on the AHB clock" + #[cfg(stm32f3)] + if config.ahb_pre != AHBPrescaler::DIV1 { + w.set_hlfcya(false); + w.set_prftbe(true); + } + #[cfg(not(stm32f3))] + w.set_prftbe(true); + }); + + // Set prescalers + // CFGR has been written before (PLL, PLL48) don't overwrite these settings + RCC.cfgr().modify(|w| { + #[cfg(not(stm32f0))] + { + w.set_ppre1(config.apb1_pre); + w.set_ppre2(config.apb2_pre); + } + #[cfg(stm32f0)] + w.set_ppre(config.apb1_pre); + w.set_hpre(config.ahb_pre); + #[cfg(stm32f1)] + w.set_adcpre(config.adc_pre); + }); + + // Wait for the new prescalers to kick in + // "The clocks are divided with the new prescaler factor from + // 1 to 16 AHB cycles after write" + cortex_m::asm::delay(16); + + // CFGR has been written before (PLL, PLL48, clock divider) don't overwrite these settings + RCC.cfgr().modify(|w| w.set_sw(config.sys)); + while RCC.cfgr().read().sws() != config.sys {} + + // Disable HSI if not used + if !config.hsi { + RCC.cr().modify(|w| w.set_hsion(false)); + } + + let rtc = config.ls.init(); + + // TODO: all this ADC stuff should probably go into the ADC module, not here. + // Most STM32s manage ADC clocks in a similar way with ADCx_COMMON. + #[cfg(all(stm32f3, not(rcc_f37)))] + use crate::pac::adccommon::vals::Ckmode; + + #[cfg(all(stm32f3, not(rcc_f37)))] + let adc = { + #[cfg(peri_adc1_common)] + let common = crate::pac::ADC1_COMMON; + #[cfg(peri_adc12_common)] + let common = crate::pac::ADC12_COMMON; + + match config.adc { + AdcClockSource::Pll(adcpres) => { + RCC.cfgr2().modify(|w| w.set_adc12pres(adcpres)); + common.ccr().modify(|w| w.set_ckmode(Ckmode::ASYNCHRONOUS)); + + unwrap!(pll) / adcpres + } + AdcClockSource::Hclk(adcpres) => { + assert!(!(adcpres == AdcHclkPrescaler::Div1 && config.ahb_pre != AHBPrescaler::DIV1)); + + let (div, ckmode) = match adcpres { + AdcHclkPrescaler::Div1 => (1u32, Ckmode::SYNCDIV1), + AdcHclkPrescaler::Div2 => (2u32, Ckmode::SYNCDIV2), + AdcHclkPrescaler::Div4 => (4u32, Ckmode::SYNCDIV4), + }; + common.ccr().modify(|w| w.set_ckmode(ckmode)); + + hclk / div + } + } + }; + + #[cfg(all(stm32f3, not(rcc_f37), any(peri_adc3_common, peri_adc34_common)))] + let adc34 = { + #[cfg(peri_adc3_common)] + let common = crate::pac::ADC3_COMMON; + #[cfg(peri_adc34_common)] + let common = crate::pac::ADC34_COMMON; + + match config.adc34 { + AdcClockSource::Pll(adcpres) => { + RCC.cfgr2().modify(|w| w.set_adc34pres(adcpres)); + common.ccr().modify(|w| w.set_ckmode(Ckmode::ASYNCHRONOUS)); + + unwrap!(pll) / adcpres + } + AdcClockSource::Hclk(adcpres) => { + assert!(!(adcpres == AdcHclkPrescaler::Div1 && config.ahb_pre != AHBPrescaler::DIV1)); + + let (div, ckmode) = match adcpres { + AdcHclkPrescaler::Div1 => (1u32, Ckmode::SYNCDIV1), + AdcHclkPrescaler::Div2 => (2u32, Ckmode::SYNCDIV2), + AdcHclkPrescaler::Div4 => (4u32, Ckmode::SYNCDIV4), + }; + common.ccr().modify(|w| w.set_ckmode(ckmode)); + + hclk / div + } + } + }; + + /* + TODO: Maybe add something like this to clock_mux? How can we autogenerate the data for this? + let hrtim = match config.hrtim { + // Must be configured after the bus is ready, otherwise it won't work + HrtimClockSource::BusClk => None, + HrtimClockSource::PllClk => { + use crate::pac::rcc::vals::Timsw; + + // Make sure that we're using the PLL + let pll = unwrap!(pll); + assert!((pclk2 == pll) || (pclk2 * 2u32 == pll)); + + RCC.cfgr3().modify(|w| w.set_hrtim1sw(Timsw::PLL1_P)); + + Some(pll * 2u32) + } + }; + */ + + config.mux.init(); + + set_clocks!( + hsi: hsi, + hse: hse, + pll1_p: pll, + #[cfg(stm32f3)] + pll1_p_mul_2: pll_mul_2, + hsi_div_244: hsi.map(|h| h / 244u32), + sys: Some(sys), + pclk1: Some(pclk1), + pclk2: Some(pclk2), + pclk1_tim: Some(pclk1_tim), + pclk2_tim: Some(pclk2_tim), + hclk1: Some(hclk), + #[cfg(all(stm32f3, not(rcc_f37)))] + adc: Some(adc), + #[cfg(all(stm32f3, not(rcc_f37), any(peri_adc3_common, peri_adc34_common)))] + adc34: Some(adc34), + rtc: rtc, + hsi48: hsi48, + #[cfg(any(rcc_f1, rcc_f1cl, stm32f3))] + usb: usb, + lse: None, + ); +} + +#[cfg(stm32f0)] +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + pub(crate) const HSE_OSC: RangeInclusive = Hertz(4_000_000)..=Hertz(32_000_000); + pub(crate) const HSE_BYP: RangeInclusive = Hertz(1_000_000)..=Hertz(32_000_000); + + pub(crate) const HCLK: RangeInclusive = Hertz(0)..=Hertz(48_000_000); + pub(crate) const PCLK1: RangeInclusive = Hertz(0)..=Hertz(48_000_000); + + pub(crate) const PLL_IN: RangeInclusive = Hertz(1_000_000)..=Hertz(24_000_000); + pub(crate) const PLL_OUT: RangeInclusive = Hertz(16_000_000)..=Hertz(48_000_000); +} + +#[cfg(stm32f1)] +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + #[cfg(not(rcc_f1cl))] + pub(crate) const HSE_OSC: RangeInclusive = Hertz(4_000_000)..=Hertz(16_000_000); + #[cfg(not(rcc_f1cl))] + pub(crate) const HSE_BYP: RangeInclusive = Hertz(1_000_000)..=Hertz(25_000_000); + + #[cfg(rcc_f1cl)] + pub(crate) const HSE_OSC: RangeInclusive = Hertz(3_000_000)..=Hertz(25_000_000); + #[cfg(rcc_f1cl)] + pub(crate) const HSE_BYP: RangeInclusive = Hertz(1_000_000)..=Hertz(50_000_000); + + pub(crate) const HCLK: RangeInclusive = Hertz(0)..=Hertz(72_000_000); + pub(crate) const PCLK1: RangeInclusive = Hertz(0)..=Hertz(36_000_000); + pub(crate) const PCLK2: RangeInclusive = Hertz(0)..=Hertz(72_000_000); + + pub(crate) const PLL_IN: RangeInclusive = Hertz(1_000_000)..=Hertz(25_000_000); + pub(crate) const PLL_OUT: RangeInclusive = Hertz(16_000_000)..=Hertz(72_000_000); + + pub(crate) const ADC: RangeInclusive = Hertz(0)..=Hertz(14_000_000); +} + +#[cfg(stm32f3)] +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + pub(crate) const HSE_OSC: RangeInclusive = Hertz(4_000_000)..=Hertz(32_000_000); + pub(crate) const HSE_BYP: RangeInclusive = Hertz(1_000_000)..=Hertz(32_000_000); + + pub(crate) const HCLK: RangeInclusive = Hertz(0)..=Hertz(72_000_000); + pub(crate) const PCLK1: RangeInclusive = Hertz(0)..=Hertz(36_000_000); + pub(crate) const PCLK2: RangeInclusive = Hertz(0)..=Hertz(72_000_000); + + pub(crate) const PLL_IN: RangeInclusive = Hertz(1_000_000)..=Hertz(24_000_000); + pub(crate) const PLL_OUT: RangeInclusive = Hertz(16_000_000)..=Hertz(72_000_000); +} diff --git a/embassy/embassy-stm32/src/rcc/f247.rs b/embassy/embassy-stm32/src/rcc/f247.rs new file mode 100644 index 0000000..3e7aff0 --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/f247.rs @@ -0,0 +1,503 @@ +use stm32_metapac::flash::vals::Latency; + +pub use crate::pac::rcc::vals::{ + Hpre as AHBPrescaler, Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv, Pllr as PllRDiv, + Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, +}; +#[cfg(any(stm32f4, stm32f7))] +use crate::pac::PWR; +use crate::pac::{FLASH, RCC}; +use crate::time::Hertz; + +// TODO: on some F4s, PLLM is shared between all PLLs. Enforce that. +// TODO: on some F4s, add support for plli2s_src +// +// plli2s plli2s_m plli2s_src pllsai pllsai_m +// f401 y shared +// f410 +// f411 y individual +// f412 y individual y +// f4[12]3 y individual y +// f446 y individual y individual +// f4[67]9 y shared y shared +// f4[23][79] y shared y shared +// f4[01][57] y shared + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(16_000_000); + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1) + Bypass, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, +} + +#[derive(Clone, Copy)] +pub struct Pll { + /// PLL pre-divider (DIVM). + pub prediv: PllPreDiv, + + /// PLL multiplication factor. + pub mul: PllMul, + + /// PLL P division factor. If None, PLL P output is disabled. + pub divp: Option, + /// PLL Q division factor. If None, PLL Q output is disabled. + pub divq: Option, + /// PLL R division factor. If None, PLL R output is disabled. + pub divr: Option, +} + +/// Voltage range of the power supply used. +/// +/// Used to calculate flash waitstates. See +/// RM0033 - Table 3. Number of wait states according to Cortex®-M3 clock frequency +#[cfg(stm32f2)] +#[derive(Clone, Copy)] +pub enum VoltageScale { + /// 2.7 to 3.6 V + Range0, + /// 2.4 to 2.7 V + Range1, + /// 2.1 to 2.4 V + Range2, + /// 1.8 to 2.1 V + Range3, +} + +/// Configuration of the core clocks +#[non_exhaustive] +#[derive(Clone, Copy)] +pub struct Config { + pub hsi: bool, + pub hse: Option, + pub sys: Sysclk, + + pub pll_src: PllSource, + + pub pll: Option, + #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] + pub plli2s: Option, + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + pub pllsai: Option, + + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + pub apb2_pre: APBPrescaler, + + pub ls: super::LsConfig, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, + + #[cfg(stm32f2)] + pub voltage: VoltageScale, +} + +impl Default for Config { + fn default() -> Self { + Self { + hsi: true, + hse: None, + sys: Sysclk::HSI, + pll_src: PllSource::HSI, + pll: None, + #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] + plli2s: None, + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + pllsai: None, + + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + apb2_pre: APBPrescaler::DIV1, + + ls: Default::default(), + + #[cfg(stm32f2)] + voltage: VoltageScale::Range3, + mux: Default::default(), + } + } +} + +pub(crate) unsafe fn init(config: Config) { + // set VOS to SCALE1, if use PLL + // TODO: check real clock speed before set VOS + #[cfg(any(stm32f4, stm32f7))] + if config.pll.is_some() { + PWR.cr1().modify(|w| w.set_vos(crate::pac::pwr::vals::Vos::SCALE1)); + } + + // always enable overdrive for now. Make it configurable in the future. + #[cfg(any(stm32f446, stm32f4x9, stm32f427, stm32f437, stm32f7))] + { + PWR.cr1().modify(|w| w.set_oden(true)); + while !PWR.csr1().read().odrdy() {} + + PWR.cr1().modify(|w| w.set_odswen(true)); + while !PWR.csr1().read().odswrdy() {} + } + + // Turn on the HSI + RCC.cr().modify(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + + // Use the HSI clock as system clock during the actual clock setup + RCC.cfgr().modify(|w| w.set_sw(Sysclk::HSI)); + while RCC.cfgr().read().sws() != Sysclk::HSI {} + + // Configure HSI + let hsi = match config.hsi { + false => None, + true => Some(HSI_FREQ), + }; + + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + match hse.mode { + HseMode::Bypass => rcc_assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => rcc_assert!(max::HSE_OSC.contains(&hse.freq)), + } + + RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + // Configure PLLs. + let pll_input = PllInput { + hse, + hsi, + source: config.pll_src, + }; + let pll = init_pll(PllInstance::Pll, config.pll, &pll_input); + #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] + let plli2s = init_pll(PllInstance::Plli2s, config.plli2s, &pll_input); + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + let pllsai = init_pll(PllInstance::Pllsai, config.pllsai, &pll_input); + + // Configure sysclk + let sys = match config.sys { + Sysclk::HSI => unwrap!(hsi), + Sysclk::HSE => unwrap!(hse), + Sysclk::PLL1_P => unwrap!(pll.p), + _ => unreachable!(), + }; + + let hclk = sys / config.ahb_pre; + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); + let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk, config.apb2_pre); + + rcc_assert!(max::SYSCLK.contains(&sys)); + rcc_assert!(max::HCLK.contains(&hclk)); + rcc_assert!(max::PCLK1.contains(&pclk1)); + rcc_assert!(max::PCLK2.contains(&pclk2)); + + let rtc = config.ls.init(); + + #[cfg(stm32f2)] + let latency = match (config.voltage, hclk.0) { + (VoltageScale::Range3, ..=16_000_000) => Latency::WS0, + (VoltageScale::Range3, ..=32_000_000) => Latency::WS1, + (VoltageScale::Range3, ..=48_000_000) => Latency::WS2, + (VoltageScale::Range3, ..=64_000_000) => Latency::WS3, + (VoltageScale::Range3, ..=80_000_000) => Latency::WS4, + (VoltageScale::Range3, ..=96_000_000) => Latency::WS5, + (VoltageScale::Range3, ..=112_000_000) => Latency::WS6, + (VoltageScale::Range3, ..=120_000_000) => Latency::WS7, + (VoltageScale::Range2, ..=18_000_000) => Latency::WS0, + (VoltageScale::Range2, ..=36_000_000) => Latency::WS1, + (VoltageScale::Range2, ..=54_000_000) => Latency::WS2, + (VoltageScale::Range2, ..=72_000_000) => Latency::WS3, + (VoltageScale::Range2, ..=90_000_000) => Latency::WS4, + (VoltageScale::Range2, ..=108_000_000) => Latency::WS5, + (VoltageScale::Range2, ..=120_000_000) => Latency::WS6, + (VoltageScale::Range1, ..=24_000_000) => Latency::WS0, + (VoltageScale::Range1, ..=48_000_000) => Latency::WS1, + (VoltageScale::Range1, ..=72_000_000) => Latency::WS2, + (VoltageScale::Range1, ..=96_000_000) => Latency::WS3, + (VoltageScale::Range1, ..=120_000_000) => Latency::WS4, + (VoltageScale::Range0, ..=30_000_000) => Latency::WS0, + (VoltageScale::Range0, ..=60_000_000) => Latency::WS1, + (VoltageScale::Range0, ..=90_000_000) => Latency::WS2, + (VoltageScale::Range0, ..=120_000_000) => Latency::WS3, + _ => unreachable!(), + }; + + #[cfg(any(stm32f4, stm32f7))] + let latency = { + // Be conservative with voltage ranges + const FLASH_LATENCY_STEP: u32 = 30_000_000; + + let latency = (hclk.0 - 1) / FLASH_LATENCY_STEP; + debug!("flash: latency={}", latency); + + Latency::from_bits(latency as u8) + }; + + FLASH.acr().write(|w| w.set_latency(latency)); + while FLASH.acr().read().latency() != latency {} + + RCC.cfgr().modify(|w| { + w.set_sw(config.sys); + w.set_hpre(config.ahb_pre); + w.set_ppre1(config.apb1_pre); + w.set_ppre2(config.apb2_pre); + }); + while RCC.cfgr().read().sws() != config.sys {} + + // Disable HSI if not used + if !config.hsi { + RCC.cr().modify(|w| w.set_hsion(false)); + } + + config.mux.init(); + + set_clocks!( + hsi: hsi, + hse: hse, + lse: None, // TODO + lsi: None, // TODO + sys: Some(sys), + hclk1: Some(hclk), + hclk2: Some(hclk), + hclk3: Some(hclk), + pclk1: Some(pclk1), + pclk2: Some(pclk2), + pclk1_tim: Some(pclk1_tim), + pclk2_tim: Some(pclk2_tim), + rtc: rtc, + pll1_q: pll.q, + pll1_r: pll.r, + + #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] + plli2s1_p: plli2s.p, + #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] + plli2s1_q: plli2s.q, + #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] + plli2s1_r: plli2s.r, + + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + pllsai1_p: pllsai.p, + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + pllsai1_q: pllsai.q, + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + pllsai1_r: pllsai.r, + + // TODO workaround until f4 rcc is fixed in stm32-data + #[cfg(not(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7)))] + pllsai1_q: None, + + #[cfg(dsihost)] + dsi_phy: None, // DSI PLL clock not supported, don't call `RccPeripheral::frequency()` in the drivers + + hsi_div488: hsi.map(|hsi| hsi/488u32), + hsi_hse: None, + afif: None, + ); +} + +struct PllInput { + source: PllSource, + hsi: Option, + hse: Option, +} + +#[derive(Default)] +#[allow(unused)] +struct PllOutput { + p: Option, + q: Option, + r: Option, +} + +#[derive(PartialEq, Eq, Clone, Copy)] +enum PllInstance { + Pll, + #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] + Plli2s, + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + Pllsai, +} + +fn pll_enable(instance: PllInstance, enabled: bool) { + match instance { + PllInstance::Pll => { + RCC.cr().modify(|w| w.set_pllon(enabled)); + while RCC.cr().read().pllrdy() != enabled {} + } + #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] + PllInstance::Plli2s => { + RCC.cr().modify(|w| w.set_plli2son(enabled)); + while RCC.cr().read().plli2srdy() != enabled {} + } + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + PllInstance::Pllsai => { + RCC.cr().modify(|w| w.set_pllsaion(enabled)); + while RCC.cr().read().pllsairdy() != enabled {} + } + } +} + +fn init_pll(instance: PllInstance, config: Option, input: &PllInput) -> PllOutput { + // Disable PLL + pll_enable(instance, false); + + let Some(pll) = config else { return PllOutput::default() }; + + let pll_src = match input.source { + PllSource::HSE => input.hse, + PllSource::HSI => input.hsi, + }; + + let pll_src = pll_src.unwrap(); + + let in_freq = pll_src / pll.prediv; + assert!(max::PLL_IN.contains(&in_freq)); + let vco_freq = in_freq * pll.mul; + assert!(max::PLL_VCO.contains(&vco_freq)); + + // stm32f2 plls are like swiss cheese + #[cfg(stm32f2)] + match instance { + PllInstance::Pll => { + assert!(pll.divr.is_none()); + } + PllInstance::Plli2s => { + assert!(pll.divp.is_none()); + assert!(pll.divq.is_none()); + } + } + + let p = pll.divp.map(|div| vco_freq / div); + let q = pll.divq.map(|div| vco_freq / div); + let r = pll.divr.map(|div| vco_freq / div); + + macro_rules! write_fields { + ($w:ident) => { + $w.set_plln(pll.mul); + if let Some(divp) = pll.divp { + $w.set_pllp(divp); + } + if let Some(divq) = pll.divq { + $w.set_pllq(divq); + } + #[cfg(any(stm32f4, stm32f7))] + if let Some(divr) = pll.divr { + $w.set_pllr(divr); + } + }; + } + + match instance { + PllInstance::Pll => RCC.pllcfgr().write(|w| { + w.set_pllm(pll.prediv); + w.set_pllsrc(input.source); + write_fields!(w); + }), + #[cfg(any(all(stm32f4, not(stm32f410)), stm32f7))] + PllInstance::Plli2s => RCC.plli2scfgr().write(|w| { + write_fields!(w); + }), + #[cfg(stm32f2)] + PllInstance::Plli2s => RCC.plli2scfgr().write(|w| { + if let Some(divr) = pll.divr { + w.set_pllr(divr); + } + }), + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + PllInstance::Pllsai => RCC.pllsaicfgr().write(|w| { + write_fields!(w); + }), + } + + // Enable PLL + pll_enable(instance, true); + + PllOutput { p, q, r } +} + +#[cfg(stm32f7)] +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + pub(crate) const HSE_OSC: RangeInclusive = Hertz(4_000_000)..=Hertz(26_000_000); + pub(crate) const HSE_BYP: RangeInclusive = Hertz(1_000_000)..=Hertz(50_000_000); + + pub(crate) const SYSCLK: RangeInclusive = Hertz(12_500_000)..=Hertz(216_000_000); + pub(crate) const HCLK: RangeInclusive = Hertz(12_500_000)..=Hertz(216_000_000); + pub(crate) const PCLK1: RangeInclusive = Hertz(12_500_000)..=Hertz(216_000_000 / 4); + pub(crate) const PCLK2: RangeInclusive = Hertz(12_500_000)..=Hertz(216_000_000 / 2); + + pub(crate) const PLL_IN: RangeInclusive = Hertz(1_000_000)..=Hertz(2_100_000); + pub(crate) const PLL_VCO: RangeInclusive = Hertz(100_000_000)..=Hertz(432_000_000); +} + +#[cfg(stm32f4)] +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + pub(crate) const HSE_OSC: RangeInclusive = Hertz(4_000_000)..=Hertz(26_000_000); + pub(crate) const HSE_BYP: RangeInclusive = Hertz(1_000_000)..=Hertz(50_000_000); + + #[cfg(stm32f401)] + pub(crate) const SYSCLK: RangeInclusive = Hertz(0)..=Hertz(84_000_000); + #[cfg(any(stm32f405, stm32f407, stm32f415, stm32f417,))] + pub(crate) const SYSCLK: RangeInclusive = Hertz(0)..=Hertz(168_000_000); + #[cfg(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423,))] + pub(crate) const SYSCLK: RangeInclusive = Hertz(0)..=Hertz(100_000_000); + #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479,))] + pub(crate) const SYSCLK: RangeInclusive = Hertz(0)..=Hertz(180_000_000); + + pub(crate) const HCLK: RangeInclusive = Hertz(0)..=Hertz(SYSCLK.end().0); + + pub(crate) const PCLK1: RangeInclusive = Hertz(0)..=Hertz(PCLK2.end().0 / 2); + + #[cfg(any(stm32f401, stm32f410, stm32f411, stm32f412, stm32f413, stm32f423,))] + pub(crate) const PCLK2: RangeInclusive = Hertz(0)..=Hertz(HCLK.end().0); + #[cfg(not(any(stm32f401, stm32f410, stm32f411, stm32f412, stm32f413, stm32f423,)))] + pub(crate) const PCLK2: RangeInclusive = Hertz(0)..=Hertz(HCLK.end().0 / 2); + + pub(crate) const PLL_IN: RangeInclusive = Hertz(1_000_000)..=Hertz(2_100_000); + pub(crate) const PLL_VCO: RangeInclusive = Hertz(100_000_000)..=Hertz(432_000_000); +} + +#[cfg(stm32f2)] +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + pub(crate) const HSE_OSC: RangeInclusive = Hertz(4_000_000)..=Hertz(26_000_000); + pub(crate) const HSE_BYP: RangeInclusive = Hertz(1_000_000)..=Hertz(26_000_000); + + pub(crate) const SYSCLK: RangeInclusive = Hertz(0)..=Hertz(120_000_000); + + pub(crate) const HCLK: RangeInclusive = Hertz(0)..=Hertz(SYSCLK.end().0); + pub(crate) const PCLK1: RangeInclusive = Hertz(0)..=Hertz(SYSCLK.end().0 / 4); + pub(crate) const PCLK2: RangeInclusive = Hertz(0)..=Hertz(SYSCLK.end().0 / 2); + + pub(crate) const PLL_IN: RangeInclusive = Hertz(0_950_000)..=Hertz(2_100_000); + pub(crate) const PLL_VCO: RangeInclusive = Hertz(192_000_000)..=Hertz(432_000_000); +} diff --git a/embassy/embassy-stm32/src/rcc/g0.rs b/embassy/embassy-stm32/src/rcc/g0.rs new file mode 100644 index 0000000..5da3372 --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/g0.rs @@ -0,0 +1,317 @@ +use crate::pac::flash::vals::Latency; +pub use crate::pac::pwr::vals::Vos as VoltageRange; +pub use crate::pac::rcc::vals::{ + Hpre as AHBPrescaler, Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv, Pllr as PllRDiv, + Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, +}; +use crate::pac::{FLASH, PWR, RCC}; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(16_000_000); + +/// HSE Mode +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1) + Bypass, +} + +/// HSE Configuration +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, +} + +/// PLL Configuration +/// +/// Use this struct to configure the PLL source, input frequency, multiplication factor, and output +/// dividers. Be sure to keep check the datasheet for your specific part for the appropriate +/// frequency ranges for each of these settings. +#[derive(Clone, Copy)] +pub struct Pll { + /// PLL Source clock selection. + pub source: PllSource, + + /// PLL pre-divider + pub prediv: PllPreDiv, + + /// PLL multiplication factor for VCO + pub mul: PllMul, + + /// PLL division factor for P clock (ADC Clock) + pub divp: Option, + + /// PLL division factor for Q clock (USB, I2S23, SAI1, FDCAN, QSPI) + pub divq: Option, + + /// PLL division factor for R clock (SYSCLK) + pub divr: Option, +} + +/// Clocks configutation +#[non_exhaustive] +#[derive(Clone, Copy)] +pub struct Config { + /// HSI Enable + pub hsi: bool, + + /// HSE Configuration + pub hse: Option, + + /// System Clock Configuration + pub sys: Sysclk, + + /// HSI48 Configuration + #[cfg(crs)] + pub hsi48: Option, + + /// PLL Configuration + pub pll: Option, + + /// If PLL is requested as the main clock source in the `sys` field then the PLL configuration + /// MUST turn on the PLLR output. + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + + /// Low-Speed Clock Configuration + pub ls: super::LsConfig, + + pub low_power_run: bool, + + pub voltage_range: VoltageRange, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, +} + +impl Default for Config { + #[inline] + fn default() -> Config { + Config { + hsi: true, + hse: None, + sys: Sysclk::HSI, + #[cfg(crs)] + hsi48: Some(Default::default()), + pll: None, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + low_power_run: false, + ls: Default::default(), + voltage_range: VoltageRange::RANGE1, + mux: Default::default(), + } + } +} + +#[derive(Default)] +pub struct PllFreq { + pub pll_p: Option, + pub pll_q: Option, + pub pll_r: Option, +} + +pub(crate) unsafe fn init(config: Config) { + // Turn on the HSI + RCC.cr().modify(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + + // Use the HSI clock as system clock during the actual clock setup + RCC.cfgr().modify(|w| w.set_sw(Sysclk::HSI)); + while RCC.cfgr().read().sws() != Sysclk::HSI {} + + // Configure HSI + let hsi = match config.hsi { + false => None, + true => Some(HSI_FREQ), + }; + + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + match hse.mode { + HseMode::Bypass => rcc_assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => rcc_assert!(max::HSE_OSC.contains(&hse.freq)), + } + + RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + // Configure HSI48 if required + #[cfg(crs)] + let hsi48 = config.hsi48.map(super::init_hsi48); + + let pll = config + .pll + .map(|pll_config| { + let src_freq = match pll_config.source { + PllSource::HSI => unwrap!(hsi), + PllSource::HSE => unwrap!(hse), + _ => unreachable!(), + }; + + // Disable PLL before configuration + RCC.cr().modify(|w| w.set_pllon(false)); + while RCC.cr().read().pllrdy() {} + + let in_freq = src_freq / pll_config.prediv; + rcc_assert!(max::PLL_IN.contains(&in_freq)); + let internal_freq = in_freq * pll_config.mul; + rcc_assert!(max::PLL_VCO.contains(&internal_freq)); + + RCC.pllcfgr().write(|w| { + w.set_plln(pll_config.mul); + w.set_pllm(pll_config.prediv); + w.set_pllsrc(pll_config.source.into()); + }); + + let pll_p_freq = pll_config.divp.map(|div_p| { + RCC.pllcfgr().modify(|w| { + w.set_pllp(div_p); + w.set_pllpen(true); + }); + let freq = internal_freq / div_p; + rcc_assert!(max::PLL_P.contains(&freq)); + freq + }); + + let pll_q_freq = pll_config.divq.map(|div_q| { + RCC.pllcfgr().modify(|w| { + w.set_pllq(div_q); + w.set_pllqen(true); + }); + let freq = internal_freq / div_q; + rcc_assert!(max::PLL_Q.contains(&freq)); + freq + }); + + let pll_r_freq = pll_config.divr.map(|div_r| { + RCC.pllcfgr().modify(|w| { + w.set_pllr(div_r); + w.set_pllren(true); + }); + let freq = internal_freq / div_r; + rcc_assert!(max::PLL_R.contains(&freq)); + freq + }); + + // Enable the PLL + RCC.cr().modify(|w| w.set_pllon(true)); + while !RCC.cr().read().pllrdy() {} + + PllFreq { + pll_p: pll_p_freq, + pll_q: pll_q_freq, + pll_r: pll_r_freq, + } + }) + .unwrap_or_default(); + + let sys = match config.sys { + Sysclk::HSI => unwrap!(hsi), + Sysclk::HSE => unwrap!(hse), + Sysclk::PLL1_R => unwrap!(pll.pll_r), + _ => unreachable!(), + }; + + rcc_assert!(max::SYSCLK.contains(&sys)); + + // Calculate the AHB frequency (HCLK), among other things so we can calculate the correct flash read latency. + let hclk = sys / config.ahb_pre; + rcc_assert!(max::HCLK.contains(&hclk)); + + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); + rcc_assert!(max::PCLK.contains(&pclk1)); + + let latency = match (config.voltage_range, hclk.0) { + (VoltageRange::RANGE1, ..=24_000_000) => Latency::WS0, + (VoltageRange::RANGE1, ..=48_000_000) => Latency::WS1, + (VoltageRange::RANGE1, _) => Latency::WS2, + (VoltageRange::RANGE2, ..=8_000_000) => Latency::WS0, + (VoltageRange::RANGE2, ..=16_000_000) => Latency::WS1, + (VoltageRange::RANGE2, _) => Latency::WS2, + _ => unreachable!(), + }; + + // Configure flash read access latency based on voltage scale and frequency (RM0444 3.3.4) + FLASH.acr().modify(|w| { + w.set_latency(latency); + }); + + // Spin until the effective flash latency is set. + while FLASH.acr().read().latency() != latency {} + + // Now that boost mode and flash read access latency are configured, set up SYSCLK + RCC.cfgr().modify(|w| { + w.set_sw(config.sys); + w.set_hpre(config.ahb_pre); + w.set_ppre(config.apb1_pre); + }); + while RCC.cfgr().read().sws() != config.sys {} + + // Disable HSI if not used + if !config.hsi { + RCC.cr().modify(|w| w.set_hsion(false)); + } + + if config.low_power_run { + assert!(sys <= Hertz(2_000_000)); + PWR.cr1().modify(|w| w.set_lpr(true)); + } + + let rtc = config.ls.init(); + + config.mux.init(); + + set_clocks!( + sys: Some(sys), + hclk1: Some(hclk), + pclk1: Some(pclk1), + pclk1_tim: Some(pclk1_tim), + pll1_p: pll.pll_p, + pll1_q: pll.pll_q, + pll1_r: pll.pll_r, + hsi: hsi, + hse: hse, + #[cfg(crs)] + hsi48: hsi48, + rtc: rtc, + hsi_div_8: hsi.map(|h| h / 8u32), + hsi_div_488: hsi.map(|h| h / 488u32), + + // TODO + lsi: None, + lse: None, + ); +} + +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + pub(crate) const HSE_OSC: RangeInclusive = Hertz(4_000_000)..=Hertz(48_000_000); + pub(crate) const HSE_BYP: RangeInclusive = Hertz(0)..=Hertz(48_000_000); + pub(crate) const SYSCLK: RangeInclusive = Hertz(0)..=Hertz(64_000_000); + pub(crate) const PCLK: RangeInclusive = Hertz(8)..=Hertz(64_000_000); + pub(crate) const HCLK: RangeInclusive = Hertz(0)..=Hertz(64_000_000); + pub(crate) const PLL_IN: RangeInclusive = Hertz(2_660_000)..=Hertz(16_000_000); + pub(crate) const PLL_VCO: RangeInclusive = Hertz(96_000_000)..=Hertz(344_000_000); + pub(crate) const PLL_P: RangeInclusive = Hertz(3_090_000)..=Hertz(122_000_000); + pub(crate) const PLL_Q: RangeInclusive = Hertz(12_000_000)..=Hertz(128_000_000); + pub(crate) const PLL_R: RangeInclusive = Hertz(12_000_000)..=Hertz(64_000_000); +} diff --git a/embassy/embassy-stm32/src/rcc/g4.rs b/embassy/embassy-stm32/src/rcc/g4.rs new file mode 100644 index 0000000..6b34aa3 --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/g4.rs @@ -0,0 +1,358 @@ +use crate::pac::flash::vals::Latency; +pub use crate::pac::rcc::vals::{ + Hpre as AHBPrescaler, Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv, Pllr as PllRDiv, + Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, +}; +use crate::pac::{FLASH, PWR, RCC}; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(16_000_000); + +/// HSE Mode +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1) + Bypass, +} + +/// HSE Configuration +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, +} + +/// PLL Configuration +/// +/// Use this struct to configure the PLL source, input frequency, multiplication factor, and output +/// dividers. Be sure to keep check the datasheet for your specific part for the appropriate +/// frequency ranges for each of these settings. +#[derive(Clone, Copy)] +pub struct Pll { + /// PLL Source clock selection. + pub source: PllSource, + + /// PLL pre-divider + pub prediv: PllPreDiv, + + /// PLL multiplication factor for VCO + pub mul: PllMul, + + /// PLL division factor for P clock (ADC Clock) + pub divp: Option, + + /// PLL division factor for Q clock (USB, I2S23, SAI1, FDCAN, QSPI) + pub divq: Option, + + /// PLL division factor for R clock (SYSCLK) + pub divr: Option, +} + +/// Clocks configutation +#[non_exhaustive] +#[derive(Clone, Copy)] +pub struct Config { + /// HSI Enable + pub hsi: bool, + + /// HSE Configuration + pub hse: Option, + + /// System Clock Configuration + pub sys: Sysclk, + + /// HSI48 Configuration + pub hsi48: Option, + + /// PLL Configuration + pub pll: Option, + + /// If PLL is requested as the main clock source in the `sys` field then the PLL configuration + /// MUST turn on the PLLR output. + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + pub apb2_pre: APBPrescaler, + + pub low_power_run: bool, + + /// Low-Speed Clock Configuration + pub ls: super::LsConfig, + + /// Enable range1 boost mode + /// Recommended when the SYSCLK frequency is greater than 150MHz. + pub boost: bool, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, +} + +impl Default for Config { + #[inline] + fn default() -> Config { + Config { + hsi: true, + hse: None, + sys: Sysclk::HSI, + hsi48: Some(Default::default()), + pll: None, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + apb2_pre: APBPrescaler::DIV1, + low_power_run: false, + ls: Default::default(), + boost: false, + mux: Default::default(), + } + } +} + +#[derive(Default)] +pub struct PllFreq { + pub pll_p: Option, + pub pll_q: Option, + pub pll_r: Option, +} + +pub(crate) unsafe fn init(config: Config) { + // Turn on the HSI + RCC.cr().modify(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + + // Use the HSI clock as system clock during the actual clock setup + RCC.cfgr().modify(|w| w.set_sw(Sysclk::HSI)); + while RCC.cfgr().read().sws() != Sysclk::HSI {} + + // Configure HSI + let hsi = match config.hsi { + false => None, + true => Some(HSI_FREQ), + }; + + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + match hse.mode { + HseMode::Bypass => rcc_assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => rcc_assert!(max::HSE_OSC.contains(&hse.freq)), + } + + RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + // Configure HSI48 if required + let hsi48 = config.hsi48.map(super::init_hsi48); + + let pll = config + .pll + .map(|pll_config| { + let src_freq = match pll_config.source { + PllSource::HSI => unwrap!(hsi), + PllSource::HSE => unwrap!(hse), + _ => unreachable!(), + }; + + // Disable PLL before configuration + RCC.cr().modify(|w| w.set_pllon(false)); + while RCC.cr().read().pllrdy() {} + + let in_freq = src_freq / pll_config.prediv; + rcc_assert!(max::PLL_IN.contains(&in_freq)); + let internal_freq = in_freq * pll_config.mul; + + rcc_assert!(max::PLL_VCO.contains(&internal_freq)); + + RCC.pllcfgr().write(|w| { + w.set_plln(pll_config.mul); + w.set_pllm(pll_config.prediv); + w.set_pllsrc(pll_config.source.into()); + }); + + let pll_p_freq = pll_config.divp.map(|div_p| { + RCC.pllcfgr().modify(|w| { + w.set_pllp(div_p); + w.set_pllpen(true); + }); + let freq = internal_freq / div_p; + rcc_assert!(max::PLL_P.contains(&freq)); + freq + }); + + let pll_q_freq = pll_config.divq.map(|div_q| { + RCC.pllcfgr().modify(|w| { + w.set_pllq(div_q); + w.set_pllqen(true); + }); + let freq = internal_freq / div_q; + rcc_assert!(max::PLL_Q.contains(&freq)); + freq + }); + + let pll_r_freq = pll_config.divr.map(|div_r| { + RCC.pllcfgr().modify(|w| { + w.set_pllr(div_r); + w.set_pllren(true); + }); + let freq = internal_freq / div_r; + rcc_assert!(max::PLL_R.contains(&freq)); + freq + }); + + // Enable the PLL + RCC.cr().modify(|w| w.set_pllon(true)); + while !RCC.cr().read().pllrdy() {} + + PllFreq { + pll_p: pll_p_freq, + pll_q: pll_q_freq, + pll_r: pll_r_freq, + } + }) + .unwrap_or_default(); + + let sys = match config.sys { + Sysclk::HSI => unwrap!(hsi), + Sysclk::HSE => unwrap!(hse), + Sysclk::PLL1_R => unwrap!(pll.pll_r), + _ => unreachable!(), + }; + + rcc_assert!(max::SYSCLK.contains(&sys)); + + // Calculate the AHB frequency (HCLK), among other things so we can calculate the correct flash read latency. + let hclk = sys / config.ahb_pre; + rcc_assert!(max::HCLK.contains(&hclk)); + + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); + let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk, config.apb2_pre); + rcc_assert!(max::PCLK.contains(&pclk1)); + rcc_assert!(max::PCLK.contains(&pclk2)); + + // Configure Core Boost mode ([RM0440] p234 – inverted because setting r1mode to 0 enables boost mode!) + if config.boost { + // RM0440 p235 + // “The sequence to switch from Range1 normal mode to Range1 boost mode is: + // 1. The system clock must be divided by 2 using the AHB prescaler before switching to a higher system frequency. + RCC.cfgr().modify(|w| w.set_hpre(AHBPrescaler::DIV2)); + // 2. Clear the R1MODE bit in the PWR_CR5 register. (enables boost mode) + PWR.cr5().modify(|w| w.set_r1mode(false)); + + // Below: + // 3. Adjust wait states according to new freq target + // 4. Configure and switch to new frequency + } + + let latency = match (config.boost, hclk.0) { + (true, ..=34_000_000) => Latency::WS0, + (true, ..=68_000_000) => Latency::WS1, + (true, ..=102_000_000) => Latency::WS2, + (true, ..=136_000_000) => Latency::WS3, + (true, _) => Latency::WS4, + + (false, ..=36_000_000) => Latency::WS0, + (false, ..=60_000_000) => Latency::WS1, + (false, ..=90_000_000) => Latency::WS2, + (false, ..=120_000_000) => Latency::WS3, + (false, _) => Latency::WS4, + }; + + // Configure flash read access latency based on boost mode and frequency (RM0440 p98) + FLASH.acr().modify(|w| { + w.set_latency(latency); + }); + + // Spin until the effective flash latency is set. + while FLASH.acr().read().latency() != latency {} + + if config.boost { + // 5. Wait for at least 1us and then reconfigure the AHB prescaler to get the needed HCLK clock frequency. + cortex_m::asm::delay(16); + } + + // Now that boost mode and flash read access latency are configured, set up SYSCLK + RCC.cfgr().modify(|w| { + w.set_sw(config.sys); + w.set_hpre(config.ahb_pre); + w.set_ppre1(config.apb1_pre); + w.set_ppre2(config.apb2_pre); + }); + while RCC.cfgr().read().sws() != config.sys {} + + // Disable HSI if not used + if !config.hsi { + RCC.cr().modify(|w| w.set_hsion(false)); + } + + if config.low_power_run { + assert!(sys <= Hertz(2_000_000)); + PWR.cr1().modify(|w| w.set_lpr(true)); + } + + let rtc = config.ls.init(); + + config.mux.init(); + + set_clocks!( + sys: Some(sys), + hclk1: Some(hclk), + hclk2: Some(hclk), + hclk3: Some(hclk), + pclk1: Some(pclk1), + pclk1_tim: Some(pclk1_tim), + pclk2: Some(pclk2), + pclk2_tim: Some(pclk2_tim), + pll1_p: pll.pll_p, + pll1_q: pll.pll_q, + pll1_r: pll.pll_r, + hsi: hsi, + hse: hse, + hsi48: hsi48, + rtc: rtc, + ); +} + +/// Acceptable Frequency Ranges +/// Currently assuming voltage scaling range 1 boost mode. +/// Where not specified in the generic G4 reference manual (RM0440), values taken from the STM32G474 datasheet. +/// If acceptable ranges for other G4-family chips differ, make additional max modules gated behind cfg attrs. +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + /// HSE Frequency Range (RM0440 p280) + pub(crate) const HSE_OSC: RangeInclusive = Hertz(4_000_000)..=Hertz(48_000_000); + + /// External Clock Frequency Range (RM0440 p280) + pub(crate) const HSE_BYP: RangeInclusive = Hertz(0)..=Hertz(48_000_000); + + /// SYSCLK Frequency Range (RM0440 p282) + pub(crate) const SYSCLK: RangeInclusive = Hertz(0)..=Hertz(170_000_000); + + /// PLL Output Frequency Range (RM0440 p281, STM32G474 Datasheet p123, Table 46) + pub(crate) const PCLK: RangeInclusive = Hertz(8)..=Hertz(170_000_000); + + /// HCLK (AHB) Clock Frequency Range (STM32G474 Datasheet) + pub(crate) const HCLK: RangeInclusive = Hertz(0)..=Hertz(170_000_000); + + /// PLL Source Frequency Range (STM32G474 Datasheet p123, Table 46) + pub(crate) const PLL_IN: RangeInclusive = Hertz(2_660_000)..=Hertz(16_000_000); + + /// PLL VCO (internal) Frequency Range (STM32G474 Datasheet p123, Table 46) + pub(crate) const PLL_VCO: RangeInclusive = Hertz(96_000_000)..=Hertz(344_000_000); + pub(crate) const PLL_P: RangeInclusive = Hertz(2_064_500)..=Hertz(170_000_000); + pub(crate) const PLL_Q: RangeInclusive = Hertz(8_000_000)..=Hertz(170_000_000); + pub(crate) const PLL_R: RangeInclusive = Hertz(8_000_000)..=Hertz(170_000_000); +} diff --git a/embassy/embassy-stm32/src/rcc/h.rs b/embassy/embassy-stm32/src/rcc/h.rs new file mode 100644 index 0000000..df2929b --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/h.rs @@ -0,0 +1,1007 @@ +use core::ops::RangeInclusive; + +use crate::pac; +pub use crate::pac::rcc::vals::{ + Hsidiv as HSIPrescaler, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, Pllsrc as PllSource, Sw as Sysclk, +}; +use crate::pac::rcc::vals::{Pllrge, Pllvcosel, Timpre}; +use crate::pac::{FLASH, PWR, RCC}; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(64_000_000); + +/// CSI speed +pub const CSI_FREQ: Hertz = Hertz(4_000_000); + +const VCO_RANGE: RangeInclusive = Hertz(150_000_000)..=Hertz(420_000_000); +#[cfg(any(stm32h5, pwr_h7rm0455))] +const VCO_WIDE_RANGE: RangeInclusive = Hertz(128_000_000)..=Hertz(560_000_000); +#[cfg(pwr_h7rm0468)] +const VCO_WIDE_RANGE: RangeInclusive = Hertz(192_000_000)..=Hertz(836_000_000); +#[cfg(any(pwr_h7rm0399, pwr_h7rm0433))] +const VCO_WIDE_RANGE: RangeInclusive = Hertz(192_000_000)..=Hertz(960_000_000); +#[cfg(any(stm32h7rs))] +const VCO_WIDE_RANGE: RangeInclusive = Hertz(384_000_000)..=Hertz(1672_000_000); + +pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Ppre as APBPrescaler}; + +#[cfg(any(stm32h5, stm32h7))] +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum VoltageScale { + Scale0, + Scale1, + Scale2, + Scale3, +} +#[cfg(stm32h7rs)] +pub use crate::pac::pwr::vals::Vos as VoltageScale; +#[cfg(all(stm32h7rs, peri_usb_otg_hs))] +pub use crate::pac::rcc::vals::{Usbphycsel, Usbrefcksel}; + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1, HSEEXT=0) + Bypass, + /// external digital clock (full swing) (HSEBYP=1, HSEEXT=1) + #[cfg(any(rcc_h5, rcc_h50, rcc_h7rs))] + BypassDigital, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, +} + +#[derive(Clone, Copy)] +pub struct Pll { + /// Source clock selection. + pub source: PllSource, + + /// PLL pre-divider (DIVM). + pub prediv: PllPreDiv, + + /// PLL multiplication factor. + pub mul: PllMul, + + /// PLL P division factor. If None, PLL P output is disabled. + /// On PLL1, it must be even for most series (in particular, + /// it cannot be 1 in series other than stm32h7, stm32h7rs23/733, + /// stm32h7, stm32h7rs25/735 and stm32h7, stm32h7rs30.) + pub divp: Option, + /// PLL Q division factor. If None, PLL Q output is disabled. + pub divq: Option, + /// PLL R division factor. If None, PLL R output is disabled. + pub divr: Option, +} + +fn apb_div_tim(apb: &APBPrescaler, clk: Hertz, tim: TimerPrescaler) -> Hertz { + match (tim, apb) { + (TimerPrescaler::DefaultX2, APBPrescaler::DIV1) => clk, + (TimerPrescaler::DefaultX2, APBPrescaler::DIV2) => clk, + (TimerPrescaler::DefaultX2, APBPrescaler::DIV4) => clk / 2u32, + (TimerPrescaler::DefaultX2, APBPrescaler::DIV8) => clk / 4u32, + (TimerPrescaler::DefaultX2, APBPrescaler::DIV16) => clk / 8u32, + + (TimerPrescaler::DefaultX4, APBPrescaler::DIV1) => clk, + (TimerPrescaler::DefaultX4, APBPrescaler::DIV2) => clk, + (TimerPrescaler::DefaultX4, APBPrescaler::DIV4) => clk, + (TimerPrescaler::DefaultX4, APBPrescaler::DIV8) => clk / 2u32, + (TimerPrescaler::DefaultX4, APBPrescaler::DIV16) => clk / 4u32, + + _ => unreachable!(), + } +} + +/// Timer prescaler +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum TimerPrescaler { + /// The timers kernel clock is equal to hclk if PPREx corresponds to a + /// division by 1 or 2, else it is equal to 2*pclk + DefaultX2, + + /// The timers kernel clock is equal to hclk if PPREx corresponds to a + /// division by 1, 2 or 4, else it is equal to 4*pclk + DefaultX4, +} + +impl From for Timpre { + fn from(value: TimerPrescaler) -> Self { + match value { + TimerPrescaler::DefaultX2 => Timpre::DEFAULTX2, + TimerPrescaler::DefaultX4 => Timpre::DEFAULTX4, + } + } +} + +/// Power supply configuration +/// See RM0433 Rev 4 7.4 +#[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] +#[derive(Clone, Copy, PartialEq)] +pub enum SupplyConfig { + /// Default power supply configuration. + /// V CORE Power Domains are supplied from the LDO according to VOS. + /// SMPS step-down converter enabled at 1.2V, may be used to supply the LDO. + Default, + + /// Power supply configuration using the LDO. + /// V CORE Power Domains are supplied from the LDO according to VOS. + /// LDO power mode (Main, LP, Off) will follow system low-power modes. + /// SMPS step-down converter disabled. + LDO, + + /// Power supply configuration directly from the SMPS step-down converter. + /// V CORE Power Domains are supplied from SMPS step-down converter according to VOS. + /// LDO bypassed. + /// SMPS step-down converter power mode (MR, LP, Off) will follow system low-power modes. + DirectSMPS, + + /// Power supply configuration from the SMPS step-down converter, that supplies the LDO. + /// V CORE Power Domains are supplied from the LDO according to VOS + /// LDO power mode (Main, LP, Off) will follow system low-power modes. + /// SMPS step-down converter enabled according to SDLEVEL, and supplies the LDO. + /// SMPS step-down converter power mode (MR, LP, Off) will follow system low-power modes. + SMPSLDO(SMPSSupplyVoltage), + + /// Power supply configuration from SMPS supplying external circuits and potentially the LDO. + /// V CORE Power Domains are supplied from voltage regulator according to VOS + /// LDO power mode (Main, LP, Off) will follow system low-power modes. + /// SMPS step-down converter enabled according to SDLEVEL used to supply external circuits and may supply the LDO. + /// SMPS step-down converter forced ON in MR mode. + SMPSExternalLDO(SMPSSupplyVoltage), + + /// Power supply configuration from SMPS supplying external circuits and bypassing the LDO. + /// V CORE supplied from external source + /// SMPS step-down converter enabled according to SDLEVEL used to supply external circuits and may supply the external source for V CORE . + /// SMPS step-down converter forced ON in MR mode. + SMPSExternalLDOBypass(SMPSSupplyVoltage), + + /// Power supply configuration from an external source, SMPS disabled and the LDO bypassed. + /// V CORE supplied from external source + /// SMPS step-down converter disabled and LDO bypassed, voltage monitoring still active. + SMPSDisabledLDOBypass, +} + +/// SMPS step-down converter voltage output level. +/// This is only used in certain power supply configurations: +/// SMPSLDO, SMPSExternalLDO, SMPSExternalLDOBypass. +#[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum SMPSSupplyVoltage { + /// 1.8v + V1_8, + /// 2.5v + #[cfg(not(pwr_h7rs))] + V2_5, +} + +/// Configuration of the core clocks +#[non_exhaustive] +#[derive(Clone, Copy)] +pub struct Config { + pub hsi: Option, + pub hse: Option, + pub csi: bool, + pub hsi48: Option, + pub sys: Sysclk, + + pub pll1: Option, + pub pll2: Option, + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + pub pll3: Option, + + #[cfg(any(stm32h7, stm32h7rs))] + pub d1c_pre: AHBPrescaler, + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + pub apb2_pre: APBPrescaler, + #[cfg(not(stm32h7rs))] + pub apb3_pre: APBPrescaler, + #[cfg(any(stm32h7, stm32h7rs))] + pub apb4_pre: APBPrescaler, + #[cfg(stm32h7rs)] + pub apb5_pre: APBPrescaler, + + pub timer_prescaler: TimerPrescaler, + pub voltage_scale: VoltageScale, + pub ls: super::LsConfig, + + #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] + pub supply_config: SupplyConfig, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, +} + +impl Default for Config { + fn default() -> Self { + Self { + hsi: Some(HSIPrescaler::DIV1), + hse: None, + csi: false, + hsi48: Some(Default::default()), + sys: Sysclk::HSI, + pll1: None, + pll2: None, + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + pll3: None, + + #[cfg(any(stm32h7, stm32h7rs))] + d1c_pre: AHBPrescaler::DIV1, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + apb2_pre: APBPrescaler::DIV1, + #[cfg(not(stm32h7rs))] + apb3_pre: APBPrescaler::DIV1, + #[cfg(any(stm32h7, stm32h7rs))] + apb4_pre: APBPrescaler::DIV1, + #[cfg(stm32h7rs)] + apb5_pre: APBPrescaler::DIV1, + + timer_prescaler: TimerPrescaler::DefaultX2, + #[cfg(not(rcc_h7rs))] + voltage_scale: VoltageScale::Scale0, + #[cfg(rcc_h7rs)] + voltage_scale: VoltageScale::HIGH, + ls: Default::default(), + + #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] + supply_config: SupplyConfig::LDO, + + mux: Default::default(), + } + } +} + +pub(crate) unsafe fn init(config: Config) { + #[cfg(any(stm32h7))] + let pwr_reg = PWR.cr3(); + #[cfg(any(stm32h7rs))] + let pwr_reg = PWR.csr2(); + + // NB. The lower bytes of CR3 can only be written once after + // POR, and must be written with a valid combination. Refer to + // RM0433 Rev 7 6.8.4. This is partially enforced by dropping + // `self` at the end of this method, but of course we cannot + // know what happened between the previous POR and here. + #[cfg(pwr_h7rm0433)] + pwr_reg.modify(|w| { + w.set_scuen(true); + w.set_ldoen(true); + w.set_bypass(false); + }); + + #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] + { + use pac::pwr::vals::Sdlevel; + match config.supply_config { + SupplyConfig::Default => { + pwr_reg.modify(|w| { + w.set_sdlevel(Sdlevel::RESET); + w.set_sdexthp(false); + w.set_sden(true); + w.set_ldoen(true); + w.set_bypass(false); + }); + } + SupplyConfig::LDO => { + pwr_reg.modify(|w| { + w.set_sden(false); + w.set_ldoen(true); + w.set_bypass(false); + }); + } + SupplyConfig::DirectSMPS => { + pwr_reg.modify(|w| { + w.set_sdexthp(false); + w.set_sden(true); + w.set_ldoen(false); + w.set_bypass(false); + }); + } + SupplyConfig::SMPSLDO(sdlevel) + | SupplyConfig::SMPSExternalLDO(sdlevel) + | SupplyConfig::SMPSExternalLDOBypass(sdlevel) => { + let sdlevel = match sdlevel { + SMPSSupplyVoltage::V1_8 => Sdlevel::V1_8, + #[cfg(not(pwr_h7rs))] + SMPSSupplyVoltage::V2_5 => Sdlevel::V2_5, + }; + pwr_reg.modify(|w| { + w.set_sdlevel(sdlevel); + w.set_sdexthp(matches!( + config.supply_config, + SupplyConfig::SMPSExternalLDO(_) | SupplyConfig::SMPSExternalLDOBypass(_) + )); + w.set_sden(true); + w.set_ldoen(matches!( + config.supply_config, + SupplyConfig::SMPSLDO(_) | SupplyConfig::SMPSExternalLDO(_) + )); + w.set_bypass(matches!(config.supply_config, SupplyConfig::SMPSExternalLDOBypass(_))); + }); + } + SupplyConfig::SMPSDisabledLDOBypass => { + pwr_reg.modify(|w| { + w.set_sden(false); + w.set_ldoen(false); + w.set_bypass(true); + }); + } + } + } + + // Validate the supply configuration. If you are stuck here, it is + // because the voltages on your board do not match those specified + // in the D3CR.VOS and CR3.SDLEVEL fields. By default after reset + // VOS = Scale 3, so check that the voltage on the VCAP pins = + // 1.0V. + #[cfg(any(stm32h7))] + while !PWR.csr1().read().actvosrdy() {} + #[cfg(any(stm32h7rs))] + while !PWR.sr1().read().actvosrdy() {} + + // Configure voltage scale. + #[cfg(any(pwr_h5, pwr_h50))] + { + PWR.voscr().modify(|w| { + w.set_vos(match config.voltage_scale { + VoltageScale::Scale0 => crate::pac::pwr::vals::Vos::SCALE0, + VoltageScale::Scale1 => crate::pac::pwr::vals::Vos::SCALE1, + VoltageScale::Scale2 => crate::pac::pwr::vals::Vos::SCALE2, + VoltageScale::Scale3 => crate::pac::pwr::vals::Vos::SCALE3, + }) + }); + while !PWR.vossr().read().vosrdy() {} + } + #[cfg(syscfg_h7)] + { + // in chips without the overdrive bit, we can go from any scale to any scale directly. + PWR.d3cr().modify(|w| { + w.set_vos(match config.voltage_scale { + VoltageScale::Scale0 => crate::pac::pwr::vals::Vos::SCALE0, + VoltageScale::Scale1 => crate::pac::pwr::vals::Vos::SCALE1, + VoltageScale::Scale2 => crate::pac::pwr::vals::Vos::SCALE2, + VoltageScale::Scale3 => crate::pac::pwr::vals::Vos::SCALE3, + }) + }); + while !PWR.d3cr().read().vosrdy() {} + } + #[cfg(pwr_h7rs)] + { + PWR.csr4().modify(|w| w.set_vos(config.voltage_scale)); + while !PWR.csr4().read().vosrdy() {} + } + + #[cfg(syscfg_h7od)] + { + match config.voltage_scale { + VoltageScale::Scale0 => { + // to go to scale0, we must go to Scale1 first... + PWR.d3cr().modify(|w| w.set_vos(crate::pac::pwr::vals::Vos::SCALE1)); + while !PWR.d3cr().read().vosrdy() {} + + // Then enable overdrive. + critical_section::with(|_| pac::SYSCFG.pwrcr().modify(|w| w.set_oden(1))); + while !PWR.d3cr().read().vosrdy() {} + } + _ => { + // for all other scales, we can go directly. + PWR.d3cr().modify(|w| { + w.set_vos(match config.voltage_scale { + VoltageScale::Scale0 => unreachable!(), + VoltageScale::Scale1 => crate::pac::pwr::vals::Vos::SCALE1, + VoltageScale::Scale2 => crate::pac::pwr::vals::Vos::SCALE2, + VoltageScale::Scale3 => crate::pac::pwr::vals::Vos::SCALE3, + }) + }); + while !PWR.d3cr().read().vosrdy() {} + } + } + } + + // Turn on the HSI + match config.hsi { + None => RCC.cr().modify(|w| w.set_hsion(true)), + Some(hsidiv) => RCC.cr().modify(|w| { + w.set_hsidiv(hsidiv); + w.set_hsion(true); + }), + } + while !RCC.cr().read().hsirdy() {} + + // Use the HSI clock as system clock during the actual clock setup + RCC.cfgr().modify(|w| w.set_sw(Sysclk::HSI)); + while RCC.cfgr().read().sws() != Sysclk::HSI {} + + // Configure HSI + let hsi = match config.hsi { + None => None, + Some(hsidiv) => Some(HSI_FREQ / hsidiv), + }; + + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + RCC.cr().modify(|w| { + w.set_hsebyp(hse.mode != HseMode::Oscillator); + #[cfg(any(rcc_h5, rcc_h50, rcc_h7rs))] + w.set_hseext(match hse.mode { + HseMode::Oscillator | HseMode::Bypass => pac::rcc::vals::Hseext::ANALOG, + HseMode::BypassDigital => pac::rcc::vals::Hseext::DIGITAL, + }); + }); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + // Configure HSI48. + let hsi48 = config.hsi48.map(super::init_hsi48); + + // Configure CSI. + RCC.cr().modify(|w| w.set_csion(config.csi)); + let csi = match config.csi { + false => None, + true => { + while !RCC.cr().read().csirdy() {} + Some(CSI_FREQ) + } + }; + + // H7 has shared PLLSRC, check it's equal in all PLLs. + #[cfg(any(stm32h7, stm32h7rs))] + { + let plls = [&config.pll1, &config.pll2, &config.pll3]; + if !super::util::all_equal(plls.into_iter().flatten().map(|p| p.source)) { + panic!("Source must be equal across all enabled PLLs.") + }; + } + + // Configure PLLs. + let pll_input = PllInput { csi, hse, hsi }; + let pll1 = init_pll(0, config.pll1, &pll_input); + let pll2 = init_pll(1, config.pll2, &pll_input); + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + let pll3 = init_pll(2, config.pll3, &pll_input); + + // Configure sysclk + let sys = match config.sys { + Sysclk::HSI => unwrap!(hsi), + Sysclk::HSE => unwrap!(hse), + Sysclk::CSI => unwrap!(csi), + Sysclk::PLL1_P => unwrap!(pll1.p), + _ => unreachable!(), + }; + + // Check limits. + #[cfg(stm32h5)] + let (hclk_max, pclk_max) = match config.voltage_scale { + VoltageScale::Scale0 => (Hertz(250_000_000), Hertz(250_000_000)), + VoltageScale::Scale1 => (Hertz(200_000_000), Hertz(200_000_000)), + VoltageScale::Scale2 => (Hertz(150_000_000), Hertz(150_000_000)), + VoltageScale::Scale3 => (Hertz(100_000_000), Hertz(100_000_000)), + }; + #[cfg(pwr_h7rm0455)] + let (d1cpre_clk_max, hclk_max, pclk_max) = match config.voltage_scale { + VoltageScale::Scale0 => (Hertz(280_000_000), Hertz(280_000_000), Hertz(140_000_000)), + VoltageScale::Scale1 => (Hertz(225_000_000), Hertz(225_000_000), Hertz(112_500_000)), + VoltageScale::Scale2 => (Hertz(160_000_000), Hertz(160_000_000), Hertz(80_000_000)), + VoltageScale::Scale3 => (Hertz(88_000_000), Hertz(88_000_000), Hertz(44_000_000)), + }; + #[cfg(pwr_h7rm0468)] + let (d1cpre_clk_max, hclk_max, pclk_max) = match config.voltage_scale { + VoltageScale::Scale0 => { + let d1cpre_clk_max = if pac::SYSCFG.ur18().read().cpu_freq_boost() { + 550_000_000 + } else { + 520_000_000 + }; + (Hertz(d1cpre_clk_max), Hertz(275_000_000), Hertz(137_500_000)) + } + VoltageScale::Scale1 => (Hertz(400_000_000), Hertz(200_000_000), Hertz(100_000_000)), + VoltageScale::Scale2 => (Hertz(300_000_000), Hertz(150_000_000), Hertz(75_000_000)), + VoltageScale::Scale3 => (Hertz(170_000_000), Hertz(85_000_000), Hertz(42_500_000)), + }; + #[cfg(all(stm32h7, not(any(pwr_h7rm0455, pwr_h7rm0468))))] + let (d1cpre_clk_max, hclk_max, pclk_max) = match config.voltage_scale { + VoltageScale::Scale0 => (Hertz(480_000_000), Hertz(240_000_000), Hertz(120_000_000)), + VoltageScale::Scale1 => (Hertz(400_000_000), Hertz(200_000_000), Hertz(100_000_000)), + VoltageScale::Scale2 => (Hertz(300_000_000), Hertz(150_000_000), Hertz(75_000_000)), + VoltageScale::Scale3 => (Hertz(200_000_000), Hertz(100_000_000), Hertz(50_000_000)), + }; + #[cfg(stm32h7rs)] + let (d1cpre_clk_max, hclk_max, pclk_max) = match config.voltage_scale { + VoltageScale::HIGH => (Hertz(600_000_000), Hertz(300_000_000), Hertz(150_000_000)), + VoltageScale::LOW => (Hertz(400_000_000), Hertz(200_000_000), Hertz(100_000_000)), + }; + + #[cfg(any(stm32h7, stm32h7rs))] + let hclk = { + let d1cpre_clk = sys / config.d1c_pre; + assert!(d1cpre_clk <= d1cpre_clk_max); + sys / config.ahb_pre + }; + #[cfg(stm32h5)] + let hclk = sys / config.ahb_pre; + assert!(hclk <= hclk_max); + + let apb1 = hclk / config.apb1_pre; + let apb1_tim = apb_div_tim(&config.apb1_pre, hclk, config.timer_prescaler); + assert!(apb1 <= pclk_max); + let apb2 = hclk / config.apb2_pre; + let apb2_tim = apb_div_tim(&config.apb2_pre, hclk, config.timer_prescaler); + assert!(apb2 <= pclk_max); + #[cfg(not(stm32h7rs))] + let apb3 = hclk / config.apb3_pre; + #[cfg(not(stm32h7rs))] + assert!(apb3 <= pclk_max); + #[cfg(any(stm32h7, stm32h7rs))] + let apb4 = hclk / config.apb4_pre; + #[cfg(any(stm32h7, stm32h7rs))] + assert!(apb4 <= pclk_max); + #[cfg(stm32h7rs)] + let apb5 = hclk / config.apb5_pre; + #[cfg(stm32h7rs)] + assert!(apb5 <= pclk_max); + + flash_setup(hclk, config.voltage_scale); + + let rtc = config.ls.init(); + + #[cfg(all(stm32h7rs, peri_usb_otg_hs))] + let usb_refck = match config.mux.usbphycsel { + Usbphycsel::HSE => hse, + Usbphycsel::HSE_DIV_2 => hse.map(|hse_val| hse_val / 2u8), + Usbphycsel::PLL3_Q => pll3.q, + _ => None, + }; + #[cfg(all(stm32h7rs, peri_usb_otg_hs))] + let usb_refck_sel = match usb_refck { + Some(clk_val) => match clk_val { + Hertz(16_000_000) => Usbrefcksel::MHZ16, + Hertz(19_200_000) => Usbrefcksel::MHZ19_2, + Hertz(20_000_000) => Usbrefcksel::MHZ20, + Hertz(24_000_000) => Usbrefcksel::MHZ24, + Hertz(26_000_000) => Usbrefcksel::MHZ26, + Hertz(32_000_000) => Usbrefcksel::MHZ32, + _ => panic!("cannot select USBPHYC reference clock with source frequency of {} Hz, must be one of 16, 19.2, 20, 24, 26, 32 MHz", clk_val), + }, + None => Usbrefcksel::MHZ24, + }; + + #[cfg(stm32h7)] + { + RCC.d1cfgr().modify(|w| { + w.set_d1cpre(config.d1c_pre); + w.set_d1ppre(config.apb3_pre); + w.set_hpre(config.ahb_pre); + }); + // Ensure core prescaler value is valid before future lower core voltage + while RCC.d1cfgr().read().d1cpre() != config.d1c_pre {} + + RCC.d2cfgr().modify(|w| { + w.set_d2ppre1(config.apb1_pre); + w.set_d2ppre2(config.apb2_pre); + }); + RCC.d3cfgr().modify(|w| { + w.set_d3ppre(config.apb4_pre); + }); + } + #[cfg(stm32h7rs)] + { + RCC.cdcfgr().write(|w| { + w.set_cpre(config.d1c_pre); + }); + while RCC.cdcfgr().read().cpre() != config.d1c_pre {} + + RCC.bmcfgr().write(|w| { + w.set_bmpre(config.ahb_pre); + }); + while RCC.bmcfgr().read().bmpre() != config.ahb_pre {} + + RCC.apbcfgr().modify(|w| { + w.set_ppre1(config.apb1_pre); + w.set_ppre2(config.apb2_pre); + w.set_ppre4(config.apb4_pre); + w.set_ppre5(config.apb5_pre); + }); + + #[cfg(peri_usb_otg_hs)] + RCC.ahbperckselr().modify(|w| { + w.set_usbrefcksel(usb_refck_sel); + }); + } + #[cfg(stm32h5)] + { + // Set hpre + RCC.cfgr2().modify(|w| w.set_hpre(config.ahb_pre)); + while RCC.cfgr2().read().hpre() != config.ahb_pre {} + + // set ppre + RCC.cfgr2().modify(|w| { + w.set_ppre1(config.apb1_pre); + w.set_ppre2(config.apb2_pre); + w.set_ppre3(config.apb3_pre); + }); + } + + RCC.cfgr().modify(|w| w.set_timpre(config.timer_prescaler.into())); + + RCC.cfgr().modify(|w| w.set_sw(config.sys)); + while RCC.cfgr().read().sws() != config.sys {} + + // Disable HSI if not used + if config.hsi.is_none() { + RCC.cr().modify(|w| w.set_hsion(false)); + } + + // IO compensation cell - Requires CSI clock and SYSCFG + #[cfg(any(stm32h7))] // TODO h5, h7rs + if csi.is_some() { + // Enable the compensation cell, using back-bias voltage code + // provide by the cell. + critical_section::with(|_| { + pac::SYSCFG.cccsr().modify(|w| { + w.set_en(true); + w.set_cs(false); + w.set_hslv(false); + }) + }); + while !pac::SYSCFG.cccsr().read().rdy() {} + } + + config.mux.init(); + + set_clocks!( + sys: Some(sys), + hclk1: Some(hclk), + hclk2: Some(hclk), + hclk3: Some(hclk), + hclk4: Some(hclk), + #[cfg(stm32h7rs)] + hclk5: Some(hclk), + pclk1: Some(apb1), + pclk2: Some(apb2), + #[cfg(not(stm32h7rs))] + pclk3: Some(apb3), + #[cfg(any(stm32h7, stm32h7rs))] + pclk4: Some(apb4), + #[cfg(stm32h7rs)] + pclk5: Some(apb5), + + pclk1_tim: Some(apb1_tim), + pclk2_tim: Some(apb2_tim), + rtc: rtc, + + hsi: hsi, + hsi48: hsi48, + csi: csi, + csi_div_122: csi.map(|c| c / 122u32), + hse: hse, + + lse: None, + lsi: None, + + pll1_q: pll1.q, + pll2_p: pll2.p, + pll2_q: pll2.q, + pll2_r: pll2.r, + #[cfg(stm32h7rs)] + pll2_s: None, // TODO + #[cfg(stm32h7rs)] + pll2_t: None, // TODO + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + pll3_p: pll3.p, + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + pll3_q: pll3.q, + #[cfg(any(rcc_h5, stm32h7, stm32h7rs))] + pll3_r: pll3.r, + + #[cfg(rcc_h50)] + pll3_p: None, + #[cfg(rcc_h50)] + pll3_q: None, + #[cfg(rcc_h50)] + pll3_r: None, + + #[cfg(dsihost)] + dsi_phy: None, // DSI PLL clock not supported, don't call `RccPeripheral::frequency()` in the drivers + + #[cfg(stm32h5)] + audioclk: None, + i2s_ckin: None, + #[cfg(stm32h7rs)] + spdifrx_symb: None, // TODO + #[cfg(stm32h7rs)] + clk48mohci: None, // TODO + #[cfg(stm32h7rs)] + hse_div_2: hse.map(|clk| clk / 2u32), + #[cfg(stm32h7rs)] + usb: Some(Hertz(48_000_000)), + ); +} + +struct PllInput { + hsi: Option, + hse: Option, + csi: Option, +} + +struct PllOutput { + p: Option, + #[allow(dead_code)] + q: Option, + #[allow(dead_code)] + r: Option, +} + +fn init_pll(num: usize, config: Option, input: &PllInput) -> PllOutput { + let Some(config) = config else { + // Stop PLL + RCC.cr().modify(|w| w.set_pllon(num, false)); + while RCC.cr().read().pllrdy(num) {} + + // "To save power when PLL1 is not used, the value of PLL1M must be set to 0."" + #[cfg(any(stm32h7, stm32h7rs))] + RCC.pllckselr().write(|w| w.set_divm(num, PllPreDiv::from_bits(0))); + #[cfg(stm32h5)] + RCC.pllcfgr(num).write(|w| w.set_divm(PllPreDiv::from_bits(0))); + + return PllOutput { + p: None, + q: None, + r: None, + }; + }; + + let in_clk = match config.source { + PllSource::DISABLE => panic!("must not set PllSource::Disable"), + PllSource::HSI => unwrap!(input.hsi), + PllSource::HSE => unwrap!(input.hse), + PllSource::CSI => unwrap!(input.csi), + }; + + let ref_clk = in_clk / config.prediv as u32; + + let ref_range = match ref_clk.0 { + ..=1_999_999 => Pllrge::RANGE1, + ..=3_999_999 => Pllrge::RANGE2, + ..=7_999_999 => Pllrge::RANGE4, + ..=16_000_000 => Pllrge::RANGE8, + x => panic!("pll ref_clk out of range: {} hz", x), + }; + + // The smaller range (150 to 420 MHz) must + // be chosen when the reference clock frequency is lower than 2 MHz. + let wide_allowed = ref_range != Pllrge::RANGE1; + + let vco_clk = ref_clk * config.mul; + let vco_range = if VCO_RANGE.contains(&vco_clk) { + Pllvcosel::MEDIUMVCO + } else if wide_allowed && VCO_WIDE_RANGE.contains(&vco_clk) { + Pllvcosel::WIDEVCO + } else { + panic!("pll vco_clk out of range: {} hz", vco_clk.0) + }; + + let p = config.divp.map(|div| { + if num == 0 { + // on PLL1, DIVP must be even for most series. + // The enum value is 1 less than the divider, so check it's odd. + #[cfg(not(any(pwr_h7rm0468, stm32h7rs)))] + assert!(div.to_bits() % 2 == 1); + #[cfg(pwr_h7rm0468)] + assert!(div.to_bits() % 2 == 1 || div.to_bits() == 0); + } + + vco_clk / div + }); + let q = config.divq.map(|div| vco_clk / div); + let r = config.divr.map(|div| vco_clk / div); + + #[cfg(stm32h5)] + RCC.pllcfgr(num).write(|w| { + w.set_pllsrc(config.source); + w.set_divm(config.prediv); + w.set_pllvcosel(vco_range); + w.set_pllrge(ref_range); + w.set_pllfracen(false); + w.set_pllpen(p.is_some()); + w.set_pllqen(q.is_some()); + w.set_pllren(r.is_some()); + }); + + #[cfg(any(stm32h7, stm32h7rs))] + { + RCC.pllckselr().modify(|w| { + w.set_divm(num, config.prediv); + w.set_pllsrc(config.source); + }); + RCC.pllcfgr().modify(|w| { + w.set_pllvcosel(num, vco_range); + w.set_pllrge(num, ref_range); + w.set_pllfracen(num, false); + w.set_divpen(num, p.is_some()); + w.set_divqen(num, q.is_some()); + w.set_divren(num, r.is_some()); + }); + } + + RCC.plldivr(num).write(|w| { + w.set_plln(config.mul); + w.set_pllp(config.divp.unwrap_or(PllDiv::DIV2)); + w.set_pllq(config.divq.unwrap_or(PllDiv::DIV2)); + w.set_pllr(config.divr.unwrap_or(PllDiv::DIV2)); + }); + + RCC.cr().modify(|w| w.set_pllon(num, true)); + while !RCC.cr().read().pllrdy(num) {} + + PllOutput { p, q, r } +} + +fn flash_setup(clk: Hertz, vos: VoltageScale) { + // RM0481 Rev 1, table 37 + // LATENCY WRHIGHFREQ VOS3 VOS2 VOS1 VOS0 + // 0 0 0 to 20 MHz 0 to 30 MHz 0 to 34 MHz 0 to 42 MHz + // 1 0 20 to 40 MHz 30 to 60 MHz 34 to 68 MHz 42 to 84 MHz + // 2 1 40 to 60 MHz 60 to 90 MHz 68 to 102 MHz 84 to 126 MHz + // 3 1 60 to 80 MHz 90 to 120 MHz 102 to 136 MHz 126 to 168 MHz + // 4 2 80 to 100 MHz 120 to 150 MHz 136 to 170 MHz 168 to 210 MHz + // 5 2 170 to 200 MHz 210 to 250 MHz + #[cfg(stm32h5)] + let (latency, wrhighfreq) = match (vos, clk.0) { + (VoltageScale::Scale0, ..=42_000_000) => (0, 0), + (VoltageScale::Scale0, ..=84_000_000) => (1, 0), + (VoltageScale::Scale0, ..=126_000_000) => (2, 1), + (VoltageScale::Scale0, ..=168_000_000) => (3, 1), + (VoltageScale::Scale0, ..=210_000_000) => (4, 2), + (VoltageScale::Scale0, ..=250_000_000) => (5, 2), + + (VoltageScale::Scale1, ..=34_000_000) => (0, 0), + (VoltageScale::Scale1, ..=68_000_000) => (1, 0), + (VoltageScale::Scale1, ..=102_000_000) => (2, 1), + (VoltageScale::Scale1, ..=136_000_000) => (3, 1), + (VoltageScale::Scale1, ..=170_000_000) => (4, 2), + (VoltageScale::Scale1, ..=200_000_000) => (5, 2), + + (VoltageScale::Scale2, ..=30_000_000) => (0, 0), + (VoltageScale::Scale2, ..=60_000_000) => (1, 0), + (VoltageScale::Scale2, ..=90_000_000) => (2, 1), + (VoltageScale::Scale2, ..=120_000_000) => (3, 1), + (VoltageScale::Scale2, ..=150_000_000) => (4, 2), + + (VoltageScale::Scale3, ..=20_000_000) => (0, 0), + (VoltageScale::Scale3, ..=40_000_000) => (1, 0), + (VoltageScale::Scale3, ..=60_000_000) => (2, 1), + (VoltageScale::Scale3, ..=80_000_000) => (3, 1), + (VoltageScale::Scale3, ..=100_000_000) => (4, 2), + + _ => unreachable!(), + }; + + #[cfg(all(flash_h7, not(pwr_h7rm0468)))] + let (latency, wrhighfreq) = match (vos, clk.0) { + // VOS 0 range VCORE 1.26V - 1.40V + (VoltageScale::Scale0, ..=70_000_000) => (0, 0), + (VoltageScale::Scale0, ..=140_000_000) => (1, 1), + (VoltageScale::Scale0, ..=185_000_000) => (2, 1), + (VoltageScale::Scale0, ..=210_000_000) => (2, 2), + (VoltageScale::Scale0, ..=225_000_000) => (3, 2), + (VoltageScale::Scale0, ..=240_000_000) => (4, 2), + // VOS 1 range VCORE 1.15V - 1.26V + (VoltageScale::Scale1, ..=70_000_000) => (0, 0), + (VoltageScale::Scale1, ..=140_000_000) => (1, 1), + (VoltageScale::Scale1, ..=185_000_000) => (2, 1), + (VoltageScale::Scale1, ..=210_000_000) => (2, 2), + (VoltageScale::Scale1, ..=225_000_000) => (3, 2), + // VOS 2 range VCORE 1.05V - 1.15V + (VoltageScale::Scale2, ..=55_000_000) => (0, 0), + (VoltageScale::Scale2, ..=110_000_000) => (1, 1), + (VoltageScale::Scale2, ..=165_000_000) => (2, 1), + (VoltageScale::Scale2, ..=224_000_000) => (3, 2), + // VOS 3 range VCORE 0.95V - 1.05V + (VoltageScale::Scale3, ..=45_000_000) => (0, 0), + (VoltageScale::Scale3, ..=90_000_000) => (1, 1), + (VoltageScale::Scale3, ..=135_000_000) => (2, 1), + (VoltageScale::Scale3, ..=180_000_000) => (3, 2), + (VoltageScale::Scale3, ..=224_000_000) => (4, 2), + _ => unreachable!(), + }; + + // See RM0468 Rev 3 Table 16. FLASH recommended number of wait + // states and programming delay + #[cfg(all(flash_h7, pwr_h7rm0468))] + let (latency, wrhighfreq) = match (vos, clk.0) { + // VOS 0 range VCORE 1.26V - 1.40V + (VoltageScale::Scale0, ..=70_000_000) => (0, 0), + (VoltageScale::Scale0, ..=140_000_000) => (1, 1), + (VoltageScale::Scale0, ..=210_000_000) => (2, 2), + (VoltageScale::Scale0, ..=275_000_000) => (3, 3), + // VOS 1 range VCORE 1.15V - 1.26V + (VoltageScale::Scale1, ..=67_000_000) => (0, 0), + (VoltageScale::Scale1, ..=133_000_000) => (1, 1), + (VoltageScale::Scale1, ..=200_000_000) => (2, 2), + // VOS 2 range VCORE 1.05V - 1.15V + (VoltageScale::Scale2, ..=50_000_000) => (0, 0), + (VoltageScale::Scale2, ..=100_000_000) => (1, 1), + (VoltageScale::Scale2, ..=150_000_000) => (2, 2), + // VOS 3 range VCORE 0.95V - 1.05V + (VoltageScale::Scale3, ..=35_000_000) => (0, 0), + (VoltageScale::Scale3, ..=70_000_000) => (1, 1), + (VoltageScale::Scale3, ..=85_000_000) => (2, 2), + _ => unreachable!(), + }; + + // See RM0455 Rev 10 Table 16. FLASH recommended number of wait + // states and programming delay + #[cfg(flash_h7ab)] + let (latency, wrhighfreq) = match (vos, clk.0) { + // VOS 0 range VCORE 1.25V - 1.35V + (VoltageScale::Scale0, ..=42_000_000) => (0, 0), + (VoltageScale::Scale0, ..=84_000_000) => (1, 0), + (VoltageScale::Scale0, ..=126_000_000) => (2, 1), + (VoltageScale::Scale0, ..=168_000_000) => (3, 1), + (VoltageScale::Scale0, ..=210_000_000) => (4, 2), + (VoltageScale::Scale0, ..=252_000_000) => (5, 2), + (VoltageScale::Scale0, ..=280_000_000) => (6, 3), + // VOS 1 range VCORE 1.15V - 1.25V + (VoltageScale::Scale1, ..=38_000_000) => (0, 0), + (VoltageScale::Scale1, ..=76_000_000) => (1, 0), + (VoltageScale::Scale1, ..=114_000_000) => (2, 1), + (VoltageScale::Scale1, ..=152_000_000) => (3, 1), + (VoltageScale::Scale1, ..=190_000_000) => (4, 2), + (VoltageScale::Scale1, ..=225_000_000) => (5, 2), + // VOS 2 range VCORE 1.05V - 1.15V + (VoltageScale::Scale2, ..=34) => (0, 0), + (VoltageScale::Scale2, ..=68) => (1, 0), + (VoltageScale::Scale2, ..=102) => (2, 1), + (VoltageScale::Scale2, ..=136) => (3, 1), + (VoltageScale::Scale2, ..=160) => (4, 2), + // VOS 3 range VCORE 0.95V - 1.05V + (VoltageScale::Scale3, ..=22) => (0, 0), + (VoltageScale::Scale3, ..=44) => (1, 0), + (VoltageScale::Scale3, ..=66) => (2, 1), + (VoltageScale::Scale3, ..=88) => (3, 1), + _ => unreachable!(), + }; + #[cfg(flash_h7rs)] + let (latency, wrhighfreq) = match (vos, clk.0) { + // VOS high range VCORE 1.30V - 1.40V + (VoltageScale::HIGH, ..=40_000_000) => (0, 0), + (VoltageScale::HIGH, ..=80_000_000) => (1, 0), + (VoltageScale::HIGH, ..=120_000_000) => (2, 1), + (VoltageScale::HIGH, ..=160_000_000) => (3, 1), + (VoltageScale::HIGH, ..=200_000_000) => (4, 2), + (VoltageScale::HIGH, ..=240_000_000) => (5, 2), + (VoltageScale::HIGH, ..=280_000_000) => (6, 3), + (VoltageScale::HIGH, ..=320_000_000) => (7, 3), + // VOS low range VCORE 1.15V - 1.26V + (VoltageScale::LOW, ..=36_000_000) => (0, 0), + (VoltageScale::LOW, ..=72_000_000) => (1, 0), + (VoltageScale::LOW, ..=108_000_000) => (2, 1), + (VoltageScale::LOW, ..=144_000_000) => (3, 1), + (VoltageScale::LOW, ..=180_000_000) => (4, 2), + (VoltageScale::LOW, ..=216_000_000) => (5, 2), + _ => unreachable!(), + }; + + debug!("flash: latency={} wrhighfreq={}", latency, wrhighfreq); + + FLASH.acr().write(|w| { + w.set_wrhighfreq(wrhighfreq); + w.set_latency(latency); + }); + while FLASH.acr().read().latency() != latency {} +} diff --git a/embassy/embassy-stm32/src/rcc/hsi48.rs b/embassy/embassy-stm32/src/rcc/hsi48.rs new file mode 100644 index 0000000..efabd05 --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/hsi48.rs @@ -0,0 +1,62 @@ +#![allow(unused)] + +use crate::pac::crs::vals::Syncsrc; +use crate::pac::{CRS, RCC}; +use crate::rcc::{self, SealedRccPeripheral}; +use crate::time::Hertz; + +/// HSI48 speed +pub const HSI48_FREQ: Hertz = Hertz(48_000_000); + +/// Configuration for the HSI48 clock +#[derive(Clone, Copy, Debug)] +pub struct Hsi48Config { + /// Enable CRS Sync from USB Start Of Frame (SOF) events. + /// Required if HSI48 is going to be used as USB clock. + /// + /// Other use cases of CRS are not supported yet. + pub sync_from_usb: bool, +} + +impl Default for Hsi48Config { + fn default() -> Self { + Self { sync_from_usb: false } + } +} + +pub(crate) fn init_hsi48(config: Hsi48Config) -> Hertz { + // Enable VREFINT reference for HSI48 oscillator + #[cfg(stm32l0)] + crate::pac::SYSCFG.cfgr3().modify(|w| { + w.set_enref_hsi48(true); + w.set_en_vrefint(true); + }); + + // Enable HSI48 + #[cfg(not(any(stm32u5, stm32g0, stm32h5, stm32h7, stm32h7rs, stm32u5, stm32wba, stm32f0)))] + let r = RCC.crrcr(); + #[cfg(any(stm32u5, stm32g0, stm32h5, stm32h7, stm32h7rs, stm32u5, stm32wba))] + let r = RCC.cr(); + #[cfg(any(stm32f0))] + let r = RCC.cr2(); + + r.modify(|w| w.set_hsi48on(true)); + while r.read().hsi48rdy() == false {} + + if config.sync_from_usb { + rcc::enable_and_reset::(); + + CRS.cfgr().modify(|w| { + w.set_syncsrc(Syncsrc::USB); + }); + + // These are the correct settings for standard USB operation. If other settings + // are needed there will need to be additional config options for the CRS. + crate::pac::CRS.cr().modify(|w| { + w.set_autotrimen(true); + w.set_cen(true); + }); + } + + HSI48_FREQ +} diff --git a/embassy/embassy-stm32/src/rcc/l.rs b/embassy/embassy-stm32/src/rcc/l.rs new file mode 100644 index 0000000..8223a65 --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/l.rs @@ -0,0 +1,673 @@ +#[cfg(any(stm32l0, stm32l1))] +pub use crate::pac::pwr::vals::Vos as VoltageScale; +use crate::pac::rcc::regs::Cfgr; +#[cfg(any(stm32wb, stm32wl))] +pub use crate::pac::rcc::vals::Hsepre as HsePrescaler; +pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Msirange as MSIRange, Ppre as APBPrescaler, Sw as Sysclk}; +use crate::pac::{FLASH, RCC}; +use crate::rcc::LSI_FREQ; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(16_000_000); + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1) + Bypass, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, + /// HSE prescaler + #[cfg(any(stm32wb, stm32wl))] + pub prescaler: HsePrescaler, +} + +/// Clocks configuration +#[derive(Clone, Copy)] +pub struct Config { + // base clock sources + pub msi: Option, + pub hsi: bool, + pub hse: Option, + #[cfg(crs)] + pub hsi48: Option, + + // pll + pub pll: Option, + #[cfg(any(stm32l4, stm32l5, stm32wb))] + pub pllsai1: Option, + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + pub pllsai2: Option, + + // sysclk, buses. + pub sys: Sysclk, + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + #[cfg(not(stm32u0))] + pub apb2_pre: APBPrescaler, + #[cfg(any(stm32wl5x, stm32wb))] + pub core2_ahb_pre: AHBPrescaler, + #[cfg(any(stm32wl, stm32wb))] + pub shared_ahb_pre: AHBPrescaler, + + // low speed LSI/LSE/RTC + pub ls: super::LsConfig, + + #[cfg(any(stm32l0, stm32l1))] + pub voltage_scale: VoltageScale, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, +} + +impl Default for Config { + #[inline] + fn default() -> Config { + Config { + hse: None, + hsi: false, + msi: Some(MSIRange::RANGE4M), + sys: Sysclk::MSI, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + #[cfg(not(stm32u0))] + apb2_pre: APBPrescaler::DIV1, + #[cfg(any(stm32wl5x, stm32wb))] + core2_ahb_pre: AHBPrescaler::DIV1, + #[cfg(any(stm32wl, stm32wb))] + shared_ahb_pre: AHBPrescaler::DIV1, + pll: None, + #[cfg(any(stm32l4, stm32l5, stm32wb))] + pllsai1: None, + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + pllsai2: None, + #[cfg(crs)] + hsi48: Some(Default::default()), + ls: Default::default(), + #[cfg(any(stm32l0, stm32l1))] + voltage_scale: VoltageScale::RANGE1, + mux: Default::default(), + } + } +} + +#[cfg(stm32wb)] +pub const WPAN_DEFAULT: Config = Config { + hse: Some(Hse { + freq: Hertz(32_000_000), + mode: HseMode::Oscillator, + prescaler: HsePrescaler::DIV1, + }), + sys: Sysclk::PLL1_R, + #[cfg(crs)] + hsi48: Some(super::Hsi48Config { sync_from_usb: false }), + msi: None, + hsi: false, + + ls: super::LsConfig::default_lse(), + + pll: Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL12, + divp: Some(PllPDiv::DIV3), // 32 / 2 * 12 / 3 = 64Mhz + divq: Some(PllQDiv::DIV4), // 32 / 2 * 12 / 4 = 48Mhz + divr: Some(PllRDiv::DIV3), // 32 / 2 * 12 / 3 = 64Mhz + }), + pllsai1: None, + + ahb_pre: AHBPrescaler::DIV1, + core2_ahb_pre: AHBPrescaler::DIV2, + shared_ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + apb2_pre: APBPrescaler::DIV1, + + mux: super::mux::ClockMux::default(), +}; + +fn msi_enable(range: MSIRange) { + #[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl, stm32u0))] + RCC.cr().modify(|w| { + #[cfg(not(stm32wb))] + w.set_msirgsel(crate::pac::rcc::vals::Msirgsel::CR); + w.set_msirange(range); + w.set_msipllen(false); + }); + #[cfg(any(stm32l0, stm32l1))] + RCC.icscr().modify(|w| w.set_msirange(range)); + + RCC.cr().modify(|w| w.set_msion(true)); + while !RCC.cr().read().msirdy() {} +} + +pub(crate) unsafe fn init(config: Config) { + // Switch to MSI to prevent problems with PLL configuration. + if !RCC.cr().read().msion() { + // Turn on MSI and configure it to 4MHz. + msi_enable(MSIRange::RANGE4M) + } + if RCC.cfgr().read().sws() != Sysclk::MSI { + // Set MSI as a clock source, reset prescalers. + RCC.cfgr().write_value(Cfgr::default()); + // Wait for clock switch status bits to change. + while RCC.cfgr().read().sws() != Sysclk::MSI {} + } + + #[cfg(stm32wl)] + { + // Set max latency + FLASH.acr().modify(|w| w.set_prften(true)); + FLASH.acr().modify(|w| w.set_latency(2)); + } + + // Set voltage scale + #[cfg(any(stm32l0, stm32l1))] + { + while crate::pac::PWR.csr().read().vosf() {} + crate::pac::PWR.cr().write(|w| w.set_vos(config.voltage_scale)); + while crate::pac::PWR.csr().read().vosf() {} + } + + #[cfg(stm32l5)] + crate::pac::PWR.cr1().modify(|w| { + w.set_vos(crate::pac::pwr::vals::Vos::RANGE0); + }); + + let rtc = config.ls.init(); + + let lse = config.ls.lse.map(|l| l.frequency); + let lsi = config.ls.lsi.then_some(LSI_FREQ); + + let msi = config.msi.map(|range| { + msi_enable(range); + msirange_to_hertz(range) + }); + + // If LSE is enabled and the right freq, enable calibration of MSI + #[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))] + if config.ls.lse.map(|x| x.frequency) == Some(Hertz(32_768)) { + RCC.cr().modify(|w| w.set_msipllen(true)); + } + + let hsi = config.hsi.then(|| { + RCC.cr().modify(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + + HSI_FREQ + }); + + let hse = config.hse.map(|hse| { + RCC.cr().modify(|w| { + #[cfg(stm32wl)] + w.set_hsebyppwr(hse.mode == HseMode::Bypass); + #[cfg(not(stm32wl))] + w.set_hsebyp(hse.mode == HseMode::Bypass); + w.set_hseon(true); + }); + while !RCC.cr().read().hserdy() {} + + hse.freq + }); + + #[cfg(crs)] + let hsi48 = config.hsi48.map(|config| super::init_hsi48(config)); + #[cfg(not(crs))] + let hsi48: Option = None; + + let _plls = [ + &config.pll, + #[cfg(any(stm32l4, stm32l5, stm32wb))] + &config.pllsai1, + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + &config.pllsai2, + ]; + + // L4 has shared PLLSRC, PLLM, check it's equal in all PLLs. + #[cfg(all(stm32l4, not(rcc_l4plus)))] + match super::util::get_equal(_plls.into_iter().flatten().map(|p| (p.source, p.prediv))) { + Err(()) => panic!("Source must be equal across all enabled PLLs."), + Ok(None) => {} + Ok(Some((source, prediv))) => RCC.pllcfgr().write(|w| { + w.set_pllm(prediv); + w.set_pllsrc(source); + }), + }; + + // L4+, WL has shared PLLSRC, check it's equal in all PLLs. + #[cfg(any(rcc_l4plus, stm32wl))] + match super::util::get_equal(_plls.into_iter().flatten().map(|p| p.source)) { + Err(()) => panic!("Source must be equal across all enabled PLLs."), + Ok(None) => {} + Ok(Some(source)) => RCC.pllcfgr().write(|w| { + w.set_pllsrc(source); + }), + }; + + let pll_input = PllInput { + hse, + hsi, + #[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl, stm32u0))] + msi, + }; + let pll = init_pll(PllInstance::Pll, config.pll, &pll_input); + #[cfg(any(stm32l4, stm32l5, stm32wb))] + let pllsai1 = init_pll(PllInstance::Pllsai1, config.pllsai1, &pll_input); + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + let pllsai2 = init_pll(PllInstance::Pllsai2, config.pllsai2, &pll_input); + + let sys_clk = match config.sys { + Sysclk::HSE => hse.unwrap(), + Sysclk::HSI => hsi.unwrap(), + Sysclk::MSI => msi.unwrap(), + Sysclk::PLL1_R => pll.r.unwrap(), + #[cfg(stm32u0)] + Sysclk::LSI | Sysclk::LSE => todo!(), + #[cfg(stm32u0)] + Sysclk::_RESERVED_6 | Sysclk::_RESERVED_7 => unreachable!(), + }; + + #[cfg(rcc_l4plus)] + assert!(sys_clk.0 <= 120_000_000); + #[cfg(all(stm32l4, not(rcc_l4plus)))] + assert!(sys_clk.0 <= 80_000_000); + + let hclk1 = sys_clk / config.ahb_pre; + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk1, config.apb1_pre); + #[cfg(not(stm32u0))] + let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk1, config.apb2_pre); + #[cfg(any(stm32l4, stm32l5, stm32wlex))] + let hclk2 = hclk1; + #[cfg(any(stm32wl5x, stm32wb))] + let hclk2 = sys_clk / config.core2_ahb_pre; + #[cfg(any(stm32l4, stm32l5, stm32wlex))] + let hclk3 = hclk1; + #[cfg(any(stm32wl5x, stm32wb))] + let hclk3 = sys_clk / config.shared_ahb_pre; + + // Set flash wait states + #[cfg(any(stm32l0, stm32l1))] + let latency = match (config.voltage_scale, sys_clk.0) { + (VoltageScale::RANGE1, ..=16_000_000) => false, + (VoltageScale::RANGE2, ..=8_000_000) => false, + (VoltageScale::RANGE3, ..=4_200_000) => false, + _ => true, + }; + #[cfg(stm32l4)] + let latency = match hclk1.0 { + 0..=16_000_000 => 0, + 0..=32_000_000 => 1, + 0..=48_000_000 => 2, + 0..=64_000_000 => 3, + _ => 4, + }; + #[cfg(stm32l5)] + let latency = match hclk1.0 { + // VCORE Range 0 (performance), others TODO + 0..=20_000_000 => 0, + 0..=40_000_000 => 1, + 0..=60_000_000 => 2, + 0..=80_000_000 => 3, + 0..=100_000_000 => 4, + _ => 5, + }; + #[cfg(stm32wl)] + let latency = match hclk3.0 { + // VOS RANGE1, others TODO. + ..=18_000_000 => 0, + ..=36_000_000 => 1, + _ => 2, + }; + #[cfg(stm32wb)] + let latency = match hclk3.0 { + // VOS RANGE1, others TODO. + ..=18_000_000 => 0, + ..=36_000_000 => 1, + ..=54_000_000 => 2, + ..=64_000_000 => 3, + _ => 4, + }; + #[cfg(stm32u0)] + let latency = match hclk1.0 { + // VOS RANGE1, others TODO. + ..=24_000_000 => 0, + ..=48_000_000 => 1, + _ => 2, + }; + + #[cfg(stm32l1)] + FLASH.acr().write(|w| w.set_acc64(true)); + #[cfg(not(stm32l5))] + FLASH.acr().modify(|w| w.set_prften(true)); + FLASH.acr().modify(|w| w.set_latency(latency)); + while FLASH.acr().read().latency() != latency {} + + RCC.cfgr().modify(|w| { + w.set_sw(config.sys); + w.set_hpre(config.ahb_pre); + #[cfg(stm32u0)] + w.set_ppre(config.apb1_pre); + #[cfg(not(stm32u0))] + w.set_ppre1(config.apb1_pre); + #[cfg(not(stm32u0))] + w.set_ppre2(config.apb2_pre); + }); + while RCC.cfgr().read().sws() != config.sys {} + + #[cfg(any(stm32wl, stm32wb))] + { + RCC.extcfgr().modify(|w| { + w.set_shdhpre(config.shared_ahb_pre); + #[cfg(any(stm32wl5x, stm32wb))] + w.set_c2hpre(config.core2_ahb_pre); + }); + while !RCC.extcfgr().read().shdhpref() {} + #[cfg(any(stm32wl5x, stm32wb))] + while !RCC.extcfgr().read().c2hpref() {} + } + + config.mux.init(); + + set_clocks!( + sys: Some(sys_clk), + hclk1: Some(hclk1), + #[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))] + hclk2: Some(hclk2), + #[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))] + hclk3: Some(hclk3), + pclk1: Some(pclk1), + #[cfg(not(stm32u0))] + pclk2: Some(pclk2), + pclk1_tim: Some(pclk1_tim), + #[cfg(not(stm32u0))] + pclk2_tim: Some(pclk2_tim), + #[cfg(stm32wl)] + pclk3: Some(hclk3), + hsi: hsi, + hse: hse, + msi: msi, + hsi48: hsi48, + + #[cfg(any(stm32l0, stm32l1))] + pll1_vco_div_2: pll.vco.map(|c| c/2u32), + + #[cfg(not(any(stm32l0, stm32l1)))] + pll1_p: pll.p, + #[cfg(not(any(stm32l0, stm32l1)))] + pll1_q: pll.q, + pll1_r: pll.r, + + #[cfg(any(stm32l4, stm32l5, stm32wb))] + pllsai1_p: pllsai1.p, + #[cfg(any(stm32l4, stm32l5, stm32wb))] + pllsai1_q: pllsai1.q, + #[cfg(any(stm32l4, stm32l5, stm32wb))] + pllsai1_r: pllsai1.r, + + #[cfg(not(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5)))] + pllsai2_p: None, + #[cfg(not(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5)))] + pllsai2_q: None, + #[cfg(not(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5)))] + pllsai2_r: None, + + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + pllsai2_p: pllsai2.p, + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + pllsai2_q: pllsai2.q, + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + pllsai2_r: pllsai2.r, + + #[cfg(dsihost)] + dsi_phy: None, // DSI PLL clock not supported, don't call `RccPeripheral::frequency()` in the drivers + + rtc: rtc, + lse: lse, + lsi: lsi, + + // TODO + sai1_extclk: None, + sai2_extclk: None, + ); +} + +#[cfg(any(stm32l0, stm32l1))] +fn msirange_to_hertz(range: MSIRange) -> Hertz { + Hertz(32_768 * (1 << (range as u8 + 1))) +} + +#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl, stm32u0))] +fn msirange_to_hertz(range: MSIRange) -> Hertz { + match range { + MSIRange::RANGE100K => Hertz(100_000), + MSIRange::RANGE200K => Hertz(200_000), + MSIRange::RANGE400K => Hertz(400_000), + MSIRange::RANGE800K => Hertz(800_000), + MSIRange::RANGE1M => Hertz(1_000_000), + MSIRange::RANGE2M => Hertz(2_000_000), + MSIRange::RANGE4M => Hertz(4_000_000), + MSIRange::RANGE8M => Hertz(8_000_000), + MSIRange::RANGE16M => Hertz(16_000_000), + MSIRange::RANGE24M => Hertz(24_000_000), + MSIRange::RANGE32M => Hertz(32_000_000), + MSIRange::RANGE48M => Hertz(48_000_000), + _ => unreachable!(), + } +} + +#[derive(PartialEq, Eq, Clone, Copy)] +enum PllInstance { + Pll, + #[cfg(any(stm32l4, stm32l5, stm32wb))] + Pllsai1, + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + Pllsai2, +} + +fn pll_enable(instance: PllInstance, enabled: bool) { + match instance { + PllInstance::Pll => { + RCC.cr().modify(|w| w.set_pllon(enabled)); + while RCC.cr().read().pllrdy() != enabled {} + } + #[cfg(any(stm32l4, stm32l5, stm32wb))] + PllInstance::Pllsai1 => { + RCC.cr().modify(|w| w.set_pllsai1on(enabled)); + while RCC.cr().read().pllsai1rdy() != enabled {} + } + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + PllInstance::Pllsai2 => { + RCC.cr().modify(|w| w.set_pllsai2on(enabled)); + while RCC.cr().read().pllsai2rdy() != enabled {} + } + } +} + +pub use pll::*; + +#[cfg(any(stm32l0, stm32l1))] +mod pll { + use super::{pll_enable, PllInstance}; + pub use crate::pac::rcc::vals::{Plldiv as PllDiv, Pllmul as PllMul, Pllsrc as PllSource}; + use crate::pac::RCC; + use crate::time::Hertz; + + #[derive(Clone, Copy)] + pub struct Pll { + /// PLL source + pub source: PllSource, + + /// PLL multiplication factor. + pub mul: PllMul, + + /// PLL main output division factor. + pub div: PllDiv, + } + + pub(super) struct PllInput { + pub hsi: Option, + pub hse: Option, + } + + #[allow(unused)] + #[derive(Default)] + pub(super) struct PllOutput { + pub r: Option, + pub vco: Option, + } + + pub(super) fn init_pll(instance: PllInstance, config: Option, input: &PllInput) -> PllOutput { + // Disable PLL + pll_enable(instance, false); + + let Some(pll) = config else { return PllOutput::default() }; + + let pll_src = match pll.source { + PllSource::HSE => unwrap!(input.hse), + PllSource::HSI => unwrap!(input.hsi), + }; + + let vco_freq = pll_src * pll.mul; + + let r = vco_freq / pll.div; + + assert!(r <= Hertz(32_000_000)); + + RCC.cfgr().write(move |w| { + w.set_pllmul(pll.mul); + w.set_plldiv(pll.div); + w.set_pllsrc(pll.source); + }); + + // Enable PLL + pll_enable(instance, true); + + PllOutput { + r: Some(r), + vco: Some(vco_freq), + } + } +} + +#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl, stm32u0))] +mod pll { + use super::{pll_enable, PllInstance}; + pub use crate::pac::rcc::vals::{ + Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv, Pllr as PllRDiv, Pllsrc as PllSource, + }; + use crate::pac::RCC; + use crate::time::Hertz; + + #[derive(Clone, Copy)] + pub struct Pll { + /// PLL source + pub source: PllSource, + + /// PLL pre-divider (DIVM). + pub prediv: PllPreDiv, + + /// PLL multiplication factor. + pub mul: PllMul, + + /// PLL P division factor. If None, PLL P output is disabled. + pub divp: Option, + /// PLL Q division factor. If None, PLL Q output is disabled. + pub divq: Option, + /// PLL R division factor. If None, PLL R output is disabled. + pub divr: Option, + } + + pub(super) struct PllInput { + pub hsi: Option, + pub hse: Option, + pub msi: Option, + } + + #[allow(unused)] + #[derive(Default)] + pub(super) struct PllOutput { + pub p: Option, + pub q: Option, + pub r: Option, + } + + pub(super) fn init_pll(instance: PllInstance, config: Option, input: &PllInput) -> PllOutput { + // Disable PLL + pll_enable(instance, false); + + let Some(pll) = config else { return PllOutput::default() }; + + let pll_src = match pll.source { + PllSource::DISABLE => panic!("must not select PLL source as DISABLE"), + PllSource::HSE => unwrap!(input.hse), + PllSource::HSI => unwrap!(input.hsi), + PllSource::MSI => unwrap!(input.msi), + }; + + let vco_freq = pll_src / pll.prediv * pll.mul; + + let p = pll.divp.map(|div| vco_freq / div); + let q = pll.divq.map(|div| vco_freq / div); + let r = pll.divr.map(|div| vco_freq / div); + + #[cfg(stm32l5)] + if instance == PllInstance::Pllsai2 { + assert!(q.is_none(), "PLLSAI2_Q is not available on L5"); + assert!(r.is_none(), "PLLSAI2_R is not available on L5"); + } + + macro_rules! write_fields { + ($w:ident) => { + $w.set_plln(pll.mul); + if let Some(divp) = pll.divp { + $w.set_pllp(divp); + $w.set_pllpen(true); + } + if let Some(divq) = pll.divq { + $w.set_pllq(divq); + $w.set_pllqen(true); + } + if let Some(divr) = pll.divr { + $w.set_pllr(divr); + $w.set_pllren(true); + } + }; + } + + match instance { + PllInstance::Pll => RCC.pllcfgr().write(|w| { + w.set_pllm(pll.prediv); + w.set_pllsrc(pll.source); + write_fields!(w); + }), + #[cfg(any(stm32l4, stm32l5, stm32wb))] + PllInstance::Pllsai1 => RCC.pllsai1cfgr().write(|w| { + #[cfg(any(rcc_l4plus, stm32l5))] + w.set_pllm(pll.prediv); + #[cfg(stm32l5)] + w.set_pllsrc(pll.source); + write_fields!(w); + }), + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + PllInstance::Pllsai2 => RCC.pllsai2cfgr().write(|w| { + #[cfg(any(rcc_l4plus, stm32l5))] + w.set_pllm(pll.prediv); + #[cfg(stm32l5)] + w.set_pllsrc(pll.source); + write_fields!(w); + }), + } + + // Enable PLL + pll_enable(instance, true); + + PllOutput { p, q, r } + } +} diff --git a/embassy/embassy-stm32/src/rcc/mco.rs b/embassy/embassy-stm32/src/rcc/mco.rs new file mode 100644 index 0000000..d1ce14c --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/mco.rs @@ -0,0 +1,109 @@ +use core::marker::PhantomData; + +use embassy_hal_internal::into_ref; + +use crate::gpio::{AfType, OutputType, Speed}; +#[cfg(not(any(stm32f1, rcc_f0v1, rcc_f3v1, rcc_f37)))] +pub use crate::pac::rcc::vals::Mcopre as McoPrescaler; +#[cfg(not(any( + rcc_f2, + rcc_f410, + rcc_f4, + rcc_f7, + rcc_h50, + rcc_h5, + rcc_h7ab, + rcc_h7rm0433, + rcc_h7, + rcc_h7rs +)))] +pub use crate::pac::rcc::vals::Mcosel as McoSource; +#[cfg(any( + rcc_f2, + rcc_f410, + rcc_f4, + rcc_f7, + rcc_h50, + rcc_h5, + rcc_h7ab, + rcc_h7rm0433, + rcc_h7, + rcc_h7rs +))] +pub use crate::pac::rcc::vals::{Mco1sel as Mco1Source, Mco2sel as Mco2Source}; +use crate::pac::RCC; +use crate::{peripherals, Peripheral}; + +#[cfg(any(stm32f1, rcc_f0v1, rcc_f3v1, rcc_f37))] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum McoPrescaler { + DIV1, +} + +pub(crate) trait SealedMcoInstance {} + +#[allow(private_bounds)] +pub trait McoInstance: SealedMcoInstance + 'static { + type Source; + + #[doc(hidden)] + unsafe fn _apply_clock_settings(source: Self::Source, prescaler: super::McoPrescaler); +} + +pin_trait!(McoPin, McoInstance); + +macro_rules! impl_peri { + ($peri:ident, $source:ident, $set_source:ident, $set_prescaler:ident) => { + impl SealedMcoInstance for peripherals::$peri {} + impl McoInstance for peripherals::$peri { + type Source = $source; + + unsafe fn _apply_clock_settings(source: Self::Source, _prescaler: McoPrescaler) { + #[cfg(not(any(stm32u5, stm32wba)))] + let r = RCC.cfgr(); + #[cfg(any(stm32u5, stm32wba))] + let r = RCC.cfgr1(); + + r.modify(|w| { + w.$set_source(source); + #[cfg(not(any(stm32f1, rcc_f0v1, rcc_f3v1, rcc_f37)))] + w.$set_prescaler(_prescaler); + }); + } + } + }; +} + +#[cfg(any(rcc_c0, rcc_g0, rcc_u0))] +#[allow(unused_imports)] +use self::{McoSource as Mco1Source, McoSource as Mco2Source}; + +#[cfg(mco)] +impl_peri!(MCO, McoSource, set_mcosel, set_mcopre); +#[cfg(mco1)] +impl_peri!(MCO1, Mco1Source, set_mco1sel, set_mco1pre); +#[cfg(mco2)] +impl_peri!(MCO2, Mco2Source, set_mco2sel, set_mco2pre); + +pub struct Mco<'d, T: McoInstance> { + phantom: PhantomData<&'d mut T>, +} + +impl<'d, T: McoInstance> Mco<'d, T> { + /// Create a new MCO instance. + pub fn new( + _peri: impl Peripheral

+ 'd, + pin: impl Peripheral

> + 'd, + source: T::Source, + prescaler: McoPrescaler, + ) -> Self { + into_ref!(pin); + + critical_section::with(|_| unsafe { + T::_apply_clock_settings(source, prescaler); + pin.set_as_af(pin.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + }); + + Self { phantom: PhantomData } + } +} diff --git a/embassy/embassy-stm32/src/rcc/mod.rs b/embassy/embassy-stm32/src/rcc/mod.rs new file mode 100644 index 0000000..4f43d37 --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/mod.rs @@ -0,0 +1,371 @@ +//! Reset and Clock Control (RCC) + +#![macro_use] +#![allow(missing_docs)] // TODO + +use core::mem::MaybeUninit; + +mod bd; +pub use bd::*; + +#[cfg(any(mco, mco1, mco2))] +mod mco; +use critical_section::CriticalSection; +#[cfg(any(mco, mco1, mco2))] +pub use mco::*; + +#[cfg(crs)] +mod hsi48; +#[cfg(crs)] +pub use hsi48::*; + +#[cfg_attr(any(stm32f0, stm32f1, stm32f3), path = "f013.rs")] +#[cfg_attr(any(stm32f2, stm32f4, stm32f7), path = "f247.rs")] +#[cfg_attr(stm32c0, path = "c0.rs")] +#[cfg_attr(stm32g0, path = "g0.rs")] +#[cfg_attr(stm32g4, path = "g4.rs")] +#[cfg_attr(any(stm32h5, stm32h7, stm32h7rs), path = "h.rs")] +#[cfg_attr(any(stm32l0, stm32l1, stm32l4, stm32l5, stm32wb, stm32wl, stm32u0), path = "l.rs")] +#[cfg_attr(stm32u5, path = "u5.rs")] +#[cfg_attr(stm32wba, path = "wba.rs")] +mod _version; + +pub use _version::*; +use stm32_metapac::RCC; + +pub use crate::_generated::{mux, Clocks}; +use crate::time::Hertz; + +#[cfg(feature = "low-power")] +/// Must be written within a critical section +/// +/// May be read without a critical section +pub(crate) static mut REFCOUNT_STOP1: u32 = 0; + +#[cfg(feature = "low-power")] +/// Must be written within a critical section +/// +/// May be read without a critical section +pub(crate) static mut REFCOUNT_STOP2: u32 = 0; + +#[cfg(not(feature = "_dual-core"))] +/// Frozen clock frequencies +/// +/// The existence of this value indicates that the clock configuration can no longer be changed +static mut CLOCK_FREQS: MaybeUninit = MaybeUninit::uninit(); + +#[cfg(feature = "_dual-core")] +static CLOCK_FREQS_PTR: core::sync::atomic::AtomicPtr> = + core::sync::atomic::AtomicPtr::new(core::ptr::null_mut()); + +#[cfg(feature = "_dual-core")] +pub(crate) fn set_freqs_ptr(freqs: *mut MaybeUninit) { + CLOCK_FREQS_PTR.store(freqs, core::sync::atomic::Ordering::SeqCst); +} + +#[cfg(not(feature = "_dual-core"))] +/// Sets the clock frequencies +/// +/// Safety: Sets a mutable global. +pub(crate) unsafe fn set_freqs(freqs: Clocks) { + debug!("rcc: {:?}", freqs); + CLOCK_FREQS = MaybeUninit::new(freqs); +} + +#[cfg(feature = "_dual-core")] +/// Sets the clock frequencies +/// +/// Safety: Sets a mutable global. +pub(crate) unsafe fn set_freqs(freqs: Clocks) { + debug!("rcc: {:?}", freqs); + CLOCK_FREQS_PTR + .load(core::sync::atomic::Ordering::SeqCst) + .write(MaybeUninit::new(freqs)); +} + +#[cfg(not(feature = "_dual-core"))] +/// Safety: Reads a mutable global. +pub(crate) unsafe fn get_freqs() -> &'static Clocks { + (*core::ptr::addr_of_mut!(CLOCK_FREQS)).assume_init_ref() +} + +#[cfg(feature = "_dual-core")] +/// Safety: Reads a mutable global. +pub(crate) unsafe fn get_freqs() -> &'static Clocks { + unwrap!(CLOCK_FREQS_PTR.load(core::sync::atomic::Ordering::SeqCst).as_ref()).assume_init_ref() +} + +pub(crate) trait SealedRccPeripheral { + fn frequency() -> Hertz; + const RCC_INFO: RccInfo; +} + +#[allow(private_bounds)] +pub trait RccPeripheral: SealedRccPeripheral + 'static {} + +/// Runtime information necessary to reset, enable and disable a peripheral. +pub(crate) struct RccInfo { + /// Offset in 32-bit words of the xxxRSTR register into the RCC register block, or 0xff if the + /// peripheral has no reset bit (we don't use an `Option` to save one byte of storage). + reset_offset_or_0xff: u8, + /// Position of the xxxRST bit within the xxxRSTR register (0..=31). + reset_bit: u8, + /// Offset in 32-bit words of the xxxENR register into the RCC register block. + enable_offset: u8, + /// Position of the xxxEN bit within the xxxENR register (0..=31). + enable_bit: u8, + /// If this peripheral shares the same xxxRSTR bit and xxxEN bit with other peripherals, we + /// maintain a refcount in `crate::_generated::REFCOUNTS` at this index. If the bit is not + /// shared, this is 0xff (we don't use an `Option` to save one byte of storage). + refcount_idx_or_0xff: u8, + /// Stop mode of the peripheral, used to maintain `REFCOUNT_STOP1` and `REFCOUNT_STOP2`. + #[cfg(feature = "low-power")] + stop_mode: StopMode, +} + +#[cfg(feature = "low-power")] +#[allow(dead_code)] +pub(crate) enum StopMode { + Standby, + Stop2, + Stop1, +} + +impl RccInfo { + /// Safety: + /// - `reset_offset_and_bit`, if set, must correspond to valid xxxRST bit + /// - `enable_offset_and_bit` must correspond to valid xxxEN bit + /// - `refcount_idx`, if set, must correspond to valid refcount in `_generated::REFCOUNTS` + /// - `stop_mode` must be valid + pub(crate) const unsafe fn new( + reset_offset_and_bit: Option<(u8, u8)>, + enable_offset_and_bit: (u8, u8), + refcount_idx: Option, + #[cfg(feature = "low-power")] stop_mode: StopMode, + ) -> Self { + let (reset_offset_or_0xff, reset_bit) = match reset_offset_and_bit { + Some((offset, bit)) => (offset, bit), + None => (0xff, 0xff), + }; + let (enable_offset, enable_bit) = enable_offset_and_bit; + let refcount_idx_or_0xff = match refcount_idx { + Some(idx) => idx, + None => 0xff, + }; + Self { + reset_offset_or_0xff, + reset_bit, + enable_offset, + enable_bit, + refcount_idx_or_0xff, + #[cfg(feature = "low-power")] + stop_mode, + } + } + + // TODO: should this be `unsafe`? + pub(crate) fn enable_and_reset_with_cs(&self, _cs: CriticalSection) { + if self.refcount_idx_or_0xff != 0xff { + let refcount_idx = self.refcount_idx_or_0xff as usize; + + // Use .get_mut instead of []-operator so that we control how bounds checks happen. + // Otherwise, core::fmt will be pulled in here in order to format the integer in the + // out-of-bounds error. + if let Some(refcount) = + unsafe { (*core::ptr::addr_of_mut!(crate::_generated::REFCOUNTS)).get_mut(refcount_idx) } + { + *refcount += 1; + if *refcount > 1 { + return; + } + } else { + panic!("refcount_idx out of bounds: {}", refcount_idx) + } + } + + #[cfg(feature = "low-power")] + match self.stop_mode { + StopMode::Standby => {} + StopMode::Stop2 => unsafe { + REFCOUNT_STOP2 += 1; + }, + StopMode::Stop1 => unsafe { + REFCOUNT_STOP1 += 1; + }, + } + + // set the xxxRST bit + let reset_ptr = self.reset_ptr(); + if let Some(reset_ptr) = reset_ptr { + unsafe { + let val = reset_ptr.read_volatile(); + reset_ptr.write_volatile(val | 1u32 << self.reset_bit); + } + } + + // set the xxxEN bit + let enable_ptr = self.enable_ptr(); + unsafe { + let val = enable_ptr.read_volatile(); + enable_ptr.write_volatile(val | 1u32 << self.enable_bit); + } + + // we must wait two peripheral clock cycles before the clock is active + // this seems to work, but might be incorrect + // see http://efton.sk/STM32/gotcha/g183.html + + // dummy read (like in the ST HALs) + let _ = unsafe { enable_ptr.read_volatile() }; + + // DSB for good measure + cortex_m::asm::dsb(); + + // clear the xxxRST bit + if let Some(reset_ptr) = reset_ptr { + unsafe { + let val = reset_ptr.read_volatile(); + reset_ptr.write_volatile(val & !(1u32 << self.reset_bit)); + } + } + } + + // TODO: should this be `unsafe`? + pub(crate) fn disable_with_cs(&self, _cs: CriticalSection) { + if self.refcount_idx_or_0xff != 0xff { + let refcount_idx = self.refcount_idx_or_0xff as usize; + + // Use .get_mut instead of []-operator so that we control how bounds checks happen. + // Otherwise, core::fmt will be pulled in here in order to format the integer in the + // out-of-bounds error. + if let Some(refcount) = + unsafe { (*core::ptr::addr_of_mut!(crate::_generated::REFCOUNTS)).get_mut(refcount_idx) } + { + *refcount -= 1; + if *refcount > 0 { + return; + } + } else { + panic!("refcount_idx out of bounds: {}", refcount_idx) + } + } + + #[cfg(feature = "low-power")] + match self.stop_mode { + StopMode::Standby => {} + StopMode::Stop2 => unsafe { + REFCOUNT_STOP2 -= 1; + }, + StopMode::Stop1 => unsafe { + REFCOUNT_STOP1 -= 1; + }, + } + + // clear the xxxEN bit + let enable_ptr = self.enable_ptr(); + unsafe { + let val = enable_ptr.read_volatile(); + enable_ptr.write_volatile(val & !(1u32 << self.enable_bit)); + } + } + + // TODO: should this be `unsafe`? + pub(crate) fn enable_and_reset(&self) { + critical_section::with(|cs| self.enable_and_reset_with_cs(cs)) + } + + // TODO: should this be `unsafe`? + pub(crate) fn disable(&self) { + critical_section::with(|cs| self.disable_with_cs(cs)) + } + + fn reset_ptr(&self) -> Option<*mut u32> { + if self.reset_offset_or_0xff != 0xff { + Some(unsafe { (RCC.as_ptr() as *mut u32).add(self.reset_offset_or_0xff as _) }) + } else { + None + } + } + + fn enable_ptr(&self) -> *mut u32 { + unsafe { (RCC.as_ptr() as *mut u32).add(self.enable_offset as _) } + } +} + +#[allow(unused)] +mod util { + use crate::time::Hertz; + + pub fn calc_pclk(hclk: Hertz, ppre: D) -> (Hertz, Hertz) + where + Hertz: core::ops::Div, + { + let pclk = hclk / ppre; + let pclk_tim = if hclk == pclk { pclk } else { pclk * 2u32 }; + (pclk, pclk_tim) + } + + pub fn all_equal(mut iter: impl Iterator) -> bool { + let Some(x) = iter.next() else { return true }; + if !iter.all(|y| y == x) { + return false; + } + true + } + + pub fn get_equal(mut iter: impl Iterator) -> Result, ()> { + let Some(x) = iter.next() else { return Ok(None) }; + if !iter.all(|y| y == x) { + return Err(()); + } + Ok(Some(x)) + } +} + +/// Get the kernel clock frequency of the peripheral `T`. +/// +/// # Panics +/// +/// Panics if the clock is not active. +pub fn frequency() -> Hertz { + T::frequency() +} + +/// Enables and resets peripheral `T`. +/// +/// # Safety +/// +/// Peripheral must not be in use. +// TODO: should this be `unsafe`? +pub fn enable_and_reset_with_cs(cs: CriticalSection) { + T::RCC_INFO.enable_and_reset_with_cs(cs); +} + +/// Disables peripheral `T`. +/// +/// # Safety +/// +/// Peripheral must not be in use. +// TODO: should this be `unsafe`? +pub fn disable_with_cs(cs: CriticalSection) { + T::RCC_INFO.disable_with_cs(cs); +} + +/// Enables and resets peripheral `T`. +/// +/// # Safety +/// +/// Peripheral must not be in use. +// TODO: should this be `unsafe`? +pub fn enable_and_reset() { + T::RCC_INFO.enable_and_reset(); +} + +/// Disables peripheral `T`. +/// +/// # Safety +/// +/// Peripheral must not be in use. +// TODO: should this be `unsafe`? +pub fn disable() { + T::RCC_INFO.disable(); +} diff --git a/embassy/embassy-stm32/src/rcc/u5.rs b/embassy/embassy-stm32/src/rcc/u5.rs new file mode 100644 index 0000000..dc77dc5 --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/u5.rs @@ -0,0 +1,514 @@ +pub use crate::pac::pwr::vals::Vos as VoltageScale; +#[cfg(all(peri_usb_otg_hs))] +pub use crate::pac::rcc::vals::Otghssel; +pub use crate::pac::rcc::vals::{ + Hpre as AHBPrescaler, Msirange, Msirange as MSIRange, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, + Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, +}; +use crate::pac::rcc::vals::{Hseext, Msirgsel, Pllmboost, Pllrge}; +#[cfg(all(peri_usb_otg_hs))] +pub use crate::pac::{syscfg::vals::Usbrefcksel, SYSCFG}; +use crate::pac::{FLASH, PWR, RCC}; +use crate::rcc::LSI_FREQ; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(16_000_000); + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1, HSEEXT=0) + Bypass, + /// external digital clock (full swing) (HSEBYP=1, HSEEXT=1) + BypassDigital, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, +} + +#[derive(Clone, Copy)] +pub struct Pll { + /// The clock source for the PLL. + pub source: PllSource, + /// The PLL pre-divider. + /// + /// The clock speed of the `source` divided by `m` must be between 4 and 16 MHz. + pub prediv: PllPreDiv, + /// The PLL multiplier. + /// + /// The multiplied clock – `source` divided by `m` times `n` – must be between 128 and 544 + /// MHz. The upper limit may be lower depending on the `Config { voltage_range }`. + pub mul: PllMul, + /// The divider for the P output. + /// + /// The P output is one of several options + /// that can be used to feed the SAI/MDF/ADF Clock mux's. + pub divp: Option, + /// The divider for the Q output. + /// + /// The Q ouput is one of severals options that can be used to feed the 48MHz clocks + /// and the OCTOSPI clock. It may also be used on the MDF/ADF clock mux's. + pub divq: Option, + /// The divider for the R output. + /// + /// When used to drive the system clock, `source` divided by `m` times `n` divided by `r` + /// must not exceed 160 MHz. System clocks above 55 MHz require a non-default + /// `Config { voltage_range }`. + pub divr: Option, +} + +#[derive(Clone, Copy)] +pub struct Config { + // base clock sources + pub msis: Option, + pub msik: Option, + pub hsi: bool, + pub hse: Option, + pub hsi48: Option, + + // pll + pub pll1: Option, + pub pll2: Option, + pub pll3: Option, + + // sysclk, buses. + pub sys: Sysclk, + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + pub apb2_pre: APBPrescaler, + pub apb3_pre: APBPrescaler, + + /// The voltage range influences the maximum clock frequencies for different parts of the + /// device. In particular, system clocks exceeding 110 MHz require `RANGE1`, and system clocks + /// exceeding 55 MHz require at least `RANGE2`. + /// + /// See RM0456 § 10.5.4 for a general overview and § 11.4.10 for clock source frequency limits. + pub voltage_range: VoltageScale, + pub ls: super::LsConfig, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, +} + +impl Default for Config { + fn default() -> Self { + Self { + msis: Some(Msirange::RANGE_4MHZ), + msik: Some(Msirange::RANGE_4MHZ), + hse: None, + hsi: false, + hsi48: Some(Default::default()), + pll1: None, + pll2: None, + pll3: None, + sys: Sysclk::MSIS, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + apb2_pre: APBPrescaler::DIV1, + apb3_pre: APBPrescaler::DIV1, + voltage_range: VoltageScale::RANGE1, + ls: Default::default(), + mux: Default::default(), + } + } +} + +pub(crate) unsafe fn init(config: Config) { + // Set the requested power mode + PWR.vosr().modify(|w| w.set_vos(config.voltage_range)); + while !PWR.vosr().read().vosrdy() {} + + let msis = config.msis.map(|range| { + // Check MSI output per RM0456 § 11.4.10 + match config.voltage_range { + VoltageScale::RANGE4 => { + assert!(msirange_to_hertz(range).0 <= 24_000_000); + } + _ => {} + } + + // RM0456 § 11.8.2: spin until MSIS is off or MSIS is ready before setting its range + loop { + let cr = RCC.cr().read(); + if cr.msison() == false || cr.msisrdy() == true { + break; + } + } + + RCC.icscr1().modify(|w| { + w.set_msisrange(range); + w.set_msirgsel(Msirgsel::ICSCR1); + }); + RCC.cr().write(|w| { + w.set_msipllen(false); + w.set_msison(true); + }); + while !RCC.cr().read().msisrdy() {} + msirange_to_hertz(range) + }); + + let msik = config.msik.map(|range| { + // Check MSI output per RM0456 § 11.4.10 + match config.voltage_range { + VoltageScale::RANGE4 => { + assert!(msirange_to_hertz(range).0 <= 24_000_000); + } + _ => {} + } + + // RM0456 § 11.8.2: spin until MSIS is off or MSIS is ready before setting its range + loop { + let cr = RCC.cr().read(); + if cr.msikon() == false || cr.msikrdy() == true { + break; + } + } + + RCC.icscr1().modify(|w| { + w.set_msikrange(range); + w.set_msirgsel(Msirgsel::ICSCR1); + }); + RCC.cr().write(|w| { + w.set_msikon(true); + }); + while !RCC.cr().read().msikrdy() {} + msirange_to_hertz(range) + }); + + let hsi = config.hsi.then(|| { + RCC.cr().write(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + + HSI_FREQ + }); + + let hse = config.hse.map(|hse| { + // Check frequency limits per RM456 § 11.4.10 + match config.voltage_range { + VoltageScale::RANGE1 | VoltageScale::RANGE2 | VoltageScale::RANGE3 => { + assert!(hse.freq.0 <= 50_000_000); + } + VoltageScale::RANGE4 => { + assert!(hse.freq.0 <= 25_000_000); + } + } + + // Enable HSE, and wait for it to stabilize + RCC.cr().write(|w| { + w.set_hseon(true); + w.set_hsebyp(hse.mode != HseMode::Oscillator); + w.set_hseext(match hse.mode { + HseMode::Oscillator | HseMode::Bypass => Hseext::ANALOG, + HseMode::BypassDigital => Hseext::DIGITAL, + }); + }); + while !RCC.cr().read().hserdy() {} + + hse.freq + }); + + let hsi48 = config.hsi48.map(super::init_hsi48); + + let pll_input = PllInput { hse, hsi, msi: msis }; + let pll1 = init_pll(PllInstance::Pll1, config.pll1, &pll_input, config.voltage_range); + let pll2 = init_pll(PllInstance::Pll2, config.pll2, &pll_input, config.voltage_range); + let pll3 = init_pll(PllInstance::Pll3, config.pll3, &pll_input, config.voltage_range); + + let sys_clk = match config.sys { + Sysclk::HSE => hse.unwrap(), + Sysclk::HSI => hsi.unwrap(), + Sysclk::MSIS => msis.unwrap(), + Sysclk::PLL1_R => pll1.r.unwrap(), + }; + + // Do we need the EPOD booster to reach the target clock speed per § 10.5.4? + if sys_clk >= Hertz::mhz(55) { + // Enable the booster + PWR.vosr().modify(|w| w.set_boosten(true)); + while !PWR.vosr().read().boostrdy() {} + } + + // The clock source is ready + // Calculate and set the flash wait states + let wait_states = match config.voltage_range { + // VOS 1 range VCORE 1.26V - 1.40V + VoltageScale::RANGE1 => match sys_clk.0 { + ..=32_000_000 => 0, + ..=64_000_000 => 1, + ..=96_000_000 => 2, + ..=128_000_000 => 3, + _ => 4, + }, + // VOS 2 range VCORE 1.15V - 1.26V + VoltageScale::RANGE2 => match sys_clk.0 { + ..=30_000_000 => 0, + ..=60_000_000 => 1, + ..=90_000_000 => 2, + _ => 3, + }, + // VOS 3 range VCORE 1.05V - 1.15V + VoltageScale::RANGE3 => match sys_clk.0 { + ..=24_000_000 => 0, + ..=48_000_000 => 1, + _ => 2, + }, + // VOS 4 range VCORE 0.95V - 1.05V + VoltageScale::RANGE4 => match sys_clk.0 { + ..=12_000_000 => 0, + _ => 1, + }, + }; + FLASH.acr().modify(|w| { + w.set_latency(wait_states); + }); + + // Switch the system clock source + RCC.cfgr1().modify(|w| w.set_sw(config.sys)); + while RCC.cfgr1().read().sws() != config.sys {} + + // Configure the bus prescalers + RCC.cfgr2().modify(|w| { + w.set_hpre(config.ahb_pre); + w.set_ppre1(config.apb1_pre); + w.set_ppre2(config.apb2_pre); + }); + RCC.cfgr3().modify(|w| { + w.set_ppre3(config.apb3_pre); + }); + + let hclk = sys_clk / config.ahb_pre; + + let hclk_max = match config.voltage_range { + VoltageScale::RANGE1 => Hertz::mhz(160), + VoltageScale::RANGE2 => Hertz::mhz(110), + VoltageScale::RANGE3 => Hertz::mhz(55), + VoltageScale::RANGE4 => Hertz::mhz(25), + }; + assert!(hclk <= hclk_max); + + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); + let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk, config.apb2_pre); + let (pclk3, _) = super::util::calc_pclk(hclk, config.apb3_pre); + + let rtc = config.ls.init(); + + #[cfg(all(stm32u5, peri_usb_otg_hs))] + let usb_refck = match config.mux.otghssel { + Otghssel::HSE => hse, + Otghssel::HSE_DIV_2 => hse.map(|hse_val| hse_val / 2u8), + Otghssel::PLL1_P => pll1.p, + Otghssel::PLL1_P_DIV_2 => pll1.p.map(|pll1p_val| pll1p_val / 2u8), + }; + #[cfg(all(stm32u5, peri_usb_otg_hs))] + let usb_refck_sel = match usb_refck { + Some(clk_val) => match clk_val { + Hertz(16_000_000) => Usbrefcksel::MHZ16, + Hertz(19_200_000) => Usbrefcksel::MHZ19_2, + Hertz(20_000_000) => Usbrefcksel::MHZ20, + Hertz(24_000_000) => Usbrefcksel::MHZ24, + Hertz(26_000_000) => Usbrefcksel::MHZ26, + Hertz(32_000_000) => Usbrefcksel::MHZ32, + _ => panic!("cannot select OTG_HS reference clock with source frequency of {} Hz, must be one of 16, 19.2, 20, 24, 26, 32 MHz", clk_val), + }, + None => Usbrefcksel::MHZ24, + }; + #[cfg(all(stm32u5, peri_usb_otg_hs))] + SYSCFG.otghsphycr().modify(|w| { + w.set_clksel(usb_refck_sel); + }); + + let lse = config.ls.lse.map(|l| l.frequency); + let lsi = config.ls.lsi.then_some(LSI_FREQ); + + config.mux.init(); + + set_clocks!( + sys: Some(sys_clk), + hclk1: Some(hclk), + hclk2: Some(hclk), + hclk3: Some(hclk), + pclk1: Some(pclk1), + pclk2: Some(pclk2), + pclk3: Some(pclk3), + pclk1_tim: Some(pclk1_tim), + pclk2_tim: Some(pclk2_tim), + msik: msik, + hsi48: hsi48, + rtc: rtc, + lse: lse, + lsi: lsi, + hse: hse, + hse_div_2: hse.map(|clk| clk / 2u32), + hsi: hsi, + pll1_p: pll1.p, + pll1_p_div_2: pll1.p.map(|clk| clk / 2u32), + pll1_q: pll1.q, + pll1_r: pll1.r, + pll2_p: pll2.p, + pll2_q: pll2.q, + pll2_r: pll2.r, + pll3_p: pll3.p, + pll3_q: pll3.q, + pll3_r: pll3.r, + + #[cfg(dsihost)] + dsi_phy: None, // DSI PLL clock not supported, don't call `RccPeripheral::frequency()` in the drivers + + // TODO + audioclk: None, + hsi48_div_2: None, + shsi: None, + shsi_div_2: None, + ); +} + +fn msirange_to_hertz(range: Msirange) -> Hertz { + match range { + Msirange::RANGE_48MHZ => Hertz(48_000_000), + Msirange::RANGE_24MHZ => Hertz(24_000_000), + Msirange::RANGE_16MHZ => Hertz(16_000_000), + Msirange::RANGE_12MHZ => Hertz(12_000_000), + Msirange::RANGE_4MHZ => Hertz(4_000_000), + Msirange::RANGE_2MHZ => Hertz(2_000_000), + Msirange::RANGE_1_33MHZ => Hertz(1_330_000), + Msirange::RANGE_1MHZ => Hertz(1_000_000), + Msirange::RANGE_3_072MHZ => Hertz(3_072_000), + Msirange::RANGE_1_536MHZ => Hertz(1_536_000), + Msirange::RANGE_1_024MHZ => Hertz(1_024_000), + Msirange::RANGE_768KHZ => Hertz(768_000), + Msirange::RANGE_400KHZ => Hertz(400_000), + Msirange::RANGE_200KHZ => Hertz(200_000), + Msirange::RANGE_133KHZ => Hertz(133_000), + Msirange::RANGE_100KHZ => Hertz(100_000), + } +} + +pub(super) struct PllInput { + pub hsi: Option, + pub hse: Option, + pub msi: Option, +} + +#[allow(unused)] +#[derive(Default)] +pub(super) struct PllOutput { + pub p: Option, + pub q: Option, + pub r: Option, +} + +#[derive(PartialEq, Eq, Clone, Copy)] +enum PllInstance { + Pll1 = 0, + Pll2 = 1, + Pll3 = 2, +} + +fn pll_enable(instance: PllInstance, enabled: bool) { + RCC.cr().modify(|w| w.set_pllon(instance as _, enabled)); + while RCC.cr().read().pllrdy(instance as _) != enabled {} +} + +fn init_pll(instance: PllInstance, config: Option, input: &PllInput, voltage_range: VoltageScale) -> PllOutput { + // Disable PLL + pll_enable(instance, false); + + let Some(pll) = config else { return PllOutput::default() }; + + let src_freq = match pll.source { + PllSource::DISABLE => panic!("must not select PLL source as DISABLE"), + PllSource::HSE => unwrap!(input.hse), + PllSource::HSI => unwrap!(input.hsi), + PllSource::MSIS => unwrap!(input.msi), + }; + + // Calculate the reference clock, which is the source divided by m + let ref_freq = src_freq / pll.prediv; + // Check limits per RM0456 § 11.4.6 + assert!(Hertz::mhz(4) <= ref_freq && ref_freq <= Hertz::mhz(16)); + + // Check PLL clocks per RM0456 § 11.4.10 + let (vco_min, vco_max, out_max) = match voltage_range { + VoltageScale::RANGE1 => (Hertz::mhz(128), Hertz::mhz(544), Hertz::mhz(208)), + VoltageScale::RANGE2 => (Hertz::mhz(128), Hertz::mhz(544), Hertz::mhz(110)), + VoltageScale::RANGE3 => (Hertz::mhz(128), Hertz::mhz(330), Hertz::mhz(55)), + VoltageScale::RANGE4 => panic!("PLL is unavailable in voltage range 4"), + }; + + // Calculate the PLL VCO clock + let vco_freq = ref_freq * pll.mul; + assert!(vco_freq >= vco_min && vco_freq <= vco_max); + + // Calculate output clocks. + let p = pll.divp.map(|div| vco_freq / div); + let q = pll.divq.map(|div| vco_freq / div); + let r = pll.divr.map(|div| vco_freq / div); + for freq in [p, q, r] { + if let Some(freq) = freq { + assert!(freq <= out_max); + } + } + + let divr = match instance { + PllInstance::Pll1 => RCC.pll1divr(), + PllInstance::Pll2 => RCC.pll2divr(), + PllInstance::Pll3 => RCC.pll3divr(), + }; + divr.write(|w| { + w.set_plln(pll.mul); + w.set_pllp(pll.divp.unwrap_or(PllDiv::DIV1)); + w.set_pllq(pll.divq.unwrap_or(PllDiv::DIV1)); + w.set_pllr(pll.divr.unwrap_or(PllDiv::DIV1)); + }); + + let input_range = match ref_freq.0 { + ..=8_000_000 => Pllrge::FREQ_4TO8MHZ, + _ => Pllrge::FREQ_8TO16MHZ, + }; + + macro_rules! write_fields { + ($w:ident) => { + $w.set_pllpen(pll.divp.is_some()); + $w.set_pllqen(pll.divq.is_some()); + $w.set_pllren(pll.divr.is_some()); + $w.set_pllm(pll.prediv); + $w.set_pllsrc(pll.source); + $w.set_pllrge(input_range); + }; + } + + match instance { + PllInstance::Pll1 => RCC.pll1cfgr().write(|w| { + // § 10.5.4: if we're targeting >= 55 MHz, we must configure PLL1MBOOST to a prescaler + // value that results in an output between 4 and 16 MHz for the PWR EPOD boost + if r.unwrap() >= Hertz::mhz(55) { + // source_clk can be up to 50 MHz, so there's just a few cases: + let mboost = match src_freq.0 { + ..=16_000_000 => Pllmboost::DIV1, // Bypass, giving EPOD 4-16 MHz + ..=32_000_000 => Pllmboost::DIV2, // Divide by 2, giving EPOD 8-16 MHz + _ => Pllmboost::DIV4, // Divide by 4, giving EPOD 8-12.5 MHz + }; + w.set_pllmboost(mboost); + } + write_fields!(w); + }), + PllInstance::Pll2 => RCC.pll2cfgr().write(|w| { + write_fields!(w); + }), + PllInstance::Pll3 => RCC.pll3cfgr().write(|w| { + write_fields!(w); + }), + } + + // Enable PLL + pll_enable(instance, true); + + PllOutput { p, q, r } +} diff --git a/embassy/embassy-stm32/src/rcc/wba.rs b/embassy/embassy-stm32/src/rcc/wba.rs new file mode 100644 index 0000000..1fee648 --- /dev/null +++ b/embassy/embassy-stm32/src/rcc/wba.rs @@ -0,0 +1,176 @@ +pub use crate::pac::pwr::vals::Vos as VoltageScale; +use crate::pac::rcc::regs::Cfgr1; +pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Hsepre as HsePrescaler, Ppre as APBPrescaler, Sw as Sysclk}; +use crate::pac::{FLASH, RCC}; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(16_000_000); +// HSE speed +pub const HSE_FREQ: Hertz = Hertz(32_000_000); + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + pub prescaler: HsePrescaler, +} + +/// Clocks configuration +#[derive(Clone, Copy)] +pub struct Config { + // base clock sources + pub hsi: bool, + pub hse: Option, + + // sysclk, buses. + pub sys: Sysclk, + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + pub apb2_pre: APBPrescaler, + pub apb7_pre: APBPrescaler, + + // low speed LSI/LSE/RTC + pub ls: super::LsConfig, + + pub voltage_scale: VoltageScale, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, +} + +impl Default for Config { + #[inline] + fn default() -> Config { + Config { + hse: None, + hsi: true, + sys: Sysclk::HSI, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + apb2_pre: APBPrescaler::DIV1, + apb7_pre: APBPrescaler::DIV1, + ls: Default::default(), + voltage_scale: VoltageScale::RANGE2, + mux: Default::default(), + } + } +} + +fn hsi_enable() { + RCC.cr().modify(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} +} + +pub(crate) unsafe fn init(config: Config) { + // Switch to HSI to prevent problems with PLL configuration. + if !RCC.cr().read().hsion() { + hsi_enable() + } + if RCC.cfgr1().read().sws() != Sysclk::HSI { + // Set HSI as a clock source, reset prescalers. + RCC.cfgr1().write_value(Cfgr1::default()); + // Wait for clock switch status bits to change. + while RCC.cfgr1().read().sws() != Sysclk::HSI {} + } + + // Set voltage scale + crate::pac::PWR.vosr().write(|w| w.set_vos(config.voltage_scale)); + while !crate::pac::PWR.vosr().read().vosrdy() {} + + let rtc = config.ls.init(); + + let hsi = config.hsi.then(|| { + hsi_enable(); + + HSI_FREQ + }); + + let hse = config.hse.map(|hse| { + RCC.cr().write(|w| { + w.set_hseon(true); + w.set_hsepre(hse.prescaler); + }); + while !RCC.cr().read().hserdy() {} + + HSE_FREQ + }); + + let sys_clk = match config.sys { + Sysclk::HSE => hse.unwrap(), + Sysclk::HSI => hsi.unwrap(), + Sysclk::_RESERVED_1 => unreachable!(), + Sysclk::PLL1_R => todo!(), + }; + + assert!(sys_clk.0 <= 100_000_000); + + let hclk1 = sys_clk / config.ahb_pre; + let hclk2 = hclk1; + let hclk4 = hclk1; + // TODO: hclk5 + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk1, config.apb1_pre); + let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk1, config.apb2_pre); + let (pclk7, _) = super::util::calc_pclk(hclk1, config.apb7_pre); + + // Set flash wait states + let flash_latency = match config.voltage_scale { + VoltageScale::RANGE1 => match sys_clk.0 { + ..=32_000_000 => 0, + ..=64_000_000 => 1, + ..=96_000_000 => 2, + ..=100_000_000 => 3, + _ => 4, + }, + VoltageScale::RANGE2 => match sys_clk.0 { + ..=8_000_000 => 0, + ..=16_000_000 => 1, + _ => 2, + }, + }; + + FLASH.acr().modify(|w| w.set_latency(flash_latency)); + while FLASH.acr().read().latency() != flash_latency {} + + // Set sram wait states + let _sram_latency = match config.voltage_scale { + VoltageScale::RANGE1 => 0, + VoltageScale::RANGE2 => match sys_clk.0 { + ..=12_000_000 => 0, + ..=16_000_000 => 1, + _ => 2, + }, + }; + // TODO: Set the SRAM wait states + + RCC.cfgr1().modify(|w| { + w.set_sw(config.sys); + }); + while RCC.cfgr1().read().sws() != config.sys {} + + RCC.cfgr2().modify(|w| { + w.set_hpre(config.ahb_pre); + w.set_ppre1(config.apb1_pre); + w.set_ppre2(config.apb2_pre); + }); + + config.mux.init(); + + set_clocks!( + sys: Some(sys_clk), + hclk1: Some(hclk1), + hclk2: Some(hclk2), + hclk4: Some(hclk4), + pclk1: Some(pclk1), + pclk2: Some(pclk2), + pclk7: Some(pclk7), + pclk1_tim: Some(pclk1_tim), + pclk2_tim: Some(pclk2_tim), + rtc: rtc, + hse: hse, + hsi: hsi, + + // TODO + lse: None, + lsi: None, + pll1_q: None, + ); +} diff --git a/embassy/embassy-stm32/src/rng.rs b/embassy/embassy-stm32/src/rng.rs new file mode 100644 index 0000000..6f4c81c --- /dev/null +++ b/embassy/embassy-stm32/src/rng.rs @@ -0,0 +1,248 @@ +//! Random Number Generator (RNG) +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use rand_core::{CryptoRng, RngCore}; + +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, pac, peripherals, rcc, Peripheral}; + +static RNG_WAKER: AtomicWaker = AtomicWaker::new(); + +/// RNG error +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Seed error. + SeedError, + /// Clock error. Double-check the RCC configuration, + /// see the Reference Manual for details on restrictions + /// on RNG clocks. + ClockError, +} + +/// RNG interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let bits = T::regs().sr().read(); + if bits.drdy() || bits.seis() || bits.ceis() { + T::regs().cr().modify(|reg| reg.set_ie(false)); + RNG_WAKER.wake(); + } + } +} + +/// RNG driver. +pub struct Rng<'d, T: Instance> { + _inner: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Rng<'d, T> { + /// Create a new RNG driver. + pub fn new( + inner: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + rcc::enable_and_reset::(); + into_ref!(inner); + let mut random = Self { _inner: inner }; + random.reset(); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + random + } + + /// Reset the RNG. + #[cfg(rng_v1)] + pub fn reset(&mut self) { + T::regs().cr().write(|reg| { + reg.set_rngen(false); + }); + T::regs().sr().modify(|reg| { + reg.set_seis(false); + reg.set_ceis(false); + }); + T::regs().cr().modify(|reg| { + reg.set_rngen(true); + }); + // Reference manual says to discard the first. + let _ = self.next_u32(); + } + + /// Reset the RNG. + #[cfg(not(rng_v1))] + pub fn reset(&mut self) { + T::regs().cr().write(|reg| { + reg.set_condrst(true); + reg.set_nistc(pac::rng::vals::Nistc::CUSTOM); + // set RNG config "A" according to reference manual + // this has to be written within the same write access as setting the CONDRST bit + reg.set_rng_config1(pac::rng::vals::RngConfig1::CONFIGA); + reg.set_clkdiv(pac::rng::vals::Clkdiv::NODIV); + reg.set_rng_config2(pac::rng::vals::RngConfig2::CONFIGA_B); + reg.set_rng_config3(pac::rng::vals::RngConfig3::CONFIGA); + reg.set_ced(true); + reg.set_ie(false); + reg.set_rngen(true); + }); + T::regs().cr().modify(|reg| { + reg.set_ced(false); + }); + // wait for CONDRST to be set + while !T::regs().cr().read().condrst() {} + // magic number must be written immediately before every read or write access to HTCR + T::regs().htcr().write(|w| w.set_htcfg(pac::rng::vals::Htcfg::MAGIC)); + // write recommended value according to reference manual + // note: HTCR can only be written during conditioning + T::regs() + .htcr() + .write(|w| w.set_htcfg(pac::rng::vals::Htcfg::RECOMMENDED)); + // finish conditioning + T::regs().cr().modify(|reg| { + reg.set_rngen(true); + reg.set_condrst(false); + }); + // wait for CONDRST to be reset + while T::regs().cr().read().condrst() {} + } + + /// Try to recover from a seed error. + pub fn recover_seed_error(&mut self) { + self.reset(); + // reset should also clear the SEIS flag + if T::regs().sr().read().seis() { + warn!("recovering from seed error failed"); + return; + } + // wait for SECS to be cleared by RNG + while T::regs().sr().read().secs() {} + } + + /// Fill the given slice with random values. + pub async fn async_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + for chunk in dest.chunks_mut(4) { + let mut bits = T::regs().sr().read(); + if !bits.seis() && !bits.ceis() && !bits.drdy() { + // wait for interrupt + poll_fn(|cx| { + // quick check to avoid registration if already done. + let bits = T::regs().sr().read(); + if bits.drdy() || bits.seis() || bits.ceis() { + return Poll::Ready(()); + } + RNG_WAKER.register(cx.waker()); + T::regs().cr().modify(|reg| reg.set_ie(true)); + // Need to check condition **after** `register` to avoid a race + // condition that would result in lost notifications. + let bits = T::regs().sr().read(); + if bits.drdy() || bits.seis() || bits.ceis() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + // Re-read the status register after wait. + bits = T::regs().sr().read() + } + if bits.seis() { + // in case of noise-source or seed error we try to recover here + // but we must not use the data in DR and we return an error + // to leave retry-logic to the application + self.recover_seed_error(); + return Err(Error::SeedError); + } else if bits.ceis() { + // clock error detected, DR could still be used but keep it safe, + // clear the error and abort + T::regs().sr().modify(|sr| sr.set_ceis(false)); + return Err(Error::ClockError); + } else if bits.drdy() { + // DR can be read up to four times until the output buffer is empty + // DRDY is cleared automatically when that happens + let random_word = T::regs().dr().read(); + // reference manual: always check if DR is zero + if random_word == 0 { + return Err(Error::SeedError); + } + // write bytes to chunk + for (dest, src) in chunk.iter_mut().zip(random_word.to_ne_bytes().iter()) { + *dest = *src + } + } + } + + Ok(()) + } +} + +impl<'d, T: Instance> RngCore for Rng<'d, T> { + fn next_u32(&mut self) -> u32 { + loop { + let sr = T::regs().sr().read(); + if sr.seis() | sr.ceis() { + self.reset(); + } else if sr.drdy() { + return T::regs().dr().read(); + } + } + } + + fn next_u64(&mut self) -> u64 { + let mut rand = self.next_u32() as u64; + rand |= (self.next_u32() as u64) << 32; + rand + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + for chunk in dest.chunks_mut(4) { + let rand = self.next_u32(); + for (slot, num) in chunk.iter_mut().zip(rand.to_ne_bytes().iter()) { + *slot = *num + } + } + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +impl<'d, T: Instance> CryptoRng for Rng<'d, T> {} + +trait SealedInstance { + fn regs() -> pac::rng::Rng; +} + +/// RNG instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + Peripheral

+ crate::rcc::RccPeripheral + 'static + Send { + /// Interrupt for this RNG instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +foreach_interrupt!( + ($inst:ident, rng, RNG, GLOBAL, $irq:ident) => { + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + + impl SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::rng::Rng { + crate::pac::$inst + } + } + }; +); diff --git a/embassy/embassy-stm32/src/rtc/datetime.rs b/embassy/embassy-stm32/src/rtc/datetime.rs new file mode 100644 index 0000000..32732e9 --- /dev/null +++ b/embassy/embassy-stm32/src/rtc/datetime.rs @@ -0,0 +1,192 @@ +#[cfg(feature = "chrono")] +use chrono::{Datelike, NaiveDate, Timelike, Weekday}; + +/// Errors regarding the [`DateTime`] struct. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] contains an invalid year value. Must be between `0..=4095`. + InvalidYear, + /// The [DateTime] contains an invalid month value. Must be between `1..=12`. + InvalidMonth, + /// The [DateTime] contains an invalid day value. Must be between `1..=31`. + InvalidDay, + /// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday. + InvalidDayOfWeek( + /// The value of the DayOfWeek that was given. + u8, + ), + /// The [DateTime] contains an invalid hour value. Must be between `0..=23`. + InvalidHour, + /// The [DateTime] contains an invalid minute value. Must be between `0..=59`. + InvalidMinute, + /// The [DateTime] contains an invalid second value. Must be between `0..=59`. + InvalidSecond, +} + +/// Structure containing date and time information +pub struct DateTime { + /// 0..4095 + year: u16, + /// 1..12, 1 is January + month: u8, + /// 1..28,29,30,31 depending on month + day: u8, + /// + day_of_week: DayOfWeek, + /// 0..23 + hour: u8, + /// 0..59 + minute: u8, + /// 0..59 + second: u8, +} + +impl DateTime { + /// Get the year (0..=4095) + pub const fn year(&self) -> u16 { + self.year + } + + /// Get the month (1..=12, 1 is January) + pub const fn month(&self) -> u8 { + self.month + } + + /// Get the day (1..=31) + pub const fn day(&self) -> u8 { + self.day + } + + /// Get the day of week + pub const fn day_of_week(&self) -> DayOfWeek { + self.day_of_week + } + + /// Get the hour (0..=23) + pub const fn hour(&self) -> u8 { + self.hour + } + + /// Get the minute (0..=59) + pub const fn minute(&self) -> u8 { + self.minute + } + + /// Get the second (0..=59) + pub const fn second(&self) -> u8 { + self.second + } + + /// Create a new DateTime with the given information. + pub fn from( + year: u16, + month: u8, + day: u8, + day_of_week: DayOfWeek, + hour: u8, + minute: u8, + second: u8, + ) -> Result { + if year > 4095 { + Err(Error::InvalidYear) + } else if !(1..=12).contains(&month) { + Err(Error::InvalidMonth) + } else if !(1..=31).contains(&day) { + Err(Error::InvalidDay) + } else if hour > 23 { + Err(Error::InvalidHour) + } else if minute > 59 { + Err(Error::InvalidMinute) + } else if second > 59 { + Err(Error::InvalidSecond) + } else { + Ok(Self { + year, + month, + day, + day_of_week, + hour, + minute, + second, + }) + } + } +} + +#[cfg(feature = "chrono")] +impl From for DateTime { + fn from(date_time: chrono::NaiveDateTime) -> Self { + Self { + year: date_time.year() as u16, + month: date_time.month() as u8, + day: date_time.day() as u8, + day_of_week: date_time.weekday().into(), + hour: date_time.hour() as u8, + minute: date_time.minute() as u8, + second: date_time.second() as u8, + } + } +} + +#[cfg(feature = "chrono")] +impl From for chrono::NaiveDateTime { + fn from(date_time: DateTime) -> Self { + NaiveDate::from_ymd_opt(date_time.year as i32, date_time.month as u32, date_time.day as u32) + .unwrap() + .and_hms_opt(date_time.hour as u32, date_time.minute as u32, date_time.second as u32) + .unwrap() + } +} + +/// A day of the week +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[allow(missing_docs)] +pub enum DayOfWeek { + Monday = 1, + Tuesday = 2, + Wednesday = 3, + Thursday = 4, + Friday = 5, + Saturday = 6, + Sunday = 7, +} + +#[cfg(feature = "chrono")] +impl From for DayOfWeek { + fn from(weekday: Weekday) -> Self { + day_of_week_from_u8(weekday.number_from_monday() as u8).unwrap() + } +} + +#[cfg(feature = "chrono")] +impl From for chrono::Weekday { + fn from(weekday: DayOfWeek) -> Self { + match weekday { + DayOfWeek::Monday => Weekday::Mon, + DayOfWeek::Tuesday => Weekday::Tue, + DayOfWeek::Wednesday => Weekday::Wed, + DayOfWeek::Thursday => Weekday::Thu, + DayOfWeek::Friday => Weekday::Fri, + DayOfWeek::Saturday => Weekday::Sat, + DayOfWeek::Sunday => Weekday::Sun, + } + } +} + +pub(super) const fn day_of_week_from_u8(v: u8) -> Result { + Ok(match v { + 1 => DayOfWeek::Monday, + 2 => DayOfWeek::Tuesday, + 3 => DayOfWeek::Wednesday, + 4 => DayOfWeek::Thursday, + 5 => DayOfWeek::Friday, + 6 => DayOfWeek::Saturday, + 7 => DayOfWeek::Sunday, + x => return Err(Error::InvalidDayOfWeek(x)), + }) +} + +pub(super) const fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw as u8 +} diff --git a/embassy/embassy-stm32/src/rtc/low_power.rs b/embassy/embassy-stm32/src/rtc/low_power.rs new file mode 100644 index 0000000..cf7c4bb --- /dev/null +++ b/embassy/embassy-stm32/src/rtc/low_power.rs @@ -0,0 +1,243 @@ +use super::{bcd2_to_byte, DateTimeError, Rtc, RtcError}; +use crate::peripherals::RTC; +use crate::rtc::SealedInstance; + +/// Represents an instant in time that can be substracted to compute a duration +pub(super) struct RtcInstant { + /// 0..59 + second: u8, + /// 0..256 + subsecond: u16, +} + +impl RtcInstant { + #[cfg(not(rtc_v2f2))] + const fn from(second: u8, subsecond: u16) -> Result { + if second > 59 { + Err(DateTimeError::InvalidSecond) + } else { + Ok(Self { second, subsecond }) + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for RtcInstant { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!( + fmt, + "{}:{}", + self.second, + RTC::regs().prer().read().prediv_s() - self.subsecond, + ) + } +} + +#[cfg(feature = "time")] +impl core::ops::Sub for RtcInstant { + type Output = embassy_time::Duration; + + fn sub(self, rhs: Self) -> Self::Output { + use embassy_time::{Duration, TICK_HZ}; + + let second = if self.second < rhs.second { + self.second + 60 + } else { + self.second + }; + + let psc = RTC::regs().prer().read().prediv_s() as u32; + + let self_ticks = second as u32 * (psc + 1) + (psc - self.subsecond as u32); + let other_ticks = rhs.second as u32 * (psc + 1) + (psc - rhs.subsecond as u32); + let rtc_ticks = self_ticks - other_ticks; + + Duration::from_ticks(((rtc_ticks * TICK_HZ as u32) / (psc + 1)) as u64) + } +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub(crate) enum WakeupPrescaler { + Div2 = 2, + Div4 = 4, + Div8 = 8, + Div16 = 16, +} + +#[cfg(any( + stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0 +))] +impl From for crate::pac::rtc::vals::Wucksel { + fn from(val: WakeupPrescaler) -> Self { + use crate::pac::rtc::vals::Wucksel; + + match val { + WakeupPrescaler::Div2 => Wucksel::DIV2, + WakeupPrescaler::Div4 => Wucksel::DIV4, + WakeupPrescaler::Div8 => Wucksel::DIV8, + WakeupPrescaler::Div16 => Wucksel::DIV16, + } + } +} + +#[cfg(any( + stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0 +))] +impl From for WakeupPrescaler { + fn from(val: crate::pac::rtc::vals::Wucksel) -> Self { + use crate::pac::rtc::vals::Wucksel; + + match val { + Wucksel::DIV2 => WakeupPrescaler::Div2, + Wucksel::DIV4 => WakeupPrescaler::Div4, + Wucksel::DIV8 => WakeupPrescaler::Div8, + Wucksel::DIV16 => WakeupPrescaler::Div16, + _ => unreachable!(), + } + } +} + +impl WakeupPrescaler { + pub fn compute_min(val: u32) -> Self { + *[ + WakeupPrescaler::Div2, + WakeupPrescaler::Div4, + WakeupPrescaler::Div8, + WakeupPrescaler::Div16, + ] + .iter() + .find(|psc| **psc as u32 > val) + .unwrap_or(&WakeupPrescaler::Div16) + } +} + +impl Rtc { + /// Return the current instant. + fn instant(&self) -> Result { + self.time_provider().read(|_, tr, ss| { + let second = bcd2_to_byte((tr.st(), tr.su())); + + RtcInstant::from(second, ss).map_err(RtcError::InvalidDateTime) + }) + } + + /// start the wakeup alarm and with a duration that is as close to but less than + /// the requested duration, and record the instant the wakeup alarm was started + pub(crate) fn start_wakeup_alarm( + &self, + requested_duration: embassy_time::Duration, + cs: critical_section::CriticalSection, + ) { + use embassy_time::{Duration, TICK_HZ}; + + #[cfg(any(rtc_v3, rtc_v3u5, rtc_v3l5))] + use crate::pac::rtc::vals::Calrf; + + // Panic if the rcc mod knows we're not using low-power rtc + #[cfg(any(rcc_wb, rcc_f4, rcc_f410))] + unsafe { crate::rcc::get_freqs() }.rtc.to_hertz().unwrap(); + + let requested_duration = requested_duration.as_ticks().clamp(0, u32::MAX as u64); + let rtc_hz = Self::frequency().0 as u64; + let rtc_ticks = requested_duration * rtc_hz / TICK_HZ; + let prescaler = WakeupPrescaler::compute_min((rtc_ticks / u16::MAX as u64) as u32); + + // adjust the rtc ticks to the prescaler and subtract one rtc tick + let rtc_ticks = rtc_ticks / prescaler as u64; + let rtc_ticks = rtc_ticks.clamp(0, (u16::MAX - 1) as u64).saturating_sub(1) as u16; + + self.write(false, |regs| { + regs.cr().modify(|w| w.set_wute(false)); + + #[cfg(any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb + ))] + { + regs.isr().modify(|w| w.set_wutf(false)); + while !regs.isr().read().wutwf() {} + } + + #[cfg(any(rtc_v3, rtc_v3u5, rtc_v3l5))] + { + regs.scr().write(|w| w.set_cwutf(Calrf::CLEAR)); + while !regs.icsr().read().wutwf() {} + } + + regs.cr().modify(|w| w.set_wucksel(prescaler.into())); + regs.wutr().write(|w| w.set_wut(rtc_ticks)); + regs.cr().modify(|w| w.set_wute(true)); + regs.cr().modify(|w| w.set_wutie(true)); + }); + + let instant = self.instant().unwrap(); + trace!( + "rtc: start wakeup alarm for {} ms (psc: {}, ticks: {}) at {}", + Duration::from_ticks(rtc_ticks as u64 * TICK_HZ * prescaler as u64 / rtc_hz).as_millis(), + prescaler as u32, + rtc_ticks, + instant, + ); + + assert!(self.stop_time.borrow(cs).replace(Some(instant)).is_none()) + } + + /// stop the wakeup alarm and return the time elapsed since `start_wakeup_alarm` + /// was called, otherwise none + pub(crate) fn stop_wakeup_alarm(&self, cs: critical_section::CriticalSection) -> Option { + use crate::interrupt::typelevel::Interrupt; + #[cfg(any(rtc_v3, rtc_v3u5, rtc_v3l5))] + use crate::pac::rtc::vals::Calrf; + + let instant = self.instant().unwrap(); + if RTC::regs().cr().read().wute() { + trace!("rtc: stop wakeup alarm at {}", instant); + + self.write(false, |regs| { + regs.cr().modify(|w| w.set_wutie(false)); + regs.cr().modify(|w| w.set_wute(false)); + + #[cfg(any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb + ))] + regs.isr().modify(|w| w.set_wutf(false)); + + #[cfg(any(rtc_v3, rtc_v3u5, rtc_v3l5))] + regs.scr().write(|w| w.set_cwutf(Calrf::CLEAR)); + + // Check RM for EXTI and/or NVIC section, "Event event input mapping" or "EXTI interrupt/event mapping" or something similar, + // there is a table for every "Event input" / "EXTI Line". + // If you find the EXTI line related to "RTC wakeup" marks as "Configurable" (not "Direct"), + // then write 1 to related field of Pending Register, to clean it's pending state. + #[cfg(any(exti_v1, stm32h7, stm32wb))] + crate::pac::EXTI + .pr(0) + .modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true)); + + ::WakeupInterrupt::unpend(); + }); + } + + self.stop_time.borrow(cs).take().map(|stop_time| instant - stop_time) + } + + pub(crate) fn enable_wakeup_line(&self) { + use crate::interrupt::typelevel::Interrupt; + + ::WakeupInterrupt::unpend(); + unsafe { ::WakeupInterrupt::enable() }; + + #[cfg(not(any(stm32u5, stm32u0)))] + { + use crate::pac::EXTI; + EXTI.rtsr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true)); + EXTI.imr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true)); + } + #[cfg(stm32u5)] + { + use crate::pac::RCC; + RCC.srdamr().modify(|w| w.set_rtcapbamen(true)); + RCC.apb3smenr().modify(|w| w.set_rtcapbsmen(true)); + } + } +} diff --git a/embassy/embassy-stm32/src/rtc/mod.rs b/embassy/embassy-stm32/src/rtc/mod.rs new file mode 100644 index 0000000..1a668cb --- /dev/null +++ b/embassy/embassy-stm32/src/rtc/mod.rs @@ -0,0 +1,311 @@ +//! Real Time Clock (RTC) +mod datetime; + +#[cfg(feature = "low-power")] +mod low_power; + +#[cfg(feature = "low-power")] +use core::cell::Cell; + +#[cfg(feature = "low-power")] +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +#[cfg(feature = "low-power")] +use embassy_sync::blocking_mutex::Mutex; + +use self::datetime::{day_of_week_from_u8, day_of_week_to_u8}; +pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; +use crate::pac::rtc::regs::{Dr, Tr}; +use crate::time::Hertz; + +/// refer to AN4759 to compare features of RTC2 and RTC3 +#[cfg_attr(any(rtc_v1), path = "v1.rs")] +#[cfg_attr( + any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb + ), + path = "v2.rs" +)] +#[cfg_attr(any(rtc_v3, rtc_v3u5, rtc_v3l5), path = "v3.rs")] +mod _version; +#[allow(unused_imports)] +pub use _version::*; +use embassy_hal_internal::Peripheral; + +use crate::peripherals::RTC; + +/// Errors that can occur on methods on [RtcClock] +#[non_exhaustive] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RtcError { + /// An invalid DateTime was given or stored on the hardware. + InvalidDateTime(DateTimeError), + + /// The current time could not be read + ReadFailure, + + /// The RTC clock is not running + NotRunning, +} + +/// Provides immutable access to the current time of the RTC. +pub struct RtcTimeProvider { + _private: (), +} + +impl RtcTimeProvider { + /// Return the current datetime. + /// + /// # Errors + /// + /// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`]. + pub fn now(&self) -> Result { + self.read(|dr, tr, _| { + let second = bcd2_to_byte((tr.st(), tr.su())); + let minute = bcd2_to_byte((tr.mnt(), tr.mnu())); + let hour = bcd2_to_byte((tr.ht(), tr.hu())); + + let weekday = day_of_week_from_u8(dr.wdu()).map_err(RtcError::InvalidDateTime)?; + let day = bcd2_to_byte((dr.dt(), dr.du())); + let month = bcd2_to_byte((dr.mt() as u8, dr.mu())); + let year = bcd2_to_byte((dr.yt(), dr.yu())) as u16 + 2000_u16; + + DateTime::from(year, month, day, weekday, hour, minute, second).map_err(RtcError::InvalidDateTime) + }) + } + + fn read(&self, mut f: impl FnMut(Dr, Tr, u16) -> Result) -> Result { + let r = RTC::regs(); + + #[cfg(not(rtc_v2f2))] + let read_ss = || r.ssr().read().ss(); + #[cfg(rtc_v2f2)] + let read_ss = || 0; + + let mut ss = read_ss(); + for _ in 0..5 { + let tr = r.tr().read(); + let dr = r.dr().read(); + let ss_after = read_ss(); + + // If an RTCCLK edge occurs during read we may see inconsistent values + // so read ssr again and see if it has changed. (see RM0433 Rev 7 46.3.9) + if ss == ss_after { + return f(dr, tr, ss.try_into().unwrap()); + } else { + ss = ss_after + } + } + + Err(RtcError::ReadFailure) + } +} + +/// RTC driver. +pub struct Rtc { + #[cfg(feature = "low-power")] + stop_time: Mutex>>, + _private: (), +} + +/// RTC configuration. +#[non_exhaustive] +#[derive(Copy, Clone, PartialEq)] +pub struct RtcConfig { + /// The subsecond counter frequency; default is 256 + /// + /// A high counter frequency may impact stop power consumption + pub frequency: Hertz, +} + +impl Default for RtcConfig { + /// LSI with prescalers assuming 32.768 kHz. + /// Raw sub-seconds in 1/256. + fn default() -> Self { + RtcConfig { frequency: Hertz(256) } + } +} + +/// Calibration cycle period. +#[derive(Default, Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum RtcCalibrationCyclePeriod { + /// 8-second calibration period + Seconds8, + /// 16-second calibration period + Seconds16, + /// 32-second calibration period + #[default] + Seconds32, +} + +impl Rtc { + /// Create a new RTC instance. + pub fn new(_rtc: impl Peripheral

, rtc_config: RtcConfig) -> Self { + #[cfg(not(any(stm32l0, stm32f3, stm32l1, stm32f0, stm32f2)))] + crate::rcc::enable_and_reset::(); + + let mut this = Self { + #[cfg(feature = "low-power")] + stop_time: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), + _private: (), + }; + + let frequency = Self::frequency(); + let async_psc = ((frequency.0 / rtc_config.frequency.0) - 1) as u8; + let sync_psc = (rtc_config.frequency.0 - 1) as u16; + + this.configure(async_psc, sync_psc); + + // Wait for the clock to update after initialization + #[cfg(not(rtc_v2f2))] + { + let now = this.time_provider().read(|_, _, ss| Ok(ss)).unwrap(); + while now == this.time_provider().read(|_, _, ss| Ok(ss)).unwrap() {} + } + + this + } + + fn frequency() -> Hertz { + let freqs = unsafe { crate::rcc::get_freqs() }; + freqs.rtc.to_hertz().unwrap() + } + + /// Acquire a [`RtcTimeProvider`] instance. + pub const fn time_provider(&self) -> RtcTimeProvider { + RtcTimeProvider { _private: () } + } + + /// Set the datetime to a new value. + /// + /// # Errors + /// + /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. + pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> { + self.write(true, |rtc| { + let (ht, hu) = byte_to_bcd2(t.hour()); + let (mnt, mnu) = byte_to_bcd2(t.minute()); + let (st, su) = byte_to_bcd2(t.second()); + + let (dt, du) = byte_to_bcd2(t.day()); + let (mt, mu) = byte_to_bcd2(t.month()); + let yr = t.year(); + let yr_offset = (yr - 2000_u16) as u8; + let (yt, yu) = byte_to_bcd2(yr_offset); + + use crate::pac::rtc::vals::Ampm; + + rtc.tr().write(|w| { + w.set_ht(ht); + w.set_hu(hu); + w.set_mnt(mnt); + w.set_mnu(mnu); + w.set_st(st); + w.set_su(su); + w.set_pm(Ampm::AM); + }); + + rtc.dr().write(|w| { + w.set_dt(dt); + w.set_du(du); + w.set_mt(mt > 0); + w.set_mu(mu); + w.set_yt(yt); + w.set_yu(yu); + w.set_wdu(day_of_week_to_u8(t.day_of_week())); + }); + }); + + Ok(()) + } + + /// Return the current datetime. + /// + /// # Errors + /// + /// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`]. + pub fn now(&self) -> Result { + self.time_provider().now() + } + + /// Check if daylight savings time is active. + pub fn get_daylight_savings(&self) -> bool { + let cr = RTC::regs().cr().read(); + cr.bkp() + } + + /// Enable/disable daylight savings time. + pub fn set_daylight_savings(&mut self, daylight_savings: bool) { + self.write(true, |rtc| { + rtc.cr().modify(|w| w.set_bkp(daylight_savings)); + }) + } + + /// Number of backup registers of this instance. + pub const BACKUP_REGISTER_COUNT: usize = RTC::BACKUP_REGISTER_COUNT; + + /// Read content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + pub fn read_backup_register(&self, register: usize) -> Option { + RTC::read_backup_register(RTC::regs(), register) + } + + /// Set content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + pub fn write_backup_register(&self, register: usize, value: u32) { + RTC::write_backup_register(RTC::regs(), register, value) + } +} + +pub(crate) fn byte_to_bcd2(byte: u8) -> (u8, u8) { + let mut bcd_high: u8 = 0; + let mut value = byte; + + while value >= 10 { + bcd_high += 1; + value -= 10; + } + + (bcd_high, ((bcd_high << 4) | value)) +} + +pub(crate) fn bcd2_to_byte(bcd: (u8, u8)) -> u8 { + let value = bcd.1 | bcd.0 << 4; + + let tmp = ((value & 0xF0) >> 0x4) * 10; + + tmp + (value & 0x0F) +} + +trait SealedInstance { + const BACKUP_REGISTER_COUNT: usize; + + #[cfg(feature = "low-power")] + #[cfg(not(any(stm32u5, stm32u0)))] + const EXTI_WAKEUP_LINE: usize; + + #[cfg(feature = "low-power")] + type WakeupInterrupt: crate::interrupt::typelevel::Interrupt; + + fn regs() -> crate::pac::rtc::Rtc { + crate::pac::RTC + } + + /// Read content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + fn read_backup_register(rtc: crate::pac::rtc::Rtc, register: usize) -> Option; + + /// Set content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + fn write_backup_register(rtc: crate::pac::rtc::Rtc, register: usize, value: u32); + + // fn apply_config(&mut self, rtc_config: RtcConfig); +} diff --git a/embassy/embassy-stm32/src/rtc/v2.rs b/embassy/embassy-stm32/src/rtc/v2.rs new file mode 100644 index 0000000..5d9025b --- /dev/null +++ b/embassy/embassy-stm32/src/rtc/v2.rs @@ -0,0 +1,159 @@ +use stm32_metapac::rtc::vals::{Osel, Pol}; + +use super::SealedInstance; +use crate::pac::rtc::Rtc; +use crate::peripherals::RTC; + +#[allow(dead_code)] +impl super::Rtc { + /// Applies the RTC config + /// It this changes the RTC clock source the time will be reset + pub(super) fn configure(&mut self, async_psc: u8, sync_psc: u16) { + self.write(true, |rtc| { + rtc.cr().modify(|w| { + #[cfg(not(rtc_v2f2))] + w.set_bypshad(true); + #[cfg(rtc_v2f2)] + w.set_fmt(false); + #[cfg(not(rtc_v2f2))] + w.set_fmt(stm32_metapac::rtc::vals::Fmt::TWENTY_FOUR_HOUR); + w.set_osel(Osel::DISABLED); + w.set_pol(Pol::HIGH); + }); + + rtc.prer().modify(|w| { + w.set_prediv_s(sync_psc); + w.set_prediv_a(async_psc); + }); + }); + } + + /// Calibrate the clock drift. + /// + /// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range. + /// + /// ### Note + /// + /// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler` + /// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12). + #[cfg(not(rtc_v2f2))] + pub fn calibrate(&mut self, mut clock_drift: f32, period: super::RtcCalibrationCyclePeriod) { + const RTC_CALR_MIN_PPM: f32 = -487.1; + const RTC_CALR_MAX_PPM: f32 = 488.5; + const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537; + + if clock_drift < RTC_CALR_MIN_PPM { + clock_drift = RTC_CALR_MIN_PPM; + } else if clock_drift > RTC_CALR_MAX_PPM { + clock_drift = RTC_CALR_MAX_PPM; + } + + clock_drift /= RTC_CALR_RESOLUTION_PPM; + + self.write(false, |rtc| { + rtc.calr().write(|w| { + match period { + super::RtcCalibrationCyclePeriod::Seconds8 => { + w.set_calw8(stm32_metapac::rtc::vals::Calw8::EIGHT_SECOND); + } + super::RtcCalibrationCyclePeriod::Seconds16 => { + w.set_calw16(stm32_metapac::rtc::vals::Calw16::SIXTEEN_SECOND); + } + super::RtcCalibrationCyclePeriod::Seconds32 => { + // Set neither `calw8` nor `calw16` to use 32 seconds + } + } + + // Extra pulses during calibration cycle period: CALP * 512 - CALM + // + // CALP sets whether pulses are added or omitted. + // + // CALM contains how many pulses (out of 512) are masked in a + // given calibration cycle period. + if clock_drift > 0.0 { + // Maximum (about 512.2) rounds to 512. + clock_drift += 0.5; + + // When the offset is positive (0 to 512), the opposite of + // the offset (512 - offset) is masked, i.e. for the + // maximum offset (512), 0 pulses are masked. + w.set_calp(stm32_metapac::rtc::vals::Calp::INCREASEFREQ); + w.set_calm(512 - clock_drift as u16); + } else { + // Minimum (about -510.7) rounds to -511. + clock_drift -= 0.5; + + // When the offset is negative or zero (-511 to 0), + // the absolute offset is masked, i.e. for the minimum + // offset (-511), 511 pulses are masked. + w.set_calp(stm32_metapac::rtc::vals::Calp::NOCHANGE); + w.set_calm((clock_drift * -1.0) as u16); + } + }); + }) + } + + pub(super) fn write(&self, init_mode: bool, f: F) -> R + where + F: FnOnce(crate::pac::rtc::Rtc) -> R, + { + let r = RTC::regs(); + // Disable write protection. + // This is safe, as we're only writin the correct and expected values. + r.wpr().write(|w| w.set_key(0xca)); + r.wpr().write(|w| w.set_key(0x53)); + + // true if initf bit indicates RTC peripheral is in init mode + if init_mode && !r.isr().read().initf() { + // to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode + r.isr().modify(|w| w.set_init(true)); + // wait till init state entered + // ~2 RTCCLK cycles + while !r.isr().read().initf() {} + } + + let result = f(r); + + if init_mode { + r.isr().modify(|w| w.set_init(false)); // Exits init mode + } + + // Re-enable write protection. + // This is safe, as the field accepts the full range of 8-bit values. + r.wpr().write(|w| w.set_key(0xff)); + result + } +} + +impl SealedInstance for crate::peripherals::RTC { + const BACKUP_REGISTER_COUNT: usize = 20; + + #[cfg(all(feature = "low-power", stm32f4))] + const EXTI_WAKEUP_LINE: usize = 22; + + #[cfg(all(feature = "low-power", stm32l4))] + const EXTI_WAKEUP_LINE: usize = 20; + + #[cfg(all(feature = "low-power", stm32l0))] + const EXTI_WAKEUP_LINE: usize = 20; + + #[cfg(all(feature = "low-power", any(stm32f4, stm32l4)))] + type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP; + + #[cfg(all(feature = "low-power", stm32l0))] + type WakeupInterrupt = crate::interrupt::typelevel::RTC; + + fn read_backup_register(rtc: Rtc, register: usize) -> Option { + if register < Self::BACKUP_REGISTER_COUNT { + Some(rtc.bkpr(register).read().bkp()) + } else { + None + } + } + + fn write_backup_register(rtc: Rtc, register: usize, value: u32) { + if register < Self::BACKUP_REGISTER_COUNT { + rtc.bkpr(register).write(|w| w.set_bkp(value)); + } + } +} diff --git a/embassy/embassy-stm32/src/rtc/v3.rs b/embassy/embassy-stm32/src/rtc/v3.rs new file mode 100644 index 0000000..de2c202 --- /dev/null +++ b/embassy/embassy-stm32/src/rtc/v3.rs @@ -0,0 +1,170 @@ +use stm32_metapac::rtc::vals::{Calp, Calw16, Calw8, Fmt, Key, Osel, Pol, TampalrmType}; + +use super::RtcCalibrationCyclePeriod; +use crate::pac::rtc::Rtc; +use crate::peripherals::RTC; +use crate::rtc::SealedInstance; + +impl super::Rtc { + /// Applies the RTC config + /// It this changes the RTC clock source the time will be reset + pub(super) fn configure(&mut self, async_psc: u8, sync_psc: u16) { + self.write(true, |rtc| { + rtc.cr().modify(|w| { + w.set_bypshad(true); + w.set_fmt(Fmt::TWENTYFOURHOUR); + w.set_osel(Osel::DISABLED); + w.set_pol(Pol::HIGH); + }); + + rtc.prer().modify(|w| { + w.set_prediv_s(sync_psc); + w.set_prediv_a(async_psc); + }); + + // TODO: configuration for output pins + rtc.cr().modify(|w| { + w.set_out2en(false); + w.set_tampalrm_type(TampalrmType::PUSHPULL); + w.set_tampalrm_pu(false); + }); + }); + } + + const RTC_CALR_MIN_PPM: f32 = -487.1; + const RTC_CALR_MAX_PPM: f32 = 488.5; + const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537; + + /// Calibrate the clock drift. + /// + /// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range. + /// + /// ### Note + /// + /// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler` + /// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12). + pub fn calibrate(&mut self, mut clock_drift: f32, period: RtcCalibrationCyclePeriod) { + if clock_drift < Self::RTC_CALR_MIN_PPM { + clock_drift = Self::RTC_CALR_MIN_PPM; + } else if clock_drift > Self::RTC_CALR_MAX_PPM { + clock_drift = Self::RTC_CALR_MAX_PPM; + } + + clock_drift /= Self::RTC_CALR_RESOLUTION_PPM; + + self.write(false, |rtc| { + rtc.calr().write(|w| { + match period { + RtcCalibrationCyclePeriod::Seconds8 => { + w.set_calw8(Calw8::EIGHTSECONDS); + } + RtcCalibrationCyclePeriod::Seconds16 => { + w.set_calw16(Calw16::SIXTEENSECONDS); + } + RtcCalibrationCyclePeriod::Seconds32 => { + // Set neither `calw8` nor `calw16` to use 32 seconds + } + } + + // Extra pulses during calibration cycle period: CALP * 512 - CALM + // + // CALP sets whether pulses are added or omitted. + // + // CALM contains how many pulses (out of 512) are masked in a + // given calibration cycle period. + if clock_drift > 0.0 { + // Maximum (about 512.2) rounds to 512. + clock_drift += 0.5; + + // When the offset is positive (0 to 512), the opposite of + // the offset (512 - offset) is masked, i.e. for the + // maximum offset (512), 0 pulses are masked. + w.set_calp(Calp::INCREASEFREQ); + w.set_calm(512 - clock_drift as u16); + } else { + // Minimum (about -510.7) rounds to -511. + clock_drift -= 0.5; + + // When the offset is negative or zero (-511 to 0), + // the absolute offset is masked, i.e. for the minimum + // offset (-511), 511 pulses are masked. + w.set_calp(Calp::NOCHANGE); + w.set_calm((clock_drift * -1.0) as u16); + } + }); + }) + } + + pub(super) fn write(&self, init_mode: bool, f: F) -> R + where + F: FnOnce(crate::pac::rtc::Rtc) -> R, + { + let r = RTC::regs(); + // Disable write protection. + // This is safe, as we're only writin the correct and expected values. + r.wpr().write(|w| w.set_key(Key::DEACTIVATE1)); + r.wpr().write(|w| w.set_key(Key::DEACTIVATE2)); + + if init_mode && !r.icsr().read().initf() { + r.icsr().modify(|w| w.set_init(true)); + // wait till init state entered + // ~2 RTCCLK cycles + while !r.icsr().read().initf() {} + } + + let result = f(r); + + if init_mode { + r.icsr().modify(|w| w.set_init(false)); // Exits init mode + } + + // Re-enable write protection. + // This is safe, as the field accepts the full range of 8-bit values. + r.wpr().write(|w| w.set_key(Key::ACTIVATE)); + + result + } +} + +impl SealedInstance for crate::peripherals::RTC { + const BACKUP_REGISTER_COUNT: usize = 32; + + #[cfg(feature = "low-power")] + cfg_if::cfg_if!( + if #[cfg(stm32g4)] { + const EXTI_WAKEUP_LINE: usize = 20; + } else if #[cfg(stm32g0)] { + const EXTI_WAKEUP_LINE: usize = 19; + } else if #[cfg(any(stm32l5, stm32h5))] { + const EXTI_WAKEUP_LINE: usize = 17; + } + ); + + #[cfg(feature = "low-power")] + cfg_if::cfg_if!( + if #[cfg(stm32g4)] { + type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP; + } else if #[cfg(any(stm32g0, stm32u0))] { + type WakeupInterrupt = crate::interrupt::typelevel::RTC_TAMP; + } else if #[cfg(any(stm32l5, stm32h5, stm32u5))] { + type WakeupInterrupt = crate::interrupt::typelevel::RTC; + } + ); + + fn read_backup_register(_rtc: Rtc, register: usize) -> Option { + #[allow(clippy::if_same_then_else)] + if register < Self::BACKUP_REGISTER_COUNT { + //Some(rtc.bkpr()[register].read().bits()) + None // RTC3 backup registers come from the TAMP peripheral, not RTC. Not() even in the L412 PAC + } else { + None + } + } + + fn write_backup_register(_rtc: Rtc, register: usize, _value: u32) { + if register < Self::BACKUP_REGISTER_COUNT { + // RTC3 backup registers come from the TAMP peripheral, not RTC. Not() even in the L412 PAC + //self.rtc.bkpr()[register].write(|w| w.bits(value)) + } + } +} diff --git a/embassy/embassy-stm32/src/sai/mod.rs b/embassy/embassy-stm32/src/sai/mod.rs new file mode 100644 index 0000000..a8d02f8 --- /dev/null +++ b/embassy/embassy-stm32/src/sai/mod.rs @@ -0,0 +1,1126 @@ +//! Serial Audio Interface (SAI) +#![macro_use] +#![cfg_attr(gpdma, allow(unused))] + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +pub use crate::dma::word; +#[cfg(not(gpdma))] +use crate::dma::{ringbuffer, Channel, ReadableRingBuffer, Request, TransferOptions, WritableRingBuffer}; +use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; +use crate::pac::sai::{vals, Sai as Regs}; +use crate::rcc::{self, RccPeripheral}; +use crate::{peripherals, Peripheral}; + +/// SAI error +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// `write` called on a SAI in receive mode. + NotATransmitter, + /// `read` called on a SAI in transmit mode. + NotAReceiver, + /// Overrun + Overrun, +} + +#[cfg(not(gpdma))] +impl From for Error { + fn from(#[allow(unused)] err: ringbuffer::Error) -> Self { + #[cfg(feature = "defmt")] + { + if err == ringbuffer::Error::DmaUnsynced { + defmt::error!("Ringbuffer broken invariants detected!"); + } + } + Self::Overrun + } +} + +/// Master/slave mode. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum Mode { + Master, + Slave, +} + +impl Mode { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn mode(&self, tx_rx: TxRx) -> vals::Mode { + match tx_rx { + TxRx::Transmitter => match self { + Mode::Master => vals::Mode::MASTERTX, + Mode::Slave => vals::Mode::SLAVETX, + }, + TxRx::Receiver => match self { + Mode::Master => vals::Mode::MASTERRX, + Mode::Slave => vals::Mode::SLAVERX, + }, + } + } +} + +/// Direction: transmit or receive +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum TxRx { + Transmitter, + Receiver, +} + +/// Data slot size. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum SlotSize { + DataSize, + /// 16 bit data length on 16 bit wide channel + Channel16, + /// 16 bit data length on 32 bit wide channel + Channel32, +} + +impl SlotSize { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn slotsz(&self) -> vals::Slotsz { + match self { + SlotSize::DataSize => vals::Slotsz::DATASIZE, + SlotSize::Channel16 => vals::Slotsz::BIT16, + SlotSize::Channel32 => vals::Slotsz::BIT32, + } + } +} + +/// Data size. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum DataSize { + Data8, + Data10, + Data16, + Data20, + Data24, + Data32, +} + +impl DataSize { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn ds(&self) -> vals::Ds { + match self { + DataSize::Data8 => vals::Ds::BIT8, + DataSize::Data10 => vals::Ds::BIT10, + DataSize::Data16 => vals::Ds::BIT16, + DataSize::Data20 => vals::Ds::BIT20, + DataSize::Data24 => vals::Ds::BIT24, + DataSize::Data32 => vals::Ds::BIT32, + } + } +} + +/// FIFO threshold level. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum FifoThreshold { + Empty, + Quarter, + Half, + ThreeQuarters, + Full, +} + +impl FifoThreshold { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn fth(&self) -> vals::Fth { + match self { + FifoThreshold::Empty => vals::Fth::EMPTY, + FifoThreshold::Quarter => vals::Fth::QUARTER1, + FifoThreshold::Half => vals::Fth::QUARTER2, + FifoThreshold::ThreeQuarters => vals::Fth::QUARTER3, + FifoThreshold::Full => vals::Fth::FULL, + } + } +} + +/// Output value on mute. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum MuteValue { + Zero, + LastValue, +} + +impl MuteValue { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn muteval(&self) -> vals::Muteval { + match self { + MuteValue::Zero => vals::Muteval::SENDZERO, + MuteValue::LastValue => vals::Muteval::SENDLAST, + } + } +} + +/// Protocol variant to use. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum Protocol { + Free, + Spdif, + Ac97, +} + +impl Protocol { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn prtcfg(&self) -> vals::Prtcfg { + match self { + Protocol::Free => vals::Prtcfg::FREE, + Protocol::Spdif => vals::Prtcfg::SPDIF, + Protocol::Ac97 => vals::Prtcfg::AC97, + } + } +} + +/// Sync input between SAI units/blocks. +#[derive(Copy, Clone, PartialEq)] +#[allow(missing_docs)] +pub enum SyncInput { + /// Not synced to any other SAI unit. + None, + /// Syncs with the other A/B sub-block within the SAI unit + Internal, + /// Syncs with a sub-block in the other SAI unit + #[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] + External(SyncInputInstance), +} + +impl SyncInput { + const fn syncen(&self) -> vals::Syncen { + match self { + SyncInput::None => vals::Syncen::ASYNCHRONOUS, + SyncInput::Internal => vals::Syncen::INTERNAL, + #[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] + SyncInput::External(_) => vals::Syncen::EXTERNAL, + } + } +} + +/// SAI instance to sync from. +#[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] +#[derive(Copy, Clone, PartialEq)] +#[allow(missing_docs)] +pub enum SyncInputInstance { + #[cfg(peri_sai1)] + Sai1 = 0, + #[cfg(peri_sai2)] + Sai2 = 1, + #[cfg(peri_sai3)] + Sai3 = 2, + #[cfg(peri_sai4)] + Sai4 = 3, +} + +/// Channels (stereo or mono). +#[derive(Copy, Clone, PartialEq)] +#[allow(missing_docs)] +pub enum StereoMono { + Stereo, + Mono, +} + +impl StereoMono { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn mono(&self) -> vals::Mono { + match self { + StereoMono::Stereo => vals::Mono::STEREO, + StereoMono::Mono => vals::Mono::MONO, + } + } +} + +/// Bit order +#[derive(Copy, Clone)] +pub enum BitOrder { + /// Least significant bit first. + LsbFirst, + /// Most significant bit first. + MsbFirst, +} + +impl BitOrder { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn lsbfirst(&self) -> vals::Lsbfirst { + match self { + BitOrder::LsbFirst => vals::Lsbfirst::LSBFIRST, + BitOrder::MsbFirst => vals::Lsbfirst::MSBFIRST, + } + } +} + +/// Frame sync offset. +#[derive(Copy, Clone)] +pub enum FrameSyncOffset { + /// This is used in modes other than standard I2S phillips mode + OnFirstBit, + /// This is used in standard I2S phillips mode + BeforeFirstBit, +} + +impl FrameSyncOffset { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn fsoff(&self) -> vals::Fsoff { + match self { + FrameSyncOffset::OnFirstBit => vals::Fsoff::ONFIRST, + FrameSyncOffset::BeforeFirstBit => vals::Fsoff::BEFOREFIRST, + } + } +} + +/// Frame sync polarity +#[derive(Copy, Clone)] +pub enum FrameSyncPolarity { + /// Sync signal is active low. + ActiveLow, + /// Sync signal is active high + ActiveHigh, +} + +impl FrameSyncPolarity { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn fspol(&self) -> vals::Fspol { + match self { + FrameSyncPolarity::ActiveLow => vals::Fspol::FALLINGEDGE, + FrameSyncPolarity::ActiveHigh => vals::Fspol::RISINGEDGE, + } + } +} + +/// Sync definition. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum FrameSyncDefinition { + StartOfFrame, + ChannelIdentification, +} + +impl FrameSyncDefinition { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn fsdef(&self) -> bool { + match self { + FrameSyncDefinition::StartOfFrame => false, + FrameSyncDefinition::ChannelIdentification => true, + } + } +} + +/// Clock strobe. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum ClockStrobe { + Falling, + Rising, +} + +impl ClockStrobe { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn ckstr(&self) -> vals::Ckstr { + match self { + ClockStrobe::Falling => vals::Ckstr::FALLINGEDGE, + ClockStrobe::Rising => vals::Ckstr::RISINGEDGE, + } + } +} + +/// Complements format for negative samples. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum ComplementFormat { + OnesComplement, + TwosComplement, +} + +impl ComplementFormat { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn cpl(&self) -> vals::Cpl { + match self { + ComplementFormat::OnesComplement => vals::Cpl::ONESCOMPLEMENT, + ComplementFormat::TwosComplement => vals::Cpl::TWOSCOMPLEMENT, + } + } +} + +/// Companding setting. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum Companding { + None, + MuLaw, + ALaw, +} + +impl Companding { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn comp(&self) -> vals::Comp { + match self { + Companding::None => vals::Comp::NOCOMPANDING, + Companding::MuLaw => vals::Comp::MULAW, + Companding::ALaw => vals::Comp::ALAW, + } + } +} + +/// Output drive +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum OutputDrive { + OnStart, + Immediately, +} + +impl OutputDrive { + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn outdriv(&self) -> vals::Outdriv { + match self { + OutputDrive::OnStart => vals::Outdriv::ONSTART, + OutputDrive::Immediately => vals::Outdriv::IMMEDIATELY, + } + } +} + +/// Master clock divider. +#[derive(Copy, Clone, PartialEq)] +#[allow(missing_docs)] +#[cfg(any(sai_v1, sai_v2))] +pub enum MasterClockDivider { + MasterClockDisabled, + Div1, + Div2, + Div4, + Div6, + Div8, + Div10, + Div12, + Div14, + Div16, + Div18, + Div20, + Div22, + Div24, + Div26, + Div28, + Div30, +} + +/// Master clock divider. +#[derive(Copy, Clone, PartialEq)] +#[allow(missing_docs)] +#[cfg(any(sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] +pub enum MasterClockDivider { + MasterClockDisabled, + Div1, + Div2, + Div3, + Div4, + Div5, + Div6, + Div7, + Div8, + Div9, + Div10, + Div11, + Div12, + Div13, + Div14, + Div15, + Div16, + Div17, + Div18, + Div19, + Div20, + Div21, + Div22, + Div23, + Div24, + Div25, + Div26, + Div27, + Div28, + Div29, + Div30, + Div31, + Div32, + Div33, + Div34, + Div35, + Div36, + Div37, + Div38, + Div39, + Div40, + Div41, + Div42, + Div43, + Div44, + Div45, + Div46, + Div47, + Div48, + Div49, + Div50, + Div51, + Div52, + Div53, + Div54, + Div55, + Div56, + Div57, + Div58, + Div59, + Div60, + Div61, + Div62, + Div63, +} + +impl MasterClockDivider { + #[cfg(any(sai_v1, sai_v2))] + const fn mckdiv(&self) -> u8 { + match self { + MasterClockDivider::MasterClockDisabled => 0, + MasterClockDivider::Div1 => 0, + MasterClockDivider::Div2 => 1, + MasterClockDivider::Div4 => 2, + MasterClockDivider::Div6 => 3, + MasterClockDivider::Div8 => 4, + MasterClockDivider::Div10 => 5, + MasterClockDivider::Div12 => 6, + MasterClockDivider::Div14 => 7, + MasterClockDivider::Div16 => 8, + MasterClockDivider::Div18 => 9, + MasterClockDivider::Div20 => 10, + MasterClockDivider::Div22 => 11, + MasterClockDivider::Div24 => 12, + MasterClockDivider::Div26 => 13, + MasterClockDivider::Div28 => 14, + MasterClockDivider::Div30 => 15, + } + } + + #[cfg(any(sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + const fn mckdiv(&self) -> u8 { + match self { + MasterClockDivider::MasterClockDisabled => 0, + MasterClockDivider::Div1 => 1, + MasterClockDivider::Div2 => 2, + MasterClockDivider::Div3 => 3, + MasterClockDivider::Div4 => 4, + MasterClockDivider::Div5 => 5, + MasterClockDivider::Div6 => 6, + MasterClockDivider::Div7 => 7, + MasterClockDivider::Div8 => 8, + MasterClockDivider::Div9 => 9, + MasterClockDivider::Div10 => 10, + MasterClockDivider::Div11 => 11, + MasterClockDivider::Div12 => 12, + MasterClockDivider::Div13 => 13, + MasterClockDivider::Div14 => 14, + MasterClockDivider::Div15 => 15, + MasterClockDivider::Div16 => 16, + MasterClockDivider::Div17 => 17, + MasterClockDivider::Div18 => 18, + MasterClockDivider::Div19 => 19, + MasterClockDivider::Div20 => 20, + MasterClockDivider::Div21 => 21, + MasterClockDivider::Div22 => 22, + MasterClockDivider::Div23 => 23, + MasterClockDivider::Div24 => 24, + MasterClockDivider::Div25 => 25, + MasterClockDivider::Div26 => 26, + MasterClockDivider::Div27 => 27, + MasterClockDivider::Div28 => 28, + MasterClockDivider::Div29 => 29, + MasterClockDivider::Div30 => 30, + MasterClockDivider::Div31 => 31, + MasterClockDivider::Div32 => 32, + MasterClockDivider::Div33 => 33, + MasterClockDivider::Div34 => 34, + MasterClockDivider::Div35 => 35, + MasterClockDivider::Div36 => 36, + MasterClockDivider::Div37 => 37, + MasterClockDivider::Div38 => 38, + MasterClockDivider::Div39 => 39, + MasterClockDivider::Div40 => 40, + MasterClockDivider::Div41 => 41, + MasterClockDivider::Div42 => 42, + MasterClockDivider::Div43 => 43, + MasterClockDivider::Div44 => 44, + MasterClockDivider::Div45 => 45, + MasterClockDivider::Div46 => 46, + MasterClockDivider::Div47 => 47, + MasterClockDivider::Div48 => 48, + MasterClockDivider::Div49 => 49, + MasterClockDivider::Div50 => 50, + MasterClockDivider::Div51 => 51, + MasterClockDivider::Div52 => 52, + MasterClockDivider::Div53 => 53, + MasterClockDivider::Div54 => 54, + MasterClockDivider::Div55 => 55, + MasterClockDivider::Div56 => 56, + MasterClockDivider::Div57 => 57, + MasterClockDivider::Div58 => 58, + MasterClockDivider::Div59 => 59, + MasterClockDivider::Div60 => 60, + MasterClockDivider::Div61 => 61, + MasterClockDivider::Div62 => 62, + MasterClockDivider::Div63 => 63, + } + } +} + +/// [`SAI`] configuration. +#[allow(missing_docs)] +#[non_exhaustive] +#[derive(Copy, Clone)] +pub struct Config { + pub mode: Mode, + pub tx_rx: TxRx, + pub sync_input: SyncInput, + pub sync_output: bool, + pub protocol: Protocol, + pub slot_size: SlotSize, + pub slot_count: word::U4, + pub slot_enable: u16, + pub first_bit_offset: word::U5, + pub data_size: DataSize, + pub stereo_mono: StereoMono, + pub bit_order: BitOrder, + pub frame_sync_offset: FrameSyncOffset, + pub frame_sync_polarity: FrameSyncPolarity, + pub frame_sync_active_level_length: word::U7, + pub frame_sync_definition: FrameSyncDefinition, + pub frame_length: u8, + pub clock_strobe: ClockStrobe, + pub output_drive: OutputDrive, + pub master_clock_divider: MasterClockDivider, + pub is_high_impedance_on_inactive_slot: bool, + pub fifo_threshold: FifoThreshold, + pub companding: Companding, + pub complement_format: ComplementFormat, + pub mute_value: MuteValue, + pub mute_detection_counter: word::U5, +} + +impl Default for Config { + fn default() -> Self { + Self { + mode: Mode::Master, + tx_rx: TxRx::Transmitter, + sync_output: false, + sync_input: SyncInput::None, + protocol: Protocol::Free, + slot_size: SlotSize::DataSize, + slot_count: word::U4(2), + first_bit_offset: word::U5(0), + slot_enable: 0b11, + data_size: DataSize::Data16, + stereo_mono: StereoMono::Stereo, + bit_order: BitOrder::LsbFirst, + frame_sync_offset: FrameSyncOffset::BeforeFirstBit, + frame_sync_polarity: FrameSyncPolarity::ActiveLow, + frame_sync_active_level_length: word::U7(16), + frame_sync_definition: FrameSyncDefinition::ChannelIdentification, + frame_length: 32, + master_clock_divider: MasterClockDivider::MasterClockDisabled, + clock_strobe: ClockStrobe::Rising, + output_drive: OutputDrive::Immediately, + is_high_impedance_on_inactive_slot: false, + fifo_threshold: FifoThreshold::ThreeQuarters, + companding: Companding::None, + complement_format: ComplementFormat::TwosComplement, + mute_value: MuteValue::Zero, + mute_detection_counter: word::U5(4), + } + } +} + +impl Config { + /// Create a new config with all default values. + pub fn new() -> Self { + return Default::default(); + } +} + +#[cfg(not(gpdma))] +enum RingBuffer<'d, W: word::Word> { + Writable(WritableRingBuffer<'d, W>), + Readable(ReadableRingBuffer<'d, W>), +} + +fn dr(w: crate::pac::sai::Sai, sub_block: WhichSubBlock) -> *mut W { + let ch = w.ch(sub_block as usize); + ch.dr().as_ptr() as _ +} + +// return the type for (sd, sck) +fn get_af_types(mode: Mode, tx_rx: TxRx) -> (AfType, AfType) { + ( + //sd is defined by tx/rx mode + match tx_rx { + TxRx::Transmitter => AfType::output(OutputType::PushPull, Speed::VeryHigh), + TxRx::Receiver => AfType::input(Pull::Down), // Ensure mute level when no input is connected. + }, + //clocks (mclk, sck and fs) are defined by master/slave + match mode { + Mode::Master => AfType::output(OutputType::PushPull, Speed::VeryHigh), + Mode::Slave => AfType::input(Pull::Down), // Ensure no clocks when no input is connected. + }, + ) +} + +#[cfg(not(gpdma))] +fn get_ring_buffer<'d, T: Instance, W: word::Word>( + dma: impl Peripheral

+ 'd, + dma_buf: &'d mut [W], + request: Request, + sub_block: WhichSubBlock, + tx_rx: TxRx, +) -> RingBuffer<'d, W> { + let opts = TransferOptions { + half_transfer_ir: true, + //the new_write() and new_read() always use circular mode + ..Default::default() + }; + match tx_rx { + TxRx::Transmitter => RingBuffer::Writable(unsafe { + WritableRingBuffer::new(dma, request, dr(T::REGS, sub_block), dma_buf, opts) + }), + TxRx::Receiver => RingBuffer::Readable(unsafe { + ReadableRingBuffer::new(dma, request, dr(T::REGS, sub_block), dma_buf, opts) + }), + } +} + +fn update_synchronous_config(config: &mut Config) { + config.mode = Mode::Slave; + config.sync_output = false; + + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm))] + { + config.sync_input = SyncInput::Internal; + } + + #[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] + { + //this must either be Internal or External + //The asynchronous sub-block on the same SAI needs to enable sync_output + assert!(config.sync_input != SyncInput::None); + } +} + +/// SAI subblock instance. +pub struct SubBlock<'d, T, S: SubBlockInstance> { + peri: PeripheralRef<'d, T>, + _phantom: PhantomData, +} + +/// Split the main SAIx peripheral into the two subblocks. +/// +/// You can then create a [`Sai`] driver for each each half. +pub fn split_subblocks<'d, T: Instance>(peri: impl Peripheral

+ 'd) -> (SubBlock<'d, T, A>, SubBlock<'d, T, B>) { + into_ref!(peri); + rcc::enable_and_reset::(); + + ( + SubBlock { + peri: unsafe { peri.clone_unchecked() }, + _phantom: PhantomData, + }, + SubBlock { + peri, + _phantom: PhantomData, + }, + ) +} + +/// SAI sub-block driver. +pub struct Sai<'d, T: Instance, W: word::Word> { + _peri: PeripheralRef<'d, T>, + sd: Option>, + fs: Option>, + sck: Option>, + mclk: Option>, + #[cfg(gpdma)] + ring_buffer: PhantomData, + #[cfg(not(gpdma))] + ring_buffer: RingBuffer<'d, W>, + sub_block: WhichSubBlock, +} + +#[cfg(not(gpdma))] +impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { + /// Create a new SAI driver in asynchronous mode with MCLK. + /// + /// You can obtain the [`SubBlock`] with [`split_subblocks`]. + pub fn new_asynchronous_with_mclk( + peri: SubBlock<'d, T, S>, + sck: impl Peripheral

> + 'd, + sd: impl Peripheral

> + 'd, + fs: impl Peripheral

> + 'd, + mclk: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + dma_buf: &'d mut [W], + mut config: Config, + ) -> Self { + into_ref!(mclk); + + let (_sd_af_type, ck_af_type) = get_af_types(config.mode, config.tx_rx); + mclk.set_as_af(mclk.af_num(), ck_af_type); + + if config.master_clock_divider == MasterClockDivider::MasterClockDisabled { + config.master_clock_divider = MasterClockDivider::Div1; + } + + Self::new_asynchronous(peri, sck, sd, fs, dma, dma_buf, config) + } + + /// Create a new SAI driver in asynchronous mode without MCLK. + /// + /// You can obtain the [`SubBlock`] with [`split_subblocks`]. + pub fn new_asynchronous( + peri: SubBlock<'d, T, S>, + sck: impl Peripheral

> + 'd, + sd: impl Peripheral

> + 'd, + fs: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + dma_buf: &'d mut [W], + config: Config, + ) -> Self { + let peri = peri.peri; + into_ref!(peri, dma, sck, sd, fs); + + let (sd_af_type, ck_af_type) = get_af_types(config.mode, config.tx_rx); + sd.set_as_af(sd.af_num(), sd_af_type); + sck.set_as_af(sck.af_num(), ck_af_type); + fs.set_as_af(fs.af_num(), ck_af_type); + + let sub_block = S::WHICH; + let request = dma.request(); + + Self::new_inner( + peri, + sub_block, + Some(sck.map_into()), + None, + Some(sd.map_into()), + Some(fs.map_into()), + get_ring_buffer::(dma, dma_buf, request, sub_block, config.tx_rx), + config, + ) + } + + /// Create a new SAI driver in synchronous mode. + /// + /// You can obtain the [`SubBlock`] with [`split_subblocks`]. + pub fn new_synchronous( + peri: SubBlock<'d, T, S>, + sd: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + dma_buf: &'d mut [W], + mut config: Config, + ) -> Self { + update_synchronous_config(&mut config); + + let peri = peri.peri; + into_ref!(dma, peri, sd); + + let (sd_af_type, _ck_af_type) = get_af_types(config.mode, config.tx_rx); + sd.set_as_af(sd.af_num(), sd_af_type); + + let sub_block = S::WHICH; + let request = dma.request(); + + Self::new_inner( + peri, + sub_block, + None, + None, + Some(sd.map_into()), + None, + get_ring_buffer::(dma, dma_buf, request, sub_block, config.tx_rx), + config, + ) + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + sub_block: WhichSubBlock, + sck: Option>, + mclk: Option>, + sd: Option>, + fs: Option>, + ring_buffer: RingBuffer<'d, W>, + config: Config, + ) -> Self { + let ch = T::REGS.ch(sub_block as usize); + + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + { + ch.cr1().modify(|w| w.set_saien(false)); + } + + ch.cr2().modify(|w| w.set_fflush(true)); + + #[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] + { + if let SyncInput::External(i) = config.sync_input { + T::REGS.gcr().modify(|w| { + w.set_syncin(i as u8); + }); + } + + if config.sync_output { + let syncout: u8 = match sub_block { + WhichSubBlock::A => 0b01, + WhichSubBlock::B => 0b10, + }; + T::REGS.gcr().modify(|w| { + w.set_syncout(syncout); + }); + } + } + + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] + { + ch.cr1().modify(|w| { + w.set_mode(config.mode.mode(if Self::is_transmitter(&ring_buffer) { + TxRx::Transmitter + } else { + TxRx::Receiver + })); + w.set_prtcfg(config.protocol.prtcfg()); + w.set_ds(config.data_size.ds()); + w.set_lsbfirst(config.bit_order.lsbfirst()); + w.set_ckstr(config.clock_strobe.ckstr()); + w.set_syncen(config.sync_input.syncen()); + w.set_mono(config.stereo_mono.mono()); + w.set_outdriv(config.output_drive.outdriv()); + w.set_mckdiv(config.master_clock_divider.mckdiv()); + w.set_nodiv( + if config.master_clock_divider == MasterClockDivider::MasterClockDisabled { + vals::Nodiv::NODIV + } else { + vals::Nodiv::MASTERCLOCK + }, + ); + w.set_dmaen(true); + }); + + ch.cr2().modify(|w| { + w.set_fth(config.fifo_threshold.fth()); + w.set_comp(config.companding.comp()); + w.set_cpl(config.complement_format.cpl()); + w.set_muteval(config.mute_value.muteval()); + w.set_mutecnt(config.mute_detection_counter.0 as u8); + w.set_tris(config.is_high_impedance_on_inactive_slot); + }); + + ch.frcr().modify(|w| { + w.set_fsoff(config.frame_sync_offset.fsoff()); + w.set_fspol(config.frame_sync_polarity.fspol()); + w.set_fsdef(config.frame_sync_definition.fsdef()); + w.set_fsall(config.frame_sync_active_level_length.0 as u8 - 1); + w.set_frl(config.frame_length - 1); + }); + + ch.slotr().modify(|w| { + w.set_nbslot(config.slot_count.0 as u8 - 1); + w.set_slotsz(config.slot_size.slotsz()); + w.set_fboff(config.first_bit_offset.0 as u8); + w.set_sloten(vals::Sloten(config.slot_enable as u16)); + }); + + ch.cr1().modify(|w| w.set_saien(true)); + + if ch.cr1().read().saien() == false { + panic!("SAI failed to enable. Check that config is valid (frame length, slot count, etc)"); + } + } + + Self { + _peri: peri.into_ref(), + sub_block, + sck, + mclk, + sd, + fs, + ring_buffer, + } + } + + /// Start the SAI driver. + /// + /// Only receivers can be started. Transmitters are started on the first writing operation. + pub fn start(&mut self) -> Result<(), Error> { + match self.ring_buffer { + RingBuffer::Writable(_) => Err(Error::NotAReceiver), + RingBuffer::Readable(ref mut rb) => { + rb.start(); + Ok(()) + } + } + } + + fn is_transmitter(ring_buffer: &RingBuffer) -> bool { + match ring_buffer { + RingBuffer::Writable(_) => true, + _ => false, + } + } + + /// Reset SAI operation. + pub fn reset() { + rcc::enable_and_reset::(); + } + + /// Enable or disable mute. + pub fn set_mute(&mut self, value: bool) { + let ch = T::REGS.ch(self.sub_block as usize); + ch.cr2().modify(|w| w.set_mute(value)); + } + + /// Determine the mute state of the receiver. + /// + /// Clears the mute state flag in the status register. + pub fn is_muted(&self) -> Result { + match &self.ring_buffer { + RingBuffer::Readable(_) => { + let ch = T::REGS.ch(self.sub_block as usize); + let mute_state = ch.sr().read().mutedet(); + ch.clrfr().write(|w| w.set_cmutedet(true)); + Ok(mute_state) + } + _ => Err(Error::NotAReceiver), + } + } + + /// Wait until any SAI write error occurs. + /// + /// One useful application for this is stopping playback as soon as the SAI + /// experiences an overrun of the ring buffer. Then, instead of letting + /// the SAI peripheral play the last written buffer over and over again, SAI + /// can be muted or dropped instead. + pub async fn wait_write_error(&mut self) -> Result<(), Error> { + match &mut self.ring_buffer { + RingBuffer::Writable(buffer) => { + buffer.wait_write_error().await?; + Ok(()) + } + _ => return Err(Error::NotATransmitter), + } + } + + /// Write data to the SAI ringbuffer. + /// + /// The first write starts the DMA after filling the ring buffer with the provided data. + /// This ensures that the DMA does not run before data is available in the ring buffer. + /// + /// This appends the data to the buffer and returns immediately. The + /// data will be transmitted in the background. + /// + /// If there's no space in the buffer, this waits until there is. + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { + match &mut self.ring_buffer { + RingBuffer::Writable(buffer) => { + if buffer.is_running() { + buffer.write_exact(data).await?; + } else { + buffer.write_immediate(data)?; + buffer.start(); + } + Ok(()) + } + _ => return Err(Error::NotATransmitter), + } + } + + /// Read data from the SAI ringbuffer. + /// + /// SAI is always receiving data in the background. This function pops already-received + /// data from the buffer. + /// + /// If there's less than `data.len()` data in the buffer, this waits until there is. + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + match &mut self.ring_buffer { + RingBuffer::Readable(buffer) => { + buffer.read_exact(data).await?; + Ok(()) + } + _ => Err(Error::NotAReceiver), + } + } +} + +impl<'d, T: Instance, W: word::Word> Drop for Sai<'d, T, W> { + fn drop(&mut self) { + let ch = T::REGS.ch(self.sub_block as usize); + ch.cr1().modify(|w| w.set_saien(false)); + ch.cr2().modify(|w| w.set_fflush(true)); + self.fs.as_ref().map(|x| x.set_as_disconnected()); + self.sd.as_ref().map(|x| x.set_as_disconnected()); + self.sck.as_ref().map(|x| x.set_as_disconnected()); + self.mclk.as_ref().map(|x| x.set_as_disconnected()); + } +} + +trait SealedInstance { + const REGS: Regs; +} + +#[derive(Copy, Clone)] +enum WhichSubBlock { + A = 0, + B = 1, +} + +trait SealedSubBlock { + const WHICH: WhichSubBlock; +} + +/// Sub-block instance trait. +#[allow(private_bounds)] +pub trait SubBlockInstance: SealedSubBlock {} + +/// Sub-block A. +pub enum A {} +impl SealedSubBlock for A { + const WHICH: WhichSubBlock = WhichSubBlock::A; +} +impl SubBlockInstance for A {} + +/// Sub-block B. +pub enum B {} +impl SealedSubBlock for B { + const WHICH: WhichSubBlock = WhichSubBlock::B; +} +impl SubBlockInstance for B {} + +/// SAI instance trait. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral {} + +pin_trait!(SckPin, Instance, SubBlockInstance); +pin_trait!(FsPin, Instance, SubBlockInstance); +pin_trait!(SdPin, Instance, SubBlockInstance); +pin_trait!(MclkPin, Instance, SubBlockInstance); + +dma_trait!(Dma, Instance, SubBlockInstance); + +foreach_peripheral!( + (sai, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); diff --git a/embassy/embassy-stm32/src/sdmmc/mod.rs b/embassy/embassy-stm32/src/sdmmc/mod.rs new file mode 100644 index 0000000..ed344c4 --- /dev/null +++ b/embassy/embassy-stm32/src/sdmmc/mod.rs @@ -0,0 +1,1554 @@ +//! Secure Digital / MultiMedia Card (SDMMC) +#![macro_use] + +use core::default::Default; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use sdio_host::{BusWidth, CardCapacity, CardStatus, CurrentState, SDStatus, CID, CSD, OCR, SCR}; + +use crate::dma::NoDma; +#[cfg(gpio_v2)] +use crate::gpio::Pull; +use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::sdmmc::Sdmmc as RegBlock; +use crate::rcc::{self, RccPeripheral}; +use crate::time::Hertz; +use crate::{interrupt, peripherals, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl InterruptHandler { + fn data_interrupts(enable: bool) { + let regs = T::regs(); + regs.maskr().write(|w| { + w.set_dcrcfailie(enable); + w.set_dtimeoutie(enable); + w.set_dataendie(enable); + + #[cfg(sdmmc_v1)] + w.set_stbiterre(enable); + #[cfg(sdmmc_v2)] + w.set_dabortie(enable); + }); + } +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + Self::data_interrupts(false); + T::state().wake(); + } +} + +/// 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] +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Signalling { + SDR12, + SDR25, + SDR50, + SDR104, + DDR50, +} + +impl Default for Signalling { + fn default() -> Self { + Signalling::SDR12 + } +} + +/// Aligned data block for SDMMC transfers. +/// +/// This is a 512-byte array, aligned to 4 bytes to satisfy DMA requirements. +#[repr(align(4))] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DataBlock(pub [u8; 512]); + +impl Deref for DataBlock { + type Target = [u8; 512]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for DataBlock { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Command Block buffer for SDMMC command transfers. +/// +/// This is a 16-word array, exposed so that DMA commpatible memory can be used if required. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CmdBlock(pub [u32; 16]); + +impl CmdBlock { + /// Creates a new instance of CmdBlock + pub const fn new() -> Self { + Self([0u32; 16]) + } +} + +impl Deref for CmdBlock { + type Target = [u32; 16]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for CmdBlock { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Errors +#[non_exhaustive] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Timeout reported by the hardware + Timeout, + /// Timeout reported by the software driver. + SoftwareTimeout, + /// Unsupported card version. + UnsupportedCardVersion, + /// Unsupported card type. + UnsupportedCardType, + /// CRC error. + Crc, + /// No card inserted. + NoCard, + /// Bad clock supplied to the SDMMC peripheral. + BadClock, + /// Signaling switch failed. + SignalingSwitchFailed, + /// ST bit error. + #[cfg(sdmmc_v1)] + StBitErr, +} + +/// A SD command +struct Cmd { + cmd: u8, + arg: u32, + resp: Response, +} + +#[derive(Clone, Copy, Debug, Default)] +/// SD Card +pub struct Card { + /// The type of this card + pub card_type: CardCapacity, + /// Operation Conditions Register + pub ocr: OCR, + /// Relative Card Address + pub rca: u32, + /// Card ID + pub cid: CID, + /// Card Specific Data + pub csd: CSD, + /// SD CARD Configuration Register + pub scr: SCR, + /// SD Status + pub status: SDStatus, +} + +impl Card { + /// Size in bytes + pub fn size(&self) -> u64 { + // SDHC / SDXC / SDUC + u64::from(self.csd.block_count()) * 512 + } +} + +#[repr(u8)] +enum PowerCtrl { + Off = 0b00, + On = 0b11, +} + +#[repr(u32)] +#[allow(dead_code)] +#[allow(non_camel_case_types)] +enum CmdAppOper { + VOLTAGE_WINDOW_SD = 0x8010_0000, + HIGH_CAPACITY = 0x4000_0000, + SDMMC_STD_CAPACITY = 0x0000_0000, + SDMMC_CHECK_PATTERN = 0x0000_01AA, + SD_SWITCH_1_8V_CAPACITY = 0x0100_0000, +} + +#[derive(Eq, PartialEq, Copy, Clone)] +enum Response { + None = 0, + Short = 1, + Long = 3, +} + +/// Calculate clock divisor. Returns a SDMMC_CK less than or equal to +/// `sdmmc_ck` in Hertz. +/// +/// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1), +/// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency. +#[cfg(sdmmc_v1)] +fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u8, Hertz), Error> { + // sdmmc_v1 maximum clock is 50 MHz + if sdmmc_ck > 50_000_000 { + return Err(Error::BadClock); + } + + // bypass divisor + if ker_ck.0 <= sdmmc_ck { + return Ok((true, 0, ker_ck)); + } + + // `ker_ck / sdmmc_ck` rounded up + let clk_div = match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { + 0 | 1 => Ok(0), + x @ 2..=258 => Ok((x - 2) as u8), + _ => Err(Error::BadClock), + }?; + + // SDIO_CK frequency = SDIOCLK / [CLKDIV + 2] + let clk_f = Hertz(ker_ck.0 / (clk_div as u32 + 2)); + Ok((false, clk_div, clk_f)) +} + +/// Calculate clock divisor. Returns a SDMMC_CK less than or equal to +/// `sdmmc_ck` in Hertz. +/// +/// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1), +/// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency. +#[cfg(sdmmc_v2)] +fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u16, Hertz), Error> { + // `ker_ck / sdmmc_ck` rounded up + match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { + 0 | 1 => Ok((false, 0, ker_ck)), + x @ 2..=2046 => { + // SDMMC_CK frequency = SDMMCCLK / [CLKDIV * 2] + let clk_div = ((x + 1) / 2) as u16; + let clk = Hertz(ker_ck.0 / (clk_div as u32 * 2)); + + Ok((false, clk_div, clk)) + } + _ => Err(Error::BadClock), + } +} + +#[cfg(sdmmc_v1)] +type Transfer<'a> = crate::dma::Transfer<'a>; +#[cfg(sdmmc_v2)] +struct Transfer<'a> { + _dummy: PhantomData<&'a ()>, +} + +#[cfg(all(sdmmc_v1, dma))] +const DMA_TRANSFER_OPTIONS: crate::dma::TransferOptions = crate::dma::TransferOptions { + pburst: crate::dma::Burst::Incr4, + mburst: crate::dma::Burst::Incr4, + flow_ctrl: crate::dma::FlowControl::Peripheral, + fifo_threshold: Some(crate::dma::FifoThreshold::Full), + priority: crate::dma::Priority::VeryHigh, + circular: false, + half_transfer_ir: false, + complete_transfer_ir: true, +}; +#[cfg(all(sdmmc_v1, not(dma)))] +const DMA_TRANSFER_OPTIONS: crate::dma::TransferOptions = crate::dma::TransferOptions { + priority: crate::dma::Priority::VeryHigh, + circular: false, + half_transfer_ir: false, + complete_transfer_ir: true, +}; + +/// SDMMC configuration +/// +/// Default values: +/// data_transfer_timeout: 5_000_000 +#[non_exhaustive] +pub struct Config { + /// The timeout to be set for data transfers, in card bus clock periods + pub data_transfer_timeout: u32, +} + +impl Default for Config { + fn default() -> Self { + Self { + data_transfer_timeout: 5_000_000, + } + } +} + +/// Sdmmc device +pub struct Sdmmc<'d, T: Instance, Dma: SdmmcDma = NoDma> { + _peri: PeripheralRef<'d, T>, + #[allow(unused)] + dma: PeripheralRef<'d, Dma>, + + clk: PeripheralRef<'d, AnyPin>, + cmd: PeripheralRef<'d, AnyPin>, + d0: PeripheralRef<'d, AnyPin>, + d1: Option>, + d2: Option>, + d3: Option>, + + config: Config, + /// Current clock to card + clock: Hertz, + /// Current signalling scheme to card + signalling: Signalling, + /// Card + card: Option, + + /// An optional buffer to be used for commands + /// This should be used if there are special memory location requirements for dma + cmd_block: Option<&'d mut CmdBlock>, +} + +const CLK_AF: AfType = AfType::output(OutputType::PushPull, Speed::VeryHigh); +#[cfg(gpio_v1)] +const CMD_AF: AfType = AfType::output(OutputType::PushPull, Speed::VeryHigh); +#[cfg(gpio_v2)] +const CMD_AF: AfType = AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up); +const DATA_AF: AfType = CMD_AF; + +#[cfg(sdmmc_v1)] +impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { + /// Create a new SDMMC driver, with 1 data lane. + pub fn new_1bit( + sdmmc: impl Peripheral

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

+ 'd, + clk: impl Peripheral

> + 'd, + cmd: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(clk, cmd, d0); + + critical_section::with(|_| { + clk.set_as_af(clk.af_num(), CLK_AF); + cmd.set_as_af(cmd.af_num(), CMD_AF); + d0.set_as_af(d0.af_num(), DATA_AF); + }); + + Self::new_inner( + sdmmc, + dma, + clk.map_into(), + cmd.map_into(), + d0.map_into(), + None, + None, + None, + config, + ) + } + + /// Create a new SDMMC driver, with 4 data lanes. + pub fn new_4bit( + sdmmc: impl Peripheral

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

+ 'd, + clk: impl Peripheral

> + 'd, + cmd: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(clk, cmd, d0, d1, d2, d3); + + critical_section::with(|_| { + clk.set_as_af(clk.af_num(), CLK_AF); + cmd.set_as_af(cmd.af_num(), CMD_AF); + d0.set_as_af(d0.af_num(), DATA_AF); + d1.set_as_af(d1.af_num(), DATA_AF); + d2.set_as_af(d2.af_num(), DATA_AF); + d3.set_as_af(d3.af_num(), DATA_AF); + }); + + Self::new_inner( + sdmmc, + dma, + clk.map_into(), + cmd.map_into(), + d0.map_into(), + Some(d1.map_into()), + Some(d2.map_into()), + Some(d3.map_into()), + config, + ) + } +} + +#[cfg(sdmmc_v2)] +impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { + /// Create a new SDMMC driver, with 1 data lane. + pub fn new_1bit( + sdmmc: impl Peripheral

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

> + 'd, + cmd: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(clk, cmd, d0); + + critical_section::with(|_| { + clk.set_as_af(clk.af_num(), CLK_AF); + cmd.set_as_af(cmd.af_num(), CMD_AF); + d0.set_as_af(d0.af_num(), DATA_AF); + }); + + Self::new_inner( + sdmmc, + NoDma.into_ref(), + clk.map_into(), + cmd.map_into(), + d0.map_into(), + None, + None, + None, + config, + ) + } + + /// Create a new SDMMC driver, with 4 data lanes. + pub fn new_4bit( + sdmmc: impl Peripheral

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

> + 'd, + cmd: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(clk, cmd, d0, d1, d2, d3); + + critical_section::with(|_| { + clk.set_as_af(clk.af_num(), CLK_AF); + cmd.set_as_af(cmd.af_num(), CMD_AF); + d0.set_as_af(d0.af_num(), DATA_AF); + d1.set_as_af(d1.af_num(), DATA_AF); + d2.set_as_af(d2.af_num(), DATA_AF); + d3.set_as_af(d3.af_num(), DATA_AF); + }); + + Self::new_inner( + sdmmc, + NoDma.into_ref(), + clk.map_into(), + cmd.map_into(), + d0.map_into(), + Some(d1.map_into()), + Some(d2.map_into()), + Some(d3.map_into()), + config, + ) + } +} + +impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { + fn new_inner( + sdmmc: impl Peripheral

+ 'd, + dma: impl Peripheral

+ 'd, + clk: PeripheralRef<'d, AnyPin>, + cmd: PeripheralRef<'d, AnyPin>, + d0: PeripheralRef<'d, AnyPin>, + d1: Option>, + d2: Option>, + d3: Option>, + config: Config, + ) -> Self { + into_ref!(sdmmc, dma); + + rcc::enable_and_reset::(); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let regs = T::regs(); + regs.clkcr().write(|w| { + w.set_pwrsav(false); + w.set_negedge(false); + + // Hardware flow control is broken on SDIOv1 and causes clock glitches, which result in CRC errors. + // See chip erratas for more details. + #[cfg(sdmmc_v1)] + w.set_hwfc_en(false); + #[cfg(sdmmc_v2)] + w.set_hwfc_en(true); + + #[cfg(sdmmc_v1)] + w.set_clken(true); + }); + + // 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)); + + Self { + _peri: sdmmc, + dma, + + clk, + cmd, + d0, + d1, + d2, + d3, + + config, + clock: SD_INIT_FREQ, + signalling: Default::default(), + card: None, + cmd_block: None, + } + } + + /// Data transfer is in progress + #[inline] + fn data_active() -> bool { + let regs = T::regs(); + + let status = regs.star().read(); + #[cfg(sdmmc_v1)] + return status.rxact() || status.txact(); + #[cfg(sdmmc_v2)] + return status.dpsmact(); + } + + /// Coammand transfer is in progress + #[inline] + fn cmd_active() -> bool { + let regs = T::regs(); + + let status = regs.star().read(); + #[cfg(sdmmc_v1)] + return status.cmdact(); + #[cfg(sdmmc_v2)] + return status.cpsmact(); + } + + /// Wait idle on CMDACT, RXACT and TXACT (v1) or DOSNACT and CPSMACT (v2) + #[inline] + fn wait_idle() { + while Self::data_active() || Self::cmd_active() {} + } + + /// # Safety + /// + /// `buffer` must be valid for the whole transfer and word aligned + #[allow(unused_variables)] + fn prepare_datapath_read<'a>( + config: &Config, + dma: &'a mut PeripheralRef<'d, Dma>, + buffer: &'a mut [u32], + length_bytes: u32, + block_size: u8, + ) -> Transfer<'a> { + assert!(block_size <= 14, "Block size up to 2^14 bytes"); + let regs = T::regs(); + + // Command AND Data state machines must be idle + Self::wait_idle(); + Self::clear_interrupt_flags(); + + regs.dtimer().write(|w| w.set_datatime(config.data_transfer_timeout)); + regs.dlenr().write(|w| w.set_datalength(length_bytes)); + + #[cfg(sdmmc_v1)] + let transfer = unsafe { + let request = dma.request(); + Transfer::new_read( + dma, + request, + regs.fifor().as_ptr() as *mut u32, + buffer, + DMA_TRANSFER_OPTIONS, + ) + }; + #[cfg(sdmmc_v2)] + let transfer = { + regs.idmabase0r().write(|w| w.set_idmabase0(buffer.as_mut_ptr() as u32)); + regs.idmactrlr().modify(|w| w.set_idmaen(true)); + Transfer { + _dummy: core::marker::PhantomData, + } + }; + + regs.dctrl().modify(|w| { + w.set_dblocksize(block_size); + w.set_dtdir(true); + #[cfg(sdmmc_v1)] + { + w.set_dmaen(true); + w.set_dten(true); + } + }); + + transfer + } + + /// # Safety + /// + /// `buffer` must be valid for the whole transfer and word aligned + fn prepare_datapath_write<'a>(&'a mut self, buffer: &'a [u32], length_bytes: u32, block_size: u8) -> Transfer<'a> { + assert!(block_size <= 14, "Block size up to 2^14 bytes"); + let regs = T::regs(); + + // Command AND Data state machines must be idle + Self::wait_idle(); + Self::clear_interrupt_flags(); + + regs.dtimer() + .write(|w| w.set_datatime(self.config.data_transfer_timeout)); + regs.dlenr().write(|w| w.set_datalength(length_bytes)); + + #[cfg(sdmmc_v1)] + let transfer = unsafe { + let request = self.dma.request(); + Transfer::new_write( + &mut self.dma, + request, + buffer, + regs.fifor().as_ptr() as *mut u32, + DMA_TRANSFER_OPTIONS, + ) + }; + #[cfg(sdmmc_v2)] + let transfer = { + regs.idmabase0r().write(|w| w.set_idmabase0(buffer.as_ptr() as u32)); + regs.idmactrlr().modify(|w| w.set_idmaen(true)); + Transfer { + _dummy: core::marker::PhantomData, + } + }; + + regs.dctrl().modify(|w| { + w.set_dblocksize(block_size); + w.set_dtdir(false); + #[cfg(sdmmc_v1)] + { + w.set_dmaen(true); + w.set_dten(true); + } + }); + + transfer + } + + /// Stops the DMA datapath + fn stop_datapath() { + let regs = T::regs(); + + #[cfg(sdmmc_v1)] + regs.dctrl().modify(|w| { + w.set_dmaen(false); + w.set_dten(false); + }); + #[cfg(sdmmc_v2)] + regs.idmactrlr().modify(|w| w.set_idmaen(false)); + } + + /// Sets the CLKDIV field in CLKCR. Updates clock field in self + fn clkcr_set_clkdiv(&mut self, freq: u32, width: BusWidth) -> Result<(), Error> { + let regs = T::regs(); + + let width_u32 = match width { + BusWidth::One => 1u32, + BusWidth::Four => 4u32, + BusWidth::Eight => 8u32, + _ => panic!("Invalid Bus Width"), + }; + + let ker_ck = T::frequency(); + let (_bypass, clkdiv, new_clock) = clk_div(ker_ck, freq)?; + + // Enforce AHB and SDMMC_CK clock relation. See RM0433 Rev 7 + // Section 55.5.8 + let sdmmc_bus_bandwidth = new_clock.0 * width_u32; + assert!(ker_ck.0 > 3 * sdmmc_bus_bandwidth / 32); + self.clock = new_clock; + + // CPSMACT and DPSMACT must be 0 to set CLKDIV + Self::wait_idle(); + regs.clkcr().modify(|w| { + w.set_clkdiv(clkdiv); + #[cfg(sdmmc_v1)] + w.set_bypass(_bypass); + }); + + Ok(()) + } + + /// Switch mode using CMD6. + /// + /// Attempt to set a new signalling mode. The selected + /// signalling mode is returned. Expects the current clock + /// frequency to be > 12.5MHz. + async fn switch_signalling_mode(&mut self, signalling: Signalling) -> Result { + // NB PLSS v7_10 4.3.10.4: "the use of SET_BLK_LEN command is not + // necessary" + + let set_function = 0x8000_0000 + | match signalling { + // See PLSS v7_10 Table 4-11 + Signalling::DDR50 => 0xFF_FF04, + Signalling::SDR104 => 0xFF_1F03, + Signalling::SDR50 => 0xFF_1F02, + Signalling::SDR25 => 0xFF_FF01, + Signalling::SDR12 => 0xFF_FF00, + }; + + let status = match self.cmd_block.as_deref_mut() { + Some(x) => x, + None => &mut CmdBlock::new(), + }; + + // Arm `OnDrop` after the buffer, so it will be dropped first + let regs = T::regs(); + let on_drop = OnDrop::new(|| Self::on_drop()); + + let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, status.as_mut(), 64, 6); + InterruptHandler::::data_interrupts(true); + Self::cmd(Cmd::cmd6(set_function), true)?; // CMD6 + + let res = poll_fn(|cx| { + T::state().register(cx.waker()); + let status = regs.star().read(); + + if status.dcrcfail() { + return Poll::Ready(Err(Error::Crc)); + } + if status.dtimeout() { + return Poll::Ready(Err(Error::Timeout)); + } + #[cfg(sdmmc_v1)] + if status.stbiterr() { + return Poll::Ready(Err(Error::StBitErr)); + } + if status.dataend() { + return Poll::Ready(Ok(())); + } + Poll::Pending + }) + .await; + Self::clear_interrupt_flags(); + + // Host is allowed to use the new functions at least 8 + // clocks after the end of the switch command + // transaction. We know the current clock period is < 80ns, + // so a total delay of 640ns is required here + for _ in 0..300 { + cortex_m::asm::nop(); + } + + match res { + Ok(_) => { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + + // Function Selection of Function Group 1 + let selection = (u32::from_be(status[4]) >> 24) & 0xF; + + match selection { + 0 => Ok(Signalling::SDR12), + 1 => Ok(Signalling::SDR25), + 2 => Ok(Signalling::SDR50), + 3 => Ok(Signalling::SDR104), + 4 => Ok(Signalling::DDR50), + _ => Err(Error::UnsupportedCardType), + } + } + Err(e) => Err(e), + } + } + + /// Query the card status (CMD13, returns R1) + fn read_status(&self, card: &Card) -> Result { + let regs = T::regs(); + let rca = card.rca; + + Self::cmd(Cmd::card_status(rca << 16), false)?; // CMD13 + + let r1 = regs.respr(0).read().cardstatus(); + Ok(r1.into()) + } + + /// Reads the SD Status (ACMD13) + async fn read_sd_status(&mut self) -> Result<(), Error> { + let card = self.card.as_mut().ok_or(Error::NoCard)?; + let rca = card.rca; + + let cmd_block = match self.cmd_block.as_deref_mut() { + Some(x) => x, + None => &mut CmdBlock::new(), + }; + + Self::cmd(Cmd::set_block_length(64), false)?; // CMD16 + Self::cmd(Cmd::app_cmd(rca << 16), false)?; // APP + + let status = cmd_block; + + // Arm `OnDrop` after the buffer, so it will be dropped first + let regs = T::regs(); + let on_drop = OnDrop::new(|| Self::on_drop()); + + let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, status.as_mut(), 64, 6); + InterruptHandler::::data_interrupts(true); + Self::cmd(Cmd::card_status(0), true)?; + + let res = poll_fn(|cx| { + T::state().register(cx.waker()); + let status = regs.star().read(); + + if status.dcrcfail() { + return Poll::Ready(Err(Error::Crc)); + } + if status.dtimeout() { + return Poll::Ready(Err(Error::Timeout)); + } + #[cfg(sdmmc_v1)] + if status.stbiterr() { + return Poll::Ready(Err(Error::StBitErr)); + } + if status.dataend() { + return Poll::Ready(Ok(())); + } + Poll::Pending + }) + .await; + Self::clear_interrupt_flags(); + + if res.is_ok() { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + + for byte in status.iter_mut() { + *byte = u32::from_be(*byte); + } + self.card.as_mut().unwrap().status = status.0.into(); + } + res + } + + /// Select one card and place it into the _Tranfer State_ + /// + /// If `None` is specifed for `card`, all cards are put back into + /// _Stand-by State_ + fn select_card(&self, card: Option<&Card>) -> Result<(), Error> { + // Determine Relative Card Address (RCA) of given card + let rca = card.map(|c| c.rca << 16).unwrap_or(0); + + let r = Self::cmd(Cmd::sel_desel_card(rca), false); + match (r, rca) { + (Err(Error::Timeout), 0) => Ok(()), + _ => r, + } + } + + /// Clear flags in interrupt clear register + #[inline] + fn clear_interrupt_flags() { + let regs = T::regs(); + regs.icr().write(|w| { + w.set_ccrcfailc(true); + w.set_dcrcfailc(true); + w.set_ctimeoutc(true); + w.set_dtimeoutc(true); + w.set_txunderrc(true); + w.set_rxoverrc(true); + w.set_cmdrendc(true); + w.set_cmdsentc(true); + w.set_dataendc(true); + w.set_dbckendc(true); + w.set_sdioitc(true); + #[cfg(sdmmc_v1)] + w.set_stbiterrc(true); + + #[cfg(sdmmc_v2)] + { + w.set_dholdc(true); + w.set_dabortc(true); + w.set_busyd0endc(true); + w.set_ackfailc(true); + w.set_acktimeoutc(true); + w.set_vswendc(true); + w.set_ckstopc(true); + w.set_idmatec(true); + w.set_idmabtcc(true); + } + }); + } + + async fn get_scr(&mut self, card: &mut Card) -> Result<(), Error> { + // Read the the 64-bit SCR register + Self::cmd(Cmd::set_block_length(8), false)?; // CMD16 + Self::cmd(Cmd::app_cmd(card.rca << 16), false)?; + + let cmd_block = match self.cmd_block.as_deref_mut() { + Some(x) => x, + None => &mut CmdBlock::new(), + }; + let scr = &mut cmd_block.0[..2]; + + // Arm `OnDrop` after the buffer, so it will be dropped first + let regs = T::regs(); + let on_drop = OnDrop::new(|| Self::on_drop()); + + let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, scr, 8, 3); + InterruptHandler::::data_interrupts(true); + Self::cmd(Cmd::cmd51(), true)?; + + let res = poll_fn(|cx| { + T::state().register(cx.waker()); + let status = regs.star().read(); + + if status.dcrcfail() { + return Poll::Ready(Err(Error::Crc)); + } + if status.dtimeout() { + return Poll::Ready(Err(Error::Timeout)); + } + #[cfg(sdmmc_v1)] + if status.stbiterr() { + return Poll::Ready(Err(Error::StBitErr)); + } + if status.dataend() { + return Poll::Ready(Ok(())); + } + Poll::Pending + }) + .await; + Self::clear_interrupt_flags(); + + if res.is_ok() { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + + unsafe { + let scr_bytes = &*(&scr as *const _ as *const [u8; 8]); + card.scr = SCR(u64::from_be_bytes(*scr_bytes)); + } + } + res + } + + /// Send command to card + #[allow(unused_variables)] + fn cmd(cmd: Cmd, data: bool) -> Result<(), Error> { + let regs = T::regs(); + + Self::clear_interrupt_flags(); + // CP state machine must be idle + while Self::cmd_active() {} + + // Command arg + regs.argr().write(|w| w.set_cmdarg(cmd.arg)); + + // Command index and start CP State Machine + regs.cmdr().write(|w| { + w.set_waitint(false); + w.set_waitresp(cmd.resp as u8); + w.set_cmdindex(cmd.cmd); + w.set_cpsmen(true); + + #[cfg(sdmmc_v2)] + { + // Special mode in CP State Machine + // CMD12: Stop Transmission + let cpsm_stop_transmission = cmd.cmd == 12; + w.set_cmdstop(cpsm_stop_transmission); + w.set_cmdtrans(data); + } + }); + + let mut status; + if cmd.resp == Response::None { + // Wait for CMDSENT or a timeout + while { + status = regs.star().read(); + !(status.ctimeout() || status.cmdsent()) + } {} + } else { + // Wait for CMDREND or CCRCFAIL or a timeout + while { + status = regs.star().read(); + !(status.ctimeout() || status.cmdrend() || status.ccrcfail()) + } {} + } + + if status.ctimeout() { + return Err(Error::Timeout); + } else if status.ccrcfail() { + return Err(Error::Crc); + } + Ok(()) + } + + fn on_drop() { + let regs = T::regs(); + if Self::data_active() { + Self::clear_interrupt_flags(); + // Send abort + // CP state machine must be idle + while Self::cmd_active() {} + + // Command arg + regs.argr().write(|w| w.set_cmdarg(0)); + + // Command index and start CP State Machine + regs.cmdr().write(|w| { + w.set_waitint(false); + w.set_waitresp(Response::Short as u8); + w.set_cmdindex(12); + w.set_cpsmen(true); + + #[cfg(sdmmc_v2)] + { + w.set_cmdstop(true); + w.set_cmdtrans(false); + } + }); + + // Wait for the abort + while Self::data_active() {} + } + InterruptHandler::::data_interrupts(false); + Self::clear_interrupt_flags(); + Self::stop_datapath(); + } + + /// Initializes card (if present) and sets the bus at the specified frequency. + pub async fn init_card(&mut self, freq: Hertz) -> Result<(), Error> { + let regs = T::regs(); + let ker_ck = T::frequency(); + + let bus_width = match self.d3.is_some() { + true => BusWidth::Four, + false => BusWidth::One, + }; + + // While the SD/SDIO card or eMMC is in identification mode, + // the SDMMC_CK frequency must be no more than 400 kHz. + let (_bypass, clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0)); + self.clock = init_clock; + + // CPSMACT and DPSMACT must be 0 to set WIDBUS + Self::wait_idle(); + + regs.clkcr().modify(|w| { + w.set_widbus(0); + w.set_clkdiv(clkdiv); + #[cfg(sdmmc_v1)] + w.set_bypass(_bypass); + }); + + regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8)); + Self::cmd(Cmd::idle(), false)?; + + // Check if cards supports CMD8 (with pattern) + Self::cmd(Cmd::hs_send_ext_csd(0x1AA), false)?; + let r1 = regs.respr(0).read().cardstatus(); + + let mut card = if r1 == 0x1AA { + // Card echoed back the pattern. Must be at least v2 + Card::default() + } else { + return Err(Error::UnsupportedCardVersion); + }; + + let ocr = loop { + // Signal that next command is a app command + Self::cmd(Cmd::app_cmd(0), false)?; // CMD55 + + let arg = CmdAppOper::VOLTAGE_WINDOW_SD as u32 + | CmdAppOper::HIGH_CAPACITY as u32 + | CmdAppOper::SD_SWITCH_1_8V_CAPACITY as u32; + + // Initialize card + match Self::cmd(Cmd::app_op_cmd(arg), false) { + // ACMD41 + Ok(_) => (), + Err(Error::Crc) => (), + Err(err) => return Err(err), + } + let ocr: OCR = regs.respr(0).read().cardstatus().into(); + if !ocr.is_busy() { + // Power up done + break ocr; + } + }; + + if ocr.high_capacity() { + // Card is SDHC or SDXC or SDUC + card.card_type = CardCapacity::SDHC; + } else { + card.card_type = CardCapacity::SDSC; + } + card.ocr = ocr; + + Self::cmd(Cmd::all_send_cid(), false)?; // CMD2 + let cid0 = regs.respr(0).read().cardstatus() as u128; + let cid1 = regs.respr(1).read().cardstatus() as u128; + let cid2 = regs.respr(2).read().cardstatus() as u128; + let cid3 = regs.respr(3).read().cardstatus() as u128; + let cid = (cid0 << 96) | (cid1 << 64) | (cid2 << 32) | (cid3); + card.cid = cid.into(); + + Self::cmd(Cmd::send_rel_addr(), false)?; + card.rca = regs.respr(0).read().cardstatus() >> 16; + + Self::cmd(Cmd::send_csd(card.rca << 16), false)?; + let csd0 = regs.respr(0).read().cardstatus() as u128; + let csd1 = regs.respr(1).read().cardstatus() as u128; + let csd2 = regs.respr(2).read().cardstatus() as u128; + let csd3 = regs.respr(3).read().cardstatus() as u128; + let csd = (csd0 << 96) | (csd1 << 64) | (csd2 << 32) | (csd3); + card.csd = csd.into(); + + self.select_card(Some(&card))?; + + self.get_scr(&mut card).await?; + + // Set bus width + let (width, acmd_arg) = match bus_width { + BusWidth::Eight => unimplemented!(), + BusWidth::Four if card.scr.bus_width_four() => (BusWidth::Four, 2), + _ => (BusWidth::One, 0), + }; + Self::cmd(Cmd::app_cmd(card.rca << 16), false)?; + Self::cmd(Cmd::cmd6(acmd_arg), false)?; + + // CPSMACT and DPSMACT must be 0 to set WIDBUS + Self::wait_idle(); + + regs.clkcr().modify(|w| { + w.set_widbus(match width { + BusWidth::One => 0, + BusWidth::Four => 1, + BusWidth::Eight => 2, + _ => panic!("Invalid Bus Width"), + }) + }); + + // Set Clock + if freq.0 <= 25_000_000 { + // Final clock frequency + self.clkcr_set_clkdiv(freq.0, width)?; + } else { + // Switch to max clock for SDR12 + self.clkcr_set_clkdiv(25_000_000, width)?; + } + + self.card = Some(card); + + // Read status + self.read_sd_status().await?; + + if freq.0 > 25_000_000 { + // Switch to SDR25 + self.signalling = self.switch_signalling_mode(Signalling::SDR25).await?; + + if self.signalling == Signalling::SDR25 { + // Set final clock frequency + self.clkcr_set_clkdiv(freq.0, width)?; + + if self.read_status(&card)?.state() != CurrentState::Transfer { + return Err(Error::SignalingSwitchFailed); + } + } + } + + // Read status after signalling change + self.read_sd_status().await?; + + Ok(()) + } + + /// Read a data block. + #[inline] + pub async fn read_block(&mut self, block_idx: u32, buffer: &mut DataBlock) -> Result<(), Error> { + let card_capacity = self.card()?.card_type; + + // NOTE(unsafe) DataBlock uses align 4 + let buffer = unsafe { &mut *((&mut buffer.0) as *mut [u8; 512] as *mut [u32; 128]) }; + + // Always read 1 block of 512 bytes + // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes + let address = match card_capacity { + CardCapacity::SDSC => block_idx * 512, + _ => block_idx, + }; + Self::cmd(Cmd::set_block_length(512), false)?; // CMD16 + + let regs = T::regs(); + let on_drop = OnDrop::new(|| Self::on_drop()); + + let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, buffer, 512, 9); + InterruptHandler::::data_interrupts(true); + Self::cmd(Cmd::read_single_block(address), true)?; + + let res = poll_fn(|cx| { + T::state().register(cx.waker()); + let status = regs.star().read(); + + if status.dcrcfail() { + return Poll::Ready(Err(Error::Crc)); + } + if status.dtimeout() { + return Poll::Ready(Err(Error::Timeout)); + } + #[cfg(sdmmc_v1)] + if status.stbiterr() { + return Poll::Ready(Err(Error::StBitErr)); + } + if status.dataend() { + return Poll::Ready(Ok(())); + } + Poll::Pending + }) + .await; + Self::clear_interrupt_flags(); + + if res.is_ok() { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + } + res + } + + /// Write a data block. + pub async fn write_block(&mut self, block_idx: u32, buffer: &DataBlock) -> Result<(), Error> { + let card = self.card.as_mut().ok_or(Error::NoCard)?; + + // NOTE(unsafe) DataBlock uses align 4 + let buffer = unsafe { &*((&buffer.0) as *const [u8; 512] as *const [u32; 128]) }; + + // Always read 1 block of 512 bytes + // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes + let address = match card.card_type { + CardCapacity::SDSC => block_idx * 512, + _ => block_idx, + }; + Self::cmd(Cmd::set_block_length(512), false)?; // CMD16 + + let regs = T::regs(); + let on_drop = OnDrop::new(|| Self::on_drop()); + + // sdmmc_v1 uses different cmd/dma order than v2, but only for writes + #[cfg(sdmmc_v1)] + Self::cmd(Cmd::write_single_block(address), true)?; + + let transfer = self.prepare_datapath_write(buffer, 512, 9); + InterruptHandler::::data_interrupts(true); + + #[cfg(sdmmc_v2)] + Self::cmd(Cmd::write_single_block(address), true)?; + + let res = poll_fn(|cx| { + T::state().register(cx.waker()); + let status = regs.star().read(); + + if status.dcrcfail() { + return Poll::Ready(Err(Error::Crc)); + } + if status.dtimeout() { + return Poll::Ready(Err(Error::Timeout)); + } + #[cfg(sdmmc_v1)] + if status.stbiterr() { + return Poll::Ready(Err(Error::StBitErr)); + } + if status.dataend() { + return Poll::Ready(Ok(())); + } + Poll::Pending + }) + .await; + Self::clear_interrupt_flags(); + + match res { + Ok(_) => { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + + // TODO: Make this configurable + let mut timeout: u32 = 0x00FF_FFFF; + + // Try to read card status (ACMD13) + while timeout > 0 { + match self.read_sd_status().await { + Ok(_) => return Ok(()), + Err(Error::Timeout) => (), // Try again + Err(e) => return Err(e), + } + timeout -= 1; + } + Err(Error::SoftwareTimeout) + } + Err(e) => Err(e), + } + } + + /// Get a reference to the initialized card + /// + /// # Errors + /// + /// Returns Error::NoCard if [`init_card`](#method.init_card) + /// has not previously succeeded + #[inline] + pub fn card(&self) -> Result<&Card, Error> { + self.card.as_ref().ok_or(Error::NoCard) + } + + /// Get the current SDMMC bus clock + pub fn clock(&self) -> Hertz { + self.clock + } + + /// Set a specific cmd buffer rather than using the default stack allocated one. + /// This is required if stack RAM cannot be used with DMA and usually manifests + /// itself as an indefinite wait on a dma transfer because the dma peripheral + /// cannot access the memory. + pub fn set_cmd_block(&mut self, cmd_block: &'d mut CmdBlock) { + self.cmd_block = Some(cmd_block) + } +} + +impl<'d, T: Instance, Dma: SdmmcDma + 'd> Drop for Sdmmc<'d, T, Dma> { + fn drop(&mut self) { + T::Interrupt::disable(); + Self::on_drop(); + + critical_section::with(|_| { + self.clk.set_as_disconnected(); + self.cmd.set_as_disconnected(); + self.d0.set_as_disconnected(); + if let Some(x) = &mut self.d1 { + x.set_as_disconnected(); + } + if let Some(x) = &mut self.d2 { + x.set_as_disconnected(); + } + if let Some(x) = &mut self.d3 { + x.set_as_disconnected(); + } + }); + } +} + +/// SD card Commands +impl Cmd { + const fn new(cmd: u8, arg: u32, resp: Response) -> Cmd { + Cmd { cmd, arg, resp } + } + + /// CMD0: Idle + const fn idle() -> Cmd { + Cmd::new(0, 0, Response::None) + } + + /// CMD2: Send CID + const fn all_send_cid() -> Cmd { + Cmd::new(2, 0, Response::Long) + } + + /// CMD3: Send Relative Address + const fn send_rel_addr() -> Cmd { + Cmd::new(3, 0, Response::Short) + } + + /// CMD6: Switch Function Command + /// ACMD6: Bus Width + const fn cmd6(arg: u32) -> Cmd { + Cmd::new(6, arg, Response::Short) + } + + /// CMD7: Select one card and put it into the _Tranfer State_ + const fn sel_desel_card(rca: u32) -> Cmd { + Cmd::new(7, rca, Response::Short) + } + + /// CMD8: + const fn hs_send_ext_csd(arg: u32) -> Cmd { + Cmd::new(8, arg, Response::Short) + } + + /// CMD9: + const fn send_csd(rca: u32) -> Cmd { + Cmd::new(9, rca, Response::Long) + } + + /// CMD12: + //const fn stop_transmission() -> Cmd { + // Cmd::new(12, 0, Response::Short) + //} + + /// CMD13: Ask card to send status register + /// ACMD13: SD Status + const fn card_status(rca: u32) -> Cmd { + Cmd::new(13, rca, Response::Short) + } + + /// CMD16: + const fn set_block_length(blocklen: u32) -> Cmd { + Cmd::new(16, blocklen, Response::Short) + } + + /// CMD17: Block Read + const fn read_single_block(addr: u32) -> Cmd { + Cmd::new(17, addr, Response::Short) + } + + /// CMD18: Multiple Block Read + //const fn read_multiple_blocks(addr: u32) -> Cmd { + // Cmd::new(18, addr, Response::Short) + //} + + /// CMD24: Block Write + const fn write_single_block(addr: u32) -> Cmd { + Cmd::new(24, addr, Response::Short) + } + + const fn app_op_cmd(arg: u32) -> Cmd { + Cmd::new(41, arg, Response::Short) + } + + const fn cmd51() -> Cmd { + Cmd::new(51, 0, Response::Short) + } + + /// App Command. Indicates that next command will be a app command + const fn app_cmd(rca: u32) -> Cmd { + Cmd::new(55, rca, Response::Short) + } +} + +////////////////////////////////////////////////////// + +trait SealedInstance { + fn regs() -> RegBlock; + fn state() -> &'static AtomicWaker; +} + +/// SDMMC instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral + 'static { + /// Interrupt for this instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +pin_trait!(CkPin, Instance); +pin_trait!(CmdPin, 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); + +#[cfg(sdmmc_v1)] +dma_trait!(SdmmcDma, Instance); + +/// DMA instance trait. +/// +/// This is only implemented for `NoDma`, since SDMMCv2 has DMA built-in, instead of +/// using ST's system-wide DMA peripheral. +#[cfg(sdmmc_v2)] +pub trait SdmmcDma {} +#[cfg(sdmmc_v2)] +impl SdmmcDma for NoDma {} + +foreach_peripheral!( + (sdmmc, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + fn regs() -> RegBlock { + crate::pac::$inst + } + + fn state() -> &'static ::embassy_sync::waitqueue::AtomicWaker { + static WAKER: ::embassy_sync::waitqueue::AtomicWaker = ::embassy_sync::waitqueue::AtomicWaker::new(); + &WAKER + } + } + + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$inst; + } + }; +); + +impl<'d, T: Instance, Dma: SdmmcDma + 'd> block_device_driver::BlockDevice<512> for Sdmmc<'d, T, Dma> { + type Error = Error; + type Align = aligned::A4; + + async fn read( + &mut self, + mut block_address: u32, + buf: &mut [aligned::Aligned], + ) -> Result<(), Self::Error> { + // FIXME/TODO because of missing read_blocks multiple we have to do this one block at a time + for block in buf.iter_mut() { + // safety aligned by block device + let block = unsafe { &mut *(block as *mut _ as *mut crate::sdmmc::DataBlock) }; + self.read_block(block_address, block).await?; + block_address += 1; + } + + Ok(()) + } + + async fn write( + &mut self, + mut block_address: u32, + buf: &[aligned::Aligned], + ) -> Result<(), Self::Error> { + // FIXME/TODO because of missing read_blocks multiple we have to do this one block at a time + for block in buf.iter() { + // safety aligned by block device + let block = unsafe { &*(block as *const _ as *const crate::sdmmc::DataBlock) }; + self.write_block(block_address, block).await?; + block_address += 1; + } + + Ok(()) + } + + async fn size(&mut self) -> Result { + Ok(self.card()?.size()) + } +} diff --git a/embassy/embassy-stm32/src/spdifrx/mod.rs b/embassy/embassy-stm32/src/spdifrx/mod.rs new file mode 100644 index 0000000..a205780 --- /dev/null +++ b/embassy/embassy-stm32/src/spdifrx/mod.rs @@ -0,0 +1,336 @@ +//! S/PDIF receiver +#![macro_use] +#![cfg_attr(gpdma, allow(unused))] + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::dma::ringbuffer::Error as RingbufferError; +pub use crate::dma::word; +#[cfg(not(gpdma))] +use crate::dma::ReadableRingBuffer; +use crate::dma::{Channel, TransferOptions}; +use crate::gpio::{AfType, AnyPin, Pull, SealedPin as _}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::spdifrx::Spdifrx as Regs; +use crate::rcc::{RccInfo, SealedRccPeripheral}; +use crate::{interrupt, peripherals, Peripheral}; + +/// Possible S/PDIF preamble types. +#[allow(dead_code)] +#[repr(u8)] +enum PreambleType { + Unused = 0x00, + /// The preamble changes to preamble “B” once every 192 frames to identify the start of the block structure used to + /// organize the channel status and user information. + B = 0x01, + /// The first sub-frame (left or “A” channel in stereophonic operation and primary channel in monophonic operation) + /// normally starts with preamble “M” + M = 0x02, + /// The second sub-frame (right or “B” channel in stereophonic operation and secondary channel in monophonic + /// operation) always starts with preamble “W”. + W = 0x03, +} + +macro_rules! new_spdifrx_pin { + ($name:ident, $af_type:expr) => {{ + let pin = $name.into_ref(); + let input_sel = pin.input_sel(); + pin.set_as_af(pin.af_num(), $af_type); + (Some(pin.map_into()), input_sel) + }}; +} + +macro_rules! impl_spdifrx_pin { + ($inst:ident, $pin:ident, $af:expr, $sel:expr) => { + impl crate::spdifrx::InPin for crate::peripherals::$pin { + fn af_num(&self) -> u8 { + $af + } + fn input_sel(&self) -> u8 { + $sel + } + } + }; +} + +/// Ring-buffered SPDIFRX driver. +/// +/// Data is read by DMAs and stored in a ring buffer. +#[cfg(not(gpdma))] +pub struct Spdifrx<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, + spdifrx_in: Option>, + data_ring_buffer: ReadableRingBuffer<'d, u32>, +} + +/// Gives the address of the data register. +fn dr_address(r: Regs) -> *mut u32 { + #[cfg(spdifrx_v1)] + let address = r.dr().as_ptr() as _; + #[cfg(spdifrx_h7)] + let address = r.fmt0_dr().as_ptr() as _; // All fmtx_dr() implementations have the same address. + + return address; +} + +/// Gives the address of the channel status register. +#[allow(unused)] +fn csr_address(r: Regs) -> *mut u32 { + r.csr().as_ptr() as _ +} + +/// Select the channel for capturing control information. +pub enum ControlChannelSelection { + /// Capture control info from channel A. + A, + /// Capture control info from channel B. + B, +} + +/// Configuration options for the SPDIFRX driver. +pub struct Config { + /// Select the channel for capturing control information. + pub control_channel_selection: ControlChannelSelection, +} + +/// S/PDIF errors. +#[derive(Debug)] +pub enum Error { + /// DMA overrun error. + RingbufferError(RingbufferError), + /// Left/right channel synchronization error. + ChannelSyncError, +} + +impl From for Error { + fn from(error: RingbufferError) -> Self { + Self::RingbufferError(error) + } +} + +impl Default for Config { + fn default() -> Self { + Self { + control_channel_selection: ControlChannelSelection::A, + } + } +} + +#[cfg(not(gpdma))] +impl<'d, T: Instance> Spdifrx<'d, T> { + fn dma_opts() -> TransferOptions { + TransferOptions { + half_transfer_ir: true, + // new_write() and new_read() always use circular mode + ..Default::default() + } + } + + /// Create a new `Spdifrx` instance. + pub fn new( + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + spdifrx_in: impl Peripheral

> + 'd, + data_dma: impl Peripheral

> + 'd, + data_dma_buf: &'d mut [u32], + ) -> Self { + let (spdifrx_in, input_sel) = new_spdifrx_pin!(spdifrx_in, AfType::input(Pull::None)); + Self::setup(config, input_sel); + + into_ref!(peri, data_dma); + + let regs = T::info().regs; + let dr_request = data_dma.request(); + let dr_ring_buffer = + unsafe { ReadableRingBuffer::new(data_dma, dr_request, dr_address(regs), data_dma_buf, Self::dma_opts()) }; + + Self { + _peri: peri, + spdifrx_in, + data_ring_buffer: dr_ring_buffer, + } + } + + fn setup(config: Config, input_sel: u8) { + T::info().rcc.enable_and_reset(); + T::GlobalInterrupt::unpend(); + unsafe { T::GlobalInterrupt::enable() }; + + let regs = T::info().regs; + + regs.imr().write(|imr| { + imr.set_ifeie(true); // Enables interrupts for TERR, SERR, FERR. + imr.set_syncdie(true); // Enables SYNCD interrupt. + }); + + regs.cr().write(|cr| { + cr.set_spdifen(0x00); // Disable SPDIF receiver synchronization. + cr.set_rxdmaen(true); // Use RX DMA for data. Enabled on `read`. + cr.set_cbdmaen(false); // Do not capture channel info. + cr.set_rxsteo(true); // Operate in stereo mode. + cr.set_drfmt(0x01); // Data is left-aligned (MSB). + + // Disable all status fields in the data register. + // Status can be obtained directly with the status register DMA. + cr.set_pmsk(false); // Write parity bit to the data register. FIXME: Add parity check. + cr.set_vmsk(false); // Write validity to the data register. + cr.set_cumsk(true); // Do not write C and U bits to the data register. + cr.set_ptmsk(false); // Write preamble bits to the data register. + + cr.set_chsel(match config.control_channel_selection { + ControlChannelSelection::A => false, + ControlChannelSelection::B => true, + }); // Select channel status source. + + cr.set_nbtr(0x02); // 16 attempts are allowed. + cr.set_wfa(true); // Wait for activity before going to synchronization phase. + cr.set_insel(input_sel); // Input pin selection. + + #[cfg(stm32h7)] + cr.set_cksen(true); // Generate a symbol clock. + + #[cfg(stm32h7)] + cr.set_cksbkpen(true); // Generate a backup symbol clock. + }); + } + + /// Start the SPDIFRX driver. + pub fn start(&mut self) { + self.data_ring_buffer.start(); + + T::info().regs.cr().modify(|cr| { + cr.set_spdifen(0x03); // Enable S/PDIF receiver. + }); + } + + /// Read from the SPDIFRX data ring buffer. + /// + /// SPDIFRX is always receiving data in the background. This function pops already-received + /// data from the buffer. + /// + /// If there's less than `data.len()` data in the buffer, this waits until there is. + pub async fn read(&mut self, data: &mut [u32]) -> Result<(), Error> { + self.data_ring_buffer.read_exact(data).await?; + + let first_preamble = (data[0] >> 4) & 0b11_u32; + if (first_preamble as u8) == (PreambleType::W as u8) { + trace!("S/PDIF left/right mismatch"); + + // Resynchronize until the first sample is for the left channel. + self.data_ring_buffer.clear(); + return Err(Error::ChannelSyncError); + }; + + for sample in data.as_mut() { + if (*sample & (0x0002_u32)) == 0x0001 { + // Discard invalid samples, setting them to mute level. + *sample = 0; + } else { + // Discard status information in the lowest byte. + *sample &= 0xFFFFFF00; + } + } + + Ok(()) + } +} + +#[cfg(not(gpdma))] +impl<'d, T: Instance> Drop for Spdifrx<'d, T> { + fn drop(&mut self) { + T::info().regs.cr().modify(|cr| cr.set_spdifen(0x00)); + self.spdifrx_in.as_ref().map(|x| x.set_as_disconnected()); + } +} + +struct State { + #[allow(unused)] + waker: AtomicWaker, +} + +impl State { + const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +struct Info { + regs: crate::pac::spdifrx::Spdifrx, + rcc: RccInfo, +} + +peri_trait!( + irqs: [GlobalInterrupt], +); + +/// SPIDFRX pin trait +pub trait InPin: crate::gpio::Pin { + /// Get the GPIO AF number needed to use this pin. + fn af_num(&self) -> u8; + /// Get the SPIDFRX INSEL number needed to use this pin. + fn input_sel(&self) -> u8; +} + +dma_trait!(Dma, Instance); + +/// Global interrupt handler. +pub struct GlobalInterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for GlobalInterruptHandler { + unsafe fn on_interrupt() { + T::state().waker.wake(); + + let regs = T::info().regs; + let sr = regs.sr().read(); + + if sr.serr() || sr.terr() || sr.ferr() { + trace!("SPDIFRX error, resync"); + + // Clear errors by disabling SPDIFRX, then reenable. + regs.cr().modify(|cr| cr.set_spdifen(0x00)); + regs.cr().modify(|cr| cr.set_spdifen(0x03)); + } else if sr.syncd() { + // Synchronization was successful. + trace!("SPDIFRX sync success"); + } + + // Clear interrupt flags. + regs.ifcr().write(|ifcr| { + ifcr.set_perrcf(true); // Clears parity error flag. + ifcr.set_ovrcf(true); // Clears overrun error flag. + ifcr.set_sbdcf(true); // Clears synchronization block detected flag. + ifcr.set_syncdcf(true); // Clears SYNCD from SR (was read above). + }); + } +} + +foreach_peripheral!( + (spdifrx, $inst:ident) => { + #[allow(private_interfaces)] + impl SealedInstance for peripherals::$inst { + fn info() -> &'static Info { + static INFO: Info = Info{ + regs: crate::pac::$inst, + rcc: crate::peripherals::$inst::RCC_INFO, + }; + &INFO + } + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for peripherals::$inst { + type GlobalInterrupt = crate::_generated::peripheral_interrupts::$inst::GLOBAL; + } + }; +); diff --git a/embassy/embassy-stm32/src/spi/mod.rs b/embassy/embassy-stm32/src/spi/mod.rs new file mode 100644 index 0000000..a65b0cc --- /dev/null +++ b/embassy/embassy-stm32/src/spi/mod.rs @@ -0,0 +1,1297 @@ +//! Serial Peripheral Interface (SPI) +#![macro_use] + +use core::marker::PhantomData; +use core::ptr; + +use embassy_embedded_hal::SetConfig; +use embassy_futures::join::join; +use embassy_hal_internal::PeripheralRef; +pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; + +use crate::dma::{word, ChannelAndRequest}; +use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; +use crate::mode::{Async, Blocking, Mode as PeriMode}; +use crate::pac::spi::{regs, vals, Spi as Regs}; +use crate::rcc::{RccInfo, SealedRccPeripheral}; +use crate::time::Hertz; +use crate::Peripheral; + +/// SPI error. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Invalid framing. + Framing, + /// CRC error (only if hardware CRC checking is enabled). + Crc, + /// Mode fault + ModeFault, + /// Overrun. + Overrun, +} + +/// SPI bit order +#[derive(Copy, Clone)] +pub enum BitOrder { + /// Least significant bit first. + LsbFirst, + /// Most significant bit first. + MsbFirst, +} + +/// SPI configuration. +#[non_exhaustive] +#[derive(Copy, Clone)] +pub struct Config { + /// SPI mode. + pub mode: Mode, + /// Bit order. + pub bit_order: BitOrder, + /// Clock frequency. + pub frequency: Hertz, + /// Enable internal pullup on MISO. + /// + /// There are some ICs that require a pull-up on the MISO pin for some applications. + /// If you are unsure, you probably don't need this. + pub miso_pull: Pull, +} + +impl Default for Config { + fn default() -> Self { + Self { + mode: MODE_0, + bit_order: BitOrder::MsbFirst, + frequency: Hertz(1_000_000), + miso_pull: Pull::None, + } + } +} + +impl Config { + fn raw_phase(&self) -> vals::Cpha { + match self.mode.phase { + Phase::CaptureOnSecondTransition => vals::Cpha::SECONDEDGE, + Phase::CaptureOnFirstTransition => vals::Cpha::FIRSTEDGE, + } + } + + fn raw_polarity(&self) -> vals::Cpol { + match self.mode.polarity { + Polarity::IdleHigh => vals::Cpol::IDLEHIGH, + Polarity::IdleLow => vals::Cpol::IDLELOW, + } + } + + fn raw_byte_order(&self) -> vals::Lsbfirst { + match self.bit_order { + BitOrder::LsbFirst => vals::Lsbfirst::LSBFIRST, + BitOrder::MsbFirst => vals::Lsbfirst::MSBFIRST, + } + } + + #[cfg(gpio_v1)] + fn sck_af(&self) -> AfType { + AfType::output(OutputType::PushPull, Speed::VeryHigh) + } + + #[cfg(gpio_v2)] + fn sck_af(&self) -> AfType { + AfType::output_pull( + OutputType::PushPull, + Speed::VeryHigh, + match self.mode.polarity { + Polarity::IdleLow => Pull::Down, + Polarity::IdleHigh => Pull::Up, + }, + ) + } +} +/// SPI driver. +pub struct Spi<'d, M: PeriMode> { + pub(crate) info: &'static Info, + kernel_clock: Hertz, + sck: Option>, + mosi: Option>, + miso: Option>, + tx_dma: Option>, + rx_dma: Option>, + _phantom: PhantomData, + current_word_size: word_impl::Config, +} + +impl<'d, M: PeriMode> Spi<'d, M> { + fn new_inner( + _peri: impl Peripheral

+ 'd, + sck: Option>, + mosi: Option>, + miso: Option>, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + let mut this = Self { + info: T::info(), + kernel_clock: T::frequency(), + sck, + mosi, + miso, + tx_dma, + rx_dma, + current_word_size: ::CONFIG, + _phantom: PhantomData, + }; + this.enable_and_init(config); + this + } + + fn enable_and_init(&mut self, config: Config) { + let br = compute_baud_rate(self.kernel_clock, config.frequency); + let cpha = config.raw_phase(); + let cpol = config.raw_polarity(); + let lsbfirst = config.raw_byte_order(); + + self.info.rcc.enable_and_reset(); + + let regs = self.info.regs; + #[cfg(any(spi_v1, spi_f1))] + { + regs.cr2().modify(|w| { + w.set_ssoe(false); + }); + regs.cr1().modify(|w| { + w.set_cpha(cpha); + w.set_cpol(cpol); + + w.set_mstr(vals::Mstr::MASTER); + w.set_br(br); + w.set_spe(true); + w.set_lsbfirst(lsbfirst); + w.set_ssi(true); + w.set_ssm(true); + w.set_crcen(false); + w.set_bidimode(vals::Bidimode::UNIDIRECTIONAL); + // we're doing "fake rxonly", by actually writing one + // byte to TXDR for each byte we want to receive. if we + // set OUTPUTDISABLED here, this hangs. + w.set_rxonly(vals::Rxonly::FULLDUPLEX); + w.set_dff(::CONFIG) + }); + } + #[cfg(spi_v2)] + { + regs.cr2().modify(|w| { + let (ds, frxth) = ::CONFIG; + w.set_frxth(frxth); + w.set_ds(ds); + w.set_ssoe(false); + }); + regs.cr1().modify(|w| { + w.set_cpha(cpha); + w.set_cpol(cpol); + + w.set_mstr(vals::Mstr::MASTER); + w.set_br(br); + w.set_lsbfirst(lsbfirst); + w.set_ssi(true); + w.set_ssm(true); + w.set_crcen(false); + w.set_bidimode(vals::Bidimode::UNIDIRECTIONAL); + w.set_spe(true); + }); + } + #[cfg(any(spi_v3, spi_v4, spi_v5))] + { + regs.ifcr().write(|w| w.0 = 0xffff_ffff); + regs.cfg2().modify(|w| { + //w.set_ssoe(true); + w.set_ssoe(false); + w.set_cpha(cpha); + w.set_cpol(cpol); + w.set_lsbfirst(lsbfirst); + w.set_ssm(true); + w.set_master(vals::Master::MASTER); + w.set_comm(vals::Comm::FULLDUPLEX); + w.set_ssom(vals::Ssom::ASSERTED); + w.set_midi(0); + w.set_mssi(0); + w.set_afcntr(true); + w.set_ssiop(vals::Ssiop::ACTIVEHIGH); + }); + regs.cfg1().modify(|w| { + w.set_crcen(false); + w.set_mbr(br); + w.set_dsize(::CONFIG); + w.set_fthlv(vals::Fthlv::ONEFRAME); + }); + regs.cr2().modify(|w| { + w.set_tsize(0); + }); + regs.cr1().modify(|w| { + w.set_ssi(false); + w.set_spe(true); + }); + } + } + + /// Reconfigures it with the supplied config. + pub fn set_config(&mut self, config: &Config) -> Result<(), ()> { + let cpha = config.raw_phase(); + let cpol = config.raw_polarity(); + + let lsbfirst = config.raw_byte_order(); + + let br = compute_baud_rate(self.kernel_clock, config.frequency); + + #[cfg(any(spi_v1, spi_f1, spi_v2))] + self.info.regs.cr1().modify(|w| { + w.set_cpha(cpha); + w.set_cpol(cpol); + w.set_br(br); + w.set_lsbfirst(lsbfirst); + }); + + #[cfg(any(spi_v3, spi_v4, spi_v5))] + { + self.info.regs.cfg2().modify(|w| { + w.set_cpha(cpha); + w.set_cpol(cpol); + w.set_lsbfirst(lsbfirst); + }); + self.info.regs.cfg1().modify(|w| { + w.set_mbr(br); + }); + } + Ok(()) + } + + /// Get current SPI configuration. + pub fn get_current_config(&self) -> Config { + #[cfg(any(spi_v1, spi_f1, spi_v2))] + let cfg = self.info.regs.cr1().read(); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + let cfg = self.info.regs.cfg2().read(); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + let cfg1 = self.info.regs.cfg1().read(); + + let polarity = if cfg.cpol() == vals::Cpol::IDLELOW { + Polarity::IdleLow + } else { + Polarity::IdleHigh + }; + let phase = if cfg.cpha() == vals::Cpha::FIRSTEDGE { + Phase::CaptureOnFirstTransition + } else { + Phase::CaptureOnSecondTransition + }; + + let bit_order = if cfg.lsbfirst() == vals::Lsbfirst::LSBFIRST { + BitOrder::LsbFirst + } else { + BitOrder::MsbFirst + }; + + let miso_pull = match &self.miso { + None => Pull::None, + Some(pin) => pin.pull(), + }; + + #[cfg(any(spi_v1, spi_f1, spi_v2))] + let br = cfg.br(); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + let br = cfg1.mbr(); + + let frequency = compute_frequency(self.kernel_clock, br); + + Config { + mode: Mode { polarity, phase }, + bit_order, + frequency, + miso_pull, + } + } + + pub(crate) fn set_word_size(&mut self, word_size: word_impl::Config) { + if self.current_word_size == word_size { + return; + } + + self.info.regs.cr1().modify(|w| { + w.set_spe(false); + }); + + #[cfg(any(spi_v1, spi_f1))] + self.info.regs.cr1().modify(|reg| { + reg.set_dff(word_size); + }); + #[cfg(spi_v2)] + self.info.regs.cr2().modify(|w| { + w.set_frxth(word_size.1); + w.set_ds(word_size.0); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + self.info.regs.cfg1().modify(|w| { + w.set_dsize(word_size); + }); + + self.current_word_size = word_size; + } + + /// Blocking write. + pub fn blocking_write(&mut self, words: &[W]) -> Result<(), Error> { + // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? + #[cfg(any(spi_v3, spi_v4, spi_v5))] + self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); + self.info.regs.cr1().modify(|w| w.set_spe(true)); + flush_rx_fifo(self.info.regs); + for word in words.iter() { + // this cannot use `transfer_word` because on SPIv2 and higher, + // the SPI RX state machine hangs if no physical pin is connected to the SCK AF. + // This is the case when the SPI has been created with `new_(blocking_?)txonly_nosck`. + // See https://github.com/embassy-rs/embassy/issues/2902 + // This is not documented as an errata by ST, and I've been unable to find anything online... + #[cfg(not(any(spi_v1, spi_f1)))] + write_word(self.info.regs, *word)?; + + // if we're doing tx only, after writing the last byte to FIFO we have to wait + // until it's actually sent. On SPIv1 you're supposed to use the BSY flag for this + // but apparently it's broken, it clears too soon. Workaround is to wait for RXNE: + // when it gets set you know the transfer is done, even if you don't care about rx. + // Luckily this doesn't affect SPIv2+. + // See http://efton.sk/STM32/gotcha/g68.html + // ST doesn't seem to document this in errata sheets (?) + #[cfg(any(spi_v1, spi_f1))] + transfer_word(self.info.regs, *word)?; + } + + // wait until last word is transmitted. (except on v1, see above) + #[cfg(not(any(spi_v1, spi_f1, spi_v2)))] + while !self.info.regs.sr().read().txc() {} + #[cfg(spi_v2)] + while self.info.regs.sr().read().bsy() {} + + Ok(()) + } + + /// Blocking read. + pub fn blocking_read(&mut self, words: &mut [W]) -> Result<(), Error> { + // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? + #[cfg(any(spi_v3, spi_v4, spi_v5))] + self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); + self.info.regs.cr1().modify(|w| w.set_spe(true)); + flush_rx_fifo(self.info.regs); + for word in words.iter_mut() { + *word = transfer_word(self.info.regs, W::default())?; + } + Ok(()) + } + + /// Blocking in-place bidirectional transfer. + /// + /// This writes the contents of `data` on MOSI, and puts the received data on MISO in `data`, at the same time. + pub fn blocking_transfer_in_place(&mut self, words: &mut [W]) -> Result<(), Error> { + // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? + #[cfg(any(spi_v3, spi_v4, spi_v5))] + self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); + self.info.regs.cr1().modify(|w| w.set_spe(true)); + flush_rx_fifo(self.info.regs); + for word in words.iter_mut() { + *word = transfer_word(self.info.regs, *word)?; + } + Ok(()) + } + + /// Blocking bidirectional transfer. + /// + /// This transfers both buffers at the same time, so it is NOT equivalent to `write` followed by `read`. + /// + /// The transfer runs for `max(read.len(), write.len())` bytes. If `read` is shorter extra bytes are ignored. + /// If `write` is shorter it is padded with zero bytes. + pub fn blocking_transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> { + // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? + #[cfg(any(spi_v3, spi_v4, spi_v5))] + self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); + self.info.regs.cr1().modify(|w| w.set_spe(true)); + flush_rx_fifo(self.info.regs); + let len = read.len().max(write.len()); + for i in 0..len { + let wb = write.get(i).copied().unwrap_or_default(); + let rb = transfer_word(self.info.regs, wb)?; + if let Some(r) = read.get_mut(i) { + *r = rb; + } + } + Ok(()) + } +} + +impl<'d> Spi<'d, Blocking> { + /// Create a new blocking SPI driver. + pub fn new_blocking( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + mosi: impl Peripheral

> + 'd, + miso: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(sck, config.sck_af()), + new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(miso, AfType::input(config.miso_pull)), + None, + None, + config, + ) + } + + /// Create a new blocking SPI driver, in RX-only mode (only MISO pin, no MOSI). + pub fn new_blocking_rxonly( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + miso: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(sck, config.sck_af()), + None, + new_pin!(miso, AfType::input(config.miso_pull)), + None, + None, + config, + ) + } + + /// Create a new blocking SPI driver, in TX-only mode (only MOSI pin, no MISO). + pub fn new_blocking_txonly( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + mosi: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(sck, config.sck_af()), + new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + config, + ) + } + + /// Create a new SPI driver, in TX-only mode, without SCK pin. + /// + /// This can be useful for bit-banging non-SPI protocols. + pub fn new_blocking_txonly_nosck( + peri: impl Peripheral

+ 'd, + mosi: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + None, + new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + config, + ) + } +} + +impl<'d> Spi<'d, Async> { + /// Create a new SPI driver. + pub fn new( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + mosi: impl Peripheral

> + 'd, + miso: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(sck, config.sck_af()), + new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(miso, AfType::input(config.miso_pull)), + new_dma!(tx_dma), + new_dma!(rx_dma), + config, + ) + } + + /// Create a new SPI driver, in RX-only mode (only MISO pin, no MOSI). + pub fn new_rxonly( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + miso: impl Peripheral

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

> + 'd, + rx_dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(sck, config.sck_af()), + None, + new_pin!(miso, AfType::input(config.miso_pull)), + #[cfg(any(spi_v1, spi_f1, spi_v2))] + new_dma!(tx_dma), + #[cfg(any(spi_v3, spi_v4, spi_v5))] + None, + new_dma!(rx_dma), + config, + ) + } + + /// Create a new SPI driver, in TX-only mode (only MOSI pin, no MISO). + pub fn new_txonly( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + mosi: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(sck, config.sck_af()), + new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + new_dma!(tx_dma), + None, + config, + ) + } + + /// Create a new SPI driver, in TX-only mode, without SCK pin. + /// + /// This can be useful for bit-banging non-SPI protocols. + pub fn new_txonly_nosck( + peri: impl Peripheral

+ 'd, + mosi: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + None, + new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + new_dma!(tx_dma), + None, + config, + ) + } + + #[cfg(stm32wl)] + /// Useful for on chip peripherals like SUBGHZ which are hardwired. + pub fn new_subghz( + peri: impl Peripheral

+ 'd, + tx_dma: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

> + 'd, + ) -> Self { + // see RM0453 rev 1 section 7.2.13 page 291 + // The SUBGHZSPI_SCK frequency is obtained by PCLK3 divided by two. + // The SUBGHZSPI_SCK clock maximum speed must not exceed 16 MHz. + let pclk3_freq = ::frequency().0; + let freq = Hertz(core::cmp::min(pclk3_freq / 2, 16_000_000)); + let mut config = Config::default(); + config.mode = MODE_0; + config.bit_order = BitOrder::MsbFirst; + config.frequency = freq; + + Self::new_inner(peri, None, None, None, new_dma!(tx_dma), new_dma!(rx_dma), config) + } + + #[allow(dead_code)] + pub(crate) fn new_internal( + peri: impl Peripheral

+ 'd, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + Self::new_inner(peri, None, None, None, tx_dma, rx_dma, config) + } + + /// SPI write, using DMA. + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { + if data.is_empty() { + return Ok(()); + } + + self.info.regs.cr1().modify(|w| { + w.set_spe(false); + }); + self.set_word_size(W::CONFIG); + + let tx_dst = self.info.regs.tx_ptr(); + let tx_f = unsafe { self.tx_dma.as_mut().unwrap().write(data, tx_dst, Default::default()) }; + + set_txdmaen(self.info.regs, true); + self.info.regs.cr1().modify(|w| { + w.set_spe(true); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + self.info.regs.cr1().modify(|w| { + w.set_cstart(true); + }); + + tx_f.await; + + finish_dma(self.info.regs); + + Ok(()) + } + + /// SPI read, using DMA. + #[cfg(any(spi_v3, spi_v4, spi_v5))] + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + if data.is_empty() { + return Ok(()); + } + + let regs = self.info.regs; + + regs.cr1().modify(|w| { + w.set_spe(false); + }); + + self.set_word_size(W::CONFIG); + + let comm = regs.cfg2().modify(|w| { + let prev = w.comm(); + w.set_comm(vals::Comm::RECEIVER); + prev + }); + + #[cfg(spi_v3)] + let i2scfg = regs.i2scfgr().modify(|w| { + w.i2smod().then(|| { + let prev = w.i2scfg(); + w.set_i2scfg(match prev { + vals::I2scfg::SLAVERX | vals::I2scfg::SLAVEFULLDUPLEX => vals::I2scfg::SLAVERX, + vals::I2scfg::MASTERRX | vals::I2scfg::MASTERFULLDUPLEX => vals::I2scfg::MASTERRX, + _ => panic!("unsupported configuration"), + }); + prev + }) + }); + + let rx_src = regs.rx_ptr(); + + for mut chunk in data.chunks_mut(u16::max_value().into()) { + set_rxdmaen(regs, true); + + let tsize = chunk.len(); + + let transfer = unsafe { + self.rx_dma + .as_mut() + .unwrap() + .read(rx_src, &mut chunk, Default::default()) + }; + + regs.cr2().modify(|w| { + w.set_tsize(tsize as u16); + }); + + regs.cr1().modify(|w| { + w.set_spe(true); + }); + + regs.cr1().modify(|w| { + w.set_cstart(true); + }); + + transfer.await; + + finish_dma(regs); + } + + regs.cr1().modify(|w| { + w.set_spe(false); + }); + + regs.cfg2().modify(|w| { + w.set_comm(comm); + }); + + regs.cr2().modify(|w| { + w.set_tsize(0); + }); + + #[cfg(spi_v3)] + if let Some(i2scfg) = i2scfg { + regs.i2scfgr().modify(|w| { + w.set_i2scfg(i2scfg); + }); + } + + Ok(()) + } + + /// SPI read, using DMA. + #[cfg(any(spi_v1, spi_f1, spi_v2))] + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + if data.is_empty() { + return Ok(()); + } + + self.info.regs.cr1().modify(|w| { + w.set_spe(false); + }); + + self.set_word_size(W::CONFIG); + + // SPIv3 clears rxfifo on SPE=0 + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + flush_rx_fifo(self.info.regs); + + set_rxdmaen(self.info.regs, true); + + let clock_byte_count = data.len(); + + let rx_src = self.info.regs.rx_ptr(); + let rx_f = unsafe { self.rx_dma.as_mut().unwrap().read(rx_src, data, Default::default()) }; + + let tx_dst = self.info.regs.tx_ptr(); + let clock_byte = W::default(); + let tx_f = unsafe { + self.tx_dma + .as_mut() + .unwrap() + .write_repeated(&clock_byte, clock_byte_count, tx_dst, Default::default()) + }; + + set_txdmaen(self.info.regs, true); + self.info.regs.cr1().modify(|w| { + w.set_spe(true); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + self.info.regs.cr1().modify(|w| { + w.set_cstart(true); + }); + + join(tx_f, rx_f).await; + + finish_dma(self.info.regs); + + Ok(()) + } + + async fn transfer_inner(&mut self, read: *mut [W], write: *const [W]) -> Result<(), Error> { + assert_eq!(read.len(), write.len()); + if read.len() == 0 { + return Ok(()); + } + + self.info.regs.cr1().modify(|w| { + w.set_spe(false); + }); + + self.set_word_size(W::CONFIG); + + // SPIv3 clears rxfifo on SPE=0 + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + flush_rx_fifo(self.info.regs); + + set_rxdmaen(self.info.regs, true); + + let rx_src = self.info.regs.rx_ptr(); + let rx_f = unsafe { self.rx_dma.as_mut().unwrap().read_raw(rx_src, read, Default::default()) }; + + let tx_dst = self.info.regs.tx_ptr(); + let tx_f = unsafe { + self.tx_dma + .as_mut() + .unwrap() + .write_raw(write, tx_dst, Default::default()) + }; + + set_txdmaen(self.info.regs, true); + self.info.regs.cr1().modify(|w| { + w.set_spe(true); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + self.info.regs.cr1().modify(|w| { + w.set_cstart(true); + }); + + join(tx_f, rx_f).await; + + finish_dma(self.info.regs); + + Ok(()) + } + + /// Bidirectional transfer, using DMA. + /// + /// This transfers both buffers at the same time, so it is NOT equivalent to `write` followed by `read`. + /// + /// The transfer runs for `max(read.len(), write.len())` bytes. If `read` is shorter extra bytes are ignored. + /// If `write` is shorter it is padded with zero bytes. + pub async fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> { + self.transfer_inner(read, write).await + } + + /// In-place bidirectional transfer, using DMA. + /// + /// This writes the contents of `data` on MOSI, and puts the received data on MISO in `data`, at the same time. + pub async fn transfer_in_place(&mut self, data: &mut [W]) -> Result<(), Error> { + self.transfer_inner(data, data).await + } +} + +impl<'d, M: PeriMode> Drop for Spi<'d, M> { + fn drop(&mut self) { + self.sck.as_ref().map(|x| x.set_as_disconnected()); + self.mosi.as_ref().map(|x| x.set_as_disconnected()); + self.miso.as_ref().map(|x| x.set_as_disconnected()); + + self.info.rcc.disable(); + } +} + +#[cfg(not(any(spi_v3, spi_v4, spi_v5)))] +use vals::Br; +#[cfg(any(spi_v3, spi_v4, spi_v5))] +use vals::Mbr as Br; + +fn compute_baud_rate(kernel_clock: Hertz, freq: Hertz) -> Br { + let val = match kernel_clock.0 / freq.0 { + 0 => panic!("You are trying to reach a frequency higher than the clock"), + 1..=2 => 0b000, + 3..=5 => 0b001, + 6..=11 => 0b010, + 12..=23 => 0b011, + 24..=39 => 0b100, + 40..=95 => 0b101, + 96..=191 => 0b110, + _ => 0b111, + }; + + Br::from_bits(val) +} + +fn compute_frequency(kernel_clock: Hertz, br: Br) -> Hertz { + let div: u16 = match br { + Br::DIV2 => 2, + Br::DIV4 => 4, + Br::DIV8 => 8, + Br::DIV16 => 16, + Br::DIV32 => 32, + Br::DIV64 => 64, + Br::DIV128 => 128, + Br::DIV256 => 256, + }; + + kernel_clock / div +} + +pub(crate) trait RegsExt { + fn tx_ptr(&self) -> *mut W; + fn rx_ptr(&self) -> *mut W; +} + +impl RegsExt for Regs { + fn tx_ptr(&self) -> *mut W { + #[cfg(any(spi_v1, spi_f1))] + let dr = self.dr(); + #[cfg(spi_v2)] + let dr = self.dr16(); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + let dr = self.txdr32(); + dr.as_ptr() as *mut W + } + + fn rx_ptr(&self) -> *mut W { + #[cfg(any(spi_v1, spi_f1))] + let dr = self.dr(); + #[cfg(spi_v2)] + let dr = self.dr16(); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + let dr = self.rxdr32(); + dr.as_ptr() as *mut W + } +} + +fn check_error_flags(sr: regs::Sr, ovr: bool) -> Result<(), Error> { + if sr.ovr() && ovr { + return Err(Error::Overrun); + } + #[cfg(not(any(spi_f1, spi_v3, spi_v4, spi_v5)))] + if sr.fre() { + return Err(Error::Framing); + } + #[cfg(any(spi_v3, spi_v4, spi_v5))] + if sr.tifre() { + return Err(Error::Framing); + } + if sr.modf() { + return Err(Error::ModeFault); + } + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + if sr.crcerr() { + return Err(Error::Crc); + } + #[cfg(any(spi_v3, spi_v4, spi_v5))] + if sr.crce() { + return Err(Error::Crc); + } + + Ok(()) +} + +fn spin_until_tx_ready(regs: Regs, ovr: bool) -> Result<(), Error> { + loop { + let sr = regs.sr().read(); + + check_error_flags(sr, ovr)?; + + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + if sr.txe() { + return Ok(()); + } + #[cfg(any(spi_v3, spi_v4, spi_v5))] + if sr.txp() { + return Ok(()); + } + } +} + +fn spin_until_rx_ready(regs: Regs) -> Result<(), Error> { + loop { + let sr = regs.sr().read(); + + check_error_flags(sr, true)?; + + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + if sr.rxne() { + return Ok(()); + } + #[cfg(any(spi_v3, spi_v4, spi_v5))] + if sr.rxp() { + return Ok(()); + } + } +} + +pub(crate) fn flush_rx_fifo(regs: Regs) { + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + while regs.sr().read().rxne() { + #[cfg(not(spi_v2))] + let _ = regs.dr().read(); + #[cfg(spi_v2)] + let _ = regs.dr16().read(); + } + #[cfg(any(spi_v3, spi_v4, spi_v5))] + while regs.sr().read().rxp() { + let _ = regs.rxdr32().read(); + } +} + +pub(crate) fn set_txdmaen(regs: Regs, val: bool) { + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + regs.cr2().modify(|reg| { + reg.set_txdmaen(val); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + regs.cfg1().modify(|reg| { + reg.set_txdmaen(val); + }); +} + +pub(crate) fn set_rxdmaen(regs: Regs, val: bool) { + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + regs.cr2().modify(|reg| { + reg.set_rxdmaen(val); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + regs.cfg1().modify(|reg| { + reg.set_rxdmaen(val); + }); +} + +fn finish_dma(regs: Regs) { + #[cfg(spi_v2)] + while regs.sr().read().ftlvl().to_bits() > 0 {} + + #[cfg(any(spi_v3, spi_v4, spi_v5))] + { + if regs.cr2().read().tsize() == 0 { + while !regs.sr().read().txc() {} + } else { + while !regs.sr().read().eot() {} + } + } + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + 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, spi_v5)))] + regs.cr2().modify(|reg| { + reg.set_txdmaen(false); + reg.set_rxdmaen(false); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + regs.cfg1().modify(|reg| { + reg.set_txdmaen(false); + reg.set_rxdmaen(false); + }); +} + +fn transfer_word(regs: Regs, tx_word: W) -> Result { + spin_until_tx_ready(regs, true)?; + + unsafe { + ptr::write_volatile(regs.tx_ptr(), tx_word); + + #[cfg(any(spi_v3, spi_v4, spi_v5))] + regs.cr1().modify(|reg| reg.set_cstart(true)); + } + + spin_until_rx_ready(regs)?; + + let rx_word = unsafe { ptr::read_volatile(regs.rx_ptr()) }; + Ok(rx_word) +} + +#[allow(unused)] // unused in SPIv1 +fn write_word(regs: Regs, tx_word: W) -> Result<(), Error> { + // for write, we intentionally ignore the rx fifo, which will cause + // overrun errors that we have to ignore. + spin_until_tx_ready(regs, false)?; + + unsafe { + ptr::write_volatile(regs.tx_ptr(), tx_word); + + #[cfg(any(spi_v3, spi_v4, spi_v5))] + regs.cr1().modify(|reg| reg.set_cstart(true)); + } + Ok(()) +} + +// Note: It is not possible to impl these traits generically in embedded-hal 0.2 due to a conflict with +// some marker traits. For details, see https://github.com/rust-embedded/embedded-hal/pull/289 +macro_rules! impl_blocking { + ($w:ident) => { + impl<'d, M: PeriMode> embedded_hal_02::blocking::spi::Write<$w> for Spi<'d, M> { + type Error = Error; + + fn write(&mut self, words: &[$w]) -> Result<(), Self::Error> { + self.blocking_write(words) + } + } + + impl<'d, M: PeriMode> embedded_hal_02::blocking::spi::Transfer<$w> for Spi<'d, M> { + type Error = Error; + + fn transfer<'w>(&mut self, words: &'w mut [$w]) -> Result<&'w [$w], Self::Error> { + self.blocking_transfer_in_place(words)?; + Ok(words) + } + } + }; +} + +impl_blocking!(u8); +impl_blocking!(u16); + +impl<'d, M: PeriMode> embedded_hal_1::spi::ErrorType for Spi<'d, M> { + type Error = Error; +} + +impl<'d, W: Word, M: PeriMode> embedded_hal_1::spi::SpiBus for Spi<'d, M> { + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn read(&mut self, words: &mut [W]) -> Result<(), Self::Error> { + self.blocking_read(words) + } + + fn write(&mut self, words: &[W]) -> Result<(), Self::Error> { + self.blocking_write(words) + } + + fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Self::Error> { + self.blocking_transfer(read, write) + } + + fn transfer_in_place(&mut self, words: &mut [W]) -> Result<(), Self::Error> { + self.blocking_transfer_in_place(words) + } +} + +impl embedded_hal_1::spi::Error for Error { + fn kind(&self) -> embedded_hal_1::spi::ErrorKind { + match *self { + Self::Framing => embedded_hal_1::spi::ErrorKind::FrameFormat, + Self::Crc => embedded_hal_1::spi::ErrorKind::Other, + Self::ModeFault => embedded_hal_1::spi::ErrorKind::ModeFault, + Self::Overrun => embedded_hal_1::spi::ErrorKind::Overrun, + } + } +} + +impl<'d, W: Word> embedded_hal_async::spi::SpiBus for Spi<'d, Async> { + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn write(&mut self, words: &[W]) -> Result<(), Self::Error> { + self.write(words).await + } + + async fn read(&mut self, words: &mut [W]) -> Result<(), Self::Error> { + self.read(words).await + } + + async fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Self::Error> { + self.transfer(read, write).await + } + + async fn transfer_in_place(&mut self, words: &mut [W]) -> Result<(), Self::Error> { + self.transfer_in_place(words).await + } +} + +pub(crate) trait SealedWord { + const CONFIG: word_impl::Config; +} + +/// Word sizes usable for SPI. +#[allow(private_bounds)] +pub trait Word: word::Word + SealedWord + Default {} + +macro_rules! impl_word { + ($T:ty, $config:expr) => { + impl SealedWord for $T { + const CONFIG: Config = $config; + } + impl Word for $T {} + }; +} + +#[cfg(any(spi_v1, spi_f1))] +mod word_impl { + use super::*; + + pub type Config = vals::Dff; + + impl_word!(u8, vals::Dff::BITS8); + impl_word!(u16, vals::Dff::BITS16); +} + +#[cfg(spi_v2)] +mod word_impl { + use super::*; + + pub type Config = (vals::Ds, vals::Frxth); + + impl_word!(word::U4, (vals::Ds::BITS4, vals::Frxth::QUARTER)); + impl_word!(word::U5, (vals::Ds::BITS5, vals::Frxth::QUARTER)); + impl_word!(word::U6, (vals::Ds::BITS6, vals::Frxth::QUARTER)); + impl_word!(word::U7, (vals::Ds::BITS7, vals::Frxth::QUARTER)); + impl_word!(u8, (vals::Ds::BITS8, vals::Frxth::QUARTER)); + impl_word!(word::U9, (vals::Ds::BITS9, vals::Frxth::HALF)); + impl_word!(word::U10, (vals::Ds::BITS10, vals::Frxth::HALF)); + impl_word!(word::U11, (vals::Ds::BITS11, vals::Frxth::HALF)); + impl_word!(word::U12, (vals::Ds::BITS12, vals::Frxth::HALF)); + impl_word!(word::U13, (vals::Ds::BITS13, vals::Frxth::HALF)); + impl_word!(word::U14, (vals::Ds::BITS14, vals::Frxth::HALF)); + impl_word!(word::U15, (vals::Ds::BITS15, vals::Frxth::HALF)); + impl_word!(u16, (vals::Ds::BITS16, vals::Frxth::HALF)); +} + +#[cfg(any(spi_v3, spi_v4, spi_v5))] +mod word_impl { + use super::*; + + pub type Config = u8; + + impl_word!(word::U4, 4 - 1); + impl_word!(word::U5, 5 - 1); + impl_word!(word::U6, 6 - 1); + impl_word!(word::U7, 7 - 1); + impl_word!(u8, 8 - 1); + impl_word!(word::U9, 9 - 1); + impl_word!(word::U10, 10 - 1); + impl_word!(word::U11, 11 - 1); + impl_word!(word::U12, 12 - 1); + impl_word!(word::U13, 13 - 1); + impl_word!(word::U14, 14 - 1); + impl_word!(word::U15, 15 - 1); + impl_word!(u16, 16 - 1); + impl_word!(word::U17, 17 - 1); + impl_word!(word::U18, 18 - 1); + impl_word!(word::U19, 19 - 1); + impl_word!(word::U20, 20 - 1); + impl_word!(word::U21, 21 - 1); + impl_word!(word::U22, 22 - 1); + impl_word!(word::U23, 23 - 1); + impl_word!(word::U24, 24 - 1); + impl_word!(word::U25, 25 - 1); + impl_word!(word::U26, 26 - 1); + impl_word!(word::U27, 27 - 1); + impl_word!(word::U28, 28 - 1); + impl_word!(word::U29, 29 - 1); + impl_word!(word::U30, 30 - 1); + impl_word!(word::U31, 31 - 1); + impl_word!(u32, 32 - 1); +} + +pub(crate) struct Info { + pub(crate) regs: Regs, + pub(crate) rcc: RccInfo, +} + +struct State {} + +impl State { + #[allow(unused)] + const fn new() -> Self { + Self {} + } +} + +peri_trait!(); + +pin_trait!(SckPin, Instance); +pin_trait!(MosiPin, Instance); +pin_trait!(MisoPin, Instance); +pin_trait!(CsPin, Instance); +pin_trait!(MckPin, Instance); +pin_trait!(CkPin, Instance); +pin_trait!(WsPin, Instance); +dma_trait!(RxDma, Instance); +dma_trait!(TxDma, Instance); + +foreach_peripheral!( + (spi, $inst:ident) => { + peri_trait_impl!($inst, Info { + regs: crate::pac::$inst, + rcc: crate::peripherals::$inst::RCC_INFO, + }); + }; +); + +impl<'d, M: PeriMode> SetConfig for Spi<'d, M> { + type Config = Config; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { + self.set_config(config) + } +} diff --git a/embassy/embassy-stm32/src/time.rs b/embassy/embassy-stm32/src/time.rs new file mode 100644 index 0000000..802ff41 --- /dev/null +++ b/embassy/embassy-stm32/src/time.rs @@ -0,0 +1,125 @@ +//! Time units + +use core::ops::{Div, Mul}; + +/// Hertz +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Hertz(pub u32); + +impl Hertz { + /// Create a `Hertz` from the given hertz. + pub const fn hz(hertz: u32) -> Self { + Self(hertz) + } + + /// Create a `Hertz` from the given kilohertz. + pub const fn khz(kilohertz: u32) -> Self { + Self(kilohertz * 1_000) + } + + /// Create a `Hertz` from the given megahertz. + pub const fn mhz(megahertz: u32) -> Self { + Self(megahertz * 1_000_000) + } +} + +/// This is a convenience shortcut for [`Hertz::hz`] +pub const fn hz(hertz: u32) -> Hertz { + Hertz::hz(hertz) +} + +/// This is a convenience shortcut for [`Hertz::khz`] +pub const fn khz(kilohertz: u32) -> Hertz { + Hertz::khz(kilohertz) +} + +/// This is a convenience shortcut for [`Hertz::mhz`] +pub const fn mhz(megahertz: u32) -> Hertz { + Hertz::mhz(megahertz) +} + +impl Mul for Hertz { + type Output = Hertz; + fn mul(self, rhs: u32) -> Self::Output { + Hertz(self.0 * rhs) + } +} + +impl Div for Hertz { + type Output = Hertz; + fn div(self, rhs: u32) -> Self::Output { + Hertz(self.0 / rhs) + } +} + +impl Mul for Hertz { + type Output = Hertz; + fn mul(self, rhs: u16) -> Self::Output { + self * (rhs as u32) + } +} + +impl Div for Hertz { + type Output = Hertz; + fn div(self, rhs: u16) -> Self::Output { + self / (rhs as u32) + } +} + +impl Mul for Hertz { + type Output = Hertz; + fn mul(self, rhs: u8) -> Self::Output { + self * (rhs as u32) + } +} + +impl Div for Hertz { + type Output = Hertz; + fn div(self, rhs: u8) -> Self::Output { + self / (rhs as u32) + } +} + +impl Div for Hertz { + type Output = u32; + fn div(self, rhs: Hertz) -> Self::Output { + self.0 / rhs.0 + } +} + +#[repr(C)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// A variant on [Hertz] that acts as an `Option` that is smaller and repr C. +/// +/// An `Option` can be `.into()`'d into this type and back. +/// The only restriction is that that [Hertz] cannot have the value 0 since that's +/// seen as the `None` variant. +pub struct MaybeHertz(u32); + +impl MaybeHertz { + /// Same as calling the `.into()` function, but without type inference. + pub fn to_hertz(self) -> Option { + self.into() + } +} + +impl From> for MaybeHertz { + fn from(value: Option) -> Self { + match value { + Some(Hertz(0)) => panic!("Hertz cannot be 0"), + Some(Hertz(val)) => Self(val), + None => Self(0), + } + } +} + +impl From for Option { + fn from(value: MaybeHertz) -> Self { + match value { + MaybeHertz(0) => None, + MaybeHertz(val) => Some(Hertz(val)), + } + } +} diff --git a/embassy/embassy-stm32/src/time_driver.rs b/embassy/embassy-stm32/src/time_driver.rs new file mode 100644 index 0000000..a4c333d --- /dev/null +++ b/embassy/embassy-stm32/src/time_driver.rs @@ -0,0 +1,523 @@ +#![allow(non_snake_case)] + +use core::cell::{Cell, RefCell}; +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; + +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time_driver::{Driver, TICK_HZ}; +use embassy_time_queue_driver::Queue; +use stm32_metapac::timer::{regs, TimGp16}; + +use crate::interrupt::typelevel::Interrupt; +use crate::pac::timer::vals; +use crate::rcc::{self, SealedRccPeripheral}; +#[cfg(feature = "low-power")] +use crate::rtc::Rtc; +use crate::timer::{CoreInstance, GeneralInstance1Channel}; +use crate::{interrupt, peripherals}; + +// NOTE regarding ALARM_COUNT: +// +// As of 2023-12-04, this driver is implemented using CC1 as the halfway rollover interrupt, and any +// additional CC capabilities to provide timer alarms to embassy-time. embassy-time requires AT LEAST +// one alarm to be allocatable, which means timers that only have CC1, such as TIM16/TIM17, are not +// candidates for use as an embassy-time driver provider. (a.k.a 1CH and 1CH_CMP are not, others are good.) + +#[cfg(time_driver_tim1)] +type T = peripherals::TIM1; +#[cfg(time_driver_tim2)] +type T = peripherals::TIM2; +#[cfg(time_driver_tim3)] +type T = peripherals::TIM3; +#[cfg(time_driver_tim4)] +type T = peripherals::TIM4; +#[cfg(time_driver_tim5)] +type T = peripherals::TIM5; +#[cfg(time_driver_tim8)] +type T = peripherals::TIM8; +#[cfg(time_driver_tim9)] +type T = peripherals::TIM9; +#[cfg(time_driver_tim12)] +type T = peripherals::TIM12; +#[cfg(time_driver_tim15)] +type T = peripherals::TIM15; +#[cfg(time_driver_tim20)] +type T = peripherals::TIM20; +#[cfg(time_driver_tim21)] +type T = peripherals::TIM21; +#[cfg(time_driver_tim22)] +type T = peripherals::TIM22; +#[cfg(time_driver_tim23)] +type T = peripherals::TIM23; +#[cfg(time_driver_tim24)] +type T = peripherals::TIM24; + +foreach_interrupt! { + (TIM1, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim1)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM2, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim2)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM3, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim3)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM4, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim4)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM5, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim5)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM8, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim8)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM9, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim9)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM12, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim12)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM15, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim15)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM20, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim20)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM21, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim21)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM22, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim22)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM23, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim23)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM24, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim24)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; +} + +fn regs_gp16() -> TimGp16 { + unsafe { TimGp16::from_ptr(T::regs()) } +} + +// Clock timekeeping works with something we call "periods", which are time intervals +// of 2^15 ticks. The Clock counter value is 16 bits, so one "overflow cycle" is 2 periods. +// +// A `period` count is maintained in parallel to the Timer hardware `counter`, like this: +// - `period` and `counter` start at 0 +// - `period` is incremented on overflow (at counter value 0) +// - `period` is incremented "midway" between overflows (at counter value 0x8000) +// +// Therefore, when `period` is even, counter is in 0..0x7FFF. When odd, counter is in 0x8000..0xFFFF +// This allows for now() to return the correct value even if it races an overflow. +// +// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches +// the expected range for the `period` parity, we're done. If it doesn't, this means that +// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value +// corresponds to the next period. +// +// `period` is a 32bit integer, so It overflows on 2^32 * 2^15 / 32768 seconds of uptime, which is 136 years. +fn calc_now(period: u32, counter: u16) -> u64 { + ((period as u64) << 15) + ((counter as u32 ^ ((period & 1) << 15)) as u64) +} + +struct AlarmState { + timestamp: Cell, +} + +unsafe impl Send for AlarmState {} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + } + } +} + +pub(crate) struct RtcDriver { + /// Number of 2^15 periods elapsed since boot. + period: AtomicU32, + alarm: Mutex, + #[cfg(feature = "low-power")] + rtc: Mutex>>, + queue: Mutex>, +} + +embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { + period: AtomicU32::new(0), + alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), + #[cfg(feature = "low-power")] + rtc: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), + queue: Mutex::new(RefCell::new(Queue::new())) +}); + +impl RtcDriver { + fn init(&'static self, cs: critical_section::CriticalSection) { + let r = regs_gp16(); + + rcc::enable_and_reset_with_cs::(cs); + + let timer_freq = T::frequency(); + + r.cr1().modify(|w| w.set_cen(false)); + r.cnt().write(|w| w.set_cnt(0)); + + let psc = timer_freq.0 / TICK_HZ as u32 - 1; + let psc: u16 = match psc.try_into() { + Err(_) => panic!("psc division overflow: {}", psc), + Ok(n) => n, + }; + + r.psc().write_value(psc); + r.arr().write(|w| w.set_arr(u16::MAX)); + + // Set URS, generate update and clear URS + r.cr1().modify(|w| w.set_urs(vals::Urs::COUNTERONLY)); + r.egr().write(|w| w.set_ug(true)); + r.cr1().modify(|w| w.set_urs(vals::Urs::ANYEVENT)); + + // Mid-way point + r.ccr(0).write(|w| w.set_ccr(0x8000)); + + // Enable overflow and half-overflow interrupts + r.dier().write(|w| { + w.set_uie(true); + w.set_ccie(0, true); + }); + + ::CaptureCompareInterrupt::unpend(); + unsafe { ::CaptureCompareInterrupt::enable() }; + + r.cr1().modify(|w| w.set_cen(true)); + } + + fn on_interrupt(&self) { + let r = regs_gp16(); + + critical_section::with(|cs| { + let sr = r.sr().read(); + let dier = r.dier().read(); + + // Clear all interrupt flags. Bits in SR are "write 0 to clear", so write the bitwise NOT. + // Other approaches such as writing all zeros, or RMWing won't work, they can + // miss interrupts. + r.sr().write_value(regs::SrGp16(!sr.0)); + + // Overflow + if sr.uif() { + self.next_period(); + } + + // Half overflow + if sr.ccif(0) { + self.next_period(); + } + + let n = 0; + if sr.ccif(n + 1) && dier.ccie(n + 1) { + self.trigger_alarm(cs); + } + }) + } + + fn next_period(&self) { + let r = regs_gp16(); + + // We only modify the period from the timer interrupt, so we know this can't race. + let period = self.period.load(Ordering::Relaxed) + 1; + self.period.store(period, Ordering::Relaxed); + let t = (period as u64) << 15; + + critical_section::with(move |cs| { + r.dier().modify(move |w| { + let n = 0; + let alarm = self.alarm.borrow(cs); + let at = alarm.timestamp.get(); + + if at < t + 0xc000 { + // just enable it. `set_alarm` has already set the correct CCR val. + w.set_ccie(n + 1, true); + } + }) + }) + } + + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + } + } + + /* + Low-power private functions: all operate within a critical seciton + */ + + #[cfg(feature = "low-power")] + /// Compute the approximate amount of time until the next alarm + fn time_until_next_alarm(&self, cs: CriticalSection) -> embassy_time::Duration { + let now = self.now() + 32; + + embassy_time::Duration::from_ticks(self.alarm.borrow(cs).timestamp.get().saturating_sub(now)) + } + + #[cfg(feature = "low-power")] + /// Add the given offset to the current time + fn add_time(&self, offset: embassy_time::Duration, cs: CriticalSection) { + let offset = offset.as_ticks(); + let cnt = regs_gp16().cnt().read().cnt() as u32; + let period = self.period.load(Ordering::SeqCst); + + // Correct the race, if it exists + let period = if period & 1 == 1 && cnt < u16::MAX as u32 / 2 { + period + 1 + } else { + period + }; + + // Normalize to the full overflow + let period = (period / 2) * 2; + + // Add the offset + let period = period + 2 * (offset / u16::MAX as u64) as u32; + let cnt = cnt + (offset % u16::MAX as u64) as u32; + + let (cnt, period) = if cnt > u16::MAX as u32 { + (cnt - u16::MAX as u32, period + 2) + } else { + (cnt, period) + }; + + let period = if cnt > u16::MAX as u32 / 2 { period + 1 } else { period }; + + self.period.store(period, Ordering::SeqCst); + regs_gp16().cnt().write(|w| w.set_cnt(cnt as u16)); + + // Now, recompute alarm + let alarm = self.alarm.borrow(cs); + + if !self.set_alarm(cs, alarm.timestamp.get()) { + // If the alarm timestamp has passed, we need to trigger it + self.trigger_alarm(cs); + } + } + + #[cfg(feature = "low-power")] + /// Stop the wakeup alarm, if enabled, and add the appropriate offset + fn stop_wakeup_alarm(&self, cs: CriticalSection) { + if let Some(offset) = self.rtc.borrow(cs).get().unwrap().stop_wakeup_alarm(cs) { + self.add_time(offset, cs); + } + } + + /* + Low-power public functions: all create a critical section + */ + #[cfg(feature = "low-power")] + /// Set the rtc but panic if it's already been set + pub(crate) fn set_rtc(&self, rtc: &'static Rtc) { + critical_section::with(|cs| { + rtc.stop_wakeup_alarm(cs); + + assert!(self.rtc.borrow(cs).replace(Some(rtc)).is_none()) + }); + } + + #[cfg(feature = "low-power")] + /// The minimum pause time beyond which the executor will enter a low-power state. + pub(crate) const MIN_STOP_PAUSE: embassy_time::Duration = embassy_time::Duration::from_millis(250); + + #[cfg(feature = "low-power")] + /// Pause the timer if ready; return err if not + pub(crate) fn pause_time(&self) -> Result<(), ()> { + critical_section::with(|cs| { + /* + If the wakeup timer is currently running, then we need to stop it and + add the elapsed time to the current time, as this will impact the result + of `time_until_next_alarm`. + */ + self.stop_wakeup_alarm(cs); + + let time_until_next_alarm = self.time_until_next_alarm(cs); + if time_until_next_alarm < Self::MIN_STOP_PAUSE { + Err(()) + } else { + self.rtc + .borrow(cs) + .get() + .unwrap() + .start_wakeup_alarm(time_until_next_alarm, cs); + + regs_gp16().cr1().modify(|w| w.set_cen(false)); + + Ok(()) + } + }) + } + + #[cfg(feature = "low-power")] + /// Resume the timer with the given offset + pub(crate) fn resume_time(&self) { + if regs_gp16().cr1().read().cen() { + // Time isn't currently stopped + + return; + } + + critical_section::with(|cs| { + self.stop_wakeup_alarm(cs); + + regs_gp16().cr1().modify(|w| w.set_cen(true)); + }) + } + + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let r = regs_gp16(); + + let n = 0; + self.alarm.borrow(cs).timestamp.set(timestamp); + + let t = self.now(); + if timestamp <= t { + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + r.dier().modify(|w| w.set_ccie(n + 1, false)); + + self.alarm.borrow(cs).timestamp.set(u64::MAX); + + return false; + } + + // Write the CCR value regardless of whether we're going to enable it now or not. + // This way, when we enable it later, the right value is already set. + r.ccr(n + 1).write(|w| w.set_ccr(timestamp as u16)); + + // Enable it if it'll happen soon. Otherwise, `next_period` will enable it. + let diff = timestamp - t; + r.dier().modify(|w| w.set_ccie(n + 1, diff < 0xc000)); + + // Reevaluate if the alarm timestamp is still in the future + let t = self.now(); + if timestamp <= t { + // If alarm timestamp has passed since we set it, we have a race condition and + // the alarm may or may not have fired. + // Disarm the alarm and return `false` to indicate that. + // It is the caller's responsibility to handle this ambiguity. + r.dier().modify(|w| w.set_ccie(n + 1, false)); + + self.alarm.borrow(cs).timestamp.set(u64::MAX); + + return false; + } + + // We're confident the alarm will ring in the future. + true + } +} + +impl Driver for RtcDriver { + fn now(&self) -> u64 { + let r = regs_gp16(); + + let period = self.period.load(Ordering::Relaxed); + compiler_fence(Ordering::Acquire); + let counter = r.cnt().read().cnt(); + calc_now(period, counter) + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} + +#[cfg(feature = "low-power")] +pub(crate) fn get_driver() -> &'static RtcDriver { + &DRIVER +} + +pub(crate) fn init(cs: CriticalSection) { + DRIVER.init(cs) +} diff --git a/embassy/embassy-stm32/src/timer/complementary_pwm.rs b/embassy/embassy-stm32/src/timer/complementary_pwm.rs new file mode 100644 index 0000000..02c01e9 --- /dev/null +++ b/embassy/embassy-stm32/src/timer/complementary_pwm.rs @@ -0,0 +1,321 @@ +//! PWM driver with complementary output support. + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use stm32_metapac::timer::vals::Ckd; + +use super::low_level::{CountingMode, OutputPolarity, Timer}; +use super::simple_pwm::{Ch1, Ch2, Ch3, Ch4, PwmPin}; +use super::{ + AdvancedInstance4Channel, Channel, Channel1ComplementaryPin, Channel2ComplementaryPin, Channel3ComplementaryPin, + Channel4ComplementaryPin, +}; +use crate::gpio::{AnyPin, OutputType}; +use crate::time::Hertz; +use crate::timer::low_level::OutputCompareMode; +use crate::Peripheral; + +/// Complementary PWM pin wrapper. +/// +/// This wraps a pin to make it usable with PWM. +pub struct ComplementaryPwmPin<'d, T, C> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! complementary_channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, T: AdvancedInstance4Channel> ComplementaryPwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance.")] + pub fn $new_chx(pin: impl Peripheral

> + 'd, output_type: OutputType) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af( + pin.af_num(), + crate::gpio::AfType::output(output_type, crate::gpio::Speed::VeryHigh), + ); + }); + ComplementaryPwmPin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + }; +} + +complementary_channel_impl!(new_ch1, Ch1, Channel1ComplementaryPin); +complementary_channel_impl!(new_ch2, Ch2, Channel2ComplementaryPin); +complementary_channel_impl!(new_ch3, Ch3, Channel3ComplementaryPin); +complementary_channel_impl!(new_ch4, Ch4, Channel4ComplementaryPin); + +/// PWM driver with support for standard and complementary outputs. +pub struct ComplementaryPwm<'d, T: AdvancedInstance4Channel> { + inner: Timer<'d, T>, +} + +impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { + /// Create a new complementary PWM driver. + #[allow(clippy::too_many_arguments)] + pub fn new( + tim: impl Peripheral

+ 'd, + _ch1: Option>, + _ch1n: Option>, + _ch2: Option>, + _ch2n: Option>, + _ch3: Option>, + _ch3n: Option>, + _ch4: Option>, + _ch4n: Option>, + freq: Hertz, + counting_mode: CountingMode, + ) -> Self { + Self::new_inner(tim, freq, counting_mode) + } + + fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz, counting_mode: CountingMode) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.set_counting_mode(counting_mode); + this.set_frequency(freq); + this.inner.start(); + + this.inner.enable_outputs(); + + [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] + .iter() + .for_each(|&channel| { + this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1); + this.inner.set_output_compare_preload(channel, true); + }); + + this + } + + /// Enable the given channel. + pub fn enable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, true); + self.inner.enable_complementary_channel(channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self, channel: Channel) { + self.inner.enable_complementary_channel(channel, false); + self.inner.enable_channel(channel, false); + } + + /// Set PWM frequency. + /// + /// Note: when you call this, the max duty value changes, so you will have to + /// call `set_duty` on all channels with the duty calculated based on the new max duty. + pub fn set_frequency(&mut self, freq: Hertz) { + let multiplier = if self.inner.get_counting_mode().is_center_aligned() { + 2u8 + } else { + 1u8 + }; + self.inner.set_frequency_internal(freq * multiplier, 16); + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn get_max_duty(&self) -> u16 { + self.inner.get_max_compare_value() as u16 + 1 + } + + /// Set the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn set_duty(&mut self, channel: Channel, duty: u16) { + assert!(duty <= self.get_max_duty()); + self.inner.set_compare_value(channel, duty as _) + } + + /// Set the output polarity for a given channel. + pub fn set_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { + self.inner.set_output_polarity(channel, polarity); + self.inner.set_complementary_output_polarity(channel, polarity); + } + + /// Set the dead time as a proportion of max_duty + pub fn set_dead_time(&mut self, value: u16) { + let (ckd, value) = compute_dead_time_value(value); + + self.inner.set_dead_time_clock_division(ckd); + self.inner.set_dead_time_value(value); + } +} + +impl<'d, T: AdvancedInstance4Channel> embedded_hal_02::Pwm for ComplementaryPwm<'d, T> { + type Channel = Channel; + type Time = Hertz; + type Duty = u16; + + fn disable(&mut self, channel: Self::Channel) { + self.inner.enable_complementary_channel(channel, false); + self.inner.enable_channel(channel, false); + } + + fn enable(&mut self, channel: Self::Channel) { + self.inner.enable_channel(channel, true); + self.inner.enable_complementary_channel(channel, true); + } + + fn get_period(&self) -> Self::Time { + self.inner.get_frequency() + } + + fn get_duty(&self, channel: Self::Channel) -> Self::Duty { + self.inner.get_compare_value(channel) as u16 + } + + fn get_max_duty(&self) -> Self::Duty { + self.inner.get_max_compare_value() as u16 + 1 + } + + fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { + assert!(duty <= self.get_max_duty()); + self.inner.set_compare_value(channel, duty as u32) + } + + fn set_period

(&mut self, period: P) + where + P: Into, + { + self.inner.set_frequency(period.into()); + } +} + +fn compute_dead_time_value(value: u16) -> (Ckd, u8) { + /* + Dead-time = T_clk * T_dts * T_dtg + + T_dts: + This bit-field indicates the division ratio between the timer clock (CK_INT) frequency and the + dead-time and sampling clock (tDTS)used by the dead-time generators and the digital filters + (ETR, TIx), + 00: tDTS=tCK_INT + 01: tDTS=2*tCK_INT + 10: tDTS=4*tCK_INT + + T_dtg: + This bit-field defines the duration of the dead-time inserted between the complementary + outputs. DT correspond to this duration. + DTG[7:5]=0xx => DT=DTG[7:0]x tdtg with tdtg=tDTS. + DTG[7:5]=10x => DT=(64+DTG[5:0])xtdtg with Tdtg=2xtDTS. + DTG[7:5]=110 => DT=(32+DTG[4:0])xtdtg with Tdtg=8xtDTS. + DTG[7:5]=111 => DT=(32+DTG[4:0])xtdtg with Tdtg=16xtDTS. + Example if TDTS=125ns (8MHz), dead-time possible values are: + 0 to 15875 ns by 125 ns steps, + 16 us to 31750 ns by 250 ns steps, + 32 us to 63us by 1 us steps, + 64 us to 126 us by 2 us steps + */ + + let mut error = u16::MAX; + let mut ckd = Ckd::DIV1; + let mut bits = 0u8; + + for this_ckd in [Ckd::DIV1, Ckd::DIV2, Ckd::DIV4] { + let outdiv = match this_ckd { + Ckd::DIV1 => 1, + Ckd::DIV2 => 2, + Ckd::DIV4 => 4, + _ => unreachable!(), + }; + + // 127 + // 128 + // .. + // 254 + // 256 + // .. + // 504 + // 512 + // .. + // 1008 + + let target = value / outdiv; + let (these_bits, result) = if target < 128 { + (target as u8, target) + } else if target < 255 { + (64 + (target / 2) as u8, (target - target % 2)) + } else if target < 508 { + (32 + (target / 8) as u8, (target - target % 8)) + } else if target < 1008 { + (32 + (target / 16) as u8, (target - target % 16)) + } else { + (u8::MAX, 1008) + }; + + let this_error = value.abs_diff(result * outdiv); + if error > this_error { + ckd = this_ckd; + bits = these_bits; + error = this_error; + } + + if error == 0 { + break; + } + } + + (ckd, bits) +} + +#[cfg(test)] +mod tests { + use super::{compute_dead_time_value, Ckd}; + + #[test] + fn test_compute_dead_time_value() { + struct TestRun { + value: u16, + ckd: Ckd, + bits: u8, + } + + let fn_results = [ + TestRun { + value: 1, + ckd: Ckd::DIV1, + bits: 1, + }, + TestRun { + value: 125, + ckd: Ckd::DIV1, + bits: 125, + }, + TestRun { + value: 245, + ckd: Ckd::DIV1, + bits: 64 + 245 / 2, + }, + TestRun { + value: 255, + ckd: Ckd::DIV2, + bits: 127, + }, + TestRun { + value: 400, + ckd: Ckd::DIV1, + bits: 32 + (400u16 / 8) as u8, + }, + TestRun { + value: 600, + ckd: Ckd::DIV4, + bits: 64 + (600u16 / 8) as u8, + }, + ]; + + for test_run in fn_results { + let (ckd, bits) = compute_dead_time_value(test_run.value); + + assert_eq!(ckd.to_bits(), test_run.ckd.to_bits()); + assert_eq!(bits, test_run.bits); + } + } +} diff --git a/embassy/embassy-stm32/src/timer/input_capture.rs b/embassy/embassy-stm32/src/timer/input_capture.rs new file mode 100644 index 0000000..341ac2c --- /dev/null +++ b/embassy/embassy-stm32/src/timer/input_capture.rs @@ -0,0 +1,214 @@ +//! Input capture driver. + +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::low_level::{CountingMode, FilterValue, InputCaptureMode, InputTISelection, Timer}; +use super::{ + CaptureCompareInterruptHandler, Channel, Channel1Pin, Channel2Pin, Channel3Pin, Channel4Pin, + GeneralInstance4Channel, +}; +use crate::gpio::{AfType, AnyPin, Pull}; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::time::Hertz; +use crate::Peripheral; + +/// Channel 1 marker type. +pub enum Ch1 {} +/// Channel 2 marker type. +pub enum Ch2 {} +/// Channel 3 marker type. +pub enum Ch3 {} +/// Channel 4 marker type. +pub enum Ch4 {} + +/// Capture pin wrapper. +/// +/// This wraps a pin to make it usable with capture. +pub struct CapturePin<'d, T, C> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, T: GeneralInstance4Channel> CapturePin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " capture pin instance.")] + pub fn $new_chx(pin: impl Peripheral

> + 'd, pull: Pull) -> Self { + into_ref!(pin); + pin.set_as_af(pin.af_num(), AfType::input(pull)); + CapturePin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + }; +} + +channel_impl!(new_ch1, Ch1, Channel1Pin); +channel_impl!(new_ch2, Ch2, Channel2Pin); +channel_impl!(new_ch3, Ch3, Channel3Pin); +channel_impl!(new_ch4, Ch4, Channel4Pin); + +/// Input capture driver. +pub struct InputCapture<'d, T: GeneralInstance4Channel> { + inner: Timer<'d, T>, +} + +impl<'d, T: GeneralInstance4Channel> InputCapture<'d, T> { + /// Create a new input capture driver. + pub fn new( + tim: impl Peripheral

+ 'd, + _ch1: Option>, + _ch2: Option>, + _ch3: Option>, + _ch4: Option>, + _irq: impl Binding> + 'd, + freq: Hertz, + counting_mode: CountingMode, + ) -> Self { + Self::new_inner(tim, freq, counting_mode) + } + + fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz, counting_mode: CountingMode) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.set_counting_mode(counting_mode); + this.inner.set_tick_freq(freq); + this.inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details + this.inner.start(); + + // enable NVIC interrupt + T::CaptureCompareInterrupt::unpend(); + unsafe { T::CaptureCompareInterrupt::enable() }; + + this + } + + /// Enable the given channel. + pub fn enable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self, channel: Channel) -> bool { + self.inner.get_channel_enable_state(channel) + } + + /// Set the input capture mode for a given channel. + pub fn set_input_capture_mode(&mut self, channel: Channel, mode: InputCaptureMode) { + self.inner.set_input_capture_mode(channel, mode); + } + + /// Set input TI selection. + pub fn set_input_ti_selection(&mut self, channel: Channel, tisel: InputTISelection) { + self.inner.set_input_ti_selection(channel, tisel) + } + + /// Get capture value for a channel. + pub fn get_capture_value(&self, channel: Channel) -> u32 { + self.inner.get_capture_value(channel) + } + + /// Get input interrupt. + pub fn get_input_interrupt(&self, channel: Channel) -> bool { + self.inner.get_input_interrupt(channel) + } + + fn new_future(&self, channel: Channel, mode: InputCaptureMode, tisel: InputTISelection) -> InputCaptureFuture { + // Configuration steps from ST RM0390 (STM32F446) chapter 17.3.5 + // or ST RM0008 (STM32F103) chapter 15.3.5 Input capture mode + self.inner.set_input_ti_selection(channel, tisel); + self.inner.set_input_capture_filter(channel, FilterValue::NOFILTER); + self.inner.set_input_capture_mode(channel, mode); + self.inner.set_input_capture_prescaler(channel, 0); + self.inner.enable_channel(channel, true); + self.inner.enable_input_interrupt(channel, true); + + InputCaptureFuture { + channel, + phantom: PhantomData, + } + } + + /// Asynchronously wait until the pin sees a rising edge. + pub async fn wait_for_rising_edge(&mut self, channel: Channel) -> u32 { + self.new_future(channel, InputCaptureMode::Rising, InputTISelection::Normal) + .await + } + + /// Asynchronously wait until the pin sees a falling edge. + pub async fn wait_for_falling_edge(&mut self, channel: Channel) -> u32 { + self.new_future(channel, InputCaptureMode::Falling, InputTISelection::Normal) + .await + } + + /// Asynchronously wait until the pin sees any edge. + pub async fn wait_for_any_edge(&mut self, channel: Channel) -> u32 { + self.new_future(channel, InputCaptureMode::BothEdges, InputTISelection::Normal) + .await + } + + /// Asynchronously wait until the (alternate) pin sees a rising edge. + pub async fn wait_for_rising_edge_alternate(&mut self, channel: Channel) -> u32 { + self.new_future(channel, InputCaptureMode::Rising, InputTISelection::Alternate) + .await + } + + /// Asynchronously wait until the (alternate) pin sees a falling edge. + pub async fn wait_for_falling_edge_alternate(&mut self, channel: Channel) -> u32 { + self.new_future(channel, InputCaptureMode::Falling, InputTISelection::Alternate) + .await + } + + /// Asynchronously wait until the (alternate) pin sees any edge. + pub async fn wait_for_any_edge_alternate(&mut self, channel: Channel) -> u32 { + self.new_future(channel, InputCaptureMode::BothEdges, InputTISelection::Alternate) + .await + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct InputCaptureFuture { + channel: Channel, + phantom: PhantomData, +} + +impl Drop for InputCaptureFuture { + fn drop(&mut self) { + critical_section::with(|_| { + let regs = unsafe { crate::pac::timer::TimGp16::from_ptr(T::regs()) }; + + // disable interrupt enable + regs.dier().modify(|w| w.set_ccie(self.channel.index(), false)); + }); + } +} + +impl Future for InputCaptureFuture { + type Output = u32; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + T::state().cc_waker[self.channel.index()].register(cx.waker()); + + let regs = unsafe { crate::pac::timer::TimGp16::from_ptr(T::regs()) }; + + let dier = regs.dier().read(); + if !dier.ccie(self.channel.index()) { + let val = regs.ccr(self.channel.index()).read().0; + Poll::Ready(val) + } else { + Poll::Pending + } + } +} diff --git a/embassy/embassy-stm32/src/timer/low_level.rs b/embassy/embassy-stm32/src/timer/low_level.rs new file mode 100644 index 0000000..7360d6a --- /dev/null +++ b/embassy/embassy-stm32/src/timer/low_level.rs @@ -0,0 +1,695 @@ +//! Low-level timer driver. +//! +//! This is an unopinionated, very low-level driver for all STM32 timers. It allows direct register +//! manipulation with the `regs_*()` methods, and has utility functions that are thin wrappers +//! over the registers. +//! +//! The available functionality depends on the timer type. + +use core::mem::ManuallyDrop; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +// Re-export useful enums +pub use stm32_metapac::timer::vals::{FilterValue, Sms as SlaveMode, Ts as TriggerSource}; + +use super::*; +use crate::pac::timer::vals; +use crate::rcc; +use crate::time::Hertz; + +/// Input capture mode. +#[derive(Clone, Copy)] +pub enum InputCaptureMode { + /// Rising edge only. + Rising, + /// Falling edge only. + Falling, + /// Both rising or falling edges. + BothEdges, +} + +/// Input TI selection. +#[derive(Clone, Copy)] +pub enum InputTISelection { + /// Normal + Normal, + /// Alternate + Alternate, + /// TRC + TRC, +} + +impl From for stm32_metapac::timer::vals::CcmrInputCcs { + fn from(tisel: InputTISelection) -> Self { + match tisel { + InputTISelection::Normal => stm32_metapac::timer::vals::CcmrInputCcs::TI4, + InputTISelection::Alternate => stm32_metapac::timer::vals::CcmrInputCcs::TI3, + InputTISelection::TRC => stm32_metapac::timer::vals::CcmrInputCcs::TRC, + } + } +} + +/// Timer counting mode. +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum CountingMode { + #[default] + /// The timer counts up to the reload value and then resets back to 0. + EdgeAlignedUp, + /// The timer counts down to 0 and then resets back to the reload value. + EdgeAlignedDown, + /// The timer counts up to the reload value and then counts back to 0. + /// + /// The output compare interrupt flags of channels configured in output are + /// set when the counter is counting down. + CenterAlignedDownInterrupts, + /// The timer counts up to the reload value and then counts back to 0. + /// + /// The output compare interrupt flags of channels configured in output are + /// set when the counter is counting up. + CenterAlignedUpInterrupts, + /// The timer counts up to the reload value and then counts back to 0. + /// + /// The output compare interrupt flags of channels configured in output are + /// set when the counter is counting both up or down. + CenterAlignedBothInterrupts, +} + +impl CountingMode { + /// Return whether this mode is edge-aligned (up or down). + pub fn is_edge_aligned(&self) -> bool { + matches!(self, CountingMode::EdgeAlignedUp | CountingMode::EdgeAlignedDown) + } + + /// Return whether this mode is center-aligned. + pub fn is_center_aligned(&self) -> bool { + matches!( + self, + CountingMode::CenterAlignedDownInterrupts + | CountingMode::CenterAlignedUpInterrupts + | CountingMode::CenterAlignedBothInterrupts + ) + } +} + +impl From for (vals::Cms, vals::Dir) { + fn from(value: CountingMode) -> Self { + match value { + CountingMode::EdgeAlignedUp => (vals::Cms::EDGEALIGNED, vals::Dir::UP), + CountingMode::EdgeAlignedDown => (vals::Cms::EDGEALIGNED, vals::Dir::DOWN), + CountingMode::CenterAlignedDownInterrupts => (vals::Cms::CENTERALIGNED1, vals::Dir::UP), + CountingMode::CenterAlignedUpInterrupts => (vals::Cms::CENTERALIGNED2, vals::Dir::UP), + CountingMode::CenterAlignedBothInterrupts => (vals::Cms::CENTERALIGNED3, vals::Dir::UP), + } + } +} + +impl From<(vals::Cms, vals::Dir)> for CountingMode { + fn from(value: (vals::Cms, vals::Dir)) -> Self { + match value { + (vals::Cms::EDGEALIGNED, vals::Dir::UP) => CountingMode::EdgeAlignedUp, + (vals::Cms::EDGEALIGNED, vals::Dir::DOWN) => CountingMode::EdgeAlignedDown, + (vals::Cms::CENTERALIGNED1, _) => CountingMode::CenterAlignedDownInterrupts, + (vals::Cms::CENTERALIGNED2, _) => CountingMode::CenterAlignedUpInterrupts, + (vals::Cms::CENTERALIGNED3, _) => CountingMode::CenterAlignedBothInterrupts, + } + } +} + +/// Output compare mode. +#[derive(Clone, Copy)] +pub enum OutputCompareMode { + /// The comparison between the output compare register TIMx_CCRx and + /// the counter TIMx_CNT has no effect on the outputs. + /// (this mode is used to generate a timing base). + Frozen, + /// Set channel to active level on match. OCxREF signal is forced high when the + /// counter TIMx_CNT matches the capture/compare register x (TIMx_CCRx). + ActiveOnMatch, + /// Set channel to inactive level on match. OCxREF signal is forced low when the + /// counter TIMx_CNT matches the capture/compare register x (TIMx_CCRx). + InactiveOnMatch, + /// Toggle - OCxREF toggles when TIMx_CNT=TIMx_CCRx. + Toggle, + /// Force inactive level - OCxREF is forced low. + ForceInactive, + /// Force active level - OCxREF is forced high. + ForceActive, + /// PWM mode 1 - In upcounting, channel is active as long as TIMx_CNTTIMx_CCRx else active (OCxREF=1). + PwmMode1, + /// PWM mode 2 - In upcounting, channel is inactive as long as + /// TIMx_CNTTIMx_CCRx else inactive. + PwmMode2, + // TODO: there's more modes here depending on the chip family. +} + +impl From for stm32_metapac::timer::vals::Ocm { + fn from(mode: OutputCompareMode) -> Self { + match mode { + OutputCompareMode::Frozen => stm32_metapac::timer::vals::Ocm::FROZEN, + OutputCompareMode::ActiveOnMatch => stm32_metapac::timer::vals::Ocm::ACTIVEONMATCH, + OutputCompareMode::InactiveOnMatch => stm32_metapac::timer::vals::Ocm::INACTIVEONMATCH, + OutputCompareMode::Toggle => stm32_metapac::timer::vals::Ocm::TOGGLE, + OutputCompareMode::ForceInactive => stm32_metapac::timer::vals::Ocm::FORCEINACTIVE, + OutputCompareMode::ForceActive => stm32_metapac::timer::vals::Ocm::FORCEACTIVE, + OutputCompareMode::PwmMode1 => stm32_metapac::timer::vals::Ocm::PWMMODE1, + OutputCompareMode::PwmMode2 => stm32_metapac::timer::vals::Ocm::PWMMODE2, + } + } +} + +/// Timer output pin polarity. +#[derive(Clone, Copy)] +pub enum OutputPolarity { + /// Active high (higher duty value makes the pin spend more time high). + ActiveHigh, + /// Active low (higher duty value makes the pin spend more time low). + ActiveLow, +} + +impl From for bool { + fn from(mode: OutputPolarity) -> Self { + match mode { + OutputPolarity::ActiveHigh => false, + OutputPolarity::ActiveLow => true, + } + } +} + +/// Low-level timer driver. +pub struct Timer<'d, T: CoreInstance> { + tim: PeripheralRef<'d, T>, +} + +impl<'d, T: CoreInstance> Drop for Timer<'d, T> { + fn drop(&mut self) { + rcc::disable::(); + } +} + +impl<'d, T: CoreInstance> Timer<'d, T> { + /// Create a new timer driver. + pub fn new(tim: impl Peripheral

+ 'd) -> Self { + into_ref!(tim); + + rcc::enable_and_reset::(); + + Self { tim } + } + + pub(crate) unsafe fn clone_unchecked(&self) -> ManuallyDrop { + let tim = unsafe { self.tim.clone_unchecked() }; + ManuallyDrop::new(Self { tim }) + } + + /// Get access to the virutal core 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + pub fn regs_core(&self) -> crate::pac::timer::TimCore { + unsafe { crate::pac::timer::TimCore::from_ptr(T::regs()) } + } + + #[cfg(not(stm32l0))] + fn regs_gp32_unchecked(&self) -> crate::pac::timer::TimGp32 { + unsafe { crate::pac::timer::TimGp32::from_ptr(T::regs()) } + } + + /// Start the timer. + pub fn start(&self) { + self.regs_core().cr1().modify(|r| r.set_cen(true)); + } + + /// Stop the timer. + pub fn stop(&self) { + self.regs_core().cr1().modify(|r| r.set_cen(false)); + } + + /// Reset the counter value to 0 + pub fn reset(&self) { + self.regs_core().cnt().write(|r| r.set_cnt(0)); + } + + /// Set the frequency of how many times per second the timer counts up to the max value or down to 0. + /// + /// This means that in the default edge-aligned mode, + /// the timer counter will wrap around at the same frequency as is being set. + /// In center-aligned mode (which not all timers support), the wrap-around frequency is effectively halved + /// because it needs to count up and down. + pub fn set_frequency(&self, frequency: Hertz) { + match T::BITS { + TimerBits::Bits16 => { + self.set_frequency_internal(frequency, 16); + } + #[cfg(not(stm32l0))] + TimerBits::Bits32 => { + self.set_frequency_internal(frequency, 32); + } + } + } + + pub(crate) fn set_frequency_internal(&self, frequency: Hertz, max_divide_by_bits: u8) { + let f = frequency.0; + assert!(f > 0); + let timer_f = T::frequency().0; + + let pclk_ticks_per_timer_period = (timer_f / f) as u64; + let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << max_divide_by_bits)).try_into()); + let divide_by = pclk_ticks_per_timer_period / (u64::from(psc) + 1); + + match T::BITS { + TimerBits::Bits16 => { + // the timer counts `0..=arr`, we want it to count `0..divide_by` + let arr = unwrap!(u16::try_from(divide_by - 1)); + + let regs = self.regs_core(); + regs.psc().write_value(psc); + regs.arr().write(|r| r.set_arr(arr)); + + regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTERONLY)); + regs.egr().write(|r| r.set_ug(true)); + regs.cr1().modify(|r| r.set_urs(vals::Urs::ANYEVENT)); + } + #[cfg(not(stm32l0))] + TimerBits::Bits32 => { + // the timer counts `0..=arr`, we want it to count `0..divide_by` + let arr: u32 = unwrap!(u32::try_from(divide_by - 1)); + + let regs = self.regs_gp32_unchecked(); + regs.psc().write_value(psc); + regs.arr().write_value(arr); + + regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTERONLY)); + regs.egr().write(|r| r.set_ug(true)); + regs.cr1().modify(|r| r.set_urs(vals::Urs::ANYEVENT)); + } + } + } + + /// Set tick frequency. + pub fn set_tick_freq(&mut self, freq: Hertz) { + let f = freq; + assert!(f.0 > 0); + let timer_f = self.get_clock_frequency(); + + let pclk_ticks_per_timer_period = timer_f / f; + let psc: u16 = unwrap!((pclk_ticks_per_timer_period - 1).try_into()); + + let regs = self.regs_core(); + regs.psc().write_value(psc); + + // Generate an Update Request + regs.egr().write(|r| r.set_ug(true)); + } + + /// Clear update interrupt. + /// + /// Returns whether the update interrupt flag was set. + pub fn clear_update_interrupt(&self) -> bool { + let regs = self.regs_core(); + let sr = regs.sr().read(); + if sr.uif() { + regs.sr().modify(|r| { + r.set_uif(false); + }); + true + } else { + false + } + } + + /// Enable/disable the update interrupt. + pub fn enable_update_interrupt(&self, enable: bool) { + self.regs_core().dier().modify(|r| r.set_uie(enable)); + } + + /// Enable/disable autoreload preload. + pub fn set_autoreload_preload(&self, enable: bool) { + self.regs_core().cr1().modify(|r| r.set_arpe(enable)); + } + + /// Get the timer frequency. + pub fn get_frequency(&self) -> Hertz { + let timer_f = T::frequency(); + + match T::BITS { + TimerBits::Bits16 => { + let regs = self.regs_core(); + let arr = regs.arr().read().arr(); + let psc = regs.psc().read(); + + timer_f / arr / (psc + 1) + } + #[cfg(not(stm32l0))] + TimerBits::Bits32 => { + let regs = self.regs_gp32_unchecked(); + let arr = regs.arr().read(); + let psc = regs.psc().read(); + + timer_f / arr / (psc + 1) + } + } + } + + /// Get the clock frequency of the timer (before prescaler is applied). + pub fn get_clock_frequency(&self) -> Hertz { + T::frequency() + } +} + +impl<'d, T: BasicNoCr2Instance> Timer<'d, T> { + /// Get access to the Baisc 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + pub fn regs_basic_no_cr2(&self) -> crate::pac::timer::TimBasicNoCr2 { + unsafe { crate::pac::timer::TimBasicNoCr2::from_ptr(T::regs()) } + } + + /// Enable/disable the update dma. + pub fn enable_update_dma(&self, enable: bool) { + self.regs_basic_no_cr2().dier().modify(|r| r.set_ude(enable)); + } + + /// Get the update dma enable/disable state. + pub fn get_update_dma_state(&self) -> bool { + self.regs_basic_no_cr2().dier().read().ude() + } +} + +impl<'d, T: BasicInstance> Timer<'d, T> { + /// Get access to the Baisc 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + pub fn regs_basic(&self) -> crate::pac::timer::TimBasic { + unsafe { crate::pac::timer::TimBasic::from_ptr(T::regs()) } + } +} + +impl<'d, T: GeneralInstance1Channel> Timer<'d, T> { + /// Get access to the general purpose 1 channel 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + pub fn regs_1ch(&self) -> crate::pac::timer::Tim1ch { + unsafe { crate::pac::timer::Tim1ch::from_ptr(T::regs()) } + } + + /// Set clock divider. + pub fn set_clock_division(&self, ckd: vals::Ckd) { + self.regs_1ch().cr1().modify(|r| r.set_ckd(ckd)); + } + + /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. + pub fn get_max_compare_value(&self) -> u32 { + match T::BITS { + TimerBits::Bits16 => self.regs_1ch().arr().read().arr() as u32, + #[cfg(not(stm32l0))] + TimerBits::Bits32 => self.regs_gp32_unchecked().arr().read(), + } + } +} + +impl<'d, T: GeneralInstance2Channel> Timer<'d, T> { + /// Get access to the general purpose 2 channel 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + pub fn regs_2ch(&self) -> crate::pac::timer::Tim2ch { + unsafe { crate::pac::timer::Tim2ch::from_ptr(T::regs()) } + } +} + +impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { + /// Get access to the general purpose 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + pub fn regs_gp16(&self) -> crate::pac::timer::TimGp16 { + unsafe { crate::pac::timer::TimGp16::from_ptr(T::regs()) } + } + + /// Enable timer outputs. + pub fn enable_outputs(&self) { + self.tim.enable_outputs() + } + + /// Set counting mode. + pub fn set_counting_mode(&self, mode: CountingMode) { + let (cms, dir) = mode.into(); + + let timer_enabled = self.regs_core().cr1().read().cen(); + // Changing from edge aligned to center aligned (and vice versa) is not allowed while the timer is running. + // Changing direction is discouraged while the timer is running. + assert!(!timer_enabled); + + self.regs_gp16().cr1().modify(|r| r.set_dir(dir)); + self.regs_gp16().cr1().modify(|r| r.set_cms(cms)) + } + + /// Get counting mode. + pub fn get_counting_mode(&self) -> CountingMode { + let cr1 = self.regs_gp16().cr1().read(); + (cr1.cms(), cr1.dir()).into() + } + + /// Set input capture filter. + pub fn set_input_capture_filter(&self, channel: Channel, icf: vals::FilterValue) { + let raw_channel = channel.index(); + self.regs_gp16() + .ccmr_input(raw_channel / 2) + .modify(|r| r.set_icf(raw_channel % 2, icf)); + } + + /// Clear input interrupt. + pub fn clear_input_interrupt(&self, channel: Channel) { + self.regs_gp16().sr().modify(|r| r.set_ccif(channel.index(), false)); + } + + /// Get input interrupt. + pub fn get_input_interrupt(&self, channel: Channel) -> bool { + self.regs_gp16().sr().read().ccif(channel.index()) + } + + /// Enable input interrupt. + pub fn enable_input_interrupt(&self, channel: Channel, enable: bool) { + self.regs_gp16().dier().modify(|r| r.set_ccie(channel.index(), enable)); + } + + /// Set input capture prescaler. + pub fn set_input_capture_prescaler(&self, channel: Channel, factor: u8) { + let raw_channel = channel.index(); + self.regs_gp16() + .ccmr_input(raw_channel / 2) + .modify(|r| r.set_icpsc(raw_channel % 2, factor)); + } + + /// Set input TI selection. + pub fn set_input_ti_selection(&self, channel: Channel, tisel: InputTISelection) { + let raw_channel = channel.index(); + self.regs_gp16() + .ccmr_input(raw_channel / 2) + .modify(|r| r.set_ccs(raw_channel % 2, tisel.into())); + } + + /// Set input capture mode. + pub fn set_input_capture_mode(&self, channel: Channel, mode: InputCaptureMode) { + self.regs_gp16().ccer().modify(|r| match mode { + InputCaptureMode::Rising => { + r.set_ccnp(channel.index(), false); + r.set_ccp(channel.index(), false); + } + InputCaptureMode::Falling => { + r.set_ccnp(channel.index(), false); + r.set_ccp(channel.index(), true); + } + InputCaptureMode::BothEdges => { + r.set_ccnp(channel.index(), true); + r.set_ccp(channel.index(), true); + } + }); + } + + /// Set output compare mode. + pub fn set_output_compare_mode(&self, channel: Channel, mode: OutputCompareMode) { + let raw_channel: usize = channel.index(); + self.regs_gp16() + .ccmr_output(raw_channel / 2) + .modify(|w| w.set_ocm(raw_channel % 2, mode.into())); + } + + /// Set output polarity. + pub fn set_output_polarity(&self, channel: Channel, polarity: OutputPolarity) { + self.regs_gp16() + .ccer() + .modify(|w| w.set_ccp(channel.index(), polarity.into())); + } + + /// Enable/disable a channel. + pub fn enable_channel(&self, channel: Channel, enable: bool) { + self.regs_gp16().ccer().modify(|w| w.set_cce(channel.index(), enable)); + } + + /// Get enable/disable state of a channel + pub fn get_channel_enable_state(&self, channel: Channel) -> bool { + self.regs_gp16().ccer().read().cce(channel.index()) + } + + /// Set compare value for a channel. + pub fn set_compare_value(&self, channel: Channel, value: u32) { + match T::BITS { + TimerBits::Bits16 => { + let value = unwrap!(u16::try_from(value)); + self.regs_gp16().ccr(channel.index()).modify(|w| w.set_ccr(value)); + } + #[cfg(not(stm32l0))] + TimerBits::Bits32 => { + self.regs_gp32_unchecked().ccr(channel.index()).write_value(value); + } + } + } + + /// Get compare value for a channel. + pub fn get_compare_value(&self, channel: Channel) -> u32 { + match T::BITS { + TimerBits::Bits16 => self.regs_gp16().ccr(channel.index()).read().ccr() as u32, + #[cfg(not(stm32l0))] + TimerBits::Bits32 => self.regs_gp32_unchecked().ccr(channel.index()).read(), + } + } + + /// Get capture value for a channel. + pub fn get_capture_value(&self, channel: Channel) -> u32 { + self.get_compare_value(channel) + } + + /// Set output compare preload. + pub fn set_output_compare_preload(&self, channel: Channel, preload: bool) { + let channel_index = channel.index(); + self.regs_gp16() + .ccmr_output(channel_index / 2) + .modify(|w| w.set_ocpe(channel_index % 2, preload)); + } + + /// Get capture compare DMA selection + pub fn get_cc_dma_selection(&self) -> vals::Ccds { + self.regs_gp16().cr2().read().ccds() + } + + /// Set capture compare DMA selection + pub fn set_cc_dma_selection(&self, ccds: vals::Ccds) { + self.regs_gp16().cr2().modify(|w| w.set_ccds(ccds)) + } + + /// Get capture compare DMA enable state + pub fn get_cc_dma_enable_state(&self, channel: Channel) -> bool { + self.regs_gp16().dier().read().ccde(channel.index()) + } + + /// Set capture compare DMA enable state + pub fn set_cc_dma_enable_state(&self, channel: Channel, ccde: bool) { + self.regs_gp16().dier().modify(|w| w.set_ccde(channel.index(), ccde)) + } + + /// Set Timer Slave Mode + pub fn set_slave_mode(&self, sms: SlaveMode) { + self.regs_gp16().smcr().modify(|r| r.set_sms(sms)); + } + + /// Set Timer Trigger Source + pub fn set_trigger_source(&self, ts: TriggerSource) { + self.regs_gp16().smcr().modify(|r| r.set_ts(ts)); + } +} + +#[cfg(not(stm32l0))] +impl<'d, T: GeneralInstance32bit4Channel> Timer<'d, T> { + /// Get access to the general purpose 32bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + pub fn regs_gp32(&self) -> crate::pac::timer::TimGp32 { + unsafe { crate::pac::timer::TimGp32::from_ptr(T::regs()) } + } +} + +#[cfg(not(stm32l0))] +impl<'d, T: AdvancedInstance1Channel> Timer<'d, T> { + /// Get access to the general purpose 1 channel with one complementary 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + pub fn regs_1ch_cmp(&self) -> crate::pac::timer::Tim1chCmp { + unsafe { crate::pac::timer::Tim1chCmp::from_ptr(T::regs()) } + } + + /// Set clock divider for the dead time. + pub fn set_dead_time_clock_division(&self, value: vals::Ckd) { + self.regs_1ch_cmp().cr1().modify(|w| w.set_ckd(value)); + } + + /// Set dead time, as a fraction of the max duty value. + pub fn set_dead_time_value(&self, value: u8) { + self.regs_1ch_cmp().bdtr().modify(|w| w.set_dtg(value)); + } + + /// Set state of MOE-bit in BDTR register to en-/disable output + pub fn set_moe(&self, enable: bool) { + self.regs_1ch_cmp().bdtr().modify(|w| w.set_moe(enable)); + } +} + +#[cfg(not(stm32l0))] +impl<'d, T: AdvancedInstance2Channel> Timer<'d, T> { + /// Get access to the general purpose 2 channel with one complementary 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + pub fn regs_2ch_cmp(&self) -> crate::pac::timer::Tim2chCmp { + unsafe { crate::pac::timer::Tim2chCmp::from_ptr(T::regs()) } + } +} + +#[cfg(not(stm32l0))] +impl<'d, T: AdvancedInstance4Channel> Timer<'d, T> { + /// Get access to the advanced timer registers. + pub fn regs_advanced(&self) -> crate::pac::timer::TimAdv { + unsafe { crate::pac::timer::TimAdv::from_ptr(T::regs()) } + } + + /// Set complementary output polarity. + pub fn set_complementary_output_polarity(&self, channel: Channel, polarity: OutputPolarity) { + self.regs_advanced() + .ccer() + .modify(|w| w.set_ccnp(channel.index(), polarity.into())); + } + + /// Enable/disable a complementary channel. + pub fn enable_complementary_channel(&self, channel: Channel, enable: bool) { + self.regs_advanced() + .ccer() + .modify(|w| w.set_ccne(channel.index(), enable)); + } +} diff --git a/embassy/embassy-stm32/src/timer/mod.rs b/embassy/embassy-stm32/src/timer/mod.rs new file mode 100644 index 0000000..97740c2 --- /dev/null +++ b/embassy/embassy-stm32/src/timer/mod.rs @@ -0,0 +1,381 @@ +//! Timers, PWM, quadrature decoder. + +use core::marker::PhantomData; + +use embassy_hal_internal::Peripheral; +use embassy_sync::waitqueue::AtomicWaker; + +#[cfg(not(stm32l0))] +pub mod complementary_pwm; +pub mod input_capture; +pub mod low_level; +pub mod pwm_input; +pub mod qei; +pub mod simple_pwm; + +use crate::interrupt; +use crate::rcc::RccPeripheral; + +/// Timer channel. +#[derive(Clone, Copy)] +pub enum Channel { + /// Channel 1. + Ch1, + /// Channel 2. + Ch2, + /// Channel 3. + Ch3, + /// Channel 4. + Ch4, +} + +impl Channel { + /// Get the channel index (0..3) + pub fn index(&self) -> usize { + match self { + Channel::Ch1 => 0, + Channel::Ch2 => 1, + Channel::Ch3 => 2, + Channel::Ch4 => 3, + } + } +} + +/// Amount of bits of a timer. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TimerBits { + /// 16 bits. + Bits16, + /// 32 bits. + #[cfg(not(stm32l0))] + Bits32, +} + +struct State { + up_waker: AtomicWaker, + cc_waker: [AtomicWaker; 4], +} + +impl State { + const fn new() -> Self { + Self { + up_waker: AtomicWaker::new(), + cc_waker: [const { AtomicWaker::new() }; 4], + } + } +} + +trait SealedInstance: RccPeripheral + Peripheral

{ + /// Async state for this timer + fn state() -> &'static State; +} + +/// Core timer instance. +#[allow(private_bounds)] +pub trait CoreInstance: SealedInstance + 'static { + /// Update Interrupt for this timer. + type UpdateInterrupt: interrupt::typelevel::Interrupt; + + /// Amount of bits this timer has. + const BITS: TimerBits; + + /// Registers for this timer. + /// + /// This is a raw pointer to the register block. The actual register block layout varies depending on the timer type. + fn regs() -> *mut (); +} +/// Cut-down basic timer instance. +pub trait BasicNoCr2Instance: CoreInstance {} +/// Basic timer instance. +pub trait BasicInstance: BasicNoCr2Instance {} + +/// General-purpose 16-bit timer with 1 channel instance. +pub trait GeneralInstance1Channel: CoreInstance { + /// Capture compare interrupt for this timer. + type CaptureCompareInterrupt: interrupt::typelevel::Interrupt; +} + +/// General-purpose 16-bit timer with 2 channels instance. +pub trait GeneralInstance2Channel: GeneralInstance1Channel { + /// Trigger event interrupt for this timer. + type TriggerInterrupt: interrupt::typelevel::Interrupt; +} + +// This trait add *extra* methods to GeneralInstance4Channel, +// that GeneralInstance4Channel doesn't use, but the "AdvancedInstance"s need. +// And it's a private trait, so it's content won't leak to outer namespace. +// +// If you want to add a new method to it, please leave a detail comment to explain it. +trait General4ChBlankSealed { + // SimplePwm<'d, T> is implemented for T: GeneralInstance4Channel + // Advanced timers implement this trait, but the output needs to be + // enabled explicitly. + // To support general-purpose and advanced timers, this function is added + // here defaulting to noop and overwritten for advanced timers. + // + // Enable timer outputs. + fn enable_outputs(&self) {} +} + +/// General-purpose 16-bit timer with 4 channels instance. +#[allow(private_bounds)] +pub trait GeneralInstance4Channel: BasicInstance + GeneralInstance2Channel + General4ChBlankSealed {} + +/// General-purpose 32-bit timer with 4 channels instance. +pub trait GeneralInstance32bit4Channel: GeneralInstance4Channel {} + +/// Advanced 16-bit timer with 1 channel instance. +pub trait AdvancedInstance1Channel: BasicNoCr2Instance + GeneralInstance1Channel { + /// Communication interrupt for this timer. + type CommunicationInterrupt: interrupt::typelevel::Interrupt; + /// Break input interrupt for this timer. + type BreakInputInterrupt: interrupt::typelevel::Interrupt; +} +/// Advanced 16-bit timer with 2 channels instance. + +pub trait AdvancedInstance2Channel: BasicInstance + GeneralInstance2Channel + AdvancedInstance1Channel {} + +/// Advanced 16-bit timer with 4 channels instance. +pub trait AdvancedInstance4Channel: AdvancedInstance2Channel + GeneralInstance4Channel {} + +pin_trait!(Channel1Pin, GeneralInstance4Channel); +pin_trait!(Channel2Pin, GeneralInstance4Channel); +pin_trait!(Channel3Pin, GeneralInstance4Channel); +pin_trait!(Channel4Pin, GeneralInstance4Channel); +pin_trait!(ExternalTriggerPin, GeneralInstance4Channel); + +pin_trait!(Channel1ComplementaryPin, AdvancedInstance4Channel); +pin_trait!(Channel2ComplementaryPin, AdvancedInstance4Channel); +pin_trait!(Channel3ComplementaryPin, AdvancedInstance4Channel); +pin_trait!(Channel4ComplementaryPin, AdvancedInstance4Channel); + +pin_trait!(BreakInputPin, AdvancedInstance4Channel); +pin_trait!(BreakInput2Pin, AdvancedInstance4Channel); + +pin_trait!(BreakInputComparator1Pin, AdvancedInstance4Channel); +pin_trait!(BreakInputComparator2Pin, AdvancedInstance4Channel); + +pin_trait!(BreakInput2Comparator1Pin, AdvancedInstance4Channel); +pin_trait!(BreakInput2Comparator2Pin, AdvancedInstance4Channel); + +// Update Event trigger DMA for every timer +dma_trait!(UpDma, BasicInstance); + +dma_trait!(Ch1Dma, GeneralInstance4Channel); +dma_trait!(Ch2Dma, GeneralInstance4Channel); +dma_trait!(Ch3Dma, GeneralInstance4Channel); +dma_trait!(Ch4Dma, GeneralInstance4Channel); + +#[allow(unused)] +macro_rules! impl_core_timer { + ($inst:ident, $bits:expr) => { + impl SealedInstance for crate::peripherals::$inst { + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl CoreInstance for crate::peripherals::$inst { + type UpdateInterrupt = crate::_generated::peripheral_interrupts::$inst::UP; + + const BITS: TimerBits = $bits; + + fn regs() -> *mut () { + crate::pac::$inst.as_ptr() + } + } + }; +} + +#[allow(unused)] +macro_rules! impl_general_1ch { + ($inst:ident) => { + impl GeneralInstance1Channel for crate::peripherals::$inst { + type CaptureCompareInterrupt = crate::_generated::peripheral_interrupts::$inst::CC; + } + }; +} + +#[allow(unused)] +macro_rules! impl_general_2ch { + ($inst:ident) => { + impl GeneralInstance2Channel for crate::peripherals::$inst { + type TriggerInterrupt = crate::_generated::peripheral_interrupts::$inst::TRG; + } + }; +} + +#[allow(unused)] +macro_rules! impl_advanced_1ch { + ($inst:ident) => { + impl AdvancedInstance1Channel for crate::peripherals::$inst { + type CommunicationInterrupt = crate::_generated::peripheral_interrupts::$inst::COM; + type BreakInputInterrupt = crate::_generated::peripheral_interrupts::$inst::BRK; + } + }; +} + +// This macro only apply to "AdvancedInstance(s)", +// not "GeneralInstance4Channel" itself. +#[allow(unused)] +macro_rules! impl_general_4ch_blank_sealed { + ($inst:ident) => { + impl General4ChBlankSealed for crate::peripherals::$inst { + fn enable_outputs(&self) { + unsafe { crate::pac::timer::Tim1chCmp::from_ptr(Self::regs()) } + .bdtr() + .modify(|w| w.set_moe(true)); + } + } + }; +} + +foreach_interrupt! { + ($inst:ident, timer, TIM_BASIC, UP, $irq:ident) => { + impl_core_timer!($inst, TimerBits::Bits16); + impl BasicNoCr2Instance for crate::peripherals::$inst {} + impl BasicInstance for crate::peripherals::$inst {} + }; + + ($inst:ident, timer, TIM_1CH, UP, $irq:ident) => { + impl_core_timer!($inst, TimerBits::Bits16); + impl BasicNoCr2Instance for crate::peripherals::$inst {} + impl BasicInstance for crate::peripherals::$inst {} + impl_general_1ch!($inst); + impl_general_2ch!($inst); + impl GeneralInstance4Channel for crate::peripherals::$inst {} + impl General4ChBlankSealed for crate::peripherals::$inst {} + }; + + ($inst:ident, timer, TIM_2CH, UP, $irq:ident) => { + impl_core_timer!($inst, TimerBits::Bits16); + impl BasicNoCr2Instance for crate::peripherals::$inst {} + impl BasicInstance for crate::peripherals::$inst {} + impl_general_1ch!($inst); + impl_general_2ch!($inst); + impl GeneralInstance4Channel for crate::peripherals::$inst {} + impl General4ChBlankSealed for crate::peripherals::$inst {} + }; + + ($inst:ident, timer, TIM_GP16, UP, $irq:ident) => { + impl_core_timer!($inst, TimerBits::Bits16); + impl BasicNoCr2Instance for crate::peripherals::$inst {} + impl BasicInstance for crate::peripherals::$inst {} + impl_general_1ch!($inst); + impl_general_2ch!($inst); + impl GeneralInstance4Channel for crate::peripherals::$inst {} + impl General4ChBlankSealed for crate::peripherals::$inst {} + }; + + ($inst:ident, timer, TIM_GP32, UP, $irq:ident) => { + impl_core_timer!($inst, TimerBits::Bits32); + impl BasicNoCr2Instance for crate::peripherals::$inst {} + impl BasicInstance for crate::peripherals::$inst {} + impl_general_1ch!($inst); + impl_general_2ch!($inst); + impl GeneralInstance4Channel for crate::peripherals::$inst {} + impl GeneralInstance32bit4Channel for crate::peripherals::$inst {} + impl General4ChBlankSealed for crate::peripherals::$inst {} + }; + + ($inst:ident, timer, TIM_1CH_CMP, UP, $irq:ident) => { + impl_core_timer!($inst, TimerBits::Bits16); + impl BasicNoCr2Instance for crate::peripherals::$inst {} + impl BasicInstance for crate::peripherals::$inst {} + impl_general_1ch!($inst); + impl_general_2ch!($inst); + impl GeneralInstance4Channel for crate::peripherals::$inst {} + impl_general_4ch_blank_sealed!($inst); + impl_advanced_1ch!($inst); + impl AdvancedInstance2Channel for crate::peripherals::$inst {} + impl AdvancedInstance4Channel for crate::peripherals::$inst {} + }; + + ($inst:ident, timer, TIM_2CH_CMP, UP, $irq:ident) => { + impl_core_timer!($inst, TimerBits::Bits16); + impl BasicNoCr2Instance for crate::peripherals::$inst {} + impl BasicInstance for crate::peripherals::$inst {} + impl_general_1ch!($inst); + impl_general_2ch!($inst); + impl GeneralInstance4Channel for crate::peripherals::$inst {} + impl_general_4ch_blank_sealed!($inst); + impl_advanced_1ch!($inst); + impl AdvancedInstance2Channel for crate::peripherals::$inst {} + impl AdvancedInstance4Channel for crate::peripherals::$inst {} + }; + + ($inst:ident, timer, TIM_ADV, UP, $irq:ident) => { + impl_core_timer!($inst, TimerBits::Bits16); + impl BasicNoCr2Instance for crate::peripherals::$inst {} + impl BasicInstance for crate::peripherals::$inst {} + impl_general_1ch!($inst); + impl_general_2ch!($inst); + impl GeneralInstance4Channel for crate::peripherals::$inst {} + impl_general_4ch_blank_sealed!($inst); + impl_advanced_1ch!($inst); + impl AdvancedInstance2Channel for crate::peripherals::$inst {} + impl AdvancedInstance4Channel for crate::peripherals::$inst {} + }; +} + +/// Update interrupt handler. +pub struct UpdateInterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for UpdateInterruptHandler { + unsafe fn on_interrupt() { + #[cfg(feature = "low-power")] + crate::low_power::on_wakeup_irq(); + + let regs = crate::pac::timer::TimCore::from_ptr(T::regs()); + + // Read TIM interrupt flags. + let sr = regs.sr().read(); + + // Mask relevant interrupts (UIE). + let bits = sr.0 & 0x00000001; + + // Mask all the channels that fired. + regs.dier().modify(|w| w.0 &= !bits); + + // Wake the tasks + if sr.uif() { + T::state().up_waker.wake(); + } + } +} + +/// Capture/Compare interrupt handler. +pub struct CaptureCompareInterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler + for CaptureCompareInterruptHandler +{ + unsafe fn on_interrupt() { + #[cfg(feature = "low-power")] + crate::low_power::on_wakeup_irq(); + + let regs = crate::pac::timer::TimGp16::from_ptr(T::regs()); + + // Read TIM interrupt flags. + let sr = regs.sr().read(); + + // Mask relevant interrupts (CCIE). + let bits = sr.0 & 0x0000001E; + + // Mask all the channels that fired. + regs.dier().modify(|w| w.0 &= !bits); + + // Wake the tasks + for ch in 0..4 { + if sr.ccif(ch) { + T::state().cc_waker[ch].wake(); + } + } + } +} diff --git a/embassy/embassy-stm32/src/timer/pwm_input.rs b/embassy/embassy-stm32/src/timer/pwm_input.rs new file mode 100644 index 0000000..e3eb604 --- /dev/null +++ b/embassy/embassy-stm32/src/timer/pwm_input.rs @@ -0,0 +1,114 @@ +//! PWM Input driver. + +use embassy_hal_internal::into_ref; + +use super::low_level::{CountingMode, InputCaptureMode, InputTISelection, SlaveMode, Timer, TriggerSource}; +use super::{Channel, Channel1Pin, Channel2Pin, GeneralInstance4Channel}; +use crate::gpio::{AfType, Pull}; +use crate::time::Hertz; +use crate::Peripheral; + +/// PWM Input driver. +pub struct PwmInput<'d, T: GeneralInstance4Channel> { + channel: Channel, + inner: Timer<'d, T>, +} + +impl<'d, T: GeneralInstance4Channel> PwmInput<'d, T> { + /// Create a new PWM input driver. + pub fn new( + tim: impl Peripheral

+ 'd, + pin: impl Peripheral

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

+ 'd, + pin: impl Peripheral

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

+ 'd, freq: Hertz, ch1: Channel, ch2: Channel) -> Self { + let mut inner = Timer::new(tim); + + inner.set_counting_mode(CountingMode::EdgeAlignedUp); + inner.set_tick_freq(freq); + inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details + inner.start(); + + // Configuration steps from ST RM0390 (STM32F446) chapter 17.3.6 + // or ST RM0008 (STM32F103) chapter 15.3.6 Input capture mode + inner.set_input_ti_selection(ch1, InputTISelection::Normal); + inner.set_input_capture_mode(ch1, InputCaptureMode::Rising); + + inner.set_input_ti_selection(ch2, InputTISelection::Alternate); + inner.set_input_capture_mode(ch2, InputCaptureMode::Falling); + + inner.set_trigger_source(match ch1 { + Channel::Ch1 => TriggerSource::TI1FP1, + Channel::Ch2 => TriggerSource::TI2FP2, + _ => panic!("Invalid channel for PWM input"), + }); + + inner.set_slave_mode(SlaveMode::RESET_MODE); + + // Must call the `enable` function after + + Self { channel: ch1, inner } + } + + /// Enable the given channel. + pub fn enable(&mut self) { + self.inner.enable_channel(Channel::Ch1, true); + self.inner.enable_channel(Channel::Ch2, true); + } + + /// Disable the given channel. + pub fn disable(&mut self) { + self.inner.enable_channel(Channel::Ch1, false); + self.inner.enable_channel(Channel::Ch2, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self) -> bool { + self.inner.get_channel_enable_state(Channel::Ch1) + } + + /// Get the period tick count + pub fn get_period_ticks(&self) -> u32 { + self.inner.get_capture_value(self.channel) + } + + /// Get the pulse width tick count + pub fn get_width_ticks(&self) -> u32 { + self.inner.get_capture_value(match self.channel { + Channel::Ch1 => Channel::Ch2, + Channel::Ch2 => Channel::Ch1, + _ => panic!("Invalid channel for PWM input"), + }) + } + + /// Get the duty cycle in 100% + pub fn get_duty_cycle(&self) -> f32 { + let period = self.get_period_ticks(); + if period == 0 { + return 0.; + } + 100. * (self.get_width_ticks() as f32) / (period as f32) + } +} diff --git a/embassy/embassy-stm32/src/timer/qei.rs b/embassy/embassy-stm32/src/timer/qei.rs new file mode 100644 index 0000000..fc58354 --- /dev/null +++ b/embassy/embassy-stm32/src/timer/qei.rs @@ -0,0 +1,106 @@ +//! Quadrature decoder using a timer. + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use stm32_metapac::timer::vals; + +use super::low_level::Timer; +use super::{Channel1Pin, Channel2Pin, GeneralInstance4Channel}; +use crate::gpio::{AfType, AnyPin, Pull}; +use crate::Peripheral; + +/// Counting direction +pub enum Direction { + /// Counting up. + Upcounting, + /// Counting down. + Downcounting, +} + +/// Channel 1 marker type. +pub enum Ch1 {} +/// Channel 2 marker type. +pub enum Ch2 {} + +/// Wrapper for using a pin with QEI. +pub struct QeiPin<'d, T, Channel> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, Channel)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, T: GeneralInstance4Channel> QeiPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " QEI pin instance.")] + pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af(pin.af_num(), AfType::input(Pull::None)); + }); + QeiPin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + }; +} + +channel_impl!(new_ch1, Ch1, Channel1Pin); +channel_impl!(new_ch2, Ch2, Channel2Pin); + +/// Quadrature decoder driver. +pub struct Qei<'d, T: GeneralInstance4Channel> { + inner: Timer<'d, T>, +} + +impl<'d, T: GeneralInstance4Channel> Qei<'d, T> { + /// Create a new quadrature decoder driver. + pub fn new(tim: impl Peripheral

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

+ 'd) -> Self { + let inner = Timer::new(tim); + let r = inner.regs_gp16(); + + // Configure TxC1 and TxC2 as captures + r.ccmr_input(0).modify(|w| { + w.set_ccs(0, vals::CcmrInputCcs::TI4); + w.set_ccs(1, vals::CcmrInputCcs::TI4); + }); + + // enable and configure to capture on rising edge + r.ccer().modify(|w| { + w.set_cce(0, true); + w.set_cce(1, true); + + w.set_ccp(0, false); + w.set_ccp(1, false); + }); + + r.smcr().modify(|w| { + w.set_sms(vals::Sms::ENCODER_MODE_3); + }); + + r.arr().modify(|w| w.set_arr(u16::MAX)); + r.cr1().modify(|w| w.set_cen(true)); + + Self { inner } + } + + /// Get direction. + pub fn read_direction(&self) -> Direction { + match self.inner.regs_gp16().cr1().read().dir() { + vals::Dir::DOWN => Direction::Downcounting, + vals::Dir::UP => Direction::Upcounting, + } + } + + /// Get count. + pub fn count(&self) -> u16 { + self.inner.regs_gp16().cnt().read().cnt() + } +} diff --git a/embassy/embassy-stm32/src/timer/simple_pwm.rs b/embassy/embassy-stm32/src/timer/simple_pwm.rs new file mode 100644 index 0000000..56fb187 --- /dev/null +++ b/embassy/embassy-stm32/src/timer/simple_pwm.rs @@ -0,0 +1,519 @@ +//! Simple PWM driver. + +use core::marker::PhantomData; +use core::mem::ManuallyDrop; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; +use super::{Channel, Channel1Pin, Channel2Pin, Channel3Pin, Channel4Pin, GeneralInstance4Channel}; +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::time::Hertz; +use crate::Peripheral; + +/// Channel 1 marker type. +pub enum Ch1 {} +/// Channel 2 marker type. +pub enum Ch2 {} +/// Channel 3 marker type. +pub enum Ch3 {} +/// Channel 4 marker type. +pub enum Ch4 {} + +/// PWM pin wrapper. +/// +/// This wraps a pin to make it usable with PWM. +pub struct PwmPin<'d, T, C> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, T: GeneralInstance4Channel> PwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] + pub fn $new_chx(pin: impl Peripheral

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

+ 'd, + _ch1: Option>, + _ch2: Option>, + _ch3: Option>, + _ch4: Option>, + freq: Hertz, + counting_mode: CountingMode, + ) -> Self { + Self::new_inner(tim, freq, counting_mode) + } + + fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz, counting_mode: CountingMode) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.set_counting_mode(counting_mode); + this.set_frequency(freq); + this.inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details + this.inner.start(); + + [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] + .iter() + .for_each(|&channel| { + this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1); + + this.inner.set_output_compare_preload(channel, true); + }); + + this + } + + /// Get a single channel + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn channel(&mut self, channel: Channel) -> SimplePwmChannel<'_, T> { + SimplePwmChannel { + timer: unsafe { self.inner.clone_unchecked() }, + channel, + } + } + + /// Channel 1 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch1(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch1) + } + + /// Channel 2 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch2(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch2) + } + + /// Channel 3 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch3(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch3) + } + + /// Channel 4 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch4(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch4) + } + + /// Splits a [`SimplePwm`] into four pwm channels. + /// + /// This returns all four channels, including channels that + /// aren't configured with a [`PwmPin`]. + // TODO: I hate the name "split" + pub fn split(self) -> SimplePwmChannels<'static, T> + where + // must be static because the timer will never be dropped/disabled + 'd: 'static, + { + // without this, the timer would be disabled at the end of this function + let timer = ManuallyDrop::new(self.inner); + + let ch = |channel| SimplePwmChannel { + timer: unsafe { timer.clone_unchecked() }, + channel, + }; + + SimplePwmChannels { + ch1: ch(Channel::Ch1), + ch2: ch(Channel::Ch2), + ch3: ch(Channel::Ch3), + ch4: ch(Channel::Ch4), + } + } + + /// Set PWM frequency. + /// + /// Note: when you call this, the max duty value changes, so you will have to + /// call `set_duty` on all channels with the duty calculated based on the new max duty. + pub fn set_frequency(&mut self, freq: Hertz) { + // TODO: prevent ARR = u16::MAX? + let multiplier = if self.inner.get_counting_mode().is_center_aligned() { + 2u8 + } else { + 1u8 + }; + self.inner.set_frequency_internal(freq * multiplier, 16); + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn max_duty_cycle(&self) -> u16 { + let max = self.inner.get_max_compare_value(); + assert!(max < u16::MAX as u32); + max as u16 + 1 + } + + /// Generate a sequence of PWM waveform + /// + /// Note: + /// you will need to provide corresponding TIMx_UP DMA channel to use this method. + pub async fn waveform_up( + &mut self, + dma: impl Peripheral

>, + channel: Channel, + duty: &[u16], + ) { + into_ref!(dma); + + #[allow(clippy::let_unit_value)] // eg. stm32f334 + let req = dma.request(); + + let original_duty_state = self.channel(channel).current_duty_cycle(); + let original_enable_state = self.channel(channel).is_enabled(); + let original_update_dma_state = self.inner.get_update_dma_state(); + + if !original_update_dma_state { + self.inner.enable_update_dma(true); + } + + if !original_enable_state { + self.channel(channel).enable(); + } + + unsafe { + #[cfg(not(any(bdma, gpdma)))] + use crate::dma::{Burst, FifoThreshold}; + use crate::dma::{Transfer, TransferOptions}; + + let dma_transfer_option = TransferOptions { + #[cfg(not(any(bdma, gpdma)))] + fifo_threshold: Some(FifoThreshold::Full), + #[cfg(not(any(bdma, gpdma)))] + mburst: Burst::Incr8, + ..Default::default() + }; + + Transfer::new_write( + &mut dma, + req, + duty, + self.inner.regs_1ch().ccr(channel.index()).as_ptr() as *mut _, + dma_transfer_option, + ) + .await + }; + + // restore output compare state + if !original_enable_state { + self.channel(channel).disable(); + } + + self.channel(channel).set_duty_cycle(original_duty_state); + + // Since DMA is closed before timer update event trigger DMA is turn off, + // this can almost always trigger a DMA FIFO error. + // + // optional TODO: + // clean FEIF after disable UDE + if !original_update_dma_state { + self.inner.enable_update_dma(false); + } + } +} + +macro_rules! impl_waveform_chx { + ($fn_name:ident, $dma_ch:ident, $cc_ch:ident) => { + impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { + /// Generate a sequence of PWM waveform + /// + /// Note: + /// you will need to provide corresponding TIMx_CHy DMA channel to use this method. + pub async fn $fn_name(&mut self, dma: impl Peripheral

>, duty: &[u16]) { + use crate::pac::timer::vals::Ccds; + + into_ref!(dma); + + #[allow(clippy::let_unit_value)] // eg. stm32f334 + let req = dma.request(); + + let cc_channel = Channel::$cc_ch; + + let original_duty_state = self.channel(cc_channel).current_duty_cycle(); + let original_enable_state = self.channel(cc_channel).is_enabled(); + let original_cc_dma_on_update = self.inner.get_cc_dma_selection() == Ccds::ONUPDATE; + let original_cc_dma_enabled = self.inner.get_cc_dma_enable_state(cc_channel); + + // redirect CC DMA request onto Update Event + if !original_cc_dma_on_update { + self.inner.set_cc_dma_selection(Ccds::ONUPDATE) + } + + if !original_cc_dma_enabled { + self.inner.set_cc_dma_enable_state(cc_channel, true); + } + + if !original_enable_state { + self.channel(cc_channel).enable(); + } + + unsafe { + #[cfg(not(any(bdma, gpdma)))] + use crate::dma::{Burst, FifoThreshold}; + use crate::dma::{Transfer, TransferOptions}; + + let dma_transfer_option = TransferOptions { + #[cfg(not(any(bdma, gpdma)))] + fifo_threshold: Some(FifoThreshold::Full), + #[cfg(not(any(bdma, gpdma)))] + mburst: Burst::Incr8, + ..Default::default() + }; + + Transfer::new_write( + &mut dma, + req, + duty, + self.inner.regs_gp16().ccr(cc_channel.index()).as_ptr() as *mut _, + dma_transfer_option, + ) + .await + }; + + // restore output compare state + if !original_enable_state { + self.channel(cc_channel).disable(); + } + + self.channel(cc_channel).set_duty_cycle(original_duty_state); + + // Since DMA is closed before timer Capture Compare Event trigger DMA is turn off, + // this can almost always trigger a DMA FIFO error. + // + // optional TODO: + // clean FEIF after disable UDE + if !original_cc_dma_enabled { + self.inner.set_cc_dma_enable_state(cc_channel, false); + } + + if !original_cc_dma_on_update { + self.inner.set_cc_dma_selection(Ccds::ONCOMPARE) + } + } + } + }; +} + +impl_waveform_chx!(waveform_ch1, Ch1Dma, Ch1); +impl_waveform_chx!(waveform_ch2, Ch2Dma, Ch2); +impl_waveform_chx!(waveform_ch3, Ch3Dma, Ch3); +impl_waveform_chx!(waveform_ch4, Ch4Dma, Ch4); + +impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> { + type Error = core::convert::Infallible; +} + +impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::SetDutyCycle for SimplePwmChannel<'d, T> { + fn max_duty_cycle(&self) -> u16 { + self.max_duty_cycle() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.set_duty_cycle(duty); + Ok(()) + } + + fn set_duty_cycle_fully_off(&mut self) -> Result<(), Self::Error> { + self.set_duty_cycle_fully_off(); + Ok(()) + } + + fn set_duty_cycle_fully_on(&mut self) -> Result<(), Self::Error> { + self.set_duty_cycle_fully_on(); + Ok(()) + } + + fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) -> Result<(), Self::Error> { + self.set_duty_cycle_fraction(num, denom); + Ok(()) + } + + fn set_duty_cycle_percent(&mut self, percent: u8) -> Result<(), Self::Error> { + self.set_duty_cycle_percent(percent); + Ok(()) + } +} + +impl<'d, T: GeneralInstance4Channel> embedded_hal_02::Pwm for SimplePwm<'d, T> { + type Channel = Channel; + type Time = Hertz; + type Duty = u32; + + fn disable(&mut self, channel: Self::Channel) { + self.inner.enable_channel(channel, false); + } + + fn enable(&mut self, channel: Self::Channel) { + self.inner.enable_channel(channel, true); + } + + fn get_period(&self) -> Self::Time { + self.inner.get_frequency() + } + + fn get_duty(&self, channel: Self::Channel) -> Self::Duty { + self.inner.get_compare_value(channel) + } + + fn get_max_duty(&self) -> Self::Duty { + self.inner.get_max_compare_value() + 1 + } + + fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { + assert!(duty <= self.max_duty_cycle() as u32); + self.inner.set_compare_value(channel, duty) + } + + fn set_period

(&mut self, period: P) + where + P: Into, + { + self.inner.set_frequency(period.into()); + } +} diff --git a/embassy/embassy-stm32/src/tsc/acquisition_banks.rs b/embassy/embassy-stm32/src/tsc/acquisition_banks.rs new file mode 100644 index 0000000..6791ef6 --- /dev/null +++ b/embassy/embassy-stm32/src/tsc/acquisition_banks.rs @@ -0,0 +1,209 @@ +use super::io_pin::*; +#[cfg(any(tsc_v2, tsc_v3))] +use super::pin_groups::G7; +#[cfg(tsc_v3)] +use super::pin_groups::G8; +use super::pin_groups::{pin_roles, G1, G2, G3, G4, G5, G6}; +use super::types::{Group, GroupStatus}; +use super::TSC_NUM_GROUPS; + +/// Represents a collection of TSC (Touch Sensing Controller) pins for an acquisition bank. +/// +/// This struct holds optional `tsc::IOPin` values for each TSC group, allowing for flexible +/// configuration of TSC acquisition banks. Each field corresponds to a specific TSC group +/// and can be set to `Some(tsc::IOPin)` if that group is to be included in the acquisition, +/// or `None` if it should be excluded. +#[allow(missing_docs)] +#[derive(Default)] +pub struct AcquisitionBankPins { + pub g1_pin: Option>, + pub g2_pin: Option>, + pub g3_pin: Option>, + pub g4_pin: Option>, + pub g5_pin: Option>, + pub g6_pin: Option>, + #[cfg(any(tsc_v2, tsc_v3))] + pub g7_pin: Option>, + #[cfg(tsc_v3)] + pub g8_pin: Option>, +} + +impl AcquisitionBankPins { + /// Returns an iterator over the pins in this acquisition bank. + /// + /// This method allows for easy traversal of all configured pins in the bank. + pub fn iter(&self) -> AcquisitionBankPinsIterator { + AcquisitionBankPinsIterator(AcquisitionBankIterator::new(self)) + } +} + +/// Iterator for TSC acquisition banks. +/// +/// This iterator allows traversing through the pins of a `AcquisitionBankPins` struct, +/// yielding each configured pin in order of the TSC groups. +pub struct AcquisitionBankIterator<'a> { + pins: &'a AcquisitionBankPins, + current_group: u8, +} + +impl<'a> AcquisitionBankIterator<'a> { + fn new(pins: &'a AcquisitionBankPins) -> Self { + Self { pins, current_group: 0 } + } + + fn next_pin(&mut self) -> Option { + while self.current_group < TSC_NUM_GROUPS as u8 { + let pin = match self.current_group { + 0 => self.pins.g1_pin.map(IOPinWithRole::get_pin), + 1 => self.pins.g2_pin.map(IOPinWithRole::get_pin), + 2 => self.pins.g3_pin.map(IOPinWithRole::get_pin), + 3 => self.pins.g4_pin.map(IOPinWithRole::get_pin), + 4 => self.pins.g5_pin.map(IOPinWithRole::get_pin), + 5 => self.pins.g6_pin.map(IOPinWithRole::get_pin), + #[cfg(any(tsc_v2, tsc_v3))] + 6 => self.pins.g7_pin.map(IOPinWithRole::get_pin), + #[cfg(tsc_v3)] + 7 => self.pins.g8_pin.map(IOPinWithRole::get_pin), + _ => None, + }; + self.current_group += 1; + if pin.is_some() { + return pin; + } + } + None + } +} + +/// Iterator for TSC acquisition bank pins. +/// +/// This iterator yields `tsc::IOPin` values for each configured pin in the acquisition bank. +pub struct AcquisitionBankPinsIterator<'a>(AcquisitionBankIterator<'a>); + +impl<'a> Iterator for AcquisitionBankPinsIterator<'a> { + type Item = IOPin; + + fn next(&mut self) -> Option { + self.0.next_pin() + } +} + +impl AcquisitionBankPins { + /// Returns an iterator over the available pins in the bank + pub fn pins_iterator(&self) -> AcquisitionBankPinsIterator { + AcquisitionBankPinsIterator(AcquisitionBankIterator::new(self)) + } +} + +/// Represents a collection of TSC pins to be acquired simultaneously. +/// +/// This struct contains a set of pins to be used in a TSC acquisition with a pre-computed and +/// verified mask for efficiently setting up the TSC peripheral before performing an acquisition. +/// It ensures that only one channel pin per TSC group is included, adhering to hardware limitations. +pub struct AcquisitionBank { + pub(super) pins: AcquisitionBankPins, + pub(super) mask: u32, +} + +impl AcquisitionBank { + /// Returns an iterator over the available pins in the bank. + pub fn pins_iterator(&self) -> AcquisitionBankPinsIterator { + self.pins.pins_iterator() + } + + /// Returns the mask for this bank. + pub fn mask(&self) -> u32 { + self.mask + } + + /// Retrieves the TSC I/O pin for a given group in this acquisition bank. + /// + /// # Arguments + /// * `group` - The TSC group to retrieve the pin for. + /// + /// # Returns + /// An `Option` containing the pin if it exists for the given group, or `None` if not. + pub fn get_pin(&self, group: Group) -> Option { + match group { + Group::One => self.pins.g1_pin.map(|p| p.pin), + Group::Two => self.pins.g2_pin.map(|p| p.pin), + Group::Three => self.pins.g3_pin.map(|p| p.pin), + Group::Four => self.pins.g4_pin.map(|p| p.pin), + Group::Five => self.pins.g5_pin.map(|p| p.pin), + Group::Six => self.pins.g6_pin.map(|p| p.pin), + #[cfg(any(tsc_v2, tsc_v3))] + Group::Seven => self.pins.g7_pin.map(|p| p.pin), + #[cfg(tsc_v3)] + Group::Eight => self.pins.g8_pin.map(|p| p.pin), + } + } +} + +/// Represents the status of all TSC groups in an acquisition bank +#[derive(Default)] +pub struct AcquisitionBankStatus { + pub(super) groups: [Option; TSC_NUM_GROUPS], +} + +impl AcquisitionBankStatus { + /// Check if all groups in the bank are complete + pub fn all_complete(&self) -> bool { + self.groups + .iter() + .all(|&status| status.map_or(true, |s| s == GroupStatus::Complete)) + } + + /// Check if any group in the bank is ongoing + pub fn any_ongoing(&self) -> bool { + self.groups.iter().any(|&status| status == Some(GroupStatus::Ongoing)) + } + + /// Get the status of a specific group, if the group is present in the bank + pub fn get_group_status(&self, group: Group) -> Option { + let index: usize = group.into(); + self.groups[index] + } + + /// Iterator for groups present in the bank + pub fn iter(&self) -> impl Iterator + '_ { + self.groups.iter().enumerate().filter_map(|(group_num, status)| { + status.and_then(|s| Group::try_from(group_num).ok().map(|group| (group, s))) + }) + } +} + +/// Represents the result of a Touch Sensing Controller (TSC) acquisition for a specific pin. +/// +/// This struct contains a reference to the `tsc::IOPin` from which a value was read, +/// along with the actual sensor reading for that pin. It provides a convenient way +/// to associate TSC readings with their corresponding pins after an acquisition. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug)] +pub struct ChannelReading { + /// The sensor reading value obtained from the TSC acquisition. + /// Lower values typically indicate a detected touch, while higher values indicate no touch. + pub sensor_value: u16, + + /// The `tsc::IOPin` associated with this reading. + /// This allows for easy identification of which pin the reading corresponds to. + pub tsc_pin: IOPin, +} + +/// Represents the readings from all TSC groups +#[derive(Default)] +pub struct AcquisitionBankReadings { + pub(super) groups: [Option; TSC_NUM_GROUPS], +} + +impl AcquisitionBankReadings { + /// Get the reading for a specific group, if the group is present in the bank + pub fn get_group_reading(&self, group: Group) -> Option { + let index: usize = group.into(); + self.groups[index] + } + + /// Iterator for readings for groups present in the bank + pub fn iter(&self) -> impl Iterator + '_ { + self.groups.iter().filter_map(|&x| x) + } +} diff --git a/embassy/embassy-stm32/src/tsc/config.rs b/embassy/embassy-stm32/src/tsc/config.rs new file mode 100644 index 0000000..efa1f9a --- /dev/null +++ b/embassy/embassy-stm32/src/tsc/config.rs @@ -0,0 +1,175 @@ +/// Charge transfer pulse cycles +#[allow(missing_docs)] +#[derive(Copy, Clone, PartialEq)] +pub enum ChargeTransferPulseCycle { + _1, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + _11, + _12, + _13, + _14, + _15, + _16, +} + +impl Into for ChargeTransferPulseCycle { + fn into(self) -> u8 { + match self { + ChargeTransferPulseCycle::_1 => 0, + ChargeTransferPulseCycle::_2 => 1, + ChargeTransferPulseCycle::_3 => 2, + ChargeTransferPulseCycle::_4 => 3, + ChargeTransferPulseCycle::_5 => 4, + ChargeTransferPulseCycle::_6 => 5, + ChargeTransferPulseCycle::_7 => 6, + ChargeTransferPulseCycle::_8 => 7, + ChargeTransferPulseCycle::_9 => 8, + ChargeTransferPulseCycle::_10 => 9, + ChargeTransferPulseCycle::_11 => 10, + ChargeTransferPulseCycle::_12 => 11, + ChargeTransferPulseCycle::_13 => 12, + ChargeTransferPulseCycle::_14 => 13, + ChargeTransferPulseCycle::_15 => 14, + ChargeTransferPulseCycle::_16 => 15, + } + } +} + +/// Max count +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum MaxCount { + _255, + _511, + _1023, + _2047, + _4095, + _8191, + _16383, +} + +impl Into for MaxCount { + fn into(self) -> u8 { + match self { + MaxCount::_255 => 0, + MaxCount::_511 => 1, + MaxCount::_1023 => 2, + MaxCount::_2047 => 3, + MaxCount::_4095 => 4, + MaxCount::_8191 => 5, + MaxCount::_16383 => 6, + } + } +} + +/// Prescaler divider +#[allow(missing_docs)] +#[derive(Copy, Clone, PartialEq)] +pub enum PGPrescalerDivider { + _1, + _2, + _4, + _8, + _16, + _32, + _64, + _128, +} + +impl Into for PGPrescalerDivider { + fn into(self) -> u8 { + match self { + PGPrescalerDivider::_1 => 0, + PGPrescalerDivider::_2 => 1, + PGPrescalerDivider::_4 => 2, + PGPrescalerDivider::_8 => 3, + PGPrescalerDivider::_16 => 4, + PGPrescalerDivider::_32 => 5, + PGPrescalerDivider::_64 => 6, + PGPrescalerDivider::_128 => 7, + } + } +} + +/// Error type for SSDeviation +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SSDeviationError { + /// The provided value is too low (0) + ValueTooLow, + /// The provided value is too high (greater than 128) + ValueTooHigh, +} + +/// Spread Spectrum Deviation +#[derive(Copy, Clone)] +pub struct SSDeviation(u8); +impl SSDeviation { + /// Create new deviation value, acceptable inputs are 1-128 + pub fn new(val: u8) -> Result { + if val == 0 { + return Err(SSDeviationError::ValueTooLow); + } else if val > 128 { + return Err(SSDeviationError::ValueTooHigh); + } + Ok(Self(val - 1)) + } +} + +impl Into for SSDeviation { + fn into(self) -> u8 { + self.0 + } +} + +/// Peripheral configuration +#[derive(Clone, Copy)] +pub struct Config { + /// Duration of high state of the charge transfer pulse + pub ct_pulse_high_length: ChargeTransferPulseCycle, + /// Duration of the low state of the charge transfer pulse + pub ct_pulse_low_length: ChargeTransferPulseCycle, + /// Enable/disable of spread spectrum feature + pub spread_spectrum: bool, + /// Adds variable number of periods of the SS clk to pulse high state + pub spread_spectrum_deviation: SSDeviation, + /// Selects AHB clock divider used to generate SS clk + pub spread_spectrum_prescaler: bool, + /// Selects AHB clock divider used to generate pulse generator clk + pub pulse_generator_prescaler: PGPrescalerDivider, + /// Maximum number of charge transfer pulses that can be generated before error + pub max_count_value: MaxCount, + /// Defines config of all IOs when no ongoing acquisition + pub io_default_mode: bool, + /// Polarity of sync input pin + pub synchro_pin_polarity: bool, + /// Acquisition starts when start bit is set or with sync pin input + pub acquisition_mode: bool, + /// Enable max count interrupt + pub max_count_interrupt: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + ct_pulse_high_length: ChargeTransferPulseCycle::_1, + ct_pulse_low_length: ChargeTransferPulseCycle::_1, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(1).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_1, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + } + } +} diff --git a/embassy/embassy-stm32/src/tsc/errors.rs b/embassy/embassy-stm32/src/tsc/errors.rs new file mode 100644 index 0000000..21f6441 --- /dev/null +++ b/embassy/embassy-stm32/src/tsc/errors.rs @@ -0,0 +1,21 @@ +/// Represents errors that can occur when configuring or validating TSC pin groups. +#[derive(Debug)] +pub enum GroupError { + /// Error when a group has no sampling capacitor + NoSamplingCapacitor, + /// Error when a group has neither channel IOs nor a shield IO + NoChannelOrShield, + /// Error when a group has both channel IOs and a shield IO + MixedChannelAndShield, + /// Error when there is more than one shield IO across all groups + MultipleShields, +} + +/// Error returned when attempting to set an invalid channel pin as active in the TSC. +#[derive(Debug)] +pub enum AcquisitionBankError { + /// Indicates that one or more of the provided pins is not a valid channel pin. + InvalidChannelPin, + /// Indicates that multiple channels from the same group were provided. + MultipleChannelsPerGroup, +} diff --git a/embassy/embassy-stm32/src/tsc/io_pin.rs b/embassy/embassy-stm32/src/tsc/io_pin.rs new file mode 100644 index 0000000..ad2010e --- /dev/null +++ b/embassy/embassy-stm32/src/tsc/io_pin.rs @@ -0,0 +1,200 @@ +use core::marker::PhantomData; +use core::ops::{BitAnd, BitOr, BitOrAssign}; + +use super::pin_roles; +use super::types::Group; + +/// Pin defines +#[allow(missing_docs)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(PartialEq, Clone, Copy, Debug)] +pub enum IOPin { + Group1Io1, + Group1Io2, + Group1Io3, + Group1Io4, + Group2Io1, + Group2Io2, + Group2Io3, + Group2Io4, + Group3Io1, + Group3Io2, + Group3Io3, + Group3Io4, + Group4Io1, + Group4Io2, + Group4Io3, + Group4Io4, + Group5Io1, + Group5Io2, + Group5Io3, + Group5Io4, + Group6Io1, + Group6Io2, + Group6Io3, + Group6Io4, + #[cfg(any(tsc_v2, tsc_v3))] + Group7Io1, + #[cfg(any(tsc_v2, tsc_v3))] + Group7Io2, + #[cfg(any(tsc_v2, tsc_v3))] + Group7Io3, + #[cfg(any(tsc_v2, tsc_v3))] + Group7Io4, + #[cfg(tsc_v3)] + Group8Io1, + #[cfg(tsc_v3)] + Group8Io2, + #[cfg(tsc_v3)] + Group8Io3, + #[cfg(tsc_v3)] + Group8Io4, +} + +/// Represents a TSC I/O pin with associated group and role information. +/// +/// This type combines an `tsc::IOPin` with phantom type parameters to statically +/// encode the pin's group and role. This allows for type-safe operations +/// on TSC pins within their specific contexts. +/// +/// - `Group`: A type parameter representing the TSC group (e.g., `G1`, `G2`). +/// - `Role`: A type parameter representing the pin's role (e.g., `Channel`, `Sample`). +#[derive(Clone, Copy, Debug)] +pub struct IOPinWithRole { + /// The underlying TSC I/O pin. + pub pin: IOPin, + pub(super) phantom: PhantomData<(Group, Role)>, +} + +impl IOPinWithRole { + pub(super) fn get_pin(wrapped_pin: IOPinWithRole) -> IOPin { + wrapped_pin.pin + } +} + +impl IOPin { + /// Maps this IOPin to the Group it belongs to. + /// + /// This method provides a convenient way to determine which Group + /// a specific TSC I/O pin is associated with. + pub const fn group(&self) -> Group { + match self { + IOPin::Group1Io1 | IOPin::Group1Io2 | IOPin::Group1Io3 | IOPin::Group1Io4 => Group::One, + IOPin::Group2Io1 | IOPin::Group2Io2 | IOPin::Group2Io3 | IOPin::Group2Io4 => Group::Two, + IOPin::Group3Io1 | IOPin::Group3Io2 | IOPin::Group3Io3 | IOPin::Group3Io4 => Group::Three, + IOPin::Group4Io1 | IOPin::Group4Io2 | IOPin::Group4Io3 | IOPin::Group4Io4 => Group::Four, + IOPin::Group5Io1 | IOPin::Group5Io2 | IOPin::Group5Io3 | IOPin::Group5Io4 => Group::Five, + IOPin::Group6Io1 | IOPin::Group6Io2 | IOPin::Group6Io3 | IOPin::Group6Io4 => Group::Six, + #[cfg(any(tsc_v2, tsc_v3))] + IOPin::Group7Io1 | IOPin::Group7Io2 | IOPin::Group7Io3 | IOPin::Group7Io4 => Group::Seven, + #[cfg(tsc_v3)] + IOPin::Group8Io1 | IOPin::Group8Io2 | IOPin::Group8Io3 | IOPin::Group8Io4 => Group::Eight, + } + } + + /// Returns the `Group` associated with the given `IOPin`. + pub fn get_group(pin: IOPin) -> Group { + pin.group() + } +} + +impl BitOr for u32 { + type Output = u32; + fn bitor(self, rhs: IOPin) -> Self::Output { + let rhs: u32 = rhs.into(); + self | rhs + } +} + +impl BitOr for IOPin { + type Output = u32; + fn bitor(self, rhs: u32) -> Self::Output { + let val: u32 = self.into(); + val | rhs + } +} + +impl BitOr for IOPin { + type Output = u32; + fn bitor(self, rhs: Self) -> Self::Output { + let val: u32 = self.into(); + let rhs: u32 = rhs.into(); + val | rhs + } +} + +impl BitOrAssign for u32 { + fn bitor_assign(&mut self, rhs: IOPin) { + let rhs: u32 = rhs.into(); + *self |= rhs; + } +} + +impl BitAnd for u32 { + type Output = u32; + fn bitand(self, rhs: IOPin) -> Self::Output { + let rhs: u32 = rhs.into(); + self & rhs + } +} + +impl BitAnd for IOPin { + type Output = u32; + fn bitand(self, rhs: u32) -> Self::Output { + let val: u32 = self.into(); + val & rhs + } +} + +impl IOPin { + const fn to_u32(self) -> u32 { + match self { + IOPin::Group1Io1 => 0x00000001, + IOPin::Group1Io2 => 0x00000002, + IOPin::Group1Io3 => 0x00000004, + IOPin::Group1Io4 => 0x00000008, + IOPin::Group2Io1 => 0x00000010, + IOPin::Group2Io2 => 0x00000020, + IOPin::Group2Io3 => 0x00000040, + IOPin::Group2Io4 => 0x00000080, + IOPin::Group3Io1 => 0x00000100, + IOPin::Group3Io2 => 0x00000200, + IOPin::Group3Io3 => 0x00000400, + IOPin::Group3Io4 => 0x00000800, + IOPin::Group4Io1 => 0x00001000, + IOPin::Group4Io2 => 0x00002000, + IOPin::Group4Io3 => 0x00004000, + IOPin::Group4Io4 => 0x00008000, + IOPin::Group5Io1 => 0x00010000, + IOPin::Group5Io2 => 0x00020000, + IOPin::Group5Io3 => 0x00040000, + IOPin::Group5Io4 => 0x00080000, + IOPin::Group6Io1 => 0x00100000, + IOPin::Group6Io2 => 0x00200000, + IOPin::Group6Io3 => 0x00400000, + IOPin::Group6Io4 => 0x00800000, + #[cfg(any(tsc_v2, tsc_v3))] + IOPin::Group7Io1 => 0x01000000, + #[cfg(any(tsc_v2, tsc_v3))] + IOPin::Group7Io2 => 0x02000000, + #[cfg(any(tsc_v2, tsc_v3))] + IOPin::Group7Io3 => 0x04000000, + #[cfg(any(tsc_v2, tsc_v3))] + IOPin::Group7Io4 => 0x08000000, + #[cfg(tsc_v3)] + IOPin::Group8Io1 => 0x10000000, + #[cfg(tsc_v3)] + IOPin::Group8Io2 => 0x20000000, + #[cfg(tsc_v3)] + IOPin::Group8Io3 => 0x40000000, + #[cfg(tsc_v3)] + IOPin::Group8Io4 => 0x80000000, + } + } +} + +impl Into for IOPin { + fn into(self) -> u32 { + self.to_u32() + } +} diff --git a/embassy/embassy-stm32/src/tsc/mod.rs b/embassy/embassy-stm32/src/tsc/mod.rs new file mode 100644 index 0000000..0d5c274 --- /dev/null +++ b/embassy/embassy-stm32/src/tsc/mod.rs @@ -0,0 +1,166 @@ +//! TSC Peripheral Interface +//! +//! This module provides an interface for the Touch Sensing Controller (TSC) peripheral. +//! It supports both blocking and async modes of operation, as well as different TSC versions (v1, v2, v3). +//! +//! # Key Concepts +//! +//! - **Pin Groups**: TSC pins are organized into groups, each containing up to four IOs. +//! - **Pin Roles**: Each pin in a group can have a role: Channel, Sample, or Shield. +//! - **Acquisition Banks**: Used for efficient, repeated TSC acquisitions on specific sets of pins. +//! +//! # Example (stm32) +//! +//! ```rust +//! let device_config = embassy_stm32::Config::default(); +//! let context = embassy_stm32::init(device_config); +//! +//! let config = tsc::Config { +//! ct_pulse_high_length: ChargeTransferPulseCycle::_4, +//! ct_pulse_low_length: ChargeTransferPulseCycle::_4, +//! spread_spectrum: false, +//! spread_spectrum_deviation: SSDeviation::new(2).unwrap(), +//! spread_spectrum_prescaler: false, +//! pulse_generator_prescaler: PGPrescalerDivider::_16, +//! max_count_value: MaxCount::_255, +//! io_default_mode: false, +//! synchro_pin_polarity: false, +//! acquisition_mode: false, +//! max_count_interrupt: false, +//! }; +//! +//! let mut g2: PinGroupWithRoles = PinGroupWithRoles::new(); +//! g2.set_io1::(context.PB4); +//! let sensor_pin = g2.set_io2::(context.PB5); +//! +//! let pin_groups = PinGroups { +//! g2: Some(g2.pin_group), +//! ..Default::default() +//! }; +//! +//! let mut touch_controller = tsc::Tsc::new_blocking( +//! context.TSC, +//! pin_groups, +//! config, +//! ).unwrap(); +//! +//! let discharge_delay = 5; // ms +//! +//! loop { +//! touch_controller.set_active_channels_mask(sensor_pin.pin.into()); +//! touch_controller.start(); +//! touch_controller.poll_for_acquisition(); +//! touch_controller.discharge_io(true); +//! Timer::after_millis(discharge_delay).await; +//! +//! match touch_controller.group_get_status(sensor_pin.pin.group()) { +//! GroupStatus::Complete => { +//! let group_val = touch_controller.group_get_value(sensor_pin.pin.group()); +//! // Process the touch value +//! // ... +//! } +//! GroupStatus::Ongoing => { +//! // Handle ongoing acquisition +//! // ... +//! } +//! } +//! } +//! ``` +//! +//! # Async Usage +//! +//! For async operation, use `Tsc::new_async` and `pend_for_acquisition` instead of polling. + +#![macro_use] + +/// Configuration structures and enums for the TSC peripheral. +pub mod config; + +/// Definitions and implementations for TSC pin groups. +pub mod pin_groups; + +/// Definitions and implementations for individual TSC I/O pins. +pub mod io_pin; + +/// Structures and implementations for TSC acquisition banks. +pub mod acquisition_banks; + +/// Core implementation of the TSC (Touch Sensing Controller) driver. +pub mod tsc; + +/// Type definitions used throughout the TSC module. +pub mod types; + +/// Error types and definitions for the TSC module. +pub mod errors; + +use core::marker::PhantomData; + +pub use acquisition_banks::*; +pub use config::*; +use embassy_sync::waitqueue::AtomicWaker; +pub use errors::*; +pub use io_pin::*; +pub use pin_groups::*; +pub use tsc::*; +pub use types::*; + +use crate::rcc::RccPeripheral; +use crate::{interrupt, peripherals, Peripheral}; + +#[cfg(tsc_v1)] +const TSC_NUM_GROUPS: usize = 6; +#[cfg(tsc_v2)] +const TSC_NUM_GROUPS: usize = 7; +#[cfg(tsc_v3)] +const TSC_NUM_GROUPS: usize = 8; + +/// Error type defined for TSC +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Test error for TSC + Test, +} + +/// TSC interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + T::regs().ier().write(|w| w.set_eoaie(false)); + T::waker().wake(); + } +} + +pub(crate) trait SealedInstance { + fn regs() -> crate::pac::tsc::Tsc; + fn waker() -> &'static AtomicWaker; +} + +/// TSC instance trait +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral { + /// Interrupt for this TSC instance + type Interrupt: interrupt::typelevel::Interrupt; +} + +foreach_interrupt!( + ($inst:ident, tsc, TSC, GLOBAL, $irq:ident) => { + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + + impl SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::tsc::Tsc { + crate::pac::$inst + } + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + } + }; +); diff --git a/embassy/embassy-stm32/src/tsc/pin_groups.rs b/embassy/embassy-stm32/src/tsc/pin_groups.rs new file mode 100644 index 0000000..1f3aafa --- /dev/null +++ b/embassy/embassy-stm32/src/tsc/pin_groups.rs @@ -0,0 +1,669 @@ +use core::marker::PhantomData; +use core::ops::BitOr; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::errors::GroupError; +use super::io_pin::*; +use super::Instance; +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::Peripheral; + +/// Pin type definition to control IO parameters +#[derive(PartialEq, Clone, Copy)] +pub enum PinType { + /// Sensing channel pin connected to an electrode + Channel, + /// Sampling capacitor pin, one required for every pin group + Sample, + /// Shield pin connected to capacitive sensing shield + Shield, +} + +/// Pin struct that maintains usage +#[allow(missing_docs)] +pub struct Pin<'d, T, Group> { + _pin: PeripheralRef<'d, AnyPin>, + role: PinType, + tsc_io_pin: IOPin, + phantom: PhantomData<(T, Group)>, +} + +impl<'d, T, Group> Pin<'d, T, Group> { + /// Returns the role of this TSC pin. + /// + /// The role indicates whether this pin is configured as a channel, + /// sampling capacitor, or shield in the TSC group. + /// + /// # Returns + /// The `PinType` representing the role of this pin. + pub fn role(&self) -> PinType { + self.role + } + + /// Returns the TSC IO pin associated with this pin. + /// + /// This method provides access to the specific TSC IO pin configuration, + /// which includes information about the pin's group and position within that group. + /// + /// # Returns + /// The `IOPin` representing this pin's TSC-specific configuration. + pub fn tsc_io_pin(&self) -> IOPin { + self.tsc_io_pin + } +} + +/// Represents a group of TSC (Touch Sensing Controller) pins. +/// +/// In the TSC peripheral, pins are organized into groups of four IOs. Each group +/// must have exactly one sampling capacitor pin and can have multiple channel pins +/// or a single shield pin. This structure encapsulates these pin configurations +/// for a single TSC group. +/// +/// # Pin Roles +/// - Sampling Capacitor: One required per group, used for charge transfer. +/// - Channel: Sensing pins connected to electrodes for touch detection. +/// - Shield: Optional, used for active shielding to improve sensitivity. +/// +/// # Constraints +/// - Each group must have exactly one sampling capacitor pin. +/// - A group can have either channel pins or a shield pin, but not both. +/// - No more than one shield pin is allowed across all groups. +#[allow(missing_docs)] +pub struct PinGroup<'d, T, Group> { + pin1: Option>, + pin2: Option>, + pin3: Option>, + pin4: Option>, +} + +impl<'d, T, G> Default for PinGroup<'d, T, G> { + fn default() -> Self { + Self { + pin1: None, + pin2: None, + pin3: None, + pin4: None, + } + } +} + +/// Defines roles and traits for TSC (Touch Sensing Controller) pins. +/// +/// This module contains marker types and traits that represent different roles +/// a TSC pin can have, such as channel, sample, or shield. +pub mod pin_roles { + use super::{OutputType, PinType}; + + /// Marker type for a TSC channel pin. + #[derive(PartialEq, Clone, Copy, Debug)] + pub struct Channel; + + /// Marker type for a TSC sampling pin. + #[derive(PartialEq, Clone, Copy, Debug)] + pub struct Sample; + + /// Marker type for a TSC shield pin. + #[derive(PartialEq, Clone, Copy, Debug)] + pub struct Shield; + + /// Trait for TSC pin roles. + /// + /// This trait defines the behavior and properties of different TSC pin roles. + /// It is implemented by the marker types `Channel`, `Sample`, and `Shield`. + pub trait Role { + /// Returns the `PinType` associated with this role. + fn pin_type() -> PinType; + + /// Returns the `OutputType` associated with this role. + fn output_type() -> OutputType; + } + + impl Role for Channel { + fn pin_type() -> PinType { + PinType::Channel + } + fn output_type() -> OutputType { + OutputType::PushPull + } + } + + impl Role for Sample { + fn pin_type() -> PinType { + PinType::Sample + } + fn output_type() -> OutputType { + OutputType::OpenDrain + } + } + + impl Role for Shield { + fn pin_type() -> PinType { + PinType::Shield + } + fn output_type() -> OutputType { + OutputType::PushPull + } + } +} + +/// Represents a group of TSC pins with their associated roles. +/// +/// This struct allows for type-safe configuration of TSC pin groups, +/// ensuring that pins are assigned appropriate roles within their group. +/// This type is essentially just a wrapper type around a `PinGroup` value. +/// +/// # Type Parameters +/// - `'d`: Lifetime of the pin group. +/// - `T`: The TSC instance type. +/// - `G`: The group identifier. +/// - `R1`, `R2`, `R3`, `R4`: Role types for each pin in the group, defaulting to `Channel`. +pub struct PinGroupWithRoles< + 'd, + T: Instance, + G, + R1 = pin_roles::Channel, + R2 = pin_roles::Channel, + R3 = pin_roles::Channel, + R4 = pin_roles::Channel, +> { + /// The underlying pin group without role information. + pub pin_group: PinGroup<'d, T, G>, + _phantom: PhantomData<(R1, R2, R3, R4)>, +} + +impl<'d, T: Instance, G, R1, R2, R3, R4> Default for PinGroupWithRoles<'d, T, G, R1, R2, R3, R4> { + fn default() -> Self { + Self { + pin_group: PinGroup::default(), + _phantom: PhantomData, + } + } +} + +impl<'d, T: Instance, G> PinGroup<'d, T, G> { + fn contains_exactly_one_shield_pin(&self) -> bool { + let shield_count = self.shield_pins().count(); + shield_count == 1 + } + + fn check_group(&self) -> Result<(), GroupError> { + let mut channel_count = 0; + let mut shield_count = 0; + let mut sample_count = 0; + for pin in self.pins().into_iter().flatten() { + match pin.role { + PinType::Channel => { + channel_count += 1; + } + PinType::Shield => { + shield_count += 1; + } + PinType::Sample => { + sample_count += 1; + } + } + } + + // Every group requires exactly one sampling capacitor + if sample_count != 1 { + return Err(GroupError::NoSamplingCapacitor); + } + + // Each group must have at least one shield or channel IO + if shield_count == 0 && channel_count == 0 { + return Err(GroupError::NoChannelOrShield); + } + + // Any group can either contain channel ios or a shield IO. + // (An active shield requires its own sampling capacitor) + if shield_count != 0 && channel_count != 0 { + return Err(GroupError::MixedChannelAndShield); + } + + // No more than one shield IO is allow per group and amongst all groups + if shield_count > 1 { + return Err(GroupError::MultipleShields); + } + + Ok(()) + } + + /// Returns a reference to the first pin in the group, if configured. + pub fn pin1(&self) -> Option<&Pin<'d, T, G>> { + self.pin1.as_ref() + } + + /// Returns a reference to the second pin in the group, if configured. + pub fn pin2(&self) -> Option<&Pin<'d, T, G>> { + self.pin2.as_ref() + } + + /// Returns a reference to the third pin in the group, if configured. + pub fn pin3(&self) -> Option<&Pin<'d, T, G>> { + self.pin3.as_ref() + } + + /// Returns a reference to the fourth pin in the group, if configured. + pub fn pin4(&self) -> Option<&Pin<'d, T, G>> { + self.pin4.as_ref() + } + + fn sample_pins(&self) -> impl Iterator + '_ { + self.pins_filtered(PinType::Sample) + } + + fn shield_pins(&self) -> impl Iterator + '_ { + self.pins_filtered(PinType::Shield) + } + + fn channel_pins(&self) -> impl Iterator + '_ { + self.pins_filtered(PinType::Channel) + } + + fn pins_filtered(&self, pin_type: PinType) -> impl Iterator + '_ { + self.pins().into_iter().filter_map(move |pin| { + pin.as_ref() + .and_then(|p| if p.role == pin_type { Some(p.tsc_io_pin) } else { None }) + }) + } + + fn make_channel_ios_mask(&self) -> u32 { + self.channel_pins().fold(0, u32::bitor) + } + + fn make_shield_ios_mask(&self) -> u32 { + self.shield_pins().fold(0, u32::bitor) + } + + fn make_sample_ios_mask(&self) -> u32 { + self.sample_pins().fold(0, u32::bitor) + } + + fn pins(&self) -> [&Option>; 4] { + [&self.pin1, &self.pin2, &self.pin3, &self.pin4] + } + + fn pins_mut(&mut self) -> [&mut Option>; 4] { + [&mut self.pin1, &mut self.pin2, &mut self.pin3, &mut self.pin4] + } +} + +#[cfg(any(tsc_v2, tsc_v3))] +macro_rules! TSC_V2_V3_GUARD { + ($e:expr) => {{ + #[cfg(any(tsc_v2, tsc_v3))] + { + $e + } + #[cfg(not(any(tsc_v2, tsc_v3)))] + { + compile_error!("Group 7 is not supported in this TSC version") + } + }}; +} + +#[cfg(tsc_v3)] +macro_rules! TSC_V3_GUARD { + ($e:expr) => {{ + #[cfg(tsc_v3)] + { + $e + } + #[cfg(not(tsc_v3))] + { + compile_error!("Group 8 is not supported in this TSC version") + } + }}; +} + +macro_rules! trait_to_io_pin { + (G1IO1Pin) => { + IOPin::Group1Io1 + }; + (G1IO2Pin) => { + IOPin::Group1Io2 + }; + (G1IO3Pin) => { + IOPin::Group1Io3 + }; + (G1IO4Pin) => { + IOPin::Group1Io4 + }; + + (G2IO1Pin) => { + IOPin::Group2Io1 + }; + (G2IO2Pin) => { + IOPin::Group2Io2 + }; + (G2IO3Pin) => { + IOPin::Group2Io3 + }; + (G2IO4Pin) => { + IOPin::Group2Io4 + }; + + (G3IO1Pin) => { + IOPin::Group3Io1 + }; + (G3IO2Pin) => { + IOPin::Group3Io2 + }; + (G3IO3Pin) => { + IOPin::Group3Io3 + }; + (G3IO4Pin) => { + IOPin::Group3Io4 + }; + + (G4IO1Pin) => { + IOPin::Group4Io1 + }; + (G4IO2Pin) => { + IOPin::Group4Io2 + }; + (G4IO3Pin) => { + IOPin::Group4Io3 + }; + (G4IO4Pin) => { + IOPin::Group4Io4 + }; + + (G5IO1Pin) => { + IOPin::Group5Io1 + }; + (G5IO2Pin) => { + IOPin::Group5Io2 + }; + (G5IO3Pin) => { + IOPin::Group5Io3 + }; + (G5IO4Pin) => { + IOPin::Group5Io4 + }; + + (G6IO1Pin) => { + IOPin::Group6Io1 + }; + (G6IO2Pin) => { + IOPin::Group6Io2 + }; + (G6IO3Pin) => { + IOPin::Group6Io3 + }; + (G6IO4Pin) => { + IOPin::Group6Io4 + }; + + (G7IO1Pin) => { + TSC_V2_V3_GUARD!(IOPin::Group7Io1) + }; + (G7IO2Pin) => { + TSC_V2_V3_GUARD!(IOPin::Group7Io2) + }; + (G7IO3Pin) => { + TSC_V2_V3_GUARD!(IOPin::Group7Io3) + }; + (G7IO4Pin) => { + TSC_V2_V3_GUARD!(IOPin::Group7Io4) + }; + + (G8IO1Pin) => { + TSC_V3_GUARD!(IOPin::Group8Io1) + }; + (G8IO2Pin) => { + TSC_V3_GUARD!(IOPin::Group8Io2) + }; + (G8IO3Pin) => { + TSC_V3_GUARD!(IOPin::Group8Io3) + }; + (G8IO4Pin) => { + TSC_V3_GUARD!(IOPin::Group8Io4) + }; +} + +macro_rules! impl_set_io { + ($method:ident, $group:ident, $trait:ident, $index:expr) => { + #[doc = concat!("Create a new pin1 for ", stringify!($group), " TSC group instance.")] + pub fn $method( + &mut self, + pin: impl Peripheral

> + 'd, + ) -> IOPinWithRole<$group, Role> { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af(pin.af_num(), AfType::output(Role::output_type(), Speed::VeryHigh)); + let tsc_io_pin = trait_to_io_pin!($trait); + let new_pin = Pin { + _pin: pin.map_into(), + role: Role::pin_type(), + tsc_io_pin, + phantom: PhantomData, + }; + *self.pin_group.pins_mut()[$index] = Some(new_pin); + IOPinWithRole { + pin: tsc_io_pin, + phantom: PhantomData, + } + }) + } + }; +} + +macro_rules! group_impl { + ($group:ident, $trait1:ident, $trait2:ident, $trait3:ident, $trait4:ident) => { + impl<'d, T: Instance, R1: pin_roles::Role, R2: pin_roles::Role, R3: pin_roles::Role, R4: pin_roles::Role> + PinGroupWithRoles<'d, T, $group, R1, R2, R3, R4> + { + impl_set_io!(set_io1, $group, $trait1, 0); + impl_set_io!(set_io2, $group, $trait2, 1); + impl_set_io!(set_io3, $group, $trait3, 2); + impl_set_io!(set_io4, $group, $trait4, 3); + } + }; +} + +group_impl!(G1, G1IO1Pin, G1IO2Pin, G1IO3Pin, G1IO4Pin); +group_impl!(G2, G2IO1Pin, G2IO2Pin, G2IO3Pin, G2IO4Pin); +group_impl!(G3, G3IO1Pin, G3IO2Pin, G3IO3Pin, G3IO4Pin); +group_impl!(G4, G4IO1Pin, G4IO2Pin, G4IO3Pin, G4IO4Pin); +group_impl!(G5, G5IO1Pin, G5IO2Pin, G5IO3Pin, G5IO4Pin); +group_impl!(G6, G6IO1Pin, G6IO2Pin, G6IO3Pin, G6IO4Pin); +#[cfg(any(tsc_v2, tsc_v3))] +group_impl!(G7, G7IO1Pin, G7IO2Pin, G7IO3Pin, G7IO4Pin); +#[cfg(tsc_v3)] +group_impl!(G8, G8IO1Pin, G8IO2Pin, G8IO3Pin, G8IO4Pin); + +/// Group 1 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G1 {} +/// Group 2 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G2 {} +/// Group 3 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G3 {} +/// Group 4 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G4 {} +/// Group 5 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G5 {} +/// Group 6 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G6 {} +/// Group 7 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G7 {} +/// Group 8 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G8 {} + +/// Represents the collection of pin groups for the Touch Sensing Controller (TSC). +/// +/// Each field corresponds to a specific group of TSC pins: +#[allow(missing_docs)] +pub struct PinGroups<'d, T: Instance> { + pub g1: Option>, + pub g2: Option>, + pub g3: Option>, + pub g4: Option>, + pub g5: Option>, + pub g6: Option>, + #[cfg(any(tsc_v2, tsc_v3))] + pub g7: Option>, + #[cfg(tsc_v3)] + pub g8: Option>, +} + +impl<'d, T: Instance> PinGroups<'d, T> { + pub(super) fn check(&self) -> Result<(), GroupError> { + let mut shield_count = 0; + + // Helper function to check a single group + fn check_group( + group: &Option>, + shield_count: &mut u32, + ) -> Result<(), GroupError> { + if let Some(group) = group { + group.check_group()?; + if group.contains_exactly_one_shield_pin() { + *shield_count += 1; + if *shield_count > 1 { + return Err(GroupError::MultipleShields); + } + } + } + Ok(()) + } + + // Check each group + check_group(&self.g1, &mut shield_count)?; + check_group(&self.g2, &mut shield_count)?; + check_group(&self.g3, &mut shield_count)?; + check_group(&self.g4, &mut shield_count)?; + check_group(&self.g5, &mut shield_count)?; + check_group(&self.g6, &mut shield_count)?; + #[cfg(any(tsc_v2, tsc_v3))] + check_group(&self.g7, &mut shield_count)?; + #[cfg(tsc_v3)] + check_group(&self.g8, &mut shield_count)?; + + Ok(()) + } + + pub(super) fn make_channel_ios_mask(&self) -> u32 { + #[allow(unused_mut)] + let mut mask = self.g1.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g2.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g3.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g4.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g5.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g6.as_ref().map_or(0, |g| g.make_channel_ios_mask()); + #[cfg(any(tsc_v2, tsc_v3))] + { + mask |= self.g7.as_ref().map_or(0, |g| g.make_channel_ios_mask()); + } + #[cfg(tsc_v3)] + { + mask |= self.g8.as_ref().map_or(0, |g| g.make_channel_ios_mask()); + } + mask + } + + pub(super) fn make_shield_ios_mask(&self) -> u32 { + #[allow(unused_mut)] + let mut mask = self.g1.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g2.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g3.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g4.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g5.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g6.as_ref().map_or(0, |g| g.make_shield_ios_mask()); + #[cfg(any(tsc_v2, tsc_v3))] + { + mask |= self.g7.as_ref().map_or(0, |g| g.make_shield_ios_mask()); + } + #[cfg(tsc_v3)] + { + mask |= self.g8.as_ref().map_or(0, |g| g.make_shield_ios_mask()); + } + mask + } + + pub(super) fn make_sample_ios_mask(&self) -> u32 { + #[allow(unused_mut)] + let mut mask = self.g1.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g2.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g3.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g4.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g5.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g6.as_ref().map_or(0, |g| g.make_sample_ios_mask()); + #[cfg(any(tsc_v2, tsc_v3))] + { + mask |= self.g7.as_ref().map_or(0, |g| g.make_sample_ios_mask()); + } + #[cfg(tsc_v3)] + { + mask |= self.g8.as_ref().map_or(0, |g| g.make_sample_ios_mask()); + } + mask + } +} + +impl<'d, T: Instance> Default for PinGroups<'d, T> { + fn default() -> Self { + Self { + g1: None, + g2: None, + g3: None, + g4: None, + g5: None, + g6: None, + #[cfg(any(tsc_v2, tsc_v3))] + g7: None, + #[cfg(tsc_v3)] + g8: None, + } + } +} + +pin_trait!(G1IO1Pin, Instance); +pin_trait!(G1IO2Pin, Instance); +pin_trait!(G1IO3Pin, Instance); +pin_trait!(G1IO4Pin, Instance); + +pin_trait!(G2IO1Pin, Instance); +pin_trait!(G2IO2Pin, Instance); +pin_trait!(G2IO3Pin, Instance); +pin_trait!(G2IO4Pin, Instance); + +pin_trait!(G3IO1Pin, Instance); +pin_trait!(G3IO2Pin, Instance); +pin_trait!(G3IO3Pin, Instance); +pin_trait!(G3IO4Pin, Instance); + +pin_trait!(G4IO1Pin, Instance); +pin_trait!(G4IO2Pin, Instance); +pin_trait!(G4IO3Pin, Instance); +pin_trait!(G4IO4Pin, Instance); + +pin_trait!(G5IO1Pin, Instance); +pin_trait!(G5IO2Pin, Instance); +pin_trait!(G5IO3Pin, Instance); +pin_trait!(G5IO4Pin, Instance); + +pin_trait!(G6IO1Pin, Instance); +pin_trait!(G6IO2Pin, Instance); +pin_trait!(G6IO3Pin, Instance); +pin_trait!(G6IO4Pin, Instance); + +pin_trait!(G7IO1Pin, Instance); +pin_trait!(G7IO2Pin, Instance); +pin_trait!(G7IO3Pin, Instance); +pin_trait!(G7IO4Pin, Instance); + +pin_trait!(G8IO1Pin, Instance); +pin_trait!(G8IO2Pin, Instance); +pin_trait!(G8IO3Pin, Instance); +pin_trait!(G8IO4Pin, Instance); diff --git a/embassy/embassy-stm32/src/tsc/tsc.rs b/embassy/embassy-stm32/src/tsc/tsc.rs new file mode 100644 index 0000000..17d2da8 --- /dev/null +++ b/embassy/embassy-stm32/src/tsc/tsc.rs @@ -0,0 +1,456 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::BitOr; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::acquisition_banks::*; +use super::config::*; +use super::errors::*; +use super::io_pin::*; +use super::pin_groups::*; +use super::types::*; +use super::{Instance, InterruptHandler, TSC_NUM_GROUPS}; +use crate::interrupt::typelevel::Interrupt; +use crate::mode::{Async, Blocking, Mode as PeriMode}; +use crate::{interrupt, rcc, Peripheral}; + +/// Internal structure holding masks for different types of TSC IOs. +/// +/// These masks are used during the initial configuration of the TSC peripheral +/// and for validating pin types during operations like creating acquisition banks. +struct IOMasks { + /// Mask representing all configured channel IOs + channel_ios: u32, + /// Mask representing all configured shield IOs + shield_ios: u32, + /// Mask representing all configured sampling IOs + sampling_ios: u32, +} + +/// TSC driver +pub struct Tsc<'d, T: Instance, K: PeriMode> { + _peri: PeripheralRef<'d, T>, + _pin_groups: PinGroups<'d, T>, + state: State, + config: Config, + masks: IOMasks, + _kind: PhantomData, +} + +impl<'d, T: Instance, K: PeriMode> Tsc<'d, T, K> { + // Helper method to check if a pin is a channel pin + fn is_channel_pin(&self, pin: IOPin) -> bool { + (self.masks.channel_ios & pin) != 0 + } + + /// Get the status of all groups involved in a AcquisitionBank + pub fn get_acquisition_bank_status(&self, bank: &AcquisitionBank) -> AcquisitionBankStatus { + let mut bank_status = AcquisitionBankStatus::default(); + for pin in bank.pins_iterator() { + let group = pin.group(); + let group_status = self.group_get_status(group); + let index: usize = group.into(); + bank_status.groups[index] = Some(group_status); + } + bank_status + } + + /// Get the values for all channels involved in a AcquisitionBank + pub fn get_acquisition_bank_values(&self, bank: &AcquisitionBank) -> AcquisitionBankReadings { + let mut bank_readings = AcquisitionBankReadings::default(); + for pin in bank.pins_iterator() { + let group = pin.group(); + let value = self.group_get_value(group); + let reading = ChannelReading { + sensor_value: value, + tsc_pin: pin, + }; + let index: usize = group.into(); + bank_readings.groups[index] = Some(reading); + } + bank_readings + } + + /// Creates a new TSC acquisition bank from the provided pin configuration. + /// + /// This method creates a `AcquisitionBank` that can be used for efficient, + /// repeated TSC acquisitions. It automatically generates the appropriate mask + /// for the provided pins. + /// + /// # Note on TSC Hardware Limitation + /// + /// The TSC hardware can only read one channel pin from each TSC group per acquisition. + /// + /// # Arguments + /// * `acquisition_bank_pins` - The pin configuration for the acquisition bank. + /// + /// # Returns + /// A new `AcquisitionBank` instance. + /// + /// # Example + /// + /// ``` + /// let tsc = // ... initialize TSC + /// let tsc_sensor1: tsc::IOPinWithRole = ...; + /// let tsc_sensor2: tsc::IOPinWithRole = ...; + /// + /// let bank = tsc.create_acquisition_bank(AcquisitionBankPins { + /// g1_pin: Some(tsc_sensor1), + /// g2_pin: Some(tsc_sensor2), + /// ..Default::default() + /// }); + /// + /// // Use the bank for acquisitions + /// tsc.set_active_channels_bank(&bank); + /// tsc.start(); + /// // ... perform acquisition ... + /// ``` + pub fn create_acquisition_bank(&self, acquisition_bank_pins: AcquisitionBankPins) -> AcquisitionBank { + let bank_mask = acquisition_bank_pins.iter().fold(0u32, BitOr::bitor); + + AcquisitionBank { + pins: acquisition_bank_pins, + mask: bank_mask, + } + } + + fn make_channels_mask(&self, channels: Itt) -> Result + where + Itt: IntoIterator, + { + let mut group_mask = 0u32; + let mut channel_mask = 0u32; + + for channel in channels { + if !self.is_channel_pin(channel) { + return Err(AcquisitionBankError::InvalidChannelPin); + } + + let group = channel.group(); + let group_bit: u32 = 1 << Into::::into(group); + if group_mask & group_bit != 0 { + return Err(AcquisitionBankError::MultipleChannelsPerGroup); + } + + group_mask |= group_bit; + channel_mask |= channel; + } + + Ok(channel_mask) + } + + /// Sets the active channels for the next TSC acquisition. + /// + /// This is a low-level method that directly sets the channel mask. For most use cases, + /// consider using `set_active_channels_bank` with a `AcquisitionBank` instead, which + /// provides a higher-level interface and additional safety checks. + /// + /// This method configures which sensor channels will be read during the next + /// touch sensing acquisition cycle. It should be called before starting a new + /// acquisition with the start() method. + /// + /// # Arguments + /// * `mask` - A 32-bit mask where each bit represents a channel. Set bits indicate + /// active channels. + /// + /// # Note + /// Only one pin from each TSC group can be read for each acquisition. This method + /// does not perform checks to ensure this limitation is met. Incorrect masks may + /// lead to unexpected behavior. + /// + /// # Safety + /// This method doesn't perform extensive checks on the provided mask. Ensure that + /// the mask is valid and adheres to hardware limitations to avoid undefined behavior. + pub fn set_active_channels_mask(&mut self, mask: u32) { + T::regs().ioccr().write(|w| w.0 = mask | self.masks.shield_ios); + } + + /// Convenience method for setting active channels directly from a slice of tsc::IOPin. + /// This method performs safety checks but is less efficient for repeated use. + pub fn set_active_channels(&mut self, channels: &[IOPin]) -> Result<(), AcquisitionBankError> { + let mask = self.make_channels_mask(channels.iter().cloned())?; + self.set_active_channels_mask(mask); + Ok(()) + } + + /// Sets the active channels for the next TSC acquisition using a pre-configured acquisition bank. + /// + /// This method efficiently configures the TSC peripheral to read the channels specified + /// in the provided `AcquisitionBank`. It's the recommended way to set up + /// channel configurations for acquisition, especially when using the same set of channels repeatedly. + /// + /// # Arguments + /// + /// * `bank` - A reference to a `AcquisitionBank` containing the pre-configured + /// TSC channel mask. + /// + /// # Example + /// + /// ``` + /// let tsc_sensor1: tsc::IOPinWithRole = ...; + /// let tsc_sensor2: tsc::IOPinWithRole = ...; + /// let mut touch_controller: Tsc<'_, TSC, Async> = ...; + /// let bank = touch_controller.create_acquisition_bank(AcquisitionBankPins { + /// g1_pin: Some(tsc_sensor1), + /// g2_pin: Some(tsc_sensor2), + /// ..Default::default() + /// }); + /// + /// touch_controller.set_active_channels_bank(&bank); + /// touch_controller.start(); + /// // ... perform acquisition ... + /// ``` + /// + /// This method should be called before starting a new acquisition with the `start()` method. + pub fn set_active_channels_bank(&mut self, bank: &AcquisitionBank) { + self.set_active_channels_mask(bank.mask) + } + + fn extract_groups(io_mask: u32) -> u32 { + let mut groups: u32 = 0; + for idx in 0..TSC_NUM_GROUPS { + if io_mask & (0x0F << (idx * 4)) != 0 { + groups |= 1 << idx + } + } + groups + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + pin_groups: PinGroups<'d, T>, + config: Config, + ) -> Result { + into_ref!(peri); + + pin_groups.check()?; + + let masks = IOMasks { + channel_ios: pin_groups.make_channel_ios_mask(), + shield_ios: pin_groups.make_shield_ios_mask(), + sampling_ios: pin_groups.make_sample_ios_mask(), + }; + + rcc::enable_and_reset::(); + + T::regs().cr().modify(|w| { + w.set_tsce(true); + w.set_ctph(config.ct_pulse_high_length.into()); + w.set_ctpl(config.ct_pulse_low_length.into()); + w.set_sse(config.spread_spectrum); + // Prevent invalid configuration for pulse generator prescaler + if config.ct_pulse_low_length == ChargeTransferPulseCycle::_1 + && (config.pulse_generator_prescaler == PGPrescalerDivider::_1 + || config.pulse_generator_prescaler == PGPrescalerDivider::_2) + { + w.set_pgpsc(PGPrescalerDivider::_4.into()); + } else if config.ct_pulse_low_length == ChargeTransferPulseCycle::_2 + && config.pulse_generator_prescaler == PGPrescalerDivider::_1 + { + w.set_pgpsc(PGPrescalerDivider::_2.into()); + } else { + w.set_pgpsc(config.pulse_generator_prescaler.into()); + } + w.set_ssd(config.spread_spectrum_deviation.into()); + w.set_sspsc(config.spread_spectrum_prescaler); + + w.set_mcv(config.max_count_value.into()); + w.set_syncpol(config.synchro_pin_polarity); + w.set_am(config.acquisition_mode); + }); + + // Set IO configuration + // Disable Schmitt trigger hysteresis on all used TSC IOs + T::regs() + .iohcr() + .write(|w| w.0 = !(masks.channel_ios | masks.shield_ios | masks.sampling_ios)); + + // Set channel and shield IOs + T::regs().ioccr().write(|w| w.0 = masks.channel_ios | masks.shield_ios); + + // Set sampling IOs + T::regs().ioscr().write(|w| w.0 = masks.sampling_ios); + + // Set the groups to be acquired + // Lower bits of `iogcsr` are for enabling groups, while the higher bits are for reading + // status of acquisiton for a group, see method `Tsc::group_get_status`. + T::regs() + .iogcsr() + .write(|w| w.0 = Self::extract_groups(masks.channel_ios)); + + // Disable interrupts + T::regs().ier().modify(|w| { + w.set_eoaie(false); + w.set_mceie(false); + }); + + // Clear flags + T::regs().icr().modify(|w| { + w.set_eoaic(true); + w.set_mceic(true); + }); + + unsafe { + T::Interrupt::enable(); + } + + Ok(Self { + _peri: peri, + _pin_groups: pin_groups, + state: State::Ready, + config, + masks, + _kind: PhantomData, + }) + } + + /// Start charge transfer acquisition + pub fn start(&mut self) { + self.state = State::Busy; + + // Disable interrupts + T::regs().ier().modify(|w| { + w.set_eoaie(false); + w.set_mceie(false); + }); + + // Clear flags + T::regs().icr().modify(|w| { + w.set_eoaic(true); + w.set_mceic(true); + }); + + // Set the touch sensing IOs not acquired to the default mode + T::regs().cr().modify(|w| { + w.set_iodef(self.config.io_default_mode); + }); + + // Start the acquisition + T::regs().cr().modify(|w| { + w.set_start(true); + }); + } + + /// Stop charge transfer acquisition + pub fn stop(&mut self) { + T::regs().cr().modify(|w| { + w.set_start(false); + }); + + // Set the touch sensing IOs in low power mode + T::regs().cr().modify(|w| { + w.set_iodef(false); + }); + + // Clear flags + T::regs().icr().modify(|w| { + w.set_eoaic(true); + w.set_mceic(true); + }); + + self.state = State::Ready; + } + + /// Get current state of acquisition + pub fn get_state(&mut self) -> State { + if self.state == State::Busy && T::regs().isr().read().eoaf() { + if T::regs().isr().read().mcef() { + self.state = State::Error + } else { + self.state = State::Ready + } + } + self.state + } + + /// Get the individual group status to check acquisition complete + pub fn group_get_status(&self, index: Group) -> GroupStatus { + // Status bits are set by hardware when the acquisition on the corresponding + // enabled analog IO group is complete, cleared when new acquisition is started + let status = match index { + Group::One => T::regs().iogcsr().read().g1s(), + Group::Two => T::regs().iogcsr().read().g2s(), + Group::Three => T::regs().iogcsr().read().g3s(), + Group::Four => T::regs().iogcsr().read().g4s(), + Group::Five => T::regs().iogcsr().read().g5s(), + Group::Six => T::regs().iogcsr().read().g6s(), + #[cfg(any(tsc_v2, tsc_v3))] + Group::Seven => T::regs().iogcsr().read().g7s(), + #[cfg(tsc_v3)] + Group::Eight => T::regs().iogcsr().read().g8s(), + }; + match status { + true => GroupStatus::Complete, + false => GroupStatus::Ongoing, + } + } + + /// Get the count for the acquisiton, valid once group status is set + pub fn group_get_value(&self, index: Group) -> u16 { + T::regs().iogcr(index.into()).read().cnt() + } + + /// Discharge the IOs for subsequent acquisition + pub fn discharge_io(&mut self, status: bool) { + // Set the touch sensing IOs in low power mode + T::regs().cr().modify(|w| { + w.set_iodef(!status); + }); + } +} + +impl<'d, T: Instance, K: PeriMode> Drop for Tsc<'d, T, K> { + fn drop(&mut self) { + rcc::disable::(); + } +} + +impl<'d, T: Instance> Tsc<'d, T, Async> { + /// Create a Tsc instance that can be awaited for completion + pub fn new_async( + peri: impl Peripheral

+ 'd, + pin_groups: PinGroups<'d, T>, + config: Config, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Result { + Self::new_inner(peri, pin_groups, config) + } + + /// Asyncronously wait for the end of an acquisition + pub async fn pend_for_acquisition(&mut self) { + poll_fn(|cx| match self.get_state() { + State::Busy => { + T::waker().register(cx.waker()); + T::regs().ier().write(|w| w.set_eoaie(true)); + if self.get_state() != State::Busy { + T::regs().ier().write(|w| w.set_eoaie(false)); + return Poll::Ready(()); + } + Poll::Pending + } + _ => { + T::regs().ier().write(|w| w.set_eoaie(false)); + Poll::Ready(()) + } + }) + .await; + } +} + +impl<'d, T: Instance> Tsc<'d, T, Blocking> { + /// Create a Tsc instance that must be polled for completion + pub fn new_blocking( + peri: impl Peripheral

+ 'd, + pin_groups: PinGroups<'d, T>, + config: Config, + ) -> Result { + Self::new_inner(peri, pin_groups, config) + } + + /// Wait for end of acquisition + pub fn poll_for_acquisition(&mut self) { + while self.get_state() == State::Busy {} + } +} diff --git a/embassy/embassy-stm32/src/tsc/types.rs b/embassy/embassy-stm32/src/tsc/types.rs new file mode 100644 index 0000000..0e8fa7f --- /dev/null +++ b/embassy/embassy-stm32/src/tsc/types.rs @@ -0,0 +1,93 @@ +/// Peripheral state +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(PartialEq, Clone, Copy)] +pub enum State { + /// Peripheral is being setup or reconfigured + Reset, + /// Ready to start acquisition + Ready, + /// In process of sensor acquisition + Busy, + /// Error occured during acquisition + Error, +} + +/// Individual group status checked after acquisition reported as complete +/// For groups with multiple channel pins, may take longer because acquisitions +/// are done sequentially. Check this status before pulling count for each +/// sampled channel +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(PartialEq, Clone, Copy)] +pub enum GroupStatus { + /// Acquisition for channel still in progress + Ongoing, + /// Acquisition either not started or complete + Complete, +} + +/// Group identifier used to interrogate status +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(missing_docs)] +#[derive(PartialEq, Clone, Copy)] +pub enum Group { + One, + Two, + Three, + Four, + Five, + Six, + #[cfg(any(tsc_v2, tsc_v3))] + Seven, + #[cfg(tsc_v3)] + Eight, +} + +impl Into for Group { + fn into(self) -> usize { + match self { + Group::One => 0, + Group::Two => 1, + Group::Three => 2, + Group::Four => 3, + Group::Five => 4, + Group::Six => 5, + #[cfg(any(tsc_v2, tsc_v3))] + Group::Seven => 6, + #[cfg(tsc_v3)] + Group::Eight => 7, + } + } +} + +/// Error returned when attempting to create a Group from an invalid numeric value. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InvalidGroupError { + invalid_value: usize, +} + +impl InvalidGroupError { + #[allow(missing_docs)] + pub fn new(value: usize) -> Self { + Self { invalid_value: value } + } +} + +impl TryFrom for Group { + type Error = InvalidGroupError; + + fn try_from(value: usize) -> Result { + match value { + 0 => Ok(Group::One), + 1 => Ok(Group::Two), + 2 => Ok(Group::Three), + 3 => Ok(Group::Four), + 4 => Ok(Group::Five), + 5 => Ok(Group::Six), + #[cfg(any(tsc_v2, tsc_v3))] + 6 => Ok(Group::Two), + #[cfg(tsc_v3)] + 7 => Ok(Group::Two), + n => Err(InvalidGroupError::new(n)), + } + } +} diff --git a/embassy/embassy-stm32/src/ucpd.rs b/embassy/embassy-stm32/src/ucpd.rs new file mode 100644 index 0000000..ee0a2c7 --- /dev/null +++ b/embassy/embassy-stm32/src/ucpd.rs @@ -0,0 +1,669 @@ +//! USB Type-C/USB Power Delivery Interface (UCPD) + +// Implementation Notes +// +// As of Feb. 2024 the UCPD peripheral is availalbe on: G0, G4, H5, L5, U5 +// +// Cube HAL LL Driver (g0): +// https://github.com/STMicroelectronics/stm32g0xx_hal_driver/blob/v1.4.6/Inc/stm32g0xx_ll_ucpd.h +// https://github.com/STMicroelectronics/stm32g0xx_hal_driver/blob/v1.4.6/Src/stm32g0xx_ll_ucpd.c +// Except for a the `LL_UCPD_RxAnalogFilterEnable/Disable()` functions the Cube HAL implementation of +// all families is the same. +// +// Dead battery pull-down resistors functionality is enabled by default on startup and must +// be disabled by setting a bit in PWR/SYSCFG registers. The exact name and location for that +// bit is different for each familily. + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, Peripheral}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::dma::{ChannelAndRequest, TransferOptions}; +use crate::interrupt; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk, Txmode}; +pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, Rxordset, TypecVstateCc as CcVState}; +use crate::rcc::{self, RccPeripheral}; + +pub(crate) fn init( + _cs: critical_section::CriticalSection, + #[cfg(peri_ucpd1)] ucpd1_db_enable: bool, + #[cfg(peri_ucpd2)] ucpd2_db_enable: bool, +) { + #[cfg(stm32g0x1)] + { + // according to RM0444 (STM32G0x1) section 8.1.1: + // when UCPD is disabled setting the strobe will disable dead battery + // (which is enabled after reset) but if UCPD is enabled, setting the + // strobe will apply the CC pin configuration from the control register + // (which is why we need to be careful about when we call this) + crate::pac::SYSCFG.cfgr1().modify(|w| { + w.set_ucpd1_strobe(!ucpd1_db_enable); + w.set_ucpd2_strobe(!ucpd2_db_enable); + }); + } + + #[cfg(any(stm32g4, stm32l5))] + { + crate::pac::PWR.cr3().modify(|w| { + #[cfg(stm32g4)] + w.set_ucpd1_dbdis(!ucpd1_db_enable); + #[cfg(stm32l5)] + w.set_ucpd_dbdis(!ucpd1_db_enable); + }) + } + + #[cfg(any(stm32h5, stm32u5, stm32h7rs))] + { + crate::pac::PWR.ucpdr().modify(|w| { + w.set_ucpd_dbdis(!ucpd1_db_enable); + }) + } +} + +/// Pull-up or Pull-down resistor state of both CC lines. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CcPull { + /// Analog PHY for CC pin disabled. + Disabled, + + /// Rd=5.1k pull-down resistor. + Sink, + + /// Rp=56k pull-up resistor to indicate default USB power. + SourceDefaultUsb, + + /// Rp=22k pull-up resistor to indicate support for up to 1.5A. + Source1_5A, + + /// Rp=10k pull-up resistor to indicate support for up to 3.0A. + Source3_0A, +} + +/// UCPD configuration +#[non_exhaustive] +#[derive(Copy, Clone, Debug)] +pub struct Config { + /// Receive SOP packets + pub sop: bool, + /// Receive SOP' packets + pub sop_prime: bool, + /// Receive SOP'' packets + pub sop_double_prime: bool, + /// Receive SOP'_Debug packets + pub sop_prime_debug: bool, + /// Receive SOP''_Debug packets + pub sop_double_prime_debug: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + sop: true, + sop_prime: false, + sop_double_prime: false, + sop_prime_debug: false, + sop_double_prime_debug: false, + } + } +} + +/// UCPD driver. +pub struct Ucpd<'d, T: Instance> { + cc_phy: CcPhy<'d, T>, +} + +impl<'d, T: Instance> Ucpd<'d, T> { + /// Creates a new UCPD driver instance. + pub fn new( + _peri: impl Peripheral

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

> + 'd, + cc2: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(cc1, cc2); + cc1.set_as_analog(); + cc2.set_as_analog(); + + rcc::enable_and_reset::(); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let r = T::REGS; + r.cfgr1().write(|w| { + // "The receiver is designed to work in the clock frequency range from 6 to 18 MHz. + // However, the optimum performance is ensured in the range from 6 to 12 MHz" + // UCPD is driven by HSI16 (16MHz internal oscillator), which we need to divide by 2. + w.set_psc_usbpdclk(PscUsbpdclk::DIV2); + + // Prescaler to produce a target half-bit frequency of 600kHz which is required + // to produce transmit with a nominal nominal bit rate of 300Kbps+-10% using + // biphase mark coding (BMC, aka differential manchester coding). + // A divider of 13 gives the target frequency closest to spec (~615kHz, 1.625us). + w.set_hbitclkdiv(13 - 1); + + // Time window for detecting non-idle (12-20us). + // 1.75us * 8 = 14us. + w.set_transwin(8 - 1); + + // Time from the end of last bit of a Frame until the start of the first bit of the + // next Preamble (min 25us). + // 1.75us * 17 = ~30us + w.set_ifrgap(17 - 1); + + // UNDOCUMENTED: This register can only be written while UCPDEN=0 (found by testing). + let rxordset = (config.sop as u16) << 0 + | (config.sop_prime as u16) << 1 + | (config.sop_double_prime as u16) << 2 + // Hard reset + | 0x1 << 3 + | (config.sop_prime_debug as u16) << 4 + | (config.sop_double_prime_debug as u16) << 5; + w.set_rxordseten(rxordset); + + // Enable DMA + w.set_txdmaen(true); + w.set_rxdmaen(true); + + w.set_ucpden(true); + }); + + Self { + cc_phy: CcPhy { _lifetime: PhantomData }, + } + } + + /// Returns the TypeC CC PHY. + pub fn cc_phy(&mut self) -> &mut CcPhy<'d, T> { + &mut self.cc_phy + } + + /// Splits the UCPD driver into a TypeC PHY to control and monitor CC voltage + /// and a Power Delivery (PD) PHY with receiver and transmitter. + pub fn split_pd_phy( + self, + rx_dma: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

> + 'd, + cc_sel: CcSel, + ) -> (CcPhy<'d, T>, PdPhy<'d, T>) { + let r = T::REGS; + + // TODO: Currently only SOP messages are supported. + r.tx_ordsetr().write(|w| w.set_txordset(0b10001_11000_11000_11000)); + + // Enable the receiver on one of the two CC lines. + r.cr().modify(|w| w.set_phyccsel(cc_sel)); + + // Enable hard reset receive interrupt. + r.imr().modify(|w| w.set_rxhrstdetie(true)); + + // Enable PD packet reception + r.cr().modify(|w| w.set_phyrxen(true)); + + // Both parts must be dropped before the peripheral can be disabled. + T::state().drop_not_ready.store(true, Ordering::Relaxed); + + into_ref!(rx_dma, tx_dma); + let rx_dma_req = rx_dma.request(); + let tx_dma_req = tx_dma.request(); + ( + self.cc_phy, + PdPhy { + _lifetime: PhantomData, + rx_dma: ChannelAndRequest { + channel: rx_dma.map_into(), + request: rx_dma_req, + }, + tx_dma: ChannelAndRequest { + channel: tx_dma.map_into(), + request: tx_dma_req, + }, + }, + ) + } +} + +/// Control and monitoring of TypeC CC pin functionailty. +pub struct CcPhy<'d, T: Instance> { + _lifetime: PhantomData<&'d mut T>, +} + +impl<'d, T: Instance> Drop for CcPhy<'d, T> { + fn drop(&mut self) { + let r = T::REGS; + r.cr().modify(|w| { + w.set_cc1tcdis(true); + w.set_cc2tcdis(true); + w.set_ccenable(Ccenable::DISABLED); + }); + + // Check if the PdPhy part was dropped already. + let drop_not_ready = &T::state().drop_not_ready; + if drop_not_ready.load(Ordering::Relaxed) { + drop_not_ready.store(true, Ordering::Relaxed); + } else { + r.cfgr1().write(|w| w.set_ucpden(false)); + rcc::disable::(); + T::Interrupt::disable(); + } + } +} + +impl<'d, T: Instance> CcPhy<'d, T> { + /// Sets the pull-up/pull-down resistor values exposed on the CC pins. + pub fn set_pull(&mut self, cc_pull: CcPull) { + T::REGS.cr().modify(|w| { + w.set_anamode(if cc_pull == CcPull::Sink { + Anamode::SINK + } else { + Anamode::SOURCE + }); + w.set_anasubmode(match cc_pull { + CcPull::SourceDefaultUsb => 1, + CcPull::Source1_5A => 2, + CcPull::Source3_0A => 3, + _ => 0, + }); + w.set_ccenable(if cc_pull == CcPull::Disabled { + Ccenable::DISABLED + } else { + Ccenable::BOTH + }); + }); + + // Disable dead-battery pull-down resistors which are enabled by default on boot. + critical_section::with(|cs| { + init( + cs, + false, + #[cfg(peri_ucpd2)] + false, + ); + }); + } + + /// Returns the current voltage level of CC1 and CC2 pin as tuple. + /// + /// Interpretation of the voltage levels depends on the configured CC line + /// pull-up/pull-down resistance. + pub fn vstate(&self) -> (CcVState, CcVState) { + let sr = T::REGS.sr().read(); + (sr.typec_vstate_cc1(), sr.typec_vstate_cc2()) + } + + /// Waits for a change in voltage state on either CC line. + pub async fn wait_for_vstate_change(&self) -> (CcVState, CcVState) { + let _on_drop = OnDrop::new(|| self.enable_cc_interrupts(false)); + let prev_vstate = self.vstate(); + poll_fn(|cx| { + let vstate = self.vstate(); + if vstate != prev_vstate { + Poll::Ready(vstate) + } else { + T::state().waker.register(cx.waker()); + self.enable_cc_interrupts(true); + Poll::Pending + } + }) + .await + } + + fn enable_cc_interrupts(&self, enable: bool) { + T::REGS.imr().modify(|w| { + w.set_typecevt1ie(enable); + w.set_typecevt2ie(enable); + }); + } +} + +/// Receive SOP. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Sop { + /// SOP + Sop, + /// SOP' + SopPrime, + /// SOP'' + SopDoublePrime, + /// SOP'_Debug + SopPrimeDebug, + /// SOP''_Debug + SopDoublePrimeDebug, +} + +/// Receive Error. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RxError { + /// Incorrect CRC or truncated message (a line becoming static before EOP is met). + Crc, + + /// Provided buffer was too small for the received message. + Overrun, + + /// Hard Reset received before or during reception. + HardReset, +} + +/// Transmit Error. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TxError { + /// Concurrent receive in progress or excessive noise on the line. + Discarded, + + /// Hard Reset received before or during transmission. + HardReset, +} + +/// Power Delivery (PD) PHY. +pub struct PdPhy<'d, T: Instance> { + _lifetime: PhantomData<&'d mut T>, + rx_dma: ChannelAndRequest<'d>, + tx_dma: ChannelAndRequest<'d>, +} + +impl<'d, T: Instance> Drop for PdPhy<'d, T> { + fn drop(&mut self) { + T::REGS.cr().modify(|w| w.set_phyrxen(false)); + // Check if the Type-C part was dropped already. + let drop_not_ready = &T::state().drop_not_ready; + if drop_not_ready.load(Ordering::Relaxed) { + drop_not_ready.store(true, Ordering::Relaxed); + } else { + T::REGS.cfgr1().write(|w| w.set_ucpden(false)); + rcc::disable::(); + T::Interrupt::disable(); + } + } +} + +impl<'d, T: Instance> PdPhy<'d, T> { + /// Receives a PD message into the provided buffer. + /// + /// Returns the number of received bytes or an error. + pub async fn receive(&mut self, buf: &mut [u8]) -> Result { + self.receive_with_sop(buf).await.map(|(_sop, size)| size) + } + + /// Receives SOP and a PD message into the provided buffer. + /// + /// Returns the start of packet type and number of received bytes or an error. + pub async fn receive_with_sop(&mut self, buf: &mut [u8]) -> Result<(Sop, usize), RxError> { + let r = T::REGS; + + let dma = unsafe { + self.rx_dma + .read(r.rxdr().as_ptr() as *mut u8, buf, TransferOptions::default()) + }; + + let _on_drop = OnDrop::new(|| { + Self::enable_rx_interrupt(false); + // Clear interrupt flags + r.icr().write(|w| { + w.set_rxorddetcf(true); + w.set_rxovrcf(true); + w.set_rxmsgendcf(true); + }); + }); + + poll_fn(|cx| { + let sr = r.sr().read(); + if sr.rxhrstdet() { + // Clean and re-enable hard reset receive interrupt. + r.icr().write(|w| w.set_rxhrstdetcf(true)); + r.imr().modify(|w| w.set_rxhrstdetie(true)); + Poll::Ready(Err(RxError::HardReset)) + } else if sr.rxmsgend() { + let ret = if sr.rxovr() { + Err(RxError::Overrun) + } else if sr.rxerr() { + Err(RxError::Crc) + } else { + Ok(()) + }; + Poll::Ready(ret) + } else { + T::state().waker.register(cx.waker()); + Self::enable_rx_interrupt(true); + Poll::Pending + } + }) + .await?; + + // Make sure that the last byte was fetched by DMA. + while r.sr().read().rxne() { + if dma.get_remaining_transfers() == 0 { + return Err(RxError::Overrun); + } + } + + let sop = match r.rx_ordsetr().read().rxordset() { + Rxordset::SOP => Sop::Sop, + Rxordset::SOPPRIME => Sop::SopPrime, + Rxordset::SOPDOUBLEPRIME => Sop::SopDoublePrime, + Rxordset::SOPPRIMEDEBUG => Sop::SopPrimeDebug, + Rxordset::SOPDOUBLEPRIMEDEBUG => Sop::SopDoublePrimeDebug, + Rxordset::CABLERESET => return Err(RxError::HardReset), + // Extension headers are not supported + _ => unreachable!(), + }; + + Ok((sop, r.rx_payszr().read().rxpaysz().into())) + } + + fn enable_rx_interrupt(enable: bool) { + T::REGS.imr().modify(|w| w.set_rxmsgendie(enable)); + } + + /// Transmits a PD message. + pub async fn transmit(&mut self, buf: &[u8]) -> Result<(), TxError> { + let r = T::REGS; + + // When a previous transmission was dropped before it had finished it + // might still be running because there is no way to abort an ongoing + // message transmission. Wait for it to finish but ignore errors. + if r.cr().read().txsend() { + if let Err(TxError::HardReset) = Self::wait_tx_done().await { + return Err(TxError::HardReset); + } + } + + // Clear the TX interrupt flags. + T::REGS.icr().write(|w| { + w.set_txmsgdisccf(true); + w.set_txmsgsentcf(true); + }); + + // Start the DMA and let it do its thing in the background. + let _dma = unsafe { + self.tx_dma + .write(buf, r.txdr().as_ptr() as *mut u8, TransferOptions::default()) + }; + + // Configure and start the transmission. + r.tx_payszr().write(|w| w.set_txpaysz(buf.len() as _)); + r.cr().modify(|w| { + w.set_txmode(Txmode::PACKET); + w.set_txsend(true); + }); + + Self::wait_tx_done().await + } + + async fn wait_tx_done() -> Result<(), TxError> { + let _on_drop = OnDrop::new(|| Self::enable_tx_interrupts(false)); + poll_fn(|cx| { + let r = T::REGS; + let sr = r.sr().read(); + if sr.rxhrstdet() { + // Clean and re-enable hard reset receive interrupt. + r.icr().write(|w| w.set_rxhrstdetcf(true)); + r.imr().modify(|w| w.set_rxhrstdetie(true)); + Poll::Ready(Err(TxError::HardReset)) + } else if sr.txmsgdisc() { + Poll::Ready(Err(TxError::Discarded)) + } else if sr.txmsgsent() { + Poll::Ready(Ok(())) + } else { + T::state().waker.register(cx.waker()); + Self::enable_tx_interrupts(true); + Poll::Pending + } + }) + .await + } + + fn enable_tx_interrupts(enable: bool) { + T::REGS.imr().modify(|w| { + w.set_txmsgdiscie(enable); + w.set_txmsgsentie(enable); + }); + } + + /// Transmit a hard reset. + pub async fn transmit_hardreset(&mut self) -> Result<(), TxError> { + let r = T::REGS; + + // Clear the hardreset interrupt flags. + T::REGS.icr().write(|w| { + w.set_hrstdisccf(true); + w.set_hrstsentcf(true); + }); + + // Trigger hard reset transmission. + r.cr().modify(|w| { + w.set_txhrst(true); + }); + + let _on_drop = OnDrop::new(|| self.enable_hardreset_interrupts(false)); + poll_fn(|cx| { + let r = T::REGS; + let sr = r.sr().read(); + if sr.rxhrstdet() { + // Clean and re-enable hard reset receive interrupt. + r.icr().write(|w| w.set_rxhrstdetcf(true)); + r.imr().modify(|w| w.set_rxhrstdetie(true)); + Poll::Ready(Err(TxError::HardReset)) + } else if sr.hrstdisc() { + Poll::Ready(Err(TxError::Discarded)) + } else if sr.hrstsent() { + Poll::Ready(Ok(())) + } else { + T::state().waker.register(cx.waker()); + self.enable_hardreset_interrupts(true); + Poll::Pending + } + }) + .await + } + + fn enable_hardreset_interrupts(&self, enable: bool) { + T::REGS.imr().modify(|w| { + w.set_hrstdiscie(enable); + w.set_hrstsentie(enable); + }); + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::REGS; + let sr = r.sr().read(); + + if sr.typecevt1() || sr.typecevt2() { + r.icr().write(|w| { + w.set_typecevt1cf(true); + w.set_typecevt2cf(true); + }); + } + + if sr.rxhrstdet() { + r.imr().modify(|w| w.set_rxhrstdetie(false)); + } + + if sr.rxmsgend() { + r.imr().modify(|w| w.set_rxmsgendie(false)); + } + + if sr.txmsgdisc() || sr.txmsgsent() { + r.imr().modify(|w| { + w.set_txmsgdiscie(false); + w.set_txmsgsentie(false); + }); + } + + if sr.hrstdisc() || sr.hrstsent() { + r.imr().modify(|w| { + w.set_hrstdiscie(false); + w.set_hrstsentie(false); + }); + } + + // Wake the task to clear and re-enabled interrupts. + T::state().waker.wake(); + } +} + +struct State { + waker: AtomicWaker, + // Inverted logic for a default state of 0 so that the data goes into the .bss section. + drop_not_ready: AtomicBool, +} + +impl State { + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + drop_not_ready: AtomicBool::new(false), + } + } +} + +trait SealedInstance { + const REGS: crate::pac::ucpd::Ucpd; + fn state() -> &'static State; +} + +/// UCPD instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral { + /// Interrupt for this instance. + type Interrupt: crate::interrupt::typelevel::Interrupt; +} + +foreach_interrupt!( + ($inst:ident, ucpd, UCPD, GLOBAL, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + const REGS: crate::pac::ucpd::Ucpd = crate::pac::$inst; + + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for crate::peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +); + +pin_trait!(Cc1Pin, Instance); +pin_trait!(Cc2Pin, Instance); + +dma_trait!(TxDma, Instance); +dma_trait!(RxDma, Instance); diff --git a/embassy/embassy-stm32/src/uid.rs b/embassy/embassy-stm32/src/uid.rs new file mode 100644 index 0000000..5e38532 --- /dev/null +++ b/embassy/embassy-stm32/src/uid.rs @@ -0,0 +1,31 @@ +//! Unique ID (UID) + +/// Get this device's unique 96-bit ID. +pub fn uid() -> &'static [u8; 12] { + unsafe { &*crate::pac::UID.uid(0).as_ptr().cast::<[u8; 12]>() } +} + +/// Get this device's unique 96-bit ID, encoded into a string of 24 hexadecimal ASCII digits. +pub fn uid_hex() -> &'static str { + unsafe { core::str::from_utf8_unchecked(uid_hex_bytes()) } +} + +/// Get this device's unique 96-bit ID, encoded into 24 hexadecimal ASCII bytes. +pub fn uid_hex_bytes() -> &'static [u8; 24] { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + static mut UID_HEX: [u8; 24] = [0; 24]; + static mut LOADED: bool = false; + critical_section::with(|_| unsafe { + if !LOADED { + let uid = uid(); + for (idx, v) in uid.iter().enumerate() { + let lo = v & 0x0f; + let hi = (v & 0xf0) >> 4; + UID_HEX[idx * 2] = HEX[hi as usize]; + UID_HEX[idx * 2 + 1] = HEX[lo as usize]; + } + LOADED = true; + } + }); + unsafe { &*core::ptr::addr_of!(UID_HEX) } +} diff --git a/embassy/embassy-stm32/src/usart/buffered.rs b/embassy/embassy-stm32/src/usart/buffered.rs new file mode 100644 index 0000000..814be28 --- /dev/null +++ b/embassy/embassy-stm32/src/usart/buffered.rs @@ -0,0 +1,927 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::slice; +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use core::task::Poll; + +use embassy_embedded_hal::SetConfig; +use embassy_hal_internal::atomic_ring_buffer::RingBuffer; +use embassy_hal_internal::{Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +#[cfg(not(any(usart_v1, usart_v2)))] +use super::DePin; +use super::{ + clear_interrupt_flags, configure, rdr, reconfigure, send_break, set_baudrate, sr, tdr, Config, ConfigError, CtsPin, + Error, Info, Instance, Regs, RtsPin, RxPin, TxPin, +}; +use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; +use crate::interrupt::{self, InterruptExt}; +use crate::time::Hertz; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + on_interrupt(T::info().regs, T::buffered_state()) + } +} + +unsafe fn on_interrupt(r: Regs, state: &'static State) { + // RX + let sr_val = sr(r).read(); + // On v1 & v2, reading DR clears the rxne, error and idle interrupt + // flags. Keep this close to the SR read to reduce the chance of a + // flag being set in-between. + let dr = if sr_val.rxne() || cfg!(any(usart_v1, usart_v2)) && (sr_val.ore() || sr_val.idle()) { + Some(rdr(r).read_volatile()) + } else { + None + }; + clear_interrupt_flags(r, sr_val); + + if sr_val.pe() { + warn!("Parity error"); + } + if sr_val.fe() { + warn!("Framing error"); + } + if sr_val.ne() { + warn!("Noise error"); + } + if sr_val.ore() { + warn!("Overrun error"); + } + if sr_val.rxne() { + let mut rx_writer = state.rx_buf.writer(); + let buf = rx_writer.push_slice(); + if !buf.is_empty() { + if let Some(byte) = dr { + buf[0] = byte; + rx_writer.push_done(1); + } + } else { + // FIXME: Should we disable any further RX interrupts when the buffer becomes full. + } + + if !state.rx_buf.is_empty() { + state.rx_waker.wake(); + } + } + + if sr_val.idle() { + state.rx_waker.wake(); + } + + // With `usart_v4` hardware FIFO is enabled and Transmission complete (TC) + // indicates that all bytes are pushed out from the FIFO. + // For other usart variants it shows that last byte from the buffer was just sent. + if sr_val.tc() { + // For others it is cleared above with `clear_interrupt_flags`. + #[cfg(any(usart_v1, usart_v2))] + sr(r).modify(|w| w.set_tc(false)); + + r.cr1().modify(|w| { + w.set_tcie(false); + }); + + state.tx_done.store(true, Ordering::Release); + state.tx_waker.wake(); + } + + // TX + if sr(r).read().txe() { + let mut tx_reader = state.tx_buf.reader(); + let buf = tx_reader.pop_slice(); + if !buf.is_empty() { + r.cr1().modify(|w| { + w.set_txeie(true); + }); + + // Enable transmission complete interrupt when last byte is going to be sent out. + if buf.len() == 1 { + r.cr1().modify(|w| { + w.set_tcie(true); + }); + } + + tdr(r).write_volatile(buf[0].into()); + tx_reader.pop_done(1); + } else { + // Disable interrupt until we have something to transmit again. + r.cr1().modify(|w| { + w.set_txeie(false); + }); + } + } +} + +pub(super) struct State { + rx_waker: AtomicWaker, + rx_buf: RingBuffer, + tx_waker: AtomicWaker, + tx_buf: RingBuffer, + tx_done: AtomicBool, + tx_rx_refcount: AtomicU8, +} + +impl State { + pub(super) const fn new() -> Self { + Self { + rx_buf: RingBuffer::new(), + tx_buf: RingBuffer::new(), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + tx_done: AtomicBool::new(true), + tx_rx_refcount: AtomicU8::new(0), + } + } +} + +/// Bidirectional buffered UART +pub struct BufferedUart<'d> { + rx: BufferedUartRx<'d>, + tx: BufferedUartTx<'d>, +} + +/// Tx-only buffered UART +/// +/// Created with [BufferedUart::split] +pub struct BufferedUartTx<'d> { + info: &'static Info, + state: &'static State, + kernel_clock: Hertz, + tx: Option>, + cts: Option>, + de: Option>, + is_borrowed: bool, +} + +/// Rx-only buffered UART +/// +/// Created with [BufferedUart::split] +pub struct BufferedUartRx<'d> { + info: &'static Info, + state: &'static State, + kernel_clock: Hertz, + rx: Option>, + rts: Option>, + is_borrowed: bool, +} + +impl<'d> SetConfig for BufferedUart<'d> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config(config) + } +} + +impl<'d> SetConfig for BufferedUartRx<'d> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config(config) + } +} + +impl<'d> SetConfig for BufferedUartTx<'d> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config(config) + } +} + +impl<'d> BufferedUart<'d> { + /// Create a new bidirectional buffered UART driver + pub fn new( + peri: impl Peripheral

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

> + 'd, + tx: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, AfType::input(config.rx_pull)), + new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + None, + None, + None, + tx_buffer, + rx_buffer, + config, + ) + } + + /// Create a new bidirectional buffered UART driver with request-to-send and clear-to-send pins + pub fn new_with_rtscts( + peri: impl Peripheral

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

> + 'd, + tx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, AfType::input(Pull::None)), + new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(cts, AfType::input(Pull::None)), + None, + tx_buffer, + rx_buffer, + config, + ) + } + + /// Create a new bidirectional buffered UART driver with only the RTS pin as the DE pin + pub fn new_with_rts_as_de( + peri: impl Peripheral

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

> + 'd, + tx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, AfType::input(Pull::None)), + new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + None, + None, + new_pin!(rts, AfType::input(Pull::None)), // RTS mapped used as DE + tx_buffer, + rx_buffer, + config, + ) + } + + /// Create a new bidirectional buffered UART driver with only the request-to-send pin + pub fn new_with_rts( + peri: impl Peripheral

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

> + 'd, + tx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, AfType::input(Pull::None)), + new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rts, AfType::input(Pull::None)), + None, // no CTS + None, // no DE + tx_buffer, + rx_buffer, + config, + ) + } + + /// Create a new bidirectional buffered UART driver with a driver-enable pin + #[cfg(not(any(usart_v1, usart_v2)))] + pub fn new_with_de( + peri: impl Peripheral

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

> + 'd, + tx: impl Peripheral

> + 'd, + de: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, AfType::input(config.rx_pull)), + new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + None, + None, + new_pin!(de, AfType::output(OutputType::PushPull, Speed::Medium)), + tx_buffer, + rx_buffer, + config, + ) + } + + fn new_inner( + _peri: impl Peripheral

+ 'd, + rx: Option>, + tx: Option>, + rts: Option>, + cts: Option>, + de: Option>, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + let info = T::info(); + let state = T::buffered_state(); + let kernel_clock = T::frequency(); + + let mut this = Self { + rx: BufferedUartRx { + info, + state, + kernel_clock, + rx, + rts, + is_borrowed: false, + }, + tx: BufferedUartTx { + info, + state, + kernel_clock, + tx, + cts, + de, + is_borrowed: false, + }, + }; + this.enable_and_configure(tx_buffer, rx_buffer, &config)?; + Ok(this) + } + + fn enable_and_configure( + &mut self, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: &Config, + ) -> Result<(), ConfigError> { + let info = self.rx.info; + let state = self.rx.state; + state.tx_rx_refcount.store(2, Ordering::Relaxed); + + info.rcc.enable_and_reset(); + + 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) }; + + info.regs.cr3().write(|w| { + w.set_rtse(self.rx.rts.is_some()); + w.set_ctse(self.tx.cts.is_some()); + #[cfg(not(any(usart_v1, usart_v2)))] + w.set_dem(self.tx.de.is_some()); + }); + configure(info, self.rx.kernel_clock, &config, true, true)?; + + info.regs.cr1().modify(|w| { + w.set_rxneie(true); + w.set_idleie(true); + }); + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) + } + + /// Split the driver into a Tx and Rx part (useful for sending to separate tasks) + pub fn split(self) -> (BufferedUartTx<'d>, BufferedUartRx<'d>) { + (self.tx, self.rx) + } + + /// Split the Uart into a transmitter and receiver, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split_ref(&mut self) -> (BufferedUartTx<'_>, BufferedUartRx<'_>) { + ( + BufferedUartTx { + info: self.tx.info, + state: self.tx.state, + kernel_clock: self.tx.kernel_clock, + tx: self.tx.tx.as_mut().map(PeripheralRef::reborrow), + cts: self.tx.cts.as_mut().map(PeripheralRef::reborrow), + de: self.tx.de.as_mut().map(PeripheralRef::reborrow), + is_borrowed: true, + }, + BufferedUartRx { + info: self.rx.info, + state: self.rx.state, + kernel_clock: self.rx.kernel_clock, + rx: self.rx.rx.as_mut().map(PeripheralRef::reborrow), + rts: self.rx.rts.as_mut().map(PeripheralRef::reborrow), + is_borrowed: true, + }, + ) + } + + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + reconfigure(self.rx.info, self.rx.kernel_clock, config)?; + + self.rx.info.regs.cr1().modify(|w| { + w.set_rxneie(true); + w.set_idleie(true); + }); + + Ok(()) + } + + /// Send break character + pub fn send_break(&self) { + self.tx.send_break() + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + self.tx.set_baudrate(baudrate)?; + self.rx.set_baudrate(baudrate)?; + Ok(()) + } +} + +impl<'d> BufferedUartRx<'d> { + async fn read(&self, buf: &mut [u8]) -> Result { + poll_fn(move |cx| { + let state = self.state; + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let data = rx_reader.pop_slice(); + + if !data.is_empty() { + let len = data.len().min(buf.len()); + buf[..len].copy_from_slice(&data[..len]); + + let do_pend = state.rx_buf.is_full(); + rx_reader.pop_done(len); + + if do_pend { + self.info.interrupt.pend(); + } + + return Poll::Ready(Ok(len)); + } + + state.rx_waker.register(cx.waker()); + Poll::Pending + }) + .await + } + + fn blocking_read(&self, buf: &mut [u8]) -> Result { + loop { + let state = self.state; + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let data = rx_reader.pop_slice(); + + if !data.is_empty() { + let len = data.len().min(buf.len()); + buf[..len].copy_from_slice(&data[..len]); + + let do_pend = state.rx_buf.is_full(); + rx_reader.pop_done(len); + + if do_pend { + self.info.interrupt.pend(); + } + + return Ok(len); + } + } + } + + async fn fill_buf(&self) -> Result<&[u8], Error> { + poll_fn(move |cx| { + let state = self.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)) + }) + .await + } + + fn consume(&self, amt: usize) { + let state = self.state; + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let full = state.rx_buf.is_full(); + rx_reader.pop_done(amt); + if full { + self.info.interrupt.pend(); + } + } + + /// we are ready to read if there is data in the buffer + fn read_ready(&mut self) -> Result { + let state = self.state; + Ok(!state.rx_buf.is_empty()) + } + + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + reconfigure(self.info, self.kernel_clock, config)?; + + self.info.regs.cr1().modify(|w| { + w.set_rxneie(true); + w.set_idleie(true); + }); + + Ok(()) + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(self.info, self.kernel_clock, baudrate) + } +} + +impl<'d> BufferedUartTx<'d> { + async fn write(&self, buf: &[u8]) -> Result { + poll_fn(move |cx| { + let state = self.state; + state.tx_done.store(false, Ordering::Release); + + let empty = state.tx_buf.is_empty(); + + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let data = tx_writer.push_slice(); + if data.is_empty() { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + let n = data.len().min(buf.len()); + data[..n].copy_from_slice(&buf[..n]); + tx_writer.push_done(n); + + if empty { + self.info.interrupt.pend(); + } + + Poll::Ready(Ok(n)) + }) + .await + } + + async fn flush(&self) -> Result<(), Error> { + poll_fn(move |cx| { + let state = self.state; + + if !state.tx_done.load(Ordering::Acquire) { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + .await + } + + fn blocking_write(&self, buf: &[u8]) -> Result { + loop { + let state = self.state; + let empty = state.tx_buf.is_empty(); + + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let data = tx_writer.push_slice(); + if !data.is_empty() { + let n = data.len().min(buf.len()); + data[..n].copy_from_slice(&buf[..n]); + tx_writer.push_done(n); + + if empty { + self.info.interrupt.pend(); + } + + return Ok(n); + } + } + } + + fn blocking_flush(&self) -> Result<(), Error> { + loop { + let state = self.state; + if state.tx_buf.is_empty() { + return Ok(()); + } + } + } + + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + reconfigure(self.info, self.kernel_clock, config)?; + + self.info.regs.cr1().modify(|w| { + w.set_rxneie(true); + w.set_idleie(true); + }); + + Ok(()) + } + + /// Send break character + pub fn send_break(&self) { + send_break(&self.info.regs); + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(self.info, self.kernel_clock, baudrate) + } +} + +impl<'d> Drop for BufferedUartRx<'d> { + fn drop(&mut self) { + if !self.is_borrowed { + let state = self.state; + unsafe { + state.rx_buf.deinit(); + + // TX is inactive if the the buffer is not available. + // We can now unregister the interrupt handler + if state.tx_buf.len() == 0 { + self.info.interrupt.disable(); + } + } + + self.rx.as_ref().map(|x| x.set_as_disconnected()); + self.rts.as_ref().map(|x| x.set_as_disconnected()); + drop_tx_rx(self.info, state); + } + } +} + +impl<'d> Drop for BufferedUartTx<'d> { + fn drop(&mut self) { + if !self.is_borrowed { + let state = self.state; + unsafe { + state.tx_buf.deinit(); + + // RX is inactive if the the buffer is not available. + // We can now unregister the interrupt handler + if state.rx_buf.len() == 0 { + self.info.interrupt.disable(); + } + } + + self.tx.as_ref().map(|x| x.set_as_disconnected()); + self.cts.as_ref().map(|x| x.set_as_disconnected()); + self.de.as_ref().map(|x| x.set_as_disconnected()); + drop_tx_rx(self.info, state); + } + } +} + +fn drop_tx_rx(info: &Info, state: &State) { + // We cannot use atomic subtraction here, because it's not supported for all targets + let is_last_drop = critical_section::with(|_| { + let refcount = state.tx_rx_refcount.load(Ordering::Relaxed); + assert!(refcount >= 1); + state.tx_rx_refcount.store(refcount - 1, Ordering::Relaxed); + refcount == 1 + }); + if is_last_drop { + info.rcc.disable(); + } +} + +impl<'d> embedded_io_async::ErrorType for BufferedUart<'d> { + type Error = Error; +} + +impl<'d> embedded_io_async::ErrorType for BufferedUartRx<'d> { + type Error = Error; +} + +impl<'d> embedded_io_async::ErrorType for BufferedUartTx<'d> { + type Error = Error; +} + +impl<'d> embedded_io_async::Read for BufferedUart<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.read(buf).await + } +} + +impl<'d> embedded_io_async::Read for BufferedUartRx<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Self::read(self, buf).await + } +} + +impl<'d> embedded_io_async::ReadReady for BufferedUart<'d> { + fn read_ready(&mut self) -> Result { + BufferedUartRx::<'d>::read_ready(&mut self.rx) + } +} + +impl<'d> embedded_io_async::ReadReady for BufferedUartRx<'d> { + fn read_ready(&mut self) -> Result { + Self::read_ready(self) + } +} + +impl<'d> embedded_io_async::BufRead for BufferedUart<'d> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.rx.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.rx.consume(amt) + } +} + +impl<'d> embedded_io_async::BufRead for BufferedUartRx<'d> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Self::fill_buf(self).await + } + + fn consume(&mut self, amt: usize) { + Self::consume(self, amt) + } +} + +impl<'d> embedded_io_async::Write for BufferedUart<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.tx.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.flush().await + } +} + +impl<'d> embedded_io_async::Write for BufferedUartTx<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + Self::write(self, buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Self::flush(self).await + } +} + +impl<'d> embedded_io::Read for BufferedUart<'d> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.blocking_read(buf) + } +} + +impl<'d> embedded_io::Read for BufferedUartRx<'d> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.blocking_read(buf) + } +} + +impl<'d> embedded_io::Write for BufferedUart<'d> { + fn write(&mut self, buf: &[u8]) -> Result { + self.tx.blocking_write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.blocking_flush() + } +} + +impl<'d> embedded_io::Write for BufferedUartTx<'d> { + fn write(&mut self, buf: &[u8]) -> Result { + Self::blocking_write(self, buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Self::blocking_flush(self) + } +} + +impl<'d> embedded_hal_02::serial::Read for BufferedUartRx<'d> { + type Error = Error; + + fn read(&mut self) -> Result> { + let r = self.info.regs; + unsafe { + let sr = sr(r).read(); + if sr.pe() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Parity)) + } else if sr.fe() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Framing)) + } else if sr.ne() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Noise)) + } else if sr.ore() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Overrun)) + } else if sr.rxne() { + Ok(rdr(r).read_volatile()) + } else { + Err(nb::Error::WouldBlock) + } + } + } +} + +impl<'d> embedded_hal_02::blocking::serial::Write for BufferedUartTx<'d> { + type Error = Error; + + fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { + while !buffer.is_empty() { + match self.blocking_write(buffer) { + Ok(0) => panic!("zero-length write."), + Ok(n) => buffer = &buffer[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d> embedded_hal_02::serial::Read for BufferedUart<'d> { + type Error = Error; + + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d> embedded_hal_02::blocking::serial::Write for BufferedUart<'d> { + type Error = Error; + + fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { + while !buffer.is_empty() { + match self.tx.blocking_write(buffer) { + Ok(0) => panic!("zero-length write."), + Ok(n) => buffer = &buffer[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.tx.blocking_flush() + } +} + +impl<'d> embedded_hal_nb::serial::ErrorType for BufferedUart<'d> { + type Error = Error; +} + +impl<'d> embedded_hal_nb::serial::ErrorType for BufferedUartTx<'d> { + type Error = Error; +} + +impl<'d> embedded_hal_nb::serial::ErrorType for BufferedUartRx<'d> { + type Error = Error; +} + +impl<'d> embedded_hal_nb::serial::Read for BufferedUartRx<'d> { + fn read(&mut self) -> nb::Result { + embedded_hal_02::serial::Read::read(self) + } +} + +impl<'d> embedded_hal_nb::serial::Write for BufferedUartTx<'d> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +impl<'d> embedded_hal_nb::serial::Read for BufferedUart<'d> { + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d> embedded_hal_nb::serial::Write for BufferedUart<'d> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.tx.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.tx.blocking_flush().map_err(nb::Error::Other) + } +} diff --git a/embassy/embassy-stm32/src/usart/mod.rs b/embassy/embassy-stm32/src/usart/mod.rs new file mode 100644 index 0000000..2d801e6 --- /dev/null +++ b/embassy/embassy-stm32/src/usart/mod.rs @@ -0,0 +1,2054 @@ +//! Universal Synchronous/Asynchronous Receiver Transmitter (USART, UART, LPUART) +#![macro_use] +#![warn(missing_docs)] + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, AtomicU8, Ordering}; +use core::task::Poll; + +use embassy_embedded_hal::SetConfig; +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::PeripheralRef; +use embassy_sync::waitqueue::AtomicWaker; +use futures_util::future::{select, Either}; + +use crate::dma::ChannelAndRequest; +use crate::gpio::{self, AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; +use crate::interrupt::typelevel::Interrupt as _; +use crate::interrupt::{self, Interrupt, InterruptExt}; +use crate::mode::{Async, Blocking, Mode}; +#[allow(unused_imports)] +#[cfg(not(any(usart_v1, usart_v2)))] +use crate::pac::usart::regs::Isr as Sr; +#[cfg(any(usart_v1, usart_v2))] +use crate::pac::usart::regs::Sr; +#[cfg(not(any(usart_v1, usart_v2)))] +use crate::pac::usart::Lpuart as Regs; +#[cfg(any(usart_v1, usart_v2))] +use crate::pac::usart::Usart as Regs; +use crate::pac::usart::{regs, vals}; +use crate::rcc::{RccInfo, SealedRccPeripheral}; +use crate::time::Hertz; +use crate::Peripheral; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + on_interrupt(T::info().regs, T::state()) + } +} + +unsafe fn on_interrupt(r: Regs, s: &'static State) { + let (sr, cr1, cr3) = (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 + 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); + }); + } else if cr1.idleie() && sr.idle() { + // IDLE detected: no more data will come + r.cr1().modify(|w| { + // disable idle line detection + w.set_idleie(false); + }); + } else if cr1.tcie() && sr.tc() { + // Transmission complete detected + r.cr1().modify(|w| { + // disable Transmission complete interrupt + w.set_tcie(false); + }); + } else if cr1.rxneie() { + // We cannot check the RXNE flag as it is auto-cleared by the DMA controller + + // It is up to the listener to determine if this in fact was a RX event and disable the RXNE detection + } else { + return; + } + + compiler_fence(Ordering::SeqCst); + s.rx_waker.wake(); +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Number of data bits +pub enum DataBits { + /// 7 Data Bits + DataBits7, + /// 8 Data Bits + DataBits8, + /// 9 Data Bits + DataBits9, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Parity +pub enum Parity { + /// No parity + ParityNone, + /// Even Parity + ParityEven, + /// Odd Parity + ParityOdd, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Number of stop bits +pub enum StopBits { + #[doc = "1 stop bit"] + STOP1, + #[doc = "0.5 stop bits"] + STOP0P5, + #[doc = "2 stop bits"] + STOP2, + #[doc = "1.5 stop bits"] + STOP1P5, +} + +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Config Error +pub enum ConfigError { + /// Baudrate too low + BaudrateTooLow, + /// Baudrate too high + BaudrateTooHigh, + /// Rx or Tx not enabled + RxOrTxNotEnabled, + /// Data bits and parity combination not supported + DataParityNotSupported, +} + +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +/// Config +pub struct Config { + /// Baud rate + pub baudrate: u32, + /// Number of data bits + pub data_bits: DataBits, + /// Number of stop bits + pub stop_bits: StopBits, + /// Parity type + pub parity: Parity, + + /// If true: on a read-like method, if there is a latent error pending, + /// the read will abort and the error will be reported and cleared + /// + /// If false: the error is ignored and cleared + pub detect_previous_overrun: bool, + + /// Set this to true if the line is considered noise free. + /// This will increase the receiver’s tolerance to clock deviations, + /// but will effectively disable noise detection. + #[cfg(not(usart_v1))] + pub assume_noise_free: bool, + + /// Set this to true to swap the RX and TX pins. + #[cfg(any(usart_v3, usart_v4))] + pub swap_rx_tx: bool, + + /// Set this to true to invert TX pin signal values (VDD = 0/mark, Gnd = 1/idle). + #[cfg(any(usart_v3, usart_v4))] + pub invert_tx: bool, + + /// Set this to true to invert RX pin signal values (VDD = 0/mark, Gnd = 1/idle). + #[cfg(any(usart_v3, usart_v4))] + pub invert_rx: bool, + + /// Set the pull configuration for the RX pin. + pub rx_pull: Pull, + + // private: set by new_half_duplex, not by the user. + half_duplex: bool, +} + +impl Config { + fn tx_af(&self) -> AfType { + #[cfg(any(usart_v3, usart_v4))] + if self.swap_rx_tx { + return AfType::input(self.rx_pull); + }; + AfType::output(OutputType::PushPull, Speed::Medium) + } + + fn rx_af(&self) -> AfType { + #[cfg(any(usart_v3, usart_v4))] + if self.swap_rx_tx { + return AfType::output(OutputType::PushPull, Speed::Medium); + }; + AfType::input(self.rx_pull) + } +} + +impl Default for Config { + fn default() -> Self { + Self { + baudrate: 115200, + data_bits: DataBits::DataBits8, + stop_bits: StopBits::STOP1, + parity: Parity::ParityNone, + // historical behavior + detect_previous_overrun: false, + #[cfg(not(usart_v1))] + assume_noise_free: false, + #[cfg(any(usart_v3, usart_v4))] + swap_rx_tx: false, + #[cfg(any(usart_v3, usart_v4))] + invert_tx: false, + #[cfg(any(usart_v3, usart_v4))] + invert_rx: false, + rx_pull: Pull::None, + half_duplex: false, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Half duplex IO mode +pub enum HalfDuplexConfig { + /// Push pull allows for faster baudrates, may require series resistor + PushPull, + /// Open drain output using external pull up resistor + OpenDrainExternal, + #[cfg(not(gpio_v1))] + /// Open drain output using internal pull up resistor + OpenDrainInternal, +} + +impl HalfDuplexConfig { + fn af_type(self) -> gpio::AfType { + match self { + HalfDuplexConfig::PushPull => AfType::output(OutputType::PushPull, Speed::Medium), + HalfDuplexConfig::OpenDrainExternal => AfType::output(OutputType::OpenDrain, Speed::Medium), + #[cfg(not(gpio_v1))] + HalfDuplexConfig::OpenDrainInternal => AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up), + } + } +} + +/// Serial error +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Framing error + Framing, + /// Noise error + Noise, + /// RX buffer overrun + Overrun, + /// Parity check error + Parity, + /// Buffer too large for DMA + BufferTooLong, +} + +enum ReadCompletionEvent { + // DMA Read transfer completed first + DmaCompleted, + // Idle line detected first + Idle(usize), +} + +/// Bidirectional UART Driver, which acts as a combination of [`UartTx`] and [`UartRx`]. +/// +/// ### Notes on [`embedded_io::Read`] +/// +/// `embedded_io::Read` requires guarantees that the base [`UartRx`] cannot provide. +/// +/// See [`UartRx`] for more details, and see [`BufferedUart`] and [`RingBufferedUartRx`] +/// as alternatives that do provide the necessary guarantees for `embedded_io::Read`. +pub struct Uart<'d, M: Mode> { + tx: UartTx<'d, M>, + rx: UartRx<'d, M>, +} + +impl<'d, M: Mode> SetConfig for Uart<'d, M> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.tx.set_config(config)?; + self.rx.set_config(config) + } +} + +/// Tx-only UART Driver. +/// +/// Can be obtained from [`Uart::split`], or can be constructed independently, +/// if you do not need the receiving half of the driver. +pub struct UartTx<'d, M: Mode> { + info: &'static Info, + state: &'static State, + kernel_clock: Hertz, + tx: Option>, + cts: Option>, + de: Option>, + tx_dma: Option>, + _phantom: PhantomData, +} + +impl<'d, M: Mode> SetConfig for UartTx<'d, M> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config(config) + } +} + +/// Rx-only UART Driver. +/// +/// Can be obtained from [`Uart::split`], or can be constructed independently, +/// if you do not need the transmitting half of the driver. +/// +/// ### Notes on [`embedded_io::Read`] +/// +/// `embedded_io::Read` requires guarantees that this struct cannot provide: +/// +/// - Any data received between calls to [`UartRx::read`] or [`UartRx::blocking_read`] +/// will be thrown away, as `UartRx` is unbuffered. +/// Users of `embedded_io::Read` are likely to not expect this behavior +/// (for instance if they read multiple small chunks in a row). +/// - [`UartRx::read`] and [`UartRx::blocking_read`] only return once the entire buffer has been +/// filled, whereas `embedded_io::Read` requires us to fill the buffer with what we already +/// received, and only block/wait until the first byte arrived. +///
+/// While [`UartRx::read_until_idle`] does return early, it will still eagerly wait for data until +/// the buffer is full or no data has been transmitted in a while, +/// which may not be what users of `embedded_io::Read` expect. +/// +/// [`UartRx::into_ring_buffered`] can be called to equip `UartRx` with a buffer, +/// that it can then use to store data received between calls to `read`, +/// provided you are using DMA already. +/// +/// Alternatively, you can use [`BufferedUartRx`], which is interrupt-based and which can also +/// store data received between calls. +/// +/// Also see [this github comment](https://github.com/embassy-rs/embassy/pull/2185#issuecomment-1810047043). +pub struct UartRx<'d, M: Mode> { + info: &'static Info, + state: &'static State, + kernel_clock: Hertz, + rx: Option>, + rts: Option>, + rx_dma: Option>, + detect_previous_overrun: bool, + #[cfg(any(usart_v1, usart_v2))] + buffered_sr: stm32_metapac::usart::regs::Sr, + _phantom: PhantomData, +} + +impl<'d, M: Mode> SetConfig for UartRx<'d, M> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config(config) + } +} + +impl<'d> UartTx<'d, Async> { + /// Useful if you only want Uart Tx. It saves 1 pin and consumes a little less power. + pub fn new( + peri: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

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

+ 'd, + tx: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

> + 'd, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(cts, AfType::input(Pull::None)), + new_dma!(tx_dma), + config, + ) + } + + /// Initiate an asynchronous UART write + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let r = self.info.regs; + + // Enable Transmitter and disable Receiver for Half-Duplex mode + let mut cr1 = r.cr1().read(); + if r.cr3().read().hdsel() && !cr1.te() { + cr1.set_te(true); + cr1.set_re(false); + r.cr1().write_value(cr1); + } + + let ch = self.tx_dma.as_mut().unwrap(); + r.cr3().modify(|reg| { + reg.set_dmat(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 = unsafe { ch.write(buffer, tdr(r), Default::default()) }; + transfer.await; + Ok(()) + } + + /// Wait until transmission complete + pub async fn flush(&mut self) -> Result<(), Error> { + flush(&self.info, &self.state).await + } +} + +impl<'d> UartTx<'d, Blocking> { + /// Create a new blocking tx-only UART with no hardware flow control. + /// + /// Useful if you only want Uart Tx. It saves 1 pin and consumes a little less power. + pub fn new_blocking( + peri: impl Peripheral

+ 'd, + tx: impl Peripheral

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

+ 'd, + tx: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(cts, AfType::input(config.rx_pull)), + None, + config, + ) + } +} + +impl<'d, M: Mode> UartTx<'d, M> { + fn new_inner( + _peri: impl Peripheral

+ 'd, + tx: Option>, + cts: Option>, + tx_dma: Option>, + config: Config, + ) -> Result { + let mut this = Self { + info: T::info(), + state: T::state(), + kernel_clock: T::frequency(), + tx, + cts, + de: None, + tx_dma, + _phantom: PhantomData, + }; + this.enable_and_configure(&config)?; + Ok(this) + } + + fn enable_and_configure(&mut self, config: &Config) -> Result<(), ConfigError> { + let info = self.info; + let state = self.state; + state.tx_rx_refcount.store(1, Ordering::Relaxed); + + info.rcc.enable_and_reset(); + + info.regs.cr3().modify(|w| { + w.set_ctse(self.cts.is_some()); + }); + configure(info, self.kernel_clock, config, false, true)?; + + Ok(()) + } + + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + reconfigure(self.info, self.kernel_clock, config) + } + + /// Perform a blocking UART write + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let r = self.info.regs; + + // Enable Transmitter and disable Receiver for Half-Duplex mode + let mut cr1 = r.cr1().read(); + if r.cr3().read().hdsel() && !cr1.te() { + cr1.set_te(true); + cr1.set_re(false); + r.cr1().write_value(cr1); + } + + for &b in buffer { + while !sr(r).read().txe() {} + unsafe { tdr(r).write_volatile(b) }; + } + Ok(()) + } + + /// Block until transmission complete + pub fn blocking_flush(&mut self) -> Result<(), Error> { + blocking_flush(self.info) + } + + /// Send break character + pub fn send_break(&self) { + send_break(&self.info.regs); + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(self.info, self.kernel_clock, baudrate) + } +} + +/// Wait until transmission complete +async fn flush(info: &Info, state: &State) -> Result<(), Error> { + let r = info.regs; + if r.cr1().read().te() && !sr(r).read().tc() { + r.cr1().modify(|w| { + // enable Transmission Complete interrupt + w.set_tcie(true); + }); + + compiler_fence(Ordering::SeqCst); + + // future which completes when Transmission complete is detected + let abort = poll_fn(move |cx| { + state.rx_waker.register(cx.waker()); + + let sr = sr(r).read(); + if sr.tc() { + // Transmission complete detected + return Poll::Ready(()); + } + + Poll::Pending + }); + + abort.await; + } + + Ok(()) +} + +fn blocking_flush(info: &Info) -> Result<(), Error> { + let r = info.regs; + if r.cr1().read().te() { + while !sr(r).read().tc() {} + } + + Ok(()) +} + +/// Send break character +pub fn send_break(regs: &Regs) { + // Busy wait until previous break has been sent + #[cfg(any(usart_v1, usart_v2))] + while regs.cr1().read().sbk() {} + #[cfg(any(usart_v3, usart_v4))] + while regs.isr().read().sbkf() {} + + // Send break right after completing the current character transmission + #[cfg(any(usart_v1, usart_v2))] + regs.cr1().modify(|w| w.set_sbk(true)); + #[cfg(any(usart_v3, usart_v4))] + regs.rqr().write(|w| w.set_sbkrq(true)); +} + +impl<'d> UartRx<'d, Async> { + /// Create a new rx-only UART with no hardware flow control. + /// + /// Useful if you only want Uart Rx. It saves 1 pin and consumes a little less power. + pub fn new( + peri: impl Peripheral

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

> + 'd, + rx_dma: impl Peripheral

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

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

> + 'd, + rts: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

> + 'd, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, AfType::input(config.rx_pull)), + new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), + new_dma!(rx_dma), + config, + ) + } + + /// Initiate an asynchronous UART read + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.inner_read(buffer, false).await?; + + Ok(()) + } + + /// Initiate an asynchronous read with idle line detection enabled + pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result { + self.inner_read(buffer, true).await + } + + async fn inner_read_run( + &mut self, + buffer: &mut [u8], + enable_idle_line_detection: bool, + ) -> Result { + let r = self.info.regs; + + // Call flush for Half-Duplex mode if some bytes were written and flush was not called. + // It prevents reading of bytes which have just been written. + if r.cr3().read().hdsel() && r.cr1().read().te() { + flush(&self.info, &self.state).await?; + + // Disable Transmitter and enable Receiver after flush + r.cr1().modify(|reg| { + reg.set_re(true); + reg.set_te(false); + }); + } + + // make sure USART state is restored to neutral state when this future is dropped + let on_drop = OnDrop::new(move || { + // defmt::trace!("Clear all USART interrupts and DMA Read Request"); + // clear all interrupts and DMA Rx Request + r.cr1().modify(|w| { + // disable RXNE interrupt + 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 = self.rx_dma.as_mut().unwrap(); + + let buffer_len = buffer.len(); + + // 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 = unsafe { ch.read(rdr(r), buffer, Default::default()) }; + + // 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. + unsafe { 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. + unsafe { 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 { + // clear idle flag + let sr = sr(r).read(); + // This read also clears the error and idle interrupt flags on v1. + unsafe { rdr(r).read_volatile() }; + clear_interrupt_flags(r, sr); + + // enable idle interrupt + r.cr1().modify(|w| { + w.set_idleie(true); + }); + } + + compiler_fence(Ordering::SeqCst); + + // future which completes when idle line or error is detected + let s = self.state; + let abort = poll_fn(move |cx| { + s.rx_waker.register(cx.waker()); + + let sr = sr(r).read(); + + // This read also clears the error and idle interrupt flags on v1. + unsafe { rdr(r).read_volatile() }; + clear_interrupt_flags(r, sr); + + if enable_idle_line_detection { + // enable idle interrupt + r.cr1().modify(|w| { + w.set_idleie(true); + }); + } + + 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 enable_idle_line_detection && 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, abort).await { + // DMA transfer completed first + Either::Left(((), _)) => Ok(ReadCompletionEvent::DmaCompleted), + + // Idle line detected first + Either::Right((Ok(()), transfer)) => Ok(ReadCompletionEvent::Idle( + buffer_len - transfer.get_remaining_transfers() as usize, + )), + + // error occurred + Either::Right((Err(e), _)) => Err(e), + }; + + drop(on_drop); + + r + } + + async fn inner_read(&mut self, buffer: &mut [u8], enable_idle_line_detection: bool) -> Result { + 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; + + match res { + Ok(ReadCompletionEvent::DmaCompleted) => Ok(buffer_len), + Ok(ReadCompletionEvent::Idle(n)) => Ok(n), + Err(e) => Err(e), + } + } +} + +impl<'d> UartRx<'d, Blocking> { + /// Create a new rx-only UART with no hardware flow control. + /// + /// Useful if you only want Uart Rx. It saves 1 pin and consumes a little less power. + pub fn new_blocking( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

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

+ 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, AfType::input(config.rx_pull)), + new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), + None, + config, + ) + } +} + +impl<'d, M: Mode> UartRx<'d, M> { + fn new_inner( + _peri: impl Peripheral

+ 'd, + rx: Option>, + rts: Option>, + rx_dma: Option>, + config: Config, + ) -> Result { + let mut this = Self { + _phantom: PhantomData, + info: T::info(), + state: T::state(), + kernel_clock: T::frequency(), + rx, + rts, + rx_dma, + detect_previous_overrun: config.detect_previous_overrun, + #[cfg(any(usart_v1, usart_v2))] + buffered_sr: stm32_metapac::usart::regs::Sr(0), + }; + this.enable_and_configure(&config)?; + Ok(this) + } + + fn enable_and_configure(&mut self, config: &Config) -> Result<(), ConfigError> { + let info = self.info; + let state = self.state; + state.tx_rx_refcount.store(1, Ordering::Relaxed); + + info.rcc.enable_and_reset(); + + info.regs.cr3().write(|w| { + w.set_rtse(self.rts.is_some()); + }); + configure(info, self.kernel_clock, &config, true, false)?; + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) + } + + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + reconfigure(self.info, self.kernel_clock, config) + } + + #[cfg(any(usart_v1, usart_v2))] + fn check_rx_flags(&mut self) -> Result { + let r = self.info.regs; + loop { + // Handle all buffered error flags. + if self.buffered_sr.pe() { + self.buffered_sr.set_pe(false); + return Err(Error::Parity); + } else if self.buffered_sr.fe() { + self.buffered_sr.set_fe(false); + return Err(Error::Framing); + } else if self.buffered_sr.ne() { + self.buffered_sr.set_ne(false); + return Err(Error::Noise); + } else if self.buffered_sr.ore() { + self.buffered_sr.set_ore(false); + return Err(Error::Overrun); + } else if self.buffered_sr.rxne() { + self.buffered_sr.set_rxne(false); + return Ok(true); + } else { + // No error flags from previous iterations were set: Check the actual status register + let sr = r.sr().read(); + if !sr.rxne() { + return Ok(false); + } + + // Buffer the status register and let the loop handle the error flags. + self.buffered_sr = sr; + } + } + } + + #[cfg(any(usart_v3, usart_v4))] + fn check_rx_flags(&mut self) -> Result { + let r = self.info.regs; + let sr = r.isr().read(); + if sr.pe() { + r.icr().write(|w| w.set_pe(true)); + return Err(Error::Parity); + } else if sr.fe() { + r.icr().write(|w| w.set_fe(true)); + return Err(Error::Framing); + } else if sr.ne() { + r.icr().write(|w| w.set_ne(true)); + return Err(Error::Noise); + } else if sr.ore() { + r.icr().write(|w| w.set_ore(true)); + return Err(Error::Overrun); + } + Ok(sr.rxne()) + } + + /// Read a single u8 if there is one available, otherwise return WouldBlock + pub(crate) fn nb_read(&mut self) -> Result> { + let r = self.info.regs; + if self.check_rx_flags()? { + Ok(unsafe { rdr(r).read_volatile() }) + } else { + Err(nb::Error::WouldBlock) + } + } + + /// Perform a blocking read into `buffer` + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + let r = self.info.regs; + + // Call flush for Half-Duplex mode if some bytes were written and flush was not called. + // It prevents reading of bytes which have just been written. + if r.cr3().read().hdsel() && r.cr1().read().te() { + blocking_flush(self.info)?; + + // Disable Transmitter and enable Receiver after flush + r.cr1().modify(|reg| { + reg.set_re(true); + reg.set_te(false); + }); + } + + for b in buffer { + while !self.check_rx_flags()? {} + unsafe { *b = rdr(r).read_volatile() } + } + Ok(()) + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(self.info, self.kernel_clock, baudrate) + } +} + +impl<'d, M: Mode> Drop for UartTx<'d, M> { + fn drop(&mut self) { + self.tx.as_ref().map(|x| x.set_as_disconnected()); + self.cts.as_ref().map(|x| x.set_as_disconnected()); + self.de.as_ref().map(|x| x.set_as_disconnected()); + drop_tx_rx(self.info, self.state); + } +} + +impl<'d, M: Mode> Drop for UartRx<'d, M> { + fn drop(&mut self) { + self.rx.as_ref().map(|x| x.set_as_disconnected()); + self.rts.as_ref().map(|x| x.set_as_disconnected()); + drop_tx_rx(self.info, self.state); + } +} + +fn drop_tx_rx(info: &Info, state: &State) { + // We cannot use atomic subtraction here, because it's not supported for all targets + let is_last_drop = critical_section::with(|_| { + let refcount = state.tx_rx_refcount.load(Ordering::Relaxed); + assert!(refcount >= 1); + state.tx_rx_refcount.store(refcount - 1, Ordering::Relaxed); + refcount == 1 + }); + if is_last_drop { + info.rcc.disable(); + } +} + +impl<'d> Uart<'d, Async> { + /// Create a new bidirectional UART + pub fn new( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

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

> + 'd, + rx_dma: impl Peripheral

> + 'd, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, config.rx_af()), + new_pin!(tx, config.tx_af()), + None, + None, + None, + new_dma!(tx_dma), + new_dma!(rx_dma), + config, + ) + } + + /// Create a new bidirectional UART with request-to-send and clear-to-send pins + pub fn new_with_rtscts( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

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

> + 'd, + cts: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

> + 'd, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, config.rx_af()), + new_pin!(tx, config.tx_af()), + new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(cts, AfType::input(Pull::None)), + None, + new_dma!(tx_dma), + new_dma!(rx_dma), + config, + ) + } + + #[cfg(not(any(usart_v1, usart_v2)))] + /// Create a new bidirectional UART with a driver-enable pin + pub fn new_with_de( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

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

> + 'd, + tx_dma: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

> + 'd, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, config.rx_af()), + new_pin!(tx, config.tx_af()), + None, + None, + new_pin!(de, AfType::output(OutputType::PushPull, Speed::Medium)), + new_dma!(tx_dma), + new_dma!(rx_dma), + config, + ) + } + + /// Create a single-wire half-duplex Uart transceiver on a single Tx pin. + /// + /// See [`new_half_duplex_on_rx`][`Self::new_half_duplex_on_rx`] if you would prefer to use an Rx pin + /// (when it is available for your chip). There is no functional difference between these methods, as both + /// allow bidirectional communication. + /// + /// The TX pin is always released when no data is transmitted. Thus, it acts as a standard + /// I/O in idle or in reception. It means that the I/O must be configured so that TX is + /// configured as alternate function open-drain with an external pull-up + /// Apart from this, the communication protocol is similar to normal USART mode. Any conflict + /// on the line must be managed by software (for instance by using a centralized arbiter). + #[doc(alias("HDSEL"))] + pub fn new_half_duplex( + peri: impl Peripheral

+ 'd, + tx: impl Peripheral

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

> + 'd, + rx_dma: impl Peripheral

> + 'd, + mut config: Config, + half_duplex: HalfDuplexConfig, + ) -> Result { + #[cfg(not(any(usart_v1, usart_v2)))] + { + config.swap_rx_tx = false; + } + config.half_duplex = true; + + Self::new_inner( + peri, + None, + new_pin!(tx, half_duplex.af_type()), + None, + None, + None, + new_dma!(tx_dma), + new_dma!(rx_dma), + config, + ) + } + + /// Create a single-wire half-duplex Uart transceiver on a single Rx pin. + /// + /// See [`new_half_duplex`][`Self::new_half_duplex`] if you would prefer to use an Tx pin. + /// There is no functional difference between these methods, as both allow bidirectional communication. + /// + /// The pin is always released when no data is transmitted. Thus, it acts as a standard + /// I/O in idle or in reception. + /// Apart from this, the communication protocol is similar to normal USART mode. Any conflict + /// on the line must be managed by software (for instance by using a centralized arbiter). + #[cfg(not(any(usart_v1, usart_v2)))] + #[doc(alias("HDSEL"))] + pub fn new_half_duplex_on_rx( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

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

> + 'd, + rx_dma: impl Peripheral

> + 'd, + mut config: Config, + half_duplex: HalfDuplexConfig, + ) -> Result { + config.swap_rx_tx = true; + config.half_duplex = true; + + Self::new_inner( + peri, + None, + None, + new_pin!(rx, half_duplex.af_type()), + None, + None, + new_dma!(tx_dma), + new_dma!(rx_dma), + config, + ) + } + + /// Perform an asynchronous write + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.write(buffer).await + } + + /// Wait until transmission complete + pub async fn flush(&mut self) -> Result<(), Error> { + self.tx.flush().await + } + + /// Perform an asynchronous read into `buffer` + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.read(buffer).await + } + + /// Perform an an asynchronous read with idle line detection enabled + pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result { + self.rx.read_until_idle(buffer).await + } +} + +impl<'d> Uart<'d, Blocking> { + /// Create a new blocking bidirectional UART. + pub fn new_blocking( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, config.rx_af()), + new_pin!(tx, config.tx_af()), + None, + None, + None, + None, + None, + config, + ) + } + + /// Create a new bidirectional UART with request-to-send and clear-to-send pins + pub fn new_blocking_with_rtscts( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, config.rx_af()), + new_pin!(tx, config.tx_af()), + new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(cts, AfType::input(Pull::None)), + None, + None, + None, + config, + ) + } + + #[cfg(not(any(usart_v1, usart_v2)))] + /// Create a new bidirectional UART with a driver-enable pin + pub fn new_blocking_with_de( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + de: impl Peripheral

> + 'd, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, config.rx_af()), + new_pin!(tx, config.tx_af()), + None, + None, + new_pin!(de, AfType::output(OutputType::PushPull, Speed::Medium)), + None, + None, + config, + ) + } + + /// Create a single-wire half-duplex Uart transceiver on a single Tx pin. + /// + /// See [`new_half_duplex_on_rx`][`Self::new_half_duplex_on_rx`] if you would prefer to use an Rx pin + /// (when it is available for your chip). There is no functional difference between these methods, as both + /// allow bidirectional communication. + /// + /// The pin is always released when no data is transmitted. Thus, it acts as a standard + /// I/O in idle or in reception. + /// Apart from this, the communication protocol is similar to normal USART mode. Any conflict + /// on the line must be managed by software (for instance by using a centralized arbiter). + #[doc(alias("HDSEL"))] + pub fn new_blocking_half_duplex( + peri: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + mut config: Config, + half_duplex: HalfDuplexConfig, + ) -> Result { + #[cfg(not(any(usart_v1, usart_v2)))] + { + config.swap_rx_tx = false; + } + config.half_duplex = true; + + Self::new_inner( + peri, + None, + new_pin!(tx, half_duplex.af_type()), + None, + None, + None, + None, + None, + config, + ) + } + + /// Create a single-wire half-duplex Uart transceiver on a single Rx pin. + /// + /// See [`new_half_duplex`][`Self::new_half_duplex`] if you would prefer to use an Tx pin. + /// There is no functional difference between these methods, as both allow bidirectional communication. + /// + /// The pin is always released when no data is transmitted. Thus, it acts as a standard + /// I/O in idle or in reception. + /// Apart from this, the communication protocol is similar to normal USART mode. Any conflict + /// on the line must be managed by software (for instance by using a centralized arbiter). + #[cfg(not(any(usart_v1, usart_v2)))] + #[doc(alias("HDSEL"))] + pub fn new_blocking_half_duplex_on_rx( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + mut config: Config, + half_duplex: HalfDuplexConfig, + ) -> Result { + config.swap_rx_tx = true; + config.half_duplex = true; + + Self::new_inner( + peri, + None, + None, + new_pin!(rx, half_duplex.af_type()), + None, + None, + None, + None, + config, + ) + } +} + +impl<'d, M: Mode> Uart<'d, M> { + fn new_inner( + _peri: impl Peripheral

+ 'd, + rx: Option>, + tx: Option>, + rts: Option>, + cts: Option>, + de: Option>, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Result { + let info = T::info(); + let state = T::state(); + let kernel_clock = T::frequency(); + + let mut this = Self { + tx: UartTx { + _phantom: PhantomData, + info, + state, + kernel_clock, + tx, + cts, + de, + tx_dma, + }, + rx: UartRx { + _phantom: PhantomData, + info, + state, + kernel_clock, + rx, + rts, + rx_dma, + detect_previous_overrun: config.detect_previous_overrun, + #[cfg(any(usart_v1, usart_v2))] + buffered_sr: stm32_metapac::usart::regs::Sr(0), + }, + }; + this.enable_and_configure(&config)?; + Ok(this) + } + + fn enable_and_configure(&mut self, config: &Config) -> Result<(), ConfigError> { + let info = self.rx.info; + let state = self.rx.state; + state.tx_rx_refcount.store(2, Ordering::Relaxed); + + info.rcc.enable_and_reset(); + + info.regs.cr3().write(|w| { + w.set_rtse(self.rx.rts.is_some()); + w.set_ctse(self.tx.cts.is_some()); + #[cfg(not(any(usart_v1, usart_v2)))] + w.set_dem(self.tx.de.is_some()); + }); + configure(info, self.rx.kernel_clock, config, true, true)?; + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) + } + + /// Perform a blocking write + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.blocking_write(buffer) + } + + /// Block until transmission complete + pub fn blocking_flush(&mut self) -> Result<(), Error> { + self.tx.blocking_flush() + } + + /// Read a single `u8` or return `WouldBlock` + pub(crate) fn nb_read(&mut self) -> Result> { + self.rx.nb_read() + } + + /// Perform a blocking read into `buffer` + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.blocking_read(buffer) + } + + /// Split the Uart into a transmitter and receiver, which is + /// particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split(self) -> (UartTx<'d, M>, UartRx<'d, M>) { + (self.tx, self.rx) + } + + /// Split the Uart into a transmitter and receiver by mutable reference, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split_ref(&mut self) -> (&mut UartTx<'d, M>, &mut UartRx<'d, M>) { + (&mut self.tx, &mut self.rx) + } + + /// Send break character + pub fn send_break(&self) { + self.tx.send_break(); + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + self.tx.set_baudrate(baudrate)?; + self.rx.set_baudrate(baudrate)?; + Ok(()) + } +} + +fn reconfigure(info: &Info, kernel_clock: Hertz, config: &Config) -> Result<(), ConfigError> { + info.interrupt.disable(); + let r = info.regs; + + let cr = r.cr1().read(); + configure(info, kernel_clock, config, cr.re(), cr.te())?; + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) +} + +fn calculate_brr(baud: u32, pclk: u32, presc: u32, mul: u32) -> u32 { + // The calculation to be done to get the BRR is `mul * pclk / presc / baud` + // To do this in 32-bit only we can't multiply `mul` and `pclk` + let clock = pclk / presc; + + // The mul is applied as the last operation to prevent overflow + let brr = clock / baud * mul; + + // The BRR calculation will be a bit off because of integer rounding. + // Because we multiplied our inaccuracy with mul, our rounding now needs to be in proportion to mul. + let rounding = ((clock % baud) * mul + (baud / 2)) / baud; + + brr + rounding +} + +fn set_baudrate(info: &Info, kernel_clock: Hertz, baudrate: u32) -> Result<(), ConfigError> { + info.interrupt.disable(); + + set_usart_baudrate(info, kernel_clock, baudrate)?; + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) +} + +fn find_and_set_brr(r: Regs, kind: Kind, kernel_clock: Hertz, baudrate: u32) -> Result { + #[cfg(not(usart_v4))] + static DIVS: [(u16, ()); 1] = [(1, ())]; + + #[cfg(usart_v4)] + static DIVS: [(u16, vals::Presc); 12] = [ + (1, vals::Presc::DIV1), + (2, vals::Presc::DIV2), + (4, vals::Presc::DIV4), + (6, vals::Presc::DIV6), + (8, vals::Presc::DIV8), + (10, vals::Presc::DIV10), + (12, vals::Presc::DIV12), + (16, vals::Presc::DIV16), + (32, vals::Presc::DIV32), + (64, vals::Presc::DIV64), + (128, vals::Presc::DIV128), + (256, vals::Presc::DIV256), + ]; + + let (mul, brr_min, brr_max) = match kind { + #[cfg(any(usart_v3, usart_v4))] + Kind::Lpuart => { + trace!("USART: Kind::Lpuart"); + (256, 0x300, 0x10_0000) + } + Kind::Uart => { + trace!("USART: Kind::Uart"); + (1, 0x10, 0x1_0000) + } + }; + + let mut found_brr = None; + #[cfg(not(usart_v1))] + let mut over8 = false; + #[cfg(usart_v1)] + let over8 = false; + + for &(presc, _presc_val) in &DIVS { + let brr = calculate_brr(baudrate, kernel_clock.0, presc as u32, mul); + trace!( + "USART: presc={}, div=0x{:08x} (mantissa = {}, fraction = {})", + presc, + brr, + brr >> 4, + brr & 0x0F + ); + + if brr < brr_min { + #[cfg(not(usart_v1))] + if brr * 2 >= brr_min && kind == Kind::Uart && !cfg!(usart_v1) { + over8 = true; + r.brr().write_value(regs::Brr(((brr << 1) & !0xF) | (brr & 0x07))); + #[cfg(usart_v4)] + r.presc().write(|w| w.set_prescaler(_presc_val)); + found_brr = Some(brr); + break; + } + return Err(ConfigError::BaudrateTooHigh); + } + + if brr < brr_max { + r.brr().write_value(regs::Brr(brr)); + #[cfg(usart_v4)] + r.presc().write(|w| w.set_prescaler(_presc_val)); + found_brr = Some(brr); + break; + } + } + + match found_brr { + Some(brr) => { + #[cfg(not(usart_v1))] + let oversampling = if over8 { "8 bit" } else { "16 bit" }; + #[cfg(usart_v1)] + let oversampling = "default"; + trace!( + "Using {} oversampling, desired baudrate: {}, actual baudrate: {}", + oversampling, + baudrate, + kernel_clock.0 / brr * mul + ); + Ok(over8) + } + None => Err(ConfigError::BaudrateTooLow), + } +} + +fn set_usart_baudrate(info: &Info, kernel_clock: Hertz, baudrate: u32) -> Result<(), ConfigError> { + let r = info.regs; + r.cr1().modify(|w| { + // disable uart + w.set_ue(false); + }); + + #[cfg(not(usart_v1))] + let over8 = find_and_set_brr(r, info.kind, kernel_clock, baudrate)?; + #[cfg(usart_v1)] + let _over8 = find_and_set_brr(r, info.kind, kernel_clock, baudrate)?; + + r.cr1().modify(|w| { + // enable uart + w.set_ue(true); + + #[cfg(not(usart_v1))] + w.set_over8(vals::Over8::from_bits(over8 as _)); + }); + + Ok(()) +} + +fn configure( + info: &Info, + kernel_clock: Hertz, + config: &Config, + enable_rx: bool, + enable_tx: bool, +) -> Result<(), ConfigError> { + let r = info.regs; + let kind = info.kind; + + if !enable_rx && !enable_tx { + return Err(ConfigError::RxOrTxNotEnabled); + } + + // UART must be disabled during configuration. + r.cr1().modify(|w| { + w.set_ue(false); + }); + + #[cfg(not(usart_v1))] + let over8 = find_and_set_brr(r, kind, kernel_clock, config.baudrate)?; + #[cfg(usart_v1)] + let _over8 = find_and_set_brr(r, kind, kernel_clock, config.baudrate)?; + + r.cr2().write(|w| { + w.set_stop(match config.stop_bits { + StopBits::STOP0P5 => vals::Stop::STOP0P5, + StopBits::STOP1 => vals::Stop::STOP1, + StopBits::STOP1P5 => vals::Stop::STOP1P5, + StopBits::STOP2 => vals::Stop::STOP2, + }); + + #[cfg(any(usart_v3, usart_v4))] + { + w.set_txinv(config.invert_tx); + w.set_rxinv(config.invert_rx); + w.set_swap(config.swap_rx_tx); + } + }); + + r.cr3().modify(|w| { + #[cfg(not(usart_v1))] + w.set_onebit(config.assume_noise_free); + w.set_hdsel(config.half_duplex); + }); + + r.cr1().write(|w| { + // enable uart + w.set_ue(true); + + if config.half_duplex { + // The te and re bits will be set by write, read and flush methods. + // Receiver should be enabled by default for Half-Duplex. + w.set_te(false); + w.set_re(true); + } else { + // enable transceiver + w.set_te(enable_tx); + // enable receiver + w.set_re(enable_rx); + } + + // configure word size and parity, since the parity bit is inserted into the MSB position, + // it increases the effective word size + match (config.parity, config.data_bits) { + (Parity::ParityNone, DataBits::DataBits8) => { + trace!("USART: m0: 8 data bits, no parity"); + w.set_m0(vals::M0::BIT8); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(false); + } + (Parity::ParityNone, DataBits::DataBits9) => { + trace!("USART: m0: 9 data bits, no parity"); + w.set_m0(vals::M0::BIT9); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(false); + } + #[cfg(any(usart_v3, usart_v4))] + (Parity::ParityNone, DataBits::DataBits7) => { + trace!("USART: m0: 7 data bits, no parity"); + w.set_m0(vals::M0::BIT8); + w.set_m1(vals::M1::BIT7); + w.set_pce(false); + } + (Parity::ParityEven, DataBits::DataBits8) => { + trace!("USART: m0: 8 data bits, even parity"); + w.set_m0(vals::M0::BIT9); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(true); + w.set_ps(vals::Ps::EVEN); + } + (Parity::ParityEven, DataBits::DataBits7) => { + trace!("USART: m0: 7 data bits, even parity"); + w.set_m0(vals::M0::BIT8); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(true); + w.set_ps(vals::Ps::EVEN); + } + (Parity::ParityOdd, DataBits::DataBits8) => { + trace!("USART: m0: 8 data bits, odd parity"); + w.set_m0(vals::M0::BIT9); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(true); + w.set_ps(vals::Ps::ODD); + } + (Parity::ParityOdd, DataBits::DataBits7) => { + trace!("USART: m0: 7 data bits, odd parity"); + w.set_m0(vals::M0::BIT8); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(true); + w.set_ps(vals::Ps::ODD); + } + _ => { + return Err(ConfigError::DataParityNotSupported); + } + } + #[cfg(not(usart_v1))] + w.set_over8(vals::Over8::from_bits(over8 as _)); + #[cfg(usart_v4)] + { + trace!("USART: set_fifoen: true (usart_v4)"); + w.set_fifoen(true); + } + + Ok(()) + })?; + + Ok(()) +} + +impl<'d, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, M> { + type Error = Error; + fn read(&mut self) -> Result> { + self.nb_read() + } +} + +impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, M> { + type Error = Error; + fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, M: Mode> embedded_hal_02::serial::Read for Uart<'d, M> { + type Error = Error; + fn read(&mut self) -> Result> { + self.nb_read() + } +} + +impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, M> { + type Error = Error; + fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { + match *self { + Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, + Self::Noise => embedded_hal_nb::serial::ErrorKind::Noise, + Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, + Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, + Self::BufferTooLong => embedded_hal_nb::serial::ErrorKind::Other, + } + } +} + +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, M> { + type Error = Error; +} + +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, M> { + type Error = Error; +} + +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, M> { + type Error = Error; +} + +impl<'d, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, M> { + fn read(&mut self) -> nb::Result { + self.nb_read() + } +} + +impl<'d, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, M> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +impl<'d, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, M> { + fn read(&mut self) -> Result> { + self.nb_read() + } +} + +impl<'d, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, M> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} + +impl embedded_io::ErrorType for Uart<'_, M> { + type Error = Error; +} + +impl embedded_io::ErrorType for UartTx<'_, M> { + type Error = Error; +} + +impl embedded_io::Write for Uart<'_, M> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf)?; + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_io::Write for UartTx<'_, M> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf)?; + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_io_async::Write for Uart<'_, Async> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await?; + Ok(buf.len()) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush().await + } +} + +impl embedded_io_async::Write for UartTx<'_, Async> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await?; + Ok(buf.len()) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush().await + } +} + +pub use buffered::*; + +pub use crate::usart::buffered::InterruptHandler as BufferedInterruptHandler; +mod buffered; + +#[cfg(not(gpdma))] +mod ringbuffered; +#[cfg(not(gpdma))] +pub use ringbuffered::RingBufferedUartRx; + +#[cfg(any(usart_v1, usart_v2))] +fn tdr(r: crate::pac::usart::Usart) -> *mut u8 { + r.dr().as_ptr() as _ +} + +#[cfg(any(usart_v1, usart_v2))] +fn rdr(r: crate::pac::usart::Usart) -> *mut u8 { + r.dr().as_ptr() as _ +} + +#[cfg(any(usart_v1, usart_v2))] +fn sr(r: crate::pac::usart::Usart) -> crate::pac::common::Reg { + r.sr() +} + +#[cfg(any(usart_v1, usart_v2))] +#[allow(unused)] +fn clear_interrupt_flags(_r: Regs, _sr: regs::Sr) { + // On v1 the flags are cleared implicitly by reads and writes to DR. +} + +#[cfg(any(usart_v3, usart_v4))] +fn tdr(r: Regs) -> *mut u8 { + r.tdr().as_ptr() as _ +} + +#[cfg(any(usart_v3, usart_v4))] +fn rdr(r: Regs) -> *mut u8 { + r.rdr().as_ptr() as _ +} + +#[cfg(any(usart_v3, usart_v4))] +fn sr(r: Regs) -> crate::pac::common::Reg { + r.isr() +} + +#[cfg(any(usart_v3, usart_v4))] +#[allow(unused)] +fn clear_interrupt_flags(r: Regs, sr: regs::Isr) { + r.icr().write(|w| *w = regs::Icr(sr.0)); +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Kind { + Uart, + #[cfg(any(usart_v3, usart_v4))] + #[allow(unused)] + Lpuart, +} + +struct State { + rx_waker: AtomicWaker, + tx_rx_refcount: AtomicU8, +} + +impl State { + const fn new() -> Self { + Self { + rx_waker: AtomicWaker::new(), + tx_rx_refcount: AtomicU8::new(0), + } + } +} + +struct Info { + regs: Regs, + rcc: RccInfo, + interrupt: Interrupt, + kind: Kind, +} + +#[allow(private_interfaces)] +pub(crate) trait SealedInstance: crate::rcc::RccPeripheral { + fn info() -> &'static Info; + fn state() -> &'static State; + fn buffered_state() -> &'static buffered::State; +} + +/// USART peripheral instance trait. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +pin_trait!(RxPin, Instance); +pin_trait!(TxPin, Instance); +pin_trait!(CtsPin, Instance); +pin_trait!(RtsPin, Instance); +pin_trait!(CkPin, Instance); +pin_trait!(DePin, Instance); + +dma_trait!(TxDma, Instance); +dma_trait!(RxDma, Instance); + +macro_rules! impl_usart { + ($inst:ident, $irq:ident, $kind:expr) => { + #[allow(private_interfaces)] + impl SealedInstance for crate::peripherals::$inst { + fn info() -> &'static Info { + static INFO: Info = Info { + regs: unsafe { Regs::from_ptr(crate::pac::$inst.as_ptr()) }, + rcc: crate::peripherals::$inst::RCC_INFO, + interrupt: crate::interrupt::typelevel::$irq::IRQ, + kind: $kind, + }; + &INFO + } + + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + + fn buffered_state() -> &'static buffered::State { + static BUFFERED_STATE: buffered::State = buffered::State::new(); + &BUFFERED_STATE + } + } + + impl Instance for crate::peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +foreach_interrupt!( + ($inst:ident, usart, LPUART, $signal_name:ident, $irq:ident) => { + impl_usart!($inst, $irq, Kind::Lpuart); + }; + ($inst:ident, usart, $block:ident, $signal_name:ident, $irq:ident) => { + impl_usart!($inst, $irq, Kind::Uart); + }; +); diff --git a/embassy/embassy-stm32/src/usart/ringbuffered.rs b/embassy/embassy-stm32/src/usart/ringbuffered.rs new file mode 100644 index 0000000..560ce4e --- /dev/null +++ b/embassy/embassy-stm32/src/usart/ringbuffered.rs @@ -0,0 +1,289 @@ +use core::future::poll_fn; +use core::mem; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_embedded_hal::SetConfig; +use embassy_hal_internal::PeripheralRef; +use embedded_io_async::ReadReady; +use futures_util::future::{select, Either}; + +use super::{ + clear_interrupt_flags, rdr, reconfigure, set_baudrate, sr, Config, ConfigError, Error, Info, State, UartRx, +}; +use crate::dma::ReadableRingBuffer; +use crate::gpio::{AnyPin, SealedPin as _}; +use crate::mode::Async; +use crate::time::Hertz; +use crate::usart::{Regs, Sr}; + +/// Rx-only Ring-buffered UART Driver +/// +/// Created with [UartRx::into_ring_buffered] +pub struct RingBufferedUartRx<'d> { + info: &'static Info, + state: &'static State, + kernel_clock: Hertz, + rx: Option>, + rts: Option>, + ring_buf: ReadableRingBuffer<'d, u8>, +} + +impl<'d> SetConfig for RingBufferedUartRx<'d> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config(config) + } +} + +impl<'d> UartRx<'d, Async> { + /// Turn the `UartRx` into a buffered uart which can continously receive in the background + /// without the possibility of losing bytes. The `dma_buf` is a buffer registered to the + /// DMA controller, and must be large enough to prevent overflows. + pub fn into_ring_buffered(mut self, dma_buf: &'d mut [u8]) -> RingBufferedUartRx<'d> { + assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); + + let opts = Default::default(); + + // Safety: we forget the struct before this function returns. + let rx_dma = self.rx_dma.as_mut().unwrap(); + let request = rx_dma.request; + let rx_dma = unsafe { rx_dma.channel.clone_unchecked() }; + + let info = self.info; + let state = self.state; + let kernel_clock = self.kernel_clock; + let ring_buf = unsafe { ReadableRingBuffer::new(rx_dma, request, rdr(info.regs), dma_buf, opts) }; + let rx = unsafe { self.rx.as_ref().map(|x| x.clone_unchecked()) }; + let rts = unsafe { self.rts.as_ref().map(|x| x.clone_unchecked()) }; + + // Don't disable the clock + mem::forget(self); + + RingBufferedUartRx { + info, + state, + kernel_clock, + rx, + rts, + ring_buf, + } + } +} + +impl<'d> RingBufferedUartRx<'d> { + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + reconfigure(self.info, self.kernel_clock, config) + } + + /// Configure and start the DMA backed UART receiver + /// + /// Note: This is also done automatically by [`read()`] if required. + pub fn start_uart(&mut self) { + // Clear the buffer so that it is ready to receive data + compiler_fence(Ordering::SeqCst); + self.ring_buf.start(); + + let r = self.info.regs; + // clear all interrupts and DMA Rx Request + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + // enable parity interrupt if not ParityNone + w.set_peie(w.pce()); + // enable idle line interrupt + w.set_idleie(true); + }); + 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); + }); + } + + /// Stop DMA backed UART receiver + fn stop_uart(&mut self) { + self.ring_buf.request_pause(); + + let r = self.info.regs; + // clear all interrupts and DMA Rx Request + 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); + } + + /// Read bytes that are readily available in the ring buffer. + /// If no bytes are currently available in the buffer the call waits until the some + /// bytes are available (at least one byte and at most half the buffer size) + /// + /// Background receive is started if `start()` has not been previously called. + /// + /// Receive in the background is terminated if an error is returned. + /// It must then manually be started again by calling `start()` or by re-calling `read()`. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + let r = self.info.regs; + + // Start DMA and Uart if it was not already started, + // otherwise check for errors in status register. + let sr = clear_idle_flag(r); + if !r.cr3().read().dmar() { + self.start_uart(); + } else { + check_for_errors(sr)?; + } + + loop { + match self.ring_buf.read(buf) { + Ok((0, _)) => {} + Ok((len, _)) => { + return Ok(len); + } + Err(_) => { + self.stop_uart(); + return Err(Error::Overrun); + } + } + + match self.wait_for_data_or_idle().await { + Ok(_) => {} + Err(err) => { + self.stop_uart(); + return Err(err); + } + } + } + } + + /// Wait for uart idle or dma half-full or full + async fn wait_for_data_or_idle(&mut self) -> Result<(), Error> { + compiler_fence(Ordering::SeqCst); + + // Future which completes when idle line is detected + let s = self.state; + let uart = poll_fn(|cx| { + s.rx_waker.register(cx.waker()); + + compiler_fence(Ordering::SeqCst); + + // Critical section is needed so that IDLE isn't set after + // our read but before we clear it. + let sr = critical_section::with(|_| clear_idle_flag(self.info.regs)); + + check_for_errors(sr)?; + + if sr.idle() { + // Idle line is detected + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }); + + let mut dma_init = false; + // Future which completes when there is dma is half full or full + let dma = poll_fn(|cx| { + self.ring_buf.set_waker(cx.waker()); + + let status = match dma_init { + false => Poll::Pending, + true => Poll::Ready(()), + }; + + dma_init = true; + status + }); + + match select(uart, dma).await { + Either::Left((result, _)) => result, + Either::Right(((), _)) => Ok(()), + } + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(self.info, self.kernel_clock, baudrate) + } +} + +impl Drop for RingBufferedUartRx<'_> { + fn drop(&mut self) { + self.stop_uart(); + self.rx.as_ref().map(|x| x.set_as_disconnected()); + self.rts.as_ref().map(|x| x.set_as_disconnected()); + super::drop_tx_rx(self.info, self.state); + } +} + +/// Return an error result if the Sr register has errors +fn check_for_errors(s: Sr) -> Result<(), Error> { + if s.pe() { + Err(Error::Parity) + } else if s.fe() { + Err(Error::Framing) + } else if s.ne() { + Err(Error::Noise) + } else if s.ore() { + Err(Error::Overrun) + } else { + Ok(()) + } +} + +/// Clear IDLE and return the Sr register +fn clear_idle_flag(r: Regs) -> Sr { + // SAFETY: read only and we only use Rx related flags + + let sr = sr(r).read(); + + // This read also clears the error and idle interrupt flags on v1. + unsafe { rdr(r).read_volatile() }; + clear_interrupt_flags(r, sr); + + r.cr1().modify(|w| w.set_idleie(true)); + + sr +} + +impl embedded_io_async::ErrorType for RingBufferedUartRx<'_> { + type Error = Error; +} + +impl embedded_io_async::Read for RingBufferedUartRx<'_> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf).await + } +} + +impl ReadReady for RingBufferedUartRx<'_> { + fn read_ready(&mut self) -> Result { + let len = self.ring_buf.len().map_err(|e| match e { + crate::dma::ringbuffer::Error::Overrun => Self::Error::Overrun, + crate::dma::ringbuffer::Error::DmaUnsynced => { + error!( + "Ringbuffer error: DmaUNsynced, driver implementation is + probably bugged please open an issue" + ); + // we report this as overrun since its recoverable in the same way + Self::Error::Overrun + } + })?; + Ok(len > 0) + } +} diff --git a/embassy/embassy-stm32/src/usb/mod.rs b/embassy/embassy-stm32/src/usb/mod.rs new file mode 100644 index 0000000..ae59634 --- /dev/null +++ b/embassy/embassy-stm32/src/usb/mod.rs @@ -0,0 +1,109 @@ +//! Universal Serial Bus (USB) + +#[cfg_attr(usb, path = "usb.rs")] +#[cfg_attr(otg, path = "otg.rs")] +mod _version; +pub use _version::*; + +use crate::interrupt::typelevel::Interrupt; +use crate::rcc; + +/// clock, power initialization stuff that's common for USB and OTG. +fn common_init() { + // Check the USB clock is enabled and running at exactly 48 MHz. + // frequency() will panic if not enabled + let freq = T::frequency(); + + // On the H7RS, the USBPHYC embeds a PLL accepting one of the input frequencies listed below and providing 48MHz to OTG_FS and 60MHz to OTG_HS internally + #[cfg(any(stm32h7rs, all(stm32u5, peri_usb_otg_hs)))] + if ![16_000_000, 19_200_000, 20_000_000, 24_000_000, 26_000_000, 32_000_000].contains(&freq.0) { + panic!( + "USB clock should be one of 16, 19.2, 20, 24, 26, 32Mhz but is {} Hz. Please double-check your RCC settings.", + freq.0 + ) + } + // Check frequency is within the 0.25% tolerance allowed by the spec. + // Clock might not be exact 48Mhz due to rounding errors in PLL calculation, or if the user + // has tight clock restrictions due to something else (like audio). + #[cfg(not(any(stm32h7rs, all(stm32u5, peri_usb_otg_hs))))] + if freq.0.abs_diff(48_000_000) > 120_000 { + panic!( + "USB clock should be 48Mhz but is {} Hz. Please double-check your RCC settings.", + freq.0 + ) + } + + #[cfg(any(stm32l4, stm32l5, stm32wb, stm32u0))] + critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true))); + + #[cfg(pwr_h5)] + critical_section::with(|_| crate::pac::PWR.usbscr().modify(|w| w.set_usb33sv(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() {} + } + + #[cfg(stm32h7rs)] + { + // If true, VDD33USB is generated by internal regulator from VDD50USB + // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo) + // TODO: unhardcode + let internal_regulator = false; + + // Enable USB power + critical_section::with(|_| { + crate::pac::PWR.csr2().modify(|w| { + w.set_usbregen(internal_regulator); + w.set_usb33den(true); + w.set_usbhsregen(true); + }) + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.csr2().read().usb33rdy() {} + } + + #[cfg(stm32u5)] + { + // Enable USB power + critical_section::with(|_| { + 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() {} + + // Now set up transceiver power if it's a OTG-HS + #[cfg(peri_usb_otg_hs)] + { + crate::pac::PWR.vosr().modify(|w| { + w.set_usbpwren(true); + w.set_usbboosten(true); + }); + while !crate::pac::PWR.vosr().read().usbboostrdy() {} + } + } + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + rcc::enable_and_reset::(); +} diff --git a/embassy/embassy-stm32/src/usb/otg.rs b/embassy/embassy-stm32/src/usb/otg.rs new file mode 100644 index 0000000..d3c7978 --- /dev/null +++ b/embassy/embassy-stm32/src/usb/otg.rs @@ -0,0 +1,584 @@ +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, Peripheral}; +use embassy_usb_driver::{EndpointAddress, EndpointAllocError, EndpointType, Event, Unsupported}; +use embassy_usb_synopsys_otg::otg_v1::vals::Dspd; +use embassy_usb_synopsys_otg::otg_v1::Otg; +pub use embassy_usb_synopsys_otg::Config; +use embassy_usb_synopsys_otg::{ + on_interrupt as on_interrupt_impl, Bus as OtgBus, ControlPipe, Driver as OtgDriver, Endpoint, In, OtgInstance, Out, + PhyType, State, +}; + +use crate::gpio::{AfType, OutputType, Speed}; +use crate::interrupt; +use crate::interrupt::typelevel::Interrupt; +use crate::rcc::{self, RccPeripheral}; + +const MAX_EP_COUNT: usize = 9; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let state = T::state(); + on_interrupt_impl(r, state, T::ENDPOINT_COUNT); + } +} + +macro_rules! config_ulpi_pins { + ($($pin:ident),*) => { + into_ref!($($pin),*); + critical_section::with(|_| { + $( + $pin.set_as_af($pin.af_num(), AfType::output(OutputType::PushPull, 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 driver. +pub struct Driver<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + inner: OtgDriver<'d, MAX_EP_COUNT>, +} + +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 received packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_fs( + _peri: impl Peripheral

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

> + 'd, + dm: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(dp, dm); + + dp.set_as_af(dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + dm.set_as_af(dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + + let regs = T::regs(); + + let instance = OtgInstance { + regs, + state: T::state(), + fifo_depth_words: T::FIFO_DEPTH_WORDS, + extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, + endpoint_count: T::ENDPOINT_COUNT, + phy_type: PhyType::InternalFullSpeed, + calculate_trdt_fn: calculate_trdt::, + }; + + Self { + inner: OtgDriver::new(ep_out_buffer, instance, config), + phantom: PhantomData, + } + } + + /// Initializes USB OTG peripheral with internal High-Speed PHY. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_hs( + _peri: impl Peripheral

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

> + 'd, + _dm: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + // For STM32U5 High speed pins need to be left in analog mode + #[cfg(not(all(stm32u5, peri_usb_otg_hs)))] + { + into_ref!(_dp, _dm); + _dp.set_as_af(_dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + _dm.set_as_af(_dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + } + + let instance = OtgInstance { + regs: T::regs(), + state: T::state(), + fifo_depth_words: T::FIFO_DEPTH_WORDS, + extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, + endpoint_count: T::ENDPOINT_COUNT, + phy_type: PhyType::InternalHighSpeed, + calculate_trdt_fn: calculate_trdt::, + }; + + Self { + inner: OtgDriver::new(ep_out_buffer, instance, config), + phantom: PhantomData, + } + } + + /// Initializes USB OTG peripheral with external Full-speed PHY (usually, a High-speed PHY in Full-speed mode). + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_fs_ulpi( + _peri: impl Peripheral

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

> + 'd, + ulpi_dir: impl Peripheral

> + 'd, + ulpi_nxt: impl Peripheral

> + 'd, + ulpi_stp: impl Peripheral

> + 'd, + ulpi_d0: impl Peripheral

> + 'd, + ulpi_d1: impl Peripheral

> + 'd, + ulpi_d2: impl Peripheral

> + 'd, + ulpi_d3: impl Peripheral

> + 'd, + ulpi_d4: impl Peripheral

> + 'd, + ulpi_d5: impl Peripheral

> + 'd, + ulpi_d6: impl Peripheral

> + 'd, + ulpi_d7: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + config_ulpi_pins!( + ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6, + ulpi_d7 + ); + + let instance = OtgInstance { + regs: T::regs(), + state: T::state(), + fifo_depth_words: T::FIFO_DEPTH_WORDS, + extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, + endpoint_count: T::ENDPOINT_COUNT, + phy_type: PhyType::ExternalFullSpeed, + calculate_trdt_fn: calculate_trdt::, + }; + + Self { + inner: OtgDriver::new(ep_out_buffer, instance, config), + phantom: PhantomData, + } + } + + /// Initializes USB OTG peripheral with external High-Speed PHY. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_hs_ulpi( + _peri: impl Peripheral

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

> + 'd, + ulpi_dir: impl Peripheral

> + 'd, + ulpi_nxt: impl Peripheral

> + 'd, + ulpi_stp: impl Peripheral

> + 'd, + ulpi_d0: impl Peripheral

> + 'd, + ulpi_d1: impl Peripheral

> + 'd, + ulpi_d2: impl Peripheral

> + 'd, + ulpi_d3: impl Peripheral

> + 'd, + ulpi_d4: impl Peripheral

> + 'd, + ulpi_d5: impl Peripheral

> + 'd, + ulpi_d6: impl Peripheral

> + 'd, + ulpi_d7: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + 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 + ); + + let instance = OtgInstance { + regs: T::regs(), + state: T::state(), + fifo_depth_words: T::FIFO_DEPTH_WORDS, + extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, + endpoint_count: T::ENDPOINT_COUNT, + phy_type: PhyType::ExternalHighSpeed, + calculate_trdt_fn: calculate_trdt::, + }; + + Self { + inner: OtgDriver::new(ep_out_buffer, instance, config), + phantom: PhantomData, + } + } +} + +impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> { + type EndpointOut = Endpoint<'d, Out>; + type EndpointIn = Endpoint<'d, In>; + type ControlPipe = ControlPipe<'d>; + type Bus = Bus<'d, T>; + + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.inner.alloc_endpoint_in(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.inner.alloc_endpoint_out(ep_type, max_packet_size, interval_ms) + } + + fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + let (bus, cp) = self.inner.start(control_max_packet_size); + + ( + Bus { + phantom: PhantomData, + inner: bus, + inited: false, + }, + cp, + ) + } +} + +/// USB bus. +pub struct Bus<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + inner: OtgBus<'d, MAX_EP_COUNT>, + inited: bool, +} + +impl<'d, T: Instance> Bus<'d, T> { + fn init(&mut self) { + super::common_init::(); + + // Enable ULPI clock if external PHY is used + let phy_type = self.inner.phy_type(); + let _ulpien = !phy_type.internal(); + + #[cfg(any(stm32f2, stm32f4, stm32f7))] + if T::HIGH_SPEED { + critical_section::with(|_| { + let rcc = crate::pac::RCC; + rcc.ahb1enr().modify(|w| w.set_usb_otg_hsulpien(_ulpien)); + rcc.ahb1lpenr().modify(|w| w.set_usb_otg_hsulpilpen(_ulpien)); + }); + } + + #[cfg(stm32h7)] + critical_section::with(|_| { + let rcc = crate::pac::RCC; + if T::HIGH_SPEED { + rcc.ahb1enr().modify(|w| w.set_usb_otg_hs_ulpien(_ulpien)); + rcc.ahb1lpenr().modify(|w| w.set_usb_otg_hs_ulpilpen(_ulpien)); + } else { + rcc.ahb1enr().modify(|w| w.set_usb_otg_fs_ulpien(_ulpien)); + rcc.ahb1lpenr().modify(|w| w.set_usb_otg_fs_ulpilpen(_ulpien)); + } + }); + + #[cfg(stm32h7rs)] + critical_section::with(|_| { + let rcc = crate::pac::RCC; + rcc.ahb1enr().modify(|w| { + w.set_usbphycen(true); + w.set_usb_otg_hsen(true); + }); + rcc.ahb1lpenr().modify(|w| { + w.set_usbphyclpen(true); + w.set_usb_otg_hslpen(true); + }); + }); + + #[cfg(all(stm32u5, peri_usb_otg_hs))] + { + crate::pac::SYSCFG.otghsphycr().modify(|w| { + w.set_en(true); + }); + + critical_section::with(|_| { + crate::pac::RCC.ahb2enr1().modify(|w| { + w.set_usb_otg_hsen(true); + w.set_usb_otg_hs_phyen(true); + }); + }); + } + + let r = T::regs(); + let core_id = r.cid().read().0; + trace!("Core id {:08x}", core_id); + + // Wait for AHB ready. + while !r.grstctl().read().ahbidl() {} + + // Configure as device. + self.inner.configure_as_device(); + + // Configuring Vbus sense and SOF output + match core_id { + 0x0000_1200 | 0x0000_1100 => self.inner.config_v1(), + 0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => self.inner.config_v2v3(), + 0x0000_5000 => self.inner.config_v5(), + _ => unimplemented!("Unknown USB core id {:X}", core_id), + } + } + + fn disable(&mut self) { + T::Interrupt::disable(); + + rcc::disable::(); + self.inited = false; + + #[cfg(stm32l4)] + crate::pac::PWR.cr2().modify(|w| w.set_usv(false)); + // Cannot disable PWR, because other peripherals might be using it + } +} + +impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> { + async fn poll(&mut self) -> Event { + if !self.inited { + self.init(); + self.inited = true; + } + + self.inner.poll().await + } + + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { + self.inner.endpoint_set_stalled(ep_addr, stalled) + } + + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + self.inner.endpoint_is_stalled(ep_addr) + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + self.inner.endpoint_set_enabled(ep_addr, enabled) + } + + async fn enable(&mut self) { + self.inner.enable().await + } + + async fn disable(&mut self) { + // NOTE: inner call is a no-op + self.inner.disable().await + } + + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + self.inner.remote_wakeup().await + } +} + +impl<'d, T: Instance> Drop for Bus<'d, T> { + fn drop(&mut self) { + Bus::disable(self); + } +} + +trait SealedInstance { + const HIGH_SPEED: bool; + const FIFO_DEPTH_WORDS: u16; + const ENDPOINT_COUNT: usize; + + fn regs() -> Otg; + fn state() -> &'static State<{ MAX_EP_COUNT }>; +} + +/// USB instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral + 'static { + /// Interrupt for this USB instance. + type Interrupt: interrupt::typelevel::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 SealedInstance for crate::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(any(stm32h7, stm32h7rs))] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 9; + } else if #[cfg(stm32u5)] { + const FIFO_DEPTH_WORDS: u16 = 320; + const ENDPOINT_COUNT: usize = 6; + } else { + compile_error!("USB_OTG_FS peripheral is not supported by this chip."); + } + } + + fn regs() -> Otg { + unsafe { Otg::from_ptr(crate::pac::USB_OTG_FS.as_ptr()) } + } + + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for crate::peripherals::USB_OTG_FS { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; + + (USB_OTG_HS, otg, $block:ident, GLOBAL, $irq:ident) => { + impl SealedInstance for crate::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, stm32h7rs, + ))] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 9; + } else if #[cfg(stm32u5)] { + 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() -> Otg { + // OTG HS registers are a superset of FS registers + unsafe { Otg::from_ptr(crate::pac::USB_OTG_HS.as_ptr()) } + } + + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for crate::peripherals::USB_OTG_HS { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +); + +fn calculate_trdt(speed: Dspd) -> u8 { + let ahb_freq = T::frequency().0; + match speed { + Dspd::HIGH_SPEED => { + // From RM0431 (F72xx), RM0090 (F429), RM0390 (F446) + if ahb_freq >= 30_000_000 || cfg!(stm32h7rs) { + 0x9 + } else { + panic!("AHB frequency is too low") + } + } + Dspd::FULL_SPEED_EXTERNAL | Dspd::FULL_SPEED_INTERNAL => { + // From RM0431 (F72xx), RM0090 (F429) + match ahb_freq { + 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/embassy-stm32/src/usb/usb.rs b/embassy/embassy-stm32/src/usb/usb.rs new file mode 100644 index 0000000..94af00b --- /dev/null +++ b/embassy/embassy-stm32/src/usb/usb.rs @@ -0,0 +1,1226 @@ +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::into_ref; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb_driver as driver; +use embassy_usb_driver::{ + Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointInfo, EndpointType, Event, Unsupported, +}; + +use crate::pac::usb::regs; +use crate::pac::usb::vals::{EpType, Stat}; +use crate::pac::USBRAM; +use crate::rcc::RccPeripheral; +use crate::{interrupt, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + //let x = regs.istr().read().0; + //trace!("USB IRQ: {:08x}", x); + + let istr = regs.istr().read(); + + if istr.susp() { + //trace!("USB IRQ: susp"); + 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"); + 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"); + 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); + + // Wake main thread. + BUS_WAKER.wake(); + } + + if istr.ctr() { + let index = istr.ep_id() as usize; + CTR_TRIGGERED[index].store(true, Ordering::Relaxed); + + let mut epr = regs.epr(index).read(); + if epr.ctr_rx() { + if index == 0 && epr.setup() { + EP0_SETUP.store(true, Ordering::Relaxed); + } + //trace!("EP {} RX, setup={}", index, epr.setup()); + EP_OUT_WAKERS[index].wake(); + } + if epr.ctr_tx() { + //trace!("EP {} TX", index); + EP_IN_WAKERS[index].wake(); + } + epr.set_dtog_rx(false); + epr.set_dtog_tx(false); + epr.set_stat_rx(Stat::from_bits(0)); + epr.set_stat_tx(Stat::from_bits(0)); + epr.set_ctr_rx(!epr.ctr_rx()); + epr.set_ctr_tx(!epr.ctr_tx()); + regs.epr(index).write_value(epr); + } + } +} + +const EP_COUNT: usize = 8; + +#[cfg(any(usbram_16x1_512, usbram_16x2_512))] +const USBRAM_SIZE: usize = 512; +#[cfg(any(usbram_16x2_1024, usbram_32_1024))] +const USBRAM_SIZE: usize = 1024; +#[cfg(usbram_32_2048)] +const USBRAM_SIZE: usize = 2048; + +#[cfg(not(any(usbram_32_2048, usbram_32_1024)))] +const USBRAM_ALIGN: usize = 2; +#[cfg(any(usbram_32_2048, usbram_32_1024))] +const USBRAM_ALIGN: usize = 4; + +static BUS_WAKER: AtomicWaker = AtomicWaker::new(); +static EP0_SETUP: AtomicBool = AtomicBool::new(false); + +static CTR_TRIGGERED: [AtomicBool; EP_COUNT] = [const { AtomicBool::new(false) }; EP_COUNT]; +static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT]; +static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT]; +static IRQ_RESET: AtomicBool = AtomicBool::new(false); +static IRQ_SUSPEND: AtomicBool = AtomicBool::new(false); +static IRQ_RESUME: AtomicBool = AtomicBool::new(false); + +fn convert_type(t: EndpointType) -> EpType { + match t { + EndpointType::Bulk => EpType::BULK, + EndpointType::Control => EpType::CONTROL, + EndpointType::Interrupt => EpType::INTERRUPT, + EndpointType::Isochronous => EpType::ISO, + } +} + +fn invariant(mut r: regs::Epr) -> regs::Epr { + r.set_ctr_rx(true); // don't clear + r.set_ctr_tx(true); // don't clear + r.set_dtog_rx(false); // don't toggle + r.set_dtog_tx(false); // don't toggle + r.set_stat_rx(Stat::from_bits(0)); + r.set_stat_tx(Stat::from_bits(0)); + r +} + +fn align_len_up(len: u16) -> u16 { + ((len as usize + USBRAM_ALIGN - 1) / USBRAM_ALIGN * USBRAM_ALIGN) as u16 +} + +// Returns (actual_len, len_bits) +fn calc_out_len(len: u16) -> (u16, u16) { + match len { + // NOTE: this could be 2..=62 with 16bit USBRAM, but not with 32bit. Limit it to 60 for simplicity. + 2..=60 => (align_len_up(len), align_len_up(len) / 2 << 10), + 61..=1024 => ((len + 31) / 32 * 32, (((len + 31) / 32 - 1) << 10) | 0x8000), + _ => panic!("invalid OUT length {}", len), + } +} + +#[cfg(not(any(usbram_32_2048, usbram_32_1024)))] +mod btable { + use super::*; + + pub(super) fn write_in_tx(index: usize, addr: u16) { + USBRAM.mem(index * 4 + 0).write_value(addr); + } + + pub(super) fn write_in_rx(index: usize, addr: u16) { + USBRAM.mem(index * 4 + 2).write_value(addr); + } + + pub(super) fn write_in_len_rx(index: usize, _addr: u16, len: u16) { + USBRAM.mem(index * 4 + 3).write_value(len); + } + + pub(super) fn write_in_len_tx(index: usize, _addr: u16, len: u16) { + USBRAM.mem(index * 4 + 1).write_value(len); + } + + pub(super) fn write_out_rx(index: usize, addr: u16, max_len_bits: u16) { + USBRAM.mem(index * 4 + 2).write_value(addr); + USBRAM.mem(index * 4 + 3).write_value(max_len_bits); + } + + pub(super) fn write_out_tx(index: usize, addr: u16, max_len_bits: u16) { + USBRAM.mem(index * 4 + 0).write_value(addr); + USBRAM.mem(index * 4 + 1).write_value(max_len_bits); + } + + pub(super) fn read_out_len_tx(index: usize) -> u16 { + USBRAM.mem(index * 4 + 1).read() + } + + pub(super) fn read_out_len_rx(index: usize) -> u16 { + USBRAM.mem(index * 4 + 3).read() + } +} +#[cfg(any(usbram_32_2048, usbram_32_1024))] +mod btable { + use super::*; + + pub(super) fn write_in_tx(_index: usize, _addr: u16) {} + + pub(super) fn write_in_rx(_index: usize, _addr: u16) {} + + pub(super) fn write_in_len_tx(index: usize, addr: u16, len: u16) { + USBRAM.mem(index * 2).write_value((addr as u32) | ((len as u32) << 16)); + } + + pub(super) fn write_in_len_rx(index: usize, addr: u16, len: u16) { + USBRAM + .mem(index * 2 + 1) + .write_value((addr as u32) | ((len as u32) << 16)); + } + + pub(super) fn write_out_tx(index: usize, addr: u16, max_len_bits: u16) { + USBRAM + .mem(index * 2) + .write_value((addr as u32) | ((max_len_bits as u32) << 16)); + } + + pub(super) fn write_out_rx(index: usize, addr: u16, max_len_bits: u16) { + USBRAM + .mem(index * 2 + 1) + .write_value((addr as u32) | ((max_len_bits as u32) << 16)); + } + + pub(super) fn read_out_len_tx(index: usize) -> u16 { + (USBRAM.mem(index * 2).read() >> 16) as u16 + } + + pub(super) fn read_out_len_rx(index: usize) -> u16 { + (USBRAM.mem(index * 2 + 1).read() >> 16) as u16 + } +} + +struct EndpointBuffer { + addr: u16, + len: u16, + _phantom: PhantomData, +} + +impl EndpointBuffer { + fn read(&mut self, buf: &mut [u8]) { + assert!(buf.len() <= self.len as usize); + for i in 0..(buf.len() + USBRAM_ALIGN - 1) / USBRAM_ALIGN { + let val = USBRAM.mem(self.addr as usize / USBRAM_ALIGN + i).read(); + let n = USBRAM_ALIGN.min(buf.len() - i * USBRAM_ALIGN); + buf[i * USBRAM_ALIGN..][..n].copy_from_slice(&val.to_le_bytes()[..n]); + } + } + + fn write(&mut self, buf: &[u8]) { + assert!(buf.len() <= self.len as usize); + for i in 0..(buf.len() + USBRAM_ALIGN - 1) / USBRAM_ALIGN { + let mut val = [0u8; USBRAM_ALIGN]; + let n = USBRAM_ALIGN.min(buf.len() - i * USBRAM_ALIGN); + val[..n].copy_from_slice(&buf[i * USBRAM_ALIGN..][..n]); + + #[cfg(not(any(usbram_32_2048, usbram_32_1024)))] + let val = u16::from_le_bytes(val); + #[cfg(any(usbram_32_2048, usbram_32_1024))] + let val = u32::from_le_bytes(val); + USBRAM.mem(self.addr as usize / USBRAM_ALIGN + i).write_value(val); + } + } +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct EndpointData { + ep_type: EndpointType, // only valid if used_in || used_out + used_in: bool, + used_out: bool, +} + +/// USB driver. +pub struct Driver<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + alloc: [EndpointData; EP_COUNT], + ep_mem_free: u16, // first free address in EP mem, in bytes. +} + +impl<'d, T: Instance> Driver<'d, T> { + /// Create a new USB driver. + pub fn new( + _usb: impl Peripheral

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

> + 'd, + dm: impl Peripheral

> + 'd, + ) -> Self { + into_ref!(dp, dm); + + super::common_init::(); + + let regs = T::regs(); + + regs.cntr().write(|w| { + w.set_pdwn(false); + w.set_fres(true); + }); + + #[cfg(feature = "time")] + embassy_time::block_for(embassy_time::Duration::from_millis(100)); + #[cfg(not(feature = "time"))] + cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 10); + + #[cfg(not(usb_v4))] + regs.btable().write(|w| w.set_btable(0)); + + #[cfg(not(stm32l1))] + { + use crate::gpio::{AfType, OutputType, Speed}; + dp.set_as_af(dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + dm.set_as_af(dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + } + #[cfg(stm32l1)] + let _ = (dp, dm); // suppress "unused" warnings. + + // Initialize the bus so that it signals that power is available + BUS_WAKER.wake(); + + Self { + phantom: PhantomData, + alloc: [EndpointData { + ep_type: EndpointType::Bulk, + used_in: false, + used_out: false, + }; EP_COUNT], + ep_mem_free: EP_COUNT as u16 * 8, // for each EP, 4 regs, so 8 bytes + } + } + + fn alloc_ep_mem(&mut self, len: u16) -> u16 { + assert!(len as usize % USBRAM_ALIGN == 0); + let addr = self.ep_mem_free; + if addr + len > USBRAM_SIZE as _ { + panic!("Endpoint memory full"); + } + self.ep_mem_free += len; + addr + } + + fn alloc_endpoint( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result, driver::EndpointAllocError> { + trace!( + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", + ep_type, + max_packet_size, + interval_ms, + D::dir() + ); + + let index = self.alloc.iter_mut().enumerate().find(|(i, ep)| { + if *i == 0 && ep_type != EndpointType::Control { + return false; // reserved for control pipe + } + let used = ep.used_out || ep.used_in; + if used && (ep.ep_type == EndpointType::Isochronous || ep.ep_type == EndpointType::Bulk) { + // Isochronous and bulk endpoints are double-buffered. + // Their corresponding endpoint/channel registers are forced to be unidirectional. + // Do not reuse this index. + return false; + } + + let used_dir = match D::dir() { + Direction::Out => ep.used_out, + Direction::In => ep.used_in, + }; + !used || (ep.ep_type == ep_type && !used_dir) + }); + + let (index, ep) = match index { + Some(x) => x, + None => return Err(EndpointAllocError), + }; + + ep.ep_type = ep_type; + + let buf = match D::dir() { + Direction::Out => { + assert!(!ep.used_out); + ep.used_out = true; + + let (len, len_bits) = calc_out_len(max_packet_size); + let addr = self.alloc_ep_mem(len); + + trace!(" len_bits = {:04x}", len_bits); + btable::write_out_rx::(index, addr, len_bits); + + if ep_type == EndpointType::Isochronous { + btable::write_out_tx::(index, addr, len_bits); + } + + EndpointBuffer { + addr, + len, + _phantom: PhantomData, + } + } + Direction::In => { + assert!(!ep.used_in); + ep.used_in = true; + + let len = align_len_up(max_packet_size); + let addr = self.alloc_ep_mem(len); + + // ep_in_len is written when actually TXing packets. + btable::write_in_tx::(index, addr); + + if ep_type == EndpointType::Isochronous { + btable::write_in_rx::(index, addr); + } + + EndpointBuffer { + addr, + len, + _phantom: PhantomData, + } + } + }; + + trace!(" index={} addr={} len={}", index, buf.addr, buf.len); + + Ok(Endpoint { + _phantom: PhantomData, + info: EndpointInfo { + addr: EndpointAddress::from_parts(index, D::dir()), + ep_type, + max_packet_size, + interval_ms, + }, + buf, + }) + } +} + +impl<'d, T: Instance> 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); + + let regs = T::regs(); + + regs.cntr().write(|w| { + w.set_pdwn(false); + w.set_fres(false); + w.set_resetm(true); + w.set_suspm(true); + w.set_wkupm(true); + w.set_ctrm(true); + }); + + #[cfg(any(usb_v3, usb_v4))] + regs.bcdr().write(|w| w.set_dppu(true)); + + #[cfg(stm32l1)] + crate::pac::SYSCFG.pmc().modify(|w| w.set_usb_pu(true)); + + trace!("enabled"); + + let mut ep_types = [EpType::BULK; EP_COUNT - 1]; + for i in 1..EP_COUNT { + ep_types[i - 1] = convert_type(self.alloc[i].ep_type); + } + + ( + Bus { + phantom: PhantomData, + ep_types, + inited: false, + }, + ControlPipe { + _phantom: PhantomData, + max_packet_size: control_max_packet_size, + ep_out, + ep_in, + }, + ) + } +} + +/// USB bus. +pub struct Bus<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + ep_types: [EpType; EP_COUNT - 1], + inited: bool, +} + +impl<'d, T: Instance> driver::Bus for Bus<'d, T> { + async fn poll(&mut self) -> Event { + poll_fn(move |cx| { + BUS_WAKER.register(cx.waker()); + + // TODO: implement VBUS detection. + if !self.inited { + self.inited = true; + return Poll::Ready(Event::PowerDetected); + } + + let regs = T::regs(); + + if IRQ_RESUME.load(Ordering::Acquire) { + IRQ_RESUME.store(false, Ordering::Relaxed); + return Poll::Ready(Event::Resume); + } + + if IRQ_RESET.load(Ordering::Acquire) { + IRQ_RESET.store(false, Ordering::Relaxed); + + trace!("RESET"); + regs.daddr().write(|w| { + w.set_ef(true); + w.set_add(0); + }); + + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::NAK); + w.set_stat_tx(Stat::NAK); + }); + + for i in 1..EP_COUNT { + regs.epr(i).write(|w| { + w.set_ea(i as _); + w.set_ep_type(self.ep_types[i - 1]); + }) + } + + for w in &EP_IN_WAKERS { + w.wake() + } + for w in &EP_OUT_WAKERS { + w.wake() + } + + return Poll::Ready(Event::Reset); + } + + if IRQ_SUSPEND.load(Ordering::Acquire) { + IRQ_SUSPEND.store(false, Ordering::Relaxed); + return Poll::Ready(Event::Suspend); + } + + Poll::Pending + }) + .await + } + + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { + // This can race, so do a retry loop. + let reg = T::regs().epr(ep_addr.index() as _); + match ep_addr.direction() { + Direction::In => { + loop { + let r = reg.read(); + match r.stat_tx() { + Stat::DISABLED => break, // if disabled, stall does nothing. + Stat::STALL => break, // done! + _ => { + let want_stat = match stalled { + false => Stat::NAK, + true => Stat::STALL, + }; + let mut w = invariant(r); + w.set_stat_tx(Stat::from_bits(r.stat_tx().to_bits() ^ want_stat.to_bits())); + reg.write_value(w); + } + } + } + EP_IN_WAKERS[ep_addr.index()].wake(); + } + Direction::Out => { + loop { + let r = reg.read(); + match r.stat_rx() { + Stat::DISABLED => break, // if disabled, stall does nothing. + Stat::STALL => break, // done! + _ => { + let want_stat = match stalled { + false => Stat::VALID, + true => Stat::STALL, + }; + let mut w = invariant(r); + w.set_stat_rx(Stat::from_bits(r.stat_rx().to_bits() ^ want_stat.to_bits())); + reg.write_value(w); + } + } + } + EP_OUT_WAKERS[ep_addr.index()].wake(); + } + } + } + + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + let regs = T::regs(); + let epr = regs.epr(ep_addr.index() as _).read(); + match ep_addr.direction() { + Direction::In => epr.stat_tx() == Stat::STALL, + Direction::Out => epr.stat_rx() == Stat::STALL, + } + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + trace!("set_enabled {:x} {}", ep_addr, enabled); + // This can race, so do a retry loop. + let reg = T::regs().epr(ep_addr.index() as _); + trace!("EPR before: {:04x}", reg.read().0); + match ep_addr.direction() { + Direction::In => { + loop { + let want_stat = match enabled { + false => Stat::DISABLED, + true => Stat::NAK, + }; + let r = reg.read(); + if r.stat_tx() == want_stat { + break; + } + let mut w = invariant(r); + w.set_stat_tx(Stat::from_bits(r.stat_tx().to_bits() ^ want_stat.to_bits())); + reg.write_value(w); + } + EP_IN_WAKERS[ep_addr.index()].wake(); + } + Direction::Out => { + loop { + let want_stat = match enabled { + false => Stat::DISABLED, + true => Stat::VALID, + }; + let r = reg.read(); + if r.stat_rx() == want_stat { + break; + } + let mut w = invariant(r); + w.set_stat_rx(Stat::from_bits(r.stat_rx().to_bits() ^ want_stat.to_bits())); + reg.write_value(w); + } + EP_OUT_WAKERS[ep_addr.index()].wake(); + } + } + trace!("EPR after: {:04x}", reg.read().0); + } + + async fn enable(&mut self) {} + async fn disable(&mut self) {} + + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } +} + +trait Dir { + fn dir() -> Direction; +} + +/// Marker type for the "IN" direction. +pub enum In {} +impl Dir for In { + fn dir() -> Direction { + Direction::In + } +} + +/// Marker type for the "OUT" direction. +pub enum Out {} +impl Dir for Out { + fn dir() -> Direction { + Direction::Out + } +} + +/// Selects the packet buffer. +/// +/// For double-buffered endpoints, both the `Rx` and `Tx` buffer from a channel are used for the same +/// direction of transfer. This is opposed to single-buffered endpoints, where one channel can serve +/// two directions at the same time. +enum PacketBuffer { + /// The RX buffer - must be used for single-buffered OUT endpoints + Rx, + /// The TX buffer - must be used for single-buffered IN endpoints + Tx, +} + +/// USB endpoint. +pub struct Endpoint<'d, T: Instance, D> { + _phantom: PhantomData<(&'d mut T, D)>, + info: EndpointInfo, + buf: EndpointBuffer, +} + +impl<'d, T: Instance, D> Endpoint<'d, T, D> { + /// Write to a double-buffered endpoint. + /// + /// For double-buffered endpoints, the data buffers overlap, but we still need to write to the right counter field. + /// The DTOG_TX bit indicates the buffer that is currently in use by the USB peripheral, that is, the buffer in + /// which the next transmit packet will be stored, so we need to write the counter of the OTHER buffer, which is + /// where the last transmitted packet was stored. + fn write_data_double_buffered(&mut self, buf: &[u8], packet_buffer: PacketBuffer) { + let index = self.info.addr.index(); + self.buf.write(buf); + + match packet_buffer { + PacketBuffer::Rx => btable::write_in_len_rx::(index, self.buf.addr, buf.len() as _), + PacketBuffer::Tx => btable::write_in_len_tx::(index, self.buf.addr, buf.len() as _), + } + } + + /// Write to a single-buffered endpoint. + fn write_data(&mut self, buf: &[u8]) { + self.write_data_double_buffered(buf, PacketBuffer::Tx); + } + + /// Read from a double-buffered endpoint. + /// + /// For double-buffered endpoints, the data buffers overlap, but we still need to read from the right counter field. + /// The DTOG_RX bit indicates the buffer that is currently in use by the USB peripheral, that is, the buffer in + /// which the next received packet will be stored, so we need to read the counter of the OTHER buffer, which is + /// where the last received packet was stored. + fn read_data_double_buffered( + &mut self, + buf: &mut [u8], + packet_buffer: PacketBuffer, + ) -> Result { + let index = self.info.addr.index(); + + let rx_len = match packet_buffer { + PacketBuffer::Rx => btable::read_out_len_rx::(index), + PacketBuffer::Tx => btable::read_out_len_tx::(index), + } as usize + & 0x3FF; + + trace!("READ DONE, rx_len = {}", rx_len); + if rx_len > buf.len() { + return Err(EndpointError::BufferOverflow); + } + self.buf.read(&mut buf[..rx_len]); + Ok(rx_len) + } + + /// Read from a single-buffered endpoint. + fn read_data(&mut self, buf: &mut [u8]) -> Result { + self.read_data_double_buffered(buf, PacketBuffer::Rx) + } +} + +impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + 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 regs = T::regs(); + if regs.epr(index).read().stat_tx() == Stat::DISABLED { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + trace!("wait_enabled IN OK"); + } +} + +impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, Out> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + 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 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> { + 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(); + let stat = regs.epr(index).read().stat_rx(); + if self.info.ep_type == EndpointType::Isochronous { + // The isochronous endpoint does not change its `STAT_RX` field to `NAK` when receiving a packet. + // Therefore, this instead waits until the `CTR` interrupt was triggered. + if matches!(stat, Stat::DISABLED) || CTR_TRIGGERED[index].load(Ordering::Relaxed) { + Poll::Ready(stat) + } else { + Poll::Pending + } + } else { + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } + } + }) + .await; + + CTR_TRIGGERED[index].store(false, Ordering::Relaxed); + + if stat == Stat::DISABLED { + return Err(EndpointError::Disabled); + } + + let regs = T::regs(); + + let packet_buffer = if self.info.ep_type == EndpointType::Isochronous { + // Find the buffer, which is currently in use. Read from the OTHER buffer. + if regs.epr(index).read().dtog_rx() { + PacketBuffer::Rx + } else { + PacketBuffer::Tx + } + } else { + PacketBuffer::Rx + }; + + let rx_len = self.read_data_double_buffered(buf, packet_buffer)?; + + regs.epr(index).write(|w| { + w.set_ep_type(convert_type(self.info.ep_type)); + w.set_ea(self.info.addr.index() as _); + if self.info.ep_type == EndpointType::Isochronous { + w.set_stat_rx(Stat::from_bits(0)); // STAT_RX remains `VALID`. + } else { + w.set_stat_rx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + } + w.set_stat_tx(Stat::from_bits(0)); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + trace!("READ OK, rx_len = {}", rx_len); + + Ok(rx_len) + } +} + +impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { + 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 = regs.epr(index).read().stat_tx(); + if self.info.ep_type == EndpointType::Isochronous { + // The isochronous endpoint does not change its `STAT_RX` field to `NAK` when receiving a packet. + // Therefore, this instead waits until the `CTR` interrupt was triggered. + if matches!(stat, Stat::DISABLED) || CTR_TRIGGERED[index].load(Ordering::Relaxed) { + Poll::Ready(stat) + } else { + Poll::Pending + } + } else { + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } + } + }) + .await; + + CTR_TRIGGERED[index].store(false, Ordering::Relaxed); + + if stat == Stat::DISABLED { + return Err(EndpointError::Disabled); + } + + let regs = T::regs(); + + let packet_buffer = if self.info.ep_type == EndpointType::Isochronous { + // Find the buffer, which is currently in use. Write to the OTHER buffer. + if regs.epr(index).read().dtog_tx() { + PacketBuffer::Tx + } else { + PacketBuffer::Rx + } + } else { + PacketBuffer::Tx + }; + + self.write_data_double_buffered(buf, packet_buffer); + + let regs = T::regs(); + regs.epr(index).write(|w| { + w.set_ep_type(convert_type(self.info.ep_type)); + w.set_ea(self.info.addr.index() as _); + if self.info.ep_type == EndpointType::Isochronous { + w.set_stat_tx(Stat::from_bits(0)); // STAT_TX remains `VALID`. + } else { + w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + } + w.set_stat_rx(Stat::from_bits(0)); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + + trace!("WRITE OK"); + + Ok(()) + } +} + +/// USB control pipe. +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> 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] { + 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; + } + } + + 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.to_bits() ^ Stat::VALID.to_bits(); + stat_tx ^= Stat::NAK.to_bits() ^ Stat::STALL.to_bits(); + } + if last { + // change STALL -> VALID + stat_tx ^= Stat::STALL.to_bits() ^ Stat::NAK.to_bits(); + } + // Note: if this is the first AND last transfer, the above effectively + // changes stat_tx like NAK -> NAK, so noop. + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::from_bits(stat_rx)); + w.set_stat_tx(Stat::from_bits(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()); + poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if 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)?; + + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::from_bits(match last { + // If last, set STAT_RX=STALL. + true => Stat::NAK.to_bits() ^ Stat::STALL.to_bits(), + // Otherwise, set STAT_RX=VALID, to allow the host to send the next packet. + false => Stat::NAK.to_bits() ^ Stat::VALID.to_bits(), + })); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + + Ok(rx_len) + } + + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError> { + trace!("control: data_in"); + + 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.to_bits() ^ Stat::STALL.to_bits(); + } + if last { + // change STALL -> VALID + stat_rx ^= Stat::STALL.to_bits() ^ Stat::VALID.to_bits(); + } + // Note: if this is the first AND last transfer, the above effectively + // does a change of NAK -> VALID. + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::from_bits(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 WAITING"); + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if 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(); + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + 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(()) + } + + async fn accept(&mut self) { + let regs = T::regs(); + trace!("control: accept"); + + self.ep_in.write_data(&[]); + + // Set OUT=stall, IN=accept + let epr = regs.epr(0).read(); + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::from_bits(epr.stat_rx().to_bits() ^ Stat::STALL.to_bits())); + w.set_stat_tx(Stat::from_bits(epr.stat_tx().to_bits() ^ Stat::VALID.to_bits())); + 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 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 + let epr = regs.epr(0).read(); + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::from_bits(epr.stat_rx().to_bits() ^ Stat::STALL.to_bits())); + w.set_stat_tx(Stat::from_bits(epr.stat_tx().to_bits() ^ Stat::STALL.to_bits())); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + } + + async fn accept_set_address(&mut self, addr: u8) { + self.accept().await; + + let regs = T::regs(); + trace!("setting addr: {}", addr); + regs.daddr().write(|w| { + w.set_ef(true); + w.set_add(addr); + }); + } +} + +trait SealedInstance { + fn regs() -> crate::pac::usb::Usb; +} + +/// USB instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral + 'static { + /// Interrupt for this USB instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +// Internal PHY pins +pin_trait!(DpPin, Instance); +pin_trait!(DmPin, Instance); + +foreach_interrupt!( + ($inst:ident, usb, $block:ident, LP, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::usb::Usb { + crate::pac::$inst + } + } + + impl Instance for crate::peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +); diff --git a/embassy/embassy-stm32/src/wdg/mod.rs b/embassy/embassy-stm32/src/wdg/mod.rs new file mode 100644 index 0000000..ab21c4b --- /dev/null +++ b/embassy/embassy-stm32/src/wdg/mod.rs @@ -0,0 +1,125 @@ +//! Watchdog Timer (IWDG, WWDG) +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, Peripheral}; +use stm32_metapac::iwdg::vals::{Key, Pr}; + +use crate::rcc::LSI_FREQ; + +/// Independent watchdog (IWDG) driver. +pub struct IndependentWatchdog<'d, T: Instance> { + wdg: PhantomData<&'d mut T>, +} + +// 12-bit counter +const MAX_RL: u16 = 0xFFF; + +/// Calculates maximum watchdog timeout in us (RL = 0xFFF) for a given prescaler +const fn get_timeout_us(prescaler: u16, reload_value: u16) -> u32 { + 1_000_000 * (reload_value + 1) as u32 / (LSI_FREQ.0 / prescaler as u32) +} + +/// Calculates watchdog reload value for the given prescaler and desired timeout +const fn reload_value(prescaler: u16, timeout_us: u32) -> u16 { + (timeout_us / prescaler as u32 * LSI_FREQ.0 / 1_000_000) as u16 - 1 +} + +impl<'d, T: Instance> IndependentWatchdog<'d, T> { + /// Creates an IWDG (Independent Watchdog) instance with a given timeout value in microseconds. + /// + /// [Self] has to be started with [Self::unleash()]. + /// Once timer expires, MCU will be reset. To prevent this, timer must be reloaded by repeatedly calling [Self::pet()] within timeout interval. + pub fn new(_instance: impl Peripheral

+ 'd, timeout_us: u32) -> Self { + into_ref!(_instance); + + // 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 = 2u16.pow(*psc_power); + timeout_us <= get_timeout_us(psc, MAX_RL) + })); + + // Prescaler value + let psc = 2u16.pow(psc_power); + + #[cfg(not(iwdg_v3))] + assert!(psc <= 256, "IWDG prescaler should be no more than 256"); + #[cfg(iwdg_v3)] // H5, U5, WBA + assert!(psc <= 1024, "IWDG prescaler should be no more than 1024"); + + // Convert prescaler power to PR register value + let pr = psc_power as u8 - 2; + + // Reload value + let rl = reload_value(psc, timeout_us); + + let wdg = T::regs(); + wdg.kr().write(|w| w.set_key(Key::ENABLE)); + wdg.pr().write(|w| w.set_pr(Pr::from_bits(pr))); + wdg.rlr().write(|w| w.set_rl(rl)); + + trace!( + "Watchdog configured with {}us timeout, desired was {}us (PR={}, RL={})", + get_timeout_us(psc, rl), + timeout_us, + pr, + rl + ); + + IndependentWatchdog { wdg: PhantomData } + } + + /// Unleash (start) the watchdog. + pub fn unleash(&mut self) { + T::regs().kr().write(|w| w.set_key(Key::START)); + } + + /// Pet (reload, refresh) the watchdog. + pub fn pet(&mut self) { + T::regs().kr().write(|w| w.set_key(Key::RESET)); + } +} + +trait SealedInstance { + fn regs() -> crate::pac::iwdg::Iwdg; +} + +/// IWDG instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance {} + +foreach_peripheral!( + (iwdg, $inst:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::iwdg::Iwdg { + crate::pac::$inst + } + } + + impl Instance for crate::peripherals::$inst {} + }; +); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_compute_timeout_us() { + assert_eq!(125, get_timeout_us(4, 0)); + assert_eq!(512_000, get_timeout_us(4, MAX_RL)); + + assert_eq!(8_000, get_timeout_us(256, 0)); + assert_eq!(32_768_000, get_timeout_us(256, MAX_RL)); + + assert_eq!(8_000_000, get_timeout_us(64, 3999)); + } + + #[test] + fn can_compute_reload_value() { + assert_eq!(0xFFF, reload_value(4, 512_000)); + assert_eq!(0xFFF, reload_value(256, 32_768_000)); + + assert_eq!(3999, reload_value(64, 8_000_000)); + } +} diff --git a/embassy/embassy-sync/CHANGELOG.md b/embassy/embassy-sync/CHANGELOG.md new file mode 100644 index 0000000..a754742 --- /dev/null +++ b/embassy/embassy-sync/CHANGELOG.md @@ -0,0 +1,60 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.6.1 - 2024-11-22 + +- Add `LazyLock` sync primitive. +- Add `Watch` sync primitive. +- Add `clear`, `len`, `is_empty` and `is_full` functions to `zerocopy_channel`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `channel::{Sender, Receiver}`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `priority_channel::{Sender, Receiver}`. +- Add `GenericAtomicWaker` utility. + +## 0.6.0 - 2024-05-29 + +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `Channel`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `PriorityChannel`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `PubSubChannel`. +- Made `PubSubBehavior` sealed + - If you called `.publish_immediate(...)` on the queue directly before, then now call `.immediate_publisher().publish_immediate(...)` +- Add `OnceLock` sync primitive. +- Add constructor for `DynamicChannel` +- Add ready_to_receive functions to `Channel` and `Receiver`. + +## 0.5.0 - 2023-12-04 + +- Add a `PriorityChannel`. +- Remove `nightly` and `unstable-traits` features in preparation for 1.75. +- Upgrade `heapless` to 0.8. +- Upgrade `static-cell` to 2.0. + +## 0.4.0 - 2023-10-31 + +- Re-add `impl_trait_projections` +- switch to `embedded-io 0.6` + +## 0.3.0 - 2023-09-14 + +- switch to `embedded-io 0.5` +- add api for polling channels with context +- standardise fn names on channels +- add zero-copy channel + +## 0.2.0 - 2023-04-13 + +- pubsub: Fix messages not getting popped when the last subscriber that needed them gets dropped. +- pubsub: Move instead of clone messages when the last subscriber pops them. +- pubsub: Pop messages which count is 0 after unsubscribe. +- Update `embedded-io` from `0.3` to `0.4` (uses `async fn` in traits). +- impl `Default` for `WakerRegistration` +- impl `Default` for `Signal` +- Remove unnecessary uses of `atomic-polyfill` +- Add `#[must_use]` to all futures. + +## 0.1.0 - 2022-08-26 + +- First release diff --git a/embassy/embassy-sync/Cargo.toml b/embassy/embassy-sync/Cargo.toml new file mode 100644 index 0000000..1bd2f29 --- /dev/null +++ b/embassy/embassy-sync/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "embassy-sync" +version = "0.6.1" +edition = "2021" +description = "no-std, no-alloc synchronization primitives with async support" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-sync" +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-sync-v$VERSION/embassy-sync/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-sync/src/" +target = "thumbv7em-none-eabi" + +[features] +std = ["critical-section/std"] +turbowakers = [] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +futures-sink = { version = "0.3", default-features = false, features = [] } +futures-util = { version = "0.3.17", default-features = false } +critical-section = "1.1" +heapless = "0.8" +cfg-if = "1.0.0" +embedded-io-async = { version = "0.6.1" } + +[dev-dependencies] +futures-executor = { version = "0.3.17", features = [ "thread-pool" ] } +futures-test = "0.3.17" +futures-timer = "3.0.2" +futures-util = { version = "0.3.17", features = [ "channel", "sink" ] } + +# Enable critical-section implementation for std, for tests +critical-section = { version = "1.1", features = ["std"] } +static_cell = { version = "2" } diff --git a/embassy/embassy-sync/README.md b/embassy/embassy-sync/README.md new file mode 100644 index 0000000..6871bca --- /dev/null +++ b/embassy/embassy-sync/README.md @@ -0,0 +1,21 @@ +# embassy-sync + +An [Embassy](https://embassy.dev) project. + +Synchronization primitives and data structures with async support: + +- [`Channel`](channel::Channel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. +- [`PriorityChannel`](priority_channel::PriorityChannel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. Higher priority items are shifted to the front of the channel. +- [`PubSubChannel`](pubsub::PubSubChannel) - A broadcast channel (publish-subscribe) channel. Each message is received by all consumers. +- [`Signal`](signal::Signal) - Signalling latest value to a single consumer. +- [`Watch`](watch::Watch) - Signalling latest value to multiple consumers. +- [`Mutex`](mutex::Mutex) - Mutex for synchronizing state between asynchronous tasks. +- [`Pipe`](pipe::Pipe) - Byte stream implementing `embedded_io` traits. +- [`WakerRegistration`](waitqueue::WakerRegistration) - Utility to register and wake a `Waker`. +- [`AtomicWaker`](waitqueue::AtomicWaker) - A variant of `WakerRegistration` accessible using a non-mut API. +- [`MultiWakerRegistration`](waitqueue::MultiWakerRegistration) - Utility registering and waking multiple `Waker`'s. +- [`LazyLock`](lazy_lock::LazyLock) - A value which is initialized on the first access + +## Interoperability + +Futures from this crate can run on any executor. diff --git a/embassy/embassy-sync/build.rs b/embassy/embassy-sync/build.rs new file mode 100644 index 0000000..ecd2c0c --- /dev/null +++ b/embassy/embassy-sync/build.rs @@ -0,0 +1,7 @@ +#[path = "./build_common.rs"] +mod common; + +fn main() { + let mut cfgs = common::CfgSet::new(); + common::set_target_cfgs(&mut cfgs); +} diff --git a/embassy/embassy-sync/build_common.rs b/embassy/embassy-sync/build_common.rs new file mode 100644 index 0000000..4f24e6d --- /dev/null +++ b/embassy/embassy-sync/build_common.rs @@ -0,0 +1,94 @@ +// NOTE: this file is copy-pasted between several Embassy crates, because there is no +// straightforward way to share this code: +// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path = +// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate +// reside in the crate's directory, +// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because +// symlinks don't work on Windows. + +use std::collections::HashSet; +use std::env; + +/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring +/// them (`cargo:rust-check-cfg=cfg(X)`). +#[derive(Debug)] +pub struct CfgSet { + enabled: HashSet, + declared: HashSet, +} + +impl CfgSet { + pub fn new() -> Self { + Self { + enabled: HashSet::new(), + declared: HashSet::new(), + } + } + + /// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation. + /// + /// All configs that can potentially be enabled should be unconditionally declared using + /// [`Self::declare()`]. + pub fn enable(&mut self, cfg: impl AsRef) { + if self.enabled.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-cfg={}", cfg.as_ref()); + } + } + + pub fn enable_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.enable(cfg.as_ref()); + } + } + + /// Declare a valid config for conditional compilation, without enabling it. + /// + /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. + pub fn declare(&mut self, cfg: impl AsRef) { + if self.declared.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); + } + } + + pub fn declare_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.declare(cfg.as_ref()); + } + } + + pub fn set(&mut self, cfg: impl Into, enable: bool) { + let cfg = cfg.into(); + if enable { + self.enable(cfg.clone()); + } + self.declare(cfg); + } +} + +/// Sets configs that describe the target platform. +pub fn set_target_cfgs(cfgs: &mut CfgSet) { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + cfgs.enable_all(&["cortex_m", "armv6m"]); + } else if target.starts_with("thumbv7m-") { + cfgs.enable_all(&["cortex_m", "armv7m"]); + } else if target.starts_with("thumbv7em-") { + cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]); + } else if target.starts_with("thumbv8m.base") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]); + } else if target.starts_with("thumbv8m.main") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]); + } + cfgs.declare_all(&[ + "cortex_m", + "armv6m", + "armv7m", + "armv7em", + "armv8m", + "armv8m_base", + "armv8m_main", + ]); + + cfgs.set("has_fpu", target.ends_with("-eabihf")); +} diff --git a/embassy/embassy-sync/src/blocking_mutex/mod.rs b/embassy/embassy-sync/src/blocking_mutex/mod.rs new file mode 100644 index 0000000..beafdb4 --- /dev/null +++ b/embassy/embassy-sync/src/blocking_mutex/mod.rs @@ -0,0 +1,190 @@ +//! Blocking mutex. +//! +//! This module provides a blocking mutex that can be used to synchronize data. +pub mod raw; + +use core::cell::UnsafeCell; + +use self::raw::RawMutex; + +/// Blocking mutex (not async) +/// +/// Provides a blocking mutual exclusion primitive backed by an implementation of [`raw::RawMutex`]. +/// +/// Which implementation you select depends on the context in which you're using the mutex, and you can choose which kind +/// of interior mutability fits your use case. +/// +/// Use [`CriticalSectionMutex`] when data can be shared between threads and interrupts. +/// +/// Use [`NoopMutex`] when data is only shared between tasks running on the same executor. +/// +/// Use [`ThreadModeMutex`] when data is shared between tasks running on the same executor but you want a global singleton. +/// +/// In all cases, the blocking mutex is intended to be short lived and not held across await points. +/// Use the async [`Mutex`](crate::mutex::Mutex) if you need a lock that is held across await points. +pub struct Mutex { + // NOTE: `raw` must be FIRST, so when using ThreadModeMutex the "can't drop in non-thread-mode" gets + // to run BEFORE dropping `data`. + raw: R, + data: UnsafeCell, +} + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +impl Mutex { + /// Creates a new mutex in an unlocked state ready for use. + #[inline] + pub const fn new(val: T) -> Mutex { + Mutex { + raw: R::INIT, + data: UnsafeCell::new(val), + } + } + + /// Creates a critical section and grants temporary access to the protected data. + pub fn lock(&self, f: impl FnOnce(&T) -> U) -> U { + self.raw.lock(|| { + let ptr = self.data.get() as *const T; + let inner = unsafe { &*ptr }; + f(inner) + }) + } +} + +impl Mutex { + /// Creates a new mutex based on a pre-existing raw mutex. + /// + /// This allows creating a mutex in a constant context on stable Rust. + #[inline] + pub const fn const_new(raw_mutex: R, val: T) -> Mutex { + Mutex { + raw: raw_mutex, + data: UnsafeCell::new(val), + } + } + + /// Consumes this mutex, returning the underlying data. + #[inline] + pub fn into_inner(self) -> T { + self.data.into_inner() + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the `Mutex` mutably, no actual locking needs to + /// take place---the mutable borrow statically guarantees no locks exist. + #[inline] + pub fn get_mut(&mut self) -> &mut T { + unsafe { &mut *self.data.get() } + } +} + +/// A mutex that allows borrowing data across executors and interrupts. +/// +/// # Safety +/// +/// This mutex is safe to share between different executors and interrupts. +pub type CriticalSectionMutex = Mutex; + +/// A mutex that allows borrowing data in the context of a single executor. +/// +/// # Safety +/// +/// **This Mutex is only safe within a single executor.** +pub type NoopMutex = Mutex; + +impl Mutex { + /// Borrows the data for the duration of the critical section + pub fn borrow<'cs>(&'cs self, _cs: critical_section::CriticalSection<'cs>) -> &'cs T { + let ptr = self.data.get() as *const T; + unsafe { &*ptr } + } +} + +impl Mutex { + /// Borrows the data + #[allow(clippy::should_implement_trait)] + pub fn borrow(&self) -> &T { + let ptr = self.data.get() as *const T; + unsafe { &*ptr } + } +} + +// ThreadModeMutex does NOT use the generic mutex from above because it's special: +// it's Send+Sync even if T: !Send. There's no way to do that without specialization (I think?). +// +// There's still a ThreadModeRawMutex for use with the generic Mutex (handy with Channel, for example), +// but that will require T: Send even though it shouldn't be needed. + +#[cfg(any(cortex_m, feature = "std"))] +pub use thread_mode_mutex::*; +#[cfg(any(cortex_m, feature = "std"))] +mod thread_mode_mutex { + use super::*; + + /// A "mutex" that only allows borrowing from thread mode. + /// + /// # Safety + /// + /// **This Mutex is only safe on single-core systems.** + /// + /// On multi-core systems, a `ThreadModeMutex` **is not sufficient** to ensure exclusive access. + pub struct ThreadModeMutex { + inner: UnsafeCell, + } + + // NOTE: ThreadModeMutex only allows borrowing from one execution context ever: thread mode. + // Therefore it cannot be used to send non-sendable stuff between execution contexts, so it can + // be Send+Sync even if T is not Send (unlike CriticalSectionMutex) + unsafe impl Sync for ThreadModeMutex {} + unsafe impl Send for ThreadModeMutex {} + + impl ThreadModeMutex { + /// Creates a new mutex + pub const fn new(value: T) -> Self { + ThreadModeMutex { + inner: UnsafeCell::new(value), + } + } + } + + impl ThreadModeMutex { + /// Lock the `ThreadModeMutex`, granting access to the data. + /// + /// # Panics + /// + /// This will panic if not currently running in thread mode. + pub fn lock(&self, f: impl FnOnce(&T) -> R) -> R { + f(self.borrow()) + } + + /// Borrows the data + /// + /// # Panics + /// + /// This will panic if not currently running in thread mode. + pub fn borrow(&self) -> &T { + assert!( + raw::in_thread_mode(), + "ThreadModeMutex can only be borrowed from thread mode." + ); + unsafe { &*self.inner.get() } + } + } + + impl Drop for ThreadModeMutex { + fn drop(&mut self) { + // Only allow dropping from thread mode. Dropping calls drop on the inner `T`, so + // `drop` needs the same guarantees as `lock`. `ThreadModeMutex` is Send even if + // T isn't, so without this check a user could create a ThreadModeMutex in thread mode, + // send it to interrupt context and drop it there, which would "send" a T even if T is not Send. + assert!( + raw::in_thread_mode(), + "ThreadModeMutex can only be dropped from thread mode." + ); + + // Drop of the inner `T` happens after this. + } + } +} diff --git a/embassy/embassy-sync/src/blocking_mutex/raw.rs b/embassy/embassy-sync/src/blocking_mutex/raw.rs new file mode 100644 index 0000000..a8afcad --- /dev/null +++ b/embassy/embassy-sync/src/blocking_mutex/raw.rs @@ -0,0 +1,149 @@ +//! Mutex primitives. +//! +//! This module provides a trait for mutexes that can be used in different contexts. +use core::marker::PhantomData; + +/// Raw mutex trait. +/// +/// This mutex is "raw", which means it does not actually contain the protected data, it +/// just implements the mutex mechanism. For most uses you should use [`super::Mutex`] instead, +/// which is generic over a RawMutex and contains the protected data. +/// +/// Note that, unlike other mutexes, implementations only guarantee no +/// concurrent access from other threads: concurrent access from the current +/// thread is allowed. For example, it's possible to lock the same mutex multiple times reentrantly. +/// +/// Therefore, locking a `RawMutex` is only enough to guarantee safe shared (`&`) access +/// to the data, it is not enough to guarantee exclusive (`&mut`) access. +/// +/// # Safety +/// +/// RawMutex implementations must ensure that, while locked, no other thread can lock +/// the RawMutex concurrently. +/// +/// Unsafe code is allowed to rely on this fact, so incorrect implementations will cause undefined behavior. +pub unsafe trait RawMutex { + /// Create a new `RawMutex` instance. + /// + /// This is a const instead of a method to allow creating instances in const context. + const INIT: Self; + + /// Lock this `RawMutex`. + fn lock(&self, f: impl FnOnce() -> R) -> R; +} + +/// A mutex that allows borrowing data across executors and interrupts. +/// +/// # Safety +/// +/// This mutex is safe to share between different executors and interrupts. +pub struct CriticalSectionRawMutex { + _phantom: PhantomData<()>, +} +unsafe impl Send for CriticalSectionRawMutex {} +unsafe impl Sync for CriticalSectionRawMutex {} + +impl CriticalSectionRawMutex { + /// Create a new `CriticalSectionRawMutex`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } +} + +unsafe impl RawMutex for CriticalSectionRawMutex { + const INIT: Self = Self::new(); + + fn lock(&self, f: impl FnOnce() -> R) -> R { + critical_section::with(|_| f()) + } +} + +// ================ + +/// A mutex that allows borrowing data in the context of a single executor. +/// +/// # Safety +/// +/// **This Mutex is only safe within a single executor.** +pub struct NoopRawMutex { + _phantom: PhantomData<*mut ()>, +} + +unsafe impl Send for NoopRawMutex {} + +impl NoopRawMutex { + /// Create a new `NoopRawMutex`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } +} + +unsafe impl RawMutex for NoopRawMutex { + const INIT: Self = Self::new(); + fn lock(&self, f: impl FnOnce() -> R) -> R { + f() + } +} + +// ================ + +#[cfg(any(cortex_m, feature = "std"))] +mod thread_mode { + use super::*; + + /// A "mutex" that only allows borrowing from thread mode. + /// + /// # Safety + /// + /// **This Mutex is only safe on single-core systems.** + /// + /// On multi-core systems, a `ThreadModeRawMutex` **is not sufficient** to ensure exclusive access. + pub struct ThreadModeRawMutex { + _phantom: PhantomData<()>, + } + + unsafe impl Send for ThreadModeRawMutex {} + unsafe impl Sync for ThreadModeRawMutex {} + + impl ThreadModeRawMutex { + /// Create a new `ThreadModeRawMutex`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } + } + + unsafe impl RawMutex for ThreadModeRawMutex { + const INIT: Self = Self::new(); + fn lock(&self, f: impl FnOnce() -> R) -> R { + assert!(in_thread_mode(), "ThreadModeMutex can only be locked from thread mode."); + + f() + } + } + + impl Drop for ThreadModeRawMutex { + fn drop(&mut self) { + // Only allow dropping from thread mode. Dropping calls drop on the inner `T`, so + // `drop` needs the same guarantees as `lock`. `ThreadModeMutex` is Send even if + // T isn't, so without this check a user could create a ThreadModeMutex in thread mode, + // send it to interrupt context and drop it there, which would "send" a T even if T is not Send. + assert!( + in_thread_mode(), + "ThreadModeMutex can only be dropped from thread mode." + ); + + // Drop of the inner `T` happens after this. + } + } + + pub(crate) fn in_thread_mode() -> bool { + #[cfg(feature = "std")] + return Some("main") == std::thread::current().name(); + + #[cfg(not(feature = "std"))] + // ICSR.VECTACTIVE == 0 + return unsafe { (0xE000ED04 as *const u32).read_volatile() } & 0x1FF == 0; + } +} +#[cfg(any(cortex_m, feature = "std"))] +pub use thread_mode::*; diff --git a/embassy/embassy-sync/src/channel.rs b/embassy/embassy-sync/src/channel.rs new file mode 100644 index 0000000..18b0531 --- /dev/null +++ b/embassy/embassy-sync/src/channel.rs @@ -0,0 +1,910 @@ +//! A queue for sending values between asynchronous tasks. +//! +//! It can be used concurrently by multiple producers (senders) and multiple +//! consumers (receivers), i.e. it is an "MPMC channel". +//! +//! Receivers are competing for messages. So a message that is received by +//! one receiver is not received by any other. +//! +//! This queue takes a Mutex type so that various +//! targets can be attained. For example, a ThreadModeMutex can be used +//! for single-core Cortex-M targets where messages are only passed +//! between tasks running in thread mode. Similarly, a CriticalSectionMutex +//! can also be used for single-core targets where messages are to be +//! passed from exception mode e.g. out of an interrupt handler. +//! +//! This module provides a bounded channel that has a limit on the number of +//! messages that it can store, and if this limit is reached, trying to send +//! another message will result in an error being returned. +//! + +use core::cell::RefCell; +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use heapless::Deque; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::WakerRegistration; + +/// Send-only access to a [`Channel`]. +pub struct Sender<'ch, M, T, const N: usize> +where + M: RawMutex, +{ + channel: &'ch Channel, +} + +impl<'ch, M, T, const N: usize> Clone for Sender<'ch, M, T, N> +where + M: RawMutex, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, M, T, const N: usize> Copy for Sender<'ch, M, T, N> where M: RawMutex {} + +impl<'ch, M, T, const N: usize> Sender<'ch, M, T, N> +where + M: RawMutex, +{ + /// Sends a value. + /// + /// See [`Channel::send()`] + pub fn send(&self, message: T) -> SendFuture<'ch, M, T, N> { + self.channel.send(message) + } + + /// Attempt to immediately send a message. + /// + /// See [`Channel::send()`] + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.channel.try_send(message) + } + + /// Allows a poll_fn to poll until the channel is ready to send + /// + /// See [`Channel::poll_ready_to_send()`] + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_send(cx) + } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +/// Send-only access to a [`Channel`] without knowing channel size. +pub struct DynamicSender<'ch, T> { + pub(crate) channel: &'ch dyn DynamicChannel, +} + +impl<'ch, T> Clone for DynamicSender<'ch, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, T> Copy for DynamicSender<'ch, T> {} + +impl<'ch, M, T, const N: usize> From> for DynamicSender<'ch, T> +where + M: RawMutex, +{ + fn from(s: Sender<'ch, M, T, N>) -> Self { + Self { channel: s.channel } + } +} + +impl<'ch, T> DynamicSender<'ch, T> { + /// Sends a value. + /// + /// See [`Channel::send()`] + pub fn send(&self, message: T) -> DynamicSendFuture<'ch, T> { + DynamicSendFuture { + channel: self.channel, + message: Some(message), + } + } + + /// Attempt to immediately send a message. + /// + /// See [`Channel::send()`] + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.channel.try_send_with_context(message, None) + } + + /// Allows a poll_fn to poll until the channel is ready to send + /// + /// See [`Channel::poll_ready_to_send()`] + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_send(cx) + } +} + +/// Receive-only access to a [`Channel`]. +pub struct Receiver<'ch, M, T, const N: usize> +where + M: RawMutex, +{ + channel: &'ch Channel, +} + +impl<'ch, M, T, const N: usize> Clone for Receiver<'ch, M, T, N> +where + M: RawMutex, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, M, T, const N: usize> Copy for Receiver<'ch, M, T, N> where M: RawMutex {} + +impl<'ch, M, T, const N: usize> Receiver<'ch, M, T, N> +where + M: RawMutex, +{ + /// Receive the next value. + /// + /// See [`Channel::receive()`]. + pub fn receive(&self) -> ReceiveFuture<'_, M, T, N> { + self.channel.receive() + } + + /// Is a value ready to be received in the channel + /// + /// See [`Channel::ready_to_receive()`]. + pub fn ready_to_receive(&self) -> ReceiveReadyFuture<'_, M, T, N> { + self.channel.ready_to_receive() + } + + /// Attempt to immediately receive the next value. + /// + /// See [`Channel::try_receive()`] + pub fn try_receive(&self) -> Result { + self.channel.try_receive() + } + + /// Allows a poll_fn to poll until the channel is ready to receive + /// + /// See [`Channel::poll_ready_to_receive()`] + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } + + /// Poll the channel for the next item + /// + /// See [`Channel::poll_receive()`] + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +/// Receive-only access to a [`Channel`] without knowing channel size. +pub struct DynamicReceiver<'ch, T> { + pub(crate) channel: &'ch dyn DynamicChannel, +} + +impl<'ch, T> Clone for DynamicReceiver<'ch, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, T> Copy for DynamicReceiver<'ch, T> {} + +impl<'ch, T> DynamicReceiver<'ch, T> { + /// Receive the next value. + /// + /// See [`Channel::receive()`]. + pub fn receive(&self) -> DynamicReceiveFuture<'_, T> { + DynamicReceiveFuture { channel: self.channel } + } + + /// Attempt to immediately receive the next value. + /// + /// See [`Channel::try_receive()`] + pub fn try_receive(&self) -> Result { + self.channel.try_receive_with_context(None) + } + + /// Allows a poll_fn to poll until the channel is ready to receive + /// + /// See [`Channel::poll_ready_to_receive()`] + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } + + /// Poll the channel for the next item + /// + /// See [`Channel::poll_receive()`] + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } +} + +impl<'ch, M, T, const N: usize> From> for DynamicReceiver<'ch, T> +where + M: RawMutex, +{ + fn from(s: Receiver<'ch, M, T, N>) -> Self { + Self { channel: s.channel } + } +} + +/// Future returned by [`Channel::receive`] and [`Receiver::receive`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReceiveFuture<'ch, M, T, const N: usize> +where + M: RawMutex, +{ + channel: &'ch Channel, +} + +impl<'ch, M, T, const N: usize> Future for ReceiveFuture<'ch, M, T, N> +where + M: RawMutex, +{ + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } +} + +/// Future returned by [`Channel::ready_to_receive`] and [`Receiver::ready_to_receive`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReceiveReadyFuture<'ch, M, T, const N: usize> +where + M: RawMutex, +{ + channel: &'ch Channel, +} + +impl<'ch, M, T, const N: usize> Future for ReceiveReadyFuture<'ch, M, T, N> +where + M: RawMutex, +{ + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } +} + +/// Future returned by [`DynamicReceiver::receive`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicReceiveFuture<'ch, T> { + channel: &'ch dyn DynamicChannel, +} + +impl<'ch, T> Future for DynamicReceiveFuture<'ch, T> { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.channel.try_receive_with_context(Some(cx)) { + Ok(v) => Poll::Ready(v), + Err(TryReceiveError::Empty) => Poll::Pending, + } + } +} + +impl<'ch, M: RawMutex, T, const N: usize> From> for DynamicReceiveFuture<'ch, T> { + fn from(value: ReceiveFuture<'ch, M, T, N>) -> Self { + Self { channel: value.channel } + } +} + +/// Future returned by [`Channel::send`] and [`Sender::send`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SendFuture<'ch, M, T, const N: usize> +where + M: RawMutex, +{ + channel: &'ch Channel, + message: Option, +} + +impl<'ch, M, T, const N: usize> Future for SendFuture<'ch, M, T, N> +where + M: RawMutex, +{ + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.message.take() { + Some(m) => match self.channel.try_send_with_context(m, Some(cx)) { + Ok(..) => Poll::Ready(()), + Err(TrySendError::Full(m)) => { + self.message = Some(m); + Poll::Pending + } + }, + None => panic!("Message cannot be None"), + } + } +} + +impl<'ch, M, T, const N: usize> Unpin for SendFuture<'ch, M, T, N> where M: RawMutex {} + +/// Future returned by [`DynamicSender::send`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicSendFuture<'ch, T> { + channel: &'ch dyn DynamicChannel, + message: Option, +} + +impl<'ch, T> Future for DynamicSendFuture<'ch, T> { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.message.take() { + Some(m) => match self.channel.try_send_with_context(m, Some(cx)) { + Ok(..) => Poll::Ready(()), + Err(TrySendError::Full(m)) => { + self.message = Some(m); + Poll::Pending + } + }, + None => panic!("Message cannot be None"), + } + } +} + +impl<'ch, T> Unpin for DynamicSendFuture<'ch, T> {} + +impl<'ch, M: RawMutex, T, const N: usize> From> for DynamicSendFuture<'ch, T> { + fn from(value: SendFuture<'ch, M, T, N>) -> Self { + Self { + channel: value.channel, + message: value.message, + } + } +} + +pub(crate) trait DynamicChannel { + fn try_send_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError>; + + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result; + + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()>; + fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()>; + + fn poll_receive(&self, cx: &mut Context<'_>) -> Poll; +} + +/// Error returned by [`try_receive`](Channel::try_receive). +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TryReceiveError { + /// A message could not be received because the channel is empty. + Empty, +} + +/// Error returned by [`try_send`](Channel::try_send). +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TrySendError { + /// The data could not be sent on the channel because the channel is + /// currently full and sending would require blocking. + Full(T), +} + +struct ChannelState { + queue: Deque, + receiver_waker: WakerRegistration, + senders_waker: WakerRegistration, +} + +impl ChannelState { + const fn new() -> Self { + ChannelState { + queue: Deque::new(), + receiver_waker: WakerRegistration::new(), + senders_waker: WakerRegistration::new(), + } + } + + fn try_receive(&mut self) -> Result { + self.try_receive_with_context(None) + } + + fn try_receive_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result { + if self.queue.is_full() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.pop_front() { + Ok(message) + } else { + if let Some(cx) = cx { + self.receiver_waker.register(cx.waker()); + } + Err(TryReceiveError::Empty) + } + } + + fn poll_receive(&mut self, cx: &mut Context<'_>) -> Poll { + if self.queue.is_full() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.pop_front() { + Poll::Ready(message) + } else { + self.receiver_waker.register(cx.waker()); + Poll::Pending + } + } + + fn poll_ready_to_receive(&mut self, cx: &mut Context<'_>) -> Poll<()> { + self.receiver_waker.register(cx.waker()); + + if !self.queue.is_empty() { + Poll::Ready(()) + } else { + Poll::Pending + } + } + + fn try_send(&mut self, message: T) -> Result<(), TrySendError> { + self.try_send_with_context(message, None) + } + + fn try_send_with_context(&mut self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + match self.queue.push_back(message) { + Ok(()) => { + self.receiver_waker.wake(); + Ok(()) + } + Err(message) => { + if let Some(cx) = cx { + self.senders_waker.register(cx.waker()); + } + Err(TrySendError::Full(message)) + } + } + } + + fn poll_ready_to_send(&mut self, cx: &mut Context<'_>) -> Poll<()> { + self.senders_waker.register(cx.waker()); + + if !self.queue.is_full() { + Poll::Ready(()) + } else { + Poll::Pending + } + } + + fn clear(&mut self) { + self.queue.clear(); + } + + fn len(&self) -> usize { + self.queue.len() + } + + fn is_empty(&self) -> bool { + self.queue.is_empty() + } + + fn is_full(&self) -> bool { + self.queue.is_full() + } +} + +/// A bounded channel for communicating between asynchronous tasks +/// with backpressure. +/// +/// The channel will buffer up to the provided number of messages. Once the +/// buffer is full, attempts to `send` new messages will wait until a message is +/// received from the channel. +/// +/// All data sent will become available in the same order as it was sent. +pub struct Channel +where + M: RawMutex, +{ + inner: Mutex>>, +} + +impl Channel +where + M: RawMutex, +{ + /// Establish a new bounded channel. For example, to create one with a NoopMutex: + /// + /// ``` + /// use embassy_sync::channel::Channel; + /// use embassy_sync::blocking_mutex::raw::NoopRawMutex; + /// + /// // Declare a bounded channel of 3 u32s. + /// let mut channel = Channel::::new(); + /// ``` + pub const fn new() -> Self { + Self { + inner: Mutex::new(RefCell::new(ChannelState::new())), + } + } + + fn lock(&self, f: impl FnOnce(&mut ChannelState) -> R) -> R { + self.inner.lock(|rc| f(&mut *unwrap!(rc.try_borrow_mut()))) + } + + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { + self.lock(|c| c.try_receive_with_context(cx)) + } + + /// Poll the channel for the next message + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.lock(|c| c.poll_receive(cx)) + } + + fn try_send_with_context(&self, m: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + self.lock(|c| c.try_send_with_context(m, cx)) + } + + /// Allows a poll_fn to poll until the channel is ready to receive + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.lock(|c| c.poll_ready_to_receive(cx)) + } + + /// Allows a poll_fn to poll until the channel is ready to send + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.lock(|c| c.poll_ready_to_send(cx)) + } + + /// Get a sender for this channel. + pub fn sender(&self) -> Sender<'_, M, T, N> { + Sender { channel: self } + } + + /// Get a receiver for this channel. + pub fn receiver(&self) -> Receiver<'_, M, T, N> { + Receiver { channel: self } + } + + /// Get a sender for this channel using dynamic dispatch. + pub fn dyn_sender(&self) -> DynamicSender<'_, T> { + DynamicSender { channel: self } + } + + /// Get a receiver for this channel using dynamic dispatch. + pub fn dyn_receiver(&self) -> DynamicReceiver<'_, T> { + DynamicReceiver { channel: self } + } + + /// Send a value, waiting until there is capacity. + /// + /// Sending completes when the value has been pushed to the channel's queue. + /// This doesn't mean the value has been received yet. + pub fn send(&self, message: T) -> SendFuture<'_, M, T, N> { + SendFuture { + channel: self, + message: Some(message), + } + } + + /// Attempt to immediately send a message. + /// + /// This method differs from [`send`](Channel::send) by returning immediately if the channel's + /// buffer is full, instead of waiting. + /// + /// # Errors + /// + /// If the channel capacity has been reached, i.e., the channel has `n` + /// buffered values where `n` is the argument passed to [`Channel`], then an + /// error is returned. + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.lock(|c| c.try_send(message)) + } + + /// Receive the next value. + /// + /// If there are no messages in the channel's buffer, this method will + /// wait until a message is sent. + pub fn receive(&self) -> ReceiveFuture<'_, M, T, N> { + ReceiveFuture { channel: self } + } + + /// Is a value ready to be received in the channel + /// + /// If there are no messages in the channel's buffer, this method will + /// wait until there is at least one + pub fn ready_to_receive(&self) -> ReceiveReadyFuture<'_, M, T, N> { + ReceiveReadyFuture { channel: self } + } + + /// Attempt to immediately receive a message. + /// + /// This method will either receive a message from the channel immediately or return an error + /// if the channel is empty. + pub fn try_receive(&self) -> Result { + self.lock(|c| c.try_receive()) + } + + /// Returns the maximum number of elements the channel can hold. + pub const fn capacity(&self) -> usize { + N + } + + /// Returns the free capacity of the channel. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + N - self.len() + } + + /// Clears all elements in the channel. + pub fn clear(&self) { + self.lock(|c| c.clear()); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.lock(|c| c.len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.lock(|c| c.is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.lock(|c| c.is_full()) + } +} + +/// Implements the DynamicChannel to allow creating types that are unaware of the queue size with the +/// tradeoff cost of dynamic dispatch. +impl DynamicChannel for Channel +where + M: RawMutex, +{ + fn try_send_with_context(&self, m: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + Channel::try_send_with_context(self, m, cx) + } + + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { + Channel::try_receive_with_context(self, cx) + } + + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + Channel::poll_ready_to_send(self, cx) + } + + fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + Channel::poll_ready_to_receive(self, cx) + } + + fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + Channel::poll_receive(self, cx) + } +} + +#[cfg(test)] +mod tests { + use core::time::Duration; + + use futures_executor::ThreadPool; + use futures_timer::Delay; + use futures_util::task::SpawnExt; + use static_cell::StaticCell; + + use super::*; + use crate::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; + + fn capacity(c: &ChannelState) -> usize { + c.queue.capacity() - c.queue.len() + } + + #[test] + fn sending_once() { + let mut c = ChannelState::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(capacity(&c), 2); + } + + #[test] + fn sending_when_full() { + let mut c = ChannelState::::new(); + let _ = c.try_send(1); + let _ = c.try_send(1); + let _ = c.try_send(1); + match c.try_send(2) { + Err(TrySendError::Full(2)) => assert!(true), + _ => assert!(false), + } + assert_eq!(capacity(&c), 0); + } + + #[test] + fn receiving_once_with_one_send() { + let mut c = ChannelState::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_receive().unwrap(), 1); + assert_eq!(capacity(&c), 3); + } + + #[test] + fn receiving_when_empty() { + let mut c = ChannelState::::new(); + match c.try_receive() { + Err(TryReceiveError::Empty) => assert!(true), + _ => assert!(false), + } + assert_eq!(capacity(&c), 3); + } + + #[test] + fn simple_send_and_receive() { + let c = Channel::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_receive().unwrap(), 1); + } + + #[test] + fn cloning() { + let c = Channel::::new(); + let r1 = c.receiver(); + let s1 = c.sender(); + + let _ = r1.clone(); + let _ = s1.clone(); + } + + #[test] + fn dynamic_dispatch_into() { + let c = Channel::::new(); + let s: DynamicSender<'_, u32> = c.sender().into(); + let r: DynamicReceiver<'_, u32> = c.receiver().into(); + + assert!(s.try_send(1).is_ok()); + assert_eq!(r.try_receive().unwrap(), 1); + } + + #[test] + fn dynamic_dispatch_constructor() { + let c = Channel::::new(); + let s = c.dyn_sender(); + let r = c.dyn_receiver(); + + assert!(s.try_send(1).is_ok()); + assert_eq!(r.try_receive().unwrap(), 1); + } + + #[futures_test::test] + async fn receiver_receives_given_try_send_async() { + let executor = ThreadPool::new().unwrap(); + + static CHANNEL: StaticCell> = StaticCell::new(); + let c = &*CHANNEL.init(Channel::new()); + let c2 = c; + assert!(executor + .spawn(async move { + assert!(c2.try_send(1).is_ok()); + }) + .is_ok()); + assert_eq!(c.receive().await, 1); + } + + #[futures_test::test] + async fn sender_send_completes_if_capacity() { + let c = Channel::::new(); + c.send(1).await; + assert_eq!(c.receive().await, 1); + } + + #[futures_test::test] + async fn senders_sends_wait_until_capacity() { + let executor = ThreadPool::new().unwrap(); + + static CHANNEL: StaticCell> = StaticCell::new(); + let c = &*CHANNEL.init(Channel::new()); + assert!(c.try_send(1).is_ok()); + + let c2 = c; + let send_task_1 = executor.spawn_with_handle(async move { c2.send(2).await }); + let c2 = c; + let send_task_2 = executor.spawn_with_handle(async move { c2.send(3).await }); + // Wish I could think of a means of determining that the async send is waiting instead. + // However, I've used the debugger to observe that the send does indeed wait. + Delay::new(Duration::from_millis(500)).await; + assert_eq!(c.receive().await, 1); + assert!(executor + .spawn(async move { + loop { + c.receive().await; + } + }) + .is_ok()); + send_task_1.unwrap().await; + send_task_2.unwrap().await; + } +} diff --git a/embassy/embassy-sync/src/fmt.rs b/embassy/embassy-sync/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-sync/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-sync/src/lazy_lock.rs b/embassy/embassy-sync/src/lazy_lock.rs new file mode 100644 index 0000000..18e3c20 --- /dev/null +++ b/embassy/embassy-sync/src/lazy_lock.rs @@ -0,0 +1,152 @@ +//! Synchronization primitive for initializing a value once, allowing others to get a reference to the value. + +use core::cell::UnsafeCell; +use core::mem::ManuallyDrop; +use core::sync::atomic::{AtomicBool, Ordering}; + +/// The `LazyLock` is a synchronization primitive that allows for +/// initializing a value once, and allowing others to obtain a +/// reference to the value. This is useful for lazy initialization of +/// a static value. +/// +/// # Example +/// ``` +/// use futures_executor::block_on; +/// use embassy_sync::lazy_lock::LazyLock; +/// +/// // Define a static value that will be lazily initialized +/// // at runtime at the first access. +/// static VALUE: LazyLock = LazyLock::new(|| 20); +/// +/// let reference = VALUE.get(); +/// assert_eq!(reference, &20); +/// ``` +pub struct LazyLock T> { + init: AtomicBool, + data: UnsafeCell>, +} + +union Data { + value: ManuallyDrop, + f: ManuallyDrop, +} + +unsafe impl Sync for LazyLock {} + +impl T> LazyLock { + /// Create a new uninitialized `StaticLock`. + pub const fn new(init_fn: F) -> Self { + Self { + init: AtomicBool::new(false), + data: UnsafeCell::new(Data { + f: ManuallyDrop::new(init_fn), + }), + } + } + + /// Get a reference to the underlying value, initializing it if it + /// has not been done already. + #[inline] + pub fn get(&self) -> &T { + self.ensure_init_fast(); + unsafe { &(*self.data.get()).value } + } + + /// Consume the `LazyLock`, returning the underlying value. The + /// initialization function will be called if it has not been + /// already. + #[inline] + pub fn into_inner(self) -> T { + self.ensure_init_fast(); + let this = ManuallyDrop::new(self); + let data = unsafe { core::ptr::read(&this.data) }.into_inner(); + + ManuallyDrop::into_inner(unsafe { data.value }) + } + + /// Initialize the `LazyLock` if it has not been initialized yet. + /// This function is a fast track to [`Self::ensure_init`] + /// which does not require a critical section in most cases when + /// the value has been initialized already. + /// When this function returns, `self.data` is guaranteed to be + /// initialized and visible on the current core. + #[inline] + fn ensure_init_fast(&self) { + if !self.init.load(Ordering::Acquire) { + self.ensure_init(); + } + } + + /// Initialize the `LazyLock` if it has not been initialized yet. + /// When this function returns, `self.data` is guaranteed to be + /// initialized and visible on the current core. + fn ensure_init(&self) { + critical_section::with(|_| { + if !self.init.load(Ordering::Acquire) { + let data = unsafe { &mut *self.data.get() }; + let f = unsafe { ManuallyDrop::take(&mut data.f) }; + let value = f(); + data.value = ManuallyDrop::new(value); + + self.init.store(true, Ordering::Release); + } + }); + } +} + +impl Drop for LazyLock { + fn drop(&mut self) { + if self.init.load(Ordering::Acquire) { + unsafe { ManuallyDrop::drop(&mut self.data.get_mut().value) }; + } else { + unsafe { ManuallyDrop::drop(&mut self.data.get_mut().f) }; + } + } +} + +#[cfg(test)] +mod tests { + use core::sync::atomic::{AtomicU32, Ordering}; + + use super::*; + + #[test] + fn test_lazy_lock() { + static VALUE: LazyLock = LazyLock::new(|| 20); + let reference = VALUE.get(); + assert_eq!(reference, &20); + } + #[test] + fn test_lazy_lock_into_inner() { + let lazy: LazyLock = LazyLock::new(|| 20); + let value = lazy.into_inner(); + assert_eq!(value, 20); + } + + static DROP_CHECKER: AtomicU32 = AtomicU32::new(0); + struct DropCheck; + + impl Drop for DropCheck { + fn drop(&mut self) { + DROP_CHECKER.fetch_add(1, Ordering::Acquire); + } + } + + #[test] + fn test_lazy_drop() { + let lazy: LazyLock = LazyLock::new(|| DropCheck); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 0); + lazy.get(); + drop(lazy); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 1); + + let dropper = DropCheck; + let lazy_fn: LazyLock = LazyLock::new(move || { + let _a = dropper; + 20 + }); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 1); + drop(lazy_fn); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 2); + } +} diff --git a/embassy/embassy-sync/src/lib.rs b/embassy/embassy-sync/src/lib.rs new file mode 100644 index 0000000..df0f5e8 --- /dev/null +++ b/embassy/embassy-sync/src/lib.rs @@ -0,0 +1,25 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(async_fn_in_trait)] +#![allow(clippy::new_without_default)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +// internal use +mod ring_buffer; + +pub mod blocking_mutex; +pub mod channel; +pub mod lazy_lock; +pub mod mutex; +pub mod once_lock; +pub mod pipe; +pub mod priority_channel; +pub mod pubsub; +pub mod semaphore; +pub mod signal; +pub mod waitqueue; +pub mod watch; +pub mod zerocopy_channel; diff --git a/embassy/embassy-sync/src/mutex.rs b/embassy/embassy-sync/src/mutex.rs new file mode 100644 index 0000000..08f66e3 --- /dev/null +++ b/embassy/embassy-sync/src/mutex.rs @@ -0,0 +1,391 @@ +//! Async mutex. +//! +//! This module provides a mutex that can be used to synchronize data between asynchronous tasks. +use core::cell::{RefCell, UnsafeCell}; +use core::future::poll_fn; +use core::ops::{Deref, DerefMut}; +use core::task::Poll; +use core::{fmt, mem}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex as BlockingMutex; +use crate::waitqueue::WakerRegistration; + +/// Error returned by [`Mutex::try_lock`] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TryLockError; + +struct State { + locked: bool, + waker: WakerRegistration, +} + +/// Async mutex. +/// +/// The mutex is generic over a blocking [`RawMutex`](crate::blocking_mutex::raw::RawMutex). +/// The raw mutex is used to guard access to the internal "is locked" flag. It +/// is held for very short periods only, while locking and unlocking. It is *not* held +/// for the entire time the async Mutex is locked. +/// +/// Which implementation you select depends on the context in which you're using the mutex. +/// +/// Use [`CriticalSectionRawMutex`](crate::blocking_mutex::raw::CriticalSectionRawMutex) when data can be shared between threads and interrupts. +/// +/// Use [`NoopRawMutex`](crate::blocking_mutex::raw::NoopRawMutex) when data is only shared between tasks running on the same executor. +/// +/// Use [`ThreadModeRawMutex`](crate::blocking_mutex::raw::ThreadModeRawMutex) when data is shared between tasks running on the same executor but you want a singleton. +/// +pub struct Mutex +where + M: RawMutex, + T: ?Sized, +{ + state: BlockingMutex>, + inner: UnsafeCell, +} + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +/// Async mutex. +impl Mutex +where + M: RawMutex, +{ + /// Create a new mutex with the given value. + pub const fn new(value: T) -> Self { + Self { + inner: UnsafeCell::new(value), + state: BlockingMutex::new(RefCell::new(State { + locked: false, + waker: WakerRegistration::new(), + })), + } + } +} + +impl Mutex +where + M: RawMutex, + T: ?Sized, +{ + /// Lock the mutex. + /// + /// This will wait for the mutex to be unlocked if it's already locked. + pub async fn lock(&self) -> MutexGuard<'_, M, T> { + poll_fn(|cx| { + let ready = self.state.lock(|s| { + let mut s = s.borrow_mut(); + if s.locked { + s.waker.register(cx.waker()); + false + } else { + s.locked = true; + true + } + }); + + if ready { + Poll::Ready(MutexGuard { mutex: self }) + } else { + Poll::Pending + } + }) + .await + } + + /// Attempt to immediately lock the mutex. + /// + /// If the mutex is already locked, this will return an error instead of waiting. + pub fn try_lock(&self) -> Result, TryLockError> { + self.state.lock(|s| { + let mut s = s.borrow_mut(); + if s.locked { + Err(TryLockError) + } else { + s.locked = true; + Ok(()) + } + })?; + + Ok(MutexGuard { mutex: self }) + } + + /// Consumes this mutex, returning the underlying data. + pub fn into_inner(self) -> T + where + T: Sized, + { + self.inner.into_inner() + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the Mutex mutably, no actual locking needs to + /// take place -- the mutable borrow statically guarantees no locks exist. + pub fn get_mut(&mut self) -> &mut T { + self.inner.get_mut() + } +} + +impl From for Mutex { + fn from(from: T) -> Self { + Self::new(from) + } +} + +impl Default for Mutex +where + M: RawMutex, + T: Default, +{ + fn default() -> Self { + Self::new(Default::default()) + } +} + +impl fmt::Debug for Mutex +where + M: RawMutex, + T: ?Sized + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_struct("Mutex"); + match self.try_lock() { + Ok(value) => { + d.field("inner", &&*value); + } + Err(TryLockError) => { + d.field("inner", &format_args!("")); + } + } + + d.finish_non_exhaustive() + } +} + +/// Async mutex guard. +/// +/// Owning an instance of this type indicates having +/// successfully locked the mutex, and grants access to the contents. +/// +/// Dropping it unlocks the mutex. +#[clippy::has_significant_drop] +pub struct MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + mutex: &'a Mutex, +} + +impl<'a, M, T> MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + /// Returns a locked view over a portion of the locked data. + pub fn map(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedMutexGuard<'a, M, U> { + let mutex = this.mutex; + let value = fun(unsafe { &mut *this.mutex.inner.get() }); + // Don't run the `drop` method for MutexGuard. The ownership of the underlying + // locked state is being moved to the returned MappedMutexGuard. + mem::forget(this); + MappedMutexGuard { + state: &mutex.state, + value, + } + } +} + +impl<'a, M, T> Drop for MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn drop(&mut self) { + self.mutex.state.lock(|s| { + let mut s = unwrap!(s.try_borrow_mut()); + s.locked = false; + s.waker.wake(); + }) + } +} + +impl<'a, M, T> Deref for MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + type Target = T; + fn deref(&self) -> &Self::Target { + // Safety: the MutexGuard represents exclusive access to the contents + // of the mutex, so it's OK to get it. + unsafe { &*(self.mutex.inner.get() as *const T) } + } +} + +impl<'a, M, T> DerefMut for MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + // Safety: the MutexGuard represents exclusive access to the contents + // of the mutex, so it's OK to get it. + unsafe { &mut *(self.mutex.inner.get()) } + } +} + +impl<'a, M, T> fmt::Debug for MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, M, T> fmt::Display for MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +/// A handle to a held `Mutex` that has had a function applied to it via [`MutexGuard::map`] or +/// [`MappedMutexGuard::map`]. +/// +/// This can be used to hold a subfield of the protected data. +#[clippy::has_significant_drop] +pub struct MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + state: &'a BlockingMutex>, + value: *mut T, +} + +impl<'a, M, T> MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + /// Returns a locked view over a portion of the locked data. + pub fn map(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedMutexGuard<'a, M, U> { + let state = this.state; + let value = fun(unsafe { &mut *this.value }); + // Don't run the `drop` method for MutexGuard. The ownership of the underlying + // locked state is being moved to the returned MappedMutexGuard. + mem::forget(this); + MappedMutexGuard { state, value } + } +} + +impl<'a, M, T> Deref for MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + type Target = T; + fn deref(&self) -> &Self::Target { + // Safety: the MutexGuard represents exclusive access to the contents + // of the mutex, so it's OK to get it. + unsafe { &*self.value } + } +} + +impl<'a, M, T> DerefMut for MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + // Safety: the MutexGuard represents exclusive access to the contents + // of the mutex, so it's OK to get it. + unsafe { &mut *self.value } + } +} + +impl<'a, M, T> Drop for MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn drop(&mut self) { + self.state.lock(|s| { + let mut s = unwrap!(s.try_borrow_mut()); + s.locked = false; + s.waker.wake(); + }) + } +} + +unsafe impl Send for MappedMutexGuard<'_, M, T> +where + M: RawMutex + Sync, + T: Send + ?Sized, +{ +} + +unsafe impl Sync for MappedMutexGuard<'_, M, T> +where + M: RawMutex + Sync, + T: Sync + ?Sized, +{ +} + +impl<'a, M, T> fmt::Debug for MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, M, T> fmt::Display for MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +#[cfg(test)] +mod tests { + use crate::blocking_mutex::raw::NoopRawMutex; + use crate::mutex::{Mutex, MutexGuard}; + + #[futures_test::test] + async fn mapped_guard_releases_lock_when_dropped() { + let mutex: Mutex = Mutex::new([0, 1]); + + { + let guard = mutex.lock().await; + assert_eq!(*guard, [0, 1]); + let mut mapped = MutexGuard::map(guard, |this| &mut this[1]); + assert_eq!(*mapped, 1); + *mapped = 2; + } + + { + let guard = mutex.lock().await; + assert_eq!(*guard, [0, 2]); + let mut mapped = MutexGuard::map(guard, |this| &mut this[1]); + assert_eq!(*mapped, 2); + *mapped = 3; + } + + assert_eq!(*mutex.lock().await, [0, 3]); + } +} diff --git a/embassy/embassy-sync/src/once_lock.rs b/embassy/embassy-sync/src/once_lock.rs new file mode 100644 index 0000000..55608ba --- /dev/null +++ b/embassy/embassy-sync/src/once_lock.rs @@ -0,0 +1,236 @@ +//! Synchronization primitive for initializing a value once, allowing others to await a reference to the value. + +use core::cell::Cell; +use core::future::poll_fn; +use core::mem::MaybeUninit; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +/// The `OnceLock` is a synchronization primitive that allows for +/// initializing a value once, and allowing others to `.await` a +/// reference to the value. This is useful for lazy initialization of +/// a static value. +/// +/// **Note**: this implementation uses a busy loop to poll the value, +/// which is not as efficient as registering a dedicated `Waker`. +/// However, if the usecase for it is to initialize a static variable +/// relatively early in the program life cycle, it should be fine. +/// +/// # Example +/// ``` +/// use futures_executor::block_on; +/// use embassy_sync::once_lock::OnceLock; +/// +/// // Define a static value that will be lazily initialized +/// static VALUE: OnceLock = OnceLock::new(); +/// +/// let f = async { +/// +/// // Initialize the value +/// let reference = VALUE.get_or_init(|| 20); +/// assert_eq!(reference, &20); +/// +/// // Wait for the value to be initialized +/// // and get a static reference it +/// assert_eq!(VALUE.get().await, &20); +/// +/// }; +/// block_on(f) +/// ``` +pub struct OnceLock { + init: AtomicBool, + data: Cell>, +} + +unsafe impl Sync for OnceLock {} + +impl OnceLock { + /// Create a new uninitialized `OnceLock`. + pub const fn new() -> Self { + Self { + init: AtomicBool::new(false), + data: Cell::new(MaybeUninit::zeroed()), + } + } + + /// Get a reference to the underlying value, waiting for it to be set. + /// If the value is already set, this will return immediately. + pub async fn get(&self) -> &T { + poll_fn(|cx| match self.try_get() { + Some(data) => Poll::Ready(data), + None => { + cx.waker().wake_by_ref(); + Poll::Pending + } + }) + .await + } + + /// Try to get a reference to the underlying value if it exists. + pub fn try_get(&self) -> Option<&T> { + if self.init.load(Ordering::Relaxed) { + Some(unsafe { self.get_ref_unchecked() }) + } else { + None + } + } + + /// Set the underlying value. If the value is already set, this will return an error with the given value. + pub fn init(&self, value: T) -> Result<(), T> { + // Critical section is required to ensure that the value is + // not simultaneously initialized elsewhere at the same time. + critical_section::with(|_| { + // If the value is not set, set it and return Ok. + if !self.init.load(Ordering::Relaxed) { + self.data.set(MaybeUninit::new(value)); + self.init.store(true, Ordering::Relaxed); + Ok(()) + + // Otherwise return an error with the given value. + } else { + Err(value) + } + }) + } + + /// Get a reference to the underlying value, initializing it if it does not exist. + pub fn get_or_init(&self, f: F) -> &T + where + F: FnOnce() -> T, + { + // Critical section is required to ensure that the value is + // not simultaneously initialized elsewhere at the same time. + critical_section::with(|_| { + // If the value is not set, set it. + if !self.init.load(Ordering::Relaxed) { + self.data.set(MaybeUninit::new(f())); + self.init.store(true, Ordering::Relaxed); + } + }); + + // Return a reference to the value. + unsafe { self.get_ref_unchecked() } + } + + /// Consume the `OnceLock`, returning the underlying value if it was initialized. + pub fn into_inner(self) -> Option { + if self.init.load(Ordering::Relaxed) { + Some(unsafe { self.data.into_inner().assume_init() }) + } else { + None + } + } + + /// Take the underlying value if it was initialized, uninitializing the `OnceLock` in the process. + pub fn take(&mut self) -> Option { + // If the value is set, uninitialize the lock and return the value. + critical_section::with(|_| { + if self.init.load(Ordering::Relaxed) { + let val = unsafe { self.data.replace(MaybeUninit::zeroed()).assume_init() }; + self.init.store(false, Ordering::Relaxed); + Some(val) + + // Otherwise return None. + } else { + None + } + }) + } + + /// Check if the value has been set. + pub fn is_set(&self) -> bool { + self.init.load(Ordering::Relaxed) + } + + /// Get a reference to the underlying value. + /// # Safety + /// Must only be used if a value has been set. + unsafe fn get_ref_unchecked(&self) -> &T { + (*self.data.as_ptr()).assume_init_ref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn once_lock() { + let lock = OnceLock::new(); + assert_eq!(lock.try_get(), None); + assert_eq!(lock.is_set(), false); + + let v = 42; + assert_eq!(lock.init(v), Ok(())); + assert_eq!(lock.is_set(), true); + assert_eq!(lock.try_get(), Some(&v)); + assert_eq!(lock.try_get(), Some(&v)); + + let v = 43; + assert_eq!(lock.init(v), Err(v)); + assert_eq!(lock.is_set(), true); + assert_eq!(lock.try_get(), Some(&42)); + } + + #[test] + fn once_lock_get_or_init() { + let lock = OnceLock::new(); + assert_eq!(lock.try_get(), None); + assert_eq!(lock.is_set(), false); + + let v = lock.get_or_init(|| 42); + assert_eq!(v, &42); + assert_eq!(lock.is_set(), true); + assert_eq!(lock.try_get(), Some(&42)); + + let v = lock.get_or_init(|| 43); + assert_eq!(v, &42); + assert_eq!(lock.is_set(), true); + assert_eq!(lock.try_get(), Some(&42)); + } + + #[test] + fn once_lock_static() { + static LOCK: OnceLock = OnceLock::new(); + + let v: &'static i32 = LOCK.get_or_init(|| 42); + assert_eq!(v, &42); + + let v: &'static i32 = LOCK.get_or_init(|| 43); + assert_eq!(v, &42); + } + + #[futures_test::test] + async fn once_lock_async() { + static LOCK: OnceLock = OnceLock::new(); + + assert!(LOCK.init(42).is_ok()); + + let v: &'static i32 = LOCK.get().await; + assert_eq!(v, &42); + } + + #[test] + fn once_lock_into_inner() { + let lock: OnceLock = OnceLock::new(); + + let v = lock.get_or_init(|| 42); + assert_eq!(v, &42); + + assert_eq!(lock.into_inner(), Some(42)); + } + + #[test] + fn once_lock_take_init() { + let mut lock: OnceLock = OnceLock::new(); + + assert_eq!(lock.get_or_init(|| 42), &42); + assert_eq!(lock.is_set(), true); + + assert_eq!(lock.take(), Some(42)); + assert_eq!(lock.is_set(), false); + + assert_eq!(lock.get_or_init(|| 43), &43); + assert_eq!(lock.is_set(), true); + } +} diff --git a/embassy/embassy-sync/src/pipe.rs b/embassy/embassy-sync/src/pipe.rs new file mode 100644 index 0000000..cd5b8ed --- /dev/null +++ b/embassy/embassy-sync/src/pipe.rs @@ -0,0 +1,646 @@ +//! Async byte stream pipe. + +use core::cell::{RefCell, UnsafeCell}; +use core::convert::Infallible; +use core::future::Future; +use core::ops::Range; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::ring_buffer::RingBuffer; +use crate::waitqueue::WakerRegistration; + +/// Write-only access to a [`Pipe`]. +pub struct Writer<'p, M, const N: usize> +where + M: RawMutex, +{ + pipe: &'p Pipe, +} + +impl<'p, M, const N: usize> Clone for Writer<'p, M, N> +where + M: RawMutex, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'p, M, const N: usize> Copy for Writer<'p, M, N> where M: RawMutex {} + +impl<'p, M, const N: usize> Writer<'p, M, N> +where + M: RawMutex, +{ + /// Write some bytes to the pipe. + /// + /// See [`Pipe::write()`] + pub fn write<'a>(&'a self, buf: &'a [u8]) -> WriteFuture<'a, M, N> { + self.pipe.write(buf) + } + + /// Attempt to immediately write some bytes to the pipe. + /// + /// See [`Pipe::try_write()`] + pub fn try_write(&self, buf: &[u8]) -> Result { + self.pipe.try_write(buf) + } +} + +/// Future returned by [`Pipe::write`] and [`Writer::write`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct WriteFuture<'p, M, const N: usize> +where + M: RawMutex, +{ + pipe: &'p Pipe, + buf: &'p [u8], +} + +impl<'p, M, const N: usize> Future for WriteFuture<'p, M, N> +where + M: RawMutex, +{ + type Output = usize; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.pipe.try_write_with_context(Some(cx), self.buf) { + Ok(n) => Poll::Ready(n), + Err(TryWriteError::Full) => Poll::Pending, + } + } +} + +impl<'p, M, const N: usize> Unpin for WriteFuture<'p, M, N> where M: RawMutex {} + +/// Read-only access to a [`Pipe`]. +pub struct Reader<'p, M, const N: usize> +where + M: RawMutex, +{ + pipe: &'p Pipe, +} + +impl<'p, M, const N: usize> Reader<'p, M, N> +where + M: RawMutex, +{ + /// Read some bytes from the pipe. + /// + /// See [`Pipe::read()`] + pub fn read<'a>(&'a self, buf: &'a mut [u8]) -> ReadFuture<'a, M, N> { + self.pipe.read(buf) + } + + /// Attempt to immediately read some bytes from the pipe. + /// + /// See [`Pipe::try_read()`] + pub fn try_read(&self, buf: &mut [u8]) -> Result { + self.pipe.try_read(buf) + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + /// + /// If no bytes are currently available to read, this function waits until at least one byte is available. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn fill_buf(&mut self) -> FillBufFuture<'_, M, N> { + FillBufFuture { pipe: Some(self.pipe) } + } + + /// Try returning contents of the internal buffer. + /// + /// If no bytes are currently available to read, this function returns `Err(TryReadError::Empty)`. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn try_fill_buf(&mut self) -> Result<&[u8], TryReadError> { + unsafe { self.pipe.try_fill_buf_with_context(None) } + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + self.pipe.consume(amt) + } +} + +/// Future returned by [`Pipe::read`] and [`Reader::read`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReadFuture<'p, M, const N: usize> +where + M: RawMutex, +{ + pipe: &'p Pipe, + buf: &'p mut [u8], +} + +impl<'p, M, const N: usize> Future for ReadFuture<'p, M, N> +where + M: RawMutex, +{ + type Output = usize; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.pipe.try_read_with_context(Some(cx), self.buf) { + Ok(n) => Poll::Ready(n), + Err(TryReadError::Empty) => Poll::Pending, + } + } +} + +impl<'p, M, const N: usize> Unpin for ReadFuture<'p, M, N> where M: RawMutex {} + +/// Future returned by [`Pipe::fill_buf`] and [`Reader::fill_buf`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct FillBufFuture<'p, M, const N: usize> +where + M: RawMutex, +{ + pipe: Option<&'p Pipe>, +} + +impl<'p, M, const N: usize> Future for FillBufFuture<'p, M, N> +where + M: RawMutex, +{ + type Output = &'p [u8]; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pipe = self.pipe.take().unwrap(); + match unsafe { pipe.try_fill_buf_with_context(Some(cx)) } { + Ok(buf) => Poll::Ready(buf), + Err(TryReadError::Empty) => { + self.pipe = Some(pipe); + Poll::Pending + } + } + } +} + +impl<'p, M, const N: usize> Unpin for FillBufFuture<'p, M, N> where M: RawMutex {} + +/// Error returned by [`try_read`](Pipe::try_read). +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TryReadError { + /// No data could be read from the pipe because it is currently + /// empty, and reading would require blocking. + Empty, +} + +/// Error returned by [`try_write`](Pipe::try_write). +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TryWriteError { + /// No data could be written to the pipe because it is + /// currently full, and writing would require blocking. + Full, +} + +struct PipeState { + buffer: RingBuffer, + read_waker: WakerRegistration, + write_waker: WakerRegistration, +} + +#[repr(transparent)] +struct Buffer(UnsafeCell<[u8; N]>); + +impl Buffer { + unsafe fn get<'a>(&self, r: Range) -> &'a [u8] { + let p = self.0.get() as *const u8; + core::slice::from_raw_parts(p.add(r.start), r.end - r.start) + } + + unsafe fn get_mut<'a>(&self, r: Range) -> &'a mut [u8] { + let p = self.0.get() as *mut u8; + core::slice::from_raw_parts_mut(p.add(r.start), r.end - r.start) + } +} + +unsafe impl Send for Buffer {} +unsafe impl Sync for Buffer {} + +/// A bounded byte-oriented pipe for communicating between asynchronous tasks +/// with backpressure. +/// +/// The pipe will buffer up to the provided number of bytes. Once the +/// buffer is full, attempts to `write` new bytes will wait until buffer space is freed up. +/// +/// All data written will become available in the same order as it was written. +pub struct Pipe +where + M: RawMutex, +{ + buf: Buffer, + inner: Mutex>>, +} + +impl Pipe +where + M: RawMutex, +{ + /// Establish a new bounded pipe. For example, to create one with a NoopMutex: + /// + /// ``` + /// use embassy_sync::pipe::Pipe; + /// use embassy_sync::blocking_mutex::raw::NoopRawMutex; + /// + /// // Declare a bounded pipe, with a buffer of 256 bytes. + /// let mut pipe = Pipe::::new(); + /// ``` + pub const fn new() -> Self { + Self { + buf: Buffer(UnsafeCell::new([0; N])), + inner: Mutex::new(RefCell::new(PipeState { + buffer: RingBuffer::new(), + read_waker: WakerRegistration::new(), + write_waker: WakerRegistration::new(), + })), + } + } + + fn lock(&self, f: impl FnOnce(&mut PipeState) -> R) -> R { + self.inner.lock(|rc| f(&mut *rc.borrow_mut())) + } + + fn try_read_with_context(&self, cx: Option<&mut Context<'_>>, buf: &mut [u8]) -> Result { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + if s.buffer.is_full() { + s.write_waker.wake(); + } + + let available = unsafe { self.buf.get(s.buffer.pop_buf()) }; + if available.is_empty() { + if let Some(cx) = cx { + s.read_waker.register(cx.waker()); + } + return Err(TryReadError::Empty); + } + + let n = available.len().min(buf.len()); + buf[..n].copy_from_slice(&available[..n]); + s.buffer.pop(n); + Ok(n) + }) + } + + // safety: While the returned slice is alive, + // no `read` or `consume` methods in the pipe must be called. + unsafe fn try_fill_buf_with_context(&self, cx: Option<&mut Context<'_>>) -> Result<&[u8], TryReadError> { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + if s.buffer.is_full() { + s.write_waker.wake(); + } + + let available = unsafe { self.buf.get(s.buffer.pop_buf()) }; + if available.is_empty() { + if let Some(cx) = cx { + s.read_waker.register(cx.waker()); + } + return Err(TryReadError::Empty); + } + + Ok(available) + }) + } + + fn consume(&self, amt: usize) { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + let available = s.buffer.pop_buf(); + assert!(amt <= available.len()); + s.buffer.pop(amt); + }) + } + + fn try_write_with_context(&self, cx: Option<&mut Context<'_>>, buf: &[u8]) -> Result { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + if s.buffer.is_empty() { + s.read_waker.wake(); + } + + let available = unsafe { self.buf.get_mut(s.buffer.push_buf()) }; + if available.is_empty() { + if let Some(cx) = cx { + s.write_waker.register(cx.waker()); + } + return Err(TryWriteError::Full); + } + + let n = available.len().min(buf.len()); + available[..n].copy_from_slice(&buf[..n]); + s.buffer.push(n); + Ok(n) + }) + } + + /// Split this pipe into a BufRead-capable reader and a writer. + /// + /// The reader and writer borrow the current pipe mutably, so it is not + /// possible to use it directly while they exist. This is needed because + /// implementing `BufRead` requires there is a single reader. + /// + /// The writer is cloneable, the reader is not. + pub fn split(&mut self) -> (Reader<'_, M, N>, Writer<'_, M, N>) { + (Reader { pipe: self }, Writer { pipe: self }) + } + + /// Write some bytes to the pipe. + /// + /// This method writes a nonzero amount of bytes from `buf` into the pipe, and + /// returns the amount of bytes written. + /// + /// If it is not possible to write a nonzero amount of bytes because the pipe's buffer is full, + /// this method will wait until it isn't. See [`try_write`](Self::try_write) for a variant that + /// returns an error instead of waiting. + /// + /// It is not guaranteed that all bytes in the buffer are written, even if there's enough + /// free space in the pipe buffer for all. In other words, it is possible for `write` to return + /// without writing all of `buf` (returning a number less than `buf.len()`) and still leave + /// free space in the pipe buffer. You should always `write` in a loop, or use helpers like + /// `write_all` from the `embedded-io` crate. + pub fn write<'a>(&'a self, buf: &'a [u8]) -> WriteFuture<'a, M, N> { + WriteFuture { pipe: self, buf } + } + + /// Write all bytes to the pipe. + /// + /// This method writes all bytes from `buf` into the pipe + pub async fn write_all(&self, mut buf: &[u8]) { + while !buf.is_empty() { + let n = self.write(buf).await; + buf = &buf[n..]; + } + } + + /// Attempt to immediately write some bytes to the pipe. + /// + /// This method will either write a nonzero amount of bytes to the pipe immediately, + /// or return an error if the pipe is empty. See [`write`](Self::write) for a variant + /// that waits instead of returning an error. + pub fn try_write(&self, buf: &[u8]) -> Result { + self.try_write_with_context(None, buf) + } + + /// Read some bytes from the pipe. + /// + /// This method reads a nonzero amount of bytes from the pipe into `buf` and + /// returns the amount of bytes read. + /// + /// If it is not possible to read a nonzero amount of bytes because the pipe's buffer is empty, + /// this method will wait until it isn't. See [`try_read`](Self::try_read) for a variant that + /// returns an error instead of waiting. + /// + /// It is not guaranteed that all bytes in the buffer are read, even if there's enough + /// space in `buf` for all. In other words, it is possible for `read` to return + /// without filling `buf` (returning a number less than `buf.len()`) and still leave bytes + /// in the pipe buffer. You should always `read` in a loop, or use helpers like + /// `read_exact` from the `embedded-io` crate. + pub fn read<'a>(&'a self, buf: &'a mut [u8]) -> ReadFuture<'a, M, N> { + ReadFuture { pipe: self, buf } + } + + /// Attempt to immediately read some bytes from the pipe. + /// + /// This method will either read a nonzero amount of bytes from the pipe immediately, + /// or return an error if the pipe is empty. See [`read`](Self::read) for a variant + /// that waits instead of returning an error. + pub fn try_read(&self, buf: &mut [u8]) -> Result { + self.try_read_with_context(None, buf) + } + + /// Clear the data in the pipe's buffer. + pub fn clear(&self) { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + s.buffer.clear(); + s.write_waker.wake(); + }) + } + + /// Return whether the pipe is full (no free space in the buffer) + pub fn is_full(&self) -> bool { + self.len() == N + } + + /// Return whether the pipe is empty (no data buffered) + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Total byte capacity. + /// + /// This is the same as the `N` generic param. + pub fn capacity(&self) -> usize { + N + } + + /// Used byte capacity. + pub fn len(&self) -> usize { + self.lock(|c| c.buffer.len()) + } + + /// Free byte capacity. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + N - self.len() + } +} + +impl embedded_io_async::ErrorType for Pipe { + type Error = Infallible; +} + +impl embedded_io_async::Read for Pipe { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Pipe::read(self, buf).await) + } +} + +impl embedded_io_async::Write for Pipe { + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Pipe::write(self, buf).await) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl embedded_io_async::ErrorType for &Pipe { + type Error = Infallible; +} + +impl embedded_io_async::Read for &Pipe { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Pipe::read(self, buf).await) + } +} + +impl embedded_io_async::Write for &Pipe { + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Pipe::write(self, buf).await) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl embedded_io_async::ErrorType for Reader<'_, M, N> { + type Error = Infallible; +} + +impl embedded_io_async::Read for Reader<'_, M, N> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Reader::read(self, buf).await) + } +} + +impl embedded_io_async::BufRead for Reader<'_, M, N> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Ok(Reader::fill_buf(self).await) + } + + fn consume(&mut self, amt: usize) { + Reader::consume(self, amt) + } +} + +impl embedded_io_async::ErrorType for Writer<'_, M, N> { + type Error = Infallible; +} + +impl embedded_io_async::Write for Writer<'_, M, N> { + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Writer::write(self, buf).await) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use futures_executor::ThreadPool; + use futures_util::task::SpawnExt; + use static_cell::StaticCell; + + use super::*; + use crate::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; + + #[test] + fn writing_once() { + let c = Pipe::::new(); + assert!(c.try_write(&[1]).is_ok()); + assert_eq!(c.free_capacity(), 2); + } + + #[test] + fn writing_when_full() { + let c = Pipe::::new(); + assert_eq!(c.try_write(&[42]), Ok(1)); + assert_eq!(c.try_write(&[43]), Ok(1)); + assert_eq!(c.try_write(&[44]), Ok(1)); + assert_eq!(c.try_write(&[45]), Err(TryWriteError::Full)); + assert_eq!(c.free_capacity(), 0); + } + + #[test] + fn receiving_once_with_one_send() { + let c = Pipe::::new(); + assert!(c.try_write(&[42]).is_ok()); + let mut buf = [0; 16]; + assert_eq!(c.try_read(&mut buf), Ok(1)); + assert_eq!(buf[0], 42); + assert_eq!(c.free_capacity(), 3); + } + + #[test] + fn receiving_when_empty() { + let c = Pipe::::new(); + let mut buf = [0; 16]; + assert_eq!(c.try_read(&mut buf), Err(TryReadError::Empty)); + assert_eq!(c.free_capacity(), 3); + } + + #[test] + fn simple_send_and_receive() { + let c = Pipe::::new(); + assert!(c.try_write(&[42]).is_ok()); + let mut buf = [0; 16]; + assert_eq!(c.try_read(&mut buf), Ok(1)); + assert_eq!(buf[0], 42); + } + + #[test] + fn read_buf() { + let mut c = Pipe::::new(); + let (mut r, w) = c.split(); + assert!(w.try_write(&[42, 43]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[43]); + r.consume(1); + assert_eq!(r.try_fill_buf(), Err(TryReadError::Empty)); + assert_eq!(w.try_write(&[44, 45, 46]), Ok(1)); + assert_eq!(w.try_write(&[45, 46]), Ok(2)); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[44]); // only one byte due to wraparound. + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46]); + assert!(w.try_write(&[47]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46, 47]); + r.consume(3); + } + + #[test] + fn writer_is_cloneable() { + let mut c = Pipe::::new(); + let (_r, w) = c.split(); + let _ = w.clone(); + } + + #[futures_test::test] + async fn receiver_receives_given_try_write_async() { + let executor = ThreadPool::new().unwrap(); + + static CHANNEL: StaticCell> = StaticCell::new(); + let c = &*CHANNEL.init(Pipe::new()); + let c2 = c; + let f = async move { + assert_eq!(c2.try_write(&[42]), Ok(1)); + }; + executor.spawn(f).unwrap(); + let mut buf = [0; 16]; + assert_eq!(c.read(&mut buf).await, 1); + assert_eq!(buf[0], 42); + } + + #[futures_test::test] + async fn sender_send_completes_if_capacity() { + let c = Pipe::::new(); + c.write(&[42]).await; + let mut buf = [0; 16]; + assert_eq!(c.read(&mut buf).await, 1); + assert_eq!(buf[0], 42); + } +} diff --git a/embassy/embassy-sync/src/priority_channel.rs b/embassy/embassy-sync/src/priority_channel.rs new file mode 100644 index 0000000..1f4d866 --- /dev/null +++ b/embassy/embassy-sync/src/priority_channel.rs @@ -0,0 +1,745 @@ +//! A queue for sending values between asynchronous tasks. +//! +//! Similar to a [`Channel`](crate::channel::Channel), however [`PriorityChannel`] sifts higher priority items to the front of the queue. +//! Priority is determined by the `Ord` trait. Priority behavior is determined by the [`Kind`](heapless::binary_heap::Kind) parameter of the channel. + +use core::cell::RefCell; +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +pub use heapless::binary_heap::{Kind, Max, Min}; +use heapless::BinaryHeap; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::channel::{DynamicChannel, DynamicReceiver, DynamicSender, TryReceiveError, TrySendError}; +use crate::waitqueue::WakerRegistration; + +/// Send-only access to a [`PriorityChannel`]. +pub struct Sender<'ch, M, T, K, const N: usize> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + channel: &'ch PriorityChannel, +} + +impl<'ch, M, T, K, const N: usize> Clone for Sender<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, M, T, K, const N: usize> Copy for Sender<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ +} + +impl<'ch, M, T, K, const N: usize> Sender<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + /// Sends a value. + /// + /// See [`PriorityChannel::send()`] + pub fn send(&self, message: T) -> SendFuture<'ch, M, T, K, N> { + self.channel.send(message) + } + + /// Attempt to immediately send a message. + /// + /// See [`PriorityChannel::send()`] + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.channel.try_send(message) + } + + /// Allows a poll_fn to poll until the channel is ready to send + /// + /// See [`PriorityChannel::poll_ready_to_send()`] + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_send(cx) + } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +impl<'ch, M, T, K, const N: usize> From> for DynamicSender<'ch, T> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + fn from(s: Sender<'ch, M, T, K, N>) -> Self { + Self { channel: s.channel } + } +} + +/// Receive-only access to a [`PriorityChannel`]. +pub struct Receiver<'ch, M, T, K, const N: usize> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + channel: &'ch PriorityChannel, +} + +impl<'ch, M, T, K, const N: usize> Clone for Receiver<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, M, T, K, const N: usize> Copy for Receiver<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ +} + +impl<'ch, M, T, K, const N: usize> Receiver<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + /// Receive the next value. + /// + /// See [`PriorityChannel::receive()`]. + pub fn receive(&self) -> ReceiveFuture<'_, M, T, K, N> { + self.channel.receive() + } + + /// Attempt to immediately receive the next value. + /// + /// See [`PriorityChannel::try_receive()`] + pub fn try_receive(&self) -> Result { + self.channel.try_receive() + } + + /// Allows a poll_fn to poll until the channel is ready to receive + /// + /// See [`PriorityChannel::poll_ready_to_receive()`] + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } + + /// Poll the channel for the next item + /// + /// See [`PriorityChannel::poll_receive()`] + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +impl<'ch, M, T, K, const N: usize> From> for DynamicReceiver<'ch, T> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + fn from(s: Receiver<'ch, M, T, K, N>) -> Self { + Self { channel: s.channel } + } +} + +/// Future returned by [`PriorityChannel::receive`] and [`Receiver::receive`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReceiveFuture<'ch, M, T, K, const N: usize> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + channel: &'ch PriorityChannel, +} + +impl<'ch, M, T, K, const N: usize> Future for ReceiveFuture<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } +} + +/// Future returned by [`PriorityChannel::send`] and [`Sender::send`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SendFuture<'ch, M, T, K, const N: usize> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + channel: &'ch PriorityChannel, + message: Option, +} + +impl<'ch, M, T, K, const N: usize> Future for SendFuture<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.message.take() { + Some(m) => match self.channel.try_send_with_context(m, Some(cx)) { + Ok(..) => Poll::Ready(()), + Err(TrySendError::Full(m)) => { + self.message = Some(m); + Poll::Pending + } + }, + None => panic!("Message cannot be None"), + } + } +} + +impl<'ch, M, T, K, const N: usize> Unpin for SendFuture<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ +} + +struct ChannelState { + queue: BinaryHeap, + receiver_waker: WakerRegistration, + senders_waker: WakerRegistration, +} + +impl ChannelState +where + T: Ord, + K: Kind, +{ + const fn new() -> Self { + ChannelState { + queue: BinaryHeap::new(), + receiver_waker: WakerRegistration::new(), + senders_waker: WakerRegistration::new(), + } + } + + fn try_receive(&mut self) -> Result { + self.try_receive_with_context(None) + } + + fn try_receive_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result { + if self.queue.len() == self.queue.capacity() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.pop() { + Ok(message) + } else { + if let Some(cx) = cx { + self.receiver_waker.register(cx.waker()); + } + Err(TryReceiveError::Empty) + } + } + + fn poll_receive(&mut self, cx: &mut Context<'_>) -> Poll { + if self.queue.len() == self.queue.capacity() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.pop() { + Poll::Ready(message) + } else { + self.receiver_waker.register(cx.waker()); + Poll::Pending + } + } + + fn poll_ready_to_receive(&mut self, cx: &mut Context<'_>) -> Poll<()> { + self.receiver_waker.register(cx.waker()); + + if !self.queue.is_empty() { + Poll::Ready(()) + } else { + Poll::Pending + } + } + + fn try_send(&mut self, message: T) -> Result<(), TrySendError> { + self.try_send_with_context(message, None) + } + + fn try_send_with_context(&mut self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + match self.queue.push(message) { + Ok(()) => { + self.receiver_waker.wake(); + Ok(()) + } + Err(message) => { + if let Some(cx) = cx { + self.senders_waker.register(cx.waker()); + } + Err(TrySendError::Full(message)) + } + } + } + + fn poll_ready_to_send(&mut self, cx: &mut Context<'_>) -> Poll<()> { + self.senders_waker.register(cx.waker()); + + if !self.queue.len() == self.queue.capacity() { + Poll::Ready(()) + } else { + Poll::Pending + } + } + + fn clear(&mut self) { + self.queue.clear(); + } + + fn len(&self) -> usize { + self.queue.len() + } + + fn is_empty(&self) -> bool { + self.queue.is_empty() + } + + fn is_full(&self) -> bool { + self.queue.len() == self.queue.capacity() + } +} + +/// A bounded channel for communicating between asynchronous tasks +/// with backpressure. +/// +/// The channel will buffer up to the provided number of messages. Once the +/// buffer is full, attempts to `send` new messages will wait until a message is +/// received from the channel. +/// +/// Sent data may be reordered based on their priority within the channel. +/// For example, in a [`Max`](heapless::binary_heap::Max) [`PriorityChannel`] +/// containing `u32`'s, data sent in the following order `[1, 2, 3]` will be received as `[3, 2, 1]`. +pub struct PriorityChannel +where + T: Ord, + K: Kind, + M: RawMutex, +{ + inner: Mutex>>, +} + +impl PriorityChannel +where + T: Ord, + K: Kind, + M: RawMutex, +{ + /// Establish a new bounded channel. For example, to create one with a NoopMutex: + /// + /// ``` + /// use embassy_sync::priority_channel::{PriorityChannel, Max}; + /// use embassy_sync::blocking_mutex::raw::NoopRawMutex; + /// + /// // Declare a bounded channel of 3 u32s. + /// let mut channel = PriorityChannel::::new(); + /// ``` + pub const fn new() -> Self { + Self { + inner: Mutex::new(RefCell::new(ChannelState::new())), + } + } + + fn lock(&self, f: impl FnOnce(&mut ChannelState) -> R) -> R { + self.inner.lock(|rc| f(&mut *unwrap!(rc.try_borrow_mut()))) + } + + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { + self.lock(|c| c.try_receive_with_context(cx)) + } + + /// Poll the channel for the next message + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.lock(|c| c.poll_receive(cx)) + } + + fn try_send_with_context(&self, m: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + self.lock(|c| c.try_send_with_context(m, cx)) + } + + /// Allows a poll_fn to poll until the channel is ready to receive + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.lock(|c| c.poll_ready_to_receive(cx)) + } + + /// Allows a poll_fn to poll until the channel is ready to send + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.lock(|c| c.poll_ready_to_send(cx)) + } + + /// Get a sender for this channel. + pub fn sender(&self) -> Sender<'_, M, T, K, N> { + Sender { channel: self } + } + + /// Get a receiver for this channel. + pub fn receiver(&self) -> Receiver<'_, M, T, K, N> { + Receiver { channel: self } + } + + /// Send a value, waiting until there is capacity. + /// + /// Sending completes when the value has been pushed to the channel's queue. + /// This doesn't mean the value has been received yet. + pub fn send(&self, message: T) -> SendFuture<'_, M, T, K, N> { + SendFuture { + channel: self, + message: Some(message), + } + } + + /// Attempt to immediately send a message. + /// + /// This method differs from [`send`](PriorityChannel::send) by returning immediately if the channel's + /// buffer is full, instead of waiting. + /// + /// # Errors + /// + /// If the channel capacity has been reached, i.e., the channel has `n` + /// buffered values where `n` is the argument passed to [`PriorityChannel`], then an + /// error is returned. + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.lock(|c| c.try_send(message)) + } + + /// Receive the next value. + /// + /// If there are no messages in the channel's buffer, this method will + /// wait until a message is sent. + pub fn receive(&self) -> ReceiveFuture<'_, M, T, K, N> { + ReceiveFuture { channel: self } + } + + /// Attempt to immediately receive a message. + /// + /// This method will either receive a message from the channel immediately or return an error + /// if the channel is empty. + pub fn try_receive(&self) -> Result { + self.lock(|c| c.try_receive()) + } + + /// Returns the maximum number of elements the channel can hold. + pub const fn capacity(&self) -> usize { + N + } + + /// Returns the free capacity of the channel. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + N - self.len() + } + + /// Clears all elements in the channel. + pub fn clear(&self) { + self.lock(|c| c.clear()); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.lock(|c| c.len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.lock(|c| c.is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.lock(|c| c.is_full()) + } +} + +/// Implements the DynamicChannel to allow creating types that are unaware of the queue size with the +/// tradeoff cost of dynamic dispatch. +impl DynamicChannel for PriorityChannel +where + T: Ord, + K: Kind, + M: RawMutex, +{ + fn try_send_with_context(&self, m: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + PriorityChannel::try_send_with_context(self, m, cx) + } + + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { + PriorityChannel::try_receive_with_context(self, cx) + } + + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + PriorityChannel::poll_ready_to_send(self, cx) + } + + fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + PriorityChannel::poll_ready_to_receive(self, cx) + } + + fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + PriorityChannel::poll_receive(self, cx) + } +} + +#[cfg(test)] +mod tests { + use core::time::Duration; + + use futures_executor::ThreadPool; + use futures_timer::Delay; + use futures_util::task::SpawnExt; + use heapless::binary_heap::{Kind, Max}; + use static_cell::StaticCell; + + use super::*; + use crate::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; + + fn capacity(c: &ChannelState) -> usize + where + T: Ord, + K: Kind, + { + c.queue.capacity() - c.queue.len() + } + + #[test] + fn sending_once() { + let mut c = ChannelState::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(capacity(&c), 2); + } + + #[test] + fn sending_when_full() { + let mut c = ChannelState::::new(); + let _ = c.try_send(1); + let _ = c.try_send(1); + let _ = c.try_send(1); + match c.try_send(2) { + Err(TrySendError::Full(2)) => assert!(true), + _ => assert!(false), + } + assert_eq!(capacity(&c), 0); + } + + #[test] + fn send_priority() { + // Prio channel with kind `Max` sifts larger numbers to the front of the queue + let mut c = ChannelState::::new(); + assert!(c.try_send(1).is_ok()); + assert!(c.try_send(2).is_ok()); + assert!(c.try_send(3).is_ok()); + assert_eq!(c.try_receive().unwrap(), 3); + assert_eq!(c.try_receive().unwrap(), 2); + assert_eq!(c.try_receive().unwrap(), 1); + } + + #[test] + fn receiving_once_with_one_send() { + let mut c = ChannelState::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_receive().unwrap(), 1); + assert_eq!(capacity(&c), 3); + } + + #[test] + fn receiving_when_empty() { + let mut c = ChannelState::::new(); + match c.try_receive() { + Err(TryReceiveError::Empty) => assert!(true), + _ => assert!(false), + } + assert_eq!(capacity(&c), 3); + } + + #[test] + fn simple_send_and_receive() { + let c = PriorityChannel::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_receive().unwrap(), 1); + } + + #[test] + fn cloning() { + let c = PriorityChannel::::new(); + let r1 = c.receiver(); + let s1 = c.sender(); + + let _ = r1.clone(); + let _ = s1.clone(); + } + + #[test] + fn dynamic_dispatch() { + let c = PriorityChannel::::new(); + let s: DynamicSender<'_, u32> = c.sender().into(); + let r: DynamicReceiver<'_, u32> = c.receiver().into(); + + assert!(s.try_send(1).is_ok()); + assert_eq!(r.try_receive().unwrap(), 1); + } + + #[futures_test::test] + async fn receiver_receives_given_try_send_async() { + let executor = ThreadPool::new().unwrap(); + + static CHANNEL: StaticCell> = StaticCell::new(); + let c = &*CHANNEL.init(PriorityChannel::new()); + let c2 = c; + assert!(executor + .spawn(async move { + assert!(c2.try_send(1).is_ok()); + }) + .is_ok()); + assert_eq!(c.receive().await, 1); + } + + #[futures_test::test] + async fn sender_send_completes_if_capacity() { + let c = PriorityChannel::::new(); + c.send(1).await; + assert_eq!(c.receive().await, 1); + } + + #[futures_test::test] + async fn senders_sends_wait_until_capacity() { + let executor = ThreadPool::new().unwrap(); + + static CHANNEL: StaticCell> = StaticCell::new(); + let c = &*CHANNEL.init(PriorityChannel::new()); + assert!(c.try_send(1).is_ok()); + + let c2 = c; + let send_task_1 = executor.spawn_with_handle(async move { c2.send(2).await }); + let c2 = c; + let send_task_2 = executor.spawn_with_handle(async move { c2.send(3).await }); + // Wish I could think of a means of determining that the async send is waiting instead. + // However, I've used the debugger to observe that the send does indeed wait. + Delay::new(Duration::from_millis(500)).await; + assert_eq!(c.receive().await, 1); + assert!(executor + .spawn(async move { + loop { + c.receive().await; + } + }) + .is_ok()); + send_task_1.unwrap().await; + send_task_2.unwrap().await; + } +} diff --git a/embassy/embassy-sync/src/pubsub/mod.rs b/embassy/embassy-sync/src/pubsub/mod.rs new file mode 100644 index 0000000..a2360a1 --- /dev/null +++ b/embassy/embassy-sync/src/pubsub/mod.rs @@ -0,0 +1,784 @@ +//! Implementation of [PubSubChannel], a queue where published messages get received by all subscribers. + +#![deny(missing_docs)] + +use core::cell::RefCell; +use core::fmt::Debug; +use core::task::{Context, Poll}; + +use heapless::Deque; + +use self::publisher::{ImmediatePub, Pub}; +use self::subscriber::Sub; +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::MultiWakerRegistration; + +pub mod publisher; +pub mod subscriber; + +pub use publisher::{DynImmediatePublisher, DynPublisher, ImmediatePublisher, Publisher}; +pub use subscriber::{DynSubscriber, Subscriber}; + +/// A broadcast channel implementation where multiple publishers can send messages to multiple subscribers +/// +/// Any published message can be read by all subscribers. +/// A publisher can choose how it sends its message. +/// +/// - With [Pub::publish()] the publisher has to wait until there is space in the internal message queue. +/// - With [Pub::publish_immediate()] the publisher doesn't await and instead lets the oldest message +/// in the queue drop if necessary. This will cause any [Subscriber] that missed the message to receive +/// an error to indicate that it has lagged. +/// +/// ## Example +/// +/// ``` +/// # use embassy_sync::blocking_mutex::raw::NoopRawMutex; +/// # use embassy_sync::pubsub::WaitResult; +/// # use embassy_sync::pubsub::PubSubChannel; +/// # use futures_executor::block_on; +/// # let test = async { +/// // Create the channel. This can be static as well +/// let channel = PubSubChannel::::new(); +/// +/// // This is a generic subscriber with a direct reference to the channel +/// let mut sub0 = channel.subscriber().unwrap(); +/// // This is a dynamic subscriber with a dynamic (trait object) reference to the channel +/// let mut sub1 = channel.dyn_subscriber().unwrap(); +/// +/// let pub0 = channel.publisher().unwrap(); +/// +/// // Publish a message, but wait if the queue is full +/// pub0.publish(42).await; +/// +/// // Publish a message, but if the queue is full, just kick out the oldest message. +/// // This may cause some subscribers to miss a message +/// pub0.publish_immediate(43); +/// +/// // Wait for a new message. If the subscriber missed a message, the WaitResult will be a Lag result +/// assert_eq!(sub0.next_message().await, WaitResult::Message(42)); +/// assert_eq!(sub1.next_message().await, WaitResult::Message(42)); +/// +/// // Wait again, but this time ignore any Lag results +/// assert_eq!(sub0.next_message_pure().await, 43); +/// assert_eq!(sub1.next_message_pure().await, 43); +/// +/// // There's also a polling interface +/// assert_eq!(sub0.try_next_message(), None); +/// assert_eq!(sub1.try_next_message(), None); +/// # }; +/// # +/// # block_on(test); +/// ``` +/// +pub struct PubSubChannel { + inner: Mutex>>, +} + +impl + PubSubChannel +{ + /// Create a new channel + pub const fn new() -> Self { + Self { + inner: Mutex::const_new(M::INIT, RefCell::new(PubSubState::new())), + } + } + + /// Create a new subscriber. It will only receive messages that are published after its creation. + /// + /// If there are no subscriber slots left, an error will be returned. + pub fn subscriber(&self) -> Result, Error> { + self.inner.lock(|inner| { + let mut s = inner.borrow_mut(); + + if s.subscriber_count >= SUBS { + Err(Error::MaximumSubscribersReached) + } else { + s.subscriber_count += 1; + Ok(Subscriber(Sub::new(s.next_message_id, self))) + } + }) + } + + /// Create a new subscriber. It will only receive messages that are published after its creation. + /// + /// If there are no subscriber slots left, an error will be returned. + pub fn dyn_subscriber(&self) -> Result, Error> { + self.inner.lock(|inner| { + let mut s = inner.borrow_mut(); + + if s.subscriber_count >= SUBS { + Err(Error::MaximumSubscribersReached) + } else { + s.subscriber_count += 1; + Ok(DynSubscriber(Sub::new(s.next_message_id, self))) + } + }) + } + + /// Create a new publisher + /// + /// If there are no publisher slots left, an error will be returned. + pub fn publisher(&self) -> Result, Error> { + self.inner.lock(|inner| { + let mut s = inner.borrow_mut(); + + if s.publisher_count >= PUBS { + Err(Error::MaximumPublishersReached) + } else { + s.publisher_count += 1; + Ok(Publisher(Pub::new(self))) + } + }) + } + + /// Create a new publisher + /// + /// If there are no publisher slots left, an error will be returned. + pub fn dyn_publisher(&self) -> Result, Error> { + self.inner.lock(|inner| { + let mut s = inner.borrow_mut(); + + if s.publisher_count >= PUBS { + Err(Error::MaximumPublishersReached) + } else { + s.publisher_count += 1; + Ok(DynPublisher(Pub::new(self))) + } + }) + } + + /// Create a new publisher that can only send immediate messages. + /// This kind of publisher does not take up a publisher slot. + pub fn immediate_publisher(&self) -> ImmediatePublisher { + ImmediatePublisher(ImmediatePub::new(self)) + } + + /// Create a new publisher that can only send immediate messages. + /// This kind of publisher does not take up a publisher slot. + pub fn dyn_immediate_publisher(&self) -> DynImmediatePublisher { + DynImmediatePublisher(ImmediatePub::new(self)) + } + + /// Returns the maximum number of elements the channel can hold. + pub const fn capacity(&self) -> usize { + CAP + } + + /// Returns the free capacity of the channel. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + CAP - self.len() + } + + /// Clears all elements in the channel. + pub fn clear(&self) { + self.inner.lock(|inner| inner.borrow_mut().clear()); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.inner.lock(|inner| inner.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.inner.lock(|inner| inner.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.inner.lock(|inner| inner.borrow().is_full()) + } +} + +impl crate::pubsub::PubSubBehavior + for PubSubChannel +{ + fn publish_immediate(&self, message: T) { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + s.publish_immediate(message) + }) + } + + fn capacity(&self) -> usize { + self.capacity() + } + + fn is_full(&self) -> bool { + self.is_full() + } +} + +impl SealedPubSubBehavior + for PubSubChannel +{ + fn get_message_with_context(&self, next_message_id: &mut u64, cx: Option<&mut Context<'_>>) -> Poll> { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + + // Check if we can read a message + match s.get_message(*next_message_id) { + // Yes, so we are done polling + Some(WaitResult::Message(message)) => { + *next_message_id += 1; + Poll::Ready(WaitResult::Message(message)) + } + // No, so we need to reregister our waker and sleep again + None => { + if let Some(cx) = cx { + s.subscriber_wakers.register(cx.waker()); + } + Poll::Pending + } + // We missed a couple of messages. We must do our internal bookkeeping and return that we lagged + Some(WaitResult::Lagged(amount)) => { + *next_message_id += amount; + Poll::Ready(WaitResult::Lagged(amount)) + } + } + }) + } + + fn available(&self, next_message_id: u64) -> u64 { + self.inner.lock(|s| s.borrow().next_message_id - next_message_id) + } + + fn publish_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), T> { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + // Try to publish the message + match s.try_publish(message) { + // We did it, we are ready + Ok(()) => Ok(()), + // The queue is full, so we need to reregister our waker and go to sleep + Err(message) => { + if let Some(cx) = cx { + s.publisher_wakers.register(cx.waker()); + } + Err(message) + } + } + }) + } + + fn unregister_subscriber(&self, subscriber_next_message_id: u64) { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + s.unregister_subscriber(subscriber_next_message_id) + }) + } + + fn unregister_publisher(&self) { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + s.unregister_publisher() + }) + } + + fn free_capacity(&self) -> usize { + self.free_capacity() + } + + fn clear(&self) { + self.clear(); + } + + fn len(&self) -> usize { + self.len() + } + + fn is_empty(&self) -> bool { + self.is_empty() + } +} + +/// Internal state for the PubSub channel +struct PubSubState { + /// The queue contains the last messages that have been published and a countdown of how many subscribers are yet to read it + queue: Deque<(T, usize), CAP>, + /// Every message has an id. + /// Don't worry, we won't run out. + /// If a million messages were published every second, then the ID's would run out in about 584942 years. + next_message_id: u64, + /// Collection of wakers for Subscribers that are waiting. + subscriber_wakers: MultiWakerRegistration, + /// Collection of wakers for Publishers that are waiting. + publisher_wakers: MultiWakerRegistration, + /// The amount of subscribers that are active + subscriber_count: usize, + /// The amount of publishers that are active + publisher_count: usize, +} + +impl PubSubState { + /// Create a new internal channel state + const fn new() -> Self { + Self { + queue: Deque::new(), + next_message_id: 0, + subscriber_wakers: MultiWakerRegistration::new(), + publisher_wakers: MultiWakerRegistration::new(), + subscriber_count: 0, + publisher_count: 0, + } + } + + fn try_publish(&mut self, message: T) -> Result<(), T> { + if self.subscriber_count == 0 { + // We don't need to publish anything because there is no one to receive it + return Ok(()); + } + + if self.queue.is_full() { + return Err(message); + } + // We just did a check for this + self.queue.push_back((message, self.subscriber_count)).ok().unwrap(); + + self.next_message_id += 1; + + // Wake all of the subscribers + self.subscriber_wakers.wake(); + + Ok(()) + } + + fn publish_immediate(&mut self, message: T) { + // Make space in the queue if required + if self.queue.is_full() { + self.queue.pop_front(); + } + + // This will succeed because we made sure there is space + self.try_publish(message).ok().unwrap(); + } + + fn get_message(&mut self, message_id: u64) -> Option> { + let start_id = self.next_message_id - self.queue.len() as u64; + + if message_id < start_id { + return Some(WaitResult::Lagged(start_id - message_id)); + } + + let current_message_index = (message_id - start_id) as usize; + + if current_message_index >= self.queue.len() { + return None; + } + + // We've checked that the index is valid + let queue_item = self.queue.iter_mut().nth(current_message_index).unwrap(); + + // We're reading this item, so decrement the counter + queue_item.1 -= 1; + + let message = if current_message_index == 0 && queue_item.1 == 0 { + let (message, _) = self.queue.pop_front().unwrap(); + self.publisher_wakers.wake(); + // Return pop'd message without clone + message + } else { + queue_item.0.clone() + }; + + Some(WaitResult::Message(message)) + } + + fn unregister_subscriber(&mut self, subscriber_next_message_id: u64) { + self.subscriber_count -= 1; + + // All messages that haven't been read yet by this subscriber must have their counter decremented + let start_id = self.next_message_id - self.queue.len() as u64; + if subscriber_next_message_id >= start_id { + let current_message_index = (subscriber_next_message_id - start_id) as usize; + self.queue + .iter_mut() + .skip(current_message_index) + .for_each(|(_, counter)| *counter -= 1); + + let mut wake_publishers = false; + while let Some((_, count)) = self.queue.front() { + if *count == 0 { + self.queue.pop_front().unwrap(); + wake_publishers = true; + } else { + break; + } + } + + if wake_publishers { + self.publisher_wakers.wake(); + } + } + } + + fn unregister_publisher(&mut self) { + self.publisher_count -= 1; + } + + fn clear(&mut self) { + self.queue.clear(); + } + + fn len(&self) -> usize { + self.queue.len() + } + + fn is_empty(&self) -> bool { + self.queue.is_empty() + } + + fn is_full(&self) -> bool { + self.queue.is_full() + } +} + +/// Error type for the [PubSubChannel] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// All subscriber slots are used. To add another subscriber, first another subscriber must be dropped or + /// the capacity of the channels must be increased. + MaximumSubscribersReached, + /// All publisher slots are used. To add another publisher, first another publisher must be dropped or + /// the capacity of the channels must be increased. + MaximumPublishersReached, +} + +trait SealedPubSubBehavior { + /// Try to get a message from the queue with the given message id. + /// + /// If the message is not yet present and a context is given, then its waker is registered in the subscriber wakers. + fn get_message_with_context(&self, next_message_id: &mut u64, cx: Option<&mut Context<'_>>) -> Poll>; + + /// Get the amount of messages that are between the given the next_message_id and the most recent message. + /// This is not necessarily the amount of messages a subscriber can still received as it may have lagged. + fn available(&self, next_message_id: u64) -> u64; + + /// Try to publish a message to the queue. + /// + /// If the queue is full and a context is given, then its waker is registered in the publisher wakers. + fn publish_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), T>; + + /// Returns the free capacity of the channel. + /// + /// This is equivalent to `capacity() - len()` + fn free_capacity(&self) -> usize; + + /// Clears all elements in the channel. + fn clear(&self); + + /// Returns the number of elements currently in the channel. + fn len(&self) -> usize; + + /// Returns whether the channel is empty. + fn is_empty(&self) -> bool; + + /// Let the channel know that a subscriber has dropped + fn unregister_subscriber(&self, subscriber_next_message_id: u64); + + /// Let the channel know that a publisher has dropped + fn unregister_publisher(&self); +} + +/// 'Middle level' behaviour of the pubsub channel. +/// This trait is used so that Sub and Pub can be generic over the channel. +#[allow(private_bounds)] +pub trait PubSubBehavior: SealedPubSubBehavior { + /// Publish a message immediately + fn publish_immediate(&self, message: T); + + /// Returns the maximum number of elements the channel can hold. + fn capacity(&self) -> usize; + + /// Returns whether the channel is full. + fn is_full(&self) -> bool; +} + +/// The result of the subscriber wait procedure +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WaitResult { + /// The subscriber did not receive all messages and lagged by the given amount of messages. + /// (This is the amount of messages that were missed) + Lagged(u64), + /// A message was received + Message(T), +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::blocking_mutex::raw::NoopRawMutex; + + #[futures_test::test] + async fn dyn_pub_sub_works() { + let channel = PubSubChannel::::new(); + + let mut sub0 = channel.dyn_subscriber().unwrap(); + let mut sub1 = channel.dyn_subscriber().unwrap(); + let pub0 = channel.dyn_publisher().unwrap(); + + pub0.publish(42).await; + + assert_eq!(sub0.next_message().await, WaitResult::Message(42)); + assert_eq!(sub1.next_message().await, WaitResult::Message(42)); + + assert_eq!(sub0.try_next_message(), None); + assert_eq!(sub1.try_next_message(), None); + } + + #[futures_test::test] + async fn all_subscribers_receive() { + let channel = PubSubChannel::::new(); + + let mut sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + let pub0 = channel.publisher().unwrap(); + + pub0.publish(42).await; + + assert_eq!(sub0.next_message().await, WaitResult::Message(42)); + assert_eq!(sub1.next_message().await, WaitResult::Message(42)); + + assert_eq!(sub0.try_next_message(), None); + assert_eq!(sub1.try_next_message(), None); + } + + #[futures_test::test] + async fn lag_when_queue_full_on_immediate_publish() { + let channel = PubSubChannel::::new(); + + let mut sub0 = channel.subscriber().unwrap(); + let pub0 = channel.publisher().unwrap(); + + pub0.publish_immediate(42); + pub0.publish_immediate(43); + pub0.publish_immediate(44); + pub0.publish_immediate(45); + pub0.publish_immediate(46); + pub0.publish_immediate(47); + + assert_eq!(sub0.try_next_message(), Some(WaitResult::Lagged(2))); + assert_eq!(sub0.next_message().await, WaitResult::Message(44)); + assert_eq!(sub0.next_message().await, WaitResult::Message(45)); + assert_eq!(sub0.next_message().await, WaitResult::Message(46)); + assert_eq!(sub0.next_message().await, WaitResult::Message(47)); + assert_eq!(sub0.try_next_message(), None); + } + + #[test] + fn limited_subs_and_pubs() { + let channel = PubSubChannel::::new(); + + let sub0 = channel.subscriber(); + let sub1 = channel.subscriber(); + let sub2 = channel.subscriber(); + let sub3 = channel.subscriber(); + let sub4 = channel.subscriber(); + + assert!(sub0.is_ok()); + assert!(sub1.is_ok()); + assert!(sub2.is_ok()); + assert!(sub3.is_ok()); + assert_eq!(sub4.err().unwrap(), Error::MaximumSubscribersReached); + + drop(sub0); + + let sub5 = channel.subscriber(); + assert!(sub5.is_ok()); + + // publishers + + let pub0 = channel.publisher(); + let pub1 = channel.publisher(); + let pub2 = channel.publisher(); + let pub3 = channel.publisher(); + let pub4 = channel.publisher(); + + assert!(pub0.is_ok()); + assert!(pub1.is_ok()); + assert!(pub2.is_ok()); + assert!(pub3.is_ok()); + assert_eq!(pub4.err().unwrap(), Error::MaximumPublishersReached); + + drop(pub0); + + let pub5 = channel.publisher(); + assert!(pub5.is_ok()); + } + + #[test] + fn publisher_wait_on_full_queue() { + let channel = PubSubChannel::::new(); + + let pub0 = channel.publisher().unwrap(); + + // There are no subscribers, so the queue will never be full + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + + let sub0 = channel.subscriber().unwrap(); + + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert!(pub0.is_full()); + assert_eq!(pub0.try_publish(0), Err(0)); + + drop(sub0); + } + + #[futures_test::test] + async fn correct_available() { + let channel = PubSubChannel::::new(); + + let sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + let pub0 = channel.publisher().unwrap(); + + assert_eq!(sub0.available(), 0); + assert_eq!(sub1.available(), 0); + + pub0.publish(42).await; + + assert_eq!(sub0.available(), 1); + assert_eq!(sub1.available(), 1); + + sub1.next_message().await; + + assert_eq!(sub1.available(), 0); + + pub0.publish(42).await; + + assert_eq!(sub0.available(), 2); + assert_eq!(sub1.available(), 1); + } + + #[futures_test::test] + async fn correct_len() { + let channel = PubSubChannel::::new(); + + let mut sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + let pub0 = channel.publisher().unwrap(); + + assert!(sub0.is_empty()); + assert!(sub1.is_empty()); + assert!(pub0.is_empty()); + assert_eq!(pub0.free_capacity(), 4); + assert_eq!(pub0.len(), 0); + + pub0.publish(42).await; + + assert_eq!(pub0.free_capacity(), 3); + assert_eq!(pub0.len(), 1); + + pub0.publish(42).await; + + assert_eq!(pub0.free_capacity(), 2); + assert_eq!(pub0.len(), 2); + + sub0.next_message().await; + sub0.next_message().await; + + assert_eq!(pub0.free_capacity(), 2); + assert_eq!(pub0.len(), 2); + + sub1.next_message().await; + assert_eq!(pub0.free_capacity(), 3); + assert_eq!(pub0.len(), 1); + + sub1.next_message().await; + assert_eq!(pub0.free_capacity(), 4); + assert_eq!(pub0.len(), 0); + } + + #[futures_test::test] + async fn empty_channel_when_last_subscriber_is_dropped() { + let channel = PubSubChannel::::new(); + + let pub0 = channel.publisher().unwrap(); + let mut sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + + assert_eq!(4, pub0.free_capacity()); + + pub0.publish(1).await; + pub0.publish(2).await; + + assert_eq!(2, channel.free_capacity()); + + assert_eq!(1, sub0.try_next_message_pure().unwrap()); + assert_eq!(2, sub0.try_next_message_pure().unwrap()); + + assert_eq!(2, channel.free_capacity()); + + drop(sub0); + + assert_eq!(2, channel.free_capacity()); + + assert_eq!(1, sub1.try_next_message_pure().unwrap()); + + assert_eq!(3, channel.free_capacity()); + + drop(sub1); + + assert_eq!(4, channel.free_capacity()); + } + + struct CloneCallCounter(usize); + + impl Clone for CloneCallCounter { + fn clone(&self) -> Self { + Self(self.0 + 1) + } + } + + #[futures_test::test] + async fn skip_clone_for_last_message() { + let channel = PubSubChannel::::new(); + let pub0 = channel.publisher().unwrap(); + let mut sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + + pub0.publish(CloneCallCounter(0)).await; + + assert_eq!(1, sub0.try_next_message_pure().unwrap().0); + assert_eq!(0, sub1.try_next_message_pure().unwrap().0); + } + + #[futures_test::test] + async fn publisher_sink() { + use futures_util::{SinkExt, StreamExt}; + + let channel = PubSubChannel::::new(); + + let mut sub = channel.subscriber().unwrap(); + + let publ = channel.publisher().unwrap(); + let mut sink = publ.sink(); + + sink.send(0).await.unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + + sink.send(1).await.unwrap(); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + + sink.send_all(&mut futures_util::stream::iter(0..4).map(Ok)) + .await + .unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + assert_eq!(2, sub.try_next_message_pure().unwrap()); + assert_eq!(3, sub.try_next_message_pure().unwrap()); + } +} diff --git a/embassy/embassy-sync/src/pubsub/publisher.rs b/embassy/embassy-sync/src/pubsub/publisher.rs new file mode 100644 index 0000000..7a1ab66 --- /dev/null +++ b/embassy/embassy-sync/src/pubsub/publisher.rs @@ -0,0 +1,314 @@ +//! Implementation of anything directly publisher related + +use core::future::Future; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use super::{PubSubBehavior, PubSubChannel}; +use crate::blocking_mutex::raw::RawMutex; + +/// A publisher to a channel +pub struct Pub<'a, PSB: PubSubBehavior + ?Sized, T: Clone> { + /// The channel we are a publisher for + channel: &'a PSB, + _phantom: PhantomData, +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Pub<'a, PSB, T> { + pub(super) fn new(channel: &'a PSB) -> Self { + Self { + channel, + _phantom: Default::default(), + } + } + + /// Publish a message right now even when the queue is full. + /// This may cause a subscriber to miss an older message. + pub fn publish_immediate(&self, message: T) { + self.channel.publish_immediate(message) + } + + /// Publish a message. But if the message queue is full, wait for all subscribers to have read the last message + pub fn publish<'s>(&'s self, message: T) -> PublisherWaitFuture<'s, 'a, PSB, T> { + PublisherWaitFuture { + message: Some(message), + publisher: self, + } + } + + /// Publish a message if there is space in the message queue + pub fn try_publish(&self, message: T) -> Result<(), T> { + self.channel.publish_with_context(message, None) + } + + /// Returns the maximum number of elements the ***channel*** can hold. + pub fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the ***channel***. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the ***channel***. + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the ***channel***. + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the ***channel*** is empty. + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the ***channel*** is full. + pub fn is_full(&self) -> bool { + self.channel.is_full() + } + + /// Create a [`futures::Sink`] adapter for this publisher. + #[inline] + pub const fn sink(&self) -> PubSink<'a, '_, PSB, T> { + PubSink { publ: self, fut: None } + } +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Drop for Pub<'a, PSB, T> { + fn drop(&mut self) { + self.channel.unregister_publisher() + } +} + +/// A publisher that holds a dynamic reference to the channel +pub struct DynPublisher<'a, T: Clone>(pub(super) Pub<'a, dyn PubSubBehavior + 'a, T>); + +impl<'a, T: Clone> Deref for DynPublisher<'a, T> { + type Target = Pub<'a, dyn PubSubBehavior + 'a, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynPublisher<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A publisher that holds a generic reference to the channel +pub struct Publisher<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize>( + pub(super) Pub<'a, PubSubChannel, T>, +); + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> Deref + for Publisher<'a, M, T, CAP, SUBS, PUBS> +{ + type Target = Pub<'a, PubSubChannel, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> DerefMut + for Publisher<'a, M, T, CAP, SUBS, PUBS> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A publisher that can only use the `publish_immediate` function, but it doesn't have to be registered with the channel. +/// (So an infinite amount is possible) +pub struct ImmediatePub<'a, PSB: PubSubBehavior + ?Sized, T: Clone> { + /// The channel we are a publisher for + channel: &'a PSB, + _phantom: PhantomData, +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> ImmediatePub<'a, PSB, T> { + pub(super) fn new(channel: &'a PSB) -> Self { + Self { + channel, + _phantom: Default::default(), + } + } + /// Publish the message right now even when the queue is full. + /// This may cause a subscriber to miss an older message. + pub fn publish_immediate(&self, message: T) { + self.channel.publish_immediate(message) + } + + /// Publish a message if there is space in the message queue + pub fn try_publish(&self, message: T) -> Result<(), T> { + self.channel.publish_with_context(message, None) + } + + /// Returns the maximum number of elements the ***channel*** can hold. + pub fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the ***channel***. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the ***channel***. + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the ***channel***. + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the ***channel*** is empty. + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the ***channel*** is full. + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +/// An immediate publisher that holds a dynamic reference to the channel +pub struct DynImmediatePublisher<'a, T: Clone>(pub(super) ImmediatePub<'a, dyn PubSubBehavior + 'a, T>); + +impl<'a, T: Clone> Deref for DynImmediatePublisher<'a, T> { + type Target = ImmediatePub<'a, dyn PubSubBehavior + 'a, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynImmediatePublisher<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// An immediate publisher that holds a generic reference to the channel +pub struct ImmediatePublisher<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize>( + pub(super) ImmediatePub<'a, PubSubChannel, T>, +); + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> Deref + for ImmediatePublisher<'a, M, T, CAP, SUBS, PUBS> +{ + type Target = ImmediatePub<'a, PubSubChannel, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> DerefMut + for ImmediatePublisher<'a, M, T, CAP, SUBS, PUBS> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[must_use = "Sinks do nothing unless polled"] +/// [`futures_sink::Sink`] adapter for [`Pub`]. +pub struct PubSink<'a, 'p, PSB, T> +where + T: Clone, + PSB: PubSubBehavior + ?Sized, +{ + publ: &'p Pub<'a, PSB, T>, + fut: Option>, +} + +impl<'a, 'p, PSB, T> PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + /// Try to make progress on the pending future if we have one. + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + let Some(mut fut) = self.fut.take() else { + return Poll::Ready(()); + }; + + if Pin::new(&mut fut).poll(cx).is_pending() { + self.fut = Some(fut); + return Poll::Pending; + } + + Poll::Ready(()) + } +} + +impl<'a, 'p, PSB, T> futures_sink::Sink for PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + type Error = core::convert::Infallible; + + #[inline] + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { + self.fut = Some(self.publ.publish(item)); + + Ok(()) + } + + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } +} + +/// Future for the publisher wait action +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct PublisherWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { + /// The message we need to publish + message: Option, + publisher: &'s Pub<'a, PSB, T>, +} + +impl<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> Future for PublisherWaitFuture<'s, 'a, PSB, T> { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let message = self.message.take().unwrap(); + match self.publisher.channel.publish_with_context(message, Some(cx)) { + Ok(()) => Poll::Ready(()), + Err(message) => { + self.message = Some(message); + Poll::Pending + } + } + } +} + +impl<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> Unpin for PublisherWaitFuture<'s, 'a, PSB, T> {} diff --git a/embassy/embassy-sync/src/pubsub/subscriber.rs b/embassy/embassy-sync/src/pubsub/subscriber.rs new file mode 100644 index 0000000..6ad660c --- /dev/null +++ b/embassy/embassy-sync/src/pubsub/subscriber.rs @@ -0,0 +1,192 @@ +//! Implementation of anything directly subscriber related + +use core::future::Future; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use super::{PubSubBehavior, PubSubChannel, WaitResult}; +use crate::blocking_mutex::raw::RawMutex; + +/// A subscriber to a channel +pub struct Sub<'a, PSB: PubSubBehavior + ?Sized, T: Clone> { + /// The message id of the next message we are yet to receive + next_message_id: u64, + /// The channel we are a subscriber to + channel: &'a PSB, + _phantom: PhantomData, +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Sub<'a, PSB, T> { + pub(super) fn new(next_message_id: u64, channel: &'a PSB) -> Self { + Self { + next_message_id, + channel, + _phantom: Default::default(), + } + } + + /// Wait for a published message + pub fn next_message<'s>(&'s mut self) -> SubscriberWaitFuture<'s, 'a, PSB, T> { + SubscriberWaitFuture { subscriber: self } + } + + /// Wait for a published message (ignoring lag results) + pub async fn next_message_pure(&mut self) -> T { + loop { + match self.next_message().await { + WaitResult::Lagged(_) => continue, + WaitResult::Message(message) => break message, + } + } + } + + /// Try to see if there's a published message we haven't received yet. + /// + /// This function does not peek. The message is received if there is one. + pub fn try_next_message(&mut self) -> Option> { + match self.channel.get_message_with_context(&mut self.next_message_id, None) { + Poll::Ready(result) => Some(result), + Poll::Pending => None, + } + } + + /// Try to see if there's a published message we haven't received yet (ignoring lag results). + /// + /// This function does not peek. The message is received if there is one. + pub fn try_next_message_pure(&mut self) -> Option { + loop { + match self.try_next_message() { + Some(WaitResult::Lagged(_)) => continue, + Some(WaitResult::Message(message)) => break Some(message), + None => break None, + } + } + } + + /// The amount of messages this subscriber hasn't received yet. This is like [Self::len] but specifically + /// for this subscriber. + pub fn available(&self) -> u64 { + self.channel.available(self.next_message_id) + } + + /// Returns the maximum number of elements the ***channel*** can hold. + pub fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the ***channel***. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the ***channel***. + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the ***channel***. + /// See [Self::available] for how many messages are available for this subscriber. + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the ***channel*** is empty. + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the ***channel*** is full. + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Drop for Sub<'a, PSB, T> { + fn drop(&mut self) { + self.channel.unregister_subscriber(self.next_message_id) + } +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Unpin for Sub<'a, PSB, T> {} + +/// Warning: The stream implementation ignores lag results and returns all messages. +/// This might miss some messages without you knowing it. +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> futures_util::Stream for Sub<'a, PSB, T> { + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self + .channel + .get_message_with_context(&mut self.next_message_id, Some(cx)) + { + Poll::Ready(WaitResult::Message(message)) => Poll::Ready(Some(message)), + Poll::Ready(WaitResult::Lagged(_)) => { + cx.waker().wake_by_ref(); + Poll::Pending + } + Poll::Pending => Poll::Pending, + } + } +} + +/// A subscriber that holds a dynamic reference to the channel +pub struct DynSubscriber<'a, T: Clone>(pub(super) Sub<'a, dyn PubSubBehavior + 'a, T>); + +impl<'a, T: Clone> Deref for DynSubscriber<'a, T> { + type Target = Sub<'a, dyn PubSubBehavior + 'a, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynSubscriber<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A subscriber that holds a generic reference to the channel +pub struct Subscriber<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize>( + pub(super) Sub<'a, PubSubChannel, T>, +); + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> Deref + for Subscriber<'a, M, T, CAP, SUBS, PUBS> +{ + type Target = Sub<'a, PubSubChannel, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> DerefMut + for Subscriber<'a, M, T, CAP, SUBS, PUBS> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Future for the subscriber wait action +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SubscriberWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { + subscriber: &'s mut Sub<'a, PSB, T>, +} + +impl<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> Future for SubscriberWaitFuture<'s, 'a, PSB, T> { + type Output = WaitResult; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.subscriber + .channel + .get_message_with_context(&mut self.subscriber.next_message_id, Some(cx)) + } +} + +impl<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> Unpin for SubscriberWaitFuture<'s, 'a, PSB, T> {} diff --git a/embassy/embassy-sync/src/ring_buffer.rs b/embassy/embassy-sync/src/ring_buffer.rs new file mode 100644 index 0000000..81e60c4 --- /dev/null +++ b/embassy/embassy-sync/src/ring_buffer.rs @@ -0,0 +1,138 @@ +use core::ops::Range; + +pub struct RingBuffer { + start: usize, + end: usize, + full: bool, +} + +impl RingBuffer { + pub const fn new() -> Self { + Self { + start: 0, + end: 0, + full: false, + } + } + + pub fn push_buf(&mut self) -> Range { + if self.is_full() { + trace!(" ringbuf: push_buf full"); + return 0..0; + } + + let n = if self.start <= self.end { + N - self.end + } else { + self.start - self.end + }; + + trace!(" ringbuf: push_buf {:?}..{:?}", self.end, self.end + n); + self.end..self.end + n + } + + pub fn push(&mut self, n: usize) { + trace!(" ringbuf: push {:?}", n); + if n == 0 { + return; + } + + self.end = self.wrap(self.end + n); + self.full = self.start == self.end; + } + + pub fn pop_buf(&mut self) -> Range { + if self.is_empty() { + trace!(" ringbuf: pop_buf empty"); + return 0..0; + } + + let n = if self.end <= self.start { + N - self.start + } else { + self.end - self.start + }; + + trace!(" ringbuf: pop_buf {:?}..{:?}", self.start, self.start + n); + self.start..self.start + n + } + + pub fn pop(&mut self, n: usize) { + trace!(" ringbuf: pop {:?}", n); + if n == 0 { + return; + } + + self.start = self.wrap(self.start + n); + self.full = false; + } + + pub fn is_full(&self) -> bool { + self.full + } + + pub fn is_empty(&self) -> bool { + self.start == self.end && !self.full + } + + #[allow(unused)] + pub fn len(&self) -> usize { + if self.is_empty() { + 0 + } else if self.start < self.end { + self.end - self.start + } else { + N + self.end - self.start + } + } + + pub fn clear(&mut self) { + self.start = 0; + self.end = 0; + self.full = false; + } + + fn wrap(&self, n: usize) -> usize { + assert!(n <= N); + if n == N { + 0 + } else { + n + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn push_pop() { + let mut rb: RingBuffer<4> = RingBuffer::new(); + let buf = rb.push_buf(); + assert_eq!(0..4, buf); + rb.push(4); + + let buf = rb.pop_buf(); + assert_eq!(0..4, buf); + rb.pop(1); + + let buf = rb.pop_buf(); + assert_eq!(1..4, buf); + rb.pop(1); + + let buf = rb.pop_buf(); + assert_eq!(2..4, buf); + rb.pop(1); + + let buf = rb.pop_buf(); + assert_eq!(3..4, buf); + rb.pop(1); + + let buf = rb.pop_buf(); + assert_eq!(0..0, buf); + + let buf = rb.push_buf(); + assert_eq!(0..4, buf); + } +} diff --git a/embassy/embassy-sync/src/semaphore.rs b/embassy/embassy-sync/src/semaphore.rs new file mode 100644 index 0000000..d30eee3 --- /dev/null +++ b/embassy/embassy-sync/src/semaphore.rs @@ -0,0 +1,772 @@ +//! A synchronization primitive for controlling access to a pool of resources. +use core::cell::{Cell, RefCell}; +use core::convert::Infallible; +use core::future::{poll_fn, Future}; +use core::task::{Poll, Waker}; + +use heapless::Deque; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::WakerRegistration; + +/// An asynchronous semaphore. +/// +/// A semaphore tracks a number of permits, typically representing a pool of shared resources. +/// Users can acquire permits to synchronize access to those resources. The semaphore does not +/// contain the resources themselves, only the count of available permits. +pub trait Semaphore: Sized { + /// The error returned when the semaphore is unable to acquire the requested permits. + type Error; + + /// Asynchronously acquire one or more permits from the semaphore. + async fn acquire(&self, permits: usize) -> Result, Self::Error>; + + /// Try to immediately acquire one or more permits from the semaphore. + fn try_acquire(&self, permits: usize) -> Option>; + + /// Asynchronously acquire all permits controlled by the semaphore. + /// + /// This method will wait until at least `min` permits are available, then acquire all available permits + /// from the semaphore. Note that other tasks may have already acquired some permits which could be released + /// back to the semaphore at any time. The number of permits actually acquired may be determined by calling + /// [`SemaphoreReleaser::permits`]. + async fn acquire_all(&self, min: usize) -> Result, Self::Error>; + + /// Try to immediately acquire all available permits from the semaphore, if at least `min` permits are available. + fn try_acquire_all(&self, min: usize) -> Option>; + + /// Release `permits` back to the semaphore, making them available to be acquired. + fn release(&self, permits: usize); + + /// Reset the number of available permints in the semaphore to `permits`. + fn set(&self, permits: usize); +} + +/// A representation of a number of acquired permits. +/// +/// The acquired permits will be released back to the [`Semaphore`] when this is dropped. +pub struct SemaphoreReleaser<'a, S: Semaphore> { + semaphore: &'a S, + permits: usize, +} + +impl<'a, S: Semaphore> Drop for SemaphoreReleaser<'a, S> { + fn drop(&mut self) { + self.semaphore.release(self.permits); + } +} + +impl<'a, S: Semaphore> SemaphoreReleaser<'a, S> { + /// The number of acquired permits. + pub fn permits(&self) -> usize { + self.permits + } + + /// Prevent the acquired permits from being released on drop. + /// + /// Returns the number of acquired permits. + pub fn disarm(self) -> usize { + let permits = self.permits; + core::mem::forget(self); + permits + } +} + +/// A greedy [`Semaphore`] implementation. +/// +/// Tasks can acquire permits as soon as they become available, even if another task +/// is waiting on a larger number of permits. +pub struct GreedySemaphore { + state: Mutex>, +} + +impl Default for GreedySemaphore { + fn default() -> Self { + Self::new(0) + } +} + +impl GreedySemaphore { + /// Create a new `Semaphore`. + pub const fn new(permits: usize) -> Self { + Self { + state: Mutex::new(Cell::new(SemaphoreState { + permits, + waker: WakerRegistration::new(), + })), + } + } + + #[cfg(test)] + fn permits(&self) -> usize { + self.state.lock(|cell| { + let state = cell.replace(SemaphoreState::EMPTY); + let permits = state.permits; + cell.replace(state); + permits + }) + } + + fn poll_acquire( + &self, + permits: usize, + acquire_all: bool, + waker: Option<&Waker>, + ) -> Poll, Infallible>> { + self.state.lock(|cell| { + let mut state = cell.replace(SemaphoreState::EMPTY); + if let Some(permits) = state.take(permits, acquire_all) { + cell.set(state); + Poll::Ready(Ok(SemaphoreReleaser { + semaphore: self, + permits, + })) + } else { + if let Some(waker) = waker { + state.register(waker); + } + cell.set(state); + Poll::Pending + } + }) + } +} + +impl Semaphore for GreedySemaphore { + type Error = Infallible; + + async fn acquire(&self, permits: usize) -> Result, Self::Error> { + poll_fn(|cx| self.poll_acquire(permits, false, Some(cx.waker()))).await + } + + fn try_acquire(&self, permits: usize) -> Option> { + match self.poll_acquire(permits, false, None) { + Poll::Ready(Ok(n)) => Some(n), + _ => None, + } + } + + async fn acquire_all(&self, min: usize) -> Result, Self::Error> { + poll_fn(|cx| self.poll_acquire(min, true, Some(cx.waker()))).await + } + + fn try_acquire_all(&self, min: usize) -> Option> { + match self.poll_acquire(min, true, None) { + Poll::Ready(Ok(n)) => Some(n), + _ => None, + } + } + + fn release(&self, permits: usize) { + if permits > 0 { + self.state.lock(|cell| { + let mut state = cell.replace(SemaphoreState::EMPTY); + state.permits += permits; + state.wake(); + cell.set(state); + }); + } + } + + fn set(&self, permits: usize) { + self.state.lock(|cell| { + let mut state = cell.replace(SemaphoreState::EMPTY); + if permits > state.permits { + state.wake(); + } + state.permits = permits; + cell.set(state); + }); + } +} + +struct SemaphoreState { + permits: usize, + waker: WakerRegistration, +} + +impl SemaphoreState { + const EMPTY: SemaphoreState = SemaphoreState { + permits: 0, + waker: WakerRegistration::new(), + }; + + fn register(&mut self, w: &Waker) { + self.waker.register(w); + } + + fn take(&mut self, mut permits: usize, acquire_all: bool) -> Option { + if self.permits < permits { + None + } else { + if acquire_all { + permits = self.permits; + } + self.permits -= permits; + Some(permits) + } + } + + fn wake(&mut self) { + self.waker.wake(); + } +} + +/// A fair [`Semaphore`] implementation. +/// +/// Tasks are allowed to acquire permits in FIFO order. A task waiting to acquire +/// a large number of permits will prevent other tasks from acquiring any permits +/// until its request is satisfied. +/// +/// Up to `N` tasks may attempt to acquire permits concurrently. If additional +/// tasks attempt to acquire a permit, a [`WaitQueueFull`] error will be returned. +pub struct FairSemaphore +where + M: RawMutex, +{ + state: Mutex>>, +} + +impl Default for FairSemaphore +where + M: RawMutex, +{ + fn default() -> Self { + Self::new(0) + } +} + +impl FairSemaphore +where + M: RawMutex, +{ + /// Create a new `FairSemaphore`. + pub const fn new(permits: usize) -> Self { + Self { + state: Mutex::new(RefCell::new(FairSemaphoreState::new(permits))), + } + } + + #[cfg(test)] + fn permits(&self) -> usize { + self.state.lock(|cell| cell.borrow().permits) + } + + fn poll_acquire( + &self, + permits: usize, + acquire_all: bool, + cx: Option<(&mut Option, &Waker)>, + ) -> Poll, WaitQueueFull>> { + let ticket = cx.as_ref().map(|(x, _)| **x).unwrap_or(None); + self.state.lock(|cell| { + let mut state = cell.borrow_mut(); + if let Some(permits) = state.take(ticket, permits, acquire_all) { + Poll::Ready(Ok(SemaphoreReleaser { + semaphore: self, + permits, + })) + } else if let Some((ticket_ref, waker)) = cx { + match state.register(ticket, waker) { + Ok(ticket) => { + *ticket_ref = Some(ticket); + Poll::Pending + } + Err(err) => Poll::Ready(Err(err)), + } + } else { + Poll::Pending + } + }) + } +} + +/// An error indicating the [`FairSemaphore`]'s wait queue is full. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct WaitQueueFull; + +impl Semaphore for FairSemaphore { + type Error = WaitQueueFull; + + fn acquire(&self, permits: usize) -> impl Future, Self::Error>> { + FairAcquire { + sema: self, + permits, + ticket: None, + } + } + + fn try_acquire(&self, permits: usize) -> Option> { + match self.poll_acquire(permits, false, None) { + Poll::Ready(Ok(x)) => Some(x), + _ => None, + } + } + + fn acquire_all(&self, min: usize) -> impl Future, Self::Error>> { + FairAcquireAll { + sema: self, + min, + ticket: None, + } + } + + fn try_acquire_all(&self, min: usize) -> Option> { + match self.poll_acquire(min, true, None) { + Poll::Ready(Ok(x)) => Some(x), + _ => None, + } + } + + fn release(&self, permits: usize) { + if permits > 0 { + self.state.lock(|cell| { + let mut state = cell.borrow_mut(); + state.permits += permits; + state.wake(); + }); + } + } + + fn set(&self, permits: usize) { + self.state.lock(|cell| { + let mut state = cell.borrow_mut(); + if permits > state.permits { + state.wake(); + } + state.permits = permits; + }); + } +} + +struct FairAcquire<'a, M: RawMutex, const N: usize> { + sema: &'a FairSemaphore, + permits: usize, + ticket: Option, +} + +impl<'a, M: RawMutex, const N: usize> Drop for FairAcquire<'a, M, N> { + fn drop(&mut self) { + self.sema + .state + .lock(|cell| cell.borrow_mut().cancel(self.ticket.take())); + } +} + +impl<'a, M: RawMutex, const N: usize> core::future::Future for FairAcquire<'a, M, N> { + type Output = Result>, WaitQueueFull>; + + fn poll(mut self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll { + self.sema + .poll_acquire(self.permits, false, Some((&mut self.ticket, cx.waker()))) + } +} + +struct FairAcquireAll<'a, M: RawMutex, const N: usize> { + sema: &'a FairSemaphore, + min: usize, + ticket: Option, +} + +impl<'a, M: RawMutex, const N: usize> Drop for FairAcquireAll<'a, M, N> { + fn drop(&mut self) { + self.sema + .state + .lock(|cell| cell.borrow_mut().cancel(self.ticket.take())); + } +} + +impl<'a, M: RawMutex, const N: usize> core::future::Future for FairAcquireAll<'a, M, N> { + type Output = Result>, WaitQueueFull>; + + fn poll(mut self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll { + self.sema + .poll_acquire(self.min, true, Some((&mut self.ticket, cx.waker()))) + } +} + +struct FairSemaphoreState { + permits: usize, + next_ticket: usize, + wakers: Deque, N>, +} + +impl FairSemaphoreState { + /// Create a new empty instance + const fn new(permits: usize) -> Self { + Self { + permits, + next_ticket: 0, + wakers: Deque::new(), + } + } + + /// Register a waker. If the queue is full the function returns an error + fn register(&mut self, ticket: Option, w: &Waker) -> Result { + self.pop_canceled(); + + match ticket { + None => { + let ticket = self.next_ticket.wrapping_add(self.wakers.len()); + self.wakers.push_back(Some(w.clone())).or(Err(WaitQueueFull))?; + Ok(ticket) + } + Some(ticket) => { + self.set_waker(ticket, Some(w.clone())); + Ok(ticket) + } + } + } + + fn cancel(&mut self, ticket: Option) { + if let Some(ticket) = ticket { + self.set_waker(ticket, None); + } + } + + fn set_waker(&mut self, ticket: usize, waker: Option) { + let i = ticket.wrapping_sub(self.next_ticket); + if i < self.wakers.len() { + let (a, b) = self.wakers.as_mut_slices(); + let x = if i < a.len() { &mut a[i] } else { &mut b[i - a.len()] }; + *x = waker; + } + } + + fn take(&mut self, ticket: Option, mut permits: usize, acquire_all: bool) -> Option { + self.pop_canceled(); + + if permits > self.permits { + return None; + } + + match ticket { + Some(n) if n != self.next_ticket => return None, + None if !self.wakers.is_empty() => return None, + _ => (), + } + + if acquire_all { + permits = self.permits; + } + self.permits -= permits; + + if ticket.is_some() { + self.pop(); + if self.permits > 0 { + self.wake(); + } + } + + Some(permits) + } + + fn pop_canceled(&mut self) { + while let Some(None) = self.wakers.front() { + self.pop(); + } + } + + /// Panics if `self.wakers` is empty + fn pop(&mut self) { + self.wakers.pop_front().unwrap(); + self.next_ticket = self.next_ticket.wrapping_add(1); + } + + fn wake(&mut self) { + self.pop_canceled(); + + if let Some(Some(waker)) = self.wakers.front() { + waker.wake_by_ref(); + } + } +} + +#[cfg(test)] +mod tests { + mod greedy { + use core::pin::pin; + + use futures_util::poll; + + use super::super::*; + use crate::blocking_mutex::raw::NoopRawMutex; + + #[test] + fn try_acquire() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + assert_eq!(a.permits(), 1); + assert_eq!(semaphore.permits(), 2); + + core::mem::drop(a); + assert_eq!(semaphore.permits(), 3); + } + + #[test] + fn disarm() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + assert_eq!(a.disarm(), 1); + assert_eq!(semaphore.permits(), 2); + } + + #[futures_test::test] + async fn acquire() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.acquire(1).await.unwrap(); + assert_eq!(a.permits(), 1); + assert_eq!(semaphore.permits(), 2); + + core::mem::drop(a); + assert_eq!(semaphore.permits(), 3); + } + + #[test] + fn try_acquire_all() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.try_acquire_all(1).unwrap(); + assert_eq!(a.permits(), 3); + assert_eq!(semaphore.permits(), 0); + } + + #[futures_test::test] + async fn acquire_all() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.acquire_all(1).await.unwrap(); + assert_eq!(a.permits(), 3); + assert_eq!(semaphore.permits(), 0); + } + + #[test] + fn release() { + let semaphore = GreedySemaphore::::new(3); + assert_eq!(semaphore.permits(), 3); + semaphore.release(2); + assert_eq!(semaphore.permits(), 5); + } + + #[test] + fn set() { + let semaphore = GreedySemaphore::::new(3); + assert_eq!(semaphore.permits(), 3); + semaphore.set(2); + assert_eq!(semaphore.permits(), 2); + } + + #[test] + fn contested() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + let b = semaphore.try_acquire(3); + assert!(b.is_none()); + + core::mem::drop(a); + + let b = semaphore.try_acquire(3); + assert!(b.is_some()); + } + + #[futures_test::test] + async fn greedy() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + + let b_fut = semaphore.acquire(3); + let mut b_fut = pin!(b_fut); + let b = poll!(b_fut.as_mut()); + assert!(b.is_pending()); + + // Succeed even through `b` is waiting + let c = semaphore.try_acquire(1); + assert!(c.is_some()); + + let b = poll!(b_fut.as_mut()); + assert!(b.is_pending()); + + core::mem::drop(a); + + let b = poll!(b_fut.as_mut()); + assert!(b.is_pending()); + + core::mem::drop(c); + + let b = poll!(b_fut.as_mut()); + assert!(b.is_ready()); + } + } + + mod fair { + use core::pin::pin; + use core::time::Duration; + + use futures_executor::ThreadPool; + use futures_timer::Delay; + use futures_util::poll; + use futures_util::task::SpawnExt; + use static_cell::StaticCell; + + use super::super::*; + use crate::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; + + #[test] + fn try_acquire() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + assert_eq!(a.permits(), 1); + assert_eq!(semaphore.permits(), 2); + + core::mem::drop(a); + assert_eq!(semaphore.permits(), 3); + } + + #[test] + fn disarm() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + assert_eq!(a.disarm(), 1); + assert_eq!(semaphore.permits(), 2); + } + + #[futures_test::test] + async fn acquire() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.acquire(1).await.unwrap(); + assert_eq!(a.permits(), 1); + assert_eq!(semaphore.permits(), 2); + + core::mem::drop(a); + assert_eq!(semaphore.permits(), 3); + } + + #[test] + fn try_acquire_all() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.try_acquire_all(1).unwrap(); + assert_eq!(a.permits(), 3); + assert_eq!(semaphore.permits(), 0); + } + + #[futures_test::test] + async fn acquire_all() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.acquire_all(1).await.unwrap(); + assert_eq!(a.permits(), 3); + assert_eq!(semaphore.permits(), 0); + } + + #[test] + fn release() { + let semaphore = FairSemaphore::::new(3); + assert_eq!(semaphore.permits(), 3); + semaphore.release(2); + assert_eq!(semaphore.permits(), 5); + } + + #[test] + fn set() { + let semaphore = FairSemaphore::::new(3); + assert_eq!(semaphore.permits(), 3); + semaphore.set(2); + assert_eq!(semaphore.permits(), 2); + } + + #[test] + fn contested() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + let b = semaphore.try_acquire(3); + assert!(b.is_none()); + + core::mem::drop(a); + + let b = semaphore.try_acquire(3); + assert!(b.is_some()); + } + + #[futures_test::test] + async fn fairness() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.try_acquire(1); + assert!(a.is_some()); + + let b_fut = semaphore.acquire(3); + let mut b_fut = pin!(b_fut); + let b = poll!(b_fut.as_mut()); // Poll `b_fut` once so it is registered + assert!(b.is_pending()); + + let c = semaphore.try_acquire(1); + assert!(c.is_none()); + + let c_fut = semaphore.acquire(1); + let mut c_fut = pin!(c_fut); + let c = poll!(c_fut.as_mut()); // Poll `c_fut` once so it is registered + assert!(c.is_pending()); // `c` is blocked behind `b` + + let d = semaphore.acquire(1).await; + assert!(matches!(d, Err(WaitQueueFull))); + + core::mem::drop(a); + + let c = poll!(c_fut.as_mut()); + assert!(c.is_pending()); // `c` is still blocked behind `b` + + let b = poll!(b_fut.as_mut()); + assert!(b.is_ready()); + + let c = poll!(c_fut.as_mut()); + assert!(c.is_pending()); // `c` is still blocked behind `b` + + core::mem::drop(b); + + let c = poll!(c_fut.as_mut()); + assert!(c.is_ready()); + } + + #[futures_test::test] + async fn wakers() { + let executor = ThreadPool::new().unwrap(); + + static SEMAPHORE: StaticCell> = StaticCell::new(); + let semaphore = &*SEMAPHORE.init(FairSemaphore::new(3)); + + let a = semaphore.try_acquire(2); + assert!(a.is_some()); + + let b_task = executor + .spawn_with_handle(async move { semaphore.acquire(2).await }) + .unwrap(); + while semaphore.state.lock(|x| x.borrow().wakers.is_empty()) { + Delay::new(Duration::from_millis(50)).await; + } + + let c_task = executor + .spawn_with_handle(async move { semaphore.acquire(1).await }) + .unwrap(); + + core::mem::drop(a); + + let b = b_task.await.unwrap(); + assert_eq!(b.permits(), 2); + + let c = c_task.await.unwrap(); + assert_eq!(c.permits(), 1); + } + } +} diff --git a/embassy/embassy-sync/src/signal.rs b/embassy/embassy-sync/src/signal.rs new file mode 100644 index 0000000..a0f4b5a --- /dev/null +++ b/embassy/embassy-sync/src/signal.rs @@ -0,0 +1,140 @@ +//! A synchronization primitive for passing the latest value to a task. +use core::cell::Cell; +use core::future::{poll_fn, Future}; +use core::task::{Context, Poll, Waker}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; + +/// Single-slot signaling primitive. +/// +/// This is similar to a [`Channel`](crate::channel::Channel) with a buffer size of 1, except +/// "sending" to it (calling [`Signal::signal`]) when full will overwrite the previous value instead +/// of waiting for the receiver to pop the previous value. +/// +/// It is useful for sending data between tasks when the receiver only cares about +/// the latest data, and therefore it's fine to "lose" messages. This is often the case for "state" +/// updates. +/// +/// For more advanced use cases, you might want to use [`Channel`](crate::channel::Channel) instead. +/// +/// Signals are generally declared as `static`s and then borrowed as required. +/// +/// ``` +/// use embassy_sync::signal::Signal; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// +/// enum SomeCommand { +/// On, +/// Off, +/// } +/// +/// static SOME_SIGNAL: Signal = Signal::new(); +/// ``` +pub struct Signal +where + M: RawMutex, +{ + state: Mutex>>, +} + +enum State { + None, + Waiting(Waker), + Signaled(T), +} + +impl Signal +where + M: RawMutex, +{ + /// Create a new `Signal`. + pub const fn new() -> Self { + Self { + state: Mutex::new(Cell::new(State::None)), + } + } +} + +impl Default for Signal +where + M: RawMutex, +{ + fn default() -> Self { + Self::new() + } +} + +impl Signal +where + M: RawMutex, +{ + /// Mark this Signal as signaled. + pub fn signal(&self, val: T) { + self.state.lock(|cell| { + let state = cell.replace(State::Signaled(val)); + if let State::Waiting(waker) = state { + waker.wake(); + } + }) + } + + /// Remove the queued value in this `Signal`, if any. + pub fn reset(&self) { + self.state.lock(|cell| cell.set(State::None)); + } + + fn poll_wait(&self, cx: &mut Context<'_>) -> Poll { + self.state.lock(|cell| { + let state = cell.replace(State::None); + match state { + State::None => { + cell.set(State::Waiting(cx.waker().clone())); + Poll::Pending + } + State::Waiting(w) if w.will_wake(cx.waker()) => { + cell.set(State::Waiting(w)); + Poll::Pending + } + State::Waiting(w) => { + cell.set(State::Waiting(cx.waker().clone())); + w.wake(); + Poll::Pending + } + State::Signaled(res) => Poll::Ready(res), + } + }) + } + + /// Future that completes when this Signal has been signaled. + pub fn wait(&self) -> impl Future + '_ { + poll_fn(move |cx| self.poll_wait(cx)) + } + + /// non-blocking method to try and take the signal value. + pub fn try_take(&self) -> Option { + self.state.lock(|cell| { + let state = cell.replace(State::None); + match state { + State::Signaled(res) => Some(res), + state => { + cell.set(state); + None + } + } + }) + } + + /// non-blocking method to check whether this signal has been signaled. This does not clear the signal. + pub fn signaled(&self) -> bool { + self.state.lock(|cell| { + let state = cell.replace(State::None); + + let res = matches!(state, State::Signaled(_)); + + cell.set(state); + + res + }) + } +} diff --git a/embassy/embassy-sync/src/waitqueue/atomic_waker.rs b/embassy/embassy-sync/src/waitqueue/atomic_waker.rs new file mode 100644 index 0000000..231902c --- /dev/null +++ b/embassy/embassy-sync/src/waitqueue/atomic_waker.rs @@ -0,0 +1,63 @@ +use core::cell::Cell; +use core::task::Waker; + +use crate::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex}; +use crate::blocking_mutex::Mutex; + +/// Utility struct to register and wake a waker. +pub struct GenericAtomicWaker { + waker: Mutex>>, +} + +impl GenericAtomicWaker { + /// Create a new `AtomicWaker`. + pub const fn new(mutex: M) -> Self { + Self { + waker: Mutex::const_new(mutex, Cell::new(None)), + } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&self, w: &Waker) { + self.waker.lock(|cell| { + cell.set(match cell.replace(None) { + Some(w2) if (w2.will_wake(w)) => Some(w2), + _ => Some(w.clone()), + }) + }) + } + + /// Wake the registered waker, if any. + pub fn wake(&self) { + self.waker.lock(|cell| { + if let Some(w) = cell.replace(None) { + w.wake_by_ref(); + cell.set(Some(w)); + } + }) + } +} + +/// Utility struct to register and wake a waker. +pub struct AtomicWaker { + waker: GenericAtomicWaker, +} + +impl AtomicWaker { + /// Create a new `AtomicWaker`. + pub const fn new() -> Self { + Self { + waker: GenericAtomicWaker::new(CriticalSectionRawMutex::new()), + } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&self, w: &Waker) { + self.waker.register(w); + } + + /// Wake the registered waker, if any. + pub fn wake(&self) { + self.waker.wake(); + } +} diff --git a/embassy/embassy-sync/src/waitqueue/atomic_waker_turbo.rs b/embassy/embassy-sync/src/waitqueue/atomic_waker_turbo.rs new file mode 100644 index 0000000..5c6a96e --- /dev/null +++ b/embassy/embassy-sync/src/waitqueue/atomic_waker_turbo.rs @@ -0,0 +1,30 @@ +use core::ptr; +use core::ptr::NonNull; +use core::sync::atomic::{AtomicPtr, Ordering}; +use core::task::Waker; + +/// Utility struct to register and wake a waker. +pub struct AtomicWaker { + waker: AtomicPtr<()>, +} + +impl AtomicWaker { + /// Create a new `AtomicWaker`. + pub const fn new() -> Self { + Self { + waker: AtomicPtr::new(ptr::null_mut()), + } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&self, w: &Waker) { + self.waker.store(w.as_turbo_ptr().as_ptr() as _, Ordering::Release); + } + + /// Wake the registered waker, if any. + pub fn wake(&self) { + if let Some(ptr) = NonNull::new(self.waker.load(Ordering::Acquire)) { + unsafe { Waker::from_turbo_ptr(ptr) }.wake(); + } + } +} diff --git a/embassy/embassy-sync/src/waitqueue/mod.rs b/embassy/embassy-sync/src/waitqueue/mod.rs new file mode 100644 index 0000000..6b0b0c6 --- /dev/null +++ b/embassy/embassy-sync/src/waitqueue/mod.rs @@ -0,0 +1,11 @@ +//! Async low-level wait queues + +#[cfg_attr(feature = "turbowakers", path = "atomic_waker_turbo.rs")] +mod atomic_waker; +pub use atomic_waker::*; + +mod waker_registration; +pub use waker_registration::*; + +mod multi_waker; +pub use multi_waker::*; diff --git a/embassy/embassy-sync/src/waitqueue/multi_waker.rs b/embassy/embassy-sync/src/waitqueue/multi_waker.rs new file mode 100644 index 0000000..0e520bf --- /dev/null +++ b/embassy/embassy-sync/src/waitqueue/multi_waker.rs @@ -0,0 +1,58 @@ +use core::task::Waker; + +use heapless::Vec; + +/// Utility struct to register and wake multiple wakers. +pub struct MultiWakerRegistration { + wakers: Vec, +} + +impl MultiWakerRegistration { + /// Create a new empty instance + pub const fn new() -> Self { + Self { wakers: Vec::new() } + } + + /// Register a waker. If the buffer is full the function returns it in the error + pub fn register(&mut self, w: &Waker) { + // If we already have some waker that wakes the same task as `w`, do nothing. + // This avoids cloning wakers, and avoids unnecessary mass-wakes. + for w2 in &self.wakers { + if w.will_wake(w2) { + return; + } + } + + if self.wakers.is_full() { + // All waker slots were full. It's a bit inefficient, but we can wake everything. + // Any future that is still active will simply reregister. + // This won't happen a lot, so it's ok. + self.wake(); + } + + if self.wakers.push(w.clone()).is_err() { + // This can't happen unless N=0 + // (Either `wakers` wasn't full, or it was in which case `wake()` empied it) + panic!("tried to push a waker to a zero-length MultiWakerRegistration") + } + } + + /// Wake all registered wakers. This clears the buffer + pub fn wake(&mut self) { + // heapless::Vec has no `drain()`, do it unsafely ourselves... + + // First set length to 0, without dropping the contents. + // This is necessary for soundness: if wake() panics and we're using panic=unwind. + // Setting len=0 upfront ensures other code can't observe the vec in an inconsistent state. + // (it'll leak wakers, but that's not UB) + let len = self.wakers.len(); + unsafe { self.wakers.set_len(0) } + + for i in 0..len { + // Move a waker out of the vec. + let waker = unsafe { self.wakers.as_mut_ptr().add(i).read() }; + // Wake it by value, which consumes (drops) it. + waker.wake(); + } + } +} diff --git a/embassy/embassy-sync/src/waitqueue/waker_registration.rs b/embassy/embassy-sync/src/waitqueue/waker_registration.rs new file mode 100644 index 0000000..9b666e7 --- /dev/null +++ b/embassy/embassy-sync/src/waitqueue/waker_registration.rs @@ -0,0 +1,52 @@ +use core::mem; +use core::task::Waker; + +/// Utility struct to register and wake a waker. +#[derive(Debug, Default)] +pub struct WakerRegistration { + waker: Option, +} + +impl WakerRegistration { + /// Create a new `WakerRegistration`. + pub const fn new() -> Self { + Self { waker: None } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&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) = 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() + } + } + } + } + + /// Wake the registered waker, if any. + pub fn wake(&mut self) { + if let Some(w) = self.waker.take() { + w.wake() + } + } + + /// Returns true if a waker is currently registered + pub fn occupied(&self) -> bool { + self.waker.is_some() + } +} diff --git a/embassy/embassy-sync/src/watch.rs b/embassy/embassy-sync/src/watch.rs new file mode 100644 index 0000000..404e317 --- /dev/null +++ b/embassy/embassy-sync/src/watch.rs @@ -0,0 +1,1121 @@ +//! A synchronization primitive for passing the latest value to **multiple** receivers. + +use core::cell::RefCell; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::task::{Context, Poll}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::MultiWakerRegistration; + +/// The `Watch` is a single-slot signaling primitive that allows multiple receivers to concurrently await +/// changes to the value. Unlike a [`Signal`](crate::signal::Signal), `Watch` supports multiple receivers, +/// and unlike a [`PubSubChannel`](crate::pubsub::PubSubChannel), `Watch` immediately overwrites the previous +/// value when a new one is sent, without waiting for all receivers to read the previous value. +/// +/// This makes `Watch` particularly useful when a single task updates a value or "state", and multiple other tasks +/// need to be notified about changes to this value asynchronously. Receivers may "lose" stale values, as they are +/// always provided with the latest value. +/// +/// Typically, `Watch` instances are declared as `static`, and a [`Sender`] and [`Receiver`] +/// (or [`DynSender`] and/or [`DynReceiver`]) are obtained where relevant. An [`AnonReceiver`] +/// and [`DynAnonReceiver`] are also available, which do not increase the receiver count for the +/// channel, and unwrapping is therefore not required, but it is not possible to `.await` the channel. +/// ``` +/// +/// use futures_executor::block_on; +/// use embassy_sync::watch::Watch; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// +/// let f = async { +/// +/// static WATCH: Watch = Watch::new(); +/// +/// // Obtain receivers and sender +/// let mut rcv0 = WATCH.receiver().unwrap(); +/// let mut rcv1 = WATCH.dyn_receiver().unwrap(); +/// let mut snd = WATCH.sender(); +/// +/// // No more receivers, and no update +/// assert!(WATCH.receiver().is_none()); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// snd.send(10); +/// +/// // Receive the new value (async or try) +/// assert_eq!(rcv0.changed().await, 10); +/// assert_eq!(rcv1.try_changed(), Some(10)); +/// +/// // No update +/// assert_eq!(rcv0.try_changed(), None); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// snd.send(20); +/// +/// // Using `get` marks the value as seen +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// // But `get` also returns when unchanged +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.get().await, 20); +/// +/// }; +/// block_on(f); +/// ``` +pub struct Watch { + mutex: Mutex>>, +} + +struct WatchState { + data: Option, + current_id: u64, + wakers: MultiWakerRegistration, + receiver_count: usize, +} + +trait SealedWatchBehavior { + /// Poll the `Watch` for the current value, making it as seen. + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Poll the `Watch` for the value if it matches the predicate function + /// `f`, making it as seen. + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Poll the `Watch` for a changed value, marking it as seen, if an id is given. + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. + fn try_changed(&self, id: &mut u64) -> Option; + + /// Poll the `Watch` for a changed value that matches the predicate function + /// `f`, marking it as seen. + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed and matches the + /// predicate function `f`, marking it as seen. + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; + + /// Used when a receiver is dropped to decrement the receiver count. + /// + /// ## This method should not be called by the user. + fn drop_receiver(&self); + + /// Clears the value of the `Watch`. + fn clear(&self); + + /// Sends a new value to the `Watch`. + fn send(&self, val: T); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_modify(&self, f: &mut dyn Fn(&mut Option)); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool); +} + +/// A trait representing the 'inner' behavior of the `Watch`. +#[allow(private_bounds)] +pub trait WatchBehavior: SealedWatchBehavior { + /// Tries to get the value of the `Watch`, marking it as seen, if an id is given. + fn try_get(&self, id: Option<&mut u64>) -> Option; + + /// Tries to get the value of the `Watch` if it matches the predicate function + /// `f`, marking it as seen. + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option; + + /// Checks if the `Watch` is been initialized with a value. + fn contains_value(&self) -> bool; +} + +impl SealedWatchBehavior for Watch { + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match &s.data { + Some(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + None => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.data { + Some(ref data) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed(&self, id: &mut u64) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.current_id > *id { + true => { + *id = s.current_id; + s.data.clone() + } + false => None, + } + }) + } + + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + s.data.clone() + } + _ => None, + } + }) + } + + fn drop_receiver(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.receiver_count -= 1; + }) + } + + fn clear(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = None; + }) + } + + fn send(&self, val: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = Some(val); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn send_modify(&self, f: &mut dyn Fn(&mut Option)) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + f(&mut s.data); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if f(&mut s.data) { + s.current_id += 1; + s.wakers.wake(); + } + }) + } +} + +impl WatchBehavior for Watch { + fn try_get(&self, id: Option<&mut u64>) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + if let Some(id) = id { + *id = s.current_id; + } + s.data.clone() + }) + } + + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.data { + Some(ref data) if f(data) => { + if let Some(id) = id { + *id = s.current_id; + } + Some(data.clone()) + } + _ => None, + } + }) + } + + fn contains_value(&self) -> bool { + self.mutex.lock(|state| state.borrow().data.is_some()) + } +} + +impl Watch { + /// Create a new `Watch` channel. + pub const fn new() -> Self { + Self { + mutex: Mutex::new(RefCell::new(WatchState { + data: None, + current_id: 0, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + } + } + + /// Create a new `Watch` channel with default data. + pub const fn new_with(data: T) -> Self { + Self { + mutex: Mutex::new(RefCell::new(WatchState { + data: Some(data), + current_id: 0, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + } + } + + /// Create a new [`Sender`] for the `Watch`. + pub fn sender(&self) -> Sender<'_, M, T, N> { + Sender(Snd::new(self)) + } + + /// Create a new [`DynSender`] for the `Watch`. + pub fn dyn_sender(&self) -> DynSender<'_, T> { + DynSender(Snd::new(self)) + } + + /// Try to create a new [`Receiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn receiver(&self) -> Option> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Some(Receiver(Rcv::new(self, 0))) + } else { + None + } + }) + } + + /// Try to create a new [`DynReceiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn dyn_receiver(&self) -> Option> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Some(DynReceiver(Rcv::new(self, 0))) + } else { + None + } + }) + } + + /// Try to create a new [`AnonReceiver`] for the `Watch`. + pub fn anon_receiver(&self) -> AnonReceiver<'_, M, T, N> { + AnonReceiver(AnonRcv::new(self, 0)) + } + + /// Try to create a new [`DynAnonReceiver`] for the `Watch`. + pub fn dyn_anon_receiver(&self) -> DynAnonReceiver<'_, T> { + DynAnonReceiver(AnonRcv::new(self, 0)) + } + + /// Returns the message ID of the latest message sent to the `Watch`. + /// + /// This counter is monotonic, and is incremented every time a new message is sent. + pub fn get_msg_id(&self) -> u64 { + self.mutex.lock(|state| state.borrow().current_id) + } + + /// Tries to get the value of the `Watch`. + pub fn try_get(&self) -> Option { + WatchBehavior::try_get(self, None) + } + + /// Tries to get the value of the `Watch` if it matches the predicate function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + WatchBehavior::try_get_and(self, None, &mut f) + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Snd<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Clone for Snd<'a, T, W> { + fn clone(&self) -> Self { + Self { + watch: self.watch, + _phantom: PhantomData, + } + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W) -> Self { + Self { + watch, + _phantom: PhantomData, + } + } + + /// Sends a new value to the `Watch`. + pub fn send(&self, val: T) { + self.watch.send(val) + } + + /// Clears the value of the `Watch`. + /// This will cause calls to [`Rcv::get`] to be pending. + pub fn clear(&self) { + self.watch.clear() + } + + /// Tries to retrieve the value of the `Watch`. + pub fn try_get(&self) -> Option { + self.watch.try_get(None) + } + + /// Tries to peek the current value of the `Watch` if it matches the predicate + /// function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(None, &mut f) + } + + /// Returns true if the `Watch` contains a value. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } + + /// Modify the value of the `Watch` using a closure. + pub fn send_modify(&self, mut f: F) + where + F: Fn(&mut Option), + { + self.watch.send_modify(&mut f) + } + + /// Modify the value of the `Watch` using a closure. The closure must return + /// `true` if the value was modified, which notifies all receivers. + pub fn send_if_modified(&self, mut f: F) + where + F: Fn(&mut Option) -> bool, + { + self.watch.send_if_modified(&mut f) + } +} + +/// A sender of a `Watch` channel. +/// +/// For a simpler type definition, consider [`DynSender`] at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct Sender<'a, M: RawMutex, T: Clone, const N: usize>(Snd<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Clone for Sender<'a, M, T, N> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Sender<'a, M, T, N> { + /// Converts the `Sender` into a [`DynSender`]. + pub fn as_dyn(self) -> DynSender<'a, T> { + DynSender(Snd::new(self.watch)) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Sender<'a, M, T, N> { + fn into(self) -> DynSender<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Sender<'a, M, T, N> { + type Target = Snd<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Sender<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A sender which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Sender`] with a simpler type definition, +pub struct DynSender<'a, T: Clone>(Snd<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Clone for DynSender<'a, T> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, T: Clone> Deref for DynSender<'a, T> { + type Target = Snd<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynSender<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Rcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Returns the current value of the `Watch` once it is initialized, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn get(&mut self) -> T { + poll_fn(|cx| self.watch.poll_get(&mut self.at_id, cx)).await + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Returns the value of the `Watch` if it matches the predicate function `f`, + /// or waits for it to match, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn get_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_get_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Waits for the `Watch` to change and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed(&mut self) -> T { + poll_fn(|cx| self.watch.poll_changed(&mut self.at_id, cx)).await + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Waits for the `Watch` to change to a value which satisfies the predicate + /// function `f` and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_changed_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Drop for Rcv<'a, T, W> { + fn drop(&mut self) { + self.watch.drop_receiver(); + } +} + +/// A anonymous receiver can NOT `.await` a change in the `Watch` value. +pub struct AnonRcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> AnonRcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + +/// A receiver of a `Watch` channel. +pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Receiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynReceiver<'a, T> { + let rcv = DynReceiver(Rcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Receiver<'a, M, T, N> { + fn into(self) -> DynReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { + type Target = Rcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Receiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynReceiver<'a, T: Clone>(Rcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynReceiver<'a, T> { + type Target = Rcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver of a `Watch` channel that cannot `.await` values. +pub struct AnonReceiver<'a, M: RawMutex, T: Clone, const N: usize>(AnonRcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> AnonReceiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynAnonReceiver<'a, T> { + let rcv = DynAnonReceiver(AnonRcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for AnonReceiver<'a, M, T, N> { + fn into(self) -> DynAnonReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for AnonReceiver<'a, M, T, N> { + type Target = AnonRcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for AnonReceiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver that cannot `.await` value, which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`AnonReceiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynAnonReceiver<'a, T: Clone>(AnonRcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynAnonReceiver<'a, T> { + type Target = AnonRcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynAnonReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(test)] +mod tests { + use futures_executor::block_on; + + use super::Watch; + use crate::blocking_mutex::raw::CriticalSectionRawMutex; + + #[test] + fn multiple_sends() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.changed().await, 10); + + // Receive another value + snd.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + + // No update + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn all_try_get() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(WATCH.try_get(), None); + assert_eq!(rcv.try_get(), None); + assert_eq!(snd.try_get(), None); + + // Receive the new value + snd.send(10); + assert_eq!(WATCH.try_get(), Some(10)); + assert_eq!(rcv.try_get(), Some(10)); + assert_eq!(snd.try_get(), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x > &5), Some(10)); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(10)); + assert_eq!(snd.try_get_and(|x| x > &5), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x < &5), None); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert_eq!(snd.try_get_and(|x| x < &5), None); + }; + block_on(f); + } + + #[test] + fn once_lock_like() { + let f = async { + static CONFIG0: u8 = 10; + static CONFIG1: u8 = 20; + + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(&CONFIG0); + let rcv0 = rcv.changed().await; + assert_eq!(rcv0, &10); + + // Receive another value + snd.send(&CONFIG1); + let rcv1 = rcv.try_changed(); + assert_eq!(rcv1, Some(&20)); + + // No update + assert_eq!(rcv.try_changed(), None); + + // Ensure similarity with original static + assert_eq!(rcv0, &CONFIG0); + assert_eq!(rcv1, Some(&CONFIG1)); + }; + block_on(f); + } + + #[test] + fn sender_modify() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Modify the value inplace + snd.send_modify(|opt| { + if let Some(inner) = opt { + *inner += 5; + } + }); + + // Get the modified value + assert_eq!(rcv.try_changed(), Some(15)); + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn predicate_fn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + snd.send(15); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(15)); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert!(rcv.try_changed().is_none()); + + snd.send(20); + assert_eq!(rcv.try_changed_and(|x| x > &5), Some(20)); + assert_eq!(rcv.try_changed_and(|x| x > &5), None); + + snd.send(25); + assert_eq!(rcv.try_changed_and(|x| x < &5), None); + assert_eq!(rcv.try_changed(), Some(25)); + + snd.send(30); + assert_eq!(rcv.changed_and(|x| x > &5).await, 30); + assert_eq!(rcv.get_and(|x| x > &5).await, 30); + }; + block_on(f); + } + + #[test] + fn receive_after_create() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain sender and send value + let snd = WATCH.sender(); + snd.send(10); + + // Obtain receiver and receive value + let mut rcv = WATCH.receiver().unwrap(); + assert_eq!(rcv.try_changed(), Some(10)); + }; + block_on(f); + } + + #[test] + fn max_receivers_drop() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Try to create 3 receivers (only 2 can exist at once) + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); + + // Drop the first receiver + drop(rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + assert!(rcv3.is_some()); + }; + block_on(f); + } + + #[test] + fn multiple_receivers() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receivers and sender + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.anon_receiver(); + let snd = WATCH.sender(); + + // No update for both + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + // Send a new value + snd.send(0); + + // Both receivers receive the new value + assert_eq!(rcv0.try_changed(), Some(0)); + assert_eq!(rcv1.try_changed(), Some(0)); + }; + block_on(f); + } + + #[test] + fn clone_senders() { + let f = async { + // Obtain different ways to send + static WATCH: Watch = Watch::new(); + let snd0 = WATCH.sender(); + let snd1 = snd0.clone(); + + // Obtain Receiver + let mut rcv = WATCH.receiver().unwrap().as_dyn(); + + // Send a value from first sender + snd0.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Send a value from second sender + snd1.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + }; + block_on(f); + } + + #[test] + fn use_dynamics() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut anon_rcv = WATCH.dyn_anon_receiver(); + let mut dyn_rcv = WATCH.dyn_receiver().unwrap(); + let dyn_snd = WATCH.dyn_sender(); + + // Send a value + dyn_snd.send(10); + + // Ensure the dynamic receiver receives the value + assert_eq!(anon_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn convert_to_dyn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let anon_rcv = WATCH.anon_receiver(); + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Convert to dynamic + let mut dyn_anon_rcv = anon_rcv.as_dyn(); + let mut dyn_rcv = rcv.as_dyn(); + let dyn_snd = snd.as_dyn(); + + // Send a value + dyn_snd.send(10); + + // Ensure the dynamic receiver receives the value + assert_eq!(dyn_anon_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn dynamic_receiver_count() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); + + // Convert to dynamic + let dyn_rcv0 = rcv0.unwrap().as_dyn(); + + // Drop the (now dynamic) receiver + drop(dyn_rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + let rcv4 = WATCH.receiver(); + assert!(rcv3.is_some()); + assert!(rcv4.is_none()); + }; + block_on(f); + } + + #[test] + fn contains_value() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), false); + assert_eq!(snd.contains_value(), false); + + // Send a value + snd.send(10); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), true); + assert_eq!(snd.contains_value(), true); + }; + block_on(f); + } +} diff --git a/embassy/embassy-sync/src/zerocopy_channel.rs b/embassy/embassy-sync/src/zerocopy_channel.rs new file mode 100644 index 0000000..fabb69b --- /dev/null +++ b/embassy/embassy-sync/src/zerocopy_channel.rs @@ -0,0 +1,342 @@ +//! A zero-copy queue for sending values between asynchronous tasks. +//! +//! It can be used concurrently by a producer (sender) and a +//! consumer (receiver), i.e. it is an "SPSC channel". +//! +//! This queue takes a Mutex type so that various +//! targets can be attained. For example, a ThreadModeMutex can be used +//! for single-core Cortex-M targets where messages are only passed +//! between tasks running in thread mode. Similarly, a CriticalSectionMutex +//! can also be used for single-core targets where messages are to be +//! passed from exception mode e.g. out of an interrupt handler. +//! +//! This module provides a bounded channel that has a limit on the number of +//! messages that it can store, and if this limit is reached, trying to send +//! another message will result in an error being returned. + +use core::cell::RefCell; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::{Context, Poll}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::WakerRegistration; + +/// A bounded zero-copy channel for communicating between asynchronous tasks +/// with backpressure. +/// +/// The channel will buffer up to the provided number of messages. Once the +/// buffer is full, attempts to `send` new messages will wait until a message is +/// received from the channel. +/// +/// All data sent will become available in the same order as it was sent. +/// +/// The channel requires a buffer of recyclable elements. Writing to the channel is done through +/// an `&mut T`. +pub struct Channel<'a, M: RawMutex, T> { + buf: *mut T, + phantom: PhantomData<&'a mut T>, + state: Mutex>, +} + +impl<'a, M: RawMutex, T> Channel<'a, M, T> { + /// Initialize a new [`Channel`]. + /// + /// The provided buffer will be used and reused by the channel's logic, and thus dictates the + /// channel's capacity. + 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 { + capacity: len, + front: 0, + back: 0, + full: false, + send_waker: WakerRegistration::new(), + receive_waker: WakerRegistration::new(), + })), + } + } + + /// Creates a [`Sender`] and [`Receiver`] from an existing channel. + /// + /// Further Senders and Receivers can be created through [`Sender::borrow`] and + /// [`Receiver::borrow`] respectively. + pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { + (Sender { channel: self }, Receiver { channel: self }) + } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.state.lock(|s| s.borrow().is_full()) + } +} + +/// Send-only access to a [`Channel`]. +pub struct Sender<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, +} + +impl<'a, M: RawMutex, T> Sender<'a, M, T> { + /// Creates one further [`Sender`] over the same channel. + pub fn borrow(&mut self) -> Sender<'_, M, T> { + Sender { channel: self.channel } + } + + /// Attempts to send a value over the 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, + } + }) + } + + /// Attempts to send a value over the channel. + 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.receive_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + /// Asynchronously send a value over the channel. + 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.receive_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + .await; + unsafe { &mut *self.channel.buf.add(i) } + } + + /// Notify the channel that the sending of the value has been finalized. + pub fn send_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().push_done()) + } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } +} + +/// Receive-only access to a [`Channel`]. +pub struct Receiver<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, +} + +impl<'a, M: RawMutex, T> Receiver<'a, M, T> { + /// Creates one further [`Sender`] over the same channel. + pub fn borrow(&mut self) -> Receiver<'_, M, T> { + Receiver { channel: self.channel } + } + + /// Attempts to receive a value over the channel. + pub fn try_receive(&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, + } + }) + } + + /// Attempts to asynchronously receive a value over the channel. + pub fn poll_receive(&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 + } + } + }) + } + + /// Asynchronously receive a value over the channel. + pub async fn receive(&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) } + } + + /// Notify the channel that the receiving of the value has been finalized. + pub fn receive_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().pop_done()) + } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } +} + +struct State { + /// Maximum number of elements the channel can hold. + capacity: 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, + receive_waker: WakerRegistration, +} + +impl State { + fn increment(&self, i: usize) -> usize { + if i + 1 == self.capacity { + 0 + } else { + i + 1 + } + } + + fn clear(&mut self) { + self.front = 0; + self.back = 0; + self.full = false; + } + + fn len(&self) -> usize { + if !self.full { + if self.back >= self.front { + self.back - self.front + } else { + self.capacity + self.back - self.front + } + } else { + self.capacity + } + } + + fn is_full(&self) -> bool { + self.full + } + + 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.receive_waker.wake(); + } +} diff --git a/embassy/embassy-time-driver/CHANGELOG.md b/embassy/embassy-time-driver/CHANGELOG.md new file mode 100644 index 0000000..2af1dc7 --- /dev/null +++ b/embassy/embassy-time-driver/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog for embassy-time-driver + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +- The `allocate_alarm`, `set_alarm_callback`, `set_alarm` functions have been removed. +- `schedule_wake` has been added to the `Driver` trait. + +## 0.1.0 - 2024-01-11 + +Initial release diff --git a/embassy/embassy-time-driver/Cargo.toml b/embassy/embassy-time-driver/Cargo.toml new file mode 100644 index 0000000..d9f2e97 --- /dev/null +++ b/embassy/embassy-time-driver/Cargo.toml @@ -0,0 +1,370 @@ +[package] +name = "embassy-time-driver" +version = "0.1.0" +edition = "2021" +description = "Driver trait for embassy-time" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-time-driver" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] + +# Prevent multiple copies of this crate in the same binary. +# Needed because different copies might get different tick rates, causing +# wrong delays if the time driver is using one copy and user code is using another. +# This is especially common when mixing crates from crates.io and git. +links = "embassy-time" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-driver-v$VERSION/embassy-time-driver/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-driver/src/" +target = "x86_64-unknown-linux-gnu" + +[features] +#! ### Tick Rate +#! +#! At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used. +#! +#! If the time driver in use supports using arbitrary tick rates, you can enable one `tick-*` +#! feature from your binary crate to set the tick rate. The driver will use configured tick rate. +#! If the time driver supports a fixed tick rate, it will enable one feature itself, so you should +#! not enable one. Check the time driver documentation for details. +#! +#! When using embassy-time from libraries, you should *not* enable any `tick-*` feature, to allow the +#! end user or the driver to pick. +#!

+#! Available tick rates: +#! +#! + +# BEGIN TICKS +# Generated by gen_tick.py. DO NOT EDIT. +## 1Hz Tick Rate +tick-hz-1 = [] +## 2Hz Tick Rate +tick-hz-2 = [] +## 4Hz Tick Rate +tick-hz-4 = [] +## 8Hz Tick Rate +tick-hz-8 = [] +## 10Hz Tick Rate +tick-hz-10 = [] +## 16Hz Tick Rate +tick-hz-16 = [] +## 32Hz Tick Rate +tick-hz-32 = [] +## 64Hz Tick Rate +tick-hz-64 = [] +## 100Hz Tick Rate +tick-hz-100 = [] +## 128Hz Tick Rate +tick-hz-128 = [] +## 256Hz Tick Rate +tick-hz-256 = [] +## 512Hz Tick Rate +tick-hz-512 = [] +## 1.0kHz Tick Rate +tick-hz-1_000 = [] +## 1.024kHz Tick Rate +tick-hz-1_024 = [] +## 2.0kHz Tick Rate +tick-hz-2_000 = [] +## 2.048kHz Tick Rate +tick-hz-2_048 = [] +## 4.0kHz Tick Rate +tick-hz-4_000 = [] +## 4.096kHz Tick Rate +tick-hz-4_096 = [] +## 8.0kHz Tick Rate +tick-hz-8_000 = [] +## 8.192kHz Tick Rate +tick-hz-8_192 = [] +## 10.0kHz Tick Rate +tick-hz-10_000 = [] +## 16.0kHz Tick Rate +tick-hz-16_000 = [] +## 16.384kHz Tick Rate +tick-hz-16_384 = [] +## 20.0kHz Tick Rate +tick-hz-20_000 = [] +## 32.0kHz Tick Rate +tick-hz-32_000 = [] +## 32.768kHz Tick Rate +tick-hz-32_768 = [] +## 40.0kHz Tick Rate +tick-hz-40_000 = [] +## 64.0kHz Tick Rate +tick-hz-64_000 = [] +## 65.536kHz Tick Rate +tick-hz-65_536 = [] +## 80.0kHz Tick Rate +tick-hz-80_000 = [] +## 100.0kHz Tick Rate +tick-hz-100_000 = [] +## 128.0kHz Tick Rate +tick-hz-128_000 = [] +## 131.072kHz Tick Rate +tick-hz-131_072 = [] +## 160.0kHz Tick Rate +tick-hz-160_000 = [] +## 256.0kHz Tick Rate +tick-hz-256_000 = [] +## 262.144kHz Tick Rate +tick-hz-262_144 = [] +## 320.0kHz Tick Rate +tick-hz-320_000 = [] +## 512.0kHz Tick Rate +tick-hz-512_000 = [] +## 524.288kHz Tick Rate +tick-hz-524_288 = [] +## 640.0kHz Tick Rate +tick-hz-640_000 = [] +## 1.0MHz Tick Rate +tick-hz-1_000_000 = [] +## 1.024MHz Tick Rate +tick-hz-1_024_000 = [] +## 1.048576MHz Tick Rate +tick-hz-1_048_576 = [] +## 1.28MHz Tick Rate +tick-hz-1_280_000 = [] +## 2.0MHz Tick Rate +tick-hz-2_000_000 = [] +## 2.048MHz Tick Rate +tick-hz-2_048_000 = [] +## 2.097152MHz Tick Rate +tick-hz-2_097_152 = [] +## 2.56MHz Tick Rate +tick-hz-2_560_000 = [] +## 3.0MHz Tick Rate +tick-hz-3_000_000 = [] +## 4.0MHz Tick Rate +tick-hz-4_000_000 = [] +## 4.096MHz Tick Rate +tick-hz-4_096_000 = [] +## 4.194304MHz Tick Rate +tick-hz-4_194_304 = [] +## 5.12MHz Tick Rate +tick-hz-5_120_000 = [] +## 6.0MHz Tick Rate +tick-hz-6_000_000 = [] +## 8.0MHz Tick Rate +tick-hz-8_000_000 = [] +## 8.192MHz Tick Rate +tick-hz-8_192_000 = [] +## 8.388608MHz Tick Rate +tick-hz-8_388_608 = [] +## 9.0MHz Tick Rate +tick-hz-9_000_000 = [] +## 10.0MHz Tick Rate +tick-hz-10_000_000 = [] +## 10.24MHz Tick Rate +tick-hz-10_240_000 = [] +## 12.0MHz Tick Rate +tick-hz-12_000_000 = [] +## 16.0MHz Tick Rate +tick-hz-16_000_000 = [] +## 16.384MHz Tick Rate +tick-hz-16_384_000 = [] +## 16.777216MHz Tick Rate +tick-hz-16_777_216 = [] +## 18.0MHz Tick Rate +tick-hz-18_000_000 = [] +## 20.0MHz Tick Rate +tick-hz-20_000_000 = [] +## 20.48MHz Tick Rate +tick-hz-20_480_000 = [] +## 24.0MHz Tick Rate +tick-hz-24_000_000 = [] +## 30.0MHz Tick Rate +tick-hz-30_000_000 = [] +## 32.0MHz Tick Rate +tick-hz-32_000_000 = [] +## 32.768MHz Tick Rate +tick-hz-32_768_000 = [] +## 36.0MHz Tick Rate +tick-hz-36_000_000 = [] +## 40.0MHz Tick Rate +tick-hz-40_000_000 = [] +## 40.96MHz Tick Rate +tick-hz-40_960_000 = [] +## 48.0MHz Tick Rate +tick-hz-48_000_000 = [] +## 50.0MHz Tick Rate +tick-hz-50_000_000 = [] +## 60.0MHz Tick Rate +tick-hz-60_000_000 = [] +## 64.0MHz Tick Rate +tick-hz-64_000_000 = [] +## 65.536MHz Tick Rate +tick-hz-65_536_000 = [] +## 70.0MHz Tick Rate +tick-hz-70_000_000 = [] +## 72.0MHz Tick Rate +tick-hz-72_000_000 = [] +## 80.0MHz Tick Rate +tick-hz-80_000_000 = [] +## 81.92MHz Tick Rate +tick-hz-81_920_000 = [] +## 90.0MHz Tick Rate +tick-hz-90_000_000 = [] +## 96.0MHz Tick Rate +tick-hz-96_000_000 = [] +## 100.0MHz Tick Rate +tick-hz-100_000_000 = [] +## 110.0MHz Tick Rate +tick-hz-110_000_000 = [] +## 120.0MHz Tick Rate +tick-hz-120_000_000 = [] +## 128.0MHz Tick Rate +tick-hz-128_000_000 = [] +## 130.0MHz Tick Rate +tick-hz-130_000_000 = [] +## 131.072MHz Tick Rate +tick-hz-131_072_000 = [] +## 140.0MHz Tick Rate +tick-hz-140_000_000 = [] +## 144.0MHz Tick Rate +tick-hz-144_000_000 = [] +## 150.0MHz Tick Rate +tick-hz-150_000_000 = [] +## 160.0MHz Tick Rate +tick-hz-160_000_000 = [] +## 163.84MHz Tick Rate +tick-hz-163_840_000 = [] +## 170.0MHz Tick Rate +tick-hz-170_000_000 = [] +## 180.0MHz Tick Rate +tick-hz-180_000_000 = [] +## 190.0MHz Tick Rate +tick-hz-190_000_000 = [] +## 192.0MHz Tick Rate +tick-hz-192_000_000 = [] +## 200.0MHz Tick Rate +tick-hz-200_000_000 = [] +## 210.0MHz Tick Rate +tick-hz-210_000_000 = [] +## 220.0MHz Tick Rate +tick-hz-220_000_000 = [] +## 230.0MHz Tick Rate +tick-hz-230_000_000 = [] +## 240.0MHz Tick Rate +tick-hz-240_000_000 = [] +## 250.0MHz Tick Rate +tick-hz-250_000_000 = [] +## 256.0MHz Tick Rate +tick-hz-256_000_000 = [] +## 260.0MHz Tick Rate +tick-hz-260_000_000 = [] +## 262.144MHz Tick Rate +tick-hz-262_144_000 = [] +## 270.0MHz Tick Rate +tick-hz-270_000_000 = [] +## 280.0MHz Tick Rate +tick-hz-280_000_000 = [] +## 288.0MHz Tick Rate +tick-hz-288_000_000 = [] +## 290.0MHz Tick Rate +tick-hz-290_000_000 = [] +## 300.0MHz Tick Rate +tick-hz-300_000_000 = [] +## 320.0MHz Tick Rate +tick-hz-320_000_000 = [] +## 327.68MHz Tick Rate +tick-hz-327_680_000 = [] +## 340.0MHz Tick Rate +tick-hz-340_000_000 = [] +## 360.0MHz Tick Rate +tick-hz-360_000_000 = [] +## 380.0MHz Tick Rate +tick-hz-380_000_000 = [] +## 384.0MHz Tick Rate +tick-hz-384_000_000 = [] +## 400.0MHz Tick Rate +tick-hz-400_000_000 = [] +## 420.0MHz Tick Rate +tick-hz-420_000_000 = [] +## 440.0MHz Tick Rate +tick-hz-440_000_000 = [] +## 460.0MHz Tick Rate +tick-hz-460_000_000 = [] +## 480.0MHz Tick Rate +tick-hz-480_000_000 = [] +## 500.0MHz Tick Rate +tick-hz-500_000_000 = [] +## 512.0MHz Tick Rate +tick-hz-512_000_000 = [] +## 520.0MHz Tick Rate +tick-hz-520_000_000 = [] +## 524.288MHz Tick Rate +tick-hz-524_288_000 = [] +## 540.0MHz Tick Rate +tick-hz-540_000_000 = [] +## 560.0MHz Tick Rate +tick-hz-560_000_000 = [] +## 576.0MHz Tick Rate +tick-hz-576_000_000 = [] +## 580.0MHz Tick Rate +tick-hz-580_000_000 = [] +## 600.0MHz Tick Rate +tick-hz-600_000_000 = [] +## 620.0MHz Tick Rate +tick-hz-620_000_000 = [] +## 640.0MHz Tick Rate +tick-hz-640_000_000 = [] +## 655.36MHz Tick Rate +tick-hz-655_360_000 = [] +## 660.0MHz Tick Rate +tick-hz-660_000_000 = [] +## 680.0MHz Tick Rate +tick-hz-680_000_000 = [] +## 700.0MHz Tick Rate +tick-hz-700_000_000 = [] +## 720.0MHz Tick Rate +tick-hz-720_000_000 = [] +## 740.0MHz Tick Rate +tick-hz-740_000_000 = [] +## 760.0MHz Tick Rate +tick-hz-760_000_000 = [] +## 768.0MHz Tick Rate +tick-hz-768_000_000 = [] +## 780.0MHz Tick Rate +tick-hz-780_000_000 = [] +## 800.0MHz Tick Rate +tick-hz-800_000_000 = [] +## 820.0MHz Tick Rate +tick-hz-820_000_000 = [] +## 840.0MHz Tick Rate +tick-hz-840_000_000 = [] +## 860.0MHz Tick Rate +tick-hz-860_000_000 = [] +## 880.0MHz Tick Rate +tick-hz-880_000_000 = [] +## 900.0MHz Tick Rate +tick-hz-900_000_000 = [] +## 920.0MHz Tick Rate +tick-hz-920_000_000 = [] +## 940.0MHz Tick Rate +tick-hz-940_000_000 = [] +## 960.0MHz Tick Rate +tick-hz-960_000_000 = [] +## 980.0MHz Tick Rate +tick-hz-980_000_000 = [] +## 1.0GHz Tick Rate +tick-hz-1_000_000_000 = [] +## 1.31072GHz Tick Rate +tick-hz-1_310_720_000 = [] +## 2.62144GHz Tick Rate +tick-hz-2_621_440_000 = [] +## 5.24288GHz Tick Rate +tick-hz-5_242_880_000 = [] +# END TICKS + +#!
+ +[dependencies] +document-features = "0.2.7" diff --git a/embassy/embassy-time-driver/README.md b/embassy/embassy-time-driver/README.md new file mode 100644 index 0000000..426252d --- /dev/null +++ b/embassy/embassy-time-driver/README.md @@ -0,0 +1,19 @@ +# embassy-time-driver + +This crate contains the driver trait necessary for adding [`embassy-time`](https://crates.io/crates/embassy-time) support +for a new hardware platform. + +If you want to *use* `embassy-time` with already made drivers, you should depend on the main `embassy-time` crate, not on this crate. + +If you are writing a driver, you should depend only on this crate, not on the main `embassy-time` crate. +This will allow your driver to continue working for newer `embassy-time` major versions, without needing an update, +if the driver trait has not had breaking changes. + +## How it works + +`embassy-time` is backed by a global "time driver" specified at build time. +Only one driver can be active in a program. + +All methods and structs transparently call into the active driver. This makes it +possible for libraries to use `embassy-time` in a driver-agnostic way without +requiring generic parameters. diff --git a/embassy/embassy-time-driver/build.rs b/embassy/embassy-time-driver/build.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/embassy/embassy-time-driver/build.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/embassy/embassy-time-driver/gen_tick.py b/embassy/embassy-time-driver/gen_tick.py new file mode 100644 index 0000000..af194c3 --- /dev/null +++ b/embassy/embassy-time-driver/gen_tick.py @@ -0,0 +1,81 @@ +import os +from glob import glob + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +ticks = [] +for i in range(10): + ticks.append(10**i) +for i in range(1, 25): + ticks.append(2**i) +for i in range(1, 20): + ticks.append(2**i * 1000) +for i in range(1, 20): + ticks.append(2**i * 10000) +for i in range(1, 10): + ticks.append(2**i * 1000000) + ticks.append(2**i * 9 // 8 * 1000000) + ticks.append(2**i * 3 // 2 * 1000000) +for i in range(1, 30): + ticks.append(10 * i * 1_000_000) +for i in range(15, 50): + ticks.append(20 * i * 1_000_000) + +seen = set() +ticks = sorted([x for x in ticks if not (x in seen or seen.add(x))]) + +# ========= Update Cargo.toml + +SEPARATOR_START = '# BEGIN TICKS\n' +SEPARATOR_END = '# END TICKS\n' +HELP = '# Generated by gen_tick.py. DO NOT EDIT.\n' + +feats_time = '' +feats_driver = '' +for freq in ticks: + feature = f'tick-hz-{freq:_}' + if freq >= 1_000_000_000: + freq_human = f"{freq / 1_000_000_000}GHz" + elif freq >= 1_000_000: + freq_human = f"{freq / 1_000_000}MHz" + elif freq >= 1_000: + freq_human = f"{freq / 1000}kHz" + else: + freq_human = f"{freq}Hz" + + feats_time += f"## {freq_human} Tick Rate\n" + feats_time += f"{feature} = [\"embassy-time-driver/{feature}\"]\n" + feats_driver += f"## {freq_human} Tick Rate\n" + feats_driver += f"{feature} = []\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) +with open('Cargo.toml', 'w') as f: + f.write(before + SEPARATOR_START + HELP + feats_driver + SEPARATOR_END + after) + +with open('../embassy-time/Cargo.toml', 'r') as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +with open('../embassy-time/Cargo.toml', 'w') as f: + f.write(before + SEPARATOR_START + HELP + feats_time + SEPARATOR_END + after) + +# ========= Update src/tick.rs + +with open('src/tick.rs', 'w') as f: + + f.write('// Generated by gen_tick.py. DO NOT EDIT.\n\n') + for hz in ticks: + f.write( + f'#[cfg(feature = "tick-hz-{hz:_}")] pub const TICK_HZ: u64 = {hz:_};\n') + f.write('#[cfg(not(any(\n') + for hz in ticks: + f.write(f'feature = "tick-hz-{hz:_}",\n') + f.write(')))] pub const TICK_HZ: u64 = 1_000_000;') + + +os.system('rustfmt src/tick.rs') diff --git a/embassy/embassy-time-driver/src/lib.rs b/embassy/embassy-time-driver/src/lib.rs new file mode 100644 index 0000000..9f2795a --- /dev/null +++ b/embassy/embassy-time-driver/src/lib.rs @@ -0,0 +1,168 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Implementing a driver +//! +//! - Define a struct `MyDriver` +//! - Implement [`Driver`] for it +//! - Register it as the global driver with [`time_driver_impl`](crate::time_driver_impl). +//! +//! If your driver has a single set tick rate, enable the corresponding [`tick-hz-*`](crate#tick-rate) feature, +//! which will prevent users from needing to configure it themselves (or selecting an incorrect configuration). +//! +//! If your driver supports a small number of set tick rates, expose your own cargo features and have each one +//! enable the corresponding `embassy-time-driver/tick-*`. +//! +//! Otherwise, don’t enable any `tick-hz-*` feature to let the user configure the tick rate themselves by +//! enabling a feature on `embassy-time`. +//! +//! ### Example +//! +//! ``` +//! use core::task::Waker; +//! +//! use embassy_time_driver::Driver; +//! +//! struct MyDriver{} // not public! +//! +//! impl Driver for MyDriver { +//! fn now(&self) -> u64 { +//! todo!() +//! } +//! +//! fn schedule_wake(&self, at: u64, waker: &Waker) { +//! todo!() +//! } +//! } +//! +//! embassy_time_driver::time_driver_impl!(static DRIVER: MyDriver = MyDriver{}); +//! ``` +//! +//! ## Implementing the timer queue +//! +//! The simplest (but suboptimal) way to implement a timer queue is to define a single queue in the +//! time driver. Declare a field protected by an appropriate mutex (e.g. `critical_section::Mutex`). +//! +//! Then, you'll need to adapt the `schedule_wake` method to use this queue. +//! +//! Note that if you are using multiple queues, you will need to ensure that a single timer +//! queue item is only ever enqueued into a single queue at a time. +//! +//! ```ignore +//! use core::cell::RefCell; +//! use core::task::Waker; +//! +//! use embassy_time_queue_driver::Queue; +//! use embassy_time_driver::Driver; +//! +//! struct MyDriver { +//! timer_queue: critical_section::Mutex>, +//! } +//! +//! impl MyDriver { +//! fn set_alarm(&self, cs: &CriticalSection, at: u64) -> bool { +//! todo!() +//! } +//! } +//! +//! impl Driver for MyDriver { +//! // fn now(&self) -> u64 { ... } +//! +//! fn schedule_wake(&self, at: u64, waker: &Waker) { +//! critical_section::with(|cs| { +//! let mut queue = self.queue.borrow(cs).borrow_mut(); +//! if queue.schedule_wake(at, waker) { +//! let mut next = queue.next_expiration(self.now()); +//! while !self.set_alarm(cs, next) { +//! next = queue.next_expiration(self.now()); +//! } +//! } +//! }); +//! } +//! } +//! ``` +//! +//! # Linkage details +//! +//! Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via `extern` functions. +//! +//! `embassy` internally defines the driver function as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls it. +//! The driver crate defines the function as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the +//! calls from the `embassy` crate to call into the driver crate. +//! +//! If there is none or multiple drivers in the crate tree, linking will fail. +//! +//! This method has a few key advantages for something as foundational as timekeeping: +//! +//! - The time driver is available everywhere easily, without having to thread the implementation +//! through generic parameters. This is especially helpful for libraries. +//! - It means comparing `Instant`s will always make sense: if there were multiple drivers +//! active, one could compare an `Instant` from driver A to an `Instant` from driver B, which +//! would yield incorrect results. + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +use core::task::Waker; + +mod tick; + +/// Ticks per second of the global timebase. +/// +/// This value is specified by the [`tick-*` Cargo features](crate#tick-rate) +pub const TICK_HZ: u64 = tick::TICK_HZ; + +/// Time driver +pub trait Driver: Send + Sync + 'static { + /// Return the current timestamp in ticks. + /// + /// Implementations MUST ensure that: + /// - This is guaranteed to be monotonic, i.e. a call to now() will always return + /// a greater or equal value than earlier calls. Time can't "roll backwards". + /// - It "never" overflows. It must not overflow in a sufficiently long time frame, say + /// in 10_000 years (Human civilization is likely to already have self-destructed + /// 10_000 years from now.). This means if your hardware only has 16bit/32bit timers + /// you MUST extend them to 64-bit, for example by counting overflows in software, + /// or chaining multiple timers together. + fn now(&self) -> u64; + + /// Schedules a waker to be awoken at moment `at`. + /// If this moment is in the past, the waker might be awoken immediately. + fn schedule_wake(&self, at: u64, waker: &Waker); +} + +extern "Rust" { + fn _embassy_time_now() -> u64; + fn _embassy_time_schedule_wake(at: u64, waker: &Waker); +} + +/// See [`Driver::now`] +pub fn now() -> u64 { + unsafe { _embassy_time_now() } +} + +/// Schedule the given waker to be woken at `at`. +pub fn schedule_wake(at: u64, waker: &Waker) { + unsafe { _embassy_time_schedule_wake(at, waker) } +} + +/// Set the time Driver implementation. +/// +/// See the module documentation for an example. +#[macro_export] +macro_rules! time_driver_impl { + (static $name:ident: $t: ty = $val:expr) => { + static $name: $t = $val; + + #[no_mangle] + fn _embassy_time_now() -> u64 { + <$t as $crate::Driver>::now(&$name) + } + + #[no_mangle] + fn _embassy_time_schedule_wake(at: u64, waker: &core::task::Waker) { + <$t as $crate::Driver>::schedule_wake(&$name, at, waker); + } + }; +} diff --git a/embassy/embassy-time-driver/src/tick.rs b/embassy/embassy-time-driver/src/tick.rs new file mode 100644 index 0000000..916ae94 --- /dev/null +++ b/embassy/embassy-time-driver/src/tick.rs @@ -0,0 +1,482 @@ +// Generated by gen_tick.py. DO NOT EDIT. + +#[cfg(feature = "tick-hz-1")] +pub const TICK_HZ: u64 = 1; +#[cfg(feature = "tick-hz-2")] +pub const TICK_HZ: u64 = 2; +#[cfg(feature = "tick-hz-4")] +pub const TICK_HZ: u64 = 4; +#[cfg(feature = "tick-hz-8")] +pub const TICK_HZ: u64 = 8; +#[cfg(feature = "tick-hz-10")] +pub const TICK_HZ: u64 = 10; +#[cfg(feature = "tick-hz-16")] +pub const TICK_HZ: u64 = 16; +#[cfg(feature = "tick-hz-32")] +pub const TICK_HZ: u64 = 32; +#[cfg(feature = "tick-hz-64")] +pub const TICK_HZ: u64 = 64; +#[cfg(feature = "tick-hz-100")] +pub const TICK_HZ: u64 = 100; +#[cfg(feature = "tick-hz-128")] +pub const TICK_HZ: u64 = 128; +#[cfg(feature = "tick-hz-256")] +pub const TICK_HZ: u64 = 256; +#[cfg(feature = "tick-hz-512")] +pub const TICK_HZ: u64 = 512; +#[cfg(feature = "tick-hz-1_000")] +pub const TICK_HZ: u64 = 1_000; +#[cfg(feature = "tick-hz-1_024")] +pub const TICK_HZ: u64 = 1_024; +#[cfg(feature = "tick-hz-2_000")] +pub const TICK_HZ: u64 = 2_000; +#[cfg(feature = "tick-hz-2_048")] +pub const TICK_HZ: u64 = 2_048; +#[cfg(feature = "tick-hz-4_000")] +pub const TICK_HZ: u64 = 4_000; +#[cfg(feature = "tick-hz-4_096")] +pub const TICK_HZ: u64 = 4_096; +#[cfg(feature = "tick-hz-8_000")] +pub const TICK_HZ: u64 = 8_000; +#[cfg(feature = "tick-hz-8_192")] +pub const TICK_HZ: u64 = 8_192; +#[cfg(feature = "tick-hz-10_000")] +pub const TICK_HZ: u64 = 10_000; +#[cfg(feature = "tick-hz-16_000")] +pub const TICK_HZ: u64 = 16_000; +#[cfg(feature = "tick-hz-16_384")] +pub const TICK_HZ: u64 = 16_384; +#[cfg(feature = "tick-hz-20_000")] +pub const TICK_HZ: u64 = 20_000; +#[cfg(feature = "tick-hz-32_000")] +pub const TICK_HZ: u64 = 32_000; +#[cfg(feature = "tick-hz-32_768")] +pub const TICK_HZ: u64 = 32_768; +#[cfg(feature = "tick-hz-40_000")] +pub const TICK_HZ: u64 = 40_000; +#[cfg(feature = "tick-hz-64_000")] +pub const TICK_HZ: u64 = 64_000; +#[cfg(feature = "tick-hz-65_536")] +pub const TICK_HZ: u64 = 65_536; +#[cfg(feature = "tick-hz-80_000")] +pub const TICK_HZ: u64 = 80_000; +#[cfg(feature = "tick-hz-100_000")] +pub const TICK_HZ: u64 = 100_000; +#[cfg(feature = "tick-hz-128_000")] +pub const TICK_HZ: u64 = 128_000; +#[cfg(feature = "tick-hz-131_072")] +pub const TICK_HZ: u64 = 131_072; +#[cfg(feature = "tick-hz-160_000")] +pub const TICK_HZ: u64 = 160_000; +#[cfg(feature = "tick-hz-256_000")] +pub const TICK_HZ: u64 = 256_000; +#[cfg(feature = "tick-hz-262_144")] +pub const TICK_HZ: u64 = 262_144; +#[cfg(feature = "tick-hz-320_000")] +pub const TICK_HZ: u64 = 320_000; +#[cfg(feature = "tick-hz-512_000")] +pub const TICK_HZ: u64 = 512_000; +#[cfg(feature = "tick-hz-524_288")] +pub const TICK_HZ: u64 = 524_288; +#[cfg(feature = "tick-hz-640_000")] +pub const TICK_HZ: u64 = 640_000; +#[cfg(feature = "tick-hz-1_000_000")] +pub const TICK_HZ: u64 = 1_000_000; +#[cfg(feature = "tick-hz-1_024_000")] +pub const TICK_HZ: u64 = 1_024_000; +#[cfg(feature = "tick-hz-1_048_576")] +pub const TICK_HZ: u64 = 1_048_576; +#[cfg(feature = "tick-hz-1_280_000")] +pub const TICK_HZ: u64 = 1_280_000; +#[cfg(feature = "tick-hz-2_000_000")] +pub const TICK_HZ: u64 = 2_000_000; +#[cfg(feature = "tick-hz-2_048_000")] +pub const TICK_HZ: u64 = 2_048_000; +#[cfg(feature = "tick-hz-2_097_152")] +pub const TICK_HZ: u64 = 2_097_152; +#[cfg(feature = "tick-hz-2_560_000")] +pub const TICK_HZ: u64 = 2_560_000; +#[cfg(feature = "tick-hz-3_000_000")] +pub const TICK_HZ: u64 = 3_000_000; +#[cfg(feature = "tick-hz-4_000_000")] +pub const TICK_HZ: u64 = 4_000_000; +#[cfg(feature = "tick-hz-4_096_000")] +pub const TICK_HZ: u64 = 4_096_000; +#[cfg(feature = "tick-hz-4_194_304")] +pub const TICK_HZ: u64 = 4_194_304; +#[cfg(feature = "tick-hz-5_120_000")] +pub const TICK_HZ: u64 = 5_120_000; +#[cfg(feature = "tick-hz-6_000_000")] +pub const TICK_HZ: u64 = 6_000_000; +#[cfg(feature = "tick-hz-8_000_000")] +pub const TICK_HZ: u64 = 8_000_000; +#[cfg(feature = "tick-hz-8_192_000")] +pub const TICK_HZ: u64 = 8_192_000; +#[cfg(feature = "tick-hz-8_388_608")] +pub const TICK_HZ: u64 = 8_388_608; +#[cfg(feature = "tick-hz-9_000_000")] +pub const TICK_HZ: u64 = 9_000_000; +#[cfg(feature = "tick-hz-10_000_000")] +pub const TICK_HZ: u64 = 10_000_000; +#[cfg(feature = "tick-hz-10_240_000")] +pub const TICK_HZ: u64 = 10_240_000; +#[cfg(feature = "tick-hz-12_000_000")] +pub const TICK_HZ: u64 = 12_000_000; +#[cfg(feature = "tick-hz-16_000_000")] +pub const TICK_HZ: u64 = 16_000_000; +#[cfg(feature = "tick-hz-16_384_000")] +pub const TICK_HZ: u64 = 16_384_000; +#[cfg(feature = "tick-hz-16_777_216")] +pub const TICK_HZ: u64 = 16_777_216; +#[cfg(feature = "tick-hz-18_000_000")] +pub const TICK_HZ: u64 = 18_000_000; +#[cfg(feature = "tick-hz-20_000_000")] +pub const TICK_HZ: u64 = 20_000_000; +#[cfg(feature = "tick-hz-20_480_000")] +pub const TICK_HZ: u64 = 20_480_000; +#[cfg(feature = "tick-hz-24_000_000")] +pub const TICK_HZ: u64 = 24_000_000; +#[cfg(feature = "tick-hz-30_000_000")] +pub const TICK_HZ: u64 = 30_000_000; +#[cfg(feature = "tick-hz-32_000_000")] +pub const TICK_HZ: u64 = 32_000_000; +#[cfg(feature = "tick-hz-32_768_000")] +pub const TICK_HZ: u64 = 32_768_000; +#[cfg(feature = "tick-hz-36_000_000")] +pub const TICK_HZ: u64 = 36_000_000; +#[cfg(feature = "tick-hz-40_000_000")] +pub const TICK_HZ: u64 = 40_000_000; +#[cfg(feature = "tick-hz-40_960_000")] +pub const TICK_HZ: u64 = 40_960_000; +#[cfg(feature = "tick-hz-48_000_000")] +pub const TICK_HZ: u64 = 48_000_000; +#[cfg(feature = "tick-hz-50_000_000")] +pub const TICK_HZ: u64 = 50_000_000; +#[cfg(feature = "tick-hz-60_000_000")] +pub const TICK_HZ: u64 = 60_000_000; +#[cfg(feature = "tick-hz-64_000_000")] +pub const TICK_HZ: u64 = 64_000_000; +#[cfg(feature = "tick-hz-65_536_000")] +pub const TICK_HZ: u64 = 65_536_000; +#[cfg(feature = "tick-hz-70_000_000")] +pub const TICK_HZ: u64 = 70_000_000; +#[cfg(feature = "tick-hz-72_000_000")] +pub const TICK_HZ: u64 = 72_000_000; +#[cfg(feature = "tick-hz-80_000_000")] +pub const TICK_HZ: u64 = 80_000_000; +#[cfg(feature = "tick-hz-81_920_000")] +pub const TICK_HZ: u64 = 81_920_000; +#[cfg(feature = "tick-hz-90_000_000")] +pub const TICK_HZ: u64 = 90_000_000; +#[cfg(feature = "tick-hz-96_000_000")] +pub const TICK_HZ: u64 = 96_000_000; +#[cfg(feature = "tick-hz-100_000_000")] +pub const TICK_HZ: u64 = 100_000_000; +#[cfg(feature = "tick-hz-110_000_000")] +pub const TICK_HZ: u64 = 110_000_000; +#[cfg(feature = "tick-hz-120_000_000")] +pub const TICK_HZ: u64 = 120_000_000; +#[cfg(feature = "tick-hz-128_000_000")] +pub const TICK_HZ: u64 = 128_000_000; +#[cfg(feature = "tick-hz-130_000_000")] +pub const TICK_HZ: u64 = 130_000_000; +#[cfg(feature = "tick-hz-131_072_000")] +pub const TICK_HZ: u64 = 131_072_000; +#[cfg(feature = "tick-hz-140_000_000")] +pub const TICK_HZ: u64 = 140_000_000; +#[cfg(feature = "tick-hz-144_000_000")] +pub const TICK_HZ: u64 = 144_000_000; +#[cfg(feature = "tick-hz-150_000_000")] +pub const TICK_HZ: u64 = 150_000_000; +#[cfg(feature = "tick-hz-160_000_000")] +pub const TICK_HZ: u64 = 160_000_000; +#[cfg(feature = "tick-hz-163_840_000")] +pub const TICK_HZ: u64 = 163_840_000; +#[cfg(feature = "tick-hz-170_000_000")] +pub const TICK_HZ: u64 = 170_000_000; +#[cfg(feature = "tick-hz-180_000_000")] +pub const TICK_HZ: u64 = 180_000_000; +#[cfg(feature = "tick-hz-190_000_000")] +pub const TICK_HZ: u64 = 190_000_000; +#[cfg(feature = "tick-hz-192_000_000")] +pub const TICK_HZ: u64 = 192_000_000; +#[cfg(feature = "tick-hz-200_000_000")] +pub const TICK_HZ: u64 = 200_000_000; +#[cfg(feature = "tick-hz-210_000_000")] +pub const TICK_HZ: u64 = 210_000_000; +#[cfg(feature = "tick-hz-220_000_000")] +pub const TICK_HZ: u64 = 220_000_000; +#[cfg(feature = "tick-hz-230_000_000")] +pub const TICK_HZ: u64 = 230_000_000; +#[cfg(feature = "tick-hz-240_000_000")] +pub const TICK_HZ: u64 = 240_000_000; +#[cfg(feature = "tick-hz-250_000_000")] +pub const TICK_HZ: u64 = 250_000_000; +#[cfg(feature = "tick-hz-256_000_000")] +pub const TICK_HZ: u64 = 256_000_000; +#[cfg(feature = "tick-hz-260_000_000")] +pub const TICK_HZ: u64 = 260_000_000; +#[cfg(feature = "tick-hz-262_144_000")] +pub const TICK_HZ: u64 = 262_144_000; +#[cfg(feature = "tick-hz-270_000_000")] +pub const TICK_HZ: u64 = 270_000_000; +#[cfg(feature = "tick-hz-280_000_000")] +pub const TICK_HZ: u64 = 280_000_000; +#[cfg(feature = "tick-hz-288_000_000")] +pub const TICK_HZ: u64 = 288_000_000; +#[cfg(feature = "tick-hz-290_000_000")] +pub const TICK_HZ: u64 = 290_000_000; +#[cfg(feature = "tick-hz-300_000_000")] +pub const TICK_HZ: u64 = 300_000_000; +#[cfg(feature = "tick-hz-320_000_000")] +pub const TICK_HZ: u64 = 320_000_000; +#[cfg(feature = "tick-hz-327_680_000")] +pub const TICK_HZ: u64 = 327_680_000; +#[cfg(feature = "tick-hz-340_000_000")] +pub const TICK_HZ: u64 = 340_000_000; +#[cfg(feature = "tick-hz-360_000_000")] +pub const TICK_HZ: u64 = 360_000_000; +#[cfg(feature = "tick-hz-380_000_000")] +pub const TICK_HZ: u64 = 380_000_000; +#[cfg(feature = "tick-hz-384_000_000")] +pub const TICK_HZ: u64 = 384_000_000; +#[cfg(feature = "tick-hz-400_000_000")] +pub const TICK_HZ: u64 = 400_000_000; +#[cfg(feature = "tick-hz-420_000_000")] +pub const TICK_HZ: u64 = 420_000_000; +#[cfg(feature = "tick-hz-440_000_000")] +pub const TICK_HZ: u64 = 440_000_000; +#[cfg(feature = "tick-hz-460_000_000")] +pub const TICK_HZ: u64 = 460_000_000; +#[cfg(feature = "tick-hz-480_000_000")] +pub const TICK_HZ: u64 = 480_000_000; +#[cfg(feature = "tick-hz-500_000_000")] +pub const TICK_HZ: u64 = 500_000_000; +#[cfg(feature = "tick-hz-512_000_000")] +pub const TICK_HZ: u64 = 512_000_000; +#[cfg(feature = "tick-hz-520_000_000")] +pub const TICK_HZ: u64 = 520_000_000; +#[cfg(feature = "tick-hz-524_288_000")] +pub const TICK_HZ: u64 = 524_288_000; +#[cfg(feature = "tick-hz-540_000_000")] +pub const TICK_HZ: u64 = 540_000_000; +#[cfg(feature = "tick-hz-560_000_000")] +pub const TICK_HZ: u64 = 560_000_000; +#[cfg(feature = "tick-hz-576_000_000")] +pub const TICK_HZ: u64 = 576_000_000; +#[cfg(feature = "tick-hz-580_000_000")] +pub const TICK_HZ: u64 = 580_000_000; +#[cfg(feature = "tick-hz-600_000_000")] +pub const TICK_HZ: u64 = 600_000_000; +#[cfg(feature = "tick-hz-620_000_000")] +pub const TICK_HZ: u64 = 620_000_000; +#[cfg(feature = "tick-hz-640_000_000")] +pub const TICK_HZ: u64 = 640_000_000; +#[cfg(feature = "tick-hz-655_360_000")] +pub const TICK_HZ: u64 = 655_360_000; +#[cfg(feature = "tick-hz-660_000_000")] +pub const TICK_HZ: u64 = 660_000_000; +#[cfg(feature = "tick-hz-680_000_000")] +pub const TICK_HZ: u64 = 680_000_000; +#[cfg(feature = "tick-hz-700_000_000")] +pub const TICK_HZ: u64 = 700_000_000; +#[cfg(feature = "tick-hz-720_000_000")] +pub const TICK_HZ: u64 = 720_000_000; +#[cfg(feature = "tick-hz-740_000_000")] +pub const TICK_HZ: u64 = 740_000_000; +#[cfg(feature = "tick-hz-760_000_000")] +pub const TICK_HZ: u64 = 760_000_000; +#[cfg(feature = "tick-hz-768_000_000")] +pub const TICK_HZ: u64 = 768_000_000; +#[cfg(feature = "tick-hz-780_000_000")] +pub const TICK_HZ: u64 = 780_000_000; +#[cfg(feature = "tick-hz-800_000_000")] +pub const TICK_HZ: u64 = 800_000_000; +#[cfg(feature = "tick-hz-820_000_000")] +pub const TICK_HZ: u64 = 820_000_000; +#[cfg(feature = "tick-hz-840_000_000")] +pub const TICK_HZ: u64 = 840_000_000; +#[cfg(feature = "tick-hz-860_000_000")] +pub const TICK_HZ: u64 = 860_000_000; +#[cfg(feature = "tick-hz-880_000_000")] +pub const TICK_HZ: u64 = 880_000_000; +#[cfg(feature = "tick-hz-900_000_000")] +pub const TICK_HZ: u64 = 900_000_000; +#[cfg(feature = "tick-hz-920_000_000")] +pub const TICK_HZ: u64 = 920_000_000; +#[cfg(feature = "tick-hz-940_000_000")] +pub const TICK_HZ: u64 = 940_000_000; +#[cfg(feature = "tick-hz-960_000_000")] +pub const TICK_HZ: u64 = 960_000_000; +#[cfg(feature = "tick-hz-980_000_000")] +pub const TICK_HZ: u64 = 980_000_000; +#[cfg(feature = "tick-hz-1_000_000_000")] +pub const TICK_HZ: u64 = 1_000_000_000; +#[cfg(feature = "tick-hz-1_310_720_000")] +pub const TICK_HZ: u64 = 1_310_720_000; +#[cfg(feature = "tick-hz-2_621_440_000")] +pub const TICK_HZ: u64 = 2_621_440_000; +#[cfg(feature = "tick-hz-5_242_880_000")] +pub const TICK_HZ: u64 = 5_242_880_000; +#[cfg(not(any( + feature = "tick-hz-1", + feature = "tick-hz-2", + feature = "tick-hz-4", + feature = "tick-hz-8", + feature = "tick-hz-10", + feature = "tick-hz-16", + feature = "tick-hz-32", + feature = "tick-hz-64", + feature = "tick-hz-100", + feature = "tick-hz-128", + feature = "tick-hz-256", + feature = "tick-hz-512", + feature = "tick-hz-1_000", + feature = "tick-hz-1_024", + feature = "tick-hz-2_000", + feature = "tick-hz-2_048", + feature = "tick-hz-4_000", + feature = "tick-hz-4_096", + feature = "tick-hz-8_000", + feature = "tick-hz-8_192", + feature = "tick-hz-10_000", + feature = "tick-hz-16_000", + feature = "tick-hz-16_384", + feature = "tick-hz-20_000", + feature = "tick-hz-32_000", + feature = "tick-hz-32_768", + feature = "tick-hz-40_000", + feature = "tick-hz-64_000", + feature = "tick-hz-65_536", + feature = "tick-hz-80_000", + feature = "tick-hz-100_000", + feature = "tick-hz-128_000", + feature = "tick-hz-131_072", + feature = "tick-hz-160_000", + feature = "tick-hz-256_000", + feature = "tick-hz-262_144", + feature = "tick-hz-320_000", + feature = "tick-hz-512_000", + feature = "tick-hz-524_288", + feature = "tick-hz-640_000", + feature = "tick-hz-1_000_000", + feature = "tick-hz-1_024_000", + feature = "tick-hz-1_048_576", + feature = "tick-hz-1_280_000", + feature = "tick-hz-2_000_000", + feature = "tick-hz-2_048_000", + feature = "tick-hz-2_097_152", + feature = "tick-hz-2_560_000", + feature = "tick-hz-3_000_000", + feature = "tick-hz-4_000_000", + feature = "tick-hz-4_096_000", + feature = "tick-hz-4_194_304", + feature = "tick-hz-5_120_000", + feature = "tick-hz-6_000_000", + feature = "tick-hz-8_000_000", + feature = "tick-hz-8_192_000", + feature = "tick-hz-8_388_608", + feature = "tick-hz-9_000_000", + feature = "tick-hz-10_000_000", + feature = "tick-hz-10_240_000", + feature = "tick-hz-12_000_000", + feature = "tick-hz-16_000_000", + feature = "tick-hz-16_384_000", + feature = "tick-hz-16_777_216", + feature = "tick-hz-18_000_000", + feature = "tick-hz-20_000_000", + feature = "tick-hz-20_480_000", + feature = "tick-hz-24_000_000", + feature = "tick-hz-30_000_000", + feature = "tick-hz-32_000_000", + feature = "tick-hz-32_768_000", + feature = "tick-hz-36_000_000", + feature = "tick-hz-40_000_000", + feature = "tick-hz-40_960_000", + feature = "tick-hz-48_000_000", + feature = "tick-hz-50_000_000", + feature = "tick-hz-60_000_000", + feature = "tick-hz-64_000_000", + feature = "tick-hz-65_536_000", + feature = "tick-hz-70_000_000", + feature = "tick-hz-72_000_000", + feature = "tick-hz-80_000_000", + feature = "tick-hz-81_920_000", + feature = "tick-hz-90_000_000", + feature = "tick-hz-96_000_000", + feature = "tick-hz-100_000_000", + feature = "tick-hz-110_000_000", + feature = "tick-hz-120_000_000", + feature = "tick-hz-128_000_000", + feature = "tick-hz-130_000_000", + feature = "tick-hz-131_072_000", + feature = "tick-hz-140_000_000", + feature = "tick-hz-144_000_000", + feature = "tick-hz-150_000_000", + feature = "tick-hz-160_000_000", + feature = "tick-hz-163_840_000", + feature = "tick-hz-170_000_000", + feature = "tick-hz-180_000_000", + feature = "tick-hz-190_000_000", + feature = "tick-hz-192_000_000", + feature = "tick-hz-200_000_000", + feature = "tick-hz-210_000_000", + feature = "tick-hz-220_000_000", + feature = "tick-hz-230_000_000", + feature = "tick-hz-240_000_000", + feature = "tick-hz-250_000_000", + feature = "tick-hz-256_000_000", + feature = "tick-hz-260_000_000", + feature = "tick-hz-262_144_000", + feature = "tick-hz-270_000_000", + feature = "tick-hz-280_000_000", + feature = "tick-hz-288_000_000", + feature = "tick-hz-290_000_000", + feature = "tick-hz-300_000_000", + feature = "tick-hz-320_000_000", + feature = "tick-hz-327_680_000", + feature = "tick-hz-340_000_000", + feature = "tick-hz-360_000_000", + feature = "tick-hz-380_000_000", + feature = "tick-hz-384_000_000", + feature = "tick-hz-400_000_000", + feature = "tick-hz-420_000_000", + feature = "tick-hz-440_000_000", + feature = "tick-hz-460_000_000", + feature = "tick-hz-480_000_000", + feature = "tick-hz-500_000_000", + feature = "tick-hz-512_000_000", + feature = "tick-hz-520_000_000", + feature = "tick-hz-524_288_000", + feature = "tick-hz-540_000_000", + feature = "tick-hz-560_000_000", + feature = "tick-hz-576_000_000", + feature = "tick-hz-580_000_000", + feature = "tick-hz-600_000_000", + feature = "tick-hz-620_000_000", + feature = "tick-hz-640_000_000", + feature = "tick-hz-655_360_000", + feature = "tick-hz-660_000_000", + feature = "tick-hz-680_000_000", + feature = "tick-hz-700_000_000", + feature = "tick-hz-720_000_000", + feature = "tick-hz-740_000_000", + feature = "tick-hz-760_000_000", + feature = "tick-hz-768_000_000", + feature = "tick-hz-780_000_000", + feature = "tick-hz-800_000_000", + feature = "tick-hz-820_000_000", + feature = "tick-hz-840_000_000", + feature = "tick-hz-860_000_000", + feature = "tick-hz-880_000_000", + feature = "tick-hz-900_000_000", + feature = "tick-hz-920_000_000", + feature = "tick-hz-940_000_000", + feature = "tick-hz-960_000_000", + feature = "tick-hz-980_000_000", + feature = "tick-hz-1_000_000_000", + feature = "tick-hz-1_310_720_000", + feature = "tick-hz-2_621_440_000", + feature = "tick-hz-5_242_880_000", +)))] +pub const TICK_HZ: u64 = 1_000_000; diff --git a/embassy/embassy-time-queue-driver/CHANGELOG.md b/embassy/embassy-time-queue-driver/CHANGELOG.md new file mode 100644 index 0000000..a99f250 --- /dev/null +++ b/embassy/embassy-time-queue-driver/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog for embassy-time-queue-driver + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +- Added `generic-queue-N` features. +- Added `embassy_time_queue_driver::Queue` struct which uses integrated or a generic storage (configured using `generic-queue-N`). + +## 0.1.0 - 2024-01-11 + +Initial release diff --git a/embassy/embassy-time-queue-driver/Cargo.toml b/embassy/embassy-time-queue-driver/Cargo.toml new file mode 100644 index 0000000..a104f5c --- /dev/null +++ b/embassy/embassy-time-queue-driver/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "embassy-time-queue-driver" +version = "0.1.0" +edition = "2021" +description = "Timer queue driver trait for embassy-time" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-time-queue-driver" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] + +# Prevent multiple copies of this crate in the same binary. +# Needed because different copies might get different tick rates, causing +# wrong delays if the time driver is using one copy and user code is using another. +# This is especially common when mixing crates from crates.io and git. +links = "embassy-time-queue" + +[dependencies] +heapless = "0.8" +embassy-executor = { version = "0.6.3", path = "../embassy-executor" } + +[features] +#! ### Generic Queue + +#! By default this crate uses a timer queue implementation that is faster but depends on `embassy-executor`. +#! It will panic if you try to await any timer when using another executor. +#! +#! Alternatively, you can choose to use a "generic" timer queue implementation that works on any executor. +#! To enable it, enable any of the features below. +#! +#! The features also set how many timers are used for the generic queue. At most one +#! `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used. +#! +#! When using embassy-time from libraries, you should *not* enable any `generic-queue-*` feature, to allow the +#! end user to pick. + +## Generic Queue with 8 timers +generic-queue-8 = ["_generic-queue"] +## Generic Queue with 16 timers +generic-queue-16 = ["_generic-queue"] +## Generic Queue with 32 timers +generic-queue-32 = ["_generic-queue"] +## Generic Queue with 64 timers +generic-queue-64 = ["_generic-queue"] +## Generic Queue with 128 timers +generic-queue-128 = ["_generic-queue"] + +_generic-queue = [] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-queue-driver-v$VERSION/embassy-time-queue-driver/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-queue-driver/src/" +target = "x86_64-unknown-linux-gnu" diff --git a/embassy/embassy-time-queue-driver/README.md b/embassy/embassy-time-queue-driver/README.md new file mode 100644 index 0000000..b9fb12d --- /dev/null +++ b/embassy/embassy-time-queue-driver/README.md @@ -0,0 +1,8 @@ +# embassy-time-queue-driver + +This crate contains the driver trait used by the [`embassy-time`](https://crates.io/crates/embassy-time) timer queue. + +You should rarely need to use this crate directly. Only use it when implementing your own timer queue. + +There is two timer queue implementations, one in `embassy-time` enabled by the `generic-queue` feature, and +another in `embassy-executor` enabled by the `integrated-timers` feature. diff --git a/embassy/embassy-time-queue-driver/build.rs b/embassy/embassy-time-queue-driver/build.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/embassy/embassy-time-queue-driver/build.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/embassy/embassy-time-queue-driver/src/lib.rs b/embassy/embassy-time-queue-driver/src/lib.rs new file mode 100644 index 0000000..333b612 --- /dev/null +++ b/embassy/embassy-time-queue-driver/src/lib.rs @@ -0,0 +1,21 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! This crate is an implementation detail of `embassy-time-driver`. +//! +//! As a HAL user, you should only depend on this crate if your application does not use +//! `embassy-executor` and your HAL does not configure a generic queue by itself. +//! +//! As a HAL implementer, you need to depend on this crate if you want to implement a time driver, +//! but how you should do so is documented in `embassy-time-driver`. + +#[cfg(feature = "_generic-queue")] +pub mod queue_generic; +#[cfg(not(feature = "_generic-queue"))] +pub mod queue_integrated; + +#[cfg(feature = "_generic-queue")] +pub use queue_generic::Queue; +#[cfg(not(feature = "_generic-queue"))] +pub use queue_integrated::Queue; diff --git a/embassy/embassy-time-queue-driver/src/queue_generic.rs b/embassy/embassy-time-queue-driver/src/queue_generic.rs new file mode 100644 index 0000000..232035b --- /dev/null +++ b/embassy/embassy-time-queue-driver/src/queue_generic.rs @@ -0,0 +1,146 @@ +//! Generic timer queue implementations. +//! +//! Time queue drivers may use this to simplify their implementation. + +use core::cmp::{min, Ordering}; +use core::task::Waker; + +use heapless::Vec; + +#[derive(Debug)] +struct Timer { + at: u64, + waker: Waker, +} + +impl PartialEq for Timer { + fn eq(&self, other: &Self) -> bool { + self.at == other.at + } +} + +impl Eq for Timer {} + +impl PartialOrd for Timer { + fn partial_cmp(&self, other: &Self) -> Option { + self.at.partial_cmp(&other.at) + } +} + +impl Ord for Timer { + fn cmp(&self, other: &Self) -> Ordering { + self.at.cmp(&other.at) + } +} + +/// A timer queue with a pre-determined capacity. +pub struct ConstGenericQueue { + queue: Vec, +} + +impl ConstGenericQueue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { queue: Vec::new() } + } + + /// Schedules a task to run at a specific time, and returns whether any changes were made. + /// + /// If this function returns `true`, the called should find the next expiration time and set + /// a new alarm for that time. + pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool { + self.queue + .iter_mut() + .find(|timer| timer.waker.will_wake(waker)) + .map(|timer| { + if timer.at > at { + timer.at = at; + true + } else { + false + } + }) + .unwrap_or_else(|| { + let mut timer = Timer { + waker: waker.clone(), + at, + }; + + loop { + match self.queue.push(timer) { + Ok(()) => break, + Err(e) => timer = e, + } + + self.queue.pop().unwrap().waker.wake(); + } + + true + }) + } + + /// Dequeues expired timers and returns the next alarm time. + pub fn next_expiration(&mut self, now: u64) -> u64 { + let mut next_alarm = u64::MAX; + + let mut i = 0; + while i < self.queue.len() { + let timer = &self.queue[i]; + if timer.at <= now { + let timer = self.queue.swap_remove(i); + timer.waker.wake(); + } else { + next_alarm = min(next_alarm, timer.at); + i += 1; + } + } + + next_alarm + } +} + +#[cfg(feature = "generic-queue-8")] +const QUEUE_SIZE: usize = 8; +#[cfg(feature = "generic-queue-16")] +const QUEUE_SIZE: usize = 16; +#[cfg(feature = "generic-queue-32")] +const QUEUE_SIZE: usize = 32; +#[cfg(feature = "generic-queue-64")] +const QUEUE_SIZE: usize = 64; +#[cfg(feature = "generic-queue-128")] +const QUEUE_SIZE: usize = 128; +#[cfg(not(any( + feature = "generic-queue-8", + feature = "generic-queue-16", + feature = "generic-queue-32", + feature = "generic-queue-64", + feature = "generic-queue-128" +)))] +const QUEUE_SIZE: usize = 64; + +/// A timer queue with a pre-determined capacity. +pub struct Queue { + queue: ConstGenericQueue, +} + +impl Queue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { + queue: ConstGenericQueue::new(), + } + } + + /// Schedules a task to run at a specific time, and returns whether any changes were made. + /// + /// If this function returns `true`, the called should find the next expiration time and set + /// a new alarm for that time. + pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool { + self.queue.schedule_wake(at, waker) + } + + /// Dequeues expired timers and returns the next alarm time. + pub fn next_expiration(&mut self, now: u64) -> u64 { + self.queue.next_expiration(now) + } +} diff --git a/embassy/embassy-time-queue-driver/src/queue_integrated.rs b/embassy/embassy-time-queue-driver/src/queue_integrated.rs new file mode 100644 index 0000000..246cf1d --- /dev/null +++ b/embassy/embassy-time-queue-driver/src/queue_integrated.rs @@ -0,0 +1,89 @@ +//! Timer queue operations. +use core::cell::Cell; +use core::cmp::min; +use core::task::Waker; + +use embassy_executor::raw::TaskRef; + +/// A timer queue, with items integrated into tasks. +pub struct Queue { + head: Cell>, +} + +impl Queue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { head: Cell::new(None) } + } + + /// Schedules a task to run at a specific time. + /// + /// If this function returns `true`, the called should find the next expiration time and set + /// a new alarm for that time. + pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool { + let task = embassy_executor::raw::task_from_waker(waker); + let item = task.timer_queue_item(); + if item.next.get().is_none() { + // If not in the queue, add it and update. + let prev = self.head.replace(Some(task)); + item.next.set(if prev.is_none() { + Some(unsafe { TaskRef::dangling() }) + } else { + prev + }); + item.expires_at.set(at); + true + } else if at <= item.expires_at.get() { + // If expiration is sooner than previously set, update. + item.expires_at.set(at); + true + } else { + // Task does not need to be updated. + false + } + } + + /// Dequeues expired timers and returns the next alarm time. + /// + /// The provided callback will be called for each expired task. Tasks that never expire + /// will be removed, but the callback will not be called. + pub fn next_expiration(&mut self, now: u64) -> u64 { + let mut next_expiration = u64::MAX; + + self.retain(|p| { + let item = p.timer_queue_item(); + let expires = item.expires_at.get(); + + if expires <= now { + // Timer expired, process task. + embassy_executor::raw::wake_task(p); + false + } else { + // Timer didn't yet expire, or never expires. + next_expiration = min(next_expiration, expires); + expires != u64::MAX + } + }); + + next_expiration + } + + fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) { + let mut prev = &self.head; + while let Some(p) = prev.get() { + if unsafe { p == TaskRef::dangling() } { + // prev was the last item, stop + break; + } + let item = p.timer_queue_item(); + if f(p) { + // Skip to next + prev = &item.next; + } else { + // Remove it + prev.set(item.next.get()); + item.next.set(None); + } + } + } +} diff --git a/embassy/embassy-time/CHANGELOG.md b/embassy/embassy-time/CHANGELOG.md new file mode 100644 index 0000000..a6acb1a --- /dev/null +++ b/embassy/embassy-time/CHANGELOG.md @@ -0,0 +1,74 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +- The `generic-queue` and related features have been removed (moved to embassy-time-queue-driver) +- embassy-time no longer provides an `embassy-time-queue-driver` implementation + +## 0.3.2 - 2024-08-05 + +- Implement with_timeout()/with_deadline() method style call on Future +- Add collapse_debuginfo to fmt.rs macros. + +## 0.3.1 - 2024-01-11 + +- Add with\_deadline convenience function and example +- Implement Clone for Delay +- Make Ticker::next Send+Sync +- Add timestamp features + +## 0.3.0 - 2024-01-11 + +- Update `embedded-hal-async` to `1.0.0` +- Update `embedded-hal v1` to `1.0.0` +- Split the time driver to a separate `embassy-time-driver` crate. + +## 0.2.0 - 2023-12-04 + +- Added tick rates in multiples of 10 kHz +- Remove nightly and unstable-traits features in preparation for 1.75. +- Update heapless to 0.8. + +## 0.1.5 - 2023-10-16 + +- Added `links` key to Cargo.toml, to prevent multiple copies of this crate in the same binary. + Needed because different copies might get different tick rates, causing + wrong delays if the time driver is using one copy and user code is using another. + This is especially common when mixing crates from crates.io and git. + +## 0.1.4 - 2023-10-12 + +- Added more tick rates + +## 0.1.3 - 2023-08-28 + +- Update `embedded-hal-async` to `1.0.0-rc.2` +- Update `embedded-hal v1` to `1.0.0-rc.2` + +## 0.1.2 - 2023-07-05 + +- Update `embedded-hal-async` to `0.2.0-alpha.2`. +- Update `embedded-hal v1` to `1.0.0-alpha.11`. (Note: v0.2 support is kept unchanged). + +## 0.1.1 - 2023-04-13 + +- Update `embedded-hal-async` to `0.2.0-alpha.1` (uses `async fn` in traits). +- Update `embedded-hal v1` to `1.0.0-alpha.10`. (Note: v0.2 support is kept unchanged). +- Remove dep on `embassy-sync`. +- Fix reentrancy issues in the `std` time driver (#1177) +- Add `Duration::from_hz()`. +- impl `From` conversions to/from `core::time::Duration`. +- Add `#[must_use]` to all futures. +- Add inherent `async fn tick()` to `Ticker`, so you can use it directly without the `Stream` trait. +- Add more tick rates. +- impl `Default` for `Signal` +- Remove unnecessary uses of `atomic-polyfill` + +## 0.1.0 - 2022-08-26 + +- First release diff --git a/embassy/embassy-time/Cargo.toml b/embassy/embassy-time/Cargo.toml new file mode 100644 index 0000000..4f4ea0b --- /dev/null +++ b/embassy/embassy-time/Cargo.toml @@ -0,0 +1,410 @@ +[package] +name = "embassy-time" +version = "0.3.2" +edition = "2021" +description = "Instant and Duration for embedded no-std systems, with async timer support" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-time" +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/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time/src/" +features = ["defmt", "std"] +target = "x86_64-unknown-linux-gnu" + +[package.metadata.docs.rs] +features = ["defmt", "std"] + +[features] +std = ["tick-hz-1_000_000", "critical-section/std", "dep:embassy-time-queue-driver"] +wasm = ["dep:wasm-bindgen", "dep:js-sys", "dep:wasm-timer", "tick-hz-1_000_000", "dep:embassy-time-queue-driver"] + +## Display the time since startup next to defmt log messages. +## At most 1 `defmt-timestamp-uptime-*` feature can be used. +## `defmt-timestamp-uptime` is provided for backwards compatibility (provides the same format as `uptime-us`). +## To use this you must have a time driver provided. +defmt-timestamp-uptime = ["defmt"] +defmt-timestamp-uptime-s = ["defmt"] +defmt-timestamp-uptime-ms = ["defmt"] +defmt-timestamp-uptime-us = ["defmt"] +defmt-timestamp-uptime-ts = ["defmt"] +defmt-timestamp-uptime-tms = ["defmt"] +defmt-timestamp-uptime-tus = ["defmt"] + +## Create a `MockDriver` that can be manually advanced for testing purposes. +mock-driver = ["tick-hz-1_000_000", "dep:embassy-time-queue-driver"] + +#! ### Tick Rate +#! +#! At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used. +#! +#! If the time driver in use supports using arbitrary tick rates, you can enable one `tick-*` +#! feature from your binary crate to set the tick rate. The driver will use configured tick rate. +#! If the time driver supports a fixed tick rate, it will enable one feature itself, so you should +#! not enable one. Check the time driver documentation for details. +#! +#! When using embassy-time from libraries, you should *not* enable any `tick-*` feature, to allow the +#! end user or the driver to pick. +#!
+#! Available tick rates: +#! +#! + +# BEGIN TICKS +# Generated by gen_tick.py. DO NOT EDIT. +## 1Hz Tick Rate +tick-hz-1 = ["embassy-time-driver/tick-hz-1"] +## 2Hz Tick Rate +tick-hz-2 = ["embassy-time-driver/tick-hz-2"] +## 4Hz Tick Rate +tick-hz-4 = ["embassy-time-driver/tick-hz-4"] +## 8Hz Tick Rate +tick-hz-8 = ["embassy-time-driver/tick-hz-8"] +## 10Hz Tick Rate +tick-hz-10 = ["embassy-time-driver/tick-hz-10"] +## 16Hz Tick Rate +tick-hz-16 = ["embassy-time-driver/tick-hz-16"] +## 32Hz Tick Rate +tick-hz-32 = ["embassy-time-driver/tick-hz-32"] +## 64Hz Tick Rate +tick-hz-64 = ["embassy-time-driver/tick-hz-64"] +## 100Hz Tick Rate +tick-hz-100 = ["embassy-time-driver/tick-hz-100"] +## 128Hz Tick Rate +tick-hz-128 = ["embassy-time-driver/tick-hz-128"] +## 256Hz Tick Rate +tick-hz-256 = ["embassy-time-driver/tick-hz-256"] +## 512Hz Tick Rate +tick-hz-512 = ["embassy-time-driver/tick-hz-512"] +## 1.0kHz Tick Rate +tick-hz-1_000 = ["embassy-time-driver/tick-hz-1_000"] +## 1.024kHz Tick Rate +tick-hz-1_024 = ["embassy-time-driver/tick-hz-1_024"] +## 2.0kHz Tick Rate +tick-hz-2_000 = ["embassy-time-driver/tick-hz-2_000"] +## 2.048kHz Tick Rate +tick-hz-2_048 = ["embassy-time-driver/tick-hz-2_048"] +## 4.0kHz Tick Rate +tick-hz-4_000 = ["embassy-time-driver/tick-hz-4_000"] +## 4.096kHz Tick Rate +tick-hz-4_096 = ["embassy-time-driver/tick-hz-4_096"] +## 8.0kHz Tick Rate +tick-hz-8_000 = ["embassy-time-driver/tick-hz-8_000"] +## 8.192kHz Tick Rate +tick-hz-8_192 = ["embassy-time-driver/tick-hz-8_192"] +## 10.0kHz Tick Rate +tick-hz-10_000 = ["embassy-time-driver/tick-hz-10_000"] +## 16.0kHz Tick Rate +tick-hz-16_000 = ["embassy-time-driver/tick-hz-16_000"] +## 16.384kHz Tick Rate +tick-hz-16_384 = ["embassy-time-driver/tick-hz-16_384"] +## 20.0kHz Tick Rate +tick-hz-20_000 = ["embassy-time-driver/tick-hz-20_000"] +## 32.0kHz Tick Rate +tick-hz-32_000 = ["embassy-time-driver/tick-hz-32_000"] +## 32.768kHz Tick Rate +tick-hz-32_768 = ["embassy-time-driver/tick-hz-32_768"] +## 40.0kHz Tick Rate +tick-hz-40_000 = ["embassy-time-driver/tick-hz-40_000"] +## 64.0kHz Tick Rate +tick-hz-64_000 = ["embassy-time-driver/tick-hz-64_000"] +## 65.536kHz Tick Rate +tick-hz-65_536 = ["embassy-time-driver/tick-hz-65_536"] +## 80.0kHz Tick Rate +tick-hz-80_000 = ["embassy-time-driver/tick-hz-80_000"] +## 100.0kHz Tick Rate +tick-hz-100_000 = ["embassy-time-driver/tick-hz-100_000"] +## 128.0kHz Tick Rate +tick-hz-128_000 = ["embassy-time-driver/tick-hz-128_000"] +## 131.072kHz Tick Rate +tick-hz-131_072 = ["embassy-time-driver/tick-hz-131_072"] +## 160.0kHz Tick Rate +tick-hz-160_000 = ["embassy-time-driver/tick-hz-160_000"] +## 256.0kHz Tick Rate +tick-hz-256_000 = ["embassy-time-driver/tick-hz-256_000"] +## 262.144kHz Tick Rate +tick-hz-262_144 = ["embassy-time-driver/tick-hz-262_144"] +## 320.0kHz Tick Rate +tick-hz-320_000 = ["embassy-time-driver/tick-hz-320_000"] +## 512.0kHz Tick Rate +tick-hz-512_000 = ["embassy-time-driver/tick-hz-512_000"] +## 524.288kHz Tick Rate +tick-hz-524_288 = ["embassy-time-driver/tick-hz-524_288"] +## 640.0kHz Tick Rate +tick-hz-640_000 = ["embassy-time-driver/tick-hz-640_000"] +## 1.0MHz Tick Rate +tick-hz-1_000_000 = ["embassy-time-driver/tick-hz-1_000_000"] +## 1.024MHz Tick Rate +tick-hz-1_024_000 = ["embassy-time-driver/tick-hz-1_024_000"] +## 1.048576MHz Tick Rate +tick-hz-1_048_576 = ["embassy-time-driver/tick-hz-1_048_576"] +## 1.28MHz Tick Rate +tick-hz-1_280_000 = ["embassy-time-driver/tick-hz-1_280_000"] +## 2.0MHz Tick Rate +tick-hz-2_000_000 = ["embassy-time-driver/tick-hz-2_000_000"] +## 2.048MHz Tick Rate +tick-hz-2_048_000 = ["embassy-time-driver/tick-hz-2_048_000"] +## 2.097152MHz Tick Rate +tick-hz-2_097_152 = ["embassy-time-driver/tick-hz-2_097_152"] +## 2.56MHz Tick Rate +tick-hz-2_560_000 = ["embassy-time-driver/tick-hz-2_560_000"] +## 3.0MHz Tick Rate +tick-hz-3_000_000 = ["embassy-time-driver/tick-hz-3_000_000"] +## 4.0MHz Tick Rate +tick-hz-4_000_000 = ["embassy-time-driver/tick-hz-4_000_000"] +## 4.096MHz Tick Rate +tick-hz-4_096_000 = ["embassy-time-driver/tick-hz-4_096_000"] +## 4.194304MHz Tick Rate +tick-hz-4_194_304 = ["embassy-time-driver/tick-hz-4_194_304"] +## 5.12MHz Tick Rate +tick-hz-5_120_000 = ["embassy-time-driver/tick-hz-5_120_000"] +## 6.0MHz Tick Rate +tick-hz-6_000_000 = ["embassy-time-driver/tick-hz-6_000_000"] +## 8.0MHz Tick Rate +tick-hz-8_000_000 = ["embassy-time-driver/tick-hz-8_000_000"] +## 8.192MHz Tick Rate +tick-hz-8_192_000 = ["embassy-time-driver/tick-hz-8_192_000"] +## 8.388608MHz Tick Rate +tick-hz-8_388_608 = ["embassy-time-driver/tick-hz-8_388_608"] +## 9.0MHz Tick Rate +tick-hz-9_000_000 = ["embassy-time-driver/tick-hz-9_000_000"] +## 10.0MHz Tick Rate +tick-hz-10_000_000 = ["embassy-time-driver/tick-hz-10_000_000"] +## 10.24MHz Tick Rate +tick-hz-10_240_000 = ["embassy-time-driver/tick-hz-10_240_000"] +## 12.0MHz Tick Rate +tick-hz-12_000_000 = ["embassy-time-driver/tick-hz-12_000_000"] +## 16.0MHz Tick Rate +tick-hz-16_000_000 = ["embassy-time-driver/tick-hz-16_000_000"] +## 16.384MHz Tick Rate +tick-hz-16_384_000 = ["embassy-time-driver/tick-hz-16_384_000"] +## 16.777216MHz Tick Rate +tick-hz-16_777_216 = ["embassy-time-driver/tick-hz-16_777_216"] +## 18.0MHz Tick Rate +tick-hz-18_000_000 = ["embassy-time-driver/tick-hz-18_000_000"] +## 20.0MHz Tick Rate +tick-hz-20_000_000 = ["embassy-time-driver/tick-hz-20_000_000"] +## 20.48MHz Tick Rate +tick-hz-20_480_000 = ["embassy-time-driver/tick-hz-20_480_000"] +## 24.0MHz Tick Rate +tick-hz-24_000_000 = ["embassy-time-driver/tick-hz-24_000_000"] +## 30.0MHz Tick Rate +tick-hz-30_000_000 = ["embassy-time-driver/tick-hz-30_000_000"] +## 32.0MHz Tick Rate +tick-hz-32_000_000 = ["embassy-time-driver/tick-hz-32_000_000"] +## 32.768MHz Tick Rate +tick-hz-32_768_000 = ["embassy-time-driver/tick-hz-32_768_000"] +## 36.0MHz Tick Rate +tick-hz-36_000_000 = ["embassy-time-driver/tick-hz-36_000_000"] +## 40.0MHz Tick Rate +tick-hz-40_000_000 = ["embassy-time-driver/tick-hz-40_000_000"] +## 40.96MHz Tick Rate +tick-hz-40_960_000 = ["embassy-time-driver/tick-hz-40_960_000"] +## 48.0MHz Tick Rate +tick-hz-48_000_000 = ["embassy-time-driver/tick-hz-48_000_000"] +## 50.0MHz Tick Rate +tick-hz-50_000_000 = ["embassy-time-driver/tick-hz-50_000_000"] +## 60.0MHz Tick Rate +tick-hz-60_000_000 = ["embassy-time-driver/tick-hz-60_000_000"] +## 64.0MHz Tick Rate +tick-hz-64_000_000 = ["embassy-time-driver/tick-hz-64_000_000"] +## 65.536MHz Tick Rate +tick-hz-65_536_000 = ["embassy-time-driver/tick-hz-65_536_000"] +## 70.0MHz Tick Rate +tick-hz-70_000_000 = ["embassy-time-driver/tick-hz-70_000_000"] +## 72.0MHz Tick Rate +tick-hz-72_000_000 = ["embassy-time-driver/tick-hz-72_000_000"] +## 80.0MHz Tick Rate +tick-hz-80_000_000 = ["embassy-time-driver/tick-hz-80_000_000"] +## 81.92MHz Tick Rate +tick-hz-81_920_000 = ["embassy-time-driver/tick-hz-81_920_000"] +## 90.0MHz Tick Rate +tick-hz-90_000_000 = ["embassy-time-driver/tick-hz-90_000_000"] +## 96.0MHz Tick Rate +tick-hz-96_000_000 = ["embassy-time-driver/tick-hz-96_000_000"] +## 100.0MHz Tick Rate +tick-hz-100_000_000 = ["embassy-time-driver/tick-hz-100_000_000"] +## 110.0MHz Tick Rate +tick-hz-110_000_000 = ["embassy-time-driver/tick-hz-110_000_000"] +## 120.0MHz Tick Rate +tick-hz-120_000_000 = ["embassy-time-driver/tick-hz-120_000_000"] +## 128.0MHz Tick Rate +tick-hz-128_000_000 = ["embassy-time-driver/tick-hz-128_000_000"] +## 130.0MHz Tick Rate +tick-hz-130_000_000 = ["embassy-time-driver/tick-hz-130_000_000"] +## 131.072MHz Tick Rate +tick-hz-131_072_000 = ["embassy-time-driver/tick-hz-131_072_000"] +## 140.0MHz Tick Rate +tick-hz-140_000_000 = ["embassy-time-driver/tick-hz-140_000_000"] +## 144.0MHz Tick Rate +tick-hz-144_000_000 = ["embassy-time-driver/tick-hz-144_000_000"] +## 150.0MHz Tick Rate +tick-hz-150_000_000 = ["embassy-time-driver/tick-hz-150_000_000"] +## 160.0MHz Tick Rate +tick-hz-160_000_000 = ["embassy-time-driver/tick-hz-160_000_000"] +## 163.84MHz Tick Rate +tick-hz-163_840_000 = ["embassy-time-driver/tick-hz-163_840_000"] +## 170.0MHz Tick Rate +tick-hz-170_000_000 = ["embassy-time-driver/tick-hz-170_000_000"] +## 180.0MHz Tick Rate +tick-hz-180_000_000 = ["embassy-time-driver/tick-hz-180_000_000"] +## 190.0MHz Tick Rate +tick-hz-190_000_000 = ["embassy-time-driver/tick-hz-190_000_000"] +## 192.0MHz Tick Rate +tick-hz-192_000_000 = ["embassy-time-driver/tick-hz-192_000_000"] +## 200.0MHz Tick Rate +tick-hz-200_000_000 = ["embassy-time-driver/tick-hz-200_000_000"] +## 210.0MHz Tick Rate +tick-hz-210_000_000 = ["embassy-time-driver/tick-hz-210_000_000"] +## 220.0MHz Tick Rate +tick-hz-220_000_000 = ["embassy-time-driver/tick-hz-220_000_000"] +## 230.0MHz Tick Rate +tick-hz-230_000_000 = ["embassy-time-driver/tick-hz-230_000_000"] +## 240.0MHz Tick Rate +tick-hz-240_000_000 = ["embassy-time-driver/tick-hz-240_000_000"] +## 250.0MHz Tick Rate +tick-hz-250_000_000 = ["embassy-time-driver/tick-hz-250_000_000"] +## 256.0MHz Tick Rate +tick-hz-256_000_000 = ["embassy-time-driver/tick-hz-256_000_000"] +## 260.0MHz Tick Rate +tick-hz-260_000_000 = ["embassy-time-driver/tick-hz-260_000_000"] +## 262.144MHz Tick Rate +tick-hz-262_144_000 = ["embassy-time-driver/tick-hz-262_144_000"] +## 270.0MHz Tick Rate +tick-hz-270_000_000 = ["embassy-time-driver/tick-hz-270_000_000"] +## 280.0MHz Tick Rate +tick-hz-280_000_000 = ["embassy-time-driver/tick-hz-280_000_000"] +## 288.0MHz Tick Rate +tick-hz-288_000_000 = ["embassy-time-driver/tick-hz-288_000_000"] +## 290.0MHz Tick Rate +tick-hz-290_000_000 = ["embassy-time-driver/tick-hz-290_000_000"] +## 300.0MHz Tick Rate +tick-hz-300_000_000 = ["embassy-time-driver/tick-hz-300_000_000"] +## 320.0MHz Tick Rate +tick-hz-320_000_000 = ["embassy-time-driver/tick-hz-320_000_000"] +## 327.68MHz Tick Rate +tick-hz-327_680_000 = ["embassy-time-driver/tick-hz-327_680_000"] +## 340.0MHz Tick Rate +tick-hz-340_000_000 = ["embassy-time-driver/tick-hz-340_000_000"] +## 360.0MHz Tick Rate +tick-hz-360_000_000 = ["embassy-time-driver/tick-hz-360_000_000"] +## 380.0MHz Tick Rate +tick-hz-380_000_000 = ["embassy-time-driver/tick-hz-380_000_000"] +## 384.0MHz Tick Rate +tick-hz-384_000_000 = ["embassy-time-driver/tick-hz-384_000_000"] +## 400.0MHz Tick Rate +tick-hz-400_000_000 = ["embassy-time-driver/tick-hz-400_000_000"] +## 420.0MHz Tick Rate +tick-hz-420_000_000 = ["embassy-time-driver/tick-hz-420_000_000"] +## 440.0MHz Tick Rate +tick-hz-440_000_000 = ["embassy-time-driver/tick-hz-440_000_000"] +## 460.0MHz Tick Rate +tick-hz-460_000_000 = ["embassy-time-driver/tick-hz-460_000_000"] +## 480.0MHz Tick Rate +tick-hz-480_000_000 = ["embassy-time-driver/tick-hz-480_000_000"] +## 500.0MHz Tick Rate +tick-hz-500_000_000 = ["embassy-time-driver/tick-hz-500_000_000"] +## 512.0MHz Tick Rate +tick-hz-512_000_000 = ["embassy-time-driver/tick-hz-512_000_000"] +## 520.0MHz Tick Rate +tick-hz-520_000_000 = ["embassy-time-driver/tick-hz-520_000_000"] +## 524.288MHz Tick Rate +tick-hz-524_288_000 = ["embassy-time-driver/tick-hz-524_288_000"] +## 540.0MHz Tick Rate +tick-hz-540_000_000 = ["embassy-time-driver/tick-hz-540_000_000"] +## 560.0MHz Tick Rate +tick-hz-560_000_000 = ["embassy-time-driver/tick-hz-560_000_000"] +## 576.0MHz Tick Rate +tick-hz-576_000_000 = ["embassy-time-driver/tick-hz-576_000_000"] +## 580.0MHz Tick Rate +tick-hz-580_000_000 = ["embassy-time-driver/tick-hz-580_000_000"] +## 600.0MHz Tick Rate +tick-hz-600_000_000 = ["embassy-time-driver/tick-hz-600_000_000"] +## 620.0MHz Tick Rate +tick-hz-620_000_000 = ["embassy-time-driver/tick-hz-620_000_000"] +## 640.0MHz Tick Rate +tick-hz-640_000_000 = ["embassy-time-driver/tick-hz-640_000_000"] +## 655.36MHz Tick Rate +tick-hz-655_360_000 = ["embassy-time-driver/tick-hz-655_360_000"] +## 660.0MHz Tick Rate +tick-hz-660_000_000 = ["embassy-time-driver/tick-hz-660_000_000"] +## 680.0MHz Tick Rate +tick-hz-680_000_000 = ["embassy-time-driver/tick-hz-680_000_000"] +## 700.0MHz Tick Rate +tick-hz-700_000_000 = ["embassy-time-driver/tick-hz-700_000_000"] +## 720.0MHz Tick Rate +tick-hz-720_000_000 = ["embassy-time-driver/tick-hz-720_000_000"] +## 740.0MHz Tick Rate +tick-hz-740_000_000 = ["embassy-time-driver/tick-hz-740_000_000"] +## 760.0MHz Tick Rate +tick-hz-760_000_000 = ["embassy-time-driver/tick-hz-760_000_000"] +## 768.0MHz Tick Rate +tick-hz-768_000_000 = ["embassy-time-driver/tick-hz-768_000_000"] +## 780.0MHz Tick Rate +tick-hz-780_000_000 = ["embassy-time-driver/tick-hz-780_000_000"] +## 800.0MHz Tick Rate +tick-hz-800_000_000 = ["embassy-time-driver/tick-hz-800_000_000"] +## 820.0MHz Tick Rate +tick-hz-820_000_000 = ["embassy-time-driver/tick-hz-820_000_000"] +## 840.0MHz Tick Rate +tick-hz-840_000_000 = ["embassy-time-driver/tick-hz-840_000_000"] +## 860.0MHz Tick Rate +tick-hz-860_000_000 = ["embassy-time-driver/tick-hz-860_000_000"] +## 880.0MHz Tick Rate +tick-hz-880_000_000 = ["embassy-time-driver/tick-hz-880_000_000"] +## 900.0MHz Tick Rate +tick-hz-900_000_000 = ["embassy-time-driver/tick-hz-900_000_000"] +## 920.0MHz Tick Rate +tick-hz-920_000_000 = ["embassy-time-driver/tick-hz-920_000_000"] +## 940.0MHz Tick Rate +tick-hz-940_000_000 = ["embassy-time-driver/tick-hz-940_000_000"] +## 960.0MHz Tick Rate +tick-hz-960_000_000 = ["embassy-time-driver/tick-hz-960_000_000"] +## 980.0MHz Tick Rate +tick-hz-980_000_000 = ["embassy-time-driver/tick-hz-980_000_000"] +## 1.0GHz Tick Rate +tick-hz-1_000_000_000 = ["embassy-time-driver/tick-hz-1_000_000_000"] +## 1.31072GHz Tick Rate +tick-hz-1_310_720_000 = ["embassy-time-driver/tick-hz-1_310_720_000"] +## 2.62144GHz Tick Rate +tick-hz-2_621_440_000 = ["embassy-time-driver/tick-hz-2_621_440_000"] +## 5.24288GHz Tick Rate +tick-hz-5_242_880_000 = ["embassy-time-driver/tick-hz-5_242_880_000"] +# END TICKS + +#!
+ +[dependencies] +embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver" } +embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver", optional = true} + +defmt = { version = "0.3", optional = true } +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" } +embedded-hal-async = { version = "1.0" } + +futures-util = { version = "0.3.17", default-features = false } +critical-section = "1.1" +cfg-if = "1.0.0" + +document-features = "0.2.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"] } +embassy-executor = { version = "0.6.3", path = "../embassy-executor" } diff --git a/embassy/embassy-time/README.md b/embassy/embassy-time/README.md new file mode 100644 index 0000000..6a4b049 --- /dev/null +++ b/embassy/embassy-time/README.md @@ -0,0 +1,47 @@ +# embassy-time + +Timekeeping, delays and timeouts. + +Timekeeping is done with elapsed time since system boot. Time is represented in +ticks, where the tick rate is defined either by the driver (in the case of a fixed-rate +tick) or chosen by the user with a [tick rate](#tick-rate) feature. The chosen +tick rate applies to everything in `embassy-time` and thus determines the maximum +timing resolution of (1 / tick_rate) seconds. + +Tick counts are 64 bits. The default tick rate of 1Mhz supports +representing time spans of up to ~584558 years, which is big enough for all practical +purposes and allows not having to worry about overflows. + +## Global time driver + +The `time` module is backed by a global "time driver" specified at build time. +Only one driver can be active in a program. + +All methods and structs transparently call into the active driver. This makes it +possible for libraries to use `embassy_time` in a driver-agnostic way without +requiring generic parameters. + +For more details, check the [`embassy_time_driver`](https://crates.io/crates/embassy-time-driver) crate. + +## Instants and Durations + +[`Instant`] represents a given instant of time (relative to system boot), and [`Duration`] +represents the duration of a span of time. They implement the math operations you'd expect, +like addition and substraction. + +## Delays and timeouts + +[`Timer`] allows performing async delays. [`Ticker`] allows periodic delays without drifting over time. + +An implementation of the `embedded-hal` delay traits is provided by [`Delay`], for compatibility +with libraries from the ecosystem. + +## Wall-clock time + +The `time` module deals exclusively with a monotonically increasing tick count. +Therefore it has no direct support for wall-clock time ("real life" datetimes +like `2021-08-24 13:33:21`). + +If persistence across reboots is not needed, support can be built on top of +`embassy_time` by storing the offset between "seconds elapsed since boot" +and "seconds since unix epoch". diff --git a/embassy/embassy-time/src/delay.rs b/embassy/embassy-time/src/delay.rs new file mode 100644 index 0000000..f77859d --- /dev/null +++ b/embassy/embassy-time/src/delay.rs @@ -0,0 +1,81 @@ +use super::{Duration, Instant}; +use crate::Timer; + +/// Blocks for at least `duration`. +pub fn block_for(duration: Duration) { + let expires_at = Instant::now() + duration; + while Instant::now() < expires_at {} +} + +/// Type implementing async delays and blocking `embedded-hal` delays. +/// +/// The delays are implemented in a "best-effort" way, meaning that the cpu will block for at least +/// the amount provided, but accuracy can be affected by many factors, including interrupt usage. +/// Make sure to use a suitable tick rate for your use case. The tick rate is defined by the currently +/// active driver. +#[derive(Clone)] +pub struct Delay; + +impl embedded_hal_1::delay::DelayNs for Delay { + fn delay_ns(&mut self, ns: u32) { + block_for(Duration::from_nanos(ns as u64)) + } + + fn delay_us(&mut self, us: u32) { + block_for(Duration::from_micros(us as u64)) + } + + fn delay_ms(&mut self, ms: u32) { + block_for(Duration::from_millis(ms as u64)) + } +} + +impl embedded_hal_async::delay::DelayNs for Delay { + async fn delay_ns(&mut self, ns: u32) { + Timer::after_nanos(ns as _).await + } + + async fn delay_us(&mut self, us: u32) { + Timer::after_micros(us as _).await + } + + async fn delay_ms(&mut self, ms: u32) { + Timer::after_millis(ms as _).await + } +} + +impl embedded_hal_02::blocking::delay::DelayMs for Delay { + fn delay_ms(&mut self, ms: u8) { + block_for(Duration::from_millis(ms as u64)) + } +} + +impl embedded_hal_02::blocking::delay::DelayMs for Delay { + fn delay_ms(&mut self, ms: u16) { + block_for(Duration::from_millis(ms as u64)) + } +} + +impl embedded_hal_02::blocking::delay::DelayMs for Delay { + fn delay_ms(&mut self, ms: u32) { + block_for(Duration::from_millis(ms as u64)) + } +} + +impl embedded_hal_02::blocking::delay::DelayUs for Delay { + fn delay_us(&mut self, us: u8) { + block_for(Duration::from_micros(us as u64)) + } +} + +impl embedded_hal_02::blocking::delay::DelayUs for Delay { + fn delay_us(&mut self, us: u16) { + block_for(Duration::from_micros(us as u64)) + } +} + +impl embedded_hal_02::blocking::delay::DelayUs for Delay { + fn delay_us(&mut self, us: u32) { + block_for(Duration::from_micros(us as u64)) + } +} diff --git a/embassy/embassy-time/src/driver_mock.rs b/embassy/embassy-time/src/driver_mock.rs new file mode 100644 index 0000000..138d604 --- /dev/null +++ b/embassy/embassy-time/src/driver_mock.rs @@ -0,0 +1,145 @@ +use core::cell::RefCell; +use core::task::Waker; + +use critical_section::Mutex as CsMutex; +use embassy_time_driver::Driver; +use embassy_time_queue_driver::Queue; + +use crate::{Duration, Instant}; + +/// A mock driver that can be manually advanced. +/// This is useful for testing code that works with [`Instant`] and [`Duration`]. +/// +/// This driver can also be used to test runtime functionality, such as +/// timers, delays, etc. +/// +/// # Example +/// +/// ```ignore +/// fn has_a_second_passed(reference: Instant) -> bool { +/// Instant::now().duration_since(reference) >= Duration::from_secs(1) +/// } +/// +/// fn test_second_passed() { +/// let driver = embassy_time::MockDriver::get(); +/// let reference = Instant::now(); +/// assert_eq!(false, has_a_second_passed(reference)); +/// driver.advance(Duration::from_secs(1)); +/// assert_eq!(true, has_a_second_passed(reference)); +/// } +/// ``` +pub struct MockDriver(CsMutex>); + +embassy_time_driver::time_driver_impl!(static DRIVER: MockDriver = MockDriver::new()); + +impl MockDriver { + /// Creates a new mock driver. + pub const fn new() -> Self { + Self(CsMutex::new(RefCell::new(InnerMockDriver::new()))) + } + + /// Gets a reference to the global mock driver. + pub fn get() -> &'static MockDriver { + &DRIVER + } + + /// Resets the internal state of the mock driver + /// This will clear and deallocate all alarms, and reset the current time to 0. + pub fn reset(&self) { + critical_section::with(|cs| { + self.0.borrow(cs).replace(InnerMockDriver::new()); + }); + } + + /// Advances the time by the specified [`Duration`]. + /// Calling any alarm callbacks that are due. + pub fn advance(&self, duration: Duration) { + critical_section::with(|cs| { + let inner = &mut *self.0.borrow_ref_mut(cs); + + inner.now += duration; + // wake expired tasks. + inner.queue.next_expiration(inner.now.as_ticks()); + }) + } +} + +impl Driver for MockDriver { + fn now(&self) -> u64 { + critical_section::with(|cs| self.0.borrow_ref(cs).now).as_ticks() + } + + fn schedule_wake(&self, at: u64, waker: &Waker) { + critical_section::with(|cs| { + let inner = &mut *self.0.borrow_ref_mut(cs); + // enqueue it + inner.queue.schedule_wake(at, waker); + // wake it if it's in the past. + inner.queue.next_expiration(inner.now.as_ticks()); + }) + } +} + +struct InnerMockDriver { + now: Instant, + queue: Queue, +} + +impl InnerMockDriver { + const fn new() -> Self { + Self { + now: Instant::from_ticks(0), + queue: Queue::new(), + } + } +} + +#[cfg(test)] +mod tests { + use core::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Arc; + use std::task::Wake; + + use serial_test::serial; + + use super::*; + + fn setup() { + DRIVER.reset(); + } + + #[test] + #[serial] + fn test_advance() { + setup(); + + let driver = MockDriver::get(); + let reference = driver.now(); + driver.advance(Duration::from_secs(1)); + assert_eq!(Duration::from_secs(1).as_ticks(), driver.now() - reference); + } + + #[test] + #[serial] + fn test_schedule_wake() { + setup(); + + static CALLBACK_CALLED: AtomicBool = AtomicBool::new(false); + + struct MockWaker; + + impl Wake for MockWaker { + fn wake(self: Arc) { + CALLBACK_CALLED.store(true, Ordering::Relaxed); + } + } + let waker = Arc::new(MockWaker).into(); + + let driver = MockDriver::get(); + + driver.schedule_wake(driver.now() + 1, &waker); + assert_eq!(false, CALLBACK_CALLED.load(Ordering::Relaxed)); + driver.advance(Duration::from_secs(1)); + assert_eq!(true, CALLBACK_CALLED.load(Ordering::Relaxed)); + } +} diff --git a/embassy/embassy-time/src/driver_std.rs b/embassy/embassy-time/src/driver_std.rs new file mode 100644 index 0000000..35888fd --- /dev/null +++ b/embassy/embassy-time/src/driver_std.rs @@ -0,0 +1,104 @@ +use std::sync::{Condvar, Mutex}; +use std::thread; +use std::time::{Duration as StdDuration, Instant as StdInstant}; + +use embassy_time_driver::Driver; +use embassy_time_queue_driver::Queue; + +struct TimeDriver { + signaler: Signaler, + inner: Mutex, +} + +struct Inner { + zero_instant: Option, + queue: Queue, +} + +embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { + inner: Mutex::new(Inner{ + zero_instant: None, + queue: Queue::new(), + }), + signaler: Signaler::new(), +}); + +impl Inner { + fn init(&mut self) -> StdInstant { + *self.zero_instant.get_or_insert_with(|| { + thread::spawn(alarm_thread); + StdInstant::now() + }) + } +} + +impl Driver for TimeDriver { + fn now(&self) -> u64 { + let mut inner = self.inner.lock().unwrap(); + let zero = inner.init(); + StdInstant::now().duration_since(zero).as_micros() as u64 + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + let mut inner = self.inner.lock().unwrap(); + inner.init(); + if inner.queue.schedule_wake(at, waker) { + self.signaler.signal(); + } + } +} + +fn alarm_thread() { + let zero = DRIVER.inner.lock().unwrap().zero_instant.unwrap(); + loop { + let now = DRIVER.now(); + + let next_alarm = DRIVER.inner.lock().unwrap().queue.next_expiration(now); + + // Ensure we don't overflow + let until = zero + .checked_add(StdDuration::from_micros(next_alarm)) + .unwrap_or_else(|| StdInstant::now() + StdDuration::from_secs(1)); + + DRIVER.signaler.wait_until(until); + } +} + +struct Signaler { + mutex: Mutex, + condvar: Condvar, +} + +impl Signaler { + const fn new() -> Self { + Self { + mutex: Mutex::new(false), + condvar: Condvar::new(), + } + } + + fn wait_until(&self, until: StdInstant) { + let mut signaled = self.mutex.lock().unwrap(); + while !*signaled { + let now = StdInstant::now(); + + if now >= until { + break; + } + + let dur = until - now; + let (signaled2, timeout) = self.condvar.wait_timeout(signaled, dur).unwrap(); + signaled = signaled2; + if timeout.timed_out() { + break; + } + } + *signaled = false; + } + + fn signal(&self) { + let mut signaled = self.mutex.lock().unwrap(); + *signaled = true; + self.condvar.notify_one(); + } +} diff --git a/embassy/embassy-time/src/driver_wasm.rs b/embassy/embassy-time/src/driver_wasm.rs new file mode 100644 index 0000000..bcdd167 --- /dev/null +++ b/embassy/embassy-time/src/driver_wasm.rs @@ -0,0 +1,103 @@ +use std::sync::Mutex; + +use embassy_time_driver::Driver; +use embassy_time_queue_driver::Queue; +use wasm_bindgen::prelude::*; +use wasm_timer::Instant as StdInstant; + +struct AlarmState { + token: Option, +} + +impl AlarmState { + const fn new() -> Self { + Self { token: None } + } +} + +#[wasm_bindgen] +extern "C" { + fn setTimeout(closure: &Closure, millis: u32) -> f64; + fn clearTimeout(token: f64); +} + +struct TimeDriver { + inner: Mutex, +} + +struct Inner { + alarm: AlarmState, + zero_instant: Option, + queue: Queue, + closure: Option>, +} + +unsafe impl Send for Inner {} + +embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { + inner: Mutex::new(Inner{ + zero_instant: None, + queue: Queue::new(), + alarm: AlarmState::new(), + closure: None, + }), +}); + +impl Inner { + fn init(&mut self) -> StdInstant { + *self.zero_instant.get_or_insert_with(StdInstant::now) + } + + fn now(&mut self) -> u64 { + StdInstant::now().duration_since(self.zero_instant.unwrap()).as_micros() as u64 + } + + fn set_alarm(&mut self, timestamp: u64) -> bool { + if let Some(token) = self.alarm.token { + clearTimeout(token); + } + + let now = self.now(); + if timestamp <= now { + false + } else { + let timeout = (timestamp - now) as u32; + let closure = self.closure.get_or_insert_with(|| Closure::new(dispatch)); + self.alarm.token = Some(setTimeout(closure, timeout / 1000)); + + true + } + } +} + +impl Driver for TimeDriver { + fn now(&self) -> u64 { + let mut inner = self.inner.lock().unwrap(); + let zero = inner.init(); + StdInstant::now().duration_since(zero).as_micros() as u64 + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + let mut inner = self.inner.lock().unwrap(); + inner.init(); + if inner.queue.schedule_wake(at, waker) { + let now = inner.now(); + let mut next = inner.queue.next_expiration(now); + while !inner.set_alarm(next) { + let now = inner.now(); + next = inner.queue.next_expiration(now); + } + } + } +} + +fn dispatch() { + let inner = &mut *DRIVER.inner.lock().unwrap(); + + let now = inner.now(); + let mut next = inner.queue.next_expiration(now); + while !inner.set_alarm(next) { + let now = inner.now(); + next = inner.queue.next_expiration(now); + } +} diff --git a/embassy/embassy-time/src/duration.rs b/embassy/embassy-time/src/duration.rs new file mode 100644 index 0000000..647d208 --- /dev/null +++ b/embassy/embassy-time/src/duration.rs @@ -0,0 +1,219 @@ +use core::fmt; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; + +use super::{GCD_1K, GCD_1M, TICK_HZ}; +use crate::GCD_1G; + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Represents the difference between two [Instant](struct.Instant.html)s +pub struct Duration { + pub(crate) ticks: u64, +} + +impl Duration { + /// The smallest value that can be represented by the `Duration` type. + pub const MIN: Duration = Duration { ticks: u64::MIN }; + /// The largest value that can be represented by the `Duration` type. + pub const MAX: Duration = Duration { ticks: u64::MAX }; + + /// Tick count of the `Duration`. + pub const fn as_ticks(&self) -> u64 { + self.ticks + } + + /// Convert the `Duration` to seconds, rounding down. + pub const fn as_secs(&self) -> u64 { + self.ticks / TICK_HZ + } + + /// Convert the `Duration` to milliseconds, rounding down. + pub const fn as_millis(&self) -> u64 { + self.ticks * (1000 / GCD_1K) / (TICK_HZ / GCD_1K) + } + + /// Convert the `Duration` to microseconds, rounding down. + pub const fn as_micros(&self) -> u64 { + self.ticks * (1_000_000 / GCD_1M) / (TICK_HZ / GCD_1M) + } + + /// Creates a duration from the specified number of clock ticks + pub const fn from_ticks(ticks: u64) -> Duration { + Duration { ticks } + } + + /// Creates a duration from the specified number of seconds, rounding up. + pub const fn from_secs(secs: u64) -> Duration { + Duration { ticks: secs * TICK_HZ } + } + + /// Creates a duration from the specified number of milliseconds, rounding up. + pub const fn from_millis(millis: u64) -> Duration { + Duration { + ticks: div_ceil(millis * (TICK_HZ / GCD_1K), 1000 / GCD_1K), + } + } + + /// Creates a duration from the specified number of microseconds, rounding up. + /// NOTE: Delays this small may be inaccurate. + pub const fn from_micros(micros: u64) -> Duration { + Duration { + ticks: div_ceil(micros * (TICK_HZ / GCD_1M), 1_000_000 / GCD_1M), + } + } + + /// Creates a duration from the specified number of nanoseconds, rounding up. + /// NOTE: Delays this small may be inaccurate. + pub const fn from_nanos(micros: u64) -> Duration { + Duration { + ticks: div_ceil(micros * (TICK_HZ / GCD_1G), 1_000_000_000 / GCD_1G), + } + } + + /// Creates a duration from the specified number of seconds, rounding down. + pub const fn from_secs_floor(secs: u64) -> Duration { + Duration { ticks: secs * TICK_HZ } + } + + /// Creates a duration from the specified number of milliseconds, rounding down. + pub const fn from_millis_floor(millis: u64) -> Duration { + Duration { + ticks: millis * (TICK_HZ / GCD_1K) / (1000 / GCD_1K), + } + } + + /// Creates a duration from the specified number of microseconds, rounding down. + /// NOTE: Delays this small may be inaccurate. + pub const fn from_micros_floor(micros: u64) -> Duration { + Duration { + ticks: micros * (TICK_HZ / GCD_1M) / (1_000_000 / GCD_1M), + } + } + + /// Creates a duration corresponding to the specified Hz. + /// NOTE: Giving this function a hz >= the TICK_HZ of your platform will clamp the Duration to 1 + /// tick. Doing so will not deadlock, but will certainly not produce the desired output. + 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 }) + } + + /// Subtracts one Duration to another, returning a new Duration or None in the event of an overflow. + pub fn checked_sub(self, rhs: Duration) -> Option { + self.ticks.checked_sub(rhs.ticks).map(|ticks| Duration { ticks }) + } + + /// Multiplies one Duration by a scalar u32, returning a new Duration or None in the event of an overflow. + pub fn checked_mul(self, rhs: u32) -> Option { + self.ticks.checked_mul(rhs as _).map(|ticks| Duration { ticks }) + } + + /// Divides one Duration a scalar u32, returning a new Duration or None in the event of an overflow. + pub fn checked_div(self, rhs: u32) -> Option { + self.ticks.checked_div(rhs as _).map(|ticks| Duration { ticks }) + } +} + +impl Add for Duration { + type Output = Duration; + + fn add(self, rhs: Duration) -> Duration { + self.checked_add(rhs).expect("overflow when adding durations") + } +} + +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: Duration) { + *self = *self + rhs; + } +} + +impl Sub for Duration { + type Output = Duration; + + fn sub(self, rhs: Duration) -> Duration { + self.checked_sub(rhs).expect("overflow when subtracting durations") + } +} + +impl SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + *self = *self - rhs; + } +} + +impl Mul for Duration { + type Output = Duration; + + fn mul(self, rhs: u32) -> Duration { + self.checked_mul(rhs) + .expect("overflow when multiplying duration by scalar") + } +} + +impl Mul for u32 { + type Output = Duration; + + fn mul(self, rhs: Duration) -> Duration { + rhs * self + } +} + +impl MulAssign for Duration { + fn mul_assign(&mut self, rhs: u32) { + *self = *self * rhs; + } +} + +impl Div for Duration { + type Output = Duration; + + fn div(self, rhs: u32) -> Duration { + self.checked_div(rhs) + .expect("divide by zero error when dividing duration by scalar") + } +} + +impl DivAssign for Duration { + fn div_assign(&mut self, rhs: u32) { + *self = *self / rhs; + } +} + +impl<'a> fmt::Display for Duration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} ticks", self.ticks) + } +} + +#[inline] +const fn div_ceil(num: u64, den: u64) -> u64 { + (num + den - 1) / den +} + +impl TryFrom for Duration { + type Error = >::Error; + + /// Converts using [`Duration::from_micros`]. Fails if value can not be represented as u64. + fn try_from(value: core::time::Duration) -> Result { + Ok(Self::from_micros(value.as_micros().try_into()?)) + } +} + +impl From for core::time::Duration { + /// Converts using [`Duration::as_micros`]. + fn from(value: Duration) -> Self { + core::time::Duration::from_micros(value.as_micros()) + } +} diff --git a/embassy/embassy-time/src/fmt.rs b/embassy/embassy-time/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-time/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-time/src/instant.rs b/embassy/embassy-time/src/instant.rs new file mode 100644 index 0000000..909f1b1 --- /dev/null +++ b/embassy/embassy-time/src/instant.rs @@ -0,0 +1,161 @@ +use core::fmt; +use core::ops::{Add, AddAssign, Sub, SubAssign}; + +use super::{Duration, GCD_1K, GCD_1M, TICK_HZ}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// An Instant in time, based on the MCU's clock ticks since startup. +pub struct Instant { + ticks: u64, +} + +impl Instant { + /// The smallest (earliest) value that can be represented by the `Instant` type. + pub const MIN: Instant = Instant { ticks: u64::MIN }; + /// The largest (latest) value that can be represented by the `Instant` type. + pub const MAX: Instant = Instant { ticks: u64::MAX }; + + /// Returns an Instant representing the current time. + pub fn now() -> Instant { + Instant { + ticks: embassy_time_driver::now(), + } + } + + /// Create an Instant from a tick count since system boot. + pub const fn from_ticks(ticks: u64) -> Self { + Self { ticks } + } + + /// Create an Instant from a microsecond count since system boot. + pub const fn from_micros(micros: u64) -> Self { + Self { + ticks: micros * (TICK_HZ / GCD_1M) / (1_000_000 / GCD_1M), + } + } + + /// Create an Instant from a millisecond count since system boot. + pub const fn from_millis(millis: u64) -> Self { + Self { + ticks: millis * (TICK_HZ / GCD_1K) / (1000 / GCD_1K), + } + } + + /// Create an Instant from a second count since system boot. + pub const fn from_secs(seconds: u64) -> Self { + Self { + ticks: seconds * TICK_HZ, + } + } + + /// Tick count since system boot. + pub const fn as_ticks(&self) -> u64 { + self.ticks + } + + /// Seconds since system boot. + pub const fn as_secs(&self) -> u64 { + self.ticks / TICK_HZ + } + + /// Milliseconds since system boot. + pub const fn as_millis(&self) -> u64 { + self.ticks * (1000 / GCD_1K) / (TICK_HZ / GCD_1K) + } + + /// Microseconds since system boot. + pub const fn as_micros(&self) -> u64 { + self.ticks * (1_000_000 / GCD_1M) / (TICK_HZ / GCD_1M) + } + + /// Duration between this Instant and another Instant + /// Panics on over/underflow. + pub fn duration_since(&self, earlier: Instant) -> Duration { + Duration { + ticks: unwrap!(self.ticks.checked_sub(earlier.ticks)), + } + } + + /// Duration between this Instant and another Instant + pub fn checked_duration_since(&self, earlier: Instant) -> Option { + if self.ticks < earlier.ticks { + None + } else { + Some(Duration { + ticks: self.ticks - earlier.ticks, + }) + } + } + + /// Returns the duration since the "earlier" Instant. + /// If the "earlier" instant is in the future, the duration is set to zero. + pub fn saturating_duration_since(&self, earlier: Instant) -> Duration { + Duration { + ticks: if self.ticks < earlier.ticks { + 0 + } else { + self.ticks - earlier.ticks + }, + } + } + + /// Duration elapsed since this Instant. + pub fn elapsed(&self) -> Duration { + Instant::now() - *self + } + + /// Adds one Duration to self, returning a new `Instant` or None in the event of an overflow. + pub fn checked_add(&self, duration: Duration) -> Option { + self.ticks.checked_add(duration.ticks).map(|ticks| Instant { ticks }) + } + + /// Subtracts one Duration to self, returning a new `Instant` or None in the event of an overflow. + pub fn checked_sub(&self, duration: Duration) -> Option { + self.ticks.checked_sub(duration.ticks).map(|ticks| Instant { ticks }) + } +} + +impl Add for Instant { + type Output = Instant; + + fn add(self, other: Duration) -> Instant { + self.checked_add(other) + .expect("overflow when adding duration to instant") + } +} + +impl AddAssign for Instant { + fn add_assign(&mut self, other: Duration) { + *self = *self + other; + } +} + +impl Sub for Instant { + type Output = Instant; + + fn sub(self, other: Duration) -> Instant { + self.checked_sub(other) + .expect("overflow when subtracting duration from instant") + } +} + +impl SubAssign for Instant { + fn sub_assign(&mut self, other: Duration) { + *self = *self - other; + } +} + +impl Sub for Instant { + type Output = Duration; + + fn sub(self, other: Instant) -> Duration { + self.duration_since(other) + } +} + +impl<'a> fmt::Display for Instant { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} ticks", self.ticks) + } +} diff --git a/embassy/embassy-time/src/lib.rs b/embassy/embassy-time/src/lib.rs new file mode 100644 index 0000000..80a3594 --- /dev/null +++ b/embassy/embassy-time/src/lib.rs @@ -0,0 +1,63 @@ +#![cfg_attr(not(any(feature = "std", feature = "wasm", test)), no_std)] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![allow(clippy::new_without_default)] +#![warn(missing_docs)] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +mod delay; +mod duration; +mod instant; +mod timer; + +#[cfg(feature = "mock-driver")] +mod driver_mock; + +#[cfg(feature = "mock-driver")] +pub use driver_mock::MockDriver; + +#[cfg(feature = "std")] +mod driver_std; +#[cfg(feature = "wasm")] +mod driver_wasm; + +pub use delay::{block_for, Delay}; +pub use duration::Duration; +pub use embassy_time_driver::TICK_HZ; +pub use instant::Instant; +pub use timer::{with_deadline, with_timeout, Ticker, TimeoutError, Timer, WithTimeout}; + +const fn gcd(a: u64, b: u64) -> u64 { + if b == 0 { + a + } else { + gcd(b, a % b) + } +} + +pub(crate) const GCD_1K: u64 = gcd(TICK_HZ, 1_000); +pub(crate) const GCD_1M: u64 = gcd(TICK_HZ, 1_000_000); +pub(crate) const GCD_1G: u64 = gcd(TICK_HZ, 1_000_000_000); + +#[cfg(feature = "defmt-timestamp-uptime-s")] +defmt::timestamp! {"{=u64}", Instant::now().as_secs() } + +#[cfg(feature = "defmt-timestamp-uptime-ms")] +defmt::timestamp! {"{=u64:ms}", Instant::now().as_millis() } + +#[cfg(any(feature = "defmt-timestamp-uptime", feature = "defmt-timestamp-uptime-us"))] +defmt::timestamp! {"{=u64:us}", Instant::now().as_micros() } + +#[cfg(feature = "defmt-timestamp-uptime-ts")] +defmt::timestamp! {"{=u64:ts}", Instant::now().as_secs() } + +#[cfg(feature = "defmt-timestamp-uptime-tms")] +defmt::timestamp! {"{=u64:tms}", Instant::now().as_millis() } + +#[cfg(feature = "defmt-timestamp-uptime-tus")] +defmt::timestamp! {"{=u64:tus}", Instant::now().as_micros() } diff --git a/embassy/embassy-time/src/timer.rs b/embassy/embassy-time/src/timer.rs new file mode 100644 index 0000000..295ddbd --- /dev/null +++ b/embassy/embassy-time/src/timer.rs @@ -0,0 +1,269 @@ +use core::future::{poll_fn, Future}; +use core::pin::{pin, Pin}; +use core::task::{Context, Poll}; + +use futures_util::future::{select, Either}; +use futures_util::stream::FusedStream; +use futures_util::Stream; + +use crate::{Duration, Instant}; + +/// Error returned by [`with_timeout`] and [`with_deadline`] on timeout. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TimeoutError; + +/// Runs a given future with a timeout. +/// +/// If the future completes before the timeout, its output is returned. Otherwise, on timeout, +/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. +pub async fn with_timeout(timeout: Duration, fut: F) -> Result { + let timeout_fut = Timer::after(timeout); + match select(pin!(fut), timeout_fut).await { + Either::Left((r, _)) => Ok(r), + Either::Right(_) => Err(TimeoutError), + } +} + +/// Runs a given future with a deadline time. +/// +/// If the future completes before the deadline, its output is returned. Otherwise, on timeout, +/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. +pub async fn with_deadline(at: Instant, fut: F) -> Result { + let timeout_fut = Timer::at(at); + match select(pin!(fut), timeout_fut).await { + Either::Left((r, _)) => Ok(r), + Either::Right(_) => Err(TimeoutError), + } +} + +/// Provides functions to run a given future with a timeout or a deadline. +pub trait WithTimeout { + /// Output type of the future. + type Output; + + /// Runs a given future with a timeout. + /// + /// If the future completes before the timeout, its output is returned. Otherwise, on timeout, + /// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. + async fn with_timeout(self, timeout: Duration) -> Result; + + /// Runs a given future with a deadline time. + /// + /// If the future completes before the deadline, its output is returned. Otherwise, on timeout, + /// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. + async fn with_deadline(self, at: Instant) -> Result; +} + +impl WithTimeout for F { + type Output = F::Output; + + async fn with_timeout(self, timeout: Duration) -> Result { + with_timeout(timeout, self).await + } + + async fn with_deadline(self, at: Instant) -> Result { + with_deadline(at, self).await + } +} + +/// A future that completes at a specified [Instant](struct.Instant.html). +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Timer { + expires_at: Instant, + yielded_once: bool, +} + +impl Timer { + /// Expire at specified [Instant](struct.Instant.html) + pub fn at(expires_at: Instant) -> Self { + Self { + expires_at, + yielded_once: false, + } + } + + /// Expire after specified [Duration](struct.Duration.html). + /// This can be used as a `sleep` abstraction. + /// + /// Example: + /// ``` no_run + /// use embassy_time::{Duration, Timer}; + /// + /// #[embassy_executor::task] + /// async fn demo_sleep_seconds() { + /// // suspend this task for one second. + /// Timer::after(Duration::from_secs(1)).await; + /// } + /// ``` + pub fn after(duration: Duration) -> Self { + Self { + expires_at: Instant::now() + duration, + yielded_once: false, + } + } + + /// Expire after the specified number of ticks. + /// + /// This method is a convenience wrapper for calling `Timer::after(Duration::from_ticks())`. + /// For more details, refer to [`Timer::after()`] and [`Duration::from_ticks()`]. + #[inline] + pub fn after_ticks(ticks: u64) -> Self { + Self::after(Duration::from_ticks(ticks)) + } + + /// Expire after the specified number of nanoseconds. + /// + /// This method is a convenience wrapper for calling `Timer::after(Duration::from_nanos())`. + /// For more details, refer to [`Timer::after()`] and [`Duration::from_nanos()`]. + #[inline] + pub fn after_nanos(nanos: u64) -> Self { + Self::after(Duration::from_nanos(nanos)) + } + + /// Expire after the specified number of microseconds. + /// + /// This method is a convenience wrapper for calling `Timer::after(Duration::from_micros())`. + /// For more details, refer to [`Timer::after()`] and [`Duration::from_micros()`]. + #[inline] + pub fn after_micros(micros: u64) -> Self { + Self::after(Duration::from_micros(micros)) + } + + /// Expire after the specified number of milliseconds. + /// + /// This method is a convenience wrapper for calling `Timer::after(Duration::from_millis())`. + /// For more details, refer to [`Timer::after`] and [`Duration::from_millis()`]. + #[inline] + pub fn after_millis(millis: u64) -> Self { + Self::after(Duration::from_millis(millis)) + } + + /// Expire after the specified number of seconds. + /// + /// This method is a convenience wrapper for calling `Timer::after(Duration::from_secs())`. + /// For more details, refer to [`Timer::after`] and [`Duration::from_secs()`]. + #[inline] + pub fn after_secs(secs: u64) -> Self { + Self::after(Duration::from_secs(secs)) + } +} + +impl Unpin for Timer {} + +impl Future for Timer { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.yielded_once && self.expires_at <= Instant::now() { + Poll::Ready(()) + } else { + embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); + self.yielded_once = true; + Poll::Pending + } + } +} + +/// Asynchronous stream that yields every Duration, indefinitely. +/// +/// This stream will tick at uniform intervals, even if blocking work is performed between ticks. +/// +/// For instance, consider the following code fragment. +/// ``` no_run +/// use embassy_time::{Duration, Timer}; +/// # fn foo() {} +/// +/// #[embassy_executor::task] +/// async fn ticker_example_0() { +/// loop { +/// foo(); +/// Timer::after(Duration::from_secs(1)).await; +/// } +/// } +/// ``` +/// +/// This fragment will not call `foo` every second. +/// Instead, it will call it every second + the time it took to previously call `foo`. +/// +/// Example using ticker, which will consistently call `foo` once a second. +/// +/// ``` no_run +/// use embassy_time::{Duration, Ticker}; +/// # fn foo(){} +/// +/// #[embassy_executor::task] +/// async fn ticker_example_1() { +/// let mut ticker = Ticker::every(Duration::from_secs(1)); +/// loop { +/// foo(); +/// ticker.next().await; +/// } +/// } +/// ``` +pub struct Ticker { + expires_at: Instant, + duration: Duration, +} + +impl Ticker { + /// Creates a new ticker that ticks at the specified duration interval. + pub fn every(duration: Duration) -> Self { + let expires_at = Instant::now() + duration; + Self { expires_at, duration } + } + + /// Resets the ticker back to its original state. + /// This causes the ticker to go back to zero, even if the current tick isn't over yet. + pub fn reset(&mut self) { + self.expires_at = Instant::now() + self.duration; + } + + /// Reset the ticker at the deadline. + /// If the deadline is in the past, the ticker will fire instantly. + pub fn reset_at(&mut self, deadline: Instant) { + self.expires_at = deadline + self.duration; + } + + /// Resets the ticker, after the specified duration has passed. + /// If the specified duration is zero, the next tick will be after the duration of the ticker. + pub fn reset_after(&mut self, after: Duration) { + self.expires_at = Instant::now() + after + self.duration; + } + + /// Waits for the next tick. + pub fn next(&mut self) -> impl Future + Send + Sync + '_ { + poll_fn(|cx| { + if self.expires_at <= Instant::now() { + let dur = self.duration; + self.expires_at += dur; + Poll::Ready(()) + } else { + embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); + Poll::Pending + } + }) + } +} + +impl Unpin for Ticker {} + +impl Stream for Ticker { + type Item = (); + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if self.expires_at <= Instant::now() { + let dur = self.duration; + self.expires_at += dur; + Poll::Ready(Some(())) + } else { + embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); + Poll::Pending + } + } +} + +impl FusedStream for Ticker { + fn is_terminated(&self) -> bool { + // `Ticker` keeps yielding values until dropped, it never terminates. + false + } +} diff --git a/embassy/embassy-usb-dfu/Cargo.toml b/embassy/embassy-usb-dfu/Cargo.toml new file mode 100644 index 0000000..763c960 --- /dev/null +++ b/embassy/embassy-usb-dfu/Cargo.toml @@ -0,0 +1,45 @@ +[package] +edition = "2021" +name = "embassy-usb-dfu" +version = "0.1.0" +description = "An implementation of the USB DFU 1.1 protocol, using embassy-boot" +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-usb-dfu" +categories = [ + "embedded", + "no-std", + "asynchronous" +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-v$VERSION/embassy-usb-dfu/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-dfu/src/" +features = ["defmt", "cortex-m"] +target = "thumbv7em-none-eabi" +flavors = [ + { name = "dfu", features = [ "dfu" ] }, + { name = "application", features = [ "application" ] }, +] + +[package.metadata.docs.rs] +features = ["defmt", "cortex-m", "dfu"] + +[dependencies] +defmt = { version = "0.3.5", optional = true } +log = { version = "0.4.17", optional = true } + +bitflags = "2.4.1" +cortex-m = { version = "0.7.7", features = ["inline-asm"], optional = true } +embassy-boot = { version = "0.3.0", path = "../embassy-boot" } +embassy-futures = { version = "0.1.1", path = "../embassy-futures" } +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-usb = { version = "0.3.0", path = "../embassy-usb", default-features = false } +embedded-storage = { version = "0.3.1" } +esp32c3-hal = { version = "0.13.0", optional = true, default-features = false } + +[features] +dfu = [] +application = [] +defmt = ["dep:defmt", "embassy-boot/defmt", "embassy-usb/defmt"] diff --git a/embassy/embassy-usb-dfu/README.md b/embassy/embassy-usb-dfu/README.md new file mode 100644 index 0000000..bdd5b03 --- /dev/null +++ b/embassy/embassy-usb-dfu/README.md @@ -0,0 +1,6 @@ +# embassy-usb-dfu + +An implementation of the USB DFU 1.1 protocol using embassy-boot. It has 2 components depending on which feature is enabled by the user. + +* DFU protocol mode, enabled by the `dfu` feature. This mode corresponds to the transfer phase DFU protocol described by the USB IF. It supports DFU_DNLOAD requests if marked by the user, and will automatically reset the chip once a DFU transaction has been completed. It also responds to DFU_GETSTATUS, DFU_GETSTATE, DFU_ABORT, and DFU_CLRSTATUS with no user intervention. +* DFU runtime mode, enabled by the `application feature`. This mode allows users to expose a DFU interface on their USB device, informing the host of the capability to DFU over USB, and allowing the host to reset the device into its bootloader to complete a DFU operation. Supports DFU_GETSTATUS and DFU_DETACH. When detach/reset is seen by the device as described by the standard, will write a new DFU magic number into the bootloader state in flash, and reset the system. diff --git a/embassy/embassy-usb-dfu/src/application.rs b/embassy/embassy-usb-dfu/src/application.rs new file mode 100644 index 0000000..f0d7626 --- /dev/null +++ b/embassy/embassy-usb-dfu/src/application.rs @@ -0,0 +1,136 @@ +use core::marker::PhantomData; + +use embassy_boot::BlockingFirmwareState; +use embassy_time::{Duration, Instant}; +use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; +use embassy_usb::driver::Driver; +use embassy_usb::{Builder, Handler}; +use embedded_storage::nor_flash::NorFlash; + +use crate::consts::{ + DfuAttributes, Request, State, Status, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_RT, + USB_CLASS_APPN_SPEC, +}; +use crate::Reset; + +/// Internal state for the DFU class +pub struct Control<'d, STATE: NorFlash, RST: Reset> { + firmware_state: BlockingFirmwareState<'d, STATE>, + attrs: DfuAttributes, + state: State, + timeout: Option, + detach_start: Option, + _rst: PhantomData, +} + +impl<'d, STATE: NorFlash, RST: Reset> Control<'d, STATE, RST> { + /// Create a new DFU instance to expose a DFU interface. + pub fn new(firmware_state: BlockingFirmwareState<'d, STATE>, attrs: DfuAttributes) -> Self { + Control { + firmware_state, + attrs, + state: State::AppIdle, + detach_start: None, + timeout: None, + _rst: PhantomData, + } + } +} + +impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> { + fn reset(&mut self) { + if let Some(start) = self.detach_start { + let delta = Instant::now() - start; + let timeout = self.timeout.unwrap(); + trace!( + "Received RESET with delta = {}, timeout = {}", + delta.as_millis(), + timeout.as_millis() + ); + if delta < timeout { + self.firmware_state + .mark_dfu() + .expect("Failed to mark DFU mode in bootloader"); + RST::sys_reset() + } + } + } + + fn control_out( + &mut self, + req: embassy_usb::control::Request, + _: &[u8], + ) -> Option { + if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { + return None; + } + + trace!("Received request {}", req); + + match Request::try_from(req.request) { + Ok(Request::Detach) => { + trace!("Received DETACH, awaiting USB reset"); + self.detach_start = Some(Instant::now()); + self.timeout = Some(Duration::from_millis(req.value as u64)); + self.state = State::AppDetach; + Some(OutResponse::Accepted) + } + _ => None, + } + } + + fn control_in<'a>( + &'a mut self, + req: embassy_usb::control::Request, + buf: &'a mut [u8], + ) -> Option> { + if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { + return None; + } + + trace!("Received request {}", req); + + match Request::try_from(req.request) { + Ok(Request::GetStatus) => { + buf[0..6].copy_from_slice(&[Status::Ok as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]); + Some(InResponse::Accepted(buf)) + } + _ => None, + } + } +} + +/// An implementation of the USB DFU 1.1 runtime protocol +/// +/// This function will add a DFU interface descriptor to the provided Builder, and register the provided Control as a handler for the USB device. The USB builder can be used as normal once this is complete. +/// The handler is responsive to DFU GetStatus and Detach commands. +/// +/// Once a detach command, followed by a USB reset is received by the host, a magic number will be written into the bootloader state partition to indicate that +/// it should expose a DFU device, and a software reset will be issued. +/// +/// To apply USB DFU updates, the bootloader must be capable of recognizing the DFU magic and exposing a device to handle the full DFU transaction with the host. +pub fn usb_dfu<'d, D: Driver<'d>, STATE: NorFlash, RST: Reset>( + builder: &mut Builder<'d, D>, + handler: &'d mut Control<'d, STATE, RST>, + timeout: Duration, +) { + let mut func = builder.function(0x00, 0x00, 0x00); + let mut iface = func.interface(); + let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_RT, None); + let timeout = timeout.as_millis() as u16; + alt.descriptor( + DESC_DFU_FUNCTIONAL, + &[ + handler.attrs.bits(), + (timeout & 0xff) as u8, + ((timeout >> 8) & 0xff) as u8, + 0x40, + 0x00, // 64B control buffer size for application side + 0x10, + 0x01, // DFU 1.1 + ], + ); + + drop(func); + builder.handler(handler); +} diff --git a/embassy/embassy-usb-dfu/src/consts.rs b/embassy/embassy-usb-dfu/src/consts.rs new file mode 100644 index 0000000..190b5ad --- /dev/null +++ b/embassy/embassy-usb-dfu/src/consts.rs @@ -0,0 +1,100 @@ +//! USB DFU constants. +pub(crate) const USB_CLASS_APPN_SPEC: u8 = 0xFE; +pub(crate) const APPN_SPEC_SUBCLASS_DFU: u8 = 0x01; +#[allow(unused)] +pub(crate) const DFU_PROTOCOL_DFU: u8 = 0x02; +#[allow(unused)] +pub(crate) const DFU_PROTOCOL_RT: u8 = 0x01; +pub(crate) const DESC_DFU_FUNCTIONAL: u8 = 0x21; + +macro_rules! define_dfu_attributes { + ($macro:path) => { + $macro! { + /// Attributes supported by the DFU controller. + pub struct DfuAttributes: u8 { + /// Generate WillDetache sequence on bus. + const WILL_DETACH = 0b0000_1000; + /// Device can communicate during manifestation phase. + const MANIFESTATION_TOLERANT = 0b0000_0100; + /// Capable of upload. + const CAN_UPLOAD = 0b0000_0010; + /// Capable of download. + const CAN_DOWNLOAD = 0b0000_0001; + } + } + }; +} + +#[cfg(feature = "defmt")] +define_dfu_attributes!(defmt::bitflags); + +#[cfg(not(feature = "defmt"))] +define_dfu_attributes!(bitflags::bitflags); + +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +#[allow(unused)] +pub(crate) enum State { + AppIdle = 0, + AppDetach = 1, + DfuIdle = 2, + DlSync = 3, + DlBusy = 4, + Download = 5, + ManifestSync = 6, + Manifest = 7, + ManifestWaitReset = 8, + UploadIdle = 9, + Error = 10, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +#[allow(unused)] +pub(crate) enum Status { + Ok = 0x00, + ErrTarget = 0x01, + ErrFile = 0x02, + ErrWrite = 0x03, + ErrErase = 0x04, + ErrCheckErased = 0x05, + ErrProg = 0x06, + ErrVerify = 0x07, + ErrAddress = 0x08, + ErrNotDone = 0x09, + ErrFirmware = 0x0A, + ErrVendor = 0x0B, + ErrUsbr = 0x0C, + ErrPor = 0x0D, + ErrUnknown = 0x0E, + ErrStalledPkt = 0x0F, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub(crate) enum Request { + Detach = 0, + Dnload = 1, + Upload = 2, + GetStatus = 3, + ClrStatus = 4, + GetState = 5, + Abort = 6, +} + +impl TryFrom for Request { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Request::Detach), + 1 => Ok(Request::Dnload), + 2 => Ok(Request::Upload), + 3 => Ok(Request::GetStatus), + 4 => Ok(Request::ClrStatus), + 5 => Ok(Request::GetState), + 6 => Ok(Request::Abort), + _ => Err(()), + } + } +} diff --git a/embassy/embassy-usb-dfu/src/dfu.rs b/embassy/embassy-usb-dfu/src/dfu.rs new file mode 100644 index 0000000..c23286c --- /dev/null +++ b/embassy/embassy-usb-dfu/src/dfu.rs @@ -0,0 +1,210 @@ +use core::marker::PhantomData; + +use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterError}; +use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; +use embassy_usb::driver::Driver; +use embassy_usb::{Builder, Handler}; +use embedded_storage::nor_flash::{NorFlash, NorFlashErrorKind}; + +use crate::consts::{ + DfuAttributes, Request, State, Status, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_DFU, + USB_CLASS_APPN_SPEC, +}; +use crate::Reset; + +/// Internal state for USB DFU +pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> { + updater: BlockingFirmwareUpdater<'d, DFU, STATE>, + attrs: DfuAttributes, + state: State, + status: Status, + offset: usize, + buf: AlignedBuffer, + _rst: PhantomData, +} + +impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, RST, BLOCK_SIZE> { + /// Create a new DFU instance to handle DFU transfers. + pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self { + Self { + updater, + attrs, + state: State::DfuIdle, + status: Status::Ok, + offset: 0, + buf: AlignedBuffer([0; BLOCK_SIZE]), + _rst: PhantomData, + } + } + + fn reset_state(&mut self) { + self.offset = 0; + self.state = State::DfuIdle; + self.status = Status::Ok; + } +} + +impl From for Status { + fn from(e: FirmwareUpdaterError) -> Self { + match e { + FirmwareUpdaterError::Flash(e) => match e { + NorFlashErrorKind::NotAligned => Status::ErrWrite, + NorFlashErrorKind::OutOfBounds => Status::ErrAddress, + _ => Status::ErrUnknown, + }, + FirmwareUpdaterError::Signature(_) => Status::ErrVerify, + FirmwareUpdaterError::BadState => Status::ErrUnknown, + } + } +} + +impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Handler + for Control<'d, DFU, STATE, RST, BLOCK_SIZE> +{ + fn control_out( + &mut self, + req: embassy_usb::control::Request, + data: &[u8], + ) -> Option { + if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { + debug!("Unknown out request: {:?}", req); + return None; + } + match Request::try_from(req.request) { + Ok(Request::Abort) => { + info!("Abort requested"); + self.reset_state(); + Some(OutResponse::Accepted) + } + Ok(Request::Dnload) if self.attrs.contains(DfuAttributes::CAN_DOWNLOAD) => { + if req.value == 0 { + info!("Download starting"); + self.state = State::Download; + self.offset = 0; + } + + if self.state != State::Download { + error!("Unexpected DNLOAD while chip is waiting for a GETSTATUS"); + self.status = Status::ErrUnknown; + self.state = State::Error; + return Some(OutResponse::Rejected); + } + + if data.len() > BLOCK_SIZE { + error!("USB data len exceeded block size"); + self.status = Status::ErrUnknown; + self.state = State::Error; + return Some(OutResponse::Rejected); + } + + debug!("Copying {} bytes to buffer", data.len()); + self.buf.as_mut()[..data.len()].copy_from_slice(data); + + let final_transfer = req.length == 0; + if final_transfer { + debug!("Receiving final transfer"); + + match self.updater.mark_updated() { + Ok(_) => { + self.status = Status::Ok; + self.state = State::ManifestSync; + info!("Update complete"); + } + Err(e) => { + error!("Error completing update: {}", e); + self.state = State::Error; + self.status = e.into(); + } + } + } else { + debug!("Writing {} bytes at {}", data.len(), self.offset); + match self.updater.write_firmware(self.offset, self.buf.as_ref()) { + Ok(_) => { + self.status = Status::Ok; + self.state = State::DlSync; + self.offset += data.len(); + } + Err(e) => { + error!("Error writing firmware: {:?}", e); + self.state = State::Error; + self.status = e.into(); + } + } + } + + Some(OutResponse::Accepted) + } + Ok(Request::Detach) => Some(OutResponse::Accepted), // Device is already in DFU mode + Ok(Request::ClrStatus) => { + info!("Clear status requested"); + self.reset_state(); + Some(OutResponse::Accepted) + } + _ => None, + } + } + + fn control_in<'a>( + &'a mut self, + req: embassy_usb::control::Request, + buf: &'a mut [u8], + ) -> Option> { + if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { + debug!("Unknown in request: {:?}", req); + return None; + } + match Request::try_from(req.request) { + Ok(Request::GetStatus) => { + //TODO: Configurable poll timeout, ability to add string for Vendor error + buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]); + match self.state { + State::DlSync => self.state = State::Download, + State::ManifestSync => RST::sys_reset(), + _ => {} + } + + Some(InResponse::Accepted(&buf[0..6])) + } + Ok(Request::GetState) => { + buf[0] = self.state as u8; + Some(InResponse::Accepted(&buf[0..1])) + } + Ok(Request::Upload) if self.attrs.contains(DfuAttributes::CAN_UPLOAD) => { + //TODO: FirmwareUpdater does not provide a way of reading the active partition, can't upload. + Some(InResponse::Rejected) + } + _ => None, + } + } +} + +/// An implementation of the USB DFU 1.1 protocol +/// +/// This function will add a DFU interface descriptor to the provided Builder, and register the provided Control as a handler for the USB device +/// The handler is responsive to DFU GetState, GetStatus, Abort, and ClrStatus commands, as well as Download if configured by the user. +/// +/// Once the host has initiated a DFU download operation, the chunks sent by the host will be written to the DFU partition. +/// Once the final sync in the manifestation phase has been received, the handler will trigger a system reset to swap the new firmware. +pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize>( + builder: &mut Builder<'d, D>, + handler: &'d mut Control<'d, DFU, STATE, RST, BLOCK_SIZE>, +) { + let mut func = builder.function(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU); + let mut iface = func.interface(); + let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, None); + alt.descriptor( + DESC_DFU_FUNCTIONAL, + &[ + handler.attrs.bits(), + 0xc4, + 0x09, // 2500ms timeout, doesn't affect operation as DETACH not necessary in bootloader code + (BLOCK_SIZE & 0xff) as u8, + ((BLOCK_SIZE & 0xff00) >> 8) as u8, + 0x10, + 0x01, // DFU 1.1 + ], + ); + + drop(func); + builder.handler(handler); +} diff --git a/embassy/embassy-usb-dfu/src/fmt.rs b/embassy/embassy-usb-dfu/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-usb-dfu/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-usb-dfu/src/lib.rs b/embassy/embassy-usb-dfu/src/lib.rs new file mode 100644 index 0000000..eaa4b6e --- /dev/null +++ b/embassy/embassy-usb-dfu/src/lib.rs @@ -0,0 +1,56 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] +mod fmt; + +pub mod consts; + +#[cfg(feature = "dfu")] +mod dfu; +#[cfg(feature = "dfu")] +pub use self::dfu::*; + +#[cfg(feature = "application")] +mod application; +#[cfg(feature = "application")] +pub use self::application::*; + +#[cfg(any( + all(feature = "dfu", feature = "application"), + not(any(feature = "dfu", feature = "application")) +))] +compile_error!("usb-dfu must be compiled with exactly one of `dfu`, or `application` features"); + +/// Provides a platform-agnostic interface for initiating a system reset. +/// +/// This crate exposes `ResetImmediate` when compiled with cortex-m or esp32c3 support, which immediately issues a +/// reset request without interfacing with any other peripherals. +/// +/// If alternate behaviour is desired, a custom implementation of Reset can be provided as a type argument to the usb_dfu function. +pub trait Reset { + /// Reset the device. + fn sys_reset() -> !; +} + +/// Reset immediately. +#[cfg(feature = "esp32c3-hal")] +pub struct ResetImmediate; + +#[cfg(feature = "esp32c3-hal")] +impl Reset for ResetImmediate { + fn sys_reset() -> ! { + esp32c3_hal::reset::software_reset(); + loop {} + } +} + +/// Reset immediately. +#[cfg(feature = "cortex-m")] +pub struct ResetImmediate; + +#[cfg(feature = "cortex-m")] +impl Reset for ResetImmediate { + fn sys_reset() -> ! { + cortex_m::peripheral::SCB::sys_reset() + } +} diff --git a/embassy/embassy-usb-driver/Cargo.toml b/embassy/embassy-usb-driver/Cargo.toml new file mode 100644 index 0000000..41493f0 --- /dev/null +++ b/embassy/embassy-usb-driver/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "embassy-usb-driver" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Driver trait for `embassy-usb`, an async USB device stack for embedded devices." +keywords = ["embedded", "async", "usb", "hal", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-usb-driver" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-driver-v$VERSION/embassy-usb-driver/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-driver/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt"] + +[dependencies] +defmt = { version = "0.3", optional = true } diff --git a/embassy/embassy-usb-driver/README.md b/embassy/embassy-usb-driver/README.md new file mode 100644 index 0000000..7628a69 --- /dev/null +++ b/embassy/embassy-usb-driver/README.md @@ -0,0 +1,17 @@ +# 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 this 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. diff --git a/embassy/embassy-usb-driver/src/lib.rs b/embassy/embassy-usb-driver/src/lib.rs new file mode 100644 index 0000000..3b705c8 --- /dev/null +++ b/embassy/embassy-usb-driver/src/lib.rs @@ -0,0 +1,397 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![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 +/// for consistency. +/// +/// The values of the enum also match the direction bit used in endpoint addresses and control +/// request types. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Direction { + /// Host to device (OUT) + Out, + /// Device to host (IN) + In, +} + +/// USB endpoint transfer type. The values of this enum can be directly cast into `u8` to get the +/// transfer bmAttributes transfer type bits. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EndpointType { + /// Control endpoint. Used for device management. Only the host can initiate requests. Usually + /// used only endpoint 0. + Control = 0b00, + /// Isochronous endpoint. Used for time-critical unreliable data. Not implemented yet. + Isochronous = 0b01, + /// Bulk endpoint. Used for large amounts of best-effort reliable data. + Bulk = 0b10, + /// Interrupt endpoint. Used for small amounts of time-critical reliable data. + Interrupt = 0b11, +} + +/// Type-safe endpoint address. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointAddress(u8); + +impl From for EndpointAddress { + #[inline] + fn from(addr: u8) -> EndpointAddress { + EndpointAddress(addr) + } +} + +impl From for u8 { + #[inline] + fn from(addr: EndpointAddress) -> u8 { + addr.0 + } +} + +impl EndpointAddress { + const INBITS: u8 = 0x80; + + /// Constructs a new EndpointAddress with the given index and direction. + #[inline] + pub fn from_parts(index: usize, dir: Direction) -> Self { + let dir_u8 = match dir { + Direction::Out => 0x00, + Direction::In => Self::INBITS, + }; + EndpointAddress(index as u8 | dir_u8) + } + + /// Gets the direction part of the address. + #[inline] + pub fn direction(&self) -> Direction { + if (self.0 & Self::INBITS) != 0 { + Direction::In + } else { + Direction::Out + } + } + + /// Returns true if the direction is IN, otherwise false. + #[inline] + pub fn is_in(&self) -> bool { + (self.0 & Self::INBITS) != 0 + } + + /// Returns true if the direction is OUT, otherwise false. + #[inline] + pub fn is_out(&self) -> bool { + (self.0 & Self::INBITS) == 0 + } + + /// Gets the index part of the endpoint address. + #[inline] + pub fn index(&self) -> usize { + (self.0 & !Self::INBITS) as usize + } +} + +/// Information 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, + /// Polling interval, in milliseconds. + pub interval_ms: u8, +} + +/// 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 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_type` - the endpoint's type. + /// * `max_packet_size` - Maximum packet size in bytes. + /// * `interval_ms` - Polling interval parameter for interrupt endpoints. + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + 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_ms: u8, + ) -> Result; + + /// Start operation of the USB device. + /// + /// This returns the `Bus` and `ControlPipe` instances that are used to operate + /// the USB device. Additionally, this makes all the previously allocated endpoints + /// start operating. + /// + /// 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); +} + +/// 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 { + /// Enable the USB peripheral. + async fn enable(&mut self); + + /// Disable and powers down the USB peripheral. + async fn disable(&mut self); + + /// 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; + + /// Enable or disable an endpoint. + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool); + + /// 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); + + /// Get whether the STALL condition is set for an endpoint. + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool; + + /// 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::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) + } + + /// Initiate a remote wakeup of the host by the device. + /// + /// # Errors + /// + /// * [`Unsupported`](crate::Unsupported) - This UsbBus implementation doesn't support + /// remote wakeup or it has not been enabled at creation time. + async fn remote_wakeup(&mut self) -> Result<(), Unsupported>; +} + +/// Endpoint trait, common for OUT and IN. +pub trait Endpoint { + /// Get the endpoint address + fn info(&self) -> &EndpointInfo; + + /// Wait for the endpoint to be enabled. + async fn wait_enabled(&mut self); +} + +/// OUT Endpoint trait. +pub trait EndpointOut: Endpoint { + /// 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. + 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 { + /// Maximum packet size for the control pipe + fn max_packet_size(&self) -> usize; + + /// Read a single setup packet from the endpoint. + async fn setup(&mut self) -> [u8; 8]; + + /// 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. + async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result; + + /// 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`. + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError>; + + /// Accept a control request. + /// + /// Causes the STATUS packet for the current request to be ACKed. + async fn accept(&mut self); + + /// Reject a control request. + /// + /// Sets a STALL condition on the pipe to indicate an error. + 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 { + /// Write a single packet of data to the endpoint. + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError>; +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Event returned by [`Bus::poll`]. +pub enum Event { + /// The USB reset condition has been detected. + Reset, + + /// A USB suspend request has been detected or, in the case of self-powered devices, the device + /// has been disconnected from the USB bus. + Suspend, + + /// A USB resume request has been detected after being suspended or, in the case of self-powered + /// devices, the device has been connected to the USB bus. + Resume, + + /// The USB power has been detected. + PowerDetected, + + /// The USB power has been removed. Not supported by all devices. + 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))] +pub struct Unsupported; + +/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +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`. + BufferOverflow, + + /// The endpoint is disabled. + Disabled, +} diff --git a/embassy/embassy-usb-logger/CHANGELOG.md b/embassy/embassy-usb-logger/CHANGELOG.md new file mode 100644 index 0000000..4cd84b8 --- /dev/null +++ b/embassy/embassy-usb-logger/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-05-20 + +### Added + +- [#2414](https://github.com/embassy-rs/embassy/pull/2414) USB logger can now use an existing USB device (@JomerDev) + +### Changed + +- Update `embassy-usb` to 0.2.0 + +### Fixed + +- No more data loss at `Pipe` wraparound +- [#2414](https://github.com/embassy-rs/embassy/pull/2414) Messages that are exactly `MAX_PACKET_SIZE` long are no +longer delayed (@JomerDev) + +## 0.1.0 - 2024-01-14 + +- Initial Release diff --git a/embassy/embassy-usb-logger/Cargo.toml b/embassy/embassy-usb-logger/Cargo.toml new file mode 100644 index 0000000..d5147de --- /dev/null +++ b/embassy/embassy-usb-logger/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "embassy-usb-logger" +version = "0.2.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "`log` implementation for USB serial using `embassy-usb`." +keywords = ["embedded", "log", "usb", "hal", "serial"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-usb-logger" + +[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.3.0", path = "../embassy-usb" } +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +log = "0.4" diff --git a/embassy/embassy-usb-logger/README.md b/embassy/embassy-usb-logger/README.md new file mode 100644 index 0000000..81b0dcd --- /dev/null +++ b/embassy/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/embassy-usb-logger/src/lib.rs b/embassy/embassy-usb-logger/src/lib.rs new file mode 100644 index 0000000..29c102f --- /dev/null +++ b/embassy/embassy-usb-logger/src/lib.rs @@ -0,0 +1,325 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +use core::fmt::Write as _; +use core::future::Future; + +use embassy_futures::join::join; +use embassy_sync::pipe::Pipe; +use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; +use embassy_usb::driver::Driver; +use embassy_usb::{Builder, Config}; +use log::{Metadata, Record}; + +type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; + +/// A trait that can be implemented and then passed to the +pub trait ReceiverHandler { + /// Data comes in from the serial port with each command and runs this function + fn handle_data(&self, data: &[u8]) -> impl Future + Send; + + /// Create a new instance of the Handler + fn new() -> Self; +} + +/// Use this Handler if you don't wish to use any handler +pub struct DummyHandler; + +impl ReceiverHandler for DummyHandler { + async fn handle_data(&self, _data: &[u8]) {} + fn new() -> Self { + Self {} + } +} + +/// The logger state containing buffers that must live as long as the USB peripheral. +pub struct LoggerState<'d> { + state: State<'d>, + config_descriptor: [u8; 128], + bos_descriptor: [u8; 16], + msos_descriptor: [u8; 256], + control_buf: [u8; 64], +} + +impl<'d> LoggerState<'d> { + /// Create a new instance of the logger state. + pub fn new() -> Self { + Self { + state: State::new(), + config_descriptor: [0; 128], + bos_descriptor: [0; 16], + msos_descriptor: [0; 256], + control_buf: [0; 64], + } + } +} + +/// The packet size used in the usb logger, to be used with `create_future_from_class` +pub const MAX_PACKET_SIZE: u8 = 64; + +/// The logger handle, which contains a pipe with configurable size for buffering log messages. +pub struct UsbLogger { + buffer: Pipe, + custom_style: Option) -> ()>, + recieve_handler: Option, +} + +impl UsbLogger { + /// Create a new logger instance. + pub const fn new() -> Self { + Self { + buffer: Pipe::new(), + custom_style: None, + recieve_handler: None, + } + } + + /// Create a new logger instance with a custom formatter. + pub const fn with_custom_style(custom_style: fn(&Record, &mut Writer<'_, N>) -> ()) -> Self { + Self { + buffer: Pipe::new(), + custom_style: Some(custom_style), + recieve_handler: None, + } + } + + /// Add a command handler to the logger + pub fn with_handler(&mut self, handler: T) { + self.recieve_handler = Some(handler); + } + + /// Run the USB logger using the state and USB driver. Never returns. + pub async fn run<'d, D>(&'d self, state: &'d mut LoggerState<'d>, driver: D) -> ! + where + D: Driver<'d>, + Self: 'd, + { + 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.config_descriptor, + &mut state.bos_descriptor, + &mut state.msos_descriptor, + &mut state.control_buf, + ); + + // Create classes on the builder. + let class = CdcAcmClass::new(&mut builder, &mut state.state, MAX_PACKET_SIZE as u16); + let (mut sender, mut receiver) = class.split(); + + // Build the builder. + let mut device = builder.build(); + loop { + let run_fut = device.run(); + let class_fut = self.run_logger_class(&mut sender, &mut receiver); + join(run_fut, class_fut).await; + } + } + + async fn run_logger_class<'d, D>(&self, sender: &mut Sender<'d, D>, receiver: &mut Receiver<'d, D>) + where + D: Driver<'d>, + { + let log_fut = async { + let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + sender.wait_connection().await; + loop { + let len = self.buffer.read(&mut rx[..]).await; + let _ = sender.write_packet(&rx[..len]).await; + if len as u8 == MAX_PACKET_SIZE { + let _ = sender.write_packet(&[]).await; + } + } + }; + let reciever_fut = async { + let mut reciever_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + receiver.wait_connection().await; + loop { + let n = receiver.read_packet(&mut reciever_buf).await.unwrap(); + match &self.recieve_handler { + Some(handler) => { + let data = &reciever_buf[..n]; + handler.handle_data(data).await; + } + None => (), + } + } + }; + + join(log_fut, reciever_fut).await; + } + + /// Creates the futures needed for the logger from a given class + /// This can be used in cases where the usb device is already in use for another connection + pub async fn create_future_from_class<'d, D>(&'d self, class: CdcAcmClass<'d, D>) + where + D: Driver<'d>, + { + let (mut sender, mut receiver) = class.split(); + loop { + self.run_logger_class(&mut sender, &mut receiver).await; + } + } +} + +impl log::Log for UsbLogger { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + if let Some(custom_style) = self.custom_style { + custom_style(record, &mut Writer(&self.buffer)); + } else { + let _ = write!(Writer(&self.buffer), "{}\r\n", record.args()); + } + } + } + + fn flush(&self) {} +} + +/// A writer that writes to the USB logger buffer. +pub struct Writer<'d, const N: usize>(&'d Pipe); + +impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + // The Pipe is implemented in such way that we cannot + // write across the wraparound discontinuity. + let b = s.as_bytes(); + if let Ok(n) = self.0.try_write(b) { + if n < b.len() { + // We wrote some data but not all, attempt again + // as the reason might be a wraparound in the + // ring buffer, which resolves on second attempt. + let _ = self.0.try_write(&b[n..]); + } + } + Ok(()) + } +} + +/// Initialize and run the USB serial logger, never returns. +/// +/// Arguments specify the buffer size, log level and the USB driver, respectively. You can optionally add a RecieverHandler. +/// +/// # 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::DummyHandler> = + ::embassy_usb_logger::UsbLogger::new(); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + } + let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await; + }; + + ( $x:expr, $l:expr, $p:ident, $h:ty ) => { + unsafe { + static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = ::embassy_usb_logger::UsbLogger::new(); + LOGGER.with_handler(<$h>::new()); + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await; + } + }; +} + +/// Initialize the USB serial logger from a serial class and return the future to run it. +/// +/// Arguments specify the buffer size, log level and the serial class, respectively. You can optionally add a RecieverHandler. +/// +/// # Usage +/// +/// ``` +/// embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, class); +/// ``` +/// +/// # Safety +/// +/// This macro should only be invoked only once since it is setting the global logging state of the application. +#[macro_export] +macro_rules! with_class { + ( $x:expr, $l:expr, $p:ident ) => {{ + static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> = + ::embassy_usb_logger::UsbLogger::new(); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + } + LOGGER.create_future_from_class($p) + }}; + + ( $x:expr, $l:expr, $p:ident, $h:ty ) => {{ + unsafe { + static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = ::embassy_usb_logger::UsbLogger::new(); + LOGGER.with_handler(<$h>::new()); + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + LOGGER.create_future_from_class($p) + } + }}; +} + +/// Initialize the USB serial logger from a serial class and return the future to run it. +/// This version of the macro allows for a custom style function to be passed in. +/// The custom style function will be called for each log record and is responsible for writing the log message to the buffer. +/// +/// Arguments specify the buffer size, log level, the serial class and the custom style function, respectively. You can optionally add a RecieverHandler. +/// +/// # Usage +/// +/// ``` +/// let log_fut = embassy_usb_logger::with_custom_style!(1024, log::LevelFilter::Info, logger_class, |record, writer| { +/// use core::fmt::Write; +/// let level = record.level().as_str(); +/// write!(writer, "[{level}] {}\r\n", record.args()).unwrap(); +/// }); +/// ``` +/// +/// # Safety +/// +/// This macro should only be invoked only once since it is setting the global logging state of the application. +#[macro_export] +macro_rules! with_custom_style { + ( $x:expr, $l:expr, $p:ident, $s:expr ) => {{ + static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> = + ::embassy_usb_logger::UsbLogger::with_custom_style($s); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + } + LOGGER.create_future_from_class($p) + }}; + + ( $x:expr, $l:expr, $p:ident, $s:expr, $h:ty ) => {{ + unsafe { + static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = + ::embassy_usb_logger::UsbLogger::with_custom_style($s); + LOGGER.with_handler(<$h>::new()); + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + LOGGER.create_future_from_class($p) + } + }}; +} diff --git a/embassy/embassy-usb-synopsys-otg/CHANGELOG.md b/embassy/embassy-usb-synopsys-otg/CHANGELOG.md new file mode 100644 index 0000000..293363d --- /dev/null +++ b/embassy/embassy-usb-synopsys-otg/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog for embassy-usb-synopsys-otg + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-12-06 + +- Fix corruption in CONTROL OUT transfers (and remove `quirk_setup_late_cnak`) +- Fix build with `defmt` enabled +- Add USBPHYC clock configuration for H7RS series +- Add support for ISO endpoints +- Add support for a full-speed ULPI mode +- Add OTG core DMA address registers +- Ensure endpoint allocation fails when `endpoint_count < MAX_EP_COUNT`. +- New configuration option: `xcvrdly` (transceiver delay). +- `EpState` now implements `Send` and `Sync`. +- The default value of `vbus_detection` is now `false`. + +## 0.1.0 - 2024-04-30 + +Initial release. diff --git a/embassy/embassy-usb-synopsys-otg/Cargo.toml b/embassy/embassy-usb-synopsys-otg/Cargo.toml new file mode 100644 index 0000000..7a9143e --- /dev/null +++ b/embassy/embassy-usb-synopsys-otg/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "embassy-usb-synopsys-otg" +version = "0.2.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "`embassy-usb-driver` implementation for Synopsys OTG USB controllers" +keywords = ["embedded", "async", "usb", "hal", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-usb-synopsys-otg" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-synopsys-otg-v$VERSION/embassy-usb-synopsys-otg/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-synopsys-otg/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[dependencies] +critical-section = "1.1" + +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } diff --git a/embassy/embassy-usb-synopsys-otg/README.md b/embassy/embassy-usb-synopsys-otg/README.md new file mode 100644 index 0000000..ba3c811 --- /dev/null +++ b/embassy/embassy-usb-synopsys-otg/README.md @@ -0,0 +1,16 @@ +# Embassy USB driver for the Synopsys USB OTG core + +This crate implements [`embassy-usb-driver`](https://crates.io/crates/embassy-usb-driver) for Synopsys USB OTG devices. + +It contains the "core" of the driver that is common across all chips using +the Synopsys OTG IP, but it doesn't contain chip-specific initialization such +as clock setup and GPIO muxing. You most likely don't want to use this crate +directly, but use it through a HAL that does the initialization for you. + +List of HALs integrating this driver: + +- [`embassy-stm32`](https://crates.io/crates/embassy-stm32), for STMicroelectronics STM32 chips. +- [`esp-hal`](https://crates.io/crates/esp-hal), for Espressif ESP32 chips. + +If you wish to integrate this crate into your device's HAL, you will need to add the +device-specific initialization. See the above crates for examples on how to do it. \ No newline at end of file diff --git a/embassy/embassy-usb-synopsys-otg/src/fmt.rs b/embassy/embassy-usb-synopsys-otg/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-usb-synopsys-otg/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-usb-synopsys-otg/src/lib.rs b/embassy/embassy-usb-synopsys-otg/src/lib.rs new file mode 100644 index 0000000..44b2bd0 --- /dev/null +++ b/embassy/embassy-usb-synopsys-otg/src/lib.rs @@ -0,0 +1,1380 @@ +#![cfg_attr(not(test), no_std)] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// This must go FIRST so that all the other modules see its macros. +mod fmt; + +use core::cell::UnsafeCell; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, AtomicU16, AtomicU32, Ordering}; +use core::task::Poll; + +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb_driver::{ + Bus as _, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointIn, EndpointInfo, EndpointOut, + EndpointType, Event, Unsupported, +}; + +use crate::fmt::Bytes; + +pub mod otg_v1; + +use otg_v1::{regs, vals, Otg}; + +/// Handle interrupts. +pub unsafe fn on_interrupt(r: Otg, state: &State, ep_count: usize) { + trace!("irq"); + + let ints = r.gintsts().read(); + if ints.wkupint() || ints.usbsusp() || ints.usbrst() || ints.enumdne() || ints.otgint() || ints.srqint() { + // Mask interrupts and notify `Bus` to process them + r.gintmsk().write(|w| { + w.set_iepint(true); + w.set_oepint(true); + w.set_rxflvlm(true); + }); + state.bus_waker.wake(); + } + + // Handle RX + while r.gintsts().read().rxflvl() { + let status = r.grxstsp().read(); + trace!("=== status {:08x}", status.0); + let ep_num = status.epnum() as usize; + let len = status.bcnt() as usize; + + assert!(ep_num < ep_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); + + // flushing TX if something stuck in control endpoint + if r.dieptsiz(ep_num).read().pktcnt() != 0 { + r.grstctl().modify(|w| { + w.set_txfnum(ep_num as _); + w.set_txfflsh(true); + }); + while r.grstctl().read().txfflsh() {} + } + + let data = &state.cp_state.setup_data; + data[0].store(r.fifo(0).read().data(), Ordering::Relaxed); + data[1].store(r.fifo(0).read().data(), Ordering::Relaxed); + } + vals::Pktstsd::OUT_DATA_RX => { + trace!("OUT_DATA_RX ep={} len={}", ep_num, len); + + if state.ep_states[ep_num].out_size.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_states[ep_num].out_buffer.get(), len) }; + + for chunk in buf.chunks_mut(4) { + // RX FIFO is shared so always read from fifo(0) + let data = r.fifo(0).read().0; + chunk.copy_from_slice(&data.to_ne_bytes()[0..chunk.len()]); + } + + state.ep_states[ep_num].out_size.store(len as u16, Ordering::Release); + state.ep_states[ep_num].out_waker.wake(); + } else { + error!("ep_out buffer overflow index={}", ep_num); + + // discard FIFO data + let len_words = (len + 3) / 4; + for _ in 0..len_words { + 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.to_bits()), + } + } + + // IN endpoint interrupt + if ints.iepint() { + let mut ep_mask = 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 { + let ep_ints = r.diepint(ep_num).read(); + + // clear all + r.diepint(ep_num).write_value(ep_ints); + + // TXFE is cleared in DIEPEMPMSK + if ep_ints.txfe() { + critical_section::with(|_| { + r.diepempmsk().modify(|w| { + w.set_ineptxfem(w.ineptxfem() & !(1 << ep_num)); + }); + }); + } + + state.ep_states[ep_num].in_waker.wake(); + trace!("in ep={} irq val={:08x}", ep_num, ep_ints.0); + } + + ep_mask >>= 1; + ep_num += 1; + } + } + + // out endpoint interrupt + if ints.oepint() { + trace!("oepint"); + let mut ep_mask = r.daint().read().oepint(); + 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 { + let ep_ints = r.doepint(ep_num).read(); + // clear all + r.doepint(ep_num).write_value(ep_ints); + + if ep_ints.stup() { + state.cp_state.setup_ready.store(true, Ordering::Release); + } + state.ep_states[ep_num].out_waker.wake(); + trace!("out ep={} irq val={:08x}", ep_num, ep_ints.0); + } + + ep_mask >>= 1; + ep_num += 1; + } + } +} + +/// USB PHY type +#[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 Full-Speed PHY (or High-Speed PHY in Full-Speed mode) + ExternalFullSpeed, + /// External ULPI High-Speed PHY + ExternalHighSpeed, +} + +impl PhyType { + /// Get whether this PHY is any of the internal types. + pub fn internal(&self) -> bool { + match self { + PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true, + PhyType::ExternalHighSpeed | PhyType::ExternalFullSpeed => false, + } + } + + /// Get whether this PHY is any of the high-speed types. + pub fn high_speed(&self) -> bool { + match self { + PhyType::InternalFullSpeed | PhyType::ExternalFullSpeed => false, + PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true, + } + } + + fn to_dspd(&self) -> vals::Dspd { + match self { + PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL, + PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED, + PhyType::ExternalFullSpeed => vals::Dspd::FULL_SPEED_EXTERNAL, + PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED, + } + } +} + +/// Indicates that [State::ep_out_buffers] is empty. +const EP_OUT_BUFFER_EMPTY: u16 = u16::MAX; + +struct EpState { + in_waker: AtomicWaker, + out_waker: AtomicWaker, + /// 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]. + out_buffer: UnsafeCell<*mut u8>, + out_size: AtomicU16, +} + +// SAFETY: The EndpointAllocator ensures that the buffer points to valid memory exclusive for each endpoint and is +// large enough to hold the maximum packet size. Access to the buffer is synchronized between the USB interrupt and the +// EndpointOut impl using the out_size atomic variable. +unsafe impl Send for EpState {} +unsafe impl Sync for EpState {} + +struct ControlPipeSetupState { + /// Holds received SETUP packets. Available if [Ep0State::setup_ready] is true. + setup_data: [AtomicU32; 2], + setup_ready: AtomicBool, +} + +/// USB OTG driver state. +pub struct State { + cp_state: ControlPipeSetupState, + ep_states: [EpState; EP_COUNT], + bus_waker: AtomicWaker, +} + +unsafe impl Send for State {} +unsafe impl Sync for State {} + +impl State { + /// Create a new State. + pub const fn new() -> Self { + Self { + cp_state: ControlPipeSetupState { + setup_data: [const { AtomicU32::new(0) }; 2], + setup_ready: AtomicBool::new(false), + }, + ep_states: [const { + EpState { + in_waker: AtomicWaker::new(), + out_waker: AtomicWaker::new(), + out_buffer: UnsafeCell::new(0 as _), + out_size: AtomicU16::new(EP_OUT_BUFFER_EMPTY), + } + }; EP_COUNT], + bus_waker: AtomicWaker::new(), + } + } +} + +#[derive(Debug, Clone, Copy)] +struct EndpointData { + ep_type: EndpointType, + max_packet_size: u16, + fifo_size_words: u16, +} + +/// USB driver config. +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Config { + /// Enable VBUS detection. + /// + /// The USB spec requires USB devices monitor for USB cable plug/unplug and react accordingly. + /// This is done by checking whether there is 5V on the VBUS pin or not. + /// + /// If your device is bus-powered (powers itself from the USB host via VBUS), then this is optional. + /// (If there's no power in VBUS your device would be off anyway, so it's fine to always assume + /// there's power in VBUS, i.e. the USB cable is always plugged in.) + /// + /// If your device is self-powered (i.e. it gets power from a source other than the USB cable, and + /// therefore can stay powered through USB cable plug/unplug) then you MUST set this to true. + /// + /// If you set this to true, you must connect VBUS to PA9 for FS, PB13 for HS, possibly with a + /// voltage divider. See ST application note AN4879 and the reference manual for more details. + pub vbus_detection: bool, + + /// Enable transceiver delay. + /// + /// Some ULPI PHYs like the Microchip USB334x series require a delay between the ULPI register write that initiates + /// the HS Chirp and the subsequent transmit command, otherwise the HS Chirp does not get executed and the deivce + /// enumerates in FS mode. Some USB Link IP like those in the STM32H7 series support adding this delay to work with + /// the affected PHYs. + pub xcvrdly: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + vbus_detection: false, + xcvrdly: false, + } + } +} + +/// USB OTG driver. +pub struct Driver<'d, const MAX_EP_COUNT: usize> { + config: Config, + ep_in: [Option; MAX_EP_COUNT], + ep_out: [Option; MAX_EP_COUNT], + ep_out_buffer: &'d mut [u8], + ep_out_buffer_offset: usize, + instance: OtgInstance<'d, MAX_EP_COUNT>, +} + +impl<'d, const MAX_EP_COUNT: usize> Driver<'d, MAX_EP_COUNT> { + /// Initializes the USB OTG peripheral. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + /// * `instance` - The USB OTG peripheral instance and its configuration. + /// * `config` - The USB driver configuration. + pub fn new(ep_out_buffer: &'d mut [u8], instance: OtgInstance<'d, MAX_EP_COUNT>, config: Config) -> Self { + Self { + config, + ep_in: [None; MAX_EP_COUNT], + ep_out: [None; MAX_EP_COUNT], + ep_out_buffer, + ep_out_buffer_offset: 0, + instance, + } + } + + /// Returns the total amount of words (u32) allocated in dedicated FIFO. + fn allocated_fifo_words(&self) -> u16 { + self.instance.extra_rx_fifo_words + ep_fifo_size(&self.ep_out) + ep_fifo_size(&self.ep_in) + } + + /// Creates an [`Endpoint`] with the given parameters. + 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() > self.instance.fifo_depth_words { + error!("Not enough FIFO capacity"); + return Err(EndpointAllocError); + } + + let eps = match D::dir() { + Direction::Out => &mut self.ep_out[..self.instance.endpoint_count], + Direction::In => &mut self.ep_in[..self.instance.endpoint_count], + }; + + // 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); + + let state = &self.instance.state.ep_states[index]; + if D::dir() == Direction::Out { + // Buffer capacity check was done above, now allocation cannot fail + unsafe { + *state.out_buffer.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, + regs: self.instance.regs, + state, + info: EndpointInfo { + addr: EndpointAddress::from_parts(index, D::dir()), + ep_type, + max_packet_size, + interval_ms, + }, + }) + } +} + +impl<'d, const MAX_EP_COUNT: usize> embassy_usb_driver::Driver<'d> for Driver<'d, MAX_EP_COUNT> { + type EndpointOut = Endpoint<'d, Out>; + type EndpointIn = Endpoint<'d, In>; + type ControlPipe = ControlPipe<'d>; + type Bus = Bus<'d, MAX_EP_COUNT>; + + 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"); + + let regs = self.instance.regs; + let cp_setup_state = &self.instance.state.cp_state; + ( + Bus { + config: self.config, + ep_in: self.ep_in, + ep_out: self.ep_out, + inited: false, + instance: self.instance, + }, + ControlPipe { + max_packet_size: control_max_packet_size, + setup_state: cp_setup_state, + ep_out, + ep_in, + regs, + }, + ) + } +} + +/// USB bus. +pub struct Bus<'d, const MAX_EP_COUNT: usize> { + config: Config, + ep_in: [Option; MAX_EP_COUNT], + ep_out: [Option; MAX_EP_COUNT], + instance: OtgInstance<'d, MAX_EP_COUNT>, + inited: bool, +} + +impl<'d, const MAX_EP_COUNT: usize> Bus<'d, MAX_EP_COUNT> { + fn restore_irqs(&mut self) { + self.instance.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); + w.set_srqim(true); + w.set_otgint(true); + }); + } + + /// Returns the PHY type. + pub fn phy_type(&self) -> PhyType { + self.instance.phy_type + } + + /// Configures the PHY as a device. + pub fn configure_as_device(&mut self) { + let r = self.instance.regs; + let phy_type = self.instance.phy_type; + r.gusbcfg().write(|w| { + // Force device mode + w.set_fdmod(true); + // Enable internal full-speed PHY + w.set_physel(phy_type.internal() && !phy_type.high_speed()); + }); + } + + /// Applies configuration specific to + /// Core ID 0x0000_1100 and 0x0000_1200 + pub fn config_v1(&mut self) { + let r = self.instance.regs; + let phy_type = self.instance.phy_type; + assert!(phy_type != PhyType::InternalHighSpeed); + + r.gccfg_v1().modify(|w| { + // Enable internal full-speed PHY, logic is inverted + w.set_pwrdwn(phy_type.internal()); + }); + + // F429-like chips have the GCCFG.NOVBUSSENS bit + r.gccfg_v1().modify(|w| { + w.set_novbussens(!self.config.vbus_detection); + w.set_vbusasen(false); + w.set_vbusbsen(self.config.vbus_detection); + w.set_sofouten(false); + }); + } + + /// Applies configuration specific to + /// Core ID 0x0000_2000, 0x0000_2100, 0x0000_2300, 0x0000_3000 and 0x0000_3100 + pub fn config_v2v3(&mut self) { + let r = self.instance.regs; + let phy_type = self.instance.phy_type; + + // 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(phy_type.internal() && !phy_type.high_speed()); + w.set_phyhsen(phy_type.internal() && phy_type.high_speed()); + }); + + r.gccfg_v2().modify(|w| { + w.set_vbden(self.config.vbus_detection); + }); + + // Force B-peripheral session + r.gotgctl().modify(|w| { + w.set_bvaloen(!self.config.vbus_detection); + w.set_bvaloval(true); + }); + } + + /// Applies configuration specific to + /// Core ID 0x0000_5000 + pub fn config_v5(&mut self) { + let r = self.instance.regs; + let phy_type = self.instance.phy_type; + + if phy_type == PhyType::InternalHighSpeed { + r.gccfg_v3().modify(|w| { + w.set_vbvaloven(!self.config.vbus_detection); + w.set_vbvaloval(!self.config.vbus_detection); + w.set_vbden(self.config.vbus_detection); + }); + } else { + r.gotgctl().modify(|w| { + w.set_bvaloen(!self.config.vbus_detection); + w.set_bvaloval(!self.config.vbus_detection); + }); + r.gccfg_v3().modify(|w| { + w.set_vbden(self.config.vbus_detection); + }); + } + } + + fn init(&mut self) { + let r = self.instance.regs; + let phy_type = self.instance.phy_type; + + // 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(phy_type.to_dspd()); + if self.config.xcvrdly { + w.set_xcvrdly(true); + } + }); + + // Unmask transfer complete EP interrupt + r.diepmsk().write(|w| { + w.set_xfrcm(true); + }); + + // Unmask SETUP received EP interrupt + r.doepmsk().write(|w| { + w.set_stupm(true); + }); + + // Unmask and clear core interrupts + self.restore_irqs(); + r.gintsts().write_value(regs::Gintsts(0xFFFF_FFFF)); + + // Unmask global interrupt + r.gahbcfg().write(|w| { + w.set_gint(true); // unmask global interrupt + }); + + // Connect + r.dctl().write(|w| w.set_sdis(false)); + } + + fn init_fifo(&mut self) { + trace!("init_fifo"); + + let regs = self.instance.regs; + // ERRATA NOTE: Don't interrupt FIFOs being written to. The interrupt + // handler COULD interrupt us here and do FIFO operations, so ensure + // the interrupt does not occur. + critical_section::with(|_| { + // Configure RX fifo size. All endpoints share the same FIFO area. + let rx_fifo_size_words = self.instance.extra_rx_fifo_words + ep_fifo_size(&self.ep_out); + trace!("configuring rx fifo size={}", rx_fifo_size_words); + + regs.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..self.instance.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 { regs.dieptxf0() } else { regs.dieptxf(i - 1) }; + + dieptxf.write(|w| { + w.set_fd(ep.fifo_size_words); + w.set_sa(fifo_top); + }); + + fifo_top += ep.fifo_size_words; + } + } + + assert!( + fifo_top <= self.instance.fifo_depth_words, + "FIFO allocations exceeded maximum capacity" + ); + + // Flush fifos + regs.grstctl().write(|w| { + w.set_rxfflsh(true); + w.set_txfflsh(true); + w.set_txfnum(0x10); + }); + }); + + loop { + let x = regs.grstctl().read(); + if !x.rxfflsh() && !x.txfflsh() { + break; + } + } + } + + fn configure_endpoints(&mut self) { + trace!("configure_endpoints"); + + let regs = self.instance.regs; + + // Configure IN endpoints + for (index, ep) in self.ep_in.iter().enumerate() { + if let Some(ep) = ep { + critical_section::with(|_| { + regs.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); + w.set_txfnum(index as _); + w.set_snak(true); + } + }); + }); + } + } + + // Configure OUT endpoints + for (index, ep) in self.ep_out.iter().enumerate() { + if let Some(ep) = ep { + critical_section::with(|_| { + regs.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); + } + }); + + regs.doeptsiz(index).modify(|w| { + w.set_xfrsiz(ep.max_packet_size as _); + if index == 0 { + w.set_rxdpid_stupcnt(3); + } else { + w.set_pktcnt(1); + } + }); + }); + } + } + + // Enable IRQs for allocated endpoints + regs.daintmsk().modify(|w| { + w.set_iepm(ep_irq_mask(&self.ep_in)); + w.set_oepm(ep_irq_mask(&self.ep_out)); + }); + } + + fn disable_all_endpoints(&mut self) { + for i in 0..self.instance.endpoint_count { + self.endpoint_set_enabled(EndpointAddress::from_parts(i, Direction::In), false); + self.endpoint_set_enabled(EndpointAddress::from_parts(i, Direction::Out), false); + } + } +} + +impl<'d, const MAX_EP_COUNT: usize> embassy_usb_driver::Bus for Bus<'d, MAX_EP_COUNT> { + async fn poll(&mut self) -> Event { + poll_fn(move |cx| { + if !self.inited { + self.init(); + self.inited = true; + + // If no vbus detection, just return a single PowerDetected event at startup. + if !self.config.vbus_detection { + return Poll::Ready(Event::PowerDetected); + } + } + + let regs = self.instance.regs; + self.instance.state.bus_waker.register(cx.waker()); + + let ints = regs.gintsts().read(); + + if ints.srqint() { + trace!("vbus detected"); + + regs.gintsts().write(|w| w.set_srqint(true)); // clear + self.restore_irqs(); + + if self.config.vbus_detection { + return Poll::Ready(Event::PowerDetected); + } + } + + if ints.otgint() { + let otgints = regs.gotgint().read(); + regs.gotgint().write_value(otgints); // clear all + self.restore_irqs(); + + if otgints.sedet() { + trace!("vbus removed"); + if self.config.vbus_detection { + self.disable_all_endpoints(); + return Poll::Ready(Event::PowerRemoved); + } + } + } + + if ints.usbrst() { + trace!("reset"); + + self.init_fifo(); + self.configure_endpoints(); + + // Reset address + critical_section::with(|_| { + regs.dcfg().modify(|w| { + w.set_dad(0); + }); + }); + + regs.gintsts().write(|w| w.set_usbrst(true)); // clear + self.restore_irqs(); + } + + if ints.enumdne() { + trace!("enumdne"); + + let speed = regs.dsts().read().enumspd(); + let trdt = (self.instance.calculate_trdt_fn)(speed); + trace!(" speed={} trdt={}", speed.to_bits(), trdt); + regs.gusbcfg().modify(|w| w.set_trdt(trdt)); + + regs.gintsts().write(|w| w.set_enumdne(true)); // clear + self.restore_irqs(); + + return Poll::Ready(Event::Reset); + } + + if ints.usbsusp() { + trace!("suspend"); + regs.gintsts().write(|w| w.set_usbsusp(true)); // clear + self.restore_irqs(); + return Poll::Ready(Event::Suspend); + } + + if ints.wkupint() { + trace!("resume"); + regs.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() < self.instance.endpoint_count, + "endpoint_set_stalled index {} out of range", + ep_addr.index() + ); + + let regs = self.instance.regs; + let state = self.instance.state; + match ep_addr.direction() { + Direction::Out => { + critical_section::with(|_| { + regs.doepctl(ep_addr.index()).modify(|w| { + w.set_stall(stalled); + }); + }); + + state.ep_states[ep_addr.index()].out_waker.wake(); + } + Direction::In => { + critical_section::with(|_| { + regs.diepctl(ep_addr.index()).modify(|w| { + w.set_stall(stalled); + }); + }); + + state.ep_states[ep_addr.index()].in_waker.wake(); + } + } + } + + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + assert!( + ep_addr.index() < self.instance.endpoint_count, + "endpoint_is_stalled index {} out of range", + ep_addr.index() + ); + + let regs = self.instance.regs; + match ep_addr.direction() { + Direction::Out => regs.doepctl(ep_addr.index()).read().stall(), + Direction::In => 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() < self.instance.endpoint_count, + "endpoint_set_enabled index {} out of range", + ep_addr.index() + ); + + let regs = self.instance.regs; + let state = self.instance.state; + match ep_addr.direction() { + Direction::Out => { + critical_section::with(|_| { + // cancel transfer if active + if !enabled && regs.doepctl(ep_addr.index()).read().epena() { + regs.doepctl(ep_addr.index()).modify(|w| { + w.set_snak(true); + w.set_epdis(true); + }) + } + + regs.doepctl(ep_addr.index()).modify(|w| { + w.set_usbaep(enabled); + }); + + // Flush tx fifo + regs.grstctl().write(|w| { + w.set_txfflsh(true); + w.set_txfnum(ep_addr.index() as _); + }); + loop { + let x = regs.grstctl().read(); + if !x.txfflsh() { + break; + } + } + }); + + // Wake `Endpoint::wait_enabled()` + state.ep_states[ep_addr.index()].out_waker.wake(); + } + Direction::In => { + critical_section::with(|_| { + // cancel transfer if active + if !enabled && regs.diepctl(ep_addr.index()).read().epena() { + regs.diepctl(ep_addr.index()).modify(|w| { + w.set_snak(true); // set NAK + w.set_epdis(true); + }) + } + + regs.diepctl(ep_addr.index()).modify(|w| { + w.set_usbaep(enabled); + w.set_cnak(enabled); // clear NAK that might've been set by SNAK above. + }) + }); + + // Wake `Endpoint::wait_enabled()` + state.ep_states[ep_addr.index()].in_waker.wake(); + } + } + } + + async fn enable(&mut self) { + trace!("enable"); + // TODO: enable the peripheral once enable/disable semantics are cleared up in embassy-usb + } + + async fn disable(&mut self) { + trace!("disable"); + + // TODO: disable the peripheral once enable/disable semantics are cleared up in embassy-usb + //Bus::disable(self); + } + + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } +} + +/// USB endpoint direction. +trait Dir { + /// Returns the direction value. + fn dir() -> Direction; +} + +/// Marker type for the "IN" direction. +pub enum In {} +impl Dir for In { + fn dir() -> Direction { + Direction::In + } +} + +/// Marker type for the "OUT" direction. +pub enum Out {} +impl Dir for Out { + fn dir() -> Direction { + Direction::Out + } +} + +/// USB endpoint. +pub struct Endpoint<'d, D> { + _phantom: PhantomData, + regs: Otg, + info: EndpointInfo, + state: &'d EpState, +} + +impl<'d> embassy_usb_driver::Endpoint for Endpoint<'d, In> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + poll_fn(|cx| { + let ep_index = self.info.addr.index(); + + self.state.in_waker.register(cx.waker()); + + if self.regs.diepctl(ep_index).read().usbaep() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d> embassy_usb_driver::Endpoint for Endpoint<'d, Out> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + poll_fn(|cx| { + let ep_index = self.info.addr.index(); + + self.state.out_waker.register(cx.waker()); + + if self.regs.doepctl(ep_index).read().usbaep() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d> embassy_usb_driver::EndpointOut for Endpoint<'d, 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(); + self.state.out_waker.register(cx.waker()); + + let doepctl = self.regs.doepctl(index).read(); + trace!("read ep={:?}: doepctl {:08x}", self.info.addr, doepctl.0,); + if !doepctl.usbaep() { + trace!("read ep={:?} error disabled", self.info.addr); + return Poll::Ready(Err(EndpointError::Disabled)); + } + + let len = self.state.out_size.load(Ordering::Relaxed); + if len != EP_OUT_BUFFER_EMPTY { + trace!("read ep={:?} done len={}", self.info.addr, len); + + if len as usize > buf.len() { + return Poll::Ready(Err(EndpointError::BufferOverflow)); + } + + // SAFETY: exclusive access ensured by `out_size` atomic variable + let data = unsafe { core::slice::from_raw_parts(*self.state.out_buffer.get(), len as usize) }; + buf[..len as usize].copy_from_slice(data); + + // Release buffer + self.state.out_size.store(EP_OUT_BUFFER_EMPTY, Ordering::Release); + + critical_section::with(|_| { + // Receive 1 packet + self.regs.doeptsiz(index).modify(|w| { + w.set_xfrsiz(self.info.max_packet_size as _); + w.set_pktcnt(1); + }); + + if self.info.ep_type == EndpointType::Isochronous { + // Isochronous endpoints must set the correct even/odd frame bit to + // correspond with the next frame's number. + let frame_number = self.regs.dsts().read().fnsof(); + let frame_is_odd = frame_number & 0x01 == 1; + + self.regs.doepctl(index).modify(|r| { + if frame_is_odd { + r.set_sd0pid_sevnfrm(true); + } else { + r.set_sd1pid_soddfrm(true); + } + }); + } + + // Clear NAK to indicate we are ready to receive more data + self.regs.doepctl(index).modify(|w| { + w.set_cnak(true); + }); + }); + + Poll::Ready(Ok(len as usize)) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d> embassy_usb_driver::EndpointIn for Endpoint<'d, In> { + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + trace!("write ep={:?} data={:?}", self.info.addr, Bytes(buf)); + + if buf.len() > self.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + let index = self.info.addr.index(); + // Wait for previous transfer to complete and check if endpoint is disabled + poll_fn(|cx| { + self.state.in_waker.register(cx.waker()); + + let diepctl = self.regs.diepctl(index).read(); + let dtxfsts = self.regs.dtxfsts(index).read(); + trace!( + "write ep={:?}: diepctl {:08x} ftxfsts {:08x}", + self.info.addr, + diepctl.0, + dtxfsts.0 + ); + if !diepctl.usbaep() { + trace!("write ep={:?} wait for prev: error disabled", self.info.addr); + Poll::Ready(Err(EndpointError::Disabled)) + } else if !diepctl.epena() { + trace!("write ep={:?} wait for prev: ready", self.info.addr); + Poll::Ready(Ok(())) + } else { + trace!("write ep={:?} wait for prev: pending", self.info.addr); + Poll::Pending + } + }) + .await?; + + if buf.len() > 0 { + poll_fn(|cx| { + self.state.in_waker.register(cx.waker()); + + let size_words = (buf.len() + 3) / 4; + + let fifo_space = self.regs.dtxfsts(index).read().ineptfsav() as usize; + if size_words > fifo_space { + // Not enough space in fifo, enable tx fifo empty interrupt + critical_section::with(|_| { + self.regs.diepempmsk().modify(|w| { + w.set_ineptxfem(w.ineptxfem() | (1 << index)); + }); + }); + + trace!("tx fifo for ep={} full, waiting for txfe", index); + + Poll::Pending + } else { + trace!("write ep={:?} wait for fifo: ready", self.info.addr); + Poll::Ready(()) + } + }) + .await + } + + // ERRATA: Transmit data FIFO is corrupted when a write sequence to the FIFO is interrupted with + // accesses to certain OTG_FS registers. + // + // Prevent the interrupt (which might poke FIFOs) from executing while copying data to FIFOs. + critical_section::with(|_| { + // Setup transfer size + self.regs.dieptsiz(index).write(|w| { + w.set_mcnt(1); + w.set_pktcnt(1); + w.set_xfrsiz(buf.len() as _); + }); + + if self.info.ep_type == EndpointType::Isochronous { + // Isochronous endpoints must set the correct even/odd frame bit to + // correspond with the next frame's number. + let frame_number = self.regs.dsts().read().fnsof(); + let frame_is_odd = frame_number & 0x01 == 1; + + self.regs.diepctl(index).modify(|r| { + if frame_is_odd { + r.set_sd0pid_sevnfrm(true); + } else { + r.set_sd1pid_soddfrm(true); + } + }); + } + + // Enable endpoint + self.regs.diepctl(index).modify(|w| { + w.set_cnak(true); + 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); + self.regs.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))); + } + }); + + trace!("write done ep={:?}", self.info.addr); + + Ok(()) + } +} + +/// USB control pipe. +pub struct ControlPipe<'d> { + max_packet_size: u16, + regs: Otg, + setup_state: &'d ControlPipeSetupState, + ep_in: Endpoint<'d, In>, + ep_out: Endpoint<'d, Out>, +} + +impl<'d> embassy_usb_driver::ControlPipe for ControlPipe<'d> { + fn max_packet_size(&self) -> usize { + usize::from(self.max_packet_size) + } + + async fn setup(&mut self) -> [u8; 8] { + poll_fn(|cx| { + self.ep_out.state.out_waker.register(cx.waker()); + + if self.setup_state.setup_ready.load(Ordering::Relaxed) { + let mut data = [0; 8]; + data[0..4].copy_from_slice(&self.setup_state.setup_data[0].load(Ordering::Relaxed).to_ne_bytes()); + data[4..8].copy_from_slice(&self.setup_state.setup_data[1].load(Ordering::Relaxed).to_ne_bytes()); + self.setup_state.setup_ready.store(false, Ordering::Release); + + // EP0 should not be controlled by `Bus` so this RMW does not need a critical section + self.regs.doeptsiz(self.ep_out.info.addr.index()).modify(|w| { + w.set_rxdpid_stupcnt(3); + }); + + // Clear NAK to indicate we are ready to receive more data + self.regs + .doepctl(self.ep_out.info.addr.index()) + .modify(|w| w.set_cnak(true)); + + trace!("SETUP received: {:?}", Bytes(&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: {:?}", Bytes(&buf[..len])); + Ok(len) + } + + async fn data_in(&mut self, data: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> { + trace!("control: data_in write: {:?}", Bytes(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 + self.regs.diepctl(self.ep_in.info.addr.index()).modify(|w| { + w.set_stall(true); + }); + self.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(|_| { + self.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), + } +} + +/// Hardware-dependent USB IP configuration. +pub struct OtgInstance<'d, const MAX_EP_COUNT: usize> { + /// The USB peripheral. + pub regs: Otg, + /// The USB state. + pub state: &'d State, + /// FIFO depth in words. + pub fifo_depth_words: u16, + /// Number of used endpoints. + pub endpoint_count: usize, + /// The PHY type. + pub phy_type: PhyType, + /// Extra RX FIFO words needed by some implementations. + pub extra_rx_fifo_words: u16, + /// Function to calculate TRDT value based on some internal clock speed. + pub calculate_trdt_fn: fn(speed: vals::Dspd) -> u8, +} diff --git a/embassy/embassy-usb-synopsys-otg/src/otg_v1.rs b/embassy/embassy-usb-synopsys-otg/src/otg_v1.rs new file mode 100644 index 0000000..9e65f23 --- /dev/null +++ b/embassy/embassy-usb-synopsys-otg/src/otg_v1.rs @@ -0,0 +1,4665 @@ +//! Register definitions for Synopsys DesignWare USB OTG core + +#![allow(missing_docs)] + +use core::marker::PhantomData; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct RW; +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct R; +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct W; + +mod sealed { + use super::*; + pub trait Access {} + impl Access for R {} + impl Access for W {} + impl Access for RW {} +} + +pub trait Access: sealed::Access + Copy {} +impl Access for R {} +impl Access for W {} +impl Access for RW {} + +pub trait Read: Access {} +impl Read for RW {} +impl Read for R {} + +pub trait Write: Access {} +impl Write for RW {} +impl Write for W {} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Reg { + ptr: *mut u8, + phantom: PhantomData<*mut (T, A)>, +} +unsafe impl Send for Reg {} +unsafe impl Sync for Reg {} + +impl Reg { + #[allow(clippy::missing_safety_doc)] + #[inline(always)] + pub const unsafe fn from_ptr(ptr: *mut T) -> Self { + Self { + ptr: ptr as _, + phantom: PhantomData, + } + } + + #[inline(always)] + pub const fn as_ptr(&self) -> *mut T { + self.ptr as _ + } +} + +impl Reg { + #[inline(always)] + pub fn read(&self) -> T { + unsafe { (self.ptr as *mut T).read_volatile() } + } +} + +impl Reg { + #[inline(always)] + pub fn write_value(&self, val: T) { + unsafe { (self.ptr as *mut T).write_volatile(val) } + } +} + +impl Reg { + #[inline(always)] + pub fn write(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + self.write_value(val); + res + } +} + +impl Reg { + #[inline(always)] + pub fn modify(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = self.read(); + let res = f(&mut val); + self.write_value(val); + res + } +} + +#[doc = "USB on the go"] +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Otg { + ptr: *mut u8, +} +unsafe impl Send for Otg {} +unsafe impl Sync for Otg {} +impl Otg { + #[inline(always)] + pub const unsafe fn from_ptr(ptr: *mut ()) -> Self { + Self { ptr: ptr as _ } + } + #[inline(always)] + pub const fn as_ptr(&self) -> *mut () { + self.ptr as _ + } + #[doc = "Control and status register"] + #[inline(always)] + pub fn gotgctl(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0usize) as _) } + } + #[doc = "Interrupt register"] + #[inline(always)] + pub fn gotgint(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x04usize) as _) } + } + #[doc = "AHB configuration register"] + #[inline(always)] + pub fn gahbcfg(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x08usize) as _) } + } + #[doc = "USB configuration register"] + #[inline(always)] + pub fn gusbcfg(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0cusize) as _) } + } + #[doc = "Reset register"] + #[inline(always)] + pub fn grstctl(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x10usize) as _) } + } + #[doc = "Core interrupt register"] + #[inline(always)] + pub fn gintsts(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x14usize) as _) } + } + #[doc = "Interrupt mask register"] + #[inline(always)] + pub fn gintmsk(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x18usize) as _) } + } + #[doc = "Receive status debug read register"] + #[inline(always)] + pub fn grxstsr(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x1cusize) as _) } + } + #[doc = "Status read and pop register"] + #[inline(always)] + pub fn grxstsp(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x20usize) as _) } + } + #[doc = "Receive FIFO size register"] + #[inline(always)] + pub fn grxfsiz(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x24usize) as _) } + } + #[doc = "Endpoint 0 transmit FIFO size register (device mode)"] + #[inline(always)] + pub fn dieptxf0(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x28usize) as _) } + } + #[doc = "Non-periodic transmit FIFO size register (host mode)"] + #[inline(always)] + pub fn hnptxfsiz(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x28usize) as _) } + } + #[doc = "Non-periodic transmit FIFO/queue status register (host mode)"] + #[inline(always)] + pub fn hnptxsts(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x2cusize) as _) } + } + #[doc = "OTG I2C access register"] + #[inline(always)] + pub fn gi2cctl(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x30usize) as _) } + } + #[doc = "General core configuration register, for core_id 0x0000_1xxx"] + #[inline(always)] + pub fn gccfg_v1(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) } + } + #[doc = "General core configuration register, for core_id 0x0000_\\[23\\]xxx"] + #[inline(always)] + pub fn gccfg_v2(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) } + } + #[doc = "General core configuration register, for core_id 0x0000_5xxx"] + #[inline(always)] + pub fn gccfg_v3(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) } + } + #[doc = "Core ID register"] + #[inline(always)] + pub fn cid(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x3cusize) as _) } + } + #[doc = "OTG core LPM configuration register"] + #[inline(always)] + pub fn glpmcfg(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x54usize) as _) } + } + #[doc = "Host periodic transmit FIFO size register"] + #[inline(always)] + pub fn hptxfsiz(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0100usize) as _) } + } + #[doc = "Device IN endpoint transmit FIFO size register"] + #[inline(always)] + pub fn dieptxf(self, n: usize) -> Reg { + assert!(n < 7usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0104usize + n * 4usize) as _) } + } + #[doc = "Host configuration register"] + #[inline(always)] + pub fn hcfg(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0400usize) as _) } + } + #[doc = "Host frame interval register"] + #[inline(always)] + pub fn hfir(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0404usize) as _) } + } + #[doc = "Host frame number/frame time remaining register"] + #[inline(always)] + pub fn hfnum(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0408usize) as _) } + } + #[doc = "Periodic transmit FIFO/queue status register"] + #[inline(always)] + pub fn hptxsts(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0410usize) as _) } + } + #[doc = "Host all channels interrupt register"] + #[inline(always)] + pub fn haint(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0414usize) as _) } + } + #[doc = "Host all channels interrupt mask register"] + #[inline(always)] + pub fn haintmsk(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0418usize) as _) } + } + #[doc = "Host port control and status register"] + #[inline(always)] + pub fn hprt(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0440usize) as _) } + } + #[doc = "Host channel characteristics register"] + #[inline(always)] + pub fn hcchar(self, n: usize) -> Reg { + assert!(n < 12usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0500usize + n * 32usize) as _) } + } + #[doc = "Host channel split control register"] + #[inline(always)] + pub fn hcsplt(self, n: usize) -> Reg { + assert!(n < 12usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0504usize + n * 32usize) as _) } + } + #[doc = "Host channel interrupt register"] + #[inline(always)] + pub fn hcint(self, n: usize) -> Reg { + assert!(n < 12usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0508usize + n * 32usize) as _) } + } + #[doc = "Host channel mask register"] + #[inline(always)] + pub fn hcintmsk(self, n: usize) -> Reg { + assert!(n < 12usize); + unsafe { Reg::from_ptr(self.ptr.add(0x050cusize + n * 32usize) as _) } + } + #[doc = "Host channel transfer size register"] + #[inline(always)] + pub fn hctsiz(self, n: usize) -> Reg { + assert!(n < 12usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0510usize + n * 32usize) as _) } + } + #[doc = "Host channel DMA address register"] + #[inline(always)] + pub fn hcdma(self, n: usize) -> Reg { + assert!(n < 12usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0514usize + n * 32usize) as _) } + } + #[doc = "Device configuration register"] + #[inline(always)] + pub fn dcfg(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0800usize) as _) } + } + #[doc = "Device control register"] + #[inline(always)] + pub fn dctl(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0804usize) as _) } + } + #[doc = "Device status register"] + #[inline(always)] + pub fn dsts(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0808usize) as _) } + } + #[doc = "Device IN endpoint common interrupt mask register"] + #[inline(always)] + pub fn diepmsk(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0810usize) as _) } + } + #[doc = "Device OUT endpoint common interrupt mask register"] + #[inline(always)] + pub fn doepmsk(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0814usize) as _) } + } + #[doc = "Device all endpoints interrupt register"] + #[inline(always)] + pub fn daint(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0818usize) as _) } + } + #[doc = "All endpoints interrupt mask register"] + #[inline(always)] + pub fn daintmsk(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x081cusize) as _) } + } + #[doc = "Device VBUS discharge time register"] + #[inline(always)] + pub fn dvbusdis(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0828usize) as _) } + } + #[doc = "Device VBUS pulsing time register"] + #[inline(always)] + pub fn dvbuspulse(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x082cusize) as _) } + } + #[doc = "Device IN endpoint FIFO empty interrupt mask register"] + #[inline(always)] + pub fn diepempmsk(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0834usize) as _) } + } + #[doc = "Device IN endpoint control register"] + #[inline(always)] + pub fn diepctl(self, n: usize) -> Reg { + assert!(n < 16usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0900usize + n * 32usize) as _) } + } + #[doc = "Device IN endpoint interrupt register"] + #[inline(always)] + pub fn diepint(self, n: usize) -> Reg { + assert!(n < 16usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0908usize + n * 32usize) as _) } + } + #[doc = "Device IN endpoint transfer size register"] + #[inline(always)] + pub fn dieptsiz(self, n: usize) -> Reg { + assert!(n < 16usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0910usize + n * 32usize) as _) } + } + #[doc = "Device IN endpoint transmit FIFO status register"] + #[inline(always)] + pub fn dtxfsts(self, n: usize) -> Reg { + assert!(n < 16usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0918usize + n * 32usize) as _) } + } + #[doc = "Device OUT endpoint control register"] + #[inline(always)] + pub fn doepctl(self, n: usize) -> Reg { + assert!(n < 16usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0b00usize + n * 32usize) as _) } + } + #[doc = "Device OUT endpoint interrupt register"] + #[inline(always)] + pub fn doepint(self, n: usize) -> Reg { + assert!(n < 16usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0b08usize + n * 32usize) as _) } + } + #[doc = "Device OUT endpoint transfer size register"] + #[inline(always)] + pub fn doeptsiz(self, n: usize) -> Reg { + assert!(n < 16usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0b10usize + n * 32usize) as _) } + } + #[doc = "Device OUT/IN endpoint DMA address register"] + #[inline(always)] + pub fn doepdma(self, n: usize) -> Reg { + assert!(n < 16usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0b14usize + n * 32usize) as _) } + } + #[doc = "Power and clock gating control register"] + #[inline(always)] + pub fn pcgcctl(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x0e00usize) as _) } + } + #[doc = "Device endpoint / host channel FIFO register"] + #[inline(always)] + pub fn fifo(self, n: usize) -> Reg { + assert!(n < 16usize); + unsafe { Reg::from_ptr(self.ptr.add(0x1000usize + n * 4096usize) as _) } + } +} +pub mod regs { + #[doc = "Core ID register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Cid(pub u32); + impl Cid { + #[doc = "Product ID field"] + #[inline(always)] + pub const fn product_id(&self) -> u32 { + let val = (self.0 >> 0usize) & 0xffff_ffff; + val as u32 + } + #[doc = "Product ID field"] + #[inline(always)] + pub fn set_product_id(&mut self, val: u32) { + self.0 = (self.0 & !(0xffff_ffff << 0usize)) | (((val as u32) & 0xffff_ffff) << 0usize); + } + } + impl Default for Cid { + #[inline(always)] + fn default() -> Cid { + Cid(0) + } + } + #[doc = "Device all endpoints interrupt register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Daint(pub u32); + impl Daint { + #[doc = "IN endpoint interrupt bits"] + #[inline(always)] + pub const fn iepint(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "IN endpoint interrupt bits"] + #[inline(always)] + pub fn set_iepint(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + #[doc = "OUT endpoint interrupt bits"] + #[inline(always)] + pub const fn oepint(&self) -> u16 { + let val = (self.0 >> 16usize) & 0xffff; + val as u16 + } + #[doc = "OUT endpoint interrupt bits"] + #[inline(always)] + pub fn set_oepint(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 16usize)) | (((val as u32) & 0xffff) << 16usize); + } + } + impl Default for Daint { + #[inline(always)] + fn default() -> Daint { + Daint(0) + } + } + #[doc = "All endpoints interrupt mask register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Daintmsk(pub u32); + impl Daintmsk { + #[doc = "IN EP interrupt mask bits"] + #[inline(always)] + pub const fn iepm(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "IN EP interrupt mask bits"] + #[inline(always)] + pub fn set_iepm(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + #[doc = "OUT EP interrupt mask bits"] + #[inline(always)] + pub const fn oepm(&self) -> u16 { + let val = (self.0 >> 16usize) & 0xffff; + val as u16 + } + #[doc = "OUT EP interrupt mask bits"] + #[inline(always)] + pub fn set_oepm(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 16usize)) | (((val as u32) & 0xffff) << 16usize); + } + } + impl Default for Daintmsk { + #[inline(always)] + fn default() -> Daintmsk { + Daintmsk(0) + } + } + #[doc = "Device configuration register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Dcfg(pub u32); + impl Dcfg { + #[doc = "Device speed"] + #[inline(always)] + pub const fn dspd(&self) -> super::vals::Dspd { + let val = (self.0 >> 0usize) & 0x03; + super::vals::Dspd::from_bits(val as u8) + } + #[doc = "Device speed"] + #[inline(always)] + pub fn set_dspd(&mut self, val: super::vals::Dspd) { + self.0 = (self.0 & !(0x03 << 0usize)) | (((val.to_bits() as u32) & 0x03) << 0usize); + } + #[doc = "Non-zero-length status OUT handshake"] + #[inline(always)] + pub const fn nzlsohsk(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "Non-zero-length status OUT handshake"] + #[inline(always)] + pub fn set_nzlsohsk(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "Device address"] + #[inline(always)] + pub const fn dad(&self) -> u8 { + let val = (self.0 >> 4usize) & 0x7f; + val as u8 + } + #[doc = "Device address"] + #[inline(always)] + pub fn set_dad(&mut self, val: u8) { + self.0 = (self.0 & !(0x7f << 4usize)) | (((val as u32) & 0x7f) << 4usize); + } + #[doc = "Periodic frame interval"] + #[inline(always)] + pub const fn pfivl(&self) -> super::vals::Pfivl { + let val = (self.0 >> 11usize) & 0x03; + super::vals::Pfivl::from_bits(val as u8) + } + #[doc = "Periodic frame interval"] + #[inline(always)] + pub fn set_pfivl(&mut self, val: super::vals::Pfivl) { + self.0 = (self.0 & !(0x03 << 11usize)) | (((val.to_bits() as u32) & 0x03) << 11usize); + } + #[doc = "Transceiver delay"] + #[inline(always)] + pub const fn xcvrdly(&self) -> bool { + let val = (self.0 >> 14usize) & 0x01; + val != 0 + } + #[doc = "Transceiver delay"] + #[inline(always)] + pub fn set_xcvrdly(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 14usize)) | (((val as u32) & 0x01) << 14usize); + } + } + impl Default for Dcfg { + #[inline(always)] + fn default() -> Dcfg { + Dcfg(0) + } + } + #[doc = "Device control register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Dctl(pub u32); + impl Dctl { + #[doc = "Remote wakeup signaling"] + #[inline(always)] + pub const fn rwusig(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Remote wakeup signaling"] + #[inline(always)] + pub fn set_rwusig(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Soft disconnect"] + #[inline(always)] + pub const fn sdis(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Soft disconnect"] + #[inline(always)] + pub fn set_sdis(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "Global IN NAK status"] + #[inline(always)] + pub const fn ginsts(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "Global IN NAK status"] + #[inline(always)] + pub fn set_ginsts(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "Global OUT NAK status"] + #[inline(always)] + pub const fn gonsts(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "Global OUT NAK status"] + #[inline(always)] + pub fn set_gonsts(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "Test control"] + #[inline(always)] + pub const fn tctl(&self) -> u8 { + let val = (self.0 >> 4usize) & 0x07; + val as u8 + } + #[doc = "Test control"] + #[inline(always)] + pub fn set_tctl(&mut self, val: u8) { + self.0 = (self.0 & !(0x07 << 4usize)) | (((val as u32) & 0x07) << 4usize); + } + #[doc = "Set global IN NAK"] + #[inline(always)] + pub const fn sginak(&self) -> bool { + let val = (self.0 >> 7usize) & 0x01; + val != 0 + } + #[doc = "Set global IN NAK"] + #[inline(always)] + pub fn set_sginak(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 7usize)) | (((val as u32) & 0x01) << 7usize); + } + #[doc = "Clear global IN NAK"] + #[inline(always)] + pub const fn cginak(&self) -> bool { + let val = (self.0 >> 8usize) & 0x01; + val != 0 + } + #[doc = "Clear global IN NAK"] + #[inline(always)] + pub fn set_cginak(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 8usize)) | (((val as u32) & 0x01) << 8usize); + } + #[doc = "Set global OUT NAK"] + #[inline(always)] + pub const fn sgonak(&self) -> bool { + let val = (self.0 >> 9usize) & 0x01; + val != 0 + } + #[doc = "Set global OUT NAK"] + #[inline(always)] + pub fn set_sgonak(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 9usize)) | (((val as u32) & 0x01) << 9usize); + } + #[doc = "Clear global OUT NAK"] + #[inline(always)] + pub const fn cgonak(&self) -> bool { + let val = (self.0 >> 10usize) & 0x01; + val != 0 + } + #[doc = "Clear global OUT NAK"] + #[inline(always)] + pub fn set_cgonak(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 10usize)) | (((val as u32) & 0x01) << 10usize); + } + #[doc = "Power-on programming done"] + #[inline(always)] + pub const fn poprgdne(&self) -> bool { + let val = (self.0 >> 11usize) & 0x01; + val != 0 + } + #[doc = "Power-on programming done"] + #[inline(always)] + pub fn set_poprgdne(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 11usize)) | (((val as u32) & 0x01) << 11usize); + } + } + impl Default for Dctl { + #[inline(always)] + fn default() -> Dctl { + Dctl(0) + } + } + #[doc = "Device endpoint control register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Diepctl(pub u32); + impl Diepctl { + #[doc = "MPSIZ"] + #[inline(always)] + pub const fn mpsiz(&self) -> u16 { + let val = (self.0 >> 0usize) & 0x07ff; + val as u16 + } + #[doc = "MPSIZ"] + #[inline(always)] + pub fn set_mpsiz(&mut self, val: u16) { + self.0 = (self.0 & !(0x07ff << 0usize)) | (((val as u32) & 0x07ff) << 0usize); + } + #[doc = "USBAEP"] + #[inline(always)] + pub const fn usbaep(&self) -> bool { + let val = (self.0 >> 15usize) & 0x01; + val != 0 + } + #[doc = "USBAEP"] + #[inline(always)] + pub fn set_usbaep(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 15usize)) | (((val as u32) & 0x01) << 15usize); + } + #[doc = "EONUM/DPID"] + #[inline(always)] + pub const fn eonum_dpid(&self) -> bool { + let val = (self.0 >> 16usize) & 0x01; + val != 0 + } + #[doc = "EONUM/DPID"] + #[inline(always)] + pub fn set_eonum_dpid(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 16usize)) | (((val as u32) & 0x01) << 16usize); + } + #[doc = "NAKSTS"] + #[inline(always)] + pub const fn naksts(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "NAKSTS"] + #[inline(always)] + pub fn set_naksts(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "EPTYP"] + #[inline(always)] + pub const fn eptyp(&self) -> super::vals::Eptyp { + let val = (self.0 >> 18usize) & 0x03; + super::vals::Eptyp::from_bits(val as u8) + } + #[doc = "EPTYP"] + #[inline(always)] + pub fn set_eptyp(&mut self, val: super::vals::Eptyp) { + self.0 = (self.0 & !(0x03 << 18usize)) | (((val.to_bits() as u32) & 0x03) << 18usize); + } + #[doc = "SNPM"] + #[inline(always)] + pub const fn snpm(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "SNPM"] + #[inline(always)] + pub fn set_snpm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + #[doc = "STALL"] + #[inline(always)] + pub const fn stall(&self) -> bool { + let val = (self.0 >> 21usize) & 0x01; + val != 0 + } + #[doc = "STALL"] + #[inline(always)] + pub fn set_stall(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize); + } + #[doc = "TXFNUM"] + #[inline(always)] + pub const fn txfnum(&self) -> u8 { + let val = (self.0 >> 22usize) & 0x0f; + val as u8 + } + #[doc = "TXFNUM"] + #[inline(always)] + pub fn set_txfnum(&mut self, val: u8) { + self.0 = (self.0 & !(0x0f << 22usize)) | (((val as u32) & 0x0f) << 22usize); + } + #[doc = "CNAK"] + #[inline(always)] + pub const fn cnak(&self) -> bool { + let val = (self.0 >> 26usize) & 0x01; + val != 0 + } + #[doc = "CNAK"] + #[inline(always)] + pub fn set_cnak(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 26usize)) | (((val as u32) & 0x01) << 26usize); + } + #[doc = "SNAK"] + #[inline(always)] + pub const fn snak(&self) -> bool { + let val = (self.0 >> 27usize) & 0x01; + val != 0 + } + #[doc = "SNAK"] + #[inline(always)] + pub fn set_snak(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 27usize)) | (((val as u32) & 0x01) << 27usize); + } + #[doc = "SD0PID/SEVNFRM"] + #[inline(always)] + pub const fn sd0pid_sevnfrm(&self) -> bool { + let val = (self.0 >> 28usize) & 0x01; + val != 0 + } + #[doc = "SD0PID/SEVNFRM"] + #[inline(always)] + pub fn set_sd0pid_sevnfrm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); + } + #[doc = "SD1PID/SODDFRM"] + #[inline(always)] + pub const fn sd1pid_soddfrm(&self) -> bool { + let val = (self.0 >> 29usize) & 0x01; + val != 0 + } + #[doc = "SD1PID/SODDFRM"] + #[inline(always)] + pub fn set_sd1pid_soddfrm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); + } + #[doc = "EPDIS"] + #[inline(always)] + pub const fn epdis(&self) -> bool { + let val = (self.0 >> 30usize) & 0x01; + val != 0 + } + #[doc = "EPDIS"] + #[inline(always)] + pub fn set_epdis(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 30usize)) | (((val as u32) & 0x01) << 30usize); + } + #[doc = "EPENA"] + #[inline(always)] + pub const fn epena(&self) -> bool { + let val = (self.0 >> 31usize) & 0x01; + val != 0 + } + #[doc = "EPENA"] + #[inline(always)] + pub fn set_epena(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 31usize)) | (((val as u32) & 0x01) << 31usize); + } + } + impl Default for Diepctl { + #[inline(always)] + fn default() -> Diepctl { + Diepctl(0) + } + } + #[doc = "Device IN endpoint FIFO empty interrupt mask register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Diepempmsk(pub u32); + impl Diepempmsk { + #[doc = "IN EP Tx FIFO empty interrupt mask bits"] + #[inline(always)] + pub const fn ineptxfem(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "IN EP Tx FIFO empty interrupt mask bits"] + #[inline(always)] + pub fn set_ineptxfem(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + } + impl Default for Diepempmsk { + #[inline(always)] + fn default() -> Diepempmsk { + Diepempmsk(0) + } + } + #[doc = "Device endpoint interrupt register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Diepint(pub u32); + impl Diepint { + #[doc = "XFRC"] + #[inline(always)] + pub const fn xfrc(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "XFRC"] + #[inline(always)] + pub fn set_xfrc(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "EPDISD"] + #[inline(always)] + pub const fn epdisd(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "EPDISD"] + #[inline(always)] + pub fn set_epdisd(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "TOC"] + #[inline(always)] + pub const fn toc(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "TOC"] + #[inline(always)] + pub fn set_toc(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "ITTXFE"] + #[inline(always)] + pub const fn ittxfe(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "ITTXFE"] + #[inline(always)] + pub fn set_ittxfe(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + #[doc = "INEPNE"] + #[inline(always)] + pub const fn inepne(&self) -> bool { + let val = (self.0 >> 6usize) & 0x01; + val != 0 + } + #[doc = "INEPNE"] + #[inline(always)] + pub fn set_inepne(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 6usize)) | (((val as u32) & 0x01) << 6usize); + } + #[doc = "TXFE"] + #[inline(always)] + pub const fn txfe(&self) -> bool { + let val = (self.0 >> 7usize) & 0x01; + val != 0 + } + #[doc = "TXFE"] + #[inline(always)] + pub fn set_txfe(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 7usize)) | (((val as u32) & 0x01) << 7usize); + } + } + impl Default for Diepint { + #[inline(always)] + fn default() -> Diepint { + Diepint(0) + } + } + #[doc = "Device IN endpoint common interrupt mask register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Diepmsk(pub u32); + impl Diepmsk { + #[doc = "Transfer completed interrupt mask"] + #[inline(always)] + pub const fn xfrcm(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Transfer completed interrupt mask"] + #[inline(always)] + pub fn set_xfrcm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Endpoint disabled interrupt mask"] + #[inline(always)] + pub const fn epdm(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Endpoint disabled interrupt mask"] + #[inline(always)] + pub fn set_epdm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "Timeout condition mask (Non-isochronous endpoints)"] + #[inline(always)] + pub const fn tom(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "Timeout condition mask (Non-isochronous endpoints)"] + #[inline(always)] + pub fn set_tom(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "IN token received when TxFIFO empty mask"] + #[inline(always)] + pub const fn ittxfemsk(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "IN token received when TxFIFO empty mask"] + #[inline(always)] + pub fn set_ittxfemsk(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + #[doc = "IN token received with EP mismatch mask"] + #[inline(always)] + pub const fn inepnmm(&self) -> bool { + let val = (self.0 >> 5usize) & 0x01; + val != 0 + } + #[doc = "IN token received with EP mismatch mask"] + #[inline(always)] + pub fn set_inepnmm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 5usize)) | (((val as u32) & 0x01) << 5usize); + } + #[doc = "IN endpoint NAK effective mask"] + #[inline(always)] + pub const fn inepnem(&self) -> bool { + let val = (self.0 >> 6usize) & 0x01; + val != 0 + } + #[doc = "IN endpoint NAK effective mask"] + #[inline(always)] + pub fn set_inepnem(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 6usize)) | (((val as u32) & 0x01) << 6usize); + } + } + impl Default for Diepmsk { + #[inline(always)] + fn default() -> Diepmsk { + Diepmsk(0) + } + } + #[doc = "Device endpoint transfer size register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Dieptsiz(pub u32); + impl Dieptsiz { + #[doc = "Transfer size"] + #[inline(always)] + pub const fn xfrsiz(&self) -> u32 { + let val = (self.0 >> 0usize) & 0x0007_ffff; + val as u32 + } + #[doc = "Transfer size"] + #[inline(always)] + pub fn set_xfrsiz(&mut self, val: u32) { + self.0 = (self.0 & !(0x0007_ffff << 0usize)) | (((val as u32) & 0x0007_ffff) << 0usize); + } + #[doc = "Packet count"] + #[inline(always)] + pub const fn pktcnt(&self) -> u16 { + let val = (self.0 >> 19usize) & 0x03ff; + val as u16 + } + #[doc = "Packet count"] + #[inline(always)] + pub fn set_pktcnt(&mut self, val: u16) { + self.0 = (self.0 & !(0x03ff << 19usize)) | (((val as u32) & 0x03ff) << 19usize); + } + #[doc = "Multi count"] + #[inline(always)] + pub const fn mcnt(&self) -> u8 { + let val = (self.0 >> 29usize) & 0x03; + val as u8 + } + #[doc = "Multi count"] + #[inline(always)] + pub fn set_mcnt(&mut self, val: u8) { + self.0 = (self.0 & !(0x03 << 29usize)) | (((val as u32) & 0x03) << 29usize); + } + } + impl Default for Dieptsiz { + #[inline(always)] + fn default() -> Dieptsiz { + Dieptsiz(0) + } + } + #[doc = "Device endpoint control register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Doepctl(pub u32); + impl Doepctl { + #[doc = "MPSIZ"] + #[inline(always)] + pub const fn mpsiz(&self) -> u16 { + let val = (self.0 >> 0usize) & 0x07ff; + val as u16 + } + #[doc = "MPSIZ"] + #[inline(always)] + pub fn set_mpsiz(&mut self, val: u16) { + self.0 = (self.0 & !(0x07ff << 0usize)) | (((val as u32) & 0x07ff) << 0usize); + } + #[doc = "USBAEP"] + #[inline(always)] + pub const fn usbaep(&self) -> bool { + let val = (self.0 >> 15usize) & 0x01; + val != 0 + } + #[doc = "USBAEP"] + #[inline(always)] + pub fn set_usbaep(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 15usize)) | (((val as u32) & 0x01) << 15usize); + } + #[doc = "EONUM/DPID"] + #[inline(always)] + pub const fn eonum_dpid(&self) -> bool { + let val = (self.0 >> 16usize) & 0x01; + val != 0 + } + #[doc = "EONUM/DPID"] + #[inline(always)] + pub fn set_eonum_dpid(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 16usize)) | (((val as u32) & 0x01) << 16usize); + } + #[doc = "NAKSTS"] + #[inline(always)] + pub const fn naksts(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "NAKSTS"] + #[inline(always)] + pub fn set_naksts(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "EPTYP"] + #[inline(always)] + pub const fn eptyp(&self) -> super::vals::Eptyp { + let val = (self.0 >> 18usize) & 0x03; + super::vals::Eptyp::from_bits(val as u8) + } + #[doc = "EPTYP"] + #[inline(always)] + pub fn set_eptyp(&mut self, val: super::vals::Eptyp) { + self.0 = (self.0 & !(0x03 << 18usize)) | (((val.to_bits() as u32) & 0x03) << 18usize); + } + #[doc = "SNPM"] + #[inline(always)] + pub const fn snpm(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "SNPM"] + #[inline(always)] + pub fn set_snpm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + #[doc = "STALL"] + #[inline(always)] + pub const fn stall(&self) -> bool { + let val = (self.0 >> 21usize) & 0x01; + val != 0 + } + #[doc = "STALL"] + #[inline(always)] + pub fn set_stall(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize); + } + #[doc = "CNAK"] + #[inline(always)] + pub const fn cnak(&self) -> bool { + let val = (self.0 >> 26usize) & 0x01; + val != 0 + } + #[doc = "CNAK"] + #[inline(always)] + pub fn set_cnak(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 26usize)) | (((val as u32) & 0x01) << 26usize); + } + #[doc = "SNAK"] + #[inline(always)] + pub const fn snak(&self) -> bool { + let val = (self.0 >> 27usize) & 0x01; + val != 0 + } + #[doc = "SNAK"] + #[inline(always)] + pub fn set_snak(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 27usize)) | (((val as u32) & 0x01) << 27usize); + } + #[doc = "SD0PID/SEVNFRM"] + #[inline(always)] + pub const fn sd0pid_sevnfrm(&self) -> bool { + let val = (self.0 >> 28usize) & 0x01; + val != 0 + } + #[doc = "SD0PID/SEVNFRM"] + #[inline(always)] + pub fn set_sd0pid_sevnfrm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); + } + #[doc = "SD1PID/SODDFRM"] + #[inline(always)] + pub const fn sd1pid_soddfrm(&self) -> bool { + let val = (self.0 >> 29usize) & 0x01; + val != 0 + } + #[doc = "SD1PID/SODDFRM"] + #[inline(always)] + pub fn set_sd1pid_soddfrm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); + } + #[doc = "EPDIS"] + #[inline(always)] + pub const fn epdis(&self) -> bool { + let val = (self.0 >> 30usize) & 0x01; + val != 0 + } + #[doc = "EPDIS"] + #[inline(always)] + pub fn set_epdis(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 30usize)) | (((val as u32) & 0x01) << 30usize); + } + #[doc = "EPENA"] + #[inline(always)] + pub const fn epena(&self) -> bool { + let val = (self.0 >> 31usize) & 0x01; + val != 0 + } + #[doc = "EPENA"] + #[inline(always)] + pub fn set_epena(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 31usize)) | (((val as u32) & 0x01) << 31usize); + } + } + impl Default for Doepctl { + #[inline(always)] + fn default() -> Doepctl { + Doepctl(0) + } + } + #[doc = "Device endpoint interrupt register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Doepint(pub u32); + impl Doepint { + #[doc = "XFRC"] + #[inline(always)] + pub const fn xfrc(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "XFRC"] + #[inline(always)] + pub fn set_xfrc(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "EPDISD"] + #[inline(always)] + pub const fn epdisd(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "EPDISD"] + #[inline(always)] + pub fn set_epdisd(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "STUP"] + #[inline(always)] + pub const fn stup(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "STUP"] + #[inline(always)] + pub fn set_stup(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "OTEPDIS"] + #[inline(always)] + pub const fn otepdis(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "OTEPDIS"] + #[inline(always)] + pub fn set_otepdis(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + #[doc = "B2BSTUP"] + #[inline(always)] + pub const fn b2bstup(&self) -> bool { + let val = (self.0 >> 6usize) & 0x01; + val != 0 + } + #[doc = "B2BSTUP"] + #[inline(always)] + pub fn set_b2bstup(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 6usize)) | (((val as u32) & 0x01) << 6usize); + } + } + impl Default for Doepint { + #[inline(always)] + fn default() -> Doepint { + Doepint(0) + } + } + #[doc = "Device OUT endpoint common interrupt mask register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Doepmsk(pub u32); + impl Doepmsk { + #[doc = "Transfer completed interrupt mask"] + #[inline(always)] + pub const fn xfrcm(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Transfer completed interrupt mask"] + #[inline(always)] + pub fn set_xfrcm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Endpoint disabled interrupt mask"] + #[inline(always)] + pub const fn epdm(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Endpoint disabled interrupt mask"] + #[inline(always)] + pub fn set_epdm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "SETUP phase done mask"] + #[inline(always)] + pub const fn stupm(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "SETUP phase done mask"] + #[inline(always)] + pub fn set_stupm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "OUT token received when endpoint disabled mask"] + #[inline(always)] + pub const fn otepdm(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "OUT token received when endpoint disabled mask"] + #[inline(always)] + pub fn set_otepdm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + } + impl Default for Doepmsk { + #[inline(always)] + fn default() -> Doepmsk { + Doepmsk(0) + } + } + #[doc = "Device OUT endpoint transfer size register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Doeptsiz(pub u32); + impl Doeptsiz { + #[doc = "Transfer size"] + #[inline(always)] + pub const fn xfrsiz(&self) -> u32 { + let val = (self.0 >> 0usize) & 0x0007_ffff; + val as u32 + } + #[doc = "Transfer size"] + #[inline(always)] + pub fn set_xfrsiz(&mut self, val: u32) { + self.0 = (self.0 & !(0x0007_ffff << 0usize)) | (((val as u32) & 0x0007_ffff) << 0usize); + } + #[doc = "Packet count"] + #[inline(always)] + pub const fn pktcnt(&self) -> u16 { + let val = (self.0 >> 19usize) & 0x03ff; + val as u16 + } + #[doc = "Packet count"] + #[inline(always)] + pub fn set_pktcnt(&mut self, val: u16) { + self.0 = (self.0 & !(0x03ff << 19usize)) | (((val as u32) & 0x03ff) << 19usize); + } + #[doc = "Received data PID/SETUP packet count"] + #[inline(always)] + pub const fn rxdpid_stupcnt(&self) -> u8 { + let val = (self.0 >> 29usize) & 0x03; + val as u8 + } + #[doc = "Received data PID/SETUP packet count"] + #[inline(always)] + pub fn set_rxdpid_stupcnt(&mut self, val: u8) { + self.0 = (self.0 & !(0x03 << 29usize)) | (((val as u32) & 0x03) << 29usize); + } + } + impl Default for Doeptsiz { + #[inline(always)] + fn default() -> Doeptsiz { + Doeptsiz(0) + } + } + #[doc = "Device status register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Dsts(pub u32); + impl Dsts { + #[doc = "Suspend status"] + #[inline(always)] + pub const fn suspsts(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Suspend status"] + #[inline(always)] + pub fn set_suspsts(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Enumerated speed"] + #[inline(always)] + pub const fn enumspd(&self) -> super::vals::Dspd { + let val = (self.0 >> 1usize) & 0x03; + super::vals::Dspd::from_bits(val as u8) + } + #[doc = "Enumerated speed"] + #[inline(always)] + pub fn set_enumspd(&mut self, val: super::vals::Dspd) { + self.0 = (self.0 & !(0x03 << 1usize)) | (((val.to_bits() as u32) & 0x03) << 1usize); + } + #[doc = "Erratic error"] + #[inline(always)] + pub const fn eerr(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "Erratic error"] + #[inline(always)] + pub fn set_eerr(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "Frame number of the received SOF"] + #[inline(always)] + pub const fn fnsof(&self) -> u16 { + let val = (self.0 >> 8usize) & 0x3fff; + val as u16 + } + #[doc = "Frame number of the received SOF"] + #[inline(always)] + pub fn set_fnsof(&mut self, val: u16) { + self.0 = (self.0 & !(0x3fff << 8usize)) | (((val as u32) & 0x3fff) << 8usize); + } + } + impl Default for Dsts { + #[inline(always)] + fn default() -> Dsts { + Dsts(0) + } + } + #[doc = "Device IN endpoint transmit FIFO status register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Dtxfsts(pub u32); + impl Dtxfsts { + #[doc = "IN endpoint TxFIFO space available"] + #[inline(always)] + pub const fn ineptfsav(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "IN endpoint TxFIFO space available"] + #[inline(always)] + pub fn set_ineptfsav(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + } + impl Default for Dtxfsts { + #[inline(always)] + fn default() -> Dtxfsts { + Dtxfsts(0) + } + } + #[doc = "Device VBUS discharge time register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Dvbusdis(pub u32); + impl Dvbusdis { + #[doc = "Device VBUS discharge time"] + #[inline(always)] + pub const fn vbusdt(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "Device VBUS discharge time"] + #[inline(always)] + pub fn set_vbusdt(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + } + impl Default for Dvbusdis { + #[inline(always)] + fn default() -> Dvbusdis { + Dvbusdis(0) + } + } + #[doc = "Device VBUS pulsing time register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Dvbuspulse(pub u32); + impl Dvbuspulse { + #[doc = "Device VBUS pulsing time"] + #[inline(always)] + pub const fn dvbusp(&self) -> u16 { + let val = (self.0 >> 0usize) & 0x0fff; + val as u16 + } + #[doc = "Device VBUS pulsing time"] + #[inline(always)] + pub fn set_dvbusp(&mut self, val: u16) { + self.0 = (self.0 & !(0x0fff << 0usize)) | (((val as u32) & 0x0fff) << 0usize); + } + } + impl Default for Dvbuspulse { + #[inline(always)] + fn default() -> Dvbuspulse { + Dvbuspulse(0) + } + } + #[doc = "FIFO register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Fifo(pub u32); + impl Fifo { + #[doc = "Data"] + #[inline(always)] + pub const fn data(&self) -> u32 { + let val = (self.0 >> 0usize) & 0xffff_ffff; + val as u32 + } + #[doc = "Data"] + #[inline(always)] + pub fn set_data(&mut self, val: u32) { + self.0 = (self.0 & !(0xffff_ffff << 0usize)) | (((val as u32) & 0xffff_ffff) << 0usize); + } + } + impl Default for Fifo { + #[inline(always)] + fn default() -> Fifo { + Fifo(0) + } + } + #[doc = "FIFO size register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Fsiz(pub u32); + impl Fsiz { + #[doc = "RAM start address"] + #[inline(always)] + pub const fn sa(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "RAM start address"] + #[inline(always)] + pub fn set_sa(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + #[doc = "FIFO depth"] + #[inline(always)] + pub const fn fd(&self) -> u16 { + let val = (self.0 >> 16usize) & 0xffff; + val as u16 + } + #[doc = "FIFO depth"] + #[inline(always)] + pub fn set_fd(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 16usize)) | (((val as u32) & 0xffff) << 16usize); + } + } + impl Default for Fsiz { + #[inline(always)] + fn default() -> Fsiz { + Fsiz(0) + } + } + #[doc = "AHB configuration register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Gahbcfg(pub u32); + impl Gahbcfg { + #[doc = "Global interrupt mask"] + #[inline(always)] + pub const fn gint(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Global interrupt mask"] + #[inline(always)] + pub fn set_gint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Burst length/type"] + #[inline(always)] + pub const fn hbstlen(&self) -> u8 { + let val = (self.0 >> 1usize) & 0x0f; + val as u8 + } + #[doc = "Burst length/type"] + #[inline(always)] + pub fn set_hbstlen(&mut self, val: u8) { + self.0 = (self.0 & !(0x0f << 1usize)) | (((val as u32) & 0x0f) << 1usize); + } + #[doc = "DMA enable"] + #[inline(always)] + pub const fn dmaen(&self) -> bool { + let val = (self.0 >> 5usize) & 0x01; + val != 0 + } + #[doc = "DMA enable"] + #[inline(always)] + pub fn set_dmaen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 5usize)) | (((val as u32) & 0x01) << 5usize); + } + #[doc = "TxFIFO empty level"] + #[inline(always)] + pub const fn txfelvl(&self) -> bool { + let val = (self.0 >> 7usize) & 0x01; + val != 0 + } + #[doc = "TxFIFO empty level"] + #[inline(always)] + pub fn set_txfelvl(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 7usize)) | (((val as u32) & 0x01) << 7usize); + } + #[doc = "Periodic TxFIFO empty level"] + #[inline(always)] + pub const fn ptxfelvl(&self) -> bool { + let val = (self.0 >> 8usize) & 0x01; + val != 0 + } + #[doc = "Periodic TxFIFO empty level"] + #[inline(always)] + pub fn set_ptxfelvl(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 8usize)) | (((val as u32) & 0x01) << 8usize); + } + } + impl Default for Gahbcfg { + #[inline(always)] + fn default() -> Gahbcfg { + Gahbcfg(0) + } + } + #[doc = "General core configuration register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct GccfgV1(pub u32); + impl GccfgV1 { + #[doc = "Power down"] + #[inline(always)] + pub const fn pwrdwn(&self) -> bool { + let val = (self.0 >> 16usize) & 0x01; + val != 0 + } + #[doc = "Power down"] + #[inline(always)] + pub fn set_pwrdwn(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 16usize)) | (((val as u32) & 0x01) << 16usize); + } + #[doc = "Enable the VBUS \"A\" sensing device"] + #[inline(always)] + pub const fn vbusasen(&self) -> bool { + let val = (self.0 >> 18usize) & 0x01; + val != 0 + } + #[doc = "Enable the VBUS \"A\" sensing device"] + #[inline(always)] + pub fn set_vbusasen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize); + } + #[doc = "Enable the VBUS \"B\" sensing device"] + #[inline(always)] + pub const fn vbusbsen(&self) -> bool { + let val = (self.0 >> 19usize) & 0x01; + val != 0 + } + #[doc = "Enable the VBUS \"B\" sensing device"] + #[inline(always)] + pub fn set_vbusbsen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize); + } + #[doc = "SOF output enable"] + #[inline(always)] + pub const fn sofouten(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "SOF output enable"] + #[inline(always)] + pub fn set_sofouten(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + #[doc = "VBUS sensing disable"] + #[inline(always)] + pub const fn novbussens(&self) -> bool { + let val = (self.0 >> 21usize) & 0x01; + val != 0 + } + #[doc = "VBUS sensing disable"] + #[inline(always)] + pub fn set_novbussens(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize); + } + } + impl Default for GccfgV1 { + #[inline(always)] + fn default() -> GccfgV1 { + GccfgV1(0) + } + } + #[doc = "General core configuration register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct GccfgV2(pub u32); + impl GccfgV2 { + #[doc = "Data contact detection (DCD) status"] + #[inline(always)] + pub const fn dcdet(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Data contact detection (DCD) status"] + #[inline(always)] + pub fn set_dcdet(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Primary detection (PD) status"] + #[inline(always)] + pub const fn pdet(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Primary detection (PD) status"] + #[inline(always)] + pub fn set_pdet(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "Secondary detection (SD) status"] + #[inline(always)] + pub const fn sdet(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "Secondary detection (SD) status"] + #[inline(always)] + pub fn set_sdet(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "DM pull-up detection status"] + #[inline(always)] + pub const fn ps2det(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "DM pull-up detection status"] + #[inline(always)] + pub fn set_ps2det(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "Power down"] + #[inline(always)] + pub const fn pwrdwn(&self) -> bool { + let val = (self.0 >> 16usize) & 0x01; + val != 0 + } + #[doc = "Power down"] + #[inline(always)] + pub fn set_pwrdwn(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 16usize)) | (((val as u32) & 0x01) << 16usize); + } + #[doc = "Battery charging detector (BCD) enable"] + #[inline(always)] + pub const fn bcden(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "Battery charging detector (BCD) enable"] + #[inline(always)] + pub fn set_bcden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "Data contact detection (DCD) mode enable"] + #[inline(always)] + pub const fn dcden(&self) -> bool { + let val = (self.0 >> 18usize) & 0x01; + val != 0 + } + #[doc = "Data contact detection (DCD) mode enable"] + #[inline(always)] + pub fn set_dcden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize); + } + #[doc = "Primary detection (PD) mode enable"] + #[inline(always)] + pub const fn pden(&self) -> bool { + let val = (self.0 >> 19usize) & 0x01; + val != 0 + } + #[doc = "Primary detection (PD) mode enable"] + #[inline(always)] + pub fn set_pden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize); + } + #[doc = "Secondary detection (SD) mode enable"] + #[inline(always)] + pub const fn sden(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "Secondary detection (SD) mode enable"] + #[inline(always)] + pub fn set_sden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + #[doc = "USB VBUS detection enable"] + #[inline(always)] + pub const fn vbden(&self) -> bool { + let val = (self.0 >> 21usize) & 0x01; + val != 0 + } + #[doc = "USB VBUS detection enable"] + #[inline(always)] + pub fn set_vbden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize); + } + #[doc = "Internal high-speed PHY enable."] + #[inline(always)] + pub const fn phyhsen(&self) -> bool { + let val = (self.0 >> 23usize) & 0x01; + val != 0 + } + #[doc = "Internal high-speed PHY enable."] + #[inline(always)] + pub fn set_phyhsen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 23usize)) | (((val as u32) & 0x01) << 23usize); + } + } + impl Default for GccfgV2 { + #[inline(always)] + fn default() -> GccfgV2 { + GccfgV2(0) + } + } + #[doc = "OTG general core configuration register."] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct GccfgV3(pub u32); + impl GccfgV3 { + #[doc = "Charger detection, result of the current mode (primary or secondary)."] + #[inline(always)] + pub const fn chgdet(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Charger detection, result of the current mode (primary or secondary)."] + #[inline(always)] + pub fn set_chgdet(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Single-Ended DP indicator This bit gives the voltage level on DP (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub const fn fsvplus(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Single-Ended DP indicator This bit gives the voltage level on DP (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub fn set_fsvplus(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "Single-Ended DM indicator This bit gives the voltage level on DM (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub const fn fsvminus(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "Single-Ended DM indicator This bit gives the voltage level on DM (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub fn set_fsvminus(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "VBUS session indicator Indicates if VBUS is above VBUS session threshold."] + #[inline(always)] + pub const fn sessvld(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "VBUS session indicator Indicates if VBUS is above VBUS session threshold."] + #[inline(always)] + pub fn set_sessvld(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "Host CDP behavior enable."] + #[inline(always)] + pub const fn hcdpen(&self) -> bool { + let val = (self.0 >> 16usize) & 0x01; + val != 0 + } + #[doc = "Host CDP behavior enable."] + #[inline(always)] + pub fn set_hcdpen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 16usize)) | (((val as u32) & 0x01) << 16usize); + } + #[doc = "Host CDP port voltage detector enable on DP."] + #[inline(always)] + pub const fn hcdpdeten(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "Host CDP port voltage detector enable on DP."] + #[inline(always)] + pub fn set_hcdpdeten(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "Host CDP port Voltage source enable on DM."] + #[inline(always)] + pub const fn hvdmsrcen(&self) -> bool { + let val = (self.0 >> 18usize) & 0x01; + val != 0 + } + #[doc = "Host CDP port Voltage source enable on DM."] + #[inline(always)] + pub fn set_hvdmsrcen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize); + } + #[doc = "Data Contact Detection enable."] + #[inline(always)] + pub const fn dcden(&self) -> bool { + let val = (self.0 >> 19usize) & 0x01; + val != 0 + } + #[doc = "Data Contact Detection enable."] + #[inline(always)] + pub fn set_dcden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize); + } + #[doc = "Primary detection enable."] + #[inline(always)] + pub const fn pden(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "Primary detection enable."] + #[inline(always)] + pub fn set_pden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + #[doc = "VBUS detection enable Enables VBUS Sensing Comparators in order to detect VBUS presence and/or perform OTG operation."] + #[inline(always)] + pub const fn vbden(&self) -> bool { + let val = (self.0 >> 21usize) & 0x01; + val != 0 + } + #[doc = "VBUS detection enable Enables VBUS Sensing Comparators in order to detect VBUS presence and/or perform OTG operation."] + #[inline(always)] + pub fn set_vbden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize); + } + #[doc = "Secondary detection enable."] + #[inline(always)] + pub const fn sden(&self) -> bool { + let val = (self.0 >> 22usize) & 0x01; + val != 0 + } + #[doc = "Secondary detection enable."] + #[inline(always)] + pub fn set_sden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 22usize)) | (((val as u32) & 0x01) << 22usize); + } + #[doc = "Software override value of the VBUS B-session detection."] + #[inline(always)] + pub const fn vbvaloval(&self) -> bool { + let val = (self.0 >> 23usize) & 0x01; + val != 0 + } + #[doc = "Software override value of the VBUS B-session detection."] + #[inline(always)] + pub fn set_vbvaloval(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 23usize)) | (((val as u32) & 0x01) << 23usize); + } + #[doc = "Enables a software override of the VBUS B-session detection."] + #[inline(always)] + pub const fn vbvaloven(&self) -> bool { + let val = (self.0 >> 24usize) & 0x01; + val != 0 + } + #[doc = "Enables a software override of the VBUS B-session detection."] + #[inline(always)] + pub fn set_vbvaloven(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 24usize)) | (((val as u32) & 0x01) << 24usize); + } + #[doc = "Force host mode pull-downs If the ID pin functions are enabled, the host mode pull-downs on DP and DM activate automatically. However, whenever that is not the case, yet host mode is required, this bit must be used to force the pull-downs active."] + #[inline(always)] + pub const fn forcehostpd(&self) -> bool { + let val = (self.0 >> 25usize) & 0x01; + val != 0 + } + #[doc = "Force host mode pull-downs If the ID pin functions are enabled, the host mode pull-downs on DP and DM activate automatically. However, whenever that is not the case, yet host mode is required, this bit must be used to force the pull-downs active."] + #[inline(always)] + pub fn set_forcehostpd(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 25usize)) | (((val as u32) & 0x01) << 25usize); + } + } + impl Default for GccfgV3 { + #[inline(always)] + fn default() -> GccfgV3 { + GccfgV3(0) + } + } + #[doc = "I2C access register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Gi2cctl(pub u32); + impl Gi2cctl { + #[doc = "I2C Read/Write Data"] + #[inline(always)] + pub const fn rwdata(&self) -> u8 { + let val = (self.0 >> 0usize) & 0xff; + val as u8 + } + #[doc = "I2C Read/Write Data"] + #[inline(always)] + pub fn set_rwdata(&mut self, val: u8) { + self.0 = (self.0 & !(0xff << 0usize)) | (((val as u32) & 0xff) << 0usize); + } + #[doc = "I2C Register Address"] + #[inline(always)] + pub const fn regaddr(&self) -> u8 { + let val = (self.0 >> 8usize) & 0xff; + val as u8 + } + #[doc = "I2C Register Address"] + #[inline(always)] + pub fn set_regaddr(&mut self, val: u8) { + self.0 = (self.0 & !(0xff << 8usize)) | (((val as u32) & 0xff) << 8usize); + } + #[doc = "I2C Address"] + #[inline(always)] + pub const fn addr(&self) -> u8 { + let val = (self.0 >> 16usize) & 0x7f; + val as u8 + } + #[doc = "I2C Address"] + #[inline(always)] + pub fn set_addr(&mut self, val: u8) { + self.0 = (self.0 & !(0x7f << 16usize)) | (((val as u32) & 0x7f) << 16usize); + } + #[doc = "I2C Enable"] + #[inline(always)] + pub const fn i2cen(&self) -> bool { + let val = (self.0 >> 23usize) & 0x01; + val != 0 + } + #[doc = "I2C Enable"] + #[inline(always)] + pub fn set_i2cen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 23usize)) | (((val as u32) & 0x01) << 23usize); + } + #[doc = "I2C ACK"] + #[inline(always)] + pub const fn ack(&self) -> bool { + let val = (self.0 >> 24usize) & 0x01; + val != 0 + } + #[doc = "I2C ACK"] + #[inline(always)] + pub fn set_ack(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 24usize)) | (((val as u32) & 0x01) << 24usize); + } + #[doc = "I2C Device Address"] + #[inline(always)] + pub const fn i2cdevadr(&self) -> u8 { + let val = (self.0 >> 26usize) & 0x03; + val as u8 + } + #[doc = "I2C Device Address"] + #[inline(always)] + pub fn set_i2cdevadr(&mut self, val: u8) { + self.0 = (self.0 & !(0x03 << 26usize)) | (((val as u32) & 0x03) << 26usize); + } + #[doc = "I2C DatSe0 USB mode"] + #[inline(always)] + pub const fn i2cdatse0(&self) -> bool { + let val = (self.0 >> 28usize) & 0x01; + val != 0 + } + #[doc = "I2C DatSe0 USB mode"] + #[inline(always)] + pub fn set_i2cdatse0(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); + } + #[doc = "Read/Write Indicator"] + #[inline(always)] + pub const fn rw(&self) -> bool { + let val = (self.0 >> 30usize) & 0x01; + val != 0 + } + #[doc = "Read/Write Indicator"] + #[inline(always)] + pub fn set_rw(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 30usize)) | (((val as u32) & 0x01) << 30usize); + } + #[doc = "I2C Busy/Done"] + #[inline(always)] + pub const fn bsydne(&self) -> bool { + let val = (self.0 >> 31usize) & 0x01; + val != 0 + } + #[doc = "I2C Busy/Done"] + #[inline(always)] + pub fn set_bsydne(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 31usize)) | (((val as u32) & 0x01) << 31usize); + } + } + impl Default for Gi2cctl { + #[inline(always)] + fn default() -> Gi2cctl { + Gi2cctl(0) + } + } + #[doc = "Interrupt mask register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Gintmsk(pub u32); + impl Gintmsk { + #[doc = "Mode mismatch interrupt mask"] + #[inline(always)] + pub const fn mmism(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Mode mismatch interrupt mask"] + #[inline(always)] + pub fn set_mmism(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "OTG interrupt mask"] + #[inline(always)] + pub const fn otgint(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "OTG interrupt mask"] + #[inline(always)] + pub fn set_otgint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "Start of frame mask"] + #[inline(always)] + pub const fn sofm(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "Start of frame mask"] + #[inline(always)] + pub fn set_sofm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "Receive FIFO non-empty mask"] + #[inline(always)] + pub const fn rxflvlm(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "Receive FIFO non-empty mask"] + #[inline(always)] + pub fn set_rxflvlm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + #[doc = "Non-periodic TxFIFO empty mask"] + #[inline(always)] + pub const fn nptxfem(&self) -> bool { + let val = (self.0 >> 5usize) & 0x01; + val != 0 + } + #[doc = "Non-periodic TxFIFO empty mask"] + #[inline(always)] + pub fn set_nptxfem(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 5usize)) | (((val as u32) & 0x01) << 5usize); + } + #[doc = "Global non-periodic IN NAK effective mask"] + #[inline(always)] + pub const fn ginakeffm(&self) -> bool { + let val = (self.0 >> 6usize) & 0x01; + val != 0 + } + #[doc = "Global non-periodic IN NAK effective mask"] + #[inline(always)] + pub fn set_ginakeffm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 6usize)) | (((val as u32) & 0x01) << 6usize); + } + #[doc = "Global OUT NAK effective mask"] + #[inline(always)] + pub const fn gonakeffm(&self) -> bool { + let val = (self.0 >> 7usize) & 0x01; + val != 0 + } + #[doc = "Global OUT NAK effective mask"] + #[inline(always)] + pub fn set_gonakeffm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 7usize)) | (((val as u32) & 0x01) << 7usize); + } + #[doc = "Early suspend mask"] + #[inline(always)] + pub const fn esuspm(&self) -> bool { + let val = (self.0 >> 10usize) & 0x01; + val != 0 + } + #[doc = "Early suspend mask"] + #[inline(always)] + pub fn set_esuspm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 10usize)) | (((val as u32) & 0x01) << 10usize); + } + #[doc = "USB suspend mask"] + #[inline(always)] + pub const fn usbsuspm(&self) -> bool { + let val = (self.0 >> 11usize) & 0x01; + val != 0 + } + #[doc = "USB suspend mask"] + #[inline(always)] + pub fn set_usbsuspm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 11usize)) | (((val as u32) & 0x01) << 11usize); + } + #[doc = "USB reset mask"] + #[inline(always)] + pub const fn usbrst(&self) -> bool { + let val = (self.0 >> 12usize) & 0x01; + val != 0 + } + #[doc = "USB reset mask"] + #[inline(always)] + pub fn set_usbrst(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 12usize)) | (((val as u32) & 0x01) << 12usize); + } + #[doc = "Enumeration done mask"] + #[inline(always)] + pub const fn enumdnem(&self) -> bool { + let val = (self.0 >> 13usize) & 0x01; + val != 0 + } + #[doc = "Enumeration done mask"] + #[inline(always)] + pub fn set_enumdnem(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 13usize)) | (((val as u32) & 0x01) << 13usize); + } + #[doc = "Isochronous OUT packet dropped interrupt mask"] + #[inline(always)] + pub const fn isoodrpm(&self) -> bool { + let val = (self.0 >> 14usize) & 0x01; + val != 0 + } + #[doc = "Isochronous OUT packet dropped interrupt mask"] + #[inline(always)] + pub fn set_isoodrpm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 14usize)) | (((val as u32) & 0x01) << 14usize); + } + #[doc = "End of periodic frame interrupt mask"] + #[inline(always)] + pub const fn eopfm(&self) -> bool { + let val = (self.0 >> 15usize) & 0x01; + val != 0 + } + #[doc = "End of periodic frame interrupt mask"] + #[inline(always)] + pub fn set_eopfm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 15usize)) | (((val as u32) & 0x01) << 15usize); + } + #[doc = "Endpoint mismatch interrupt mask"] + #[inline(always)] + pub const fn epmism(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "Endpoint mismatch interrupt mask"] + #[inline(always)] + pub fn set_epmism(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "IN endpoints interrupt mask"] + #[inline(always)] + pub const fn iepint(&self) -> bool { + let val = (self.0 >> 18usize) & 0x01; + val != 0 + } + #[doc = "IN endpoints interrupt mask"] + #[inline(always)] + pub fn set_iepint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize); + } + #[doc = "OUT endpoints interrupt mask"] + #[inline(always)] + pub const fn oepint(&self) -> bool { + let val = (self.0 >> 19usize) & 0x01; + val != 0 + } + #[doc = "OUT endpoints interrupt mask"] + #[inline(always)] + pub fn set_oepint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize); + } + #[doc = "Incomplete isochronous IN transfer mask"] + #[inline(always)] + pub const fn iisoixfrm(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "Incomplete isochronous IN transfer mask"] + #[inline(always)] + pub fn set_iisoixfrm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + #[doc = "Incomplete periodic transfer mask (host mode) / Incomplete isochronous OUT transfer mask (device mode)"] + #[inline(always)] + pub const fn ipxfrm_iisooxfrm(&self) -> bool { + let val = (self.0 >> 21usize) & 0x01; + val != 0 + } + #[doc = "Incomplete periodic transfer mask (host mode) / Incomplete isochronous OUT transfer mask (device mode)"] + #[inline(always)] + pub fn set_ipxfrm_iisooxfrm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize); + } + #[doc = "Data fetch suspended mask"] + #[inline(always)] + pub const fn fsuspm(&self) -> bool { + let val = (self.0 >> 22usize) & 0x01; + val != 0 + } + #[doc = "Data fetch suspended mask"] + #[inline(always)] + pub fn set_fsuspm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 22usize)) | (((val as u32) & 0x01) << 22usize); + } + #[doc = "Reset detected interrupt mask"] + #[inline(always)] + pub const fn rstde(&self) -> bool { + let val = (self.0 >> 23usize) & 0x01; + val != 0 + } + #[doc = "Reset detected interrupt mask"] + #[inline(always)] + pub fn set_rstde(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 23usize)) | (((val as u32) & 0x01) << 23usize); + } + #[doc = "Host port interrupt mask"] + #[inline(always)] + pub const fn prtim(&self) -> bool { + let val = (self.0 >> 24usize) & 0x01; + val != 0 + } + #[doc = "Host port interrupt mask"] + #[inline(always)] + pub fn set_prtim(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 24usize)) | (((val as u32) & 0x01) << 24usize); + } + #[doc = "Host channels interrupt mask"] + #[inline(always)] + pub const fn hcim(&self) -> bool { + let val = (self.0 >> 25usize) & 0x01; + val != 0 + } + #[doc = "Host channels interrupt mask"] + #[inline(always)] + pub fn set_hcim(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 25usize)) | (((val as u32) & 0x01) << 25usize); + } + #[doc = "Periodic TxFIFO empty mask"] + #[inline(always)] + pub const fn ptxfem(&self) -> bool { + let val = (self.0 >> 26usize) & 0x01; + val != 0 + } + #[doc = "Periodic TxFIFO empty mask"] + #[inline(always)] + pub fn set_ptxfem(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 26usize)) | (((val as u32) & 0x01) << 26usize); + } + #[doc = "LPM interrupt mask"] + #[inline(always)] + pub const fn lpmintm(&self) -> bool { + let val = (self.0 >> 27usize) & 0x01; + val != 0 + } + #[doc = "LPM interrupt mask"] + #[inline(always)] + pub fn set_lpmintm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 27usize)) | (((val as u32) & 0x01) << 27usize); + } + #[doc = "Connector ID status change mask"] + #[inline(always)] + pub const fn cidschgm(&self) -> bool { + let val = (self.0 >> 28usize) & 0x01; + val != 0 + } + #[doc = "Connector ID status change mask"] + #[inline(always)] + pub fn set_cidschgm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); + } + #[doc = "Disconnect detected interrupt mask"] + #[inline(always)] + pub const fn discint(&self) -> bool { + let val = (self.0 >> 29usize) & 0x01; + val != 0 + } + #[doc = "Disconnect detected interrupt mask"] + #[inline(always)] + pub fn set_discint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); + } + #[doc = "Session request/new session detected interrupt mask"] + #[inline(always)] + pub const fn srqim(&self) -> bool { + let val = (self.0 >> 30usize) & 0x01; + val != 0 + } + #[doc = "Session request/new session detected interrupt mask"] + #[inline(always)] + pub fn set_srqim(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 30usize)) | (((val as u32) & 0x01) << 30usize); + } + #[doc = "Resume/remote wakeup detected interrupt mask"] + #[inline(always)] + pub const fn wuim(&self) -> bool { + let val = (self.0 >> 31usize) & 0x01; + val != 0 + } + #[doc = "Resume/remote wakeup detected interrupt mask"] + #[inline(always)] + pub fn set_wuim(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 31usize)) | (((val as u32) & 0x01) << 31usize); + } + } + impl Default for Gintmsk { + #[inline(always)] + fn default() -> Gintmsk { + Gintmsk(0) + } + } + #[doc = "Core interrupt register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Gintsts(pub u32); + impl Gintsts { + #[doc = "Current mode of operation"] + #[inline(always)] + pub const fn cmod(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Current mode of operation"] + #[inline(always)] + pub fn set_cmod(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Mode mismatch interrupt"] + #[inline(always)] + pub const fn mmis(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Mode mismatch interrupt"] + #[inline(always)] + pub fn set_mmis(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "OTG interrupt"] + #[inline(always)] + pub const fn otgint(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "OTG interrupt"] + #[inline(always)] + pub fn set_otgint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "Start of frame"] + #[inline(always)] + pub const fn sof(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "Start of frame"] + #[inline(always)] + pub fn set_sof(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "RxFIFO non-empty"] + #[inline(always)] + pub const fn rxflvl(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "RxFIFO non-empty"] + #[inline(always)] + pub fn set_rxflvl(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + #[doc = "Non-periodic TxFIFO empty"] + #[inline(always)] + pub const fn nptxfe(&self) -> bool { + let val = (self.0 >> 5usize) & 0x01; + val != 0 + } + #[doc = "Non-periodic TxFIFO empty"] + #[inline(always)] + pub fn set_nptxfe(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 5usize)) | (((val as u32) & 0x01) << 5usize); + } + #[doc = "Global IN non-periodic NAK effective"] + #[inline(always)] + pub const fn ginakeff(&self) -> bool { + let val = (self.0 >> 6usize) & 0x01; + val != 0 + } + #[doc = "Global IN non-periodic NAK effective"] + #[inline(always)] + pub fn set_ginakeff(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 6usize)) | (((val as u32) & 0x01) << 6usize); + } + #[doc = "Global OUT NAK effective"] + #[inline(always)] + pub const fn goutnakeff(&self) -> bool { + let val = (self.0 >> 7usize) & 0x01; + val != 0 + } + #[doc = "Global OUT NAK effective"] + #[inline(always)] + pub fn set_goutnakeff(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 7usize)) | (((val as u32) & 0x01) << 7usize); + } + #[doc = "Early suspend"] + #[inline(always)] + pub const fn esusp(&self) -> bool { + let val = (self.0 >> 10usize) & 0x01; + val != 0 + } + #[doc = "Early suspend"] + #[inline(always)] + pub fn set_esusp(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 10usize)) | (((val as u32) & 0x01) << 10usize); + } + #[doc = "USB suspend"] + #[inline(always)] + pub const fn usbsusp(&self) -> bool { + let val = (self.0 >> 11usize) & 0x01; + val != 0 + } + #[doc = "USB suspend"] + #[inline(always)] + pub fn set_usbsusp(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 11usize)) | (((val as u32) & 0x01) << 11usize); + } + #[doc = "USB reset"] + #[inline(always)] + pub const fn usbrst(&self) -> bool { + let val = (self.0 >> 12usize) & 0x01; + val != 0 + } + #[doc = "USB reset"] + #[inline(always)] + pub fn set_usbrst(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 12usize)) | (((val as u32) & 0x01) << 12usize); + } + #[doc = "Enumeration done"] + #[inline(always)] + pub const fn enumdne(&self) -> bool { + let val = (self.0 >> 13usize) & 0x01; + val != 0 + } + #[doc = "Enumeration done"] + #[inline(always)] + pub fn set_enumdne(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 13usize)) | (((val as u32) & 0x01) << 13usize); + } + #[doc = "Isochronous OUT packet dropped interrupt"] + #[inline(always)] + pub const fn isoodrp(&self) -> bool { + let val = (self.0 >> 14usize) & 0x01; + val != 0 + } + #[doc = "Isochronous OUT packet dropped interrupt"] + #[inline(always)] + pub fn set_isoodrp(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 14usize)) | (((val as u32) & 0x01) << 14usize); + } + #[doc = "End of periodic frame interrupt"] + #[inline(always)] + pub const fn eopf(&self) -> bool { + let val = (self.0 >> 15usize) & 0x01; + val != 0 + } + #[doc = "End of periodic frame interrupt"] + #[inline(always)] + pub fn set_eopf(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 15usize)) | (((val as u32) & 0x01) << 15usize); + } + #[doc = "IN endpoint interrupt"] + #[inline(always)] + pub const fn iepint(&self) -> bool { + let val = (self.0 >> 18usize) & 0x01; + val != 0 + } + #[doc = "IN endpoint interrupt"] + #[inline(always)] + pub fn set_iepint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize); + } + #[doc = "OUT endpoint interrupt"] + #[inline(always)] + pub const fn oepint(&self) -> bool { + let val = (self.0 >> 19usize) & 0x01; + val != 0 + } + #[doc = "OUT endpoint interrupt"] + #[inline(always)] + pub fn set_oepint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize); + } + #[doc = "Incomplete isochronous IN transfer"] + #[inline(always)] + pub const fn iisoixfr(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "Incomplete isochronous IN transfer"] + #[inline(always)] + pub fn set_iisoixfr(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + #[doc = "Incomplete periodic transfer (host mode) / Incomplete isochronous OUT transfer (device mode)"] + #[inline(always)] + pub const fn ipxfr_incompisoout(&self) -> bool { + let val = (self.0 >> 21usize) & 0x01; + val != 0 + } + #[doc = "Incomplete periodic transfer (host mode) / Incomplete isochronous OUT transfer (device mode)"] + #[inline(always)] + pub fn set_ipxfr_incompisoout(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize); + } + #[doc = "Data fetch suspended"] + #[inline(always)] + pub const fn datafsusp(&self) -> bool { + let val = (self.0 >> 22usize) & 0x01; + val != 0 + } + #[doc = "Data fetch suspended"] + #[inline(always)] + pub fn set_datafsusp(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 22usize)) | (((val as u32) & 0x01) << 22usize); + } + #[doc = "Host port interrupt"] + #[inline(always)] + pub const fn hprtint(&self) -> bool { + let val = (self.0 >> 24usize) & 0x01; + val != 0 + } + #[doc = "Host port interrupt"] + #[inline(always)] + pub fn set_hprtint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 24usize)) | (((val as u32) & 0x01) << 24usize); + } + #[doc = "Host channels interrupt"] + #[inline(always)] + pub const fn hcint(&self) -> bool { + let val = (self.0 >> 25usize) & 0x01; + val != 0 + } + #[doc = "Host channels interrupt"] + #[inline(always)] + pub fn set_hcint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 25usize)) | (((val as u32) & 0x01) << 25usize); + } + #[doc = "Periodic TxFIFO empty"] + #[inline(always)] + pub const fn ptxfe(&self) -> bool { + let val = (self.0 >> 26usize) & 0x01; + val != 0 + } + #[doc = "Periodic TxFIFO empty"] + #[inline(always)] + pub fn set_ptxfe(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 26usize)) | (((val as u32) & 0x01) << 26usize); + } + #[doc = "Connector ID status change"] + #[inline(always)] + pub const fn cidschg(&self) -> bool { + let val = (self.0 >> 28usize) & 0x01; + val != 0 + } + #[doc = "Connector ID status change"] + #[inline(always)] + pub fn set_cidschg(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); + } + #[doc = "Disconnect detected interrupt"] + #[inline(always)] + pub const fn discint(&self) -> bool { + let val = (self.0 >> 29usize) & 0x01; + val != 0 + } + #[doc = "Disconnect detected interrupt"] + #[inline(always)] + pub fn set_discint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); + } + #[doc = "Session request/new session detected interrupt"] + #[inline(always)] + pub const fn srqint(&self) -> bool { + let val = (self.0 >> 30usize) & 0x01; + val != 0 + } + #[doc = "Session request/new session detected interrupt"] + #[inline(always)] + pub fn set_srqint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 30usize)) | (((val as u32) & 0x01) << 30usize); + } + #[doc = "Resume/remote wakeup detected interrupt"] + #[inline(always)] + pub const fn wkupint(&self) -> bool { + let val = (self.0 >> 31usize) & 0x01; + val != 0 + } + #[doc = "Resume/remote wakeup detected interrupt"] + #[inline(always)] + pub fn set_wkupint(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 31usize)) | (((val as u32) & 0x01) << 31usize); + } + } + impl Default for Gintsts { + #[inline(always)] + fn default() -> Gintsts { + Gintsts(0) + } + } + #[doc = "Core LPM configuration register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Glpmcfg(pub u32); + impl Glpmcfg { + #[doc = "LPM support enable"] + #[inline(always)] + pub const fn lpmen(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "LPM support enable"] + #[inline(always)] + pub fn set_lpmen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "LPM token acknowledge enable"] + #[inline(always)] + pub const fn lpmack(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "LPM token acknowledge enable"] + #[inline(always)] + pub fn set_lpmack(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "Best effort service latency"] + #[inline(always)] + pub const fn besl(&self) -> u8 { + let val = (self.0 >> 2usize) & 0x0f; + val as u8 + } + #[doc = "Best effort service latency"] + #[inline(always)] + pub fn set_besl(&mut self, val: u8) { + self.0 = (self.0 & !(0x0f << 2usize)) | (((val as u32) & 0x0f) << 2usize); + } + #[doc = "bRemoteWake value"] + #[inline(always)] + pub const fn remwake(&self) -> bool { + let val = (self.0 >> 6usize) & 0x01; + val != 0 + } + #[doc = "bRemoteWake value"] + #[inline(always)] + pub fn set_remwake(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 6usize)) | (((val as u32) & 0x01) << 6usize); + } + #[doc = "L1 Shallow Sleep enable"] + #[inline(always)] + pub const fn l1ssen(&self) -> bool { + let val = (self.0 >> 7usize) & 0x01; + val != 0 + } + #[doc = "L1 Shallow Sleep enable"] + #[inline(always)] + pub fn set_l1ssen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 7usize)) | (((val as u32) & 0x01) << 7usize); + } + #[doc = "BESL threshold"] + #[inline(always)] + pub const fn beslthrs(&self) -> u8 { + let val = (self.0 >> 8usize) & 0x0f; + val as u8 + } + #[doc = "BESL threshold"] + #[inline(always)] + pub fn set_beslthrs(&mut self, val: u8) { + self.0 = (self.0 & !(0x0f << 8usize)) | (((val as u32) & 0x0f) << 8usize); + } + #[doc = "L1 deep sleep enable"] + #[inline(always)] + pub const fn l1dsen(&self) -> bool { + let val = (self.0 >> 12usize) & 0x01; + val != 0 + } + #[doc = "L1 deep sleep enable"] + #[inline(always)] + pub fn set_l1dsen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 12usize)) | (((val as u32) & 0x01) << 12usize); + } + #[doc = "LPM response"] + #[inline(always)] + pub const fn lpmrst(&self) -> u8 { + let val = (self.0 >> 13usize) & 0x03; + val as u8 + } + #[doc = "LPM response"] + #[inline(always)] + pub fn set_lpmrst(&mut self, val: u8) { + self.0 = (self.0 & !(0x03 << 13usize)) | (((val as u32) & 0x03) << 13usize); + } + #[doc = "Port sleep status"] + #[inline(always)] + pub const fn slpsts(&self) -> bool { + let val = (self.0 >> 15usize) & 0x01; + val != 0 + } + #[doc = "Port sleep status"] + #[inline(always)] + pub fn set_slpsts(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 15usize)) | (((val as u32) & 0x01) << 15usize); + } + #[doc = "Sleep State Resume OK"] + #[inline(always)] + pub const fn l1rsmok(&self) -> bool { + let val = (self.0 >> 16usize) & 0x01; + val != 0 + } + #[doc = "Sleep State Resume OK"] + #[inline(always)] + pub fn set_l1rsmok(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 16usize)) | (((val as u32) & 0x01) << 16usize); + } + #[doc = "LPM Channel Index"] + #[inline(always)] + pub const fn lpmchidx(&self) -> u8 { + let val = (self.0 >> 17usize) & 0x0f; + val as u8 + } + #[doc = "LPM Channel Index"] + #[inline(always)] + pub fn set_lpmchidx(&mut self, val: u8) { + self.0 = (self.0 & !(0x0f << 17usize)) | (((val as u32) & 0x0f) << 17usize); + } + #[doc = "LPM retry count"] + #[inline(always)] + pub const fn lpmrcnt(&self) -> u8 { + let val = (self.0 >> 21usize) & 0x07; + val as u8 + } + #[doc = "LPM retry count"] + #[inline(always)] + pub fn set_lpmrcnt(&mut self, val: u8) { + self.0 = (self.0 & !(0x07 << 21usize)) | (((val as u32) & 0x07) << 21usize); + } + #[doc = "Send LPM transaction"] + #[inline(always)] + pub const fn sndlpm(&self) -> bool { + let val = (self.0 >> 24usize) & 0x01; + val != 0 + } + #[doc = "Send LPM transaction"] + #[inline(always)] + pub fn set_sndlpm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 24usize)) | (((val as u32) & 0x01) << 24usize); + } + #[doc = "LPM retry count status"] + #[inline(always)] + pub const fn lpmrcntsts(&self) -> u8 { + let val = (self.0 >> 25usize) & 0x07; + val as u8 + } + #[doc = "LPM retry count status"] + #[inline(always)] + pub fn set_lpmrcntsts(&mut self, val: u8) { + self.0 = (self.0 & !(0x07 << 25usize)) | (((val as u32) & 0x07) << 25usize); + } + #[doc = "Enable best effort service latency"] + #[inline(always)] + pub const fn enbesl(&self) -> bool { + let val = (self.0 >> 28usize) & 0x01; + val != 0 + } + #[doc = "Enable best effort service latency"] + #[inline(always)] + pub fn set_enbesl(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); + } + } + impl Default for Glpmcfg { + #[inline(always)] + fn default() -> Glpmcfg { + Glpmcfg(0) + } + } + #[doc = "Control and status register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Gotgctl(pub u32); + impl Gotgctl { + #[doc = "Session request success"] + #[inline(always)] + pub const fn srqscs(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Session request success"] + #[inline(always)] + pub fn set_srqscs(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Session request"] + #[inline(always)] + pub const fn srq(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Session request"] + #[inline(always)] + pub fn set_srq(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "VBUS valid override enable"] + #[inline(always)] + pub const fn vbvaloen(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "VBUS valid override enable"] + #[inline(always)] + pub fn set_vbvaloen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "VBUS valid override value"] + #[inline(always)] + pub const fn vbvaloval(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "VBUS valid override value"] + #[inline(always)] + pub fn set_vbvaloval(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "A-peripheral session valid override enable"] + #[inline(always)] + pub const fn avaloen(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "A-peripheral session valid override enable"] + #[inline(always)] + pub fn set_avaloen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + #[doc = "A-peripheral session valid override value"] + #[inline(always)] + pub const fn avaloval(&self) -> bool { + let val = (self.0 >> 5usize) & 0x01; + val != 0 + } + #[doc = "A-peripheral session valid override value"] + #[inline(always)] + pub fn set_avaloval(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 5usize)) | (((val as u32) & 0x01) << 5usize); + } + #[doc = "B-peripheral session valid override enable"] + #[inline(always)] + pub const fn bvaloen(&self) -> bool { + let val = (self.0 >> 6usize) & 0x01; + val != 0 + } + #[doc = "B-peripheral session valid override enable"] + #[inline(always)] + pub fn set_bvaloen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 6usize)) | (((val as u32) & 0x01) << 6usize); + } + #[doc = "B-peripheral session valid override value"] + #[inline(always)] + pub const fn bvaloval(&self) -> bool { + let val = (self.0 >> 7usize) & 0x01; + val != 0 + } + #[doc = "B-peripheral session valid override value"] + #[inline(always)] + pub fn set_bvaloval(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 7usize)) | (((val as u32) & 0x01) << 7usize); + } + #[doc = "Host negotiation success"] + #[inline(always)] + pub const fn hngscs(&self) -> bool { + let val = (self.0 >> 8usize) & 0x01; + val != 0 + } + #[doc = "Host negotiation success"] + #[inline(always)] + pub fn set_hngscs(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 8usize)) | (((val as u32) & 0x01) << 8usize); + } + #[doc = "HNP request"] + #[inline(always)] + pub const fn hnprq(&self) -> bool { + let val = (self.0 >> 9usize) & 0x01; + val != 0 + } + #[doc = "HNP request"] + #[inline(always)] + pub fn set_hnprq(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 9usize)) | (((val as u32) & 0x01) << 9usize); + } + #[doc = "Host set HNP enable"] + #[inline(always)] + pub const fn hshnpen(&self) -> bool { + let val = (self.0 >> 10usize) & 0x01; + val != 0 + } + #[doc = "Host set HNP enable"] + #[inline(always)] + pub fn set_hshnpen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 10usize)) | (((val as u32) & 0x01) << 10usize); + } + #[doc = "Device HNP enabled"] + #[inline(always)] + pub const fn dhnpen(&self) -> bool { + let val = (self.0 >> 11usize) & 0x01; + val != 0 + } + #[doc = "Device HNP enabled"] + #[inline(always)] + pub fn set_dhnpen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 11usize)) | (((val as u32) & 0x01) << 11usize); + } + #[doc = "Embedded host enable"] + #[inline(always)] + pub const fn ehen(&self) -> bool { + let val = (self.0 >> 12usize) & 0x01; + val != 0 + } + #[doc = "Embedded host enable"] + #[inline(always)] + pub fn set_ehen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 12usize)) | (((val as u32) & 0x01) << 12usize); + } + #[doc = "Connector ID status"] + #[inline(always)] + pub const fn cidsts(&self) -> bool { + let val = (self.0 >> 16usize) & 0x01; + val != 0 + } + #[doc = "Connector ID status"] + #[inline(always)] + pub fn set_cidsts(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 16usize)) | (((val as u32) & 0x01) << 16usize); + } + #[doc = "Long/short debounce time"] + #[inline(always)] + pub const fn dbct(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "Long/short debounce time"] + #[inline(always)] + pub fn set_dbct(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "A-session valid"] + #[inline(always)] + pub const fn asvld(&self) -> bool { + let val = (self.0 >> 18usize) & 0x01; + val != 0 + } + #[doc = "A-session valid"] + #[inline(always)] + pub fn set_asvld(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize); + } + #[doc = "B-session valid"] + #[inline(always)] + pub const fn bsvld(&self) -> bool { + let val = (self.0 >> 19usize) & 0x01; + val != 0 + } + #[doc = "B-session valid"] + #[inline(always)] + pub fn set_bsvld(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize); + } + } + impl Default for Gotgctl { + #[inline(always)] + fn default() -> Gotgctl { + Gotgctl(0) + } + } + #[doc = "Interrupt register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Gotgint(pub u32); + impl Gotgint { + #[doc = "Session end detected"] + #[inline(always)] + pub const fn sedet(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "Session end detected"] + #[inline(always)] + pub fn set_sedet(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "Session request success status change"] + #[inline(always)] + pub const fn srsschg(&self) -> bool { + let val = (self.0 >> 8usize) & 0x01; + val != 0 + } + #[doc = "Session request success status change"] + #[inline(always)] + pub fn set_srsschg(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 8usize)) | (((val as u32) & 0x01) << 8usize); + } + #[doc = "Host negotiation success status change"] + #[inline(always)] + pub const fn hnsschg(&self) -> bool { + let val = (self.0 >> 9usize) & 0x01; + val != 0 + } + #[doc = "Host negotiation success status change"] + #[inline(always)] + pub fn set_hnsschg(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 9usize)) | (((val as u32) & 0x01) << 9usize); + } + #[doc = "Host negotiation detected"] + #[inline(always)] + pub const fn hngdet(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "Host negotiation detected"] + #[inline(always)] + pub fn set_hngdet(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "A-device timeout change"] + #[inline(always)] + pub const fn adtochg(&self) -> bool { + let val = (self.0 >> 18usize) & 0x01; + val != 0 + } + #[doc = "A-device timeout change"] + #[inline(always)] + pub fn set_adtochg(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize); + } + #[doc = "Debounce done"] + #[inline(always)] + pub const fn dbcdne(&self) -> bool { + let val = (self.0 >> 19usize) & 0x01; + val != 0 + } + #[doc = "Debounce done"] + #[inline(always)] + pub fn set_dbcdne(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize); + } + #[doc = "ID input pin changed"] + #[inline(always)] + pub const fn idchng(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "ID input pin changed"] + #[inline(always)] + pub fn set_idchng(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + } + impl Default for Gotgint { + #[inline(always)] + fn default() -> Gotgint { + Gotgint(0) + } + } + #[doc = "Reset register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Grstctl(pub u32); + impl Grstctl { + #[doc = "Core soft reset"] + #[inline(always)] + pub const fn csrst(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Core soft reset"] + #[inline(always)] + pub fn set_csrst(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "HCLK soft reset"] + #[inline(always)] + pub const fn hsrst(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "HCLK soft reset"] + #[inline(always)] + pub fn set_hsrst(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "Host frame counter reset"] + #[inline(always)] + pub const fn fcrst(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "Host frame counter reset"] + #[inline(always)] + pub fn set_fcrst(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "RxFIFO flush"] + #[inline(always)] + pub const fn rxfflsh(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "RxFIFO flush"] + #[inline(always)] + pub fn set_rxfflsh(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + #[doc = "TxFIFO flush"] + #[inline(always)] + pub const fn txfflsh(&self) -> bool { + let val = (self.0 >> 5usize) & 0x01; + val != 0 + } + #[doc = "TxFIFO flush"] + #[inline(always)] + pub fn set_txfflsh(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 5usize)) | (((val as u32) & 0x01) << 5usize); + } + #[doc = "TxFIFO number"] + #[inline(always)] + pub const fn txfnum(&self) -> u8 { + let val = (self.0 >> 6usize) & 0x1f; + val as u8 + } + #[doc = "TxFIFO number"] + #[inline(always)] + pub fn set_txfnum(&mut self, val: u8) { + self.0 = (self.0 & !(0x1f << 6usize)) | (((val as u32) & 0x1f) << 6usize); + } + #[doc = "DMA request signal enabled for USB OTG HS"] + #[inline(always)] + pub const fn dmareq(&self) -> bool { + let val = (self.0 >> 30usize) & 0x01; + val != 0 + } + #[doc = "DMA request signal enabled for USB OTG HS"] + #[inline(always)] + pub fn set_dmareq(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 30usize)) | (((val as u32) & 0x01) << 30usize); + } + #[doc = "AHB master idle"] + #[inline(always)] + pub const fn ahbidl(&self) -> bool { + let val = (self.0 >> 31usize) & 0x01; + val != 0 + } + #[doc = "AHB master idle"] + #[inline(always)] + pub fn set_ahbidl(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 31usize)) | (((val as u32) & 0x01) << 31usize); + } + } + impl Default for Grstctl { + #[inline(always)] + fn default() -> Grstctl { + Grstctl(0) + } + } + #[doc = "Receive FIFO size register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Grxfsiz(pub u32); + impl Grxfsiz { + #[doc = "RxFIFO depth"] + #[inline(always)] + pub const fn rxfd(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "RxFIFO depth"] + #[inline(always)] + pub fn set_rxfd(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + } + impl Default for Grxfsiz { + #[inline(always)] + fn default() -> Grxfsiz { + Grxfsiz(0) + } + } + #[doc = "Status read and pop register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Grxsts(pub u32); + impl Grxsts { + #[doc = "Endpoint number (device mode) / Channel number (host mode)"] + #[inline(always)] + pub const fn epnum(&self) -> u8 { + let val = (self.0 >> 0usize) & 0x0f; + val as u8 + } + #[doc = "Endpoint number (device mode) / Channel number (host mode)"] + #[inline(always)] + pub fn set_epnum(&mut self, val: u8) { + self.0 = (self.0 & !(0x0f << 0usize)) | (((val as u32) & 0x0f) << 0usize); + } + #[doc = "Byte count"] + #[inline(always)] + pub const fn bcnt(&self) -> u16 { + let val = (self.0 >> 4usize) & 0x07ff; + val as u16 + } + #[doc = "Byte count"] + #[inline(always)] + pub fn set_bcnt(&mut self, val: u16) { + self.0 = (self.0 & !(0x07ff << 4usize)) | (((val as u32) & 0x07ff) << 4usize); + } + #[doc = "Data PID"] + #[inline(always)] + pub const fn dpid(&self) -> super::vals::Dpid { + let val = (self.0 >> 15usize) & 0x03; + super::vals::Dpid::from_bits(val as u8) + } + #[doc = "Data PID"] + #[inline(always)] + pub fn set_dpid(&mut self, val: super::vals::Dpid) { + self.0 = (self.0 & !(0x03 << 15usize)) | (((val.to_bits() as u32) & 0x03) << 15usize); + } + #[doc = "Packet status (device mode)"] + #[inline(always)] + pub const fn pktstsd(&self) -> super::vals::Pktstsd { + let val = (self.0 >> 17usize) & 0x0f; + super::vals::Pktstsd::from_bits(val as u8) + } + #[doc = "Packet status (device mode)"] + #[inline(always)] + pub fn set_pktstsd(&mut self, val: super::vals::Pktstsd) { + self.0 = (self.0 & !(0x0f << 17usize)) | (((val.to_bits() as u32) & 0x0f) << 17usize); + } + #[doc = "Packet status (host mode)"] + #[inline(always)] + pub const fn pktstsh(&self) -> super::vals::Pktstsh { + let val = (self.0 >> 17usize) & 0x0f; + super::vals::Pktstsh::from_bits(val as u8) + } + #[doc = "Packet status (host mode)"] + #[inline(always)] + pub fn set_pktstsh(&mut self, val: super::vals::Pktstsh) { + self.0 = (self.0 & !(0x0f << 17usize)) | (((val.to_bits() as u32) & 0x0f) << 17usize); + } + #[doc = "Frame number (device mode)"] + #[inline(always)] + pub const fn frmnum(&self) -> u8 { + let val = (self.0 >> 21usize) & 0x0f; + val as u8 + } + #[doc = "Frame number (device mode)"] + #[inline(always)] + pub fn set_frmnum(&mut self, val: u8) { + self.0 = (self.0 & !(0x0f << 21usize)) | (((val as u32) & 0x0f) << 21usize); + } + } + impl Default for Grxsts { + #[inline(always)] + fn default() -> Grxsts { + Grxsts(0) + } + } + #[doc = "USB configuration register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Gusbcfg(pub u32); + impl Gusbcfg { + #[doc = "FS timeout calibration"] + #[inline(always)] + pub const fn tocal(&self) -> u8 { + let val = (self.0 >> 0usize) & 0x07; + val as u8 + } + #[doc = "FS timeout calibration"] + #[inline(always)] + pub fn set_tocal(&mut self, val: u8) { + self.0 = (self.0 & !(0x07 << 0usize)) | (((val as u32) & 0x07) << 0usize); + } + #[doc = "Full-speed internal serial transceiver enable"] + #[inline(always)] + pub const fn physel(&self) -> bool { + let val = (self.0 >> 6usize) & 0x01; + val != 0 + } + #[doc = "Full-speed internal serial transceiver enable"] + #[inline(always)] + pub fn set_physel(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 6usize)) | (((val as u32) & 0x01) << 6usize); + } + #[doc = "SRP-capable"] + #[inline(always)] + pub const fn srpcap(&self) -> bool { + let val = (self.0 >> 8usize) & 0x01; + val != 0 + } + #[doc = "SRP-capable"] + #[inline(always)] + pub fn set_srpcap(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 8usize)) | (((val as u32) & 0x01) << 8usize); + } + #[doc = "HNP-capable"] + #[inline(always)] + pub const fn hnpcap(&self) -> bool { + let val = (self.0 >> 9usize) & 0x01; + val != 0 + } + #[doc = "HNP-capable"] + #[inline(always)] + pub fn set_hnpcap(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 9usize)) | (((val as u32) & 0x01) << 9usize); + } + #[doc = "USB turnaround time"] + #[inline(always)] + pub const fn trdt(&self) -> u8 { + let val = (self.0 >> 10usize) & 0x0f; + val as u8 + } + #[doc = "USB turnaround time"] + #[inline(always)] + pub fn set_trdt(&mut self, val: u8) { + self.0 = (self.0 & !(0x0f << 10usize)) | (((val as u32) & 0x0f) << 10usize); + } + #[doc = "PHY Low-power clock select"] + #[inline(always)] + pub const fn phylpcs(&self) -> bool { + let val = (self.0 >> 15usize) & 0x01; + val != 0 + } + #[doc = "PHY Low-power clock select"] + #[inline(always)] + pub fn set_phylpcs(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 15usize)) | (((val as u32) & 0x01) << 15usize); + } + #[doc = "ULPI FS/LS select"] + #[inline(always)] + pub const fn ulpifsls(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "ULPI FS/LS select"] + #[inline(always)] + pub fn set_ulpifsls(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "ULPI Auto-resume"] + #[inline(always)] + pub const fn ulpiar(&self) -> bool { + let val = (self.0 >> 18usize) & 0x01; + val != 0 + } + #[doc = "ULPI Auto-resume"] + #[inline(always)] + pub fn set_ulpiar(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize); + } + #[doc = "ULPI Clock SuspendM"] + #[inline(always)] + pub const fn ulpicsm(&self) -> bool { + let val = (self.0 >> 19usize) & 0x01; + val != 0 + } + #[doc = "ULPI Clock SuspendM"] + #[inline(always)] + pub fn set_ulpicsm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize); + } + #[doc = "ULPI External VBUS Drive"] + #[inline(always)] + pub const fn ulpievbusd(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "ULPI External VBUS Drive"] + #[inline(always)] + pub fn set_ulpievbusd(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + #[doc = "ULPI external VBUS indicator"] + #[inline(always)] + pub const fn ulpievbusi(&self) -> bool { + let val = (self.0 >> 21usize) & 0x01; + val != 0 + } + #[doc = "ULPI external VBUS indicator"] + #[inline(always)] + pub fn set_ulpievbusi(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize); + } + #[doc = "TermSel DLine pulsing selection"] + #[inline(always)] + pub const fn tsdps(&self) -> bool { + let val = (self.0 >> 22usize) & 0x01; + val != 0 + } + #[doc = "TermSel DLine pulsing selection"] + #[inline(always)] + pub fn set_tsdps(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 22usize)) | (((val as u32) & 0x01) << 22usize); + } + #[doc = "Indicator complement"] + #[inline(always)] + pub const fn pcci(&self) -> bool { + let val = (self.0 >> 23usize) & 0x01; + val != 0 + } + #[doc = "Indicator complement"] + #[inline(always)] + pub fn set_pcci(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 23usize)) | (((val as u32) & 0x01) << 23usize); + } + #[doc = "Indicator pass through"] + #[inline(always)] + pub const fn ptci(&self) -> bool { + let val = (self.0 >> 24usize) & 0x01; + val != 0 + } + #[doc = "Indicator pass through"] + #[inline(always)] + pub fn set_ptci(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 24usize)) | (((val as u32) & 0x01) << 24usize); + } + #[doc = "ULPI interface protect disable"] + #[inline(always)] + pub const fn ulpiipd(&self) -> bool { + let val = (self.0 >> 25usize) & 0x01; + val != 0 + } + #[doc = "ULPI interface protect disable"] + #[inline(always)] + pub fn set_ulpiipd(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 25usize)) | (((val as u32) & 0x01) << 25usize); + } + #[doc = "Force host mode"] + #[inline(always)] + pub const fn fhmod(&self) -> bool { + let val = (self.0 >> 29usize) & 0x01; + val != 0 + } + #[doc = "Force host mode"] + #[inline(always)] + pub fn set_fhmod(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); + } + #[doc = "Force device mode"] + #[inline(always)] + pub const fn fdmod(&self) -> bool { + let val = (self.0 >> 30usize) & 0x01; + val != 0 + } + #[doc = "Force device mode"] + #[inline(always)] + pub fn set_fdmod(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 30usize)) | (((val as u32) & 0x01) << 30usize); + } + #[doc = "Corrupt Tx packet"] + #[inline(always)] + pub const fn ctxpkt(&self) -> bool { + let val = (self.0 >> 31usize) & 0x01; + val != 0 + } + #[doc = "Corrupt Tx packet"] + #[inline(always)] + pub fn set_ctxpkt(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 31usize)) | (((val as u32) & 0x01) << 31usize); + } + } + impl Default for Gusbcfg { + #[inline(always)] + fn default() -> Gusbcfg { + Gusbcfg(0) + } + } + #[doc = "Host all channels interrupt register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Haint(pub u32); + impl Haint { + #[doc = "Channel interrupts"] + #[inline(always)] + pub const fn haint(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "Channel interrupts"] + #[inline(always)] + pub fn set_haint(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + } + impl Default for Haint { + #[inline(always)] + fn default() -> Haint { + Haint(0) + } + } + #[doc = "Host all channels interrupt mask register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Haintmsk(pub u32); + impl Haintmsk { + #[doc = "Channel interrupt mask"] + #[inline(always)] + pub const fn haintm(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "Channel interrupt mask"] + #[inline(always)] + pub fn set_haintm(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + } + impl Default for Haintmsk { + #[inline(always)] + fn default() -> Haintmsk { + Haintmsk(0) + } + } + #[doc = "Host channel characteristics register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Hcchar(pub u32); + impl Hcchar { + #[doc = "Maximum packet size"] + #[inline(always)] + pub const fn mpsiz(&self) -> u16 { + let val = (self.0 >> 0usize) & 0x07ff; + val as u16 + } + #[doc = "Maximum packet size"] + #[inline(always)] + pub fn set_mpsiz(&mut self, val: u16) { + self.0 = (self.0 & !(0x07ff << 0usize)) | (((val as u32) & 0x07ff) << 0usize); + } + #[doc = "Endpoint number"] + #[inline(always)] + pub const fn epnum(&self) -> u8 { + let val = (self.0 >> 11usize) & 0x0f; + val as u8 + } + #[doc = "Endpoint number"] + #[inline(always)] + pub fn set_epnum(&mut self, val: u8) { + self.0 = (self.0 & !(0x0f << 11usize)) | (((val as u32) & 0x0f) << 11usize); + } + #[doc = "Endpoint direction"] + #[inline(always)] + pub const fn epdir(&self) -> bool { + let val = (self.0 >> 15usize) & 0x01; + val != 0 + } + #[doc = "Endpoint direction"] + #[inline(always)] + pub fn set_epdir(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 15usize)) | (((val as u32) & 0x01) << 15usize); + } + #[doc = "Low-speed device"] + #[inline(always)] + pub const fn lsdev(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "Low-speed device"] + #[inline(always)] + pub fn set_lsdev(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "Endpoint type"] + #[inline(always)] + pub const fn eptyp(&self) -> super::vals::Eptyp { + let val = (self.0 >> 18usize) & 0x03; + super::vals::Eptyp::from_bits(val as u8) + } + #[doc = "Endpoint type"] + #[inline(always)] + pub fn set_eptyp(&mut self, val: super::vals::Eptyp) { + self.0 = (self.0 & !(0x03 << 18usize)) | (((val.to_bits() as u32) & 0x03) << 18usize); + } + #[doc = "Multicount"] + #[inline(always)] + pub const fn mcnt(&self) -> u8 { + let val = (self.0 >> 20usize) & 0x03; + val as u8 + } + #[doc = "Multicount"] + #[inline(always)] + pub fn set_mcnt(&mut self, val: u8) { + self.0 = (self.0 & !(0x03 << 20usize)) | (((val as u32) & 0x03) << 20usize); + } + #[doc = "Device address"] + #[inline(always)] + pub const fn dad(&self) -> u8 { + let val = (self.0 >> 22usize) & 0x7f; + val as u8 + } + #[doc = "Device address"] + #[inline(always)] + pub fn set_dad(&mut self, val: u8) { + self.0 = (self.0 & !(0x7f << 22usize)) | (((val as u32) & 0x7f) << 22usize); + } + #[doc = "Odd frame"] + #[inline(always)] + pub const fn oddfrm(&self) -> bool { + let val = (self.0 >> 29usize) & 0x01; + val != 0 + } + #[doc = "Odd frame"] + #[inline(always)] + pub fn set_oddfrm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); + } + #[doc = "Channel disable"] + #[inline(always)] + pub const fn chdis(&self) -> bool { + let val = (self.0 >> 30usize) & 0x01; + val != 0 + } + #[doc = "Channel disable"] + #[inline(always)] + pub fn set_chdis(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 30usize)) | (((val as u32) & 0x01) << 30usize); + } + #[doc = "Channel enable"] + #[inline(always)] + pub const fn chena(&self) -> bool { + let val = (self.0 >> 31usize) & 0x01; + val != 0 + } + #[doc = "Channel enable"] + #[inline(always)] + pub fn set_chena(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 31usize)) | (((val as u32) & 0x01) << 31usize); + } + } + impl Default for Hcchar { + #[inline(always)] + fn default() -> Hcchar { + Hcchar(0) + } + } + #[doc = "Host configuration register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Hcfg(pub u32); + impl Hcfg { + #[doc = "FS/LS PHY clock select"] + #[inline(always)] + pub const fn fslspcs(&self) -> u8 { + let val = (self.0 >> 0usize) & 0x03; + val as u8 + } + #[doc = "FS/LS PHY clock select"] + #[inline(always)] + pub fn set_fslspcs(&mut self, val: u8) { + self.0 = (self.0 & !(0x03 << 0usize)) | (((val as u32) & 0x03) << 0usize); + } + #[doc = "FS- and LS-only support"] + #[inline(always)] + pub const fn fslss(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "FS- and LS-only support"] + #[inline(always)] + pub fn set_fslss(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + } + impl Default for Hcfg { + #[inline(always)] + fn default() -> Hcfg { + Hcfg(0) + } + } + #[doc = "Host channel interrupt register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Hcint(pub u32); + impl Hcint { + #[doc = "Transfer completed"] + #[inline(always)] + pub const fn xfrc(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Transfer completed"] + #[inline(always)] + pub fn set_xfrc(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Channel halted"] + #[inline(always)] + pub const fn chh(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Channel halted"] + #[inline(always)] + pub fn set_chh(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "STALL response received interrupt"] + #[inline(always)] + pub const fn stall(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "STALL response received interrupt"] + #[inline(always)] + pub fn set_stall(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "NAK response received interrupt"] + #[inline(always)] + pub const fn nak(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "NAK response received interrupt"] + #[inline(always)] + pub fn set_nak(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + #[doc = "ACK response received/transmitted interrupt"] + #[inline(always)] + pub const fn ack(&self) -> bool { + let val = (self.0 >> 5usize) & 0x01; + val != 0 + } + #[doc = "ACK response received/transmitted interrupt"] + #[inline(always)] + pub fn set_ack(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 5usize)) | (((val as u32) & 0x01) << 5usize); + } + #[doc = "Transaction error"] + #[inline(always)] + pub const fn txerr(&self) -> bool { + let val = (self.0 >> 7usize) & 0x01; + val != 0 + } + #[doc = "Transaction error"] + #[inline(always)] + pub fn set_txerr(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 7usize)) | (((val as u32) & 0x01) << 7usize); + } + #[doc = "Babble error"] + #[inline(always)] + pub const fn bberr(&self) -> bool { + let val = (self.0 >> 8usize) & 0x01; + val != 0 + } + #[doc = "Babble error"] + #[inline(always)] + pub fn set_bberr(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 8usize)) | (((val as u32) & 0x01) << 8usize); + } + #[doc = "Frame overrun"] + #[inline(always)] + pub const fn frmor(&self) -> bool { + let val = (self.0 >> 9usize) & 0x01; + val != 0 + } + #[doc = "Frame overrun"] + #[inline(always)] + pub fn set_frmor(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 9usize)) | (((val as u32) & 0x01) << 9usize); + } + #[doc = "Data toggle error"] + #[inline(always)] + pub const fn dterr(&self) -> bool { + let val = (self.0 >> 10usize) & 0x01; + val != 0 + } + #[doc = "Data toggle error"] + #[inline(always)] + pub fn set_dterr(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 10usize)) | (((val as u32) & 0x01) << 10usize); + } + } + impl Default for Hcint { + #[inline(always)] + fn default() -> Hcint { + Hcint(0) + } + } + #[doc = "Host channel mask register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Hcintmsk(pub u32); + impl Hcintmsk { + #[doc = "Transfer completed mask"] + #[inline(always)] + pub const fn xfrcm(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Transfer completed mask"] + #[inline(always)] + pub fn set_xfrcm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Channel halted mask"] + #[inline(always)] + pub const fn chhm(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Channel halted mask"] + #[inline(always)] + pub fn set_chhm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "STALL response received interrupt mask"] + #[inline(always)] + pub const fn stallm(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "STALL response received interrupt mask"] + #[inline(always)] + pub fn set_stallm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "NAK response received interrupt mask"] + #[inline(always)] + pub const fn nakm(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "NAK response received interrupt mask"] + #[inline(always)] + pub fn set_nakm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + #[doc = "ACK response received/transmitted interrupt mask"] + #[inline(always)] + pub const fn ackm(&self) -> bool { + let val = (self.0 >> 5usize) & 0x01; + val != 0 + } + #[doc = "ACK response received/transmitted interrupt mask"] + #[inline(always)] + pub fn set_ackm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 5usize)) | (((val as u32) & 0x01) << 5usize); + } + #[doc = "Response received interrupt mask"] + #[inline(always)] + pub const fn nyet(&self) -> bool { + let val = (self.0 >> 6usize) & 0x01; + val != 0 + } + #[doc = "Response received interrupt mask"] + #[inline(always)] + pub fn set_nyet(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 6usize)) | (((val as u32) & 0x01) << 6usize); + } + #[doc = "Transaction error mask"] + #[inline(always)] + pub const fn txerrm(&self) -> bool { + let val = (self.0 >> 7usize) & 0x01; + val != 0 + } + #[doc = "Transaction error mask"] + #[inline(always)] + pub fn set_txerrm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 7usize)) | (((val as u32) & 0x01) << 7usize); + } + #[doc = "Babble error mask"] + #[inline(always)] + pub const fn bberrm(&self) -> bool { + let val = (self.0 >> 8usize) & 0x01; + val != 0 + } + #[doc = "Babble error mask"] + #[inline(always)] + pub fn set_bberrm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 8usize)) | (((val as u32) & 0x01) << 8usize); + } + #[doc = "Frame overrun mask"] + #[inline(always)] + pub const fn frmorm(&self) -> bool { + let val = (self.0 >> 9usize) & 0x01; + val != 0 + } + #[doc = "Frame overrun mask"] + #[inline(always)] + pub fn set_frmorm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 9usize)) | (((val as u32) & 0x01) << 9usize); + } + #[doc = "Data toggle error mask"] + #[inline(always)] + pub const fn dterrm(&self) -> bool { + let val = (self.0 >> 10usize) & 0x01; + val != 0 + } + #[doc = "Data toggle error mask"] + #[inline(always)] + pub fn set_dterrm(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 10usize)) | (((val as u32) & 0x01) << 10usize); + } + } + impl Default for Hcintmsk { + #[inline(always)] + fn default() -> Hcintmsk { + Hcintmsk(0) + } + } + #[doc = "Host channel transfer size register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Hctsiz(pub u32); + impl Hctsiz { + #[doc = "Transfer size"] + #[inline(always)] + pub const fn xfrsiz(&self) -> u32 { + let val = (self.0 >> 0usize) & 0x0007_ffff; + val as u32 + } + #[doc = "Transfer size"] + #[inline(always)] + pub fn set_xfrsiz(&mut self, val: u32) { + self.0 = (self.0 & !(0x0007_ffff << 0usize)) | (((val as u32) & 0x0007_ffff) << 0usize); + } + #[doc = "Packet count"] + #[inline(always)] + pub const fn pktcnt(&self) -> u16 { + let val = (self.0 >> 19usize) & 0x03ff; + val as u16 + } + #[doc = "Packet count"] + #[inline(always)] + pub fn set_pktcnt(&mut self, val: u16) { + self.0 = (self.0 & !(0x03ff << 19usize)) | (((val as u32) & 0x03ff) << 19usize); + } + #[doc = "Data PID"] + #[inline(always)] + pub const fn dpid(&self) -> u8 { + let val = (self.0 >> 29usize) & 0x03; + val as u8 + } + #[doc = "Data PID"] + #[inline(always)] + pub fn set_dpid(&mut self, val: u8) { + self.0 = (self.0 & !(0x03 << 29usize)) | (((val as u32) & 0x03) << 29usize); + } + } + impl Default for Hctsiz { + #[inline(always)] + fn default() -> Hctsiz { + Hctsiz(0) + } + } + #[doc = "Host frame interval register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Hfir(pub u32); + impl Hfir { + #[doc = "Frame interval"] + #[inline(always)] + pub const fn frivl(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "Frame interval"] + #[inline(always)] + pub fn set_frivl(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + } + impl Default for Hfir { + #[inline(always)] + fn default() -> Hfir { + Hfir(0) + } + } + #[doc = "Host frame number/frame time remaining register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Hfnum(pub u32); + impl Hfnum { + #[doc = "Frame number"] + #[inline(always)] + pub const fn frnum(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "Frame number"] + #[inline(always)] + pub fn set_frnum(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + #[doc = "Frame time remaining"] + #[inline(always)] + pub const fn ftrem(&self) -> u16 { + let val = (self.0 >> 16usize) & 0xffff; + val as u16 + } + #[doc = "Frame time remaining"] + #[inline(always)] + pub fn set_ftrem(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 16usize)) | (((val as u32) & 0xffff) << 16usize); + } + } + impl Default for Hfnum { + #[inline(always)] + fn default() -> Hfnum { + Hfnum(0) + } + } + #[doc = "Non-periodic transmit FIFO/queue status register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Hnptxsts(pub u32); + impl Hnptxsts { + #[doc = "Non-periodic TxFIFO space available"] + #[inline(always)] + pub const fn nptxfsav(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "Non-periodic TxFIFO space available"] + #[inline(always)] + pub fn set_nptxfsav(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + #[doc = "Non-periodic transmit request queue space available"] + #[inline(always)] + pub const fn nptqxsav(&self) -> u8 { + let val = (self.0 >> 16usize) & 0xff; + val as u8 + } + #[doc = "Non-periodic transmit request queue space available"] + #[inline(always)] + pub fn set_nptqxsav(&mut self, val: u8) { + self.0 = (self.0 & !(0xff << 16usize)) | (((val as u32) & 0xff) << 16usize); + } + #[doc = "Top of the non-periodic transmit request queue"] + #[inline(always)] + pub const fn nptxqtop(&self) -> u8 { + let val = (self.0 >> 24usize) & 0x7f; + val as u8 + } + #[doc = "Top of the non-periodic transmit request queue"] + #[inline(always)] + pub fn set_nptxqtop(&mut self, val: u8) { + self.0 = (self.0 & !(0x7f << 24usize)) | (((val as u32) & 0x7f) << 24usize); + } + } + impl Default for Hnptxsts { + #[inline(always)] + fn default() -> Hnptxsts { + Hnptxsts(0) + } + } + #[doc = "Host port control and status register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Hprt(pub u32); + impl Hprt { + #[doc = "Port connect status"] + #[inline(always)] + pub const fn pcsts(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Port connect status"] + #[inline(always)] + pub fn set_pcsts(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Port connect detected"] + #[inline(always)] + pub const fn pcdet(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Port connect detected"] + #[inline(always)] + pub fn set_pcdet(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "Port enable"] + #[inline(always)] + pub const fn pena(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "Port enable"] + #[inline(always)] + pub fn set_pena(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "Port enable/disable change"] + #[inline(always)] + pub const fn penchng(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "Port enable/disable change"] + #[inline(always)] + pub fn set_penchng(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "Port overcurrent active"] + #[inline(always)] + pub const fn poca(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "Port overcurrent active"] + #[inline(always)] + pub fn set_poca(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + #[doc = "Port overcurrent change"] + #[inline(always)] + pub const fn pocchng(&self) -> bool { + let val = (self.0 >> 5usize) & 0x01; + val != 0 + } + #[doc = "Port overcurrent change"] + #[inline(always)] + pub fn set_pocchng(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 5usize)) | (((val as u32) & 0x01) << 5usize); + } + #[doc = "Port resume"] + #[inline(always)] + pub const fn pres(&self) -> bool { + let val = (self.0 >> 6usize) & 0x01; + val != 0 + } + #[doc = "Port resume"] + #[inline(always)] + pub fn set_pres(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 6usize)) | (((val as u32) & 0x01) << 6usize); + } + #[doc = "Port suspend"] + #[inline(always)] + pub const fn psusp(&self) -> bool { + let val = (self.0 >> 7usize) & 0x01; + val != 0 + } + #[doc = "Port suspend"] + #[inline(always)] + pub fn set_psusp(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 7usize)) | (((val as u32) & 0x01) << 7usize); + } + #[doc = "Port reset"] + #[inline(always)] + pub const fn prst(&self) -> bool { + let val = (self.0 >> 8usize) & 0x01; + val != 0 + } + #[doc = "Port reset"] + #[inline(always)] + pub fn set_prst(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 8usize)) | (((val as u32) & 0x01) << 8usize); + } + #[doc = "Port line status"] + #[inline(always)] + pub const fn plsts(&self) -> u8 { + let val = (self.0 >> 10usize) & 0x03; + val as u8 + } + #[doc = "Port line status"] + #[inline(always)] + pub fn set_plsts(&mut self, val: u8) { + self.0 = (self.0 & !(0x03 << 10usize)) | (((val as u32) & 0x03) << 10usize); + } + #[doc = "Port power"] + #[inline(always)] + pub const fn ppwr(&self) -> bool { + let val = (self.0 >> 12usize) & 0x01; + val != 0 + } + #[doc = "Port power"] + #[inline(always)] + pub fn set_ppwr(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 12usize)) | (((val as u32) & 0x01) << 12usize); + } + #[doc = "Port test control"] + #[inline(always)] + pub const fn ptctl(&self) -> u8 { + let val = (self.0 >> 13usize) & 0x0f; + val as u8 + } + #[doc = "Port test control"] + #[inline(always)] + pub fn set_ptctl(&mut self, val: u8) { + self.0 = (self.0 & !(0x0f << 13usize)) | (((val as u32) & 0x0f) << 13usize); + } + #[doc = "Port speed"] + #[inline(always)] + pub const fn pspd(&self) -> u8 { + let val = (self.0 >> 17usize) & 0x03; + val as u8 + } + #[doc = "Port speed"] + #[inline(always)] + pub fn set_pspd(&mut self, val: u8) { + self.0 = (self.0 & !(0x03 << 17usize)) | (((val as u32) & 0x03) << 17usize); + } + } + impl Default for Hprt { + #[inline(always)] + fn default() -> Hprt { + Hprt(0) + } + } + #[doc = "Periodic transmit FIFO/queue status register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Hptxsts(pub u32); + impl Hptxsts { + #[doc = "Periodic transmit data FIFO space available"] + #[inline(always)] + pub const fn ptxfsavl(&self) -> u16 { + let val = (self.0 >> 0usize) & 0xffff; + val as u16 + } + #[doc = "Periodic transmit data FIFO space available"] + #[inline(always)] + pub fn set_ptxfsavl(&mut self, val: u16) { + self.0 = (self.0 & !(0xffff << 0usize)) | (((val as u32) & 0xffff) << 0usize); + } + #[doc = "Periodic transmit request queue space available"] + #[inline(always)] + pub const fn ptxqsav(&self) -> u8 { + let val = (self.0 >> 16usize) & 0xff; + val as u8 + } + #[doc = "Periodic transmit request queue space available"] + #[inline(always)] + pub fn set_ptxqsav(&mut self, val: u8) { + self.0 = (self.0 & !(0xff << 16usize)) | (((val as u32) & 0xff) << 16usize); + } + #[doc = "Top of the periodic transmit request queue"] + #[inline(always)] + pub const fn ptxqtop(&self) -> u8 { + let val = (self.0 >> 24usize) & 0xff; + val as u8 + } + #[doc = "Top of the periodic transmit request queue"] + #[inline(always)] + pub fn set_ptxqtop(&mut self, val: u8) { + self.0 = (self.0 & !(0xff << 24usize)) | (((val as u32) & 0xff) << 24usize); + } + } + impl Default for Hptxsts { + #[inline(always)] + fn default() -> Hptxsts { + Hptxsts(0) + } + } + #[doc = "Power and clock gating control register"] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Pcgcctl(pub u32); + impl Pcgcctl { + #[doc = "Stop PHY clock"] + #[inline(always)] + pub const fn stppclk(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Stop PHY clock"] + #[inline(always)] + pub fn set_stppclk(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Gate HCLK"] + #[inline(always)] + pub const fn gatehclk(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Gate HCLK"] + #[inline(always)] + pub fn set_gatehclk(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "PHY Suspended"] + #[inline(always)] + pub const fn physusp(&self) -> bool { + let val = (self.0 >> 4usize) & 0x01; + val != 0 + } + #[doc = "PHY Suspended"] + #[inline(always)] + pub fn set_physusp(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 4usize)) | (((val as u32) & 0x01) << 4usize); + } + } + impl Default for Pcgcctl { + #[inline(always)] + fn default() -> Pcgcctl { + Pcgcctl(0) + } + } +} +pub mod vals { + #[repr(u8)] + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] + #[allow(non_camel_case_types)] + pub enum Dpid { + DATA0 = 0x0, + DATA2 = 0x01, + DATA1 = 0x02, + MDATA = 0x03, + } + impl Dpid { + #[inline(always)] + pub const fn from_bits(val: u8) -> Dpid { + unsafe { core::mem::transmute(val & 0x03) } + } + #[inline(always)] + pub const fn to_bits(self) -> u8 { + unsafe { core::mem::transmute(self) } + } + } + impl From for Dpid { + #[inline(always)] + fn from(val: u8) -> Dpid { + Dpid::from_bits(val) + } + } + impl From for u8 { + #[inline(always)] + fn from(val: Dpid) -> u8 { + Dpid::to_bits(val) + } + } + #[repr(u8)] + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] + #[allow(non_camel_case_types)] + pub enum Dspd { + #[doc = "High speed"] + HIGH_SPEED = 0x0, + #[doc = "Full speed using external ULPI PHY"] + FULL_SPEED_EXTERNAL = 0x01, + _RESERVED_2 = 0x02, + #[doc = "Full speed using internal embedded PHY"] + FULL_SPEED_INTERNAL = 0x03, + } + impl Dspd { + #[inline(always)] + pub const fn from_bits(val: u8) -> Dspd { + unsafe { core::mem::transmute(val & 0x03) } + } + #[inline(always)] + pub const fn to_bits(self) -> u8 { + unsafe { core::mem::transmute(self) } + } + } + impl From for Dspd { + #[inline(always)] + fn from(val: u8) -> Dspd { + Dspd::from_bits(val) + } + } + impl From for u8 { + #[inline(always)] + fn from(val: Dspd) -> u8 { + Dspd::to_bits(val) + } + } + #[repr(u8)] + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] + #[allow(non_camel_case_types)] + pub enum Eptyp { + CONTROL = 0x0, + ISOCHRONOUS = 0x01, + BULK = 0x02, + INTERRUPT = 0x03, + } + impl Eptyp { + #[inline(always)] + pub const fn from_bits(val: u8) -> Eptyp { + unsafe { core::mem::transmute(val & 0x03) } + } + #[inline(always)] + pub const fn to_bits(self) -> u8 { + unsafe { core::mem::transmute(self) } + } + } + impl From for Eptyp { + #[inline(always)] + fn from(val: u8) -> Eptyp { + Eptyp::from_bits(val) + } + } + impl From for u8 { + #[inline(always)] + fn from(val: Eptyp) -> u8 { + Eptyp::to_bits(val) + } + } + #[repr(u8)] + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] + #[allow(non_camel_case_types)] + pub enum Pfivl { + #[doc = "80% of the frame interval"] + FRAME_INTERVAL_80 = 0x0, + #[doc = "85% of the frame interval"] + FRAME_INTERVAL_85 = 0x01, + #[doc = "90% of the frame interval"] + FRAME_INTERVAL_90 = 0x02, + #[doc = "95% of the frame interval"] + FRAME_INTERVAL_95 = 0x03, + } + impl Pfivl { + #[inline(always)] + pub const fn from_bits(val: u8) -> Pfivl { + unsafe { core::mem::transmute(val & 0x03) } + } + #[inline(always)] + pub const fn to_bits(self) -> u8 { + unsafe { core::mem::transmute(self) } + } + } + impl From for Pfivl { + #[inline(always)] + fn from(val: u8) -> Pfivl { + Pfivl::from_bits(val) + } + } + impl From for u8 { + #[inline(always)] + fn from(val: Pfivl) -> u8 { + Pfivl::to_bits(val) + } + } + #[repr(u8)] + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] + #[allow(non_camel_case_types)] + pub enum Pktstsd { + _RESERVED_0 = 0x0, + #[doc = "Global OUT NAK (triggers an interrupt)"] + OUT_NAK = 0x01, + #[doc = "OUT data packet received"] + OUT_DATA_RX = 0x02, + #[doc = "OUT transfer completed (triggers an interrupt)"] + OUT_DATA_DONE = 0x03, + #[doc = "SETUP transaction completed (triggers an interrupt)"] + SETUP_DATA_DONE = 0x04, + _RESERVED_5 = 0x05, + #[doc = "SETUP data packet received"] + SETUP_DATA_RX = 0x06, + _RESERVED_7 = 0x07, + _RESERVED_8 = 0x08, + _RESERVED_9 = 0x09, + _RESERVED_a = 0x0a, + _RESERVED_b = 0x0b, + _RESERVED_c = 0x0c, + _RESERVED_d = 0x0d, + _RESERVED_e = 0x0e, + _RESERVED_f = 0x0f, + } + impl Pktstsd { + #[inline(always)] + pub const fn from_bits(val: u8) -> Pktstsd { + unsafe { core::mem::transmute(val & 0x0f) } + } + #[inline(always)] + pub const fn to_bits(self) -> u8 { + unsafe { core::mem::transmute(self) } + } + } + impl From for Pktstsd { + #[inline(always)] + fn from(val: u8) -> Pktstsd { + Pktstsd::from_bits(val) + } + } + impl From for u8 { + #[inline(always)] + fn from(val: Pktstsd) -> u8 { + Pktstsd::to_bits(val) + } + } + #[repr(u8)] + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] + #[allow(non_camel_case_types)] + pub enum Pktstsh { + _RESERVED_0 = 0x0, + _RESERVED_1 = 0x01, + #[doc = "IN data packet received"] + IN_DATA_RX = 0x02, + #[doc = "IN transfer completed (triggers an interrupt)"] + IN_DATA_DONE = 0x03, + _RESERVED_4 = 0x04, + #[doc = "Data toggle error (triggers an interrupt)"] + DATA_TOGGLE_ERR = 0x05, + _RESERVED_6 = 0x06, + #[doc = "Channel halted (triggers an interrupt)"] + CHANNEL_HALTED = 0x07, + _RESERVED_8 = 0x08, + _RESERVED_9 = 0x09, + _RESERVED_a = 0x0a, + _RESERVED_b = 0x0b, + _RESERVED_c = 0x0c, + _RESERVED_d = 0x0d, + _RESERVED_e = 0x0e, + _RESERVED_f = 0x0f, + } + impl Pktstsh { + #[inline(always)] + pub const fn from_bits(val: u8) -> Pktstsh { + unsafe { core::mem::transmute(val & 0x0f) } + } + #[inline(always)] + pub const fn to_bits(self) -> u8 { + unsafe { core::mem::transmute(self) } + } + } + impl From for Pktstsh { + #[inline(always)] + fn from(val: u8) -> Pktstsh { + Pktstsh::from_bits(val) + } + } + impl From for u8 { + #[inline(always)] + fn from(val: Pktstsh) -> u8 { + Pktstsh::to_bits(val) + } + } +} diff --git a/embassy/embassy-usb/CHANGELOG.md b/embassy/embassy-usb/CHANGELOG.md new file mode 100644 index 0000000..efdda96 --- /dev/null +++ b/embassy/embassy-usb/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.3.0 - 2024-08-05 + +- bump usbd-hid from 0.7.0 to 0.8.1 +- Add collapse_debuginfo to fmt.rs macros. +- update embassy-sync dependency + +## 0.2.0 - 2024-05-20 + +- [#2862](https://github.com/embassy-rs/embassy/pull/2862) WebUSB implementation by @chmanie +- Removed dynamically sized `device_descriptor` fields + +## 0.1.0 - 2024-01-11 + +- Initial Release diff --git a/embassy/embassy-usb/Cargo.toml b/embassy/embassy-usb/Cargo.toml new file mode 100644 index 0000000..9abf2f2 --- /dev/null +++ b/embassy/embassy-usb/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "embassy-usb" +version = "0.3.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Async USB device stack for embedded devices in Rust." +keywords = ["embedded", "async", "usb", "hal", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-usb" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-v$VERSION/embassy-usb/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb/src/" +features = ["defmt", "usbd-hid"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt", "usbd-hid"] + +[features] +defmt = ["dep:defmt", "embassy-usb-driver/defmt"] +usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] +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.6.1", path = "../embassy-sync" } +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +heapless = "0.8" + +# for HID +usbd-hid = { version = "0.8.1", optional = true } +ssmarshal = { version = "1.0", default-features = false, optional = true } diff --git a/embassy/embassy-usb/README.md b/embassy/embassy-usb/README.md new file mode 100644 index 0000000..400fc66 --- /dev/null +++ b/embassy/embassy-usb/README.md @@ -0,0 +1,50 @@ +# embassy-usb + +Async USB device stack for embedded devices in Rust. + +## Features + +- Native async. +- Fully lock-free: endpoints are separate objects that can be used independently without needing a central mutex. If the driver supports it, they can even be used from different priority levels. +- Suspend/resume, remote wakeup. +- USB composite devices. +- Ergonomic descriptor builder. +- Ready-to-use implementations for a few USB classes (note you can still implement any class yourself outside the crate). + - Serial ports (CDC ACM) + - Ethernet (CDC NCM) + - Human Interface Devices (HID) + - MIDI + +## Adding support for new hardware + +To add `embassy-usb` support for new hardware (i.e. a new MCU chip), you have to write a driver that implements +the [`embassy-usb-driver`](https://crates.io/crates/embassy-usb-driver) traits. + +Driver crates should depend only on `embassy-usb-driver`, not on the main `embassy-usb` crate. +This allows existing drivers to continue working for newer `embassy-usb` major versions, without needing an update, if the driver +trait has not had breaking changes. + +## 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. diff --git a/embassy/embassy-usb/build.rs b/embassy/embassy-usb/build.rs new file mode 100644 index 0000000..5e3bec4 --- /dev/null +++ b/embassy/embassy-usb/build.rs @@ -0,0 +1,96 @@ +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 { + assert!( + !cfg.seen_feature, + "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/embassy-usb/gen_config.py b/embassy/embassy-usb/gen_config.py new file mode 100644 index 0000000..67ce359 --- /dev/null +++ b/embassy/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/embassy-usb/src/builder.rs b/embassy/embassy-usb/src/builder.rs new file mode 100644 index 0000000..008a10f --- /dev/null +++ b/embassy/embassy-usb/src/builder.rs @@ -0,0 +1,617 @@ +use heapless::Vec; + +use crate::config::MAX_HANDLER_COUNT; +use crate::descriptor::{BosWriter, DescriptorWriter, SynchronizationType, UsageType}; +use crate::driver::{Driver, Endpoint, EndpointInfo, EndpointType}; +use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter}; +use crate::types::{InterfaceNumber, StringIndex}; +use crate::{Handler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +/// Allows Configuring the Bcd USB version below 2.1 +pub enum UsbVersion { + /// Usb version 2.0 + Two = 0x0200, + /// Usb version 2.1 + TwoOne = 0x0210, +} + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +/// Configuration used when creating [`UsbDevice`]. +pub struct Config<'a> { + pub(crate) vendor_id: u16, + pub(crate) product_id: u16, + + /// Device BCD USB version. + /// + /// Default: `0x0210` ("2.1") + pub bcd_usb: UsbVersion, + + /// Device class code assigned by USB.org. Set to `0xff` for vendor-specific + /// devices that do not conform to any class. + /// + /// Default: `0x00` (class code specified by interfaces) + pub device_class: u8, + + /// Device sub-class code. Depends on class. + /// + /// Default: `0x00` + pub device_sub_class: u8, + + /// Device protocol code. Depends on class and sub-class. + /// + /// Default: `0x00` + pub device_protocol: u8, + + /// Device release version in BCD. + /// + /// Default: `0x0010` ("0.1") + pub device_release: u16, + + /// Maximum packet size in bytes for the control endpoint 0. + /// + /// Valid values depend on the speed at which the bus is enumerated. + /// - low speed: 8 + /// - full speed: 8, 16, 32, or 64 + /// - high speed: 64 + /// + /// Default: 64 bytes + pub max_packet_size_0: u8, + + /// Manufacturer name string descriptor. + /// + /// Default: (none) + pub manufacturer: Option<&'a str>, + + /// Product name string descriptor. + /// + /// Default: (none) + pub product: Option<&'a str>, + + /// Serial number string descriptor. + /// + /// Default: (none) + pub serial_number: Option<&'a str>, + + /// Whether the device supports remotely waking up the host is requested. + /// + /// Default: `false` + pub supports_remote_wakeup: bool, + + /// Configures the device as a composite device with interface association descriptors. + /// + /// If set to `true`, the following fields should have the given values: + /// + /// - `device_class` = `0xEF` + /// - `device_sub_class` = `0x02` + /// - `device_protocol` = `0x01` + pub composite_with_iads: bool, + + /// Whether the device has its own power source. + /// + /// This should be set to `true` even if the device is sometimes self-powered and may not + /// always draw power from the USB bus. + /// + /// Default: `false` + /// + /// See also: `max_power` + pub self_powered: bool, + + /// Maximum current drawn from the USB bus by the device, in milliamps. + /// + /// The default is 100 mA. If your device always uses an external power source and never draws + /// power from the USB bus, this can be set to 0. + /// + /// See also: `self_powered` + /// + /// Default: 100mA + /// Max: 500mA + pub max_power: u16, +} + +impl<'a> Config<'a> { + /// Create default configuration with the provided vid and pid values. + pub const fn new(vid: u16, pid: u16) -> Self { + Self { + device_class: 0x00, + device_sub_class: 0x00, + device_protocol: 0x00, + max_packet_size_0: 64, + vendor_id: vid, + product_id: pid, + device_release: 0x0010, + bcd_usb: UsbVersion::TwoOne, + manufacturer: None, + product: None, + serial_number: None, + self_powered: false, + supports_remote_wakeup: false, + composite_with_iads: false, + max_power: 100, + } + } +} + +/// [`UsbDevice`] builder. +pub struct Builder<'d, D: Driver<'d>> { + config: Config<'d>, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, + interfaces: Vec, + control_buf: &'d mut [u8], + + driver: D, + next_string_index: u8, + + config_descriptor: DescriptorWriter<'d>, + bos_descriptor: BosWriter<'d>, + + msos_descriptor: MsOsDescriptorWriter<'d>, +} + +impl<'d, D: Driver<'d>> Builder<'d, D> { + /// Creates a builder for constructing a new [`UsbDevice`]. + /// + /// `control_buf` is a buffer used for USB control request data. It should be sized + /// large enough for the length of the largest control request (in or out) + /// anticipated by any class added to the device. + pub fn new( + driver: D, + config: Config<'d>, + config_descriptor_buf: &'d mut [u8], + bos_descriptor_buf: &'d mut [u8], + msos_descriptor_buf: &'d mut [u8], + control_buf: &'d mut [u8], + ) -> Self { + // Magic values specified in USB-IF ECN on IADs. + if config.composite_with_iads + && (config.device_class != 0xEF || config.device_sub_class != 0x02 || config.device_protocol != 0x01) + { + panic!("if composite_with_iads is set, you must set device_class = 0xEF, device_sub_class = 0x02, device_protocol = 0x01"); + } + + assert!( + config.max_power <= 500, + "The maximum allowed value for `max_power` is 500mA" + ); + + match config.max_packet_size_0 { + 8 | 16 | 32 | 64 => {} + _ => panic!("invalid max_packet_size_0, the allowed values are 8, 16, 32 or 64"), + } + + let mut config_descriptor = DescriptorWriter::new(config_descriptor_buf); + let mut bos_descriptor = BosWriter::new(DescriptorWriter::new(bos_descriptor_buf)); + + config_descriptor.configuration(&config); + bos_descriptor.bos(); + + Builder { + driver, + config, + interfaces: Vec::new(), + handlers: Vec::new(), + control_buf, + next_string_index: STRING_INDEX_CUSTOM_START, + + config_descriptor, + bos_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> { + let msos_descriptor = self.msos_descriptor.build(&mut self.bos_descriptor); + + self.config_descriptor.end_configuration(); + self.bos_descriptor.end_bos(); + + // Log the number of allocator bytes actually used in descriptor buffers + info!("USB: config_descriptor used: {}", self.config_descriptor.position()); + info!("USB: bos_descriptor used: {}", self.bos_descriptor.writer.position()); + info!("USB: msos_descriptor used: {}", msos_descriptor.len()); + info!("USB: control_buf size: {}", self.control_buf.len()); + + UsbDevice::build( + self.driver, + self.config, + self.handlers, + self.config_descriptor.into_buf(), + self.bos_descriptor.writer.into_buf(), + msos_descriptor, + self.interfaces, + self.control_buf, + ) + } + + /// Returns the size of the control request data buffer. Can be used by + /// classes to validate the buffer is large enough for their needs. + pub fn control_buf_len(&self) -> usize { + self.control_buf.len() + } + + /// Add an USB function. + /// + /// If [`Config::composite_with_iads`] is set, this will add an IAD descriptor + /// with the given class/subclass/protocol, associating all the child interfaces. + /// + /// 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(first_interface, 0, class, subclass, protocol); + + Some(self.config_descriptor.position() - 5) + } else { + None + }; + + FunctionBuilder { + builder: self, + iface_count_index, + + 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) { + assert!( + self.handlers.push(handler).is_ok(), + "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) + } + + /// 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); + } + + /// Add an MS OS 2.0 Device Level Feature Descriptor. + pub fn msos_feature(&mut self, desc: T) { + self.msos_descriptor.device_feature(desc); + } + + /// 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. +/// +/// A function is a logical grouping of interfaces that perform a given USB function. +/// If [`Config::composite_with_iads`] is set, each function will have an IAD descriptor. +/// If not, functions will not be visible as descriptors. +pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> { + builder: &'a mut Builder<'d, D>, + iface_count_index: Option, + + first_interface: InterfaceNumber, +} + +impl<'a, 'd, D: Driver<'d>> Drop for FunctionBuilder<'a, 'd, D> { + fn drop(&mut self) { + self.builder.msos_descriptor.end_function(); + } +} + +impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { + /// Add an interface to the function. + /// + /// Interface numbers are guaranteed to be allocated consecutively, starting from 0. + pub fn interface(&mut self) -> InterfaceBuilder<'_, 'd, D> { + if let Some(i) = self.iface_count_index { + self.builder.config_descriptor.buf[i] += 1; + } + + let number = self.builder.interfaces.len() as _; + let iface = Interface { + current_alt_setting: 0, + num_alt_settings: 0, + }; + + assert!(self.builder.interfaces.push(iface).is_ok(), + "embassy-usb: interface list full. Increase the `max_interface_count` compile-time setting. Current value: {}", + MAX_INTERFACE_COUNT + ); + + InterfaceBuilder { + builder: self.builder, + interface_number: InterfaceNumber::new(number), + next_alt_setting_number: 0, + } + } + + /// 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); + } + + self.builder.msos_descriptor.function_feature(desc); + } +} + +/// Interface builder. +pub struct InterfaceBuilder<'a, 'd, D: Driver<'d>> { + builder: &'a mut Builder<'d, D>, + interface_number: InterfaceNumber, + next_alt_setting_number: u8, +} + +impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> { + /// Get the interface number. + pub const fn interface_number(&self) -> InterfaceNumber { + self.interface_number + } + + /// Allocates a new string index. + pub fn string(&mut self) -> StringIndex { + self.builder.string() + } + + /// Add an alternate setting to the interface and write its descriptor. + /// + /// 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, + 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, + interface_string, + ); + + InterfaceAltBuilder { + builder: self.builder, + interface_number: self.interface_number, + alt_setting_number: number, + } + } +} + +/// Interface alternate setting builder. +pub struct InterfaceAltBuilder<'a, 'd, D: Driver<'d>> { + builder: &'a mut Builder<'d, D>, + interface_number: InterfaceNumber, + alt_setting_number: u8, +} + +impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { + /// Get the interface number. + pub const fn interface_number(&self) -> InterfaceNumber { + self.interface_number + } + + /// Get the alternate setting number. + pub const fn alt_setting_number(&self) -> u8 { + self.alt_setting_number + } + + /// Add a custom descriptor to this alternate setting. + /// + /// Descriptors are written in the order builder functions are called. Note that some + /// classes care about the order. + pub fn descriptor(&mut self, descriptor_type: u8, descriptor: &[u8]) { + self.builder.config_descriptor.write(descriptor_type, descriptor, &[]); + } + + /// Add a custom Binary Object Store (BOS) descriptor to this alternate setting. + pub fn bos_capability(&mut self, capability_type: u8, capability: &[u8]) { + self.builder.bos_descriptor.capability(capability_type, capability); + } + + /// Write a custom endpoint descriptor for a certain endpoint. + /// + /// This can be necessary, if the endpoint descriptors can only be written + /// after the endpoint was created. As an example, an endpoint descriptor + /// may contain the address of an endpoint that was allocated earlier. + pub fn endpoint_descriptor( + &mut self, + endpoint: &EndpointInfo, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) { + self.builder + .config_descriptor + .endpoint(endpoint, synchronization_type, usage_type, extra_fields); + } + + /// Allocate an IN endpoint, without writing its descriptor. + /// + /// Used for granular control over the order of endpoint and descriptor creation. + pub fn alloc_endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { + let ep = self + .builder + .driver + .alloc_endpoint_in(ep_type, max_packet_size, interval_ms) + .expect("alloc_endpoint_in failed"); + + ep + } + + fn endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointIn { + let ep = self.alloc_endpoint_in(ep_type, max_packet_size, interval_ms); + self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields); + + ep + } + + /// Allocate an OUT endpoint, without writing its descriptor. + /// + /// Use for granular control over the order of endpoint and descriptor creation. + pub fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> D::EndpointOut { + let ep = self + .builder + .driver + .alloc_endpoint_out(ep_type, max_packet_size, interval_ms) + .expect("alloc_endpoint_out failed"); + + ep + } + + fn endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointOut { + let ep = self.alloc_endpoint_out(ep_type, max_packet_size, interval_ms); + self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields); + + ep + } + + /// Allocate a BULK 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_bulk_in(&mut self, max_packet_size: u16) -> D::EndpointIn { + self.endpoint_in( + EndpointType::Bulk, + max_packet_size, + 0, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) + } + + /// Allocate a BULK OUT 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_bulk_out(&mut self, max_packet_size: u16) -> D::EndpointOut { + self.endpoint_out( + EndpointType::Bulk, + max_packet_size, + 0, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) + } + + /// Allocate a INTERRUPT 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_interrupt_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { + self.endpoint_in( + EndpointType::Interrupt, + max_packet_size, + interval_ms, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) + } + + /// Allocate a INTERRUPT OUT endpoint and write its descriptor. + pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { + self.endpoint_out( + EndpointType::Interrupt, + max_packet_size, + interval_ms, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) + } + + /// Allocate a ISOCHRONOUS IN endpoint and write its descriptor. + /// + /// Descriptors are written in the order builder functions are called. Note that some + /// classes care about the order. + pub fn endpoint_isochronous_in( + &mut self, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointIn { + self.endpoint_in( + EndpointType::Isochronous, + max_packet_size, + interval_ms, + synchronization_type, + usage_type, + extra_fields, + ) + } + + /// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor. + pub fn endpoint_isochronous_out( + &mut self, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointOut { + self.endpoint_out( + EndpointType::Isochronous, + max_packet_size, + interval_ms, + synchronization_type, + usage_type, + extra_fields, + ) + } +} diff --git a/embassy/embassy-usb/src/class/cdc_acm.rs b/embassy/embassy-usb/src/class/cdc_acm.rs new file mode 100644 index 0000000..2823e52 --- /dev/null +++ b/embassy/embassy-usb/src/class/cdc_acm.rs @@ -0,0 +1,547 @@ +//! CDC-ACM class implementation, aka Serial over USB. + +use core::cell::{Cell, RefCell}; +use core::future::poll_fn; +use core::mem::{self, MaybeUninit}; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_sync::blocking_mutex::CriticalSectionMutex; +use embassy_sync::waitqueue::WakerRegistration; + +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::InterfaceNumber; +use crate::{Builder, Handler}; + +/// This should be used as `device_class` when building the `UsbDevice`. +pub const USB_CLASS_CDC: u8 = 0x02; + +const USB_CLASS_CDC_DATA: u8 = 0x0a; +const CDC_SUBCLASS_ACM: u8 = 0x02; +const CDC_PROTOCOL_NONE: u8 = 0x00; + +const CS_INTERFACE: u8 = 0x24; +const CDC_TYPE_HEADER: u8 = 0x00; +const CDC_TYPE_ACM: u8 = 0x02; +const CDC_TYPE_UNION: u8 = 0x06; + +const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; +#[allow(unused)] +const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; +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> Default for State<'a> { + fn default() -> Self { + Self::new() + } +} + +impl<'a> State<'a> { + /// Create a new `State`. + pub fn new() -> Self { + Self { + control: MaybeUninit::uninit(), + shared: ControlShared::default(), + } + } +} + +/// Packet level implementation of a CDC-ACM serial port. +/// +/// This class can be used directly and it has the least overhead due to directly reading and +/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial +/// port. The following constraints must be followed if you use this class directly: +/// +/// - `read_packet` must be called with a buffer large enough to hold `max_packet_size` bytes. +/// - `write_packet` must not be called with a buffer larger than `max_packet_size` bytes. +/// - If you write a packet that is exactly `max_packet_size` bytes long, it won't be processed by the +/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP) +/// can be sent if there is no other data to send. This is because USB bulk transactions must be +/// terminated with a short packet, even if the bulk endpoint is used for stream-like data. +pub struct CdcAcmClass<'d, D: Driver<'d>> { + _comm_ep: D::EndpointIn, + _data_if: InterfaceNumber, + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, + control: &'d ControlShared, +} + +struct Control<'a> { + comm_if: InterfaceNumber, + shared: &'a ControlShared, +} + +/// Shared data between Control and CdcAcmClass +struct ControlShared { + line_coding: CriticalSectionMutex>, + dtr: AtomicBool, + rts: AtomicBool, + + waker: RefCell, + changed: AtomicBool, +} + +impl Default for ControlShared { + fn default() -> Self { + ControlShared { + dtr: AtomicBool::new(false), + rts: AtomicBool::new(false), + line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + })), + waker: RefCell::new(WakerRegistration::new()), + changed: AtomicBool::new(false), + } + } +} + +impl ControlShared { + async fn changed(&self) { + poll_fn(|cx| { + if self.changed.load(Ordering::Relaxed) { + self.changed.store(false, Ordering::Relaxed); + Poll::Ready(()) + } else { + self.waker.borrow_mut().register(cx.waker()); + Poll::Pending + } + }) + .await; + } +} + +impl<'a> Control<'a> { + fn shared(&mut self) -> &'a ControlShared { + self.shared + } +} + +impl<'d> Handler for Control<'d> { + fn reset(&mut self) { + let shared = self.shared(); + shared.line_coding.lock(|x| x.set(LineCoding::default())); + shared.dtr.store(false, Ordering::Relaxed); + shared.rts.store(false, Ordering::Relaxed); + + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + } + + 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. + Some(OutResponse::Accepted) + } + REQ_SET_LINE_CODING if data.len() >= 7 => { + let coding = LineCoding { + data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()), + stop_bits: data[4].into(), + parity_type: data[5].into(), + data_bits: data[6], + }; + let shared = self.shared(); + shared.line_coding.lock(|x| x.set(coding)); + debug!("Set line coding to: {:?}", coding); + + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + + Some(OutResponse::Accepted) + } + REQ_SET_CONTROL_LINE_STATE => { + let dtr = (req.value & 0x0001) != 0; + let rts = (req.value & 0x0002) != 0; + + let shared = self.shared(); + shared.dtr.store(dtr, Ordering::Relaxed); + shared.rts.store(rts, Ordering::Relaxed); + debug!("Set dtr {}, rts {}", dtr, rts); + + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + + Some(OutResponse::Accepted) + } + _ => Some(OutResponse::Rejected), + } + } + + 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 => { + debug!("Sending line coding"); + let coding = self.shared().line_coding.lock(Cell::get); + assert!(buf.len() >= 7); + buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); + buf[4] = coding.stop_bits as u8; + buf[5] = coding.parity_type as u8; + buf[6] = coding.data_bits; + Some(InResponse::Accepted(&buf[0..7])) + } + _ => Some(InResponse::Rejected), + } + } +} + +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 { + 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(); + 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, None); + + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_HEADER, // bDescriptorSubtype + 0x10, + 0x01, // bcdCDC (1.10) + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_ACM, // bDescriptorSubtype + 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( + CS_INTERFACE, + &[ + CDC_TYPE_UNION, // bDescriptorSubtype + comm_if.into(), // bControlInterface + data_if, // bSubordinateInterface + ], + ); + + 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, 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, + read_ep, + write_ep, + control: control_shared, + } + } + + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.read_ep.info().max_packet_size + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> LineCoding { + self.control.line_coding.lock(Cell::get) + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.control.dtr.load(Ordering::Relaxed) + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.control.rts.load(Ordering::Relaxed) + } + + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.write_ep.write(data).await + } + + /// Reads a single packet from the OUT endpoint. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.read_ep.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await; + } + + /// 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 { + write_ep: self.write_ep, + control: self.control, + }, + Receiver { + read_ep: self.read_ep, + control: self.control, + }, + ) + } + + /// Split the class into sender, receiver and control + /// + /// Allows concurrently sending and receiving packets whilst monitoring for + /// control changes (dtr, rts) + pub fn split_with_control(self) -> (Sender<'d, D>, Receiver<'d, D>, ControlChanged<'d>) { + ( + Sender { + write_ep: self.write_ep, + control: self.control, + }, + Receiver { + read_ep: self.read_ep, + control: self.control, + }, + ControlChanged { control: self.control }, + ) + } +} + +/// CDC ACM Control status change monitor +/// +/// You can obtain a `ControlChanged` with [`CdcAcmClass::split_with_control`] +pub struct ControlChanged<'d> { + control: &'d ControlShared, +} + +impl<'d> ControlChanged<'d> { + /// Return a future for when the control settings change + pub async fn control_changed(&self) { + self.control.changed().await; + } +} + +/// CDC ACM class packet sender. +/// +/// You can obtain a `Sender` with [`CdcAcmClass::split`] +pub struct Sender<'d, D: Driver<'d>> { + write_ep: D::EndpointIn, + control: &'d ControlShared, +} + +impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.write_ep.info().max_packet_size + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> LineCoding { + self.control.line_coding.lock(Cell::get) + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.control.dtr.load(Ordering::Relaxed) + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.control.rts.load(Ordering::Relaxed) + } + + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.write_ep.write(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.write_ep.wait_enabled().await; + } +} + +/// CDC ACM class packet receiver. +/// +/// You can obtain a `Receiver` with [`CdcAcmClass::split`] +pub struct Receiver<'d, D: Driver<'d>> { + read_ep: D::EndpointOut, + control: &'d ControlShared, +} + +impl<'d, D: Driver<'d>> Receiver<'d, D> { + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.read_ep.info().max_packet_size + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> LineCoding { + self.control.line_coding.lock(Cell::get) + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.control.dtr.load(Ordering::Relaxed) + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.control.rts.load(Ordering::Relaxed) + } + + /// Reads a single packet from the OUT endpoint. + /// Must be called with a buffer large enough to hold max_packet_size bytes. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.read_ep.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await; + } +} + +/// Number of stop bits for LineCoding +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum StopBits { + /// 1 stop bit + One = 0, + + /// 1.5 stop bits + OnePointFive = 1, + + /// 2 stop bits + Two = 2, +} + +impl From for StopBits { + fn from(value: u8) -> Self { + if value <= 2 { + unsafe { mem::transmute(value) } + } else { + StopBits::One + } + } +} + +/// Parity for LineCoding +#[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, +} + +impl From for ParityType { + fn from(value: u8) -> Self { + if value <= 4 { + unsafe { mem::transmute(value) } + } else { + ParityType::None + } + } +} + +/// Line coding parameters +/// +/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can +/// be ignored if you don't plan to interface with a physical UART. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct LineCoding { + stop_bits: StopBits, + data_bits: u8, + parity_type: ParityType, + data_rate: u32, +} + +impl LineCoding { + /// Gets the number of stop bits for UART communication. + pub fn stop_bits(&self) -> StopBits { + self.stop_bits + } + + /// Gets the number of data bits for UART communication. + pub const fn data_bits(&self) -> u8 { + self.data_bits + } + + /// Gets the parity type for UART communication. + pub const fn parity_type(&self) -> ParityType { + self.parity_type + } + + /// Gets the data rate in bits per second for UART communication. + pub const fn data_rate(&self) -> u32 { + self.data_rate + } +} + +impl Default for LineCoding { + fn default() -> Self { + LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + } + } +} diff --git a/embassy/embassy-usb/src/class/cdc_ncm/embassy_net.rs b/embassy/embassy-usb/src/class/cdc_ncm/embassy_net.rs new file mode 100644 index 0000000..57d3229 --- /dev/null +++ b/embassy/embassy-usb/src/class/cdc_ncm/embassy_net.rs @@ -0,0 +1,104 @@ +//! [`embassy-net`](https://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`](https://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, + ch::driver::HardwareAddress::Ethernet(ethernet_address), + ); + + ( + Runner { + tx_usb, + rx_usb, + ch: runner, + }, + device, + ) + } +} diff --git a/embassy/embassy-usb/src/class/cdc_ncm/mod.rs b/embassy/embassy-usb/src/class/cdc_ncm/mod.rs new file mode 100644 index 0000000..bea9dac --- /dev/null +++ b/embassy/embassy-usb/src/class/cdc_ncm/mod.rs @@ -0,0 +1,529 @@ +//! 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: +//! and this nonsense in the linux kernel: + +use core::intrinsics::copy_nonoverlapping; +use core::mem::{size_of, MaybeUninit}; +use core::ptr::addr_of; + +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::{InterfaceNumber, StringIndex}; +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; + +const USB_CLASS_CDC_DATA: u8 = 0x0a; +const CDC_SUBCLASS_NCM: u8 = 0x0d; + +const CDC_PROTOCOL_NONE: u8 = 0x00; +const CDC_PROTOCOL_NTB: u8 = 0x01; + +const CS_INTERFACE: u8 = 0x24; +const CDC_TYPE_HEADER: u8 = 0x00; +const CDC_TYPE_UNION: u8 = 0x06; +const CDC_TYPE_ETHERNET: u8 = 0x0F; +const CDC_TYPE_NCM: u8 = 0x1A; + +const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; +//const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; +//const REQ_SET_ETHERNET_MULTICAST_FILTERS: u8 = 0x40; +//const REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x41; +//const REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x42; +//const REQ_SET_ETHERNET_PACKET_FILTER: u8 = 0x43; +//const REQ_GET_ETHERNET_STATISTIC: u8 = 0x44; +const REQ_GET_NTB_PARAMETERS: u8 = 0x80; +//const REQ_GET_NET_ADDRESS: u8 = 0x81; +//const REQ_SET_NET_ADDRESS: u8 = 0x82; +//const REQ_GET_NTB_FORMAT: u8 = 0x83; +//const REQ_SET_NTB_FORMAT: u8 = 0x84; +//const REQ_GET_NTB_INPUT_SIZE: u8 = 0x85; +const REQ_SET_NTB_INPUT_SIZE: u8 = 0x86; +//const REQ_GET_MAX_DATAGRAM_SIZE: u8 = 0x87; +//const REQ_SET_MAX_DATAGRAM_SIZE: u8 = 0x88; +//const REQ_GET_CRC_MODE: u8 = 0x89; +//const REQ_SET_CRC_MODE: u8 = 0x8A; + +//const NOTIF_MAX_PACKET_SIZE: u16 = 8; +//const NOTIF_POLL_INTERVAL: u8 = 20; + +const NTB_MAX_SIZE: usize = 2048; +const SIG_NTH: u32 = 0x484d_434e; +const SIG_NDP_NO_FCS: u32 = 0x304d_434e; +const SIG_NDP_WITH_FCS: u32 = 0x314d_434e; + +const ALTERNATE_SETTING_DISABLED: u8 = 0x00; +const ALTERNATE_SETTING_ENABLED: u8 = 0x01; + +/// Simple NTB header (NTH+NDP all in one) for sending packets +#[repr(packed)] +#[allow(unused)] +struct NtbOutHeader { + // NTH + nth_sig: u32, + nth_len: u16, + nth_seq: u16, + nth_total_len: u16, + nth_first_index: u16, + + // NDP + ndp_sig: u32, + ndp_len: u16, + ndp_next_index: u16, + ndp_datagram_index: u16, + ndp_datagram_len: u16, + ndp_term1: u16, + ndp_term2: u16, +} + +#[repr(packed)] +#[allow(unused)] +struct NtbParameters { + length: u16, + formats_supported: u16, + in_params: NtbParametersDir, + out_params: NtbParametersDir, +} + +#[repr(packed)] +#[allow(unused)] +struct NtbParametersDir { + max_size: u32, + divisor: u16, + payload_remainder: u16, + out_alignment: u16, + max_datagram_count: u16, +} + +fn byteify(buf: &mut [u8], data: T) -> &[u8] { + let len = size_of::(); + unsafe { copy_nonoverlapping(addr_of!(data).cast(), buf.as_mut_ptr(), len) } + &buf[..len] +} + +/// Internal state for the CDC-NCM class. +pub struct State<'a> { + control: MaybeUninit>, + shared: ControlShared, +} + +impl<'a> Default for State<'a> { + fn default() -> Self { + Self::new() + } +} + +impl<'a> State<'a> { + /// Create a new `State`. + pub fn new() -> Self { + Self { + control: MaybeUninit::uninit(), + shared: ControlShared::default(), + } + } +} + +/// Shared data between Control and `CdcAcmClass` +#[derive(Default)] +struct ControlShared { + mac_addr: [u8; 6], +} + +struct Control<'a> { + mac_addr_string: StringIndex, + shared: &'a ControlShared, + mac_addr_str: [u8; 12], + comm_if: InterfaceNumber, + data_if: InterfaceNumber, +} + +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. + Some(OutResponse::Accepted) + } + REQ_SET_NTB_INPUT_SIZE => { + // TODO + Some(OutResponse::Accepted) + } + _ => Some(OutResponse::Rejected), + } + } + + 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 { + length: size_of::() as _, + formats_supported: 1, // only 16bit, + in_params: NtbParametersDir { + max_size: NTB_MAX_SIZE as _, + divisor: 4, + payload_remainder: 0, + out_alignment: 4, + max_datagram_count: 0, // not used + }, + out_params: NtbParametersDir { + max_size: NTB_MAX_SIZE as _, + divisor: 4, + payload_remainder: 0, + out_alignment: 4, + max_datagram_count: 1, // We only decode 1 packet per NTB + }, + }; + Some(InResponse::Accepted(byteify(buf, res))) + } + _ => Some(InResponse::Rejected), + } + } + + fn get_string(&mut self, index: StringIndex, _lang_id: u16) -> Option<&str> { + if index == self.mac_addr_string { + let mac_addr = self.shared.mac_addr; + let s = &mut self.mac_addr_str; + for i in 0..12 { + let n = (mac_addr[i / 2] >> ((1 - i % 2) * 4)) & 0xF; + s[i] = match n { + 0x0..=0x9 => b'0' + n, + 0xA..=0xF => b'A' + n - 0xA, + _ => unreachable!(), + } + } + + Some(unsafe { core::str::from_utf8_unchecked(s) }) + } else { + warn!("unknown string index requested"); + None + } + } +} + +/// CDC-NCM class +pub struct CdcNcmClass<'d, D: Driver<'d>> { + _comm_if: InterfaceNumber, + comm_ep: D::EndpointIn, + + data_if: InterfaceNumber, + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, + + _control: &'d ControlShared, + + max_packet_size: usize, +} + +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>, + mac_address: [u8; 6], + max_packet_size: u16, + ) -> Self { + state.shared.mac_addr = mac_address; + + let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); + + // Control interface + let mut iface = func.interface(); + let mac_addr_string = iface.string(); + let comm_if = iface.interface_number(); + let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE, None); + + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_HEADER, // bDescriptorSubtype + 0x10, + 0x01, // bcdCDC (1.10) + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_UNION, // bDescriptorSubtype + comm_if.into(), // bControlInterface + u8::from(comm_if) + 1, // bSubordinateInterface + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_ETHERNET, // bDescriptorSubtype + mac_addr_string.into(), // iMACAddress + 0, // bmEthernetStatistics + 0, // | + 0, // | + 0, // | + 0xea, // wMaxSegmentSize = 1514 + 0x05, // | + 0, // wNumberMCFilters + 0, // | + 0, // bNumberPowerFilters + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_NCM, // bDescriptorSubtype + 0x00, // bcdNCMVersion + 0x01, // | + 0, // bmNetworkCapabilities + ], + ); + + let comm_ep = alt.endpoint_interrupt_in(8, 255); + + // Data interface + let mut iface = func.interface(); + let data_if = iface.interface_number(); + 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, + data_if, + read_ep, + write_ep, + _control: &state.shared, + max_packet_size: max_packet_size as usize, + } + } + + /// 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 { + write_ep: self.write_ep, + seq: 0, + max_packet_size: self.max_packet_size, + }, + Receiver { + data_if: self.data_if, + comm_ep: self.comm_ep, + read_ep: self.read_ep, + }, + ) + } +} + +/// 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, + max_packet_size: usize, +} + +impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Write a packet. + /// + /// This waits until the packet is successfully stored in the CDC-NCM endpoint buffers. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + const OUT_HEADER_LEN: usize = 28; + const ABS_MAX_PACKET_SIZE: usize = 512; + + let seq = self.seq; + self.seq = self.seq.wrapping_add(1); + + let header = NtbOutHeader { + nth_sig: SIG_NTH, + nth_len: 0x0c, + nth_seq: seq, + nth_total_len: (data.len() + OUT_HEADER_LEN) as u16, + nth_first_index: 0x0c, + + ndp_sig: SIG_NDP_NO_FCS, + ndp_len: 0x10, + ndp_next_index: 0x00, + ndp_datagram_index: OUT_HEADER_LEN as u16, + ndp_datagram_len: data.len() as u16, + ndp_term1: 0x00, + ndp_term2: 0x00, + }; + + // Build first packet on a buffer, send next packets straight from `data`. + let mut buf = [0; ABS_MAX_PACKET_SIZE]; + let n = byteify(&mut buf, header); + assert_eq!(n.len(), OUT_HEADER_LEN); + + if OUT_HEADER_LEN + data.len() < self.max_packet_size { + // First packet is not full, just send it. + // No need to send ZLP because it's short for sure. + buf[OUT_HEADER_LEN..][..data.len()].copy_from_slice(data); + self.write_ep.write(&buf[..OUT_HEADER_LEN + data.len()]).await?; + } else { + let (d1, d2) = data.split_at(self.max_packet_size - OUT_HEADER_LEN); + + buf[OUT_HEADER_LEN..self.max_packet_size].copy_from_slice(d1); + self.write_ep.write(&buf[..self.max_packet_size]).await?; + + for chunk in d2.chunks(self.max_packet_size) { + self.write_ep.write(chunk).await?; + } + + // Send ZLP if needed. + if d2.len() % self.max_packet_size == 0 { + self.write_ep.write(&[]).await?; + } + } + + Ok(()) + } +} + +/// 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, + read_ep: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> Receiver<'d, D> { + /// Write a network packet. + /// + /// This waits until a packet is successfully received from the endpoint buffers. + pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result { + // Retry loop + loop { + // read NTB + let mut ntb = [0u8; NTB_MAX_SIZE]; + let mut pos = 0; + loop { + let n = self.read_ep.read(&mut ntb[pos..]).await?; + pos += n; + if n < self.read_ep.info().max_packet_size as usize || pos == NTB_MAX_SIZE { + break; + } + } + + let ntb = &ntb[..pos]; + + // Process NTB header (NTH) + let Some(nth) = ntb.get(..12) else { + warn!("Received too short NTB"); + continue; + }; + let sig = u32::from_le_bytes(nth[0..4].try_into().unwrap()); + if sig != SIG_NTH { + warn!("Received bad NTH sig."); + continue; + } + let ndp_idx = u16::from_le_bytes(nth[10..12].try_into().unwrap()) as usize; + + // Process NTB Datagram Pointer (NDP) + let Some(ndp) = ntb.get(ndp_idx..ndp_idx + 12) else { + warn!("NTH has an NDP pointer out of range."); + continue; + }; + let sig = u32::from_le_bytes(ndp[0..4].try_into().unwrap()); + if sig != SIG_NDP_NO_FCS && sig != SIG_NDP_WITH_FCS { + warn!("Received bad NDP sig."); + continue; + } + let datagram_index = u16::from_le_bytes(ndp[8..10].try_into().unwrap()) as usize; + let datagram_len = u16::from_le_bytes(ndp[10..12].try_into().unwrap()) as usize; + + if datagram_index == 0 || datagram_len == 0 { + // empty, ignore. This is allowed by the spec, so don't warn. + continue; + } + + // Process actual datagram, finally. + let Some(datagram) = ntb.get(datagram_index..datagram_index + datagram_len) else { + warn!("NDP has a datagram pointer out of range."); + continue; + }; + buf[..datagram_len].copy_from_slice(datagram); + + return Ok(datagram_len); + } + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) -> Result<(), EndpointError> { + loop { + self.read_ep.wait_enabled().await; + self.comm_ep.wait_enabled().await; + + let buf = [ + 0xA1, //bmRequestType + 0x00, //bNotificationType = NETWORK_CONNECTION + 0x01, // wValue = connected + 0x00, + self.data_if.into(), // wIndex = interface + 0x00, + 0x00, // wLength + 0x00, + ]; + match self.comm_ep.write(&buf).await { + Ok(()) => break, // Done! + Err(EndpointError::Disabled) => {} // Got disabled again, wait again. + Err(e) => return Err(e), + } + } + + Ok(()) + } +} diff --git a/embassy/embassy-usb/src/class/hid.rs b/embassy/embassy-usb/src/class/hid.rs new file mode 100644 index 0000000..6d9e0ac --- /dev/null +++ b/embassy/embassy-usb/src/class/hid.rs @@ -0,0 +1,552 @@ +//! USB HID (Human Interface Device) class implementation. + +use core::mem::MaybeUninit; +use core::ops::Range; +use core::sync::atomic::{AtomicUsize, Ordering}; + +#[cfg(feature = "usbd-hid")] +use ssmarshal::serialize; +#[cfg(feature = "usbd-hid")] +use usbd_hid::descriptor::AsInputReport; + +use crate::control::{InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::InterfaceNumber; +use crate::{Builder, Handler}; + +const USB_CLASS_HID: u8 = 0x03; +const USB_SUBCLASS_NONE: u8 = 0x00; +const USB_PROTOCOL_NONE: u8 = 0x00; + +// HID +const HID_DESC_DESCTYPE_HID: u8 = 0x21; +const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22; +const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01]; +const HID_DESC_COUNTRY_UNSPEC: u8 = 0x00; + +const HID_REQ_SET_IDLE: u8 = 0x0a; +const HID_REQ_GET_IDLE: u8 = 0x02; +const HID_REQ_GET_REPORT: u8 = 0x01; +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], + + /// Handler for control requests. + pub request_handler: Option<&'d mut dyn RequestHandler>, + + /// Configures how frequently the host should poll for reading/writing HID reports. + /// + /// A lower value means better throughput & latency, at the expense + /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for + /// high performance uses, and a value of 255 is good for best-effort usecases. + pub poll_ms: u8, + + /// Max packet size for both the IN and OUT endpoints. + 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), +} + +impl ReportId { + const fn try_from(value: u16) -> Result { + match value >> 8 { + 1 => Ok(ReportId::In(value as u8)), + 2 => Ok(ReportId::Out(value as u8)), + 3 => Ok(ReportId::Feature(value as u8)), + _ => Err(()), + } + } +} + +/// Internal state for USB HID. +pub struct State<'d> { + control: MaybeUninit>, + out_report_offset: AtomicUsize, +} + +impl<'d> Default for State<'d> { + fn default() -> Self { + Self::new() + } +} + +impl<'d> State<'d> { + /// Create a new `State`. + pub const fn new() -> Self { + State { + control: MaybeUninit::uninit(), + out_report_offset: AtomicUsize::new(0), + } + } +} + +/// 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>, +} + +fn build<'d, D: Driver<'d>>( + builder: &mut Builder<'d, D>, + state: &'d mut State<'d>, + config: Config<'d>, + with_out_endpoint: bool, +) -> (Option, D::EndpointIn, &'d AtomicUsize) { + 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(); + 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( + HID_DESC_DESCTYPE_HID, + &[ + // HID Class spec version + HID_DESC_SPEC_1_10[0], + HID_DESC_SPEC_1_10[1], + // Country code not supported + HID_DESC_COUNTRY_UNSPEC, + // Number of following descriptors + 1, + // We have a HID report descriptor the host should read + HID_DESC_DESCTYPE_HID_REPORT, + // HID report descriptor size, + (len & 0xFF) as u8, + (len >> 8 & 0xFF) as u8, + ], + ); + + let ep_in = alt.endpoint_interrupt_in(config.max_packet_size, config.poll_ms); + let ep_out = if with_out_endpoint { + Some(alt.endpoint_interrupt_out(config.max_packet_size, config.poll_ms)) + } else { + 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) +} + +impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWriter<'d, D, READ_N, WRITE_N> { + /// Creates a new `HidReaderWriter`. + /// + /// This will allocate one IN and one OUT endpoints. If you only need writing (sending) + /// HID reports, consider using [`HidWriter::new`] instead, which allocates an IN endpoint only. + /// + pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: Config<'d>) -> Self { + let (ep_out, ep_in, offset) = build(builder, state, config, true); + + Self { + reader: HidReader { + ep_out: ep_out.unwrap(), + offset, + }, + writer: HidWriter { ep_in }, + } + } + + /// Splits into separate readers/writers for input and output reports. + pub fn split(self) -> (HidReader<'d, D, READ_N>, HidWriter<'d, D, WRITE_N>) { + (self.reader, self.writer) + } + + /// Waits for both IN and OUT endpoints to be enabled. + pub async fn ready(&mut self) { + self.reader.ready().await; + self.writer.ready().await; + } + + /// Writes an input report by serializing the given report structure. + #[cfg(feature = "usbd-hid")] + pub async fn write_serialize(&mut self, r: &IR) -> Result<(), EndpointError> { + self.writer.write_serialize(r).await + } + + /// Writes `report` to its interrupt endpoint. + pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { + self.writer.write(report).await + } + + /// Reads an output report from the Interrupt Out pipe. + /// + /// See [`HidReader::read`]. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.reader.read(buf).await + } +} + +/// 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), +} + +impl From for ReadError { + fn from(val: EndpointError) -> Self { + use EndpointError::{BufferOverflow, Disabled}; + match val { + BufferOverflow => ReadError::BufferOverflow, + Disabled => ReadError::Disabled, + } + } +} + +impl<'d, D: Driver<'d>, const N: usize> HidWriter<'d, D, N> { + /// Creates a new HidWriter. + /// + /// This will allocate one IN endpoint only, so the host won't be able to send + /// reports to us. If you need that, consider using [`HidReaderWriter::new`] instead. + /// + /// poll_ms configures how frequently the host should poll for reading/writing + /// HID reports. A lower value means better throughput & latency, at the expense + /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for + /// high performance uses, and a value of 255 is good for best-effort usecases. + pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: Config<'d>) -> Self { + let (ep_out, ep_in, _offset) = build(builder, state, config, false); + + assert!(ep_out.is_none()); + + Self { ep_in } + } + + /// Waits for the interrupt in endpoint to be enabled. + pub async fn ready(&mut self) { + self.ep_in.wait_enabled().await; + } + + /// Writes an input report by serializing the given report structure. + #[cfg(feature = "usbd-hid")] + pub async fn write_serialize(&mut self, r: &IR) -> Result<(), EndpointError> { + let mut buf: [u8; N] = [0; N]; + let Ok(size) = serialize(&mut buf, r) else { + return Err(EndpointError::BufferOverflow); + }; + self.write(&buf[0..size]).await + } + + /// Writes `report` to its interrupt endpoint. + pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { + assert!(report.len() <= N); + + let max_packet_size = usize::from(self.ep_in.info().max_packet_size); + let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0); + for chunk in report.chunks(max_packet_size) { + self.ep_in.write(chunk).await?; + } + + if zlp_needed { + self.ep_in.write(&[]).await?; + } + + Ok(()) + } +} + +impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { + /// Waits for the interrupt out endpoint to be enabled. + pub async fn ready(&mut self) { + self.ep_out.wait_enabled().await; + } + + /// Delivers output reports from the Interrupt Out pipe to `handler`. + /// + /// If `use_report_ids` is true, the first byte of the report will be used as + /// the `ReportId` value. Otherwise the `ReportId` value will be 0. + pub async fn run(mut self, use_report_ids: bool, handler: &mut T) -> ! { + let offset = self.offset.load(Ordering::Acquire); + assert!(offset == 0); + let mut buf = [0; N]; + loop { + match self.read(&mut buf).await { + Ok(len) => { + let id = if use_report_ids { buf[0] } else { 0 }; + handler.set_report(ReportId::Out(id), &buf[..len]); + } + Err(ReadError::BufferOverflow) => warn!( + "Host sent output report larger than the configured maximum output report length ({})", + N + ), + Err(ReadError::Disabled) => self.ep_out.wait_enabled().await, + Err(ReadError::Sync(_)) => unreachable!(), + } + } + } + + /// Reads an output report from the Interrupt Out pipe. + /// + /// **Note:** Any reports sent from the host over the control pipe will be + /// passed to [`RequestHandler::set_report()`] for handling. The application + /// is responsible for ensuring output reports from both pipes are handled + /// correctly. + /// + /// **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::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. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + assert!(N != 0); + assert!(buf.len() >= N); + + // Read packets from the endpoint + let max_packet_size = usize::from(self.ep_out.info().max_packet_size); + let starting_offset = self.offset.load(Ordering::Acquire); + let mut total = starting_offset; + loop { + for chunk in buf[starting_offset..N].chunks_mut(max_packet_size) { + match self.ep_out.read(chunk).await { + Ok(size) => { + total += size; + if size < max_packet_size || total == N { + self.offset.store(0, Ordering::Release); + break; + } + self.offset.store(total, Ordering::Release); + } + Err(err) => { + self.offset.store(0, Ordering::Release); + return Err(err.into()); + } + } + } + + // Some hosts may send ZLPs even when not required by the HID spec, so we'll loop as long as total == 0. + if total > 0 { + break; + } + } + + if starting_offset > 0 { + Err(ReadError::Sync(starting_offset..total)) + } else { + Ok(total) + } + } +} + +/// Handler for HID-related control requests. +pub trait RequestHandler { + /// Reads the value of report `id` into `buf` returning the size. + /// + /// Returns `None` if `id` is invalid or no data is available. + fn get_report(&mut self, id: ReportId, buf: &mut [u8]) -> Option { + let _ = (id, buf); + None + } + + /// Sets the value of report `id` to `data`. + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + let _ = (id, data); + OutResponse::Rejected + } + + /// Get the idle rate for `id`. + /// + /// If `id` is `None`, get the idle rate for all reports. Returning `None` + /// will reject the control request. Any duration at or above 1.024 seconds + /// or below 4ms will be returned as an indefinite idle rate. + fn get_idle_ms(&mut self, id: Option) -> Option { + let _ = id; + None + } + + /// Set the idle rate for `id` to `dur`. + /// + /// If `id` is `None`, set the idle rate of all input reports to `dur`. If + /// an indefinite duration is requested, `dur` will be set to `u32::MAX`. + fn set_idle_ms(&mut self, id: Option, duration_ms: u32) { + let _ = (id, duration_ms); + } +} + +struct Control<'d> { + if_num: InterfaceNumber, + report_descriptor: &'d [u8], + request_handler: Option<&'d mut dyn RequestHandler>, + out_report_offset: &'d AtomicUsize, + hid_descriptor: [u8; 9], +} + +impl<'d> Control<'d> { + fn new( + if_num: InterfaceNumber, + report_descriptor: &'d [u8], + request_handler: Option<&'d mut dyn RequestHandler>, + out_report_offset: &'d AtomicUsize, + ) -> Self { + Control { + if_num, + report_descriptor, + request_handler, + out_report_offset, + hid_descriptor: [ + // Length of buf inclusive of size prefix + 9, + // Descriptor type + HID_DESC_DESCTYPE_HID, + // HID Class spec version + HID_DESC_SPEC_1_10[0], + HID_DESC_SPEC_1_10[1], + // Country code not supported + HID_DESC_COUNTRY_UNSPEC, + // Number of following descriptors + 1, + // We have a HID report descriptor the host should read + HID_DESC_DESCTYPE_HID_REPORT, + // HID report descriptor size, + (report_descriptor.len() & 0xFF) as u8, + (report_descriptor.len() >> 8 & 0xFF) as u8, + ], + } + } +} + +impl<'d> Handler for Control<'d> { + fn reset(&mut self) { + self.out_report_offset.store(0, Ordering::Release); + } + + 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; + } + + // This uses a defmt-specific formatter that causes use of the `log` + // feature to fail to build, so leave it defmt-specific for now. + #[cfg(feature = "defmt")] + trace!("HID control_out {:?} {=[u8]:x}", req, data); + match req.request { + HID_REQ_SET_IDLE => { + if let Some(handler) = self.request_handler.as_mut() { + let id = req.value as u8; + let id = (id != 0).then_some(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); + } + Some(OutResponse::Accepted) + } + HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler.as_mut()) { + (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 { + warn!("HID Boot Protocol is unsupported."); + Some(OutResponse::Rejected) // UNSUPPORTED: Boot Protocol + } + } + _ => 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.as_mut().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.as_mut() { + let id = req.value as u8; + let id = (id != 0).then_some(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), + } + } + _ => None, + } + } +} diff --git a/embassy/embassy-usb/src/class/midi.rs b/embassy/embassy-usb/src/class/midi.rs new file mode 100644 index 0000000..52a96f2 --- /dev/null +++ b/embassy/embassy-usb/src/class/midi.rs @@ -0,0 +1,227 @@ +//! MIDI class implementation. + +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::Builder; + +/// This should be used as `device_class` when building the `UsbDevice`. +pub const USB_AUDIO_CLASS: u8 = 0x01; + +const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01; +const USB_MIDISTREAMING_SUBCLASS: u8 = 0x03; +const MIDI_IN_JACK_SUBTYPE: u8 = 0x02; +const MIDI_OUT_JACK_SUBTYPE: u8 = 0x03; +const EMBEDDED: u8 = 0x01; +const EXTERNAL: u8 = 0x02; +const CS_INTERFACE: u8 = 0x24; +const CS_ENDPOINT: u8 = 0x25; +const HEADER_SUBTYPE: u8 = 0x01; +const MS_HEADER_SUBTYPE: u8 = 0x01; +const MS_GENERAL: u8 = 0x01; +const PROTOCOL_NONE: u8 = 0x00; +const MIDI_IN_SIZE: u8 = 0x06; +const MIDI_OUT_SIZE: u8 = 0x09; + +/// Packet level implementation of a USB MIDI device. +/// +/// This class can be used directly and it has the least overhead due to directly reading and +/// writing USB packets with no intermediate buffers, but it will not act like a stream-like port. +/// The following constraints must be followed if you use this class directly: +/// +/// - `read_packet` must be called with a buffer large enough to hold `max_packet_size` bytes. +/// - `write_packet` must not be called with a buffer larger than `max_packet_size` bytes. +/// - If you write a packet that is exactly `max_packet_size` bytes long, it won't be processed by the +/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP) +/// can be sent if there is no other data to send. This is because USB bulk transactions must be +/// terminated with a short packet, even if the bulk endpoint is used for stream-like data. +pub struct MidiClass<'d, D: Driver<'d>> { + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, +} + +impl<'d, D: Driver<'d>> MidiClass<'d, D> { + /// Creates a new `MidiClass` with the provided UsbBus, number of input and output jacks 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>, n_in_jacks: u8, n_out_jacks: u8, max_packet_size: u16) -> Self { + let mut func = builder.function(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE); + + // Audio control interface + let mut iface = func.interface(); + let audio_if = iface.interface_number(); + let midi_if = u8::from(audio_if) + 1; + let mut alt = iface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE, None); + alt.descriptor(CS_INTERFACE, &[HEADER_SUBTYPE, 0x00, 0x01, 0x09, 0x00, 0x01, midi_if]); + + // MIDIStreaming interface + let mut iface = func.interface(); + let _midi_if = iface.interface_number(); + let mut alt = iface.alt_setting(USB_AUDIO_CLASS, USB_MIDISTREAMING_SUBCLASS, PROTOCOL_NONE, None); + + let midi_streaming_total_length = 7 + + (n_in_jacks + n_out_jacks) as usize * (MIDI_IN_SIZE + MIDI_OUT_SIZE) as usize + + 7 + + (4 + n_out_jacks as usize) + + 7 + + (4 + n_in_jacks as usize); + + alt.descriptor( + CS_INTERFACE, + &[ + MS_HEADER_SUBTYPE, + 0x00, + 0x01, + (midi_streaming_total_length & 0xFF) as u8, + ((midi_streaming_total_length >> 8) & 0xFF) as u8, + ], + ); + + // Calculates the index'th external midi in jack id + let in_jack_id_ext = |index| 2 * index + 1; + // Calculates the index'th embedded midi out jack id + let out_jack_id_emb = |index| 2 * index + 2; + // Calculates the index'th external midi out jack id + let out_jack_id_ext = |index| 2 * n_in_jacks + 2 * index + 1; + // Calculates the index'th embedded midi in jack id + let in_jack_id_emb = |index| 2 * n_in_jacks + 2 * index + 2; + + for i in 0..n_in_jacks { + alt.descriptor(CS_INTERFACE, &[MIDI_IN_JACK_SUBTYPE, EXTERNAL, in_jack_id_ext(i), 0x00]); + } + + for i in 0..n_out_jacks { + alt.descriptor(CS_INTERFACE, &[MIDI_IN_JACK_SUBTYPE, EMBEDDED, in_jack_id_emb(i), 0x00]); + } + + for i in 0..n_out_jacks { + alt.descriptor( + CS_INTERFACE, + &[ + MIDI_OUT_JACK_SUBTYPE, + EXTERNAL, + out_jack_id_ext(i), + 0x01, + in_jack_id_emb(i), + 0x01, + 0x00, + ], + ); + } + + for i in 0..n_in_jacks { + alt.descriptor( + CS_INTERFACE, + &[ + MIDI_OUT_JACK_SUBTYPE, + EMBEDDED, + out_jack_id_emb(i), + 0x01, + in_jack_id_ext(i), + 0x01, + 0x00, + ], + ); + } + + let mut endpoint_data = [ + MS_GENERAL, 0, // Number of jacks + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Jack mappings + ]; + endpoint_data[1] = n_out_jacks; + for i in 0..n_out_jacks { + endpoint_data[2 + i as usize] = in_jack_id_emb(i); + } + let read_ep = alt.endpoint_bulk_out(max_packet_size); + alt.descriptor(CS_ENDPOINT, &endpoint_data[0..2 + n_out_jacks as usize]); + + endpoint_data[1] = n_in_jacks; + for i in 0..n_in_jacks { + endpoint_data[2 + i as usize] = out_jack_id_emb(i); + } + let write_ep = alt.endpoint_bulk_in(max_packet_size); + alt.descriptor(CS_ENDPOINT, &endpoint_data[0..2 + n_in_jacks as usize]); + + MidiClass { read_ep, write_ep } + } + + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.read_ep.info().max_packet_size + } + + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.write_ep.write(data).await + } + + /// Reads a single packet from the OUT endpoint. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.read_ep.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await; + } + + /// 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 { + write_ep: self.write_ep, + }, + Receiver { read_ep: self.read_ep }, + ) + } +} + +/// Midi class packet sender. +/// +/// You can obtain a `Sender` with [`MidiClass::split`] +pub struct Sender<'d, D: Driver<'d>> { + write_ep: D::EndpointIn, +} + +impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.write_ep.info().max_packet_size + } + + /// Writes a single packet. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.write_ep.write(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.write_ep.wait_enabled().await; + } +} + +/// Midi class packet receiver. +/// +/// You can obtain a `Receiver` with [`MidiClass::split`] +pub struct Receiver<'d, D: Driver<'d>> { + read_ep: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> Receiver<'d, D> { + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.read_ep.info().max_packet_size + } + + /// Reads a single packet. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.read_ep.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await; + } +} diff --git a/embassy/embassy-usb/src/class/mod.rs b/embassy/embassy-usb/src/class/mod.rs new file mode 100644 index 0000000..4bd89eb --- /dev/null +++ b/embassy/embassy-usb/src/class/mod.rs @@ -0,0 +1,7 @@ +//! Implementations of well-known USB classes. +pub mod cdc_acm; +pub mod cdc_ncm; +pub mod hid; +pub mod midi; +pub mod uac1; +pub mod web_usb; diff --git a/embassy/embassy-usb/src/class/uac1/class_codes.rs b/embassy/embassy-usb/src/class/uac1/class_codes.rs new file mode 100644 index 0000000..3f69567 --- /dev/null +++ b/embassy/embassy-usb/src/class/uac1/class_codes.rs @@ -0,0 +1,151 @@ +//! Audio Device Class Codes as defined in Universal Serial Bus Device Class +//! Definition for Audio Devices, Release 1.0, Appendix A and Universal Serial +//! Bus Device Class Definition for Audio Data Formats, Release 1.0, Appendix +//! A.1.1 (Audio Data Format Type I Codes) +#![allow(dead_code)] + +/// The current version of the ADC specification (1.0) +pub const ADC_VERSION: u16 = 0x0100; + +/// The current version of the USB device (1.0) +pub const DEVICE_VERSION: u16 = 0x0100; + +/// Audio Interface Class Code +pub const USB_AUDIO_CLASS: u8 = 0x01; + +// Audio Interface Subclass Codes +pub const USB_UNDEFINED_SUBCLASS: u8 = 0x00; +pub const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01; +pub const USB_AUDIOSTREAMING_SUBCLASS: u8 = 0x02; +pub const USB_MIDISTREAMING_SUBCLASS: u8 = 0x03; + +// Audio Protocol Code +pub const PROTOCOL_NONE: u8 = 0x00; + +// Audio Class-Specific Descriptor Types +pub const CS_UNDEFINED: u8 = 0x20; +pub const CS_DEVICE: u8 = 0x21; +pub const CS_CONFIGURATION: u8 = 0x22; +pub const CS_STRING: u8 = 0x23; +pub const CS_INTERFACE: u8 = 0x24; +pub const CS_ENDPOINT: u8 = 0x25; + +// Descriptor Subtype +pub const AC_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const HEADER_SUBTYPE: u8 = 0x01; +pub const INPUT_TERMINAL: u8 = 0x02; +pub const OUTPUT_TERMINAL: u8 = 0x03; +pub const MIXER_UNIT: u8 = 0x04; +pub const SELECTOR_UNIT: u8 = 0x05; +pub const FEATURE_UNIT: u8 = 0x06; +pub const PROCESSING_UNIT: u8 = 0x07; +pub const EXTENSION_UNIT: u8 = 0x08; + +// Audio Class-Specific AS Interface Descriptor Subtypes +pub const AS_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const AS_GENERAL: u8 = 0x01; +pub const FORMAT_TYPE: u8 = 0x02; +pub const FORMAT_SPECIFIC: u8 = 0x03; + +// Processing Unit Process Types +pub const PROCESS_UNDEFINED: u16 = 0x00; +pub const UP_DOWNMIX_PROCESS: u16 = 0x01; +pub const DOLBY_PROLOGIC_PROCESS: u16 = 0x02; +pub const DDD_STEREO_EXTENDER_PROCESS: u16 = 0x03; +pub const REVERBERATION_PROCESS: u16 = 0x04; +pub const CHORUS_PROCESS: u16 = 0x05; +pub const DYN_RANGE_COMP_PROCESS: u16 = 0x06; + +// Audio Class-Specific Endpoint Descriptor Subtypes +pub const EP_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const EP_GENERAL: u8 = 0x01; + +// Audio Class-Specific Request Codes +pub const REQUEST_CODE_UNDEFINED: u8 = 0x00; +pub const SET_CUR: u8 = 0x01; +pub const GET_CUR: u8 = 0x81; +pub const SET_MIN: u8 = 0x02; +pub const GET_MIN: u8 = 0x82; +pub const SET_MAX: u8 = 0x03; +pub const GET_MAX: u8 = 0x83; +pub const SET_RES: u8 = 0x04; +pub const GET_RES: u8 = 0x84; +pub const SET_MEM: u8 = 0x05; +pub const GET_MEM: u8 = 0x85; +pub const GET_STAT: u8 = 0xFF; + +// Terminal Control Selectors +pub const TE_CONTROL_UNDEFINED: u8 = 0x00; +pub const COPY_PROTECT_CONTROL: u8 = 0x01; + +// Feature Unit Control Selectors +pub const FU_CONTROL_UNDEFINED: u8 = 0x00; +pub const MUTE_CONTROL: u8 = 0x01; +pub const VOLUME_CONTROL: u8 = 0x02; +pub const BASS_CONTROL: u8 = 0x03; +pub const MID_CONTROL: u8 = 0x04; +pub const TREBLE_CONTROL: u8 = 0x05; +pub const GRAPHIC_EQUALIZER_CONTROL: u8 = 0x06; +pub const AUTOMATIC_GAIN_CONTROL: u8 = 0x07; +pub const DELAY_CONTROL: u8 = 0x08; +pub const BASS_BOOST_CONTROL: u8 = 0x09; +pub const LOUDNESS_CONTROL: u8 = 0x0A; + +// Up/Down-mix Processing Unit Control Selectors +pub const UD_CONTROL_UNDEFINED: u8 = 0x00; +pub const UD_ENABLE_CONTROL: u8 = 0x01; +pub const UD_MODE_SELECT_CONTROL: u8 = 0x02; + +// Dolby Prologic Processing Unit Control Selectors +pub const DP_CONTROL_UNDEFINED: u8 = 0x00; +pub const DP_ENABLE_CONTROL: u8 = 0x01; +pub const DP_MODE_SELECT_CONTROL: u8 = 0x2; + +// 3D Stereo Extender Processing Unit Control Selectors +pub const DDD_CONTROL_UNDEFINED: u8 = 0x00; +pub const DDD_ENABLE_CONTROL: u8 = 0x01; +pub const DDD_SPACIOUSNESS_CONTROL: u8 = 0x03; + +// Reverberation Processing Unit Control Selectors +pub const RV_CONTROL_UNDEFINED: u8 = 0x00; +pub const RV_ENABLE_CONTROL: u8 = 0x01; +pub const REVERB_LEVEL_CONTROL: u8 = 0x02; +pub const REVERB_TIME_CONTROL: u8 = 0x03; +pub const REVERB_FEEDBACK_CONTROL: u8 = 0x04; + +// Chorus Processing Unit Control Selectors +pub const CH_CONTROL_UNDEFINED: u8 = 0x00; +pub const CH_ENABLE_CONTROL: u8 = 0x01; +pub const CHORUS_LEVEL_CONTROL: u8 = 0x02; +pub const CHORUS_RATE_CONTROL: u8 = 0x03; +pub const CHORUS_DEPTH_CONTROL: u8 = 0x04; + +// Dynamic Range Compressor Processing Unit Control Selectors +pub const DR_CONTROL_UNDEFINED: u8 = 0x00; +pub const DR_ENABLE_CONTROL: u8 = 0x01; +pub const COMPRESSION_RATE_CONTROL: u8 = 0x02; +pub const MAXAMPL_CONTROL: u8 = 0x03; +pub const THRESHOLD_CONTROL: u8 = 0x04; +pub const ATTACK_TIME: u8 = 0x05; +pub const RELEASE_TIME: u8 = 0x06; + +// Extension Unit Control Selectors +pub const XU_CONTROL_UNDEFINED: u16 = 0x00; +pub const XU_ENABLE_CONTROL: u16 = 0x01; + +// Endpoint Control Selectors +pub const EP_CONTROL_UNDEFINED: u8 = 0x00; +pub const SAMPLING_FREQ_CONTROL: u8 = 0x01; +pub const PITCH_CONTROL: u8 = 0x02; + +// Format Type Codes +pub const FORMAT_TYPE_UNDEFINED: u8 = 0x00; +pub const FORMAT_TYPE_I: u8 = 0x01; + +// Audio Data Format Type I Codes +pub const TYPE_I_UNDEFINED: u16 = 0x0000; +pub const PCM: u16 = 0x0001; +pub const PCM8: u16 = 0x0002; +pub const IEEE_FLOAT: u16 = 0x0003; +pub const ALAW: u16 = 0x0004; +pub const MULAW: u16 = 0x0005; diff --git a/embassy/embassy-usb/src/class/uac1/mod.rs b/embassy/embassy-usb/src/class/uac1/mod.rs new file mode 100644 index 0000000..3d5f4e5 --- /dev/null +++ b/embassy/embassy-usb/src/class/uac1/mod.rs @@ -0,0 +1,134 @@ +//! USB Audio Class 1.0 implementations for different applications. +//! +//! Contains: +//! - The `speaker` class with a single audio streaming interface (host to device) + +pub mod speaker; + +mod class_codes; +mod terminal_type; + +/// The maximum supported audio channel index (corresponds to `Top`). +/// FIXME: Use `core::mem::variant_count(...)` when stabilized. +const MAX_AUDIO_CHANNEL_INDEX: usize = 12; + +/// The maximum number of supported audio channels. +/// +/// Includes all twelve channels from `Channel`, plus the Master channel. +const MAX_AUDIO_CHANNEL_COUNT: usize = MAX_AUDIO_CHANNEL_INDEX + 1; + +/// USB Audio Channel +#[derive(Debug, Clone, Copy, PartialEq)] +#[allow(missing_docs)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Channel { + LeftFront, + RightFront, + CenterFront, + Lfe, + LeftSurround, + RightSurround, + LeftOfCenter, + RightOfCenter, + Surround, + SideLeft, + SideRight, + Top, +} + +impl Channel { + /// Map a `Channel` to its corresponding USB Audio `ChannelConfig`. + fn get_channel_config(&self) -> ChannelConfig { + match self { + Channel::LeftFront => ChannelConfig::LeftFront, + Channel::RightFront => ChannelConfig::RightFront, + Channel::CenterFront => ChannelConfig::CenterFront, + Channel::Lfe => ChannelConfig::Lfe, + Channel::LeftSurround => ChannelConfig::LeftSurround, + Channel::RightSurround => ChannelConfig::RightSurround, + Channel::LeftOfCenter => ChannelConfig::LeftOfCenter, + Channel::RightOfCenter => ChannelConfig::RightOfCenter, + Channel::Surround => ChannelConfig::Surround, + Channel::SideLeft => ChannelConfig::SideLeft, + Channel::SideRight => ChannelConfig::SideRight, + Channel::Top => ChannelConfig::Top, + } + } +} + +/// USB Audio Channel configuration +#[repr(u16)] +#[non_exhaustive] +// #[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum ChannelConfig { + None = 0x0000, + LeftFront = 0x0001, + RightFront = 0x0002, + CenterFront = 0x0004, + Lfe = 0x0008, + LeftSurround = 0x0010, + RightSurround = 0x0020, + LeftOfCenter = 0x0040, + RightOfCenter = 0x0080, + Surround = 0x0100, + SideLeft = 0x0200, + SideRight = 0x0400, + Top = 0x0800, +} + +impl From for u16 { + fn from(t: ChannelConfig) -> u16 { + t as u16 + } +} + +/// Feedback period adjustment `bRefresh` [UAC 3.7.2.2] +/// +/// From the specification: "A new Ff value is available every 2^(10 – P) frames with P ranging from 1 to 9. The +/// bRefresh field of the synch standard endpoint descriptor is used to report the exponent (10-P) to the Host." +/// +/// This means: +/// - 512 ms (2^9 frames) to 2 ms (2^1 frames) for USB full-speed +/// - 64 ms (2^9 microframes) to 0.25 ms (2^1 microframes) for USB high-speed +#[repr(u8)] +#[allow(missing_docs)] +#[derive(Clone, Copy)] +pub enum FeedbackRefresh { + Period2Frames = 1, + Period4Frames = 2, + Period8Frames = 3, + Period16Frames = 4, + Period32Frames = 5, + Period64Frames = 6, + Period128Frames = 7, + Period256Frames = 8, + Period512Frames = 9, +} + +impl FeedbackRefresh { + /// Gets the number of frames, after which a new feedback frame is returned. + pub const fn frame_count(&self) -> usize { + 1 << (*self as usize) + } +} + +/// Audio sample width. +/// +/// Stored in number of bytes per sample. +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum SampleWidth { + /// 16 bit audio + Width2Byte = 2, + /// 24 bit audio + Width3Byte = 3, + /// 32 bit audio + Width4Byte = 4, +} + +impl SampleWidth { + /// Get the audio sample resolution in number of bit. + pub const fn in_bit(self) -> usize { + 8 * self as usize + } +} diff --git a/embassy/embassy-usb/src/class/uac1/speaker.rs b/embassy/embassy-usb/src/class/uac1/speaker.rs new file mode 100644 index 0000000..96456d9 --- /dev/null +++ b/embassy/embassy-usb/src/class/uac1/speaker.rs @@ -0,0 +1,778 @@ +//! USB Audio Class 1.0 - Speaker device +//! +//! Provides a class with a single audio streaming interface (host to device), +//! that advertises itself as a speaker. Includes explicit sample rate feedback. +//! +//! Various aspects of the audio stream can be configured, for example: +//! - sample rate +//! - sample resolution +//! - audio channel count and assignment +//! +//! The class provides volume and mute controls for each channel. + +use core::cell::{Cell, RefCell}; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use core::task::Poll; + +use embassy_sync::blocking_mutex::CriticalSectionMutex; +use embassy_sync::waitqueue::WakerRegistration; +use heapless::Vec; + +use super::class_codes::*; +use super::terminal_type::TerminalType; +use super::{Channel, ChannelConfig, FeedbackRefresh, SampleWidth, MAX_AUDIO_CHANNEL_COUNT, MAX_AUDIO_CHANNEL_INDEX}; +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::descriptor::{SynchronizationType, UsageType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut, EndpointType}; +use crate::types::InterfaceNumber; +use crate::{Builder, Handler}; + +/// Maximum allowed sampling rate (3 bytes) in Hz. +const MAX_SAMPLE_RATE_HZ: u32 = 0x7FFFFF; + +/// Arbitrary unique identifier for the input unit. +const INPUT_UNIT_ID: u8 = 0x01; + +/// Arbitrary unique identifier for the feature unit. +const FEATURE_UNIT_ID: u8 = 0x02; + +/// Arbitrary unique identifier for the output unit. +const OUTPUT_UNIT_ID: u8 = 0x03; + +// Volume settings go from -25600 to 0, in steps of 256. +// Therefore, the volume settings are 8q8 values in units of dB. +const VOLUME_STEPS_PER_DB: i16 = 256; +const MIN_VOLUME_DB: i16 = -100; +const MAX_VOLUME_DB: i16 = 0; + +// Maximum number of supported discrete sample rates. +const MAX_SAMPLE_RATE_COUNT: usize = 10; + +/// The volume of an audio channel. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Volume { + /// The channel is muted. + Muted, + /// The channel volume in dB. Ranges from `MIN_VOLUME_DB` (quietest) to `MAX_VOLUME_DB` (loudest). + DeciBel(f32), +} + +/// Internal state for the USB Audio Class. +pub struct State<'d> { + control: Option>, + shared: SharedControl<'d>, +} + +impl<'d> Default for State<'d> { + fn default() -> Self { + Self::new() + } +} + +impl<'d> State<'d> { + /// Create a new `State`. + pub fn new() -> Self { + Self { + control: None, + shared: SharedControl::default(), + } + } +} + +/// Implementation of the USB audio class 1.0. +pub struct Speaker<'d, D: Driver<'d>> { + phantom: PhantomData<&'d D>, +} + +impl<'d, D: Driver<'d>> Speaker<'d, D> { + /// Creates a new [`Speaker`] device, split into a stream, feedback, and a control change notifier. + /// + /// The packet size should be chosen, based on the expected transfer size of samples per (micro)frame. + /// For example, a stereo stream at 32 bit resolution and 48 kHz sample rate yields packets of 384 byte for + /// full-speed USB (1 ms frame interval) or 48 byte for high-speed USB (125 us microframe interval). + /// When using feedback, the packet size varies and thus, the `max_packet_size` should be increased (e.g. to double). + /// + /// # Arguments + /// + /// * `builder` - The builder for the class. + /// * `state` - The internal state of the class. + /// * `max_packet_size` - The maximum packet size per (micro)frame. + /// * `resolution` - The audio sample resolution. + /// * `sample_rates_hz` - The supported sample rates in Hz. + /// * `channels` - The advertised audio channels (up to 12). Entries must be unique, or this function panics. + /// * `feedback_refresh_period` - The refresh period for the feedback value. + pub fn new( + builder: &mut Builder<'d, D>, + state: &'d mut State<'d>, + max_packet_size: u16, + resolution: SampleWidth, + sample_rates_hz: &[u32], + channels: &'d [Channel], + feedback_refresh_period: FeedbackRefresh, + ) -> (Stream<'d, D>, Feedback<'d, D>, ControlMonitor<'d>) { + // The class and subclass fields of the IAD aren't required to match the class and subclass fields of + // the interfaces in the interface collection that the IAD describes. Microsoft recommends that + // the first interface of the collection has class and subclass fields that match the class and + // subclass fields of the IAD. + let mut func = builder.function(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE); + + // Audio control interface (mandatory) [UAC 4.3.1] + let mut interface = func.interface(); + let control_interface = interface.interface_number().into(); + let streaming_interface = u8::from(control_interface) + 1; + let mut alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE, None); + + // Terminal topology: + // Input terminal (receives audio stream) -> Feature Unit (mute and volume) -> Output terminal (e.g. towards speaker) + + // ======================================= + // Input Terminal Descriptor [UAC 3.3.2.1] + // Audio input + let terminal_type: u16 = TerminalType::UsbStreaming.into(); + + // Assemble channel configuration field + let mut channel_config: u16 = ChannelConfig::None.into(); + for channel in channels { + let channel: u16 = channel.get_channel_config().into(); + + if channel_config & channel != 0 { + panic!("Invalid channel config, duplicate channel {}.", channel); + } + channel_config |= channel; + } + + let input_terminal_descriptor = [ + INPUT_TERMINAL, // bDescriptorSubtype + INPUT_UNIT_ID, // bTerminalID + terminal_type as u8, + (terminal_type >> 8) as u8, // wTerminalType + 0x00, // bAssocTerminal (none) + channels.len() as u8, // bNrChannels + channel_config as u8, + (channel_config >> 8) as u8, // wChannelConfig + 0x00, // iChannelNames (none) + 0x00, // iTerminal (none) + ]; + + // ======================================== + // Output Terminal Descriptor [UAC 4.3.2.2] + // Speaker output + let terminal_type: u16 = TerminalType::OutSpeaker.into(); + let output_terminal_descriptor = [ + OUTPUT_TERMINAL, // bDescriptorSubtype + OUTPUT_UNIT_ID, // bTerminalID + terminal_type as u8, + (terminal_type >> 8) as u8, // wTerminalType + 0x00, // bAssocTerminal (none) + FEATURE_UNIT_ID, // bSourceID (the feature unit) + 0x00, // iTerminal (none) + ]; + + // ===================================== + // Feature Unit Descriptor [UAC 4.3.2.5] + // Mute and volume control + let controls = MUTE_CONTROL | VOLUME_CONTROL; + + const FEATURE_UNIT_DESCRIPTOR_SIZE: usize = 5; + let mut feature_unit_descriptor: Vec = + Vec::from_slice(&[ + FEATURE_UNIT, // bDescriptorSubtype (Feature Unit) + FEATURE_UNIT_ID, // bUnitID + INPUT_UNIT_ID, // bSourceID + 1, // bControlSize (one byte per control) + FU_CONTROL_UNDEFINED, // Master controls (disabled, use only per-channel control) + ]) + .unwrap(); + + // Add per-channel controls + for _channel in channels { + feature_unit_descriptor.push(controls).unwrap(); + } + feature_unit_descriptor.push(0x00).unwrap(); // iFeature (none) + + // =============================================== + // Format desciptor [UAC 4.5.3] + // Used later, for operational streaming interface + let mut format_descriptor: Vec = Vec::from_slice(&[ + FORMAT_TYPE, // bDescriptorSubtype + FORMAT_TYPE_I, // bFormatType + channels.len() as u8, // bNrChannels + resolution as u8, // bSubframeSize + resolution.in_bit() as u8, // bBitResolution + ]) + .unwrap(); + + format_descriptor.push(sample_rates_hz.len() as u8).unwrap(); + + for sample_rate_hz in sample_rates_hz { + assert!(*sample_rate_hz <= MAX_SAMPLE_RATE_HZ); + format_descriptor.push((sample_rate_hz & 0xFF) as u8).unwrap(); + format_descriptor.push(((sample_rate_hz >> 8) & 0xFF) as u8).unwrap(); + format_descriptor.push(((sample_rate_hz >> 16) & 0xFF) as u8).unwrap(); + } + + // ================================================== + // Class-specific AC Interface Descriptor [UAC 4.3.2] + const DESCRIPTOR_HEADER_SIZE: usize = 2; + const INTERFACE_DESCRIPTOR_SIZE: usize = 7; + + let mut total_descriptor_length = 0; + + for size in [ + INTERFACE_DESCRIPTOR_SIZE, + input_terminal_descriptor.len(), + feature_unit_descriptor.len(), + output_terminal_descriptor.len(), + ] { + total_descriptor_length += size + DESCRIPTOR_HEADER_SIZE; + } + + let interface_descriptor: [u8; INTERFACE_DESCRIPTOR_SIZE] = [ + HEADER_SUBTYPE, // bDescriptorSubtype (Header) + ADC_VERSION as u8, + (ADC_VERSION >> 8) as u8, // bcdADC + total_descriptor_length as u8, + (total_descriptor_length >> 8) as u8, // wTotalLength + 0x01, // bInCollection (1 streaming interface) + streaming_interface, // baInterfaceNr + ]; + + alt.descriptor(CS_INTERFACE, &interface_descriptor); + alt.descriptor(CS_INTERFACE, &input_terminal_descriptor); + alt.descriptor(CS_INTERFACE, &feature_unit_descriptor); + alt.descriptor(CS_INTERFACE, &output_terminal_descriptor); + + // ===================================================== + // Audio streaming interface, zero-bandwidth [UAC 4.5.1] + let mut interface = func.interface(); + let alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOSTREAMING_SUBCLASS, PROTOCOL_NONE, None); + drop(alt); + + // ================================================== + // Audio streaming interface, operational [UAC 4.5.1] + let mut alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOSTREAMING_SUBCLASS, PROTOCOL_NONE, None); + + alt.descriptor( + CS_INTERFACE, + &[ + AS_GENERAL, // bDescriptorSubtype + INPUT_UNIT_ID, // bTerminalLink + 0x00, // bDelay (none) + PCM as u8, + (PCM >> 8) as u8, // wFormatTag (PCM format) + ], + ); + + alt.descriptor(CS_INTERFACE, &format_descriptor); + + let streaming_endpoint = alt.alloc_endpoint_out(EndpointType::Isochronous, max_packet_size, 1); + let feedback_endpoint = alt.alloc_endpoint_in( + EndpointType::Isochronous, + 4, // Feedback packets are 24 bit (10.14 format). + 1, + ); + + // Write the descriptor for the streaming endpoint, after knowing the address of the feedback endpoint. + alt.endpoint_descriptor( + streaming_endpoint.info(), + SynchronizationType::Asynchronous, + UsageType::DataEndpoint, + &[ + 0x00, // bRefresh (0) + feedback_endpoint.info().addr.into(), // bSynchAddress (the feedback endpoint) + ], + ); + + alt.descriptor( + CS_ENDPOINT, + &[ + AS_GENERAL, // bDescriptorSubtype (General) + SAMPLING_FREQ_CONTROL, // bmAttributes (support sampling frequency control) + 0x02, // bLockDelayUnits (PCM) + 0x0000 as u8, + (0x0000 >> 8) as u8, // wLockDelay (0) + ], + ); + + // Write the feedback endpoint descriptor after the streaming endpoint descriptor + // This is demanded by the USB audio class specification. + alt.endpoint_descriptor( + feedback_endpoint.info(), + SynchronizationType::NoSynchronization, + UsageType::FeedbackEndpoint, + &[ + feedback_refresh_period as u8, // bRefresh + 0x00, // bSynchAddress (none) + ], + ); + + // Free up the builder. + drop(func); + + // Store channel information + state.shared.channels = channels; + + state.control = Some(Control { + shared: &state.shared, + streaming_endpoint_address: streaming_endpoint.info().addr.into(), + control_interface_number: control_interface, + }); + + builder.handler(state.control.as_mut().unwrap()); + + let control = &state.shared; + + ( + Stream { streaming_endpoint }, + Feedback { feedback_endpoint }, + ControlMonitor { shared: control }, + ) + } +} + +/// Audio settings for the feature unit. +/// +/// Contains volume and mute control. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AudioSettings { + /// Channel mute states. + muted: [bool; MAX_AUDIO_CHANNEL_COUNT], + /// Channel volume levels in 8.8 format (in dB). + volume_8q8_db: [i16; MAX_AUDIO_CHANNEL_COUNT], +} + +impl Default for AudioSettings { + fn default() -> Self { + AudioSettings { + muted: [true; MAX_AUDIO_CHANNEL_COUNT], + volume_8q8_db: [MAX_VOLUME_DB * VOLUME_STEPS_PER_DB; MAX_AUDIO_CHANNEL_COUNT], + } + } +} + +struct Control<'d> { + control_interface_number: InterfaceNumber, + streaming_endpoint_address: u8, + shared: &'d SharedControl<'d>, +} + +/// Shared data between [`Control`] and the [`Speaker`] class. +struct SharedControl<'d> { + /// The collection of audio settings (volumes, mute states). + audio_settings: CriticalSectionMutex>, + + /// Channel assignments. + channels: &'d [Channel], + + /// The audio sample rate in Hz. + sample_rate_hz: AtomicU32, + + // Notification mechanism. + waker: RefCell, + changed: AtomicBool, +} + +impl<'d> Default for SharedControl<'d> { + fn default() -> Self { + SharedControl { + audio_settings: CriticalSectionMutex::new(Cell::new(AudioSettings::default())), + channels: &[], + sample_rate_hz: AtomicU32::new(0), + waker: RefCell::new(WakerRegistration::new()), + changed: AtomicBool::new(false), + } + } +} + +impl<'d> SharedControl<'d> { + async fn changed(&self) { + poll_fn(|context| { + if self.changed.load(Ordering::Relaxed) { + self.changed.store(false, Ordering::Relaxed); + Poll::Ready(()) + } else { + self.waker.borrow_mut().register(context.waker()); + Poll::Pending + } + }) + .await; + } +} + +/// Used for reading audio frames. +pub struct Stream<'d, D: Driver<'d>> { + streaming_endpoint: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> Stream<'d, D> { + /// Reads a single packet from the OUT endpoint + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.streaming_endpoint.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.streaming_endpoint.wait_enabled().await; + } +} + +/// Used for writing sample rate information over the feedback endpoint. +pub struct Feedback<'d, D: Driver<'d>> { + feedback_endpoint: D::EndpointIn, +} + +impl<'d, D: Driver<'d>> Feedback<'d, D> { + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.feedback_endpoint.write(data).await + } + + /// Waits for the USB host to enable this interface. + pub async fn wait_connection(&mut self) { + self.feedback_endpoint.wait_enabled().await; + } +} + +/// Control status change monitor +/// +/// Await [`ControlMonitor::changed`] for being notified of configuration changes. Afterwards, the updated +/// configuration settings can be read with [`ControlMonitor::volume`] and [`ControlMonitor::sample_rate_hz`]. +pub struct ControlMonitor<'d> { + shared: &'d SharedControl<'d>, +} + +impl<'d> ControlMonitor<'d> { + fn audio_settings(&self) -> AudioSettings { + let audio_settings = self.shared.audio_settings.lock(|x| x.get()); + + audio_settings + } + + fn get_logical_channel(&self, search_channel: Channel) -> Option { + let index = self.shared.channels.iter().position(|&c| c == search_channel)?; + + // The logical channels start at one (zero is the master channel). + Some(index + 1) + } + + /// Get the volume of a selected channel. + pub fn volume(&self, channel: Channel) -> Option { + let channel_index = self.get_logical_channel(channel)?; + + if self.audio_settings().muted[channel_index] { + return Some(Volume::Muted); + } + + Some(Volume::DeciBel( + (self.audio_settings().volume_8q8_db[channel_index] as f32) / 256.0f32, + )) + } + + /// Get the streaming endpoint's sample rate in Hz. + pub fn sample_rate_hz(&self) -> u32 { + self.shared.sample_rate_hz.load(Ordering::Relaxed) + } + + /// Return a future for when the control settings change. + pub async fn changed(&self) { + self.shared.changed().await; + } +} + +impl<'d> Control<'d> { + fn changed(&mut self) { + self.shared.changed.store(true, Ordering::Relaxed); + self.shared.waker.borrow_mut().wake(); + } + + fn interface_set_mute_state( + &mut self, + audio_settings: &mut AudioSettings, + channel_index: u8, + data: &[u8], + ) -> OutResponse { + let mute_state = data[0] != 0; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => { + audio_settings.muted[channel_index as usize] = mute_state; + } + _ => { + debug!("Failed to set channel {} mute state: {}", channel_index, mute_state); + return OutResponse::Rejected; + } + } + + debug!("Set channel {} mute state: {}", channel_index, mute_state); + OutResponse::Accepted + } + + fn interface_set_volume( + &mut self, + audio_settings: &mut AudioSettings, + channel_index: u8, + data: &[u8], + ) -> OutResponse { + let volume = i16::from_ne_bytes(data[..2].try_into().expect("Failed to read volume.")); + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => { + audio_settings.volume_8q8_db[channel_index as usize] = volume; + } + _ => { + debug!("Failed to set channel {} volume: {}", channel_index, volume); + return OutResponse::Rejected; + } + } + + debug!("Set channel {} volume: {}", channel_index, volume); + OutResponse::Accepted + } + + fn interface_set_request(&mut self, req: control::Request, data: &[u8]) -> Option { + let interface_number = req.index as u8; + let entity_index = (req.index >> 8) as u8; + let channel_index = req.value as u8; + let control_unit = (req.value >> 8) as u8; + + if interface_number != self.control_interface_number.into() { + debug!("Unhandled interface set request for interface {}", interface_number); + return None; + } + + if entity_index != FEATURE_UNIT_ID { + debug!("Unsupported interface set request for entity {}", entity_index); + return Some(OutResponse::Rejected); + } + + if req.request != SET_CUR { + debug!("Unsupported interface set request type {}", req.request); + return Some(OutResponse::Rejected); + } + + let mut audio_settings = self.shared.audio_settings.lock(|x| x.get()); + let response = match control_unit { + MUTE_CONTROL => self.interface_set_mute_state(&mut audio_settings, channel_index, data), + VOLUME_CONTROL => self.interface_set_volume(&mut audio_settings, channel_index, data), + _ => OutResponse::Rejected, + }; + + if response == OutResponse::Rejected { + return Some(response); + } + + // Store updated settings + self.shared.audio_settings.lock(|x| x.set(audio_settings)); + + self.changed(); + + Some(OutResponse::Accepted) + } + + fn endpoint_set_request(&mut self, req: control::Request, data: &[u8]) -> Option { + let control_selector = (req.value >> 8) as u8; + let endpoint_address = req.index as u8; + + if endpoint_address != self.streaming_endpoint_address { + debug!( + "Unhandled endpoint set request for endpoint {} and control {} with data {}", + endpoint_address, control_selector, data + ); + return None; + } + + if control_selector != SAMPLING_FREQ_CONTROL { + debug!( + "Unsupported endpoint set request for control selector {}", + control_selector + ); + return Some(OutResponse::Rejected); + } + + let sample_rate_hz: u32 = (data[0] as u32) | (data[1] as u32) << 8 | (data[2] as u32) << 16; + self.shared.sample_rate_hz.store(sample_rate_hz, Ordering::Relaxed); + + debug!("Set endpoint {} sample rate to {} Hz", endpoint_address, sample_rate_hz); + + self.changed(); + + Some(OutResponse::Accepted) + } + + fn interface_get_request<'r>(&'r mut self, req: Request, buf: &'r mut [u8]) -> Option> { + let interface_number = req.index as u8; + let entity_index = (req.index >> 8) as u8; + let channel_index = req.value as u8; + let control_unit = (req.value >> 8) as u8; + + if interface_number != self.control_interface_number.into() { + debug!("Unhandled interface get request for interface {}.", interface_number); + return None; + } + + if entity_index != FEATURE_UNIT_ID { + // Only this function unit can be handled at the moment. + debug!("Unsupported interface get request for entity {}.", entity_index); + return Some(InResponse::Rejected); + } + + let audio_settings = self.shared.audio_settings.lock(|x| x.get()); + + match req.request { + GET_CUR => match control_unit { + VOLUME_CONTROL => { + let volume: i16; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => volume = audio_settings.volume_8q8_db[channel_index as usize], + _ => return Some(InResponse::Rejected), + } + + buf[0] = volume as u8; + buf[1] = (volume >> 8) as u8; + + debug!("Got channel {} volume: {}.", channel_index, volume); + return Some(InResponse::Accepted(&buf[..2])); + } + MUTE_CONTROL => { + let mute_state: bool; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => mute_state = audio_settings.muted[channel_index as usize], + _ => return Some(InResponse::Rejected), + } + + buf[0] = mute_state.into(); + debug!("Got channel {} mute state: {}.", channel_index, mute_state); + return Some(InResponse::Accepted(&buf[..1])); + } + _ => return Some(InResponse::Rejected), + }, + GET_MIN => match control_unit { + VOLUME_CONTROL => { + let min_volume = MIN_VOLUME_DB * VOLUME_STEPS_PER_DB; + buf[0] = min_volume as u8; + buf[1] = (min_volume >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + GET_MAX => match control_unit { + VOLUME_CONTROL => { + let max_volume = MAX_VOLUME_DB * VOLUME_STEPS_PER_DB; + buf[0] = max_volume as u8; + buf[1] = (max_volume >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + GET_RES => match control_unit { + VOLUME_CONTROL => { + buf[0] = VOLUME_STEPS_PER_DB as u8; + buf[1] = (VOLUME_STEPS_PER_DB >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + _ => return Some(InResponse::Rejected), + } + } + + fn endpoint_get_request<'r>(&'r mut self, req: Request, buf: &'r mut [u8]) -> Option> { + let control_selector = (req.value >> 8) as u8; + let endpoint_address = req.index as u8; + + if endpoint_address != self.streaming_endpoint_address { + debug!("Unhandled endpoint get request for endpoint {}.", endpoint_address); + return None; + } + + if control_selector != SAMPLING_FREQ_CONTROL as u8 { + debug!( + "Unsupported endpoint get request for control selector {}.", + control_selector + ); + return Some(InResponse::Rejected); + } + + let sample_rate_hz = self.shared.sample_rate_hz.load(Ordering::Relaxed); + + buf[0] = (sample_rate_hz & 0xFF) as u8; + buf[1] = ((sample_rate_hz >> 8) & 0xFF) as u8; + buf[2] = ((sample_rate_hz >> 16) & 0xFF) as u8; + + Some(InResponse::Accepted(&buf[..3])) + } +} + +impl<'d> Handler for Control<'d> { + /// Called when the USB device has been enabled or disabled. + fn enabled(&mut self, enabled: bool) { + debug!("USB device enabled: {}", enabled); + } + + /// Called when the host has set the address of the device to `addr`. + fn addressed(&mut self, addr: u8) { + debug!("Host set address to: {}", addr); + } + + /// Called when the host has enabled or disabled the configuration of the device. + fn configured(&mut self, configured: bool) { + debug!("USB device configured: {}", configured); + } + + /// Called when remote wakeup feature is enabled or disabled. + fn remote_wakeup_enabled(&mut self, enabled: bool) { + debug!("USB remote wakeup enabled: {}", enabled); + } + + /// Called when a "set alternate setting" control request is done on the interface. + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + debug!( + "USB set interface number {} to alt setting {}.", + iface, alternate_setting + ); + } + + /// Called after a USB reset after the bus reset sequence is complete. + fn reset(&mut self) { + let shared = self.shared; + shared.audio_settings.lock(|x| x.set(AudioSettings::default())); + + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + } + + /// Called when the bus has entered or exited the suspend state. + fn suspended(&mut self, suspended: bool) { + debug!("USB device suspended: {}", suspended); + } + + // Handle control set requests. + fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option { + match req.request_type { + RequestType::Class => match req.recipient { + Recipient::Interface => self.interface_set_request(req, data), + Recipient::Endpoint => self.endpoint_set_request(req, data), + _ => Some(OutResponse::Rejected), + }, + _ => None, + } + } + + // Handle control get requests. + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + match req.request_type { + RequestType::Class => match req.recipient { + Recipient::Interface => self.interface_get_request(req, buf), + Recipient::Endpoint => self.endpoint_get_request(req, buf), + _ => None, + }, + _ => None, + } + } +} diff --git a/embassy/embassy-usb/src/class/uac1/terminal_type.rs b/embassy/embassy-usb/src/class/uac1/terminal_type.rs new file mode 100644 index 0000000..65474a7 --- /dev/null +++ b/embassy/embassy-usb/src/class/uac1/terminal_type.rs @@ -0,0 +1,50 @@ +//! USB Audio Terminal Types from Universal Serial Bus Device Class Definition +//! for Terminal Types, Release 1.0 + +/// USB Audio Terminal Types from "Universal Serial Bus Device Class Definition +/// for Terminal Types, Release 1.0" +#[repr(u16)] +#[non_exhaustive] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[allow(missing_docs)] +pub enum TerminalType { + // USB Terminal Types + UsbUndefined = 0x0100, + UsbStreaming = 0x0101, + UsbVendor = 0x01ff, + + // Input Terminal Types + InUndefined = 0x0200, + InMicrophone = 0x0201, + InDesktopMicrophone = 0x0202, + InPersonalMicrophone = 0x0203, + InOmniDirectionalMicrophone = 0x0204, + InMicrophoneArray = 0x0205, + InProcessingMicrophoneArray = 0x0206, + + // Output Terminal Types + OutUndefined = 0x0300, + OutSpeaker = 0x0301, + OutHeadphones = 0x0302, + OutHeadMountedDisplayAudio = 0x0303, + OutDesktopSpeaker = 0x0304, + OutRoomSpeaker = 0x0305, + OutCommunicationSpeaker = 0x0306, + OutLowFrequencyEffectsSpeaker = 0x0307, + + // External Terminal Types + ExtUndefined = 0x0600, + ExtAnalogConnector = 0x0601, + ExtDigitalAudioInterface = 0x0602, + ExtLineConnector = 0x0603, + ExtLegacyAudioConnector = 0x0604, + ExtSpdifConnector = 0x0605, + Ext1394DaStream = 0x0606, + Ext1394DvStreamSoundtrack = 0x0607, +} + +impl From for u16 { + fn from(t: TerminalType) -> u16 { + t as u16 + } +} diff --git a/embassy/embassy-usb/src/class/web_usb.rs b/embassy/embassy-usb/src/class/web_usb.rs new file mode 100644 index 0000000..10ebf31 --- /dev/null +++ b/embassy/embassy-usb/src/class/web_usb.rs @@ -0,0 +1,186 @@ +//! WebUSB API capability implementation. +//! +//! See https://wicg.github.io/webusb + +use core::mem::MaybeUninit; + +use crate::control::{InResponse, Recipient, Request, RequestType}; +use crate::descriptor::capability_type; +use crate::driver::Driver; +use crate::{Builder, Handler}; + +const USB_CLASS_VENDOR: u8 = 0xff; +const USB_SUBCLASS_NONE: u8 = 0x00; +const USB_PROTOCOL_NONE: u8 = 0x00; + +const WEB_USB_REQUEST_GET_URL: u16 = 0x02; +const WEB_USB_DESCRIPTOR_TYPE_URL: u8 = 0x03; + +/// URL descriptor for WebUSB landing page. +/// +/// An ecoded URL descriptor to point to a website that is suggested to the user when the device is connected. +pub struct Url<'d>(&'d str, u8); + +impl<'d> Url<'d> { + /// Create a new WebUSB URL descriptor. + pub fn new(url: &'d str) -> Self { + let (prefix, stripped_url) = if let Some(stripped) = url.strip_prefix("https://") { + (1, stripped) + } else if let Some(stripped) = url.strip_prefix("http://") { + (0, stripped) + } else { + (255, url) + }; + assert!( + stripped_url.len() <= 252, + "URL too long. ({} bytes). Maximum length is 252 bytes.", + stripped_url.len() + ); + Self(stripped_url, prefix) + } + + fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } + + fn scheme(&self) -> u8 { + self.1 + } +} + +/// Configuration for WebUSB. +pub struct Config<'d> { + /// Maximum packet size in bytes for the data endpoints. + /// + /// Valid values depend on the speed at which the bus is enumerated. + /// - low speed: 8 + /// - full speed: 8, 16, 32, or 64 + /// - high speed: 64 + pub max_packet_size: u16, + /// URL to navigate to when the device is connected. + /// + /// If defined, shows a landing page which the device manufacturer would like the user to visit in order to control their device. + pub landing_url: Option>, + /// Vendor code for the WebUSB request. + /// + /// This value defines the request id (bRequest) the device expects the host to use when issuing control transfers these requests. This can be an arbitrary u8 and is not to be confused with the USB Vendor ID. + pub vendor_code: u8, +} + +struct Control<'d> { + ep_buf: [u8; 128], + vendor_code: u8, + landing_url: Option<&'d Url<'d>>, +} + +impl<'d> Control<'d> { + fn new(config: &'d Config<'d>) -> Self { + Control { + ep_buf: [0u8; 128], + vendor_code: config.vendor_code, + landing_url: config.landing_url.as_ref(), + } + } +} + +impl<'d> Handler for Control<'d> { + fn control_in(&mut self, req: Request, _data: &mut [u8]) -> Option { + let landing_value = if self.landing_url.is_some() { 1 } else { 0 }; + if req.request_type == RequestType::Vendor + && req.recipient == Recipient::Device + && req.request == self.vendor_code + && req.value == landing_value + && req.index == WEB_USB_REQUEST_GET_URL + { + if let Some(url) = self.landing_url { + let url_bytes = url.as_bytes(); + let len = url_bytes.len(); + + self.ep_buf[0] = len as u8 + 3; + self.ep_buf[1] = WEB_USB_DESCRIPTOR_TYPE_URL; + self.ep_buf[2] = url.scheme(); + self.ep_buf[3..3 + len].copy_from_slice(url_bytes); + + return Some(InResponse::Accepted(&self.ep_buf[..3 + len])); + } + } + None + } +} + +/// Internal state for WebUSB +pub struct State<'d> { + control: MaybeUninit>, +} + +impl<'d> Default for State<'d> { + fn default() -> Self { + Self::new() + } +} + +impl<'d> State<'d> { + /// Create a new `State`. + pub const fn new() -> Self { + State { + control: MaybeUninit::uninit(), + } + } +} + +/// WebUSB capability implementation. +/// +/// WebUSB is a W3C standard that allows a web page to communicate with USB devices. +/// See See https://wicg.github.io/webusb for more information and the browser API. +/// This implementation provides one read and one write endpoint. +pub struct WebUsb<'d, D: Driver<'d>> { + _driver: core::marker::PhantomData<&'d D>, +} + +impl<'d, D: Driver<'d>> WebUsb<'d, D> { + /// Builder for the WebUSB capability implementation. + /// + /// Pass in a USB `Builder`, a `State`, which holds the the control endpoint state, and a `Config` for the WebUSB configuration. + pub fn configure(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: &'d Config<'d>) { + let mut func = builder.function(USB_CLASS_VENDOR, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); + let mut iface = func.interface(); + let mut alt = iface.alt_setting(USB_CLASS_VENDOR, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None); + + alt.bos_capability( + capability_type::PLATFORM, + &[ + // PlatformCapabilityUUID (3408b638-09a9-47a0-8bfd-a0768815b665) + 0x0, + 0x38, + 0xb6, + 0x08, + 0x34, + 0xa9, + 0x09, + 0xa0, + 0x47, + 0x8b, + 0xfd, + 0xa0, + 0x76, + 0x88, + 0x15, + 0xb6, + 0x65, + // bcdVersion of WebUSB (1.0) + 0x00, + 0x01, + // bVendorCode + config.vendor_code, + // iLandingPage + if config.landing_url.is_some() { 1 } else { 0 }, + ], + ); + + let control = state.control.write(Control::new(config)); + + drop(func); + + builder.handler(control); + } +} diff --git a/embassy/embassy-usb/src/control.rs b/embassy/embassy-usb/src/control.rs new file mode 100644 index 0000000..79f7363 --- /dev/null +++ b/embassy/embassy-usb/src/control.rs @@ -0,0 +1,146 @@ +//! USB control data types. +use core::mem; + +use crate::driver::Direction; + +/// Control request type. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RequestType { + /// Request is a USB standard request. Usually handled by + /// [`UsbDevice`](crate::UsbDevice). + Standard = 0, + /// Request is intended for a USB class. + Class = 1, + /// Request is vendor-specific. + Vendor = 2, + /// Reserved. + Reserved = 3, +} + +/// Control request recipient. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Recipient { + /// Request is intended for the entire device. + Device = 0, + /// Request is intended for an interface. Generally, the `index` field of the request specifies + /// the interface number. + Interface = 1, + /// Request is intended for an endpoint. Generally, the `index` field of the request specifies + /// the endpoint address. + Endpoint = 2, + /// None of the above. + Other = 3, + /// Reserved. + Reserved = 4, +} + +/// A control request read from a SETUP packet. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Request { + /// Direction of the request. + pub direction: Direction, + /// Type of the request. + pub request_type: RequestType, + /// Recipient of the request. + pub recipient: Recipient, + /// Request code. The meaning of the value depends on the previous fields. + pub request: u8, + /// Request value. The meaning of the value depends on the previous fields. + pub value: u16, + /// Request index. The meaning of the value depends on the previous fields. + pub index: u16, + /// Length of the DATA stage. For control OUT transfers this is the exact length of the data the + /// host sent. For control IN transfers this is the maximum length of data the device should + /// return. + pub length: u16, +} + +impl Request { + /// Standard USB control request Get Status + pub const GET_STATUS: u8 = 0; + + /// Standard USB control request Clear Feature + pub const CLEAR_FEATURE: u8 = 1; + + /// Standard USB control request Set Feature + pub const SET_FEATURE: u8 = 3; + + /// Standard USB control request Set Address + pub const SET_ADDRESS: u8 = 5; + + /// Standard USB control request Get Descriptor + pub const GET_DESCRIPTOR: u8 = 6; + + /// Standard USB control request Set Descriptor + pub const SET_DESCRIPTOR: u8 = 7; + + /// Standard USB control request Get Configuration + pub const GET_CONFIGURATION: u8 = 8; + + /// Standard USB control request Set Configuration + pub const SET_CONFIGURATION: u8 = 9; + + /// Standard USB control request Get Interface + pub const GET_INTERFACE: u8 = 10; + + /// Standard USB control request Set Interface + pub const SET_INTERFACE: u8 = 11; + + /// Standard USB control request Synch Frame + pub const SYNCH_FRAME: u8 = 12; + + /// Standard USB feature Endpoint Halt for Set/Clear Feature + pub const FEATURE_ENDPOINT_HALT: u16 = 0; + + /// Standard USB feature Device Remote Wakeup for Set/Clear Feature + pub const FEATURE_DEVICE_REMOTE_WAKEUP: u16 = 1; + + /// Parses a USB control request from a byte array. + pub fn parse(buf: &[u8; 8]) -> Request { + let rt = buf[0]; + let recipient = rt & 0b11111; + + Request { + direction: if rt & 0x80 == 0 { Direction::Out } else { Direction::In }, + request_type: unsafe { mem::transmute((rt >> 5) & 0b11) }, + recipient: if recipient <= 3 { + unsafe { mem::transmute(recipient) } + } else { + Recipient::Reserved + }, + request: buf[1], + value: (buf[2] as u16) | ((buf[3] as u16) << 8), + index: (buf[4] as u16) | ((buf[5] as u16) << 8), + length: (buf[6] as u16) | ((buf[7] as u16) << 8), + } + } + + /// Gets the descriptor type and index from the value field of a GET_DESCRIPTOR request. + pub const fn descriptor_type_index(&self) -> (u8, u8) { + ((self.value >> 8) as u8, self.value as u8) + } +} + +/// 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, +} diff --git a/embassy/embassy-usb/src/descriptor.rs b/embassy/embassy-usb/src/descriptor.rs new file mode 100644 index 0000000..e9a6fd7 --- /dev/null +++ b/embassy/embassy-usb/src/descriptor.rs @@ -0,0 +1,434 @@ +//! Utilities for writing USB descriptors. +use embassy_usb_driver::EndpointType; + +use crate::builder::Config; +use crate::driver::EndpointInfo; +use crate::types::{InterfaceNumber, StringIndex}; +use crate::CONFIGURATION_VALUE; + +/// Standard descriptor types +#[allow(missing_docs)] +pub mod descriptor_type { + pub const DEVICE: u8 = 1; + pub const CONFIGURATION: u8 = 2; + pub const STRING: u8 = 3; + pub const INTERFACE: u8 = 4; + pub const ENDPOINT: u8 = 5; + pub const DEVICE_QUALIFIER: u8 = 6; + pub const OTHER_SPEED_CONFIGURATION: u8 = 7; + pub const IAD: u8 = 11; + pub const BOS: u8 = 15; + pub const CAPABILITY: u8 = 16; +} + +/// String descriptor language IDs. +pub mod lang_id { + /// English (US) + /// + /// Recommended for use as the first language ID for compatibility. + pub const ENGLISH_US: u16 = 0x0409; +} + +/// Standard capability descriptor types +#[allow(missing_docs)] +pub mod capability_type { + pub const WIRELESS_USB: u8 = 1; + pub const USB_2_0_EXTENSION: u8 = 2; + pub const SS_USB_DEVICE: u8 = 3; + pub const CONTAINER_ID: u8 = 4; + pub const PLATFORM: u8 = 5; +} + +/// USB endpoint synchronization type. The values of this enum can be directly +/// cast into `u8` to get the bmAttributes synchronization type bits. +/// Values other than `NoSynchronization` are only allowed on isochronous endpoints. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SynchronizationType { + /// No synchronization is used. + NoSynchronization = 0b00, + /// Unsynchronized, although sinks provide data rate feedback. + Asynchronous = 0b01, + /// Synchronized using feedback or feedforward data rate information. + Adaptive = 0b10, + /// Synchronized to the USB’s SOF. + Synchronous = 0b11, +} + +/// USB endpoint usage type. The values of this enum can be directly cast into +/// `u8` to get the bmAttributes usage type bits. +/// Values other than `DataEndpoint` are only allowed on isochronous endpoints. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum UsageType { + /// Use the endpoint for regular data transfer. + DataEndpoint = 0b00, + /// Endpoint conveys explicit feedback information for one or more data endpoints. + FeedbackEndpoint = 0b01, + /// A data endpoint that also serves as an implicit feedback endpoint for one or more data endpoints. + ImplicitFeedbackDataEndpoint = 0b10, + /// Reserved usage type. + Reserved = 0b11, +} + +/// A writer for USB descriptors. +pub(crate) struct DescriptorWriter<'a> { + pub buf: &'a mut [u8], + position: usize, + num_interfaces_mark: Option, + num_endpoints_mark: Option, +} + +impl<'a> DescriptorWriter<'a> { + pub(crate) fn new(buf: &'a mut [u8]) -> Self { + DescriptorWriter { + buf, + position: 0, + num_interfaces_mark: None, + num_endpoints_mark: None, + } + } + + pub fn into_buf(self) -> &'a mut [u8] { + &mut self.buf[..self.position] + } + + /// Gets the current position in the buffer, i.e. the number of bytes written so far. + pub const fn position(&self) -> usize { + self.position + } + + /// Writes an arbitrary (usually class-specific) descriptor with optional extra fields. + pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8], extra_fields: &[u8]) { + let descriptor_length = descriptor.len(); + let extra_fields_length = extra_fields.len(); + let total_length = descriptor_length + extra_fields_length; + + assert!( + (self.position + 2 + total_length) <= self.buf.len() && (total_length + 2) <= 255, + "Descriptor buffer full" + ); + + self.buf[self.position] = (total_length + 2) as u8; + self.buf[self.position + 1] = descriptor_type; + + let start = self.position + 2; + + self.buf[start..start + descriptor_length].copy_from_slice(descriptor); + self.buf[start + descriptor_length..start + total_length].copy_from_slice(extra_fields); + + self.position = start + total_length; + } + + pub(crate) fn configuration(&mut self, config: &Config) { + self.num_interfaces_mark = Some(self.position + 4); + + self.write( + descriptor_type::CONFIGURATION, + &[ + 0, + 0, // wTotalLength + 0, // bNumInterfaces + CONFIGURATION_VALUE, // bConfigurationValue + 0, // iConfiguration + 0x80 | if config.self_powered { 0x40 } else { 0x00 } + | if config.supports_remote_wakeup { 0x20 } else { 0x00 }, // bmAttributes + (config.max_power / 2) as u8, // bMaxPower + ], + &[], + ); + } + + #[allow(unused)] + pub(crate) fn end_class(&mut self) { + self.num_endpoints_mark = None; + } + + pub(crate) fn end_configuration(&mut self) { + let position = self.position as u16; + self.buf[2..4].copy_from_slice(&position.to_le_bytes()); + } + + /// Writes a interface association descriptor. Call from `UsbClass::get_configuration_descriptors` + /// before writing the USB class or function's interface descriptors if your class has more than + /// one interface and wants to play nicely with composite devices on Windows. If the USB device + /// hosting the class was not configured as composite with IADs enabled, calling this function + /// does nothing, so it is safe to call from libraries. + /// + /// # Arguments + /// + /// * `first_interface` - Number of the function's first interface, previously allocated with + /// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::interface). + /// * `interface_count` - Number of interfaces in the function. + /// * `function_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices + /// that do not conform to any class. + /// * `function_sub_class` - Sub-class code. Depends on class. + /// * `function_protocol` - Protocol code. Depends on class and sub-class. + pub fn iad( + &mut self, + first_interface: InterfaceNumber, + interface_count: u8, + function_class: u8, + function_sub_class: u8, + function_protocol: u8, + ) { + self.write( + descriptor_type::IAD, + &[ + first_interface.into(), // bFirstInterface + interface_count, // bInterfaceCount + function_class, + function_sub_class, + function_protocol, + 0, + ], + &[], + ); + } + + /// Writes a interface descriptor with a specific alternate setting and + /// interface string identifier. + /// + /// # Arguments + /// + /// * `number` - Interface number previously allocated with + /// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::interface). + /// * `alternate_setting` - Number of the alternate setting + /// * `interface_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices + /// that do not conform to any class. + /// * `interface_sub_class` - Sub-class code. Depends on class. + /// * `interface_protocol` - Protocol code. Depends on class and sub-class. + /// * `interface_string` - Index of string descriptor describing this interface + + pub fn interface_alt( + &mut self, + number: InterfaceNumber, + alternate_setting: u8, + interface_class: u8, + interface_sub_class: u8, + interface_protocol: u8, + interface_string: Option, + ) { + if alternate_setting == 0 { + match self.num_interfaces_mark { + Some(mark) => self.buf[mark] += 1, + None => { + panic!("you can only call `interface/interface_alt` after `configuration`.") + } + }; + } + + let str_index = interface_string.map_or(0, Into::into); + + self.num_endpoints_mark = Some(self.position + 4); + + self.write( + descriptor_type::INTERFACE, + &[ + number.into(), // bInterfaceNumber + alternate_setting, // bAlternateSetting + 0, // bNumEndpoints + interface_class, // bInterfaceClass + interface_sub_class, // bInterfaceSubClass + interface_protocol, // bInterfaceProtocol + str_index, // iInterface + ], + &[], + ); + } + + /// Writes an endpoint descriptor. + /// + /// # Arguments + /// + /// * `endpoint` - Endpoint previously allocated with + /// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder). + /// * `synchronization_type` - The synchronization type of the endpoint. + /// * `usage_type` - The usage type of the endpoint. + /// * `extra_fields` - Additional, class-specific entries at the end of the endpoint descriptor. + pub fn endpoint( + &mut self, + endpoint: &EndpointInfo, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) { + match self.num_endpoints_mark { + Some(mark) => self.buf[mark] += 1, + None => panic!("you can only call `endpoint` after `interface/interface_alt`."), + }; + + let mut bm_attributes = endpoint.ep_type as u8; + + // Synchronization types other than `NoSynchronization`, + // and usage types other than `DataEndpoint` + // are only allowed for isochronous endpoints. + if endpoint.ep_type != EndpointType::Isochronous { + assert_eq!(synchronization_type, SynchronizationType::NoSynchronization); + assert_eq!(usage_type, UsageType::DataEndpoint); + } else { + if usage_type == UsageType::FeedbackEndpoint { + assert_eq!(synchronization_type, SynchronizationType::NoSynchronization) + } + + let synchronization_bm_attibutes: u8 = (synchronization_type as u8) << 2; + let usage_bm_attibutes: u8 = (usage_type as u8) << 4; + + bm_attributes |= usage_bm_attibutes | synchronization_bm_attibutes; + } + + self.write( + descriptor_type::ENDPOINT, + &[ + endpoint.addr.into(), // bEndpointAddress + bm_attributes, // bmAttributes + endpoint.max_packet_size as u8, + (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize + endpoint.interval_ms, // bInterval + ], + extra_fields, + ); + } + + /// Writes a string descriptor. + #[allow(unused)] + pub(crate) fn string(&mut self, string: &str) { + let mut pos = self.position; + + assert!(pos + 2 <= self.buf.len(), "Descriptor buffer full"); + + self.buf[pos] = 0; // length placeholder + self.buf[pos + 1] = descriptor_type::STRING; + + pos += 2; + + for c in string.encode_utf16() { + assert!(pos < self.buf.len(), "Descriptor buffer full"); + + self.buf[pos..pos + 2].copy_from_slice(&c.to_le_bytes()); + pos += 2; + } + + self.buf[self.position] = (pos - self.position) as u8; + + self.position = pos; + } +} + +/// Create a new Device Descriptor array. +/// +/// All device descriptors are always 18 bytes, so there's no need for +/// a variable-length buffer or DescriptorWriter. +pub(crate) fn device_descriptor(config: &Config) -> [u8; 18] { + [ + 18, // bLength + 0x01, // bDescriptorType + config.bcd_usb as u8, + (config.bcd_usb as u16 >> 8) as u8, // bcdUSB + config.device_class, // bDeviceClass + config.device_sub_class, // bDeviceSubClass + config.device_protocol, // bDeviceProtocol + config.max_packet_size_0, // bMaxPacketSize0 + config.vendor_id as u8, + (config.vendor_id >> 8) as u8, // idVendor + config.product_id as u8, + (config.product_id >> 8) as u8, // idProduct + config.device_release as u8, + (config.device_release >> 8) as u8, // bcdDevice + config.manufacturer.map_or(0, |_| 1), // iManufacturer + config.product.map_or(0, |_| 2), // iProduct + config.serial_number.map_or(0, |_| 3), // iSerialNumber + 1, // bNumConfigurations + ] +} + +/// Create a new Device Qualifier Descriptor array. +/// +/// All device qualifier descriptors are always 10 bytes, so there's no need for +/// a variable-length buffer or DescriptorWriter. +pub(crate) fn device_qualifier_descriptor(config: &Config) -> [u8; 10] { + [ + 10, // bLength + 0x06, // bDescriptorType + config.bcd_usb as u8, + (config.bcd_usb as u16 >> 8) as u8, // bcdUSB + config.device_class, // bDeviceClass + config.device_sub_class, // bDeviceSubClass + config.device_protocol, // bDeviceProtocol + config.max_packet_size_0, // bMaxPacketSize0 + 1, // bNumConfigurations + 0, // Reserved + ] +} + +/// A writer for Binary Object Store descriptor. +pub struct BosWriter<'a> { + pub(crate) writer: DescriptorWriter<'a>, + num_caps_mark: Option, +} + +impl<'a> BosWriter<'a> { + pub(crate) const fn new(writer: DescriptorWriter<'a>) -> Self { + Self { + writer, + num_caps_mark: None, + } + } + + pub(crate) fn bos(&mut self) { + if (self.writer.buf.len() - self.writer.position) < 5 { + return; + } + self.num_caps_mark = Some(self.writer.position + 4); + self.writer.write( + descriptor_type::BOS, + &[ + 0x00, 0x00, // wTotalLength + 0x00, // bNumDeviceCaps + ], + &[], + ); + + self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4]); + } + + /// Writes capability descriptor to a BOS + /// + /// # Arguments + /// + /// * `capability_type` - Type of a capability + /// * `data` - Binary data of the descriptor + pub fn capability(&mut self, capability_type: u8, data: &[u8]) { + match self.num_caps_mark { + Some(mark) => self.writer.buf[mark] += 1, + None => panic!("called `capability` not between `bos` and `end_bos`."), + } + + let mut start = self.writer.position; + let blen = data.len(); + + assert!( + (start + blen + 3) <= self.writer.buf.len() && (blen + 3) <= 255, + "Descriptor buffer full" + ); + + self.writer.buf[start] = (blen + 3) as u8; + self.writer.buf[start + 1] = descriptor_type::CAPABILITY; + self.writer.buf[start + 2] = capability_type; + + start += 3; + self.writer.buf[start..start + blen].copy_from_slice(data); + self.writer.position = start + blen; + } + + pub(crate) fn end_bos(&mut self) { + if self.writer.position == 0 { + return; + } + self.num_caps_mark = None; + let position = self.writer.position as u16; + self.writer.buf[2..4].copy_from_slice(&position.to_le_bytes()); + } +} diff --git a/embassy/embassy-usb/src/descriptor_reader.rs b/embassy/embassy-usb/src/descriptor_reader.rs new file mode 100644 index 0000000..abb4b37 --- /dev/null +++ b/embassy/embassy-usb/src/descriptor_reader.rs @@ -0,0 +1,111 @@ +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))] +pub struct ReadError; + +pub struct Reader<'a> { + data: &'a [u8], +} + +impl<'a> Reader<'a> { + pub const fn new(data: &'a [u8]) -> Self { + Self { data } + } + + pub const fn eof(&self) -> bool { + self.data.is_empty() + } + + pub fn read(&mut self) -> Result<[u8; N], ReadError> { + let n = self.data.get(0..N).ok_or(ReadError)?; + self.data = &self.data[N..]; + Ok(n.try_into().unwrap()) + } + + pub fn read_u8(&mut self) -> Result { + Ok(u8::from_le_bytes(self.read()?)) + } + pub fn read_u16(&mut self) -> Result { + Ok(u16::from_le_bytes(self.read()?)) + } + + pub fn read_slice(&mut self, len: usize) -> Result<&'a [u8], ReadError> { + let res = self.data.get(0..len).ok_or(ReadError)?; + self.data = &self.data[len..]; + Ok(res) + } + + pub fn read_descriptors(&mut self) -> DescriptorIter<'_, 'a> { + DescriptorIter { r: self } + } +} + +pub struct DescriptorIter<'a, 'b> { + r: &'a mut Reader<'b>, +} + +impl<'a, 'b> Iterator for DescriptorIter<'a, 'b> { + type Item = Result<(u8, Reader<'a>), ReadError>; + + fn next(&mut self) -> Option { + if self.r.eof() { + return None; + } + + let len = match self.r.read_u8() { + Ok(x) => x, + Err(e) => return Some(Err(e)), + }; + let type_ = match self.r.read_u8() { + Ok(x) => x, + Err(e) => return Some(Err(e)), + }; + let data = match self.r.read_slice(len as usize - 2) { + Ok(x) => x, + Err(e) => return Some(Err(e)), + }; + + Some(Ok((type_, Reader::new(data)))) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointInfo { + pub configuration: u8, + pub interface: InterfaceNumber, + pub interface_alt: u8, + pub ep_address: EndpointAddress, +} + +pub fn foreach_endpoint(data: &[u8], mut f: impl FnMut(EndpointInfo)) -> Result<(), ReadError> { + let mut ep = EndpointInfo { + configuration: 0, + interface: InterfaceNumber(0), + interface_alt: 0, + ep_address: EndpointAddress::from(0), + }; + for res in Reader::new(data).read_descriptors() { + let (kind, mut r) = res?; + match kind { + descriptor_type::CONFIGURATION => { + let _total_length = r.read_u16()?; + let _total_length = r.read_u8()?; + ep.configuration = r.read_u8()?; + } + descriptor_type::INTERFACE => { + ep.interface = InterfaceNumber(r.read_u8()?); + ep.interface_alt = r.read_u8()?; + } + descriptor_type::ENDPOINT => { + ep.ep_address = EndpointAddress::from(r.read_u8()?); + f(ep); + } + _ => {} + } + } + Ok(()) +} diff --git a/embassy/embassy-usb/src/fmt.rs b/embassy/embassy-usb/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-usb/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-usb/src/lib.rs b/embassy/embassy-usb/src/lib.rs new file mode 100644 index 0000000..93dd6b4 --- /dev/null +++ b/embassy/embassy-usb/src/lib.rs @@ -0,0 +1,786 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +pub use embassy_usb_driver as driver; + +mod builder; +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, FunctionBuilder, InterfaceAltBuilder, InterfaceBuilder, UsbVersion}; +use crate::config::{MAX_HANDLER_COUNT, MAX_INTERFACE_COUNT}; +use crate::control::{InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::descriptor::{descriptor_type, lang_id}; +use crate::descriptor_reader::foreach_endpoint; +use crate::driver::{Bus, ControlPipe, Direction, Driver, EndpointAddress, Event}; +use crate::types::{InterfaceNumber, StringIndex}; + +/// The global state of the USB device. +/// +/// In general class traffic is only possible in the `Configured` state. +#[repr(u8)] +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum UsbDeviceState { + /// The USB device has no power. + Unpowered, + + /// The USB device is disabled. + Disabled, + + /// The USB device has just been enabled or reset. + Default, + + /// The USB device has received an address from the host. + Addressed, + + /// The USB device has been configured and is fully functional. + 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, +} + +impl From for RemoteWakeupError { + fn from(_: driver::Unsupported) -> Self { + RemoteWakeupError::Unsupported + } +} + +/// The bConfiguration value for the not configured state. +pub const CONFIGURATION_NONE: u8 = 0; + +/// The bConfiguration value for the single configuration supported by this device. +pub const CONFIGURATION_VALUE: u8 = 1; + +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; + +/// 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(&mut self, _enabled: bool) {} + + /// 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(&mut self, _addr: u8) {} + + /// Called when the host has enabled or disabled the configuration of the device. + fn configured(&mut self, _configured: bool) {} + + /// Called when the bus has entered or exited the suspend state. + fn suspended(&mut self, _suspended: bool) {} + + /// Called when remote wakeup feature is enabled or disabled. + 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 { + current_alt_setting: u8, + num_alt_settings: u8, +} + +/// A report of the used size of the runtime allocated buffers +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UsbBufferReport { + /// Number of config descriptor bytes used + pub config_descriptor_used: usize, + /// Number of bos descriptor bytes used + pub bos_descriptor_used: usize, + /// Number of msos descriptor bytes used + pub msos_descriptor_used: usize, + /// Size of the control buffer + pub control_buffer_size: usize, +} + +/// Main struct for the USB device stack. +pub struct UsbDevice<'d, D: Driver<'d>> { + control_buf: &'d mut [u8], + control: D::ControlPipe, + inner: Inner<'d, D>, +} + +struct Inner<'d, D: Driver<'d>> { + bus: D::Bus, + + config: Config<'d>, + device_descriptor: [u8; 18], + device_qualifier_descriptor: [u8; 10], + config_descriptor: &'d [u8], + bos_descriptor: &'d [u8], + msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, + + device_state: UsbDeviceState, + suspended: bool, + remote_wakeup_enabled: bool, + self_powered: bool, + + /// Our device address, or 0 if none. + address: u8, + /// 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, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, +} + +impl<'d, D: Driver<'d>> UsbDevice<'d, D> { + pub(crate) fn build( + driver: D, + config: Config<'d>, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, + config_descriptor: &'d [u8], + bos_descriptor: &'d [u8], + msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, + interfaces: Vec, + control_buf: &'d mut [u8], + ) -> UsbDevice<'d, D> { + // Start the USB bus. + // This prevent further allocation by consuming the driver. + let (bus, control) = driver.start(config.max_packet_size_0 as u16); + let device_descriptor = descriptor::device_descriptor(&config); + let device_qualifier_descriptor = descriptor::device_qualifier_descriptor(&config); + + Self { + control_buf, + control, + inner: Inner { + bus, + config, + device_descriptor, + device_qualifier_descriptor, + config_descriptor, + bos_descriptor, + msos_descriptor, + + device_state: UsbDeviceState::Unpowered, + suspended: false, + remote_wakeup_enabled: false, + self_powered: false, + address: 0, + set_address_pending: false, + interfaces, + handlers, + }, + } + } + + /// Returns a report of the consumed buffers + /// + /// Useful for tuning buffer sizes for actual usage + pub fn buffer_usage(&self) -> UsbBufferReport { + UsbBufferReport { + config_descriptor_used: self.inner.config_descriptor.len(), + bos_descriptor_used: self.inner.bos_descriptor.len(), + msos_descriptor_used: self.inner.msos_descriptor.len(), + control_buffer_size: self.control_buf.len(), + } + } + + /// Runs the `UsbDevice` forever. + /// + /// This future may leave the bus in an invalid state if it is dropped. + /// After dropping the future, [`UsbDevice::disable()`] should be called + /// before calling any other `UsbDevice` methods to fully reset the + /// peripheral. + pub async fn run(&mut self) -> ! { + loop { + self.run_until_suspend().await; + self.wait_resume().await; + } + } + + /// Runs the `UsbDevice` until the bus is suspended. + /// + /// This future may leave the bus in an invalid state if it is dropped. + /// After dropping the future, [`UsbDevice::disable()`] should be called + /// before calling any other `UsbDevice` methods to fully reset the + /// peripheral. + pub async fn run_until_suspend(&mut self) { + while !self.inner.suspended { + let control_fut = self.control.setup(); + let bus_fut = self.inner.bus.poll(); + match select(bus_fut, control_fut).await { + Either::First(evt) => self.inner.handle_bus_event(evt).await, + Either::Second(req) => self.handle_control(req).await, + } + } + } + + /// Disables the USB peripheral. + pub async fn disable(&mut self) { + if self.inner.device_state != UsbDeviceState::Disabled { + self.inner.bus.disable().await; + self.inner.device_state = UsbDeviceState::Disabled; + self.inner.suspended = false; + self.inner.remote_wakeup_enabled = false; + + for h in &mut self.inner.handlers { + h.enabled(false); + } + } + } + + /// Waits for a resume condition on the USB bus. + /// + /// This future is cancel-safe. + pub async fn wait_resume(&mut self) { + while self.inner.suspended { + let evt = self.inner.bus.poll().await; + self.inner.handle_bus_event(evt).await; + } + } + + /// Initiates a device remote wakeup on the USB bus. + /// + /// If the bus is not suspended or remote wakeup is not enabled, an error + /// will be returned. + /// + /// This future may leave the bus in an inconsistent state if dropped. + /// After dropping the future, [`UsbDevice::disable()`] should be called + /// before calling any other `UsbDevice` methods to fully reset the peripheral. + pub async fn remote_wakeup(&mut self) -> Result<(), RemoteWakeupError> { + if self.inner.suspended && self.inner.remote_wakeup_enabled { + self.inner.bus.remote_wakeup().await?; + self.inner.suspended = false; + + for h in &mut self.inner.handlers { + h.suspended(false); + } + + Ok(()) + } else { + Err(RemoteWakeupError::InvalidState) + } + } + + async fn handle_control(&mut self, req: [u8; 8]) { + let req = Request::parse(&req); + + trace!("control request: {:?}", req); + + match req.direction { + Direction::In => self.handle_control_in(req).await, + Direction::Out => self.handle_control_out(req).await, + } + } + + async fn handle_control_in(&mut self, req: Request) { + const DEVICE_DESCRIPTOR_LEN: usize = 18; + + let mut resp_length = req.length as usize; + let max_packet_size = self.control.max_packet_size(); + + // If we don't have an address yet, respond with max 1 packet. + // The host doesn't know our EP0 max packet size yet, and might assume + // a full-length packet is a short packet, thinking we're done sending data. + // See https://github.com/hathach/tinyusb/issues/184 + if self.inner.address == 0 && max_packet_size < DEVICE_DESCRIPTOR_LEN && max_packet_size < resp_length { + trace!("received control req while not addressed: capping response to 1 packet."); + resp_length = max_packet_size; + } + + match self.inner.handle_control_in(req, self.control_buf) { + InResponse::Accepted(data) => { + let len = data.len().min(resp_length); + let need_zlp = len != resp_length && (len % max_packet_size) == 0; + + let chunks = data[0..len] + .chunks(max_packet_size) + .chain(need_zlp.then(|| -> &[u8] { &[] })); + + for (first, last, chunk) in first_last(chunks) { + match self.control.data_in(chunk, first, last).await { + Ok(()) => {} + Err(e) => { + warn!("control accept_in failed: {:?}", e); + return; + } + } + } + } + InResponse::Rejected => self.control.reject().await, + } + } + + async fn handle_control_out(&mut self, req: Request) { + let req_length = req.length as usize; + let max_packet_size = self.control.max_packet_size(); + let mut total = 0; + + if req_length > self.control_buf.len() { + warn!( + "got CONTROL OUT with length {} higher than the control_buf len {}, rejecting.", + req_length, + self.control_buf.len() + ); + self.control.reject().await; + return; + } + + let chunks = self.control_buf[..req_length].chunks_mut(max_packet_size); + for (first, last, chunk) in first_last(chunks) { + let size = match self.control.data_out(chunk, first, last).await { + Ok(x) => x, + Err(e) => { + warn!("usb: failed to read CONTROL OUT data stage: {:?}", e); + return; + } + }; + total += size; + if size < max_packet_size || total == req_length { + break; + } + } + + let data = &self.control_buf[0..total]; + #[cfg(feature = "defmt")] + trace!(" control out data: {:02x}", data); + #[cfg(not(feature = "defmt"))] + trace!(" control out data: {:02x?}", data); + + match self.inner.handle_control_out(req, data) { + 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, + } + } +} + +impl<'d, D: Driver<'d>> Inner<'d, D> { + async fn handle_bus_event(&mut self, evt: Event) { + match evt { + Event::Reset => { + trace!("usb: reset"); + self.device_state = UsbDeviceState::Default; + self.suspended = false; + self.remote_wakeup_enabled = false; + self.address = 0; + + for h in &mut self.handlers { + 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; + for h in &mut self.handlers { + h.suspended(false); + } + } + Event::Suspend => { + trace!("usb: suspend"); + self.suspended = true; + for h in &mut self.handlers { + h.suspended(true); + } + } + Event::PowerDetected => { + trace!("usb: power detected"); + self.bus.enable().await; + self.device_state = UsbDeviceState::Default; + + for h in &mut self.handlers { + h.enabled(true); + } + } + Event::PowerRemoved => { + trace!("usb: power removed"); + self.bus.disable().await; + self.device_state = UsbDeviceState::Unpowered; + + for h in &mut self.handlers { + h.enabled(false); + } + } + } + } + + fn handle_control_out(&mut self, req: Request, data: &[u8]) -> OutResponse { + const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16; + const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16; + + match (req.request_type, req.recipient) { + (RequestType::Standard, Recipient::Device) => match (req.request, req.value) { + (Request::CLEAR_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { + self.remote_wakeup_enabled = false; + 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; + for h in &mut self.handlers { + h.remote_wakeup_enabled(true); + } + OutResponse::Accepted + } + (Request::SET_ADDRESS, addr @ 1..=127) => { + self.address = addr as u8; + self.set_address_pending = true; + self.device_state = UsbDeviceState::Addressed; + for h in &mut self.handlers { + h.addressed(self.address); + } + OutResponse::Accepted + } + (Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => { + debug!("SET_CONFIGURATION: configured"); + self.device_state = UsbDeviceState::Configured; + + // Enable all endpoints of selected alt settings. + foreach_endpoint(self.config_descriptor, |ep| { + 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 handlers. + for h in &mut self.handlers { + h.configured(true); + } + + OutResponse::Accepted + } + (Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => { + if self.device_state != UsbDeviceState::Default { + debug!("SET_CONFIGURATION: unconfigured"); + self.device_state = UsbDeviceState::Addressed; + + // Disable all endpoints. + foreach_endpoint(self.config_descriptor, |ep| { + self.bus.endpoint_set_enabled(ep.ep_address, false); + }) + .unwrap(); + + // Notify handlers. + for h in &mut self.handlers { + h.configured(false); + } + } + OutResponse::Accepted + } + _ => OutResponse::Rejected, + }, + (RequestType::Standard, Recipient::Interface) => { + let iface_num = InterfaceNumber::new(req.index as _); + let Some(iface) = self.interfaces.get_mut(iface_num.0 as usize) else { + return OutResponse::Rejected; + }; + + match req.request { + Request::SET_INTERFACE => { + let new_altsetting = req.value as u8; + + if new_altsetting >= iface.num_alt_settings { + warn!("SET_INTERFACE: trying to select alt setting out of range."); + return OutResponse::Rejected; + } + + iface.current_alt_setting = new_altsetting; + + // Enable/disable EPs of this interface as needed. + foreach_endpoint(self.config_descriptor, |ep| { + if ep.interface == iface_num { + self.bus + .endpoint_set_enabled(ep.ep_address, iface.current_alt_setting == ep.interface_alt); + } + }) + .unwrap(); + + // TODO check it is valid (not out of range) + + for h in &mut self.handlers { + h.set_alternate_setting(iface_num, new_altsetting); + } + OutResponse::Accepted + } + _ => OutResponse::Rejected, + } + } + (RequestType::Standard, Recipient::Endpoint) => match (req.request, req.value) { + (Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { + let ep_addr = ((req.index as u8) & 0x8f).into(); + self.bus.endpoint_set_stalled(ep_addr, true); + OutResponse::Accepted + } + (Request::CLEAR_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { + let ep_addr = ((req.index as u8) & 0x8f).into(); + self.bus.endpoint_set_stalled(ep_addr, false); + OutResponse::Accepted + } + _ => OutResponse::Rejected, + }, + _ => self.handle_control_out_delegated(req, data), + } + } + + fn handle_control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + match (req.request_type, req.recipient) { + (RequestType::Standard, Recipient::Device) => match req.request { + Request::GET_STATUS => { + let mut status: u16 = 0x0000; + if self.self_powered { + status |= 0x0001; + } + if self.remote_wakeup_enabled { + status |= 0x0002; + } + buf[..2].copy_from_slice(&status.to_le_bytes()); + InResponse::Accepted(&buf[..2]) + } + Request::GET_DESCRIPTOR => self.handle_get_descriptor(req, buf), + Request::GET_CONFIGURATION => { + let status = match self.device_state { + UsbDeviceState::Configured => CONFIGURATION_VALUE, + _ => CONFIGURATION_NONE, + }; + buf[0] = status; + InResponse::Accepted(&buf[..1]) + } + _ => InResponse::Rejected, + }, + (RequestType::Standard, Recipient::Interface) => { + let Some(iface) = self.interfaces.get_mut(req.index as usize) else { + return InResponse::Rejected; + }; + + match req.request { + Request::GET_STATUS => { + let status: u16 = 0; + buf[..2].copy_from_slice(&status.to_le_bytes()); + InResponse::Accepted(&buf[..2]) + } + Request::GET_INTERFACE => { + buf[0] = iface.current_alt_setting; + InResponse::Accepted(&buf[..1]) + } + _ => self.handle_control_in_delegated(req, buf), + } + } + (RequestType::Standard, Recipient::Endpoint) => match req.request { + Request::GET_STATUS => { + let ep_addr: EndpointAddress = ((req.index as u8) & 0x8f).into(); + let mut status: u16 = 0x0000; + if self.bus.endpoint_is_stalled(ep_addr) { + status |= 0x0001; + } + buf[..2].copy_from_slice(&status.to_le_bytes()); + InResponse::Accepted(&buf[..2]) + } + _ => InResponse::Rejected, + }, + + (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) + } + } + _ => 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<'y>(r: InResponse<'_>) -> 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(); + + match dtype { + descriptor_type::BOS => InResponse::Accepted(self.bos_descriptor), + descriptor_type::DEVICE => InResponse::Accepted(&self.device_descriptor), + descriptor_type::CONFIGURATION => InResponse::Accepted(self.config_descriptor), + descriptor_type::STRING => { + if index == 0 { + buf[0] = 4; // len + buf[1] = descriptor_type::STRING; + buf[2] = lang_id::ENGLISH_US as u8; + buf[3] = (lang_id::ENGLISH_US >> 8) as u8; + InResponse::Accepted(&buf[..4]) + } else { + let s = match index { + STRING_INDEX_MANUFACTURER => self.config.manufacturer, + STRING_INDEX_PRODUCT => self.config.product, + STRING_INDEX_SERIAL_NUMBER => self.config.serial_number, + _ => { + 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; + } + } + s + } + }; + + if let Some(s) = s { + assert!(buf.len() >= 2, "control buffer too small"); + + buf[1] = descriptor_type::STRING; + let mut pos = 2; + for c in s.encode_utf16() { + assert!(pos + 2 < buf.len(), "control buffer too small"); + + buf[pos..pos + 2].copy_from_slice(&c.to_le_bytes()); + pos += 2; + } + + buf[0] = pos as u8; + InResponse::Accepted(&buf[..pos]) + } else { + InResponse::Rejected + } + } + } + descriptor_type::DEVICE_QUALIFIER => InResponse::Accepted(&self.device_qualifier_descriptor), + _ => InResponse::Rejected, + } + } +} + +fn first_last(iter: T) -> impl Iterator { + let mut iter = iter.peekable(); + let mut first = true; + core::iter::from_fn(move || { + let val = iter.next()?; + let is_first = first; + first = false; + let is_last = iter.peek().is_none(); + Some((is_first, is_last, val)) + }) +} diff --git a/embassy/embassy-usb/src/msos.rs b/embassy/embassy-usb/src/msos.rs new file mode 100644 index 0000000..9f4e1a5 --- /dev/null +++ b/embassy/embassy-usb/src/msos.rs @@ -0,0 +1,728 @@ +//! Microsoft OS Descriptors +//! +//! + +use core::mem::size_of; + +use crate::descriptor::{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() + } + + /// Returns the length of the descriptor field + pub fn len(&self) -> usize { + self.descriptor.len() + } +} + +/// 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 compatibility + 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; +} + +/// A trait for descriptors +trait Descriptor: Sized { + const TYPE: DescriptorType; + + /// The size of the descriptor's header. + fn size(&self) -> usize { + size_of::() + } + + fn write_to(&self, buf: &mut [u8]); +} + +trait DescriptorSet: Descriptor { + const LENGTH_OFFSET: usize; +} + +/// 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"); + 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)] +#[allow(unused)] +#[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)] +#[allow(unused)] +#[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. +#[allow(private_bounds)] +pub trait DeviceLevelDescriptor: Descriptor {} + +/// A marker trait for feature descriptors that are valid at the function level. +#[allow(private_bounds)] +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]; + cid[..compatible_id.len()].copy_from_slice(compatible_id.as_bytes()); + let mut scid = [0u8; 8]; + 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/embassy-usb/src/types.rs b/embassy/embassy-usb/src/types.rs new file mode 100644 index 0000000..cb9fe25 --- /dev/null +++ b/embassy/embassy-usb/src/types.rs @@ -0,0 +1,37 @@ +//! USB types. + +/// A handle for a USB interface that contains its number. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(transparent)] +pub struct InterfaceNumber(pub u8); + +impl InterfaceNumber { + pub(crate) const fn new(index: u8) -> InterfaceNumber { + InterfaceNumber(index) + } +} + +impl From for u8 { + fn from(n: InterfaceNumber) -> u8 { + n.0 + } +} + +/// A handle for a USB string descriptor that contains its index. +#[derive(Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(transparent)] +pub struct StringIndex(pub u8); + +impl StringIndex { + pub(crate) const fn new(index: u8) -> StringIndex { + StringIndex(index) + } +} + +impl From for u8 { + fn from(i: StringIndex) -> u8 { + i.0 + } +} diff --git a/embassy/examples/.cargo/config.toml b/embassy/examples/.cargo/config.toml new file mode 100644 index 0000000..84d2663 --- /dev/null +++ b/embassy/examples/.cargo/config.toml @@ -0,0 +1,3 @@ +[profile.release] +# Allows defmt to display log locations even in release +debug = true \ No newline at end of file diff --git a/embassy/examples/boot/.cargo/config.toml b/embassy/examples/boot/.cargo/config.toml new file mode 100644 index 0000000..be1b73e --- /dev/null +++ b/embassy/examples/boot/.cargo/config.toml @@ -0,0 +1,9 @@ +[unstable] +#build-std = ["core"] +#build-std-features = ["panic_immediate_abort"] + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/application/nrf/.cargo/config.toml b/embassy/examples/boot/application/nrf/.cargo/config.toml new file mode 100644 index 0000000..17616a0 --- /dev/null +++ b/embassy/examples/boot/application/nrf/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF52840_xxAA" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/application/nrf/Cargo.toml b/embassy/examples/boot/application/nrf/Cargo.toml new file mode 100644 index 0000000..45ad341 --- /dev/null +++ b/embassy/examples/boot/application/nrf/Cargo.toml @@ -0,0 +1,34 @@ +[package] +edition = "2021" +name = "embassy-boot-nrf-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } +embassy-nrf = { version = "0.2.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", ] } +embassy-boot = { version = "0.3.0", path = "../../../../embassy-boot", features = [] } +embassy-boot-nrf = { version = "0.3.0", path = "../../../../embassy-boot-nrf", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +panic-reset = { version = "0.1.1" } +embedded-hal = { version = "0.2.6" } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +ed25519-dalek = ["embassy-boot/ed25519-dalek"] +ed25519-salty = ["embassy-boot/ed25519-salty"] +skip-include = [] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-nrf/defmt", + "embassy-boot-nrf/defmt", + "embassy-sync/defmt", +] diff --git a/embassy/examples/boot/application/nrf/README.md b/embassy/examples/boot/application/nrf/README.md new file mode 100644 index 0000000..9d6d203 --- /dev/null +++ b/embassy/examples/boot/application/nrf/README.md @@ -0,0 +1,38 @@ +# Examples using bootloader + +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. + + +## Prerequisites + +* `cargo-binutils` +* `cargo-flash` +* `embassy-boot-nrf` + +## Usage + + + +``` +# Use bare metal linker script +cp memory-bl.x ../../bootloader/nrf/memory.x + +# Flash bootloader +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 --features embassy-nrf/nrf52840 +# Generate binary for 'b' +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 --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/embassy/examples/boot/application/nrf/build.rs b/embassy/examples/boot/application/nrf/build.rs new file mode 100644 index 0000000..e1da693 --- /dev/null +++ b/embassy/examples/boot/application/nrf/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/application/nrf/memory-bl-nrf91.x b/embassy/examples/boot/application/nrf/memory-bl-nrf91.x new file mode 100644 index 0000000..14ceffa --- /dev/null +++ b/embassy/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/embassy/examples/boot/application/nrf/memory-bl.x b/embassy/examples/boot/application/nrf/memory-bl.x new file mode 100644 index 0000000..257d656 --- /dev/null +++ b/embassy/examples/boot/application/nrf/memory-bl.x @@ -0,0 +1,18 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K + ACTIVE : ORIGIN = 0x00007000, LENGTH = 64K + DFU : ORIGIN = 0x00017000, LENGTH = 68K + RAM (rwx) : ORIGIN = 0x20000000, 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/embassy/examples/boot/application/nrf/memory-nrf91.x b/embassy/examples/boot/application/nrf/memory-nrf91.x new file mode 100644 index 0000000..2bc13c0 --- /dev/null +++ b/embassy/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/embassy/examples/boot/application/nrf/memory.x b/embassy/examples/boot/application/nrf/memory.x new file mode 100644 index 0000000..c6926e4 --- /dev/null +++ b/embassy/examples/boot/application/nrf/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOTLOADER : ORIGIN = 0x00000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K + FLASH : ORIGIN = 0x00007000, LENGTH = 64K + DFU : ORIGIN = 0x00017000, LENGTH = 68K + RAM (rwx) : ORIGIN = 0x20000000, 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/embassy/examples/boot/application/nrf/src/bin/a.rs b/embassy/examples/boot/application/nrf/src/bin/a.rs new file mode 100644 index 0000000..2c1d1a7 --- /dev/null +++ b/embassy/examples/boot/application/nrf/src/bin/a.rs @@ -0,0 +1,83 @@ +#![no_std] +#![no_main] +#![macro_use] + +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot::State; +use embassy_boot_nrf::{FirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_embedded_hal::adapter::BlockingAsync; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; +use embassy_nrf::nvmc::Nvmc; +use embassy_nrf::wdt::{self, Watchdog}; +use embassy_sync::mutex::Mutex; +use panic_reset as _; + +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] +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_reverted = Output::new(p.P0_14, Level::High, OutputDrive::Standard); + + //let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard); + //let mut button = Input::new(p.P1_02, Pull::Up); + + // 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 nvmc = Mutex::new(BlockingAsync::new(nvmc)); + + let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc, &nvmc); + let mut magic = [0; 4]; + let mut updater = FirmwareUpdater::new(config, &mut magic); + let state = updater.get_state().await.unwrap(); + if state == State::Revert { + led_reverted.set_low(); + } else { + led_reverted.set_high(); + } + + loop { + led.set_low(); + button.wait_for_any_edge().await; + if button.is_low() { + let mut offset = 0; + for chunk in APP_B.chunks(4096) { + let mut buf: [u8; 4096] = [0; 4096]; + buf[..chunk.len()].copy_from_slice(chunk); + updater.write_firmware(offset, &buf).await.unwrap(); + offset += chunk.len(); + } + updater.mark_updated().await.unwrap(); + led.set_high(); + cortex_m::peripheral::SCB::sys_reset(); + } + } +} diff --git a/embassy/examples/boot/application/nrf/src/bin/b.rs b/embassy/examples/boot/application/nrf/src/bin/b.rs new file mode 100644 index 0000000..de97b6a --- /dev/null +++ b/embassy/examples/boot/application/nrf/src/bin/b.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] +#![macro_use] + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::Timer; +use panic_reset as _; + +#[embassy_executor::main] +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); + + // nRF91 DK + // let mut led = Output::new(p.P0_02, Level::Low, OutputDrive::Standard); + + loop { + led.set_high(); + Timer::after_millis(300).await; + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/boot/application/rp/.cargo/config.toml b/embassy/examples/boot/application/rp/.cargo/config.toml new file mode 100644 index 0000000..22ab3a5 --- /dev/null +++ b/embassy/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-rs run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/application/rp/Cargo.toml b/embassy/examples/boot/application/rp/Cargo.toml new file mode 100644 index 0000000..ec99f26 --- /dev/null +++ b/embassy/examples/boot/application/rp/Cargo.toml @@ -0,0 +1,36 @@ +[package] +edition = "2021" +name = "embassy-boot-rp-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } +embassy-rp = { version = "0.2.0", path = "../../../../embassy-rp", features = ["time-driver", "rp2040"] } +embassy-boot-rp = { version = "0.3.0", path = "../../../../embassy-boot-rp", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = "0.3" +defmt-rtt = "0.4" +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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-storage = "0.3.1" + +[features] +default = ["panic-reset"] +debug = [ + "embassy-rp/defmt", + "embassy-boot-rp/defmt", + "embassy-sync/defmt", + "panic-probe" +] +skip-include = [] + +[profile.release] +debug = true diff --git a/embassy/examples/boot/application/rp/README.md b/embassy/examples/boot/application/rp/README.md new file mode 100644 index 0000000..41304c5 --- /dev/null +++ b/embassy/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/embassy/examples/boot/application/rp/build.rs b/embassy/examples/boot/application/rp/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/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/embassy/examples/boot/application/rp/memory.x b/embassy/examples/boot/application/rp/memory.x new file mode 100644 index 0000000..2f8434b --- /dev/null +++ b/embassy/examples/boot/application/rp/memory.x @@ -0,0 +1,27 @@ +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 + + /* Pick one of the two options for RAM layout */ + + /* OPTION A: Use all RAM banks as one big block */ + /* Reasonable, unless you are doing something */ + /* really particular with DMA or other concurrent */ + /* access that would benefit from striping */ + RAM : ORIGIN = 0x20000000, LENGTH = 264K + + /* OPTION B: Keep the unstriped sections separate */ + /* RAM: ORIGIN = 0x20000000, LENGTH = 256K */ + /* SCRATCH_A: ORIGIN = 0x20040000, LENGTH = 4K */ + /* SCRATCH_B: ORIGIN = 0x20041000, LENGTH = 4K */ +} + +__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/embassy/examples/boot/application/rp/src/bin/a.rs b/embassy/examples/boot/application/rp/src/bin/a.rs new file mode 100644 index 0000000..ede0c07 --- /dev/null +++ b/embassy/examples/boot/application/rp/src/bin/a.rs @@ -0,0 +1,67 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +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_sync::blocking_mutex::Mutex; +use embassy_time::{Duration, Timer}; +use embedded_storage::nor_flash::NorFlash; +#[cfg(feature = "panic-probe")] +use panic_probe as _; +#[cfg(feature = "panic-reset")] +use panic_reset as _; + +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] +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 flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + let mut aligned = AlignedBuffer([0; 1]); + let mut updater = BlockingFirmwareUpdater::new(config, &mut aligned.0); + + Timer::after_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 writer = updater + .prepare_update() + .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(offset, &buf.0[..]).unwrap(); + offset += chunk.len() as u32; + } + watchdog.feed(); + defmt::info!("firmware written, marking update"); + updater.mark_updated().unwrap(); + Timer::after_secs(2).await; + led.set_low(); + defmt::info!("update marked, resetting"); + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/embassy/examples/boot/application/rp/src/bin/b.rs b/embassy/examples/boot/application/rp/src/bin/b.rs new file mode 100644 index 0000000..a46d095 --- /dev/null +++ b/embassy/examples/boot/application/rp/src/bin/b.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_time::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_millis(100).await; + + led.set_low(); + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/boot/application/stm32f3/.cargo/config.toml b/embassy/examples/boot/application/stm32f3/.cargo/config.toml new file mode 100644 index 0000000..4a7ec0a --- /dev/null +++ b/embassy/examples/boot/application/stm32f3/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F303VCTx" + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/application/stm32f3/Cargo.toml b/embassy/examples/boot/application/stm32f3/Cargo.toml new file mode 100644 index 0000000..d2138db --- /dev/null +++ b/embassy/examples/boot/application/stm32f3/Cargo.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32f3-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f303re", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32" } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { 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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/embassy/examples/boot/application/stm32f3/README.md b/embassy/examples/boot/application/stm32f3/README.md new file mode 100644 index 0000000..46f033d --- /dev/null +++ b/embassy/examples/boot/application/stm32f3/README.md @@ -0,0 +1,29 @@ +# Examples using bootloader + +Example for STM32F3 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. + + +## Prerequisites + +* `cargo-binutils` +* `cargo-flash` +* `embassy-boot-stm32` + +## Usage + +``` +# Flash bootloader +cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32f303re --chip STM32F303RETx +# 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 STM32F303RETx +``` diff --git a/embassy/examples/boot/application/stm32f3/build.rs b/embassy/examples/boot/application/stm32f3/build.rs new file mode 100644 index 0000000..e1da693 --- /dev/null +++ b/embassy/examples/boot/application/stm32f3/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/application/stm32f3/memory.x b/embassy/examples/boot/application/stm32f3/memory.x new file mode 100644 index 0000000..02ebe3e --- /dev/null +++ b/embassy/examples/boot/application/stm32f3/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K + FLASH : ORIGIN = 0x08008000, LENGTH = 64K + DFU : ORIGIN = 0x08018000, LENGTH = 66K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER); diff --git a/embassy/examples/boot/application/stm32f3/src/bin/a.rs b/embassy/examples/boot/application/stm32f3/src/bin/a.rs new file mode 100644 index 0000000..b608b2e --- /dev/null +++ b/embassy/examples/boot/application/stm32f3/src/bin/a.rs @@ -0,0 +1,45 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_embedded_hal::adapter::BlockingAsync; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_sync::mutex::Mutex; +use panic_reset as _; + +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] +static APP_B: &[u8] = include_bytes!("../../b.bin"); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(BlockingAsync::new(flash)); + + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); + + let mut led = Output::new(p.PA5, Level::Low, Speed::Low); + led.set_high(); + + let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); + let mut updater = FirmwareUpdater::new(config, &mut magic.0); + button.wait_for_falling_edge().await; + let mut offset = 0; + for chunk in APP_B.chunks(2048) { + let mut buf: [u8; 2048] = [0; 2048]; + buf[..chunk.len()].copy_from_slice(chunk); + updater.write_firmware(offset, &buf).await.unwrap(); + offset += chunk.len(); + } + updater.mark_updated().await.unwrap(); + led.set_low(); + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/embassy/examples/boot/application/stm32f3/src/bin/b.rs b/embassy/examples/boot/application/stm32f3/src/bin/b.rs new file mode 100644 index 0000000..b1a5056 --- /dev/null +++ b/embassy/examples/boot/application/stm32f3/src/bin/b.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use panic_reset as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut led = Output::new(p.PA5, Level::High, Speed::Low); + + loop { + led.set_high(); + Timer::after_millis(500).await; + + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/boot/application/stm32f7/.cargo/config.toml b/embassy/examples/boot/application/stm32f7/.cargo/config.toml new file mode 100644 index 0000000..9088eea --- /dev/null +++ b/embassy/examples/boot/application/stm32f7/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F767ZITx" + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/application/stm32f7/Cargo.toml b/embassy/examples/boot/application/stm32f7/Cargo.toml new file mode 100644 index 0000000..b86c66f --- /dev/null +++ b/embassy/examples/boot/application/stm32f7/Cargo.toml @@ -0,0 +1,32 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32f7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +panic-reset = { version = "0.1.1" } +embedded-hal = { version = "0.2.6" } +embedded-storage = "0.3.1" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/embassy/examples/boot/application/stm32f7/README.md b/embassy/examples/boot/application/stm32f7/README.md new file mode 100644 index 0000000..bf9142a --- /dev/null +++ b/embassy/examples/boot/application/stm32f7/README.md @@ -0,0 +1,29 @@ +# Examples using bootloader + +Example for STM32F7 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. + + +## Prerequisites + +* `cargo-binutils` +* `cargo-flash` +* `embassy-boot-stm32` + +## Usage + +``` +# Flash bootloader +./flash-boot.sh +# 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 STM32F767ZITx +``` diff --git a/embassy/examples/boot/application/stm32f7/build.rs b/embassy/examples/boot/application/stm32f7/build.rs new file mode 100644 index 0000000..e1da693 --- /dev/null +++ b/embassy/examples/boot/application/stm32f7/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/application/stm32f7/flash-boot.sh b/embassy/examples/boot/application/stm32f7/flash-boot.sh new file mode 100755 index 0000000..debdb17 --- /dev/null +++ b/embassy/examples/boot/application/stm32f7/flash-boot.sh @@ -0,0 +1,8 @@ +#!/bin/bash +mv ../../bootloader/stm32/memory.x ../../bootloader/stm32/memory-old.x +cp memory-bl.x ../../bootloader/stm32/memory.x + +cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32f767zi --chip STM32F767ZITx --target thumbv7em-none-eabihf + +rm ../../bootloader/stm32/memory.x +mv ../../bootloader/stm32/memory-old.x ../../bootloader/stm32/memory.x diff --git a/embassy/examples/boot/application/stm32f7/memory-bl.x b/embassy/examples/boot/application/stm32f7/memory-bl.x new file mode 100644 index 0000000..2058f6b --- /dev/null +++ b/embassy/examples/boot/application/stm32f7/memory-bl.x @@ -0,0 +1,18 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 256K + BOOTLOADER_STATE : ORIGIN = 0x08040000, LENGTH = 256K + ACTIVE : ORIGIN = 0x08080000, LENGTH = 256K + DFU : ORIGIN = 0x080c0000, LENGTH = 512K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 368K + 16K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(FLASH); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(FLASH); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(FLASH); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(FLASH); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(FLASH); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(FLASH); diff --git a/embassy/examples/boot/application/stm32f7/memory.x b/embassy/examples/boot/application/stm32f7/memory.x new file mode 100644 index 0000000..1c5537d --- /dev/null +++ b/embassy/examples/boot/application/stm32f7/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 256K + BOOTLOADER_STATE : ORIGIN = 0x08040000, LENGTH = 256K + FLASH : ORIGIN = 0x08080000, LENGTH = 256K + DFU : ORIGIN = 0x080c0000, LENGTH = 512K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 368K + 16K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER); diff --git a/embassy/examples/boot/application/stm32f7/src/bin/a.rs b/embassy/examples/boot/application/stm32f7/src/bin/a.rs new file mode 100644 index 0000000..172b4c2 --- /dev/null +++ b/embassy/examples/boot/application/stm32f7/src/bin/a.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::NorFlash; +use panic_reset as _; + +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] +static APP_B: &[u8] = include_bytes!("../../b.bin"); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); + + let mut led = Output::new(p.PB7, Level::Low, Speed::Low); + led.set_high(); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); + let mut updater = BlockingFirmwareUpdater::new(config, &mut magic.0); + let writer = updater.prepare_update().unwrap(); + button.wait_for_rising_edge().await; + let mut offset = 0; + let mut buf = AlignedBuffer([0; 4096]); + for chunk in APP_B.chunks(4096) { + buf.as_mut()[..chunk.len()].copy_from_slice(chunk); + writer.write(offset, buf.as_ref()).unwrap(); + offset += chunk.len() as u32; + } + updater.mark_updated().unwrap(); + led.set_low(); + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/embassy/examples/boot/application/stm32f7/src/bin/b.rs b/embassy/examples/boot/application/stm32f7/src/bin/b.rs new file mode 100644 index 0000000..6bc9c9a --- /dev/null +++ b/embassy/examples/boot/application/stm32f7/src/bin/b.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use panic_reset as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + Timer::after_millis(300).await; + let mut led = Output::new(p.PB7, Level::High, Speed::Low); + led.set_high(); + + loop { + led.set_high(); + Timer::after_millis(500).await; + + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/boot/application/stm32h7/.cargo/config.toml b/embassy/examples/boot/application/stm32h7/.cargo/config.toml new file mode 100644 index 0000000..caa0d3a --- /dev/null +++ b/embassy/examples/boot/application/stm32h7/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32H743ZITx" + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/application/stm32h7/Cargo.toml b/embassy/examples/boot/application/stm32h7/Cargo.toml new file mode 100644 index 0000000..e2e2fe7 --- /dev/null +++ b/embassy/examples/boot/application/stm32h7/Cargo.toml @@ -0,0 +1,32 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32h7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32h743zi", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +panic-reset = { version = "0.1.1" } +embedded-hal = { version = "0.2.6" } +embedded-storage = "0.3.1" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/embassy/examples/boot/application/stm32h7/README.md b/embassy/examples/boot/application/stm32h7/README.md new file mode 100644 index 0000000..1fdc305 --- /dev/null +++ b/embassy/examples/boot/application/stm32h7/README.md @@ -0,0 +1,29 @@ +# Examples using bootloader + +Example for STM32H7 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. + + +## Prerequisites + +* `cargo-binutils` +* `cargo-flash` +* `embassy-boot-stm32` + +## Usage + +``` +# Flash bootloader +./flash-boot.sh +# 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 STM32H743ZITx +``` diff --git a/embassy/examples/boot/application/stm32h7/build.rs b/embassy/examples/boot/application/stm32h7/build.rs new file mode 100644 index 0000000..e1da693 --- /dev/null +++ b/embassy/examples/boot/application/stm32h7/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/application/stm32h7/flash-boot.sh b/embassy/examples/boot/application/stm32h7/flash-boot.sh new file mode 100755 index 0000000..4912a50 --- /dev/null +++ b/embassy/examples/boot/application/stm32h7/flash-boot.sh @@ -0,0 +1,9 @@ +#!/bin/bash +probe-rs erase --chip STM32H743ZITx +mv ../../bootloader/stm32/memory.x ../../bootloader/stm32/memory-old.x +cp memory-bl.x ../../bootloader/stm32/memory.x + +cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32h743zi --chip STM32H743ZITx --target thumbv7em-none-eabihf + +rm ../../bootloader/stm32/memory.x +mv ../../bootloader/stm32/memory-old.x ../../bootloader/stm32/memory.x diff --git a/embassy/examples/boot/application/stm32h7/memory-bl.x b/embassy/examples/boot/application/stm32h7/memory-bl.x new file mode 100644 index 0000000..c6f447d --- /dev/null +++ b/embassy/examples/boot/application/stm32h7/memory-bl.x @@ -0,0 +1,18 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 128K + BOOTLOADER_STATE : ORIGIN = 0x08020000, LENGTH = 128K + ACTIVE : ORIGIN = 0x08040000, LENGTH = 128K + DFU : ORIGIN = 0x08100000, LENGTH = 512K + RAM (rwx) : ORIGIN = 0x24000000, LENGTH = 368K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(FLASH); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(FLASH); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(FLASH); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(FLASH); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(FLASH); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(FLASH); diff --git a/embassy/examples/boot/application/stm32h7/memory.x b/embassy/examples/boot/application/stm32h7/memory.x new file mode 100644 index 0000000..497a09e --- /dev/null +++ b/embassy/examples/boot/application/stm32h7/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 128K + BOOTLOADER_STATE : ORIGIN = 0x08020000, LENGTH = 128K + FLASH : ORIGIN = 0x08040000, LENGTH = 256K + DFU : ORIGIN = 0x08100000, LENGTH = 512K + RAM (rwx) : ORIGIN = 0x24000000, LENGTH = 368K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER); diff --git a/embassy/examples/boot/application/stm32h7/src/bin/a.rs b/embassy/examples/boot/application/stm32h7/src/bin/a.rs new file mode 100644 index 0000000..c1b1a26 --- /dev/null +++ b/embassy/examples/boot/application/stm32h7/src/bin/a.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::NorFlash; +use panic_reset as _; + +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] +static APP_B: &[u8] = include_bytes!("../../b.bin"); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); + + let mut led = Output::new(p.PB14, Level::Low, Speed::Low); + led.set_high(); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); + let mut updater = BlockingFirmwareUpdater::new(config, &mut magic.0); + let writer = updater.prepare_update().unwrap(); + button.wait_for_rising_edge().await; + let mut offset = 0; + let mut buf = AlignedBuffer([0; 4096]); + for chunk in APP_B.chunks(4096) { + buf.as_mut()[..chunk.len()].copy_from_slice(chunk); + writer.write(offset, buf.as_ref()).unwrap(); + offset += chunk.len() as u32; + } + updater.mark_updated().unwrap(); + led.set_low(); + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/embassy/examples/boot/application/stm32h7/src/bin/b.rs b/embassy/examples/boot/application/stm32h7/src/bin/b.rs new file mode 100644 index 0000000..13bdae1 --- /dev/null +++ b/embassy/examples/boot/application/stm32h7/src/bin/b.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use panic_reset as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + Timer::after_millis(300).await; + let mut led = Output::new(p.PB14, Level::High, Speed::Low); + led.set_high(); + + loop { + led.set_high(); + Timer::after_millis(500).await; + + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/boot/application/stm32l0/.cargo/config.toml b/embassy/examples/boot/application/stm32l0/.cargo/config.toml new file mode 100644 index 0000000..6099f01 --- /dev/null +++ b/embassy/examples/boot/application/stm32l0/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L072CZTx" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/application/stm32l0/Cargo.toml b/embassy/examples/boot/application/stm32l0/Cargo.toml new file mode 100644 index 0000000..7e9c52f --- /dev/null +++ b/embassy/examples/boot/application/stm32l0/Cargo.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32l0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l072cz", "time-driver-any", "exti", "memory-x"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { 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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/embassy/examples/boot/application/stm32l0/README.md b/embassy/examples/boot/application/stm32l0/README.md new file mode 100644 index 0000000..762bdfe --- /dev/null +++ b/embassy/examples/boot/application/stm32l0/README.md @@ -0,0 +1,29 @@ +# Examples using bootloader + +Example for STM32L0 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. + + +## Prerequisites + +* `cargo-binutils` +* `cargo-flash` +* `embassy-boot-stm32` + +## Usage + +``` +# Flash bootloader +cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32l072cz --chip STM32L072CZTx +# 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 STM32L072CZTx +``` diff --git a/embassy/examples/boot/application/stm32l0/build.rs b/embassy/examples/boot/application/stm32l0/build.rs new file mode 100644 index 0000000..e1da693 --- /dev/null +++ b/embassy/examples/boot/application/stm32l0/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/application/stm32l0/memory.x b/embassy/examples/boot/application/stm32l0/memory.x new file mode 100644 index 0000000..8866506 --- /dev/null +++ b/embassy/examples/boot/application/stm32l0/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K + FLASH : ORIGIN = 0x08008000, LENGTH = 64K + DFU : ORIGIN = 0x08018000, LENGTH = 66K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER); diff --git a/embassy/examples/boot/application/stm32l0/src/bin/a.rs b/embassy/examples/boot/application/stm32l0/src/bin/a.rs new file mode 100644 index 0000000..dcc10e5 --- /dev/null +++ b/embassy/examples/boot/application/stm32l0/src/bin/a.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_embedded_hal::adapter::BlockingAsync; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_sync::mutex::Mutex; +use embassy_time::Timer; +use panic_reset as _; + +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] +static APP_B: &[u8] = include_bytes!("../../b.bin"); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(BlockingAsync::new(flash)); + + let mut button = ExtiInput::new(p.PB2, p.EXTI2, Pull::Up); + + let mut led = Output::new(p.PB5, Level::Low, Speed::Low); + + led.set_high(); + + let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); + let mut updater = FirmwareUpdater::new(config, &mut magic.0); + button.wait_for_falling_edge().await; + let mut offset = 0; + for chunk in APP_B.chunks(128) { + let mut buf: [u8; 128] = [0; 128]; + buf[..chunk.len()].copy_from_slice(chunk); + updater.write_firmware(offset, &buf).await.unwrap(); + offset += chunk.len(); + } + + updater.mark_updated().await.unwrap(); + led.set_low(); + Timer::after_secs(1).await; + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/embassy/examples/boot/application/stm32l0/src/bin/b.rs b/embassy/examples/boot/application/stm32l0/src/bin/b.rs new file mode 100644 index 0000000..a59c6f5 --- /dev/null +++ b/embassy/examples/boot/application/stm32l0/src/bin/b.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use panic_reset as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut led = Output::new(p.PB6, Level::High, Speed::Low); + + loop { + led.set_high(); + Timer::after_millis(500).await; + + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/boot/application/stm32l1/.cargo/config.toml b/embassy/examples/boot/application/stm32l1/.cargo/config.toml new file mode 100644 index 0000000..9cabd14 --- /dev/null +++ b/embassy/examples/boot/application/stm32l1/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L151CBxxA" + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/application/stm32l1/Cargo.toml b/embassy/examples/boot/application/stm32l1/Cargo.toml new file mode 100644 index 0000000..42353a2 --- /dev/null +++ b/embassy/examples/boot/application/stm32l1/Cargo.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32l1-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l151cb-a", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { 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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/embassy/examples/boot/application/stm32l1/README.md b/embassy/examples/boot/application/stm32l1/README.md new file mode 100644 index 0000000..428dde7 --- /dev/null +++ b/embassy/examples/boot/application/stm32l1/README.md @@ -0,0 +1,29 @@ +# Examples using bootloader + +Example for STM32L1 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. + + +## Prerequisites + +* `cargo-binutils` +* `cargo-flash` +* `embassy-boot-stm32` + +## Usage + +``` +# Flash bootloader +cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32l151cb-a --chip STM32L151CBxxA +# 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 STM32L151CBxxA +``` diff --git a/embassy/examples/boot/application/stm32l1/build.rs b/embassy/examples/boot/application/stm32l1/build.rs new file mode 100644 index 0000000..e1da693 --- /dev/null +++ b/embassy/examples/boot/application/stm32l1/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/application/stm32l1/memory.x b/embassy/examples/boot/application/stm32l1/memory.x new file mode 100644 index 0000000..caa5252 --- /dev/null +++ b/embassy/examples/boot/application/stm32l1/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K + FLASH : ORIGIN = 0x08008000, LENGTH = 46K + DFU : ORIGIN = 0x08013800, LENGTH = 54K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER); diff --git a/embassy/examples/boot/application/stm32l1/src/bin/a.rs b/embassy/examples/boot/application/stm32l1/src/bin/a.rs new file mode 100644 index 0000000..dcc10e5 --- /dev/null +++ b/embassy/examples/boot/application/stm32l1/src/bin/a.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_embedded_hal::adapter::BlockingAsync; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_sync::mutex::Mutex; +use embassy_time::Timer; +use panic_reset as _; + +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] +static APP_B: &[u8] = include_bytes!("../../b.bin"); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(BlockingAsync::new(flash)); + + let mut button = ExtiInput::new(p.PB2, p.EXTI2, Pull::Up); + + let mut led = Output::new(p.PB5, Level::Low, Speed::Low); + + led.set_high(); + + let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); + let mut updater = FirmwareUpdater::new(config, &mut magic.0); + button.wait_for_falling_edge().await; + let mut offset = 0; + for chunk in APP_B.chunks(128) { + let mut buf: [u8; 128] = [0; 128]; + buf[..chunk.len()].copy_from_slice(chunk); + updater.write_firmware(offset, &buf).await.unwrap(); + offset += chunk.len(); + } + + updater.mark_updated().await.unwrap(); + led.set_low(); + Timer::after_secs(1).await; + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/embassy/examples/boot/application/stm32l1/src/bin/b.rs b/embassy/examples/boot/application/stm32l1/src/bin/b.rs new file mode 100644 index 0000000..a59c6f5 --- /dev/null +++ b/embassy/examples/boot/application/stm32l1/src/bin/b.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use panic_reset as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut led = Output::new(p.PB6, Level::High, Speed::Low); + + loop { + led.set_high(); + Timer::after_millis(500).await; + + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/boot/application/stm32l4/.cargo/config.toml b/embassy/examples/boot/application/stm32l4/.cargo/config.toml new file mode 100644 index 0000000..c803215 --- /dev/null +++ b/embassy/examples/boot/application/stm32l4/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L475VG" + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/application/stm32l4/Cargo.toml b/embassy/examples/boot/application/stm32l4/Cargo.toml new file mode 100644 index 0000000..cf0b024 --- /dev/null +++ b/embassy/examples/boot/application/stm32l4/Cargo.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32l4-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { 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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/embassy/examples/boot/application/stm32l4/README.md b/embassy/examples/boot/application/stm32l4/README.md new file mode 100644 index 0000000..83f5074 --- /dev/null +++ b/embassy/examples/boot/application/stm32l4/README.md @@ -0,0 +1,29 @@ +# Examples using bootloader + +Example for STM32L4 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. + + +## Prerequisites + +* `cargo-binutils` +* `cargo-flash` +* `embassy-boot-stm32` + +## Usage + +``` +# Flash bootloader +cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32l475vg --chip STM32L475VG +# 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 STM32L475VG +``` diff --git a/embassy/examples/boot/application/stm32l4/build.rs b/embassy/examples/boot/application/stm32l4/build.rs new file mode 100644 index 0000000..e1da693 --- /dev/null +++ b/embassy/examples/boot/application/stm32l4/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/application/stm32l4/memory.x b/embassy/examples/boot/application/stm32l4/memory.x new file mode 100644 index 0000000..e1d4e7f --- /dev/null +++ b/embassy/examples/boot/application/stm32l4/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K + FLASH : ORIGIN = 0x08008000, LENGTH = 64K + DFU : ORIGIN = 0x08018000, LENGTH = 68K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER); diff --git a/embassy/examples/boot/application/stm32l4/src/bin/a.rs b/embassy/examples/boot/application/stm32l4/src/bin/a.rs new file mode 100644 index 0000000..7f8015c --- /dev/null +++ b/embassy/examples/boot/application/stm32l4/src/bin/a.rs @@ -0,0 +1,45 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_embedded_hal::adapter::BlockingAsync; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_sync::mutex::Mutex; +use panic_reset as _; + +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] +static APP_B: &[u8] = include_bytes!("../../b.bin"); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(BlockingAsync::new(flash)); + + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); + + let mut led = Output::new(p.PB14, Level::Low, Speed::Low); + led.set_high(); + + let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); + let mut updater = FirmwareUpdater::new(config, &mut magic.0); + button.wait_for_falling_edge().await; + let mut offset = 0; + for chunk in APP_B.chunks(2048) { + let mut buf: [u8; 2048] = [0; 2048]; + buf[..chunk.len()].copy_from_slice(chunk); + updater.write_firmware(offset, &buf).await.unwrap(); + offset += chunk.len(); + } + updater.mark_updated().await.unwrap(); + led.set_low(); + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/embassy/examples/boot/application/stm32l4/src/bin/b.rs b/embassy/examples/boot/application/stm32l4/src/bin/b.rs new file mode 100644 index 0000000..b1a5056 --- /dev/null +++ b/embassy/examples/boot/application/stm32l4/src/bin/b.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use panic_reset as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut led = Output::new(p.PA5, Level::High, Speed::Low); + + loop { + led.set_high(); + Timer::after_millis(500).await; + + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/boot/application/stm32wb-dfu/.cargo/config.toml b/embassy/examples/boot/application/stm32wb-dfu/.cargo/config.toml new file mode 100644 index 0000000..4f8094f --- /dev/null +++ b/embassy/examples/boot/application/stm32wb-dfu/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32WLE5JCIx" + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/application/stm32wb-dfu/Cargo.toml b/embassy/examples/boot/application/stm32wb-dfu/Cargo.toml new file mode 100644 index 0000000..ea2879f --- /dev/null +++ b/embassy/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -0,0 +1,32 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32wb-dfu-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-usb = { version = "0.3.0", path = "../../../../embassy-usb" } +embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application", "cortex-m"] } + +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +panic-reset = { version = "0.1.1" } +embedded-hal = { version = "0.2.6" } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] diff --git a/embassy/examples/boot/application/stm32wb-dfu/README.md b/embassy/examples/boot/application/stm32wb-dfu/README.md new file mode 100644 index 0000000..7f656cd --- /dev/null +++ b/embassy/examples/boot/application/stm32wb-dfu/README.md @@ -0,0 +1,9 @@ +# Examples using bootloader + +Example for STM32WB demonstrating the USB DFU application. + +## Usage + +``` +cargo flash --release --chip STM32WB55RGVx +``` diff --git a/embassy/examples/boot/application/stm32wb-dfu/build.rs b/embassy/examples/boot/application/stm32wb-dfu/build.rs new file mode 100644 index 0000000..e1da693 --- /dev/null +++ b/embassy/examples/boot/application/stm32wb-dfu/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/application/stm32wb-dfu/memory.x b/embassy/examples/boot/application/stm32wb-dfu/memory.x new file mode 100644 index 0000000..ff1b800 --- /dev/null +++ b/embassy/examples/boot/application/stm32wb-dfu/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K + FLASH : ORIGIN = 0x08008000, LENGTH = 128K + DFU : ORIGIN = 0x08028000, LENGTH = 132K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER); diff --git a/embassy/examples/boot/application/stm32wb-dfu/src/main.rs b/embassy/examples/boot/application/stm32wb-dfu/src/main.rs new file mode 100644 index 0000000..0ab99ff --- /dev/null +++ b/embassy/examples/boot/application/stm32wb-dfu/src/main.rs @@ -0,0 +1,61 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareState, FirmwareUpdaterConfig}; +use embassy_executor::Spawner; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32::usb::{self, Driver}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::Duration; +use embassy_usb::Builder; +use embassy_usb_dfu::consts::DfuAttributes; +use embassy_usb_dfu::{usb_dfu, Control, ResetImmediate}; +use panic_reset as _; + +bind_interrupts!(struct Irqs { + USB_LP => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); + let mut firmware_state = BlockingFirmwareState::from_config(config, &mut magic.0); + firmware_state.mark_booted().expect("Failed to mark booted"); + + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-DFU Runtime example"); + config.serial_number = Some("1235678"); + + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut state = Control::new(firmware_state, DfuAttributes::CAN_DOWNLOAD); + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], + &mut control_buf, + ); + + usb_dfu::<_, _, ResetImmediate>(&mut builder, &mut state, Duration::from_millis(2500)); + + let mut dev = builder.build(); + dev.run().await +} diff --git a/embassy/examples/boot/application/stm32wl/.cargo/config.toml b/embassy/examples/boot/application/stm32wl/.cargo/config.toml new file mode 100644 index 0000000..4f8094f --- /dev/null +++ b/embassy/examples/boot/application/stm32wl/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32WLE5JCIx" + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/application/stm32wl/Cargo.toml b/embassy/examples/boot/application/stm32wl/Cargo.toml new file mode 100644 index 0000000..6417b84 --- /dev/null +++ b/embassy/examples/boot/application/stm32wl/Cargo.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32wl-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wl55jc-cm4", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } + +defmt = { 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 = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/embassy/examples/boot/application/stm32wl/README.md b/embassy/examples/boot/application/stm32wl/README.md new file mode 100644 index 0000000..c8dce03 --- /dev/null +++ b/embassy/examples/boot/application/stm32wl/README.md @@ -0,0 +1,29 @@ +# Examples using bootloader + +Example for STM32WL 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. + + +## Prerequisites + +* `cargo-binutils` +* `cargo-flash` +* `embassy-boot-stm32` + +## Usage + +``` +# Flash bootloader +cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32wl55jc-cm4 --chip STM32WLE5JCIx +# 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 STM32WLE5JCIx +``` diff --git a/embassy/examples/boot/application/stm32wl/build.rs b/embassy/examples/boot/application/stm32wl/build.rs new file mode 100644 index 0000000..e1da693 --- /dev/null +++ b/embassy/examples/boot/application/stm32wl/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/application/stm32wl/memory.x b/embassy/examples/boot/application/stm32wl/memory.x new file mode 100644 index 0000000..20109e3 --- /dev/null +++ b/embassy/examples/boot/application/stm32wl/memory.x @@ -0,0 +1,24 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K + FLASH : ORIGIN = 0x08008000, LENGTH = 64K + DFU : ORIGIN = 0x08018000, LENGTH = 68K + SHARED_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128 + RAM (rwx) : ORIGIN = 0x20000080, LENGTH = 32K - 128 +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER); + +SECTIONS +{ + .shared_data : + { + *(.shared_data) + } > SHARED_RAM +} diff --git a/embassy/examples/boot/application/stm32wl/src/bin/a.rs b/embassy/examples/boot/application/stm32wl/src/bin/a.rs new file mode 100644 index 0000000..127de02 --- /dev/null +++ b/embassy/examples/boot/application/stm32wl/src/bin/a.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_embedded_hal::adapter::BlockingAsync; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_stm32::SharedData; +use embassy_sync::mutex::Mutex; +use panic_reset as _; + +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] +static APP_B: &[u8] = include_bytes!("../../b.bin"); + +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(BlockingAsync::new(flash)); + + let mut button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Up); + + let mut led = Output::new(p.PB9, Level::Low, Speed::Low); + led.set_high(); + + let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); + let mut updater = FirmwareUpdater::new(config, &mut magic.0); + button.wait_for_falling_edge().await; + //defmt::info!("Starting update"); + let mut offset = 0; + for chunk in APP_B.chunks(2048) { + let mut buf: [u8; 2048] = [0; 2048]; + buf[..chunk.len()].copy_from_slice(chunk); + // defmt::info!("Writing chunk at 0x{:x}", offset); + updater.write_firmware(offset, &buf).await.unwrap(); + offset += chunk.len(); + } + updater.mark_updated().await.unwrap(); + //defmt::info!("Marked as updated"); + led.set_low(); + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/embassy/examples/boot/application/stm32wl/src/bin/b.rs b/embassy/examples/boot/application/stm32wl/src/bin/b.rs new file mode 100644 index 0000000..768dadf --- /dev/null +++ b/embassy/examples/boot/application/stm32wl/src/bin/b.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +#[cfg(feature = "defmt")] +use defmt_rtt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::SharedData; +use embassy_time::Timer; +use panic_reset as _; + +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); + let mut led = Output::new(p.PB15, Level::High, Speed::Low); + + loop { + led.set_high(); + Timer::after_millis(500).await; + + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/boot/bootloader/nrf/.cargo/config.toml b/embassy/examples/boot/bootloader/nrf/.cargo/config.toml new file mode 100644 index 0000000..58acd1a --- /dev/null +++ b/embassy/examples/boot/bootloader/nrf/.cargo/config.toml @@ -0,0 +1,20 @@ +[unstable] +#build-std = ["core"] +#build-std-features = ["panic_immediate_abort"] + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +#runner = "./fruitrunner" +runner = "probe-rs run --chip nrf52840_xxAA" + +rustflags = [ + # Code-size optimizations. + #"-Z", "trap-unreachable=no", + #"-C", "no-vectorize-loops", + "-C", "force-frame-pointers=yes", +] + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/bootloader/nrf/Cargo.toml b/embassy/examples/boot/bootloader/nrf/Cargo.toml new file mode 100644 index 0000000..c2e8bbe --- /dev/null +++ b/embassy/examples/boot/bootloader/nrf/Cargo.toml @@ -0,0 +1,59 @@ +[package] +edition = "2021" +name = "nrf-bootloader-example" +version = "0.1.0" +description = "Bootloader for nRF chips" +license = "MIT OR Apache-2.0" + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } + +embassy-nrf = { path = "../../../../embassy-nrf", features = [] } +embassy-boot-nrf = { path = "../../../../embassy-boot-nrf" } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +cortex-m-rt = { version = "0.7" } +cfg-if = "1.0.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-boot-nrf/defmt", + "embassy-nrf/defmt", +] +softdevice = [ + "embassy-boot-nrf/softdevice", +] + +[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/examples/boot/bootloader/nrf/README.md b/embassy/examples/boot/bootloader/nrf/README.md new file mode 100644 index 0000000..23497a0 --- /dev/null +++ b/embassy/examples/boot/bootloader/nrf/README.md @@ -0,0 +1,11 @@ +# Bootloader for nRF + +The bootloader uses `embassy-boot` to interact with the flash. + +# Usage + +Flash the bootloader + +``` +cargo flash --features embassy-nrf/nrf52832 --release --chip nRF52832_xxAA +``` diff --git a/embassy/examples/boot/bootloader/nrf/build.rs b/embassy/examples/boot/bootloader/nrf/build.rs new file mode 100644 index 0000000..e1da693 --- /dev/null +++ b/embassy/examples/boot/bootloader/nrf/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/bootloader/nrf/memory-bm.x b/embassy/examples/boot/bootloader/nrf/memory-bm.x new file mode 100644 index 0000000..257d656 --- /dev/null +++ b/embassy/examples/boot/bootloader/nrf/memory-bm.x @@ -0,0 +1,18 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K + ACTIVE : ORIGIN = 0x00007000, LENGTH = 64K + DFU : ORIGIN = 0x00017000, LENGTH = 68K + RAM (rwx) : ORIGIN = 0x20000000, 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/embassy/examples/boot/bootloader/nrf/memory-s140.x b/embassy/examples/boot/bootloader/nrf/memory-s140.x new file mode 100644 index 0000000..105db99 --- /dev/null +++ b/embassy/examples/boot/bootloader/nrf/memory-s140.x @@ -0,0 +1,31 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + MBR : ORIGIN = 0x00000000, LENGTH = 4K + SOFTDEVICE : ORIGIN = 0x00001000, LENGTH = 155648 + ACTIVE : ORIGIN = 0x00027000, LENGTH = 425984 + DFU : ORIGIN = 0x0008F000, LENGTH = 430080 + FLASH : ORIGIN = 0x000f9000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x000ff000, LENGTH = 4K + RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 0x2fff8 + uicr_bootloader_start_address (r) : ORIGIN = 0x10001014, LENGTH = 0x4 +} + +__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); + +__bootloader_start = ORIGIN(FLASH); + +SECTIONS +{ + .uicr_bootloader_start_address : + { + LONG(__bootloader_start) + } > uicr_bootloader_start_address +} diff --git a/embassy/examples/boot/bootloader/nrf/memory.x b/embassy/examples/boot/bootloader/nrf/memory.x new file mode 100644 index 0000000..257d656 --- /dev/null +++ b/embassy/examples/boot/bootloader/nrf/memory.x @@ -0,0 +1,18 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K + ACTIVE : ORIGIN = 0x00007000, LENGTH = 64K + DFU : ORIGIN = 0x00017000, LENGTH = 68K + RAM (rwx) : ORIGIN = 0x20000000, 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/embassy/examples/boot/bootloader/nrf/src/main.rs b/embassy/examples/boot/bootloader/nrf/src/main.rs new file mode 100644 index 0000000..b849a0d --- /dev/null +++ b/embassy/examples/boot/bootloader/nrf/src/main.rs @@ -0,0 +1,58 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use cortex_m_rt::{entry, exception}; +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot_nrf::*; +use embassy_nrf::nvmc::Nvmc; +use embassy_nrf::wdt::{self, HaltConfig, SleepConfig}; +use embassy_sync::blocking_mutex::Mutex; + +#[entry] +fn main() -> ! { + let p = embassy_nrf::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 wdt_config = wdt::Config::default(); + wdt_config.timeout_ticks = 32768 * 5; // timeout seconds + wdt_config.action_during_sleep = SleepConfig::RUN; + wdt_config.action_during_debug_halt = HaltConfig::PAUSE; + + let flash = WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, wdt_config); + let flash = Mutex::new(RefCell::new(flash)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); + let active_offset = config.active.offset(); + let bl: BootLoader = BootLoader::prepare(config); + + unsafe { bl.load(active_offset) } +} + +#[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/embassy/examples/boot/bootloader/rp/.cargo/config.toml b/embassy/examples/boot/bootloader/rp/.cargo/config.toml new file mode 100644 index 0000000..9d48ecd --- /dev/null +++ b/embassy/examples/boot/bootloader/rp/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/boot/bootloader/rp/Cargo.toml b/embassy/examples/boot/bootloader/rp/Cargo.toml new file mode 100644 index 0000000..24df3da --- /dev/null +++ b/embassy/examples/boot/bootloader/rp/Cargo.toml @@ -0,0 +1,33 @@ +[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", features = ["rp2040"] } +embassy-boot-rp = { path = "../../../../embassy-boot-rp" } +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +embassy-time = { path = "../../../../embassy-time", features = [] } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = "0.4.0" +cfg-if = "1.0.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-boot-rp/defmt", + "embassy-rp/defmt", +] + +[profile.release] +debug = true +opt-level = 's' diff --git a/embassy/examples/boot/bootloader/rp/README.md b/embassy/examples/boot/bootloader/rp/README.md new file mode 100644 index 0000000..064e872 --- /dev/null +++ b/embassy/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/embassy/examples/boot/bootloader/rp/build.rs b/embassy/examples/boot/bootloader/rp/build.rs new file mode 100644 index 0000000..c201704 --- /dev/null +++ b/embassy/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/embassy/examples/boot/bootloader/rp/memory.x b/embassy/examples/boot/bootloader/rp/memory.x new file mode 100644 index 0000000..88b5bbb --- /dev/null +++ b/embassy/examples/boot/bootloader/rp/memory.x @@ -0,0 +1,31 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 24K - 0x100 + BOOTLOADER_STATE : ORIGIN = 0x10006000, LENGTH = 4K + ACTIVE : ORIGIN = 0x10007000, LENGTH = 512K + DFU : ORIGIN = 0x10087000, LENGTH = 516K + + /* Pick one of the two options for RAM layout */ + + /* OPTION A: Use all RAM banks as one big block */ + /* Reasonable, unless you are doing something */ + /* really particular with DMA or other concurrent */ + /* access that would benefit from striping */ + RAM : ORIGIN = 0x20000000, LENGTH = 264K + + /* OPTION B: Keep the unstriped sections separate */ + /* RAM: ORIGIN = 0x20000000, LENGTH = 256K */ + /* SCRATCH_A: ORIGIN = 0x20040000, LENGTH = 4K */ + /* SCRATCH_B: ORIGIN = 0x20041000, LENGTH = 4K */ +} + +__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/embassy/examples/boot/bootloader/rp/src/main.rs b/embassy/examples/boot/bootloader/rp/src/main.rs new file mode 100644 index 0000000..25b1657 --- /dev/null +++ b/embassy/examples/boot/bootloader/rp/src/main.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use cortex_m_rt::{entry, exception}; +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot_rp::*; +use embassy_sync::blocking_mutex::Mutex; +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 flash = WatchdogFlash::::start(p.FLASH, p.WATCHDOG, Duration::from_secs(8)); + let flash = Mutex::new(RefCell::new(flash)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); + let active_offset = config.active.offset(); + let bl: BootLoader = BootLoader::prepare(config); + + unsafe { bl.load(embassy_rp::flash::FLASH_BASE as u32 + active_offset) } +} + +#[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/embassy/examples/boot/bootloader/stm32-dual-bank/Cargo.toml b/embassy/examples/boot/bootloader/stm32-dual-bank/Cargo.toml new file mode 100644 index 0000000..81e0026 --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32-dual-bank/Cargo.toml @@ -0,0 +1,56 @@ +[package] +edition = "2021" +name = "stm32-bootloader-dual-bank-flash-example" +version = "0.1.0" +description = "Example bootloader for dual-bank flash STM32 chips" +license = "MIT OR Apache-2.0" + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } + +embassy-stm32 = { path = "../../../../embassy-stm32", features = [] } +embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" } +cortex-m = { version = "0.7.6", features = [ + "inline-asm", + "critical-section-single-core", +] } +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = "0.4.0" +cfg-if = "1.0.0" + +[features] +defmt = ["dep:defmt", "dep:defmt-rtt", "embassy-boot-stm32/defmt", "embassy-stm32/defmt"] + +[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/examples/boot/bootloader/stm32-dual-bank/README.md b/embassy/examples/boot/bootloader/stm32-dual-bank/README.md new file mode 100644 index 0000000..cd6c0bc --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32-dual-bank/README.md @@ -0,0 +1,44 @@ +# STM32 dual-bank flash Bootloader + +## Overview + +This bootloader leverages `embassy-boot` to interact with the flash. +This example targets STM32 devices with dual-bank flash memory, with a primary focus on the STM32H747XI series. +Users must modify the `memory.x` configuration file to match with the memory layout of their specific STM32 device. + +Additionally, this example can be extended to utilize external flash memory, such as QSPI, for storing partitions. + +## Memory Configuration + +In this example's `memory.x` file, various symbols are defined to assist in effective memory management within the bootloader environment. +For dual-bank STM32 devices, it's crucial to assign these symbols correctly to their respective memory banks. + +### Symbol Definitions + +The bootloader's state and active symbols are anchored to the flash origin of **bank 1**: + +- `__bootloader_state_start` and `__bootloader_state_end` +- `__bootloader_active_start` and `__bootloader_active_end` + +In contrast, the Device Firmware Upgrade (DFU) symbols are aligned with the DFU flash origin in **bank 2**: + +- `__bootloader_dfu_start` and `__bootloader_dfu_end` + +```rust +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(**FLASH**); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(**FLASH**); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(**FLASH**); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(**FLASH**); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(**DFU**); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(**DFU**); +``` + +## Flashing the Bootloader + +To flash the bootloader onto your STM32H747XI device, use the following command: + +```bash +cargo flash --features embassy-stm32/stm32h747xi-cm7 --release --chip STM32H747XIHx +``` diff --git a/embassy/examples/boot/bootloader/stm32-dual-bank/build.rs b/embassy/examples/boot/bootloader/stm32-dual-bank/build.rs new file mode 100644 index 0000000..fd60599 --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32-dual-bank/build.rs @@ -0,0 +1,27 @@ +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"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/bootloader/stm32-dual-bank/memory.x b/embassy/examples/boot/bootloader/stm32-dual-bank/memory.x new file mode 100644 index 0000000..665da71 --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32-dual-bank/memory.x @@ -0,0 +1,18 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 128K + BOOTLOADER_STATE : ORIGIN = 0x08020000, LENGTH = 128K + ACTIVE : ORIGIN = 0x08040000, LENGTH = 512K + DFU : ORIGIN = 0x08100000, LENGTH = 640K + RAM (rwx) : ORIGIN = 0x24000000, LENGTH = 512K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(FLASH); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(FLASH); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(FLASH); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(FLASH); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(DFU); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(DFU); diff --git a/embassy/examples/boot/bootloader/stm32-dual-bank/src/main.rs b/embassy/examples/boot/bootloader/stm32-dual-bank/src/main.rs new file mode 100644 index 0000000..4d2e82d --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32-dual-bank/src/main.rs @@ -0,0 +1,53 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use cortex_m_rt::{entry, exception}; +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot_stm32::*; +use embassy_stm32::flash::{Flash, BANK1_REGION}; +use embassy_sync::blocking_mutex::Mutex; + +#[entry] +fn main() -> ! { + let p = embassy_stm32::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 layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); + let flash_bank1 = Mutex::new(RefCell::new(layout.bank1_region)); + let flash_bank2 = Mutex::new(RefCell::new(layout.bank2_region)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash_bank1, &flash_bank2, &flash_bank1); + let active_offset = config.active.offset(); + let bl = BootLoader::prepare::<_, _, _, 2048>(config); + + unsafe { bl.load(BANK1_REGION.base + active_offset) } +} + +#[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/embassy/examples/boot/bootloader/stm32/Cargo.toml b/embassy/examples/boot/bootloader/stm32/Cargo.toml new file mode 100644 index 0000000..f35e4e7 --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32/Cargo.toml @@ -0,0 +1,58 @@ +[package] +edition = "2021" +name = "stm32-bootloader-example" +version = "0.1.0" +description = "Example bootloader for STM32 chips" +license = "MIT OR Apache-2.0" + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } + +embassy-stm32 = { path = "../../../../embassy-stm32", features = [] } +embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = "0.4.0" +cfg-if = "1.0.0" + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-boot-stm32/defmt", + "embassy-stm32/defmt", +] + +[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/examples/boot/bootloader/stm32/README.md b/embassy/examples/boot/bootloader/stm32/README.md new file mode 100644 index 0000000..a82b730 --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32/README.md @@ -0,0 +1,11 @@ +# Bootloader for STM32 + +The bootloader uses `embassy-boot` to interact with the flash. + +# Usage + +Flash the bootloader + +``` +cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx +``` diff --git a/embassy/examples/boot/bootloader/stm32/build.rs b/embassy/examples/boot/bootloader/stm32/build.rs new file mode 100644 index 0000000..fd60599 --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32/build.rs @@ -0,0 +1,27 @@ +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"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/bootloader/stm32/memory.x b/embassy/examples/boot/bootloader/stm32/memory.x new file mode 100644 index 0000000..1982905 --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32/memory.x @@ -0,0 +1,18 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 8K + ACTIVE : ORIGIN = 0x08008000, LENGTH = 32K + DFU : ORIGIN = 0x08010000, LENGTH = 36K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(FLASH); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(FLASH); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(FLASH); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(FLASH); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(FLASH); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(FLASH); diff --git a/embassy/examples/boot/bootloader/stm32/src/main.rs b/embassy/examples/boot/bootloader/stm32/src/main.rs new file mode 100644 index 0000000..99a7a6a --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32/src/main.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use cortex_m_rt::{entry, exception}; +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot_stm32::*; +use embassy_stm32::flash::{Flash, BANK1_REGION}; +use embassy_sync::blocking_mutex::Mutex; + +#[entry] +fn main() -> ! { + let p = embassy_stm32::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 layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); + let flash = Mutex::new(RefCell::new(layout.bank1_region)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); + let active_offset = config.active.offset(); + let bl = BootLoader::prepare::<_, _, _, 2048>(config); + + unsafe { bl.load(BANK1_REGION.base + active_offset) } +} + +#[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/embassy/examples/boot/bootloader/stm32wb-dfu/Cargo.toml b/embassy/examples/boot/bootloader/stm32wb-dfu/Cargo.toml new file mode 100644 index 0000000..1431e7c --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32wb-dfu/Cargo.toml @@ -0,0 +1,63 @@ +[package] +edition = "2021" +name = "stm32wb-dfu-bootloader-example" +version = "0.1.0" +description = "Example USB DFUbootloader for the STM32WB series of chips" +license = "MIT OR Apache-2.0" + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } + +embassy-stm32 = { path = "../../../../embassy-stm32", features = [] } +embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +embassy-sync = { version = "0.6.1", path = "../../../../embassy-sync" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = "0.4.0" +cfg-if = "1.0.0" +embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["dfu", "cortex-m"] } +embassy-usb = { version = "0.3.0", path = "../../../../embassy-usb", default-features = false } +embassy-futures = { version = "0.1.1", path = "../../../../embassy-futures" } + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "embassy-boot-stm32/defmt", + "embassy-stm32/defmt", + "embassy-usb/defmt", + "embassy-usb-dfu/defmt" +] + +[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/examples/boot/bootloader/stm32wb-dfu/README.md b/embassy/examples/boot/bootloader/stm32wb-dfu/README.md new file mode 100644 index 0000000..3c5f268 --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32wb-dfu/README.md @@ -0,0 +1,37 @@ +# Bootloader for STM32 + +This bootloader implementation uses `embassy-boot` and `embassy-usb-dfu` to manage firmware updates and interact with the flash memory on STM32WB55 devices. + +## Prerequisites + +- Rust toolchain with `cargo` installed +- `cargo-flash` for flashing the bootloader +- `dfu-util` for firmware updates +- `cargo-binutils` for binary generation + +## Usage + +### 1. Flash the Bootloader + +First, flash the bootloader to your device: + +``` +cargo flash --features embassy-stm32/stm32wb55rg --release --chip STM32WB55RGVx +``` + +### 2. Build and Flash Application + +Generate your application binary and flash it using DFU: + +``` +cargo objcopy --release -- -O binary fw.bin +dfu-util -d c0de:cafe -w -D fw.bin +``` + +## Troubleshooting + +- Make sure your device is in DFU mode before flashing +- Verify the USB VID:PID matches your device (c0de:cafe) +- Check USB connections if the device is not detected +- Make sure the transfer size option of `dfu-util` matches the bootloader configuration. By default, `dfu-util` will use the transfer size reported by the device, but you can override it with the `-t` option if needed. +- Make sure `control_buf` size is larger than or equal to the `usb_dfu` `BLOCK_SIZE` parameter (in this example, both are set to 4096 bytes). diff --git a/embassy/examples/boot/bootloader/stm32wb-dfu/build.rs b/embassy/examples/boot/bootloader/stm32wb-dfu/build.rs new file mode 100644 index 0000000..fd60599 --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32wb-dfu/build.rs @@ -0,0 +1,27 @@ +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"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/embassy/examples/boot/bootloader/stm32wb-dfu/memory.x b/embassy/examples/boot/bootloader/stm32wb-dfu/memory.x new file mode 100644 index 0000000..8580626 --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32wb-dfu/memory.x @@ -0,0 +1,18 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K + ACTIVE : ORIGIN = 0x08008000, LENGTH = 128K + DFU : ORIGIN = 0x08028000, LENGTH = 132K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(FLASH); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(FLASH); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(FLASH); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(FLASH); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(FLASH); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(FLASH); diff --git a/embassy/examples/boot/bootloader/stm32wb-dfu/src/main.rs b/embassy/examples/boot/bootloader/stm32wb-dfu/src/main.rs new file mode 100644 index 0000000..b09d53c --- /dev/null +++ b/embassy/examples/boot/bootloader/stm32wb-dfu/src/main.rs @@ -0,0 +1,106 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use cortex_m_rt::{entry, exception}; +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot_stm32::*; +use embassy_stm32::flash::{Flash, BANK1_REGION, WRITE_SIZE}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb}; +use embassy_sync::blocking_mutex::Mutex; +use embassy_usb::{msos, Builder}; +use embassy_usb_dfu::consts::DfuAttributes; +use embassy_usb_dfu::{usb_dfu, Control, ResetImmediate}; + +bind_interrupts!(struct Irqs { + USB_LP => usb::InterruptHandler; +}); + +// 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}"]; + +#[entry] +fn main() -> ! { + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + + // Prevent a hard fault when accessing flash 'too early' after boot. + #[cfg(feature = "defmt")] + for _ in 0..10000000 { + cortex_m::asm::nop(); + } + + let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); + let flash = Mutex::new(RefCell::new(layout.bank1_region)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); + let active_offset = config.active.offset(); + let bl = BootLoader::prepare::<_, _, _, 2048>(config); + if bl.state == State::DfuDetach { + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-DFU Bootloader example"); + config.serial_number = Some("1235678"); + + let fw_config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + let mut buffer = AlignedBuffer([0; WRITE_SIZE]); + let updater = BlockingFirmwareUpdater::new(fw_config, &mut buffer.0[..]); + + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 4096]; + let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD); + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], + &mut control_buf, + ); + + // We add MSOS headers so that the device automatically gets assigned the WinUSB driver on Windows. + // Otherwise users need to do this manually using a tool like Zadig. + // + // It seems it is important for the DFU class that these headers be on the Device level. + // + builder.msos_descriptor(msos::windows_version::WIN8_1, 2); + builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + usb_dfu::<_, _, _, ResetImmediate, 4096>(&mut builder, &mut state); + + let mut dev = builder.build(); + embassy_futures::block_on(dev.run()); + } + + unsafe { bl.load(BANK1_REGION.base + active_offset) } +} + +#[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/embassy/examples/lpc55s69/.cargo/config.toml b/embassy/examples/lpc55s69/.cargo/config.toml new file mode 100644 index 0000000..9556de7 --- /dev/null +++ b/embassy/examples/lpc55s69/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip LPC55S69JBD100" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "debug" diff --git a/embassy/examples/lpc55s69/Cargo.toml b/embassy/examples/lpc55s69/Cargo.toml new file mode 100644 index 0000000..41a88f0 --- /dev/null +++ b/embassy/examples/lpc55s69/Cargo.toml @@ -0,0 +1,22 @@ +[package] +edition = "2021" +name = "embassy-nxp-lpc55s69-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["rt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt"] } +panic-halt = "0.2.0" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = {version = "0.7.0"} +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3.2", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[profile.release] +debug = 2 diff --git a/embassy/examples/lpc55s69/build.rs b/embassy/examples/lpc55s69/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/lpc55s69/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/embassy/examples/lpc55s69/memory.x b/embassy/examples/lpc55s69/memory.x new file mode 100644 index 0000000..1483b2f --- /dev/null +++ b/embassy/examples/lpc55s69/memory.x @@ -0,0 +1,28 @@ +/* File originally from lpc55-hal repo: https://github.com/lpc55/lpc55-hal/blob/main/memory.x */ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 512K + + /* for use with standard link.x */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + + /* would be used with proper link.x */ + /* needs changes to r0 (initialization code) */ + /* SRAM0 : ORIGIN = 0x20000000, LENGTH = 64K */ + /* SRAM1 : ORIGIN = 0x20010000, LENGTH = 64K */ + /* SRAM2 : ORIGIN = 0x20020000, LENGTH = 64K */ + /* SRAM3 : ORIGIN = 0x20030000, LENGTH = 64K */ + + /* CASPER SRAM regions */ + /* SRAMX0: ORIGIN = 0x1400_0000, LENGTH = 4K /1* to 0x1400_0FFF *1/ */ + /* SRAMX1: ORIGIN = 0x1400_4000, LENGTH = 4K /1* to 0x1400_4FFF *1/ */ + + /* USB1 SRAM regin */ + /* USB1_SRAM : ORIGIN = 0x40100000, LENGTH = 16K */ + + /* To define our own USB RAM section in one regular */ + /* RAM, probably easiest to shorten length of RAM */ + /* above, and use this freed RAM section */ + +} + diff --git a/embassy/examples/lpc55s69/src/bin/blinky_nop.rs b/embassy/examples/lpc55s69/src/bin/blinky_nop.rs new file mode 100644 index 0000000..58e2d98 --- /dev/null +++ b/embassy/examples/lpc55s69/src/bin/blinky_nop.rs @@ -0,0 +1,33 @@ +//! This example has been made with the LPCXpresso55S69 board in mind, which has a built-in LED on PIO1_6. + +#![no_std] +#![no_main] + +use cortex_m::asm::nop; +use defmt::*; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Level, Output}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nxp::init(Default::default()); + + let mut led = Output::new(p.PIO1_6, Level::Low); + + loop { + info!("led off!"); + led.set_high(); + + for _ in 0..200_000 { + nop(); + } + + info!("led on!"); + led.set_low(); + + for _ in 0..200_000 { + nop(); + } + } +} diff --git a/embassy/examples/lpc55s69/src/bin/button_executor.rs b/embassy/examples/lpc55s69/src/bin/button_executor.rs new file mode 100644 index 0000000..836b1c9 --- /dev/null +++ b/embassy/examples/lpc55s69/src/bin/button_executor.rs @@ -0,0 +1,25 @@ +//! This example has been made with the LPCXpresso55S69 board in mind, which has a built-in LED on +//! PIO1_6 and a button (labeled "user") on PIO1_9. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_nxp::init(Default::default()); + + let mut led = Output::new(p.PIO1_6, Level::Low); + let mut button = Input::new(p.PIO1_9, Pull::Up); + + info!("Entered main loop"); + loop { + button.wait_for_rising_edge().await; + info!("Button pressed"); + led.toggle(); + } +} diff --git a/embassy/examples/nrf-rtos-trace/.cargo/config.toml b/embassy/examples/nrf-rtos-trace/.cargo/config.toml new file mode 100644 index 0000000..17616a0 --- /dev/null +++ b/embassy/examples/nrf-rtos-trace/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF52840_xxAA" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/nrf-rtos-trace/Cargo.toml b/embassy/examples/nrf-rtos-trace/Cargo.toml new file mode 100644 index 0000000..6d13d66 --- /dev/null +++ b/embassy/examples/nrf-rtos-trace/Cargo.toml @@ -0,0 +1,37 @@ +[package] +edition = "2021" +name = "embassy-nrf-rtos-trace-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[features] +default = ["log"] +log = [ + "dep:log", + "embassy-sync/log", + "embassy-executor/log", + "embassy-time/log", + "embassy-nrf/log", +] + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time" } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3" } +rand = { version = "0.8.4", default-features = false } +serde = { version = "1.0.136", default-features = false } +rtos-trace = "0.1.3" +systemview-target = { version = "0.1.2", features = ["callbacks-app", "callbacks-os", "log", "cortex-m"] } +log = { version = "0.4.17", optional = true } + +[[bin]] +name = "rtos_trace" + + +[profile.release] +debug = 2 diff --git a/embassy/examples/nrf-rtos-trace/build.rs b/embassy/examples/nrf-rtos-trace/build.rs new file mode 100644 index 0000000..36cdb65 --- /dev/null +++ b/embassy/examples/nrf-rtos-trace/build.rs @@ -0,0 +1,36 @@ +//! 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"); + #[cfg(feature = "defmt")] + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/embassy/examples/nrf-rtos-trace/memory.x b/embassy/examples/nrf-rtos-trace/memory.x new file mode 100644 index 0000000..9b04ede --- /dev/null +++ b/embassy/examples/nrf-rtos-trace/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/embassy/examples/nrf-rtos-trace/src/bin/rtos_trace.rs b/embassy/examples/nrf-rtos-trace/src/bin/rtos_trace.rs new file mode 100644 index 0000000..41cc064 --- /dev/null +++ b/embassy/examples/nrf-rtos-trace/src/bin/rtos_trace.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] + +use core::future::poll_fn; +use core::task::Poll; + +use embassy_executor::Spawner; +use embassy_time::{Instant, Timer}; +#[cfg(feature = "log")] +use log::*; +use panic_probe as _; +// N.B. systemview_target cannot be used at the same time as defmt_rtt. +use rtos_trace; +use systemview_target::SystemView; + +static LOGGER: systemview_target::SystemView = systemview_target::SystemView::new(); +rtos_trace::global_trace! {SystemView} + +struct TraceInfo(); + +impl rtos_trace::RtosTraceApplicationCallbacks for TraceInfo { + fn system_description() {} + fn sysclock() -> u32 { + 64000000 + } +} +rtos_trace::global_application_callbacks! {TraceInfo} + +#[embassy_executor::task] +async fn run1() { + loop { + #[cfg(feature = "log")] + info!("DING DONG"); + #[cfg(not(feature = "log"))] + rtos_trace::trace::marker(13); + Timer::after_ticks(16000).await; + } +} + +#[embassy_executor::task] +async fn run2() { + loop { + Timer::at(Instant::from_ticks(0)).await; + } +} + +#[embassy_executor::task] +async fn run3() { + poll_fn(|cx| { + cx.waker().wake_by_ref(); + Poll::<()>::Pending + }) + .await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let _p = embassy_nrf::init(Default::default()); + LOGGER.init(); + #[cfg(feature = "log")] + { + ::log::set_logger(&LOGGER).ok(); + ::log::set_max_level(::log::LevelFilter::Trace); + } + + spawner.spawn(run1()).unwrap(); + spawner.spawn(run2()).unwrap(); + spawner.spawn(run3()).unwrap(); +} diff --git a/embassy/examples/nrf51/.cargo/config.toml b/embassy/examples/nrf51/.cargo/config.toml new file mode 100644 index 0000000..1671f5d --- /dev/null +++ b/embassy/examples/nrf51/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF51422_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF51422_xxAA" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/nrf51/Cargo.toml b/embassy/examples/nrf51/Cargo.toml new file mode 100644 index 0000000..8d995cf --- /dev/null +++ b/embassy/examples/nrf51/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "embassy-nrf51-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf51", "gpiote", "time-driver-rtc1", "unstable-pac", "time", "rt"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/embassy/examples/nrf51/build.rs b/embassy/examples/nrf51/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/nrf51/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/embassy/examples/nrf51/memory.x b/embassy/examples/nrf51/memory.x new file mode 100644 index 0000000..98b3c79 --- /dev/null +++ b/embassy/examples/nrf51/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 128K + RAM : ORIGIN = 0x20000000, LENGTH = 16K +} diff --git a/embassy/examples/nrf51/src/bin/blinky.rs b/embassy/examples/nrf51/src/bin/blinky.rs new file mode 100644 index 0000000..7c12ffc --- /dev/null +++ b/embassy/examples/nrf51/src/bin/blinky.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P0_21, Level::Low, OutputDrive::Standard); + + loop { + led.set_high(); + Timer::after_millis(300).await; + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/nrf52810/.cargo/config.toml b/embassy/examples/nrf52810/.cargo/config.toml new file mode 100644 index 0000000..917a536 --- /dev/null +++ b/embassy/examples/nrf52810/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF82810_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF52810_xxAA" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/nrf52810/Cargo.toml b/embassy/examples/nrf52810/Cargo.toml new file mode 100644 index 0000000..fa2a27a --- /dev/null +++ b/embassy/examples/nrf52810/Cargo.toml @@ -0,0 +1,24 @@ +[package] +edition = "2021" +name = "embassy-nrf52810-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52810", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +fixed = "1.10.0" +static_cell = { version = "2" } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/embassy/examples/nrf52810/build.rs b/embassy/examples/nrf52810/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/nrf52810/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/embassy/examples/nrf52810/memory.x b/embassy/examples/nrf52810/memory.x new file mode 100644 index 0000000..7cf560e --- /dev/null +++ b/embassy/examples/nrf52810/memory.x @@ -0,0 +1,7 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 24K + +} diff --git a/embassy/examples/nrf52810/src/bin/blinky.rs b/embassy/examples/nrf52810/src/bin/blinky.rs new file mode 100644 index 0000000..1da039f --- /dev/null +++ b/embassy/examples/nrf52810/src/bin/blinky.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P0_18, Level::Low, OutputDrive::Standard); + + loop { + led.set_high(); + Timer::after_millis(300).await; + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/nrf52840-rtic/.cargo/config.toml b/embassy/examples/nrf52840-rtic/.cargo/config.toml new file mode 100644 index 0000000..17616a0 --- /dev/null +++ b/embassy/examples/nrf52840-rtic/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF52840_xxAA" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/nrf52840-rtic/Cargo.toml b/embassy/examples/nrf52840-rtic/Cargo.toml new file mode 100644 index 0000000..6b15b24 --- /dev/null +++ b/embassy/examples/nrf52840-rtic/Cargo.toml @@ -0,0 +1,24 @@ +[package] +edition = "2021" +name = "embassy-nrf52840-rtic-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +rtic = { version = "2", features = ["thumbv7-backend"] } + +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = [ "defmt", "defmt-timestamp-uptime"] } +embassy-time-queue-driver = { version = "0.1.0", path = "../../embassy-time-queue-driver", features = ["generic-queue-8"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = [ "defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/embassy/examples/nrf52840-rtic/build.rs b/embassy/examples/nrf52840-rtic/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/nrf52840-rtic/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/embassy/examples/nrf52840-rtic/memory.x b/embassy/examples/nrf52840-rtic/memory.x new file mode 100644 index 0000000..15b492b --- /dev/null +++ b/embassy/examples/nrf52840-rtic/memory.x @@ -0,0 +1,12 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K + + /* These values correspond to the NRF52840 with Softdevices S140 7.3.0 */ + /* + FLASH : ORIGIN = 0x00027000, LENGTH = 868K + RAM : ORIGIN = 0x20020000, LENGTH = 128K + */ +} diff --git a/embassy/examples/nrf52840-rtic/src/bin/blinky.rs b/embassy/examples/nrf52840-rtic/src/bin/blinky.rs new file mode 100644 index 0000000..5a074ea --- /dev/null +++ b/embassy/examples/nrf52840-rtic/src/bin/blinky.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use {defmt_rtt as _, panic_probe as _}; + +#[rtic::app(device = embassy_nrf, peripherals = false, dispatchers = [EGU0_SWI0, EGU1_SWI1])] +mod app { + use defmt::info; + use embassy_nrf::gpio::{Level, Output, OutputDrive}; + use embassy_nrf::peripherals; + use embassy_time::Timer; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + info!("Hello World!"); + + let p = embassy_nrf::init(Default::default()); + blink::spawn(p.P0_13).map_err(|_| ()).unwrap(); + + (Shared {}, Local {}) + } + + #[task(priority = 1)] + async fn blink(_cx: blink::Context, pin: peripherals::P0_13) { + let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); + + loop { + info!("off!"); + led.set_high(); + Timer::after_millis(300).await; + info!("on!"); + led.set_low(); + Timer::after_millis(300).await; + } + } +} diff --git a/embassy/examples/nrf52840/.cargo/config.toml b/embassy/examples/nrf52840/.cargo/config.toml new file mode 100644 index 0000000..17616a0 --- /dev/null +++ b/embassy/examples/nrf52840/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF52840_xxAA" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/nrf52840/Cargo.toml b/embassy/examples/nrf52840/Cargo.toml new file mode 100644 index 0000000..fa29d52 --- /dev/null +++ b/embassy/examples/nrf52840/Cargo.toml @@ -0,0 +1,39 @@ +[package] +edition = "2021" +name = "embassy-nrf52840-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embedded-io = { version = "0.6.0", features = ["defmt-03"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } +embassy-net-enc28j60 = { version = "0.1.0", path = "../../embassy-net-enc28j60", features = ["defmt"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +fixed = "1.10.0" +static_cell = { version = "2" } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +rand = { version = "0.8.4", default-features = false } +embedded-storage = "0.3.1" +usbd-hid = "0.8.1" +serde = { version = "1.0.136", default-features = false } +embedded-hal = { version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } +num-integer = { version = "0.1.45", default-features = false } +microfft = "0.5.0" + +[profile.release] +debug = 2 diff --git a/embassy/examples/nrf52840/build.rs b/embassy/examples/nrf52840/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/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/embassy/examples/nrf52840/memory.x b/embassy/examples/nrf52840/memory.x new file mode 100644 index 0000000..15b492b --- /dev/null +++ b/embassy/examples/nrf52840/memory.x @@ -0,0 +1,12 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K + + /* These values correspond to the NRF52840 with Softdevices S140 7.3.0 */ + /* + FLASH : ORIGIN = 0x00027000, LENGTH = 868K + RAM : ORIGIN = 0x20020000, LENGTH = 128K + */ +} diff --git a/embassy/examples/nrf52840/src/bin/blinky.rs b/embassy/examples/nrf52840/src/bin/blinky.rs new file mode 100644 index 0000000..58a3d2c --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/blinky.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard); + + loop { + led.set_high(); + Timer::after_millis(300).await; + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/nrf52840/src/bin/buffered_uart.rs b/embassy/examples/nrf52840/src/bin/buffered_uart.rs new file mode 100644 index 0000000..77d0179 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/buffered_uart.rs @@ -0,0 +1,53 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::buffered_uarte::{self, BufferedUarte}; +use embassy_nrf::{bind_interrupts, peripherals, uarte}; +use embedded_io_async::Write; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UARTE0 => buffered_uarte::InterruptHandler; +}); + +#[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 mut tx_buffer = [0u8; 4096]; + let mut rx_buffer = [0u8; 4096]; + + let mut u = BufferedUarte::new( + p.UARTE0, + p.TIMER0, + p.PPI_CH0, + p.PPI_CH1, + p.PPI_GROUP0, + Irqs, + p.P0_08, + p.P0_06, + config, + &mut rx_buffer, + &mut tx_buffer, + ); + + info!("uarte initialized!"); + + unwrap!(u.write_all(b"Hello!\r\n").await); + info!("wrote hello in uart!"); + + loop { + info!("reading..."); + let buf = unwrap!(u.fill_buf().await); + info!("read done, got {}", buf); + + // Read bytes have to be explicitly consumed, otherwise fill_buf() will return them again + let n = buf.len(); + u.consume(n); + } +} diff --git a/embassy/examples/nrf52840/src/bin/channel.rs b/embassy/examples/nrf52840/src/bin/channel.rs new file mode 100644 index 0000000..e06ba1c --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/channel.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +use defmt::unwrap; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::Channel; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +enum LedState { + On, + Off, +} + +static CHANNEL: Channel = Channel::new(); + +#[embassy_executor::task] +async fn my_task() { + loop { + CHANNEL.send(LedState::On).await; + Timer::after_secs(1).await; + CHANNEL.send(LedState::Off).await; + Timer::after_secs(1).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard); + + unwrap!(spawner.spawn(my_task())); + + loop { + match CHANNEL.receive().await { + LedState::On => led.set_low(), + LedState::Off => led.set_high(), + } + } +} diff --git a/embassy/examples/nrf52840/src/bin/channel_sender_receiver.rs b/embassy/examples/nrf52840/src/bin/channel_sender_receiver.rs new file mode 100644 index 0000000..29f70f9 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/channel_sender_receiver.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use defmt::unwrap; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::channel::{Channel, Receiver, Sender}; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +enum LedState { + On, + Off, +} + +static CHANNEL: StaticCell> = StaticCell::new(); + +#[embassy_executor::task] +async fn send_task(sender: Sender<'static, NoopRawMutex, LedState, 1>) { + loop { + sender.send(LedState::On).await; + Timer::after_secs(1).await; + sender.send(LedState::Off).await; + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn recv_task(led: AnyPin, receiver: Receiver<'static, NoopRawMutex, LedState, 1>) { + let mut led = Output::new(led, Level::Low, OutputDrive::Standard); + + loop { + match receiver.receive().await { + LedState::On => led.set_low(), + LedState::Off => led.set_high(), + } + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let channel = CHANNEL.init(Channel::new()); + + unwrap!(spawner.spawn(send_task(channel.sender()))); + unwrap!(spawner.spawn(recv_task(p.P0_13.degrade(), channel.receiver()))); +} diff --git a/embassy/examples/nrf52840/src/bin/egu.rs b/embassy/examples/nrf52840/src/bin/egu.rs new file mode 100644 index 0000000..8bf7126 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/egu.rs @@ -0,0 +1,43 @@ +//! This example shows the use of the EGU peripheral combined with PPI. +//! +//! It chains events from button -> egu0-trigger0 -> egu0-trigger1 -> led +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::egu::{Egu, TriggerNumber}; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; +use embassy_nrf::gpiote::{InputChannel, InputChannelPolarity, OutputChannel, OutputChannelPolarity}; +use embassy_nrf::peripherals::{PPI_CH0, PPI_CH1, PPI_CH2}; +use embassy_nrf::ppi::Ppi; +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 led1 = Output::new(p.P0_13, Level::High, OutputDrive::Standard); + let btn1 = Input::new(p.P0_11, Pull::Up); + + let mut egu1 = Egu::new(p.EGU0); + let led1 = OutputChannel::new(p.GPIOTE_CH0, led1, OutputChannelPolarity::Toggle); + let btn1 = InputChannel::new(p.GPIOTE_CH1, btn1, InputChannelPolarity::LoToHi); + + let trigger0 = egu1.trigger(TriggerNumber::Trigger0); + let trigger1 = egu1.trigger(TriggerNumber::Trigger1); + + let mut ppi1: Ppi = Ppi::new_one_to_one(p.PPI_CH0, btn1.event_in(), trigger0.task()); + ppi1.enable(); + + let mut ppi2: Ppi = Ppi::new_one_to_one(p.PPI_CH1, trigger0.event(), trigger1.task()); + ppi2.enable(); + + let mut ppi3: Ppi = Ppi::new_one_to_one(p.PPI_CH2, trigger1.event(), led1.task_out()); + ppi3.enable(); + + defmt::info!("Push the button to toggle the LED"); + loop { + Timer::after(Duration::from_secs(60)).await; + } +} diff --git a/embassy/examples/nrf52840/src/bin/ethernet_enc28j60.rs b/embassy/examples/nrf52840/src/bin/ethernet_enc28j60.rs new file mode 100644 index 0000000..0946492 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/ethernet_enc28j60.rs @@ -0,0 +1,117 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::StackResources; +use embassy_net_enc28j60::Enc28j60; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::rng::Rng; +use embassy_nrf::spim::Spim; +use embassy_nrf::{bind_interrupts, peripherals, spim}; +use embassy_time::Delay; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_io_async::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; + RNG => embassy_nrf::rng::InterruptHandler; +}); + +#[embassy_executor::task] +async fn net_task( + mut runner: embassy_net::Runner< + 'static, + Enc28j60, Output<'static>, Delay>, Output<'static>>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("running!"); + + let eth_sck = p.P0_20; + let eth_mosi = p.P0_22; + let eth_miso = p.P0_24; + let eth_cs = p.P0_15; + let eth_rst = p.P0_13; + let _eth_irq = p.P0_12; + + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M16; + let spi = spim::Spim::new(p.SPI3, Irqs, eth_sck, eth_miso, eth_mosi, config); + let cs = Output::new(eth_cs, Level::High, OutputDrive::Standard); + let spi = ExclusiveDevice::new(spi, cs, Delay); + + let rst = Output::new(eth_rst, Level::High, OutputDrive::Standard); + let mac_addr = [2, 3, 4, 5, 6, 7]; + let device = Enc28j60::new(spi, Some(rst), mac_addr); + + let config = embassy_net::Config::dhcpv4(Default::default()); + // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + // }); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + // 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_time::Duration::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/embassy/examples/nrf52840/src/bin/executor_fairness_test.rs b/embassy/examples/nrf52840/src/bin/executor_fairness_test.rs new file mode 100644 index 0000000..df6e7af --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/executor_fairness_test.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +use core::future::poll_fn; +use core::task::Poll; + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_time::{Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn run1() { + loop { + info!("DING DONG"); + Timer::after_ticks(16000).await; + } +} + +#[embassy_executor::task] +async fn run2() { + loop { + Timer::at(Instant::from_ticks(0)).await; + } +} + +#[embassy_executor::task] +async fn run3() { + poll_fn(|cx| { + cx.waker().wake_by_ref(); + Poll::<()>::Pending + }) + .await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let _p = embassy_nrf::init(Default::default()); + unwrap!(spawner.spawn(run1())); + unwrap!(spawner.spawn(run2())); + unwrap!(spawner.spawn(run3())); +} diff --git a/embassy/examples/nrf52840/src/bin/gpiote_channel.rs b/embassy/examples/nrf52840/src/bin/gpiote_channel.rs new file mode 100644 index 0000000..dcfe772 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/gpiote_channel.rs @@ -0,0 +1,65 @@ +#![no_std] +#![no_main] + +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_11, Pull::Up), + InputChannelPolarity::HiToLo, + ); + let ch2 = InputChannel::new( + p.GPIOTE_CH1, + Input::new(p.P0_12, Pull::Up), + InputChannelPolarity::LoToHi, + ); + let ch3 = InputChannel::new( + p.GPIOTE_CH2, + Input::new(p.P0_24, Pull::Up), + InputChannelPolarity::Toggle, + ); + let ch4 = InputChannel::new( + p.GPIOTE_CH3, + Input::new(p.P0_25, 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") + } + }; + + embassy_futures::join::join4(button1, button2, button3, button4).await; +} diff --git a/embassy/examples/nrf52840/src/bin/gpiote_port.rs b/embassy/examples/nrf52840/src/bin/gpiote_port.rs new file mode 100644 index 0000000..0dddb1a --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/gpiote_port.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Input, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task(pool_size = 4)] +async fn button_task(n: usize, mut pin: Input<'static>) { + loop { + pin.wait_for_low().await; + info!("Button {:?} pressed!", n); + pin.wait_for_high().await; + info!("Button {:?} released!", n); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("Starting!"); + + let btn1 = Input::new(p.P0_11, Pull::Up); + let btn2 = Input::new(p.P0_12, Pull::Up); + let btn3 = Input::new(p.P0_24, Pull::Up); + let btn4 = Input::new(p.P0_25, Pull::Up); + + unwrap!(spawner.spawn(button_task(1, btn1))); + unwrap!(spawner.spawn(button_task(2, btn2))); + unwrap!(spawner.spawn(button_task(3, btn3))); + unwrap!(spawner.spawn(button_task(4, btn4))); +} diff --git a/embassy/examples/nrf52840/src/bin/i2s_effect.rs b/embassy/examples/nrf52840/src/bin/i2s_effect.rs new file mode 100644 index 0000000..9eadeb4 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/i2s_effect.rs @@ -0,0 +1,115 @@ +#![no_std] +#![no_main] + +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::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_BUFFERS: usize = 2; +const NUM_SAMPLES: usize = 4; + +bind_interrupts!(struct Irqs { + I2S => i2s::InterruptHandler; +}); + +#[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 buffers_out = MultiBuffering::::new(); + let buffers_in = MultiBuffering::::new(); + let mut full_duplex_stream = I2S::new_master(p.I2S, Irqs, 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/embassy/examples/nrf52840/src/bin/i2s_monitor.rs b/embassy/examples/nrf52840/src/bin/i2s_monitor.rs new file mode 100644 index 0000000..799be35 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/i2s_monitor.rs @@ -0,0 +1,117 @@ +#![no_std] +#![no_main] + +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::pwm::{Prescaler, SimplePwm}; +use embassy_nrf::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_SAMPLES: usize = 500; + +bind_interrupts!(struct Irqs { + I2S => i2s::InterruptHandler; +}); + +#[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 buffers = DoubleBuffering::::new(); + let mut input_stream = + I2S::new_master(p.I2S, Irqs, 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/embassy/examples/nrf52840/src/bin/i2s_waveform.rs b/embassy/examples/nrf52840/src/bin/i2s_waveform.rs new file mode 100644 index 0000000..137d828 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/i2s_waveform.rs @@ -0,0 +1,153 @@ +#![no_std] +#![no_main] + +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::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_SAMPLES: usize = 50; + +bind_interrupts!(struct Irqs { + I2S => i2s::InterruptHandler; +}); + +#[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 buffers = DoubleBuffering::::new(); + let mut output_stream = + I2S::new_master(p.I2S, Irqs, 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/embassy/examples/nrf52840/src/bin/manually_create_executor.rs b/embassy/examples/nrf52840/src/bin/manually_create_executor.rs new file mode 100644 index 0000000..7ca3934 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/manually_create_executor.rs @@ -0,0 +1,48 @@ +// This example showcases how to manually create an executor. +// This is what the #[embassy::main] macro does behind the scenes. + +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::{info, unwrap}; +use embassy_executor::Executor; +use embassy_time::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_ticks(64000).await; + } +} + +#[embassy_executor::task] +async fn run2() { + loop { + info!("tick"); + Timer::after_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/embassy/examples/nrf52840/src/bin/multiprio.rs b/embassy/examples/nrf52840/src/bin/multiprio.rs new file mode 100644 index 0000000..d58613d --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/multiprio.rs @@ -0,0 +1,145 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::{info, unwrap}; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_nrf::interrupt; +use embassy_nrf::interrupt::{InterruptExt, Priority}; +use embassy_time::{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_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 + embassy_time::block_for(embassy_time::Duration::from_secs(1)); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!(" [med] done in {} ms", ms); + + Timer::after_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 + embassy_time::block_for(embassy_time::Duration::from_secs(2)); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!("[low] done in {} ms", ms); + + Timer::after_ticks(32983).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[interrupt] +unsafe fn EGU1_SWI1() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn EGU0_SWI0() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_nrf::init(Default::default()); + + // High-priority executor: EGU1_SWI1, priority level 6 + interrupt::EGU1_SWI1.set_priority(Priority::P6); + let spawner = EXECUTOR_HIGH.start(interrupt::EGU1_SWI1); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: EGU0_SWI0, priority level 7 + interrupt::EGU0_SWI0.set_priority(Priority::P7); + let spawner = EXECUTOR_MED.start(interrupt::EGU0_SWI0); + unwrap!(spawner.spawn(run_med())); + + // Low priority executor: runs in thread mode, using WFE/SEV + let executor = EXECUTOR_LOW.init(Executor::new()); + executor.run(|spawner| { + unwrap!(spawner.spawn(run_low())); + }); +} diff --git a/embassy/examples/nrf52840/src/bin/mutex.rs b/embassy/examples/nrf52840/src/bin/mutex.rs new file mode 100644 index 0000000..5c22279 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/mutex.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +static MUTEX: Mutex = Mutex::new(0); + +#[embassy_executor::task] +async fn my_task() { + loop { + { + let mut m = MUTEX.lock().await; + info!("start long operation"); + *m += 1000; + + // Hold the mutex for a long time. + Timer::after_secs(1).await; + info!("end long operation: count = {}", *m); + } + + Timer::after_secs(1).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let _p = embassy_nrf::init(Default::default()); + unwrap!(spawner.spawn(my_task())); + + loop { + Timer::after_millis(300).await; + let mut m = MUTEX.lock().await; + *m += 1; + info!("short operation: count = {}", *m); + } +} diff --git a/embassy/examples/nrf52840/src/bin/nfct.rs b/embassy/examples/nrf52840/src/bin/nfct.rs new file mode 100644 index 0000000..d559d00 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/nfct.rs @@ -0,0 +1,79 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::config::HfclkSource; +use embassy_nrf::nfct::{Config as NfcConfig, NfcId, NfcT}; +use embassy_nrf::{bind_interrupts, nfct}; +use {defmt_rtt as _, embassy_nrf as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + NFCT => nfct::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_nrf::config::Config::default(); + config.hfclk_source = HfclkSource::ExternalXtal; + let p = embassy_nrf::init(config); + + dbg!("Setting up..."); + let config = NfcConfig { + nfcid1: NfcId::DoubleSize([0x04, 0x68, 0x95, 0x71, 0xFA, 0x5C, 0x64]), + sdd_pat: nfct::SddPat::SDD00100, + plat_conf: 0b0000, + protocol: nfct::SelResProtocol::Type4A, + }; + + let mut nfc = NfcT::new(p.NFCT, Irqs, &config); + + let mut buf = [0u8; 256]; + + loop { + info!("activating"); + nfc.activate().await; + + loop { + info!("rxing"); + let n = match nfc.receive(&mut buf).await { + Ok(n) => n, + Err(e) => { + error!("rx error {}", e); + break; + } + }; + let req = &buf[..n]; + info!("received frame {:02x}", req); + + let mut deselect = false; + let resp = match req { + [0xe0, ..] => { + info!("Got RATS, tx'ing ATS"); + &[0x06, 0x77, 0x77, 0x81, 0x02, 0x80][..] + } + [0xc2] => { + info!("Got deselect!"); + deselect = true; + &[0xc2] + } + _ => { + info!("Got unknown command!"); + &[0xFF] + } + }; + + match nfc.transmit(resp).await { + Ok(()) => {} + Err(e) => { + error!("tx error {}", e); + break; + } + } + + if deselect { + break; + } + } + } +} diff --git a/embassy/examples/nrf52840/src/bin/nvmc.rs b/embassy/examples/nrf52840/src/bin/nvmc.rs new file mode 100644 index 0000000..a79385b --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/nvmc.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_nrf::nvmc::Nvmc; +use embassy_time::Timer; +use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("Hello NVMC!"); + + // probe-rs run breaks without this, I'm not sure why. + Timer::after_secs(1).await; + + let mut f = Nvmc::new(p.NVMC); + const ADDR: u32 = 0x80000; + + info!("Reading..."); + let mut buf = [0u8; 4]; + unwrap!(f.read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.erase(ADDR, ADDR + 4096)); + + info!("Reading..."); + let mut buf = [0u8; 4]; + unwrap!(f.read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.write(ADDR, &[1, 2, 3, 4])); + + info!("Reading..."); + let mut buf = [0u8; 4]; + unwrap!(f.read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); +} diff --git a/embassy/examples/nrf52840/src/bin/pdm.rs b/embassy/examples/nrf52840/src/bin/pdm.rs new file mode 100644 index 0000000..52dadc8 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/pdm.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::pdm::{self, Config, Pdm}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use fixed::types::I7F1; +use num_integer::Roots; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PDM => pdm::InterruptHandler; +}); + +#[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, Irqs, p.P0_01, p.P0_00, config); + + loop { + for gain in [I7F1::from_num(-20), I7F1::from_num(0), I7F1::from_num(20)] { + pdm.set_gain(gain, gain); + info!("Gain = {} dB", defmt::Debug2Format(&gain)); + pdm.start().await; + + // wait some time till the microphon settled + Timer::after_millis(1000).await; + + const SAMPLES: usize = 2048; + let mut buf = [0i16; SAMPLES]; + pdm.sample(&mut buf).await.unwrap(); + + let mean = (buf.iter().map(|v| i32::from(*v)).sum::() / buf.len() as i32) as i16; + info!( + "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}", + buf.len(), + buf.iter().min().unwrap(), + buf.iter().max().unwrap(), + mean, + (buf.iter() + .map(|v| i32::from(*v - mean).pow(2)) + .fold(0i32, |a, b| a.saturating_add(b)) + / buf.len() as i32) + .sqrt() as i16, + ); + + info!("samples: {:?}", &buf); + + pdm.stop().await; + Timer::after_millis(100).await; + } + } +} diff --git a/embassy/examples/nrf52840/src/bin/pdm_continuous.rs b/embassy/examples/nrf52840/src/bin/pdm_continuous.rs new file mode 100644 index 0000000..e948203 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/pdm_continuous.rs @@ -0,0 +1,80 @@ +#![no_std] +#![no_main] + +use core::cmp::Ordering; + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::pdm::{self, Config, Frequency, OperationMode, Pdm, Ratio, SamplerState}; +use embassy_nrf::{bind_interrupts, peripherals}; +use fixed::types::I7F1; +use microfft::real::rfft_1024; +use num_integer::Roots; +use {defmt_rtt as _, panic_probe as _}; + +// Demonstrates both continuous sampling and scanning multiple channels driven by a PPI linked timer + +bind_interrupts!(struct Irqs { + PDM => pdm::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_p: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let mut config = Config::default(); + // Pins are correct for the onboard microphone on the Feather nRF52840 Sense. + config.frequency = Frequency::_1280K; // 16 kHz sample rate + config.ratio = Ratio::RATIO80; + config.operation_mode = OperationMode::Mono; + config.gain_left = I7F1::from_bits(5); // 2.5 dB + let mut pdm = Pdm::new(p.PDM, Irqs, &mut p.P0_00, &mut p.P0_01, config); + + let mut bufs = [[0; 1024]; 2]; + + pdm.run_task_sampler(&mut bufs, move |buf| { + // NOTE: It is important that the time spent within this callback + // does not exceed the time taken to acquire the 1500 samples we + // have in this example, which would be 10us + 2us per + // sample * 1500 = 18ms. You need to measure the time taken here + // and set the sample buffer size accordingly. Exceeding this + // time can lead to the peripheral re-writing the other buffer. + let mean = (buf.iter().map(|v| i32::from(*v)).sum::() / buf.len() as i32) as i16; + let (peak_freq_index, peak_mag) = fft_peak_freq(&buf); + let peak_freq = peak_freq_index * 16000 / buf.len(); + info!( + "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}, peak {} @ {} Hz", + buf.len(), + buf.iter().min().unwrap(), + buf.iter().max().unwrap(), + mean, + (buf.iter() + .map(|v| i32::from(*v - mean).pow(2)) + .fold(0i32, |a, b| a.saturating_add(b)) + / buf.len() as i32) + .sqrt() as i16, + peak_mag, + peak_freq, + ); + SamplerState::Sampled + }) + .await + .unwrap(); +} + +fn fft_peak_freq(input: &[i16; 1024]) -> (usize, u32) { + let mut f = [0f32; 1024]; + for i in 0..input.len() { + f[i] = (input[i] as f32) / 32768.0; + } + // N.B. rfft_1024 does the FFT in-place so result is actually also a reference to f. + let result = rfft_1024(&mut f); + result[0].im = 0.0; + + result + .iter() + .map(|c| c.norm_sqr()) + .enumerate() + .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal)) + .map(|(i, v)| (i, ((v * 32768.0) as u32).sqrt())) + .unwrap() +} diff --git a/embassy/examples/nrf52840/src/bin/ppi.rs b/embassy/examples/nrf52840/src/bin/ppi.rs new file mode 100644 index 0000000..129ad06 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/ppi.rs @@ -0,0 +1,72 @@ +#![no_std] +#![no_main] + +use core::future::pending; + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; +use embassy_nrf::gpiote::{self, InputChannel, InputChannelPolarity}; +use embassy_nrf::ppi::Ppi; +use gpiote::{OutputChannel, OutputChannelPolarity}; +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 button1 = InputChannel::new( + p.GPIOTE_CH0, + Input::new(p.P0_11, Pull::Up), + InputChannelPolarity::HiToLo, + ); + let button2 = InputChannel::new( + p.GPIOTE_CH1, + Input::new(p.P0_12, Pull::Up), + InputChannelPolarity::HiToLo, + ); + let button3 = InputChannel::new( + p.GPIOTE_CH2, + Input::new(p.P0_24, Pull::Up), + InputChannelPolarity::HiToLo, + ); + let button4 = InputChannel::new( + p.GPIOTE_CH3, + Input::new(p.P0_25, Pull::Up), + InputChannelPolarity::HiToLo, + ); + + let led1 = OutputChannel::new( + p.GPIOTE_CH4, + Output::new(p.P0_13, Level::Low, OutputDrive::Standard), + OutputChannelPolarity::Toggle, + ); + + let led2 = OutputChannel::new( + p.GPIOTE_CH5, + Output::new(p.P0_14, Level::Low, OutputDrive::Standard), + OutputChannelPolarity::Toggle, + ); + + let mut ppi = Ppi::new_one_to_one(p.PPI_CH0, button1.event_in(), led1.task_out()); + ppi.enable(); + + let mut ppi = Ppi::new_one_to_one(p.PPI_CH1, button2.event_in(), led1.task_clr()); + ppi.enable(); + + let mut ppi = Ppi::new_one_to_one(p.PPI_CH2, button3.event_in(), led1.task_set()); + ppi.enable(); + + let mut ppi = Ppi::new_one_to_two(p.PPI_CH3, button4.event_in(), led1.task_out(), led2.task_out()); + ppi.enable(); + + info!("PPI setup!"); + info!("Press button 1 to toggle LED 1"); + info!("Press button 2 to turn on LED 1"); + info!("Press button 3 to turn off LED 1"); + info!("Press button 4 to toggle LEDs 1 and 2"); + + // Block forever so the above drivers don't get dropped + pending::<()>().await; +} diff --git a/embassy/examples/nrf52840/src/bin/pubsub.rs b/embassy/examples/nrf52840/src/bin/pubsub.rs new file mode 100644 index 0000000..5ebea92 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/pubsub.rs @@ -0,0 +1,106 @@ +#![no_std] +#![no_main] + +use defmt::unwrap; +use embassy_executor::Spawner; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::pubsub::{DynSubscriber, PubSubChannel, Subscriber}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +/// Create the message bus. It has a queue of 4, supports 3 subscribers and 1 publisher +static MESSAGE_BUS: PubSubChannel = PubSubChannel::new(); + +#[derive(Clone, defmt::Format)] +enum Message { + A, + B, + C, +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let _p = embassy_nrf::init(Default::default()); + defmt::info!("Hello World!"); + + // It's good to set up the subscribers before publishing anything. + // A subscriber will only yield messages that have been published after its creation. + + spawner.must_spawn(fast_logger(unwrap!(MESSAGE_BUS.subscriber()))); + spawner.must_spawn(slow_logger(unwrap!(MESSAGE_BUS.dyn_subscriber()))); + spawner.must_spawn(slow_logger_pure(unwrap!(MESSAGE_BUS.dyn_subscriber()))); + + // Get a publisher + let message_publisher = unwrap!(MESSAGE_BUS.publisher()); + // We can't get more (normal) publishers + // We can have an infinite amount of immediate publishers. They can't await a publish, only do an immediate publish + defmt::assert!(MESSAGE_BUS.publisher().is_err()); + + let mut index = 0; + loop { + Timer::after_millis(500).await; + + let message = match index % 3 { + 0 => Message::A, + 1 => Message::B, + 2..=u32::MAX => Message::C, + }; + + // We publish immediately and don't await anything. + // If the queue is full, it will cause the oldest message to not be received by some/all subscribers + message_publisher.publish_immediate(message); + + // Try to comment out the last one and uncomment this line below. + // The behaviour will change: + // - The subscribers won't miss any messages any more + // - Trying to publish now has some wait time when the queue is full + + // message_publisher.publish(message).await; + + index += 1; + } +} + +/// A logger task that just awaits the messages it receives +/// +/// This takes the generic `Subscriber`. This is most performant, but requires you to write down all of the generics +#[embassy_executor::task] +async fn fast_logger(mut messages: Subscriber<'static, ThreadModeRawMutex, Message, 4, 3, 1>) { + loop { + let message = messages.next_message().await; + defmt::info!("Received message at fast logger: {:?}", message); + } +} + +/// A logger task that awaits the messages, but also does some other work. +/// Because of this, depending on how the messages were published, the subscriber might miss some messages. +/// +/// This takes the dynamic `DynSubscriber`. This is not as performant as the generic version, but let's you ignore some of the generics. +#[embassy_executor::task] +async fn slow_logger(mut messages: DynSubscriber<'static, Message>) { + loop { + // Do some work + Timer::after_millis(2000).await; + + // If the publisher has used the `publish_immediate` function, then we may receive a lag message here + let message = messages.next_message().await; + defmt::info!("Received message at slow logger: {:?}", message); + + // If the previous one was a lag message, then we should receive the next message here immediately + let message = messages.next_message().await; + defmt::info!("Received message at slow logger: {:?}", message); + } +} + +/// Same as `slow_logger` but it ignores lag results +#[embassy_executor::task] +async fn slow_logger_pure(mut messages: DynSubscriber<'static, Message>) { + loop { + // Do some work + Timer::after_millis(2000).await; + + // Instead of receiving lags here, we just ignore that and read the next message + let message = messages.next_message_pure().await; + defmt::info!("Received message at slow logger pure: {:?}", message); + } +} diff --git a/embassy/examples/nrf52840/src/bin/pwm.rs b/embassy/examples/nrf52840/src/bin/pwm.rs new file mode 100644 index 0000000..a5bb134 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/pwm.rs @@ -0,0 +1,88 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::pwm::{Prescaler, SimplePwm}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// for i in range(1024): print(int((math.sin(i/512*math.pi)*0.4+0.5)**2*32767), ', ', end='') +static DUTY: [u16; 1024] = [ + 8191, 8272, 8353, 8434, 8516, 8598, 8681, 8764, 8847, 8931, 9015, 9099, 9184, 9269, 9354, 9440, 9526, 9613, 9700, + 9787, 9874, 9962, 10050, 10139, 10227, 10316, 10406, 10495, 10585, 10675, 10766, 10857, 10948, 11039, 11131, 11223, + 11315, 11407, 11500, 11592, 11685, 11779, 11872, 11966, 12060, 12154, 12248, 12343, 12438, 12533, 12628, 12723, + 12818, 12914, 13010, 13106, 13202, 13298, 13394, 13491, 13587, 13684, 13781, 13878, 13975, 14072, 14169, 14266, + 14364, 14461, 14558, 14656, 14754, 14851, 14949, 15046, 15144, 15242, 15339, 15437, 15535, 15632, 15730, 15828, + 15925, 16023, 16120, 16218, 16315, 16412, 16510, 16607, 16704, 16801, 16898, 16995, 17091, 17188, 17284, 17380, + 17477, 17572, 17668, 17764, 17859, 17955, 18050, 18145, 18239, 18334, 18428, 18522, 18616, 18710, 18803, 18896, + 18989, 19082, 19174, 19266, 19358, 19449, 19540, 19631, 19722, 19812, 19902, 19991, 20081, 20169, 20258, 20346, + 20434, 20521, 20608, 20695, 20781, 20867, 20952, 21037, 21122, 21206, 21290, 21373, 21456, 21538, 21620, 21701, + 21782, 21863, 21943, 22022, 22101, 22179, 22257, 22335, 22412, 22488, 22564, 22639, 22714, 22788, 22861, 22934, + 23007, 23079, 23150, 23220, 23290, 23360, 23429, 23497, 23564, 23631, 23698, 23763, 23828, 23892, 23956, 24019, + 24081, 24143, 24204, 24264, 24324, 24383, 24441, 24499, 24555, 24611, 24667, 24721, 24775, 24828, 24881, 24933, + 24983, 25034, 25083, 25132, 25180, 25227, 25273, 25319, 25363, 25407, 25451, 25493, 25535, 25575, 25615, 25655, + 25693, 25731, 25767, 25803, 25838, 25873, 25906, 25939, 25971, 26002, 26032, 26061, 26089, 26117, 26144, 26170, + 26195, 26219, 26242, 26264, 26286, 26307, 26327, 26346, 26364, 26381, 26397, 26413, 26427, 26441, 26454, 26466, + 26477, 26487, 26496, 26505, 26512, 26519, 26525, 26530, 26534, 26537, 26539, 26540, 26541, 26540, 26539, 26537, + 26534, 26530, 26525, 26519, 26512, 26505, 26496, 26487, 26477, 26466, 26454, 26441, 26427, 26413, 26397, 26381, + 26364, 26346, 26327, 26307, 26286, 26264, 26242, 26219, 26195, 26170, 26144, 26117, 26089, 26061, 26032, 26002, + 25971, 25939, 25906, 25873, 25838, 25803, 25767, 25731, 25693, 25655, 25615, 25575, 25535, 25493, 25451, 25407, + 25363, 25319, 25273, 25227, 25180, 25132, 25083, 25034, 24983, 24933, 24881, 24828, 24775, 24721, 24667, 24611, + 24555, 24499, 24441, 24383, 24324, 24264, 24204, 24143, 24081, 24019, 23956, 23892, 23828, 23763, 23698, 23631, + 23564, 23497, 23429, 23360, 23290, 23220, 23150, 23079, 23007, 22934, 22861, 22788, 22714, 22639, 22564, 22488, + 22412, 22335, 22257, 22179, 22101, 22022, 21943, 21863, 21782, 21701, 21620, 21538, 21456, 21373, 21290, 21206, + 21122, 21037, 20952, 20867, 20781, 20695, 20608, 20521, 20434, 20346, 20258, 20169, 20081, 19991, 19902, 19812, + 19722, 19631, 19540, 19449, 19358, 19266, 19174, 19082, 18989, 18896, 18803, 18710, 18616, 18522, 18428, 18334, + 18239, 18145, 18050, 17955, 17859, 17764, 17668, 17572, 17477, 17380, 17284, 17188, 17091, 16995, 16898, 16801, + 16704, 16607, 16510, 16412, 16315, 16218, 16120, 16023, 15925, 15828, 15730, 15632, 15535, 15437, 15339, 15242, + 15144, 15046, 14949, 14851, 14754, 14656, 14558, 14461, 14364, 14266, 14169, 14072, 13975, 13878, 13781, 13684, + 13587, 13491, 13394, 13298, 13202, 13106, 13010, 12914, 12818, 12723, 12628, 12533, 12438, 12343, 12248, 12154, + 12060, 11966, 11872, 11779, 11685, 11592, 11500, 11407, 11315, 11223, 11131, 11039, 10948, 10857, 10766, 10675, + 10585, 10495, 10406, 10316, 10227, 10139, 10050, 9962, 9874, 9787, 9700, 9613, 9526, 9440, 9354, 9269, 9184, 9099, + 9015, 8931, 8847, 8764, 8681, 8598, 8516, 8434, 8353, 8272, 8191, 8111, 8031, 7952, 7873, 7794, 7716, 7638, 7561, + 7484, 7407, 7331, 7255, 7180, 7105, 7031, 6957, 6883, 6810, 6738, 6665, 6594, 6522, 6451, 6381, 6311, 6241, 6172, + 6104, 6036, 5968, 5901, 5834, 5767, 5702, 5636, 5571, 5507, 5443, 5379, 5316, 5253, 5191, 5130, 5068, 5008, 4947, + 4888, 4828, 4769, 4711, 4653, 4596, 4539, 4482, 4426, 4371, 4316, 4261, 4207, 4153, 4100, 4047, 3995, 3943, 3892, + 3841, 3791, 3741, 3691, 3642, 3594, 3546, 3498, 3451, 3404, 3358, 3312, 3267, 3222, 3178, 3134, 3090, 3047, 3005, + 2962, 2921, 2879, 2839, 2798, 2758, 2719, 2680, 2641, 2603, 2565, 2528, 2491, 2454, 2418, 2382, 2347, 2312, 2278, + 2244, 2210, 2177, 2144, 2112, 2080, 2048, 2017, 1986, 1956, 1926, 1896, 1867, 1838, 1810, 1781, 1754, 1726, 1699, + 1673, 1646, 1620, 1595, 1570, 1545, 1520, 1496, 1472, 1449, 1426, 1403, 1380, 1358, 1336, 1315, 1294, 1273, 1252, + 1232, 1212, 1192, 1173, 1154, 1135, 1117, 1099, 1081, 1063, 1046, 1029, 1012, 996, 980, 964, 948, 933, 918, 903, + 888, 874, 860, 846, 833, 819, 806, 793, 781, 768, 756, 744, 733, 721, 710, 699, 688, 677, 667, 657, 647, 637, 627, + 618, 609, 599, 591, 582, 574, 565, 557, 549, 541, 534, 526, 519, 512, 505, 498, 492, 485, 479, 473, 467, 461, 455, + 450, 444, 439, 434, 429, 424, 419, 415, 410, 406, 402, 398, 394, 390, 386, 383, 379, 376, 373, 370, 367, 364, 361, + 359, 356, 354, 351, 349, 347, 345, 343, 342, 340, 338, 337, 336, 334, 333, 332, 331, 330, 330, 329, 328, 328, 328, + 327, 327, 327, 327, 327, 328, 328, 328, 329, 330, 330, 331, 332, 333, 334, 336, 337, 338, 340, 342, 343, 345, 347, + 349, 351, 354, 356, 359, 361, 364, 367, 370, 373, 376, 379, 383, 386, 390, 394, 398, 402, 406, 410, 415, 419, 424, + 429, 434, 439, 444, 450, 455, 461, 467, 473, 479, 485, 492, 498, 505, 512, 519, 526, 534, 541, 549, 557, 565, 574, + 582, 591, 599, 609, 618, 627, 637, 647, 657, 667, 677, 688, 699, 710, 721, 733, 744, 756, 768, 781, 793, 806, 819, + 833, 846, 860, 874, 888, 903, 918, 933, 948, 964, 980, 996, 1012, 1029, 1046, 1063, 1081, 1099, 1117, 1135, 1154, + 1173, 1192, 1212, 1232, 1252, 1273, 1294, 1315, 1336, 1358, 1380, 1403, 1426, 1449, 1472, 1496, 1520, 1545, 1570, + 1595, 1620, 1646, 1673, 1699, 1726, 1754, 1781, 1810, 1838, 1867, 1896, 1926, 1956, 1986, 2017, 2048, 2080, 2112, + 2144, 2177, 2210, 2244, 2278, 2312, 2347, 2382, 2418, 2454, 2491, 2528, 2565, 2603, 2641, 2680, 2719, 2758, 2798, + 2839, 2879, 2921, 2962, 3005, 3047, 3090, 3134, 3178, 3222, 3267, 3312, 3358, 3404, 3451, 3498, 3546, 3594, 3642, + 3691, 3741, 3791, 3841, 3892, 3943, 3995, 4047, 4100, 4153, 4207, 4261, 4316, 4371, 4426, 4482, 4539, 4596, 4653, + 4711, 4769, 4828, 4888, 4947, 5008, 5068, 5130, 5191, 5253, 5316, 5379, 5443, 5507, 5571, 5636, 5702, 5767, 5834, + 5901, 5968, 6036, 6104, 6172, 6241, 6311, 6381, 6451, 6522, 6594, 6665, 6738, 6810, 6883, 6957, 7031, 7105, 7180, + 7255, 7331, 7407, 7484, 7561, 7638, 7716, 7794, 7873, 7952, 8031, 8111, +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut pwm = SimplePwm::new_4ch(p.PWM0, p.P0_13, p.P0_14, p.P0_16, p.P0_15); + pwm.set_prescaler(Prescaler::Div1); + pwm.set_max_duty(32767); + info!("pwm initialized!"); + + let mut i = 0; + loop { + i += 1; + pwm.set_duty(0, DUTY[i % 1024]); + pwm.set_duty(1, DUTY[(i + 256) % 1024]); + pwm.set_duty(2, DUTY[(i + 512) % 1024]); + pwm.set_duty(3, DUTY[(i + 768) % 1024]); + Timer::after_millis(3).await; + } +} diff --git a/embassy/examples/nrf52840/src/bin/pwm_double_sequence.rs b/embassy/examples/nrf52840/src/bin/pwm_double_sequence.rs new file mode 100644 index 0000000..386c483 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/pwm_double_sequence.rs @@ -0,0 +1,40 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::pwm::{ + Config, Prescaler, Sequence, SequenceConfig, SequenceMode, SequencePwm, Sequencer, StartSequence, +}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let seq_words_0: [u16; 5] = [1000, 250, 100, 50, 0]; + let seq_words_1: [u16; 4] = [50, 100, 250, 1000]; + + let mut config = Config::default(); + config.prescaler = Prescaler::Div128; + // 1 period is 1000 * (128/16mhz = 0.000008s = 0.008ms) = 8us + // but say we want to hold the value for 5000ms + // so we want to repeat our value as many times as necessary until 5000ms passes + // want 5000/8 = 625 periods total to occur, so 624 (we get the one period for free remember) + let mut seq_config = SequenceConfig::default(); + seq_config.refresh = 624; + // thus our sequence takes 5 * 5000ms or 25 seconds + + let mut pwm = unwrap!(SequencePwm::new_1ch(p.PWM0, p.P0_13, config)); + + let sequence_0 = Sequence::new(&seq_words_0, seq_config.clone()); + let sequence_1 = Sequence::new(&seq_words_1, seq_config); + let sequencer = Sequencer::new(&mut pwm, sequence_0, Some(sequence_1)); + unwrap!(sequencer.start(StartSequence::Zero, SequenceMode::Loop(1))); + + // we can abort a sequence if we need to before its complete with pwm.stop() + // or stop is also implicitly called when the pwm peripheral is dropped + // when it goes out of scope + Timer::after_millis(40000).await; + info!("pwm stopped early!"); +} diff --git a/embassy/examples/nrf52840/src/bin/pwm_sequence.rs b/embassy/examples/nrf52840/src/bin/pwm_sequence.rs new file mode 100644 index 0000000..87eda26 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/pwm_sequence.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::pwm::{Config, Prescaler, SequenceConfig, SequencePwm, SingleSequenceMode, SingleSequencer}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; + + let mut config = Config::default(); + config.prescaler = Prescaler::Div128; + // 1 period is 1000 * (128/16mhz = 0.000008s = 0.008ms) = 8us + // but say we want to hold the value for 5000ms + // so we want to repeat our value as many times as necessary until 5000ms passes + // want 5000/8 = 625 periods total to occur, so 624 (we get the one period for free remember) + let mut seq_config = SequenceConfig::default(); + seq_config.refresh = 624; + // thus our sequence takes 5 * 5000ms or 25 seconds + + let mut pwm = unwrap!(SequencePwm::new_1ch(p.PWM0, p.P0_13, config,)); + + let sequencer = SingleSequencer::new(&mut pwm, &seq_words, seq_config); + unwrap!(sequencer.start(SingleSequenceMode::Times(1))); + + // we can abort a sequence if we need to before its complete with pwm.stop() + // or stop is also implicitly called when the pwm peripheral is dropped + // when it goes out of scope + Timer::after_millis(20000).await; + info!("pwm stopped early!"); +} diff --git a/embassy/examples/nrf52840/src/bin/pwm_sequence_ppi.rs b/embassy/examples/nrf52840/src/bin/pwm_sequence_ppi.rs new file mode 100644 index 0000000..60ea712 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/pwm_sequence_ppi.rs @@ -0,0 +1,66 @@ +#![no_std] +#![no_main] + +use core::future::pending; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Input, Pull}; +use embassy_nrf::gpiote::{InputChannel, InputChannelPolarity}; +use embassy_nrf::ppi::Ppi; +use embassy_nrf::pwm::{Config, Prescaler, SequenceConfig, SequencePwm, SingleSequenceMode, SingleSequencer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; + + let mut config = Config::default(); + config.prescaler = Prescaler::Div128; + // 1 period is 1000 * (128/16mhz = 0.000008s = 0.008ms) = 8us + // but say we want to hold the value for 250ms 250ms/8 = 31.25 periods + // so round to 31 - 1 (we get the one period for free remember) + // thus our sequence takes 5 * 250ms or 1.25 seconds + let mut seq_config = SequenceConfig::default(); + seq_config.refresh = 30; + + let mut pwm = unwrap!(SequencePwm::new_1ch(p.PWM0, p.P0_13, config)); + + // pwm.stop() deconfigures pins, and then the task_start_seq0 task cant work + // so its going to have to start running in order load the configuration + + let button1 = InputChannel::new( + p.GPIOTE_CH0, + Input::new(p.P0_11, Pull::Up), + InputChannelPolarity::HiToLo, + ); + + let button2 = InputChannel::new( + p.GPIOTE_CH1, + Input::new(p.P0_12, Pull::Up), + InputChannelPolarity::HiToLo, + ); + + // messing with the pwm tasks is ill advised + // Times::Ininite and Times even are seq0, Times odd is seq1 + let start = unsafe { pwm.task_start_seq0() }; + let stop = unsafe { pwm.task_stop() }; + + let sequencer = SingleSequencer::new(&mut pwm, &seq_words, seq_config); + unwrap!(sequencer.start(SingleSequenceMode::Infinite)); + + let mut ppi = Ppi::new_one_to_one(p.PPI_CH1, button1.event_in(), start); + ppi.enable(); + + let mut ppi2 = Ppi::new_one_to_one(p.PPI_CH0, button2.event_in(), stop); + ppi2.enable(); + + info!("PPI setup!"); + info!("Press button 1 to start LED 1"); + info!("Press button 2 to stop LED 1"); + info!("Note! task_stop stops the sequence, but not the pin output"); + + // Block forever so the above drivers don't get dropped + pending::<()>().await; +} diff --git a/embassy/examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs b/embassy/examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs new file mode 100644 index 0000000..751cf44 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs @@ -0,0 +1,74 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::pwm::{ + Config, Prescaler, SequenceConfig, SequenceLoad, SequencePwm, SingleSequenceMode, SingleSequencer, +}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// WS2812B LED light demonstration. Drives just one light. +// The following reference on WS2812B may be of use: +// https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf. +// This demo lights up a single LED in blue. It then proceeds +// to pulsate the LED rapidly. + +// In the following declarations, setting the high bit tells the PWM +// to reverse polarity, which is what the WS2812B expects. + +const T1H: u16 = 0x8000 | 13; // Duty = 13/20 ticks (0.8us/1.25us) for a 1 +const T0H: u16 = 0x8000 | 7; // Duty 7/20 ticks (0.4us/1.25us) for a 0 +const RES: u16 = 0x8000; + +// Provides data to a WS2812b (Neopixel) LED and makes it go blue. The data +// line is assumed to be P1_05. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut config = Config::default(); + config.sequence_load = SequenceLoad::Common; + config.prescaler = Prescaler::Div1; + config.max_duty = 20; // 1.25us (1s / 16Mhz * 20) + let mut pwm = unwrap!(SequencePwm::new_1ch(p.PWM0, p.P1_05, config)); + + // Declare the bits of 24 bits in a buffer we'll be + // mutating later. + let mut seq_words = [ + T0H, T0H, T0H, T0H, T0H, T0H, T0H, T0H, // G + T0H, T0H, T0H, T0H, T0H, T0H, T0H, T0H, // R + T1H, T1H, T1H, T1H, T1H, T1H, T1H, T1H, // B + RES, + ]; + let mut seq_config = SequenceConfig::default(); + seq_config.end_delay = 799; // 50us (20 ticks * 40) - 1 tick because we've already got one RES; + + let mut color_bit = 16; + let mut bit_value = T0H; + + loop { + let sequences = SingleSequencer::new(&mut pwm, &seq_words, seq_config.clone()); + unwrap!(sequences.start(SingleSequenceMode::Times(1))); + + Timer::after_millis(50).await; + + if bit_value == T0H { + if color_bit == 20 { + bit_value = T1H; + } else { + color_bit += 1; + } + } else { + if color_bit == 16 { + bit_value = T0H; + } else { + color_bit -= 1; + } + } + + drop(sequences); + + seq_words[color_bit] = bit_value; + } +} diff --git a/embassy/examples/nrf52840/src/bin/pwm_servo.rs b/embassy/examples/nrf52840/src/bin/pwm_servo.rs new file mode 100644 index 0000000..d772d2f --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/pwm_servo.rs @@ -0,0 +1,46 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::pwm::{Prescaler, SimplePwm}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut pwm = SimplePwm::new_1ch(p.PWM0, p.P0_05); + // sg90 microervo requires 50hz or 20ms period + // set_period can only set down to 125khz so we cant use it directly + // Div128 is 125khz or 0.000008s or 0.008ms, 20/0.008 is 2500 is top + pwm.set_prescaler(Prescaler::Div128); + pwm.set_max_duty(2500); + info!("pwm initialized!"); + + Timer::after_millis(5000).await; + + // 1ms 0deg (1/.008=125), 1.5ms 90deg (1.5/.008=187.5), 2ms 180deg (2/.008=250), + loop { + info!("45 deg"); + // poor mans inverting, subtract our value from max_duty + pwm.set_duty(0, 2500 - 156); + Timer::after_millis(5000).await; + + info!("90 deg"); + pwm.set_duty(0, 2500 - 187); + Timer::after_millis(5000).await; + + info!("135 deg"); + pwm.set_duty(0, 2500 - 218); + Timer::after_millis(5000).await; + + info!("180 deg"); + pwm.set_duty(0, 2500 - 250); + Timer::after_millis(5000).await; + + info!("0 deg"); + pwm.set_duty(0, 2500 - 125); + Timer::after_millis(5000).await; + } +} diff --git a/embassy/examples/nrf52840/src/bin/qdec.rs b/embassy/examples/nrf52840/src/bin/qdec.rs new file mode 100644 index 0000000..ea849be --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/qdec.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::qdec::{self, Qdec}; +use embassy_nrf::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + QDEC => qdec::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let config = qdec::Config::default(); + let mut rotary_enc = Qdec::new(p.QDEC, Irqs, p.P0_31, p.P0_30, config); + + info!("Turn rotary encoder!"); + let mut value = 0; + loop { + value += rotary_enc.read().await; + info!("Value: {}", value); + } +} diff --git a/embassy/examples/nrf52840/src/bin/qspi.rs b/embassy/examples/nrf52840/src/bin/qspi.rs new file mode 100644 index 0000000..4539dd0 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/qspi.rs @@ -0,0 +1,81 @@ +#![no_std] +#![no_main] + +use defmt::{assert_eq, info, unwrap}; +use embassy_executor::Spawner; +use embassy_nrf::qspi::Frequency; +use embassy_nrf::{bind_interrupts, peripherals, qspi}; +use {defmt_rtt as _, panic_probe as _}; + +const PAGE_SIZE: usize = 4096; + +// Workaround for alignment requirements. +// Nicer API will probably come in the future. +#[repr(C, align(4))] +struct AlignedBuf([u8; 4096]); + +bind_interrupts!(struct Irqs { + QSPI => qspi::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + // Config for the MX25R64 present in the nRF52840 DK + let mut config = qspi::Config::default(); + config.capacity = 8 * 1024 * 1024; // 8 MB + config.frequency = Frequency::M32; + config.read_opcode = qspi::ReadOpcode::READ4IO; + config.write_opcode = qspi::WriteOpcode::PP4IO; + config.write_page_size = qspi::WritePageSize::_256BYTES; + + let mut q = qspi::Qspi::new( + p.QSPI, Irqs, p.P0_19, p.P0_17, p.P0_20, p.P0_21, p.P0_22, p.P0_23, config, + ); + + let mut id = [1; 3]; + unwrap!(q.custom_instruction(0x9F, &[], &mut id).await); + info!("id: {}", id); + + // Read status register + let mut status = [4; 1]; + unwrap!(q.custom_instruction(0x05, &[], &mut status).await); + + info!("status: {:?}", status[0]); + + if status[0] & 0x40 == 0 { + status[0] |= 0x40; + + unwrap!(q.custom_instruction(0x01, &status, &mut []).await); + + info!("enabled quad in status"); + } + + let mut buf = AlignedBuf([0u8; PAGE_SIZE]); + + let pattern = |a: u32| (a ^ (a >> 8) ^ (a >> 16) ^ (a >> 24)) as u8; + + for i in 0..8 { + info!("page {:?}: erasing... ", i); + unwrap!(q.erase(i * PAGE_SIZE as u32).await); + + for j in 0..PAGE_SIZE { + buf.0[j] = pattern((j as u32 + i * PAGE_SIZE as u32) as u32); + } + + info!("programming..."); + unwrap!(q.write(i * PAGE_SIZE as u32, &buf.0).await); + } + + for i in 0..8 { + info!("page {:?}: reading... ", i); + unwrap!(q.read(i * PAGE_SIZE as u32, &mut buf.0).await); + + info!("verifying..."); + for j in 0..PAGE_SIZE { + assert_eq!(buf.0[j], pattern((j as u32 + i * PAGE_SIZE as u32) as u32)); + } + } + + info!("done!") +} diff --git a/embassy/examples/nrf52840/src/bin/qspi_lowpower.rs b/embassy/examples/nrf52840/src/bin/qspi_lowpower.rs new file mode 100644 index 0000000..516c9b4 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/qspi_lowpower.rs @@ -0,0 +1,83 @@ +#![no_std] +#![no_main] + +use core::mem; + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_nrf::qspi::Frequency; +use embassy_nrf::{bind_interrupts, peripherals, qspi}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// Workaround for alignment requirements. +// Nicer API will probably come in the future. +#[repr(C, align(4))] +struct AlignedBuf([u8; 64]); + +bind_interrupts!(struct Irqs { + QSPI => qspi::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_p: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + + loop { + // Config for the MX25R64 present in the nRF52840 DK + let mut config = qspi::Config::default(); + config.capacity = 8 * 1024 * 1024; // 8 MB + config.frequency = Frequency::M32; + config.read_opcode = qspi::ReadOpcode::READ4IO; + config.write_opcode = qspi::WriteOpcode::PP4IO; + config.write_page_size = qspi::WritePageSize::_256BYTES; + config.deep_power_down = Some(qspi::DeepPowerDownConfig { + enter_time: 3, // tDP = 30uS + exit_time: 3, // tRDP = 35uS + }); + + let mut q = qspi::Qspi::new( + &mut p.QSPI, + Irqs, + &mut p.P0_19, + &mut p.P0_17, + &mut p.P0_20, + &mut p.P0_21, + &mut p.P0_22, + &mut p.P0_23, + config, + ); + + let mut id = [1; 3]; + unwrap!(q.custom_instruction(0x9F, &[], &mut id).await); + info!("id: {}", id); + + // Read status register + let mut status = [4; 1]; + unwrap!(q.custom_instruction(0x05, &[], &mut status).await); + + info!("status: {:?}", status[0]); + + if status[0] & 0x40 == 0 { + status[0] |= 0x40; + + unwrap!(q.custom_instruction(0x01, &status, &mut []).await); + + info!("enabled quad in status"); + } + + let mut buf = AlignedBuf([0u8; 64]); + + info!("reading..."); + unwrap!(q.read(0, &mut buf.0).await); + info!("read: {=[u8]:x}", buf.0); + + // Drop the QSPI instance. This disables the peripehral and deconfigures the pins. + // This clears the borrow on the singletons, so they can now be used again. + mem::drop(q); + + // Sleep for 1 second. The executor ensures the core sleeps with a WFE when it has nothing to do. + // During this sleep, the nRF chip should only use ~3uA + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/nrf52840/src/bin/raw_spawn.rs b/embassy/examples/nrf52840/src/bin/raw_spawn.rs new file mode 100644 index 0000000..717b0fa --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/raw_spawn.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +use core::mem; + +use cortex_m_rt::entry; +use defmt::{info, unwrap}; +use embassy_executor::raw::TaskStorage; +use embassy_executor::Executor; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +async fn run1() { + loop { + info!("BIG INFREQUENT TICK"); + Timer::after_ticks(64000).await; + } +} + +async fn run2() { + loop { + info!("tick"); + Timer::after_ticks(13000).await; + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_nrf::init(Default::default()); + let executor = EXECUTOR.init(Executor::new()); + + let run1_task = TaskStorage::new(); + let run2_task = TaskStorage::new(); + + // Safety: these variables do live forever if main never returns. + let run1_task = unsafe { make_static(&run1_task) }; + let run2_task = unsafe { make_static(&run2_task) }; + + executor.run(|spawner| { + unwrap!(spawner.spawn(run1_task.spawn(|| run1()))); + unwrap!(spawner.spawn(run2_task.spawn(|| run2()))); + }); +} + +unsafe fn make_static(t: &T) -> &'static T { + mem::transmute(t) +} diff --git a/embassy/examples/nrf52840/src/bin/rng.rs b/embassy/examples/nrf52840/src/bin/rng.rs new file mode 100644 index 0000000..326054c --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/rng.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::rng::Rng; +use embassy_nrf::{bind_interrupts, peripherals, rng}; +use rand::Rng as _; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut rng = Rng::new(p.RNG, Irqs); + + // Async API + let mut bytes = [0; 4]; + rng.fill_bytes(&mut bytes).await; + defmt::info!("Some random bytes: {:?}", bytes); + + // Sync API with `rand` + defmt::info!("A random number from 1 to 10: {:?}", rng.gen_range(1..=10)); + + let mut bytes = [0; 1024]; + rng.fill_bytes(&mut bytes).await; + let zero_count: u32 = bytes.iter().fold(0, |acc, val| acc + val.count_zeros()); + let one_count: u32 = bytes.iter().fold(0, |acc, val| acc + val.count_ones()); + defmt::info!("Chance of zero: {}%", zero_count * 100 / (bytes.len() as u32 * 8)); + defmt::info!("Chance of one: {}%", one_count * 100 / (bytes.len() as u32 * 8)); +} diff --git a/embassy/examples/nrf52840/src/bin/saadc.rs b/embassy/examples/nrf52840/src/bin/saadc.rs new file mode 100644 index 0000000..653b7d6 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/saadc.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::saadc::{ChannelConfig, Config, Saadc}; +use embassy_nrf::{bind_interrupts, saadc}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SAADC => saadc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_p: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let config = Config::default(); + let channel_config = ChannelConfig::single_ended(&mut p.P0_02); + let mut saadc = Saadc::new(p.SAADC, Irqs, config, [channel_config]); + + loop { + let mut buf = [0; 1]; + saadc.sample(&mut buf).await; + info!("sample: {=i16}", &buf[0]); + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/nrf52840/src/bin/saadc_continuous.rs b/embassy/examples/nrf52840/src/bin/saadc_continuous.rs new file mode 100644 index 0000000..f76fa35 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/saadc_continuous.rs @@ -0,0 +1,70 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::saadc::{CallbackResult, ChannelConfig, Config, Saadc}; +use embassy_nrf::timer::Frequency; +use embassy_nrf::{bind_interrupts, saadc}; +use {defmt_rtt as _, panic_probe as _}; + +// Demonstrates both continuous sampling and scanning multiple channels driven by a PPI linked timer + +bind_interrupts!(struct Irqs { + SAADC => saadc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_p: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let config = Config::default(); + let channel_1_config = ChannelConfig::single_ended(&mut p.P0_02); + let channel_2_config = ChannelConfig::single_ended(&mut p.P0_03); + let channel_3_config = ChannelConfig::single_ended(&mut p.P0_04); + let mut saadc = Saadc::new( + p.SAADC, + Irqs, + config, + [channel_1_config, channel_2_config, channel_3_config], + ); + + // This delay demonstrates that starting the timer prior to running + // the task sampler is benign given the calibration that follows. + embassy_time::Timer::after_millis(500).await; + saadc.calibrate().await; + + let mut bufs = [[[0; 3]; 500]; 2]; + + let mut c = 0; + let mut a: i32 = 0; + + saadc + .run_task_sampler( + &mut p.TIMER0, + &mut p.PPI_CH0, + &mut p.PPI_CH1, + Frequency::F1MHz, + 1000, // We want to sample at 1KHz + &mut bufs, + move |buf| { + // NOTE: It is important that the time spent within this callback + // does not exceed the time taken to acquire the 1500 samples we + // have in this example, which would be 10us + 2us per + // sample * 1500 = 18ms. You need to measure the time taken here + // and set the sample buffer size accordingly. Exceeding this + // time can lead to the peripheral re-writing the other buffer. + for b in buf { + a += b[0] as i32; + } + c += buf.len(); + if c > 1000 { + a = a / c as i32; + info!("channel 1: {=i32}", a); + c = 0; + a = 0; + } + CallbackResult::Continue + }, + ) + .await; +} diff --git a/embassy/examples/nrf52840/src/bin/self_spawn.rs b/embassy/examples/nrf52840/src/bin/self_spawn.rs new file mode 100644 index 0000000..5bfefc2 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/self_spawn.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +mod config { + pub const MY_TASK_POOL_SIZE: usize = 2; +} + +#[embassy_executor::task(pool_size = config::MY_TASK_POOL_SIZE)] +async fn my_task(spawner: Spawner, n: u32) { + Timer::after_secs(1).await; + info!("Spawning self! {}", n); + unwrap!(spawner.spawn(my_task(spawner, n + 1))); +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let _p = embassy_nrf::init(Default::default()); + info!("Hello World!"); + unwrap!(spawner.spawn(my_task(spawner, 0))); +} diff --git a/embassy/examples/nrf52840/src/bin/self_spawn_current_executor.rs b/embassy/examples/nrf52840/src/bin/self_spawn_current_executor.rs new file mode 100644 index 0000000..ec9569a --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/self_spawn_current_executor.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task(pool_size = 2)] +async fn my_task(n: u32) { + Timer::after_secs(1).await; + info!("Spawning self! {}", n); + unwrap!(Spawner::for_current_executor().await.spawn(my_task(n + 1))); +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let _p = embassy_nrf::init(Default::default()); + info!("Hello World!"); + unwrap!(spawner.spawn(my_task(0))); +} diff --git a/embassy/examples/nrf52840/src/bin/spim.rs b/embassy/examples/nrf52840/src/bin/spim.rs new file mode 100644 index 0000000..1311876 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/spim.rs @@ -0,0 +1,70 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::{bind_interrupts, peripherals, spim}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("running!"); + + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M16; + + let mut spim = spim::Spim::new(p.SPI3, Irqs, p.P0_29, p.P0_28, p.P0_30, config); + + let mut ncs = Output::new(p.P0_31, Level::High, OutputDrive::Standard); + + // Example on how to talk to an ENC28J60 chip + + // softreset + cortex_m::asm::delay(10); + ncs.set_low(); + cortex_m::asm::delay(5); + let tx = [0xFF]; + unwrap!(spim.transfer(&mut [], &tx).await); + cortex_m::asm::delay(10); + ncs.set_high(); + + cortex_m::asm::delay(100000); + + let mut rx = [0; 2]; + + // read ESTAT + cortex_m::asm::delay(5000); + ncs.set_low(); + cortex_m::asm::delay(5000); + let tx = [0b000_11101, 0]; + unwrap!(spim.transfer(&mut rx, &tx).await); + cortex_m::asm::delay(5000); + ncs.set_high(); + info!("estat: {=[?]}", rx); + + // Switch to bank 3 + cortex_m::asm::delay(10); + ncs.set_low(); + cortex_m::asm::delay(5); + let tx = [0b100_11111, 0b11]; + unwrap!(spim.transfer(&mut rx, &tx).await); + cortex_m::asm::delay(10); + ncs.set_high(); + + // read EREVID + cortex_m::asm::delay(10); + ncs.set_low(); + cortex_m::asm::delay(5); + let tx = [0b000_10010, 0]; + unwrap!(spim.transfer(&mut rx, &tx).await); + cortex_m::asm::delay(10); + ncs.set_high(); + + info!("erevid: {=[?]}", rx); +} diff --git a/embassy/examples/nrf52840/src/bin/spis.rs b/embassy/examples/nrf52840/src/bin/spis.rs new file mode 100644 index 0000000..4f28da0 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/spis.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::spis::{Config, Spis}; +use embassy_nrf::{bind_interrupts, peripherals, spis}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPI2 => spis::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("Running!"); + + let mut spis = Spis::new(p.SPI2, Irqs, 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/embassy/examples/nrf52840/src/bin/temp.rs b/embassy/examples/nrf52840/src/bin/temp.rs new file mode 100644 index 0000000..1d28f8e --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/temp.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::temp::Temp; +use embassy_nrf::{bind_interrupts, temp}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TEMP => temp::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut temp = Temp::new(p.TEMP, Irqs); + + loop { + let value = temp.read().await; + info!("temperature: {}℃", value.to_num::()); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/nrf52840/src/bin/timer.rs b/embassy/examples/nrf52840/src/bin/timer.rs new file mode 100644 index 0000000..365695a --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/timer.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn run1() { + loop { + info!("BIG INFREQUENT TICK"); + Timer::after_ticks(64000).await; + } +} + +#[embassy_executor::task] +async fn run2() { + loop { + info!("tick"); + Timer::after_ticks(13000).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let _p = embassy_nrf::init(Default::default()); + unwrap!(spawner.spawn(run1())); + unwrap!(spawner.spawn(run2())); +} diff --git a/embassy/examples/nrf52840/src/bin/twim.rs b/embassy/examples/nrf52840/src/bin/twim.rs new file mode 100644 index 0000000..ceaafd7 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/twim.rs @@ -0,0 +1,33 @@ +//! Example on how to read a 24C/24LC i2c eeprom. +//! +//! Connect SDA to P0.03, SCL to P0.04 + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::twim::{self, Twim}; +use embassy_nrf::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x50; + +bind_interrupts!(struct Irqs { + TWISPI0 => twim::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("Initializing TWI..."); + let config = twim::Config::default(); + let mut twi = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, config); + + info!("Reading..."); + + let mut buf = [0u8; 16]; + unwrap!(twi.blocking_write_read(ADDRESS, &mut [0x00], &mut buf)); + + info!("Read: {=[u8]:x}", buf); +} diff --git a/embassy/examples/nrf52840/src/bin/twim_lowpower.rs b/embassy/examples/nrf52840/src/bin/twim_lowpower.rs new file mode 100644 index 0000000..e2efbdd --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/twim_lowpower.rs @@ -0,0 +1,52 @@ +//! Example on how to read a 24C/24LC i2c eeprom with low power consumption. +//! The eeprom is read every 1 second, while ensuring lowest possible power while +//! sleeping between reads. +//! +//! Connect SDA to P0.03, SCL to P0.04 + +#![no_std] +#![no_main] + +use core::mem; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::twim::{self, Twim}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x50; + +bind_interrupts!(struct Irqs { + TWISPI0 => twim::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_p: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + info!("Started!"); + + loop { + info!("Initializing TWI..."); + let config = twim::Config::default(); + + // Create the TWIM instance with borrowed singletons, so they're not consumed. + let mut twi = Twim::new(&mut p.TWISPI0, Irqs, &mut p.P0_03, &mut p.P0_04, config); + + info!("Reading..."); + + let mut buf = [0u8; 16]; + unwrap!(twi.blocking_write_read(ADDRESS, &mut [0x00], &mut buf)); + + info!("Read: {=[u8]:x}", buf); + + // Drop the TWIM instance. This disables the peripehral and deconfigures the pins. + // This clears the borrow on the singletons, so they can now be used again. + mem::drop(twi); + + // Sleep for 1 second. The executor ensures the core sleeps with a WFE when it has nothing to do. + // During this sleep, the nRF chip should only use ~3uA + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/nrf52840/src/bin/twis.rs b/embassy/examples/nrf52840/src/bin/twis.rs new file mode 100644 index 0000000..856b341 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/twis.rs @@ -0,0 +1,47 @@ +//! TWIS example + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::twis::{self, Command, Twis}; +use embassy_nrf::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TWISPI0 => twis::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let mut config = twis::Config::default(); + config.address0 = 0x55; // Set i2c address + let mut i2c = Twis::new(p.TWISPI0, Irqs, 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/embassy/examples/nrf52840/src/bin/uart.rs b/embassy/examples/nrf52840/src/bin/uart.rs new file mode 100644 index 0000000..2315467 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/uart.rs @@ -0,0 +1,37 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::{bind_interrupts, peripherals, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UARTE0 => uarte::InterruptHandler; +}); + +#[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 mut uart = uarte::Uarte::new(p.UARTE0, Irqs, p.P0_08, p.P0_06, 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/embassy/examples/nrf52840/src/bin/uart_idle.rs b/embassy/examples/nrf52840/src/bin/uart_idle.rs new file mode 100644 index 0000000..a42e84f --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/uart_idle.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::peripherals::UARTE0; +use embassy_nrf::{bind_interrupts, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UARTE0 => uarte::InterruptHandler; +}); + +#[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 uart = uarte::Uarte::new(p.UARTE0, Irqs, 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!"); + + // Message must be in SRAM + let mut buf = [0; 8]; + buf.copy_from_slice(b"Hello!\r\n"); + + unwrap!(tx.write(&buf).await); + info!("wrote hello in uart!"); + + loop { + info!("reading..."); + let n = unwrap!(rx.read_until_idle(&mut buf).await); + info!("got {} bytes", n); + } +} diff --git a/embassy/examples/nrf52840/src/bin/uart_split.rs b/embassy/examples/nrf52840/src/bin/uart_split.rs new file mode 100644 index 0000000..94af4be --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/uart_split.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::peripherals::UARTE0; +use embassy_nrf::uarte::UarteRx; +use embassy_nrf::{bind_interrupts, uarte}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::Channel; +use {defmt_rtt as _, panic_probe as _}; + +static CHANNEL: Channel = Channel::new(); + +bind_interrupts!(struct Irqs { + UARTE0 => uarte::InterruptHandler; +}); + +#[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 uart = uarte::Uarte::new(p.UARTE0, Irqs, p.P0_08, p.P0_06, config); + let (mut tx, rx) = uart.split(); + + info!("uarte initialized!"); + + // Spawn a task responsible purely for reading + + unwrap!(spawner.spawn(reader(rx))); + + // Message must be in SRAM + { + let mut buf = [0; 23]; + buf.copy_from_slice(b"Type 8 chars to echo!\r\n"); + + unwrap!(tx.write(&buf).await); + info!("wrote hello in uart!"); + } + + // Continue reading in this main task and write + // back out the buffer we receive from the read + // task. + loop { + let buf = CHANNEL.receive().await; + info!("writing..."); + unwrap!(tx.write(&buf).await); + } +} + +#[embassy_executor::task] +async fn reader(mut rx: UarteRx<'static, UARTE0>) { + let mut buf = [0; 8]; + loop { + info!("reading..."); + unwrap!(rx.read(&mut buf).await); + CHANNEL.send(buf).await; + } +} diff --git a/embassy/examples/nrf52840/src/bin/usb_ethernet.rs b/embassy/examples/nrf52840/src/bin/usb_ethernet.rs new file mode 100644 index 0000000..88314b7 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/usb_ethernet.rs @@ -0,0 +1,162 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::StackResources; +use embassy_nrf::rng::Rng; +use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; +use embassy_nrf::usb::Driver; +use embassy_nrf::{bind_interrupts, pac, peripherals, rng, usb}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; +use embassy_usb::{Builder, Config, UsbDevice}; +use embedded_io_async::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; + RNG => rng::InterruptHandler; +}); + +type MyDriver = Driver<'static, peripherals::USBD, HardwareVbusDetect>; + +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(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + info!("Enabling ext hfosc..."); + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} + + // Create the driver, from the HAL. + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); + + // 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. + static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new(); + static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new(); + static MSOS_DESC: StaticCell<[u8; 128]> = StaticCell::new(); + static CONTROL_BUF: StaticCell<[u8; 128]> = StaticCell::new(); + let mut builder = Builder::new( + driver, + config, + &mut CONFIG_DESC.init([0; 256])[..], + &mut BOS_DESC.init([0; 256])[..], + &mut MSOS_DESC.init([0; 128])[..], + &mut CONTROL_BUF.init([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. + static STATE: StaticCell = StaticCell::new(); + let class = CdcNcmClass::new(&mut builder, STATE.init(State::new()), host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + static NET_STATE: StaticCell> = StaticCell::new(); + let (runner, device) = class.into_embassy_net_device::(NET_STATE.init(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); + + let config = embassy_net::Config::dhcpv4(Default::default()); + // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + // }); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + // 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_time::Duration::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/embassy/examples/nrf52840/src/bin/usb_hid_keyboard.rs b/embassy/examples/nrf52840/src/bin/usb_hid_keyboard.rs new file mode 100644 index 0000000..5a9dc90 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/usb_hid_keyboard.rs @@ -0,0 +1,224 @@ +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_futures::select::{select, Either}; +use embassy_nrf::gpio::{Input, Pull}; +use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; +use embassy_nrf::usb::Driver; +use embassy_nrf::{bind_interrupts, pac, peripherals, usb}; +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, Handler}; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; +}); + +static SUSPENDED: AtomicBool = AtomicBool::new(false); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + info!("Enabling ext hfosc..."); + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} + + // Create the driver, from the HAL. + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("HID keyboard example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + config.supports_remote_wakeup = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut request_handler = MyRequestHandler {}; + let mut device_handler = MyDeviceHandler::new(); + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + builder.handler(&mut device_handler); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 64, + }; + let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + let remote_wakeup: Signal = Signal::new(); + + // Run the USB device. + let usb_fut = async { + loop { + usb.run_until_suspend().await; + match select(usb.wait_resume(), remote_wakeup.wait()).await { + Either::First(_) => (), + Either::Second(_) => unwrap!(usb.remote_wakeup().await), + } + } + }; + + let mut button = Input::new(p.P0_11, Pull::Up); + + let (reader, mut writer) = hid.split(); + + // Do stuff with the class! + let in_fut = async { + loop { + button.wait_for_low().await; + info!("PRESSED"); + + if SUSPENDED.load(Ordering::Acquire) { + info!("Triggering remote wakeup"); + remote_wakeup.signal(()); + } else { + let report = KeyboardReport { + keycodes: [4, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + + button.wait_for_high().await; + info!("RELEASED"); + let report = KeyboardReport { + keycodes: [0, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + }; + + let out_fut = async { + reader.run(false, &mut request_handler).await; + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(in_fut, out_fut)).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} + +struct MyDeviceHandler { + configured: AtomicBool, +} + +impl MyDeviceHandler { + fn new() -> Self { + MyDeviceHandler { + configured: AtomicBool::new(false), + } + } +} + +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { + self.configured.store(false, Ordering::Relaxed); + SUSPENDED.store(false, Ordering::Release); + if enabled { + info!("Device enabled"); + } else { + info!("Device disabled"); + } + } + + fn reset(&mut self) { + self.configured.store(false, Ordering::Relaxed); + info!("Bus reset, the Vbus current limit is 100mA"); + } + + fn addressed(&mut self, addr: u8) { + self.configured.store(false, Ordering::Relaxed); + info!("USB address set to: {}", addr); + } + + fn configured(&mut self, configured: bool) { + self.configured.store(configured, Ordering::Relaxed); + if configured { + info!("Device configured, it may now draw up to the configured current limit from Vbus.") + } else { + info!("Device is no longer configured, the Vbus current limit is 100mA."); + } + } + + 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); + } else { + SUSPENDED.store(false, Ordering::Release); + if self.configured.load(Ordering::Relaxed) { + info!("Device resumed, it may now draw up to the configured current limit from Vbus"); + } else { + info!("Device resumed, the Vbus current limit is 100mA"); + } + } + } +} diff --git a/embassy/examples/nrf52840/src/bin/usb_hid_mouse.rs b/embassy/examples/nrf52840/src/bin/usb_hid_mouse.rs new file mode 100644 index 0000000..80cda70 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/usb_hid_mouse.rs @@ -0,0 +1,123 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; +use embassy_nrf::usb::Driver; +use embassy_nrf::{bind_interrupts, pac, peripherals, usb}; +use embassy_time::Timer; +use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State}; +use embassy_usb::control::OutResponse; +use embassy_usb::{Builder, Config}; +use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + info!("Enabling ext hfosc..."); + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} + + // Create the driver, from the HAL. + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("HID mouse example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut request_handler = MyRequestHandler {}; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: MouseReport::desc(), + request_handler: Some(&mut request_handler), + poll_ms: 60, + max_packet_size: 8, + }; + + let mut writer = HidWriter::<_, 5>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let hid_fut = async { + let mut y: i8 = 5; + loop { + Timer::after_millis(500).await; + + y = -y; + let report = MouseReport { + buttons: 0, + x: 0, + y, + wheel: 0, + pan: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + } + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, hid_fut).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} diff --git a/embassy/examples/nrf52840/src/bin/usb_serial.rs b/embassy/examples/nrf52840/src/bin/usb_serial.rs new file mode 100644 index 0000000..a534046 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/usb_serial.rs @@ -0,0 +1,109 @@ +#![no_std] +#![no_main] + +use defmt::{info, panic}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::usb::vbus_detect::{HardwareVbusDetect, VbusDetect}; +use embassy_nrf::usb::{Driver, Instance}; +use embassy_nrf::{bind_interrupts, pac, peripherals, usb}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + info!("Enabling ext hfosc..."); + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} + + // Create the driver, from the HAL. + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); + + // 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 compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + 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 config_descriptor, + &mut bos_descriptor, + &mut msos_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, 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/embassy/examples/nrf52840/src/bin/usb_serial_multitask.rs b/embassy/examples/nrf52840/src/bin/usb_serial_multitask.rs new file mode 100644 index 0000000..32fc5e0 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/usb_serial_multitask.rs @@ -0,0 +1,109 @@ +#![no_std] +#![no_main] + +use defmt::{info, panic, unwrap}; +use embassy_executor::Spawner; +use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; +use embassy_nrf::usb::Driver; +use embassy_nrf::{bind_interrupts, pac, peripherals, usb}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config, UsbDevice}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; +}); + +type MyDriver = Driver<'static, peripherals::USBD, HardwareVbusDetect>; + +#[embassy_executor::task] +async fn usb_task(mut device: UsbDevice<'static, MyDriver>) { + device.run().await; +} + +#[embassy_executor::task] +async fn echo_task(mut class: CdcAcmClass<'static, MyDriver>) { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + info!("Enabling ext hfosc..."); + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} + + // Create the driver, from the HAL. + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); + + // 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 compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(State::new()); + + // Create embassy-usb DeviceBuilder using the driver and config. + static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new(); + static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new(); + static MSOS_DESC: StaticCell<[u8; 128]> = StaticCell::new(); + static CONTROL_BUF: StaticCell<[u8; 128]> = StaticCell::new(); + let mut builder = Builder::new( + driver, + config, + &mut CONFIG_DESC.init([0; 256])[..], + &mut BOS_DESC.init([0; 256])[..], + &mut MSOS_DESC.init([0; 128])[..], + &mut CONTROL_BUF.init([0; 128])[..], + ); + + // Create classes on the builder. + let class = CdcAcmClass::new(&mut builder, state, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + unwrap!(spawner.spawn(echo_task(class))); +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo(class: &mut CdcAcmClass<'static, MyDriver>) -> 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/embassy/examples/nrf52840/src/bin/usb_serial_winusb.rs b/embassy/examples/nrf52840/src/bin/usb_serial_winusb.rs new file mode 100644 index 0000000..0352f9c --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/usb_serial_winusb.rs @@ -0,0 +1,128 @@ +#![no_std] +#![no_main] + +use defmt::{info, panic}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::usb::vbus_detect::{HardwareVbusDetect, VbusDetect}; +use embassy_nrf::usb::{Driver, Instance}; +use embassy_nrf::{bind_interrupts, pac, peripherals, usb}; +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 _}; + +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; +}); + +// 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()); + + info!("Enabling ext hfosc..."); + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} + + // Create the driver, from the HAL. + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); + + // 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 compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + 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 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/embassy/examples/nrf52840/src/bin/wdt.rs b/embassy/examples/nrf52840/src/bin/wdt.rs new file mode 100644 index 0000000..0d9ee3c --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/wdt.rs @@ -0,0 +1,40 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Input, Pull}; +use embassy_nrf::wdt::{Config, HaltConfig, Watchdog}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("Hello World!"); + + let mut config = Config::default(); + config.timeout_ticks = 32768 * 3; // 3 seconds + + // This is needed for `probe-rs run` to be able to catch the panic message + // in the WDT interrupt. The core resets 2 ticks after firing the interrupt. + config.action_during_debug_halt = HaltConfig::PAUSE; + + let (_wdt, [mut handle]) = match Watchdog::try_new(p.WDT, config) { + Ok(x) => x, + Err(_) => { + info!("Watchdog already active with wrong config, waiting for it to timeout..."); + loop {} + } + }; + + let mut button = Input::new(p.P0_11, Pull::Up); + + info!("Watchdog started, press button 1 to pet it or I'll reset in 3 seconds!"); + + loop { + button.wait_for_high().await; + button.wait_for_low().await; + info!("Button pressed, petting watchdog!"); + handle.pet(); + } +} diff --git a/embassy/examples/nrf52840/src/bin/wifi_esp_hosted.rs b/embassy/examples/nrf52840/src/bin/wifi_esp_hosted.rs new file mode 100644 index 0000000..26eaf48 --- /dev/null +++ b/embassy/examples/nrf52840/src/bin/wifi_esp_hosted.rs @@ -0,0 +1,139 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap, warn}; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::StackResources; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; +use embassy_nrf::rng::Rng; +use embassy_nrf::spim::{self, Spim}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embassy_time::Delay; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_io_async::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _}; + +const WIFI_NETWORK: &str = "EmbassyTest"; +const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; + +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; + RNG => embassy_nrf::rng::InterruptHandler; +}); + +#[embassy_executor::task] +async fn wifi_task( + runner: hosted::Runner< + 'static, + ExclusiveDevice, Output<'static>, Delay>, + Input<'static>, + Output<'static>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, hosted::NetDriver<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_nrf::init(Default::default()); + + let miso = p.P0_28; + let sck = p.P0_29; + let mosi = p.P0_30; + let cs = Output::new(p.P0_31, Level::High, OutputDrive::HighDrive); + let handshake = Input::new(p.P1_01, Pull::Up); + let ready = Input::new(p.P1_04, Pull::None); + let reset = Output::new(p.P1_05, Level::Low, OutputDrive::Standard); + + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M32; + config.mode = spim::MODE_2; // !!! + let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config); + let spi = ExclusiveDevice::new(spi, cs, Delay); + + static ESP_STATE: StaticCell = StaticCell::new(); + let (device, mut control, runner) = embassy_net_esp_hosted::new( + ESP_STATE.init(embassy_net_esp_hosted::State::new()), + spi, + handshake, + ready, + reset, + ) + .await; + + unwrap!(spawner.spawn(wifi_task(runner))); + + unwrap!(control.init().await); + unwrap!(control.connect(WIFI_NETWORK, WIFI_PASSWORD).await); + + let config = embassy_net::Config::dhcpv4(Default::default()); + // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + // }); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + // 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_time::Duration::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/embassy/examples/nrf5340/.cargo/config.toml b/embassy/examples/nrf5340/.cargo/config.toml new file mode 100644 index 0000000..4c3cf3d --- /dev/null +++ b/embassy/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-rs chip list` +runner = "probe-rs run --chip nRF5340_xxAA" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/nrf5340/Cargo.toml b/embassy/examples/nrf5340/Cargo.toml new file mode 100644 index 0000000..1792b27 --- /dev/null +++ b/embassy/examples/nrf5340/Cargo.toml @@ -0,0 +1,30 @@ +[package] +edition = "2021" +name = "embassy-nrf5340-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf5340-app-s", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embedded-io-async = { version = "0.6.1" } + +defmt = "0.3" +defmt-rtt = "0.4" + +static_cell = "2" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +rand = { version = "0.8.4", default-features = false } +embedded-storage = "0.3.1" +usbd-hid = "0.8.1" +serde = { version = "1.0.136", default-features = false } + +[profile.release] +debug = 2 diff --git a/embassy/examples/nrf5340/build.rs b/embassy/examples/nrf5340/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/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/embassy/examples/nrf5340/memory.x b/embassy/examples/nrf5340/memory.x new file mode 100644 index 0000000..a122dc2 --- /dev/null +++ b/embassy/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/embassy/examples/nrf5340/src/bin/blinky.rs b/embassy/examples/nrf5340/src/bin/blinky.rs new file mode 100644 index 0000000..5c533c9 --- /dev/null +++ b/embassy/examples/nrf5340/src/bin/blinky.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P0_28, Level::Low, OutputDrive::Standard); + + loop { + led.set_high(); + Timer::after_millis(300).await; + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/nrf5340/src/bin/gpiote_channel.rs b/embassy/examples/nrf5340/src/bin/gpiote_channel.rs new file mode 100644 index 0000000..23f6fca --- /dev/null +++ b/embassy/examples/nrf5340/src/bin/gpiote_channel.rs @@ -0,0 +1,65 @@ +#![no_std] +#![no_main] + +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") + } + }; + + embassy_futures::join::join4(button1, button2, button3, button4).await; +} diff --git a/embassy/examples/nrf5340/src/bin/uart.rs b/embassy/examples/nrf5340/src/bin/uart.rs new file mode 100644 index 0000000..7b41d74 --- /dev/null +++ b/embassy/examples/nrf5340/src/bin/uart.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::peripherals::SERIAL0; +use embassy_nrf::{bind_interrupts, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SERIAL0 => uarte::InterruptHandler; +}); + +#[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 mut uart = uarte::Uarte::new(p.SERIAL0, Irqs, 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/embassy/examples/nrf54l15/.cargo/config.toml b/embassy/examples/nrf54l15/.cargo/config.toml new file mode 100644 index 0000000..4a026eb --- /dev/null +++ b/embassy/examples/nrf54l15/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "../../sshprobe.sh" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/nrf54l15/Cargo.toml b/embassy/examples/nrf54l15/Cargo.toml new file mode 100644 index 0000000..7288ef6 --- /dev/null +++ b/embassy/examples/nrf54l15/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "embassy-nrf54l15-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf54l15-app-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } + +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[profile.release] +debug = 2 diff --git a/embassy/examples/nrf54l15/build.rs b/embassy/examples/nrf54l15/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/nrf54l15/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/embassy/examples/nrf54l15/memory.x b/embassy/examples/nrf54l15/memory.x new file mode 100644 index 0000000..1064c8a --- /dev/null +++ b/embassy/examples/nrf54l15/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 1536K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/embassy/examples/nrf54l15/src/bin/blinky.rs b/embassy/examples/nrf54l15/src/bin/blinky.rs new file mode 100644 index 0000000..71fcc46 --- /dev/null +++ b/embassy/examples/nrf54l15/src/bin/blinky.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P2_09, Level::Low, OutputDrive::Standard); + + loop { + info!("high!"); + led.set_high(); + Timer::after_millis(300).await; + info!("low!"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/nrf9151/ns/.cargo/config.toml b/embassy/examples/nrf9151/ns/.cargo/config.toml new file mode 100644 index 0000000..1444b0c --- /dev/null +++ b/embassy/examples/nrf9151/ns/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF9160_xxAA" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/nrf9151/ns/Cargo.toml b/embassy/examples/nrf9151/ns/Cargo.toml new file mode 100644 index 0000000..0353cf5 --- /dev/null +++ b/embassy/examples/nrf9151/ns/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "embassy-nrf9151-non-secure-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.6.3", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-ns", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/embassy/examples/nrf9151/ns/README.md b/embassy/examples/nrf9151/ns/README.md new file mode 100644 index 0000000..a3f81d2 --- /dev/null +++ b/embassy/examples/nrf9151/ns/README.md @@ -0,0 +1,4 @@ +You must flash the TFM before running any non-secure examples. The TFM +configures the secure and non-secure execution environments and then loads the +non-secure application. A reference TFM is included, and you can use the +provided helper script to flash it. diff --git a/embassy/examples/nrf9151/ns/build.rs b/embassy/examples/nrf9151/ns/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/nrf9151/ns/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/embassy/examples/nrf9151/ns/flash_tfm.sh b/embassy/examples/nrf9151/ns/flash_tfm.sh new file mode 100644 index 0000000..29e4e0e --- /dev/null +++ b/embassy/examples/nrf9151/ns/flash_tfm.sh @@ -0,0 +1,2 @@ +nrfjprog --family NRF91 --recover +nrfjprog --family NRF91 --chiperase --verify --program tfm.hex diff --git a/embassy/examples/nrf9151/ns/memory.x b/embassy/examples/nrf9151/ns/memory.x new file mode 100644 index 0000000..8d7b66f --- /dev/null +++ b/embassy/examples/nrf9151/ns/memory.x @@ -0,0 +1,7 @@ +MEMORY +{ + /* Trusted Firmware-M (TF-M) is flashed at the start */ + FLASH : ORIGIN = 0x00008000, LENGTH = 0xf8000 + RAM (rwx) : ORIGIN = 0x2000C568, LENGTH = 0x33a98 +} + diff --git a/embassy/examples/nrf9151/ns/src/bin/blinky.rs b/embassy/examples/nrf9151/ns/src/bin/blinky.rs new file mode 100644 index 0000000..7457a95 --- /dev/null +++ b/embassy/examples/nrf9151/ns/src/bin/blinky.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P0_00, Level::Low, OutputDrive::Standard); + + loop { + led.set_high(); + defmt::info!("high"); + Timer::after_millis(500).await; + led.set_low(); + defmt::info!("low"); + Timer::after_millis(1000).await; + } +} diff --git a/embassy/examples/nrf9151/ns/src/bin/uart.rs b/embassy/examples/nrf9151/ns/src/bin/uart.rs new file mode 100644 index 0000000..234ff35 --- /dev/null +++ b/embassy/examples/nrf9151/ns/src/bin/uart.rs @@ -0,0 +1,37 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::{bind_interrupts, peripherals, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SERIAL0 => uarte::InterruptHandler; +}); + +#[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 mut uart = uarte::Uarte::new(p.SERIAL0, Irqs, p.P0_26, p.P0_27, 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/embassy/examples/nrf9151/ns/tfm.hex b/embassy/examples/nrf9151/ns/tfm.hex new file mode 100644 index 0000000..9864a18 --- /dev/null +++ b/embassy/examples/nrf9151/ns/tfm.hex @@ -0,0 +1,1543 @@ +:10000000F80B0020690600000D070000255A0000CB +:10001000455A0000655A0000A55A0000855A0000A4 +:100020000000000000000000000000001D2700008C +:100030000D070000000000000D0700000D07000084 +:10004000000000000000000000000000F13200008D +:10005000000000000D07000000000000000000008C +:100060000D0700000D0700000D0700000D07000040 +:10007000000000000D0700000D0700000D07000044 +:100080000D0700000D070000000000000000000048 +:100090000D0700000D070000000000000000000038 +:1000A0000D07000000000000000000000D07000028 +:1000B0000D0700000D0700000D0700000D070000F0 +:1000C0000D0700000D0700000D0700000D070000E0 +:1000D0000D070000000000000D07000000000000F8 +:1000E0000D070000000000000D07000000000000E8 +:1000F0000D070000000000000000000000000000EC +:10010000000000000D0700000000000000000000DB +:1001100000000000000000000000000000000000DF +:10012000000000000D0700000000000000000000BB +:1001300000000000000000000000000000000000BF +:100140000D07000000015F5F00000000FE0B0000D3 +:10015000F9270000800600000000000000000000F9 +:10016000000000000000000000000000401200201D +:100170000000000001015F5F030100001F0100009B +:1001800085040000000000000000000001000000E5 +:10019000010000000000000000000000000000005E +:1001A0000000000070000000CC5E00008000000035 +:1001B000000F000001000000D102000001015F5F9C +:1001C000040100001F010000F30500000000000012 +:1001D000000000000000000001000000000000001E +:1001E000000000000000000000000000D75E0000DA +:1001F000400000000507000001000000DF040000CF +:1002000038B50025054C29462046D4F80024C4F80A +:10021000045405F0B0FCC4F8005438BD200C002094 +:1002200013B511460022CDE9002242680346382A60 +:10023000046844D194F82820022A19D01AB900F091 +:1002400047F902B010BD21482468D0F80404013AEF +:10025000CDE90040082A35D8DFE802F0050A0E1380 +:10026000181D22272C006A46184600F02EF9E8E7F0 +:10027000184600F018F9E4E76A46184600F016F947 +:10028000DFE76A46184600F00BF9DAE76A461846D7 +:1002900000F00FF9D5E76A46184600F00DF9D0E7EF +:1002A0006A46184600F00BF9CBE76A46184600F09C +:1002B00009F9C6E76A46184600F00EF9C1E76FF083 +:1002C0008000BEE76FF08500BBE700BF200C002078 +:1002D0002DE9F04F05680446A1B0002D40F0AA803A +:1002E0002022294602A805F046FC12AF202229460A +:1002F0000AA805F040FC38222946384605F03BFCA8 +:10030000E26904F11C039846B2B10426E26A04F1E2 +:100310002C03EAB10425EEB96FF08306304621B014 +:10032000BDE8F08F002EF1D053F8041D3246013E97 +:100330000029F7D01646E9E70326F5E7002DEAD0B5 +:1003400053F8041D2A46013D0029F7D01546E2E77F +:100350000325F5E738233A460021606805F048FC9C +:100360003828D9D14FF00109CDE90270334F0DF192 +:10037000080A04F1100B4E45D7F800141CD10DF1FA +:1003800028094B46002008EB850EC64533D108B13D +:10039000C7F80014A3683146C7F804340AAA2B46EC +:1003A00002A8FFF73DFF00270646BD4209F10809F4 +:1003B00038D1FFF725FFB1E75BF8043FC1F5806254 +:1003C000D81C20F00300904204D9FFF719FF6FF00A +:1003D0008C06A3E70844CA19C7F8000449466068B8 +:1003E000019205F005FC019ACAF80C0009F1010917 +:1003F0004AF8082FBFE758F804CFC1F5806A0CF11E +:10040000030222F00302524503F1080305D87818CD +:1004100043E9020C11440120B7E70028D5D0C7F802 +:100420000014D2E7394659E90223606805F0F5FB6C +:100430000137BAE76FF0850670E700BF200C002097 +:1004400008B528220021024805F095FB002008BDD0 +:100450002810002010B5044C0F222046034905F057 +:1004600065FB204610BD00BF51100020F6050000BE +:1004700008B50121024803F011FB002008BD00BFB0 +:100480005010002008B5FFF7DBFF40B9FFF7E2FF8F +:10049000FFF7EEFF18B9BDE8084003F06DBB08BDDB +:1004A0006FF0850070476FF0850070476FF0850032 +:1004B00070476FF0850070476FF0850070476FF0F0 +:1004C000850070476FF0850070476FF085007047BA +:1004D000D1E9000103F030BB6FF085007047F0B543 +:1004E00003680546B3F57D7FA7B007D040F2F5322B +:1004F000934207D06FF0850027B0F0BD01F08EFC6D +:100500000020F9E700243C22214607A8CDE9024457 +:10051000CDE90444069405F02EFB3C22214617A8A1 +:10052000169405F028FBEA69019405F11C0302B357 +:100530000426EA6A05F12C033AB30424731E012B46 +:1005400052D8012C50D80423002168680DEB030217 +:1005500005F04EFB04281AD06FF08000CCE7002E87 +:10056000E7D053F8041D3246013E0029F7D0164665 +:10057000DFE70326F5E7002CE0D053F8041D224600 +:10058000013C0029F7D01446D8E70324F5E7022EF2 +:1005900016D16E69402E29D806AF33463A4601215E +:1005A000686805F025FB8642D6D1CDE9027602A91E +:1005B00044B92246019801F01AFE044620469BE702 +:1005C0000021F5E72B6A402BC6D816AACDE90423F3 +:1005D000019804AA01F00BFEDDE904230446002182 +:1005E000686805F01AFBE9E7012085E76FF08900EC +:1005F00082E7002070474D62656420544C532033DD +:100600002E352E3000000000000000000000000029 +:1006100000000000000000000000000000000000DA +:100620005431000001000000407C000080100020D8 +:100630004A000000407C0000200C00200000000068 +:10064000407C0000000C002000000000A8110020E9 +:10065000C4050000200C002018010000000C002040 +:100660000800000002F07ABC08B572B61E4A1F4BA3 +:100670009A601F4B83F309881E4B83F30A8883F328 +:100680000B881D4B16A1D1E90001C3E9000100F060 +:1006900089F81A4B1A498B4212D30020194B1A4978 +:1006A0008B4218D303F06AF8186850F82240586853 +:1006B00040F82240013298688242F5D30C33EAE7D1 +:1006C0000022F8E71C6844F8220001325C68A2426C +:1006D000F8D30833E4E70022F8E700BFAFF3008067 +:1006E000A5EDF5FEA5EDF5FE0000000000ED00E033 +:1006F000F80B002000040020F80B00202806000062 +:100700004C0600004C06000064060000EFF3088071 +:10071000EFF309812DE9F00F6B46724600F038F9CE +:1007200008B0FFF79FFFFEE708B5EFF31083012B3A +:100730000C4A07D1126ABFF34F8F511C4A424A41FB +:10074000104608BD01F0BEFB00B172B6126ABFF3DD +:100750004F8F531C5A425A4101F0B4FB0028EFD08E +:1007600062B6EDE70080FF0008B5EFF31083012BC0 +:100770000B4A06D1D269BFF34F8F02F00102104637 +:1007800008BD01F09FFB00B172B6D269BFF34F8F75 +:1007900002F0010201F096FB0028F0D062B6EEE70D +:1007A0000080FF006F4A10B5D2F8D03043F002034A +:1007B000C2F8D03001F086FB30B14FF05023002258 +:1007C000C3F81421C3F8182101F08BFB28B10122D2 +:1007D000654BC3F8382AC3F878254FF47F02D2F866 +:1007E0003031D2F83421BFF34F8FC3F30B03092B01 +:1007F00005D1012A1EBF01225B4BC3F8782501F009 +:1008000070FB18B10E22594BC3F8E42E01F05AFBCD +:1008100030B10022544BC3F80C270122C3F8102733 +:100820000022D30003F57F03D3F80013BFF34F8FEB +:10083000013108D0D3F800130132D3F80433B2F5F4 +:10084000807F0B60EDD1EFF31084FFF78DFF002860 +:100850004DD0BFF34F8F464BD3F80024012AFBD174 +:10086000C3F80425D3F80024012AFBD1012C03D0BE +:1008700001F028FB00B172B6FFF776FF58B13D4A90 +:10088000D36923F00103D361BFF34F8F384AD2F805 +:100890000034012BFBD1FFF747FF68B1354A136ADB +:1008A00023F0FF0343F020031362BFF34F8F304A5E +:1008B000D2F80034012BFBD1012C03D001F002FB54 +:1008C00000B162B600222A4BC3F80425D3F80024F5 +:1008D000012AFBD1BFF34F8F2249274BCA6802F48C +:1008E000E0621343CB60BFF34F8F00BFFDE7FFF71C +:1008F0001BFF0028ADD11B4AD2F88C3043F4406373 +:10090000C2F88C304FF47F03D3F83021D3F8343160 +:10091000BFF34F8FC2F30B02092A16D1013B022B02 +:1009200002D8164AD35C83B1124815490368104AAD +:100930008B420CBF5A230023C2F8103EC36A8B427D +:100940000CBF5A230023C2F8003E064AD2F8883072 +:1009500043F47003C2F88830BFF34F8FBFF36F8F3B +:1009600010BD00BF00ED00E00040005000A00350AB +:10097000009003500080FF000400FA05C43300001B +:10098000FA50FA507C22014905F0D0B8DC1800205A +:1009900038B51D46374B384C5B68C3F308032360FA +:1009A000EFF30383C4E901206365C2F3801302F00F +:1009B0000C020C2AE16038D10BB9EFF389812022B7 +:1009C0002E48216105F0B2F825B1202229462C4895 +:1009D00005F0ACF8274B9A6AA265D96AE165986B75 +:1009E000206602F400406066586BA06602F080004A +:1009F000E0669A62D962D3F8E4202267D3F8E8105F +:100A0000616702F04001A167C3F8E42000F06AF8D2 +:100A10000E211C4801F077FA2268D31E042B1CD843 +:100A2000DFE803F0090F1215180013B9EFF38881FE +:100A3000C5E70146C3E70C211348BDE8384001F083 +:100A400062BA12211148F8E70B211148F5E70D2190 +:100A50001048F2E70E211048EFE70F2A98BF142143 +:100A6000BDE8384089BF25210C480D48103A01F0F7 +:100A7000F5BB00BF00ED00E0DC180020F0180020FE +:100A800010190020C7330000D5330000E133000007 +:100A9000F3330000FE3300000B3400001934000073 +:100AA0002D3400002DE9F34106460F460C4DD5F8D4 +:100AB0002C80C04710F00104FBD1394630466B69E9 +:100AC000984740B9C047C307FCD42B6A019302B0D2 +:100AD000BDE8F0411847204602B0BDE8F08100BFF4 +:100AE0005434000010B5094C0020A36898472369CE +:100AF00002209847A46A4FF4E1310120A047234621 +:100B0000BDE8104001211520184700BF54340000F3 +:100B100010B5044C002023699847E368BDE81040F5 +:100B2000184700BF54340000064B82B018680023F9 +:100B300082B262F30F03000C60F31F43184602B049 +:100B4000704700BF9C340000014B1869704700BF1C +:100B500084100020014B5869704700BF84100020AA +:100B600038B50C4D0C4695F8203013B94FF0FF30D6 +:100B700038BD0A460146284600F06CFF064B9842F5 +:100B800005D00B3B9842F1D100206C61F0E76FF08B +:100B90000100EDE7841000200B00AD0BF0B51B4EFB +:100BA000054696F820300C4691B01BB94FF0FF3047 +:100BB00011B0F0BD00F06043B3F1005F21D100231C +:100BC0000A460146304600F031FC114B98421AD0DB +:100BD0000B3B9842EAD1346111E0A71B402F28BF9C +:100BE0004027A9193A46684604F0A0FF39466846EE +:100BF000FFF7D4FF0028DBD14036B442EDD8002007 +:100C0000D6E70026F9E76FF00100D1E78410002055 +:100C10000B00AD0BC3B2012B10B540F096804DF622 +:100C2000C023994270D028D8B1F5615F6FD011D838 +:100C3000B1F5965F6DD008D8B1F5966F6BD0B1F570 +:100C4000166F6BD06FF007005DE0B1F5165FF9D15C +:100C5000454B33E047F61223994262D007D8B1F5ED +:100C6000964F61D0B1F5E14FECD1404B26E0B1F5A4 +:100C7000164FE7D14FF41D0320E03D4B994255D06C +:100C800011D8B1F5E13F54D007D8B1F5614F53D039 +:100C9000B1F5963FD6D1374B10E0B1F5613FD1D1D8 +:100CA0004FF06C730AE0B1F5612F48D0324B994296 +:100CB00048D0B1F5E12FC5D14FF0E86310F4E06FF3 +:100CC00046D110F4404103D0B1F5804F43D11021FB +:100CD00010F4405203D0B2F5805F3FD10E2210F4E1 +:100CE000403003D0B0F5403F3BD10120234C20766B +:100CF0006276E361A1760A4324681043C4F8243580 +:100D0000C4F86C05002010BD4FF46503D6E71C4BFA +:100D1000D4E71C4BD2E74FF49E23CFE74FF41D23BB +:100D2000CCE74FF40003C9E7174BC7E74FF08063E8 +:100D3000C4E74FF0EB73C1E74FF46B03BEE74FF02E +:100D40007063BBE74FF08053B8E76FF00300DAE75A +:100D50006FF00800D7E76FF00A00D4E76FF00900E2 +:100D6000D1E76FF00B00CEE70050270000C0750000 +:100D700090D0030000903A0140420F008410002000 +:100D800000F03A0000B0130000A04E0010B5114C66 +:100D9000204600F0D5FD0021206884F8201000F0E6 +:100DA00047FF226842F30732002A11DB0121530971 +:100DB0009B0003F1604303F56143D3F8800202F026 +:100DC0001F0201FA02F20243C3F88022D3F88032F4 +:100DD000002010BD8410002010B5264C0021206892 +:100DE0008EB000F017FF226842F30732002A12DBB0 +:100DF000012053099B0003F1604303F56143D3F8DD +:100E0000801202F01F0200FA02F221EA0202C3F885 +:100E10008022D3F880322422002105A804F0ABFE02 +:100E2000D4E902124FF0FF33CDE90133CDE90333AA +:100E30004FF0EB730B93072301A88DF8363001F0C8 +:100E400080F8014600220B4800F03AFA0A4B98421B +:100E50000DD10C9B002023831B0CA3760B9BC4E9B4 +:100E60000400E361012384F820300EB010BD6FF060 +:100E70000100FAE7841000200000AD0B7FB50025CB +:100E80000C46C0F808511646D0F80821D0F80022C8 +:100E900022F02002C0F80022104AC0F8082301F016 +:100EA000A0F8A560256104F14C03D3E8EF1F21F4FD +:100EB0007F0121F47041C3E8E21F002AF5D110221E +:100EC0002946684604F057FE042368468DF8003032 +:100ED0000196D4E90013984704B070BD10020A00CF +:100EE00000F01F03400909D1074A02EB8303D3F83E +:100EF0000012064A0A40C3F80022704700228033DD +:100F000052F82330FFDE00BF00258450F0F8FCFFCC +:100F10002DE9F04F804690F804A05020664F0E4611 +:100F200000FB0A70416885B049B113F0020304D098 +:100F3000836B002B00F0B2800123009300E000914E +:100F40004FF0500909FB0A73DB6CD90600F1A6804B +:100F50004FF0010BB3180293029BB34200F0A7803D +:100F600006F06043039398F80450D8F8004009FB5A +:100F700005739A6B9AB9039BB3F1005F40F09980B7 +:100F8000019200F0A1FD09FB0572D16C204601F031 +:100F9000010100F0F4FF28B900F09EFDE3E7012312 +:100FA0000193EEE7516C33460029BCBFD4F84C15D1 +:100FB0005164019A42B109FB05713078D1E90E23E1 +:100FC000D054D1E90E3213441BB1C4F84435C4F8EF +:100FD00048B50022C4F82021D4F82011C4F85821C3 +:100FE000D4F85811C4F85021D4F850210822C4F87C +:100FF0000025009AE2B14FF48003C4F80433C4F82A +:1010000008B000F069FD502303FB0A725168002903 +:101010004BD1D26C120748D503FB0A734C33D3E88B +:10102000EF1F21F00801C3E8E21F002A36D0F6E7DF +:10103000C4F808B0019300F04FFD09FB0572D16CB4 +:10104000019B01F001012046019300F098FF8446C6 +:10105000019BD4F84C05D4F84425BCF1000F0DD108 +:101060009342F0D005EB850507EB0515D1B100F0F3 +:101070002BFD2946204600F0F9FFC2E705EB850568 +:10108000934207EB0515F1D10028EFD100F01CFDCC +:101090002946204600F0EAFF00F01EFD074805B093 +:1010A000BDE8F08FC4F80CB0E1E7013654E704481E +:1010B000F5E70448F3E700BF581900200800AD0B1E +:1010C0000000AD0B0400AD0B430904D10123034A1A +:1010D00083409360704700239B60FFDE00258450AF +:1010E00000F01F0340090BD1084A02EB8302D2F83B +:1010F0000012074B0B4043F00303C2F80032704765 +:101100000022803352F82330FFDE00BF00258450D8 +:10111000F0F8FCFF2DE9F84391F8303005460C4615 +:1011200090F804800F6853BB5023DFF85091781C6F +:1011300003FB0893DE6C46F00072DA6404D0384694 +:10114000FFF7C2FFFFF7CCFF6068411C01D0FFF73B +:10115000C7FE94F82C30012B11D1502303FB0893C8 +:10116000E06846F02066421CDE6401D0FFF7B8FE5E +:10117000A068431C03D0FFF7A7FFFFF7B1FF94F867 +:101180002F0038B950223D4B02FB0833DA6C42F095 +:101190008072DA6494F82CC094F82D1094F82EE044 +:1011A000A26A2B6841EA0C01E668D4E90198C3F809 +:1011B00024254EEA0102C3F86C2550B9BCF1010F99 +:1011C000C3F80C75C3F8149504BFC3F80885C3F8B9 +:1011D0001065D3F8682503F26458012A08BFDA6065 +:1011E000D3F86425012A13D1082149F64046244F3B +:1011F000C3F8001547F001075A60D8F800309BBBD0 +:101200002B68D3F88024C3F880240022C3F800257B +:1012100000212B6894F83220C3F81011D3F8100184 +:10122000C3F82011D3F82001C3F82411D3F8240106 +:10123000C3F84411D3F84401C3F85811D3F8581136 +:1012400043F307335201002BD2B212DB0D49C81809 +:1012500080F800230122580903F01F0302FA03F368 +:1012600041F82030BDE8F8834020B847013EC4D1A2 +:10127000C6E7054903F00F03CA54F3E758190020E5 +:10128000A034000000E100E014ED00E000F01F03D6 +:1012900040090BD1084A02EB8302D2F80012074B37 +:1012A0000B4043F00203C2F8003270470022803343 +:1012B00052F82330FFDE00BF00258450F0F8FCFF19 +:1012C0002DE9F3479046502290F80490514C02FBD0 +:1012D00009FA04EB0A070D4697F84810064600295C +:1012E00040F0858009EB890004EB001004F043FC1A +:1012F0002B69AA6944F80A306B69BB6302B1013AF1 +:10130000502303FB0943DA632A6A042A81BFEA698E +:101310009A610022DA6195F83130B8F1000F37D0C8 +:1013200083F001073F0233B1502303FB0943DA6C1A +:1013300042F00102DA64502303FB09440123C4F89C +:10134000048084F848303468D4F84C2112BB294614 +:101350003046FFF7DFFE0822002334688DF807309F +:10136000C4F800250DF10702C4F84425C4F8483537 +:1013700001230A25A360E360D4F85831002B38D14B +:10138000254B402043F001039847013DF4D128E06C +:101390004746C8E708224FF06409C4F80025636097 +:1013A0001D4B43F0010AD4F84431F3B9D4F8243189 +:1013B000DBB94FF42070D047B9F10109F3D14B46A6 +:1013C0000022C4F84C21D4F84C11C4F81021D4F8F0 +:1013D0001011C4F84421D4F84411C4F80025002B9E +:1013E000B5D10E4802B0BDE8F0870123E8E70C480C +:1013F000F8E70023C4F820319845D4F82021C4F838 +:101400000035084B08BF00231F4333680648C3F864 +:101410000473E7E758190020A03400000100AD0B69 +:101420000C00AD0B10020A000000AD0B2DE9F047D7 +:1014300084460F469E4616460579002A51D04FF045 +:10144000500ADFF8BC80D0F800900AFB058ADAF871 +:10145000044074B96BB9DAF82C50002D43D1CAF8A6 +:101460002C20FFF755FD0446CAF82C502046BDE855 +:10147000F0871EF0030F07D0734632463946BDE8A9 +:10148000F0476046FFF744BD07F06043B3F1005FEB +:101490004FF0500414D104FB058400F015FBE26A00 +:1014A0001AB1134C00F018FBE0E7C4E90A7605EB2B +:1014B0008505484608EB051100F0E3FD0D4CF1E70A +:1014C00004FB0584A36B83B100F0FEFAE36A002BF2 +:1014D000E7D1E36C012243F00203C4E90A76E36436 +:1014E000E5E7054CC2E7024CC0E7044CBEE700BF8D +:1014F0000B00AD0B0000AD0B0600AD0B0A00AD0BF1 +:10150000581900202DE9F84350230679284D046826 +:1015100003FB0653D4F80473DA6AC4F8087322B9DB +:101520002448C4F80473BDE8F88306EB860005F18F +:101530004C024FEA001802EB0012D2E8EFCF4CF059 +:10154000080CC2E8E0CF0028F7D10122E26031B3F5 +:10155000D96C01EA02094946204600F010FD002836 +:10156000FAD000F0B1FAB9F1000F1CD1D4F8203153 +:1015700053B10123E360D4F85831002BFBD020464F +:1015800005EB080100F072FD00F0A6FA0023C4F894 +:101590002031D4F82021502202FB0655EB62064888 +:1015A000C4F80473BFE7D4F85831002BE7D1EBE758 +:1015B000581900200500AD0B0000AD0B2DE9F843D4 +:1015C00090F80480484F08EB8806360106F14C0578 +:1015D00004683D44D5E8EF3F43F48052C5E8E02F6E +:1015E0000028F7D1DB047BD4480705D5502303FB43 +:1015F00008739B69002B75D03C4B3E44C4F80833FC +:101600000823C4F800350023C4F81031D4F81021A1 +:10161000C4F84C31D4F84C21C4F844310A03D4F84E +:1016200044318B0303F4804302F400524903134313 +:1016300001F400410B43D5E8EF2F1A43C5E8E12F31 +:101640000029F8D14FF0500909FB0879D9F8083082 +:10165000BBB9D9F8043063B9D5E8EF3F23F47F0371 +:1016600023F47043C5E8E23F002AF5D12048BDE8E5 +:10167000F8830321304600F08FFCD9F80830002BA6 +:10168000EAD0D4F80433590713D5502303FB087369 +:101690005A6872B1DB6C1A030BD5D5E8EF3F23F41F +:1016A0000023C5E8E23F002AF7D10621304600F0CA +:1016B00073FC502303FB0873DB6CDB0401D40D487F +:1016C000D5E7D4F84C310BB901232360502303FB39 +:1016D00008735B68002BF2D0044BC4F80433EEE7C8 +:1016E0000548C4E70548C2E75819002010020A005F +:1016F0000200AD0B0000AD0B0B00AD0B0800AD0BF5 +:101700002DE9F84F056801F06043D5F804A3B3F163 +:10171000005F90F804B089461746C5F808A37CD14D +:101720005023404E03FB0B639A6852B15B68002B59 +:101730005ED1D5F81021002A5AD0C5F81031D5F85D +:101740001031502404FB0B64E269C4E90297002ABB +:101750003CD00BEB8B0897424FEA081824D24FF08D +:10176000000B3A46A1694846C4E902BB04F0DEF921 +:10177000E269A069D21BC119E26104F0A1F96368B2 +:101780006BB1B04449463A46404600F016FCE36C63 +:10179000990404D55A4641462846FFF76FFB224874 +:1017A000C5F804A3BDE8F88F4846A16904F0BEF966 +:1017B000E36963620023E3611C4B9844D8E8EF3F80 +:1017C00043F40023C8E8E23F002AF7D1502303FB8B +:1017D0000B63586ADA689968DB6C121A0144C5F821 +:1017E0003415C5F83825DA04D9D501232B60D6E79E +:1017F000502303FB0B6633698BB9F36CC5F8349542 +:101800005B04C6E90497C5F83875C8D5D5F8003229 +:1018100043F02003C5F80032C1E70548C0E705489A +:10182000BEE700BF581900200000AD0BA41900202E +:101830000A00AD0B0B00AD0B50232DE9F0413C4DE0 +:1018400090F80480046803FB0853DB6CD4F80002B2 +:1018500003F48223B3F5805F68D1D4F80463C4F83D +:10186000086309B9830656D4D4F8003223F0200364 +:10187000C4F800324FF4842308EB8801090101F118 +:101880004C072F44D7E8EF0F1843C7E8EC0FBCF123 +:10189000000FF7D12AB9502303FB08535B68002BD4 +:1018A00041D1244BC4F8083301236360D4F8443198 +:1018B000002BFBD00023C4F84C31D4F84C21C4F8E1 +:1018C0001031D4F81021C4F84431D4F844315023F5 +:1018D00003FB0853DA6C12F400421FD1DA61204690 +:1018E00000F07FFB502303FB08550023AB602B6106 +:1018F000D7E8EF3F23F47F0323F47043C7E8E23FC8 +:10190000002AF5D126F4202626F404760A48C4F8E5 +:101910000463BDE8F0814FF48033ADE720462944ED +:1019200000F071FBDBE701236360EFE70348F0E7BA +:101930005819002010020A000000AD0B0500AD0B85 +:101940002DE9F8432F4A03680679C3F8082343F3C7 +:101950000733002B04460DDB01215A0903F01F0356 +:10196000994002F12003284A42F82310BFF34F8F19 +:10197000BFF36F8F012220461146FFF75DFF012163 +:101980002046FFF7BFFD50212279204D236801FB3F +:101990000252D46CD3F80C05E201D3F81495D3F8B5 +:1019A0000875D3F8108509D54FF0FF32C3F80C2520 +:1019B000C3F81425C3F80825C3F81025A30116D5CC +:1019C000411C01D0FFF762FCB9F1FF3F02D048464D +:1019D000FFF75CFC22010AD5B8F1FF3F02D0404678 +:1019E000FFF754FC7B1C02D03846FFF74FFC502316 +:1019F000002203FB0653DA6483F848205A60BDE8EE +:101A0000F88300BF10034A0000E100E058190020ED +:101A100010B5502402790B4B04FB02335B686BB9A1 +:101A20000368D3F810211AB9D3F80025082A07D083 +:101A300011B1D3F83C350B60034810BD0348FCE7F7 +:101A40000348FAE7581900200000AD0B0800AD0B61 +:101A50000B00AD0BF7B505461646FFF751FE164BCA +:101A60000446984222D1032128462F79FFF7A6FD8C +:101A7000124B984201D0A0421BD15022104B02FBC6 +:101A800007335B6893B90C4C284601A9FFF7C0FFE8 +:101A9000A042F9D1019BB34207D301222846114647 +:101AA00003B0BDE8F040FFF7C7BE064C204603B0C8 +:101AB000F0BD0446FAE700BF0000AD0B0B00AD0B14 +:101AC000581900200800AD0B72B6024A13680133A2 +:101AD00013607047A8190020034A1368013B136084 +:101AE00003B962B6704700BFA81900200722024B55 +:101AF000C3F80423704700BF00300050084BD3F8F0 +:101B00000801D3F80021003818BF012080000AB175 +:101B100040F00100D3F804310BB140F002007047EF +:101B2000003000500022074BC3F80021D3F8001109 +:101B3000C3F80421D3F80411C3F80821D3F80831FD +:101B4000704700BF0030005000231720094A03F5FA +:101B5000C0710133202B42F82100F8D10023172057 +:101B6000044A03F5E0710133202B42F82100F8D13B +:101B7000704700BF0030005010B59DF80840C00B02 +:101B800043EA0423064CC90B43EA0213884200D9F6 +:101B900010BDC2B202F5C07244F822300130F5E740 +:101BA0000030005010B59DF8084000F1604043EA55 +:101BB000042301F16041074C400B490B43EA021337 +:101BC000884200D910BDC2B202F5E07244F822305A +:101BD0000130F5E700300050C0F30E03C3F50043B9 +:101BE000B3FA83F3C3F11B03064AC0F3C730DBB279 +:101BF00040F4807043F48073C2F80005C2F80435E5 +:101C0000704700BF0030005043030148184470473C +:101C1000FF1FF81F054BC0F30730090241F03001E8 +:101C200000F5007043F82010704700BF00300050EE +:101C3000044BC0F30730090200F5007043F8201090 +:101C4000704700BF00300050003A18BF0122003931 +:101C500018BF012103688900074841EA420118635F +:101C600059605A6842F001025A60BFF34F8FBFF3C8 +:101C70006F8F0020704700BF04AAFF0008B5FFF770 +:101C80003DFF044B162118600348BDE8084000F0F2 +:101C90003AB900BFEC1F0020A63400000C22014915 +:101CA00003F044BFEC1F002010B501F006FB044612 +:101CB00090B901F097FF044680B90A4A2E21D368F3 +:101CC000094823F008031B041B0C43F0BF6343F4D3 +:101CD0000033D36000F017F9204610BD45F22354BD +:101CE000FAE700BF00ED00E0BC340000024B1860D2 +:101CF000024B002019607047FC1F0020F81F0020D5 +:101D00001FB5374C426923685A6282699A62C26978 +:101D1000DA62026A1A63426A5A63826A9A63C26A20 +:101D2000DA63026B1A64426B5A60826B9A60C26B10 +:101D3000DA60026C1A61426C5A61826C9A61C26C00 +:101D4000DA61026D1A62826D5A65C26D9A65026F20 +:101D5000DA65026E1A66826E5A66426F9A66EFF311 +:101D600088825A64EFF389829A644268DA64EFF3F6 +:101D700094821A65026801A81A67FFF78FFF23682B +:101D8000019ADA6601221A70164B1C686408640016 +:101D90002046214622462346B7EE000AF7EE000A07 +:101DA000B7EE001AF7EE001AB7EE002AF7EE002A97 +:101DB000B7EE003AF7EE003AB7EE004AF7EE004A07 +:101DC000B7EE005AF7EE005AB7EE006AF7EE006A77 +:101DD000B7EE007AF7EE007A03F081FF04B010BD91 +:101DE000FC1F0020F81F002000B5A1B001A8FEF7DD +:101DF000C9FD029B63B15B060AD4019B23F0020379 +:101E0000052B05D1044B1B6813B101A8FFF778FF20 +:101E100021B05DF804FB00BFF81F0020BFF34F8F17 +:101E20000549064BCA6802F4E0621343CB60BFF376 +:101E30004F8F00BFFDE700BF00ED00E00400FA0592 +:101E400038B543680C2B30D14B68042B2DD14FF0A3 +:101E5000FF3304680D6821682B6031B3A26822B398 +:101E60000623012001F081F9F8B96168104B99420D +:101E70000DD9A2680F485318834202D9F43883421F +:101E800010D8206803F052FE0020286038BD0A4BAD +:101E900099420AD9A26809488B188342F1D90730C0 +:101EA000814202D906488342EBD90220EEE700BF07 +:101EB000FF01FF002C02FF002F01FF003801FF008F +:101EC0004C01FF004FF47F03D3F83001D3F83431D5 +:101ED000BFF34F8FC0F30B00A0F1090358425841E4 +:101EE00070474FF47F02D2F83031D2F83401BFF39B +:101EF0004F8FC3F30B03092B03D1431E58425841A4 +:101F0000704700207047FEF7CDBD012070476FF08D +:101F100003007047002382B00193019802B070471C +:101F20006FF003007047002382B00193019802B064 +:101F30007047022814BF6FF0030000207047FEF7BF +:101F40002DBE30B501EB8202914200D130BD51F877 +:101F5000044B04F07F037F2B4FEA144408BF4FF07B +:101F6000FF33032CF0D8DFE804F002040608036016 +:101F7000EAE74360E8E78360E6E7C360E4E7034637 +:101F8000D0F8580130B931B9D3F82001003818BF62 +:101F9000012070470120704730B504460D4685B0DA +:101FA00010220021684603F0E6FDD4E900136846DC +:101FB0008DF80050984705B030BD7FB504460E46F9 +:101FC000154600211022684603F0D5FD012368461E +:101FD0008DF80030CDE90165D4E90013984704B0CD +:101FE00070BD10B50446FFF76FFDD4F8583133B11A +:101FF000D4F8043313F4800308BFC4F80035BDE8F7 +:102000001040FFF769BD70B50D4604460522AA21B0 +:10201000D0F83C65A86903F0AEFD0522AB69C4F8B1 +:102020003435C4F838250022C4F81021D4F8101132 +:102030000121E162D4F810110029FBD0C4F84C2131 +:10204000D4F84C11C4F81021D4F81021D4F83C2550 +:102050009642EA6109D15A1E043312F8011FAA29D7 +:1020600003D19A42F9D10023EB6170BDD0F8583109 +:1020700033B1CB6CC3F30032DB0458BFC0F800258A +:10208000704770B5CE6C0D4606F001010446FFF7AF +:1020900076FF38B94FF4800346F00406EE64C4F8C6 +:1020A000043370BDD5E90A365AB1296CEA6B761A49 +:1020B000964228BF164619443246A86B03F036FDF7 +:1020C000AB6B4FF0FF326A641BB1C4F84435C4F8FF +:1020D00048650023C4F82031D4F82021C4F85831D1 +:1020E000D4F85821C4F85031D4F850310823C4F83A +:1020F00000354FF48003C4F804330123A360D0E714 +:10210000C0037047C00300F5FF407F3070470020D8 +:1021100070471F2070474FF40040704700F57F4024 +:10212000C03040037047402070475F2070474FF435 +:10213000005070474B6830B513F01F052DD1026871 +:102140000C681068C0F30720844226D2506855609E +:1021500094600C7C23F01F0304F001041C438B7C6F +:10216000DB0003F018031C434B7C5B0003F0060309 +:102170002343D3608B68013B23F01F04CB685B00D3 +:1021800003F00E03234343F0010313615060BFF3D8 +:102190004F8FBFF36F8F284630BD0120FCE70368E7 +:1021A00000205A68C9B258609960D86018615A60B6 +:1021B000704770B50546002403681E68C6F30726FD +:1021C000B44201D1002070BD21462846FFF7E7FF49 +:1021D0000134F5E70020704708B5FFF705FE72B639 +:1021E00020BFFDE7002070470020704770470B4676 +:1021F00070B5114618B1032806D0032070BD1846EB +:10220000BDE87040FFF71CBE5A68082A01D00220C2 +:10221000F4E74C68042CFAD11D684FF0FF330E68C8 +:10222000296833600029F2D06B68002BEFD0262399 +:102230007422012000F099FF0028E8D122462323D0 +:102240000120696800F091FF04460028DFD1D5E93C +:102250000001FFF74BFD30602046CFE730B50C465C +:10226000154685B038B139B1FFF74DFE041E03DACB +:10227000204605B030BD044640F60D23ADF80E30C3 +:1022800009230B4901A805F00F028A5C2D091A5495 +:10229000013B012BF7D147F630030C21ADF8043098 +:1022A000FFF731FE0028B4BF04462418E0E700BF62 +:1022B000EA34000008B5194B83F30A88184800F087 +:1022C000E7FF40BBFFF7F0FC28BBFFF783FF10BB25 +:1022D000FFF788FFD8B9FFF789FF00F067FA3521CB +:1022E0001048FFF710FE17210F48FFF70CFE1821CA +:1022F0000E48FFF708FE00F03FFC21210C48FFF7D5 +:1023000002FE00F03FFA00F0CDFE002008BDFFF70E +:102310006BFF0028DFD000F021FEEEE70004002074 +:10232000AC190020FA3400002F35000046350000BB +:102330005E3500002DE9F04F93B00093574B814676 +:10234000D3F800B00BB2002B0D461646C1F302685D +:10235000C1F3024A04DA6FF0800013B0BDE8F08FD9 +:102360008B42C36001D10020F7E708EB0A03042B7E +:10237000F1D8CC0F64014FEAC80731463A46DBF882 +:10238000080044F0020300F0F0FE01460028E2D10C +:10239000202202A803F0EFFB3A46314602A84FEA9A +:1023A000CA0603F0C3FB32460099DBF8080044F08C +:1023B000060300F0DAFE07460028CCD101462022B1 +:1023C0000AA803F0D8FB324600990AA802AE03F02F +:1023D000ADFB33460137B84514D91A46BC460DE06B +:1023E000D2F808E0D16818687144884203D259686D +:1023F00008448645AFD30CF1010C0832C445EFD137 +:102400000833E7E715F0006F18BF202409F118071B +:1024100007EB880844F00203474513D1202C26D14E +:10242000002D02DBC5F3C0456C0109F128050AAE99 +:1024300005EB8A0A44F0060455451AD1009BC9F8F9 +:102440007C3090E77268DBF8080056F8081B0193AF +:1024500000F08BFE00287FF47EAF56F8042C019B21 +:1024600047F8042F56F8082C38633A62D4E7002462 +:10247000DBE77268234656F8081BDBF8080000F01B +:1024800074FE00287FF467AF56F8043C45F8043F1B +:1024900056F8083C28642B63CEE700BFB019002033 +:1024A0002DE9F0411F461A4B0D461646D3F8008021 +:1024B00000F051FF044608B900F050FDE368002B1E +:1024C00001DA00F04BFD032D01D900F047FD04EBCC +:1024D0008504E569E36CED1A16D006233A463146C9 +:1024E000D8F8080000F041FE08B100F037FDBD4209 +:1024F00028BF3D46E36CE16B2A461944304603F0A1 +:1025000015FBE36C2B44E3642846BDE8F08100BF73 +:10251000B01900202DE9F0411D461A4B0F46164612 +:10252000D3F8008000F017FF044608B900F016FD4C +:10253000E368002B01DA00F011FD032F01D900F050 +:102540000DFD04EB8704E36AE26E9B1AAB4201D2F5 +:1025500000F004FD02232A463146D8F8080000F0B6 +:1025600004FE08B100F0FAFCE36EE06D2A46314645 +:10257000184403F0DBFAE36E00202B44E366BDE869 +:10258000F08100BFB01900202DE9F04105464FF061 +:1025900001080E4E0E4B37685C681CB92846376040 +:1025A000BDE8F0812268936813F400700CD1A36930 +:1025B000012B09D0D368346023B19847002801DA91 +:1025C00000F0CCFCC4F81880246AE6E7B0190020BB +:1025D000B419002038B5044610B96FF0800038BD3A +:1025E0008368002BF9D01A68002AF6D05D68002DA8 +:1025F000F3D00B4BE861A8691D6020B92B68DB683C +:1026000053B90123AB61A36804F10C001B681B697B +:10261000984701232360E2E798470028F1DADCE7D6 +:10262000B01900204268024B08461A60704700BF8C +:10263000B0190020002330B505680446AA68C0E937 +:10264000063312F4406F85B01CD012F4006202D041 +:1026500000F0C2F90246A96A2B6968460B440731AB +:1026600023F0070321F00701CDE9021300930023B3 +:1026700005490193EB6800F09FFA684600F04EFEB2 +:10268000024B1C6005B030BD89250000B019002048 +:1026900001F00C0370B50C2B0CBF154605464A061D +:1026A0000C461ED58B0658BF2835AB6913F8026C53 +:1026B00084F00803C3F3C003C6F38012934201D031 +:1026C00000F04CFC16F0A00F1ED176B1032E17D0EF +:1026D00032461C210E48FFF7C1FD6FF083032B60CB +:1026E0000CE000F03BFC2026E2E700F03BFB044658 +:1026F000FEF70EFA2146074800F00AFE204670BD9C +:10270000284600F03FFAF9E732461F210248E2E787 +:102710007F350000F80B00209B350000EFF30880A8 +:102720007146EFF30982EFF30B830CB406B4FFF7A5 +:10273000AFFF8646009900F0080001F00801401A3A +:1027400002DC12DB04B070471EF020031CBF2DE931 +:10275000F00FBDF1080D0A4C254626462746A04637 +:10276000A146A246A34630B4704704B030BC1EF068 +:1027700020031CBF1DF1080DBDE8F00F04B0704729 +:10278000A5EDF5FE084BDA6882F07F4282F47F0205 +:1027900042F48042DA6000221A765A76DA76DA77E4 +:1027A000602283F82220704700ED00E0114BD3F83F +:1027B000882042F47002C3F88820BFF34F8FBFF324 +:1027C0006F8FD3F88C2042F44062C3F88C20D3F88A +:1027D000342242F08042C3F83422D3F8342242F04B +:1027E0003C42C3F834224FF0E022D36843F4200384 +:1027F000D360704700ED00E0009A164B9A4227D153 +:1028000001229043014641EC100B41EC110B41ECCD +:10281000120B41EC130B41EC140B41EC150B41EC8A +:10282000160B41EC170BEFF3148222F0040282F333 +:102830001488BFF36F8F02460346044605460646DA +:10284000074680468146824683468446864604473C +:10285000FEE70000A5EDF5FE2DE9F041044618B9AC +:1028600043F6DA30BDE8F0810029F9D0836800250D +:1028700013F4406F14BF01230023134F43F00203EE +:10288000DFF848800B6000F13006236AAB4201D8C4 +:102890000020E7E71421A369E26906EB830301FB4B +:1028A00002330C226A4399188968090709D59B5895 +:1028B000BB4201D04345D3D1186810B10121FFF7C5 +:1028C000A9F90135E1E700BFB4100020AC100020E9 +:1028D00070B5324C86B03248FFF76BFC2368012597 +:1028E0005A1C01932F4B2260DA681B694FF4407623 +:1028F000039300232A4801A902920495ADF81460BD +:102900008DF81630FFF716FC18B143F6DA3006B032 +:1029100070BD23688DF816005A1C0193224B204885 +:102920000293224B01A9CDE903352260ADF8146072 +:10293000FFF700FC0028E8D123688DF816005A1C28 +:1029400001931B4B164802931A4B01A9CDE903359D +:102950002260ADF81460FFF7EDFB0028D5D12368A5 +:102960008DF816005A1C0193134B0D480293134B1C +:1029700001A903930223049340F201132260ADF8EE +:102980001430FFF7D7FB04460028BED12A4629465B +:102990000348FFF759F92046B9E700BFBC190020EA +:1029A000A81000202C36000060360000005F0000F8 +:1029B0002006000020060000000C0020200C002053 +:1029C000014B1868704700BF2C360000014B1B6894 +:1029D000186870472C360000014B1B685868704718 +:1029E0002C36000008B5FFF749F9FFF79BF80A4BB2 +:1029F0005A68103AD3B2120609D401215A0903F0D9 +:102A00001F03994002F16003044A42F82310BDE815 +:102A1000084000F0A3BA00BF00ED00E000E100E0D4 +:102A20000020034BD8765A6A42F470225A627047EB +:102A300000ED00E00349044BCA68002092B2134342 +:102A4000CB60704700ED00E00800FA0500224FF06F +:102A5000FF300D4B02F1A0010132102A43F8210092 +:102A6000F8D1D3F88022002022F00802C3F8802297 +:102A7000D3F88022D3F8802222F40072C3F8802297 +:102A8000D3F88032704700BF00E100E008B5FFF7DF +:102A90002DF80822024BC3F880211A60002008BDDF +:102AA00000E100E0064BD3F8D02022F00102C3F889 +:102AB000D020D3F8D02042F00202C3F8D0207047D3 +:102AC00000ED00E037B50124FFF73EF8134D224634 +:102AD000052347F6FF7100200094FFF74DF82246CA +:102AE00007234FF000500E490094FFF75BF8D5E93B +:102AF0000101072300220094FFF73EF8072300227C +:102B0000084909480094FFF74DF82969E868013938 +:102B1000FFF762F8002003B030BD00BF2C36000084 +:102B2000FF7F0020FFFF03200080002038B5002435 +:102B30000E4D002155F8040B0134FFF779F81C2CD9 +:102B4000F7D10020012240F20111094BC3F88004A3 +:102B5000C3F88424C3F8C004C3F8C424054AC2F8E7 +:102B600040154FF48072C3F8402438BDBC350000D6 +:102B700000300050009003500122014B1A60704752 +:102B8000C019002010B50446094B228918686168F5 +:102B90000623806800F0E9FA28B9064B1B68012B70 +:102BA00001D100F087F86FF08603236010BD00BFED +:102BB000B0190020C01900202DE9F84305461E4633 +:102BC0000068134B9046AA68A0F1080440E902335C +:102BD000503890420F4601D200F0C0F9A4F14809E4 +:102BE00048220021484602F0C6FF002344E907338B +:102BF00044F8143C4FF0807344F8043C6FF0020337 +:102C000044E9036744F8208CC5E90093BDE8F883E4 +:102C1000A5EDF5FE30B5094C024661680023084673 +:102C200040B105686D68954205D11BB1826860604E +:102C30009A60816030BD03468068F1E7441A002045 +:102C400038B504462AB10B689B68DB0512D5002015 +:102C500038BD0B4B1D680DB900F080F900222B68C0 +:102C600099692C338A4203D053F8040FA04204D14F +:102C70009142ECD16FF0FC00EAE70132F2E700BFCD +:102C8000B019002070B504460E46154600213022CA +:102C90000C3002F070FF064B20461B68A660636094 +:102CA000656100F001FA00232061236070BD00BF60 +:102CB000B0190020044B1B681BB11B680BB158688E +:102CC00070474FF0FF307047B0190020064B10B529 +:102CD0001C680CB900F042F923689B6813F4406F3C +:102CE00014BF0120002010BDB01900202DE9F843C9 +:102CF000904607460C46FFF7E9FF024624B96FF0FD +:102D000080042046BDE8F8836300F8D5144B04F036 +:102D10001F0153F82150002DF1D02B682946586827 +:102D2000FFF78EFFB0B9C4F30721284600F001FB7E +:102D300004460028E3D1EFF3108972B600F0C1F920 +:102D4000064689F3108840B142462946FFF79AFFAC +:102D50003E60D6E76FF08104D3E76FF08204D0E7DE +:102D6000C41900202DE9F04100F0AAF90023134E08 +:102D7000134F73607B6000F0E5F8DFF84880304661 +:102D800000F022F80446A8B139468023424600F0FC +:102D90007FF80546204600F011FB206804F1080189 +:102DA000FFF75AFD08B100F0D9F829462046FFF791 +:102DB00041FCE4E7BDE8F04100F071B9B41900202E +:102DC000441A0020C4190020F8B5054608B900F0DF +:102DD000C5F82A4E346814F1290F4CD8284B1968CD +:102DE00004F128038B4246D2236AA26903EB430312 +:102DF0000C321344606AE269024402EB820213441B +:102E00009B00E31801D2994201D200F0A7F8236891 +:102E10001C4A1B0C1B04934201D000F09FF823882E +:102E2000B3F5817F01D300F099F86368002B01DAD4 +:102E300000F094F8144A176807F124031360134A4A +:102E40001268934201D900F089F8236AA26903EB62 +:102E500043030C32616A1344E2693C600A4402EBAA +:102E600082021344326802EB83026B6832603B6279 +:102E70006F603846F8BD0027FBE700BFC410002094 +:102E80004836000000005F5FC0100020443600009C +:102E90002DE9F74F0F4690460646009300B109B959 +:102EA00000F05CF8D6F800A0DAF818200AF1300338 +:102EB00003EB820BDAF81C40204B1D6854B10C2246 +:102EC00002FB04541C601E4B1B689C4201D900F09D +:102ED00045F82C4625464FF00009DAF81C204A45F3 +:102EE00000D91CB9002003B0BDE8F08F142303FB08 +:102EF00009B20023C5E900269268AB60930516D598 +:102F0000B8F1000F02D0009B802B03D0019200F09B +:102F100025F8019A12F0E00FD0B202D158F8202023 +:102F20001AB1019000F01AF8019848F820507A6818 +:102F300009F10109AA607D600C35CEE7BC100020C4 +:102F40004036000008B5FFF73BFD034B9860FFF7E4 +:102F50003DFD80F3888808BD00ED02E0FFF73CB935 +:102F60008230012808B506D8FFF7B0FE18B9BDE8D1 +:102F70000840FFF7F3BF08BDF8B506460D4600F060 +:102F8000EAF9044608B9FFF7E9FFA7680FB9FFF7A8 +:102F9000E5FFE368991C20D05A1C21D1A5B315F197 +:102FA000820F13D102232360EFF3108672B62946F5 +:102FB0002046FFF737FB054686F310882368022B6F +:102FC00024D1204600F086F82846F8BD15F1830F7D +:102FD000EAD0FFF7C3FF0025E6E702232360FAE704 +:102FE000002BF6DB204600F0DAF93B689B689B0576 +:102FF00001D50223236015F1810FD5D16369002B20 +:10300000D2DBFFF7ABFFCFE73546F4E700232360C1 +:10301000DAE708B5FFF7E0F86FF0830008BD2DE9A7 +:10302000F3410E46174698460546FFF74FFE00F05F +:10303000A6F92946024601A8FFF758FE044660B9E2 +:1030400043463A4631460198FFF774F9044640B1C9 +:103050006B0002D5019800F03DF8204602B0BDE8B3 +:10306000F0810198FFF7B6FA0446F6E740F2011046 +:10307000704770B50546FFF729FE06462846FFF75C +:10308000C9FD044608B9002070BD014632462846F5 +:10309000FFF7D6FD0028F6D12368D868F4E76FF073 +:1030A0000200704700DF70474FF480507047B0F562 +:1030B000805F03D1EFF30B808038704700207047AA +:1030C0007047EFF30B8000F1800383F30B887047A8 +:1030D00000207047EFF30B83803B83F30B8870472E +:1030E00080EA0000E1EE100A00EE100A00EE900AFD +:1030F00001EE100A01EE900A02EE100A02EE900AAA +:1031000003EE100A03EE900A04EE100A04EE900A91 +:1031100005EE100A05EE900A06EE100A06EE900A79 +:1031200007EE100A07EE900A08EE100A08EE900A61 +:1031300009EE100A09EE900A0AEE100A0AEE900A49 +:103140000BEE100A0BEE900A0CEE100A0CEE900A31 +:103150000DEE100A0DEE900A0EEE100A0EEE900A19 +:103160000FEE100A0FEE900A70472DE9F0410E465F +:103170001546002A4AD0C4074BD480F00204C4F399 +:103180004004A40003F00602062A4AD144F00104D8 +:1031900022462946304602F077FBB8BB46E800F2EB +:1031A00005EB0608100E120208F1FF3747E800F39E +:1031B0004FEA136166D51B0264D5884262D046E8A7 +:1031C00000F547E800F7FEF7A2FF2D0E85424FEA13 +:1031D000176734D3FEF79DFF874230D82846FEF7A5 +:1031E00091FF2246C6F101010144304602F04CFB3A +:1031F000002847D00135AF4218D83846FEF780FF87 +:103200002246A8EB000102F03FFBD8B30020BDE846 +:10321000F081EFF39484A40004F0040444F0120459 +:10322000B0E7990731D544F00804B1E72846FEF726 +:1032300067FF0646FEF76FFF22460146D5E7FEF719 +:1032400072FF85421ED3FEF770FF87421AD82846C8 +:10325000FEF7DAFC2246C6F101010144304602F0D5 +:1032600013FB78B10135AF4203D83846FEF756FF5D +:10327000C6E72846FEF752FF0646FEF758FF2246ED +:103280000146EBE76FF07F00C1E74CF2DA20BEE7C2 +:1032900010B50446FFF706FCFFF714FC10B143F627 +:1032A000DA3010BDFFF742FC0028F8D1FFF710FB21 +:1032B0000028F4D102232360F3E708B5FFF7B0FB41 +:1032C00010B143F6DA3008BDFFF7B4FB0028F8D19F +:1032D00000F01CF80028F4D162B6FDF703FCFFF7FC +:1032E000B5FB0028EDD1FFF7D1FB0028EBD0E8E7D4 +:1032F000EFF30880EFF309812DE9F00F6B4672467A +:10330000FDF746FB08B0FFF76DFBFEE700207047B6 +:1033100004460D46FFF7E4FEA54628470368283318 +:1033200083F30988836883F30B88BFF36F8F40684A +:10333000704703689A68DB6812F4806F05D18B428E +:103340002CBF00206FF0FA0070478B420CBF0020AA +:103350006FF0FA00704710B5FFF7A9FE0446FFF7BB +:10336000B7FE50B9FFF7A6FCA3685B681B685B68F3 +:10337000834218BF0024204610BD0024FBE738B567 +:10338000054648B100F01BF8B5EBD07F044601D0EC +:10339000FFF7E4FD204638BDFFF78CFCF4E700237F +:1033A00010B500F1280252F8041F19B1C16F146C56 +:1033B00019444C600833202BF5D110BD70474FF0F5 +:1033C000FF307047000001464154414C20455252A5 +:1033D0004F523A2000486172644661756C740D0A60 +:1033E000004D656D4D616E616765206661756C7439 +:1033F0000D0A004275734661756C740D0A005573B1 +:103400006167654661756C740D0A0053656375727A +:10341000654661756C740D0A005265736572766558 +:103420006420457863657074696F6E2000506C612C +:1034300074666F726D2065787465726E616C206958 +:103440006E7465727275707420284952516E293AF3 +:1034500020000000290B00000B1F0000D90D000008 +:103460008D0D0000331F00003F1F0000610B0000A6 +:103470000F1F0000490B0000550B0000150C000049 +:10348000151F0000211F0000271F00001D00000065 +:10349000100004001C0002001100060003020202DA +:1034A0000338FDD87047506C6174666F726D2045AB +:1034B0007863657074696F6E3A0D0A00416C6C2018 +:1034C00070696E732068617665206265656E206341 +:1034D0006F6E66696775726564206173206E6F6ECA +:1034E0002D7365637572650D0A00303132333435E2 +:1034F000363738394142434445461B5B313B333410 +:103500006D5B536563205468726561645D2053652B +:103510006375726520696D61676520696E6974699C +:10352000616C697A696E67211B5B306D0D0A00540E +:10353000462D4D20466C6F6174204142493A204827 +:103540006172640D0A004C617A7920737461636B57 +:10355000696E6720656E61626C65640D0A001B5BB5 +:10356000313B33346D426F6F74696E672054462D62 +:103570004D2076322E302E301B5B306D0D0A0055FB +:103580006E6B6E6F776E2053504D205356432072F2 +:1035900065717565737465643A2000556E6B6E6F66 +:1035A000776E20535643206E756D6265722072658A +:1035B000717565737465643A200000000040005026 +:1035C000005000500080005000A0005000B000509B +:1035D00000E0005000F000500000015000100150C9 +:1035E0000040015000500150007001500080015017 +:1035F00000B0015000C0015000D0015000E0015067 +:1036000000F0015000000250001002500020025053 +:103610000030025000400250006002500080025012 +:1036200000A002500090035000258450008000004C +:1036300000800000FFFF0F00007C00000080000001 +:0C3640002C1200201412002000020000D8 +:10366000009A204B9A4208D110B502F0A7FA02F056 +:10367000A9F906BC96460C46744702F0E3FA009A94 +:10368000184B9A4208D110B502F0A4FA02F09AF948 +:1036900006BC96460C46744702F0D4FA0CB4029A63 +:1036A000104B9A420CD14FF0004319430CBC10B59B +:1036B00000F09EF802F086F906BC96460C46744768 +:1036C00002F0C0FA009A074B9A4202D106490868F4 +:1036D000744702F0B7FA009A024B9A4200D174473D +:1036E00002F0B0FAA5EDF5FE801000200348044B6F +:1036F000834202D0034B03B118477047A811002042 +:10370000A8110020000000000548064B1B1AD90F25 +:1037100001EBA301491002D0034B03B118477047D6 +:10372000A8110020A81100200000000010B5064CD0 +:10373000237843B9FFF7DAFF044B13B10448AFF322 +:1037400000800123237010BDC0180020000000007D +:10375000EC5E000008B5044B1BB104490448AFF30C +:103760000080BDE80840CFE700000000C41800203A +:10377000EC5E0000A3F5803A704700BF174B002BAA +:1037800008BF134B9D46FFF7F5FF00218B460F4600 +:103790001348144A121A02F0EEF90E4B002B00D017 +:1037A00098470D4B002B00D09847002000210400C3 +:1037B0000D000D48002802D00C48AFF3008002F045 +:1037C000DBF820002900FEF775FD02F0C1F800BF0C +:1037D000000008000000000000000000F80B0020BE +:1037E000A8110020B8280020000000000000000000 +:1037F0002DE9F84304460D4616461F46EFF30583B0 +:10380000C3F308030BB1FFF7A9FBDFF834903B4685 +:10381000204632462946D9F80080FFF700FCD9F847 +:1038200000300446984506D0DB6901461869BDE8BA +:10383000F843FFF7A1BBFFF793FB2046BDE8F883F1 +:10384000B019002001680E4A0346914215D1C169A2 +:10385000A2F11022A2F1EF1291420ED18268012A48 +:103860000BD8C26812B101698A4206D0586928B1E2 +:103870009B691B1A5842584170470120704700BF8E +:1038800055AA00FF0D4B70B59E68A6B13046FFF7F4 +:10389000D9FF044678B9F3686BB935690DB920468C +:1038A00070BD2846FFF7CEFF28B9EB68B34202D1BE +:1038B0002E462D69F2E70124F1E700BF4C1A0020E3 +:1038C0002DE9F041394E3468F368D4B1B468C4B11D +:1038D000002861D000295FD001FB00F5B5FBF0F0B6 +:1038E000884259D1291D57D8AA070DD025F00302C7 +:1038F00004321C4603E0616891424FD2A469002C57 +:10390000F9D12046BDE8F0812A46F2E7891A232939 +:1039100004F12007D4E9050C1AD80123A360A0B153 +:10392000C0F818C0A36903B158610023C4E9053386 +:103930003369DB0702D5FFF7A5FFA0BB2A460021AC +:1039400038463C4602F017F9DBE7C6F80CC0E9E759 +:1039500002F1200804EB08032039C3E9011E2169A4 +:10396000DFF84CE0DC60196144F808E0AEF1102E9D +:10397000AEF1EF1EC3F81CE001B1CB60C3E9050C4A +:1039800040B18361996901B14B6101212361C4E9AF +:103990000121CAE7F360F5E70024B2E7D4F808E0B4 +:1039A000BEF1000FB2D0012001F0D2FF4C1A00206E +:1039B00055AA00FFF8B5054600283AD0364E3368C0 +:1039C000002B36D0B268002A33D0834203D8726805 +:1039D0001344984202D3012001F0BAFFA0F1200461 +:1039E0002046FFF72FFF0028F5D155F8183C012B92 +:1039F000F1D145F8180C55E90570DFB1BB68CBB9BA +:103A000055F81C2C7B68203213447B60386100B170 +:103A1000C76020220021204602F0ADF8386908B1C5 +:103A200083682BB333699B0703D5FFF72BFF00286F +:103A3000D1D1F8BD40B3836833BB42686368134497 +:103A4000203363600369D4E90521236192B9B9B9D0 +:103A5000426981696261A161A2B19461A26902B106 +:103A6000546103B1DC602022002102F084F8D9E720 +:103A70003C46E2E79161A1690029E9D04A61E7E7A4 +:103A8000F160F8E7F460E9E7F36845F8083C03B152 +:103A90005C61F460C6E700BF4C1A002038B50D46E3 +:103AA000142200210446124802F065F81149124818 +:103AB00000F02CF8232D1AD914F003031FBF043D86 +:103AC000ED18C3F10403E4182A460021204602F051 +:103AD00052F8074B094AC3E900459C602260A2F1F5 +:103AE0001022203DA2F1EF126560E261DC6038BD7A +:103AF0004C1A0020B5390000C138000055AA00FF5B +:103B0000014B1B68184700BFC8100020024B18600B +:103B1000024B002019607047CC100020C810002014 +:103B200010B50A46044619B1024B00211B6898479C +:103B300010BD00BFD0100020054B0A46197819B1FE +:103B40000146181D02F0B8B86FF08800704700BF3A +:103B5000601A002010B5074C00F032F8204602F041 +:103B6000BCF8201F0821FFF7DBFFBDE8104002F082 +:103B7000A0B800BF641A002070B50C4D2B787BB93B +:103B800002F095F8044670B90126281D6E7002F007 +:103B900091F8044638B900F00BF8044618B92E70B5 +:103BA0000024204670BDFFF7D5FFFAE7601A002019 +:103BB0000122024B002083F880257047681A0020FC +:103BC000F8B5074C2025264601272046276202F03B +:103BD0005DF8013D04F12C04F7D186F88055F8BD5D +:103BE000681A002010B50C4C236813B10B4B1B68EE +:103BF0002BB900F089FC30B90122084B1A6001236F +:103C00002360002010BD0138062801D9044810BDEA +:103C1000044B53F8200010BD042000200020002099 +:103C2000FE8FFFFF545D0000144B2DE9F043002888 +:103C300014BF04461C46124F236883B0BB4219D000 +:103C400016460D4620464FF4147104F1040900F0A5 +:103C500047F804F58E78484600F0CEF8404600F06C +:103C600019FA2B464A4640460649009600F034FBB6 +:103C700008B92760002003B0BDE8F08308200020C9 +:103C8000A5BCC95A7D3E000070B50D4C1D460028EC +:103C900014BF064626460B4B306882B098420DD1C1 +:103CA00006F58E70002633460096144600F03EFB63 +:103CB000002818BF34462C6002B070BD0248FBE7F4 +:103CC00008200020A5BCC95AE88FFFFF38B131B1E8 +:103CD000002201440346013081421A70FAD1704734 +:103CE00038B131B1002201440346013088421A70D4 +:103CF000FAD170472DE9F043202B1D460646884631 +:103D000091468BB026D8DBB202AF8DF80480B04666 +:103D100058F8041B8DF8053021B9404601F038F9F8 +:103D2000044640B9012302224046336001A901F054 +:103D300043F9044638B138462021FFF7D1FF204629 +:103D40000BB0BDE8F0832A464946404601F034F9FD +:103D50000446F0E702AF1946104600233A4601F048 +:103D6000B1FA04460028E6D12023B9461D46CCE727 +:103D70002DE9F041D0F8F830A6B0002BD8BF02AE44 +:103D800031DD05460027D0F80C4102AEA4F1010850 +:103D90009022D5F8FC403146D0F8000101AB0197E4 +:103DA000B8FA88F8A0474FEA5818044620B9019B92 +:103DB00053B9B8F1000F16D030469021FFF790FFAD +:103DC000204626B0BDE8F081014632462846FFF77E +:103DD00091FF58B9D5F80431019A1344C5F804315C +:103DE000B8F1000FE8D16FF03C04E5E70446204647 +:103DF00026B0BDE8F08100BF70B50025044614224E +:103E000004F588762946C0F8F850FC3001F0B3FE7E +:103E100008222946304601F0AEFE144B30461B689E +:103E20009847204640F8045B01F098F8104B304664 +:103E30001B689847C8B9D4F8F830AB420FDC03EBE5 +:103E4000830204EB82022021C2F800010120C2E9B2 +:103E5000421008490133C2F8FC10C4F8F830064B90 +:103E60003046BDE870401B68184770BDF010002058 +:103E7000EC100020614E0000E81000202DE9F04712 +:103E8000202A88B07AD83F4B00F5887904461B6811 +:103E900048461746884698470546F8B940F2011645 +:103EA000D4F8F830002B65D02046FFF761FF0546B7 +:103EB00038B9D4F8F830002B14DC013EF2D16FF0A1 +:103EC0003B056E4620213046FFF70AFF2E4B484641 +:103ED0001B689847002818BF6FF01D05284608B0DA +:103EE000BDE8F087D4E94132934203D3D4F80C21E2 +:103EF000012A02D0013ED7D1E1E71F2BFAD96E4645 +:103F000001462022304604F1040A01F034FE314615 +:103F1000504601F07BF805460028D3D1504601F009 +:103F200031F8504601F01AF82946504601F030F8B1 +:103F300005460028C6D150462022314601F03CF803 +:103F400005460028BED1034632462021304601F006 +:103F5000B9F905460028B5D1D4F8F8303A46002B17 +:103F6000C8BF002340463146C8BFC4F8043101F041 +:103F7000DDFDA7E76FF03F056E46A3E76FF03B0559 +:103F8000ACE700BFEC100020E810002070B590B046 +:103F900000287AD03E4C0D46402236210646204667 +:103FA00001F0E9FD40225C21A01801F0E4FD2B1D89 +:103FB000A34203D904F144039D4252D32B682268E3 +:103FC000534023602B68226C534023646B68626803 +:103FD000534063606B68626C53406364AB68A26873 +:103FE0005340A360AB68A26C5340A364EB68E268E3 +:103FF0005340E360EB68E26C5340E3642B69226951 +:1040000053402361226D2B695340236562696B69BC +:10401000534063616B69626D53406365AB69A2692C +:104020005340A361AB69A26D5340A365EB69E2699C +:104030005340E361EB69E26D5340E3650021304694 +:1040400000F0A6FF044628B940223046104900F08F +:10405000B3FF044640216846FFF742FE204610B0F9 +:1040600070BD631E04F13F01013D1F3415F8010FBF +:1040700013F8012F42401A7011F8012F2878A3423B +:1040800082EA00020A70F1D1D8E76FF07304E5E725 +:104090005822002010B54FF49A720021044601F016 +:1040A0006AFD42F21072044BC4F8202104F59670A8 +:1040B0001B68BDE810401847F01000202DE9F04FB4 +:1040C00004468846914695B0002900F0A780002A52 +:1040D0000CBF0126022600238DF80C30002C00F0C6 +:1040E0009F80DFF844A104AF0AF1400B0021204675 +:1040F00000F04EFF034648B1384620210193FFF7F8 +:10410000EFFD019B184615B0BDE8F08F40225146E7 +:10411000204600F051FF03460028EDD104F1F405DC +:1041200020222946204600F047FF03460028E3D11D +:104130000122204603A900F03FFF03460028DBD1FF +:10414000022E62D020460CA900F060FF0346002832 +:10415000D2D10146204600F01BFF03460028CBD1F8 +:1041600040225946204600F027FF03460028C3D1CD +:10417000202220460CA900F01FFF03460028BBD1D7 +:104180003946204600F042FF03460028B4D13946A4 +:104190002046FFF7FBFE03460028ADD1202229462A +:1041A000204600F009FF03460028A5D120460CA9AF +:1041B00000F02CFF034600289ED10146204600F067 +:1041C000E7FE0346002897D140225946204600F0DA +:1041D000F3FE034600288FD1202220460CA900F0D0 +:1041E000EBFE0346002887D12946204600F00EFF4B +:1041F0000346002880D19DF80C200132D2B2B24291 +:104200008DF80C20FFF472AF76E74A46414620460F +:1042100000F0D2FE0346002894D06DE701265AE74D +:104220006FF0730304AF67E7582200202DE9F041D7 +:104230000446884615461F46D0F81801E0B013B171 +:1042400000EB40004008B5F5807F3ED82844B0F52B +:10425000C07F3AD84FF4C0720021684601F08BFC51 +:104260006946D4F82431D4F81821D4F828019847A5 +:1042700070BBD4F8186157B1D4E9493072080DEB1E +:104280000601984720BBD4F8183106EB5306B8F165 +:10429000000F00D08DB9324669462046FFF70EFF69 +:1042A000054610B90123C4F8143131466846FFF7BA +:1042B00017FD284660B0BDE8F0810DEB06004146D1 +:1042C0002A462E4401F032FCE5E76FF00405F0E7E2 +:1042D0006FF00805EDE700BF2DE9F047044604F153 +:1042E000F4050F4691461E46DDF8208000F036FEAC +:1042F00029462046FFF74AFE08B1BDE8F087284668 +:104300002022012101F037FCD4F81831C4F82471BF +:10431000C4F8289113B92023C4F818314246314615 +:104320002046BDE8F0470123FFF780BFB2F5806F5C +:1043300000F295802DE9F04F89B09A46129BB3F5B3 +:10434000807F00F29080D0F82431164604460F4654 +:104350004BB3D0F81C31012B05D0D0F81421D0F884 +:1043600020319A421FDD514600232046129AFFF762 +:104370005DFF014698B900231293002E5ED0DFF84E +:10438000EC9009F1400B0021204600F001FE202EA8 +:104390003546014628BF202504F1F40890B10846AF +:1043A00009B0BDE8F08FBAF1000FE6D0129B002BE8 +:1043B000E1D051461A462046FFF780FE014600280C +:1043C000DBD0ECE749464022204600F0F5FD0146EF +:1043D0000028E4D141462022204600F0EDFD0146B0 +:1043E0000028DCD16946204600F010FE0146002876 +:1043F000D5D1204600F0CCFD01460028CFD159464A +:104400004022204600F0D8FD01460028C7D1694669 +:104410002022204600F0D0FD01460028BFD14146B1 +:10442000204600F0F3FD01460028B8D138462A4660 +:10443000414601F07BFB761B2F44A4D15146204618 +:10444000129AFFF73BFE01460028A8D10846D4F88F +:1044500014310133C4F8143109B0BDE8F08F6FF0A6 +:104460000201084670476FF0040198E758220020C7 +:10447000002130B58DB0282202A8019101F07BFB0C +:1044800002A800F047FF044610B120460DB030BD31 +:10449000174D4FF0FF31286800F042F9044620B173 +:1044A000144800F0D3F9012C18D0002000F068F96E +:1044B000D0B9114A02A901A800F0C4FC04460120A9 +:1044C00000F05EF960B9286800F032F90028DCD00D +:1044D0000A4800F0BBF920460DB030BD0524F2E7D4 +:1044E000074800F0B3F9EEE7064800F0AFF9E0E75F +:1044F000D4100020705D0000D8220020C85D0000AC +:10450000A85D0000885D000030B583B000F05CF865 +:1045100028B100F0ABF80324204603B030BD00F012 +:1045200077F8D8B900F02CF9E0B9224CD4F8283942 +:104530001B0EF02B0AD1D4F8242A1F4B9A4231D0FB +:1045400000F044F8072400F091F8E5E7062400F0B5 +:104550003DF800F08BF8204603B030BD042420461F +:1045600003B030BD00F032F800F080F8FFF780FFB4 +:10457000044698B9114D2B68002BCDD10246014657 +:10458000FFF752FB50B968220D4901ABFFF77CFBE6 +:1045900020B920460B4B2B6003B030BD05242046CC +:1045A00003B030BD012000F0EBF80028DAD1C4F8E8 +:1045B0000C0ADBE7001084500000E020F824002003 +:1045C000FC2400205AEA5A5A002070470020704705 +:1045D000430504D54FF0FF32034BC3F80821024BCB +:1045E000C3F8080A704700BF00108450014BC3F89D +:1045F000040A70470010845008B100F06BB84FF403 +:104600007500704708B100F04FB84FF4750070475F +:1046100010B5114800F070F800B110BD0F4800F05F +:104620006BF80028F9D10E4C204600F065F8002800 +:10463000F3D10C4B0C481C6000F05EF801460028DA +:10464000EBD100F02DF8044608B1204610BD00F073 +:1046500079F82046FAE700BFE4100020E0100020BF +:10466000DC100020D4100020D810002008B5084825 +:1046700000F04CF8074800F049F8074800F046F809 +:10468000064800F043F8BDE8084000F00BB800BF52 +:10469000E4100020E0100020DC100020D8100020E2 +:1046A00000207047704700BF024610B4084CD4F891 +:1046B000003A1342FBD021B1D4F804310B60C4F8A6 +:1046C0000831034B0020C3F8082A5DF8044B7047FB +:1046D00000108450044AD2F8003A1842FBD0C2F8C5 +:1046E000080A0020704700BF0010845001F0E8B9AC +:1046F0000A46002101F03FBA08B5034B02681B6867 +:1047000010689847002008BD1011002008B5034B21 +:1047100002685B6810689847002008BD10110020EF +:10472000024B02689B681068184700BF10110020F8 +:1047300008B5034B0268DB6810689847002008BD85 +:104740001011002070B5094C094D2069AB689847DD +:1047500018B1084B08485B6898470021074AEB6886 +:1047600011602069BDE87040184700BFFC100020B0 +:1047700010110020F4100020E05D000064250020EE +:10478000F8B51A4D1A4E0446B26828699047E8B940 +:10479000184F3B684CB1A3B1012B19D0013B3B60D2 +:1047A0002869F36898470020F8BD43B90121124AEF +:1047B000C2F800151149D1F81029002AFBD10133A4 +:1047C0003B60F368286998470020F8BD0C48F8BDA5 +:1047D0000A4AD2F81039002BFBD1074A6FF07E400D +:1047E000C2F80035FFF702FF3B68D7E7FC10002056 +:1047F0001011002064250020000084500010845017 +:10480000E98FFFFF014B1B68184700BF1011002004 +:10481000014B9B68184700BF10110020014BDB685B +:10482000184700BF10110020BFF34F8F0549064BFA +:10483000CA6802F4E0621343CB60BFF34F8F00BF3E +:10484000FDE700BF00ED00E00400FA0530B44FF0D2 +:10485000FE320025074B084C08494968C3F800247C +:10486000C3F80424C3F80824C3F80C24C4F8005582 +:1048700030BC08470010845000008450F410002021 +:1048800010B5044650B1636813F0685F05D0064A5E +:10489000934202D000236260236010BD034B0448A2 +:1048A0005B689847EFE700BF2C5F5CA9F41000201D +:1048B000005E0000C8B143680D4A934213D013F064 +:1048C000685F0ED0012350E8002F194640E8001C15 +:1048D0009CF0000FF7D1012AF5D0BFF35F8F0020C5 +:1048E000704704487047044870476FF4E0407047D1 +:1048F0002C5F5CA9E98FFFFFEA8FFFFF034680B1C1 +:10490000426809498A420AD012F0685F05D0BFF3B5 +:104910005F8F002210461A607047044870470448B1 +:1049200070476FF4E04070472C5F5CA9E98FFFFF90 +:10493000EA8FFFFF10B5044620B10023034A23602D +:10494000626010BD0248FFF781FFF6E73A00003AC7 +:10495000285E00002DE9F047002878D00C46002999 +:1049600075D01D46002B72D007461AB101220023D4 +:104970002A6003602B68002B64D02946204600F093 +:10498000B7FD804600285FD12146286800F096FDDB +:104990008046002858D12B68082B6CD0042B6DD092 +:1049A000A3F10209B9FA89F94FEA59190121354EE3 +:1049B000C6F8C411C6F84011C6F8C411636AC6F837 +:1049C0003031D6F830219342F6D14FF0000A4FF043 +:1049D000FF30C6F82CA1FFF7FBFD6FF01B0350461C +:1049E000C6F80031FFF702FEC6F80C91D6F8040AAB +:1049F00040F48060FFF7FAFD0A23C6F83831089BBF +:104A0000012B29D02369204AA2FB03231B09626AD8 +:104A1000404602FB03F303EB43031A4ADB039B0903 +:104A2000C2F8D8310123C2F82C313B682A6823F040 +:104A30007F4343EA02633B602A6843EA02233B6008 +:104A4000BDE8F087DFF844804046BDE8F087DFF836 +:104A500040804046BDE8F0874FF47F03D3F80C2C2C +:104A6000013206D0D3F80C3C074AA2FB03231B09F2 +:104A7000CDE71623CBE74FF0030997E74FF0020984 +:104A800094E700BF00108450ABAAAAAA310CF10031 +:104A9000350CF10058B34B1EB3F5047F27D230B567 +:104AA0004FF0000ECD00744671464B0901F01F0C0B +:104AB00050F82330BCF11F0F23FA0CF306D003F09B +:104AC000010319B901211C468E46EEE79C4201F113 +:104AD000010106D01C464FF0010EA942E5D100208D +:104AE00030BD0EF1010E9645F7D1024830BD0148A8 +:104AF000704700BF360CF100C0B32DE9F0434C1EE7 +:104B0000B4F5047F1DD2E3B1DAB100252E46A846E4 +:104B10002C46CF0003F1FF394FEA541C04F01F0E5E +:104B200050F82C10BEF11F0F21FA0EFC0CD00CF027 +:104B3000010C4CB90126E04634463546ECE7964276 +:104B400016D90C48BDE8F083AB420BD0C44508BF72 +:104B50000136A945F3D001350134BC42DCD1002037 +:104B6000BDE8F0830125E0462E46F5E70148704791 +:104B70001D46F1E7370CF1002DE9F04F91B0834667 +:104B8000DDE91B460D46002E6ED10F691C60002327 +:104B90001A990B60002A00F02F819D4B6A6AD3F8A6 +:104BA0003811D3F830319A4269D10A2967D19BF87C +:104BB0000330089304F10803079304230593954BEE +:104BC000934CA3FB07239B0803EB4303A7EB43038F +:104BD000039306971A9B069E1E60002E00F0EA8043 +:104BE00000F096FC00230993BBF1000F00F0D58084 +:104BF00098464FF0010ADDF81C90002D00F0CD80A2 +:104C0000089B002B3DD0284608A900F071FC002825 +:104C100039D0814B984220D0089B082B00F0E480CB +:104C200000225B000893294608AB58460092FFF724 +:104C300091FE7A4B984200F0D38070B9DBF80020E7 +:104C4000130A03F47F03134323F07F43CBF80030B0 +:104C5000059B013B0593BDD10020039000F058FC5B +:104C6000039811B0BDE8F08F4FF47F01D1F80C0C20 +:104C700001300CBF4FF40477D1F80C7C86E768480C +:104C8000EBE76648C5E72946089800F017FC0028BE +:104C9000BFD1089B082B00F0A980042B00F0A9804D +:104CA000A3F10203B3FA83F35B090493C4F8C4A12C +:104CB000C4F840A1C4F8C4A16B6AC4F83031D4F878 +:104CC00030219342F6D100234FF0FF30C4F82C314D +:104CD000FFF77EFC002318466FF01B03C4F8003179 +:104CE000FFF784FC049B4B4FC4F80C31D4F8040A42 +:104CF00040F48060FFF77AFC0A23C4F838312B694E +:104D000009A8A7FB03236A6A1B0902FB03F303EB51 +:104D10004303DB039B09C4F8D831C4F82CA1DBF8AA +:104D20000030089923F07F420B0243EA01631343EA +:104D3000CBF8003000F0B2FB099B03F01A030343E9 +:104D40007FF46AAFA7FB0637D4F81431B8EB970F9E +:104D50000A93D4F818310B93D4F81C310C93D4F87F +:104D600020310D93D4F824310E93D4F828310F93C9 +:104D700001D1039B9BB9484618220AA9183EFFF7A8 +:104D8000B5FC09F1180908F101089EB100F0C0FB5B +:104D900000230993002D7FF433AF224839E74846BA +:104DA0001A46F61A0AA9994408F10108FFF79EFC71 +:104DB000002EEBD1069F079E6A6939463046FFF701 +:104DC00069FEAA6940B94FF4806339463046FFF75F +:104DD00093FE00283FF440AF00231A9A136018E7AF +:104DE0002B6A002B3FF439AF0F4836E703230493B7 +:104DF0005CE70223049359E70122294658460096AE +:104E000008ABFFF7A7FD00283FF4D4AE25E700BFAD +:104E100000108450ABAAAAAA020CF100310CF100D8 +:104E2000300CF100350CF100320CF10030B4DDE94A +:104E3000023402940024049DCDE9035430BCFFF7F2 +:104E40009BBE00BF30B51546012487B005AB00936B +:104E50000022CDE9015404ABFFF78EFE07B030BD50 +:104E6000F0B587B0002966D01E46002B63D01446EB +:104E7000002A60D0314F0D4638684FF0FF31FFF700 +:104E80004FFC00284DD10020FFF77AFC002844D1C8 +:104E90002B4800F03FFAD8B101204FF0FF35FFF763 +:104EA0006FFC002835D14FF408712648FEF70EFF3D +:104EB00004212548FEF70AFF21482821FEF706FFB6 +:104EC0003868FFF735FC00282FD1284607B0F0BD21 +:104ED0001C4A0346029205AA019204AA039000927A +:104EE000174902461848FFF7A1FF03460028D3D10F +:104EF00028461D46059B22469C4228BF1A46049917 +:104F000032600831FFF7F2FB0120FFF739FC00287F +:104F1000C9D00E48FFF79AFCC5E70D48FFF796FC8D +:104F2000B6E70C48FFF792FCADE70B48FFF78EFCA5 +:104F3000284607B0F0BD4FF0FF35C6E7D41000207B +:104F40007C250020A425002078250020A85D0000F5 +:104F5000885D0000705D00004C5E000010B50446E6 +:104F600028B12046BDE81040F421FFF7C1BB04483A +:104F7000FFF76CFC2046BDE81040F421FFF7B8BBFA +:104F8000645E000010B1F421FEF7A0BE704700BFC0 +:104F900008B521B1012908D06FF0360008BD012104 +:104FA00000F058F80028F7D108BD022100F052F8AF +:104FB0000028F9D0F0E700BF30B505468818B0F5F5 +:104FC000801F0B461446A1B001D8802A0CD9B3F536 +:104FD000801F17D922461946284600F07DF804465E +:104FE00084B9204621B030BD6846FFF77FFB2246DA +:104FF0006946284600F070F8802104466846FFF7AD +:1050000077FBEDE76FF03604EBE700BF70B5E8B172 +:105010000E46D9B1044600F031F80546B0B9236810 +:10502000012B0BD0022B01D0284670BD1C2230462C +:1050300004F10801FFF75AFB284670BD20223046D4 +:1050400004F10801FFF752FB284670BD6FF03605EA +:10505000EAE700BF78B138B50D46F0210446FFF706 +:1050600047FB402320462560E36500F091FA0038B5 +:1050700018BF012038BD0120704700BFF0B5056E94 +:10508000A5B0044604AEE5B90023012701933046DC +:105090006760294602AA009300F046FA10B1012089 +:1050A00025B0F0BD2A46204602A900F099FA002852 +:1050B000F5D1204600F0AAFB0028F0D1206625B0EB +:1050C000F0BD802D2A46304628BF802204F16401BD +:1050D000FFF70CFB256ED7E7002800F0AE802DE926 +:1050E000F04F1446A5B0002A3DD00F46002949D004 +:1050F000B2F5803F0546006E20D34FF6FF7805F1EC +:10510000640AEB6D1A1AB2FBF3F103FB1122002AB9 +:1051100065D18342B9464FF6FF7672D0B6FBF3FBFA +:1051200003FB0BFBBBF1000F30D1002E45D1A4F5E2 +:105130007F44FF3CB4F5803F4744E2D2EA6D161A43 +:10514000B6FBF2F302FB1366A64228BF2646002EEA +:1051500075D1824200F08D80B4FBF2F602FB06F6B8 +:105160002EB9002C79D1002025B0BDE8F08F0023A6 +:1051700031463846CDE9003302AA00F0D5F90028BF +:1051800000F09580012025B0BDE8F08F002359463E +:105190004846CDE9003302AA00F0C6F90028F1D153 +:1051A0005A46284602A900F01BFA0028EAD1A6EBCD +:1051B0000B06286ED944002EB9D019304946324624 +:1051C00005EB8000FFF792FAA4F57F44286EFF3CC0 +:1051D0003044B4F5803F4744286692D2AEE742455A +:1051E00028BF4246164639465044FFF77FFA286EDC +:1051F000EB6D3044834207EB06092866A8EB0606F0 +:105200008CD1802B28BF802351461A4604A8FFF773 +:105210006DFA0023E96D02AACDE9003304A800F07D +:1052200083F90028AED12846EA6D02A900F0D8F92A +:105230000028A7D1EB6D286670E70120704705F1C3 +:105240006403394632461844FFF750FA286EEA6D77 +:105250003044A41B374428667BE72B6E2246193363 +:1052600005EB83003946FFF741FA2B6E23442B668A +:1052700079E7802A28BF802205F1640104A8FFF79E +:1052800035FA0023E96D04A8CDE9003302AA00F045 +:105290004BF900287FF476AF2846EA6D02A900F0AA +:1052A0009FF900287FF46EAFEA6D286654E7324616 +:1052B000284602A900F094F900287FF463AF374430 +:1052C000A41B4EE7F8B51C460546114816460F4686 +:1052D000FFF744FE21460E48FFF75AFE044620B170 +:1052E0000B48FFF74FFE2046F8BD3A4629460848CE +:1052F000FFF762FE04460028F2D131460448FFF76A +:1053000085FE04460248FFF73DFE2046F8BD00BF7B +:10531000C42700201C2370B582B001A90546019363 +:1053200000F032F8044638B9019B1C2B07D0144C0E +:105330001C212846FFF7DCF9204602B070BDD5E9F4 +:105340000112131E18BF0123003918BF01210126C5 +:1053500028689B00EA6843EA4103002818BF43F02D +:105360000103EE612AB9C5E90834002BE4D1054CEC +:10537000DEE7204643F00803C5E9083402B070BDFB +:10538000370CF0000E0CF0000346002866D0002910 +:1053900064D00A681C2A61D14FF47F023B49D2F8DD +:1053A000100C88425CD0D2F8101C013158D0D2F8D1 +:1053B000101C4FF47F021960D2F8140C3449884253 +:1053C00060D0D2F8141C01315CD0D2F8141C4FF418 +:1053D0007F025960D2F8181C11F5947F4FD0D2F893 +:1053E000181C01314BD0D2F8181C4FF47F02996081 +:1053F000D2F81C0C274988423ED0D2F81C1C01313F +:105400003AD0D2F81C1C4FF47F02D960D2F8001CAD +:1054100070312FD0D2F8001C01312BD0D2F8001CF3 +:105420004FF47F021961D2F8041CAF3120D0D2F8BA +:10543000041C01311CD0D2F8041C4FF47F025961C6 +:10544000D2F8080C144988420DD0D2F8081C01315A +:1054500009D0D2F8082C00209A61704701207047CB +:1054600040F6FC01A5E740F2373200209A61704710 +:105470005121E2E79021D3E742F60411C3E740F659 +:10548000D861B2E742F2D001A1E700BFFCF8FFFF0C +:10549000D020FFFF0429FFFF37F3FFFF014608B5C7 +:1054A0004FF48060FFF7AEF80022034BC3F8C82129 +:1054B000C3F82C2108BD00BF001084500138072814 +:1054C00005D8DFE800F0100E040A040404060748BB +:1054D0007047CB6800204B6270478B6800204B629E +:1054E00070474B68F6E70B68F4E700BF310CF1003A +:1054F0000B6802E0082B0B6006D8026A13424FEAE1 +:105500004303F7D00020704700487047310CF1008A +:105510000022044B4FF48060C3F82C21C3F8C4214F +:10552000FFF756B800108450F0B5002483B0B0F1F6 +:10553000005F8DF8074025D38C468444BCF1804F32 +:1055400004461FD817461D4601220DF1070300F03F +:1055500097F90646B0B99DF80730099A3C603B714F +:1055600092B14DB10246089928460DF1070300F0AB +:1055700087F938B99DF807303046099A15601371DC +:1055800003B0F0BD4FF47506304603B0F0BD00BF68 +:10559000D8B108B50368012B04D0022B0FD043B15A +:1055A0000B4808BD20220B490830FFF79FF8002068 +:1055B00008BD142208490830FFF798F8F7E72022C1 +:1055C00006490830FFF792F8F1E74FF4730070478F +:1055D0000100F300745E0000B45E0000945E000001 +:1055E0002DE9F843002900F0F9800446002800F076 +:1055F000F98003680F469046002B00F0C380013B02 +:10560000012B00F2D7804FF0FF3178484FF00209AC +:10561000FFF786F8002840F0BF800020FFF7B0F8C1 +:105620000646002840F0C280714AD2F81C39002B8F +:10563000FBD16F4DD5F8203C002BFBD14FF0FF3054 +:10564000FEF7C6FFD5F8040A20F04000FEF7CEFFB3 +:1056500001230722C5F81838C5F80029C5F8C43752 +:10566000A36CC5F8CC37E36CC5F8D037C5F8C09744 +:1056700023685BB1013B012B18D8636AC5F85C361F +:10568000236AC5F85836E369C5F85436A269584B01 +:10569000C3F850266269C3F84C262269C3F848262D +:1056A000E268C3F84426A268C3F84026504AD2F8FC +:1056B0001039002BFBD1B8F1000F5FD06368402098 +:1056C000012B08BFC2F884363A68494BC3F8282C2E +:1056D000C3F82C8CFEF790FF0646454AD2F81039E5 +:1056E000002BFBD1424AD2F8203C002BFBD123688F +:1056F0005BB1013B012B18D8D2F85C366362D2F85B +:1057000058362362D2F85436E361394BD3F8502629 +:10571000A261D3F84C266261D3F848262261D3F8FF +:105720004426E260D3F84036A36000230121304ACA +:10573000D2F8CC07A064D2F8D007E064C2F8C4174E +:10574000C2F88436C2F8C837D2F81039002BFBD122 +:10575000C2F81838002E3BD1254BD3F8040A40F08C +:105760004000FEF743FF0120FFF70AF860BB1F4827 +:10577000FEF7DEFF10BB3046BDE8F8830423C2F815 +:10578000C837AAE74FF0FF3118484FF00109FEF77C +:10579000C7FF00283FF441AF1648FFF757F8002035 +:1057A000FEF7EEFF064600283FF43EAF1248FFF733 +:1057B0004DF839E7114E3046BDE8F8831048FFF741 +:1057C00045F83046BDE8F8830E48FFF73FF8CEE7CE +:1057D000102104F10800FEF78BFFBDE70A4E3046AA +:1057E000BDE8F8834FF47306C5E700BFE41000205E +:1057F00000108450705D0000885D00000100F3001F +:10580000C85D0000A85D00000300F300A0B30346DC +:1058100070B4D0E90240D3E9041226BA05BA0CBA32 +:1058200010BAC3E90440D3E90612986A09BAC3E979 +:10583000026512BA996100BAD3E9084125BA0CBAD7 +:10584000C3E90725D3E90B12C3E9094009BAD86BAC +:1058500012BAD962D3E90D4125BAC3E90C250CBAB5 +:1058600000BAD3E9101209BA12BAC3E90E4019649A +:1058700000205A6470BC70474FF47300704700BF3B +:1058800000201870704700BF431810B501D21F2ABE +:1058900001D9002010BD013902F014030144102B7E +:1058A00080EA01043CD009DC8BB1042BF1D11F2C20 +:1058B00040E840F315D941E840F10EE0142BE8D15F +:1058C0001F2C40E8C0F30CD941E8C0F105E01F2CC3 +:1058D00040E800F305D941E800F11C460B468C4234 +:1058E000D7D122F01402013A0A2AD2D801A151F8E4 +:1058F00022F000BF435900003D5900002F5900001D +:1059000093580000935800009358000093580000EB +:105910004959000043590000375900002F59000031 +:105920001F2C40E880F3DCD941E880F1D5E713F47F +:10593000001FAFD1ADE713F4801FFAE75B02A9D5D2 +:10594000A7E713F4002FF4E713F4802FF1E700BF6B +:1059500008B5074B044613B10021AFF30080054B97 +:105960001868836A03B19847204600F029F800BF01 +:1059700000000000C85E000070B50D4D00260D4C03 +:10598000641BA410A64209D10B4D00260B4C00F05D +:10599000D5F9641BA410A64205D170BD55F8043B8F +:1059A00001369847EEE755F8043B01369847F2E791 +:1059B000A0110020A0110020A0110020A41100209F +:1059C000FEE700BFB7EE000AF7EE000AB7EE001AD6 +:1059D000F7EE001AB7EE002AF7EE002AB7EE003A0B +:1059E000F7EE003AB7EE004AF7EE004AB7EE005A7B +:1059F000F7EE005AB7EE006AF7EE006AB7EE007AEB +:105A0000F7EE007AF1EE10CA40F29F01CFF20001EA +:105A10003CEA010CE1EE10CA002383F300887047D2 +:105A2000FDF79CBAEFF30880EFF309812DE9F00F41 +:105A30006B467246FAF7ACFF08B0FFF7F1FFFEE7DE +:105A4000FDF78CBAEFF30880EFF309812DE9F00F31 +:105A50006B467246FAF79CFF08B0FFF7F1FFFEE7CE +:105A6000FDF77CBAEFF30880EFF309812DE9F00F21 +:105A70006B467246FAF78CFF08B0FFF7F1FFFEE7BE +:105A8000FDF76CBAEFF30880EFF309812DE9F00F11 +:105A90006B467246FAF77CFF08B0FFF7F1FFFEE7AE +:105AA000FDF75CBAEFF30880EFF309812DE9F00F01 +:105AB0006B467246FAF76CFF08B0FFF7F1FFFEE79E +:105AC000814270B40546144602D370BC00F02EB873 +:105AD000821821443CB141EA020313F003030FD1C1 +:105AE000E018032812D86FF00300A30843430020F6 +:105AF00019441A442344984210D1284670BC704778 +:105B000011F8013D013C02F8013DE3E7581850F857 +:105B1000046C981840F8046C043BE1E70C1A14F884 +:105B2000016C141A04F8016C0130E4E770B5044606 +:105B30003AB141EA040313F003030CD1D518032D45 +:105B40000FD822F003031C441944002302F003027F +:105B500093420CD170BD11F8013B013A04F8013BAE +:105B6000E6E7CD1A2E68E51A2E60043BE6E75D5C99 +:105B7000E5540133ECE7F0B5044662B922F00303C3 +:105B8000234402F003021A44934214D1F0BD04F8F6 +:105B9000011B013AF1E7A307F9D115460B0443EACB +:105BA00001630B4343EA01231619032DE6D9771B42 +:105BB0003B60043DF9E703F8011BE5E708B5EFF3A7 +:105BC0000583C3F308030BB1FDF7C8F9BDE808402E +:105BD000FDF74CBA10B50446EFF30583C3F3080391 +:105BE0000BB1FDF7BBF92046BDE81040FDF741BA07 +:105BF0002DE9F04104460D4616461F46EFF3058396 +:105C0000C3F308030BB1FDF7A9F93B463246294619 +:105C10002046BDE8F041FCF743BC2DE9F0410446C5 +:105C20000D4616461F46EFF30583C3F308030BB179 +:105C3000FDF794F93B46324629462046BDE8F0413F +:105C4000FCF768BC08B5EFF30583C3F308030BB199 +:105C5000FDF784F9BDE80840FDF7DBB90020704787 +:105C6000704710B5044608B1FDF75AFF2046BDE85D +:105C70001040FDF745BF10B50446406A10B1A16A57 +:105C8000FFF7EFFF0020C4E9090010BD38B5044656 +:105C9000FFF7F1FF236A2C22012B0CBF05466FF0A2 +:105CA000960500212046FFF766FF284638BD0020F4 +:105CB000704770470020704713B5002001AB1446B1 +:105CC000FDF7E2FF30B9019BA34218BF6FF09300CC +:105CD00002B010BD6FF09200FAE7002070472DE986 +:105CE000E04F2746A046A146A246A346A4462DED76 +:105CF000108B4FF0000545EC185B45EC195A45EC4C +:105D00001A5A45EC1B5A45EC1C5A45EC1D5A45ECF9 +:105D10001E5A45EC1F5AF1EE105A4FF66076C0F647 +:105D2000FF763540E1EE105A84F30088254626467A +:105D3000A447BDEC108BBDE8E08F0000F8B500BFB4 +:105D4000F8BC08BC9E467047F8B500BFF8BC08BC5C +:105D50009E467047FF8FFFFFFF8FFFFFFE8FFFFF05 +:105D6000FE8FFFFFFD8FFFFFFC8FFFFFFC8FFFFF0C +:105D70004661696C20746F2061637175697265207A +:105D80006D757465780A00004661696C20746F2037 +:105D9000696E63726561736520504D20636F756E27 +:105DA0007465720A000000004661696C20746F20FF +:105DB000646563726561736520504D20636F756E15 +:105DC0007465720A000000004661696C20746F20DF +:105DD00072656C65617365206D757465780A000085 +:105DE000436F756C64206E6F74206C6F636B2070F2 +:105DF0006F7765722073617665206D7574657800C4 +:105E00006D757465785F667265652063616C6C653D +:105E1000642077697468204E554C4C207061726123 +:105E20006D6574657200000043616E277420696EB1 +:105E3000697469616C697A65206D757465782C2068 +:105E4000776173204E554C4C0D0A00004661696C19 +:105E500020746F20756E6C6F636B206D7574657840 +:105E60000A0000000A637478206973204E554C4C78 +:105E70000A00000067E6096A85AE67BB72F36E3CF4 +:105E80003AF54FA57F520E518C68059BABD9831F05 +:105E900019CDE05BD89E05C107D57C3617DD703083 +:105EA00039590EF7310BC0FF11155868A78FF964E7 +:105EB000A44FFABE0123456789ABCDEFFEDCBA984B +:105EC00076543210F0E1D2C34011002054464D5FA9 +:105ED00043525950544F0054464D5F504C41544624 +:105EE0004F524D5F53455256494345000000000054 +:105EF00000000000000000000000000000000000A2 +:107C00007FE97FE9FBF72CBD7FE97FE9FBF737BD13 +:107C10007FE97FE9FBF75FBD7FE97FE9FBF752BDB5 +:107C20007FE97FE9FBF73ABD00000000000000009B +:107C30000000000000000000000000000000000044 +:107C40007AFFFFFF00900050000000008C3400001D +:107C50000400000000000000000000000000000020 +:107C6000000000000000000090ED00E000A00350C4 +:107C70007FA60350009000506F9500501412002012 +:107C8000A811002044010000615C00005D5C000060 +:107C9000775B0000DC1000200C1100200411002094 +:107CA00000110020FC1000201D48000011480000B9 +:107CB000054800000000000029480000381100209D +:107CC00030110020281100200000000020110020A9 +:107CD0003549000081480000B5480000FD4800001B +:107CE000682500203A00003A6C2500203A00003A4E +:107CF000702500203A00003A742500203A00003A2E +:107D00000000000000000000000000000000000073 +:107D10000000000000000000000000000000000063 +:107D20000000000000000000000000000000000053 +:107D30000000000000000000000000000000000043 +:107D40000000000000000000000000000000000033 +:107D50000000000000000000000000000000000023 +:087D6000553700002D3700002B +:00000001FF diff --git a/embassy/examples/nrf9151/s/.cargo/config.toml b/embassy/examples/nrf9151/s/.cargo/config.toml new file mode 100644 index 0000000..f64c639 --- /dev/null +++ b/embassy/examples/nrf9151/s/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip nRF9160_xxAA" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/nrf9151/s/Cargo.toml b/embassy/examples/nrf9151/s/Cargo.toml new file mode 100644 index 0000000..5d23025 --- /dev/null +++ b/embassy/examples/nrf9151/s/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "embassy-nrf9151-secure-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.6.3", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/embassy/examples/nrf9151/s/build.rs b/embassy/examples/nrf9151/s/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/nrf9151/s/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/embassy/examples/nrf9151/s/memory.x b/embassy/examples/nrf9151/s/memory.x new file mode 100644 index 0000000..4c7d4eb --- /dev/null +++ b/embassy/examples/nrf9151/s/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20018000, LENGTH = 160K +} diff --git a/embassy/examples/nrf9151/s/src/bin/blinky.rs b/embassy/examples/nrf9151/s/src/bin/blinky.rs new file mode 100644 index 0000000..7457a95 --- /dev/null +++ b/embassy/examples/nrf9151/s/src/bin/blinky.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P0_00, Level::Low, OutputDrive::Standard); + + loop { + led.set_high(); + defmt::info!("high"); + Timer::after_millis(500).await; + led.set_low(); + defmt::info!("low"); + Timer::after_millis(1000).await; + } +} diff --git a/embassy/examples/nrf9160/.cargo/config.toml b/embassy/examples/nrf9160/.cargo/config.toml new file mode 100644 index 0000000..6072b85 --- /dev/null +++ b/embassy/examples/nrf9160/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# runner = "probe-rs run --chip nRF9160_xxAA" +runner = [ "probe-rs", "run", "--chip=nRF9160_xxAA", "--always-print-stacktrace", "--log-format={t} {[{L}]%bold} {s} {{c} {ff}:{l:1}%dimmed}" ] + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/nrf9160/Cargo.toml b/embassy/examples/nrf9160/Cargo.toml new file mode 100644 index 0000000..b52cd4a --- /dev/null +++ b/embassy/examples/nrf9160/Cargo.toml @@ -0,0 +1,26 @@ +[package] +edition = "2021" +name = "embassy-nrf9160-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-net-nrf91 = { version = "0.1.0", path = "../../embassy-net-nrf91", features = ["defmt"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "proto-ipv4", "medium-ip"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +heapless = "0.8" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +static_cell = { version = "2" } +embedded-io = "0.6.1" +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } + +[profile.release] +debug = 2 diff --git a/embassy/examples/nrf9160/build.rs b/embassy/examples/nrf9160/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/nrf9160/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/embassy/examples/nrf9160/memory.x b/embassy/examples/nrf9160/memory.x new file mode 100644 index 0000000..e334987 --- /dev/null +++ b/embassy/examples/nrf9160/memory.x @@ -0,0 +1,9 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20010000, LENGTH = 192K + IPC : ORIGIN = 0x20000000, LENGTH = 64K +} + +PROVIDE(__start_ipc = ORIGIN(IPC)); +PROVIDE(__end_ipc = ORIGIN(IPC) + LENGTH(IPC)); diff --git a/embassy/examples/nrf9160/src/bin/blinky.rs b/embassy/examples/nrf9160/src/bin/blinky.rs new file mode 100644 index 0000000..7ac5c31 --- /dev/null +++ b/embassy/examples/nrf9160/src/bin/blinky.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P0_02, Level::Low, OutputDrive::Standard); + + loop { + led.set_high(); + Timer::after_millis(300).await; + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/nrf9160/src/bin/modem_tcp_client.rs b/embassy/examples/nrf9160/src/bin/modem_tcp_client.rs new file mode 100644 index 0000000..35900cd --- /dev/null +++ b/embassy/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -0,0 +1,197 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; +use core::net::IpAddr; +use core::ptr::addr_of_mut; +use core::slice; +use core::str::FromStr; + +use defmt::{info, unwrap, warn}; +use embassy_executor::Spawner; +use embassy_net::{Ipv4Cidr, Stack, StackResources}; +use embassy_net_nrf91::context::Status; +use embassy_net_nrf91::{context, Runner, State, TraceBuffer, TraceReader}; +use embassy_nrf::buffered_uarte::{self, BufferedUarteTx}; +use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin}; +use embassy_nrf::uarte::Baudrate; +use embassy_nrf::{bind_interrupts, interrupt, peripherals, uarte}; +use embassy_time::{Duration, Timer}; +use embedded_io_async::Write; +use heapless::Vec; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[interrupt] +fn IPC() { + embassy_net_nrf91::on_ipc_irq(); +} + +bind_interrupts!(struct Irqs { + SERIAL0 => buffered_uarte::InterruptHandler; +}); + +#[embassy_executor::task] +async fn trace_task(mut uart: BufferedUarteTx<'static, peripherals::SERIAL0>, reader: TraceReader<'static>) -> ! { + let mut rx = [0u8; 1024]; + loop { + let n = reader.read(&mut rx[..]).await; + unwrap!(uart.write_all(&rx[..n]).await); + } +} + +#[embassy_executor::task] +async fn modem_task(runner: Runner<'static>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, embassy_net_nrf91::NetDriver<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn control_task( + control: &'static context::Control<'static>, + config: context::Config<'static>, + stack: Stack<'static>, +) { + unwrap!(control.configure(&config).await); + unwrap!( + control + .run(|status| { + stack.set_config_v4(status_to_config(status)); + }) + .await + ); +} + +fn status_to_config(status: &Status) -> embassy_net::ConfigV4 { + let Some(IpAddr::V4(addr)) = status.ip else { + panic!("Unexpected IP address"); + }; + + let gateway = match status.gateway { + Some(IpAddr::V4(addr)) => Some(addr), + _ => None, + }; + + let mut dns_servers = Vec::new(); + for dns in status.dns.iter() { + if let IpAddr::V4(ip) = dns { + unwrap!(dns_servers.push(*ip)); + } + } + + embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(addr, 32), + gateway, + dns_servers, + }) +} + +#[embassy_executor::task] +async fn blink_task(pin: AnyPin) { + let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); + loop { + led.set_high(); + Timer::after_millis(1000).await; + led.set_low(); + Timer::after_millis(1000).await; + } +} + +extern "C" { + static __start_ipc: u8; + static __end_ipc: u8; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + info!("Hello World!"); + + unwrap!(spawner.spawn(blink_task(p.P0_02.degrade()))); + + let ipc_mem = unsafe { + let ipc_start = &__start_ipc as *const u8 as *mut MaybeUninit; + let ipc_end = &__end_ipc as *const u8 as *mut MaybeUninit; + let ipc_len = ipc_end.offset_from(ipc_start) as usize; + slice::from_raw_parts_mut(ipc_start, ipc_len) + }; + + static mut TRACE_BUF: [u8; 4096] = [0u8; 4096]; + let mut config = uarte::Config::default(); + config.baudrate = Baudrate::BAUD1M; + let uart = BufferedUarteTx::new( + //let trace_uart = BufferedUarteTx::new( + unsafe { peripherals::SERIAL0::steal() }, + Irqs, + unsafe { peripherals::P0_01::steal() }, + //unsafe { peripherals::P0_14::steal() }, + config, + unsafe { &mut *addr_of_mut!(TRACE_BUF) }, + ); + + static STATE: StaticCell = StaticCell::new(); + static TRACE: StaticCell = StaticCell::new(); + let (device, control, runner, tracer) = + embassy_net_nrf91::new_with_trace(STATE.init(State::new()), ipc_mem, TRACE.init(TraceBuffer::new())).await; + unwrap!(spawner.spawn(modem_task(runner))); + unwrap!(spawner.spawn(trace_task(uart, tracer))); + + let config = embassy_net::Config::default(); + + // Generate "random" seed. nRF91 has no RNG, TODO figure out something... + let seed = 123456; + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::<2>::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + static CONTROL: StaticCell> = StaticCell::new(); + let control = CONTROL.init(context::Control::new(control, 0).await); + + unwrap!(spawner.spawn(control_task( + control, + context::Config { + apn: b"iot.nat.es", + auth_prot: context::AuthProt::Pap, + auth: Some((b"orange", b"orange")), + pin: None, + }, + stack + ))); + + stack.wait_config_up().await; + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("Connecting..."); + let host_addr = embassy_net::Ipv4Address::from_str("45.79.112.203").unwrap(); + if let Err(e) = socket.connect((host_addr, 4242)).await { + warn!("connect error: {:?}", e); + Timer::after_secs(10).await; + continue; + } + info!("Connected to {:?}", socket.remote_endpoint()); + + let msg = b"Hello world!\n"; + for _ in 0..10 { + if let Err(e) = socket.write_all(msg).await { + warn!("write error: {:?}", e); + break; + } + info!("txd: {}", core::str::from_utf8(msg).unwrap()); + Timer::after_secs(1).await; + } + Timer::after_secs(4).await; + } +} diff --git a/embassy/examples/rp/.cargo/config.toml b/embassy/examples/rp/.cargo/config.toml new file mode 100644 index 0000000..3d7d617 --- /dev/null +++ b/embassy/examples/rp/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ + +[env] +DEFMT_LOG = "debug" diff --git a/embassy/examples/rp/Cargo.toml b/embassy/examples/rp/Cargo.toml new file mode 100644 index 0000000..ce812b2 --- /dev/null +++ b/embassy/examples/rp/Cargo.toml @@ -0,0 +1,83 @@ +[package] +edition = "2021" +name = "embassy-rp-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns", "proto-ipv4", "proto-ipv6", "multicast"] } +embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-usb-logger = { version = "0.2.0", path = "../../embassy-usb-logger" } +cyw43 = { version = "0.2.0", path = "../../cyw43", features = ["defmt", "firmware-logs", "bluetooth"] } +cyw43-pio = { version = "0.2.0", path = "../../cyw43-pio", features = ["defmt"] } + +defmt = "0.3" +defmt-rtt = "0.4" +fixed = "1.23.1" +fixed-macro = "1.2" + +# for web request example +reqwless = { version = "0.13.0", features = ["defmt"] } +serde = { version = "1.0.203", default-features = false, features = ["derive"] } +serde-json-core = "0.5.1" + +# for assign resources example +assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } + +#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm"] } +cortex-m-rt = "0.7.0" +critical-section = "1.1" +panic-probe = { version = "0.3", features = ["print-defmt"] } +display-interface-spi = "0.5.0" +embedded-graphics = "0.8.1" +mipidsi = "0.8.0" +display-interface = "0.5.0" +byte-slice-cast = { version = "1.2.0", default-features = false } +smart-leds = "0.4.0" +heapless = "0.8" +usbd-hid = "0.8.1" +rand_core = "0.6.4" + +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0" +embedded-hal-bus = { version = "0.1", features = ["async"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-storage = { version = "0.3" } +static_cell = "2.1" +portable-atomic = { version = "1.5", features = ["critical-section"] } +log = "0.4" +pio-proc = "0.2" +pio = "0.2.1" +rand = { version = "0.8.5", default-features = false } +embedded-sdmmc = "0.7.0" + +bt-hci = { version = "0.1.0", default-features = false, features = ["defmt"] } +trouble-host = { version = "0.1.0", features = ["defmt", "gatt"] } + +[profile.release] +debug = 2 +lto = true +opt-level = 'z' + +[profile.dev] +debug = 2 +lto = true +opt-level = "z" + +[patch.crates-io] +trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" } +embassy-executor = { path = "../../embassy-executor" } +embassy-sync = { path = "../../embassy-sync" } +embassy-futures = { path = "../../embassy-futures" } +embassy-time = { path = "../../embassy-time" } +embassy-time-driver = { path = "../../embassy-time-driver" } +embassy-embedded-hal = { path = "../../embassy-embedded-hal" } diff --git a/embassy/examples/rp/assets/ferris.raw b/embassy/examples/rp/assets/ferris.raw new file mode 100644 index 0000000000000000000000000000000000000000..9733889c577bdcc9277858ce019abff5b7dd23d0 GIT binary patch literal 11008 zcmd5?F^ua(5VexV#V@WS1%*kP(1k9)^CeA06$+&At2oh@0@XE0K{Vfvgzh8?f}lvb zaTTFoMCc>A0LerGojwRfh{6R({wd%DqNSjK9Zxp1YdbzW$#K|Hylc$27uoSiXeOk>`2)ETl94!X5 z7Q>oZcLS)K_4`3-o24s~T)IN69FL)C0^2HN9bPjKDaXE$Ic$?^cUdyd0GqoOdUSk& z)m^jRz!NpcXI*DkTWzhvYD}(y;w}+V+oZYh0`w@b-S)J(p2N{9vWWl6CTvs4TZIEp zjPY$qVZOB%L?z8dQo~6~tV8R|HSw}(AjjO>R-?O3dK=P)$+xlX57w1NDRi&LI%rLY>sr5EE+juPZOcMX}yJB|mkDykmG+wG!3h1HTYe>d!M?e7lGC6 z*CPx~h==!G##uBQ12?+&w8cHc-QHX25Ibgg1deqZUEVF4{H^cPD!jixPnCv0%w84v z9`yfmyFr${f@Z6`o!)(89(aH!>$KBeG2&lFFpG5`z_}1?1`ZJ zzdYT*@mtKNoQ}5jtBFy_hZy}K?`K9&cQgmekZnQU4d+9MR8IK)kI#O-dPmdEiq%ZF zpLzJvAD{m8_@Sm-jp}rDQ>1;Q75OHC_^|Q+?)QJ9>x0*@9m*FGqCY=JnpFj8{1JW0 zS8~^s$3rQH&HtDFL>zB?h^=VaV5uOu3CESMkmkV36t9Zas@`UY{l z`&3|6_ht~||NRTn{O~@~!0ssjE$8fnjPL?;V)H*Oy51 z{1MWal_ECl1_MuCzYM*&PI;ehH2uL4aeVtwVAXWTr~bl<{`?Wpy!@-P%N`e>8<4ld ztEdGb7Tm+V{ra0ve>)c~@uz~-AHkQ$uYUH3kaC;$s5f~_nC(dSG`F{rzTY$ytm*sb zL$LB#R;SGK?dW0@H&$KJ=eB56X!lH+c$Kb*y^_pu9FD10w3K#Y!D6{Dd0rsmLo*|- z#X;k-^Er^&mMDtjgp%`EIYqZ>m$$oNcE0y}`1NwxaV@ zIM*6GS+|$vS-el~+KEmYWGwCF=CU@MB>EuJsO7=ZAC;~$ddT;hTmu=g4YlX4YqQNI z-1H7h125?X+yH6I)AnK_`N}x2Qa>OfQTQXPBAi@XEdIU4(sDsHhG;jJ_EfOaR>A|sbvyIQkX zDuS;z6DZt(=b6lTe>L7b**qyv%QKF2Ib60ET>3{q5ASMGAD|yHuIS4m!pf1(M^5LQ zdopRZ@HNzG2$@`lG8=?e!BfXiTKHP8pVyIdHFkW#>8>#v(e!F;)`3?l&*uHFNBq8k z-}z|XpZA=u)2T64?~V^=&&HYTgCKI4_fVqKQSrnx1@Q06y8nrpOg>(GZ03D6SCy83QFk90Az3DH8)x}RC72juAcQ zf!J1$kVgA!XE&1ja`G~Tv(3;r-Oc5>0Xoj>ulQbW8v*3&AY!`+ON%XiwnDdZz8Cc@AOcvBcOCKD8vW4f211 zh$8MU(vc*+U^-p8UQ6#Gb$U+-3zQHocz*Rg)KveKjsX76lBZ8x8JU zd?+d1uH*ioWE&N$VRu;1ABE$RUAltRlJy*a;0>0oMT=FZrK|9Q_3V!7m${Emo#U`Z zq+1{%luNMsrH?`KKO}ii)KOnF7U{a+dgXfp+HJ_F_LWT|eS|b!T{Ylz`L;O#GU9!t zf4syI*qyw+k8`Hg9cf(wJtf!HI2VtT1JbDa-?BLEY3pj-0T|*tT2)>=qR*yQc~4P` wR$bCnkn#T-omP-|F6|UZ^0i?#!;9#oVeR<*+&^_zgQd0@_=A$oddFDezjq(d^8f$< literal 0 HcmV?d00001 diff --git a/embassy/examples/rp/build.rs b/embassy/examples/rp/build.rs new file mode 100644 index 0000000..3f915f9 --- /dev/null +++ b/embassy/examples/rp/build.rs @@ -0,0 +1,36 @@ +//! 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=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/embassy/examples/rp/memory.x b/embassy/examples/rp/memory.x new file mode 100644 index 0000000..ef19dff --- /dev/null +++ b/embassy/examples/rp/memory.x @@ -0,0 +1,17 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + + /* Pick one of the two options for RAM layout */ + + /* OPTION A: Use all RAM banks as one big block */ + /* Reasonable, unless you are doing something */ + /* really particular with DMA or other concurrent */ + /* access that would benefit from striping */ + RAM : ORIGIN = 0x20000000, LENGTH = 264K + + /* OPTION B: Keep the unstriped sections separate */ + /* RAM: ORIGIN = 0x20000000, LENGTH = 256K */ + /* SCRATCH_A: ORIGIN = 0x20040000, LENGTH = 4K */ + /* SCRATCH_B: ORIGIN = 0x20041000, LENGTH = 4K */ +} diff --git a/embassy/examples/rp/src/bin/adc.rs b/embassy/examples/rp/src/bin/adc.rs new file mode 100644 index 0000000..1bb7c22 --- /dev/null +++ b/embassy/examples/rp/src/bin/adc.rs @@ -0,0 +1,48 @@ +//! This example test the ADC (Analog to Digital Conversion) of the RS2040 pin 26, 27 and 28. +//! It also reads the temperature sensor in the chip. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::Pull; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut adc = Adc::new(p.ADC, Irqs, Config::default()); + + let mut p26 = Channel::new_pin(p.PIN_26, Pull::None); + let mut p27 = Channel::new_pin(p.PIN_27, Pull::None); + let mut p28 = Channel::new_pin(p.PIN_28, Pull::None); + let mut ts = Channel::new_temp_sensor(p.ADC_TEMP_SENSOR); + + loop { + let level = adc.read(&mut p26).await.unwrap(); + info!("Pin 26 ADC: {}", level); + let level = adc.read(&mut p27).await.unwrap(); + info!("Pin 27 ADC: {}", level); + let level = adc.read(&mut p28).await.unwrap(); + info!("Pin 28 ADC: {}", level); + let temp = adc.read(&mut ts).await.unwrap(); + info!("Temp: {} degrees", convert_to_celsius(temp)); + Timer::after_secs(1).await; + } +} + +fn convert_to_celsius(raw_temp: u16) -> f32 { + // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet + let temp = 27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721; + let sign = if temp < 0.0 { -1.0 } else { 1.0 }; + let rounded_temp_x10: i16 = ((temp * 10.0) + 0.5 * sign) as i16; + (rounded_temp_x10 as f32) / 10.0 +} diff --git a/embassy/examples/rp/src/bin/adc_dma.rs b/embassy/examples/rp/src/bin/adc_dma.rs new file mode 100644 index 0000000..f755cf5 --- /dev/null +++ b/embassy/examples/rp/src/bin/adc_dma.rs @@ -0,0 +1,54 @@ +//! This example shows how to use the RP2040 ADC with DMA, both single- and multichannel reads. +//! For multichannel, the samples are interleaved in the buffer: +//! `[ch1, ch2, ch3, ch4, ch1, ch2, ch3, ch4, ...]` +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::Pull; +use embassy_time::{Duration, Ticker}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + let mut adc = Adc::new(p.ADC, Irqs, Config::default()); + let mut dma = p.DMA_CH0; + let mut pin = Channel::new_pin(p.PIN_26, Pull::Up); + let mut pins = [ + Channel::new_pin(p.PIN_27, Pull::Down), + Channel::new_pin(p.PIN_28, Pull::None), + Channel::new_pin(p.PIN_29, Pull::Up), + Channel::new_temp_sensor(p.ADC_TEMP_SENSOR), + ]; + + const BLOCK_SIZE: usize = 100; + const NUM_CHANNELS: usize = 4; + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + // Read 100 samples from a single channel + let mut buf = [0_u16; BLOCK_SIZE]; + let div = 479; // 100kHz sample rate (48Mhz / 100kHz - 1) + adc.read_many(&mut pin, &mut buf, div, &mut dma).await.unwrap(); + info!("single: {:?} ...etc", buf[..8]); + + // Read 100 samples from 4 channels interleaved + let mut buf = [0_u16; { BLOCK_SIZE * NUM_CHANNELS }]; + let div = 119; // 100kHz sample rate (48Mhz / 100kHz * 4ch - 1) + adc.read_many_multichannel(&mut pins, &mut buf, div, &mut dma) + .await + .unwrap(); + info!("multi: {:?} ...etc", buf[..NUM_CHANNELS * 2]); + + ticker.next().await; + } +} diff --git a/embassy/examples/rp/src/bin/assign_resources.rs b/embassy/examples/rp/src/bin/assign_resources.rs new file mode 100644 index 0000000..ff6eff4 --- /dev/null +++ b/embassy/examples/rp/src/bin/assign_resources.rs @@ -0,0 +1,79 @@ +//! This example demonstrates how to assign resources to multiple tasks by splitting up the peripherals. +//! It is not about sharing the same resources between tasks, see sharing.rs for that or head to https://embassy.dev/book/#_sharing_peripherals_between_tasks) +//! Of course splitting up resources and sharing resources can be combined, yet this example is only about splitting up resources. +//! +//! There are basically two ways we demonstrate here: +//! 1) Assigning resources to a task by passing parts of the peripherals +//! 2) Assigning resources to a task by passing a struct with the split up peripherals, using the assign-resources macro +//! +//! using four LEDs on Pins 10, 11, 20 and 21 + +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{self, PIN_20, PIN_21}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // initialize the peripherals + let p = embassy_rp::init(Default::default()); + + // 1) Assigning a resource to a task by passing parts of the peripherals. + spawner + .spawn(double_blinky_manually_assigned(spawner, p.PIN_20, p.PIN_21)) + .unwrap(); + + // 2) Using the assign-resources macro to assign resources to a task. + // we perform the split, see further below for the definition of the resources struct + let r = split_resources!(p); + // and then we can use them + spawner.spawn(double_blinky_macro_assigned(spawner, r.leds)).unwrap(); +} + +// 1) Assigning a resource to a task by passing parts of the peripherals. +#[embassy_executor::task] +async fn double_blinky_manually_assigned(_spawner: Spawner, pin_20: PIN_20, pin_21: PIN_21) { + let mut led_20 = Output::new(pin_20, Level::Low); + let mut led_21 = Output::new(pin_21, Level::High); + + loop { + info!("toggling leds"); + led_20.toggle(); + led_21.toggle(); + Timer::after_secs(1).await; + } +} + +// 2) Using the assign-resources macro to assign resources to a task. +// first we define the resources we want to assign to the task using the assign_resources! macro +// basically this will split up the peripherals struct into smaller structs, that we define here +// naming is up to you, make sure your future self understands what you did here +assign_resources! { + leds: Leds{ + led_10: PIN_10, + led_11: PIN_11, + } + // add more resources to more structs if needed, for example defining one struct for each task +} +// this could be done in another file and imported here, but for the sake of simplicity we do it here +// see https://github.com/adamgreig/assign-resources for more information + +// 2) Using the split resources in a task +#[embassy_executor::task] +async fn double_blinky_macro_assigned(_spawner: Spawner, r: Leds) { + let mut led_10 = Output::new(r.led_10, Level::Low); + let mut led_11 = Output::new(r.led_11, Level::High); + + loop { + info!("toggling leds"); + led_10.toggle(); + led_11.toggle(); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp/src/bin/blinky.rs b/embassy/examples/rp/src/bin/blinky.rs new file mode 100644 index 0000000..60fc45a --- /dev/null +++ b/embassy/examples/rp/src/bin/blinky.rs @@ -0,0 +1,29 @@ +//! This example test the RP Pico on board LED. +//! +//! It does not work with the RP Pico W board. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_time::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()); + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + info!("led on!"); + led.set_high(); + Timer::after_secs(1).await; + + info!("led off!"); + led.set_low(); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp/src/bin/blinky_two_channels.rs b/embassy/examples/rp/src/bin/blinky_two_channels.rs new file mode 100644 index 0000000..b2eec2a --- /dev/null +++ b/embassy/examples/rp/src/bin/blinky_two_channels.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] +/// This example demonstrates how to access a given pin from more than one embassy task +/// The on-board LED is toggled by two tasks with slightly different periods, leading to the +/// apparent duty cycle of the LED increasing, then decreasing, linearly. The phenomenon is similar +/// to interference and the 'beats' you can hear if you play two frequencies close to one another +/// [Link explaining it](https://www.physicsclassroom.com/class/sound/Lesson-3/Interference-and-Beats) +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::{Channel, Sender}; +use embassy_time::{Duration, Ticker}; +use gpio::{AnyPin, Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +enum LedState { + Toggle, +} +static CHANNEL: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(AnyPin::from(p.PIN_25), Level::High); + + let dt = 100 * 1_000_000; + let k = 1.003; + + unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos(dt)))); + unwrap!(spawner.spawn(toggle_led( + CHANNEL.sender(), + Duration::from_nanos((dt as f64 * k) as u64) + ))); + + loop { + match CHANNEL.receive().await { + LedState::Toggle => led.toggle(), + } + } +} + +#[embassy_executor::task(pool_size = 2)] +async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>, delay: Duration) { + let mut ticker = Ticker::every(delay); + loop { + control.send(LedState::Toggle).await; + ticker.next().await; + } +} diff --git a/embassy/examples/rp/src/bin/blinky_two_tasks.rs b/embassy/examples/rp/src/bin/blinky_two_tasks.rs new file mode 100644 index 0000000..a57b513 --- /dev/null +++ b/embassy/examples/rp/src/bin/blinky_two_tasks.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] +/// This example demonstrates how to access a given pin from more than one embassy task +/// The on-board LED is toggled by two tasks with slightly different periods, leading to the +/// apparent duty cycle of the LED increasing, then decreasing, linearly. The phenomenon is similar +/// to interference and the 'beats' you can hear if you play two frequencies close to one another +/// [Link explaining it](https://www.physicsclassroom.com/class/sound/Lesson-3/Interference-and-Beats) +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_time::{Duration, Ticker}; +use gpio::{AnyPin, Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +type LedType = Mutex>>; +static LED: LedType = Mutex::new(None); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + // set the content of the global LED reference to the real LED pin + let led = Output::new(AnyPin::from(p.PIN_25), Level::High); + // inner scope is so that once the mutex is written to, the MutexGuard is dropped, thus the + // Mutex is released + { + *(LED.lock().await) = Some(led); + } + let dt = 100 * 1_000_000; + let k = 1.003; + + unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos(dt)))); + unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos((dt as f64 * k) as u64)))); +} + +#[embassy_executor::task(pool_size = 2)] +async fn toggle_led(led: &'static LedType, delay: Duration) { + let mut ticker = Ticker::every(delay); + loop { + { + let mut led_unlocked = led.lock().await; + if let Some(pin_ref) = led_unlocked.as_mut() { + pin_ref.toggle(); + } + } + ticker.next().await; + } +} diff --git a/embassy/examples/rp/src/bin/bluetooth.rs b/embassy/examples/rp/src/bin/bluetooth.rs new file mode 100644 index 0000000..7524e79 --- /dev/null +++ b/embassy/examples/rp/src/bin/bluetooth.rs @@ -0,0 +1,150 @@ +//! This example test the RP Pico W on board LED. +//! +//! It does not work with the RP Pico board. See blinky.rs. + +#![no_std] +#![no_main] + +use bt_hci::controller::ExternalController; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join3; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; +use trouble_host::advertise::{AdStructure, Advertisement, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE}; +use trouble_host::attribute::{AttributeTable, CharacteristicProp, Service, Uuid}; +use trouble_host::gatt::GattEvent; +use trouble_host::{Address, BleHost, BleHostResources, PacketQos}; +use {defmt_rtt as _, embassy_time as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + let btfw = include_bytes!("../../../../cyw43-firmware/43439A0_btfw.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 + // probe-rs download 43439A0_btfw.bin --format bin --chip RP2040 --base-address 0x10141400 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + //let btfw = unsafe { core::slice::from_raw_parts(0x10141400 as *const u8, 6164) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (_net_device, bt_device, mut control, runner) = cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + control.init(clm).await; + + let controller: ExternalController<_, 10> = ExternalController::new(bt_device); + static HOST_RESOURCES: StaticCell> = StaticCell::new(); + let host_resources = HOST_RESOURCES.init(BleHostResources::new(PacketQos::None)); + + let mut ble: BleHost<'_, _> = BleHost::new(controller, host_resources); + + ble.set_random_address(Address::random([0xff, 0x9f, 0x1a, 0x05, 0xe4, 0xff])); + let mut table: AttributeTable<'_, NoopRawMutex, 10> = AttributeTable::new(); + + // Generic Access Service (mandatory) + let id = b"Pico W Bluetooth"; + let appearance = [0x80, 0x07]; + let mut bat_level = [0; 1]; + let handle = { + let mut svc = table.add_service(Service::new(0x1800)); + let _ = svc.add_characteristic_ro(0x2a00, id); + let _ = svc.add_characteristic_ro(0x2a01, &appearance[..]); + svc.build(); + + // Generic attribute service (mandatory) + table.add_service(Service::new(0x1801)); + + // Battery service + let mut svc = table.add_service(Service::new(0x180f)); + + svc.add_characteristic( + 0x2a19, + &[CharacteristicProp::Read, CharacteristicProp::Notify], + &mut bat_level, + ) + .build() + }; + + let mut adv_data = [0; 31]; + AdStructure::encode_slice( + &[ + AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), + AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]), + AdStructure::CompleteLocalName(b"Pico W Bluetooth"), + ], + &mut adv_data[..], + ) + .unwrap(); + + let server = ble.gatt_server(&table); + + info!("Starting advertising and GATT service"); + let _ = join3( + ble.run(), + async { + loop { + match server.next().await { + Ok(GattEvent::Write { handle, connection: _ }) => { + let _ = table.get(handle, |value| { + info!("Write event. Value written: {:?}", value); + }); + } + Ok(GattEvent::Read { .. }) => { + info!("Read event"); + } + Err(e) => { + error!("Error processing GATT events: {:?}", e); + } + } + } + }, + async { + let mut advertiser = ble + .advertise( + &Default::default(), + Advertisement::ConnectableScannableUndirected { + adv_data: &adv_data[..], + scan_data: &[], + }, + ) + .await + .unwrap(); + let conn = advertiser.accept().await.unwrap(); + // Keep connection alive + let mut tick: u8 = 0; + loop { + Timer::after(Duration::from_secs(10)).await; + tick += 1; + server.notify(handle, &conn, &[tick]).await.unwrap(); + } + }, + ) + .await; +} diff --git a/embassy/examples/rp/src/bin/button.rs b/embassy/examples/rp/src/bin/button.rs new file mode 100644 index 0000000..4ad2ca3 --- /dev/null +++ b/embassy/examples/rp/src/bin/button.rs @@ -0,0 +1,28 @@ +//! This example uses the RP Pico on board LED to test input pin 28. This is not the button on the board. +//! +//! It does not work with the RP Pico W board. Use wifi_blinky.rs and add input pin. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + + // Use PIN_28, Pin34 on J0 for RP Pico, as a input. + // You need to add your own button. + let button = Input::new(p.PIN_28, Pull::Up); + + loop { + if button.is_high() { + led.set_high(); + } else { + led.set_low(); + } + } +} diff --git a/embassy/examples/rp/src/bin/debounce.rs b/embassy/examples/rp/src/bin/debounce.rs new file mode 100644 index 0000000..0077f19 --- /dev/null +++ b/embassy/examples/rp/src/bin/debounce.rs @@ -0,0 +1,80 @@ +//! This example shows the ease of debouncing a button with async rust. +//! Hook up a button or switch between pin 9 and ground. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Input, Level, Pull}; +use embassy_time::{with_deadline, Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +pub struct Debouncer<'a> { + input: Input<'a>, + debounce: Duration, +} + +impl<'a> Debouncer<'a> { + pub fn new(input: Input<'a>, debounce: Duration) -> Self { + Self { input, debounce } + } + + pub async fn debounce(&mut self) -> Level { + loop { + let l1 = self.input.get_level(); + + self.input.wait_for_any_edge().await; + + Timer::after(self.debounce).await; + + let l2 = self.input.get_level(); + if l1 != l2 { + break l2; + } + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut btn = Debouncer::new(Input::new(p.PIN_9, Pull::Up), Duration::from_millis(20)); + + info!("Debounce Demo"); + + loop { + // button pressed + btn.debounce().await; + let start = Instant::now(); + info!("Button Press"); + + match with_deadline(start + Duration::from_secs(1), btn.debounce()).await { + // Button Released < 1s + Ok(_) => { + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + continue; + } + // button held for > 1s + Err(_) => { + info!("Button Held"); + } + } + + match with_deadline(start + Duration::from_secs(5), btn.debounce()).await { + // Button released <5s + Ok(_) => { + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + continue; + } + // button held for > >5s + Err(_) => { + info!("Button Long Held"); + } + } + + // wait for button release before handling another press + btn.debounce().await; + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + } +} diff --git a/embassy/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/embassy/examples/rp/src/bin/ethernet_w5500_multisocket.rs new file mode 100644 index 0000000..12003ad --- /dev/null +++ b/embassy/examples/rp/src/bin/ethernet_w5500_multisocket.rs @@ -0,0 +1,140 @@ +//! This example shows how you can allow multiple simultaneous TCP connections, by having multiple sockets listening on the same port. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_wiznet::chip::W5500; +use embassy_net_wiznet::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::SPI0; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::{Delay, Duration}; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_io_async::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + W5500, + ExclusiveDevice, Output<'static>, Delay>, + Input<'static>, + Output<'static>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(State::<8, 8>::new()); + let (device, runner) = embassy_net_wiznet::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await + .unwrap(); + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + RESOURCES.init(StackResources::new()), + seed, + ); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + // Create two sockets listening to the same port, to handle simultaneous connections + unwrap!(spawner.spawn(listen_task(stack, 0, 1234))); + unwrap!(spawner.spawn(listen_task(stack, 1, 1234))); +} + +#[embassy_executor::task(pool_size = 2)] +async fn listen_task(stack: Stack<'static>, id: u8, port: u16) { + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("SOCKET {}: Listening on TCP:{}...", id, port); + if let Err(e) = socket.accept(port).await { + warn!("accept error: {:?}", e); + continue; + } + info!("SOCKET {}: Received connection from {:?}", id, socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("SOCKET {}: {:?}", id, e); + break; + } + }; + info!("SOCKET {}: rxd {}", id, core::str::from_utf8(&buf[..n]).unwrap()); + + if let Err(e) = socket.write_all(&buf[..n]).await { + warn!("write error: {:?}", e); + break; + } + } + } +} + +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/embassy/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/embassy/examples/rp/src/bin/ethernet_w5500_tcp_client.rs new file mode 100644 index 0000000..d66a43a --- /dev/null +++ b/embassy/examples/rp/src/bin/ethernet_w5500_tcp_client.rs @@ -0,0 +1,128 @@ +//! This example implements a TCP client that attempts to connect to a host on port 1234 and send it some data once per second. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] + +use core::str::FromStr; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_wiznet::chip::W5500; +use embassy_net_wiznet::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::SPI0; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::{Delay, Duration, Timer}; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_io_async::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + W5500, + ExclusiveDevice, Output<'static>, Delay>, + Input<'static>, + Output<'static>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + let mut led = Output::new(p.PIN_25, Level::Low); + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(State::<8, 8>::new()); + let (device, runner) = embassy_net_wiznet::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await + .unwrap(); + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + RESOURCES.init(StackResources::new()), + seed, + ); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + led.set_low(); + info!("Connecting..."); + let host_addr = embassy_net::Ipv4Address::from_str("192.168.1.110").unwrap(); + if let Err(e) = socket.connect((host_addr, 1234)).await { + warn!("connect error: {:?}", e); + continue; + } + info!("Connected to {:?}", socket.remote_endpoint()); + led.set_high(); + + let msg = b"Hello world!\n"; + loop { + if let Err(e) = socket.write_all(msg).await { + warn!("write error: {:?}", e); + break; + } + info!("txd: {}", core::str::from_utf8(msg).unwrap()); + Timer::after_secs(1).await; + } + } +} + +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/embassy/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/embassy/examples/rp/src/bin/ethernet_w5500_tcp_server.rs new file mode 100644 index 0000000..97d9bd4 --- /dev/null +++ b/embassy/examples/rp/src/bin/ethernet_w5500_tcp_server.rs @@ -0,0 +1,137 @@ +//! This example implements a TCP echo server on port 1234 and using DHCP. +//! Send it some data, you should see it echoed back and printed in the console. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_wiznet::chip::W5500; +use embassy_net_wiznet::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::SPI0; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::{Delay, Duration}; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_io_async::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + W5500, + ExclusiveDevice, Output<'static>, Delay>, + Input<'static>, + Output<'static>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + let mut led = Output::new(p.PIN_25, Level::Low); + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(State::<8, 8>::new()); + let (device, runner) = embassy_net_wiznet::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await + .unwrap(); + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + RESOURCES.init(StackResources::new()), + seed, + ); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + led.set_low(); + 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()); + led.set_high(); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("{:?}", e); + break; + } + }; + info!("rxd {}", core::str::from_utf8(&buf[..n]).unwrap()); + + if let Err(e) = socket.write_all(&buf[..n]).await { + warn!("write error: {:?}", e); + break; + } + } + } +} + +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/embassy/examples/rp/src/bin/ethernet_w5500_udp.rs b/embassy/examples/rp/src/bin/ethernet_w5500_udp.rs new file mode 100644 index 0000000..b1b5f97 --- /dev/null +++ b/embassy/examples/rp/src/bin/ethernet_w5500_udp.rs @@ -0,0 +1,117 @@ +//! This example implements a UDP server listening on port 1234 and echoing back the data. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::udp::{PacketMetadata, UdpSocket}; +use embassy_net::{Stack, StackResources}; +use embassy_net_wiznet::chip::W5500; +use embassy_net_wiznet::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::SPI0; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::Delay; +use embedded_hal_bus::spi::ExclusiveDevice; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + W5500, + ExclusiveDevice, Output<'static>, Delay>, + Input<'static>, + Output<'static>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(State::<8, 8>::new()); + let (device, runner) = embassy_net_wiznet::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await + .unwrap(); + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + RESOURCES.init(StackResources::new()), + seed, + ); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut rx_meta = [PacketMetadata::EMPTY; 16]; + let mut tx_meta = [PacketMetadata::EMPTY; 16]; + let mut buf = [0; 4096]; + loop { + let mut socket = UdpSocket::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); + socket.bind(1234).unwrap(); + + loop { + let (n, ep) = socket.recv_from(&mut buf).await.unwrap(); + if let Ok(s) = core::str::from_utf8(&buf[..n]) { + info!("rxd from {}: {}", ep, s); + } + socket.send_to(&buf[..n], ep).await.unwrap(); + } + } +} + +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/embassy/examples/rp/src/bin/flash.rs b/embassy/examples/rp/src/bin/flash.rs new file mode 100644 index 0000000..eb3e6a2 --- /dev/null +++ b/embassy/examples/rp/src/bin/flash.rs @@ -0,0 +1,134 @@ +//! This example test the flash connected to the RP2040 chip. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::flash::{Async, ERASE_SIZE, FLASH_BASE}; +use embassy_rp::peripherals::FLASH; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const ADDR_OFFSET: u32 = 0x100000; +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after_millis(10).await; + + let mut flash = embassy_rp::flash::Flash::<_, Async, FLASH_SIZE>::new(p.FLASH, p.DMA_CH0); + + // Get JEDEC id + let jedec = flash.blocking_jedec_id().unwrap(); + info!("jedec id: 0x{:x}", jedec); + + // Get unique id + let mut uid = [0; 8]; + flash.blocking_unique_id(&mut uid).unwrap(); + info!("unique id: {:?}", uid); + + erase_write_sector(&mut flash, 0x00); + + multiwrite_bytes(&mut flash, ERASE_SIZE as u32); + + background_read(&mut flash, (ERASE_SIZE * 2) as u32).await; + + loop {} +} + +fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { + info!(">>>> [multiwrite_bytes]"); + let mut read_buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", read_buf[0..4]); + + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); + info!("Contents after erase starts with {=[u8]}", read_buf[0..4]); + if read_buf.iter().any(|x| *x != 0xFF) { + defmt::panic!("unexpected"); + } + + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, &[0x01])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 1, &[0x02])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 2, &[0x03])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 3, &[0x04])); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); + info!("Contents after write starts with {=[u8]}", read_buf[0..4]); + if &read_buf[0..4] != &[0x01, 0x02, 0x03, 0x04] { + defmt::panic!("unexpected"); + } +} + +fn erase_write_sector(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { + info!(">>>> [erase_write_sector]"); + let mut buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", buf[0..4]); + + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); + info!("Contents after erase starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xFF) { + defmt::panic!("unexpected"); + } + + for b in buf.iter_mut() { + *b = 0xDA; + } + + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, &buf)); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); + info!("Contents after write starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xDA) { + defmt::panic!("unexpected"); + } +} + +async fn background_read(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { + info!(">>>> [background_read]"); + + let mut buf = [0u32; 8]; + defmt::unwrap!(flash.background_read(ADDR_OFFSET + offset, &mut buf)).await; + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=u32:x}", buf[0]); + + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.background_read(ADDR_OFFSET + offset, &mut buf)).await; + info!("Contents after erase starts with {=u32:x}", buf[0]); + if buf.iter().any(|x| *x != 0xFFFFFFFF) { + defmt::panic!("unexpected"); + } + + for b in buf.iter_mut() { + *b = 0xDABA1234; + } + + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, unsafe { + core::slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len() * 4) + })); + + defmt::unwrap!(flash.background_read(ADDR_OFFSET + offset, &mut buf)).await; + info!("Contents after write starts with {=u32:x}", buf[0]); + if buf.iter().any(|x| *x != 0xDABA1234) { + defmt::panic!("unexpected"); + } +} diff --git a/embassy/examples/rp/src/bin/gpio_async.rs b/embassy/examples/rp/src/bin/gpio_async.rs new file mode 100644 index 0000000..b79fb2a --- /dev/null +++ b/embassy/examples/rp/src/bin/gpio_async.rs @@ -0,0 +1,40 @@ +//! This example shows how async gpio can be used with a RP2040. +//! +//! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_time::Timer; +use gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +/// It requires an external signal to be manually triggered on PIN 16. For +/// example, this could be accomplished using an external power source with a +/// button so that it is possible to toggle the signal from low to high. +/// +/// This example will begin with turning on the LED on the board and wait for a +/// high signal on PIN 16. Once the high event/signal occurs the program will +/// continue and turn off the LED, and then wait for 2 seconds before completing +/// the loop and starting over again. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + let mut async_input = Input::new(p.PIN_16, Pull::None); + + loop { + info!("wait_for_high. Turn on LED"); + led.set_high(); + + async_input.wait_for_high().await; + + info!("done wait_for_high. Turn off LED"); + led.set_low(); + + Timer::after_secs(2).await; + } +} diff --git a/embassy/examples/rp/src/bin/gpout.rs b/embassy/examples/rp/src/bin/gpout.rs new file mode 100644 index 0000000..0113592 --- /dev/null +++ b/embassy/examples/rp/src/bin/gpout.rs @@ -0,0 +1,37 @@ +//! This example shows how GPOUT (General purpose clock outputs) can toggle a output pin. +//! +//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::clocks; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let gpout3 = clocks::Gpout::new(p.PIN_25); + gpout3.set_div(1000, 0); + gpout3.enable(); + + loop { + gpout3.set_src(clocks::GpoutSrc::Sys); + info!( + "Pin 25 is now outputing CLK_SYS/1000, should be toggling at {}", + gpout3.get_freq() + ); + Timer::after_secs(2).await; + + gpout3.set_src(clocks::GpoutSrc::Ref); + info!( + "Pin 25 is now outputing CLK_REF/1000, should be toggling at {}", + gpout3.get_freq() + ); + Timer::after_secs(2).await; + } +} diff --git a/embassy/examples/rp/src/bin/i2c_async.rs b/embassy/examples/rp/src/bin/i2c_async.rs new file mode 100644 index 0000000..e31cc89 --- /dev/null +++ b/embassy/examples/rp/src/bin/i2c_async.rs @@ -0,0 +1,110 @@ +//! This example shows how to communicate asynchronous using i2c with external chips. +//! +//! Example written for the [`MCP23017 16-Bit I2C I/O Expander with Serial Interface`] chip. +//! (https://www.microchip.com/en-us/product/mcp23017) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::i2c::{self, Config, InterruptHandler}; +use embassy_rp::peripherals::I2C1; +use embassy_time::Timer; +use embedded_hal_async::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + I2C1_IRQ => InterruptHandler; +}); + +#[allow(dead_code)] +mod mcp23017 { + pub const ADDR: u8 = 0x20; // default addr + + macro_rules! mcpregs { + ($($name:ident : $val:expr),* $(,)?) => { + $( + pub const $name: u8 = $val; + )* + + pub fn regname(reg: u8) -> &'static str { + match reg { + $( + $val => stringify!($name), + )* + _ => panic!("bad reg"), + } + } + } + } + + // These are correct for IOCON.BANK=0 + mcpregs! { + IODIRA: 0x00, + IPOLA: 0x02, + GPINTENA: 0x04, + DEFVALA: 0x06, + INTCONA: 0x08, + IOCONA: 0x0A, + GPPUA: 0x0C, + INTFA: 0x0E, + INTCAPA: 0x10, + GPIOA: 0x12, + OLATA: 0x14, + IODIRB: 0x01, + IPOLB: 0x03, + GPINTENB: 0x05, + DEFVALB: 0x07, + INTCONB: 0x09, + IOCONB: 0x0B, + GPPUB: 0x0D, + INTFB: 0x0F, + INTCAPB: 0x11, + GPIOB: 0x13, + OLATB: 0x15, + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let sda = p.PIN_14; + let scl = p.PIN_15; + + info!("set up i2c "); + let mut i2c = i2c::I2c::new_async(p.I2C1, scl, sda, Irqs, Config::default()); + + use mcp23017::*; + + info!("init mcp23017 config for IxpandO"); + // init - a outputs, b inputs + i2c.write(ADDR, &[IODIRA, 0x00]).await.unwrap(); + i2c.write(ADDR, &[IODIRB, 0xff]).await.unwrap(); + i2c.write(ADDR, &[GPPUB, 0xff]).await.unwrap(); // pullups + + let mut val = 1; + loop { + let mut portb = [0]; + + i2c.write_read(mcp23017::ADDR, &[GPIOB], &mut portb).await.unwrap(); + info!("portb = {:02x}", portb[0]); + i2c.write(mcp23017::ADDR, &[GPIOA, val | portb[0]]).await.unwrap(); + val = val.rotate_left(1); + + // get a register dump + info!("getting register dump"); + let mut regs = [0; 22]; + i2c.write_read(ADDR, &[0], &mut regs).await.unwrap(); + // always get the regdump but only display it if portb'0 is set + if portb[0] & 1 != 0 { + for (idx, reg) in regs.into_iter().enumerate() { + info!("{} => {:02x}", regname(idx as u8), reg); + } + } + + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/rp/src/bin/i2c_async_embassy.rs b/embassy/examples/rp/src/bin/i2c_async_embassy.rs new file mode 100644 index 0000000..a65b71b --- /dev/null +++ b/embassy/examples/rp/src/bin/i2c_async_embassy.rs @@ -0,0 +1,85 @@ +//! This example shows how to communicate asynchronous using i2c with external chip. +//! +//! It's using embassy's functions directly instead of traits from embedded_hal_async::i2c::I2c. +//! While most of i2c devices are addressed using 7 bits, an extension allows 10 bits too. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_rp::i2c::InterruptHandler; +use {defmt_rtt as _, panic_probe as _}; + +// Our anonymous hypotetical temperature sensor could be: +// a 12-bit sensor, with 100ms startup time, range of -40*C - 125*C, and precision 0.25*C +// It requires no configuration or calibration, works with all i2c bus speeds, +// never stretches clock or does anything complicated. Replies with one u16. +// It requires only one write to take it out of suspend mode, and stays on. +// Often result would be just on 12 bits, but here we'll simplify it to 16. + +enum UncomplicatedSensorId { + A(UncomplicatedSensorU8), + B(UncomplicatedSensorU16), +} +enum UncomplicatedSensorU8 { + First = 0x48, +} +enum UncomplicatedSensorU16 { + Other = 0x0049, +} + +impl Into for UncomplicatedSensorU16 { + fn into(self) -> u16 { + self as u16 + } +} +impl Into for UncomplicatedSensorU8 { + fn into(self) -> u16 { + 0x48 + } +} +impl From for u16 { + fn from(t: UncomplicatedSensorId) -> Self { + match t { + UncomplicatedSensorId::A(x) => x.into(), + UncomplicatedSensorId::B(x) => x.into(), + } + } +} + +embassy_rp::bind_interrupts!(struct Irqs { + I2C1_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_task_spawner: embassy_executor::Spawner) { + let p = embassy_rp::init(Default::default()); + let sda = p.PIN_14; + let scl = p.PIN_15; + let config = embassy_rp::i2c::Config::default(); + let mut bus = embassy_rp::i2c::I2c::new_async(p.I2C1, scl, sda, Irqs, config); + + const WAKEYWAKEY: u16 = 0xBABE; + let mut result: [u8; 2] = [0, 0]; + // wait for sensors to initialize + embassy_time::Timer::after(embassy_time::Duration::from_millis(100)).await; + + let _res_1 = bus + .write_async(UncomplicatedSensorU8::First, WAKEYWAKEY.to_be_bytes()) + .await; + let _res_2 = bus + .write_async(UncomplicatedSensorU16::Other, WAKEYWAKEY.to_be_bytes()) + .await; + + loop { + let s1 = UncomplicatedSensorId::A(UncomplicatedSensorU8::First); + let s2 = UncomplicatedSensorId::B(UncomplicatedSensorU16::Other); + let sensors = [s1, s2]; + for sensor in sensors { + if bus.read_async(sensor, &mut result).await.is_ok() { + info!("Result {}", u16::from_be_bytes(result.into())); + } + } + embassy_time::Timer::after(embassy_time::Duration::from_millis(200)).await; + } +} diff --git a/embassy/examples/rp/src/bin/i2c_blocking.rs b/embassy/examples/rp/src/bin/i2c_blocking.rs new file mode 100644 index 0000000..c9c8a27 --- /dev/null +++ b/embassy/examples/rp/src/bin/i2c_blocking.rs @@ -0,0 +1,74 @@ +//! This example shows how to communicate using i2c with external chips. +//! +//! Example written for the [`MCP23017 16-Bit I2C I/O Expander with Serial Interface`] chip. +//! (https://www.microchip.com/en-us/product/mcp23017) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::i2c::{self, Config}; +use embassy_time::Timer; +use embedded_hal_1::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +#[allow(dead_code)] +mod mcp23017 { + pub const ADDR: u8 = 0x20; // default addr + + pub const IODIRA: u8 = 0x00; + pub const IPOLA: u8 = 0x02; + pub const GPINTENA: u8 = 0x04; + pub const DEFVALA: u8 = 0x06; + pub const INTCONA: u8 = 0x08; + pub const IOCONA: u8 = 0x0A; + pub const GPPUA: u8 = 0x0C; + pub const INTFA: u8 = 0x0E; + pub const INTCAPA: u8 = 0x10; + pub const GPIOA: u8 = 0x12; + pub const OLATA: u8 = 0x14; + pub const IODIRB: u8 = 0x01; + pub const IPOLB: u8 = 0x03; + pub const GPINTENB: u8 = 0x05; + pub const DEFVALB: u8 = 0x07; + pub const INTCONB: u8 = 0x09; + pub const IOCONB: u8 = 0x0B; + pub const GPPUB: u8 = 0x0D; + pub const INTFB: u8 = 0x0F; + pub const INTCAPB: u8 = 0x11; + pub const GPIOB: u8 = 0x13; + pub const OLATB: u8 = 0x15; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let sda = p.PIN_14; + let scl = p.PIN_15; + + info!("set up i2c "); + let mut i2c = i2c::I2c::new_blocking(p.I2C1, scl, sda, Config::default()); + + use mcp23017::*; + + info!("init mcp23017 config for IxpandO"); + // init - a outputs, b inputs + i2c.write(ADDR, &[IODIRA, 0x00]).unwrap(); + i2c.write(ADDR, &[IODIRB, 0xff]).unwrap(); + i2c.write(ADDR, &[GPPUB, 0xff]).unwrap(); // pullups + + let mut val = 0xaa; + loop { + let mut portb = [0]; + + i2c.write(mcp23017::ADDR, &[GPIOA, val]).unwrap(); + i2c.write_read(mcp23017::ADDR, &[GPIOB], &mut portb).unwrap(); + + info!("portb = {:02x}", portb[0]); + val = !val; + + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp/src/bin/i2c_slave.rs b/embassy/examples/rp/src/bin/i2c_slave.rs new file mode 100644 index 0000000..9fffb46 --- /dev/null +++ b/embassy/examples/rp/src/bin/i2c_slave.rs @@ -0,0 +1,117 @@ +//! This example shows how to use the 2040 as an i2c slave. +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::peripherals::{I2C0, I2C1}; +use embassy_rp::{bind_interrupts, i2c, i2c_slave}; +use embassy_time::Timer; +use embedded_hal_async::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + I2C0_IRQ => i2c::InterruptHandler; + I2C1_IRQ => i2c::InterruptHandler; +}); + +const DEV_ADDR: u8 = 0x42; + +#[embassy_executor::task] +async fn device_task(mut dev: i2c_slave::I2cSlave<'static, I2C1>) -> ! { + info!("Device start"); + + let mut state = 0; + + loop { + let mut buf = [0u8; 128]; + match dev.listen(&mut buf).await { + Ok(i2c_slave::Command::GeneralCall(len)) => info!("Device received general call write: {}", buf[..len]), + Ok(i2c_slave::Command::Read) => loop { + match dev.respond_to_read(&[state]).await { + Ok(x) => match x { + i2c_slave::ReadStatus::Done => break, + i2c_slave::ReadStatus::NeedMoreBytes => (), + i2c_slave::ReadStatus::LeftoverBytes(x) => { + info!("tried to write {} extra bytes", x); + break; + } + }, + Err(e) => error!("error while responding {}", e), + } + }, + Ok(i2c_slave::Command::Write(len)) => info!("Device received write: {}", buf[..len]), + Ok(i2c_slave::Command::WriteRead(len)) => { + info!("device received write read: {:x}", buf[..len]); + match buf[0] { + // Set the state + 0xC2 => { + state = buf[1]; + match dev.respond_and_fill(&[state], 0x00).await { + Ok(read_status) => info!("response read status {}", read_status), + Err(e) => error!("error while responding {}", e), + } + } + // Reset State + 0xC8 => { + state = 0; + match dev.respond_and_fill(&[state], 0x00).await { + Ok(read_status) => info!("response read status {}", read_status), + Err(e) => error!("error while responding {}", e), + } + } + x => error!("Invalid Write Read {:x}", x), + } + } + Err(e) => error!("{}", e), + } + } +} + +#[embassy_executor::task] +async fn controller_task(mut con: i2c::I2c<'static, I2C0, i2c::Async>) { + info!("Controller start"); + + loop { + let mut resp_buff = [0u8; 2]; + for i in 0..10 { + match con.write_read(DEV_ADDR, &[0xC2, i], &mut resp_buff).await { + Ok(_) => info!("write_read response: {}", resp_buff), + Err(e) => error!("Error writing {}", e), + } + + Timer::after_millis(100).await; + } + match con.read(DEV_ADDR, &mut resp_buff).await { + Ok(_) => info!("read response: {}", resp_buff), + Err(e) => error!("Error writing {}", e), + } + match con.write_read(DEV_ADDR, &[0xC8], &mut resp_buff).await { + Ok(_) => info!("write_read response: {}", resp_buff), + Err(e) => error!("Error writing {}", e), + } + Timer::after_millis(100).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let d_sda = p.PIN_3; + let d_scl = p.PIN_2; + let mut config = i2c_slave::Config::default(); + config.addr = DEV_ADDR as u16; + let device = i2c_slave::I2cSlave::new(p.I2C1, d_sda, d_scl, Irqs, config); + + unwrap!(spawner.spawn(device_task(device))); + + let c_sda = p.PIN_1; + let c_scl = p.PIN_0; + let mut config = i2c::Config::default(); + config.frequency = 1_000_000; + let controller = i2c::I2c::new_async(p.I2C0, c_sda, c_scl, Irqs, config); + + unwrap!(spawner.spawn(controller_task(controller))); +} diff --git a/embassy/examples/rp/src/bin/interrupt.rs b/embassy/examples/rp/src/bin/interrupt.rs new file mode 100644 index 0000000..5b9d702 --- /dev/null +++ b/embassy/examples/rp/src/bin/interrupt.rs @@ -0,0 +1,94 @@ +//! This example shows how you can use raw interrupt handlers alongside embassy. +//! The example also showcases some of the options available for sharing resources/data. +//! +//! In the example, an ADC reading is triggered every time the PWM wraps around. +//! The sample data is sent down a channel, to be processed inside a low priority task. +//! The processed data is then used to adjust the PWM duty cycle, once every second. + +#![no_std] +#![no_main] + +use core::cell::{Cell, RefCell}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{self, Adc, Blocking}; +use embassy_rp::gpio::Pull; +use embassy_rp::interrupt; +use embassy_rp::pwm::{Config, Pwm}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::channel::Channel; +use embassy_time::{Duration, Ticker}; +use portable_atomic::{AtomicU32, Ordering}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +static COUNTER: AtomicU32 = AtomicU32::new(0); +static PWM: Mutex>> = Mutex::new(RefCell::new(None)); +static ADC: Mutex, adc::Channel)>>> = + Mutex::new(RefCell::new(None)); +static ADC_VALUES: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + embassy_rp::pac::SIO.spinlock(31).write_value(1); + let p = embassy_rp::init(Default::default()); + + let adc = Adc::new_blocking(p.ADC, Default::default()); + let p26 = adc::Channel::new_pin(p.PIN_26, Pull::None); + ADC.lock(|a| a.borrow_mut().replace((adc, p26))); + + let pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, Default::default()); + PWM.lock(|p| p.borrow_mut().replace(pwm)); + + // Enable the interrupt for pwm slice 4 + embassy_rp::pac::PWM.inte().modify(|w| w.set_ch4(true)); + unsafe { + cortex_m::peripheral::NVIC::unmask(interrupt::PWM_IRQ_WRAP); + } + + // Tasks require their resources to have 'static lifetime + // No Mutex needed when sharing within the same executor/prio level + static AVG: StaticCell> = StaticCell::new(); + let avg = AVG.init(Default::default()); + spawner.must_spawn(processing(avg)); + + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + ticker.next().await; + let freq = COUNTER.swap(0, Ordering::Relaxed); + info!("pwm freq: {:?} Hz", freq); + info!("adc average: {:?}", avg.get()); + + // Update the pwm duty cycle, based on the averaged adc reading + let mut config = Config::default(); + config.compare_b = ((avg.get() as f32 / 4095.0) * config.top as f32) as _; + PWM.lock(|p| p.borrow_mut().as_mut().unwrap().set_config(&config)); + } +} + +#[embassy_executor::task] +async fn processing(avg: &'static Cell) { + let mut buffer: heapless::HistoryBuffer = Default::default(); + loop { + let val = ADC_VALUES.receive().await; + buffer.write(val); + let sum: u32 = buffer.iter().map(|x| *x as u32).sum(); + avg.set(sum / buffer.len() as u32); + } +} + +#[interrupt] +fn PWM_IRQ_WRAP() { + critical_section::with(|cs| { + let mut adc = ADC.borrow(cs).borrow_mut(); + let (adc, p26) = adc.as_mut().unwrap(); + let val = adc.blocking_read(p26).unwrap(); + ADC_VALUES.try_send(val).ok(); + + // Clear the interrupt, so we don't immediately re-enter this irq handler + PWM.borrow(cs).borrow_mut().as_mut().unwrap().clear_wrapped(); + }); + COUNTER.fetch_add(1, Ordering::Relaxed); +} diff --git a/embassy/examples/rp/src/bin/multicore.rs b/embassy/examples/rp/src/bin/multicore.rs new file mode 100644 index 0000000..7cb546c --- /dev/null +++ b/embassy/examples/rp/src/bin/multicore.rs @@ -0,0 +1,66 @@ +//! This example shows how to send messages between the two cores in the RP2040 chip. +//! +//! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Executor; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<4096> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); +static CHANNEL: Channel = Channel::new(); + +enum LedState { + On, + Off, +} + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + let led = Output::new(p.PIN_25, Level::Low); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led)))); + }, + ); + + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +} + +#[embassy_executor::task] +async fn core0_task() { + info!("Hello from core 0"); + loop { + CHANNEL.send(LedState::On).await; + Timer::after_millis(100).await; + CHANNEL.send(LedState::Off).await; + Timer::after_millis(400).await; + } +} + +#[embassy_executor::task] +async fn core1_task(mut led: Output<'static>) { + info!("Hello from core 1"); + loop { + match CHANNEL.receive().await { + LedState::On => led.set_high(), + LedState::Off => led.set_low(), + } + } +} diff --git a/embassy/examples/rp/src/bin/multiprio.rs b/embassy/examples/rp/src/bin/multiprio.rs new file mode 100644 index 0000000..2b397f9 --- /dev/null +++ b/embassy/examples/rp/src/bin/multiprio.rs @@ -0,0 +1,145 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::{info, unwrap}; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_time::{Instant, Timer, TICK_HZ}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn run_high() { + loop { + info!(" [high] tick!"); + Timer::after_ticks(673740).await; + } +} + +#[embassy_executor::task] +async fn run_med() { + loop { + let start = Instant::now(); + info!(" [med] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + embassy_time::block_for(embassy_time::Duration::from_secs(1)); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; + info!(" [med] done in {} ms", ms); + + Timer::after_ticks(53421).await; + } +} + +#[embassy_executor::task] +async fn run_low() { + loop { + let start = Instant::now(); + info!("[low] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + embassy_time::block_for(embassy_time::Duration::from_secs(2)); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; + info!("[low] done in {} ms", ms); + + Timer::after_ticks(82983).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn SWI_IRQ_0() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_rp::init(Default::default()); + + // High-priority executor: SWI_IRQ_1, priority level 2 + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + let spawner = EXECUTOR_HIGH.start(interrupt::SWI_IRQ_1); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: SWI_IRQ_0, priority level 3 + interrupt::SWI_IRQ_0.set_priority(Priority::P3); + let spawner = EXECUTOR_MED.start(interrupt::SWI_IRQ_0); + unwrap!(spawner.spawn(run_med())); + + // Low priority executor: runs in thread mode, using WFE/SEV + let executor = EXECUTOR_LOW.init(Executor::new()); + executor.run(|spawner| { + unwrap!(spawner.spawn(run_low())); + }); +} diff --git a/embassy/examples/rp/src/bin/orchestrate_tasks.rs b/embassy/examples/rp/src/bin/orchestrate_tasks.rs new file mode 100644 index 0000000..0e21d58 --- /dev/null +++ b/embassy/examples/rp/src/bin/orchestrate_tasks.rs @@ -0,0 +1,318 @@ +//! This example demonstrates some approaches to communicate between tasks in order to orchestrate the state of the system. +//! +//! We demonstrate how to: +//! - use a channel to send messages between tasks, in this case here in order to have one task control the state of the system. +//! - use a signal to terminate a task. +//! - use command channels to send commands to another task. +//! - use different ways to receive messages, from a straightforwar awaiting on one channel to a more complex awaiting on multiple futures. +//! +//! There are more patterns to orchestrate tasks, this is just one example. +//! +//! We will use these tasks to generate example "state information": +//! - a task that generates random numbers in intervals of 60s +//! - a task that generates random numbers in intervals of 30s +//! - a task that generates random numbers in intervals of 90s +//! - a task that notifies about being attached/disattached from usb power +//! - a task that measures vsys voltage in intervals of 30s +//! - a task that consumes the state information and reacts to it + +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::select::{select, Either}; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Pull}; +use embassy_rp::{bind_interrupts, peripherals}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::{channel, signal}; +use embassy_time::{Duration, Timer}; +use rand::RngCore; +use {defmt_rtt as _, panic_probe as _}; + +// This is just some preparation, see example `assign_resources.rs` for more information on this. We prep the rresources that we will be using in different tasks. +// **Note**: This will not work with a board that has a wifi chip, because the wifi chip uses pins 24 and 29 for its own purposes. A way around this in software +// is not trivial, at least if you intend to use wifi, too. Workaround is to wire from vsys and vbus pins to appropriate pins on the board through a voltage divider. Then use those pins. +// For this example it will not matter much, the concept of what we are showing remains valid. +assign_resources! { + vsys: Vsys { + adc: ADC, + pin_29: PIN_29, + }, + vbus: Vbus { + pin_24: PIN_24, + }, +} + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +/// This is the type of Events that we will send from the worker tasks to the orchestrating task. +enum Events { + UsbPowered(bool), + VsysVoltage(f32), + FirstRandomSeed(u32), + SecondRandomSeed(u32), + ThirdRandomSeed(u32), + ResetFirstRandomSeed, +} + +/// This is the type of Commands that we will send from the orchestrating task to the worker tasks. +/// Note that we are lazy here and only have one command, you might want to have more. +enum Commands { + /// This command will stop the appropriate worker task + Stop, +} + +/// This is the state of the system, we will use this to orchestrate the system. This is a simple example, in a real world application this would be more complex. +#[derive(Default, Debug, Clone, Format)] +struct State { + usb_powered: bool, + vsys_voltage: f32, + first_random_seed: u32, + second_random_seed: u32, + third_random_seed: u32, + times_we_got_first_random_seed: u8, + maximum_times_we_want_first_random_seed: u8, +} + +impl State { + fn new() -> Self { + Self { + usb_powered: false, + vsys_voltage: 0.0, + first_random_seed: 0, + second_random_seed: 0, + third_random_seed: 0, + times_we_got_first_random_seed: 0, + maximum_times_we_want_first_random_seed: 3, + } + } +} + +/// Channel for the events that we want the orchestrator to react to, all state events are of the type Enum Events. +/// We use a channel with an arbitrary size of 10, the precise size of the queue depends on your use case. This depends on how many events we +/// expect to be generated in a given time frame and how fast the orchestrator can react to them. And then if we rather want the senders to wait for +/// new slots in the queue or if we want the orchestrator to have a backlog of events to process. In this case here we expect to always be enough slots +/// in the queue, so the worker tasks can in all nominal cases send their events and continue with their work without waiting. +/// For the events we - in this case here - do not want to loose any events, so a channel is a good choice. See embassy_sync docs for other options. +static EVENT_CHANNEL: channel::Channel = channel::Channel::new(); + +/// Signal for stopping the first random signal task. We use a signal here, because we need no queue. It is suffiient to have one signal active. +static STOP_FIRST_RANDOM_SIGNAL: signal::Signal = signal::Signal::new(); + +/// Channel for the state that we want the consumer task to react to. We use a channel here, because we want to have a queue of state changes, although +/// we want the queue to be of size 1, because we want to finish rwacting to the state change before the next one comes in. This is just a design choice +/// and depends on your use case. +static CONSUMER_CHANNEL: channel::Channel = channel::Channel::new(); + +// And now we can put all this into use + +/// This is the main task, that will not do very much besides spawning the other tasks. This is a design choice, you could do the +/// orchestrating here. This is to show that we do not need a main loop here, the system will run indefinitely as long as at least one task is running. +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // initialize the peripherals + let p = embassy_rp::init(Default::default()); + // split the resources, for convenience - see above + let r = split_resources! {p}; + + // spawn the tasks + spawner.spawn(orchestrate(spawner)).unwrap(); + spawner.spawn(random_60s(spawner)).unwrap(); + spawner.spawn(random_90s(spawner)).unwrap(); + spawner.spawn(usb_power(spawner, r.vbus)).unwrap(); + spawner.spawn(vsys_voltage(spawner, r.vsys)).unwrap(); + spawner.spawn(consumer(spawner)).unwrap(); +} + +/// This is the task handling the system state and orchestrating the other tasks. WEe can regard this as the "main loop" of the system. +#[embassy_executor::task] +async fn orchestrate(_spawner: Spawner) { + let mut state = State::new(); + + // we need to have a receiver for the events + let receiver = EVENT_CHANNEL.receiver(); + + // and we need a sender for the consumer task + let state_sender = CONSUMER_CHANNEL.sender(); + + loop { + // we await on the receiver, this will block until a new event is available + // as an alternative to this, we could also await on multiple channels, this would block until at least one of the channels has an event + // see the embassy_futures docs: https://docs.embassy.dev/embassy-futures/git/default/select/index.html + // The task random_30s does a select, if you want to have a look at that. + // Another reason to use select may also be that we want to have a timeout, so we can react to the absence of events within a time frame. + // We keep it simple here. + let event = receiver.receive().await; + + // react to the events + match event { + Events::UsbPowered(usb_powered) => { + // update the state and/or react to the event here + state.usb_powered = usb_powered; + info!("Usb powered: {}", usb_powered); + } + Events::VsysVoltage(voltage) => { + // update the state and/or react to the event here + state.vsys_voltage = voltage; + info!("Vsys voltage: {}", voltage); + } + Events::FirstRandomSeed(seed) => { + // update the state and/or react to the event here + state.first_random_seed = seed; + // here we change some meta state, we count how many times we got the first random seed + state.times_we_got_first_random_seed += 1; + info!( + "First random seed: {}, and that was iteration {} of receiving this.", + seed, &state.times_we_got_first_random_seed + ); + } + Events::SecondRandomSeed(seed) => { + // update the state and/or react to the event here + state.second_random_seed = seed; + info!("Second random seed: {}", seed); + } + Events::ThirdRandomSeed(seed) => { + // update the state and/or react to the event here + state.third_random_seed = seed; + info!("Third random seed: {}", seed); + } + Events::ResetFirstRandomSeed => { + // update the state and/or react to the event here + state.times_we_got_first_random_seed = 0; + state.first_random_seed = 0; + info!("Resetting the first random seed counter"); + } + } + // we now have an altered state + // there is a crate for detecting field changes on crates.io (https://crates.io/crates/fieldset) that might be useful here + // for now we just keep it simple + + // we send the state to the consumer task + // since the channel has a size of 1, this will block until the consumer task has received the state, which is what we want here in this example + // **Note:** It is bad design to send too much data between tasks, with no clear definition of what "too much" is. In this example we send the + // whole state, in a real world application you might want to send only the data, that is relevant to the consumer task AND only when it has changed. + // We keep it simple here. + state_sender.send(state.clone()).await; + } +} + +/// This task will consume the state information and react to it. This is a simple example, in a real world application this would be more complex +/// and we could have multiple consumer tasks, each reacting to different parts of the state. +#[embassy_executor::task] +async fn consumer(spawner: Spawner) { + // we need to have a receiver for the state + let receiver = CONSUMER_CHANNEL.receiver(); + let sender = EVENT_CHANNEL.sender(); + loop { + // we await on the receiver, this will block until a new state is available + let state = receiver.receive().await; + // react to the state, in this case here we just log it + info!("The consumer has reveived this state: {:?}", &state); + + // here we react to the state, in this case here we want to start or stop the first random signal task depending on the state of the system + match state.times_we_got_first_random_seed { + max if max == state.maximum_times_we_want_first_random_seed => { + info!("Stopping the first random signal task"); + // we send a command to the task + STOP_FIRST_RANDOM_SIGNAL.signal(Commands::Stop); + // we notify the orchestrator that we have sent the command + sender.send(Events::ResetFirstRandomSeed).await; + } + 0 => { + // we start the task, which presents us with an interesting problem, because we may return here before the task has started + // here we just try and log if the task has started, in a real world application you might want to handle this more gracefully + info!("Starting the first random signal task"); + match spawner.spawn(random_30s(spawner)) { + Ok(_) => info!("Successfully spawned random_30s task"), + Err(e) => info!("Failed to spawn random_30s task: {:?}", e), + } + } + _ => {} + } + } +} + +/// This task will generate random numbers in intervals of 30s +/// The task will terminate after it has received a command signal to stop, see the orchestrate task for that. +/// Note that we are not spawning this task from main, as we will show how such a task can be spawned and closed dynamically. +#[embassy_executor::task] +async fn random_30s(_spawner: Spawner) { + let mut rng = RoscRng; + let sender = EVENT_CHANNEL.sender(); + loop { + // we either await on the timer or the signal, whichever comes first. + let futures = select(Timer::after(Duration::from_secs(30)), STOP_FIRST_RANDOM_SIGNAL.wait()).await; + match futures { + Either::First(_) => { + // we received are operating on the timer + info!("30s are up, generating random number"); + let random_number = rng.next_u32(); + sender.send(Events::FirstRandomSeed(random_number)).await; + } + Either::Second(_) => { + // we received the signal to stop + info!("Received signal to stop, goodbye!"); + break; + } + } + } +} + +/// This task will generate random numbers in intervals of 60s +#[embassy_executor::task] +async fn random_60s(_spawner: Spawner) { + let mut rng = RoscRng; + let sender = EVENT_CHANNEL.sender(); + loop { + Timer::after(Duration::from_secs(60)).await; + let random_number = rng.next_u32(); + sender.send(Events::SecondRandomSeed(random_number)).await; + } +} + +/// This task will generate random numbers in intervals of 90s +#[embassy_executor::task] +async fn random_90s(_spawner: Spawner) { + let mut rng = RoscRng; + let sender = EVENT_CHANNEL.sender(); + loop { + Timer::after(Duration::from_secs(90)).await; + let random_number = rng.next_u32(); + sender.send(Events::ThirdRandomSeed(random_number)).await; + } +} + +/// This task will notify if we are connected to usb power +#[embassy_executor::task] +pub async fn usb_power(_spawner: Spawner, r: Vbus) { + let mut vbus_in = Input::new(r.pin_24, Pull::None); + let sender = EVENT_CHANNEL.sender(); + loop { + sender.send(Events::UsbPowered(vbus_in.is_high())).await; + vbus_in.wait_for_any_edge().await; + } +} + +/// This task will measure the vsys voltage in intervals of 30s +#[embassy_executor::task] +pub async fn vsys_voltage(_spawner: Spawner, r: Vsys) { + let mut adc = Adc::new(r.adc, Irqs, Config::default()); + let vsys_in = r.pin_29; + let mut channel = Channel::new_pin(vsys_in, Pull::None); + let sender = EVENT_CHANNEL.sender(); + loop { + // read the adc value + let adc_value = adc.read(&mut channel).await.unwrap(); + // convert the adc value to voltage. + // 3.3 is the reference voltage, 3.0 is the factor for the inbuilt voltage divider and 4096 is the resolution of the adc + let voltage = (adc_value as f32) * 3.3 * 3.0 / 4096.0; + sender.send(Events::VsysVoltage(voltage)).await; + Timer::after(Duration::from_secs(30)).await; + } +} diff --git a/embassy/examples/rp/src/bin/pio_async.rs b/embassy/examples/rp/src/bin/pio_async.rs new file mode 100644 index 0000000..ee24859 --- /dev/null +++ b/embassy/examples/rp/src/bin/pio_async.rs @@ -0,0 +1,130 @@ +//! This example shows powerful PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Common, Config, InterruptHandler, Irq, Pio, PioPin, ShiftDirection, StateMachine}; +use fixed::traits::ToFixed; +use fixed_macro::types::U56F8; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +fn setup_pio_task_sm0<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 0>, pin: impl PioPin) { + // Setup sm0 + + // Send data serially to pin + let prg = pio_proc::pio_asm!( + ".origin 16", + "set pindirs, 1", + ".wrap_target", + "out pins,1 [19]", + ".wrap", + ); + + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&prg.program), &[]); + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + cfg.clock_divider = (U56F8!(125_000_000) / 20 / 200).to_fixed(); + cfg.shift_out.auto_fill = true; + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm0(mut sm: StateMachine<'static, PIO0, 0>) { + sm.set_enable(true); + + let mut v = 0x0f0caffa; + loop { + sm.tx().wait_push(v).await; + v ^= 0xffff; + info!("Pushed {:032b} to FIFO", v); + } +} + +fn setup_pio_task_sm1<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 1>) { + // Setupm sm1 + + // Read 0b10101 repeatedly until ISR is full + let prg = pio_proc::pio_asm!( + // + ".origin 8", + "set x, 0x15", + ".wrap_target", + "in x, 5 [31]", + ".wrap", + ); + + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&prg.program), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed(); + cfg.shift_in.auto_fill = true; + cfg.shift_in.direction = ShiftDirection::Right; + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm1(mut sm: StateMachine<'static, PIO0, 1>) { + sm.set_enable(true); + loop { + let rx = sm.rx().wait_pull().await; + info!("Pulled {:032b} from FIFO", rx); + } +} + +fn setup_pio_task_sm2<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 2>) { + // Setup sm2 + + // Repeatedly trigger IRQ 3 + let prg = pio_proc::pio_asm!( + ".origin 0", + ".wrap_target", + "set x,10", + "delay:", + "jmp x-- delay [15]", + "irq 3 [15]", + ".wrap", + ); + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&prg.program), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed(); + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm2(mut irq: Irq<'static, PIO0, 3>, mut sm: StateMachine<'static, PIO0, 2>) { + sm.set_enable(true); + loop { + irq.wait().await; + info!("IRQ trigged"); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + + let Pio { + mut common, + irq3, + mut sm0, + mut sm1, + mut sm2, + .. + } = Pio::new(pio, Irqs); + + setup_pio_task_sm0(&mut common, &mut sm0, p.PIN_0); + setup_pio_task_sm1(&mut common, &mut sm1); + setup_pio_task_sm2(&mut common, &mut sm2); + spawner.spawn(pio_task_sm0(sm0)).unwrap(); + spawner.spawn(pio_task_sm1(sm1)).unwrap(); + spawner.spawn(pio_task_sm2(irq3, sm2)).unwrap(); +} diff --git a/embassy/examples/rp/src/bin/pio_dma.rs b/embassy/examples/rp/src/bin/pio_dma.rs new file mode 100644 index 0000000..0270026 --- /dev/null +++ b/embassy/examples/rp/src/bin/pio_dma.rs @@ -0,0 +1,83 @@ +//! This example shows powerful PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use defmt::info; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Config, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; +use embassy_rp::{bind_interrupts, Peripheral}; +use fixed::traits::ToFixed; +use fixed_macro::types::U56F8; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +fn swap_nibbles(v: u32) -> u32 { + let v = (v & 0x0f0f_0f0f) << 4 | (v & 0xf0f0_f0f0) >> 4; + let v = (v & 0x00ff_00ff) << 8 | (v & 0xff00_ff00) >> 8; + (v & 0x0000_ffff) << 16 | (v & 0xffff_0000) >> 16 +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + let Pio { + mut common, + sm0: mut sm, + .. + } = Pio::new(pio, Irqs); + + let prg = pio_proc::pio_asm!( + ".origin 0", + "set pindirs,1", + ".wrap_target", + "set y,7", + "loop:", + "out x,4", + "in x,4", + "jmp y--, loop", + ".wrap", + ); + + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&prg.program), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / U56F8!(10_000)).to_fixed(); + cfg.shift_in = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Left, + }; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Right, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + let mut dma_out_ref = p.DMA_CH0.into_ref(); + let mut dma_in_ref = p.DMA_CH1.into_ref(); + let mut dout = [0x12345678u32; 29]; + for i in 1..dout.len() { + dout[i] = (dout[i - 1] & 0x0fff_ffff) * 13 + 7; + } + let mut din = [0u32; 29]; + loop { + let (rx, tx) = sm.rx_tx(); + join( + tx.dma_push(dma_out_ref.reborrow(), &dout), + rx.dma_pull(dma_in_ref.reborrow(), &mut din), + ) + .await; + for i in 0..din.len() { + assert_eq!(din[i], swap_nibbles(dout[i])); + } + info!("Swapped {} words", dout.len()); + } +} diff --git a/embassy/examples/rp/src/bin/pio_hd44780.rs b/embassy/examples/rp/src/bin/pio_hd44780.rs new file mode 100644 index 0000000..164e6f8 --- /dev/null +++ b/embassy/examples/rp/src/bin/pio_hd44780.rs @@ -0,0 +1,87 @@ +//! This example shows powerful PIO module in the RP2040 chip to communicate with a HD44780 display. +//! See (https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +#![no_std] +#![no_main] + +use core::fmt::Write; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram}; +use embassy_rp::pwm::{self, Pwm}; +use embassy_time::{Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(pub struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // this test assumes a 2x16 HD44780 display attached as follow: + // rs = PIN0 + // rw = PIN1 + // e = PIN2 + // db4 = PIN3 + // db5 = PIN4 + // db6 = PIN5 + // db7 = PIN6 + // additionally a pwm signal for a bias voltage charge pump is provided on pin 15, + // allowing direct connection of the display to the RP2040 without level shifters. + let p = embassy_rp::init(Default::default()); + + let _pwm = Pwm::new_output_b(p.PWM_SLICE7, p.PIN_15, { + let mut c = pwm::Config::default(); + c.divider = 125.into(); + c.top = 100; + c.compare_b = 50; + c + }); + + let Pio { + mut common, sm0, irq0, .. + } = Pio::new(p.PIO0, Irqs); + + let word_prg = PioHD44780CommandWordProgram::new(&mut common); + let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common); + + let mut hd = PioHD44780::new( + &mut common, + sm0, + irq0, + p.DMA_CH3, + p.PIN_0, + p.PIN_1, + p.PIN_2, + p.PIN_3, + p.PIN_4, + p.PIN_5, + p.PIN_6, + &word_prg, + &seq_prg, + ) + .await; + + loop { + struct Buf([u8; N], usize); + impl Write for Buf { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + for b in s.as_bytes() { + if self.1 >= N { + return Err(core::fmt::Error); + } + self.0[self.1] = *b; + self.1 += 1; + } + Ok(()) + } + } + let mut buf = Buf([0; 16], 0); + write!(buf, "up {}s", Instant::now().as_micros() as f32 / 1e6).unwrap(); + hd.add_line(&buf.0[0..buf.1]).await; + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp/src/bin/pio_i2s.rs b/embassy/examples/rp/src/bin/pio_i2s.rs new file mode 100644 index 0000000..447100d --- /dev/null +++ b/embassy/examples/rp/src/bin/pio_i2s.rs @@ -0,0 +1,92 @@ +//! This example shows generating audio and sending it to a connected i2s DAC using the PIO +//! module of the RP2040. +//! +//! Connect the i2s DAC as follows: +//! bclk : GPIO 18 +//! lrc : GPIO 19 +//! din : GPIO 20 +//! Then hold down the boot select button to trigger a rising triangle waveform. + +#![no_std] +#![no_main] + +use core::mem; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const SAMPLE_RATE: u32 = 48_000; +const BIT_DEPTH: u32 = 16; +const CHANNELS: u32 = 2; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_rp::init(Default::default()); + + // Setup pio state machine for i2s output + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + let bit_clock_pin = p.PIN_18; + let left_right_clock_pin = p.PIN_19; + let data_pin = p.PIN_20; + + let program = PioI2sOutProgram::new(&mut common); + let mut i2s = PioI2sOut::new( + &mut common, + sm0, + p.DMA_CH0, + data_pin, + bit_clock_pin, + left_right_clock_pin, + SAMPLE_RATE, + BIT_DEPTH, + CHANNELS, + &program, + ); + + // create two audio buffers (back and front) which will take turns being + // filled with new audio data and being sent to the pio fifo using dma + const BUFFER_SIZE: usize = 960; + static DMA_BUFFER: StaticCell<[u32; BUFFER_SIZE * 2]> = StaticCell::new(); + let dma_buffer = DMA_BUFFER.init_with(|| [0u32; BUFFER_SIZE * 2]); + let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); + + // start pio state machine + let mut fade_value: i32 = 0; + let mut phase: i32 = 0; + + loop { + // trigger transfer of front buffer data to the pio fifo + // but don't await the returned future, yet + let dma_future = i2s.write(front_buffer); + + // fade in audio when bootsel is pressed + let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 }; + + // fill back buffer with fresh audio samples before awaiting the dma future + for s in back_buffer.iter_mut() { + // exponential approach of fade_value => fade_target + fade_value += (fade_target - fade_value) >> 14; + // generate triangle wave with amplitude and frequency based on fade value + phase = (phase + (fade_value >> 22)) & 0xffff; + let triangle_sample = (phase as i16 as i32).abs() - 16384; + let sample = (triangle_sample * (fade_value >> 15)) >> 16; + // duplicate mono sample into lower and upper half of dma word + *s = (sample as u16 as u32) * 0x10001; + } + + // now await the dma future. once the dma finishes, the next buffer needs to be queued + // within DMA_DEPTH / SAMPLE_RATE = 8 / 48000 seconds = 166us + dma_future.await; + mem::swap(&mut back_buffer, &mut front_buffer); + } +} diff --git a/embassy/examples/rp/src/bin/pio_onewire.rs b/embassy/examples/rp/src/bin/pio_onewire.rs new file mode 100644 index 0000000..9915108 --- /dev/null +++ b/embassy/examples/rp/src/bin/pio_onewire.rs @@ -0,0 +1,83 @@ +//! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor. + +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{self, InterruptHandler, Pio}; +use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut pio = Pio::new(p.PIO0, Irqs); + + let prg = PioOneWireProgram::new(&mut pio.common); + let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); + + let mut sensor = Ds18b20::new(onewire); + + loop { + sensor.start().await; // Start a new measurement + Timer::after_secs(1).await; // Allow 1s for the measurement to finish + match sensor.temperature().await { + Ok(temp) => info!("temp = {:?} deg C", temp), + _ => error!("sensor error"), + } + Timer::after_secs(1).await; + } +} + +/// DS18B20 temperature sensor driver +pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { + wire: PioOneWire<'d, PIO, SM>, +} + +impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { + pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self { + Self { wire } + } + + /// Calculate CRC8 of the data + fn crc8(data: &[u8]) -> u8 { + let mut temp; + let mut data_byte; + let mut crc = 0; + for b in data { + data_byte = *b; + for _ in 0..8 { + temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc + } + + /// Start a new measurement. Allow at least 1000ms before getting `temperature`. + pub async fn start(&mut self) { + self.wire.write_bytes(&[0xCC, 0x44]).await; + } + + /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. + pub async fn temperature(&mut self) -> Result { + self.wire.write_bytes(&[0xCC, 0xBE]).await; + let mut data = [0; 9]; + self.wire.read_bytes(&mut data).await; + match Self::crc8(&data) == 0 { + true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), + false => Err(()), + } + } +} diff --git a/embassy/examples/rp/src/bin/pio_pwm.rs b/embassy/examples/rp/src/bin/pio_pwm.rs new file mode 100644 index 0000000..7eabb22 --- /dev/null +++ b/embassy/examples/rp/src/bin/pio_pwm.rs @@ -0,0 +1,38 @@ +//! This example shows how to create a pwm using the PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use core::time::Duration; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const REFRESH_INTERVAL: u64 = 20000; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + // Note that PIN_25 is the led pin on the Pico + let prg = PioPwmProgram::new(&mut common); + let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg); + pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); + pwm_pio.start(); + + let mut duration = 0; + loop { + duration = (duration + 1) % 1000; + pwm_pio.write(Duration::from_micros(duration)); + Timer::after_millis(1).await; + } +} diff --git a/embassy/examples/rp/src/bin/pio_rotary_encoder.rs b/embassy/examples/rp/src/bin/pio_rotary_encoder.rs new file mode 100644 index 0000000..2750f61 --- /dev/null +++ b/embassy/examples/rp/src/bin/pio_rotary_encoder.rs @@ -0,0 +1,55 @@ +//! This example shows how to use the PIO module in the RP2040 to read a quadrature rotary encoder. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) { + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} + +#[embassy_executor::task] +async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) { + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, sm0, sm1, .. + } = Pio::new(p.PIO0, Irqs); + + let prg = PioEncoderProgram::new(&mut common); + let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg); + let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg); + + spawner.must_spawn(encoder_0(encoder0)); + spawner.must_spawn(encoder_1(encoder1)); +} diff --git a/embassy/examples/rp/src/bin/pio_servo.rs b/embassy/examples/rp/src/bin/pio_servo.rs new file mode 100644 index 0000000..c52ee74 --- /dev/null +++ b/embassy/examples/rp/src/bin/pio_servo.rs @@ -0,0 +1,128 @@ +//! This example shows how to create a pwm using the PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use core::time::Duration; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Instance, InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo +const DEFAULT_MAX_PULSE_WIDTH: u64 = 2000; // uncalibrated default, the longest duty cycle sent to a servo +const DEFAULT_MAX_DEGREE_ROTATION: u64 = 160; // 160 degrees is typical +const REFRESH_INTERVAL: u64 = 20000; // The period of each cycle + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +pub struct ServoBuilder<'d, T: Instance, const SM: usize> { + pwm: PioPwm<'d, T, SM>, + period: Duration, + min_pulse_width: Duration, + max_pulse_width: Duration, + max_degree_rotation: u64, +} + +impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { + pub fn new(pwm: PioPwm<'d, T, SM>) -> Self { + Self { + pwm, + period: Duration::from_micros(REFRESH_INTERVAL), + min_pulse_width: Duration::from_micros(DEFAULT_MIN_PULSE_WIDTH), + max_pulse_width: Duration::from_micros(DEFAULT_MAX_PULSE_WIDTH), + max_degree_rotation: DEFAULT_MAX_DEGREE_ROTATION, + } + } + + pub fn set_period(mut self, duration: Duration) -> Self { + self.period = duration; + self + } + + pub fn set_min_pulse_width(mut self, duration: Duration) -> Self { + self.min_pulse_width = duration; + self + } + + pub fn set_max_pulse_width(mut self, duration: Duration) -> Self { + self.max_pulse_width = duration; + self + } + + pub fn set_max_degree_rotation(mut self, degree: u64) -> Self { + self.max_degree_rotation = degree; + self + } + + pub fn build(mut self) -> Servo<'d, T, SM> { + self.pwm.set_period(self.period); + Servo { + pwm: self.pwm, + min_pulse_width: self.min_pulse_width, + max_pulse_width: self.max_pulse_width, + max_degree_rotation: self.max_degree_rotation, + } + } +} + +pub struct Servo<'d, T: Instance, const SM: usize> { + pwm: PioPwm<'d, T, SM>, + min_pulse_width: Duration, + max_pulse_width: Duration, + max_degree_rotation: u64, +} + +impl<'d, T: Instance, const SM: usize> Servo<'d, T, SM> { + pub fn start(&mut self) { + self.pwm.start(); + } + + pub fn stop(&mut self) { + self.pwm.stop(); + } + + pub fn write_time(&mut self, duration: Duration) { + self.pwm.write(duration); + } + + pub fn rotate(&mut self, degree: u64) { + let degree_per_nano_second = (self.max_pulse_width.as_nanos() as u64 - self.min_pulse_width.as_nanos() as u64) + / self.max_degree_rotation; + let mut duration = + Duration::from_nanos(degree * degree_per_nano_second + self.min_pulse_width.as_nanos() as u64); + if self.max_pulse_width < duration { + duration = self.max_pulse_width; + } + + self.pwm.write(duration); + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + let prg = PioPwmProgram::new(&mut common); + let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg); + let mut servo = ServoBuilder::new(pwm_pio) + .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo + .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment. + .set_max_pulse_width(Duration::from_micros(2600)) // Along with this value. + .build(); + + servo.start(); + + let mut degree = 0; + loop { + degree = (degree + 1) % 120; + servo.rotate(degree); + Timer::after_millis(50).await; + } +} diff --git a/embassy/examples/rp/src/bin/pio_stepper.rs b/embassy/examples/rp/src/bin/pio_stepper.rs new file mode 100644 index 0000000..3862c24 --- /dev/null +++ b/embassy/examples/rp/src/bin/pio_stepper.rs @@ -0,0 +1,49 @@ +//! This example shows how to use the PIO module in the RP2040 to implement a stepper motor driver +//! for a 5-wire stepper such as the 28BYJ-48. You can halt an ongoing rotation by dropping the future. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram}; +use embassy_time::{with_timeout, Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, irq0, sm0, .. + } = Pio::new(p.PIO0, Irqs); + + let prg = PioStepperProgram::new(&mut common); + let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg); + stepper.set_frequency(120); + loop { + info!("CW full steps"); + stepper.step(1000).await; + + info!("CCW full steps, drop after 1 sec"); + if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)) + .await + .is_err() + { + info!("Time's up!"); + Timer::after(Duration::from_secs(1)).await; + } + + info!("CW half steps"); + stepper.step_half(1000).await; + + info!("CCW half steps"); + stepper.step_half(-1000).await; + } +} diff --git a/embassy/examples/rp/src/bin/pio_uart.rs b/embassy/examples/rp/src/bin/pio_uart.rs new file mode 100644 index 0000000..aaf2a52 --- /dev/null +++ b/embassy/examples/rp/src/bin/pio_uart.rs @@ -0,0 +1,199 @@ +//! This example shows how to use the PIO module in the RP2040 chip to implement a duplex UART. +//! The PIO module is a very powerful peripheral that can be used to implement many different +//! protocols. It is a very flexible state machine that can be programmed to do almost anything. +//! +//! This example opens up a USB device that implements a CDC ACM serial port. It then uses the +//! PIO module to implement a UART that is connected to the USB serial port. This allows you to +//! communicate with a device connected to the RP2040 over USB serial. + +#![no_std] +#![no_main] +#![allow(async_fn_in_trait)] + +use defmt::{info, panic, trace}; +use embassy_executor::Spawner; +use embassy_futures::join::{join, join3}; +use embassy_rp::peripherals::{PIO0, USB}; +use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; +use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_rp::{bind_interrupts, pio}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pipe::Pipe; +use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use embedded_io_async::{Read, Write}; +use {defmt_rtt as _, panic_probe as _}; + +//use crate::uart::PioUart; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; + PIO0_IRQ_0 => pio::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("PIO UART example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // PIO UART setup + let pio::Pio { + mut common, sm0, sm1, .. + } = pio::Pio::new(p.PIO0, Irqs); + + let tx_program = PioUartTxProgram::new(&mut common); + let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program); + + let rx_program = PioUartRxProgram::new(&mut common); + let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program); + + // Pipe setup + let mut usb_pipe: Pipe = Pipe::new(); + let (mut usb_pipe_reader, mut usb_pipe_writer) = usb_pipe.split(); + + let mut uart_pipe: Pipe = Pipe::new(); + let (mut uart_pipe_reader, mut uart_pipe_writer) = uart_pipe.split(); + + let (mut usb_tx, mut usb_rx) = class.split(); + + // Read + write from USB + let usb_future = async { + loop { + info!("Wait for USB connection"); + usb_rx.wait_connection().await; + info!("Connected"); + let _ = join( + usb_read(&mut usb_rx, &mut uart_pipe_writer), + usb_write(&mut usb_tx, &mut usb_pipe_reader), + ) + .await; + info!("Disconnected"); + } + }; + + // Read + write from UART + let uart_future = join( + uart_read(&mut uart_rx, &mut usb_pipe_writer), + uart_write(&mut uart_tx, &mut uart_pipe_reader), + ); + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join3(usb_fut, usb_future, uart_future).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +/// Read from the USB and write it to the UART TX pipe +async fn usb_read<'d, T: Instance + 'd>( + usb_rx: &mut Receiver<'d, Driver<'d, T>>, + uart_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = usb_rx.read_packet(&mut buf).await?; + let data = &buf[..n]; + trace!("USB IN: {:x}", data); + (*uart_pipe_writer).write(data).await; + } +} + +/// Read from the USB TX pipe and write it to the USB +async fn usb_write<'d, T: Instance + 'd>( + usb_tx: &mut Sender<'d, Driver<'d, T>>, + usb_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = (*usb_pipe_reader).read(&mut buf).await; + let data = &buf[..n]; + trace!("USB OUT: {:x}", data); + usb_tx.write_packet(&data).await?; + } +} + +/// Read from the UART and write it to the USB TX pipe +async fn uart_read( + uart_rx: &mut PioUartRx<'_, PIO, SM>, + usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, +) -> ! { + let mut buf = [0; 64]; + loop { + let n = uart_rx.read(&mut buf).await.expect("UART read error"); + if n == 0 { + continue; + } + let data = &buf[..n]; + trace!("UART IN: {:x}", buf); + (*usb_pipe_writer).write(data).await; + } +} + +/// Read from the UART TX pipe and write it to the UART +async fn uart_write( + uart_tx: &mut PioUartTx<'_, PIO, SM>, + uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, +) -> ! { + let mut buf = [0; 64]; + loop { + let n = (*uart_pipe_reader).read(&mut buf).await; + let data = &buf[..n]; + trace!("UART OUT: {:x}", data); + let _ = uart_tx.write(&data).await; + } +} diff --git a/embassy/examples/rp/src/bin/pio_ws2812.rs b/embassy/examples/rp/src/bin/pio_ws2812.rs new file mode 100644 index 0000000..d1fcfc4 --- /dev/null +++ b/embassy/examples/rp/src/bin/pio_ws2812.rs @@ -0,0 +1,68 @@ +//! This example shows powerful PIO module in the RP2040 chip to communicate with WS2812 LED modules. +//! See (https://www.sparkfun.com/categories/tags/ws2812) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; +use embassy_time::{Duration, Ticker}; +use smart_leds::RGB8; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +/// Input a value 0 to 255 to get a color value +/// The colours are a transition r - g - b - back to r. +fn wheel(mut wheel_pos: u8) -> RGB8 { + wheel_pos = 255 - wheel_pos; + if wheel_pos < 85 { + return (255 - wheel_pos * 3, 0, wheel_pos * 3).into(); + } + if wheel_pos < 170 { + wheel_pos -= 85; + return (0, wheel_pos * 3, 255 - wheel_pos * 3).into(); + } + wheel_pos -= 170; + (wheel_pos * 3, 255 - wheel_pos * 3, 0).into() +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start"); + let p = embassy_rp::init(Default::default()); + + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + // This is the number of leds in the string. Helpfully, the sparkfun thing plus and adafruit + // feather boards for the 2040 both have one built in. + const NUM_LEDS: usize = 1; + let mut data = [RGB8::default(); NUM_LEDS]; + + // Common neopixel pins: + // Thing plus: 8 + // Adafruit Feather: 16; Adafruit Feather+RFM95: 4 + let program = PioWs2812Program::new(&mut common); + let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program); + + // Loop forever making RGB values and pushing them out to the WS2812. + let mut ticker = Ticker::every(Duration::from_millis(10)); + loop { + for j in 0..(256 * 5) { + debug!("New Colors:"); + for i in 0..NUM_LEDS { + data[i] = wheel((((i * 256) as u16 / NUM_LEDS as u16 + j as u16) & 255) as u8); + debug!("R: {} G: {} B: {}", data[i].r, data[i].g, data[i].b); + } + ws2812.write(&data).await; + + ticker.next().await; + } + } +} diff --git a/embassy/examples/rp/src/bin/pwm.rs b/embassy/examples/rp/src/bin/pwm.rs new file mode 100644 index 0000000..2f5f948 --- /dev/null +++ b/embassy/examples/rp/src/bin/pwm.rs @@ -0,0 +1,79 @@ +//! This example shows how to use PWM (Pulse Width Modulation) in the RP2040 chip. +//! +//! We demonstrate two ways of using PWM: +//! 1. Via config +//! 2. Via setting a duty cycle + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::peripherals::{PIN_25, PIN_4, PWM_SLICE2, PWM_SLICE4}; +use embassy_rp::pwm::{Config, Pwm, SetDutyCycle}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + spawner.spawn(pwm_set_config(p.PWM_SLICE4, p.PIN_25)).unwrap(); + spawner.spawn(pwm_set_dutycycle(p.PWM_SLICE2, p.PIN_4)).unwrap(); +} + +/// Demonstrate PWM by modifying & applying the config +/// +/// Using the onboard led, if You are using a different Board than plain Pico2 (i.e. W variant) +/// you must use another slice & pin and an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_config(slice4: PWM_SLICE4, pin25: PIN_25) { + let mut c = Config::default(); + c.top = 32_768; + c.compare_b = 8; + let mut pwm = Pwm::new_output_b(slice4, pin25, c.clone()); + + loop { + info!("current LED duty cycle: {}/32768", c.compare_b); + Timer::after_secs(1).await; + c.compare_b = c.compare_b.rotate_left(4); + pwm.set_config(&c); + } +} + +/// Demonstrate PWM by setting duty cycle +/// +/// Using GP4 in Slice2, make sure to use an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_dutycycle(slice2: PWM_SLICE2, pin4: PIN_4) { + // If we aim for a specific frequency, here is how we can calculate the top value. + // The top value sets the period of the PWM cycle, so a counter goes from 0 to top and then wraps around to 0. + // Every such wraparound is one PWM cycle. So here is how we get 25KHz: + let desired_freq_hz = 25_000; + let clock_freq_hz = embassy_rp::clocks::clk_sys_freq(); + let divider = 16u8; + let period = (clock_freq_hz / (desired_freq_hz * divider as u32)) as u16 - 1; + + let mut c = Config::default(); + c.top = period; + c.divider = divider.into(); + + let mut pwm = Pwm::new_output_a(slice2, pin4, c.clone()); + + loop { + // 100% duty cycle, fully on + pwm.set_duty_cycle_fully_on().unwrap(); + Timer::after_secs(1).await; + + // 66% duty cycle. Expressed as simple percentage. + pwm.set_duty_cycle_percent(66).unwrap(); + Timer::after_secs(1).await; + + // 25% duty cycle. Expressed as 32768/4 = 8192. + pwm.set_duty_cycle(c.top / 4).unwrap(); + Timer::after_secs(1).await; + + // 0% duty cycle, fully off. + pwm.set_duty_cycle_fully_off().unwrap(); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp/src/bin/pwm_input.rs b/embassy/examples/rp/src/bin/pwm_input.rs new file mode 100644 index 0000000..bf454a9 --- /dev/null +++ b/embassy/examples/rp/src/bin/pwm_input.rs @@ -0,0 +1,26 @@ +//! This example shows how to use the PWM module to measure the frequency of an input signal. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::gpio::Pull; +use embassy_rp::pwm::{Config, InputMode, Pwm}; +use embassy_time::{Duration, Ticker}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let cfg: Config = Default::default(); + let pwm = Pwm::new_input(p.PWM_SLICE2, p.PIN_5, Pull::None, InputMode::RisingEdge, cfg); + + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + info!("Input frequency: {} Hz", pwm.counter()); + pwm.set_counter(0); + ticker.next().await; + } +} diff --git a/embassy/examples/rp/src/bin/rosc.rs b/embassy/examples/rp/src/bin/rosc.rs new file mode 100644 index 0000000..942b723 --- /dev/null +++ b/embassy/examples/rp/src/bin/rosc.rs @@ -0,0 +1,31 @@ +//! This example test the RP Pico on board LED. +//! +//! It does not work with the RP Pico W board. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::{clocks, gpio}; +use embassy_time::Timer; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_rp::config::Config::default(); + config.clocks = clocks::ClockConfig::rosc(); + let p = embassy_rp::init(config); + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + info!("led on!"); + led.set_high(); + Timer::after_secs(1).await; + + info!("led off!"); + led.set_low(); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp/src/bin/rtc.rs b/embassy/examples/rp/src/bin/rtc.rs new file mode 100644 index 0000000..e9a5e43 --- /dev/null +++ b/embassy/examples/rp/src/bin/rtc.rs @@ -0,0 +1,45 @@ +//! This example shows how to use RTC (Real Time Clock) in the RP2040 chip. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::rtc::{DateTime, DayOfWeek, Rtc}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Wait for 20s"); + + let mut rtc = Rtc::new(p.RTC); + + if !rtc.is_running() { + info!("Start RTC"); + let now = DateTime { + year: 2000, + month: 1, + day: 1, + day_of_week: DayOfWeek::Saturday, + hour: 0, + minute: 0, + second: 0, + }; + rtc.set_datetime(now).unwrap(); + } + + Timer::after_millis(20000).await; + + if let Ok(dt) = rtc.now() { + info!( + "Now: {}-{:02}-{:02} {}:{:02}:{:02}", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, + ); + } + + info!("Reboot."); + Timer::after_millis(200).await; + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/embassy/examples/rp/src/bin/shared_bus.rs b/embassy/examples/rp/src/bin/shared_bus.rs new file mode 100644 index 0000000..c6cb5d6 --- /dev/null +++ b/embassy/examples/rp/src/bin/shared_bus.rs @@ -0,0 +1,115 @@ +//! This example shows how to share (async) I2C and SPI buses between multiple devices. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; +use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{AnyPin, Level, Output}; +use embassy_rp::i2c::{self, I2c, InterruptHandler}; +use embassy_rp::peripherals::{I2C1, SPI1}; +use embassy_rp::spi::{self, Spi}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +type Spi1Bus = Mutex>; +type I2c1Bus = Mutex>; + +bind_interrupts!(struct Irqs { + I2C1_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + // Shared I2C bus + let i2c = I2c::new_async(p.I2C1, p.PIN_15, p.PIN_14, Irqs, i2c::Config::default()); + static I2C_BUS: StaticCell = StaticCell::new(); + let i2c_bus = I2C_BUS.init(Mutex::new(i2c)); + + spawner.must_spawn(i2c_task_a(i2c_bus)); + spawner.must_spawn(i2c_task_b(i2c_bus)); + + // Shared SPI bus + let spi_cfg = spi::Config::default(); + let spi = Spi::new(p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, spi_cfg); + static SPI_BUS: StaticCell = StaticCell::new(); + let spi_bus = SPI_BUS.init(Mutex::new(spi)); + + // Chip select pins for the SPI devices + let cs_a = Output::new(AnyPin::from(p.PIN_0), Level::High); + let cs_b = Output::new(AnyPin::from(p.PIN_1), Level::High); + + spawner.must_spawn(spi_task_a(spi_bus, cs_a)); + spawner.must_spawn(spi_task_b(spi_bus, cs_b)); +} + +#[embassy_executor::task] +async fn i2c_task_a(i2c_bus: &'static I2c1Bus) { + let i2c_dev = I2cDevice::new(i2c_bus); + let _sensor = DummyI2cDeviceDriver::new(i2c_dev, 0xC0); + loop { + info!("i2c task A"); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn i2c_task_b(i2c_bus: &'static I2c1Bus) { + let i2c_dev = I2cDevice::new(i2c_bus); + let _sensor = DummyI2cDeviceDriver::new(i2c_dev, 0xDE); + loop { + info!("i2c task B"); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn spi_task_a(spi_bus: &'static Spi1Bus, cs: Output<'static>) { + let spi_dev = SpiDevice::new(spi_bus, cs); + let _sensor = DummySpiDeviceDriver::new(spi_dev); + loop { + info!("spi task A"); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn spi_task_b(spi_bus: &'static Spi1Bus, cs: Output<'static>) { + let spi_dev = SpiDevice::new(spi_bus, cs); + let _sensor = DummySpiDeviceDriver::new(spi_dev); + loop { + info!("spi task B"); + Timer::after_secs(1).await; + } +} + +// Dummy I2C device driver, using `embedded-hal-async` +struct DummyI2cDeviceDriver { + _i2c: I2C, +} + +impl DummyI2cDeviceDriver { + fn new(i2c_dev: I2C, _address: u8) -> Self { + Self { _i2c: i2c_dev } + } +} + +// Dummy SPI device driver, using `embedded-hal-async` +struct DummySpiDeviceDriver { + _spi: SPI, +} + +impl DummySpiDeviceDriver { + fn new(spi_dev: SPI) -> Self { + Self { _spi: spi_dev } + } +} diff --git a/embassy/examples/rp/src/bin/sharing.rs b/embassy/examples/rp/src/bin/sharing.rs new file mode 100644 index 0000000..5416e20 --- /dev/null +++ b/embassy/examples/rp/src/bin/sharing.rs @@ -0,0 +1,150 @@ +//! This example shows some common strategies for sharing resources between tasks. +//! +//! We demonstrate five different ways of sharing, covering different use cases: +//! - Atomics: This method is used for simple values, such as bool and u8..u32 +//! - Blocking Mutex: This is used for sharing non-async things, using Cell/RefCell for interior mutability. +//! - Async Mutex: This is used for sharing async resources, where you need to hold the lock across await points. +//! The async Mutex has interior mutability built-in, so no RefCell is needed. +//! - Cell: For sharing Copy types between tasks running on the same executor. +//! - RefCell: When you want &mut access to a value shared between tasks running on the same executor. +//! +//! More information: https://embassy.dev/book/#_sharing_peripherals_between_tasks + +#![no_std] +#![no_main] + +use core::cell::{Cell, RefCell}; +use core::sync::atomic::{AtomicU32, Ordering}; + +use cortex_m_rt::entry; +use defmt::info; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_rp::clocks::RoscRng; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{self, InterruptHandler, UartTx}; +use embassy_rp::{bind_interrupts, interrupt}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::{blocking_mutex, mutex}; +use embassy_time::{Duration, Ticker}; +use rand::RngCore; +use static_cell::{ConstStaticCell, StaticCell}; +use {defmt_rtt as _, panic_probe as _}; + +type UartAsyncMutex = mutex::Mutex>; + +struct MyType { + inner: u32, +} + +static EXECUTOR_HI: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +// Use Atomics for simple values +static ATOMIC: AtomicU32 = AtomicU32::new(0); + +// Use blocking Mutex with Cell/RefCell for sharing non-async things +static MUTEX_BLOCKING: blocking_mutex::Mutex> = + blocking_mutex::Mutex::new(RefCell::new(MyType { inner: 0 })); + +bind_interrupts!(struct Irqs { + UART0_IRQ => InterruptHandler; +}); + +#[interrupt] +unsafe fn SWI_IRQ_0() { + EXECUTOR_HI.on_interrupt() +} + +#[entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + let uart = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, uart::Config::default()); + // Use the async Mutex for sharing async things (built-in interior mutability) + static UART: StaticCell = StaticCell::new(); + let uart = UART.init(mutex::Mutex::new(uart)); + + // High-priority executor: runs in interrupt mode + interrupt::SWI_IRQ_0.set_priority(Priority::P3); + let spawner = EXECUTOR_HI.start(interrupt::SWI_IRQ_0); + spawner.must_spawn(task_a(uart)); + + // Low priority executor: runs in thread mode + let executor = EXECUTOR_LOW.init(Executor::new()); + executor.run(|spawner| { + // No Mutex needed when sharing between tasks running on the same executor + + // Use Cell for Copy-types + static CELL: ConstStaticCell> = ConstStaticCell::new(Cell::new([0; 4])); + let cell = CELL.take(); + + // Use RefCell for &mut access + static REF_CELL: ConstStaticCell> = ConstStaticCell::new(RefCell::new(MyType { inner: 0 })); + let ref_cell = REF_CELL.take(); + + spawner.must_spawn(task_b(uart, cell, ref_cell)); + spawner.must_spawn(task_c(cell, ref_cell)); + }); +} + +#[embassy_executor::task] +async fn task_a(uart: &'static UartAsyncMutex) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + let random = RoscRng.next_u32(); + + { + let mut uart = uart.lock().await; + uart.write(b"task a").await.unwrap(); + // The uart lock is released when it goes out of scope + } + + ATOMIC.store(random, Ordering::Relaxed); + + MUTEX_BLOCKING.lock(|x| x.borrow_mut().inner = random); + + ticker.next().await; + } +} + +#[embassy_executor::task] +async fn task_b(uart: &'static UartAsyncMutex, cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + let random = RoscRng.next_u32(); + + uart.lock().await.write(b"task b").await.unwrap(); + + cell.set(random.to_be_bytes()); + + ref_cell.borrow_mut().inner = random; + + ticker.next().await; + } +} + +#[embassy_executor::task] +async fn task_c(cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + info!("======================="); + + let atomic_val = ATOMIC.load(Ordering::Relaxed); + info!("atomic: {}", atomic_val); + + MUTEX_BLOCKING.lock(|x| { + let val = x.borrow().inner; + info!("blocking mutex: {}", val); + }); + + let cell_val = cell.get(); + info!("cell: {:?}", cell_val); + + let ref_cell_val = ref_cell.borrow().inner; + info!("ref_cell: {:?}", ref_cell_val); + + ticker.next().await; + } +} diff --git a/embassy/examples/rp/src/bin/spi.rs b/embassy/examples/rp/src/bin/spi.rs new file mode 100644 index 0000000..4cc4f52 --- /dev/null +++ b/embassy/examples/rp/src/bin/spi.rs @@ -0,0 +1,46 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! +//! Example for resistive touch sensor in Waveshare Pico-ResTouch + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::spi::Spi; +use embassy_rp::{gpio, spi}; +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!"); + + // Example for resistive touch sensor in Waveshare Pico-ResTouch + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let touch_cs = p.PIN_16; + + // create SPI + let mut config = spi::Config::default(); + config.frequency = 2_000_000; + let mut spi = Spi::new_blocking(p.SPI1, clk, mosi, miso, config); + + // Configure CS + let mut cs = Output::new(touch_cs, Level::Low); + + loop { + cs.set_low(); + let mut buf = [0x90, 0x00, 0x00, 0xd0, 0x00, 0x00]; + spi.blocking_transfer_in_place(&mut buf).unwrap(); + cs.set_high(); + + let x = (buf[1] as u32) << 5 | (buf[2] as u32) >> 3; + let y = (buf[4] as u32) << 5 | (buf[5] as u32) >> 3; + + info!("touch: {=u32} {=u32}", x, y); + } +} diff --git a/embassy/examples/rp/src/bin/spi_async.rs b/embassy/examples/rp/src/bin/spi_async.rs new file mode 100644 index 0000000..266584e --- /dev/null +++ b/embassy/examples/rp/src/bin/spi_async.rs @@ -0,0 +1,31 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! No specific hardware is specified in this example. If you connect pin 11 and 12 you should get the same data back. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::spi::{Config, Spi}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + + let mut spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + loop { + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + info!("{:?}", rx_buf); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp/src/bin/spi_display.rs b/embassy/examples/rp/src/bin/spi_display.rs new file mode 100644 index 0000000..dd114a4 --- /dev/null +++ b/embassy/examples/rp/src/bin/spi_display.rs @@ -0,0 +1,177 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! +//! Example written for a display using the ST7789 chip. Possibly the Waveshare Pico-ResTouch +//! (https://www.waveshare.com/wiki/Pico-ResTouch-LCD-2.8) + +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use defmt::*; +use display_interface_spi::SPIInterface; +use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::spi; +use embassy_rp::spi::Spi; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::Delay; +use embedded_graphics::image::{Image, ImageRawLE}; +use embedded_graphics::mono_font::ascii::FONT_10X20; +use embedded_graphics::mono_font::MonoTextStyle; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; +use embedded_graphics::text::Text; +use mipidsi::models::ST7789; +use mipidsi::options::{Orientation, Rotation}; +use mipidsi::Builder; +use {defmt_rtt as _, panic_probe as _}; + +use crate::touch::Touch; + +const DISPLAY_FREQ: u32 = 64_000_000; +const TOUCH_FREQ: u32 = 200_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let bl = p.PIN_13; + let rst = p.PIN_15; + let display_cs = p.PIN_9; + let dcx = p.PIN_8; + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let touch_cs = p.PIN_16; + //let touch_irq = p.PIN_17; + + // create SPI + let mut display_config = spi::Config::default(); + display_config.frequency = DISPLAY_FREQ; + display_config.phase = spi::Phase::CaptureOnSecondTransition; + display_config.polarity = spi::Polarity::IdleHigh; + let mut touch_config = spi::Config::default(); + touch_config.frequency = TOUCH_FREQ; + touch_config.phase = spi::Phase::CaptureOnSecondTransition; + touch_config.polarity = spi::Polarity::IdleHigh; + + let spi = Spi::new_blocking(p.SPI1, clk, mosi, miso, touch_config.clone()); + let spi_bus: Mutex = Mutex::new(RefCell::new(spi)); + + let display_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(display_cs, Level::High), display_config); + let touch_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(touch_cs, Level::High), touch_config); + + let mut touch = Touch::new(touch_spi); + + let dcx = Output::new(dcx, Level::Low); + let rst = Output::new(rst, Level::Low); + // dcx: 0 = command, 1 = data + + // Enable LCD backlight + let _bl = Output::new(bl, Level::High); + + // display interface abstraction from SPI and DC + let di = SPIInterface::new(display_spi, dcx); + + // Define the display from the display interface and initialize it + let mut display = Builder::new(ST7789, di) + .display_size(240, 320) + .reset_pin(rst) + .orientation(Orientation::new().rotate(Rotation::Deg90)) + .init(&mut Delay) + .unwrap(); + display.clear(Rgb565::BLACK).unwrap(); + + let raw_image_data = ImageRawLE::new(include_bytes!("../../assets/ferris.raw"), 86); + let ferris = Image::new(&raw_image_data, Point::new(34, 68)); + + // Display the image + ferris.draw(&mut display).unwrap(); + + let style = MonoTextStyle::new(&FONT_10X20, Rgb565::GREEN); + Text::new( + "Hello embedded_graphics \n + embassy + RP2040!", + Point::new(20, 200), + style, + ) + .draw(&mut display) + .unwrap(); + + loop { + if let Some((x, y)) = touch.read() { + let style = PrimitiveStyleBuilder::new().fill_color(Rgb565::BLUE).build(); + + Rectangle::new(Point::new(x - 1, y - 1), Size::new(3, 3)) + .into_styled(style) + .draw(&mut display) + .unwrap(); + } + } +} + +/// Driver for the XPT2046 resistive touchscreen sensor +mod touch { + use embedded_hal_1::spi::{Operation, SpiDevice}; + + struct Calibration { + x1: i32, + x2: i32, + y1: i32, + y2: i32, + sx: i32, + sy: i32, + } + + const CALIBRATION: Calibration = Calibration { + x1: 3880, + x2: 340, + y1: 262, + y2: 3850, + sx: 320, + sy: 240, + }; + + pub struct Touch { + spi: SPI, + } + + impl Touch + where + SPI: SpiDevice, + { + pub fn new(spi: SPI) -> Self { + Self { spi } + } + + pub fn read(&mut self) -> Option<(i32, i32)> { + let mut x = [0; 2]; + let mut y = [0; 2]; + self.spi + .transaction(&mut [ + Operation::Write(&[0x90]), + Operation::Read(&mut x), + Operation::Write(&[0xd0]), + Operation::Read(&mut y), + ]) + .unwrap(); + + let x = (u16::from_be_bytes(x) >> 3) as i32; + let y = (u16::from_be_bytes(y) >> 3) as i32; + + let cal = &CALIBRATION; + + let x = ((x - cal.x1) * cal.sx / (cal.x2 - cal.x1)).clamp(0, cal.sx); + let y = ((y - cal.y1) * cal.sy / (cal.y2 - cal.y1)).clamp(0, cal.sy); + if x == 0 && y == 0 { + None + } else { + Some((x, y)) + } + } + } +} diff --git a/embassy/examples/rp/src/bin/spi_gc9a01.rs b/embassy/examples/rp/src/bin/spi_gc9a01.rs new file mode 100644 index 0000000..30afc25 --- /dev/null +++ b/embassy/examples/rp/src/bin/spi_gc9a01.rs @@ -0,0 +1,126 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! +//! Example written for a display using the GC9A01 chip. Possibly the Waveshare RP2040-LCD-1.28 +//! (https://www.waveshare.com/wiki/RP2040-LCD-1.28) + +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use defmt::*; +use display_interface_spi::SPIInterface; +use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; +use embassy_executor::Spawner; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::spi; +use embassy_rp::spi::{Blocking, Spi}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::{Delay, Duration, Timer}; +use embedded_graphics::image::{Image, ImageRawLE}; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; +use mipidsi::models::GC9A01; +use mipidsi::options::{ColorInversion, ColorOrder}; +use mipidsi::Builder; +use rand_core::RngCore; +use {defmt_rtt as _, panic_probe as _}; + +const DISPLAY_FREQ: u32 = 64_000_000; +const LCD_X_RES: i32 = 240; +const LCD_Y_RES: i32 = 240; +const FERRIS_WIDTH: u32 = 86; +const FERRIS_HEIGHT: u32 = 64; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + info!("Hello World!"); + + let bl = p.PIN_25; + let rst = p.PIN_12; + let display_cs = p.PIN_9; + let dcx = p.PIN_8; + let mosi = p.PIN_11; + let clk = p.PIN_10; + + // create SPI + let mut display_config = spi::Config::default(); + display_config.frequency = DISPLAY_FREQ; + display_config.phase = spi::Phase::CaptureOnSecondTransition; + display_config.polarity = spi::Polarity::IdleHigh; + + let spi: Spi<'_, _, Blocking> = Spi::new_blocking_txonly(p.SPI1, clk, mosi, display_config.clone()); + let spi_bus: Mutex = Mutex::new(RefCell::new(spi)); + + let display_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(display_cs, Level::High), display_config); + let dcx = Output::new(dcx, Level::Low); + let rst = Output::new(rst, Level::Low); + // dcx: 0 = command, 1 = data + + // Enable LCD backlight + let _bl = Output::new(bl, Level::High); + + // display interface abstraction from SPI and DC + let di = SPIInterface::new(display_spi, dcx); + + // Define the display from the display interface and initialize it + let mut display = Builder::new(GC9A01, di) + .display_size(240, 240) + .reset_pin(rst) + .color_order(ColorOrder::Bgr) + .invert_colors(ColorInversion::Inverted) + .init(&mut Delay) + .unwrap(); + display.clear(Rgb565::BLACK).unwrap(); + + let raw_image_data = ImageRawLE::new(include_bytes!("../../assets/ferris.raw"), FERRIS_WIDTH); + let mut ferris = Image::new(&raw_image_data, Point::zero()); + + let r = rng.next_u32(); + let mut delta = Point { + x: ((r % 10) + 5) as i32, + y: (((r >> 8) % 10) + 5) as i32, + }; + loop { + // Move Ferris + let bb = ferris.bounding_box(); + let tl = bb.top_left; + let br = bb.bottom_right().unwrap(); + if tl.x < 0 || br.x > LCD_X_RES { + delta.x = -delta.x; + } + if tl.y < 0 || br.y > LCD_Y_RES { + delta.y = -delta.y; + } + + // Erase ghosting + let style = PrimitiveStyleBuilder::new().fill_color(Rgb565::BLACK).build(); + let mut off = Point { x: 0, y: 0 }; + if delta.x < 0 { + off.x = FERRIS_WIDTH as i32; + } + Rectangle::new(tl + off, Size::new(delta.x as u32, FERRIS_HEIGHT)) + .into_styled(style) + .draw(&mut display) + .unwrap(); + off = Point { x: 0, y: 0 }; + if delta.y < 0 { + off.y = FERRIS_HEIGHT as i32; + } + Rectangle::new(tl + off, Size::new(FERRIS_WIDTH, delta.y as u32)) + .into_styled(style) + .draw(&mut display) + .unwrap(); + // Translate Ferris + ferris.translate_mut(delta); + // Display the image + ferris.draw(&mut display).unwrap(); + Timer::after(Duration::from_millis(50)).await; + } +} diff --git a/embassy/examples/rp/src/bin/spi_sdmmc.rs b/embassy/examples/rp/src/bin/spi_sdmmc.rs new file mode 100644 index 0000000..a60850d --- /dev/null +++ b/embassy/examples/rp/src/bin/spi_sdmmc.rs @@ -0,0 +1,82 @@ +//! This example shows how to use `embedded-sdmmc` with the RP2040 chip, over SPI. +//! +//! The example will attempt to read a file `MY_FILE.TXT` from the root directory +//! of the SD card and print its contents. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::spi::Spi; +use embassy_rp::{gpio, spi}; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_sdmmc::sdcard::{DummyCsPin, SdCard}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +struct DummyTimesource(); + +impl embedded_sdmmc::TimeSource for DummyTimesource { + fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { + embedded_sdmmc::Timestamp { + year_since_1970: 0, + zero_indexed_month: 0, + zero_indexed_day: 0, + hours: 0, + minutes: 0, + seconds: 0, + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + embassy_rp::pac::SIO.spinlock(31).write_value(1); + let p = embassy_rp::init(Default::default()); + + // SPI clock needs to be running at <= 400kHz during initialization + let mut config = spi::Config::default(); + config.frequency = 400_000; + let spi = Spi::new_blocking(p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, config); + // Use a dummy cs pin here, for embedded-hal SpiDevice compatibility reasons + let spi_dev = ExclusiveDevice::new_no_delay(spi, DummyCsPin); + // Real cs pin + let cs = Output::new(p.PIN_16, Level::High); + + let sdcard = SdCard::new(spi_dev, cs, embassy_time::Delay); + info!("Card size is {} bytes", sdcard.num_bytes().unwrap()); + + // Now that the card is initialized, the SPI clock can go faster + let mut config = spi::Config::default(); + config.frequency = 16_000_000; + sdcard.spi(|dev| dev.bus_mut().set_config(&config)); + + // Now let's look for volumes (also known as partitions) on our block device. + // To do this we need a Volume Manager. It will take ownership of the block device. + let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, DummyTimesource()); + + // Try and access Volume 0 (i.e. the first partition). + // The volume object holds information about the filesystem on that volume. + let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0)).unwrap(); + info!("Volume 0: {:?}", defmt::Debug2Format(&volume0)); + + // Open the root directory (mutably borrows from the volume). + let mut root_dir = volume0.open_root_dir().unwrap(); + + // Open a file called "MY_FILE.TXT" in the root directory + // This mutably borrows the directory. + let mut my_file = root_dir + .open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly) + .unwrap(); + + // Print the contents of the file + while !my_file.is_eof() { + let mut buf = [0u8; 32]; + if let Ok(n) = my_file.read(&mut buf) { + info!("{:a}", buf[..n]); + } + } + + loop {} +} diff --git a/embassy/examples/rp/src/bin/uart.rs b/embassy/examples/rp/src/bin/uart.rs new file mode 100644 index 0000000..6a2816c --- /dev/null +++ b/embassy/examples/rp/src/bin/uart.rs @@ -0,0 +1,25 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! No specific hardware is specified in this example. Only output on pin 0 is tested. +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_rp::uart; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let config = uart::Config::default(); + let mut uart = uart::Uart::new_with_rtscts_blocking(p.UART0, p.PIN_0, p.PIN_1, p.PIN_3, p.PIN_2, config); + uart.blocking_write("Hello World!\r\n".as_bytes()).unwrap(); + + loop { + uart.blocking_write("hello there!\r\n".as_bytes()).unwrap(); + cortex_m::asm::delay(1_000_000); + } +} diff --git a/embassy/examples/rp/src/bin/uart_buffered_split.rs b/embassy/examples/rp/src/bin/uart_buffered_split.rs new file mode 100644 index 0000000..468d2b6 --- /dev/null +++ b/embassy/examples/rp/src/bin/uart_buffered_split.rs @@ -0,0 +1,58 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! No specific hardware is specified in this example. If you connect pin 0 and 1 you should get the same data back. +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config}; +use embassy_time::Timer; +use embedded_io_async::{Read, Write}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART0_IRQ => BufferedInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let (tx_pin, rx_pin, uart) = (p.PIN_0, p.PIN_1, p.UART0); + + static TX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); + let tx_buf = &mut TX_BUF.init([0; 16])[..]; + static RX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); + let rx_buf = &mut RX_BUF.init([0; 16])[..]; + let uart = BufferedUart::new(uart, Irqs, tx_pin, rx_pin, tx_buf, rx_buf, Config::default()); + let (mut tx, rx) = uart.split(); + + unwrap!(spawner.spawn(reader(rx))); + + info!("Writing..."); + loop { + let data = [ + 1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, + ]; + info!("TX {:?}", data); + tx.write_all(&data).await.unwrap(); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn reader(mut rx: BufferedUartRx<'static, UART0>) { + info!("Reading..."); + loop { + let mut buf = [0; 31]; + rx.read_exact(&mut buf).await.unwrap(); + info!("RX {:?}", buf); + } +} diff --git a/embassy/examples/rp/src/bin/uart_r503.rs b/embassy/examples/rp/src/bin/uart_r503.rs new file mode 100644 index 0000000..085be28 --- /dev/null +++ b/embassy/examples/rp/src/bin/uart_r503.rs @@ -0,0 +1,158 @@ +#![no_std] +#![no_main] + +use defmt::{debug, error, info}; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{Config, DataBits, InterruptHandler as UARTInterruptHandler, Parity, StopBits, Uart}; +use embassy_time::{with_timeout, Duration, Timer}; +use heapless::Vec; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(pub struct Irqs { + UART0_IRQ => UARTInterruptHandler; +}); + +const START: u16 = 0xEF01; +const ADDRESS: u32 = 0xFFFFFFFF; + +// ================================================================================ + +// Data package format +// Name Length Description +// ========================================================================================================== +// Start 2 bytes Fixed value of 0xEF01; High byte transferred first. +// Address 4 bytes Default value is 0xFFFFFFFF, which can be modified by command. +// High byte transferred first and at wrong adder value, module +// will reject to transfer. +// PID 1 byte 01H Command packet; +// 02H Data packet; Data packet shall not appear alone in executing +// processs, must follow command packet or acknowledge packet. +// 07H Acknowledge packet; +// 08H End of Data packet. +// LENGTH 2 bytes Refers to the length of package content (command packets and data packets) +// plus the length of Checksum (2 bytes). Unit is byte. Max length is 256 bytes. +// And high byte is transferred first. +// DATA - It can be commands, data, command’s parameters, acknowledge result, etc. +// (fingerprint character value, template are all deemed as data); +// SUM 2 bytes The arithmetic sum of package identifier, package length and all package +// contens. Overflowing bits are omitted. high byte is transferred first. + +// ================================================================================ + +// Checksum is calculated on 'length (2 bytes) + data (??)'. +fn compute_checksum(buf: Vec) -> u16 { + let mut checksum = 0u16; + + let check_end = buf.len(); + let checked_bytes = &buf[6..check_end]; + for byte in checked_bytes { + checksum += (*byte) as u16; + } + return checksum; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start"); + + let p = embassy_rp::init(Default::default()); + + // Initialize the fingerprint scanner. + let mut config = Config::default(); + config.baudrate = 57600; + config.stop_bits = StopBits::STOP1; + config.data_bits = DataBits::DataBits8; + config.parity = Parity::ParityNone; + + let (uart, tx_pin, tx_dma, rx_pin, rx_dma) = (p.UART0, p.PIN_16, p.DMA_CH0, p.PIN_17, p.DMA_CH1); + let uart = Uart::new(uart, tx_pin, rx_pin, Irqs, tx_dma, rx_dma, config); + let (mut tx, mut rx) = uart.split(); + + let mut vec_buf: Vec = heapless::Vec::new(); + let mut data: Vec = heapless::Vec::new(); + + let mut speeds: Vec = heapless::Vec::new(); + let _ = speeds.push(0xC8); // Slow + let _ = speeds.push(0x20); // Medium + let _ = speeds.push(0x02); // Fast + + // Cycle through the three colours Red, Blue and Purple forever. + loop { + for colour in 1..=3 { + for speed in &speeds { + // Set the data first, because the length is dependent on that. + // However, we write the length bits before we do the data. + data.clear(); + let _ = data.push(0x01); // ctrl=Breathing light + let _ = data.push(*speed); + let _ = data.push(colour as u8); // colour=Red, Blue, Purple + let _ = data.push(0x00); // times=Infinite + + // Clear buffers + vec_buf.clear(); + + // START + let _ = vec_buf.extend_from_slice(&START.to_be_bytes()[..]); + + // ADDRESS + let _ = vec_buf.extend_from_slice(&ADDRESS.to_be_bytes()[..]); + + // PID + let _ = vec_buf.extend_from_slice(&[0x01]); + + // LENGTH + let len: u16 = (1 + data.len() + 2).try_into().unwrap(); + let _ = vec_buf.extend_from_slice(&len.to_be_bytes()[..]); + + // COMMAND + let _ = vec_buf.push(0x35); // Command: AuraLedConfig + + // DATA + let _ = vec_buf.extend_from_slice(&data); + + // SUM + let chk = compute_checksum(vec_buf.clone()); + let _ = vec_buf.extend_from_slice(&chk.to_be_bytes()[..]); + + // ===== + + // Send command buffer. + let data_write: [u8; 16] = vec_buf.clone().into_array().unwrap(); + debug!(" write='{:?}'", data_write[..]); + match tx.write(&data_write).await { + Ok(..) => info!("Write successful."), + Err(e) => error!("Write error: {:?}", e), + } + + // ===== + + // Read command buffer. + let mut read_buf: [u8; 1] = [0; 1]; // Can only read one byte at a time! + let mut data_read: Vec = heapless::Vec::new(); // Save buffer. + + info!("Attempting read."); + loop { + // Some commands, like `Img2Tz()` needs longer, but we hard-code this to 200ms + // for this command. + match with_timeout(Duration::from_millis(200), rx.read(&mut read_buf)).await { + Ok(..) => { + // Extract and save read byte. + debug!(" r='{=u8:#04x}H' ({:03}D)", read_buf[0], read_buf[0]); + let _ = data_read.push(read_buf[0]).unwrap(); + } + Err(..) => break, // TimeoutError -> Ignore. + } + } + info!("Read successful"); + debug!(" read='{:?}'", data_read[..]); + + Timer::after_secs(3).await; + info!("Changing speed."); + } + + info!("Changing colour."); + } + } +} diff --git a/embassy/examples/rp/src/bin/uart_unidir.rs b/embassy/examples/rp/src/bin/uart_unidir.rs new file mode 100644 index 0000000..a45f407 --- /dev/null +++ b/embassy/examples/rp/src/bin/uart_unidir.rs @@ -0,0 +1,50 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! Test TX-only and RX-only on two different UARTs. You need to connect GPIO0 to GPIO5 for +//! this to work +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::UART1; +use embassy_rp::uart::{Async, Config, InterruptHandler, UartRx, UartTx}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART1_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let mut uart_tx = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, Config::default()); + let uart_rx = UartRx::new(p.UART1, p.PIN_5, Irqs, p.DMA_CH1, Config::default()); + + unwrap!(spawner.spawn(reader(uart_rx))); + + info!("Writing..."); + loop { + let data = [1u8, 2, 3, 4, 5, 6, 7, 8]; + info!("TX {:?}", data); + uart_tx.write(&data).await.unwrap(); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn reader(mut rx: UartRx<'static, UART1, Async>) { + info!("Reading..."); + loop { + // read a total of 4 transmissions (32 / 8) and then print the result + let mut buf = [0; 32]; + rx.read(&mut buf).await.unwrap(); + info!("RX {:?}", buf); + } +} diff --git a/embassy/examples/rp/src/bin/usb_ethernet.rs b/embassy/examples/rp/src/bin/usb_ethernet.rs new file mode 100644 index 0000000..9a15125 --- /dev/null +++ b/embassy/examples/rp/src/bin/usb_ethernet.rs @@ -0,0 +1,158 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This is a CDC-NCM class implementation, aka Ethernet over USB. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::StackResources; +use embassy_rp::clocks::RoscRng; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_rp::{bind_interrupts, 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_async::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +type MyDriver = Driver<'static, peripherals::USB>; + +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(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("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. + static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new(); + static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new(); + static CONTROL_BUF: StaticCell<[u8; 128]> = StaticCell::new(); + let mut builder = Builder::new( + driver, + config, + &mut CONFIG_DESC.init([0; 256])[..], + &mut BOS_DESC.init([0; 256])[..], + &mut [], // no msos descriptors + &mut CONTROL_BUF.init([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. + static STATE: StaticCell = StaticCell::new(); + let class = CdcNcmClass::new(&mut builder, STATE.init(State::new()), host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + static NET_STATE: StaticCell> = StaticCell::new(); + let (runner, device) = class.into_embassy_net_device::(NET_STATE.init(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + // 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_time::Duration::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/embassy/examples/rp/src/bin/usb_hid_keyboard.rs b/embassy/examples/rp/src/bin/usb_hid_keyboard.rs new file mode 100644 index 0000000..a7cb322 --- /dev/null +++ b/embassy/examples/rp/src/bin/usb_hid_keyboard.rs @@ -0,0 +1,188 @@ +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Input, Pull}; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; +use embassy_usb::control::OutResponse; +use embassy_usb::{Builder, Config, Handler}; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("HID keyboard example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + // You can also add a Microsoft OS descriptor. + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut request_handler = MyRequestHandler {}; + let mut device_handler = MyDeviceHandler::new(); + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + builder.handler(&mut device_handler); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 64, + }; + let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Set up the signal pin that will be used to trigger the keyboard. + let mut signal_pin = Input::new(p.PIN_16, Pull::None); + + // Enable the schmitt trigger to slightly debounce. + signal_pin.set_schmitt(true); + + let (reader, mut writer) = hid.split(); + + // Do stuff with the class! + let in_fut = async { + loop { + info!("Waiting for HIGH on pin 16"); + signal_pin.wait_for_high().await; + info!("HIGH DETECTED"); + // Create a report with the A key pressed. (no shift modifier) + let report = KeyboardReport { + keycodes: [4, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + // Send the report. + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + signal_pin.wait_for_low().await; + info!("LOW DETECTED"); + let report = KeyboardReport { + keycodes: [0, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + }; + + let out_fut = async { + reader.run(false, &mut request_handler).await; + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(in_fut, out_fut)).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} + +struct MyDeviceHandler { + configured: AtomicBool, +} + +impl MyDeviceHandler { + fn new() -> Self { + MyDeviceHandler { + configured: AtomicBool::new(false), + } + } +} + +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { + self.configured.store(false, Ordering::Relaxed); + if enabled { + info!("Device enabled"); + } else { + info!("Device disabled"); + } + } + + fn reset(&mut self) { + self.configured.store(false, Ordering::Relaxed); + info!("Bus reset, the Vbus current limit is 100mA"); + } + + fn addressed(&mut self, addr: u8) { + self.configured.store(false, Ordering::Relaxed); + info!("USB address set to: {}", addr); + } + + fn configured(&mut self, configured: bool) { + self.configured.store(configured, Ordering::Relaxed); + if configured { + info!("Device configured, it may now draw up to the configured current limit from Vbus.") + } else { + info!("Device is no longer configured, the Vbus current limit is 100mA."); + } + } +} diff --git a/embassy/examples/rp/src/bin/usb_hid_mouse.rs b/embassy/examples/rp/src/bin/usb_hid_mouse.rs new file mode 100644 index 0000000..5ee6509 --- /dev/null +++ b/embassy/examples/rp/src/bin/usb_hid_mouse.rs @@ -0,0 +1,173 @@ +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_time::Timer; +use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; +use embassy_usb::control::OutResponse; +use embassy_usb::{Builder, Config, Handler}; +use rand::Rng; +use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("HID keyboard example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + // You can also add a Microsoft OS descriptor. + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut request_handler = MyRequestHandler {}; + let mut device_handler = MyDeviceHandler::new(); + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + builder.handler(&mut device_handler); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: MouseReport::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 64, + }; + let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + let (reader, mut writer) = hid.split(); + + // Do stuff with the class! + let in_fut = async { + let mut rng = RoscRng; + + loop { + // every 1 second + _ = Timer::after_secs(1).await; + let report = MouseReport { + buttons: 0, + x: rng.gen_range(-100..100), // random small x movement + y: rng.gen_range(-100..100), // random small y movement + wheel: 0, + pan: 0, + }; + // Send the report. + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + } + } + }; + + let out_fut = async { + reader.run(false, &mut request_handler).await; + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(in_fut, out_fut)).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} + +struct MyDeviceHandler { + configured: AtomicBool, +} + +impl MyDeviceHandler { + fn new() -> Self { + MyDeviceHandler { + configured: AtomicBool::new(false), + } + } +} + +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { + self.configured.store(false, Ordering::Relaxed); + if enabled { + info!("Device enabled"); + } else { + info!("Device disabled"); + } + } + + fn reset(&mut self) { + self.configured.store(false, Ordering::Relaxed); + info!("Bus reset, the Vbus current limit is 100mA"); + } + + fn addressed(&mut self, addr: u8) { + self.configured.store(false, Ordering::Relaxed); + info!("USB address set to: {}", addr); + } + + fn configured(&mut self, configured: bool) { + self.configured.store(configured, Ordering::Relaxed); + if configured { + info!("Device configured, it may now draw up to the configured current limit from Vbus.") + } else { + info!("Device is no longer configured, the Vbus current limit is 100mA."); + } + } +} diff --git a/embassy/examples/rp/src/bin/usb_logger.rs b/embassy/examples/rp/src/bin/usb_logger.rs new file mode 100644 index 0000000..af401ed --- /dev/null +++ b/embassy/examples/rp/src/bin/usb_logger.rs @@ -0,0 +1,36 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This creates the possibility to send log::info/warn/error/debug! to USB serial port. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[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 driver = Driver::new(p.USB, Irqs); + spawner.spawn(logger_task(driver)).unwrap(); + + let mut counter = 0; + loop { + counter += 1; + log::info!("Tick {}", counter); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp/src/bin/usb_midi.rs b/embassy/examples/rp/src/bin/usb_midi.rs new file mode 100644 index 0000000..11db1b2 --- /dev/null +++ b/embassy/examples/rp/src/bin/usb_midi.rs @@ -0,0 +1,108 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This creates a USB MIDI device that echoes MIDI messages back to the host. + +#![no_std] +#![no_main] + +use defmt::{info, panic}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_usb::class::midi::MidiClass; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello world!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-MIDI example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = MidiClass::new(&mut builder, 1, 1, 64); + + // The `MidiClass` can be split into `Sender` and `Receiver`, to be used in separate tasks. + // let (sender, receiver) = class.split(); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Use the Midi class! + let midi_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = midi_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, midi_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 midi_echo<'d, T: Instance + 'd>(class: &mut MidiClass<'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/embassy/examples/rp/src/bin/usb_raw.rs b/embassy/examples/rp/src/bin/usb_raw.rs new file mode 100644 index 0000000..97e7e02 --- /dev/null +++ b/embassy/examples/rp/src/bin/usb_raw.rs @@ -0,0 +1,196 @@ +//! Example of using USB without a pre-defined class, but instead responding to +//! raw USB control requests. +//! +//! The host computer can either: +//! * send a command, with a 16-bit request ID, a 16-bit value, and an optional data buffer +//! * request some data, with a 16-bit request ID, a 16-bit value, and a length of data to receive +//! +//! For higher throughput data, you can add some bulk endpoints after creating the alternate, +//! but for low rate command/response, plain control transfers can be very simple and effective. +//! +//! Example code to send/receive data using `nusb`: +//! +//! ```ignore +//! use futures_lite::future::block_on; +//! use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient}; +//! +//! fn main() { +//! let di = nusb::list_devices() +//! .unwrap() +//! .find(|d| d.vendor_id() == 0xc0de && d.product_id() == 0xcafe) +//! .expect("no device found"); +//! let device = di.open().expect("error opening device"); +//! let interface = device.claim_interface(0).expect("error claiming interface"); +//! +//! // Send "hello world" to device +//! let result = block_on(interface.control_out(ControlOut { +//! control_type: ControlType::Vendor, +//! recipient: Recipient::Interface, +//! request: 100, +//! value: 200, +//! index: 0, +//! data: b"hello world", +//! })); +//! println!("{result:?}"); +//! +//! // Receive "hello" from device +//! let result = block_on(interface.control_in(ControlIn { +//! control_type: ControlType::Vendor, +//! recipient: Recipient::Interface, +//! request: 101, +//! value: 201, +//! index: 0, +//! length: 5, +//! })); +//! println!("{result:?}"); +//! } +//! ``` + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_usb::control::{InResponse, OutResponse, Recipient, Request, RequestType}; +use embassy_usb::msos::{self, windows_version}; +use embassy_usb::types::InterfaceNumber; +use embassy_usb::{Builder, Config, Handler}; +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] = &["{AFB9A6FB-30BA-44BC-9232-806CFC875321}"]; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB raw example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // // Required for windows compatibility. + // // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut handler = ControlHandler { + if_num: InterfaceNumber(0), + }; + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + // Add the Microsoft OS Descriptor (MSOS/MOD) descriptor. + // We tell Windows that this entire device is compatible with the "WINUSB" feature, + // which causes it to use the built-in WinUSB driver automatically, which in turn + // can be used by libusb/rusb software without needing a custom driver or INF file. + // In principle you might want to call msos_feature() just on a specific function, + // if your device also has other functions that still use standard class drivers. + builder.msos_descriptor(windows_version::WIN8_1, 0); + builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + // Add a vendor-specific function (class 0xFF), and corresponding interface, + // that uses our custom handler. + let mut function = builder.function(0xFF, 0, 0); + let mut interface = function.interface(); + let _alt = interface.alt_setting(0xFF, 0, 0, None); + handler.if_num = interface.interface_number(); + drop(function); + builder.handler(&mut handler); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + usb.run().await; +} + +/// Handle CONTROL endpoint requests and responses. For many simple requests and responses +/// you can get away with only using the control endpoint. +struct ControlHandler { + if_num: InterfaceNumber, +} + +impl Handler for ControlHandler { + /// Respond to HostToDevice control messages, where the host sends us a command and + /// optionally some data, and we can only acknowledge or reject it. + fn control_out<'a>(&'a mut self, req: Request, buf: &'a [u8]) -> Option { + // Log the request before filtering to help with debugging. + info!("Got control_out, request={}, buf={:a}", req, buf); + + // Only handle Vendor request types to an Interface. + if req.request_type != RequestType::Vendor || req.recipient != Recipient::Interface { + return None; + } + + // Ignore requests to other interfaces. + if req.index != self.if_num.0 as u16 { + return None; + } + + // Accept request 100, value 200, reject others. + if req.request == 100 && req.value == 200 { + Some(OutResponse::Accepted) + } else { + Some(OutResponse::Rejected) + } + } + + /// Respond to DeviceToHost control messages, where the host requests some data from us. + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + info!("Got control_in, request={}", req); + + // Only handle Vendor request types to an Interface. + if req.request_type != RequestType::Vendor || req.recipient != Recipient::Interface { + return None; + } + + // Ignore requests to other interfaces. + if req.index != self.if_num.0 as u16 { + return None; + } + + // Respond "hello" to request 101, value 201, when asked for 5 bytes, otherwise reject. + if req.request == 101 && req.value == 201 && req.length == 5 { + buf[..5].copy_from_slice(b"hello"); + Some(InResponse::Accepted(&buf[..5])) + } else { + Some(InResponse::Rejected) + } + } +} diff --git a/embassy/examples/rp/src/bin/usb_raw_bulk.rs b/embassy/examples/rp/src/bin/usb_raw_bulk.rs new file mode 100644 index 0000000..331c3da --- /dev/null +++ b/embassy/examples/rp/src/bin/usb_raw_bulk.rs @@ -0,0 +1,139 @@ +//! Example of using USB without a pre-defined class, but instead using raw USB bulk transfers. +//! +//! Example code to send/receive data using `nusb`: +//! +//! ```ignore +//! use futures_lite::future::block_on; +//! use nusb::transfer::RequestBuffer; +//! +//! const BULK_OUT_EP: u8 = 0x01; +//! const BULK_IN_EP: u8 = 0x81; +//! +//! fn main() { +//! let di = nusb::list_devices() +//! .unwrap() +//! .find(|d| d.vendor_id() == 0xc0de && d.product_id() == 0xcafe) +//! .expect("no device found"); +//! let device = di.open().expect("error opening device"); +//! let interface = device.claim_interface(0).expect("error claiming interface"); +//! +//! let result = block_on(interface.bulk_out(BULK_OUT_EP, b"hello world".into())); +//! println!("{result:?}"); +//! let result = block_on(interface.bulk_in(BULK_IN_EP, RequestBuffer::new(64))); +//! println!("{result:?}"); +//! } +//! ``` + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut}; +use embassy_usb::msos::{self, windows_version}; +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] = &["{AFB9A6FB-30BA-44BC-9232-806CFC875321}"]; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB raw example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // // Required for windows compatibility. + // // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + // Add the Microsoft OS Descriptor (MSOS/MOD) descriptor. + // We tell Windows that this entire device is compatible with the "WINUSB" feature, + // which causes it to use the built-in WinUSB driver automatically, which in turn + // can be used by libusb/rusb software without needing a custom driver or INF file. + // In principle you might want to call msos_feature() just on a specific function, + // if your device also has other functions that still use standard class drivers. + builder.msos_descriptor(windows_version::WIN8_1, 0); + builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + // Add a vendor-specific function (class 0xFF), and corresponding interface, + // that uses our custom handler. + let mut function = builder.function(0xFF, 0, 0); + let mut interface = function.interface(); + let mut alt = interface.alt_setting(0xFF, 0, 0, None); + let mut read_ep = alt.endpoint_bulk_out(64); + let mut write_ep = alt.endpoint_bulk_in(64); + drop(function); + + // 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 { + read_ep.wait_enabled().await; + info!("Connected"); + loop { + let mut data = [0; 64]; + match read_ep.read(&mut data).await { + Ok(n) => { + info!("Got bulk: {:a}", data[..n]); + // Echo back to the host: + write_ep.write(&data[..n]).await.ok(); + } + Err(_) => break, + } + } + 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; +} diff --git a/embassy/examples/rp/src/bin/usb_serial.rs b/embassy/examples/rp/src/bin/usb_serial.rs new file mode 100644 index 0000000..4a80299 --- /dev/null +++ b/embassy/examples/rp/src/bin/usb_serial.rs @@ -0,0 +1,117 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This creates a USB serial port that echos. + +#![no_std] +#![no_main] + +use defmt::{info, panic, unwrap}; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::UsbDevice; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let 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"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + config + }; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut builder = { + static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); + static BOS_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); + static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new(); + + let builder = embassy_usb::Builder::new( + driver, + config, + CONFIG_DESCRIPTOR.init([0; 256]), + BOS_DESCRIPTOR.init([0; 256]), + &mut [], // no msos descriptors + CONTROL_BUF.init([0; 64]), + ); + builder + }; + + // Create classes on the builder. + let mut class = { + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(State::new()); + CdcAcmClass::new(&mut builder, state, 64) + }; + + // Build the builder. + let usb = builder.build(); + + // Run the USB device. + unwrap!(spawner.spawn(usb_task(usb))); + + // Do stuff with the class! + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } +} + +type MyUsbDriver = Driver<'static, USB>; +type MyUsbDevice = UsbDevice<'static, MyUsbDriver>; + +#[embassy_executor::task] +async fn usb_task(mut usb: MyUsbDevice) -> ! { + usb.run().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/embassy/examples/rp/src/bin/usb_serial_with_handler.rs b/embassy/examples/rp/src/bin/usb_serial_with_handler.rs new file mode 100644 index 0000000..a9e65be --- /dev/null +++ b/embassy/examples/rp/src/bin/usb_serial_with_handler.rs @@ -0,0 +1,64 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This creates the possibility to send log::info/warn/error/debug! to USB serial port. + +#![no_std] +#![no_main] + +use core::str; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::rom_data::reset_to_usb_boot; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_time::Timer; +use embassy_usb_logger::ReceiverHandler; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +struct Handler; + +impl ReceiverHandler for Handler { + async fn handle_data(&self, data: &[u8]) { + if let Ok(data) = str::from_utf8(data) { + let data = data.trim(); + + // If you are using elf2uf2-term with the '-t' flag, then when closing the serial monitor, + // this will automatically put the pico into boot mode + if data == "q" || data == "elf2uf2-term" { + reset_to_usb_boot(0, 0); // Restart the chip + } else if data.eq_ignore_ascii_case("hello") { + log::info!("World!"); + } else { + log::info!("Recieved: {:?}", data); + } + } + } + + fn new() -> Self { + Self + } +} + +#[embassy_executor::task] +async fn logger_task(driver: Driver<'static, USB>) { + embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver, Handler); +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let driver = Driver::new(p.USB, Irqs); + spawner.spawn(logger_task(driver)).unwrap(); + + let mut counter = 0; + loop { + counter += 1; + log::info!("Tick {}", counter); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp/src/bin/usb_serial_with_logger.rs b/embassy/examples/rp/src/bin/usb_serial_with_logger.rs new file mode 100644 index 0000000..f9cfdef --- /dev/null +++ b/embassy/examples/rp/src/bin/usb_serial_with_logger.rs @@ -0,0 +1,115 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip as well as how to create multiple usb classes for one device +//! +//! This creates a USB serial port that echos. It will also print out logging information on a separate serial device + +#![no_std] +#![no_main] + +use defmt::{info, panic}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + let mut logger_state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Create a class for the logger + let logger_class = CdcAcmClass::new(&mut builder, &mut logger_state, 64); + + // Creates the logger and returns the logger future + // Note: You'll need to use log::info! afterwards instead of info! for this to work (this also applies to all the other log::* macros) + let log_fut = embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, logger_class); + + // 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; + log::info!("Connected"); + let _ = echo(&mut class).await; + log::info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(echo_fut, log_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/embassy/examples/rp/src/bin/usb_webusb.rs b/embassy/examples/rp/src/bin/usb_webusb.rs new file mode 100644 index 0000000..e73938a --- /dev/null +++ b/embassy/examples/rp/src/bin/usb_webusb.rs @@ -0,0 +1,155 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This creates a WebUSB capable device that echoes data back to the host. +//! +//! To test this in the browser (ideally host this on localhost:8080, to test the landing page +//! feature): +//! ```js +//! (async () => { +//! const device = await navigator.usb.requestDevice({ filters: [{ vendorId: 0xf569 }] }); +//! await device.open(); +//! await device.claimInterface(1); +//! device.transferIn(1, 64).then(data => console.log(data)); +//! await device.transferOut(1, new Uint8Array([1,2,3])); +//! })(); +//! ``` + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver as UsbDriver, InterruptHandler}; +use embassy_usb::class::web_usb::{Config as WebUsbConfig, State, Url, WebUsb}; +use embassy_usb::driver::{Driver, Endpoint, EndpointIn, EndpointOut}; +use embassy_usb::msos::{self, windows_version}; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +// This is a randomly generated GUID to allow clients on Windows to find our device +const DEVICE_INTERFACE_GUIDS: &[&str] = &["{AFB9A6FB-30BA-44BC-9232-806CFC875321}"]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = UsbDriver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xf569, 0x0001); + config.manufacturer = Some("Embassy"); + config.product = Some("WebUSB example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xff; + config.device_sub_class = 0x00; + config.device_protocol = 0x00; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut msos_descriptor = [0; 256]; + + let webusb_config = WebUsbConfig { + max_packet_size: 64, + vendor_code: 1, + // If defined, shows a landing page which the device manufacturer would like the user to visit in order to control their device. Suggest the user to navigate to this URL when the device is connected. + landing_url: Some(Url::new("http://localhost:8080")), + }; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + // Add the Microsoft OS Descriptor (MSOS/MOD) descriptor. + // We tell Windows that this entire device is compatible with the "WINUSB" feature, + // which causes it to use the built-in WinUSB driver automatically, which in turn + // can be used by libusb/rusb software without needing a custom driver or INF file. + // In principle you might want to call msos_feature() just on a specific function, + // if your device also has other functions that still use standard class drivers. + builder.msos_descriptor(windows_version::WIN8_1, 0); + builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + // Create classes on the builder (WebUSB just needs some setup, but doesn't return anything) + WebUsb::configure(&mut builder, &mut state, &webusb_config); + // Create some USB bulk endpoints for testing. + let mut endpoints = WebEndpoints::new(&mut builder, &webusb_config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do some WebUSB transfers. + let webusb_fut = async { + loop { + endpoints.wait_connected().await; + info!("Connected"); + endpoints.echo().await; + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, webusb_fut).await; +} + +struct WebEndpoints<'d, D: Driver<'d>> { + write_ep: D::EndpointIn, + read_ep: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> WebEndpoints<'d, D> { + fn new(builder: &mut Builder<'d, D>, config: &'d WebUsbConfig<'d>) -> Self { + let mut func = builder.function(0xff, 0x00, 0x00); + let mut iface = func.interface(); + let mut alt = iface.alt_setting(0xff, 0x00, 0x00, None); + + let write_ep = alt.endpoint_bulk_in(config.max_packet_size); + let read_ep = alt.endpoint_bulk_out(config.max_packet_size); + + WebEndpoints { write_ep, read_ep } + } + + // Wait until the device's endpoints are enabled. + async fn wait_connected(&mut self) { + self.read_ep.wait_enabled().await + } + + // Echo data back to the host. + async fn echo(&mut self) { + let mut buf = [0; 64]; + loop { + let n = self.read_ep.read(&mut buf).await.unwrap(); + let data = &buf[..n]; + info!("Data read: {:x}", data); + self.write_ep.write(data).await.unwrap(); + } + } +} diff --git a/embassy/examples/rp/src/bin/watchdog.rs b/embassy/examples/rp/src/bin/watchdog.rs new file mode 100644 index 0000000..b9d4ef2 --- /dev/null +++ b/embassy/examples/rp/src/bin/watchdog.rs @@ -0,0 +1,51 @@ +//! This example shows how to use Watchdog in the RP2040 chip. +//! +//! It does not work with the RP Pico W board. See wifi_blinky.rs or connect external LED and resistor. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::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_secs(2).await; + + // Set to watchdog to reset if it's not fed within 1.05 seconds, and start it + watchdog.start(Duration::from_millis(1_050)); + info!("Started the watchdog timer"); + + // Blink once a second for 5 seconds, feed the watchdog timer once a second to avoid a reset + for _ in 1..=5 { + led.set_low(); + Timer::after_millis(500).await; + led.set_high(); + Timer::after_millis(500).await; + info!("Feeding watchdog"); + watchdog.feed(); + } + + info!("Stopped feeding, device will reset in 1.05 seconds"); + // Blink 10 times per second, not feeding the watchdog. + // The processor should reset in 1.05 seconds. + loop { + led.set_low(); + Timer::after_millis(100).await; + led.set_high(); + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/rp/src/bin/wifi_ap_tcp_server.rs b/embassy/examples/rp/src/bin/wifi_ap_tcp_server.rs new file mode 100644 index 0000000..4c96514 --- /dev/null +++ b/embassy/examples/rp/src/bin/wifi_ap_tcp_server.rs @@ -0,0 +1,135 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Creates an Access point Wifi network and creates a TCP endpoint on port 1234. + +#![no_std] +#![no_main] +#![allow(async_fn_in_trait)] + +use core::str::from_utf8; + +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_time::Duration; +use embedded_io_async::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + // Use a link-local address for communication without DHCP server + let config = Config::ipv4_static(embassy_net::StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new(embassy_net::Ipv4Address::new(169, 254, 1, 1), 16), + dns_servers: heapless::Vec::new(), + gateway: None, + }); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + //control.start_ap_open("cyw43", 5).await; + control.start_ap_wpa2("cyw43", "password", 5).await; + + // 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(Duration::from_secs(10))); + + control.gpio_set(0, false).await; + 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()); + control.gpio_set(0, true).await; + + 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 {}", from_utf8(&buf[..n]).unwrap()); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/embassy/examples/rp/src/bin/wifi_blinky.rs b/embassy/examples/rp/src/bin/wifi_blinky.rs new file mode 100644 index 0000000..0107a23 --- /dev/null +++ b/embassy/examples/rp/src/bin/wifi_blinky.rs @@ -0,0 +1,66 @@ +//! This example test the RP Pico W on board LED. +//! +//! It does not work with the RP Pico board. See blinky.rs. + +#![no_std] +#![no_main] + +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download ../../cyw43-firmware/43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download ../../cyw43-firmware/43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let delay = Duration::from_secs(1); + loop { + info!("led on!"); + control.gpio_set(0, true).await; + Timer::after(delay).await; + + info!("led off!"); + control.gpio_set(0, false).await; + Timer::after(delay).await; + } +} diff --git a/embassy/examples/rp/src/bin/wifi_scan.rs b/embassy/examples/rp/src/bin/wifi_scan.rs new file mode 100644 index 0000000..2ef8990 --- /dev/null +++ b/embassy/examples/rp/src/bin/wifi_scan.rs @@ -0,0 +1,66 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Scans Wifi for ssid names. + +#![no_std] +#![no_main] +#![allow(async_fn_in_trait)] + +use core::str; + +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_rp::init(Default::default()); + + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let mut scanner = control.scan(Default::default()).await; + while let Some(bss) = scanner.next().await { + if let Ok(ssid_str) = str::from_utf8(&bss.ssid) { + info!("scanned {} == {:x}", ssid_str, bss.bssid); + } + } +} diff --git a/embassy/examples/rp/src/bin/wifi_tcp_server.rs b/embassy/examples/rp/src/bin/wifi_tcp_server.rs new file mode 100644 index 0000000..7bf546e --- /dev/null +++ b/embassy/examples/rp/src/bin/wifi_tcp_server.rs @@ -0,0 +1,155 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Connects to specified Wifi network and creates a TCP endpoint on port 1234. + +#![no_std] +#![no_main] +#![allow(async_fn_in_trait)] + +use core::str::from_utf8; + +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_time::{Duration, Timer}; +use embedded_io_async::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "LadronDeWifi"; +const WIFI_PASSWORD: &str = "MBfcaedHmyRFE4kaQ1O5SsY8"; + +#[embassy_executor::task] +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + // 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(Duration::from_secs(10))); + + control.gpio_set(0, false).await; + 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()); + control.gpio_set(0, true).await; + + 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 {}", from_utf8(&buf[..n]).unwrap()); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/embassy/examples/rp/src/bin/wifi_webrequest.rs b/embassy/examples/rp/src/bin/wifi_webrequest.rs new file mode 100644 index 0000000..1ae9099 --- /dev/null +++ b/embassy/examples/rp/src/bin/wifi_webrequest.rs @@ -0,0 +1,190 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Connects to Wifi network and makes a web request to get the current time. + +#![no_std] +#![no_main] +#![allow(async_fn_in_trait)] + +use core::str::from_utf8; + +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::dns::DnsSocket; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_time::{Duration, Timer}; +use rand::RngCore; +use reqwless::client::{HttpClient, TlsConfig, TlsVerify}; +use reqwless::request::Method; +use serde::Deserialize; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _, serde_json_core}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "ssid"; // change to your network SSID +const WIFI_PASSWORD: &str = "pwd"; // change to your network password + +#[embassy_executor::task] +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 + // let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; + // let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + // And now we can use it! + + loop { + let mut rx_buffer = [0; 8192]; + let mut tls_read_buffer = [0; 16640]; + let mut tls_write_buffer = [0; 16640]; + + let client_state = TcpClientState::<1, 1024, 1024>::new(); + let tcp_client = TcpClient::new(stack, &client_state); + let dns_client = DnsSocket::new(stack); + let tls_config = TlsConfig::new(seed, &mut tls_read_buffer, &mut tls_write_buffer, TlsVerify::None); + + let mut http_client = HttpClient::new_with_tls(&tcp_client, &dns_client, tls_config); + let url = "https://worldtimeapi.org/api/timezone/Europe/Berlin"; + // for non-TLS requests, use this instead: + // let mut http_client = HttpClient::new(&tcp_client, &dns_client); + // let url = "http://worldtimeapi.org/api/timezone/Europe/Berlin"; + + info!("connecting to {}", &url); + + let mut request = match http_client.request(Method::GET, &url).await { + Ok(req) => req, + Err(e) => { + error!("Failed to make HTTP request: {:?}", e); + return; // handle the error + } + }; + + let response = match request.send(&mut rx_buffer).await { + Ok(resp) => resp, + Err(_e) => { + error!("Failed to send HTTP request"); + return; // handle the error; + } + }; + + let body = match from_utf8(response.body().read_to_end().await.unwrap()) { + Ok(b) => b, + Err(_e) => { + error!("Failed to read response body"); + return; // handle the error + } + }; + info!("Response body: {:?}", &body); + + // parse the response body and update the RTC + + #[derive(Deserialize)] + struct ApiResponse<'a> { + datetime: &'a str, + // other fields as needed + } + + let bytes = body.as_bytes(); + match serde_json_core::de::from_slice::(bytes) { + Ok((output, _used)) => { + info!("Datetime: {:?}", output.datetime); + } + Err(_e) => { + error!("Failed to parse response body"); + return; // handle the error + } + } + + Timer::after(Duration::from_secs(5)).await; + } +} diff --git a/embassy/examples/rp/src/bin/zerocopy.rs b/embassy/examples/rp/src/bin/zerocopy.rs new file mode 100644 index 0000000..39f03c8 --- /dev/null +++ b/embassy/examples/rp/src/bin/zerocopy.rs @@ -0,0 +1,94 @@ +//! This example shows how to use `zerocopy_channel` from `embassy_sync` for +//! sending large values between two tasks without copying. +//! The example also shows how to use the RP2040 ADC with DMA. +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU16, Ordering}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{self, Adc, Async, Config, InterruptHandler}; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::Pull; +use embassy_rp::peripherals::DMA_CH0; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::zerocopy_channel::{Channel, Receiver, Sender}; +use embassy_time::{Duration, Ticker, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +type SampleBuffer = [u16; 512]; + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +const BLOCK_SIZE: usize = 512; +const NUM_BLOCKS: usize = 2; +static MAX: AtomicU16 = AtomicU16::new(0); + +struct AdcParts { + adc: Adc<'static, Async>, + pin: adc::Channel<'static>, + dma: DMA_CH0, +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + let adc_parts = AdcParts { + adc: Adc::new(p.ADC, Irqs, Config::default()), + pin: adc::Channel::new_pin(p.PIN_29, Pull::None), + dma: p.DMA_CH0, + }; + + static BUF: StaticCell<[SampleBuffer; NUM_BLOCKS]> = StaticCell::new(); + let buf = BUF.init([[0; BLOCK_SIZE]; NUM_BLOCKS]); + + static CHANNEL: StaticCell> = StaticCell::new(); + let channel = CHANNEL.init(Channel::new(buf)); + let (sender, receiver) = channel.split(); + + spawner.must_spawn(consumer(receiver)); + spawner.must_spawn(producer(sender, adc_parts)); + + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + ticker.next().await; + let max = MAX.load(Ordering::Relaxed); + info!("latest block's max value: {:?}", max); + } +} + +#[embassy_executor::task] +async fn producer(mut sender: Sender<'static, NoopRawMutex, SampleBuffer>, mut adc: AdcParts) { + loop { + // Obtain a free buffer from the channel + let buf = sender.send().await; + + // Fill it with data + adc.adc.read_many(&mut adc.pin, buf, 1, &mut adc.dma).await.unwrap(); + + // Notify the channel that the buffer is now ready to be received + sender.send_done(); + } +} + +#[embassy_executor::task] +async fn consumer(mut receiver: Receiver<'static, NoopRawMutex, SampleBuffer>) { + loop { + // Receive a buffer from the channel + let buf = receiver.receive().await; + + // Simulate using the data, while the producer is filling up the next buffer + Timer::after_micros(1000).await; + let max = buf.iter().max().unwrap(); + MAX.store(*max, Ordering::Relaxed); + + // Notify the channel that the buffer is now ready to be reused + receiver.receive_done(); + } +} diff --git a/embassy/examples/rp23/.cargo/config.toml b/embassy/examples/rp23/.cargo/config.toml new file mode 100644 index 0000000..9a92b1c --- /dev/null +++ b/embassy/examples/rp23/.cargo/config.toml @@ -0,0 +1,10 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +#runner = "probe-rs run --chip RP2040" +#runner = "elf2uf2-rs -d" +runner = "picotool load -u -v -x -t elf" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "debug" diff --git a/embassy/examples/rp23/Cargo.toml b/embassy/examples/rp23/Cargo.toml new file mode 100644 index 0000000..72eef22 --- /dev/null +++ b/embassy/examples/rp23/Cargo.toml @@ -0,0 +1,80 @@ +[package] +edition = "2021" +name = "embassy-rp2350-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns"] } +embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-usb-logger = { version = "0.2.0", path = "../../embassy-usb-logger" } +cyw43 = { version = "0.2.0", path = "../../cyw43", features = ["defmt", "firmware-logs", "bluetooth"] } +cyw43-pio = { version = "0.2.0", path = "../../cyw43-pio", features = ["defmt"] } + +defmt = "0.3" +defmt-rtt = "0.4" +fixed = "1.23.1" +fixed-macro = "1.2" + +serde = { version = "1.0.203", default-features = false, features = ["derive"] } +serde-json-core = "0.5.1" + +# for assign resources example +assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } + +# for TB6612FNG example +tb6612fng = "1.0.0" + +#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm"] } +cortex-m-rt = "0.7.0" +critical-section = "1.1" +panic-probe = { version = "0.3", features = ["print-defmt"] } +display-interface-spi = "0.5.0" +embedded-graphics = "0.8.1" +mipidsi = "0.8.0" +display-interface = "0.5.0" +byte-slice-cast = { version = "1.2.0", default-features = false } +smart-leds = "0.3.0" +heapless = "0.8" +usbd-hid = "0.8.1" + +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0" +embedded-hal-bus = { version = "0.1", features = ["async"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-storage = { version = "0.3" } +static_cell = "2.1" +portable-atomic = { version = "1.5", features = ["critical-section"] } +log = "0.4" +pio-proc = "0.2" +pio = "0.2.1" +rand = { version = "0.8.5", default-features = false } +embedded-sdmmc = "0.7.0" + +bt-hci = { version = "0.1.0", default-features = false, features = ["defmt"] } +trouble-host = { version = "0.1.0", features = ["defmt", "gatt"] } + +[profile.release] +debug = 2 + +[profile.dev] +lto = true +opt-level = "z" + +[patch.crates-io] +trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" } +embassy-executor = { path = "../../embassy-executor" } +embassy-sync = { path = "../../embassy-sync" } +embassy-futures = { path = "../../embassy-futures" } +embassy-time = { path = "../../embassy-time" } +embassy-time-driver = { path = "../../embassy-time-driver" } +embassy-embedded-hal = { path = "../../embassy-embedded-hal" } diff --git a/embassy/examples/rp23/assets/ferris.raw b/embassy/examples/rp23/assets/ferris.raw new file mode 100644 index 0000000000000000000000000000000000000000..9733889c577bdcc9277858ce019abff5b7dd23d0 GIT binary patch literal 11008 zcmd5?F^ua(5VexV#V@WS1%*kP(1k9)^CeA06$+&At2oh@0@XE0K{Vfvgzh8?f}lvb zaTTFoMCc>A0LerGojwRfh{6R({wd%DqNSjK9Zxp1YdbzW$#K|Hylc$27uoSiXeOk>`2)ETl94!X5 z7Q>oZcLS)K_4`3-o24s~T)IN69FL)C0^2HN9bPjKDaXE$Ic$?^cUdyd0GqoOdUSk& z)m^jRz!NpcXI*DkTWzhvYD}(y;w}+V+oZYh0`w@b-S)J(p2N{9vWWl6CTvs4TZIEp zjPY$qVZOB%L?z8dQo~6~tV8R|HSw}(AjjO>R-?O3dK=P)$+xlX57w1NDRi&LI%rLY>sr5EE+juPZOcMX}yJB|mkDykmG+wG!3h1HTYe>d!M?e7lGC6 z*CPx~h==!G##uBQ12?+&w8cHc-QHX25Ibgg1deqZUEVF4{H^cPD!jixPnCv0%w84v z9`yfmyFr${f@Z6`o!)(89(aH!>$KBeG2&lFFpG5`z_}1?1`ZJ zzdYT*@mtKNoQ}5jtBFy_hZy}K?`K9&cQgmekZnQU4d+9MR8IK)kI#O-dPmdEiq%ZF zpLzJvAD{m8_@Sm-jp}rDQ>1;Q75OHC_^|Q+?)QJ9>x0*@9m*FGqCY=JnpFj8{1JW0 zS8~^s$3rQH&HtDFL>zB?h^=VaV5uOu3CESMkmkV36t9Zas@`UY{l z`&3|6_ht~||NRTn{O~@~!0ssjE$8fnjPL?;V)H*Oy51 z{1MWal_ECl1_MuCzYM*&PI;ehH2uL4aeVtwVAXWTr~bl<{`?Wpy!@-P%N`e>8<4ld ztEdGb7Tm+V{ra0ve>)c~@uz~-AHkQ$uYUH3kaC;$s5f~_nC(dSG`F{rzTY$ytm*sb zL$LB#R;SGK?dW0@H&$KJ=eB56X!lH+c$Kb*y^_pu9FD10w3K#Y!D6{Dd0rsmLo*|- z#X;k-^Er^&mMDtjgp%`EIYqZ>m$$oNcE0y}`1NwxaV@ zIM*6GS+|$vS-el~+KEmYWGwCF=CU@MB>EuJsO7=ZAC;~$ddT;hTmu=g4YlX4YqQNI z-1H7h125?X+yH6I)AnK_`N}x2Qa>OfQTQXPBAi@XEdIU4(sDsHhG;jJ_EfOaR>A|sbvyIQkX zDuS;z6DZt(=b6lTe>L7b**qyv%QKF2Ib60ET>3{q5ASMGAD|yHuIS4m!pf1(M^5LQ zdopRZ@HNzG2$@`lG8=?e!BfXiTKHP8pVyIdHFkW#>8>#v(e!F;)`3?l&*uHFNBq8k z-}z|XpZA=u)2T64?~V^=&&HYTgCKI4_fVqKQSrnx1@Q06y8nrpOg>(GZ03D6SCy83QFk90Az3DH8)x}RC72juAcQ zf!J1$kVgA!XE&1ja`G~Tv(3;r-Oc5>0Xoj>ulQbW8v*3&AY!`+ON%XiwnDdZz8Cc@AOcvBcOCKD8vW4f211 zh$8MU(vc*+U^-p8UQ6#Gb$U+-3zQHocz*Rg)KveKjsX76lBZ8x8JU zd?+d1uH*ioWE&N$VRu;1ABE$RUAltRlJy*a;0>0oMT=FZrK|9Q_3V!7m${Emo#U`Z zq+1{%luNMsrH?`KKO}ii)KOnF7U{a+dgXfp+HJ_F_LWT|eS|b!T{Ylz`L;O#GU9!t zf4syI*qyw+k8`Hg9cf(wJtf!HI2VtT1JbDa-?BLEY3pj-0T|*tT2)>=qR*yQc~4P` wR$bCnkn#T-omP-|F6|UZ^0i?#!;9#oVeR<*+&^_zgQd0@_=A$oddFDezjq(d^8f$< literal 0 HcmV?d00001 diff --git a/embassy/examples/rp23/build.rs b/embassy/examples/rp23/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/rp23/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/embassy/examples/rp23/memory.x b/embassy/examples/rp23/memory.x new file mode 100644 index 0000000..c803896 --- /dev/null +++ b/embassy/examples/rp23/memory.x @@ -0,0 +1,75 @@ +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.start_block) + SIZEOF(.start_block); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; + +SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + +} INSERT AFTER .uninit; + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); diff --git a/embassy/examples/rp23/src/bin/adc.rs b/embassy/examples/rp23/src/bin/adc.rs new file mode 100644 index 0000000..f7db965 --- /dev/null +++ b/embassy/examples/rp23/src/bin/adc.rs @@ -0,0 +1,53 @@ +//! This example test the ADC (Analog to Digital Conversion) of the RP2350A pins 26, 27 and 28. +//! It also reads the temperature sensor in the chip. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Pull; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut adc = Adc::new(p.ADC, Irqs, Config::default()); + + let mut p26 = Channel::new_pin(p.PIN_26, Pull::None); + let mut p27 = Channel::new_pin(p.PIN_27, Pull::None); + let mut p28 = Channel::new_pin(p.PIN_28, Pull::None); + let mut ts = Channel::new_temp_sensor(p.ADC_TEMP_SENSOR); + + loop { + let level = adc.read(&mut p26).await.unwrap(); + info!("Pin 26 ADC: {}", level); + let level = adc.read(&mut p27).await.unwrap(); + info!("Pin 27 ADC: {}", level); + let level = adc.read(&mut p28).await.unwrap(); + info!("Pin 28 ADC: {}", level); + let temp = adc.read(&mut ts).await.unwrap(); + info!("Temp: {} degrees", convert_to_celsius(temp)); + Timer::after_secs(1).await; + } +} + +fn convert_to_celsius(raw_temp: u16) -> f32 { + // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet + let temp = 27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721; + let sign = if temp < 0.0 { -1.0 } else { 1.0 }; + let rounded_temp_x10: i16 = ((temp * 10.0) + 0.5 * sign) as i16; + (rounded_temp_x10 as f32) / 10.0 +} diff --git a/embassy/examples/rp23/src/bin/adc_dma.rs b/embassy/examples/rp23/src/bin/adc_dma.rs new file mode 100644 index 0000000..a6814c2 --- /dev/null +++ b/embassy/examples/rp23/src/bin/adc_dma.rs @@ -0,0 +1,59 @@ +//! This example shows how to use the RP2040 ADC with DMA, both single- and multichannel reads. +//! For multichannel, the samples are interleaved in the buffer: +//! `[ch1, ch2, ch3, ch4, ch1, ch2, ch3, ch4, ...]` +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Pull; +use embassy_time::{Duration, Ticker}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + let mut adc = Adc::new(p.ADC, Irqs, Config::default()); + let mut dma = p.DMA_CH0; + let mut pin = Channel::new_pin(p.PIN_26, Pull::Up); + let mut pins = [ + Channel::new_pin(p.PIN_27, Pull::Down), + Channel::new_pin(p.PIN_28, Pull::None), + Channel::new_pin(p.PIN_29, Pull::Up), + Channel::new_temp_sensor(p.ADC_TEMP_SENSOR), + ]; + + const BLOCK_SIZE: usize = 100; + const NUM_CHANNELS: usize = 4; + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + // Read 100 samples from a single channel + let mut buf = [0_u16; BLOCK_SIZE]; + let div = 479; // 100kHz sample rate (48Mhz / 100kHz - 1) + adc.read_many(&mut pin, &mut buf, div, &mut dma).await.unwrap(); + info!("single: {:?} ...etc", buf[..8]); + + // Read 100 samples from 4 channels interleaved + let mut buf = [0_u16; { BLOCK_SIZE * NUM_CHANNELS }]; + let div = 119; // 100kHz sample rate (48Mhz / 100kHz * 4ch - 1) + adc.read_many_multichannel(&mut pins, &mut buf, div, &mut dma) + .await + .unwrap(); + info!("multi: {:?} ...etc", buf[..NUM_CHANNELS * 2]); + + ticker.next().await; + } +} diff --git a/embassy/examples/rp23/src/bin/assign_resources.rs b/embassy/examples/rp23/src/bin/assign_resources.rs new file mode 100644 index 0000000..0d4ad8d --- /dev/null +++ b/embassy/examples/rp23/src/bin/assign_resources.rs @@ -0,0 +1,84 @@ +//! This example demonstrates how to assign resources to multiple tasks by splitting up the peripherals. +//! It is not about sharing the same resources between tasks, see sharing.rs for that or head to https://embassy.dev/book/#_sharing_peripherals_between_tasks) +//! Of course splitting up resources and sharing resources can be combined, yet this example is only about splitting up resources. +//! +//! There are basically two ways we demonstrate here: +//! 1) Assigning resources to a task by passing parts of the peripherals +//! 2) Assigning resources to a task by passing a struct with the split up peripherals, using the assign-resources macro +//! +//! using four LEDs on Pins 10, 11, 20 and 21 + +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{self, PIN_20, PIN_21}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // initialize the peripherals + let p = embassy_rp::init(Default::default()); + + // 1) Assigning a resource to a task by passing parts of the peripherals. + spawner + .spawn(double_blinky_manually_assigned(spawner, p.PIN_20, p.PIN_21)) + .unwrap(); + + // 2) Using the assign-resources macro to assign resources to a task. + // we perform the split, see further below for the definition of the resources struct + let r = split_resources!(p); + // and then we can use them + spawner.spawn(double_blinky_macro_assigned(spawner, r.leds)).unwrap(); +} + +// 1) Assigning a resource to a task by passing parts of the peripherals. +#[embassy_executor::task] +async fn double_blinky_manually_assigned(_spawner: Spawner, pin_20: PIN_20, pin_21: PIN_21) { + let mut led_20 = Output::new(pin_20, Level::Low); + let mut led_21 = Output::new(pin_21, Level::High); + + loop { + info!("toggling leds"); + led_20.toggle(); + led_21.toggle(); + Timer::after_secs(1).await; + } +} + +// 2) Using the assign-resources macro to assign resources to a task. +// first we define the resources we want to assign to the task using the assign_resources! macro +// basically this will split up the peripherals struct into smaller structs, that we define here +// naming is up to you, make sure your future self understands what you did here +assign_resources! { + leds: Leds{ + led_10: PIN_10, + led_11: PIN_11, + } + // add more resources to more structs if needed, for example defining one struct for each task +} +// this could be done in another file and imported here, but for the sake of simplicity we do it here +// see https://github.com/adamgreig/assign-resources for more information + +// 2) Using the split resources in a task +#[embassy_executor::task] +async fn double_blinky_macro_assigned(_spawner: Spawner, r: Leds) { + let mut led_10 = Output::new(r.led_10, Level::Low); + let mut led_11 = Output::new(r.led_11, Level::High); + + loop { + info!("toggling leds"); + led_10.toggle(); + led_11.toggle(); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp23/src/bin/blinky.rs b/embassy/examples/rp23/src/bin/blinky.rs new file mode 100644 index 0000000..c1ddbb7 --- /dev/null +++ b/embassy/examples/rp23/src/bin/blinky.rs @@ -0,0 +1,47 @@ +//! This example test the RP Pico on board LED. +//! +//! It does not work with the RP Pico W board. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio; +use embassy_time::Timer; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info`. +// This isn't needed, but it's recomended to have these minimal entries. +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"Blinky Example"), + embassy_rp::binary_info::rp_program_description!( + c"This example tests the RP Pico on board LED, connected to gpio 25" + ), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + info!("led on!"); + led.set_high(); + Timer::after_millis(250).await; + + info!("led off!"); + led.set_low(); + Timer::after_millis(250).await; + } +} diff --git a/embassy/examples/rp23/src/bin/blinky_two_channels.rs b/embassy/examples/rp23/src/bin/blinky_two_channels.rs new file mode 100644 index 0000000..ce48285 --- /dev/null +++ b/embassy/examples/rp23/src/bin/blinky_two_channels.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] +/// This example demonstrates how to access a given pin from more than one embassy task +/// The on-board LED is toggled by two tasks with slightly different periods, leading to the +/// apparent duty cycle of the LED increasing, then decreasing, linearly. The phenomenon is similar +/// to interference and the 'beats' you can hear if you play two frequencies close to one another +/// [Link explaining it](https://www.physicsclassroom.com/class/sound/Lesson-3/Interference-and-Beats) +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::{Channel, Sender}; +use embassy_time::{Duration, Ticker}; +use gpio::{AnyPin, Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +enum LedState { + Toggle, +} +static CHANNEL: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(AnyPin::from(p.PIN_25), Level::High); + + let dt = 100 * 1_000_000; + let k = 1.003; + + unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos(dt)))); + unwrap!(spawner.spawn(toggle_led( + CHANNEL.sender(), + Duration::from_nanos((dt as f64 * k) as u64) + ))); + + loop { + match CHANNEL.receive().await { + LedState::Toggle => led.toggle(), + } + } +} + +#[embassy_executor::task(pool_size = 2)] +async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>, delay: Duration) { + let mut ticker = Ticker::every(delay); + loop { + control.send(LedState::Toggle).await; + ticker.next().await; + } +} diff --git a/embassy/examples/rp23/src/bin/blinky_two_tasks.rs b/embassy/examples/rp23/src/bin/blinky_two_tasks.rs new file mode 100644 index 0000000..5dc6224 --- /dev/null +++ b/embassy/examples/rp23/src/bin/blinky_two_tasks.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] +/// This example demonstrates how to access a given pin from more than one embassy task +/// The on-board LED is toggled by two tasks with slightly different periods, leading to the +/// apparent duty cycle of the LED increasing, then decreasing, linearly. The phenomenon is similar +/// to interference and the 'beats' you can hear if you play two frequencies close to one another +/// [Link explaining it](https://www.physicsclassroom.com/class/sound/Lesson-3/Interference-and-Beats) +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_time::{Duration, Ticker}; +use gpio::{AnyPin, Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +type LedType = Mutex>>; +static LED: LedType = Mutex::new(None); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + // set the content of the global LED reference to the real LED pin + let led = Output::new(AnyPin::from(p.PIN_25), Level::High); + // inner scope is so that once the mutex is written to, the MutexGuard is dropped, thus the + // Mutex is released + { + *(LED.lock().await) = Some(led); + } + let dt = 100 * 1_000_000; + let k = 1.003; + + unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos(dt)))); + unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos((dt as f64 * k) as u64)))); +} + +#[embassy_executor::task(pool_size = 2)] +async fn toggle_led(led: &'static LedType, delay: Duration) { + let mut ticker = Ticker::every(delay); + loop { + { + let mut led_unlocked = led.lock().await; + if let Some(pin_ref) = led_unlocked.as_mut() { + pin_ref.toggle(); + } + } + ticker.next().await; + } +} diff --git a/embassy/examples/rp23/src/bin/button.rs b/embassy/examples/rp23/src/bin/button.rs new file mode 100644 index 0000000..85f1bca --- /dev/null +++ b/embassy/examples/rp23/src/bin/button.rs @@ -0,0 +1,33 @@ +//! This example uses the RP Pico on board LED to test input pin 28. This is not the button on the board. +//! +//! It does not work with the RP Pico W board. Use wifi_blinky.rs and add input pin. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + + // Use PIN_28, Pin34 on J0 for RP Pico, as a input. + // You need to add your own button. + let button = Input::new(p.PIN_28, Pull::Up); + + loop { + if button.is_high() { + led.set_high(); + } else { + led.set_low(); + } + } +} diff --git a/embassy/examples/rp23/src/bin/debounce.rs b/embassy/examples/rp23/src/bin/debounce.rs new file mode 100644 index 0000000..4c8b80d --- /dev/null +++ b/embassy/examples/rp23/src/bin/debounce.rs @@ -0,0 +1,85 @@ +//! This example shows the ease of debouncing a button with async rust. +//! Hook up a button or switch between pin 9 and ground. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Input, Level, Pull}; +use embassy_time::{with_deadline, Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +pub struct Debouncer<'a> { + input: Input<'a>, + debounce: Duration, +} + +impl<'a> Debouncer<'a> { + pub fn new(input: Input<'a>, debounce: Duration) -> Self { + Self { input, debounce } + } + + pub async fn debounce(&mut self) -> Level { + loop { + let l1 = self.input.get_level(); + + self.input.wait_for_any_edge().await; + + Timer::after(self.debounce).await; + + let l2 = self.input.get_level(); + if l1 != l2 { + break l2; + } + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut btn = Debouncer::new(Input::new(p.PIN_9, Pull::Up), Duration::from_millis(20)); + + info!("Debounce Demo"); + + loop { + // button pressed + btn.debounce().await; + let start = Instant::now(); + info!("Button Press"); + + match with_deadline(start + Duration::from_secs(1), btn.debounce()).await { + // Button Released < 1s + Ok(_) => { + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + continue; + } + // button held for > 1s + Err(_) => { + info!("Button Held"); + } + } + + match with_deadline(start + Duration::from_secs(5), btn.debounce()).await { + // Button released <5s + Ok(_) => { + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + continue; + } + // button held for > >5s + Err(_) => { + info!("Button Long Held"); + } + } + + // wait for button release before handling another press + btn.debounce().await; + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + } +} diff --git a/embassy/examples/rp23/src/bin/flash.rs b/embassy/examples/rp23/src/bin/flash.rs new file mode 100644 index 0000000..28dec24 --- /dev/null +++ b/embassy/examples/rp23/src/bin/flash.rs @@ -0,0 +1,130 @@ +//! This example test the flash connected to the RP2040 chip. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::flash::{Async, ERASE_SIZE, FLASH_BASE}; +use embassy_rp::peripherals::FLASH; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +const ADDR_OFFSET: u32 = 0x100000; +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after_millis(10).await; + + let mut flash = embassy_rp::flash::Flash::<_, Async, FLASH_SIZE>::new(p.FLASH, p.DMA_CH0); + + erase_write_sector(&mut flash, 0x00); + + multiwrite_bytes(&mut flash, ERASE_SIZE as u32); + + background_read(&mut flash, (ERASE_SIZE * 2) as u32).await; + + info!("Flash Works!"); +} + +fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { + info!(">>>> [multiwrite_bytes]"); + let mut read_buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", read_buf[0..4]); + + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); + info!("Contents after erase starts with {=[u8]}", read_buf[0..4]); + if read_buf.iter().any(|x| *x != 0xFF) { + defmt::panic!("unexpected"); + } + + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, &[0x01])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 1, &[0x02])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 2, &[0x03])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 3, &[0x04])); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); + info!("Contents after write starts with {=[u8]}", read_buf[0..4]); + if read_buf[0..4] != [0x01, 0x02, 0x03, 0x04] { + defmt::panic!("unexpected"); + } +} + +fn erase_write_sector(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { + info!(">>>> [erase_write_sector]"); + let mut buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", buf[0..4]); + + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); + info!("Contents after erase starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xFF) { + defmt::panic!("unexpected"); + } + + for b in buf.iter_mut() { + *b = 0xDA; + } + + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, &buf)); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); + info!("Contents after write starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xDA) { + defmt::panic!("unexpected"); + } +} + +async fn background_read(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { + info!(">>>> [background_read]"); + + let mut buf = [0u32; 8]; + defmt::unwrap!(flash.background_read(ADDR_OFFSET + offset, &mut buf)).await; + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=u32:x}", buf[0]); + + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.background_read(ADDR_OFFSET + offset, &mut buf)).await; + info!("Contents after erase starts with {=u32:x}", buf[0]); + if buf.iter().any(|x| *x != 0xFFFFFFFF) { + defmt::panic!("unexpected"); + } + + for b in buf.iter_mut() { + *b = 0xDABA1234; + } + + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, unsafe { + core::slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len() * 4) + })); + + defmt::unwrap!(flash.background_read(ADDR_OFFSET + offset, &mut buf)).await; + info!("Contents after write starts with {=u32:x}", buf[0]); + if buf.iter().any(|x| *x != 0xDABA1234) { + defmt::panic!("unexpected"); + } +} diff --git a/embassy/examples/rp23/src/bin/gpio_async.rs b/embassy/examples/rp23/src/bin/gpio_async.rs new file mode 100644 index 0000000..bfb9a3f --- /dev/null +++ b/embassy/examples/rp23/src/bin/gpio_async.rs @@ -0,0 +1,45 @@ +//! This example shows how async gpio can be used with a RP2040. +//! +//! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio; +use embassy_time::Timer; +use gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +/// It requires an external signal to be manually triggered on PIN 16. For +/// example, this could be accomplished using an external power source with a +/// button so that it is possible to toggle the signal from low to high. +/// +/// This example will begin with turning on the LED on the board and wait for a +/// high signal on PIN 16. Once the high event/signal occurs the program will +/// continue and turn off the LED, and then wait for 2 seconds before completing +/// the loop and starting over again. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + let mut async_input = Input::new(p.PIN_16, Pull::None); + + loop { + info!("wait_for_high. Turn on LED"); + led.set_high(); + + async_input.wait_for_high().await; + + info!("done wait_for_high. Turn off LED"); + led.set_low(); + + Timer::after_secs(2).await; + } +} diff --git a/embassy/examples/rp23/src/bin/gpout.rs b/embassy/examples/rp23/src/bin/gpout.rs new file mode 100644 index 0000000..3cc2ea9 --- /dev/null +++ b/embassy/examples/rp23/src/bin/gpout.rs @@ -0,0 +1,42 @@ +//! This example shows how GPOUT (General purpose clock outputs) can toggle a output pin. +//! +//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::clocks; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let gpout3 = clocks::Gpout::new(p.PIN_25); + gpout3.set_div(1000, 0); + gpout3.enable(); + + loop { + gpout3.set_src(clocks::GpoutSrc::Sys); + info!( + "Pin 25 is now outputing CLK_SYS/1000, should be toggling at {}", + gpout3.get_freq() + ); + Timer::after_secs(2).await; + + gpout3.set_src(clocks::GpoutSrc::Ref); + info!( + "Pin 25 is now outputing CLK_REF/1000, should be toggling at {}", + gpout3.get_freq() + ); + Timer::after_secs(2).await; + } +} diff --git a/embassy/examples/rp23/src/bin/i2c_async.rs b/embassy/examples/rp23/src/bin/i2c_async.rs new file mode 100644 index 0000000..b30088b --- /dev/null +++ b/embassy/examples/rp23/src/bin/i2c_async.rs @@ -0,0 +1,115 @@ +//! This example shows how to communicate asynchronous using i2c with external chips. +//! +//! Example written for the [`MCP23017 16-Bit I2C I/O Expander with Serial Interface`] chip. +//! (https://www.microchip.com/en-us/product/mcp23017) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::i2c::{self, Config, InterruptHandler}; +use embassy_rp::peripherals::I2C1; +use embassy_time::Timer; +use embedded_hal_async::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + I2C1_IRQ => InterruptHandler; +}); + +#[allow(dead_code)] +mod mcp23017 { + pub const ADDR: u8 = 0x20; // default addr + + macro_rules! mcpregs { + ($($name:ident : $val:expr),* $(,)?) => { + $( + pub const $name: u8 = $val; + )* + + pub fn regname(reg: u8) -> &'static str { + match reg { + $( + $val => stringify!($name), + )* + _ => panic!("bad reg"), + } + } + } + } + + // These are correct for IOCON.BANK=0 + mcpregs! { + IODIRA: 0x00, + IPOLA: 0x02, + GPINTENA: 0x04, + DEFVALA: 0x06, + INTCONA: 0x08, + IOCONA: 0x0A, + GPPUA: 0x0C, + INTFA: 0x0E, + INTCAPA: 0x10, + GPIOA: 0x12, + OLATA: 0x14, + IODIRB: 0x01, + IPOLB: 0x03, + GPINTENB: 0x05, + DEFVALB: 0x07, + INTCONB: 0x09, + IOCONB: 0x0B, + GPPUB: 0x0D, + INTFB: 0x0F, + INTCAPB: 0x11, + GPIOB: 0x13, + OLATB: 0x15, + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let sda = p.PIN_14; + let scl = p.PIN_15; + + info!("set up i2c "); + let mut i2c = i2c::I2c::new_async(p.I2C1, scl, sda, Irqs, Config::default()); + + use mcp23017::*; + + info!("init mcp23017 config for IxpandO"); + // init - a outputs, b inputs + i2c.write(ADDR, &[IODIRA, 0x00]).await.unwrap(); + i2c.write(ADDR, &[IODIRB, 0xff]).await.unwrap(); + i2c.write(ADDR, &[GPPUB, 0xff]).await.unwrap(); // pullups + + let mut val = 1; + loop { + let mut portb = [0]; + + i2c.write_read(mcp23017::ADDR, &[GPIOB], &mut portb).await.unwrap(); + info!("portb = {:02x}", portb[0]); + i2c.write(mcp23017::ADDR, &[GPIOA, val | portb[0]]).await.unwrap(); + val = val.rotate_left(1); + + // get a register dump + info!("getting register dump"); + let mut regs = [0; 22]; + i2c.write_read(ADDR, &[0], &mut regs).await.unwrap(); + // always get the regdump but only display it if portb'0 is set + if portb[0] & 1 != 0 { + for (idx, reg) in regs.into_iter().enumerate() { + info!("{} => {:02x}", regname(idx as u8), reg); + } + } + + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/rp23/src/bin/i2c_async_embassy.rs b/embassy/examples/rp23/src/bin/i2c_async_embassy.rs new file mode 100644 index 0000000..c783a80 --- /dev/null +++ b/embassy/examples/rp23/src/bin/i2c_async_embassy.rs @@ -0,0 +1,90 @@ +//! This example shows how to communicate asynchronous using i2c with external chip. +//! +//! It's using embassy's functions directly instead of traits from embedded_hal_async::i2c::I2c. +//! While most of i2c devices are addressed using 7 bits, an extension allows 10 bits too. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_rp::block::ImageDef; +use embassy_rp::i2c::InterruptHandler; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Our anonymous hypotetical temperature sensor could be: +// a 12-bit sensor, with 100ms startup time, range of -40*C - 125*C, and precision 0.25*C +// It requires no configuration or calibration, works with all i2c bus speeds, +// never stretches clock or does anything complicated. Replies with one u16. +// It requires only one write to take it out of suspend mode, and stays on. +// Often result would be just on 12 bits, but here we'll simplify it to 16. + +enum UncomplicatedSensorId { + A(UncomplicatedSensorU8), + B(UncomplicatedSensorU16), +} +enum UncomplicatedSensorU8 { + First = 0x48, +} +enum UncomplicatedSensorU16 { + Other = 0x0049, +} + +impl Into for UncomplicatedSensorU16 { + fn into(self) -> u16 { + self as u16 + } +} +impl Into for UncomplicatedSensorU8 { + fn into(self) -> u16 { + 0x48 + } +} +impl From for u16 { + fn from(t: UncomplicatedSensorId) -> Self { + match t { + UncomplicatedSensorId::A(x) => x.into(), + UncomplicatedSensorId::B(x) => x.into(), + } + } +} + +embassy_rp::bind_interrupts!(struct Irqs { + I2C1_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_task_spawner: embassy_executor::Spawner) { + let p = embassy_rp::init(Default::default()); + let sda = p.PIN_14; + let scl = p.PIN_15; + let config = embassy_rp::i2c::Config::default(); + let mut bus = embassy_rp::i2c::I2c::new_async(p.I2C1, scl, sda, Irqs, config); + + const WAKEYWAKEY: u16 = 0xBABE; + let mut result: [u8; 2] = [0, 0]; + // wait for sensors to initialize + embassy_time::Timer::after(embassy_time::Duration::from_millis(100)).await; + + let _res_1 = bus + .write_async(UncomplicatedSensorU8::First, WAKEYWAKEY.to_be_bytes()) + .await; + let _res_2 = bus + .write_async(UncomplicatedSensorU16::Other, WAKEYWAKEY.to_be_bytes()) + .await; + + loop { + let s1 = UncomplicatedSensorId::A(UncomplicatedSensorU8::First); + let s2 = UncomplicatedSensorId::B(UncomplicatedSensorU16::Other); + let sensors = [s1, s2]; + for sensor in sensors { + if bus.read_async(sensor, &mut result).await.is_ok() { + info!("Result {}", u16::from_be_bytes(result.into())); + } + } + embassy_time::Timer::after(embassy_time::Duration::from_millis(200)).await; + } +} diff --git a/embassy/examples/rp23/src/bin/i2c_blocking.rs b/embassy/examples/rp23/src/bin/i2c_blocking.rs new file mode 100644 index 0000000..a686773 --- /dev/null +++ b/embassy/examples/rp23/src/bin/i2c_blocking.rs @@ -0,0 +1,79 @@ +//! This example shows how to communicate using i2c with external chips. +//! +//! Example written for the [`MCP23017 16-Bit I2C I/O Expander with Serial Interface`] chip. +//! (https://www.microchip.com/en-us/product/mcp23017) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::i2c::{self, Config}; +use embassy_time::Timer; +use embedded_hal_1::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[allow(dead_code)] +mod mcp23017 { + pub const ADDR: u8 = 0x20; // default addr + + pub const IODIRA: u8 = 0x00; + pub const IPOLA: u8 = 0x02; + pub const GPINTENA: u8 = 0x04; + pub const DEFVALA: u8 = 0x06; + pub const INTCONA: u8 = 0x08; + pub const IOCONA: u8 = 0x0A; + pub const GPPUA: u8 = 0x0C; + pub const INTFA: u8 = 0x0E; + pub const INTCAPA: u8 = 0x10; + pub const GPIOA: u8 = 0x12; + pub const OLATA: u8 = 0x14; + pub const IODIRB: u8 = 0x01; + pub const IPOLB: u8 = 0x03; + pub const GPINTENB: u8 = 0x05; + pub const DEFVALB: u8 = 0x07; + pub const INTCONB: u8 = 0x09; + pub const IOCONB: u8 = 0x0B; + pub const GPPUB: u8 = 0x0D; + pub const INTFB: u8 = 0x0F; + pub const INTCAPB: u8 = 0x11; + pub const GPIOB: u8 = 0x13; + pub const OLATB: u8 = 0x15; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let sda = p.PIN_14; + let scl = p.PIN_15; + + info!("set up i2c "); + let mut i2c = i2c::I2c::new_blocking(p.I2C1, scl, sda, Config::default()); + + use mcp23017::*; + + info!("init mcp23017 config for IxpandO"); + // init - a outputs, b inputs + i2c.write(ADDR, &[IODIRA, 0x00]).unwrap(); + i2c.write(ADDR, &[IODIRB, 0xff]).unwrap(); + i2c.write(ADDR, &[GPPUB, 0xff]).unwrap(); // pullups + + let mut val = 0xaa; + loop { + let mut portb = [0]; + + i2c.write(mcp23017::ADDR, &[GPIOA, val]).unwrap(); + i2c.write_read(mcp23017::ADDR, &[GPIOB], &mut portb).unwrap(); + + info!("portb = {:02x}", portb[0]); + val = !val; + + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp23/src/bin/i2c_slave.rs b/embassy/examples/rp23/src/bin/i2c_slave.rs new file mode 100644 index 0000000..8817538 --- /dev/null +++ b/embassy/examples/rp23/src/bin/i2c_slave.rs @@ -0,0 +1,122 @@ +//! This example shows how to use the 2040 as an i2c slave. +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::{I2C0, I2C1}; +use embassy_rp::{bind_interrupts, i2c, i2c_slave}; +use embassy_time::Timer; +use embedded_hal_async::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + I2C0_IRQ => i2c::InterruptHandler; + I2C1_IRQ => i2c::InterruptHandler; +}); + +const DEV_ADDR: u8 = 0x42; + +#[embassy_executor::task] +async fn device_task(mut dev: i2c_slave::I2cSlave<'static, I2C1>) -> ! { + info!("Device start"); + + let mut state = 0; + + loop { + let mut buf = [0u8; 128]; + match dev.listen(&mut buf).await { + Ok(i2c_slave::Command::GeneralCall(len)) => info!("Device received general call write: {}", buf[..len]), + Ok(i2c_slave::Command::Read) => loop { + match dev.respond_to_read(&[state]).await { + Ok(x) => match x { + i2c_slave::ReadStatus::Done => break, + i2c_slave::ReadStatus::NeedMoreBytes => (), + i2c_slave::ReadStatus::LeftoverBytes(x) => { + info!("tried to write {} extra bytes", x); + break; + } + }, + Err(e) => error!("error while responding {}", e), + } + }, + Ok(i2c_slave::Command::Write(len)) => info!("Device received write: {}", buf[..len]), + Ok(i2c_slave::Command::WriteRead(len)) => { + info!("device received write read: {:x}", buf[..len]); + match buf[0] { + // Set the state + 0xC2 => { + state = buf[1]; + match dev.respond_and_fill(&[state], 0x00).await { + Ok(read_status) => info!("response read status {}", read_status), + Err(e) => error!("error while responding {}", e), + } + } + // Reset State + 0xC8 => { + state = 0; + match dev.respond_and_fill(&[state], 0x00).await { + Ok(read_status) => info!("response read status {}", read_status), + Err(e) => error!("error while responding {}", e), + } + } + x => error!("Invalid Write Read {:x}", x), + } + } + Err(e) => error!("{}", e), + } + } +} + +#[embassy_executor::task] +async fn controller_task(mut con: i2c::I2c<'static, I2C0, i2c::Async>) { + info!("Controller start"); + + loop { + let mut resp_buff = [0u8; 2]; + for i in 0..10 { + match con.write_read(DEV_ADDR, &[0xC2, i], &mut resp_buff).await { + Ok(_) => info!("write_read response: {}", resp_buff), + Err(e) => error!("Error writing {}", e), + } + + Timer::after_millis(100).await; + } + match con.read(DEV_ADDR, &mut resp_buff).await { + Ok(_) => info!("read response: {}", resp_buff), + Err(e) => error!("Error writing {}", e), + } + match con.write_read(DEV_ADDR, &[0xC8], &mut resp_buff).await { + Ok(_) => info!("write_read response: {}", resp_buff), + Err(e) => error!("Error writing {}", e), + } + Timer::after_millis(100).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let d_sda = p.PIN_3; + let d_scl = p.PIN_2; + let mut config = i2c_slave::Config::default(); + config.addr = DEV_ADDR as u16; + let device = i2c_slave::I2cSlave::new(p.I2C1, d_sda, d_scl, Irqs, config); + + unwrap!(spawner.spawn(device_task(device))); + + let c_sda = p.PIN_1; + let c_scl = p.PIN_0; + let mut config = i2c::Config::default(); + config.frequency = 1_000_000; + let controller = i2c::I2c::new_async(p.I2C0, c_sda, c_scl, Irqs, config); + + unwrap!(spawner.spawn(controller_task(controller))); +} diff --git a/embassy/examples/rp23/src/bin/interrupt.rs b/embassy/examples/rp23/src/bin/interrupt.rs new file mode 100644 index 0000000..d9b6622 --- /dev/null +++ b/embassy/examples/rp23/src/bin/interrupt.rs @@ -0,0 +1,99 @@ +//! This example shows how you can use raw interrupt handlers alongside embassy. +//! The example also showcases some of the options available for sharing resources/data. +//! +//! In the example, an ADC reading is triggered every time the PWM wraps around. +//! The sample data is sent down a channel, to be processed inside a low priority task. +//! The processed data is then used to adjust the PWM duty cycle, once every second. + +#![no_std] +#![no_main] + +use core::cell::{Cell, RefCell}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{self, Adc, Blocking}; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Pull; +use embassy_rp::interrupt; +use embassy_rp::pwm::{Config, Pwm}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::channel::Channel; +use embassy_time::{Duration, Ticker}; +use portable_atomic::{AtomicU32, Ordering}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +static COUNTER: AtomicU32 = AtomicU32::new(0); +static PWM: Mutex>> = Mutex::new(RefCell::new(None)); +static ADC: Mutex, adc::Channel)>>> = + Mutex::new(RefCell::new(None)); +static ADC_VALUES: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + embassy_rp::pac::SIO.spinlock(31).write_value(1); + let p = embassy_rp::init(Default::default()); + + let adc = Adc::new_blocking(p.ADC, Default::default()); + let p26 = adc::Channel::new_pin(p.PIN_26, Pull::None); + ADC.lock(|a| a.borrow_mut().replace((adc, p26))); + + let pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, Default::default()); + PWM.lock(|p| p.borrow_mut().replace(pwm)); + + // Enable the interrupt for pwm slice 4 + embassy_rp::pac::PWM.irq0_inte().modify(|w| w.set_ch4(true)); + unsafe { + cortex_m::peripheral::NVIC::unmask(interrupt::PWM_IRQ_WRAP_0); + } + + // Tasks require their resources to have 'static lifetime + // No Mutex needed when sharing within the same executor/prio level + static AVG: StaticCell> = StaticCell::new(); + let avg = AVG.init(Default::default()); + spawner.must_spawn(processing(avg)); + + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + ticker.next().await; + let freq = COUNTER.swap(0, Ordering::Relaxed); + info!("pwm freq: {:?} Hz", freq); + info!("adc average: {:?}", avg.get()); + + // Update the pwm duty cycle, based on the averaged adc reading + let mut config = Config::default(); + config.compare_b = ((avg.get() as f32 / 4095.0) * config.top as f32) as _; + PWM.lock(|p| p.borrow_mut().as_mut().unwrap().set_config(&config)); + } +} + +#[embassy_executor::task] +async fn processing(avg: &'static Cell) { + let mut buffer: heapless::HistoryBuffer = Default::default(); + loop { + let val = ADC_VALUES.receive().await; + buffer.write(val); + let sum: u32 = buffer.iter().map(|x| *x as u32).sum(); + avg.set(sum / buffer.len() as u32); + } +} + +#[interrupt] +fn PWM_IRQ_WRAP_0() { + critical_section::with(|cs| { + let mut adc = ADC.borrow(cs).borrow_mut(); + let (adc, p26) = adc.as_mut().unwrap(); + let val = adc.blocking_read(p26).unwrap(); + ADC_VALUES.try_send(val).ok(); + + // Clear the interrupt, so we don't immediately re-enter this irq handler + PWM.borrow(cs).borrow_mut().as_mut().unwrap().clear_wrapped(); + }); + COUNTER.fetch_add(1, Ordering::Relaxed); +} diff --git a/embassy/examples/rp23/src/bin/multicore.rs b/embassy/examples/rp23/src/bin/multicore.rs new file mode 100644 index 0000000..d4d470f --- /dev/null +++ b/embassy/examples/rp23/src/bin/multicore.rs @@ -0,0 +1,71 @@ +//! This example shows how to send messages between the two cores in the RP2040 chip. +//! +//! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Executor; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +static mut CORE1_STACK: Stack<4096> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); +static CHANNEL: Channel = Channel::new(); + +enum LedState { + On, + Off, +} + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + let led = Output::new(p.PIN_25, Level::Low); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led)))); + }, + ); + + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +} + +#[embassy_executor::task] +async fn core0_task() { + info!("Hello from core 0"); + loop { + CHANNEL.send(LedState::On).await; + Timer::after_millis(100).await; + CHANNEL.send(LedState::Off).await; + Timer::after_millis(400).await; + } +} + +#[embassy_executor::task] +async fn core1_task(mut led: Output<'static>) { + info!("Hello from core 1"); + loop { + match CHANNEL.receive().await { + LedState::On => led.set_high(), + LedState::Off => led.set_low(), + } + } +} diff --git a/embassy/examples/rp23/src/bin/multiprio.rs b/embassy/examples/rp23/src/bin/multiprio.rs new file mode 100644 index 0000000..787854a --- /dev/null +++ b/embassy/examples/rp23/src/bin/multiprio.rs @@ -0,0 +1,150 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::{info, unwrap}; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_rp::block::ImageDef; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_time::{Instant, Timer, TICK_HZ}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::task] +async fn run_high() { + loop { + info!(" [high] tick!"); + Timer::after_ticks(673740).await; + } +} + +#[embassy_executor::task] +async fn run_med() { + loop { + let start = Instant::now(); + info!(" [med] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + embassy_time::block_for(embassy_time::Duration::from_secs(1)); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; + info!(" [med] done in {} ms", ms); + + Timer::after_ticks(53421).await; + } +} + +#[embassy_executor::task] +async fn run_low() { + loop { + let start = Instant::now(); + info!("[low] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + embassy_time::block_for(embassy_time::Duration::from_secs(2)); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; + info!("[low] done in {} ms", ms); + + Timer::after_ticks(82983).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn SWI_IRQ_0() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_rp::init(Default::default()); + + // High-priority executor: SWI_IRQ_1, priority level 2 + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + let spawner = EXECUTOR_HIGH.start(interrupt::SWI_IRQ_1); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: SWI_IRQ_0, priority level 3 + interrupt::SWI_IRQ_0.set_priority(Priority::P3); + let spawner = EXECUTOR_MED.start(interrupt::SWI_IRQ_0); + unwrap!(spawner.spawn(run_med())); + + // Low priority executor: runs in thread mode, using WFE/SEV + let executor = EXECUTOR_LOW.init(Executor::new()); + executor.run(|spawner| { + unwrap!(spawner.spawn(run_low())); + }); +} diff --git a/embassy/examples/rp23/src/bin/otp.rs b/embassy/examples/rp23/src/bin/otp.rs new file mode 100644 index 0000000..c67c982 --- /dev/null +++ b/embassy/examples/rp23/src/bin/otp.rs @@ -0,0 +1,36 @@ +//! This example shows reading the OTP constants on the RP235x. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::otp; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let _ = embassy_rp::init(Default::default()); + // + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after_millis(10).await; + + let chip_id = unwrap!(otp::get_chipid()); + info!("Unique id:{:X}", chip_id); + + let private_rand = unwrap!(otp::get_private_random_number()); + info!("Private Rand:{:X}", private_rand); + + loop { + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp23/src/bin/pio_async.rs b/embassy/examples/rp23/src/bin/pio_async.rs new file mode 100644 index 0000000..896447e --- /dev/null +++ b/embassy/examples/rp23/src/bin/pio_async.rs @@ -0,0 +1,135 @@ +//! This example shows powerful PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Common, Config, InterruptHandler, Irq, Pio, PioPin, ShiftDirection, StateMachine}; +use fixed::traits::ToFixed; +use fixed_macro::types::U56F8; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +fn setup_pio_task_sm0<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 0>, pin: impl PioPin) { + // Setup sm0 + + // Send data serially to pin + let prg = pio_proc::pio_asm!( + ".origin 16", + "set pindirs, 1", + ".wrap_target", + "out pins,1 [19]", + ".wrap", + ); + + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&prg.program), &[]); + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + cfg.clock_divider = (U56F8!(125_000_000) / 20 / 200).to_fixed(); + cfg.shift_out.auto_fill = true; + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm0(mut sm: StateMachine<'static, PIO0, 0>) { + sm.set_enable(true); + + let mut v = 0x0f0caffa; + loop { + sm.tx().wait_push(v).await; + v ^= 0xffff; + info!("Pushed {:032b} to FIFO", v); + } +} + +fn setup_pio_task_sm1<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 1>) { + // Setupm sm1 + + // Read 0b10101 repeatedly until ISR is full + let prg = pio_proc::pio_asm!( + // + ".origin 8", + "set x, 0x15", + ".wrap_target", + "in x, 5 [31]", + ".wrap", + ); + + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&prg.program), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed(); + cfg.shift_in.auto_fill = true; + cfg.shift_in.direction = ShiftDirection::Right; + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm1(mut sm: StateMachine<'static, PIO0, 1>) { + sm.set_enable(true); + loop { + let rx = sm.rx().wait_pull().await; + info!("Pulled {:032b} from FIFO", rx); + } +} + +fn setup_pio_task_sm2<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 2>) { + // Setup sm2 + + // Repeatedly trigger IRQ 3 + let prg = pio_proc::pio_asm!( + ".origin 0", + ".wrap_target", + "set x,10", + "delay:", + "jmp x-- delay [15]", + "irq 3 [15]", + ".wrap", + ); + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&prg.program), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed(); + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm2(mut irq: Irq<'static, PIO0, 3>, mut sm: StateMachine<'static, PIO0, 2>) { + sm.set_enable(true); + loop { + irq.wait().await; + info!("IRQ trigged"); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + + let Pio { + mut common, + irq3, + mut sm0, + mut sm1, + mut sm2, + .. + } = Pio::new(pio, Irqs); + + setup_pio_task_sm0(&mut common, &mut sm0, p.PIN_0); + setup_pio_task_sm1(&mut common, &mut sm1); + setup_pio_task_sm2(&mut common, &mut sm2); + spawner.spawn(pio_task_sm0(sm0)).unwrap(); + spawner.spawn(pio_task_sm1(sm1)).unwrap(); + spawner.spawn(pio_task_sm2(irq3, sm2)).unwrap(); +} diff --git a/embassy/examples/rp23/src/bin/pio_dma.rs b/embassy/examples/rp23/src/bin/pio_dma.rs new file mode 100644 index 0000000..b5f7547 --- /dev/null +++ b/embassy/examples/rp23/src/bin/pio_dma.rs @@ -0,0 +1,88 @@ +//! This example shows powerful PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use defmt::info; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Config, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; +use embassy_rp::{bind_interrupts, Peripheral}; +use fixed::traits::ToFixed; +use fixed_macro::types::U56F8; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +fn swap_nibbles(v: u32) -> u32 { + let v = (v & 0x0f0f_0f0f) << 4 | (v & 0xf0f0_f0f0) >> 4; + let v = (v & 0x00ff_00ff) << 8 | (v & 0xff00_ff00) >> 8; + (v & 0x0000_ffff) << 16 | (v & 0xffff_0000) >> 16 +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + let Pio { + mut common, + sm0: mut sm, + .. + } = Pio::new(pio, Irqs); + + let prg = pio_proc::pio_asm!( + ".origin 0", + "set pindirs,1", + ".wrap_target", + "set y,7", + "loop:", + "out x,4", + "in x,4", + "jmp y--, loop", + ".wrap", + ); + + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&prg.program), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / U56F8!(10_000)).to_fixed(); + cfg.shift_in = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Left, + }; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Right, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + let mut dma_out_ref = p.DMA_CH0.into_ref(); + let mut dma_in_ref = p.DMA_CH1.into_ref(); + let mut dout = [0x12345678u32; 29]; + for i in 1..dout.len() { + dout[i] = (dout[i - 1] & 0x0fff_ffff) * 13 + 7; + } + let mut din = [0u32; 29]; + loop { + let (rx, tx) = sm.rx_tx(); + join( + tx.dma_push(dma_out_ref.reborrow(), &dout), + rx.dma_pull(dma_in_ref.reborrow(), &mut din), + ) + .await; + for i in 0..din.len() { + assert_eq!(din[i], swap_nibbles(dout[i])); + } + info!("Swapped {} words", dout.len()); + } +} diff --git a/embassy/examples/rp23/src/bin/pio_hd44780.rs b/embassy/examples/rp23/src/bin/pio_hd44780.rs new file mode 100644 index 0000000..c6f5f6d --- /dev/null +++ b/embassy/examples/rp23/src/bin/pio_hd44780.rs @@ -0,0 +1,92 @@ +//! This example shows powerful PIO module in the RP2040 chip to communicate with a HD44780 display. +//! See (https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +#![no_std] +#![no_main] + +use core::fmt::Write; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram}; +use embassy_rp::pwm::{self, Pwm}; +use embassy_time::{Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(pub struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // this test assumes a 2x16 HD44780 display attached as follow: + // rs = PIN0 + // rw = PIN1 + // e = PIN2 + // db4 = PIN3 + // db5 = PIN4 + // db6 = PIN5 + // db7 = PIN6 + // additionally a pwm signal for a bias voltage charge pump is provided on pin 15, + // allowing direct connection of the display to the RP2040 without level shifters. + let p = embassy_rp::init(Default::default()); + + let _pwm = Pwm::new_output_b(p.PWM_SLICE7, p.PIN_15, { + let mut c = pwm::Config::default(); + c.divider = 125.into(); + c.top = 100; + c.compare_b = 50; + c + }); + + let Pio { + mut common, sm0, irq0, .. + } = Pio::new(p.PIO0, Irqs); + + let word_prg = PioHD44780CommandWordProgram::new(&mut common); + let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common); + + let mut hd = PioHD44780::new( + &mut common, + sm0, + irq0, + p.DMA_CH3, + p.PIN_0, + p.PIN_1, + p.PIN_2, + p.PIN_3, + p.PIN_4, + p.PIN_5, + p.PIN_6, + &word_prg, + &seq_prg, + ) + .await; + + loop { + struct Buf([u8; N], usize); + impl Write for Buf { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + for b in s.as_bytes() { + if self.1 >= N { + return Err(core::fmt::Error); + } + self.0[self.1] = *b; + self.1 += 1; + } + Ok(()) + } + } + let mut buf = Buf([0; 16], 0); + write!(buf, "up {}s", Instant::now().as_micros() as f32 / 1e6).unwrap(); + hd.add_line(&buf.0[0..buf.1]).await; + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp23/src/bin/pio_i2s.rs b/embassy/examples/rp23/src/bin/pio_i2s.rs new file mode 100644 index 0000000..1fd3435 --- /dev/null +++ b/embassy/examples/rp23/src/bin/pio_i2s.rs @@ -0,0 +1,100 @@ +//! This example shows generating audio and sending it to a connected i2s DAC using the PIO +//! module of the RP2040. +//! +//! Connect the i2s DAC as follows: +//! bclk : GPIO 18 +//! lrc : GPIO 19 +//! din : GPIO 20 +//! Then hold down the boot select button to trigger a rising triangle waveform. + +#![no_std] +#![no_main] + +use core::mem; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Input, Pull}; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const SAMPLE_RATE: u32 = 48_000; +const BIT_DEPTH: u32 = 16; +const CHANNELS: u32 = 2; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + // Setup pio state machine for i2s output + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + let bit_clock_pin = p.PIN_18; + let left_right_clock_pin = p.PIN_19; + let data_pin = p.PIN_20; + + let program = PioI2sOutProgram::new(&mut common); + let mut i2s = PioI2sOut::new( + &mut common, + sm0, + p.DMA_CH0, + data_pin, + bit_clock_pin, + left_right_clock_pin, + SAMPLE_RATE, + BIT_DEPTH, + CHANNELS, + &program, + ); + + let fade_input = Input::new(p.PIN_0, Pull::Up); + + // create two audio buffers (back and front) which will take turns being + // filled with new audio data and being sent to the pio fifo using dma + const BUFFER_SIZE: usize = 960; + static DMA_BUFFER: StaticCell<[u32; BUFFER_SIZE * 2]> = StaticCell::new(); + let dma_buffer = DMA_BUFFER.init_with(|| [0u32; BUFFER_SIZE * 2]); + let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); + + // start pio state machine + let mut fade_value: i32 = 0; + let mut phase: i32 = 0; + + loop { + // trigger transfer of front buffer data to the pio fifo + // but don't await the returned future, yet + let dma_future = i2s.write(front_buffer); + + // fade in audio when bootsel is pressed + let fade_target = if fade_input.is_low() { i32::MAX } else { 0 }; + + // fill back buffer with fresh audio samples before awaiting the dma future + for s in back_buffer.iter_mut() { + // exponential approach of fade_value => fade_target + fade_value += (fade_target - fade_value) >> 14; + // generate triangle wave with amplitude and frequency based on fade value + phase = (phase + (fade_value >> 22)) & 0xffff; + let triangle_sample = (phase as i16 as i32).abs() - 16384; + let sample = (triangle_sample * (fade_value >> 15)) >> 16; + // duplicate mono sample into lower and upper half of dma word + *s = (sample as u16 as u32) * 0x10001; + } + + // now await the dma future. once the dma finishes, the next buffer needs to be queued + // within DMA_DEPTH / SAMPLE_RATE = 8 / 48000 seconds = 166us + dma_future.await; + mem::swap(&mut back_buffer, &mut front_buffer); + } +} diff --git a/embassy/examples/rp23/src/bin/pio_onewire.rs b/embassy/examples/rp23/src/bin/pio_onewire.rs new file mode 100644 index 0000000..7f227d0 --- /dev/null +++ b/embassy/examples/rp23/src/bin/pio_onewire.rs @@ -0,0 +1,88 @@ +//! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor. + +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{self, InterruptHandler, Pio}; +use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut pio = Pio::new(p.PIO0, Irqs); + + let prg = PioOneWireProgram::new(&mut pio.common); + let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); + + let mut sensor = Ds18b20::new(onewire); + + loop { + sensor.start().await; // Start a new measurement + Timer::after_secs(1).await; // Allow 1s for the measurement to finish + match sensor.temperature().await { + Ok(temp) => info!("temp = {:?} deg C", temp), + _ => error!("sensor error"), + } + Timer::after_secs(1).await; + } +} + +/// DS18B20 temperature sensor driver +pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { + wire: PioOneWire<'d, PIO, SM>, +} + +impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { + pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self { + Self { wire } + } + + /// Calculate CRC8 of the data + fn crc8(data: &[u8]) -> u8 { + let mut temp; + let mut data_byte; + let mut crc = 0; + for b in data { + data_byte = *b; + for _ in 0..8 { + temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc + } + + /// Start a new measurement. Allow at least 1000ms before getting `temperature`. + pub async fn start(&mut self) { + self.wire.write_bytes(&[0xCC, 0x44]).await; + } + + /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. + pub async fn temperature(&mut self) -> Result { + self.wire.write_bytes(&[0xCC, 0xBE]).await; + let mut data = [0; 9]; + self.wire.read_bytes(&mut data).await; + match Self::crc8(&data) == 0 { + true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), + false => Err(()), + } + } +} diff --git a/embassy/examples/rp23/src/bin/pio_pwm.rs b/embassy/examples/rp23/src/bin/pio_pwm.rs new file mode 100644 index 0000000..11af62a --- /dev/null +++ b/embassy/examples/rp23/src/bin/pio_pwm.rs @@ -0,0 +1,43 @@ +//! This example shows how to create a pwm using the PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use core::time::Duration; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +const REFRESH_INTERVAL: u64 = 20000; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + // Note that PIN_25 is the led pin on the Pico + let prg = PioPwmProgram::new(&mut common); + let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg); + pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); + pwm_pio.start(); + + let mut duration = 0; + loop { + duration = (duration + 1) % 1000; + pwm_pio.write(Duration::from_micros(duration)); + Timer::after_millis(1).await; + } +} diff --git a/embassy/examples/rp23/src/bin/pio_rotary_encoder.rs b/embassy/examples/rp23/src/bin/pio_rotary_encoder.rs new file mode 100644 index 0000000..2bb0e67 --- /dev/null +++ b/embassy/examples/rp23/src/bin/pio_rotary_encoder.rs @@ -0,0 +1,60 @@ +//! This example shows how to use the PIO module in the RP2040 to read a quadrature rotary encoder. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) { + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} + +#[embassy_executor::task] +async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) { + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, sm0, sm1, .. + } = Pio::new(p.PIO0, Irqs); + + let prg = PioEncoderProgram::new(&mut common); + let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg); + let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg); + + spawner.must_spawn(encoder_0(encoder0)); + spawner.must_spawn(encoder_1(encoder1)); +} diff --git a/embassy/examples/rp23/src/bin/pio_servo.rs b/embassy/examples/rp23/src/bin/pio_servo.rs new file mode 100644 index 0000000..4e94103 --- /dev/null +++ b/embassy/examples/rp23/src/bin/pio_servo.rs @@ -0,0 +1,133 @@ +//! This example shows how to create a pwm using the PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use core::time::Duration; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Instance, InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo +const DEFAULT_MAX_PULSE_WIDTH: u64 = 2000; // uncalibrated default, the longest duty cycle sent to a servo +const DEFAULT_MAX_DEGREE_ROTATION: u64 = 160; // 160 degrees is typical +const REFRESH_INTERVAL: u64 = 20000; // The period of each cycle + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +pub struct ServoBuilder<'d, T: Instance, const SM: usize> { + pwm: PioPwm<'d, T, SM>, + period: Duration, + min_pulse_width: Duration, + max_pulse_width: Duration, + max_degree_rotation: u64, +} + +impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { + pub fn new(pwm: PioPwm<'d, T, SM>) -> Self { + Self { + pwm, + period: Duration::from_micros(REFRESH_INTERVAL), + min_pulse_width: Duration::from_micros(DEFAULT_MIN_PULSE_WIDTH), + max_pulse_width: Duration::from_micros(DEFAULT_MAX_PULSE_WIDTH), + max_degree_rotation: DEFAULT_MAX_DEGREE_ROTATION, + } + } + + pub fn set_period(mut self, duration: Duration) -> Self { + self.period = duration; + self + } + + pub fn set_min_pulse_width(mut self, duration: Duration) -> Self { + self.min_pulse_width = duration; + self + } + + pub fn set_max_pulse_width(mut self, duration: Duration) -> Self { + self.max_pulse_width = duration; + self + } + + pub fn set_max_degree_rotation(mut self, degree: u64) -> Self { + self.max_degree_rotation = degree; + self + } + + pub fn build(mut self) -> Servo<'d, T, SM> { + self.pwm.set_period(self.period); + Servo { + pwm: self.pwm, + min_pulse_width: self.min_pulse_width, + max_pulse_width: self.max_pulse_width, + max_degree_rotation: self.max_degree_rotation, + } + } +} + +pub struct Servo<'d, T: Instance, const SM: usize> { + pwm: PioPwm<'d, T, SM>, + min_pulse_width: Duration, + max_pulse_width: Duration, + max_degree_rotation: u64, +} + +impl<'d, T: Instance, const SM: usize> Servo<'d, T, SM> { + pub fn start(&mut self) { + self.pwm.start(); + } + + pub fn stop(&mut self) { + self.pwm.stop(); + } + + pub fn write_time(&mut self, duration: Duration) { + self.pwm.write(duration); + } + + pub fn rotate(&mut self, degree: u64) { + let degree_per_nano_second = (self.max_pulse_width.as_nanos() as u64 - self.min_pulse_width.as_nanos() as u64) + / self.max_degree_rotation; + let mut duration = + Duration::from_nanos(degree * degree_per_nano_second + self.min_pulse_width.as_nanos() as u64); + if self.max_pulse_width < duration { + duration = self.max_pulse_width; + } + + self.pwm.write(duration); + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + let prg = PioPwmProgram::new(&mut common); + let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg); + let mut servo = ServoBuilder::new(pwm_pio) + .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo + .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment. + .set_max_pulse_width(Duration::from_micros(2600)) // Along with this value. + .build(); + + servo.start(); + + let mut degree = 0; + loop { + degree = (degree + 1) % 120; + servo.rotate(degree); + Timer::after_millis(50).await; + } +} diff --git a/embassy/examples/rp23/src/bin/pio_stepper.rs b/embassy/examples/rp23/src/bin/pio_stepper.rs new file mode 100644 index 0000000..4fabe78 --- /dev/null +++ b/embassy/examples/rp23/src/bin/pio_stepper.rs @@ -0,0 +1,54 @@ +//! This example shows how to use the PIO module in the RP2040 to implement a stepper motor driver +//! for a 5-wire stepper such as the 28BYJ-48. You can halt an ongoing rotation by dropping the future. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram}; +use embassy_time::{with_timeout, Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, irq0, sm0, .. + } = Pio::new(p.PIO0, Irqs); + + let prg = PioStepperProgram::new(&mut common); + let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg); + stepper.set_frequency(120); + loop { + info!("CW full steps"); + stepper.step(1000).await; + + info!("CCW full steps, drop after 1 sec"); + if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)) + .await + .is_err() + { + info!("Time's up!"); + Timer::after(Duration::from_secs(1)).await; + } + + info!("CW half steps"); + stepper.step_half(1000).await; + + info!("CCW half steps"); + stepper.step_half(-1000).await; + } +} diff --git a/embassy/examples/rp23/src/bin/pio_uart.rs b/embassy/examples/rp23/src/bin/pio_uart.rs new file mode 100644 index 0000000..f8398c2 --- /dev/null +++ b/embassy/examples/rp23/src/bin/pio_uart.rs @@ -0,0 +1,202 @@ +//! This example shows how to use the PIO module in the RP2040 chip to implement a duplex UART. +//! The PIO module is a very powerful peripheral that can be used to implement many different +//! protocols. It is a very flexible state machine that can be programmed to do almost anything. +//! +//! This example opens up a USB device that implements a CDC ACM serial port. It then uses the +//! PIO module to implement a UART that is connected to the USB serial port. This allows you to +//! communicate with a device connected to the RP2040 over USB serial. + +#![no_std] +#![no_main] +#![allow(async_fn_in_trait)] + +use defmt::{info, panic, trace}; +use embassy_executor::Spawner; +use embassy_futures::join::{join, join3}; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::{PIO0, USB}; +use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; +use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_rp::{bind_interrupts, pio}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pipe::Pipe; +use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use embedded_io_async::{Read, Write}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; + PIO0_IRQ_0 => pio::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("PIO UART example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // PIO UART setup + let pio::Pio { + mut common, sm0, sm1, .. + } = pio::Pio::new(p.PIO0, Irqs); + + let tx_program = PioUartTxProgram::new(&mut common); + let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program); + + let rx_program = PioUartRxProgram::new(&mut common); + let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program); + + // Pipe setup + let mut usb_pipe: Pipe = Pipe::new(); + let (mut usb_pipe_reader, mut usb_pipe_writer) = usb_pipe.split(); + + let mut uart_pipe: Pipe = Pipe::new(); + let (mut uart_pipe_reader, mut uart_pipe_writer) = uart_pipe.split(); + + let (mut usb_tx, mut usb_rx) = class.split(); + + // Read + write from USB + let usb_future = async { + loop { + info!("Wait for USB connection"); + usb_rx.wait_connection().await; + info!("Connected"); + let _ = join( + usb_read(&mut usb_rx, &mut uart_pipe_writer), + usb_write(&mut usb_tx, &mut usb_pipe_reader), + ) + .await; + info!("Disconnected"); + } + }; + + // Read + write from UART + let uart_future = join( + uart_read(&mut uart_rx, &mut usb_pipe_writer), + uart_write(&mut uart_tx, &mut uart_pipe_reader), + ); + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join3(usb_fut, usb_future, uart_future).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +/// Read from the USB and write it to the UART TX pipe +async fn usb_read<'d, T: Instance + 'd>( + usb_rx: &mut Receiver<'d, Driver<'d, T>>, + uart_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = usb_rx.read_packet(&mut buf).await?; + let data = &buf[..n]; + trace!("USB IN: {:x}", data); + (*uart_pipe_writer).write(data).await; + } +} + +/// Read from the USB TX pipe and write it to the USB +async fn usb_write<'d, T: Instance + 'd>( + usb_tx: &mut Sender<'d, Driver<'d, T>>, + usb_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = (*usb_pipe_reader).read(&mut buf).await; + let data = &buf[..n]; + trace!("USB OUT: {:x}", data); + usb_tx.write_packet(&data).await?; + } +} + +/// Read from the UART and write it to the USB TX pipe +async fn uart_read( + uart_rx: &mut PioUartRx<'_, PIO, SM>, + usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, +) -> ! { + let mut buf = [0; 64]; + loop { + let n = uart_rx.read(&mut buf).await.expect("UART read error"); + if n == 0 { + continue; + } + let data = &buf[..n]; + trace!("UART IN: {:x}", buf); + (*usb_pipe_writer).write(data).await; + } +} + +/// Read from the UART TX pipe and write it to the UART +async fn uart_write( + uart_tx: &mut PioUartTx<'_, PIO, SM>, + uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, +) -> ! { + let mut buf = [0; 64]; + loop { + let n = (*uart_pipe_reader).read(&mut buf).await; + let data = &buf[..n]; + trace!("UART OUT: {:x}", data); + let _ = uart_tx.write(&data).await; + } +} diff --git a/embassy/examples/rp23/src/bin/pio_ws2812.rs b/embassy/examples/rp23/src/bin/pio_ws2812.rs new file mode 100644 index 0000000..4d25823 --- /dev/null +++ b/embassy/examples/rp23/src/bin/pio_ws2812.rs @@ -0,0 +1,73 @@ +//! This example shows powerful PIO module in the RP2040 chip to communicate with WS2812 LED modules. +//! See (https://www.sparkfun.com/categories/tags/ws2812) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; +use embassy_time::{Duration, Ticker}; +use smart_leds::RGB8; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +/// Input a value 0 to 255 to get a color value +/// The colours are a transition r - g - b - back to r. +fn wheel(mut wheel_pos: u8) -> RGB8 { + wheel_pos = 255 - wheel_pos; + if wheel_pos < 85 { + return (255 - wheel_pos * 3, 0, wheel_pos * 3).into(); + } + if wheel_pos < 170 { + wheel_pos -= 85; + return (0, wheel_pos * 3, 255 - wheel_pos * 3).into(); + } + wheel_pos -= 170; + (wheel_pos * 3, 255 - wheel_pos * 3, 0).into() +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start"); + let p = embassy_rp::init(Default::default()); + + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + // This is the number of leds in the string. Helpfully, the sparkfun thing plus and adafruit + // feather boards for the 2040 both have one built in. + const NUM_LEDS: usize = 1; + let mut data = [RGB8::default(); NUM_LEDS]; + + // Common neopixel pins: + // Thing plus: 8 + // Adafruit Feather: 16; Adafruit Feather+RFM95: 4 + let program = PioWs2812Program::new(&mut common); + let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program); + + // Loop forever making RGB values and pushing them out to the WS2812. + let mut ticker = Ticker::every(Duration::from_millis(10)); + loop { + for j in 0..(256 * 5) { + debug!("New Colors:"); + for i in 0..NUM_LEDS { + data[i] = wheel((((i * 256) as u16 / NUM_LEDS as u16 + j as u16) & 255) as u8); + debug!("R: {} G: {} B: {}", data[i].r, data[i].g, data[i].b); + } + ws2812.write(&data).await; + + ticker.next().await; + } + } +} diff --git a/embassy/examples/rp23/src/bin/pwm.rs b/embassy/examples/rp23/src/bin/pwm.rs new file mode 100644 index 0000000..ed3c94f --- /dev/null +++ b/embassy/examples/rp23/src/bin/pwm.rs @@ -0,0 +1,84 @@ +//! This example shows how to use PWM (Pulse Width Modulation) in the RP235x chip. +//! +//! We demonstrate two ways of using PWM: +//! 1. Via config +//! 2. Via setting a duty cycle + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::{PIN_25, PIN_4, PWM_SLICE2, PWM_SLICE4}; +use embassy_rp::pwm::{Config, Pwm, SetDutyCycle}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + spawner.spawn(pwm_set_config(p.PWM_SLICE4, p.PIN_25)).unwrap(); + spawner.spawn(pwm_set_dutycycle(p.PWM_SLICE2, p.PIN_4)).unwrap(); +} + +/// Demonstrate PWM by modifying & applying the config +/// +/// Using the onboard led, if You are using a different Board than plain Pico2 (i.e. W variant) +/// you must use another slice & pin and an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_config(slice4: PWM_SLICE4, pin25: PIN_25) { + let mut c = Config::default(); + c.top = 32_768; + c.compare_b = 8; + let mut pwm = Pwm::new_output_b(slice4, pin25, c.clone()); + + loop { + info!("current LED duty cycle: {}/32768", c.compare_b); + Timer::after_secs(1).await; + c.compare_b = c.compare_b.rotate_left(4); + pwm.set_config(&c); + } +} + +/// Demonstrate PWM by setting duty cycle +/// +/// Using GP4 in Slice2, make sure to use an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_dutycycle(slice2: PWM_SLICE2, pin4: PIN_4) { + // If we aim for a specific frequency, here is how we can calculate the top value. + // The top value sets the period of the PWM cycle, so a counter goes from 0 to top and then wraps around to 0. + // Every such wraparound is one PWM cycle. So here is how we get 25KHz: + let desired_freq_hz = 25_000; + let clock_freq_hz = embassy_rp::clocks::clk_sys_freq(); + let divider = 16u8; + let period = (clock_freq_hz / (desired_freq_hz * divider as u32)) as u16 - 1; + + let mut c = Config::default(); + c.top = period; + c.divider = divider.into(); + + let mut pwm = Pwm::new_output_a(slice2, pin4, c.clone()); + + loop { + // 100% duty cycle, fully on + pwm.set_duty_cycle_fully_on().unwrap(); + Timer::after_secs(1).await; + + // 66% duty cycle. Expressed as simple percentage. + pwm.set_duty_cycle_percent(66).unwrap(); + Timer::after_secs(1).await; + + // 25% duty cycle. Expressed as 32768/4 = 8192. + pwm.set_duty_cycle(c.top / 4).unwrap(); + Timer::after_secs(1).await; + + // 0% duty cycle, fully off. + pwm.set_duty_cycle_fully_off().unwrap(); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp23/src/bin/pwm_input.rs b/embassy/examples/rp23/src/bin/pwm_input.rs new file mode 100644 index 0000000..ef87fe8 --- /dev/null +++ b/embassy/examples/rp23/src/bin/pwm_input.rs @@ -0,0 +1,31 @@ +//! This example shows how to use the PWM module to measure the frequency of an input signal. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Pull; +use embassy_rp::pwm::{Config, InputMode, Pwm}; +use embassy_time::{Duration, Ticker}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let cfg: Config = Default::default(); + let pwm = Pwm::new_input(p.PWM_SLICE2, p.PIN_5, Pull::None, InputMode::RisingEdge, cfg); + + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + info!("Input frequency: {} Hz", pwm.counter()); + pwm.set_counter(0); + ticker.next().await; + } +} diff --git a/embassy/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs b/embassy/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs new file mode 100644 index 0000000..0682888 --- /dev/null +++ b/embassy/examples/rp23/src/bin/pwm_tb6612fng_motor_driver.rs @@ -0,0 +1,110 @@ +//! # PWM TB6612FNG motor driver +//! +//! This example shows the use of a TB6612FNG motor driver. The driver is built on top of embedded_hal and the example demonstrates how embassy_rp can be used to interact with ist. + +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::config::Config; +use embassy_rp::gpio::Output; +use embassy_rp::{gpio, peripherals, pwm}; +use embassy_time::{Duration, Timer}; +use tb6612fng::{DriveCommand, Motor, Tb6612fng}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +assign_resources! { + motor: MotorResources { + standby_pin: PIN_22, + left_slice: PWM_SLICE6, + left_pwm_pin: PIN_28, + left_forward_pin: PIN_21, + left_backward_pin: PIN_20, + right_slice: PWM_SLICE5, + right_pwm_pin: PIN_27, + right_forward_pin: PIN_19, + right_backward_pin: PIN_18, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Config::default()); + let s = split_resources!(p); + let r = s.motor; + + // we want a PWM frequency of 10KHz, especially cheaper motors do not respond well to higher frequencies + let desired_freq_hz = 10_000; + let clock_freq_hz = embassy_rp::clocks::clk_sys_freq(); + let divider = 16u8; + let period = (clock_freq_hz / (desired_freq_hz * divider as u32)) as u16 - 1; + + // we need a standby output and two motors to construct a full TB6612FNG + + // standby pin + let stby = Output::new(r.standby_pin, gpio::Level::Low); + + // motor A, here defined to be the left motor + let left_fwd = gpio::Output::new(r.left_forward_pin, gpio::Level::Low); + let left_bckw = gpio::Output::new(r.left_backward_pin, gpio::Level::Low); + let mut left_speed = pwm::Config::default(); + left_speed.top = period; + left_speed.divider = divider.into(); + let left_pwm = pwm::Pwm::new_output_a(r.left_slice, r.left_pwm_pin, left_speed); + let left_motor = Motor::new(left_fwd, left_bckw, left_pwm).unwrap(); + + // motor B, here defined to be the right motor + let right_fwd = gpio::Output::new(r.right_forward_pin, gpio::Level::Low); + let right_bckw = gpio::Output::new(r.right_backward_pin, gpio::Level::Low); + let mut right_speed = pwm::Config::default(); + right_speed.top = period; + right_speed.divider = divider.into(); + let right_pwm = pwm::Pwm::new_output_b(r.right_slice, r.right_pwm_pin, right_speed); + let right_motor = Motor::new(right_fwd, right_bckw, right_pwm).unwrap(); + + // construct the motor driver + let mut control = Tb6612fng::new(left_motor, right_motor, stby).unwrap(); + + loop { + // wake up the motor driver + info!("end standby"); + control.disable_standby().unwrap(); + Timer::after(Duration::from_millis(100)).await; + + // drive a straight line forward at 20% speed for 5s + info!("drive straight"); + control.motor_a.drive(DriveCommand::Forward(80)).unwrap(); + control.motor_b.drive(DriveCommand::Forward(80)).unwrap(); + Timer::after(Duration::from_secs(5)).await; + + // coast for 2s + info!("coast"); + control.motor_a.drive(DriveCommand::Stop).unwrap(); + control.motor_b.drive(DriveCommand::Stop).unwrap(); + Timer::after(Duration::from_secs(2)).await; + + // actively brake + info!("brake"); + control.motor_a.drive(DriveCommand::Brake).unwrap(); + control.motor_b.drive(DriveCommand::Brake).unwrap(); + Timer::after(Duration::from_secs(1)).await; + + // slowly turn for 3s + info!("turn"); + control.motor_a.drive(DriveCommand::Backward(50)).unwrap(); + control.motor_b.drive(DriveCommand::Forward(50)).unwrap(); + Timer::after(Duration::from_secs(3)).await; + + // and put the driver in standby mode and wait for 5s + info!("standby"); + control.enable_standby().unwrap(); + Timer::after(Duration::from_secs(5)).await; + } +} diff --git a/embassy/examples/rp23/src/bin/rosc.rs b/embassy/examples/rp23/src/bin/rosc.rs new file mode 100644 index 0000000..a096f0b --- /dev/null +++ b/embassy/examples/rp23/src/bin/rosc.rs @@ -0,0 +1,36 @@ +//! This example test the RP Pico on board LED. +//! +//! It does not work with the RP Pico W board. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::{clocks, gpio}; +use embassy_time::Timer; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_rp::config::Config::default(); + config.clocks = clocks::ClockConfig::rosc(); + let p = embassy_rp::init(config); + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + info!("led on!"); + led.set_high(); + Timer::after_secs(1).await; + + info!("led off!"); + led.set_low(); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp23/src/bin/shared_bus.rs b/embassy/examples/rp23/src/bin/shared_bus.rs new file mode 100644 index 0000000..2151ccb --- /dev/null +++ b/embassy/examples/rp23/src/bin/shared_bus.rs @@ -0,0 +1,120 @@ +//! This example shows how to share (async) I2C and SPI buses between multiple devices. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; +use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{AnyPin, Level, Output}; +use embassy_rp::i2c::{self, I2c, InterruptHandler}; +use embassy_rp::peripherals::{I2C1, SPI1}; +use embassy_rp::spi::{self, Spi}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +type Spi1Bus = Mutex>; +type I2c1Bus = Mutex>; + +bind_interrupts!(struct Irqs { + I2C1_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + // Shared I2C bus + let i2c = I2c::new_async(p.I2C1, p.PIN_15, p.PIN_14, Irqs, i2c::Config::default()); + static I2C_BUS: StaticCell = StaticCell::new(); + let i2c_bus = I2C_BUS.init(Mutex::new(i2c)); + + spawner.must_spawn(i2c_task_a(i2c_bus)); + spawner.must_spawn(i2c_task_b(i2c_bus)); + + // Shared SPI bus + let spi_cfg = spi::Config::default(); + let spi = Spi::new(p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, spi_cfg); + static SPI_BUS: StaticCell = StaticCell::new(); + let spi_bus = SPI_BUS.init(Mutex::new(spi)); + + // Chip select pins for the SPI devices + let cs_a = Output::new(AnyPin::from(p.PIN_0), Level::High); + let cs_b = Output::new(AnyPin::from(p.PIN_1), Level::High); + + spawner.must_spawn(spi_task_a(spi_bus, cs_a)); + spawner.must_spawn(spi_task_b(spi_bus, cs_b)); +} + +#[embassy_executor::task] +async fn i2c_task_a(i2c_bus: &'static I2c1Bus) { + let i2c_dev = I2cDevice::new(i2c_bus); + let _sensor = DummyI2cDeviceDriver::new(i2c_dev, 0xC0); + loop { + info!("i2c task A"); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn i2c_task_b(i2c_bus: &'static I2c1Bus) { + let i2c_dev = I2cDevice::new(i2c_bus); + let _sensor = DummyI2cDeviceDriver::new(i2c_dev, 0xDE); + loop { + info!("i2c task B"); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn spi_task_a(spi_bus: &'static Spi1Bus, cs: Output<'static>) { + let spi_dev = SpiDevice::new(spi_bus, cs); + let _sensor = DummySpiDeviceDriver::new(spi_dev); + loop { + info!("spi task A"); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn spi_task_b(spi_bus: &'static Spi1Bus, cs: Output<'static>) { + let spi_dev = SpiDevice::new(spi_bus, cs); + let _sensor = DummySpiDeviceDriver::new(spi_dev); + loop { + info!("spi task B"); + Timer::after_secs(1).await; + } +} + +// Dummy I2C device driver, using `embedded-hal-async` +struct DummyI2cDeviceDriver { + _i2c: I2C, +} + +impl DummyI2cDeviceDriver { + fn new(i2c_dev: I2C, _address: u8) -> Self { + Self { _i2c: i2c_dev } + } +} + +// Dummy SPI device driver, using `embedded-hal-async` +struct DummySpiDeviceDriver { + _spi: SPI, +} + +impl DummySpiDeviceDriver { + fn new(spi_dev: SPI) -> Self { + Self { _spi: spi_dev } + } +} diff --git a/embassy/examples/rp23/src/bin/sharing.rs b/embassy/examples/rp23/src/bin/sharing.rs new file mode 100644 index 0000000..68eb5d1 --- /dev/null +++ b/embassy/examples/rp23/src/bin/sharing.rs @@ -0,0 +1,155 @@ +//! This example shows some common strategies for sharing resources between tasks. +//! +//! We demonstrate five different ways of sharing, covering different use cases: +//! - Atomics: This method is used for simple values, such as bool and u8..u32 +//! - Blocking Mutex: This is used for sharing non-async things, using Cell/RefCell for interior mutability. +//! - Async Mutex: This is used for sharing async resources, where you need to hold the lock across await points. +//! The async Mutex has interior mutability built-in, so no RefCell is needed. +//! - Cell: For sharing Copy types between tasks running on the same executor. +//! - RefCell: When you want &mut access to a value shared between tasks running on the same executor. +//! +//! More information: https://embassy.dev/book/#_sharing_peripherals_between_tasks + +#![no_std] +#![no_main] + +use core::cell::{Cell, RefCell}; +use core::sync::atomic::{AtomicU32, Ordering}; + +use cortex_m_rt::entry; +use defmt::info; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_rp::block::ImageDef; +use embassy_rp::clocks::RoscRng; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{self, InterruptHandler, UartTx}; +use embassy_rp::{bind_interrupts, interrupt}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::{blocking_mutex, mutex}; +use embassy_time::{Duration, Ticker}; +use rand::RngCore; +use static_cell::{ConstStaticCell, StaticCell}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +type UartAsyncMutex = mutex::Mutex>; + +struct MyType { + inner: u32, +} + +static EXECUTOR_HI: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +// Use Atomics for simple values +static ATOMIC: AtomicU32 = AtomicU32::new(0); + +// Use blocking Mutex with Cell/RefCell for sharing non-async things +static MUTEX_BLOCKING: blocking_mutex::Mutex> = + blocking_mutex::Mutex::new(RefCell::new(MyType { inner: 0 })); + +bind_interrupts!(struct Irqs { + UART0_IRQ => InterruptHandler; +}); + +#[interrupt] +unsafe fn SWI_IRQ_0() { + EXECUTOR_HI.on_interrupt() +} + +#[entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + let uart = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, uart::Config::default()); + // Use the async Mutex for sharing async things (built-in interior mutability) + static UART: StaticCell = StaticCell::new(); + let uart = UART.init(mutex::Mutex::new(uart)); + + // High-priority executor: runs in interrupt mode + interrupt::SWI_IRQ_0.set_priority(Priority::P3); + let spawner = EXECUTOR_HI.start(interrupt::SWI_IRQ_0); + spawner.must_spawn(task_a(uart)); + + // Low priority executor: runs in thread mode + let executor = EXECUTOR_LOW.init(Executor::new()); + executor.run(|spawner| { + // No Mutex needed when sharing between tasks running on the same executor + + // Use Cell for Copy-types + static CELL: ConstStaticCell> = ConstStaticCell::new(Cell::new([0; 4])); + let cell = CELL.take(); + + // Use RefCell for &mut access + static REF_CELL: ConstStaticCell> = ConstStaticCell::new(RefCell::new(MyType { inner: 0 })); + let ref_cell = REF_CELL.take(); + + spawner.must_spawn(task_b(uart, cell, ref_cell)); + spawner.must_spawn(task_c(cell, ref_cell)); + }); +} + +#[embassy_executor::task] +async fn task_a(uart: &'static UartAsyncMutex) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + let random = RoscRng.next_u32(); + + { + let mut uart = uart.lock().await; + uart.write(b"task a").await.unwrap(); + // The uart lock is released when it goes out of scope + } + + ATOMIC.store(random, Ordering::Relaxed); + + MUTEX_BLOCKING.lock(|x| x.borrow_mut().inner = random); + + ticker.next().await; + } +} + +#[embassy_executor::task] +async fn task_b(uart: &'static UartAsyncMutex, cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + let random = RoscRng.next_u32(); + + uart.lock().await.write(b"task b").await.unwrap(); + + cell.set(random.to_be_bytes()); + + ref_cell.borrow_mut().inner = random; + + ticker.next().await; + } +} + +#[embassy_executor::task] +async fn task_c(cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + info!("======================="); + + let atomic_val = ATOMIC.load(Ordering::Relaxed); + info!("atomic: {}", atomic_val); + + MUTEX_BLOCKING.lock(|x| { + let val = x.borrow().inner; + info!("blocking mutex: {}", val); + }); + + let cell_val = cell.get(); + info!("cell: {:?}", cell_val); + + let ref_cell_val = ref_cell.borrow().inner; + info!("ref_cell: {:?}", ref_cell_val); + + ticker.next().await; + } +} diff --git a/embassy/examples/rp23/src/bin/spi.rs b/embassy/examples/rp23/src/bin/spi.rs new file mode 100644 index 0000000..aacb8c7 --- /dev/null +++ b/embassy/examples/rp23/src/bin/spi.rs @@ -0,0 +1,51 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! +//! Example for resistive touch sensor in Waveshare Pico-ResTouch + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::spi::Spi; +use embassy_rp::{gpio, spi}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // Example for resistive touch sensor in Waveshare Pico-ResTouch + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let touch_cs = p.PIN_16; + + // create SPI + let mut config = spi::Config::default(); + config.frequency = 2_000_000; + let mut spi = Spi::new_blocking(p.SPI1, clk, mosi, miso, config); + + // Configure CS + let mut cs = Output::new(touch_cs, Level::Low); + + loop { + cs.set_low(); + let mut buf = [0x90, 0x00, 0x00, 0xd0, 0x00, 0x00]; + spi.blocking_transfer_in_place(&mut buf).unwrap(); + cs.set_high(); + + let x = (buf[1] as u32) << 5 | (buf[2] as u32) >> 3; + let y = (buf[4] as u32) << 5 | (buf[5] as u32) >> 3; + + info!("touch: {=u32} {=u32}", x, y); + } +} diff --git a/embassy/examples/rp23/src/bin/spi_async.rs b/embassy/examples/rp23/src/bin/spi_async.rs new file mode 100644 index 0000000..ac7f02f --- /dev/null +++ b/embassy/examples/rp23/src/bin/spi_async.rs @@ -0,0 +1,36 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! No specific hardware is specified in this example. If you connect pin 11 and 12 you should get the same data back. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::spi::{Config, Spi}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + + let mut spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + loop { + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + info!("{:?}", rx_buf); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/rp23/src/bin/spi_display.rs b/embassy/examples/rp23/src/bin/spi_display.rs new file mode 100644 index 0000000..6b7c078 --- /dev/null +++ b/embassy/examples/rp23/src/bin/spi_display.rs @@ -0,0 +1,182 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2350 chip. +//! +//! Example written for a display using the ST7789 chip. Possibly the Waveshare Pico-ResTouch +//! (https://www.waveshare.com/wiki/Pico-ResTouch-LCD-2.8) + +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use defmt::*; +use display_interface_spi::SPIInterface; +use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::spi; +use embassy_rp::spi::{Blocking, Spi}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::Delay; +use embedded_graphics::image::{Image, ImageRawLE}; +use embedded_graphics::mono_font::ascii::FONT_10X20; +use embedded_graphics::mono_font::MonoTextStyle; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; +use embedded_graphics::text::Text; +use mipidsi::models::ST7789; +use mipidsi::options::{Orientation, Rotation}; +use mipidsi::Builder; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +use crate::touch::Touch; + +const DISPLAY_FREQ: u32 = 64_000_000; +const TOUCH_FREQ: u32 = 200_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let bl = p.PIN_13; + let rst = p.PIN_15; + let display_cs = p.PIN_9; + let dcx = p.PIN_8; + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let touch_cs = p.PIN_16; + //let touch_irq = p.PIN_17; + + // create SPI + let mut display_config = spi::Config::default(); + display_config.frequency = DISPLAY_FREQ; + display_config.phase = spi::Phase::CaptureOnSecondTransition; + display_config.polarity = spi::Polarity::IdleHigh; + let mut touch_config = spi::Config::default(); + touch_config.frequency = TOUCH_FREQ; + touch_config.phase = spi::Phase::CaptureOnSecondTransition; + touch_config.polarity = spi::Polarity::IdleHigh; + + let spi: Spi<'_, _, Blocking> = Spi::new_blocking(p.SPI1, clk, mosi, miso, touch_config.clone()); + let spi_bus: Mutex = Mutex::new(RefCell::new(spi)); + + let display_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(display_cs, Level::High), display_config); + let touch_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(touch_cs, Level::High), touch_config); + + let mut touch = Touch::new(touch_spi); + + let dcx = Output::new(dcx, Level::Low); + let rst = Output::new(rst, Level::Low); + // dcx: 0 = command, 1 = data + + // Enable LCD backlight + let _bl = Output::new(bl, Level::High); + + // display interface abstraction from SPI and DC + let di = SPIInterface::new(display_spi, dcx); + + // Define the display from the display interface and initialize it + let mut display = Builder::new(ST7789, di) + .display_size(240, 320) + .reset_pin(rst) + .orientation(Orientation::new().rotate(Rotation::Deg90)) + .init(&mut Delay) + .unwrap(); + display.clear(Rgb565::BLACK).unwrap(); + + let raw_image_data = ImageRawLE::new(include_bytes!("../../assets/ferris.raw"), 86); + let ferris = Image::new(&raw_image_data, Point::new(34, 68)); + + // Display the image + ferris.draw(&mut display).unwrap(); + + let style = MonoTextStyle::new(&FONT_10X20, Rgb565::GREEN); + Text::new( + "Hello embedded_graphics \n + embassy + RP2040!", + Point::new(20, 200), + style, + ) + .draw(&mut display) + .unwrap(); + + loop { + if let Some((x, y)) = touch.read() { + let style = PrimitiveStyleBuilder::new().fill_color(Rgb565::BLUE).build(); + + Rectangle::new(Point::new(x - 1, y - 1), Size::new(3, 3)) + .into_styled(style) + .draw(&mut display) + .unwrap(); + } + } +} + +/// Driver for the XPT2046 resistive touchscreen sensor +mod touch { + use embedded_hal_1::spi::{Operation, SpiDevice}; + + struct Calibration { + x1: i32, + x2: i32, + y1: i32, + y2: i32, + sx: i32, + sy: i32, + } + + const CALIBRATION: Calibration = Calibration { + x1: 3880, + x2: 340, + y1: 262, + y2: 3850, + sx: 320, + sy: 240, + }; + + pub struct Touch { + spi: SPI, + } + + impl Touch + where + SPI: SpiDevice, + { + pub fn new(spi: SPI) -> Self { + Self { spi } + } + + pub fn read(&mut self) -> Option<(i32, i32)> { + let mut x = [0; 2]; + let mut y = [0; 2]; + self.spi + .transaction(&mut [ + Operation::Write(&[0x90]), + Operation::Read(&mut x), + Operation::Write(&[0xd0]), + Operation::Read(&mut y), + ]) + .unwrap(); + + let x = (u16::from_be_bytes(x) >> 3) as i32; + let y = (u16::from_be_bytes(y) >> 3) as i32; + + let cal = &CALIBRATION; + + let x = ((x - cal.x1) * cal.sx / (cal.x2 - cal.x1)).clamp(0, cal.sx); + let y = ((y - cal.y1) * cal.sy / (cal.y2 - cal.y1)).clamp(0, cal.sy); + if x == 0 && y == 0 { + None + } else { + Some((x, y)) + } + } + } +} diff --git a/embassy/examples/rp23/src/bin/spi_sdmmc.rs b/embassy/examples/rp23/src/bin/spi_sdmmc.rs new file mode 100644 index 0000000..cfc38df --- /dev/null +++ b/embassy/examples/rp23/src/bin/spi_sdmmc.rs @@ -0,0 +1,88 @@ +//! This example shows how to use `embedded-sdmmc` with the RP2040 chip, over SPI. +//! +//! The example will attempt to read a file `MY_FILE.TXT` from the root directory +//! of the SD card and print its contents. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_embedded_hal::SetConfig; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::spi::Spi; +use embassy_rp::{gpio, spi}; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_sdmmc::sdcard::{DummyCsPin, SdCard}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +struct DummyTimesource(); + +impl embedded_sdmmc::TimeSource for DummyTimesource { + fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { + embedded_sdmmc::Timestamp { + year_since_1970: 0, + zero_indexed_month: 0, + zero_indexed_day: 0, + hours: 0, + minutes: 0, + seconds: 0, + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + embassy_rp::pac::SIO.spinlock(31).write_value(1); + let p = embassy_rp::init(Default::default()); + + // SPI clock needs to be running at <= 400kHz during initialization + let mut config = spi::Config::default(); + config.frequency = 400_000; + let spi = Spi::new_blocking(p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, config); + // Use a dummy cs pin here, for embedded-hal SpiDevice compatibility reasons + let spi_dev = ExclusiveDevice::new_no_delay(spi, DummyCsPin); + // Real cs pin + let cs = Output::new(p.PIN_16, Level::High); + + let sdcard = SdCard::new(spi_dev, cs, embassy_time::Delay); + info!("Card size is {} bytes", sdcard.num_bytes().unwrap()); + + // Now that the card is initialized, the SPI clock can go faster + let mut config = spi::Config::default(); + config.frequency = 16_000_000; + sdcard.spi(|dev| SetConfig::set_config(dev.bus_mut(), &config)).ok(); + + // Now let's look for volumes (also known as partitions) on our block device. + // To do this we need a Volume Manager. It will take ownership of the block device. + let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, DummyTimesource()); + + // Try and access Volume 0 (i.e. the first partition). + // The volume object holds information about the filesystem on that volume. + let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0)).unwrap(); + info!("Volume 0: {:?}", defmt::Debug2Format(&volume0)); + + // Open the root directory (mutably borrows from the volume). + let mut root_dir = volume0.open_root_dir().unwrap(); + + // Open a file called "MY_FILE.TXT" in the root directory + // This mutably borrows the directory. + let mut my_file = root_dir + .open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly) + .unwrap(); + + // Print the contents of the file + while !my_file.is_eof() { + let mut buf = [0u8; 32]; + if let Ok(n) = my_file.read(&mut buf) { + info!("{:a}", buf[..n]); + } + } + + loop {} +} diff --git a/embassy/examples/rp23/src/bin/trng.rs b/embassy/examples/rp23/src/bin/trng.rs new file mode 100644 index 0000000..8251ebd --- /dev/null +++ b/embassy/examples/rp23/src/bin/trng.rs @@ -0,0 +1,54 @@ +//! This example shows TRNG usage + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::TRNG; +use embassy_rp::trng::Trng; +use embassy_time::Timer; +use rand::RngCore; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + TRNG_IRQ => embassy_rp::trng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_rp::init(Default::default()); + + // Initialize the TRNG with default configuration + let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default()); + // A buffer to collect random bytes in. + let mut randomness = [0u8; 58]; + + let mut led = Output::new(peripherals.PIN_25, Level::Low); + + loop { + trng.fill_bytes(&mut randomness).await; + info!("Random bytes async {}", &randomness); + trng.blocking_fill_bytes(&mut randomness); + info!("Random bytes blocking {}", &randomness); + let random_u32 = trng.next_u32(); + let random_u64 = trng.next_u64(); + info!("Random u32 {} u64 {}", random_u32, random_u64); + // Random number of blinks between 0 and 31 + let blinks = random_u32 % 32; + for _ in 0..blinks { + led.set_high(); + Timer::after_millis(20).await; + led.set_low(); + Timer::after_millis(20).await; + } + Timer::after_millis(1000).await; + } +} diff --git a/embassy/examples/rp23/src/bin/uart.rs b/embassy/examples/rp23/src/bin/uart.rs new file mode 100644 index 0000000..fe28bb0 --- /dev/null +++ b/embassy/examples/rp23/src/bin/uart.rs @@ -0,0 +1,30 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! No specific hardware is specified in this example. Only output on pin 0 is tested. +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::uart; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let config = uart::Config::default(); + let mut uart = uart::Uart::new_blocking(p.UART1, p.PIN_4, p.PIN_5, config); + uart.blocking_write("Hello World!\r\n".as_bytes()).unwrap(); + + loop { + uart.blocking_write("hello there!\r\n".as_bytes()).unwrap(); + cortex_m::asm::delay(1_000_000); + } +} diff --git a/embassy/examples/rp23/src/bin/uart_buffered_split.rs b/embassy/examples/rp23/src/bin/uart_buffered_split.rs new file mode 100644 index 0000000..9ed1307 --- /dev/null +++ b/embassy/examples/rp23/src/bin/uart_buffered_split.rs @@ -0,0 +1,63 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! No specific hardware is specified in this example. If you connect pin 0 and 1 you should get the same data back. +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config}; +use embassy_time::Timer; +use embedded_io_async::{Read, Write}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + UART0_IRQ => BufferedInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let (tx_pin, rx_pin, uart) = (p.PIN_0, p.PIN_1, p.UART0); + + static TX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); + let tx_buf = &mut TX_BUF.init([0; 16])[..]; + static RX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); + let rx_buf = &mut RX_BUF.init([0; 16])[..]; + let uart = BufferedUart::new(uart, Irqs, tx_pin, rx_pin, tx_buf, rx_buf, Config::default()); + let (mut tx, rx) = uart.split(); + + unwrap!(spawner.spawn(reader(rx))); + + info!("Writing..."); + loop { + let data = [ + 1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, + ]; + info!("TX {:?}", data); + tx.write_all(&data).await.unwrap(); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn reader(mut rx: BufferedUartRx<'static, UART0>) { + info!("Reading..."); + loop { + let mut buf = [0; 31]; + rx.read_exact(&mut buf).await.unwrap(); + info!("RX {:?}", buf); + } +} diff --git a/embassy/examples/rp23/src/bin/uart_r503.rs b/embassy/examples/rp23/src/bin/uart_r503.rs new file mode 100644 index 0000000..9aed427 --- /dev/null +++ b/embassy/examples/rp23/src/bin/uart_r503.rs @@ -0,0 +1,163 @@ +#![no_std] +#![no_main] + +use defmt::{debug, error, info}; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{Config, DataBits, InterruptHandler as UARTInterruptHandler, Parity, StopBits, Uart}; +use embassy_time::{with_timeout, Duration, Timer}; +use heapless::Vec; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(pub struct Irqs { + UART0_IRQ => UARTInterruptHandler; +}); + +const START: u16 = 0xEF01; +const ADDRESS: u32 = 0xFFFFFFFF; + +// ================================================================================ + +// Data package format +// Name Length Description +// ========================================================================================================== +// Start 2 bytes Fixed value of 0xEF01; High byte transferred first. +// Address 4 bytes Default value is 0xFFFFFFFF, which can be modified by command. +// High byte transferred first and at wrong adder value, module +// will reject to transfer. +// PID 1 byte 01H Command packet; +// 02H Data packet; Data packet shall not appear alone in executing +// processs, must follow command packet or acknowledge packet. +// 07H Acknowledge packet; +// 08H End of Data packet. +// LENGTH 2 bytes Refers to the length of package content (command packets and data packets) +// plus the length of Checksum (2 bytes). Unit is byte. Max length is 256 bytes. +// And high byte is transferred first. +// DATA - It can be commands, data, command’s parameters, acknowledge result, etc. +// (fingerprint character value, template are all deemed as data); +// SUM 2 bytes The arithmetic sum of package identifier, package length and all package +// contens. Overflowing bits are omitted. high byte is transferred first. + +// ================================================================================ + +// Checksum is calculated on 'length (2 bytes) + data (??)'. +fn compute_checksum(buf: Vec) -> u16 { + let mut checksum = 0u16; + + let check_end = buf.len(); + let checked_bytes = &buf[6..check_end]; + for byte in checked_bytes { + checksum += (*byte) as u16; + } + return checksum; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start"); + + let p = embassy_rp::init(Default::default()); + + // Initialize the fingerprint scanner. + let mut config = Config::default(); + config.baudrate = 57600; + config.stop_bits = StopBits::STOP1; + config.data_bits = DataBits::DataBits8; + config.parity = Parity::ParityNone; + + let (uart, tx_pin, tx_dma, rx_pin, rx_dma) = (p.UART0, p.PIN_16, p.DMA_CH0, p.PIN_17, p.DMA_CH1); + let uart = Uart::new(uart, tx_pin, rx_pin, Irqs, tx_dma, rx_dma, config); + let (mut tx, mut rx) = uart.split(); + + let mut vec_buf: Vec = heapless::Vec::new(); + let mut data: Vec = heapless::Vec::new(); + + let mut speeds: Vec = heapless::Vec::new(); + let _ = speeds.push(0xC8); // Slow + let _ = speeds.push(0x20); // Medium + let _ = speeds.push(0x02); // Fast + + // Cycle through the three colours Red, Blue and Purple forever. + loop { + for colour in 1..=3 { + for speed in &speeds { + // Set the data first, because the length is dependent on that. + // However, we write the length bits before we do the data. + data.clear(); + let _ = data.push(0x01); // ctrl=Breathing light + let _ = data.push(*speed); + let _ = data.push(colour as u8); // colour=Red, Blue, Purple + let _ = data.push(0x00); // times=Infinite + + // Clear buffers + vec_buf.clear(); + + // START + let _ = vec_buf.extend_from_slice(&START.to_be_bytes()[..]); + + // ADDRESS + let _ = vec_buf.extend_from_slice(&ADDRESS.to_be_bytes()[..]); + + // PID + let _ = vec_buf.extend_from_slice(&[0x01]); + + // LENGTH + let len: u16 = (1 + data.len() + 2).try_into().unwrap(); + let _ = vec_buf.extend_from_slice(&len.to_be_bytes()[..]); + + // COMMAND + let _ = vec_buf.push(0x35); // Command: AuraLedConfig + + // DATA + let _ = vec_buf.extend_from_slice(&data); + + // SUM + let chk = compute_checksum(vec_buf.clone()); + let _ = vec_buf.extend_from_slice(&chk.to_be_bytes()[..]); + + // ===== + + // Send command buffer. + let data_write: [u8; 16] = vec_buf.clone().into_array().unwrap(); + debug!(" write='{:?}'", data_write[..]); + match tx.write(&data_write).await { + Ok(..) => info!("Write successful."), + Err(e) => error!("Write error: {:?}", e), + } + + // ===== + + // Read command buffer. + let mut read_buf: [u8; 1] = [0; 1]; // Can only read one byte at a time! + let mut data_read: Vec = heapless::Vec::new(); // Save buffer. + + info!("Attempting read."); + loop { + // Some commands, like `Img2Tz()` needs longer, but we hard-code this to 200ms + // for this command. + match with_timeout(Duration::from_millis(200), rx.read(&mut read_buf)).await { + Ok(..) => { + // Extract and save read byte. + debug!(" r='{=u8:#04x}H' ({:03}D)", read_buf[0], read_buf[0]); + let _ = data_read.push(read_buf[0]).unwrap(); + } + Err(..) => break, // TimeoutError -> Ignore. + } + } + info!("Read successful"); + debug!(" read='{:?}'", data_read[..]); + + Timer::after_secs(3).await; + info!("Changing speed."); + } + + info!("Changing colour."); + } + } +} diff --git a/embassy/examples/rp23/src/bin/uart_unidir.rs b/embassy/examples/rp23/src/bin/uart_unidir.rs new file mode 100644 index 0000000..12214c4 --- /dev/null +++ b/embassy/examples/rp23/src/bin/uart_unidir.rs @@ -0,0 +1,55 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! Test TX-only and RX-only on two different UARTs. You need to connect GPIO0 to GPIO5 for +//! this to work +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::UART1; +use embassy_rp::uart::{Async, Config, InterruptHandler, UartRx, UartTx}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + UART1_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let mut uart_tx = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, Config::default()); + let uart_rx = UartRx::new(p.UART1, p.PIN_5, Irqs, p.DMA_CH1, Config::default()); + + unwrap!(spawner.spawn(reader(uart_rx))); + + info!("Writing..."); + loop { + let data = [1u8, 2, 3, 4, 5, 6, 7, 8]; + info!("TX {:?}", data); + uart_tx.write(&data).await.unwrap(); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn reader(mut rx: UartRx<'static, UART1, Async>) { + info!("Reading..."); + loop { + // read a total of 4 transmissions (32 / 8) and then print the result + let mut buf = [0; 32]; + rx.read(&mut buf).await.unwrap(); + info!("RX {:?}", buf); + } +} diff --git a/embassy/examples/rp23/src/bin/usb_hid_keyboard.rs b/embassy/examples/rp23/src/bin/usb_hid_keyboard.rs new file mode 100644 index 0000000..ec1e887 --- /dev/null +++ b/embassy/examples/rp23/src/bin/usb_hid_keyboard.rs @@ -0,0 +1,193 @@ +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Input, Pull}; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver as UsbDriver, InterruptHandler}; +use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State as HidState}; +use embassy_usb::control::OutResponse; +use embassy_usb::{Builder, Config, Handler}; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + // Create the driver, from the HAL. + let driver = UsbDriver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("HID keyboard example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + // You can also add a Microsoft OS descriptor. + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut request_handler = MyRequestHandler {}; + let mut device_handler = MyDeviceHandler::new(); + + let mut state = HidState::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + builder.handler(&mut device_handler); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 64, + }; + let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Set up the signal pin that will be used to trigger the keyboard. + let mut signal_pin = Input::new(p.PIN_16, Pull::None); + + // Enable the schmitt trigger to slightly debounce. + signal_pin.set_schmitt(true); + + let (reader, mut writer) = hid.split(); + + // Do stuff with the class! + let in_fut = async { + loop { + info!("Waiting for HIGH on pin 16"); + signal_pin.wait_for_high().await; + info!("HIGH DETECTED"); + // Create a report with the A key pressed. (no shift modifier) + let report = KeyboardReport { + keycodes: [4, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + // Send the report. + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + signal_pin.wait_for_low().await; + info!("LOW DETECTED"); + let report = KeyboardReport { + keycodes: [0, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + }; + + let out_fut = async { + reader.run(false, &mut request_handler).await; + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(in_fut, out_fut)).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} + +struct MyDeviceHandler { + configured: AtomicBool, +} + +impl MyDeviceHandler { + fn new() -> Self { + MyDeviceHandler { + configured: AtomicBool::new(false), + } + } +} + +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { + self.configured.store(false, Ordering::Relaxed); + if enabled { + info!("Device enabled"); + } else { + info!("Device disabled"); + } + } + + fn reset(&mut self) { + self.configured.store(false, Ordering::Relaxed); + info!("Bus reset, the Vbus current limit is 100mA"); + } + + fn addressed(&mut self, addr: u8) { + self.configured.store(false, Ordering::Relaxed); + info!("USB address set to: {}", addr); + } + + fn configured(&mut self, configured: bool) { + self.configured.store(configured, Ordering::Relaxed); + if configured { + info!("Device configured, it may now draw up to the configured current limit from Vbus.") + } else { + info!("Device is no longer configured, the Vbus current limit is 100mA."); + } + } +} diff --git a/embassy/examples/rp23/src/bin/usb_webusb.rs b/embassy/examples/rp23/src/bin/usb_webusb.rs new file mode 100644 index 0000000..15279ca --- /dev/null +++ b/embassy/examples/rp23/src/bin/usb_webusb.rs @@ -0,0 +1,160 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This creates a WebUSB capable device that echoes data back to the host. +//! +//! To test this in the browser (ideally host this on localhost:8080, to test the landing page +//! feature): +//! ```js +//! (async () => { +//! const device = await navigator.usb.requestDevice({ filters: [{ vendorId: 0xf569 }] }); +//! await device.open(); +//! await device.claimInterface(1); +//! device.transferIn(1, 64).then(data => console.log(data)); +//! await device.transferOut(1, new Uint8Array([1,2,3])); +//! })(); +//! ``` + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver as UsbDriver, InterruptHandler}; +use embassy_usb::class::web_usb::{Config as WebUsbConfig, State, Url, WebUsb}; +use embassy_usb::driver::{Driver, Endpoint, EndpointIn, EndpointOut}; +use embassy_usb::msos::{self, windows_version}; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +// This is a randomly generated GUID to allow clients on Windows to find our device +const DEVICE_INTERFACE_GUIDS: &[&str] = &["{AFB9A6FB-30BA-44BC-9232-806CFC875321}"]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = UsbDriver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xf569, 0x0001); + config.manufacturer = Some("Embassy"); + config.product = Some("WebUSB example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xff; + config.device_sub_class = 0x00; + config.device_protocol = 0x00; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut msos_descriptor = [0; 256]; + + let webusb_config = WebUsbConfig { + max_packet_size: 64, + vendor_code: 1, + // If defined, shows a landing page which the device manufacturer would like the user to visit in order to control their device. Suggest the user to navigate to this URL when the device is connected. + landing_url: Some(Url::new("http://localhost:8080")), + }; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + // Add the Microsoft OS Descriptor (MSOS/MOD) descriptor. + // We tell Windows that this entire device is compatible with the "WINUSB" feature, + // which causes it to use the built-in WinUSB driver automatically, which in turn + // can be used by libusb/rusb software without needing a custom driver or INF file. + // In principle you might want to call msos_feature() just on a specific function, + // if your device also has other functions that still use standard class drivers. + builder.msos_descriptor(windows_version::WIN8_1, 0); + builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + // Create classes on the builder (WebUSB just needs some setup, but doesn't return anything) + WebUsb::configure(&mut builder, &mut state, &webusb_config); + // Create some USB bulk endpoints for testing. + let mut endpoints = WebEndpoints::new(&mut builder, &webusb_config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do some WebUSB transfers. + let webusb_fut = async { + loop { + endpoints.wait_connected().await; + info!("Connected"); + endpoints.echo().await; + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, webusb_fut).await; +} + +struct WebEndpoints<'d, D: Driver<'d>> { + write_ep: D::EndpointIn, + read_ep: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> WebEndpoints<'d, D> { + fn new(builder: &mut Builder<'d, D>, config: &'d WebUsbConfig<'d>) -> Self { + let mut func = builder.function(0xff, 0x00, 0x00); + let mut iface = func.interface(); + let mut alt = iface.alt_setting(0xff, 0x00, 0x00, None); + + let write_ep = alt.endpoint_bulk_in(config.max_packet_size); + let read_ep = alt.endpoint_bulk_out(config.max_packet_size); + + WebEndpoints { write_ep, read_ep } + } + + // Wait until the device's endpoints are enabled. + async fn wait_connected(&mut self) { + self.read_ep.wait_enabled().await + } + + // Echo data back to the host. + async fn echo(&mut self) { + let mut buf = [0; 64]; + loop { + let n = self.read_ep.read(&mut buf).await.unwrap(); + let data = &buf[..n]; + info!("Data read: {:x}", data); + self.write_ep.write(data).await.unwrap(); + } + } +} diff --git a/embassy/examples/rp23/src/bin/watchdog.rs b/embassy/examples/rp23/src/bin/watchdog.rs new file mode 100644 index 0000000..efc24c4 --- /dev/null +++ b/embassy/examples/rp23/src/bin/watchdog.rs @@ -0,0 +1,56 @@ +//! This example shows how to use Watchdog in the RP2040 chip. +//! +//! It does not work with the RP Pico W board. See wifi_blinky.rs or connect external LED and resistor. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio; +use embassy_rp::watchdog::*; +use embassy_time::{Duration, Timer}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello world!"); + + let mut watchdog = Watchdog::new(p.WATCHDOG); + let mut led = Output::new(p.PIN_25, Level::Low); + + // Set the LED high for 2 seconds so we know when we're about to start the watchdog + led.set_high(); + Timer::after_secs(2).await; + + // Set to watchdog to reset if it's not fed within 1.05 seconds, and start it + watchdog.start(Duration::from_millis(1_050)); + info!("Started the watchdog timer"); + + // Blink once a second for 5 seconds, feed the watchdog timer once a second to avoid a reset + for _ in 1..=5 { + led.set_low(); + Timer::after_millis(500).await; + led.set_high(); + Timer::after_millis(500).await; + info!("Feeding watchdog"); + watchdog.feed(); + } + + info!("Stopped feeding, device will reset in 1.05 seconds"); + // Blink 10 times per second, not feeding the watchdog. + // The processor should reset in 1.05 seconds. + loop { + led.set_low(); + Timer::after_millis(100).await; + led.set_high(); + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/rp23/src/bin/zerocopy.rs b/embassy/examples/rp23/src/bin/zerocopy.rs new file mode 100644 index 0000000..d317c4b --- /dev/null +++ b/embassy/examples/rp23/src/bin/zerocopy.rs @@ -0,0 +1,99 @@ +//! This example shows how to use `zerocopy_channel` from `embassy_sync` for +//! sending large values between two tasks without copying. +//! The example also shows how to use the RP2040 ADC with DMA. +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU16, Ordering}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{self, Adc, Async, Config, InterruptHandler}; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Pull; +use embassy_rp::peripherals::DMA_CH0; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::zerocopy_channel::{Channel, Receiver, Sender}; +use embassy_time::{Duration, Ticker, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +type SampleBuffer = [u16; 512]; + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +const BLOCK_SIZE: usize = 512; +const NUM_BLOCKS: usize = 2; +static MAX: AtomicU16 = AtomicU16::new(0); + +struct AdcParts { + adc: Adc<'static, Async>, + pin: adc::Channel<'static>, + dma: DMA_CH0, +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + let adc_parts = AdcParts { + adc: Adc::new(p.ADC, Irqs, Config::default()), + pin: adc::Channel::new_pin(p.PIN_29, Pull::None), + dma: p.DMA_CH0, + }; + + static BUF: StaticCell<[SampleBuffer; NUM_BLOCKS]> = StaticCell::new(); + let buf = BUF.init([[0; BLOCK_SIZE]; NUM_BLOCKS]); + + static CHANNEL: StaticCell> = StaticCell::new(); + let channel = CHANNEL.init(Channel::new(buf)); + let (sender, receiver) = channel.split(); + + spawner.must_spawn(consumer(receiver)); + spawner.must_spawn(producer(sender, adc_parts)); + + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + ticker.next().await; + let max = MAX.load(Ordering::Relaxed); + info!("latest block's max value: {:?}", max); + } +} + +#[embassy_executor::task] +async fn producer(mut sender: Sender<'static, NoopRawMutex, SampleBuffer>, mut adc: AdcParts) { + loop { + // Obtain a free buffer from the channel + let buf = sender.send().await; + + // Fill it with data + adc.adc.read_many(&mut adc.pin, buf, 1, &mut adc.dma).await.unwrap(); + + // Notify the channel that the buffer is now ready to be received + sender.send_done(); + } +} + +#[embassy_executor::task] +async fn consumer(mut receiver: Receiver<'static, NoopRawMutex, SampleBuffer>) { + loop { + // Receive a buffer from the channel + let buf = receiver.receive().await; + + // Simulate using the data, while the producer is filling up the next buffer + Timer::after_micros(1000).await; + let max = buf.iter().max().unwrap(); + MAX.store(*max, Ordering::Relaxed); + + // Notify the channel that the buffer is now ready to be reused + receiver.receive_done(); + } +} diff --git a/embassy/examples/std/Cargo.toml b/embassy/examples/std/Cargo.toml new file mode 100644 index 0000000..e43fd77 --- /dev/null +++ b/embassy/examples/std/Cargo.toml @@ -0,0 +1,29 @@ +[package] +edition = "2021" +name = "embassy-std-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["log"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-std", "executor-thread", "log"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "std", ] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features=[ "std", "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } +embassy-net-tuntap = { version = "0.1.0", path = "../../embassy-net-tuntap" } +embassy-net-ppp = { version = "0.1.0", path = "../../embassy-net-ppp", features = ["log"]} +embedded-io-async = { version = "0.6.1" } +embedded-io-adapters = { version = "0.6.1", features = ["futures-03"] } +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.26.2" +clap = { version = "3.0.0-beta.5", features = ["derive"] } +rand_core = { version = "0.6.3", features = ["std"] } +heapless = { version = "0.8", default-features = false } +static_cell = "2" + +[profile.release] +debug = 2 diff --git a/embassy/examples/std/README.md b/embassy/examples/std/README.md new file mode 100644 index 0000000..e3a59d6 --- /dev/null +++ b/embassy/examples/std/README.md @@ -0,0 +1,23 @@ + +## Running the `embassy-net` examples + +First, create the tap0 interface. You only need to do this once. + +```sh +sudo ip tuntap add name tap0 mode tap user $USER +sudo ip link set tap0 up +sudo ip addr add 192.168.69.100/24 dev tap0 +sudo ip -6 addr add fe80::100/64 dev tap0 +sudo ip -6 addr add fdaa::100/64 dev tap0 +sudo ip -6 route add fe80::/64 dev tap0 +sudo ip -6 route add fdaa::/64 dev tap0 +``` + +Second, have something listening there. For example `nc -lp 8000` + +Then run the example located in the `examples` folder: + +```sh +cd $EMBASSY_ROOT/examples/std/ +cargo run --bin net -- --static-ip +``` diff --git a/embassy/examples/std/src/bin/net.rs b/embassy/examples/std/src/bin/net.rs new file mode 100644 index 0000000..cefa544 --- /dev/null +++ b/embassy/examples/std/src/bin/net.rs @@ -0,0 +1,96 @@ +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; +use embassy_net_tuntap::TunTapDevice; +use embassy_time::Duration; +use embedded_io_async::Write; +use heapless::Vec; +use log::*; +use rand_core::{OsRng, RngCore}; +use static_cell::StaticCell; + +#[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(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.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::ipv4_static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + dns_servers: Vec::new(), + gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + }) + } else { + Config::dhcpv4(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 + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + spawner.spawn(net_task(runner)).unwrap(); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + + socket.set_timeout(Some(Duration::from_secs(10))); + + let remote_endpoint = (Ipv4Address::new(192, 168, 69, 100), 8000); + info!("connecting to {:?}...", remote_endpoint); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + warn!("connect error: {:?}", e); + return; + } + info!("connected!"); + loop { + let r = socket.write_all(b"Hello!\n").await; + if let Err(e) = r { + warn!("write error: {:?}", e); + return; + } + } +} + +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/embassy/examples/std/src/bin/net_dns.rs b/embassy/examples/std/src/bin/net_dns.rs new file mode 100644 index 0000000..a42c5db --- /dev/null +++ b/embassy/examples/std/src/bin/net_dns.rs @@ -0,0 +1,83 @@ +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use embassy_net::dns::DnsQueryType; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; +use embassy_net_tuntap::TunTapDevice; +use heapless::Vec; +use log::*; +use rand_core::{OsRng, RngCore}; +use static_cell::StaticCell; + +#[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(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.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::ipv4_static(embassy_net::StaticConfigV4 { + 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::dhcpv4(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 + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + spawner.spawn(net_task(runner)).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/embassy/examples/std/src/bin/net_ppp.rs b/embassy/examples/std/src/bin/net_ppp.rs new file mode 100644 index 0000000..ea3fbeb --- /dev/null +++ b/embassy/examples/std/src/bin/net_ppp.rs @@ -0,0 +1,215 @@ +//! Testing against pppd: +//! +//! echo myuser $(hostname) mypass 192.168.7.10 >> /etc/ppp/pap-secrets +//! socat -v -x PTY,link=pty1,rawer PTY,link=pty2,rawer +//! sudo pppd $PWD/pty1 115200 192.168.7.1: ms-dns 8.8.4.4 ms-dns 8.8.8.8 nodetach debug local persist silent noproxyarp +//! RUST_LOG=trace cargo run --bin net_ppp -- --device pty2 +//! ping 192.168.7.10 +//! nc 192.168.7.10 1234 + +#![allow(async_fn_in_trait)] + +#[path = "../serial_port.rs"] +mod serial_port; + +use async_io::Async; +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, ConfigV4, Ipv4Cidr, Stack, StackResources}; +use embassy_net_ppp::Runner; +use embedded_io_async::Write; +use futures::io::BufReader; +use heapless::Vec; +use log::*; +use nix::sys::termios; +use rand_core::{OsRng, RngCore}; +use static_cell::StaticCell; + +use crate::serial_port::SerialPort; + +#[derive(Parser)] +#[clap(version = "1.0")] +struct Opts { + /// Serial port device name + #[clap(short, long)] + device: String, +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, embassy_net_ppp::Device<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn ppp_task(stack: Stack<'static>, mut runner: Runner<'static>, port: SerialPort) -> ! { + let port = Async::new(port).unwrap(); + let port = BufReader::new(port); + let port = adapter::FromFutures::new(port); + + let config = embassy_net_ppp::Config { + username: b"myuser", + password: b"mypass", + }; + + runner + .run(port, config, |ipv4| { + let Some(addr) = ipv4.address else { + warn!("PPP did not provide an IP address."); + return; + }; + let mut dns_servers = Vec::new(); + for s in ipv4.dns_servers.iter().flatten() { + let _ = dns_servers.push(*s); + } + let config = ConfigV4::Static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(addr, 0), + gateway: None, + dns_servers, + }); + stack.set_config_v4(config); + }) + .await + .unwrap(); + unreachable!() +} + +#[embassy_executor::task] +async fn main_task(spawner: Spawner) { + let opts: Opts = Opts::parse(); + + // Open serial port + let baudrate = termios::BaudRate::B115200; + let port = SerialPort::new(opts.device.as_str(), baudrate).unwrap(); + + // Init network device + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(embassy_net_ppp::State::<4, 4>::new()); + let (device, runner) = embassy_net_ppp::new(state); + + // Generate random seed + let mut seed = [0; 8]; + OsRng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, net_runner) = embassy_net::new( + device, + Config::default(), // don't configure IP yet + RESOURCES.init(StackResources::new()), + seed, + ); + + // Launch network task + spawner.spawn(net_task(net_runner)).unwrap(); + spawner.spawn(ppp_task(stack, runner, port)).unwrap(); + + // Then 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_time::Duration::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 EXECUTOR: StaticCell = StaticCell::new(); + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Trace) + .filter_module("polling", log::LevelFilter::Info) + .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(); + }); +} + +mod adapter { + use core::future::poll_fn; + use core::pin::Pin; + + use futures::AsyncBufReadExt; + + /// Adapter from `futures::io` traits. + #[derive(Clone)] + pub struct FromFutures { + inner: T, + } + + impl FromFutures { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + } + + impl embedded_io_async::ErrorType for FromFutures { + type Error = std::io::Error; + } + + impl embedded_io_async::Read for FromFutures { + async fn read(&mut self, buf: &mut [u8]) -> Result { + poll_fn(|cx| Pin::new(&mut self.inner).poll_read(cx, buf)).await + } + } + + impl embedded_io_async::BufRead for FromFutures { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + Pin::new(&mut self.inner).consume(amt) + } + } + + impl embedded_io_async::Write for FromFutures { + async fn write(&mut self, buf: &[u8]) -> Result { + poll_fn(|cx| Pin::new(&mut self.inner).poll_write(cx, buf)).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + poll_fn(|cx| Pin::new(&mut self.inner).poll_flush(cx)).await + } + } +} diff --git a/embassy/examples/std/src/bin/net_udp.rs b/embassy/examples/std/src/bin/net_udp.rs new file mode 100644 index 0000000..02d4d3e --- /dev/null +++ b/embassy/examples/std/src/bin/net_udp.rs @@ -0,0 +1,91 @@ +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use embassy_net::udp::{PacketMetadata, UdpSocket}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; +use embassy_net_tuntap::TunTapDevice; +use heapless::Vec; +use log::*; +use rand_core::{OsRng, RngCore}; +use static_cell::StaticCell; + +#[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(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.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::ipv4_static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + dns_servers: Vec::new(), + gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + }) + } else { + Config::dhcpv4(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 + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + spawner.spawn(net_task(runner)).unwrap(); + + // Then we can use it! + let mut rx_meta = [PacketMetadata::EMPTY; 16]; + let mut rx_buffer = [0; 4096]; + let mut tx_meta = [PacketMetadata::EMPTY; 16]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + let mut socket = UdpSocket::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); + socket.bind(9400).unwrap(); + + loop { + let (n, ep) = socket.recv_from(&mut buf).await.unwrap(); + if let Ok(s) = core::str::from_utf8(&buf[..n]) { + info!("ECHO (to {}): {}", ep, s); + } else { + info!("ECHO (to {}): bytearray len {}", ep, n); + } + socket.send_to(&buf[..n], ep).await.unwrap(); + } +} + +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/embassy/examples/std/src/bin/serial.rs b/embassy/examples/std/src/bin/serial.rs new file mode 100644 index 0000000..10c8551 --- /dev/null +++ b/embassy/examples/std/src/bin/serial.rs @@ -0,0 +1,55 @@ +#[path = "../serial_port.rs"] +mod serial_port; + +use async_io::Async; +use embassy_executor::Executor; +use embassy_time as _; +use embedded_io_async::Read; +use log::*; +use nix::sys::termios; +use static_cell::StaticCell; + +use self::serial_port::SerialPort; + +#[embassy_executor::task] +async fn run() { + // Open the serial port. + let baudrate = termios::BaudRate::B115200; + let port = SerialPort::new("/dev/ttyACM0", baudrate).unwrap(); + //let port = Spy::new(port); + + // Use async_io's reactor for async IO. + // This demonstrates how embassy's executor can drive futures from another IO library. + // Essentially, async_io::Async converts from AsRawFd+Read+Write to futures's AsyncRead+AsyncWrite + let port = Async::new(port).unwrap(); + + // We can then use FromStdIo to convert from futures's AsyncRead+AsyncWrite + // to embedded_io's async Read+Write. + // + // This is not really needed, you could write the code below using futures::io directly. + // It's useful if you want to have portable code across embedded and std. + let mut port = embedded_io_adapters::futures_03::FromFutures::new(port); + + info!("Serial opened!"); + + loop { + let mut buf = [0u8; 256]; + let n = port.read(&mut buf).await.unwrap(); + info!("read {:?}", &buf[..n]); + } +} + +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(run()).unwrap(); + }); +} diff --git a/embassy/examples/std/src/bin/tcp_accept.rs b/embassy/examples/std/src/bin/tcp_accept.rs new file mode 100644 index 0000000..5d36b73 --- /dev/null +++ b/embassy/examples/std/src/bin/tcp_accept.rs @@ -0,0 +1,119 @@ +use core::fmt::Write as _; + +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; +use embassy_net_tuntap::TunTapDevice; +use embassy_time::{Duration, Timer}; +use embedded_io_async::Write as _; +use heapless::Vec; +use log::*; +use rand_core::{OsRng, RngCore}; +use static_cell::StaticCell; + +#[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(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await +} + +#[derive(Default)] +struct StrWrite(pub heapless::Vec); + +impl core::fmt::Write for StrWrite { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + self.0.extend_from_slice(s.as_bytes()).unwrap(); + Ok(()) + } +} + +#[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::ipv4_static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + dns_servers: Vec::new(), + gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + }) + } else { + Config::dhcpv4(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 + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + spawner.spawn(net_task(runner)).unwrap(); + + // Then we can use it! + 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); + socket.set_timeout(Some(Duration::from_secs(10))); + info!("Listening on TCP:9999..."); + if let Err(_) = socket.accept(9999).await { + warn!("accept error"); + continue; + } + + info!("Accepted a connection"); + + // Write some quick output + for i in 1..=5 { + let mut w = StrWrite::default(); + write!(w, "{}! ", i).unwrap(); + let r = socket.write_all(&w.0).await; + if let Err(e) = r { + warn!("write error: {:?}", e); + return; + } + + Timer::after_millis(500).await; + } + info!("Closing the connection"); + socket.abort(); + info!("Flushing the RST out..."); + _ = socket.flush().await; + info!("Finished with the socket"); + } +} + +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/embassy/examples/std/src/bin/tick.rs b/embassy/examples/std/src/bin/tick.rs new file mode 100644 index 0000000..f23cf35 --- /dev/null +++ b/embassy/examples/std/src/bin/tick.rs @@ -0,0 +1,21 @@ +use embassy_executor::Spawner; +use embassy_time::Timer; +use log::*; + +#[embassy_executor::task] +async fn run() { + loop { + info!("tick"); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .format_timestamp_nanos() + .init(); + + spawner.spawn(run()).unwrap(); +} diff --git a/embassy/examples/std/src/serial_port.rs b/embassy/examples/std/src/serial_port.rs new file mode 100644 index 0000000..c41abd4 --- /dev/null +++ b/embassy/examples/std/src/serial_port.rs @@ -0,0 +1,66 @@ +use std::io; +use std::os::unix::io::{AsRawFd, RawFd}; + +use nix::errno::Errno; +use nix::fcntl::OFlag; +use nix::sys::termios; + +pub struct SerialPort { + fd: RawFd, +} + +impl SerialPort { + pub fn new(path: &P, baudrate: termios::BaudRate) -> io::Result { + let fd = nix::fcntl::open( + path, + OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK, + nix::sys::stat::Mode::empty(), + ) + .map_err(to_io_error)?; + + let mut cfg = termios::tcgetattr(fd).map_err(to_io_error)?; + cfg.input_flags = termios::InputFlags::empty(); + cfg.output_flags = termios::OutputFlags::empty(); + cfg.control_flags = termios::ControlFlags::empty(); + cfg.local_flags = termios::LocalFlags::empty(); + termios::cfmakeraw(&mut cfg); + cfg.input_flags |= termios::InputFlags::IGNBRK; + cfg.control_flags |= termios::ControlFlags::CREAD; + //cfg.control_flags |= termios::ControlFlags::CRTSCTS; + termios::cfsetospeed(&mut cfg, baudrate).map_err(to_io_error)?; + termios::cfsetispeed(&mut cfg, baudrate).map_err(to_io_error)?; + termios::cfsetspeed(&mut cfg, baudrate).map_err(to_io_error)?; + // Set VMIN = 1 to block until at least one character is received. + cfg.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; + termios::tcsetattr(fd, termios::SetArg::TCSANOW, &cfg).map_err(to_io_error)?; + termios::tcflush(fd, termios::FlushArg::TCIOFLUSH).map_err(to_io_error)?; + + Ok(Self { fd }) + } +} + +impl AsRawFd for SerialPort { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl io::Read for SerialPort { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + nix::unistd::read(self.fd, buf).map_err(to_io_error) + } +} + +impl io::Write for SerialPort { + fn write(&mut self, buf: &[u8]) -> io::Result { + nix::unistd::write(self.fd, buf).map_err(to_io_error) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +fn to_io_error(e: Errno) -> io::Error { + e.into() +} diff --git a/embassy/examples/stm32c0/.cargo/config.toml b/embassy/examples/stm32c0/.cargo/config.toml new file mode 100644 index 0000000..29a8be7 --- /dev/null +++ b/embassy/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-rs chip list` +runner = "probe-rs run --speed 100 --chip STM32c031c6tx" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32c0/Cargo.toml b/embassy/examples/stm32c0/Cargo.toml new file mode 100644 index 0000000..5ac3018 --- /dev/null +++ b/embassy/examples/stm32c0/Cargo.toml @@ -0,0 +1,24 @@ +[package] +edition = "2021" +name = "embassy-stm32c0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32c031c6 to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32c031c6", "memory-x", "unstable-pac", "exti"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +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"] } +heapless = { version = "0.8", default-features = false } + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32c0/build.rs b/embassy/examples/stm32c0/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/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/embassy/examples/stm32c0/src/bin/blinky.rs b/embassy/examples/stm32c0/src/bin/blinky.rs new file mode 100644 index 0000000..90e479a --- /dev/null +++ b/embassy/examples/stm32c0/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32c0/src/bin/button.rs b/embassy/examples/stm32c0/src/bin/button.rs new file mode 100644 index 0000000..8017f02 --- /dev/null +++ b/embassy/examples/stm32c0/src/bin/button.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] + +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/embassy/examples/stm32c0/src/bin/button_exti.rs b/embassy/examples/stm32c0/src/bin/button_exti.rs new file mode 100644 index 0000000..34a08bb --- /dev/null +++ b/embassy/examples/stm32c0/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); + + 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/embassy/examples/stm32f0/.cargo/config.toml b/embassy/examples/stm32f0/.cargo/config.toml new file mode 100644 index 0000000..def4c8c --- /dev/null +++ b/embassy/examples/stm32f0/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv6m-none-eabi] +runner = 'probe-rs run --chip STM32F091RCTX' + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32f0/Cargo.toml b/embassy/examples/stm32f0/Cargo.toml new file mode 100644 index 0000000..af3ef7a --- /dev/null +++ b/embassy/examples/stm32f0/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "embassy-stm32f0-examples" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f091rc to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "memory-x", "stm32f091rc", "time-driver-tim2", "exti", "unstable-pac"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3", features = ["print-defmt"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +static_cell = "2" +portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32f0/build.rs b/embassy/examples/stm32f0/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32f0/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/embassy/examples/stm32f0/src/bin/adc.rs b/embassy/examples/stm32f0/src/bin/adc.rs new file mode 100644 index 0000000..8825e26 --- /dev/null +++ b/embassy/examples/stm32f0/src/bin/adc.rs @@ -0,0 +1,40 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::peripherals::ADC1; +use embassy_stm32::{adc, bind_interrupts}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC1_COMP => adc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC1, Irqs); + adc.set_sample_time(SampleTime::CYCLES71_5); + let mut pin = p.PA1; + + let mut vrefint = adc.enable_vref(); + let vrefint_sample = adc.read(&mut vrefint).await; + let convert_to_millivolts = |sample| { + // From https://www.st.com/resource/en/datasheet/stm32f031c6.pdf + // 6.3.4 Embedded reference voltage + const VREFINT_MV: u32 = 1230; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + + loop { + let v = adc.read(&mut pin).await; + info!("--> {} - {} mV", v, convert_to_millivolts(v)); + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/stm32f0/src/bin/blinky.rs b/embassy/examples/stm32f0/src/bin/blinky.rs new file mode 100644 index 0000000..2572be1 --- /dev/null +++ b/embassy/examples/stm32f0/src/bin/blinky.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32f0/src/bin/button_controlled_blink.rs b/embassy/examples/stm32f0/src/bin/button_controlled_blink.rs new file mode 100644 index 0000000..4465483 --- /dev/null +++ b/embassy/examples/stm32f0/src/bin/button_controlled_blink.rs @@ -0,0 +1,62 @@ +//! This example showcases how to create task + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::{AnyPin, Level, Output, Pin, Pull, Speed}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +static BLINK_MS: AtomicU32 = AtomicU32::new(0); + +#[embassy_executor::task] +async fn led_task(led: AnyPin) { + // Configure the LED pin as a push pull output and obtain handler. + // On the Nucleo F091RC there's an on-board LED connected to pin PA5. + let mut led = Output::new(led, Level::Low, Speed::Low); + + loop { + let del = BLINK_MS.load(Ordering::Relaxed); + info!("Value of del is {}", del); + Timer::after_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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::None); + + // 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/embassy/examples/stm32f0/src/bin/button_exti.rs b/embassy/examples/stm32f0/src/bin/button_exti.rs new file mode 100644 index 0000000..fd615a2 --- /dev/null +++ b/embassy/examples/stm32f0/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); + + 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/embassy/examples/stm32f0/src/bin/hello.rs b/embassy/examples/stm32f0/src/bin/hello.rs new file mode 100644 index 0000000..ccd6a0a --- /dev/null +++ b/embassy/examples/stm32f0/src/bin/hello.rs @@ -0,0 +1,16 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let _p = embassy_stm32::init(Default::default()); + loop { + Timer::after_secs(1).await; + info!("Hello"); + } +} diff --git a/embassy/examples/stm32f0/src/bin/multiprio.rs b/embassy/examples/stm32f0/src/bin/multiprio.rs new file mode 100644 index 0000000..84e4077 --- /dev/null +++ b/embassy/examples/stm32f0/src/bin/multiprio.rs @@ -0,0 +1,149 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_stm32::interrupt; +use embassy_stm32::interrupt::{InterruptExt, Priority}; +use embassy_time::{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_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 + embassy_time::block_for(embassy_time::Duration::from_secs(1)); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!(" [med] done in {} ms", ms); + + Timer::after_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 + embassy_time::block_for(embassy_time::Duration::from_secs(2)); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!("[low] done in {} ms", ms); + + Timer::after_ticks(32983).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[interrupt] +unsafe fn USART1() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn USART2() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + // Initialize and create handle for devicer peripherals + let _p = embassy_stm32::init(Default::default()); + + // STM32s don’t have any interrupts exclusively for software use, but they can all be triggered by software as well as + // by the peripheral, so we can just use any free interrupt vectors which aren’t used by the rest of your application. + // In this case we’re using UART1 and UART2, but there’s nothing special about them. Any otherwise unused interrupt + // vector would work exactly the same. + + // High-priority executor: USART1, priority level 6 + interrupt::USART1.set_priority(Priority::P6); + let spawner = EXECUTOR_HIGH.start(interrupt::USART1); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: USART2, priority level 7 + interrupt::USART2.set_priority(Priority::P7); + let spawner = EXECUTOR_MED.start(interrupt::USART2); + 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/embassy/examples/stm32f0/src/bin/wdg.rs b/embassy/examples/stm32f0/src/bin/wdg.rs new file mode 100644 index 0000000..b974bff --- /dev/null +++ b/embassy/examples/stm32f0/src/bin/wdg.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::wdg::IndependentWatchdog; +use embassy_time::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"); + wdg.unleash(); + + loop { + Timer::after_secs(1).await; + wdg.pet(); + } +} diff --git a/embassy/examples/stm32f1/.cargo/config.toml b/embassy/examples/stm32f1/.cargo/config.toml new file mode 100644 index 0000000..ce6fef1 --- /dev/null +++ b/embassy/examples/stm32f1/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F103C8 with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F103C8" + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32f1/Cargo.toml b/embassy/examples/stm32f1/Cargo.toml new file mode 100644 index 0000000..538e95d --- /dev/null +++ b/embassy/examples/stm32f1/Cargo.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-stm32f1-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f103c8 to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any" ] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +nb = "1.0.0" +static_cell = "2.0.0" + +[profile.dev] +opt-level = "s" + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32f1/build.rs b/embassy/examples/stm32f1/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32f1/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/embassy/examples/stm32f1/src/bin/adc.rs b/embassy/examples/stm32f1/src/bin/adc.rs new file mode 100644 index 0000000..541ff15 --- /dev/null +++ b/embassy/examples/stm32f1/src/bin/adc.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::Adc; +use embassy_stm32::peripherals::ADC1; +use embassy_stm32::{adc, bind_interrupts}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC1_2 => adc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC1); + let mut pin = p.PB1; + + let mut vrefint = adc.enable_vref(); + let vrefint_sample = adc.read(&mut vrefint).await; + 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).await; + info!("--> {} - {} mV", v, convert_to_millivolts(v)); + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/stm32f1/src/bin/blinky.rs b/embassy/examples/stm32f1/src/bin/blinky.rs new file mode 100644 index 0000000..cc43f85 --- /dev/null +++ b/embassy/examples/stm32f1/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PC13, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32f1/src/bin/can.rs b/embassy/examples/stm32f1/src/bin/can.rs new file mode 100644 index 0000000..ad0c8a5 --- /dev/null +++ b/embassy/examples/stm32f1/src/bin/can.rs @@ -0,0 +1,140 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::can::frame::Envelope; +use embassy_stm32::can::{ + filter, Can, Fifo, Frame, Id, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, StandardId, + TxInterruptHandler, +}; +use embassy_stm32::peripherals::CAN; +use embassy_stm32::{bind_interrupts, Config}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_LP_CAN1_RX0 => Rx0InterruptHandler; + CAN1_RX1 => Rx1InterruptHandler; + CAN1_SCE => SceInterruptHandler; + USB_HP_CAN1_TX => TxInterruptHandler; +}); + +// This example is configured to work with real CAN transceivers on B8/B9. +// See other examples for loopback. + +fn handle_frame(env: Envelope, read_mode: &str) { + match env.frame.id() { + Id::Extended(id) => { + defmt::println!( + "{} Extended Frame id={:x} {:02x}", + read_mode, + id.as_raw(), + env.frame.data() + ); + } + Id::Standard(id) => { + defmt::println!( + "{} Standard Frame id={:x} {:02x}", + read_mode, + id.as_raw(), + env.frame.data() + ); + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Config::default()); + + // Set alternate pin mapping to B8/B9 + embassy_stm32::pac::AFIO.mapr().modify(|w| w.set_can1_remap(2)); + + static RX_BUF: StaticCell> = StaticCell::new(); + static TX_BUF: StaticCell> = StaticCell::new(); + + let mut can = Can::new(p.CAN, p.PB8, p.PB9, Irqs); + + can.modify_filters() + .enable_bank(0, Fifo::Fifo0, filter::Mask32::accept_all()); + + can.modify_config() + .set_loopback(false) + .set_silent(false) + .set_bitrate(250_000); + + can.enable().await; + let mut i: u8 = 0; + + /* + // Example for using buffered Tx and Rx without needing to + // split first as is done below. + let mut can = can.buffered( + TX_BUF.init(embassy_stm32::can::TxBuf::<10>::new()), + RX_BUF.init(embassy_stm32::can::RxBuf::<10>::new())); + loop { + let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), &[i, 0, 1, 2, 3, 4, 5, 6]).unwrap(); + can.write(&tx_frame).await; + + match can.read().await { + Ok((frame, ts)) => { + handle_frame(Envelope { ts, frame }, "Buf"); + } + Err(err) => { + defmt::println!("Error {}", err); + } + } + i = i.wrapping_add(1); + } + + */ + let (mut tx, mut rx) = can.split(); + + // This example shows using the wait_not_empty API before try read + while i < 3 { + let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), &[i, 0, 1, 2, 3, 4, 5, 6]).unwrap(); + tx.write(&tx_frame).await; + + rx.wait_not_empty().await; + let env = rx.try_read().unwrap(); + handle_frame(env, "Wait"); + i += 1; + } + + // This example shows using the full async non-buffered API + while i < 6 { + let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), &[i, 0, 1, 2, 3, 4, 5, 6]).unwrap(); + tx.write(&tx_frame).await; + + match rx.read().await { + Ok(env) => { + handle_frame(env, "NoBuf"); + } + Err(err) => { + defmt::println!("Error {}", err); + } + } + i += 1; + } + + // This example shows using buffered RX and TX. User passes in desired buffer (size) + // It's possible this way to have just RX or TX buffered. + let mut rx = rx.buffered(RX_BUF.init(embassy_stm32::can::RxBuf::<10>::new())); + let mut tx = tx.buffered(TX_BUF.init(embassy_stm32::can::TxBuf::<10>::new())); + + loop { + let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), &[i, 0, 1, 2, 3, 4, 5, 6]).unwrap(); + tx.write(&tx_frame).await; + + match rx.read().await { + Ok(envelope) => { + handle_frame(envelope, "Buf"); + } + Err(err) => { + defmt::println!("Error {}", err); + } + } + i = i.wrapping_add(1); + } +} diff --git a/embassy/examples/stm32f1/src/bin/hello.rs b/embassy/examples/stm32f1/src/bin/hello.rs new file mode 100644 index 0000000..3c29561 --- /dev/null +++ b/embassy/examples/stm32f1/src/bin/hello.rs @@ -0,0 +1,19 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let config = Config::default(); + let _p = embassy_stm32::init(config); + + loop { + info!("Hello World!"); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/stm32f1/src/bin/input_capture.rs b/embassy/examples/stm32f1/src/bin/input_capture.rs new file mode 100644 index 0000000..5e2dab9 --- /dev/null +++ b/embassy/examples/stm32f1/src/bin/input_capture.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_stm32::time::khz; +use embassy_stm32::timer::input_capture::{CapturePin, InputCapture}; +use embassy_stm32::timer::{self, Channel}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +/// Connect PA2 and PC13 with a 1k Ohm resistor + +#[embassy_executor::task] +async fn blinky(led: peripherals::PC13) { + let mut led = Output::new(led, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} + +bind_interrupts!(struct Irqs { + TIM2 => timer::CaptureCompareInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + unwrap!(spawner.spawn(blinky(p.PC13))); + + let ch3 = CapturePin::new_ch3(p.PA2, Pull::None); + let mut ic = InputCapture::new(p.TIM2, None, None, Some(ch3), None, Irqs, khz(1000), Default::default()); + + loop { + info!("wait for rising edge"); + ic.wait_for_rising_edge(Channel::Ch3).await; + + let capture_value = ic.get_capture_value(Channel::Ch3); + info!("new capture! {}", capture_value); + } +} diff --git a/embassy/examples/stm32f1/src/bin/pwm_input.rs b/embassy/examples/stm32f1/src/bin/pwm_input.rs new file mode 100644 index 0000000..f74853d --- /dev/null +++ b/embassy/examples/stm32f1/src/bin/pwm_input.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_stm32::time::khz; +use embassy_stm32::timer::pwm_input::PwmInput; +use embassy_stm32::{bind_interrupts, peripherals, timer}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +/// Connect PA0 and PC13 with a 1k Ohm resistor + +#[embassy_executor::task] +async fn blinky(led: peripherals::PC13) { + let mut led = Output::new(led, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} + +bind_interrupts!(struct Irqs { + TIM2 => timer::CaptureCompareInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + unwrap!(spawner.spawn(blinky(p.PC13))); + + let mut pwm_input = PwmInput::new(p.TIM2, p.PA0, Pull::None, khz(10)); + pwm_input.enable(); + + loop { + Timer::after_millis(500).await; + let period = pwm_input.get_period_ticks(); + let width = pwm_input.get_width_ticks(); + let duty_cycle = pwm_input.get_duty_cycle(); + info!( + "period ticks: {} width ticks: {} duty cycle: {}", + period, width, duty_cycle + ); + } +} diff --git a/embassy/examples/stm32f1/src/bin/usb_serial.rs b/embassy/examples/stm32f1/src/bin/usb_serial.rs new file mode 100644 index 0000000..ee99acf --- /dev/null +++ b/embassy/examples/stm32f1/src/bin/usb_serial.rs @@ -0,0 +1,121 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_time::Timer; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_LP_CAN1_RX0 => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + // Oscillator for bluepill, Bypass for nucleos. + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + } + let mut p = embassy_stm32::init(config); + + info!("Hello World!"); + + { + // BluePill board has a pull-up resistor on the D+ line. + // Pull the D+ pin down to send a RESET condition to the USB bus. + // This forced reset is needed only for development, without it host + // will not reset your device when you upload new firmware. + let _dp = Output::new(&mut p.PA12, Level::Low, Speed::Low); + Timer::after_millis(10).await; + } + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Create embassy-usb Config + let config = embassy_usb::Config::new(0xc0de, 0xcafe); + //config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 7]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32f2/.cargo/config.toml b/embassy/examples/stm32f2/.cargo/config.toml new file mode 100644 index 0000000..1198fca --- /dev/null +++ b/embassy/examples/stm32f2/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F207ZGTx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F207ZGTx" + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32f2/Cargo.toml b/embassy/examples/stm32f2/Cargo.toml new file mode 100644 index 0000000..48d524b --- /dev/null +++ b/embassy/examples/stm32f2/Cargo.toml @@ -0,0 +1,25 @@ +[package] +edition = "2021" +name = "embassy-stm32f2-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f207zg to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f207zg", "unstable-pac", "memory-x", "time-driver-any", "exti"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +nb = "1.0.0" + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32f2/build.rs b/embassy/examples/stm32f2/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32f2/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/embassy/examples/stm32f2/src/bin/blinky.rs b/embassy/examples/stm32f2/src/bin/blinky.rs new file mode 100644 index 0000000..d9833ba --- /dev/null +++ b/embassy/examples/stm32f2/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PB14, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(1000).await; + + info!("low"); + led.set_low(); + Timer::after_millis(1000).await; + } +} diff --git a/embassy/examples/stm32f2/src/bin/pll.rs b/embassy/examples/stm32f2/src/bin/pll.rs new file mode 100644 index 0000000..e39e2da --- /dev/null +++ b/embassy/examples/stm32f2/src/bin/pll.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Example config for maximum performance on a NUCLEO-F207ZG board + + let mut config = Config::default(); + + { + use embassy_stm32::rcc::*; + + // By default, HSE on the board comes from a 8 MHz clock signal (not a crystal) + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + // PLL uses HSE as the clock source + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + // 8 MHz clock source / 8 = 1 MHz PLL input + prediv: unwrap!(PllPreDiv::try_from(8)), + // 1 MHz PLL input * 240 = 240 MHz PLL VCO + mul: unwrap!(PllMul::try_from(240)), + // 240 MHz PLL VCO / 2 = 120 MHz main PLL output + divp: Some(PllPDiv::DIV2), + // 240 MHz PLL VCO / 5 = 48 MHz PLL48 output + divq: Some(PllQDiv::DIV5), + divr: None, + }); + // System clock comes from PLL (= the 120 MHz main PLL output) + config.rcc.sys = Sysclk::PLL1_P; + // 120 MHz / 4 = 30 MHz APB1 frequency + config.rcc.apb1_pre = APBPrescaler::DIV4; + // 120 MHz / 2 = 60 MHz APB2 frequency + config.rcc.apb2_pre = APBPrescaler::DIV2; + } + + let _p = embassy_stm32::init(config); + + loop { + Timer::after_millis(1000).await; + info!("1s elapsed"); + } +} diff --git a/embassy/examples/stm32f3/.cargo/config.toml b/embassy/examples/stm32f3/.cargo/config.toml new file mode 100644 index 0000000..cb8a7c5 --- /dev/null +++ b/embassy/examples/stm32f3/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F303ZETx" + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32f3/Cargo.toml b/embassy/examples/stm32f3/Cargo.toml new file mode 100644 index 0000000..66fb342 --- /dev/null +++ b/embassy/examples/stm32f3/Cargo.toml @@ -0,0 +1,29 @@ +[package] +edition = "2021" +name = "embassy-stm32f3-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f303ze to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-tim2", "exti"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +nb = "1.0.0" +embedded-storage = "0.3.1" +static_cell = "2" + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32f3/README.md b/embassy/examples/stm32f3/README.md new file mode 100644 index 0000000..0a85c48 --- /dev/null +++ b/embassy/examples/stm32f3/README.md @@ -0,0 +1,24 @@ +# Examples for STM32F3 family +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for F303ZE it should be `probe-rs run --chip STM32F303ZETx`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for F303ZE it should be `stm32f303ze`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/embassy/examples/stm32f3/build.rs b/embassy/examples/stm32f3/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32f3/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/embassy/examples/stm32f3/src/bin/blinky.rs b/embassy/examples/stm32f3/src/bin/blinky.rs new file mode 100644 index 0000000..0ea1052 --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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_millis(1000).await; + + info!("low"); + led.set_low(); + Timer::after_millis(1000).await; + } +} diff --git a/embassy/examples/stm32f3/src/bin/button.rs b/embassy/examples/stm32f3/src/bin/button.rs new file mode 100644 index 0000000..406730a --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/button.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +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.PA0, Pull::Down); + let mut led1 = Output::new(p.PE9, Level::High, Speed::Low); + let mut led2 = Output::new(p.PE15, Level::High, Speed::Low); + + loop { + if button.is_high() { + info!("high"); + led1.set_high(); + led2.set_low(); + } else { + info!("low"); + led1.set_low(); + led2.set_high(); + } + } +} diff --git a/embassy/examples/stm32f3/src/bin/button_events.rs b/embassy/examples/stm32f3/src/bin/button_events.rs new file mode 100644 index 0000000..f5ed5d2 --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/button_events.rs @@ -0,0 +1,154 @@ +//! This example showcases channels and timing utilities of embassy. +//! +//! This example works best on STM32F3DISCOVERY board. It flashes a single LED, then if the USER +//! button is pressed the next LED is flashed (in clockwise fashion). If the USER button is double +//! clicked, then the direction changes. If the USER button is pressed for 1 second, then all the +//! LEDS flash 3 times. +//! + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::Channel; +use embassy_time::{with_timeout, Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +struct Leds<'a> { + leds: [Output<'a>; 8], + direction: i8, + current_led: usize, +} + +impl<'a> Leds<'a> { + fn new(pins: [Output<'a>; 8]) -> Self { + Self { + leds: pins, + direction: 1, + current_led: 0, + } + } + + fn change_direction(&mut self) { + self.direction *= -1; + } + + fn move_next(&mut self) { + if self.direction > 0 { + self.current_led = (self.current_led + 1) & 7; + } else { + self.current_led = (8 + self.current_led - 1) & 7; + } + } + + async fn show(&mut self) { + self.leds[self.current_led].set_high(); + if let Ok(new_message) = with_timeout(Duration::from_millis(500), CHANNEL.receive()).await { + self.leds[self.current_led].set_low(); + self.process_event(new_message).await; + } else { + self.leds[self.current_led].set_low(); + if let Ok(new_message) = with_timeout(Duration::from_millis(200), CHANNEL.receive()).await { + self.process_event(new_message).await; + } + } + } + + async fn flash(&mut self) { + for _ in 0..3 { + for led in &mut self.leds { + led.set_high(); + } + Timer::after_millis(500).await; + for led in &mut self.leds { + led.set_low(); + } + Timer::after_millis(200).await; + } + } + + async fn process_event(&mut self, event: ButtonEvent) { + match event { + ButtonEvent::SingleClick => { + self.move_next(); + } + ButtonEvent::DoubleClick => { + self.change_direction(); + self.move_next(); + } + ButtonEvent::Hold => { + self.flash().await; + } + } + } +} + +#[derive(Format)] +enum ButtonEvent { + SingleClick, + DoubleClick, + Hold, +} + +static CHANNEL: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Down); + info!("Press the USER button..."); + let leds = [ + Output::new(p.PE9, Level::Low, Speed::Low), + Output::new(p.PE10, Level::Low, Speed::Low), + Output::new(p.PE11, Level::Low, Speed::Low), + Output::new(p.PE12, Level::Low, Speed::Low), + Output::new(p.PE13, Level::Low, Speed::Low), + Output::new(p.PE14, Level::Low, Speed::Low), + Output::new(p.PE15, Level::Low, Speed::Low), + Output::new(p.PE8, Level::Low, Speed::Low), + ]; + let leds = Leds::new(leds); + + spawner.spawn(button_waiter(button)).unwrap(); + spawner.spawn(led_blinker(leds)).unwrap(); +} + +#[embassy_executor::task] +async fn led_blinker(mut leds: Leds<'static>) { + loop { + leds.show().await; + } +} + +#[embassy_executor::task] +async fn button_waiter(mut button: ExtiInput<'static>) { + const DOUBLE_CLICK_DELAY: u64 = 250; + const HOLD_DELAY: u64 = 1000; + + button.wait_for_rising_edge().await; + loop { + if with_timeout(Duration::from_millis(HOLD_DELAY), button.wait_for_falling_edge()) + .await + .is_err() + { + info!("Hold"); + CHANNEL.send(ButtonEvent::Hold).await; + button.wait_for_falling_edge().await; + } else if with_timeout(Duration::from_millis(DOUBLE_CLICK_DELAY), button.wait_for_rising_edge()) + .await + .is_err() + { + info!("Single click"); + CHANNEL.send(ButtonEvent::SingleClick).await; + } else { + info!("Double click"); + CHANNEL.send(ButtonEvent::DoubleClick).await; + button.wait_for_falling_edge().await; + } + button.wait_for_rising_edge().await; + } +} diff --git a/embassy/examples/stm32f3/src/bin/button_exti.rs b/embassy/examples/stm32f3/src/bin/button_exti.rs new file mode 100644 index 0000000..a55530e --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Down); + + info!("Press the USER button..."); + + loop { + button.wait_for_rising_edge().await; + info!("Pressed!"); + button.wait_for_falling_edge().await; + info!("Released!"); + } +} diff --git a/embassy/examples/stm32f3/src/bin/flash.rs b/embassy/examples/stm32f3/src/bin/flash.rs new file mode 100644 index 0000000..2812569 --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/flash.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello Flash!"); + + const ADDR: u32 = 0x26000; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.blocking_erase(ADDR, ADDR + 2048)); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); +} diff --git a/embassy/examples/stm32f3/src/bin/hello.rs b/embassy/examples/stm32f3/src/bin/hello.rs new file mode 100644 index 0000000..3c29561 --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/hello.rs @@ -0,0 +1,19 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let config = Config::default(); + let _p = embassy_stm32::init(config); + + loop { + info!("Hello World!"); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/stm32f3/src/bin/multiprio.rs b/embassy/examples/stm32f3/src/bin/multiprio.rs new file mode 100644 index 0000000..b462088 --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/multiprio.rs @@ -0,0 +1,150 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_stm32::interrupt; +use embassy_stm32::interrupt::{InterruptExt, Priority}; +use embassy_time::{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_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 + embassy_time::block_for(embassy_time::Duration::from_secs(1)); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!(" [med] done in {} ms", ms); + + Timer::after_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 + embassy_time::block_for(embassy_time::Duration::from_secs(2)); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!("[low] done in {} ms", ms); + + Timer::after_ticks(32983).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[interrupt] +unsafe fn UART4() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn UART5() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_stm32::init(Default::default()); + + // STM32s don’t have any interrupts exclusively for software use, but they can all be triggered by software as well as + // by the peripheral, so we can just use any free interrupt vectors which aren’t used by the rest of your application. + // In this case we’re using UART4 and UART5, but there’s nothing special about them. Any otherwise unused interrupt + // vector would work exactly the same. + + // High-priority executor: UART4, priority level 6 + interrupt::UART4.set_priority(Priority::P6); + let spawner = EXECUTOR_HIGH.start(interrupt::UART4); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: UART5, priority level 7 + interrupt::UART5.set_priority(Priority::P7); + let spawner = EXECUTOR_MED.start(interrupt::UART5); + 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/embassy/examples/stm32f3/src/bin/spi_dma.rs b/embassy/examples/stm32f3/src/bin/spi_dma.rs new file mode 100644 index 0000000..54498d5 --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/spi_dma.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; +use core::str::from_utf8; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use heapless::String; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut spi_config = Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new(p.SPI1, p.PB3, p.PB5, p.PB4, p.DMA1_CH3, p.DMA1_CH2, spi_config); + + for n in 0u32.. { + let mut write: String<128> = String::new(); + let mut read = [0; 128]; + core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); + spi.transfer(&mut read[0..write.len()], write.as_bytes()).await.ok(); + info!("read via spi+dma: {}", from_utf8(&read).unwrap()); + } +} diff --git a/embassy/examples/stm32f3/src/bin/tsc_blocking.rs b/embassy/examples/stm32f3/src/bin/tsc_blocking.rs new file mode 100644 index 0000000..2c33838 --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/tsc_blocking.rs @@ -0,0 +1,138 @@ +// Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32F303ZE Nucleo board: +// - Connect a 1000pF capacitor between pin PA10 and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PA9 and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 4 of the TSC: +// - PA10 as the sampling capacitor, TSC group 4 IO2 (D68 on the STM32F303ZE nucleo-board) +// - PA9 as the channel pin, TSC group 4 IO1 (D69 on the STM32F303ZE nucleo-board) +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED is turned on when touch is detected (sensor value < 40). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32F303ZE board. Refer to the +// official relevant STM32 datasheets and user nucleo-board user manuals to find suitable +// alternative pins. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut g: PinGroupWithRoles = PinGroupWithRoles::default(); + // D68 on the STM32F303ZE nucleo-board + g.set_io2::(context.PA10); + // D69 on the STM32F303ZE nucleo-board + let tsc_sensor = g.set_io1::(context.PA9); + + let pin_groups: PinGroups = PinGroups { + g4: Some(g.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + info!("TSC initialized successfully"); + + // LED2 on the STM32F303ZE nucleo-board + let mut led = Output::new(context.PB7, Level::High, Speed::Low); + + // smaller sample capacitor discharge faster and can be used with shorter delay. + let discharge_delay = 5; // ms + + // the interval at which the loop polls for new touch sensor values + let polling_interval = 100; // ms + + info!("polling for touch"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.poll_for_acquisition(); + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { + Some(v) => { + info!("sensor value {}", v); + if v < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + } + None => led.set_low(), + } + + Timer::after_millis(polling_interval).await; + } +} + +const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; + +// attempt to read group status and delay when still ongoing +async fn read_touch_value( + touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, + sensor_pin: tsc::IOPin, +) -> Option { + for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { + match touch_controller.group_get_status(sensor_pin.group()) { + GroupStatus::Complete => { + return Some(touch_controller.group_get_value(sensor_pin.group())); + } + GroupStatus::Ongoing => { + // if you end up here a lot, then you prob need to increase discharge_delay + // or consider changing the code to adjust the discharge_delay dynamically + info!("Acquisition still ongoing"); + Timer::after_millis(1).await; + } + } + } + None +} diff --git a/embassy/examples/stm32f3/src/bin/tsc_multipin.rs b/embassy/examples/stm32f3/src/bin/tsc_multipin.rs new file mode 100644 index 0000000..c524c37 --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/tsc_multipin.rs @@ -0,0 +1,204 @@ +// Example of TSC (Touch Sensing Controller) using multiple pins from the same tsc-group. +// +// What is special about using multiple TSC pins as sensor channels from the same TSC group, +// is that only one TSC pin for each TSC group can be acquired and read at the time. +// To control which channel pins are acquired and read, we must write a mask before initiating an +// acquisition. To help manage and abstract all this business away, we can organize our channel +// pins into acquisition banks. Each acquisition bank can contain exactly one channel pin per TSC +// group and it will contain the relevant mask. +// +// This example demonstrates how to: +// 1. Configure multiple channel pins within a single TSC group +// 2. Use the set_active_channels_bank method to switch between sets of different channels (acquisition banks) +// 3. Read and interpret touch values from multiple channels in the same group +// +// Suggested physical setup on STM32F303ZE Nucleo board: +// - Connect a 1000pF capacitor between pin PA10 and GND. This is the sampling capacitor for TSC +// group 4. +// - Connect one end of a 1K resistor to pin PA9 and leave the other end loose. +// The loose end will act as a touch sensor. +// +// - Connect a 1000pF capacitor between pin PA7 and GND. This is the sampling capacitor for TSC +// group 2. +// - Connect one end of another 1K resistor to pin PA6 and leave the other end loose. +// The loose end will act as a touch sensor. +// - Connect one end of another 1K resistor to pin PA5 and leave the other end loose. +// The loose end will act as a touch sensor. +// +// The example uses pins from two TSC groups. +// - PA10 as sampling capacitor, TSC group 4 IO2 +// - PA9 as channel, TSC group 4 IO1 +// - PA7 as sampling capacitor, TSC group 2 IO4 +// - PA6 as channel, TSC group 2 IO3 +// - PA5 as channel, TSC group 2 IO2 +// +// The pins have been chosen to make it easy to simply add capacitors directly onto the board and +// connect one leg to GND, and to easily add resistors to the board with no special connectors, +// breadboards, special wires or soldering required. All you need is the capacitors and resistors. +// +// The program reads the designated channel pins and adjusts the LED blinking +// pattern based on which sensor(s) are touched: +// - No touch: LED off +// - one sensor touched: Slow blinking +// - two sensors touched: Fast blinking +// - three sensors touched: LED constantly on +// +// ## Troubleshooting: +// +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 20). +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// - Be aware that for some boards there will be overlapping concerns between some pins, for +// example UART connection for the programmer to the MCU and a TSC pin. No errors or warning will +// be emitted if you try to use such a pin for TSC, but you will get strange sensor readings. +// +// Note: Configuration values and sampling capacitor values have been determined experimentally. Optimal values may vary based on your specific hardware setup. Refer to the official STM32 datasheet and user manuals for more information on pin configurations and TSC functionality. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const SENSOR_THRESHOLD: u16 = 10; + +async fn acquire_sensors( + touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Blocking>, + tsc_acquisition_bank: &AcquisitionBank, +) { + touch_controller.set_active_channels_bank(tsc_acquisition_bank); + touch_controller.start(); + touch_controller.poll_for_acquisition(); + touch_controller.discharge_io(true); + let discharge_delay = 5; // ms + Timer::after_millis(discharge_delay).await; +} + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + // ---------- initial configuration of TSC ---------- + // + let mut pin_group4: PinGroupWithRoles = PinGroupWithRoles::default(); + // D68 on the STM32F303ZE nucleo-board + pin_group4.set_io2::(context.PA10); + // D69 on the STM32F303ZE nucleo-board + let tsc_sensor0 = pin_group4.set_io1(context.PA9); + + let mut pin_group2: PinGroupWithRoles = PinGroupWithRoles::default(); + // D11 on the STM32F303ZE nucleo-board + pin_group2.set_io4::(context.PA7); + // D12 on the STM32F303ZE nucleo-board + let tsc_sensor1 = pin_group2.set_io3(context.PA6); + // D13 on the STM32F303ZE nucleo-board + let tsc_sensor2 = pin_group2.set_io2(context.PA5); + + let config = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let pin_groups: PinGroups = PinGroups { + g4: Some(pin_group4.pin_group), + g2: Some(pin_group2.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, config).unwrap(); + + // ---------- setting up acquisition banks ---------- + // sensor0 and sensor1 in this example belong to different TSC-groups, + // therefore we can acquire and read them both in one go. + let bank1 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g4_pin: Some(tsc_sensor0), + g2_pin: Some(tsc_sensor1), + ..Default::default() + }); + // `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to + // acquire them one at the time. Therefore, we organize them into different acquisition banks. + let bank2 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g2_pin: Some(tsc_sensor2), + ..Default::default() + }); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + + info!("TSC initialized successfully"); + + // LED2 on the STM32F303ZE nucleo-board + let mut led = Output::new(context.PB7, Level::High, Speed::Low); + + let mut led_state = false; + + loop { + acquire_sensors(&mut touch_controller, &bank1).await; + let readings1 = touch_controller.get_acquisition_bank_values(&bank1); + acquire_sensors(&mut touch_controller, &bank2).await; + let readings2 = touch_controller.get_acquisition_bank_values(&bank2); + + let mut touched_sensors_count = 0; + for reading in readings1.iter() { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + for reading in readings2.iter() { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + + match touched_sensors_count { + 0 => { + // No sensors touched, turn off the LED + led.set_low(); + led_state = false; + } + 1 => { + // One sensor touched, blink slowly + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(200).await; + } + 2 => { + // Two sensors touched, blink faster + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(50).await; + } + 3 => { + // All three sensors touched, LED constantly on + led.set_high(); + led_state = true; + } + _ => crate::unreachable!(), // This case should never occur with 3 sensors + } + } +} diff --git a/embassy/examples/stm32f3/src/bin/usart_dma.rs b/embassy/examples/stm32f3/src/bin/usart_dma.rs new file mode 100644 index 0000000..573a49f --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/usart_dma.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use heapless::String; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USART1 => usart::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mut usart = Uart::new(p.USART1, p.PE1, p.PE0, Irqs, p.DMA1_CH4, p.DMA1_CH5, config).unwrap(); + + for n in 0u32.. { + let mut s: String<128> = String::new(); + core::write!(&mut s, "Hello DMA World {}!\r\n", n).unwrap(); + + unwrap!(usart.write(s.as_bytes()).await); + info!("wrote DMA"); + } +} diff --git a/embassy/examples/stm32f3/src/bin/usb_serial.rs b/embassy/examples/stm32f3/src/bin/usb_serial.rs new file mode 100644 index 0000000..5760f2c --- /dev/null +++ b/embassy/examples/stm32f3/src/bin/usb_serial.rs @@ -0,0 +1,115 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::time::mhz; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_time::Timer; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_LP_CAN_RX0 => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: mhz(8), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Needed for nucleo-stm32f303ze + let mut dp_pullup = Output::new(p.PG6, Level::Low, Speed::Medium); + Timer::after_millis(10).await; + dp_pullup.set_high(); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Create embassy-usb Config + let config = embassy_usb::Config::new(0xc0de, 0xcafe); + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 7]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32f334/.cargo/config.toml b/embassy/examples/stm32f334/.cargo/config.toml new file mode 100644 index 0000000..f38c90a --- /dev/null +++ b/embassy/examples/stm32f334/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F429ZITx with your chip as listed in `probe-rs-cli chip list` +runner = "probe-rs run --chip STM32F334R8" + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32f334/Cargo.toml b/embassy/examples/stm32f334/Cargo.toml new file mode 100644 index 0000000..c6b311f --- /dev/null +++ b/embassy/examples/stm32f334/Cargo.toml @@ -0,0 +1,25 @@ +[package] +edition = "2021" +name = "embassy-stm32f334-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f334r8", "unstable-pac", "memory-x", "time-driver-any", "exti"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +nb = "1.0.0" +embedded-storage = "0.3.1" +static_cell = "2" diff --git a/embassy/examples/stm32f334/build.rs b/embassy/examples/stm32f334/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32f334/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/embassy/examples/stm32f334/src/bin/adc.rs b/embassy/examples/stm32f334/src/bin/adc.rs new file mode 100644 index 0000000..0528a96 --- /dev/null +++ b/embassy/examples/stm32f334/src/bin/adc.rs @@ -0,0 +1,65 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::peripherals::ADC1; +use embassy_stm32::time::mhz; +use embassy_stm32::{adc, bind_interrupts, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC1_2 => adc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: mhz(8), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + config.rcc.adc = AdcClockSource::Pll(AdcPllPrescaler::DIV1); + } + let mut p = embassy_stm32::init(config); + + info!("create adc..."); + + let mut adc = Adc::new(p.ADC1, Irqs); + + adc.set_sample_time(SampleTime::CYCLES601_5); + + info!("enable vrefint..."); + + let mut vrefint = adc.enable_vref(); + let mut temperature = adc.enable_temperature(); + + loop { + let vref = adc.read(&mut vrefint).await; + info!("read vref: {} (should be {})", vref, vrefint.value()); + + let temp = adc.read(&mut temperature).await; + info!("read temperature: {}", temp); + + let pin = adc.read(&mut p.PA0).await; + info!("read pin: {}", pin); + + let pin_mv = (pin as u32 * vrefint.value() as u32 / vref as u32) * 3300 / 4095; + info!("computed pin mv: {}", pin_mv); + + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32f334/src/bin/button.rs b/embassy/examples/stm32f334/src/bin/button.rs new file mode 100644 index 0000000..256f9a4 --- /dev/null +++ b/embassy/examples/stm32f334/src/bin/button.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let mut out1 = Output::new(p.PA8, Level::Low, Speed::High); + + out1.set_high(); + Timer::after_millis(500).await; + out1.set_low(); + + Timer::after_millis(500).await; + info!("end program"); + + cortex_m::asm::bkpt(); +} diff --git a/embassy/examples/stm32f334/src/bin/hello.rs b/embassy/examples/stm32f334/src/bin/hello.rs new file mode 100644 index 0000000..3c29561 --- /dev/null +++ b/embassy/examples/stm32f334/src/bin/hello.rs @@ -0,0 +1,19 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let config = Config::default(); + let _p = embassy_stm32::init(config); + + loop { + info!("Hello World!"); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/stm32f334/src/bin/opamp.rs b/embassy/examples/stm32f334/src/bin/opamp.rs new file mode 100644 index 0000000..2dbf1bd --- /dev/null +++ b/embassy/examples/stm32f334/src/bin/opamp.rs @@ -0,0 +1,68 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::opamp::{OpAmp, OpAmpGain}; +use embassy_stm32::peripherals::ADC2; +use embassy_stm32::time::mhz; +use embassy_stm32::{adc, bind_interrupts, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC1_2 => adc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: mhz(8), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + config.rcc.adc = AdcClockSource::Pll(AdcPllPrescaler::DIV1); + } + let mut p = embassy_stm32::init(config); + + info!("create adc..."); + + let mut adc = Adc::new(p.ADC2, Irqs); + let mut opamp = OpAmp::new(p.OPAMP2); + + adc.set_sample_time(SampleTime::CYCLES601_5); + + info!("enable vrefint..."); + + let mut vrefint = adc.enable_vref(); + let mut temperature = adc.enable_temperature(); + let mut buffer = opamp.buffer_ext(&mut p.PA7, &mut p.PA6, OpAmpGain::Mul1); + + loop { + let vref = adc.read(&mut vrefint).await; + info!("read vref: {} (should be {})", vref, vrefint.value()); + + let temp = adc.read(&mut temperature).await; + info!("read temperature: {}", temp); + + let buffer = adc.read(&mut buffer).await; + info!("read buffer: {}", buffer); + + let pin_mv = (buffer as u32 * vrefint.value() as u32 / vref as u32) * 3300 / 4095; + info!("computed pin mv: {}", pin_mv); + + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32f334/src/bin/pwm.rs b/embassy/examples/stm32f334/src/bin/pwm.rs new file mode 100644 index 0000000..2b06861 --- /dev/null +++ b/embassy/examples/stm32f334/src/bin/pwm.rs @@ -0,0 +1,84 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::hrtim::*; +use embassy_stm32::time::{khz, mhz}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: mhz(8), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + + config.rcc.mux.hrtim1sw = embassy_stm32::rcc::mux::Timsw::PLL1_P; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let ch1 = PwmPin::new_cha(p.PA8); + let ch1n = ComplementaryPwmPin::new_cha(p.PA9); + let pwm = AdvancedPwm::new( + p.HRTIM1, + Some(ch1), + Some(ch1n), + None, + None, + None, + None, + None, + None, + None, + None, + ); + + info!("pwm constructed"); + + let mut buck_converter = BridgeConverter::new(pwm.ch_a, khz(5)); + + // embassy_stm32::pac::HRTIM1 + // .tim(0) + // .setr(0) + // .modify(|w| w.set_sst(true)); + // + // Timer::after_millis(500).await; + // + // embassy_stm32::pac::HRTIM1 + // .tim(0) + // .rstr(0) + // .modify(|w| w.set_srt(true)); + + let max_duty = buck_converter.get_max_compare_value(); + + info!("max compare value: {}", max_duty); + + buck_converter.set_dead_time(max_duty / 20); + buck_converter.set_primary_duty(max_duty / 2); + buck_converter.set_secondary_duty(3 * max_duty / 4); + + buck_converter.start(); + + Timer::after_millis(500).await; + + info!("end program"); + + cortex_m::asm::bkpt(); +} diff --git a/embassy/examples/stm32f4/.cargo/config.toml b/embassy/examples/stm32f4/.cargo/config.toml new file mode 100644 index 0000000..16efa8e --- /dev/null +++ b/embassy/examples/stm32f4/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F429ZITx" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32f4/Cargo.toml b/embassy/examples/stm32f4/Cargo.toml new file mode 100644 index 0000000..4f0629f --- /dev/null +++ b/embassy/examples/stm32f4/Cargo.toml @@ -0,0 +1,39 @@ +[package] +edition = "2021" +name = "embassy-stm32f4-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f429zi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-tim4", "exti", "chrono"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt" ] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } +embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-bus = { version = "0.2", features = ["async"] } +embedded-io = { version = "0.6.0" } +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +futures-util = { version = "0.3.30", default-features = false } +heapless = { version = "0.8", default-features = false } +critical-section = "1.1" +nb = "1.0.0" +embedded-storage = "0.3.1" +micromath = "2.0.0" +usbd-hid = "0.8.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false} + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32f4/build.rs b/embassy/examples/stm32f4/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32f4/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/embassy/examples/stm32f4/src/bin/adc.rs b/embassy/examples/stm32f4/src/bin/adc.rs new file mode 100644 index 0000000..423d292 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/adc.rs @@ -0,0 +1,67 @@ +#![no_std] +#![no_main] + +use cortex_m::prelude::_embedded_hal_blocking_delay_DelayUs; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, Temperature, VrefInt}; +use embassy_time::{Delay, 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 delay = Delay; + let mut adc = Adc::new(p.ADC1); + let mut pin = p.PC1; + + let mut vrefint = adc.enable_vrefint(); + let mut temp = adc.enable_temperature(); + + // 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.blocking_read(&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.blocking_read(&mut pin); + info!("PC1: {} ({} mV)", v, convert_to_millivolts(v)); + + // Read internal temperature + let v = adc.blocking_read(&mut temp); + let celcius = convert_to_celcius(v); + info!("Internal temp: {} ({} C)", v, celcius); + + // Read internal voltage reference + let v = adc.blocking_read(&mut vrefint); + info!("VrefInt: {}", v); + + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/stm32f4/src/bin/adc_dma.rs b/embassy/examples/stm32f4/src/bin/adc_dma.rs new file mode 100644 index 0000000..43a761e --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/adc_dma.rs @@ -0,0 +1,83 @@ +#![no_std] +#![no_main] +use cortex_m::singleton; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, RingBufferedAdc, SampleTime, Sequence}; +use embassy_stm32::Peripherals; +use embassy_time::Instant; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + spawner.must_spawn(adc_task(p)); +} + +#[embassy_executor::task] +async fn adc_task(mut p: Peripherals) { + const ADC_BUF_SIZE: usize = 1024; + let adc_data: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); + let adc_data2: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT2 : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); + + let adc = Adc::new(p.ADC1); + let adc2 = Adc::new(p.ADC2); + + let mut adc: RingBufferedAdc = adc.into_ring_buffered(p.DMA2_CH0, adc_data); + let mut adc2: RingBufferedAdc = adc2.into_ring_buffered(p.DMA2_CH2, adc_data2); + + adc.set_sample_sequence(Sequence::One, &mut p.PA0, SampleTime::CYCLES112); + adc.set_sample_sequence(Sequence::Two, &mut p.PA2, SampleTime::CYCLES112); + adc2.set_sample_sequence(Sequence::One, &mut p.PA1, SampleTime::CYCLES112); + adc2.set_sample_sequence(Sequence::Two, &mut p.PA3, SampleTime::CYCLES112); + + // Note that overrun is a big consideration in this implementation. Whatever task is running the adc.read() calls absolutely must circle back around + // to the adc.read() call before the DMA buffer is wrapped around > 1 time. At this point, the overrun is so significant that the context of + // what channel is at what index is lost. The buffer must be cleared and reset. This *is* handled here, but allowing this to happen will cause + // a reduction of performance as each time the buffer is reset, the adc & dma buffer must be restarted. + + // An interrupt executor with a higher priority than other tasks may be a good approach here, allowing this task to wake and read the buffer most + // frequently. + let mut tic = Instant::now(); + let mut buffer1 = [0u16; 512]; + let mut buffer2 = [0u16; 512]; + let _ = adc.start(); + let _ = adc2.start(); + loop { + match adc.read(&mut buffer1).await { + Ok(_data) => { + let toc = Instant::now(); + info!( + "\n adc1: {} dt = {}, n = {}", + buffer1[0..16], + (toc - tic).as_micros(), + _data + ); + tic = toc; + } + Err(e) => { + warn!("Error: {:?}", e); + buffer1 = [0u16; 512]; + let _ = adc.start(); + } + } + + match adc2.read(&mut buffer2).await { + Ok(_data) => { + let toc = Instant::now(); + info!( + "\n adc2: {} dt = {}, n = {}", + buffer2[0..16], + (toc - tic).as_micros(), + _data + ); + tic = toc; + } + Err(e) => { + warn!("Error: {:?}", e); + buffer2 = [0u16; 512]; + let _ = adc2.start(); + } + } + } +} diff --git a/embassy/examples/stm32f4/src/bin/blinky.rs b/embassy/examples/stm32f4/src/bin/blinky.rs new file mode 100644 index 0000000..31cce82 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PB7, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32f4/src/bin/button.rs b/embassy/examples/stm32f4/src/bin/button.rs new file mode 100644 index 0000000..5649089 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/button.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +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::Down); + let mut led1 = Output::new(p.PB0, Level::High, Speed::Low); + let _led2 = Output::new(p.PB7, Level::High, Speed::Low); + let mut led3 = Output::new(p.PB14, Level::High, Speed::Low); + + loop { + if button.is_high() { + info!("high"); + led1.set_high(); + led3.set_low(); + } else { + info!("low"); + led1.set_low(); + led3.set_high(); + } + } +} diff --git a/embassy/examples/stm32f4/src/bin/button_exti.rs b/embassy/examples/stm32f4/src/bin/button_exti.rs new file mode 100644 index 0000000..2a546da --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); + + info!("Press the USER button..."); + + loop { + button.wait_for_rising_edge().await; + info!("Pressed!"); + button.wait_for_falling_edge().await; + info!("Released!"); + } +} diff --git a/embassy/examples/stm32f4/src/bin/can.rs b/embassy/examples/stm32f4/src/bin/can.rs new file mode 100644 index 0000000..8e3beee --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/can.rs @@ -0,0 +1,68 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::can::filter::Mask32; +use embassy_stm32::can::{ + Can, Fifo, Frame, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, StandardId, TxInterruptHandler, +}; +use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::peripherals::CAN1; +use embassy_time::Instant; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + CAN1_RX0 => Rx0InterruptHandler; + CAN1_RX1 => Rx1InterruptHandler; + CAN1_SCE => SceInterruptHandler; + CAN1_TX => TxInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut p = embassy_stm32::init(Default::default()); + + // The next two lines are a workaround for testing without transceiver. + // To synchronise to the bus the RX input needs to see a high level. + // Use `mem::forget()` to release the borrow on the pin but keep the + // pull-up resistor enabled. + let rx_pin = Input::new(&mut p.PA11, Pull::Up); + core::mem::forget(rx_pin); + + let mut can = Can::new(p.CAN1, p.PA11, p.PA12, Irqs); + + can.modify_filters().enable_bank(0, Fifo::Fifo0, Mask32::accept_all()); + + can.modify_config() + .set_loopback(true) // Receive own frames + .set_silent(true) + .set_bitrate(1_000_000); + + can.enable().await; + + let mut i: u8 = 0; + loop { + let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), &[i]).unwrap(); + let tx_ts = Instant::now(); + can.write(&tx_frame).await; + + let envelope = can.read().await.unwrap(); + + // We can measure loopback latency by using receive timestamp in the `Envelope`. + // Our frame is ~55 bits long (exlcuding bit stuffing), so at 1mbps loopback delay is at least 55 us. + // When measured with `tick-hz-1_000_000` actual latency is 80~83 us, giving a combined hardware and software + // overhead of ~25 us. Note that CPU frequency can greatly affect the result. + let latency = envelope.ts.saturating_duration_since(tx_ts); + + info!( + "loopback frame {=u8}, latency: {} us", + envelope.frame.data()[0], + latency.as_micros() + ); + i = i.wrapping_add(1); + } +} diff --git a/embassy/examples/stm32f4/src/bin/dac.rs b/embassy/examples/stm32f4/src/bin/dac.rs new file mode 100644 index 0000000..dd2a457 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/dac.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dac::{DacCh1, Value}; +use embassy_stm32::dma::NoDma; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_stm32::init(Default::default()); + info!("Hello World, dude!"); + + let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); + + loop { + for v in 0..=255 { + dac.set(Value::Bit8(to_sine_wave(v))); + } + } +} + +use micromath::F32Ext; + +fn to_sine_wave(v: u8) -> u8 { + if v >= 128 { + // top half + let r = 3.14 * ((v - 128) as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } else { + // bottom half + let r = 3.14 + 3.14 * (v as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } +} diff --git a/embassy/examples/stm32f4/src/bin/eth.rs b/embassy/examples/stm32f4/src/bin/eth.rs new file mode 100644 index 0000000..baed964 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/eth.rs @@ -0,0 +1,130 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, StackResources}; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_stm32::peripherals::ETH; +use embassy_stm32::rng::Rng; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; +use embassy_time::Timer; +use embedded_io_async::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + HASH_RNG => rng::InterruptHandler; +}); + +type Device = Ethernet<'static, ETH, GenericSMI>; + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL180, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 180 / 2 = 180Mhz. + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Generate random seed. + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + let _ = rng.async_fill_bytes(&mut seed).await; + let seed = u64::from_le_bytes(seed); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + static PACKETS: StaticCell> = StaticCell::new(); + let device = Ethernet::new( + PACKETS.init(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI::new(0), + mac_addr, + ); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + // Ensure DHCP configuration is up before trying connect + stack.wait_config_up().await; + + info!("Network task initialized"); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); + info!("connecting..."); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + info!("connect error: {:?}", e); + Timer::after_secs(1).await; + continue; + } + info!("connected!"); + let buf = [0; 1024]; + loop { + let r = socket.write_all(&buf).await; + if let Err(e) = r { + info!("write error: {:?}", e); + break; + } + Timer::after_secs(1).await; + } + } +} diff --git a/embassy/examples/stm32f4/src/bin/eth_compliance_test.rs b/embassy/examples/stm32f4/src/bin/eth_compliance_test.rs new file mode 100644 index 0000000..5946fed --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/eth_compliance_test.rs @@ -0,0 +1,77 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue, StationManagement}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + HASH_RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL180, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 180 / 2 = 180Mhz. + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + + info!("Hello Compliance World!"); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + const PHY_ADDR: u8 = 0; + static PACKETS: StaticCell> = StaticCell::new(); + let mut device = Ethernet::new( + PACKETS.init(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI::new(PHY_ADDR), + mac_addr, + ); + + let sm = unsafe { device.station_management() }; + + // Just an example. Exact register settings depend on the specific PHY and test. + sm.smi_write(PHY_ADDR, 0, 0x2100); + sm.smi_write(PHY_ADDR, 11, 0xA000); + + // NB: Remember to reset the PHY after testing before starting the networking stack + + loop { + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/stm32f4/src/bin/eth_w5500.rs b/embassy/examples/stm32f4/src/bin/eth_w5500.rs new file mode 100644 index 0000000..6e6bef0 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/eth_w5500.rs @@ -0,0 +1,134 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, StackResources}; +use embassy_net_wiznet::chip::W5500; +use embassy_net_wiznet::{Device, Runner, State}; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_stm32::mode::Async; +use embassy_stm32::rng::Rng; +use embassy_stm32::spi::Spi; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, peripherals, rng, spi, Config}; +use embassy_time::{Delay, Timer}; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_io_async::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + HASH_RNG => rng::InterruptHandler; +}); + +type EthernetSPI = ExclusiveDevice, Output<'static>, Delay>; +#[embassy_executor::task] +async fn ethernet_task(runner: Runner<'static, W5500, EthernetSPI, ExtiInput<'static>, Output<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL180, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 180 / 2 = 180Mhz. + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + unwrap!(rng.async_fill_bytes(&mut seed).await); + let seed = u64::from_le_bytes(seed); + + let mut spi_cfg = spi::Config::default(); + spi_cfg.frequency = Hertz(50_000_000); // up to 50m works + let (miso, mosi, clk) = (p.PA6, p.PA7, p.PA5); + let spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA2_CH3, p.DMA2_CH0, spi_cfg); + let cs = Output::new(p.PA4, Level::High, Speed::VeryHigh); + let spi = unwrap!(ExclusiveDevice::new(spi, cs, Delay)); + + let w5500_int = ExtiInput::new(p.PB0, p.EXTI0, Pull::Up); + let w5500_reset = Output::new(p.PB1, Level::High, Speed::VeryHigh); + + let mac_addr = [0x02, 234, 3, 4, 82, 231]; + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(State::<2, 2>::new()); + let (device, runner) = embassy_net_wiznet::new(mac_addr, state, spi, w5500_int, w5500_reset) + .await + .unwrap(); + unwrap!(spawner.spawn(ethernet_task(runner))); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + // Ensure DHCP configuration is up before trying connect + stack.wait_config_up().await; + + info!("Network task initialized"); + + // Then we can use it! + let mut rx_buffer = [0; 1024]; + let mut tx_buffer = [0; 1024]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); + info!("connecting..."); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + info!("connect error: {:?}", e); + Timer::after_secs(1).await; + continue; + } + info!("connected!"); + let buf = [0; 1024]; + loop { + let r = socket.write_all(&buf).await; + if let Err(e) = r { + info!("write error: {:?}", e); + break; + } + Timer::after_secs(1).await; + } + } +} diff --git a/embassy/examples/stm32f4/src/bin/flash.rs b/embassy/examples/stm32f4/src/bin/flash.rs new file mode 100644 index 0000000..1e8caba --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/flash.rs @@ -0,0 +1,64 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::{Blocking, Flash}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello Flash!"); + + // Once can also call `into_regions()` to get access to NorFlash implementations + // for each of the unique characteristics. + let mut f = Flash::new_blocking(p.FLASH); + + // Sector 5 + test_flash(&mut f, 128 * 1024, 128 * 1024); + + // Sectors 11..=16, across banks (128K, 16K, 16K, 16K, 16K, 64K) + test_flash(&mut f, (1024 - 128) * 1024, 256 * 1024); + + // Sectors 23, last in bank 2 + test_flash(&mut f, (2048 - 128) * 1024, 128 * 1024); +} + +fn test_flash(f: &mut Flash<'_, Blocking>, offset: u32, size: u32) { + info!("Testing offset: {=u32:#X}, size: {=u32:#X}", offset, size); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(offset, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.blocking_erase(offset, offset + size)); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(offset, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write( + offset, + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + )); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(offset, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!( + &buf[..], + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + ); +} diff --git a/embassy/examples/stm32f4/src/bin/flash_async.rs b/embassy/examples/stm32f4/src/bin/flash_async.rs new file mode 100644 index 0000000..493a536 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/flash_async.rs @@ -0,0 +1,84 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::flash::{Flash, InterruptHandler}; +use embassy_stm32::gpio::{AnyPin, Level, Output, Pin, Speed}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FLASH => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello Flash!"); + + let mut f = Flash::new(p.FLASH, Irqs); + + // Led should blink uninterrupted during ~2sec erase operation + spawner.spawn(blinky(p.PB7.degrade())).unwrap(); + + // Test on bank 2 in order not to stall CPU. + test_flash(&mut f, 1024 * 1024, 128 * 1024).await; +} + +#[embassy_executor::task] +async fn blinky(p: AnyPin) { + let mut led = Output::new(p, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} + +async fn test_flash<'a>(f: &mut Flash<'a>, offset: u32, size: u32) { + info!("Testing offset: {=u32:#X}, size: {=u32:#X}", offset, size); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(offset, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.erase(offset, offset + size).await); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(offset, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!( + f.write( + offset, + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32 + ] + ) + .await + ); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(offset, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!( + &buf[..], + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + ); +} diff --git a/embassy/examples/stm32f4/src/bin/hello.rs b/embassy/examples/stm32f4/src/bin/hello.rs new file mode 100644 index 0000000..3c29561 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/hello.rs @@ -0,0 +1,19 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let config = Config::default(); + let _p = embassy_stm32::init(config); + + loop { + info!("Hello World!"); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/stm32f4/src/bin/i2c.rs b/embassy/examples/stm32f4/src/bin/i2c.rs new file mode 100644 index 0000000..4a96357 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/i2c.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Error, I2c}; +use embassy_stm32::time::Hertz; +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 mut i2c = I2c::new_blocking(p.I2C2, p.PB10, p.PB11, Hertz(100_000), Default::default()); + + let mut data = [0u8; 1]; + + match 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/embassy/examples/stm32f4/src/bin/i2c_async.rs b/embassy/examples/stm32f4/src/bin/i2c_async.rs new file mode 100644 index 0000000..90d11d4 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/i2c_async.rs @@ -0,0 +1,61 @@ +#![no_std] +#![no_main] + +// Example originally designed for stm32f411ceu6 reading an A1454 hall effect sensor on I2C1 +// DMA peripherals changed to compile for stm32f429zi, for the CI. + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::I2c; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 96; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello world!"); + let p = embassy_stm32::init(Default::default()); + + let mut i2c = I2c::new( + p.I2C1, + p.PB8, + p.PB7, + Irqs, + p.DMA1_CH6, + p.DMA1_CH0, + Hertz(100_000), + Default::default(), + ); + + loop { + let a1454_read_sensor_command = [0x1F]; + let mut sensor_data_buffer: [u8; 4] = [0, 0, 0, 0]; + + match i2c + .write_read(ADDRESS, &a1454_read_sensor_command, &mut sensor_data_buffer) + .await + { + Ok(()) => { + // Convert 12-bit signed integer into 16-bit signed integer. + // Is the 12 bit number negative? + if (sensor_data_buffer[2] & 0b00001000) == 0b0001000 { + sensor_data_buffer[2] = sensor_data_buffer[2] | 0b11110000; + } + + let mut sensor_value_raw: u16 = sensor_data_buffer[3].into(); + sensor_value_raw |= (sensor_data_buffer[2] as u16) << 8; + let sensor_value: u16 = sensor_value_raw.into(); + let sensor_value = sensor_value as i16; + info!("Data: {}", sensor_value); + } + Err(e) => error!("I2C Error during read: {:?}", e), + } + } +} diff --git a/embassy/examples/stm32f4/src/bin/i2c_comparison.rs b/embassy/examples/stm32f4/src/bin/i2c_comparison.rs new file mode 100644 index 0000000..55c4891 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/i2c_comparison.rs @@ -0,0 +1,134 @@ +#![no_std] +#![no_main] + +// Example originally designed for stm32f411ceu6 with three A1454 hall effect sensors, connected to I2C1, 2 and 3 +// on the pins referenced in the peripheral definitions. +// Pins and DMA peripherals changed to compile for stm32f429zi, to work with the CI. +// MUST be compiled in release mode to see actual performance, otherwise the async transactions take 2x +// as long to complete as the blocking ones! + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::I2c; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use embassy_time::Instant; +use futures_util::future::try_join3; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 96; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; + I2C2_EV => i2c::EventInterruptHandler; + I2C2_ER => i2c::ErrorInterruptHandler; + I2C3_EV => i2c::EventInterruptHandler; + I2C3_ER => i2c::ErrorInterruptHandler; +}); + +/// Convert 12-bit signed integer within a 4 byte long buffer into 16-bit signed integer. +fn a1454_buf_to_i16(buffer: &[u8; 4]) -> i16 { + let lower = buffer[3]; + let mut upper = buffer[2]; + // Fill in additional 1s if the 12 bit number is negative. + if (upper & 0b00001000) == 0b0001000 { + upper = upper | 0b11110000; + } + + let mut sensor_value_raw: u16 = lower.into(); + sensor_value_raw |= (upper as u16) << 8; + let sensor_value: u16 = sensor_value_raw.into(); + let sensor_value = sensor_value as i16; + sensor_value +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Setting up peripherals."); + let p = embassy_stm32::init(Default::default()); + + let mut i2c1 = I2c::new( + p.I2C1, + p.PB8, + p.PB7, + Irqs, + p.DMA1_CH6, + p.DMA1_CH0, + Hertz(100_000), + Default::default(), + ); + + let mut i2c2 = I2c::new( + p.I2C2, + p.PB10, + p.PB11, + Irqs, + p.DMA1_CH7, + p.DMA1_CH3, + Hertz(100_000), + Default::default(), + ); + + let mut i2c3 = I2c::new( + p.I2C3, + p.PA8, + p.PC9, + Irqs, + p.DMA1_CH4, + p.DMA1_CH2, + Hertz(100_000), + Default::default(), + ); + + let a1454_read_sensor_command = [0x1F]; + let mut i2c1_buffer: [u8; 4] = [0, 0, 0, 0]; + let mut i2c2_buffer: [u8; 4] = [0, 0, 0, 0]; + let mut i2c3_buffer: [u8; 4] = [0, 0, 0, 0]; + loop { + // Blocking reads one after the other. Completes in about 2000us. + let blocking_read_start_us = Instant::now().as_micros(); + match i2c1.blocking_write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c1_buffer) { + Ok(()) => {} + Err(e) => error!("I2C Error: {:?}", e), + } + match i2c2.blocking_write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c2_buffer) { + Ok(()) => {} + Err(e) => error!("I2C Error: {:?}", e), + } + match i2c3.blocking_write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c3_buffer) { + Ok(()) => {} + Err(e) => error!("I2C Error: {:?}", e), + } + let blocking_read_total_us = Instant::now().as_micros() - blocking_read_start_us; + info!( + "Blocking reads completed in {}us: i2c1: {} i2c2: {} i2c3: {}", + blocking_read_total_us, + a1454_buf_to_i16(&i2c1_buffer), + a1454_buf_to_i16(&i2c2_buffer), + a1454_buf_to_i16(&i2c3_buffer) + ); + + // Async reads overlapping. Completes in about 1000us. + let async_read_start_us = Instant::now().as_micros(); + + let i2c1_result = i2c1.write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c1_buffer); + let i2c2_result = i2c2.write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c2_buffer); + let i2c3_result = i2c3.write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c3_buffer); + + // Wait for all three transactions to finish, or any one of them to fail. + match try_join3(i2c1_result, i2c2_result, i2c3_result).await { + Ok(_) => { + let async_read_total_us = Instant::now().as_micros() - async_read_start_us; + info!( + "Async reads completed in {}us: i2c1: {} i2c2: {} i2c3: {}", + async_read_total_us, + a1454_buf_to_i16(&i2c1_buffer), + a1454_buf_to_i16(&i2c2_buffer), + a1454_buf_to_i16(&i2c3_buffer) + ); + } + Err(e) => error!("I2C Error during async write-read: {}", e), + }; + } +} diff --git a/embassy/examples/stm32f4/src/bin/i2s_dma.rs b/embassy/examples/stm32f4/src/bin/i2s_dma.rs new file mode 100644 index 0000000..6839284 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/i2s_dma.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2s::{Config, I2S}; +use embassy_stm32::time::Hertz; +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 dma_buffer = [0x00_u16; 128]; + + let mut i2s = I2S::new_txonly( + p.SPI2, + p.PC3, // sd + p.PB12, // ws + p.PB10, // ck + p.PC6, // mck + p.DMA1_CH4, + &mut dma_buffer, + Hertz(1_000_000), + Config::default(), + ); + + for i in 0_u16.. { + i2s.write(&mut [i * 2; 64]).await.ok(); + i2s.write(&mut [i * 2 + 1; 64]).await.ok(); + } +} diff --git a/embassy/examples/stm32f4/src/bin/input_capture.rs b/embassy/examples/stm32f4/src/bin/input_capture.rs new file mode 100644 index 0000000..49de33d --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/input_capture.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_stm32::time::khz; +use embassy_stm32::timer::input_capture::{CapturePin, InputCapture}; +use embassy_stm32::timer::{self, Channel}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +/// Connect PB2 and PB10 with a 1k Ohm resistor + +#[embassy_executor::task] +async fn blinky(led: peripherals::PB2) { + let mut led = Output::new(led, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} + +bind_interrupts!(struct Irqs { + TIM2 => timer::CaptureCompareInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + unwrap!(spawner.spawn(blinky(p.PB2))); + + let ch3 = CapturePin::new_ch3(p.PB10, Pull::None); + let mut ic = InputCapture::new(p.TIM2, None, None, Some(ch3), None, Irqs, khz(1000), Default::default()); + + loop { + info!("wait for risign edge"); + ic.wait_for_rising_edge(Channel::Ch3).await; + + let capture_value = ic.get_capture_value(Channel::Ch3); + info!("new capture! {}", capture_value); + } +} diff --git a/embassy/examples/stm32f4/src/bin/mco.rs b/embassy/examples/stm32f4/src/bin/mco.rs new file mode 100644 index 0000000..eb7bb62 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/mco.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::rcc::{Mco, Mco1Source, Mco2Source, McoPrescaler}; +use embassy_time::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 _mco1 = Mco::new(p.MCO1, p.PA8, Mco1Source::HSI, McoPrescaler::DIV1); + let _mco2 = Mco::new(p.MCO2, p.PC9, Mco2Source::PLL, McoPrescaler::DIV4); + let mut led = Output::new(p.PB7, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32f4/src/bin/multiprio.rs b/embassy/examples/stm32f4/src/bin/multiprio.rs new file mode 100644 index 0000000..b462088 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/multiprio.rs @@ -0,0 +1,150 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_stm32::interrupt; +use embassy_stm32::interrupt::{InterruptExt, Priority}; +use embassy_time::{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_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 + embassy_time::block_for(embassy_time::Duration::from_secs(1)); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!(" [med] done in {} ms", ms); + + Timer::after_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 + embassy_time::block_for(embassy_time::Duration::from_secs(2)); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!("[low] done in {} ms", ms); + + Timer::after_ticks(32983).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[interrupt] +unsafe fn UART4() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn UART5() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_stm32::init(Default::default()); + + // STM32s don’t have any interrupts exclusively for software use, but they can all be triggered by software as well as + // by the peripheral, so we can just use any free interrupt vectors which aren’t used by the rest of your application. + // In this case we’re using UART4 and UART5, but there’s nothing special about them. Any otherwise unused interrupt + // vector would work exactly the same. + + // High-priority executor: UART4, priority level 6 + interrupt::UART4.set_priority(Priority::P6); + let spawner = EXECUTOR_HIGH.start(interrupt::UART4); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: UART5, priority level 7 + interrupt::UART5.set_priority(Priority::P7); + let spawner = EXECUTOR_MED.start(interrupt::UART5); + 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/embassy/examples/stm32f4/src/bin/pwm.rs b/embassy/examples/stm32f4/src/bin/pwm.rs new file mode 100644 index 0000000..0481116 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/pwm.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::khz; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_time::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 ch1_pin = PwmPin::new_ch1(p.PE9, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); + + info!("PWM initialized"); + info!("PWM max duty {}", ch1.max_duty_cycle()); + + loop { + ch1.set_duty_cycle_fully_off(); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 4); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 2); + Timer::after_millis(300).await; + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32f4/src/bin/pwm_complementary.rs b/embassy/examples/stm32f4/src/bin/pwm_complementary.rs new file mode 100644 index 0000000..161f43c --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/pwm_complementary.rs @@ -0,0 +1,53 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::khz; +use embassy_stm32::timer::complementary_pwm::{ComplementaryPwm, ComplementaryPwmPin}; +use embassy_stm32::timer::simple_pwm::PwmPin; +use embassy_stm32::timer::Channel; +use embassy_time::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 ch1 = PwmPin::new_ch1(p.PE9, OutputType::PushPull); + let ch1n = ComplementaryPwmPin::new_ch1(p.PA7, OutputType::PushPull); + let mut pwm = ComplementaryPwm::new( + p.TIM1, + Some(ch1), + Some(ch1n), + None, + None, + None, + None, + None, + None, + khz(10), + Default::default(), + ); + + let max = pwm.get_max_duty(); + pwm.set_dead_time(max / 1024); + + pwm.enable(Channel::Ch1); + + info!("PWM initialized"); + info!("PWM max duty {}", max); + + loop { + pwm.set_duty(Channel::Ch1, 0); + Timer::after_millis(300).await; + pwm.set_duty(Channel::Ch1, max / 4); + Timer::after_millis(300).await; + pwm.set_duty(Channel::Ch1, max / 2); + Timer::after_millis(300).await; + pwm.set_duty(Channel::Ch1, max - 1); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32f4/src/bin/pwm_input.rs b/embassy/examples/stm32f4/src/bin/pwm_input.rs new file mode 100644 index 0000000..ce20054 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/pwm_input.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_stm32::time::khz; +use embassy_stm32::timer::pwm_input::PwmInput; +use embassy_stm32::{bind_interrupts, peripherals, timer}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +/// Connect PB2 and PA6 with a 1k Ohm resistor + +#[embassy_executor::task] +async fn blinky(led: peripherals::PB2) { + let mut led = Output::new(led, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} + +bind_interrupts!(struct Irqs { + TIM2 => timer::CaptureCompareInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + unwrap!(spawner.spawn(blinky(p.PB2))); + + let mut pwm_input = PwmInput::new(p.TIM3, p.PA6, Pull::None, khz(10)); + pwm_input.enable(); + + loop { + Timer::after_millis(500).await; + let period = pwm_input.get_period_ticks(); + let width = pwm_input.get_width_ticks(); + let duty_cycle = pwm_input.get_duty_cycle(); + info!( + "period ticks: {} width ticks: {} duty cycle: {}", + period, width, duty_cycle + ); + } +} diff --git a/embassy/examples/stm32f4/src/bin/rtc.rs b/embassy/examples/stm32f4/src/bin/rtc.rs new file mode 100644 index 0000000..82d8a37 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/rtc.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] + +use chrono::{NaiveDate, NaiveDateTime}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let config = Config::default(); + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + loop { + let now: NaiveDateTime = rtc.now().unwrap().into(); + + info!("{}", now.and_utc().timestamp()); + + Timer::after_millis(1000).await; + } +} diff --git a/embassy/examples/stm32f4/src/bin/sdmmc.rs b/embassy/examples/stm32f4/src/bin/sdmmc.rs new file mode 100644 index 0000000..66e4e52 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/sdmmc.rs @@ -0,0 +1,107 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::sdmmc::{DataBlock, Sdmmc}; +use embassy_stm32::time::{mhz, Hertz}; +use embassy_stm32::{bind_interrupts, peripherals, sdmmc, Config}; +use {defmt_rtt as _, panic_probe as _}; + +/// This is a safeguard to not overwrite any data on the SD card. +/// If you don't care about SD card contents, set this to `true` to test writes. +const ALLOW_WRITES: bool = false; + +bind_interrupts!(struct Irqs { + SDIO => sdmmc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL168, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 168 / 2 = 168Mhz. + divq: Some(PllQDiv::DIV7), // 8mhz / 4 * 168 / 7 = 48Mhz. + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let mut sdmmc = Sdmmc::new_4bit( + p.SDIO, + Irqs, + p.DMA2_CH3, + p.PC12, + p.PD2, + p.PC8, + p.PC9, + p.PC10, + p.PC11, + Default::default(), + ); + + // Should print 400kHz for initialization + info!("Configured clock: {}", sdmmc.clock().0); + + let mut err = None; + loop { + match sdmmc.init_card(mhz(24)).await { + Ok(_) => break, + Err(e) => { + if err != Some(e) { + info!("waiting for card error, retrying: {:?}", e); + err = Some(e); + } + } + } + } + + let card = unwrap!(sdmmc.card()); + + info!("Card: {:#?}", Debug2Format(card)); + info!("Clock: {}", sdmmc.clock()); + + // Arbitrary block index + let block_idx = 16; + + // SDMMC uses `DataBlock` instead of `&[u8]` to ensure 4 byte alignment required by the hardware. + let mut block = DataBlock([0u8; 512]); + + sdmmc.read_block(block_idx, &mut block).await.unwrap(); + info!("Read: {=[u8]:X}...{=[u8]:X}", block[..8], block[512 - 8..]); + + if !ALLOW_WRITES { + info!("Writing is disabled."); + loop {} + } + + info!("Filling block with 0x55"); + block.fill(0x55); + sdmmc.write_block(block_idx, &block).await.unwrap(); + info!("Write done"); + + sdmmc.read_block(block_idx, &mut block).await.unwrap(); + info!("Read: {=[u8]:X}...{=[u8]:X}", block[..8], block[512 - 8..]); + + info!("Filling block with 0xAA"); + block.fill(0xAA); + sdmmc.write_block(block_idx, &block).await.unwrap(); + info!("Write done"); + + sdmmc.read_block(block_idx, &mut block).await.unwrap(); + info!("Read: {=[u8]:X}...{=[u8]:X}", block[..8], block[512 - 8..]); +} diff --git a/embassy/examples/stm32f4/src/bin/spi.rs b/embassy/examples/stm32f4/src/bin/spi.rs new file mode 100644 index 0000000..970d819 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/spi.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use {defmt_rtt as _, panic_probe as _}; + +#[entry] +fn main() -> ! { + info!("Hello World, dude!"); + + let p = embassy_stm32::init(Default::default()); + + let mut spi_config = Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new_blocking(p.SPI3, p.PC10, p.PC12, p.PC11, spi_config); + + let mut cs = Output::new(p.PE0, Level::High, Speed::VeryHigh); + + loop { + let mut buf = [0x0Au8; 4]; + cs.set_low(); + unwrap!(spi.blocking_transfer_in_place(&mut buf)); + cs.set_high(); + info!("xfer {=[u8]:x}", buf); + } +} diff --git a/embassy/examples/stm32f4/src/bin/spi_dma.rs b/embassy/examples/stm32f4/src/bin/spi_dma.rs new file mode 100644 index 0000000..7249c83 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/spi_dma.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; +use core::str::from_utf8; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use heapless::String; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut spi_config = Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new(p.SPI1, p.PB3, p.PB5, p.PB4, p.DMA2_CH3, p.DMA2_CH2, spi_config); + + for n in 0u32.. { + let mut write: String<128> = String::new(); + let mut read = [0; 128]; + core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); + spi.transfer(&mut read[0..write.len()], write.as_bytes()).await.ok(); + info!("read via spi+dma: {}", from_utf8(&read).unwrap()); + } +} diff --git a/embassy/examples/stm32f4/src/bin/usart.rs b/embassy/examples/stm32f4/src/bin/usart.rs new file mode 100644 index 0000000..991bf66 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/usart.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USART3 => usart::InterruptHandler; +}); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let config = Config::default(); + let mut usart = Uart::new_blocking(p.USART3, p.PD9, p.PD8, config).unwrap(); + + unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.blocking_read(&mut buf)); + unwrap!(usart.blocking_write(&buf)); + } +} diff --git a/embassy/examples/stm32f4/src/bin/usart_buffered.rs b/embassy/examples/stm32f4/src/bin/usart_buffered.rs new file mode 100644 index 0000000..c99807f --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/usart_buffered.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{BufferedUart, Config}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use embedded_io_async::BufRead; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USART3 => usart::BufferedInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + + let mut tx_buf = [0u8; 32]; + let mut rx_buf = [0u8; 32]; + let mut buf_usart = BufferedUart::new(p.USART3, Irqs, p.PD9, p.PD8, &mut tx_buf, &mut rx_buf, config).unwrap(); + + loop { + let buf = buf_usart.fill_buf().await.unwrap(); + info!("Received: {}", buf); + + // Read bytes have to be explicitly consumed, otherwise fill_buf() will return them again + let n = buf.len(); + buf_usart.consume(n); + } +} diff --git a/embassy/examples/stm32f4/src/bin/usart_dma.rs b/embassy/examples/stm32f4/src/bin/usart_dma.rs new file mode 100644 index 0000000..aaf8d6c --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/usart_dma.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use heapless::String; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USART3 => usart::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, Irqs, p.DMA1_CH3, p.DMA1_CH1, config).unwrap(); + + for n in 0u32.. { + let mut s: String<128> = String::new(); + core::write!(&mut s, "Hello DMA World {}!\r\n", n).unwrap(); + + unwrap!(usart.write(s.as_bytes()).await); + info!("wrote DMA"); + } +} diff --git a/embassy/examples/stm32f4/src/bin/usb_ethernet.rs b/embassy/examples/stm32f4/src/bin/usb_ethernet.rs new file mode 100644 index 0000000..a9504ec --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/usb_ethernet.rs @@ -0,0 +1,194 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::StackResources; +use embassy_stm32::rng::{self, Rng}; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb, 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_async::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +type UsbDriver = Driver<'static, embassy_stm32::peripherals::USB_OTG_FS>; + +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(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await +} + +bind_interrupts!(struct Irqs { + OTG_FS => usb::InterruptHandler; + HASH_RNG => rng::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL168, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 168 / 2 = 168Mhz. + divq: Some(PllQDiv::DIV7), // 8mhz / 4 * 168 / 7 = 48Mhz. + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; + } + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + static OUTPUT_BUFFER: StaticCell<[u8; 256]> = StaticCell::new(); + let ep_out_buffer = &mut OUTPUT_BUFFER.init([0; 256])[..]; + let mut config = embassy_stm32::usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-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. + static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new(); + static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new(); + static CONTROL_BUF: StaticCell<[u8; 128]> = StaticCell::new(); + let mut builder = Builder::new( + driver, + config, + &mut CONFIG_DESC.init([0; 256])[..], + &mut BOS_DESC.init([0; 256])[..], + &mut [], // no msos descriptors + &mut CONTROL_BUF.init([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. + static STATE: StaticCell = StaticCell::new(); + let class = CdcNcmClass::new(&mut builder, STATE.init(State::new()), host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + static NET_STATE: StaticCell> = StaticCell::new(); + let (runner, device) = class.into_embassy_net_device::(NET_STATE.init(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + unwrap!(rng.async_fill_bytes(&mut seed).await); + let seed = u64::from_le_bytes(seed); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + // 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_time::Duration::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/embassy/examples/stm32f4/src/bin/usb_hid_keyboard.rs b/embassy/examples/stm32f4/src/bin/usb_hid_keyboard.rs new file mode 100644 index 0000000..1270995 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/usb_hid_keyboard.rs @@ -0,0 +1,232 @@ +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::Pull; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; +use embassy_usb::control::OutResponse; +use embassy_usb::{Builder, Handler}; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL168, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 168 / 2 = 168Mhz. + divq: Some(PllQDiv::DIV7), // 8mhz / 4 * 168 / 7 = 48Mhz. + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; + } + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("HID keyboard example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + // You can also add a Microsoft OS descriptor. + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut request_handler = MyRequestHandler {}; + let mut device_handler = MyDeviceHandler::new(); + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + builder.handler(&mut device_handler); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 8, + }; + + let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + let (reader, mut writer) = hid.split(); + + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); + + // Do stuff with the class! + let in_fut = async { + loop { + button.wait_for_rising_edge().await; + // signal_pin.wait_for_high().await; + info!("Button pressed!"); + // Create a report with the A key pressed. (no shift modifier) + let report = KeyboardReport { + keycodes: [4, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + // Send the report. + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + + button.wait_for_falling_edge().await; + // signal_pin.wait_for_low().await; + info!("Button released!"); + let report = KeyboardReport { + keycodes: [0, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + }; + + let out_fut = async { + reader.run(false, &mut request_handler).await; + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(in_fut, out_fut)).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} + +struct MyDeviceHandler { + configured: AtomicBool, +} + +impl MyDeviceHandler { + fn new() -> Self { + MyDeviceHandler { + configured: AtomicBool::new(false), + } + } +} + +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { + self.configured.store(false, Ordering::Relaxed); + if enabled { + info!("Device enabled"); + } else { + info!("Device disabled"); + } + } + + fn reset(&mut self) { + self.configured.store(false, Ordering::Relaxed); + info!("Bus reset, the Vbus current limit is 100mA"); + } + + fn addressed(&mut self, addr: u8) { + self.configured.store(false, Ordering::Relaxed); + info!("USB address set to: {}", addr); + } + + fn configured(&mut self, configured: bool) { + self.configured.store(configured, Ordering::Relaxed); + if configured { + info!("Device configured, it may now draw up to the configured current limit from Vbus.") + } else { + info!("Device is no longer configured, the Vbus current limit is 100mA."); + } + } +} diff --git a/embassy/examples/stm32f4/src/bin/usb_hid_mouse.rs b/embassy/examples/stm32f4/src/bin/usb_hid_mouse.rs new file mode 100644 index 0000000..45136f9 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/usb_hid_mouse.rs @@ -0,0 +1,158 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_time::Timer; +use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State}; +use embassy_usb::control::OutResponse; +use embassy_usb::Builder; +use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL168, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 168 / 2 = 168Mhz. + divq: Some(PllQDiv::DIV7), // 8mhz / 4 * 168 / 7 = 48Mhz. + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; + } + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("HID mouse example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut request_handler = MyRequestHandler {}; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: MouseReport::desc(), + request_handler: Some(&mut request_handler), + poll_ms: 60, + max_packet_size: 8, + }; + + let mut writer = HidWriter::<_, 5>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let hid_fut = async { + let mut y: i8 = 5; + loop { + Timer::after_millis(500).await; + + y = -y; + let report = MouseReport { + buttons: 0, + x: 0, + y, + wheel: 0, + pan: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + } + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, hid_fut).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} diff --git a/embassy/examples/stm32f4/src/bin/usb_raw.rs b/embassy/examples/stm32f4/src/bin/usb_raw.rs new file mode 100644 index 0000000..b2d7062 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/usb_raw.rs @@ -0,0 +1,231 @@ +//! Example of using USB without a pre-defined class, but instead responding to +//! raw USB control requests. +//! +//! The host computer can either: +//! * send a command, with a 16-bit request ID, a 16-bit value, and an optional data buffer +//! * request some data, with a 16-bit request ID, a 16-bit value, and a length of data to receive +//! +//! For higher throughput data, you can add some bulk endpoints after creating the alternate, +//! but for low rate command/response, plain control transfers can be very simple and effective. +//! +//! Example code to send/receive data using `nusb`: +//! +//! ```ignore +//! use futures_lite::future::block_on; +//! use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient}; +//! +//! fn main() { +//! let di = nusb::list_devices() +//! .unwrap() +//! .find(|d| d.vendor_id() == 0xc0de && d.product_id() == 0xcafe) +//! .expect("no device found"); +//! let device = di.open().expect("error opening device"); +//! let interface = device.claim_interface(0).expect("error claiming interface"); +//! +//! // Send "hello world" to device +//! let result = block_on(interface.control_out(ControlOut { +//! control_type: ControlType::Vendor, +//! recipient: Recipient::Interface, +//! request: 100, +//! value: 200, +//! index: 0, +//! data: b"hello world", +//! })); +//! println!("{result:?}"); +//! +//! // Receive "hello" from device +//! let result = block_on(interface.control_in(ControlIn { +//! control_type: ControlType::Vendor, +//! recipient: Recipient::Interface, +//! request: 101, +//! value: 201, +//! index: 0, +//! length: 5, +//! })); +//! println!("{result:?}"); +//! } +//! ``` + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::control::{InResponse, OutResponse, Recipient, Request, RequestType}; +use embassy_usb::msos::{self, windows_version}; +use embassy_usb::types::InterfaceNumber; +use embassy_usb::{Builder, Handler}; +use {defmt_rtt as _, panic_probe as _}; + +// Randomly generated UUID because Windows requires you provide one to use WinUSB. +// In principle WinUSB-using software could find this device (or a specific interface +// on it) by its GUID instead of using the VID/PID, but in practice that seems unhelpful. +const DEVICE_INTERFACE_GUIDS: &[&str] = &["{DAC2087C-63FA-458D-A55D-827C0762DEC7}"]; + +bind_interrupts!(struct Irqs { + OTG_FS => usb::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL168, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 168 / 2 = 168Mhz. + divq: Some(PllQDiv::DIV7), // 8mhz / 4 * 168 / 7 = 48Mhz. + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; + } + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-raw example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut handler = ControlHandler { + if_num: InterfaceNumber(0), + }; + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + // Add the Microsoft OS Descriptor (MSOS/MOD) descriptor. + // We tell Windows that this entire device is compatible with the "WINUSB" feature, + // which causes it to use the built-in WinUSB driver automatically, which in turn + // can be used by libusb/rusb software without needing a custom driver or INF file. + // In principle you might want to call msos_feature() just on a specific function, + // if your device also has other functions that still use standard class drivers. + builder.msos_descriptor(windows_version::WIN8_1, 0); + builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + // Add a vendor-specific function (class 0xFF), and corresponding interface, + // that uses our custom handler. + let mut function = builder.function(0xFF, 0, 0); + let mut interface = function.interface(); + let _alternate = interface.alt_setting(0xFF, 0, 0, None); + handler.if_num = interface.interface_number(); + drop(function); + builder.handler(&mut handler); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + usb.run().await; +} + +/// Handle CONTROL endpoint requests and responses. For many simple requests and responses +/// you can get away with only using the control endpoint. +struct ControlHandler { + if_num: InterfaceNumber, +} + +impl Handler for ControlHandler { + /// Respond to HostToDevice control messages, where the host sends us a command and + /// optionally some data, and we can only acknowledge or reject it. + fn control_out<'a>(&'a mut self, req: Request, buf: &'a [u8]) -> Option { + // Log the request before filtering to help with debugging. + info!("Got control_out, request={}, buf={:a}", req, buf); + + // Only handle Vendor request types to an Interface. + if req.request_type != RequestType::Vendor || req.recipient != Recipient::Interface { + return None; + } + + // Ignore requests to other interfaces. + if req.index != self.if_num.0 as u16 { + return None; + } + + // Accept request 100, value 200, reject others. + if req.request == 100 && req.value == 200 { + Some(OutResponse::Accepted) + } else { + Some(OutResponse::Rejected) + } + } + + /// Respond to DeviceToHost control messages, where the host requests some data from us. + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + info!("Got control_in, request={}", req); + + // Only handle Vendor request types to an Interface. + if req.request_type != RequestType::Vendor || req.recipient != Recipient::Interface { + return None; + } + + // Ignore requests to other interfaces. + if req.index != self.if_num.0 as u16 { + return None; + } + + // Respond "hello" to request 101, value 201, when asked for 5 bytes, otherwise reject. + if req.request == 101 && req.value == 201 && req.length == 5 { + buf[..5].copy_from_slice(b"hello"); + Some(InResponse::Accepted(&buf[..5])) + } else { + Some(InResponse::Rejected) + } + } +} diff --git a/embassy/examples/stm32f4/src/bin/usb_serial.rs b/embassy/examples/stm32f4/src/bin/usb_serial.rs new file mode 100644 index 0000000..328b5ef --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/usb_serial.rs @@ -0,0 +1,136 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL168, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 168 / 2 = 168Mhz. + divq: Some(PllQDiv::DIV7), // 8mhz / 4 * 168 / 7 = 48Mhz. + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; + } + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32f4/src/bin/usb_uac_speaker.rs b/embassy/examples/stm32f4/src/bin/usb_uac_speaker.rs new file mode 100644 index 0000000..e22e07e --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/usb_uac_speaker.rs @@ -0,0 +1,390 @@ +#![no_std] +#![no_main] + +use core::cell::{Cell, RefCell}; + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, interrupt, peripherals, timer, usb, Config}; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::signal::Signal; +use embassy_sync::zerocopy_channel; +use embassy_usb::class::uac1; +use embassy_usb::class::uac1::speaker::{self, Speaker}; +use embassy_usb::driver::EndpointError; +use heapless::Vec; +use micromath::F32Ext; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb::InterruptHandler; +}); + +static TIMER: Mutex>>> = + Mutex::new(RefCell::new(None)); + +// A counter signal that is written by the feedback timer, once every `FEEDBACK_REFRESH_PERIOD`. +// At that point, a feedback value is sent to the host. +pub static FEEDBACK_SIGNAL: Signal = Signal::new(); + +// Stereo input +pub const INPUT_CHANNEL_COUNT: usize = 2; + +// This example uses a fixed sample rate of 48 kHz. +pub const SAMPLE_RATE_HZ: u32 = 48_000; +pub const FEEDBACK_COUNTER_TICK_RATE: u32 = 42_000_000; + +// Use 32 bit samples, which allow for a lot of (software) volume adjustment without degradation of quality. +pub const SAMPLE_WIDTH: uac1::SampleWidth = uac1::SampleWidth::Width4Byte; +pub const SAMPLE_WIDTH_BIT: usize = SAMPLE_WIDTH.in_bit(); +pub const SAMPLE_SIZE: usize = SAMPLE_WIDTH as usize; +pub const SAMPLE_SIZE_PER_S: usize = (SAMPLE_RATE_HZ as usize) * INPUT_CHANNEL_COUNT * SAMPLE_SIZE; + +// Size of audio samples per 1 ms - for the full-speed USB frame period of 1 ms. +pub const USB_FRAME_SIZE: usize = SAMPLE_SIZE_PER_S.div_ceil(1000); + +// Select front left and right audio channels. +pub const AUDIO_CHANNELS: [uac1::Channel; INPUT_CHANNEL_COUNT] = [uac1::Channel::LeftFront, uac1::Channel::RightFront]; + +// Factor of two as a margin for feedback (this is an excessive amount) +pub const USB_MAX_PACKET_SIZE: usize = 2 * USB_FRAME_SIZE; +pub const USB_MAX_SAMPLE_COUNT: usize = USB_MAX_PACKET_SIZE / SAMPLE_SIZE; + +// The data type that is exchanged via the zero-copy channel (a sample vector). +pub type SampleBlock = Vec; + +// Feedback is provided in 10.14 format for full-speed endpoints. +pub const FEEDBACK_REFRESH_PERIOD: uac1::FeedbackRefresh = uac1::FeedbackRefresh::Period8Frames; +const FEEDBACK_SHIFT: usize = 14; + +const TICKS_PER_SAMPLE: f32 = (FEEDBACK_COUNTER_TICK_RATE as f32) / (SAMPLE_RATE_HZ as f32); + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +/// Sends feedback messages to the host. +async fn feedback_handler<'d, T: usb::Instance + 'd>( + feedback: &mut speaker::Feedback<'d, usb::Driver<'d, T>>, + feedback_factor: f32, +) -> Result<(), Disconnected> { + let mut packet: Vec = Vec::new(); + + // Collects the fractional component of the feedback value that is lost by rounding. + let mut rest = 0.0_f32; + + loop { + let counter = FEEDBACK_SIGNAL.wait().await; + + packet.clear(); + + let raw_value = counter as f32 * feedback_factor + rest; + let value = raw_value.round(); + rest = raw_value - value; + + let value = value as u32; + packet.push(value as u8).unwrap(); + packet.push((value >> 8) as u8).unwrap(); + packet.push((value >> 16) as u8).unwrap(); + + feedback.write_packet(&packet).await?; + } +} + +/// Handles streaming of audio data from the host. +async fn stream_handler<'d, T: usb::Instance + 'd>( + stream: &mut speaker::Stream<'d, usb::Driver<'d, T>>, + sender: &mut zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) -> Result<(), Disconnected> { + loop { + let mut usb_data = [0u8; USB_MAX_PACKET_SIZE]; + let data_size = stream.read_packet(&mut usb_data).await?; + + let word_count = data_size / SAMPLE_SIZE; + + if word_count * SAMPLE_SIZE == data_size { + // Obtain a buffer from the channel + let samples = sender.send().await; + samples.clear(); + + for w in 0..word_count { + let byte_offset = w * SAMPLE_SIZE; + let sample = u32::from_le_bytes(usb_data[byte_offset..byte_offset + SAMPLE_SIZE].try_into().unwrap()); + + // Fill the sample buffer with data. + samples.push(sample).unwrap(); + } + + sender.send_done(); + } else { + debug!("Invalid USB buffer size of {}, skipped.", data_size); + } + } +} + +/// Receives audio samples from the USB streaming task and can play them back. +#[embassy_executor::task] +async fn audio_receiver_task(mut usb_audio_receiver: zerocopy_channel::Receiver<'static, NoopRawMutex, SampleBlock>) { + loop { + let _samples = usb_audio_receiver.receive().await; + // Use the samples, for example play back via the SAI peripheral. + + // Notify the channel that the buffer is now ready to be reused + usb_audio_receiver.receive_done(); + } +} + +/// Receives audio samples from the host. +#[embassy_executor::task] +async fn usb_streaming_task( + mut stream: speaker::Stream<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>, + mut sender: zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) { + loop { + stream.wait_connection().await; + _ = stream_handler(&mut stream, &mut sender).await; + } +} + +/// Sends sample rate feedback to the host. +/// +/// The `feedback_factor` scales the feedback timer's counter value so that the result is the number of samples that +/// this device played back or "consumed" during one SOF period (1 ms) - in 10.14 format. +/// +/// Ideally, the `feedback_factor` that is calculated below would be an integer for avoiding numerical errors. +/// This is achieved by having `TICKS_PER_SAMPLE` be a power of two. For audio applications at a sample rate of 48 kHz, +/// 24.576 MHz would be one such option. +/// +/// A good choice for the STM32F4, which also has to generate a 48 MHz clock from its HSE (e.g. running at 8 MHz) +/// for USB, is to clock the feedback timer from the MCLK output of the SAI peripheral. The SAI peripheral then uses an +/// external clock. In that case, wiring the MCLK output to the timer clock input is required. +/// +/// This simple example just uses the internal clocks for supplying the feedback timer, +/// and does not even set up a SAI peripheral. +#[embassy_executor::task] +async fn usb_feedback_task(mut feedback: speaker::Feedback<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>) { + let feedback_factor = + ((1 << FEEDBACK_SHIFT) as f32 / TICKS_PER_SAMPLE) / FEEDBACK_REFRESH_PERIOD.frame_count() as f32; + + // Should be 2.3405714285714287... + info!("Using a feedback factor of {}.", feedback_factor); + + loop { + feedback.wait_connection().await; + _ = feedback_handler(&mut feedback, feedback_factor).await; + } +} + +#[embassy_executor::task] +async fn usb_task(mut usb_device: embassy_usb::UsbDevice<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>) { + usb_device.run().await; +} + +/// Checks for changes on the control monitor of the class. +/// +/// In this case, monitor changes of volume or mute state. +#[embassy_executor::task] +async fn usb_control_task(control_monitor: speaker::ControlMonitor<'static>) { + loop { + control_monitor.changed().await; + + for channel in AUDIO_CHANNELS { + let volume = control_monitor.volume(channel).unwrap(); + info!("Volume changed to {} on channel {}.", volume, channel); + } + } +} + +/// Feedback value measurement and calculation +/// +/// Used for measuring/calculating the number of samples that were received from the host during the +/// `FEEDBACK_REFRESH_PERIOD`. +/// +/// Configured in this example with +/// - a refresh period of 8 ms, and +/// - a tick rate of 42 MHz. +/// +/// This gives an (ideal) counter value of 336.000 for every update of the `FEEDBACK_SIGNAL`. +#[interrupt] +fn TIM2() { + static LAST_TICKS: Mutex> = Mutex::new(Cell::new(0)); + static FRAME_COUNT: Mutex> = Mutex::new(Cell::new(0)); + + critical_section::with(|cs| { + // Read timer counter. + let timer = TIMER.borrow(cs).borrow().as_ref().unwrap().regs_gp32(); + + let status = timer.sr().read(); + + const CHANNEL_INDEX: usize = 0; + if status.ccif(CHANNEL_INDEX) { + let ticks = timer.ccr(CHANNEL_INDEX).read(); + + let frame_count = FRAME_COUNT.borrow(cs); + let last_ticks = LAST_TICKS.borrow(cs); + + frame_count.set(frame_count.get() + 1); + if frame_count.get() >= FEEDBACK_REFRESH_PERIOD.frame_count() { + frame_count.set(0); + FEEDBACK_SIGNAL.signal(ticks.wrapping_sub(last_ticks.get())); + last_ticks.set(ticks); + } + }; + + // Clear trigger interrupt flag. + timer.sr().modify(|r| r.set_tif(false)); + }); +} + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL168, + divp: Some(PllPDiv::DIV2), // ((8 MHz / 4) * 168) / 2 = 168 Mhz. + divq: Some(PllQDiv::DIV7), // ((8 MHz / 4) * 168) / 7 = 48 Mhz. + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; + } + let p = embassy_stm32::init(config); + + // Configure all required buffers in a static way. + debug!("USB packet size is {} byte", USB_MAX_PACKET_SIZE); + static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); + let config_descriptor = CONFIG_DESCRIPTOR.init([0; 256]); + + static BOS_DESCRIPTOR: StaticCell<[u8; 32]> = StaticCell::new(); + let bos_descriptor = BOS_DESCRIPTOR.init([0; 32]); + + const CONTROL_BUF_SIZE: usize = 64; + static CONTROL_BUF: StaticCell<[u8; CONTROL_BUF_SIZE]> = StaticCell::new(); + let control_buf = CONTROL_BUF.init([0; CONTROL_BUF_SIZE]); + + const FEEDBACK_BUF_SIZE: usize = 4; + static EP_OUT_BUFFER: StaticCell<[u8; FEEDBACK_BUF_SIZE + CONTROL_BUF_SIZE + USB_MAX_PACKET_SIZE]> = + StaticCell::new(); + let ep_out_buffer = EP_OUT_BUFFER.init([0u8; FEEDBACK_BUF_SIZE + CONTROL_BUF_SIZE + USB_MAX_PACKET_SIZE]); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(speaker::State::new()); + + // Create the driver, from the HAL. + let mut usb_config = usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + usb_config.vbus_detection = false; + + let usb_driver = usb::Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, ep_out_buffer, usb_config); + + // Basic USB device configuration + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-audio-speaker example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + let mut builder = embassy_usb::Builder::new( + usb_driver, + config, + config_descriptor, + bos_descriptor, + &mut [], // no msos descriptors + control_buf, + ); + + // Create the UAC1 Speaker class components + let (stream, feedback, control_monitor) = Speaker::new( + &mut builder, + state, + USB_MAX_PACKET_SIZE as u16, + uac1::SampleWidth::Width4Byte, + &[SAMPLE_RATE_HZ], + &AUDIO_CHANNELS, + FEEDBACK_REFRESH_PERIOD, + ); + + // Create the USB device + let usb_device = builder.build(); + + // Establish a zero-copy channel for transferring received audio samples between tasks + static SAMPLE_BLOCKS: StaticCell<[SampleBlock; 2]> = StaticCell::new(); + let sample_blocks = SAMPLE_BLOCKS.init([Vec::new(), Vec::new()]); + + static CHANNEL: StaticCell> = StaticCell::new(); + let channel = CHANNEL.init(zerocopy_channel::Channel::new(sample_blocks)); + let (sender, receiver) = channel.split(); + + // Run a timer for counting between SOF interrupts. + let mut tim2 = timer::low_level::Timer::new(p.TIM2); + tim2.set_tick_freq(Hertz(FEEDBACK_COUNTER_TICK_RATE)); + tim2.set_trigger_source(timer::low_level::TriggerSource::ITR1); // The USB SOF signal. + + const TIMER_CHANNEL: timer::Channel = timer::Channel::Ch1; + tim2.set_input_ti_selection(TIMER_CHANNEL, timer::low_level::InputTISelection::TRC); + tim2.set_input_capture_prescaler(TIMER_CHANNEL, 0); + tim2.set_input_capture_filter(TIMER_CHANNEL, timer::low_level::FilterValue::FCK_INT_N2); + + // Reset all interrupt flags. + tim2.regs_gp32().sr().write(|r| r.0 = 0); + + // Enable routing of SOF to the timer. + tim2.regs_gp32().or().write(|r| *r = 0b10 << 10); + + tim2.enable_channel(TIMER_CHANNEL, true); + tim2.enable_input_interrupt(TIMER_CHANNEL, true); + + tim2.start(); + + TIMER.lock(|p| p.borrow_mut().replace(tim2)); + + // Unmask the TIM2 interrupt. + unsafe { + cortex_m::peripheral::NVIC::unmask(interrupt::TIM2); + } + + // Launch USB audio tasks. + unwrap!(spawner.spawn(usb_control_task(control_monitor))); + unwrap!(spawner.spawn(usb_streaming_task(stream, sender))); + unwrap!(spawner.spawn(usb_feedback_task(feedback))); + unwrap!(spawner.spawn(usb_task(usb_device))); + unwrap!(spawner.spawn(audio_receiver_task(receiver))); +} diff --git a/embassy/examples/stm32f4/src/bin/wdt.rs b/embassy/examples/stm32f4/src/bin/wdt.rs new file mode 100644 index 0000000..ea27ebc --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/wdt.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::wdg::IndependentWatchdog; +use embassy_time::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.PB7, Level::High, Speed::Low); + + let mut wdt = IndependentWatchdog::new(p.IWDG, 1_000_000); + wdt.unleash(); + + let mut i = 0; + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + + // Pet watchdog for 5 iterations and then stop. + // MCU should restart in 1 second after the last pet. + if i < 5 { + info!("Petting watchdog"); + wdt.pet(); + } + + i += 1; + } +} diff --git a/embassy/examples/stm32f4/src/bin/ws2812_pwm.rs b/embassy/examples/stm32f4/src/bin/ws2812_pwm.rs new file mode 100644 index 0000000..3ab93d6 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/ws2812_pwm.rs @@ -0,0 +1,102 @@ +// Configure TIM3 in PWM mode, and start DMA Transfer(s) to send color data into ws2812. +// We assume the DIN pin of ws2812 connect to GPIO PB4, and ws2812 is properly powered. +// +// The idea is that the data rate of ws2812 is 800 kHz, and it use different duty ratio to represent bit 0 and bit 1. +// Thus we can set TIM overflow at 800 kHz, and change duty ratio of TIM to meet the bit representation of ws2812. +// +// you may also want to take a look at `ws2812_spi.rs` file, which make use of SPI instead. +// +// Warning: +// DO NOT stare at ws2812 directy (especially after each MCU Reset), its (max) brightness could easily make your eyes feel burn. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::khz; +use embassy_stm32::timer::low_level::CountingMode; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_stm32::timer::Channel; +use embassy_time::{Duration, Ticker, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut device_config = embassy_stm32::Config::default(); + + // set SYSCLK/HCLK/PCLK2 to 20 MHz, thus each tick is 0.05 us, + // and ws2812 timings are integer multiples of 0.05 us + { + use embassy_stm32::rcc::*; + use embassy_stm32::time::*; + device_config.enable_debug_during_sleep = true; + device_config.rcc.hse = Some(Hse { + freq: mhz(12), + mode: HseMode::Oscillator, + }); + device_config.rcc.pll_src = PllSource::HSE; + device_config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV6, + mul: PllMul::MUL80, + divp: Some(PllPDiv::DIV8), + divq: None, + divr: None, + }); + device_config.rcc.sys = Sysclk::PLL1_P; + } + + let mut dp = embassy_stm32::init(device_config); + + let mut ws2812_pwm = SimplePwm::new( + dp.TIM3, + Some(PwmPin::new_ch1(dp.PB4, OutputType::PushPull)), + None, + None, + None, + khz(800), // data rate of ws2812 + CountingMode::EdgeAlignedUp, + ); + + // construct ws2812 non-return-to-zero (NRZ) code bit by bit + // ws2812 only need 24 bits for each LED, but we add one bit more to keep PWM output low + + let max_duty = ws2812_pwm.max_duty_cycle(); + let n0 = 8 * max_duty / 25; // ws2812 Bit 0 high level timing + let n1 = 2 * n0; // ws2812 Bit 1 high level timing + + let turn_off = [ + n0, n0, n0, n0, n0, n0, n0, n0, // Green + n0, n0, n0, n0, n0, n0, n0, n0, // Red + n0, n0, n0, n0, n0, n0, n0, n0, // Blue + 0, // keep PWM output low after a transfer + ]; + + let dim_white = [ + n0, n0, n0, n0, n0, n0, n1, n0, // Green + n0, n0, n0, n0, n0, n0, n1, n0, // Red + n0, n0, n0, n0, n0, n0, n1, n0, // Blue + 0, // keep PWM output low after a transfer + ]; + + let color_list = &[&turn_off, &dim_white]; + + let pwm_channel = Channel::Ch1; + + // make sure PWM output keep low on first start + ws2812_pwm.channel(pwm_channel).set_duty_cycle(0); + + // flip color at 2 Hz + let mut ticker = Ticker::every(Duration::from_millis(500)); + + loop { + for &color in color_list { + // with &mut, we can easily reuse same DMA channel multiple times + ws2812_pwm.waveform_up(&mut dp.DMA1_CH2, pwm_channel, color).await; + // ws2812 need at least 50 us low level input to confirm the input data and change it's state + Timer::after_micros(50).await; + // wait until ticker tick + ticker.next().await; + } + } +} diff --git a/embassy/examples/stm32f4/src/bin/ws2812_spi.rs b/embassy/examples/stm32f4/src/bin/ws2812_spi.rs new file mode 100644 index 0000000..e00d143 --- /dev/null +++ b/embassy/examples/stm32f4/src/bin/ws2812_spi.rs @@ -0,0 +1,95 @@ +// Mimic PWM with SPI, to control ws2812 +// We assume the DIN pin of ws2812 connect to GPIO PB5, and ws2812 is properly powered. +// +// The idea is that the data rate of ws2812 is 800 kHz, and it use different duty ratio to represent bit 0 and bit 1. +// Thus we can adjust SPI to send each *round* of data at 800 kHz, and in each *round*, we can adjust each *bit* to mimic 2 different PWM waveform. +// such that the output waveform meet the bit representation of ws2812. +// +// If you want to save SPI for other purpose, you may want to take a look at `ws2812_pwm_dma.rs` file, which make use of TIM and DMA. +// +// Warning: +// DO NOT stare at ws2812 directly (especially after each MCU Reset), its (max) brightness could easily make your eyes feel burn. + +#![no_std] +#![no_main] + +use embassy_stm32::spi; +use embassy_stm32::time::khz; +use embassy_time::{Duration, Ticker, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +// we use 16 bit data frame format of SPI, to let timing as accurate as possible. +// thanks to loose tolerance of ws2812 timing, you can also use 8 bit data frame format, thus you will need to adjust the bit representation. +const N0: u16 = 0b1111100000000000u16; // ws2812 Bit 0 high level timing +const N1: u16 = 0b1111111111000000u16; // ws2812 Bit 1 high level timing + +// ws2812 only need 24 bits for each LED, +// but we add one bit more to keep SPI output low at the end + +static TURN_OFF: [u16; 25] = [ + N0, N0, N0, N0, N0, N0, N0, N0, // Green + N0, N0, N0, N0, N0, N0, N0, N0, // Red + N0, N0, N0, N0, N0, N0, N0, N0, // Blue + 0, // keep SPI output low after last bit +]; + +static DIM_WHITE: [u16; 25] = [ + N0, N0, N0, N0, N0, N0, N1, N0, // Green + N0, N0, N0, N0, N0, N0, N1, N0, // Red + N0, N0, N0, N0, N0, N0, N1, N0, // Blue + 0, // keep SPI output low after last bit +]; + +static COLOR_LIST: &[&[u16]] = &[&TURN_OFF, &DIM_WHITE]; + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let mut device_config = embassy_stm32::Config::default(); + + // Since we use 16 bit SPI, and we need each round 800 kHz, + // thus SPI output speed should be 800 kHz * 16 = 12.8 MHz, and APB clock should be 2 * 12.8 MHz = 25.6 MHz. + // + // As for my setup, with 12 MHz HSE, I got 25.5 MHz SYSCLK, which is slightly slower, but it's ok for ws2812. + { + use embassy_stm32::rcc::{Hse, HseMode, Pll, PllMul, PllPDiv, PllPreDiv, PllSource, Sysclk}; + use embassy_stm32::time::mhz; + device_config.enable_debug_during_sleep = true; + device_config.rcc.hse = Some(Hse { + freq: mhz(12), + mode: HseMode::Oscillator, + }); + device_config.rcc.pll_src = PllSource::HSE; + device_config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV6, + mul: PllMul::MUL102, + divp: Some(PllPDiv::DIV8), + divq: None, + divr: None, + }); + device_config.rcc.sys = Sysclk::PLL1_P; + } + + let dp = embassy_stm32::init(device_config); + + // Set SPI output speed. + // It's ok to blindly set frequency to 12800 kHz, the hal crate will take care of the SPI CR1 BR field. + // And in my case, the real bit rate will be 25.5 MHz / 2 = 12_750 kHz + let mut spi_config = spi::Config::default(); + spi_config.frequency = khz(12_800); + + // Since we only output waveform, then the Rx and Sck and RxDma it is not considered + let mut ws2812_spi = spi::Spi::new_txonly_nosck(dp.SPI1, dp.PB5, dp.DMA2_CH3, spi_config); + + // flip color at 2 Hz + let mut ticker = Ticker::every(Duration::from_millis(500)); + + loop { + for &color in COLOR_LIST { + ws2812_spi.write(color).await.unwrap(); + // ws2812 need at least 50 us low level input to confirm the input data and change it's state + Timer::after_micros(50).await; + // wait until ticker tick + ticker.next().await; + } + } +} diff --git a/embassy/examples/stm32f469/.cargo/config.toml b/embassy/examples/stm32f469/.cargo/config.toml new file mode 100644 index 0000000..0525095 --- /dev/null +++ b/embassy/examples/stm32f469/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F469NIHx" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32f469/Cargo.toml b/embassy/examples/stm32f469/Cargo.toml new file mode 100644 index 0000000..a804098 --- /dev/null +++ b/embassy/examples/stm32f469/Cargo.toml @@ -0,0 +1,22 @@ +[package] +edition = "2021" +name = "embassy-stm32f469-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Specific examples only for stm32f469 +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f469ni", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "1.0.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32f469/build.rs b/embassy/examples/stm32f469/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32f469/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/embassy/examples/stm32f469/src/bin/dsi_bsp.rs b/embassy/examples/stm32f469/src/bin/dsi_bsp.rs new file mode 100644 index 0000000..e4e9e9c --- /dev/null +++ b/embassy/examples/stm32f469/src/bin/dsi_bsp.rs @@ -0,0 +1,694 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dsihost::{blocking_delay_ms, DsiHost, PacketType}; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::ltdc::Ltdc; +use embassy_stm32::pac::dsihost::regs::{Ier0, Ier1}; +use embassy_stm32::pac::ltdc::vals::{Bf1, Bf2, Depol, Hspol, Imr, Pcpol, Pf, Vspol}; +use embassy_stm32::pac::{DSIHOST, LTDC}; +use embassy_stm32::rcc::{ + AHBPrescaler, APBPrescaler, Hse, HseMode, Pll, PllMul, PllPDiv, PllPreDiv, PllQDiv, PllRDiv, PllSource, Sysclk, +}; +use embassy_stm32::time::mhz; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +enum _Orientation { + Landscape, + Portrait, +} + +const _LCD_ORIENTATION: _Orientation = _Orientation::Landscape; +const LCD_X_SIZE: u16 = 800; +const LCD_Y_SIZE: u16 = 480; + +static FERRIS_IMAGE: &[u8; 1536000] = include_bytes!("ferris.bin"); + +// This example allows to display an image on the STM32F469NI-DISCO boards +// with the Revision C, that is at least the boards marked DK32F469I$AU1. +// These boards have the NT35510 display driver. This example does not work +// for the older revisions with OTM8009A, though there are lots of C-examples +// available online where the correct config for the OTM8009A could be gotten from. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + + // HSE is on and ready + config.rcc.hse = Some(Hse { + freq: mhz(8), + mode: HseMode::Oscillator, + }); + config.rcc.pll_src = PllSource::HSE; + + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV8, // PLLM + mul: PllMul::MUL360, // PLLN + divp: Some(PllPDiv::DIV2), + divq: Some(PllQDiv::DIV7), // was DIV4, but STM BSP example uses 7 + divr: Some(PllRDiv::DIV6), + }); + + // This seems to be working, the values in the RCC.PLLSAICFGR are correct according to the debugger. Also on and ready according to CR + config.rcc.pllsai = Some(Pll { + prediv: PllPreDiv::DIV8, // Actually ignored + mul: PllMul::MUL384, // PLLN + divp: None, // PLLP + divq: None, // PLLQ + divr: Some(PllRDiv::DIV7), // PLLR (Sai actually has special clockdiv register) + }); + + let p = embassy_stm32::init(config); + info!("Starting..."); + + let mut led = Output::new(p.PG6, Level::High, Speed::Low); + + // According to UM for the discovery kit, PH7 is an active-low reset for the LCD and touchsensor + let mut reset = Output::new(p.PH7, Level::Low, Speed::High); + + // CubeMX example waits 20 ms before de-asserting reset + embassy_time::block_for(embassy_time::Duration::from_millis(20)); + + // Disable the reset signal and wait 140ms as in the Linux driver (CubeMX waits only 20) + reset.set_high(); + embassy_time::block_for(embassy_time::Duration::from_millis(140)); + + let mut ltdc = Ltdc::new(p.LTDC); + let mut dsi = DsiHost::new(p.DSIHOST, p.PJ2); + let version = dsi.get_version(); + defmt::warn!("en: {:x}", version); + + // Disable the DSI wrapper + dsi.disable_wrapper_dsi(); + + // Disable the DSI host + dsi.disable(); + + // D-PHY clock and digital disable + DSIHOST.pctlr().modify(|w| { + w.set_cke(false); + w.set_den(false) + }); + + // Turn off the DSI PLL + DSIHOST.wrpcr().modify(|w| w.set_pllen(false)); + + // Disable the regulator + DSIHOST.wrpcr().write(|w| w.set_regen(false)); + + // Enable regulator + info!("DSIHOST: enabling regulator"); + DSIHOST.wrpcr().write(|w| w.set_regen(true)); + + for _ in 1..1000 { + // The regulator status (ready or not) can be monitored with the RRS flag in the DSI_WISR register. + // Once it is set, we stop waiting. + if DSIHOST.wisr().read().rrs() { + info!("DSIHOST Regulator ready"); + break; + } + embassy_time::block_for(embassy_time::Duration::from_millis(1)); + } + + if !DSIHOST.wisr().read().rrs() { + defmt::panic!("DSIHOST: enabling regulator FAILED"); + } + + // Set up PLL and enable it + DSIHOST.wrpcr().modify(|w| { + w.set_pllen(true); + w.set_ndiv(125); // PLL loop division factor set to 125 + w.set_idf(2); // PLL input divided by 2 + w.set_odf(0); // PLL output divided by 1 + }); + + /* 500 MHz / 8 = 62.5 MHz = 62500 kHz */ + const LANE_BYTE_CLK_K_HZ: u16 = 62500; // https://github.com/STMicroelectronics/32f469idiscovery-bsp/blob/ec051de2bff3e1b73a9ccd49c9b85abf7320add9/stm32469i_discovery_lcd.c#L224C21-L224C26 + + const _LCD_CLOCK: u16 = 27429; // https://github.com/STMicroelectronics/32f469idiscovery-bsp/blob/ec051de2bff3e1b73a9ccd49c9b85abf7320add9/stm32469i_discovery_lcd.c#L183 + + /* TX_ESCAPE_CKDIV = f(LaneByteClk)/15.62 = 4 */ + const TX_ESCAPE_CKDIV: u8 = (LANE_BYTE_CLK_K_HZ / 15620) as u8; // https://github.com/STMicroelectronics/32f469idiscovery-bsp/blob/ec051de2bff3e1b73a9ccd49c9b85abf7320add9/stm32469i_discovery_lcd.c#L230 + + for _ in 1..1000 { + embassy_time::block_for(embassy_time::Duration::from_millis(1)); + // The PLL status (lock or unlock) can be monitored with the PLLLS flag in the DSI_WISR register. + // Once it is set, we stop waiting. + if DSIHOST.wisr().read().pllls() { + info!("DSIHOST PLL locked"); + break; + } + } + + if !DSIHOST.wisr().read().pllls() { + defmt::panic!("DSIHOST: enabling PLL FAILED"); + } + + // Set the PHY parameters + + // D-PHY clock and digital enable + DSIHOST.pctlr().write(|w| { + w.set_cke(true); + w.set_den(true); + }); + + // Set Clock lane to high-speed mode and disable automatic clock lane control + DSIHOST.clcr().modify(|w| { + w.set_dpcc(true); + w.set_acr(false); + }); + + // Set number of active data lanes to two (lanes 0 and 1) + DSIHOST.pconfr().modify(|w| w.set_nl(1)); + + // Set the DSI clock parameters + + // Set the TX escape clock division factor to 4 + DSIHOST.ccr().modify(|w| w.set_txeckdiv(TX_ESCAPE_CKDIV)); + + // Calculate the bit period in high-speed mode in unit of 0.25 ns (UIX4) + // The equation is : UIX4 = IntegerPart( (1000/F_PHY_Mhz) * 4 ) + // Where : F_PHY_Mhz = (NDIV * HSE_Mhz) / (IDF * ODF) + // Set the bit period in high-speed mode + DSIHOST.wpcr0().modify(|w| w.set_uix4(8)); // 8 is set in the BSP example (confirmed with Debugger) + + // Disable all error interrupts and reset the Error Mask + DSIHOST.ier0().write_value(Ier0(0)); + DSIHOST.ier1().write_value(Ier1(0)); + + // Enable this to fix read timeout + DSIHOST.pcr().modify(|w| w.set_btae(true)); + + const DSI_PIXEL_FORMAT_RGB888: u8 = 0x05; + const _DSI_PIXEL_FORMAT_ARGB888: u8 = 0x00; + + const HACT: u16 = LCD_X_SIZE; + const VACT: u16 = LCD_Y_SIZE; + + const VSA: u16 = 120; + const VBP: u16 = 150; + const VFP: u16 = 150; + const HSA: u16 = 2; + const HBP: u16 = 34; + const HFP: u16 = 34; + + const VIRTUAL_CHANNEL_ID: u8 = 0; + + const COLOR_CODING: u8 = DSI_PIXEL_FORMAT_RGB888; + const VS_POLARITY: bool = false; // DSI_VSYNC_ACTIVE_HIGH == 0 + const HS_POLARITY: bool = false; // DSI_HSYNC_ACTIVE_HIGH == 0 + const DE_POLARITY: bool = false; // DSI_DATA_ENABLE_ACTIVE_HIGH == 0 + const MODE: u8 = 2; // DSI_VID_MODE_BURST; /* Mode Video burst ie : one LgP per line */ + const NULL_PACKET_SIZE: u16 = 0xFFF; + const NUMBER_OF_CHUNKS: u16 = 0; + const PACKET_SIZE: u16 = HACT; /* Value depending on display orientation choice portrait/landscape */ + const HORIZONTAL_SYNC_ACTIVE: u16 = 4; // ((HSA as u32 * LANE_BYTE_CLK_K_HZ as u32 ) / LCD_CLOCK as u32 ) as u16; + const HORIZONTAL_BACK_PORCH: u16 = 77; //((HBP as u32 * LANE_BYTE_CLK_K_HZ as u32 ) / LCD_CLOCK as u32) as u16; + const HORIZONTAL_LINE: u16 = 1982; //(((HACT + HSA + HBP + HFP) as u32 * LANE_BYTE_CLK_K_HZ as u32 ) / LCD_CLOCK as u32 ) as u16; /* Value depending on display orientation choice portrait/landscape */ + // FIXME: Make depend on orientation + const VERTICAL_SYNC_ACTIVE: u16 = VSA; + const VERTICAL_BACK_PORCH: u16 = VBP; + const VERTICAL_FRONT_PORCH: u16 = VFP; + const VERTICAL_ACTIVE: u16 = VACT; + const LP_COMMAND_ENABLE: bool = true; /* Enable sending commands in mode LP (Low Power) */ + + /* Largest packet size possible to transmit in LP mode in VSA, VBP, VFP regions */ + /* Only useful when sending LP packets is allowed while streaming is active in video mode */ + const LP_LARGEST_PACKET_SIZE: u8 = 16; + + /* Largest packet size possible to transmit in LP mode in HFP region during VACT period */ + /* Only useful when sending LP packets is allowed while streaming is active in video mode */ + const LPVACT_LARGEST_PACKET_SIZE: u8 = 0; + + const LPHORIZONTAL_FRONT_PORCH_ENABLE: bool = true; /* Allow sending LP commands during HFP period */ + const LPHORIZONTAL_BACK_PORCH_ENABLE: bool = true; /* Allow sending LP commands during HBP period */ + const LPVERTICAL_ACTIVE_ENABLE: bool = true; /* Allow sending LP commands during VACT period */ + const LPVERTICAL_FRONT_PORCH_ENABLE: bool = true; /* Allow sending LP commands during VFP period */ + const LPVERTICAL_BACK_PORCH_ENABLE: bool = true; /* Allow sending LP commands during VBP period */ + const LPVERTICAL_SYNC_ACTIVE_ENABLE: bool = true; /* Allow sending LP commands during VSync = VSA period */ + const FRAME_BTAACKNOWLEDGE_ENABLE: bool = false; /* Frame bus-turn-around acknowledge enable => false according to debugger */ + + /* Select video mode by resetting CMDM and DSIM bits */ + DSIHOST.mcr().modify(|w| w.set_cmdm(false)); + DSIHOST.wcfgr().modify(|w| w.set_dsim(false)); + + /* Configure the video mode transmission type */ + DSIHOST.vmcr().modify(|w| w.set_vmt(MODE)); + + /* Configure the video packet size */ + DSIHOST.vpcr().modify(|w| w.set_vpsize(PACKET_SIZE)); + + /* Set the chunks number to be transmitted through the DSI link */ + DSIHOST.vccr().modify(|w| w.set_numc(NUMBER_OF_CHUNKS)); + + /* Set the size of the null packet */ + DSIHOST.vnpcr().modify(|w| w.set_npsize(NULL_PACKET_SIZE)); + + /* Select the virtual channel for the LTDC interface traffic */ + DSIHOST.lvcidr().modify(|w| w.set_vcid(VIRTUAL_CHANNEL_ID)); + + /* Configure the polarity of control signals */ + DSIHOST.lpcr().modify(|w| { + w.set_dep(DE_POLARITY); + w.set_hsp(HS_POLARITY); + w.set_vsp(VS_POLARITY); + }); + + /* Select the color coding for the host */ + DSIHOST.lcolcr().modify(|w| w.set_colc(COLOR_CODING)); + + /* Select the color coding for the wrapper */ + DSIHOST.wcfgr().modify(|w| w.set_colmux(COLOR_CODING)); + + /* Set the Horizontal Synchronization Active (HSA) in lane byte clock cycles */ + DSIHOST.vhsacr().modify(|w| w.set_hsa(HORIZONTAL_SYNC_ACTIVE)); + + /* Set the Horizontal Back Porch (HBP) in lane byte clock cycles */ + DSIHOST.vhbpcr().modify(|w| w.set_hbp(HORIZONTAL_BACK_PORCH)); + + /* Set the total line time (HLINE=HSA+HBP+HACT+HFP) in lane byte clock cycles */ + DSIHOST.vlcr().modify(|w| w.set_hline(HORIZONTAL_LINE)); + + /* Set the Vertical Synchronization Active (VSA) */ + DSIHOST.vvsacr().modify(|w| w.set_vsa(VERTICAL_SYNC_ACTIVE)); + + /* Set the Vertical Back Porch (VBP)*/ + DSIHOST.vvbpcr().modify(|w| w.set_vbp(VERTICAL_BACK_PORCH)); + + /* Set the Vertical Front Porch (VFP)*/ + DSIHOST.vvfpcr().modify(|w| w.set_vfp(VERTICAL_FRONT_PORCH)); + + /* Set the Vertical Active period*/ + DSIHOST.vvacr().modify(|w| w.set_va(VERTICAL_ACTIVE)); + + /* Configure the command transmission mode */ + DSIHOST.vmcr().modify(|w| w.set_lpce(LP_COMMAND_ENABLE)); + + /* Low power largest packet size */ + DSIHOST.lpmcr().modify(|w| w.set_lpsize(LP_LARGEST_PACKET_SIZE)); + + /* Low power VACT largest packet size */ + DSIHOST.lpmcr().modify(|w| w.set_lpsize(LP_LARGEST_PACKET_SIZE)); + DSIHOST.lpmcr().modify(|w| w.set_vlpsize(LPVACT_LARGEST_PACKET_SIZE)); + + /* Enable LP transition in HFP period */ + DSIHOST.vmcr().modify(|w| w.set_lphfpe(LPHORIZONTAL_FRONT_PORCH_ENABLE)); + + /* Enable LP transition in HBP period */ + DSIHOST.vmcr().modify(|w| w.set_lphbpe(LPHORIZONTAL_BACK_PORCH_ENABLE)); + + /* Enable LP transition in VACT period */ + DSIHOST.vmcr().modify(|w| w.set_lpvae(LPVERTICAL_ACTIVE_ENABLE)); + + /* Enable LP transition in VFP period */ + DSIHOST.vmcr().modify(|w| w.set_lpvfpe(LPVERTICAL_FRONT_PORCH_ENABLE)); + + /* Enable LP transition in VBP period */ + DSIHOST.vmcr().modify(|w| w.set_lpvbpe(LPVERTICAL_BACK_PORCH_ENABLE)); + + /* Enable LP transition in vertical sync period */ + DSIHOST.vmcr().modify(|w| w.set_lpvsae(LPVERTICAL_SYNC_ACTIVE_ENABLE)); + + /* Enable the request for an acknowledge response at the end of a frame */ + DSIHOST.vmcr().modify(|w| w.set_fbtaae(FRAME_BTAACKNOWLEDGE_ENABLE)); + + /* Configure DSI PHY HS2LP and LP2HS timings */ + const CLOCK_LANE_HS2_LPTIME: u16 = 35; + const CLOCK_LANE_LP2_HSTIME: u16 = 35; + const DATA_LANE_HS2_LPTIME: u8 = 35; + const DATA_LANE_LP2_HSTIME: u8 = 35; + const DATA_LANE_MAX_READ_TIME: u16 = 0; + const STOP_WAIT_TIME: u8 = 10; + + const MAX_TIME: u16 = if CLOCK_LANE_HS2_LPTIME > CLOCK_LANE_LP2_HSTIME { + CLOCK_LANE_HS2_LPTIME + } else { + CLOCK_LANE_LP2_HSTIME + }; + + /* Clock lane timer configuration */ + + /* In Automatic Clock Lane control mode, the DSI Host can turn off the clock lane between two + High-Speed transmission. + To do so, the DSI Host calculates the time required for the clock lane to change from HighSpeed + to Low-Power and from Low-Power to High-Speed. + This timings are configured by the HS2LP_TIME and LP2HS_TIME in the DSI Host Clock Lane Timer Configuration + Register (DSI_CLTCR). + But the DSI Host is not calculating LP2HS_TIME + HS2LP_TIME but 2 x HS2LP_TIME. + + Workaround : Configure HS2LP_TIME and LP2HS_TIME with the same value being the max of HS2LP_TIME or LP2HS_TIME. + */ + + DSIHOST.cltcr().modify(|w| { + w.set_hs2lp_time(MAX_TIME); + w.set_lp2hs_time(MAX_TIME) + }); + + // Data lane timer configuration + DSIHOST.dltcr().modify(|w| { + w.set_hs2lp_time(DATA_LANE_HS2_LPTIME); + w.set_lp2hs_time(DATA_LANE_LP2_HSTIME); + w.set_mrd_time(DATA_LANE_MAX_READ_TIME); + }); + + // Configure the wait period to request HS transmission after a stop state + DSIHOST.pconfr().modify(|w| w.set_sw_time(STOP_WAIT_TIME)); + + const _PCPOLARITY: bool = false; // LTDC_PCPOLARITY_IPC == 0 + + const LTDC_DE_POLARITY: Depol = if !DE_POLARITY { + Depol::ACTIVELOW + } else { + Depol::ACTIVEHIGH + }; + const LTDC_VS_POLARITY: Vspol = if !VS_POLARITY { + Vspol::ACTIVEHIGH + } else { + Vspol::ACTIVELOW + }; + + const LTDC_HS_POLARITY: Hspol = if !HS_POLARITY { + Hspol::ACTIVEHIGH + } else { + Hspol::ACTIVELOW + }; + + /* Timing Configuration */ + const HORIZONTAL_SYNC: u16 = HSA - 1; + const VERTICAL_SYNC: u16 = VERTICAL_SYNC_ACTIVE - 1; + const ACCUMULATED_HBP: u16 = HSA + HBP - 1; + const ACCUMULATED_VBP: u16 = VERTICAL_SYNC_ACTIVE + VERTICAL_BACK_PORCH - 1; + const ACCUMULATED_ACTIVE_W: u16 = LCD_X_SIZE + HSA + HBP - 1; + const ACCUMULATED_ACTIVE_H: u16 = VERTICAL_SYNC_ACTIVE + VERTICAL_BACK_PORCH + VERTICAL_ACTIVE - 1; + const TOTAL_WIDTH: u16 = LCD_X_SIZE + HSA + HBP + HFP - 1; + const TOTAL_HEIGHT: u16 = VERTICAL_SYNC_ACTIVE + VERTICAL_BACK_PORCH + VERTICAL_ACTIVE + VERTICAL_FRONT_PORCH - 1; + + // DISABLE LTDC before making changes + ltdc.disable(); + + // Configure the HS, VS, DE and PC polarity + LTDC.gcr().modify(|w| { + w.set_hspol(LTDC_HS_POLARITY); + w.set_vspol(LTDC_VS_POLARITY); + w.set_depol(LTDC_DE_POLARITY); + w.set_pcpol(Pcpol::RISINGEDGE); + }); + + // Set Synchronization size + LTDC.sscr().modify(|w| { + w.set_hsw(HORIZONTAL_SYNC); + w.set_vsh(VERTICAL_SYNC) + }); + + // Set Accumulated Back porch + LTDC.bpcr().modify(|w| { + w.set_ahbp(ACCUMULATED_HBP); + w.set_avbp(ACCUMULATED_VBP); + }); + + // Set Accumulated Active Width + LTDC.awcr().modify(|w| { + w.set_aah(ACCUMULATED_ACTIVE_H); + w.set_aaw(ACCUMULATED_ACTIVE_W); + }); + + // Set Total Width + LTDC.twcr().modify(|w| { + w.set_totalh(TOTAL_HEIGHT); + w.set_totalw(TOTAL_WIDTH); + }); + + // Set the background color value + LTDC.bccr().modify(|w| { + w.set_bcred(0); + w.set_bcgreen(0); + w.set_bcblue(0) + }); + + // Enable the Transfer Error and FIFO underrun interrupts + LTDC.ier().modify(|w| { + w.set_terrie(true); + w.set_fuie(true); + }); + + // ENABLE LTDC after making changes + ltdc.enable(); + + dsi.enable(); + dsi.enable_wrapper_dsi(); + + // First, delay 120 ms (reason unknown, STM32 Cube Example does it) + blocking_delay_ms(120); + + // 1 to 26 + dsi.write_cmd(0, NT35510_WRITES_0[0], &NT35510_WRITES_0[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_1[0], &NT35510_WRITES_1[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_2[0], &NT35510_WRITES_2[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_3[0], &NT35510_WRITES_3[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_4[0], &NT35510_WRITES_4[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_5[0], &NT35510_WRITES_5[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_6[0], &NT35510_WRITES_6[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_7[0], &NT35510_WRITES_7[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_8[0], &NT35510_WRITES_8[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_9[0], &NT35510_WRITES_9[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_10[0], &NT35510_WRITES_10[1..]).unwrap(); + // 11 missing + dsi.write_cmd(0, NT35510_WRITES_12[0], &NT35510_WRITES_12[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_13[0], &NT35510_WRITES_13[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_14[0], &NT35510_WRITES_14[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_15[0], &NT35510_WRITES_15[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_16[0], &NT35510_WRITES_16[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_17[0], &NT35510_WRITES_17[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_18[0], &NT35510_WRITES_18[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_19[0], &NT35510_WRITES_19[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_20[0], &NT35510_WRITES_20[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_21[0], &NT35510_WRITES_21[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_22[0], &NT35510_WRITES_22[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_23[0], &NT35510_WRITES_23[1..]).unwrap(); + dsi.write_cmd(0, NT35510_WRITES_24[0], &NT35510_WRITES_24[1..]).unwrap(); + + // Tear on + dsi.write_cmd(0, NT35510_WRITES_26[0], &NT35510_WRITES_26[1..]).unwrap(); + + // Set Pixel color format to RGB888 + dsi.write_cmd(0, NT35510_WRITES_37[0], &NT35510_WRITES_37[1..]).unwrap(); + + // Add a delay, otherwise MADCTL not taken + blocking_delay_ms(200); + + // Configure orientation as landscape + dsi.write_cmd(0, NT35510_MADCTL_LANDSCAPE[0], &NT35510_MADCTL_LANDSCAPE[1..]) + .unwrap(); + dsi.write_cmd(0, NT35510_CASET_LANDSCAPE[0], &NT35510_CASET_LANDSCAPE[1..]) + .unwrap(); + dsi.write_cmd(0, NT35510_RASET_LANDSCAPE[0], &NT35510_RASET_LANDSCAPE[1..]) + .unwrap(); + + // Sleep out + dsi.write_cmd(0, NT35510_WRITES_27[0], &NT35510_WRITES_27[1..]).unwrap(); + + // Wait for sleep out exit + blocking_delay_ms(120); + + // Configure COLOR_CODING + dsi.write_cmd(0, NT35510_WRITES_37[0], &NT35510_WRITES_37[1..]).unwrap(); + + /* CABC : Content Adaptive Backlight Control section start >> */ + /* Note : defaut is 0 (lowest Brightness), 0xFF is highest Brightness, try 0x7F : intermediate value */ + dsi.write_cmd(0, NT35510_WRITES_31[0], &NT35510_WRITES_31[1..]).unwrap(); + /* defaut is 0, try 0x2C - Brightness Control Block, Display Dimming & BackLight on */ + dsi.write_cmd(0, NT35510_WRITES_32[0], &NT35510_WRITES_32[1..]).unwrap(); + /* defaut is 0, try 0x02 - image Content based Adaptive Brightness [Still Picture] */ + dsi.write_cmd(0, NT35510_WRITES_33[0], &NT35510_WRITES_33[1..]).unwrap(); + /* defaut is 0 (lowest Brightness), 0xFF is highest Brightness */ + dsi.write_cmd(0, NT35510_WRITES_34[0], &NT35510_WRITES_34[1..]).unwrap(); + /* CABC : Content Adaptive Backlight Control section end << */ + /* Display on */ + dsi.write_cmd(0, NT35510_WRITES_30[0], &NT35510_WRITES_30[1..]).unwrap(); + + /* Send Command GRAM memory write (no parameters) : this initiates frame write via other DSI commands sent by */ + /* DSI host from LTDC incoming pixels in video mode */ + dsi.write_cmd(0, NT35510_WRITES_35[0], &NT35510_WRITES_35[1..]).unwrap(); + + /* Initialize the LCD pixel width and pixel height */ + const WINDOW_X0: u16 = 0; + const WINDOW_X1: u16 = LCD_X_SIZE; // 480 for ferris + const WINDOW_Y0: u16 = 0; + const WINDOW_Y1: u16 = LCD_Y_SIZE; // 800 for ferris + const PIXEL_FORMAT: Pf = Pf::ARGB8888; + //const FBStartAdress: u16 = FB_Address; + const ALPHA: u8 = 255; + const ALPHA0: u8 = 0; + const BACKCOLOR_BLUE: u8 = 0; + const BACKCOLOR_GREEN: u8 = 0; + const BACKCOLOR_RED: u8 = 0; + const IMAGE_WIDTH: u16 = LCD_X_SIZE; // 480 for ferris + const IMAGE_HEIGHT: u16 = LCD_Y_SIZE; // 800 for ferris + + const PIXEL_SIZE: u8 = match PIXEL_FORMAT { + Pf::ARGB8888 => 4, + Pf::RGB888 => 3, + Pf::ARGB4444 | Pf::RGB565 | Pf::ARGB1555 | Pf::AL88 => 2, + _ => 1, + }; + + // Configure the horizontal start and stop position + LTDC.layer(0).whpcr().write(|w| { + w.set_whstpos(LTDC.bpcr().read().ahbp() + 1 + WINDOW_X0); + w.set_whsppos(LTDC.bpcr().read().ahbp() + WINDOW_X1); + }); + + // Configures the vertical start and stop position + LTDC.layer(0).wvpcr().write(|w| { + w.set_wvstpos(LTDC.bpcr().read().avbp() + 1 + WINDOW_Y0); + w.set_wvsppos(LTDC.bpcr().read().avbp() + WINDOW_Y1); + }); + + // Specify the pixel format + LTDC.layer(0).pfcr().write(|w| w.set_pf(PIXEL_FORMAT)); + + // Configures the default color values as zero + LTDC.layer(0).dccr().modify(|w| { + w.set_dcblue(BACKCOLOR_BLUE); + w.set_dcgreen(BACKCOLOR_GREEN); + w.set_dcred(BACKCOLOR_RED); + w.set_dcalpha(ALPHA0); + }); + + // Specifies the constant ALPHA value + LTDC.layer(0).cacr().write(|w| w.set_consta(ALPHA)); + + // Specifies the blending factors + LTDC.layer(0).bfcr().write(|w| { + w.set_bf1(Bf1::CONSTANT); + w.set_bf2(Bf2::CONSTANT); + }); + + // Configure the color frame buffer start address + let fb_start_address: u32 = &FERRIS_IMAGE[0] as *const _ as u32; + info!("Setting Framebuffer Start Address: {:010x}", fb_start_address); + LTDC.layer(0).cfbar().write(|w| w.set_cfbadd(fb_start_address)); + + // Configures the color frame buffer pitch in byte + LTDC.layer(0).cfblr().write(|w| { + w.set_cfbp(IMAGE_WIDTH * PIXEL_SIZE as u16); + w.set_cfbll(((WINDOW_X1 - WINDOW_X0) * PIXEL_SIZE as u16) + 3); + }); + + // Configures the frame buffer line number + LTDC.layer(0).cfblnr().write(|w| w.set_cfblnbr(IMAGE_HEIGHT)); + + // Enable LTDC_Layer by setting LEN bit + LTDC.layer(0).cr().modify(|w| w.set_len(true)); + + //LTDC->SRCR = LTDC_SRCR_IMR; + LTDC.srcr().modify(|w| w.set_imr(Imr::RELOAD)); + + blocking_delay_ms(5000); + + const READ_SIZE: u16 = 1; + let mut data = [1u8; READ_SIZE as usize]; + dsi.read(0, PacketType::DcsShortPktRead(0xDA), READ_SIZE, &mut data) + .unwrap(); + info!("Display ID1: {:#04x}", data); + + dsi.read(0, PacketType::DcsShortPktRead(0xDB), READ_SIZE, &mut data) + .unwrap(); + info!("Display ID2: {:#04x}", data); + + dsi.read(0, PacketType::DcsShortPktRead(0xDC), READ_SIZE, &mut data) + .unwrap(); + info!("Display ID3: {:#04x}", data); + + blocking_delay_ms(500); + + info!("Config done, start blinking LED"); + loop { + led.set_high(); + Timer::after_millis(1000).await; + + // Increase screen brightness + dsi.write_cmd(0, NT35510_CMD_WRDISBV, &[0xFF]).unwrap(); + + led.set_low(); + Timer::after_millis(1000).await; + + // Reduce screen brightness + dsi.write_cmd(0, NT35510_CMD_WRDISBV, &[0x50]).unwrap(); + } +} + +const NT35510_WRITES_0: &[u8] = &[0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01]; // LV2: Page 1 enable +const NT35510_WRITES_1: &[u8] = &[0xB0, 0x03, 0x03, 0x03]; // AVDD: 5.2V +const NT35510_WRITES_2: &[u8] = &[0xB6, 0x46, 0x46, 0x46]; // AVDD: Ratio +const NT35510_WRITES_3: &[u8] = &[0xB1, 0x03, 0x03, 0x03]; // AVEE: -5.2V +const NT35510_WRITES_4: &[u8] = &[0xB7, 0x36, 0x36, 0x36]; // AVEE: Ratio +const NT35510_WRITES_5: &[u8] = &[0xB2, 0x00, 0x00, 0x02]; // VCL: -2.5V +const NT35510_WRITES_6: &[u8] = &[0xB8, 0x26, 0x26, 0x26]; // VCL: Ratio +const NT35510_WRITES_7: &[u8] = &[0xBF, 0x01]; // VGH: 15V (Free Pump) +const NT35510_WRITES_8: &[u8] = &[0xB3, 0x09, 0x09, 0x09]; +const NT35510_WRITES_9: &[u8] = &[0xB9, 0x36, 0x36, 0x36]; // VGH: Ratio +const NT35510_WRITES_10: &[u8] = &[0xB5, 0x08, 0x08, 0x08]; // VGL_REG: -10V +const NT35510_WRITES_12: &[u8] = &[0xBA, 0x26, 0x26, 0x26]; // VGLX: Ratio +const NT35510_WRITES_13: &[u8] = &[0xBC, 0x00, 0x80, 0x00]; // VGMP/VGSP: 4.5V/0V +const NT35510_WRITES_14: &[u8] = &[0xBD, 0x00, 0x80, 0x00]; // VGMN/VGSN:-4.5V/0V +const NT35510_WRITES_15: &[u8] = &[0xBE, 0x00, 0x50]; // VCOM: -1.325V +const NT35510_WRITES_16: &[u8] = &[0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00]; // LV2: Page 0 enable +const NT35510_WRITES_17: &[u8] = &[0xB1, 0xFC, 0x00]; // Display control +const NT35510_WRITES_18: &[u8] = &[0xB6, 0x03]; // Src hold time +const NT35510_WRITES_19: &[u8] = &[0xB5, 0x51]; +const NT35510_WRITES_20: &[u8] = &[0x00, 0x00, 0xB7]; // Gate EQ control +const NT35510_WRITES_21: &[u8] = &[0xB8, 0x01, 0x02, 0x02, 0x02]; // Src EQ control(Mode2) +const NT35510_WRITES_22: &[u8] = &[0xBC, 0x00, 0x00, 0x00]; // Inv. mode(2-dot) +const NT35510_WRITES_23: &[u8] = &[0xCC, 0x03, 0x00, 0x00]; +const NT35510_WRITES_24: &[u8] = &[0xBA, 0x01]; + +const _NT35510_MADCTL_PORTRAIT: &[u8] = &[NT35510_CMD_MADCTL, 0x00]; +const _NT35510_CASET_PORTRAIT: &[u8] = &[NT35510_CMD_CASET, 0x00, 0x00, 0x01, 0xDF]; +const _NT35510_RASET_PORTRAIT: &[u8] = &[NT35510_CMD_RASET, 0x00, 0x00, 0x03, 0x1F]; +const NT35510_MADCTL_LANDSCAPE: &[u8] = &[NT35510_CMD_MADCTL, 0x60]; +const NT35510_CASET_LANDSCAPE: &[u8] = &[NT35510_CMD_CASET, 0x00, 0x00, 0x03, 0x1F]; +const NT35510_RASET_LANDSCAPE: &[u8] = &[NT35510_CMD_RASET, 0x00, 0x00, 0x01, 0xDF]; + +const NT35510_WRITES_26: &[u8] = &[NT35510_CMD_TEEON, 0x00]; // Tear on +const NT35510_WRITES_27: &[u8] = &[NT35510_CMD_SLPOUT, 0x00]; // Sleep out + // 28,29 missing +const NT35510_WRITES_30: &[u8] = &[NT35510_CMD_DISPON, 0x00]; // Display on + +const NT35510_WRITES_31: &[u8] = &[NT35510_CMD_WRDISBV, 0x7F]; +const NT35510_WRITES_32: &[u8] = &[NT35510_CMD_WRCTRLD, 0x2C]; +const NT35510_WRITES_33: &[u8] = &[NT35510_CMD_WRCABC, 0x02]; +const NT35510_WRITES_34: &[u8] = &[NT35510_CMD_WRCABCMB, 0xFF]; +const NT35510_WRITES_35: &[u8] = &[NT35510_CMD_RAMWR, 0x00]; + +//const NT35510_WRITES_36: &[u8] = &[NT35510_CMD_COLMOD, NT35510_COLMOD_RGB565]; // FIXME: Example sets it to 888 but rest of the code seems to configure DSI for 565 +const NT35510_WRITES_37: &[u8] = &[NT35510_CMD_COLMOD, NT35510_COLMOD_RGB888]; + +// More of these: https://elixir.bootlin.com/linux/latest/source/include/video/mipi_display.h#L83 +const _NT35510_CMD_TEEON_GET_DISPLAY_ID: u8 = 0x04; + +const NT35510_CMD_TEEON: u8 = 0x35; +const NT35510_CMD_MADCTL: u8 = 0x36; + +const NT35510_CMD_SLPOUT: u8 = 0x11; +const NT35510_CMD_DISPON: u8 = 0x29; +const NT35510_CMD_CASET: u8 = 0x2A; +const NT35510_CMD_RASET: u8 = 0x2B; +const NT35510_CMD_RAMWR: u8 = 0x2C; /* Memory write */ +const NT35510_CMD_COLMOD: u8 = 0x3A; + +const NT35510_CMD_WRDISBV: u8 = 0x51; /* Write display brightness */ +const _NT35510_CMD_RDDISBV: u8 = 0x52; /* Read display brightness */ +const NT35510_CMD_WRCTRLD: u8 = 0x53; /* Write CTRL display */ +const _NT35510_CMD_RDCTRLD: u8 = 0x54; /* Read CTRL display value */ +const NT35510_CMD_WRCABC: u8 = 0x55; /* Write content adaptative brightness control */ +const NT35510_CMD_WRCABCMB: u8 = 0x5E; /* Write CABC minimum brightness */ + +const _NT35510_COLMOD_RGB565: u8 = 0x55; +const NT35510_COLMOD_RGB888: u8 = 0x77; diff --git a/embassy/examples/stm32f469/src/bin/ferris.bin b/embassy/examples/stm32f469/src/bin/ferris.bin new file mode 100644 index 0000000..ae1c466 --- /dev/null +++ b/embassy/examples/stm32f469/src/bin/ferris.bin @@ -0,0 +1,70 @@ +f2Hx$$ -'edV~-R%a_5}+lD '`EM ]hcdu+h40@N +7@ +RS -^XWs(9ye +=wFF@m { +I~ ;VAcnn +E |!e5;O +%.x +[K)/ +PGJagUgg0YT_{3b7)n|]C u;XJ:0\V ]n i )*hF_=[pv +2FpM uFc$5HP +T3C;vb;;f$] 0C-I-Vxm1HOk +2j)5eqJuTmLr/G 74q<tN0zNY"Q6Nt 1yP. +=cG(\DPvp+ Qqx}t)Zg_''XR\FQR~5`7d= 7hGVE.-yE8 !"x.n!0f2m3!e0j#_HYm#FKXF&Me |W & (,:GR[c#j#q'x'}~~~~~~~~~~*~~~~~~~~~~~~~~~~~~~*~~~~~~~~~~~~~~~~~~~~~~~~~~~.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}0~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}1~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}3~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}3~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||6~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||6~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||8~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||8~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||||||||||||{{{{{;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{=d02Fr%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{=~N9|zzz|A( Uz/~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{;^zzzzzzzzzzzzG2Kb~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzz|~a@zzzyyyyyyyyyyyyy.-i~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzz {J zyyyyyyyyyyyyyyyyyyyyy:IHd}t~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzj4yyyyyyyyyyyyyyyyyyyyyyyyyy|%H|_~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyy z?*~{yyyyyyyyyyyyyyyyyyyyyyyyyyyyyza'\|S~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||||{||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyth zyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxx y!1A-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyy zEQ={yyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxx~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwx ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwJ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwww xw~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww x~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww${(~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvpM~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvve~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuun$~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuK~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuwz~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu+{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttt9~&~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttiV~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttts}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttt~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttss+y*}}}}}}}}}}}}}}}}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttssssssssssr8}}}}}||||||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttssssssssssssssssssssssssssS|||||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttttttttsssssssssssssssssssssssssssssssssu~||||||||||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttsssssssssssssssssssssssssssssssssssssssssssssrrr9|||||||||||||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttttttttsssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrQ!||||||||||{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{z{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttsttttttssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrlZ{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttttttttssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr~{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttttttttsssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr{{{{{{{{{{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttttttttsssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqq r4{{{{{{{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttttttttsssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqkKzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttttttttsssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttttttttsssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzzzzzzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttttttttsssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpp;{zzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqppppppppppppppppppa#|yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpppppppppppppppppppppppppp|^yyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttttttttsssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpppppppppppppppppppppppppppppppppppppppp qjyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuttttttttttttttttttttttttttttttttttttttttttttttssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqppppppppppppppppppppppppppppppppppppppppppppppooo pyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqppppppppppppppppppppppppppppppppppppppppppppppooooooooooooooooooo;z+|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqppppppppppppppppppppppppppppppppppppppppppppppooooooooooooooooooooooooooooG<xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpppppppppppppppppppppppppppppppppppppppppppppppooooooooooooooooooooooooooooooooooooolUxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqppppppppppppppppppppppppppppppppppppppppppppppooooooooooooooooooooooooooooooooooooooooooooooonnnnnnp}yxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttssssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqppppppppppppppppppppppppppppppppppppppppppppppooooooooooooooooooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnd3~xxxxxxxxxy z,|2}9@EJNTW[_bfimry~%{xwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttttttttttttttttttttttttttttttsssssssssssssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpppppppppppppppppppppppppppppppppppppppppppppppoooooooooooooooooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn1v|zvspjec\VRJ~D|ypppppppppppppppppppppppppppppppppooooooooooooooooooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmlllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeapppppppppppppppppppppppppoooooooooooooooooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeee f6wpppppppppppppppooooooooooooooooooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmlllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee;rwooooooooooooooooooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedd}.toooooooooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddd[oooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddduoooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddfRnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddX~nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccG{nnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccc#hJ{nnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccAs~nnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmlllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccc'qmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbkcmmmmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbb'h+rmmmmmmmmmmmmlllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbzkmmmmmlllllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbnllllllllllllllllllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaquGylllllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaesX'plllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaCr}m^N{8t mllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````cNwl~d9tonmllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````` ac#f.iKu}o[Bwmllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````bU{}iGy7t,q n lkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````________ `+g@oMvdYmkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````________________________DqW}mkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````__________________________________________HsHxkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````_______________________________________________^^^^bckkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`````````````````````````````````````````````_______________________________________________^^^^^^^^^^^^^^^^^^^^^bkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````_______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^VzLyjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````_______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^c-pjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]!mjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]`ljjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````_______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]].fkiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]kiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\7i%miiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\j5qiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddccccddddddddddddccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabba[TQONOQU[aaa```````````````````````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\khhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccbbaUI?70{,s+q)o*p+r-v29DS`bbcccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`[C1'mYC4 ) ' -6E[)p3J]_`````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\@m+nhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccc_XTE.uBH8RXabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```^TD U.yOX```````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[~ihhhhhhhhhhhhhgggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccc`K7&t =;0C^bbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`````````````````]F.. [;W``______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[zX|hhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccb]+_iv[[[3=Zabbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````bN{OOO +?Z______________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[Dn8pgggggggggggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbb^;|RRR6@Zbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````_,q((( [F_____________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[iggggggggggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbb1Q DK`aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````````````]TTT#/}X___________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[viggggggggggggggggggfffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbaHu + + +5Zaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````___]uuuVN__^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZMr?rggffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbC|#aSaaaaaaaaaaaa``````````````````````````````````````````````__________________Yo{fff4B\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZ_!jfffffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'nBNaaa``````````````````````````````````````````````_________________________MPUpUUU=Z^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ{ugfffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddcccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaa^qqq:Ha```````````````````````````````________________________________________B!>&&&4Z^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZPtMvfffffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaj5556L```````````````````````______________________________________________\? .Z]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYY'`/leeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*mKS```````_______________________________________________^^^^^^^^^^^^^[:,,,$$$2X]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYgeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaap333TX____________________________________________^^^^^^^^^^^^^^^^^^^^^];8\]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYKp_}eeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_0\___________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^P %000S]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY.c5meeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````_=E____________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^'kssshhh-{]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXX Yhedddddddddddddddddddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````^ccc U___________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]B :P\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXUum +edddddddddddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````^-|\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]V\+++ ;[\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXX*aDqdddddddddddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````Ik K^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]EMMMKV\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[(iddddddddddddddccccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````````````````______^x}999$d\^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]Y@bbb[[[D[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]zzeccccccccccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````````````````______________________ITTTG^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\]:uuuN[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWW[Uxccccccccccccccccccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````````````````_____________________________])mbbbXZ]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\VNI[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWZ.jcccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````````````````____________________________________________N /mmm"G]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\B2[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWe~ecccccccccccbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````______________________________________________^^^^^^\7 vvv2[]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\X#dPRZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW0bdbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^Z !}}} $V]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[NIZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVV8lbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^?{{{=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[)q-YZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVv#fbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa```````````````````````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\RtttE[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[R ,VZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVV5crcbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]I & + + +hhh!L\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[<zzzAZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVKraaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````_______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]\8^^^9\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[X+}fffiii*xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV Waaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`````````````````````````````````````````````_______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]WVLLL)vX[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZP -SSS+++TRYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUU"[kaaaaaaaaaaaaaaaaaaaaa``````````````````````````````````````````````_______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]Rkkk000:Q[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZG444$GYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUX1iaaaaaa``````````````````````````````````````````````_______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\8888N[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZY)t>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU6j`````````````````````````````````````````````______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\?D[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZY #rrr*|VXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUr9k`````````````````````````````````````_______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\OFFF2ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYK###???2TXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTYCn```````````````````````_______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\]<  [YZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYZ7NXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTGp````````````````_______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\1eee1YZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYZ*{;;;EEE:WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTChOs``______________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[V#iZZZ###YZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYW"j (uXWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTS-]Tu_________________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[O7 SYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXNL;;;888QXWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSVi\x__________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[LFFFJYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXJ 2wwwxxx?SWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSJk_z___________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[IvvvBBBDYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXG ,FVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS*\h____________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZY:ooo?ZYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXE@VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRig~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZY)u*** 9YXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWD;VVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRAfs +_^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYSxxx6YXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWAYYYRRR8VVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRr +_^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZX? ggg6YXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWW>5VUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRbyya^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYW,<<UVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU$t +7SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOO=aAj\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWF%mUVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUV0;SSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOUKn\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWJ7UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTB ,DSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOWqQp[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWOTGUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTQAORRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNN8^Sq[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVU* +(JUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTS&s]SRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNQ|_w[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVW8 + fPUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSB3RRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN]t\v[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVO$/TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSKBJRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN3\g{ \[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV#i<TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSQ,^ORRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMM}j}\ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUG 1PTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSB!8PQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMPkp [ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUQ!j(zTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS\@FQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONMMMMMMMMMMMMMMMMMMMMMMMMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMv]ZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;HSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRC&zQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNMLLLLLLLLLLLLLLLLLLLLLLLMNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLo| [ZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUURXdOSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRO&{HQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLL6[]YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTUD;SSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRC /2PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLQ]YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTR.PNSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR*ZIPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL N^t]YYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTKN9SSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQMP=PPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL-X0aYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTB hORRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQ@(3OPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLQ*`YXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSR6 +.DRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ2'LPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKg:dXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSQ,0QRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPN+ kJOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL1YPQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOON5cBMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLGe\sWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQFZPAQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOON4'V8MNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL N^uWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQK0 jGPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOON9B7GONNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLk~hz WVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQP@U  ,1NPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNMAGD0KMNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJfj{VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQM4A nAPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNF-k6HMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLxm}XVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPOJ-#I>LPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNL@Z +-%|BIMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLv WVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUURJ>JQTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPG.Uk6MOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN:$ 8M&5IMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKJIIIIIJKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLqtYVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTSF6,+,5BQTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPI9C ),BJOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMKD:$x ( , j5AELMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKGC>:765333333468:=AEKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLR}YUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTSL5,+++++,.>NRTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPON?%xJ + 6^%{=MNOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMLLH:2,***.4>JLLMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLE5-,,++++++++++++++++,,,1:DHJLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLO| ZUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTN=,+++++++++*-:FQTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOF<-;C#t3<BLPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNNNNMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLD,+++++++++++++++++++++++++*-4;CJLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLO(\UUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTRC/++++++++++++++,0@ORSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOONLG2&x-6?HKLMNOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL9++++++++++++++++++++++++++++++-/:GKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL N-]UUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTM9+++++++++++++++++++-<HOTSSSSSSSSSSSSSSSSSRNNPQRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOPOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLI1+++++++++++++++++++++++++++++++++,5AHLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLO1^TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSTH0++++++++++++++++++++++-2?NSSSSSSRRRRRRRRRRRQLLMMOPRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLD+++++++++++++++++ +.++++++++++++++++++,0<JLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLOBdTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSS1]{Y`++++++++++++++++++++++++++,9INRRRRRRRRRRRRRRRNLLLLLLNPQRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL7++++++++++++++++4C3++++++++++++++++++++/@ILLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL'Uo~TTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSHgx{/++++++++++++++++++++++++++++2=IRRRRRRRRRRRRRRMLLLLLLLMMNPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLG,+++++++++++++++8G+++++++++++++++++++++++3A\"SLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLh|%ZTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS TQl->++++++++++++++++++++++++++++++-3DNQRRRRRRRRRRPLLLLLLLLLLLMNPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL9++++++++++++++ +.Yb+++++++++++++++++++++++/@\sOLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL USSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRSSat]d++++++++++++++++++++++++++++++++*/;FNRQQQQQQQQNLLLLLLLLLLLLLMNOPQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLH0++++++++++++++U^+++++++++++++++++++++++fm +MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL#Sq~SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRS`t1++++++++++++++++++++++++++++++++++-0;JPQQQQQQQMLLLLLLLLLLLLLLLLMNOQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL?+++++++++++++3C8G+++++++++++++++++++++++9]LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLqgxSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRR S`tin++++++++++++++++++++++++++++++++++++++3BJOQQQQNLLLLLLLLLLLLLLLLLLLMMNOPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ3++++++++++++el+++++++++++++++++++++++rx,WLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLOhySSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNj5D+++++++++++++++++++++++++++++++++++++++.6AMQPPMLLLLLLLLLLLLLLLLLLLLLLMNOPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLC+++++++++++!7pv++++++++++++++++++++++/@TmLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL8]r~SSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRSrw,+++++++++++++++++++++++++++++++++++++++++-7ELNLLLLLLLLLLLLLLLLLLLLLLLLLMNOOPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK1+++++++++ +.S] +.++++++++++++++++++++++}LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL SRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQ+YU]++++++++++++++++++++++++++++++++++++++++++++09ELLLLLLLLLLLLLLLLLLLLLLLLLLLLMNNOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLA,+++++++++++++++++++++++++++++++4DLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEdURRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQ+Y6E+++++++++++++++++++++++++++++++++++++++++++++0HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMNOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKJJJJJJKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ5+++++++0ZbOY++++++++++++++++++++++LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL NNiRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQR|/+++++++++++++++++++++++++06E++++++++++++++++++9KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMNOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMIB=:85.)&#y${'*/68:>DLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLC+++++++:H+>+++++++++++++++++++++gnLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLOj|RRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ`f+++++++++++++++++++++++++4|sw4,+++++++++++++++,DLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKJ?- q^A) 0Jf&6FKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL3+++++3tz +.++++++++++++++++++++%:h{LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL7^QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPo|BN+++++++++++++++++++++++++agZa0+++++++++++++++1JLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLMKEB@>7.("t lha l#u'/7>@BEJMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLIB:"t?.@ELLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL?+++++fmai+++++++++++++++++++++LhLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLk~evQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPIf%9++++++++++++++++++++++++%9?L+++++++++++++++;LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLC5,$V 0 +-T!y*3AKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLD/!t /V(:LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLH1+++CO +.++++++++++++++++++++]dEdLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL5[&XQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP0Zuy +.++++++++++++++++++++++++quuy6E,++++++++++++.ELLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJGA+A :&=FJLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLHA$y#$ 3 :@@= 6+@4EKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL=++CO++++++++++++++++++++%:9]LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL NixQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQ~ci++++++++++++++++++++++++.?tw%9++++++++++++4KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMH8)D 5'5DMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLE4hQ'4:<>@@BBA@>=;8.h , 4,@MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLG?CLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLG--?.?++++++++++++++++++++~%TLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLIf>`PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOO[oFQ+++++++++++++++++++++++ +.v{_f%9,++++++++++;LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJD(G>"r@JLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKI1B =Z+AIJJKLLLLLLLLLLLLLKJIE3jF# +5$zDKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKJB5.:KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL2I +.+++++++++++++++++++AMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL{SPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOR';+++++++++++++++++++++++ISfk#8+++++++++.ELLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMH;#x`8DLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMF4=A+6DLMLLLLLLLLLLLLLLLLLLLLLLLMH:/T-CLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLIC;0++8JLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLl{jp+++++++++++++++++++!8LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL NYmPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOn{ .++++++++++++++++++++++/bh3++++++++4KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ3`O,HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ7Y`;FJKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKF>gI-JLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKE91,+++7IKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKFc3C+++++++++++++++++++sx]sLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL<_<_PPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO0Zdj,++++++++++++++++++++++T\]d+=+++++++=LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLH:E'1DLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKA`M)>KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKA+OY>KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLIE>2++++++5HKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK +L+++++++++++++++++++:HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL)WOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNQ}GR++++++++++++++++++++++,pt%9+++++-ELLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK8n *Y&19?@@@@@@@?8.$xOI0HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLF+ 5 15CKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKD2 "+GLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKC:3-+++++++6HKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKBN+++++++++++++++++++LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL~tROOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNTk2B++++++++++++++++++++++RZpt7E/+++5JLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKE!q&To+;HKKKLLLLLLLLLLLLKKKKF8(lM! >:JLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK=KN.HKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKF*@>?KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJE:/,+++++++++4HKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKLg++++++++++++++++++ +.|LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLBbmzOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNR 7+++++++++++++++++++++&:~LU+++<LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLIFKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLG/ +-o5<FLMLLLLLLLLLLLLLLLLLLLLLLLMLD<5i$~>MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLE-1DKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKL@(.JLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLIC;2++++++++++++6HKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKdk++++++++++++++++++8F9]LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL_qPONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN]ppt +.+++++++++++++++++++++fkT]6-CLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK808CJKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK=W/[8HJLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLIG4W/ 4.HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKAM +!${DJKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKI;D aCLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKJA5/-+++++++++++++5HKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK5D++++++++++++++++++RLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLl~TjNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMO_e+++++++++++++++++++++4nsAQILLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLH/+*-5>DHLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLF0h1@LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL@1XU?KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLI3b7JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJE- +I;LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJE@6-++++++++++++++++6HJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ^r++++++++++++++++++{ +MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL<_OgNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMN_qFQ+++++++++++++++++++++^epzNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLA,++++-04;CJLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL@^g?HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLG<^ +3,KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLG"u-FJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI<G ,0KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKC92.++++++++++++++++++8HJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ*TJU+++++++++++++++++DPsLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLOCaNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM1X,=++++++++++++++++++++#8euLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK8++++++++++3<EHJLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ5;'>KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL?$ +-^BLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL@R\<JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJD$,HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJGB8.++++++++++++++++++++,8IJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ K++++++++++++++++++LhLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL]s<]MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLmz1+++++++++++++++++++,ioGbLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLI1++++++++++++/5:@GLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLG&'BLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK@*B4LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL5;-DJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJH2$$}ELLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJB:4.++++++++++++++++++++++,;IJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ~W_+++++++++++++++++~LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL,W8[MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLRmr/+++++++++++++++++++2B%SLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLE-++++++++++++++,-19CIJKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLAT)!sCKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKB o"*GLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ1 @5IJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ;C]ELLLLLLLLLLLLLLLLLLLLLLLLLLLKJH?4-,++++++++++++++++++++++++,;IJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJWn 7++++++++++++++++JULLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLO1XMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLetU]++++++++++++++++++++quvLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK=,+++++++++++++++++*,29>CGLLLLLLLLLLLLLLLLLLLLLLLLM:Hk;KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ:gnALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLG*o>JIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII@aNALLLLLLLLLLLLLLLLLLLLLLLKF@:2,*++++++++++++++++++++++++++->IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII<\+++++++++++++++++=_LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLcw-WMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL/WAL+++++++++++++++++++#8]pLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK4++++++++++++++++++++++-.28@IKKLLLLLLLLLLLLLLLLLK1, +02ILLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLI1 +2C=KLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLF s#yFIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIFlHBLLLLLLLLLLLLLLLLLLKJB70.,+++++++++++++++++++++++++++++-@IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII#QCO++++++++++++++++zLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"S)ULLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLiw.?++++++++++++++++++,in:\LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLH0++++++++++++++++++++++++++*08?DGKLLLLLLLLLLLLI0&DLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLC$~7JLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLC` //IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIF(2ALLLLLLLLLLLLLJFC<3,+++++++++++++++++++++++++++++++++0BIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII++++++++++++++++OYLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLOQLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL%S,++++++++++++++++++3BMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLA-+++++++++++++++++++++++++++++,038>DKLLLLLLLH' 3;ILLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLI; 2-JLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL@T%:HIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIF-D@LLLLLLLLKF>72/++++++++++++++++++++++++++++++++++++2EIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIAM+++++++++++++++$9WoLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLh{}#SLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLbrns .++++++++++++++++++pto|MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL:+++++++++++++++++++++++++++++++++++,28@GHJG% #xCLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLD#{$xHLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLA8J>IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIH/:BLLJHF@6.+++++++++++++++++++++++++++++++++++++++,6GIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII Jn}++++++++++++++++|.XLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL2Zy NLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLQY`,+++++++++++++++++#8SjLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ7+++++++++++++++++++++++++++++++++++++*,01n!3JLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ6 5 oGLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL> 7W@HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHF/H9:50+*+++++++++++++++++++++++++++++++++++++++++:GHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHVldl+++++++++++++++V_LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLxOLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL\n`LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLynzLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLGbah+++++++++++++++++]d@^KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ8+++++++++++++++++++++++++++++++)Y 3?KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKC[LALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLAD(DGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGCVr+++++++++++++++++++++++++++++++++++-=FGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGK=J+++++++++++++ +.%TLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLBbo{OLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL M{IS++++++++++++++++6.VKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ6+++++++++++++++++++++++++++++*iV@KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKF$}=BLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDL&DGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGBN "++++++++++++++++++++++++++++++"8COU^^hJaGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG&P0+++++++++++++V_LLLLLLLLLLLLLLLLLLLLLLLLLLLLLL +MlxLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL7[0@+++++++++++++++,]dKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKH3++++++++++++++++++++++++++++nmBKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKG-TCLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLD]%DGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG<@ +6'++++++++++++++++++++++++++AMuzRhGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGNT]+++++++++++++2CVoLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLjw MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLhv 7+++++++++++++++3p~JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ&QkrR[2B)<3-+++++++++++++++++++++v qDJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJH/EDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLG s"vDGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG3B*++++++++++++++++++ -61BEQz~%PGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGN+++++++++++++3LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLRlgu MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL&Twz/+++++++++++++++PXZnKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJMesx`gHR5+++++++++++++++++$%"zDJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ3XELLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLJ)kBGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG,d*++++++++++++++*=[dx}vGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG.TQ[+++++++++++++gmLLLLLLLLLLLLLLLLLLLLLLLLLLLLL +MfuLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMardj+++++++++++++++,:ZJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJNfrvKT31 .-+++++++++' +3$EJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI7%bILLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK+XCGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGD!v!+++++++-047FpvJcGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGL2++++++++++++-?.XLLLLLLLLLLLLLLLLLLLLLLLLLLLLLdsNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLOSZ,++++++++++++++DO3WJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJNfnsYaIS2B-++++)H!tEJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ5 2kHLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL5 S@FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF>B'++4ANZbms"NFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF?\ms++++++++++++5 +MLLLLLLLLLLLLLLLLLLLLLLLLLLLLZqbrLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLC`9F++++++++++++++/~$QJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJMekpIS/g"yEJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ: +/ 'HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLM9 0=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97:XgwFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF?\#9++++++++++++rxnLLLLLLLLLLLLLLLLLLLLLLLLLLLL)Var NLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL Nt$9++++++++++++++8FyLIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII@]gCIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII7 +2o,-..0012344455555567755555444, 2 -:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, ]]]3UFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF GQg}++++++++++++;I:]LLLLLLLLLLLLLLLLLLLLLLLLLLLLbrLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL7[5++++++++++++++otq} +JIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII>\SAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJ8 +v+++++++++++++++++++++++++++( ?'4EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAO\]]xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFct2+++++++++++5LLLLLLLLLLLLLLLLLLLLLLLLLLLLfzbrMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLhvko +.+++++++++++++0@]nJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'RA@IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII7,"+++++++++++++++++++++++++*D+EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=\\\=Z GFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF Gu}+++++++++++ +.SlLLLLLLLLLLLLLLLLLLLLLLLLLLL9]cr MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL MZa+++++++++++++,hmUiIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIOz:IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJ4%+++++++++++++++++++++++*a%DEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED-\\\IEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE>K+++++++++++LVLLLLLLLLLLLLLLLLLLLLLLLLLLLLbrLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKeIS+++++++++++++3KcHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHJXk[jHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH4 <'++++++++++++++++++++++!gAEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEAa\\\`qJb'OEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"M+++++++++++0xLLLLLLLLLLLLLLLLLLLLLLLLLLLj}bsNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLO|1A+++++++++++++?K;ZHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH>[~LHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHG.W)++++++++++++++++++++%I>EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE6\]_RgHFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEG`5++++++++++0EdLLLLLLLLLLLLLLLLLLLLLLLLLL?``qLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL9[2++++++++++++,pu0UHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHLiw#PHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHG.c+++++++++++++++++++' 9 *9EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\dy]o3TEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEbspv+++++++++++T]PLLLLLLLLLLLLLLLLLLLLLLLLLObr MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLgvwz0++++++++++++0@,SHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHI1U.THHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHE& v+++++++++++++++++*_/EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE.P\m3T$NIEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEJu*=++++++++++ 7yLLLLLLLLLLLLLLLLLLLLLLLLLLobrMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLR`g+++++++++++++_eNHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHMdVjHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHCi($++++++++++++++++v&CEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE+Qy++++++++++ .!SLLLLLLLLLLLLLLLLLLLLLLLLLPjbrMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMWlNW++++++++++++/MGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGKMMNefests5KDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDG5++++++++++PLLLLLLLLLLLLLLLLLLLLLLLL\rbrNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLM^p}1+++++++++++5D{LGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHxGDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDRgv{+++++++++ .w|uLLLLLLLLLLLLLLLLLLLLLLLL1YarLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL!Rmr/+++++++++++ko}HGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG N8UCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC D6E+++++++++8G-WLLLLLLLLLLLLLLLLLLLLLLLLcs MLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLB_T\+++++++++++)<}KFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"NRfCCCCCCCCCCG&M.P6T=XC\TgftvucqNbA[>Y:V5T/Q'MHDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC+++++++syRlLLLLLLLLLLLLLLLLLLLLL7\r|LLLLLLLLLLLLLLLLLLLLLLLLLLLL^q/++++++++diwIDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDE FGHII?ZZkmyGBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCF^0++++++:H$TLLLLLLLLLLLLLLLLLLLL +Mq|OLLLLLLLLLLLLLLLLLLLLLLLLLLQv{1+++++++1A2RDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD K7UE]Re[lgtx(LAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA>Xdk++++++0}LLLLLLLLLLLLLLLLLLLLLl~r}LLLLLLLLLLLLLLLLLLLLLLLLLLL>^bh,++++++,uxF]DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD1RSfesxK`AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE|2++++++'ULLLLLLLLLLLLLLLLLLLLJfuPLLLLLLLLLLLLLLLLLLLLLLLLLLbrNW+++++++=J\lGDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD KzlyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'Ldr++++++S\LLLLLLLLLLLLLLLLLLLLQt~ NLLLLLLLLLLLLLLLLLLLLLLLLLQBN+++++++zIDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD`oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAap&:+++++ .4[LLLLLLLLLLLLLLLLLLLLxOLLLLLLLLLLLLLLLLLLLLLLLLLEa';++++++CNC[DCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDH^FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEF] .+++++OLLLLLLLLLLLLLLLLLLLRkwPLLLLLLLLLLLLLLLLLLLLLLLLLjx0+++++5fsFCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC-P.OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgkq+++++`hdwLLLLLLLLLLLLLLLLLLL-Wy NLLLLLLLLLLLLLLLLLLLLLLLLPjn+++++,ek5SCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC&L{4Q@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AD[@M++++7PLLLLLLLLLLLLLLLLLLLyQLLLLLLLLLLLLLLLLLLLLLLLLB_CO+++++8F]lICCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDlx9T@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@DUf+++++}bwLLLLLLLLLLLLLLLLLLL_u{OLLLLLLLLLLLLLLLLLLLLLLLLWk1+++++?YCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCReM`@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@drkq++++JULLLLLLLLLLLLLLLLLLL?`~ RLLLLLLLLLLLLLLLLLLLLLLLMkxx{,++++JTjv+NBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.OCZ@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.N]l++++2ZqLLLLLLLLLLLLLLLLLLO|PLLLLLLLLLLLLLLLLLLLLLLL!RRZ++++,_m DBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB Dp{Pb@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=W++++\dQLLLLLLLLLLLLLLLLLLuPLLLLLLLLLLLLLLLLLLLLLLL2X$9+++,joE[EBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCG]F[??????????????????????????????????????????????????????????????F]e+++ .WoLLLLLLLLLLLLLLLLLLIf}%SLLLLLLLLLLLLLLLLLLLLLLLNf1+++FPRdEBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBGH]??????????????????????????????????????????????????????????????F;I+++LLLLLLLLLLLLLLLLLL RPLLLLLLLLLLLLLLLLLLLLLL Np{ek,++6TeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA CQcCZ??????????????????????????????????????????????????????????????C4++LVNiLLLLLLLLLLLLLLLLLL*ULLLLLLLLLLLLLLLLLLLLLLQCN+++tx[i6RBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA H,L???????????????????????????????????????????????????????????????w++ .QLLLLLLLLLLLLLLLLL\s"RLLLLLLLLLLLLLLLLLLLLLL>]*=++HRSeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[j.M???????????????????????????????????????????????????????????????vW_++oueyLLLLLLLLLLLLLLLLL9]'TLLLLLLLLLLLLLLLLLLLLLM_p}1++Pb.N"IDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA$Jt~F>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>cp++9GLLLLLLLLLLLLLLLLL +M(TLLLLLLLLLLLLLLLLLLLLLP_f++vzs|&KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH]t~?>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>cp+0\rLLLLLLLLLLLLLLLLLv+ULLLLLLLLLLLLLLLLLLLLL-VJT+MWD@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ H_lRc>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Xhns+R[PLLLLLLLLLLLLLLLLPj2XLLLLLLLLLLLLLLLLLLLLLVk;H6Ehs@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'J~7Q>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Oa2C0AsLLLLLLLLLLLLLLLLP/WLLLLLLLLLLLLLLLLLLLLO|2BUe@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@E[$H>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Oa.?6[LLLLLLLLLLLLLLLL6ZLLLLLLLLLLLLLLLLLLLL4Y>V@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ BM``m=================================================================H\lrLLLLLLLLLLLLLLLLdw4YLLLLLLLLLLLLLLLLLLLLYmG@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@Edp>U=================================================================G[1YLLLLLLLLLLLLLLL2Y8[MLLLLLLLLLLLLLLLLLLP} A????????????????????????????????????????????????????????????????? G[jkvE=================================================================DYLLLLLLLLLLLLLLLL=]LLLLLLLLLLLLLLLLLLLC`qzB??????????????????????????????????????????????????????????????????CoxFZ==================================================================G[SlLLLLLLLLLLLLLLLz@^LLLLLLLLLLLLLLLLLLLmyYh????????????????????????????????????????????????????????????????????.M_lhtE==================================================================H\)VLLLLLLLLLLLLLLPjA_LLLLLLLLLLLLLLLLLLNI]?????????????????????????????????????????????????????????????????????@Te&H===================================================================K^LLLLLLLLLLLLLLQC`LLLLLLLLLLLLLLLLLLPg:S>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>BVfWf=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>A,KvcoA<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Dboy!E<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>D>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>^lxD<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=w MLLLLLLLLLLL M}QhLLLLLLLLLLLLLLLMcsjt>==============================================================================9QXf`l.J;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AXoLLLLLLLLLLLLJfRhLLLLLLLLLLLLLLLOXg=================================================================================A\iN_<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+HLLLLLLLLLLLLRWlLLLLLLLLLLLLLLLOgUd==================================================================================='HVes|s|EX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1LLLLLLLLLLLLLXkMLLLLLLLLLLLLLPJ\====================================================================================>B#FM_}=SA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;(H8PBWZh{fqAU(F::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::S<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@Uu|We)G> =;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;`mLLLLLLLLLLXkLLLLLLLLLLLQ8P;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;|[g[h[g[hVdP_IZ?T7O"C:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::B`uLLLLLLLLLeyKdMLLLLLLLLLL\n]LLLLLLOf`k>:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;:PzNhLLLL[r+ULLLLNfdn:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::dnK\:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::EX.WLLLL|QLLL N~(F:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;\h9P>:::::::::::::::::::::::::::::::::::::::::::::::::::::::::BK\(ULLL{"RLLL}Sa:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::=jt[h>:::::::::::::::::::::::::::::::::::::::::::::::::::::BVu~"SLLXo{QLL^py?:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: \hzM^9P'F =:::::::::::::::::::::::;!C4MAUbnZpfp=::::::::::::::::::::::::::::::::::::::::::::::::7NpyZh?T C:::::::::::::=;QVdjt|Uc@::::::::::::::::::::::::::::::::::::::::::::@\h`k'F:::::::::::::::::::::::::::::::::::::::::Q`t{)G:::::::::::::::::::::::::::::::::::::BUN],H:::::::::::::::::::::::::::::::"CN^Ye;::::::::::::::::::::::::::@Tv}[g4L)G@;:::::::::::::::@*G=Rjtyks[gM] {} - {} mV", v, convert_to_millivolts(v)); + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/stm32f7/src/bin/blinky.rs b/embassy/examples/stm32f7/src/bin/blinky.rs new file mode 100644 index 0000000..31cce82 --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PB7, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32f7/src/bin/button.rs b/embassy/examples/stm32f7/src/bin/button.rs new file mode 100644 index 0000000..5649089 --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/button.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +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::Down); + let mut led1 = Output::new(p.PB0, Level::High, Speed::Low); + let _led2 = Output::new(p.PB7, Level::High, Speed::Low); + let mut led3 = Output::new(p.PB14, Level::High, Speed::Low); + + loop { + if button.is_high() { + info!("high"); + led1.set_high(); + led3.set_low(); + } else { + info!("low"); + led1.set_low(); + led3.set_high(); + } + } +} diff --git a/embassy/examples/stm32f7/src/bin/button_exti.rs b/embassy/examples/stm32f7/src/bin/button_exti.rs new file mode 100644 index 0000000..2a546da --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); + + info!("Press the USER button..."); + + loop { + button.wait_for_rising_edge().await; + info!("Pressed!"); + button.wait_for_falling_edge().await; + info!("Released!"); + } +} diff --git a/embassy/examples/stm32f7/src/bin/can.rs b/embassy/examples/stm32f7/src/bin/can.rs new file mode 100644 index 0000000..a82e335 --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/can.rs @@ -0,0 +1,73 @@ +#![no_std] +#![no_main] + +use core::num::{NonZeroU16, NonZeroU8}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::can::filter::Mask32; +use embassy_stm32::can::{ + Can, CanTx, Fifo, Frame, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, StandardId, + TxInterruptHandler, +}; +use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::peripherals::CAN3; +use embassy_stm32::{bind_interrupts, can}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + CAN3_RX0 => Rx0InterruptHandler; + CAN3_RX1 => Rx1InterruptHandler; + CAN3_SCE => SceInterruptHandler; + CAN3_TX => TxInterruptHandler; +}); + +#[embassy_executor::task] +pub async fn send_can_message(tx: &'static mut CanTx<'static>) { + loop { + let frame = Frame::new_data(unwrap!(StandardId::new(0 as _)), &[0]).unwrap(); + tx.write(&frame).await; + embassy_time::Timer::after_secs(1).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let mut p = embassy_stm32::init(Default::default()); + + // The next two lines are a workaround for testing without transceiver. + // To synchronise to the bus the RX input needs to see a high level. + // Use `mem::forget()` to release the borrow on the pin but keep the + // pull-up resistor enabled. + let rx_pin = Input::new(&mut p.PA15, Pull::Up); + core::mem::forget(rx_pin); + + static CAN: StaticCell> = StaticCell::new(); + let can = CAN.init(Can::new(p.CAN3, p.PA8, p.PA15, Irqs)); + can.modify_filters().enable_bank(0, Fifo::Fifo0, Mask32::accept_all()); + + can.modify_config() + .set_bit_timing(can::util::NominalBitTiming { + prescaler: NonZeroU16::new(2).unwrap(), + seg1: NonZeroU8::new(13).unwrap(), + seg2: NonZeroU8::new(2).unwrap(), + sync_jump_width: NonZeroU8::new(1).unwrap(), + }) // http://www.bittiming.can-wiki.info/ + .set_loopback(true); + + can.enable().await; + + let (tx, mut rx) = can.split(); + + static CAN_TX: StaticCell> = StaticCell::new(); + let tx = CAN_TX.init(tx); + spawner.spawn(send_can_message(tx)).unwrap(); + + loop { + let envelope = rx.read().await.unwrap(); + println!("Received: {:?}", envelope); + } +} diff --git a/embassy/examples/stm32f7/src/bin/cryp.rs b/embassy/examples/stm32f7/src/bin/cryp.rs new file mode 100644 index 0000000..235853c --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/cryp.rs @@ -0,0 +1,80 @@ +#![no_std] +#![no_main] + +use aes_gcm::aead::heapless::Vec; +use aes_gcm::aead::{AeadInPlace, KeyInit}; +use aes_gcm::Aes128Gcm; +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::cryp::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals, Config}; +use embassy_time::Instant; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + CRYP => cryp::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let config = Config::default(); + let p = embassy_stm32::init(config); + + let payload: &[u8] = b"hello world"; + let aad: &[u8] = b"additional data"; + + let mut hw_cryp = Cryp::new(p.CRYP, p.DMA2_CH6, p.DMA2_CH5, Irqs); + let key: [u8; 16] = [0; 16]; + let mut ciphertext: [u8; 11] = [0; 11]; + let mut plaintext: [u8; 11] = [0; 11]; + let iv: [u8; 12] = [0; 12]; + + let hw_start_time = Instant::now(); + + // Encrypt in hardware using AES-GCM 128-bit + let aes_gcm = AesGcm::new(&key, &iv); + let mut gcm_encrypt = hw_cryp.start(&aes_gcm, Direction::Encrypt).await; + hw_cryp.aad(&mut gcm_encrypt, aad, true).await; + hw_cryp.payload(&mut gcm_encrypt, payload, &mut ciphertext, true).await; + let encrypt_tag = hw_cryp.finish(gcm_encrypt).await; + + // Decrypt in hardware using AES-GCM 128-bit + let mut gcm_decrypt = hw_cryp.start(&aes_gcm, Direction::Decrypt).await; + hw_cryp.aad(&mut gcm_decrypt, aad, true).await; + hw_cryp + .payload(&mut gcm_decrypt, &ciphertext, &mut plaintext, true) + .await; + let decrypt_tag = hw_cryp.finish(gcm_decrypt).await; + + let hw_end_time = Instant::now(); + let hw_execution_time = hw_end_time - hw_start_time; + + info!("AES-GCM Ciphertext: {:?}", ciphertext); + info!("AES-GCM Plaintext: {:?}", plaintext); + assert_eq!(payload, plaintext); + assert_eq!(encrypt_tag, decrypt_tag); + + let sw_start_time = Instant::now(); + + // Encrypt in software using AES-GCM 128-bit + let mut payload_vec: Vec = Vec::from_slice(&payload).unwrap(); + let cipher = Aes128Gcm::new(&key.into()); + let _ = cipher.encrypt_in_place(&iv.into(), aad.into(), &mut payload_vec); + + assert_eq!(ciphertext, payload_vec[0..ciphertext.len()]); + assert_eq!( + encrypt_tag, + payload_vec[ciphertext.len()..ciphertext.len() + encrypt_tag.len()] + ); + + // Decrypt in software using AES-GCM 128-bit + let _ = cipher.decrypt_in_place(&iv.into(), aad.into(), &mut payload_vec); + + let sw_end_time = Instant::now(); + let sw_execution_time = sw_end_time - sw_start_time; + + info!("Hardware Execution Time: {:?}", hw_execution_time); + info!("Software Execution Time: {:?}", sw_execution_time); + + loop {} +} diff --git a/embassy/examples/stm32f7/src/bin/eth.rs b/embassy/examples/stm32f7/src/bin/eth.rs new file mode 100644 index 0000000..1f1eadf --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/eth.rs @@ -0,0 +1,131 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, StackResources}; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_stm32::peripherals::ETH; +use embassy_stm32::rng::Rng; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; +use embassy_time::Timer; +use embedded_io_async::Write; +use rand_core::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + HASH_RNG => rng::InterruptHandler; +}); + +type Device = Ethernet<'static, ETH, GenericSMI>; + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL216, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 216 / 2 = 216Mhz + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Generate random seed. + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + static PACKETS: StaticCell> = StaticCell::new(); + let device = Ethernet::new( + PACKETS.init(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI::new(0), + mac_addr, + ); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + // Ensure DHCP configuration is up before trying connect + stack.wait_config_up().await; + + info!("Network task initialized"); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); + info!("connecting..."); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + info!("connect error: {:?}", e); + Timer::after_secs(1).await; + continue; + } + info!("connected!"); + let buf = [0; 1024]; + loop { + let r = socket.write_all(&buf).await; + if let Err(e) = r { + info!("write error: {:?}", e); + break; + } + Timer::after_secs(1).await; + } + } +} diff --git a/embassy/examples/stm32f7/src/bin/flash.rs b/embassy/examples/stm32f7/src/bin/flash.rs new file mode 100644 index 0000000..8855704 --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/flash.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +use embassy_time::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 Flash!"); + + const ADDR: u32 = 0x8_0000; // This is the offset into the third region, the absolute address is 4x32K + 128K + 0x8_0000. + + // wait a bit before accessing the flash + Timer::after_millis(300).await; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region3; + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.blocking_erase(ADDR, ADDR + 256 * 1024)); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write( + ADDR, + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + )); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!( + &buf[..], + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + ); +} diff --git a/embassy/examples/stm32f7/src/bin/hash.rs b/embassy/examples/stm32f7/src/bin/hash.rs new file mode 100644 index 0000000..c2d1a71 --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/hash.rs @@ -0,0 +1,78 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::hash::*; +use embassy_stm32::{bind_interrupts, hash, peripherals, Config}; +use embassy_time::Instant; +use hmac::{Hmac, Mac}; +use sha2::{Digest, Sha256}; +use {defmt_rtt as _, panic_probe as _}; + +type HmacSha256 = Hmac; + +bind_interrupts!(struct Irqs { + HASH_RNG => hash::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let config = Config::default(); + let p = embassy_stm32::init(config); + + let test_1: &[u8] = b"as;dfhaslfhas;oifvnasd;nifvnhasd;nifvhndlkfghsd;nvfnahssdfgsdafgsasdfasdfasdfasdfasdfghjklmnbvcalskdjghalskdjgfbaslkdjfgbalskdjgbalskdjbdfhsdfhsfghsfghfgh"; + let test_2: &[u8] = b"fdhalksdjfhlasdjkfhalskdjfhgal;skdjfgalskdhfjgalskdjfglafgadfgdfgdafgaadsfgfgdfgadrgsyfthxfgjfhklhjkfgukhulkvhlvhukgfhfsrghzdhxyfufynufyuszeradrtydyytserr"; + + let mut hw_hasher = Hash::new(p.HASH, p.DMA2_CH7, Irqs); + + let hw_start_time = Instant::now(); + + // Compute a digest in hardware. + let mut context = hw_hasher.start(Algorithm::SHA256, DataType::Width8, None); + hw_hasher.update(&mut context, test_1).await; + hw_hasher.update(&mut context, test_2).await; + let mut hw_digest: [u8; 32] = [0; 32]; + hw_hasher.finish(context, &mut hw_digest).await; + + let hw_end_time = Instant::now(); + let hw_execution_time = hw_end_time - hw_start_time; + + let sw_start_time = Instant::now(); + + // Compute a digest in software. + let mut sw_hasher = Sha256::new(); + sw_hasher.update(test_1); + sw_hasher.update(test_2); + let sw_digest = sw_hasher.finalize(); + + let sw_end_time = Instant::now(); + let sw_execution_time = sw_end_time - sw_start_time; + + info!("Hardware Digest: {:?}", hw_digest); + info!("Software Digest: {:?}", sw_digest[..]); + info!("Hardware Execution Time: {:?}", hw_execution_time); + info!("Software Execution Time: {:?}", sw_execution_time); + assert_eq!(hw_digest, sw_digest[..]); + + let hmac_key: [u8; 64] = [0x55; 64]; + + // Compute HMAC in hardware. + let mut sha256hmac_context = hw_hasher.start(Algorithm::SHA256, DataType::Width8, Some(&hmac_key)); + hw_hasher.update(&mut sha256hmac_context, test_1).await; + hw_hasher.update(&mut sha256hmac_context, test_2).await; + let mut hw_hmac: [u8; 32] = [0; 32]; + hw_hasher.finish(sha256hmac_context, &mut hw_hmac).await; + + // Compute HMAC in software. + let mut sw_mac = HmacSha256::new_from_slice(&hmac_key).unwrap(); + sw_mac.update(test_1); + sw_mac.update(test_2); + let sw_hmac = sw_mac.finalize().into_bytes(); + + info!("Hardware HMAC: {:?}", hw_hmac); + info!("Software HMAC: {:?}", sw_hmac[..]); + assert_eq!(hw_hmac, sw_hmac[..]); + + loop {} +} diff --git a/embassy/examples/stm32f7/src/bin/hello.rs b/embassy/examples/stm32f7/src/bin/hello.rs new file mode 100644 index 0000000..3c29561 --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/hello.rs @@ -0,0 +1,19 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let config = Config::default(); + let _p = embassy_stm32::init(config); + + loop { + info!("Hello World!"); + Timer::after_secs(1).await; + } +} diff --git a/embassy/examples/stm32f7/src/bin/qspi.rs b/embassy/examples/stm32f7/src/bin/qspi.rs new file mode 100644 index 0000000..bd32879 --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/qspi.rs @@ -0,0 +1,301 @@ +#![no_std] +#![no_main] +#![allow(dead_code)] // Allow dead code as not all commands are used in the example + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::mode::Async; +use embassy_stm32::qspi::enums::{AddressSize, ChipSelectHighTime, FIFOThresholdLevel, MemorySize, *}; +use embassy_stm32::qspi::{Config as QspiCfg, Instance, Qspi, TransferConfig}; +use embassy_stm32::time::mhz; +use embassy_stm32::Config as StmCfg; +use {defmt_rtt as _, panic_probe as _}; + +const MEMORY_PAGE_SIZE: usize = 256; + +const CMD_READ: u8 = 0x03; +const CMD_HS_READ: u8 = 0x0B; +const CMD_QUAD_READ: u8 = 0x6B; + +const CMD_WRITE_PG: u8 = 0xF2; +const CMD_QUAD_WRITE_PG: u8 = 0x32; + +const CMD_READ_ID: u8 = 0x9F; +const CMD_READ_UUID: u8 = 0x4B; + +const CMD_ENABLE_RESET: u8 = 0x66; +const CMD_RESET: u8 = 0x99; + +const CMD_WRITE_ENABLE: u8 = 0x06; +const CMD_WRITE_DISABLE: u8 = 0x04; + +const CMD_CHIP_ERASE: u8 = 0xC7; +const CMD_SECTOR_ERASE: u8 = 0x20; +const CMD_BLOCK_ERASE_32K: u8 = 0x52; +const CMD_BLOCK_ERASE_64K: u8 = 0xD8; + +const CMD_READ_SR: u8 = 0x05; +const CMD_READ_CR: u8 = 0x35; + +const CMD_WRITE_SR: u8 = 0x01; +const CMD_WRITE_CR: u8 = 0x31; +const MEMORY_ADDR: u32 = 0x00000000u32; + +/// Implementation of access to flash chip. +/// Chip commands are hardcoded as it depends on used chip. +/// This implementation is using chip GD25Q64C from Giga Device +pub struct FlashMemory { + qspi: Qspi<'static, I, Async>, +} + +impl FlashMemory { + pub fn new(qspi: Qspi<'static, I, Async>) -> Self { + let mut memory = Self { qspi }; + + memory.reset_memory(); + memory.enable_quad(); + + memory + } + + fn enable_quad(&mut self) { + let cr = self.read_cr(); + self.write_cr(cr | 0x02); + } + + fn exec_command(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::NONE, + instruction: cmd, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_command(transaction); + } + + pub fn reset_memory(&mut self) { + self.exec_command(CMD_ENABLE_RESET); + self.exec_command(CMD_RESET); + self.wait_write_finish(); + } + + pub fn enable_write(&mut self) { + self.exec_command(CMD_WRITE_ENABLE); + } + + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::SING, + instruction: CMD_READ_ID, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_read(&mut buffer, transaction); + buffer + } + + pub fn read_uuid(&mut self) -> [u8; 16] { + let mut buffer = [0; 16]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::SING, + instruction: CMD_READ_UUID, + address: Some(0), + dummy: DummyCycles::_8, + }; + self.qspi.blocking_read(&mut buffer, transaction); + buffer + } + + pub fn read_memory(&mut self, addr: u32, buffer: &mut [u8], use_dma: bool) { + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::QUAD, + instruction: CMD_QUAD_READ, + address: Some(addr), + dummy: DummyCycles::_8, + }; + if use_dma { + self.qspi.blocking_read_dma(buffer, transaction); + } else { + self.qspi.blocking_read(buffer, transaction); + } + } + + fn wait_write_finish(&mut self) { + while (self.read_sr() & 0x01) != 0 {} + } + + fn perform_erase(&mut self, addr: u32, cmd: u8) { + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::NONE, + instruction: cmd, + address: Some(addr), + dummy: DummyCycles::_0, + }; + self.enable_write(); + self.qspi.blocking_command(transaction); + self.wait_write_finish(); + } + + pub fn erase_sector(&mut self, addr: u32) { + self.perform_erase(addr, CMD_SECTOR_ERASE); + } + + pub fn erase_block_32k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_32K); + } + + pub fn erase_block_64k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_64K); + } + + pub fn erase_chip(&mut self) { + self.exec_command(CMD_CHIP_ERASE); + } + + fn write_page(&mut self, addr: u32, buffer: &[u8], len: usize, use_dma: bool) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::QUAD, + instruction: CMD_QUAD_WRITE_PG, + address: Some(addr), + dummy: DummyCycles::_0, + }; + self.enable_write(); + if use_dma { + self.qspi.blocking_write_dma(buffer, transaction); + } else { + self.qspi.blocking_write(buffer, transaction); + } + self.wait_write_finish(); + } + + pub fn write_memory(&mut self, addr: u32, buffer: &[u8], use_dma: bool) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page(place, chunk, chunk_size, use_dma); + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + fn read_register(&mut self, cmd: u8) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::SING, + instruction: cmd, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_read(&mut buffer, transaction); + buffer[0] + } + + fn write_register(&mut self, cmd: u8, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::SING, + instruction: cmd, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_write(&buffer, transaction); + } + + pub fn read_sr(&mut self) -> u8 { + self.read_register(CMD_READ_SR) + } + + pub fn read_cr(&mut self) -> u8 { + self.read_register(CMD_READ_CR) + } + + pub fn write_sr(&mut self, value: u8) { + self.write_register(CMD_WRITE_SR, value); + } + + pub fn write_cr(&mut self, value: u8) { + self.write_register(CMD_WRITE_CR, value); + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let mut config = StmCfg::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: mhz(8), + mode: HseMode::Oscillator, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL216, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 216 / 2 = 216Mhz + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + info!("Embassy initialized"); + + let config = QspiCfg { + memory_size: MemorySize::_8MiB, + address_size: AddressSize::_24bit, + prescaler: 16, + cs_high_time: ChipSelectHighTime::_1Cycle, + fifo_threshold: FIFOThresholdLevel::_16Bytes, + }; + let driver = Qspi::new_bank1( + p.QUADSPI, p.PF8, p.PF9, p.PE2, p.PF6, p.PF10, p.PB10, p.DMA2_CH7, config, + ); + let mut flash = FlashMemory::new(driver); + let flash_id = flash.read_id(); + info!("FLASH ID: {:?}", flash_id); + let mut wr_buf = [0u8; 256]; + for i in 0..256 { + wr_buf[i] = i as u8; + } + let mut rd_buf = [0u8; 256]; + flash.erase_sector(MEMORY_ADDR); + flash.write_memory(MEMORY_ADDR, &wr_buf, true); + flash.read_memory(MEMORY_ADDR, &mut rd_buf, true); + info!("WRITE BUF: {:?}", wr_buf); + info!("READ BUF: {:?}", rd_buf); + info!("End of Program, proceed to empty endless loop"); + loop {} +} diff --git a/embassy/examples/stm32f7/src/bin/sdmmc.rs b/embassy/examples/stm32f7/src/bin/sdmmc.rs new file mode 100644 index 0000000..6d36ef5 --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/sdmmc.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::sdmmc::Sdmmc; +use embassy_stm32::time::{mhz, Hertz}; +use embassy_stm32::{bind_interrupts, peripherals, sdmmc, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SDMMC1 => sdmmc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL216, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 216 / 2 = 216Mhz + divq: Some(PllQDiv::DIV9), // 8mhz / 4 * 216 / 9 = 48Mhz + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut sdmmc = Sdmmc::new_4bit( + p.SDMMC1, + Irqs, + p.DMA2_CH3, + p.PC12, + p.PD2, + p.PC8, + p.PC9, + p.PC10, + p.PC11, + Default::default(), + ); + + // Should print 400kHz for initialization + info!("Configured clock: {}", sdmmc.clock().0); + + unwrap!(sdmmc.init_card(mhz(25)).await); + + let card = unwrap!(sdmmc.card()); + + info!("Card: {:#?}", Debug2Format(card)); +} diff --git a/embassy/examples/stm32f7/src/bin/usart_dma.rs b/embassy/examples/stm32f7/src/bin/usart_dma.rs new file mode 100644 index 0000000..47456ad --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/usart_dma.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use heapless::String; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + +#[embassy_executor::main] +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, Irqs, p.DMA1_CH1, p.DMA1_CH3, config).unwrap(); + + for n in 0u32.. { + let mut s: String<128> = String::new(); + core::write!(&mut s, "Hello DMA World {}!\r\n", n).unwrap(); + + unwrap!(usart.write(s.as_bytes()).await); + + info!("wrote DMA"); + } +} diff --git a/embassy/examples/stm32f7/src/bin/usb_serial.rs b/embassy/examples/stm32f7/src/bin/usb_serial.rs new file mode 100644 index 0000000..1906b28 --- /dev/null +++ b/embassy/examples/stm32f7/src/bin/usb_serial.rs @@ -0,0 +1,136 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL216, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 216 / 2 = 216Mhz + divq: Some(PllQDiv::DIV9), // 8mhz / 4 * 216 / 9 = 48Mhz + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; + } + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32g0/.cargo/config.toml b/embassy/examples/stm32g0/.cargo/config.toml new file mode 100644 index 0000000..f395d89 --- /dev/null +++ b/embassy/examples/stm32g0/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32G0B1RETx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32G0B1RETx" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32g0/Cargo.toml b/embassy/examples/stm32g0/Cargo.toml new file mode 100644 index 0000000..3d11610 --- /dev/null +++ b/embassy/examples/stm32g0/Cargo.toml @@ -0,0 +1,29 @@ +[package] +edition = "2021" +name = "embassy-stm32g0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32g0b1re to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g0b1re", "memory-x", "unstable-pac", "exti"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } + +embedded-io-async = { version = "0.6.1" } + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32g0/build.rs b/embassy/examples/stm32g0/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32g0/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/embassy/examples/stm32g0/src/bin/adc.rs b/embassy/examples/stm32g0/src/bin/adc.rs new file mode 100644 index 0000000..6c7f3b4 --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/adc.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_time::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 adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES79_5); + let mut pin = p.PA1; + + let mut vrefint = adc.enable_vrefint(); + let vrefint_sample = adc.blocking_read(&mut vrefint); + let convert_to_millivolts = |sample| { + // From https://www.st.com/resource/en/datasheet/stm32g031g8.pdf + // 6.3.3 Embedded internal reference voltage + const VREFINT_MV: u32 = 1212; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + + loop { + let v = adc.blocking_read(&mut pin); + info!("--> {} - {} mV", v, convert_to_millivolts(v)); + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/stm32g0/src/bin/adc_dma.rs b/embassy/examples/stm32g0/src/bin/adc_dma.rs new file mode 100644 index 0000000..3713e5a --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/adc_dma.rs @@ -0,0 +1,44 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, AdcChannel as _, SampleTime}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +static mut DMA_BUF: [u16; 2] = [0; 2]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut read_buffer = unsafe { &mut DMA_BUF[..] }; + + let p = embassy_stm32::init(Default::default()); + + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC1); + + let mut dma = p.DMA1_CH1; + let mut vrefint_channel = adc.enable_vrefint().degrade_adc(); + let mut pa0 = p.PA0.degrade_adc(); + + loop { + adc.read( + &mut dma, + [ + (&mut vrefint_channel, SampleTime::CYCLES160_5), + (&mut pa0, SampleTime::CYCLES160_5), + ] + .into_iter(), + &mut read_buffer, + ) + .await; + + let vrefint = read_buffer[0]; + let measured = read_buffer[1]; + info!("vrefint: {}", vrefint); + info!("measured: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32g0/src/bin/adc_oversampling.rs b/embassy/examples/stm32g0/src/bin/adc_oversampling.rs new file mode 100644 index 0000000..9c5dd87 --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/adc_oversampling.rs @@ -0,0 +1,43 @@ +//! adc oversampling example +//! +//! This example uses adc oversampling to achieve 16bit data + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Adc oversample test"); + + let mut adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES1_5); + let mut pin = p.PA1; + + // From https://www.st.com/resource/en/reference_manual/rm0444-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf + // page373 15.8 Oversampler + // Table 76. Maximum output results vs N and M. Grayed values indicates truncation + // 0x00 oversampling ratio X2 + // 0x01 oversampling ratio X4 + // 0x02 oversampling ratio X8 + // 0x03 oversampling ratio X16 + // 0x04 oversampling ratio X32 + // 0x05 oversampling ratio X64 + // 0x06 oversampling ratio X128 + // 0x07 oversampling ratio X256 + adc.set_oversampling_ratio(0x03); + adc.set_oversampling_shift(0b0000); + adc.oversampling_enable(true); + + loop { + let v = adc.blocking_read(&mut pin); + info!("--> {} ", v); //max 65520 = 0xFFF0 + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/stm32g0/src/bin/blinky.rs b/embassy/examples/stm32g0/src/bin/blinky.rs new file mode 100644 index 0000000..31cce82 --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PB7, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32g0/src/bin/button.rs b/embassy/examples/stm32g0/src/bin/button.rs new file mode 100644 index 0000000..8017f02 --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/button.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] + +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/embassy/examples/stm32g0/src/bin/button_exti.rs b/embassy/examples/stm32g0/src/bin/button_exti.rs new file mode 100644 index 0000000..34a08bb --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); + + 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/embassy/examples/stm32g0/src/bin/flash.rs b/embassy/examples/stm32g0/src/bin/flash.rs new file mode 100644 index 0000000..acef87b --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/flash.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +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 addr: u32 = 0x8000; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(addr, &mut buf)); + info!("Read: {=[u8]:x}", buf); + info!("Erasing..."); + unwrap!(f.blocking_erase(addr, addr + 2 * 1024)); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(addr, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write( + addr, + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + )); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(addr, &mut buf)); + info!("Read: {=[u8]:x}", buf); +} diff --git a/embassy/examples/stm32g0/src/bin/hf_timer.rs b/embassy/examples/stm32g0/src/bin/hf_timer.rs new file mode 100644 index 0000000..3ea06cd --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/hf_timer.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::khz; +use embassy_stm32::timer::complementary_pwm::{ComplementaryPwm, ComplementaryPwmPin}; +use embassy_stm32::timer::simple_pwm::PwmPin; +use embassy_stm32::timer::Channel; +use embassy_stm32::Config as PeripheralConfig; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = PeripheralConfig::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL16, + divp: None, + divq: Some(PllQDiv::DIV2), // 16 / 1 * 16 / 2 = 128 Mhz + divr: Some(PllRDiv::DIV4), // 16 / 1 * 16 / 4 = 64 Mhz + }); + config.rcc.sys = Sysclk::PLL1_R; + + // configure TIM1 mux to select PLLQ as clock source + // https://www.st.com/resource/en/reference_manual/rm0444-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf + // RM0444 page 210 + // RCC - Peripherals Independent Clock Control Register - bit 22 -> 1 + config.rcc.mux.tim1sel = embassy_stm32::rcc::mux::Tim1sel::PLL1_Q; + } + let p = embassy_stm32::init(config); + + let ch1 = PwmPin::new_ch1(p.PA8, OutputType::PushPull); + let ch1n = ComplementaryPwmPin::new_ch1(p.PA7, OutputType::PushPull); + + let mut pwm = ComplementaryPwm::new( + p.TIM1, + Some(ch1), + Some(ch1n), + None, + None, + None, + None, + None, + None, + khz(512), + Default::default(), + ); + + let max = pwm.get_max_duty(); + info!("Max duty: {}", max); + + pwm.set_duty(Channel::Ch1, max / 2); + pwm.enable(Channel::Ch1); + + loop {} +} diff --git a/embassy/examples/stm32g0/src/bin/i2c_async.rs b/embassy/examples/stm32g0/src/bin/i2c_async.rs new file mode 100644 index 0000000..7e3189b --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/i2c_async.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{self, I2c}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + I2C1 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; +}); + +const TMP117_ADDR: u8 = 0x48; +const TMP117_TEMP_RESULT: u8 = 0x00; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello world"); + + let p = embassy_stm32::init(Default::default()); + + let mut data = [0u8; 2]; + let mut i2c = I2c::new( + p.I2C1, + p.PB8, + p.PB9, + Irqs, + p.DMA1_CH1, + p.DMA1_CH2, + Hertz(100_000), + Default::default(), + ); + + loop { + match i2c.write_read(TMP117_ADDR, &[TMP117_TEMP_RESULT], &mut data).await { + Ok(()) => { + let temp = f32::from(i16::from_be_bytes(data)) * 7.8125 / 1000.0; + info!("Temperature {}", temp); + } + Err(_) => error!("I2C Error"), + } + + Timer::after(Duration::from_millis(1000)).await; + } +} diff --git a/embassy/examples/stm32g0/src/bin/input_capture.rs b/embassy/examples/stm32g0/src/bin/input_capture.rs new file mode 100644 index 0000000..bc814cb --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/input_capture.rs @@ -0,0 +1,67 @@ +//! Input capture example +//! +//! This example showcases how to use the input capture feature of the timer peripheral. +//! Connect PB1 and PA6 with a 1k Ohm resistor or Connect PB1 and PA8 with a 1k Ohm resistor +//! to see the output. +//! When connecting PB1 (software pwm) and PA6 the output is around 10000 (it will be a bit bigger, around 10040) +//! Output is 1000 when connecting PB1 (PWMOUT) and PA6. +//! +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, OutputType, Pull, Speed}; +use embassy_stm32::time::khz; +use embassy_stm32::timer::input_capture::{CapturePin, InputCapture}; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_stm32::timer::Channel; +use embassy_stm32::{bind_interrupts, peripherals, timer}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// Connect PB1 and PA6 with a 1k Ohm resistor + +#[embassy_executor::task] +async fn blinky(led: peripherals::PB1) { + let mut led = Output::new(led, Level::High, Speed::Low); + + loop { + led.set_high(); + Timer::after_millis(50).await; + + led.set_low(); + Timer::after_millis(50).await; + } +} + +bind_interrupts!(struct Irqs { + TIM2 => timer::CaptureCompareInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + unwrap!(spawner.spawn(blinky(p.PB1))); + + // Connect PB1 and PA8 with a 1k Ohm resistor + let ch1_pin = PwmPin::new_ch1(p.PA8, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(1), Default::default()); + pwm.ch1().enable(); + pwm.ch1().set_duty_cycle(50); + + let ch1 = CapturePin::new_ch1(p.PA0, Pull::None); + let mut ic = InputCapture::new(p.TIM2, Some(ch1), None, None, None, Irqs, khz(1000), Default::default()); + + let mut old_capture = 0; + + loop { + ic.wait_for_rising_edge(Channel::Ch1).await; + + let capture_value = ic.get_capture_value(Channel::Ch1); + info!("{}", capture_value - old_capture); + old_capture = capture_value; + } +} diff --git a/embassy/examples/stm32g0/src/bin/pwm_complementary.rs b/embassy/examples/stm32g0/src/bin/pwm_complementary.rs new file mode 100644 index 0000000..97b163c --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/pwm_complementary.rs @@ -0,0 +1,57 @@ +//! PWM complementary example +//! +//! This example uses two complementary pwm outputs from TIM1 with different duty cycles +//! ___ ___ +//! |_________| |_________| PA8 +//! _________ _________ +//! ___| |___| | PA7 +//! _________ _________ +//! |___| |___| PB3 +//! ___ ___ +//! _________| |_________| | PB0 + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::khz; +use embassy_stm32::timer::complementary_pwm::{ComplementaryPwm, ComplementaryPwmPin}; +use embassy_stm32::timer::simple_pwm::PwmPin; +use embassy_stm32::timer::Channel; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + let ch1 = PwmPin::new_ch1(p.PA8, OutputType::PushPull); + let ch1n = ComplementaryPwmPin::new_ch1(p.PA7, OutputType::PushPull); + let ch2 = PwmPin::new_ch2(p.PB3, OutputType::PushPull); + let ch2n = ComplementaryPwmPin::new_ch2(p.PB0, OutputType::PushPull); + + let mut pwm = ComplementaryPwm::new( + p.TIM1, + Some(ch1), + Some(ch1n), + Some(ch2), + Some(ch2n), + None, + None, + None, + None, + khz(100), + Default::default(), + ); + + let max = pwm.get_max_duty(); + info!("Max duty: {}", max); + + pwm.set_duty(Channel::Ch1, max / 4); + pwm.enable(Channel::Ch1); + pwm.set_duty(Channel::Ch2, max * 3 / 4); + pwm.enable(Channel::Ch2); + + loop {} +} diff --git a/embassy/examples/stm32g0/src/bin/pwm_input.rs b/embassy/examples/stm32g0/src/bin/pwm_input.rs new file mode 100644 index 0000000..db9cf4f --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/pwm_input.rs @@ -0,0 +1,63 @@ +//! PWM input example +//! +//! This program demonstrates how to capture the parameters of the input waveform (frequency, width and duty cycle) +//! Connect PB1 and PA6 with a 1k Ohm resistor or Connect PB1 and PA8 with a 1k Ohm resistor +//! to see the output. +//! + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, OutputType, Pull, Speed}; +use embassy_stm32::time::khz; +use embassy_stm32::timer::pwm_input::PwmInput; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_stm32::{bind_interrupts, peripherals, timer}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// Connect PB1 and PA6 with a 1k Ohm resistor +#[embassy_executor::task] +async fn blinky(led: peripherals::PB1) { + let mut led = Output::new(led, Level::High, Speed::Low); + + loop { + led.set_high(); + Timer::after_millis(50).await; + + led.set_low(); + Timer::after_millis(50).await; + } +} + +bind_interrupts!(struct Irqs { + TIM2 => timer::CaptureCompareInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + unwrap!(spawner.spawn(blinky(p.PB1))); + // Connect PA8 and PA6 with a 1k Ohm resistor + let ch1_pin = PwmPin::new_ch1(p.PA8, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(1), Default::default()); + pwm.ch1().set_duty_cycle_fraction(1, 4); + pwm.ch1().enable(); + + let mut pwm_input = PwmInput::new(p.TIM2, p.PA0, Pull::None, khz(1000)); + pwm_input.enable(); + + loop { + Timer::after_millis(500).await; + let period = pwm_input.get_period_ticks(); + let width = pwm_input.get_width_ticks(); + let duty_cycle = pwm_input.get_duty_cycle(); + info!( + "period ticks: {} width ticks: {} duty cycle: {}", + period, width, duty_cycle + ); + } +} diff --git a/embassy/examples/stm32g0/src/bin/rtc.rs b/embassy/examples/stm32g0/src/bin/rtc.rs new file mode 100644 index 0000000..c02c1ec --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/rtc.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rtc::{DateTime, DayOfWeek, Rtc, RtcConfig}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let config = Config::default(); + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let now = DateTime::from(2023, 6, 14, DayOfWeek::Friday, 15, 59, 10); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + + rtc.set_datetime(now.unwrap()).expect("datetime not set"); + + loop { + let now: DateTime = rtc.now().unwrap().into(); + + info!("{}:{}:{}", now.hour(), now.minute(), now.second()); + + Timer::after_millis(1000).await; + } +} diff --git a/embassy/examples/stm32g0/src/bin/spi_neopixel.rs b/embassy/examples/stm32g0/src/bin/spi_neopixel.rs new file mode 100644 index 0000000..edcae74 --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/spi_neopixel.rs @@ -0,0 +1,101 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dma::word::U5; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const NR_PIXELS: usize = 15; +const BITS_PER_PIXEL: usize = 24; // 24 for rgb, 32 for rgbw +const TOTAL_BITS: usize = NR_PIXELS * BITS_PER_PIXEL; + +struct RGB { + r: u8, + g: u8, + b: u8, +} +impl Default for RGB { + fn default() -> RGB { + RGB { r: 0, g: 0, b: 0 } + } +} +pub struct Ws2812 { + // Note that the U5 type controls the selection of 5 bits to output + bitbuffer: [U5; TOTAL_BITS], +} + +impl Ws2812 { + pub fn new() -> Ws2812 { + Ws2812 { + bitbuffer: [U5(0); TOTAL_BITS], + } + } + fn len(&self) -> usize { + return NR_PIXELS; + } + fn set(&mut self, idx: usize, rgb: RGB) { + self.render_color(idx, 0, rgb.g); + self.render_color(idx, 8, rgb.r); + self.render_color(idx, 16, rgb.b); + } + // transform one color byte into an array of 8 byte. Each byte in the array does represent 1 neopixel bit pattern + fn render_color(&mut self, pixel_idx: usize, offset: usize, color: u8) { + let mut bits = color as usize; + let mut idx = pixel_idx * BITS_PER_PIXEL + offset; + + // render one bit in one spi byte. High time first, then the low time + // clock should be 4 Mhz, 5 bits, each bit is 0.25 us. + // a one bit is send as a pulse of 0.75 high -- 0.50 low + // a zero bit is send as a pulse of 0.50 high -- 0.75 low + // clock frequency for the neopixel is exact 800 khz + // note that the mosi output should have a resistor to ground of 10k, + // to assure that between the bursts the line is low + for _i in 0..8 { + if idx >= TOTAL_BITS { + return; + } + let pattern = match bits & 0x80 { + 0x80 => 0b0000_1110, + _ => 0b000_1100, + }; + bits = bits << 1; + self.bitbuffer[idx] = U5(pattern); + idx += 1; + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Start test using spi as neopixel driver"); + + let mut config = Config::default(); + config.frequency = Hertz(4_000_000); + let mut spi = Spi::new_txonly(p.SPI1, p.PB3, p.PB5, p.DMA1_CH3, config); // SCK is unused. + + let mut neopixels = Ws2812::new(); + + loop { + let mut cnt: usize = 0; + for _i in 0..10 { + for idx in 0..neopixels.len() { + let color = match (cnt + idx) % 3 { + 0 => RGB { r: 0x21, g: 0, b: 0 }, + 1 => RGB { r: 0, g: 0x31, b: 0 }, + _ => RGB { r: 0, g: 0, b: 0x41 }, + }; + neopixels.set(idx, color); + } + cnt += 1; + // start sending the neopixel bit patters over spi to the neopixel string + spi.write(&neopixels.bitbuffer).await.ok(); + Timer::after_millis(500).await; + } + Timer::after_millis(1000).await; + } +} diff --git a/embassy/examples/stm32g0/src/bin/usart.rs b/embassy/examples/stm32g0/src/bin/usart.rs new file mode 100644 index 0000000..037a5c8 --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/usart.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::usart::{Config, Uart}; +use {defmt_rtt as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let config = Config::default(); + let mut usart = Uart::new_blocking(p.USART2, p.PA3, p.PA2, config).unwrap(); + + unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.blocking_read(&mut buf)); + unwrap!(usart.blocking_write(&buf)); + } +} diff --git a/embassy/examples/stm32g0/src/bin/usart_buffered.rs b/embassy/examples/stm32g0/src/bin/usart_buffered.rs new file mode 100644 index 0000000..c097a0c --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/usart_buffered.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{BufferedUart, Config}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use embedded_io_async::{Read, Write}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USART1 => usart::BufferedInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hi!"); + + let mut config = Config::default(); + config.baudrate = 115200; + let mut tx_buf = [0u8; 256]; + let mut rx_buf = [0u8; 256]; + let mut usart = BufferedUart::new(p.USART1, Irqs, p.PB7, p.PB6, &mut tx_buf, &mut rx_buf, config).unwrap(); + + usart.write_all(b"Hello Embassy World!\r\n").await.unwrap(); + info!("wrote Hello, starting echo"); + + let mut buf = [0; 4]; + loop { + usart.read_exact(&mut buf[..]).await.unwrap(); + usart.write_all(&buf[..]).await.unwrap(); + } +} diff --git a/embassy/examples/stm32g0/src/bin/usart_dma.rs b/embassy/examples/stm32g0/src/bin/usart_dma.rs new file mode 100644 index 0000000..8212153 --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/usart_dma.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USART1 => usart::InterruptHandler; +}); + +#[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, Irqs, p.DMA1_CH2, p.DMA1_CH3, Config::default()).unwrap(); + + usart.write(b"Hello Embassy World!\r\n").await.unwrap(); + info!("wrote Hello, starting echo"); + + let mut buf = [0; 5]; + loop { + usart.read(&mut buf[..]).await.unwrap(); + usart.write(&buf[..]).await.unwrap(); + } +} diff --git a/embassy/examples/stm32g0/src/bin/usb_serial.rs b/embassy/examples/stm32g0/src/bin/usb_serial.rs new file mode 100644 index 0000000..162dfd8 --- /dev/null +++ b/embassy/examples/stm32g0/src/bin/usb_serial.rs @@ -0,0 +1,97 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_UCPD1_2 => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); + config.rcc.mux.usbsel = mux::Usbsel::HSI48; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Create embassy-usb Config + let config = embassy_usb::Config::new(0xc0de, 0xcafe); + //config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 7]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32g4/.cargo/config.toml b/embassy/examples/stm32g4/.cargo/config.toml new file mode 100644 index 0000000..d28ad06 --- /dev/null +++ b/embassy/examples/stm32g4/.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-rs chip list` +runner = "probe-rs run --chip STM32G484VETx" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32g4/Cargo.toml b/embassy/examples/stm32g4/Cargo.toml new file mode 100644 index 0000000..87fa2c5 --- /dev/null +++ b/embassy/examples/stm32g4/Cargo.toml @@ -0,0 +1,29 @@ +[package] +edition = "2021" +name = "embassy-stm32g4-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32g491re to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g491re", "memory-x", "unstable-pac", "exti"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +usbd-hid = "0.8.1" + +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" +embedded-can = { version = "0.4" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +static_cell = "2.0.0" + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32g4/build.rs b/embassy/examples/stm32g4/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32g4/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/embassy/examples/stm32g4/src/bin/adc.rs b/embassy/examples/stm32g4/src/bin/adc.rs new file mode 100644 index 0000000..adca846 --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/adc.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, + // Main system clock at 170 MHz + divr: Some(PllRDiv::DIV2), + }); + config.rcc.mux.adc12sel = mux::Adcsel::SYS; + config.rcc.sys = Sysclk::PLL1_R; + } + let mut p = embassy_stm32::init(config); + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC2); + adc.set_sample_time(SampleTime::CYCLES24_5); + + loop { + let measured = adc.blocking_read(&mut p.PA7); + info!("measured: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32g4/src/bin/adc_differential.rs b/embassy/examples/stm32g4/src/bin/adc_differential.rs new file mode 100644 index 0000000..78d071d --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/adc_differential.rs @@ -0,0 +1,47 @@ +//! adc differential mode example +//! +//! This example uses adc1 in differential mode +//! p:pa0 n:pa1 + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, + // Main system clock at 170 MHz + divr: Some(PllRDiv::DIV2), + }); + config.rcc.mux.adc12sel = mux::Adcsel::SYS; + config.rcc.sys = Sysclk::PLL1_R; + } + let mut p = embassy_stm32::init(config); + + let mut adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES247_5); + adc.set_differential(&mut p.PA0, true); //p:pa0,n:pa1 + + // can also use + // adc.set_differential_channel(1, true); + info!("adc initialized"); + loop { + let measured = adc.blocking_read(&mut p.PA0); + info!("data: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32g4/src/bin/adc_dma.rs b/embassy/examples/stm32g4/src/bin/adc_dma.rs new file mode 100644 index 0000000..970623b --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/adc_dma.rs @@ -0,0 +1,60 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, AdcChannel as _, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +static mut DMA_BUF: [u16; 2] = [0; 2]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut read_buffer = unsafe { &mut DMA_BUF[..] }; + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, + // Main system clock at 170 MHz + divr: Some(PllRDiv::DIV2), + }); + config.rcc.mux.adc12sel = mux::Adcsel::SYS; + config.rcc.sys = Sysclk::PLL1_R; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC1); + + let mut dma = p.DMA1_CH1; + let mut vrefint_channel = adc.enable_vrefint().degrade_adc(); + let mut pa0 = p.PA0.degrade_adc(); + + loop { + adc.read( + &mut dma, + [ + (&mut vrefint_channel, SampleTime::CYCLES247_5), + (&mut pa0, SampleTime::CYCLES247_5), + ] + .into_iter(), + &mut read_buffer, + ) + .await; + + let vrefint = read_buffer[0]; + let measured = read_buffer[1]; + info!("vrefint: {}", vrefint); + info!("measured: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32g4/src/bin/adc_oversampling.rs b/embassy/examples/stm32g4/src/bin/adc_oversampling.rs new file mode 100644 index 0000000..d31eb20 --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/adc_oversampling.rs @@ -0,0 +1,57 @@ +//! adc oversampling example +//! +//! This example uses adc oversampling to achieve 16bit data + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::vals::{Rovsm, Trovs}; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, + // Main system clock at 170 MHz + divr: Some(PllRDiv::DIV2), + }); + config.rcc.mux.adc12sel = mux::Adcsel::SYS; + config.rcc.sys = Sysclk::PLL1_R; + } + let mut p = embassy_stm32::init(config); + + let mut adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES6_5); + // From https://www.st.com/resource/en/reference_manual/rm0440-stm32g4-series-advanced-armbased-32bit-mcus-stmicroelectronics.pdf + // page652 Oversampler + // Table 172. Maximum output results vs N and M. Grayed values indicates truncation + // 0x00 oversampling ratio X2 + // 0x01 oversampling ratio X4 + // 0x02 oversampling ratio X8 + // 0x03 oversampling ratio X16 + // 0x04 oversampling ratio X32 + // 0x05 oversampling ratio X64 + // 0x06 oversampling ratio X128 + // 0x07 oversampling ratio X256 + adc.set_oversampling_ratio(0x03); // ratio X3 + adc.set_oversampling_shift(0b0000); // no shift + adc.enable_regular_oversampling_mode(Rovsm::RESUMED, Trovs::AUTOMATIC, true); + + loop { + let measured = adc.blocking_read(&mut p.PA0); + info!("data: 0x{:X}", measured); //max 0xFFF0 -> 65520 + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32g4/src/bin/blinky.rs b/embassy/examples/stm32g4/src/bin/blinky.rs new file mode 100644 index 0000000..90e479a --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32g4/src/bin/button.rs b/embassy/examples/stm32g4/src/bin/button.rs new file mode 100644 index 0000000..daebdd0 --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/button.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] + +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::Down); + + loop { + if button.is_high() { + info!("high"); + } else { + info!("low"); + } + } +} diff --git a/embassy/examples/stm32g4/src/bin/button_exti.rs b/embassy/examples/stm32g4/src/bin/button_exti.rs new file mode 100644 index 0000000..2a546da --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); + + info!("Press the USER button..."); + + loop { + button.wait_for_rising_edge().await; + info!("Pressed!"); + button.wait_for_falling_edge().await; + info!("Released!"); + } +} diff --git a/embassy/examples/stm32g4/src/bin/can.rs b/embassy/examples/stm32g4/src/bin/can.rs new file mode 100644 index 0000000..90004f8 --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/can.rs @@ -0,0 +1,234 @@ +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::peripherals::*; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, can, Config}; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => can::IT0InterruptHandler; + FDCAN1_IT1 => can::IT1InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV6, + mul: PllMul::MUL85, + divp: None, + divq: Some(PllQDiv::DIV8), // 42.5 Mhz for fdcan. + divr: Some(PllRDiv::DIV2), // Main system clock at 170 MHz + }); + config.rcc.mux.fdcansel = mux::Fdcansel::PLL1_Q; + config.rcc.sys = Sysclk::PLL1_R; + } + let peripherals = embassy_stm32::init(config); + + let mut can = can::CanConfigurator::new(peripherals.FDCAN1, peripherals.PA11, peripherals.PA12, Irqs); + + can.properties().set_extended_filter( + can::filter::ExtendedFilterSlot::_0, + can::filter::ExtendedFilter::accept_all_into_fifo1(), + ); + + // 250k bps + can.set_bitrate(250_000); + + let use_fd = false; + + // 1M bps + if use_fd { + can.set_fd_data_bitrate(1_000_000, false); + } + + info!("Configured"); + + let mut can = can.start(match use_fd { + true => can::OperatingMode::InternalLoopbackMode, + false => can::OperatingMode::NormalOperationMode, + }); + + let mut i = 0; + let mut last_read_ts = embassy_time::Instant::now(); + + loop { + let frame = can::frame::Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + + _ = can.write(&frame).await; + + match can.read().await { + Ok(envelope) => { + let (ts, rx_frame) = (envelope.ts, envelope.frame); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- {}ms", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + if i > 2 { + break; + } + } + + // Use the FD API's even if we don't get FD packets. + + loop { + if use_fd { + let frame = can::frame::FdFrame::new_extended(0x123456F, &[i; 16]).unwrap(); + info!("Writing frame using FD API"); + _ = can.write_fd(&frame).await; + } else { + let frame = can::frame::FdFrame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame using FD API"); + _ = can.write_fd(&frame).await; + } + + match can.read_fd().await { + Ok(envelope) => { + let (ts, rx_frame) = (envelope.ts, envelope.frame); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- using FD API {}ms", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + if i > 4 { + break; + } + } + i = 0; + let (mut tx, mut rx, _props) = can.split(); + // With split + loop { + let frame = can::frame::Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = tx.write(&frame).await; + + match rx.read().await { + Ok(envelope) => { + let (ts, rx_frame) = (envelope.ts, envelope.frame); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- {}ms", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + + if i > 2 { + break; + } + } + + let can = can::Can::join(tx, rx); + + info!("\n\n\nBuffered\n"); + if use_fd { + static TX_BUF: StaticCell> = StaticCell::new(); + static RX_BUF: StaticCell> = StaticCell::new(); + let mut can = can.buffered_fd( + TX_BUF.init(can::TxFdBuf::<8>::new()), + RX_BUF.init(can::RxFdBuf::<10>::new()), + ); + loop { + let frame = can::frame::FdFrame::new_extended(0x123456F, &[i; 16]).unwrap(); + info!("Writing frame"); + + _ = can.write(frame).await; + + match can.read().await { + Ok(envelope) => { + let (ts, rx_frame) = (envelope.ts, envelope.frame); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- {}ms", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i = i.wrapping_add(1); + } + } else { + static TX_BUF: StaticCell> = StaticCell::new(); + static RX_BUF: StaticCell> = StaticCell::new(); + let mut can = can.buffered( + TX_BUF.init(can::TxBuf::<8>::new()), + RX_BUF.init(can::RxBuf::<10>::new()), + ); + loop { + let frame = can::frame::Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + + // You can use any of these approaches to send. The writer makes it + // easy to share sending from multiple tasks. + //_ = can.write(frame).await; + //can.writer().try_write(frame).unwrap(); + can.writer().write(frame).await; + + match can.read().await { + Ok(envelope) => { + let (ts, rx_frame) = (envelope.ts, envelope.frame); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- {}ms", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i = i.wrapping_add(1); + } + } +} diff --git a/embassy/examples/stm32g4/src/bin/pll.rs b/embassy/examples/stm32g4/src/bin/pll.rs new file mode 100644 index 0000000..08ed95b --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/pll.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, + // Main system clock at 170 MHz + divr: Some(PllRDiv::DIV2), + }); + config.rcc.sys = Sysclk::PLL1_R; + } + let _p = embassy_stm32::init(config); + info!("Hello World!"); + + loop { + Timer::after_millis(1000).await; + info!("1s elapsed"); + } +} diff --git a/embassy/examples/stm32g4/src/bin/pwm.rs b/embassy/examples/stm32g4/src/bin/pwm.rs new file mode 100644 index 0000000..6c96501 --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/pwm.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::khz; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_time::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 ch1_pin = PwmPin::new_ch1(p.PC0, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); + + info!("PWM initialized"); + info!("PWM max duty {}", ch1.max_duty_cycle()); + + loop { + ch1.set_duty_cycle_fully_off(); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 4); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 2); + Timer::after_millis(300).await; + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32g4/src/bin/usb_c_pd.rs b/embassy/examples/stm32g4/src/bin/usb_c_pd.rs new file mode 100644 index 0000000..2e87d39 --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/usb_c_pd.rs @@ -0,0 +1,86 @@ +#![no_std] +#![no_main] + +use defmt::{error, info, Format}; +use embassy_executor::Spawner; +use embassy_stm32::ucpd::{self, CcPhy, CcPull, CcSel, CcVState, Ucpd}; +use embassy_stm32::{bind_interrupts, peripherals, Config}; +use embassy_time::{with_timeout, Duration}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UCPD1 => ucpd::InterruptHandler; +}); + +#[derive(Debug, Format)] +enum CableOrientation { + Normal, + Flipped, + DebugAccessoryMode, +} + +// Returns true when the cable +async fn wait_attached(cc_phy: &mut CcPhy<'_, T>) -> CableOrientation { + loop { + let (cc1, cc2) = cc_phy.vstate(); + if cc1 == CcVState::LOWEST && cc2 == CcVState::LOWEST { + // Detached, wait until attached by monitoring the CC lines. + cc_phy.wait_for_vstate_change().await; + continue; + } + + // Attached, wait for CC lines to be stable for tCCDebounce (100..200ms). + if with_timeout(Duration::from_millis(100), cc_phy.wait_for_vstate_change()) + .await + .is_ok() + { + // State has changed, restart detection procedure. + continue; + }; + + // State was stable for the complete debounce period, check orientation. + return match (cc1, cc2) { + (_, CcVState::LOWEST) => CableOrientation::Normal, // CC1 connected + (CcVState::LOWEST, _) => CableOrientation::Flipped, // CC2 connected + _ => CableOrientation::DebugAccessoryMode, // Both connected (special cable) + }; + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.enable_ucpd1_dead_battery = true; + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut ucpd = Ucpd::new(p.UCPD1, Irqs {}, p.PB6, p.PB4, Default::default()); + ucpd.cc_phy().set_pull(CcPull::Sink); + + info!("Waiting for USB connection..."); + let cable_orientation = wait_attached(ucpd.cc_phy()).await; + info!("USB cable connected, orientation: {}", cable_orientation); + + let cc_sel = match cable_orientation { + CableOrientation::Normal => { + info!("Starting PD communication on CC1 pin"); + CcSel::CC1 + } + CableOrientation::Flipped => { + info!("Starting PD communication on CC2 pin"); + CcSel::CC2 + } + CableOrientation::DebugAccessoryMode => panic!("No PD communication in DAM"), + }; + let (_cc_phy, mut pd_phy) = ucpd.split_pd_phy(p.DMA1_CH1, p.DMA1_CH2, cc_sel); + + loop { + // Enough space for the longest non-extended data message. + let mut buf = [0_u8; 30]; + match pd_phy.receive(buf.as_mut()).await { + Ok(n) => info!("USB PD RX: {=[u8]:?}", &buf[..n]), + Err(e) => error!("USB PD RX: {}", e), + } + } +} diff --git a/embassy/examples/stm32g4/src/bin/usb_serial.rs b/embassy/examples/stm32g4/src/bin/usb_serial.rs new file mode 100644 index 0000000..ed2ac7f --- /dev/null +++ b/embassy/examples/stm32g4/src/bin/usb_serial.rs @@ -0,0 +1,111 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::{self, Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_LP => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + // Sets up the Clock Recovery System (CRS) to use the USB SOF to trim the HSI48 oscillator. + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL72, + divp: None, + divq: Some(PllQDiv::DIV6), // 48mhz + divr: Some(PllRDiv::DIV2), // Main system clock at 144 MHz + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.boost = true; // BOOST! + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; + //config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; // uncomment to use PLL1_Q instead. + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-Serial Example"); + config.serial_number = Some("123456"); + + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + let mut usb = builder.build(); + + let usb_fut = usb.run(); + + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + 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/embassy/examples/stm32h5/.cargo/config.toml b/embassy/examples/stm32h5/.cargo/config.toml new file mode 100644 index 0000000..4781461 --- /dev/null +++ b/embassy/examples/stm32h5/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv8m.main-none-eabihf] +runner = 'probe-rs run --chip STM32H563ZITx' + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32h5/Cargo.toml b/embassy/examples/stm32h5/Cargo.toml new file mode 100644 index 0000000..516d491 --- /dev/null +++ b/embassy/examples/stm32h5/Cargo.toml @@ -0,0 +1,72 @@ +[package] +edition = "2021" +name = "embassy-stm32h5-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h563zi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h563zi", "memory-x", "time-driver-any", "exti", "unstable-pac", "low-power"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-io-async = { version = "0.6.1" } +embedded-nal-async = "0.8.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/embassy/examples/stm32h5/build.rs b/embassy/examples/stm32h5/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32h5/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/embassy/examples/stm32h5/src/bin/adc.rs b/embassy/examples/stm32h5/src/bin/adc.rs new file mode 100644 index 0000000..c5d508e --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/adc.rs @@ -0,0 +1,59 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL25, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV4), // SPI1 cksel defaults to pll1_q + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL25, + divp: None, + divq: None, + divr: Some(PllDiv::DIV4), // 100mhz + }); + config.rcc.sys = Sysclk::PLL1_P; // 200 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV1; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.adcdacsel = mux::Adcdacsel::PLL2_R; + } + let mut p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC1); + + adc.set_sample_time(SampleTime::CYCLES24_5); + + let mut vrefint_channel = adc.enable_vrefint(); + + loop { + let vrefint = adc.blocking_read(&mut vrefint_channel); + info!("vrefint: {}", vrefint); + let measured = adc.blocking_read(&mut p.PA0); + info!("measured: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32h5/src/bin/blinky.rs b/embassy/examples/stm32h5/src/bin/blinky.rs new file mode 100644 index 0000000..f37e8b1 --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PB0, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(500).await; + + info!("low"); + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32h5/src/bin/button_exti.rs b/embassy/examples/stm32h5/src/bin/button_exti.rs new file mode 100644 index 0000000..2a546da --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); + + info!("Press the USER button..."); + + loop { + button.wait_for_rising_edge().await; + info!("Pressed!"); + button.wait_for_falling_edge().await; + info!("Released!"); + } +} diff --git a/embassy/examples/stm32h5/src/bin/can.rs b/embassy/examples/stm32h5/src/bin/can.rs new file mode 100644 index 0000000..194239d --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/can.rs @@ -0,0 +1,98 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::peripherals::*; +use embassy_stm32::{bind_interrupts, can, rcc, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => can::IT0InterruptHandler; + FDCAN1_IT1 => can::IT1InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: embassy_stm32::time::Hertz(25_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.mux.fdcan12sel = rcc::mux::Fdcansel::HSE; + + let peripherals = embassy_stm32::init(config); + + let mut can = can::CanConfigurator::new(peripherals.FDCAN1, peripherals.PA11, peripherals.PA12, Irqs); + + // 250k bps + can.set_bitrate(250_000); + + //let mut can = can.into_internal_loopback_mode(); + let mut can = can.into_normal_mode(); + + info!("CAN Configured"); + + let mut i = 0; + let mut last_read_ts = embassy_time::Instant::now(); + + loop { + let frame = can::frame::Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = can.write(&frame).await; + + match can.read().await { + Ok(envelope) => { + let (rx_frame, ts) = envelope.parts(); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {:x} {:x} {:x} {:x} --- NEW {}", + rx_frame.data()[0], + rx_frame.data()[1], + rx_frame.data()[2], + rx_frame.data()[3], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + if i > 3 { + break; + } + } + + let (mut tx, mut rx, _props) = can.split(); + // With split + loop { + let frame = can::frame::Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = tx.write(&frame).await; + + match rx.read().await { + Ok(envelope) => { + let (rx_frame, ts) = envelope.parts(); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {:x} {:x} {:x} {:x} --- NEW {}", + rx_frame.data()[0], + rx_frame.data()[1], + rx_frame.data()[2], + rx_frame.data()[3], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i = i.wrapping_add(1); + } +} diff --git a/embassy/examples/stm32h5/src/bin/cordic.rs b/embassy/examples/stm32h5/src/bin/cordic.rs new file mode 100644 index 0000000..73e8735 --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/cordic.rs @@ -0,0 +1,78 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::cordic::{self, utils}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut dp = embassy_stm32::init(Default::default()); + + let mut cordic = cordic::Cordic::new( + &mut dp.CORDIC, + unwrap!(cordic::Config::new( + cordic::Function::Sin, + Default::default(), + Default::default(), + )), + ); + + // for output buf, the length is not that strict, larger than minimal required is ok. + let mut output_f64 = [0f64; 19]; + let mut output_u32 = [0u32; 21]; + + // tips: + // CORDIC peripheral has some strict on input value, you can also use ".check_argX_fXX()" methods + // to make sure your input values are compatible with current CORDIC setup. + let arg1 = [-1.0, -0.5, 0.0, 0.5, 1.0]; // for trigonometric function, the ARG1 value [-pi, pi] should be map to [-1, 1] + let arg2 = [0.5]; // and for Sin function, ARG2 should be in [0, 1] + + let mut input_buf = [0u32; 9]; + + // convert input from floating point to fixed point + input_buf[0] = unwrap!(utils::f64_to_q1_31(arg1[0])); + input_buf[1] = unwrap!(utils::f64_to_q1_31(arg2[0])); + + // If input length is small, blocking mode can be used to minimize overhead. + let cnt0 = unwrap!(cordic.blocking_calc_32bit( + &input_buf[..2], // input length is strict, since driver use its length to detect calculation count + &mut output_u32, + false, + false + )); + + // convert result from fixed point into floating point + for (&u32_val, f64_val) in output_u32[..cnt0].iter().zip(output_f64.iter_mut()) { + *f64_val = utils::q1_31_to_f64(u32_val); + } + + // convert input from floating point to fixed point + // + // first value from arg1 is used, so truncate to arg1[1..] + for (&f64_val, u32_val) in arg1[1..].iter().zip(input_buf.iter_mut()) { + *u32_val = unwrap!(utils::f64_to_q1_31(f64_val)); + } + + // If calculation is a little longer, async mode can make use of DMA, and let core do some other stuff. + let cnt1 = unwrap!( + cordic + .async_calc_32bit( + &mut dp.GPDMA1_CH0, + &mut dp.GPDMA1_CH1, + &input_buf[..arg1.len() - 1], // limit input buf to its actual length + &mut output_u32, + true, + false + ) + .await + ); + + // convert result from fixed point into floating point + for (&u32_val, f64_val) in output_u32[..cnt1].iter().zip(output_f64[cnt0..cnt0 + cnt1].iter_mut()) { + *f64_val = utils::q1_31_to_f64(u32_val); + } + + println!("result: {}", output_f64[..cnt0 + cnt1]); +} diff --git a/embassy/examples/stm32h5/src/bin/eth.rs b/embassy/examples/stm32h5/src/bin/eth.rs new file mode 100644 index 0000000..eee1632 --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/eth.rs @@ -0,0 +1,133 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, StackResources}; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_stm32::peripherals::ETH; +use embassy_stm32::rcc::{ + AHBPrescaler, APBPrescaler, Hse, HseMode, Pll, PllDiv, PllMul, PllPreDiv, PllSource, Sysclk, VoltageScale, +}; +use embassy_stm32::rng::Rng; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; +use embassy_time::Timer; +use embedded_io_async::Write; +use rand_core::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + RNG => rng::InterruptHandler; +}); + +type Device = Ethernet<'static, ETH, GenericSMI>; + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let mut config = Config::default(); + config.rcc.hsi = None; + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::BypassDigital, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL125, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV1; + config.rcc.apb2_pre = APBPrescaler::DIV1; + config.rcc.apb3_pre = APBPrescaler::DIV1; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.voltage_scale = VoltageScale::Scale0; + let p = embassy_stm32::init(config); + info!("Hello World!"); + + // Generate random seed. + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + static PACKETS: StaticCell> = StaticCell::new(); + let device = Ethernet::new( + PACKETS.init(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB15, + p.PG11, + GenericSMI::new(0), + mac_addr, + ); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + // Ensure DHCP configuration is up before trying connect + stack.wait_config_up().await; + + info!("Network task initialized"); + + // Then we can use it! + let mut rx_buffer = [0; 1024]; + let mut tx_buffer = [0; 1024]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); + info!("connecting..."); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + info!("connect error: {:?}", e); + Timer::after_secs(3).await; + continue; + } + info!("connected!"); + loop { + let r = socket.write_all(b"Hello\n").await; + if let Err(e) = r { + info!("write error: {:?}", e); + break; + } + Timer::after_secs(1).await; + } + } +} diff --git a/embassy/examples/stm32h5/src/bin/i2c.rs b/embassy/examples/stm32h5/src/bin/i2c.rs new file mode 100644 index 0000000..31e83cb --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/i2c.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Error, I2c}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +bind_interrupts!(struct Irqs { + I2C2_EV => i2c::EventInterruptHandler; + I2C2_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello world!"); + let p = embassy_stm32::init(Default::default()); + + let mut i2c = I2c::new( + p.I2C2, + p.PB10, + p.PB11, + Irqs, + p.GPDMA1_CH4, + p.GPDMA1_CH5, + Hertz(100_000), + Default::default(), + ); + + let mut data = [0u8; 1]; + + match 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/embassy/examples/stm32h5/src/bin/rng.rs b/embassy/examples/stm32h5/src/bin/rng.rs new file mode 100644 index 0000000..9c0d704 --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/rng.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, peripherals, rng}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG, Irqs); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); +} diff --git a/embassy/examples/stm32h5/src/bin/stop.rs b/embassy/examples/stm32h5/src/bin/stop.rs new file mode 100644 index 0000000..0d14c06 --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/stop.rs @@ -0,0 +1,71 @@ +// Notice: +// the MCU might need an extra reset to make the code actually running + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{AnyPin, Level, Output, Speed}; +use embassy_stm32::low_power::Executor; +use embassy_stm32::rcc::{HSIPrescaler, LsConfig}; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::Config; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + Executor::take().run(|spawner| { + unwrap!(spawner.spawn(async_main(spawner))); + }) +} + +#[embassy_executor::task] +async fn async_main(spawner: Spawner) { + defmt::info!("Program Start"); + + let mut config = Config::default(); + + // System Clock seems need to be equal or lower than 16 MHz + config.rcc.hsi = Some(HSIPrescaler::DIV4); + + config.rcc.ls = LsConfig::default_lsi(); + // when enabled the power-consumption is much higher during stop, but debugging and RTT is working + // if you wan't to measure the power-consumption, or for production: uncomment this line + // config.enable_debug_during_sleep = false; + let p = embassy_stm32::init(config); + + // give the RTC to the executor... + let rtc = Rtc::new(p.RTC, RtcConfig::default()); + static RTC: StaticCell = StaticCell::new(); + let rtc = RTC.init(rtc); + embassy_stm32::low_power::stop_with_rtc(rtc); + + unwrap!(spawner.spawn(blinky(p.PB4.into()))); + unwrap!(spawner.spawn(timeout())); +} + +#[embassy_executor::task] +async fn blinky(led: AnyPin) { + let mut led = Output::new(led, Level::Low, Speed::Low); + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} + +// when enable_debug_during_sleep is false, it is more difficult to reprogram the MCU +// therefore we block the MCU after 30s to be able to reprogram it easily +#[embassy_executor::task] +async fn timeout() -> ! { + Timer::after_secs(30).await; + #[allow(clippy::empty_loop)] + loop {} +} diff --git a/embassy/examples/stm32h5/src/bin/usart.rs b/embassy/examples/stm32h5/src/bin/usart.rs new file mode 100644 index 0000000..cc49c2f --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/usart.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::usart::{Config, Uart}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn main_task() { + let p = embassy_stm32::init(Default::default()); + + let config = Config::default(); + let mut usart = Uart::new_blocking(p.UART7, p.PF6, p.PF7, config).unwrap(); + + unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.blocking_read(&mut buf)); + unwrap!(usart.blocking_write(&buf)); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task())); + }) +} diff --git a/embassy/examples/stm32h5/src/bin/usart_dma.rs b/embassy/examples/stm32h5/src/bin/usart_dma.rs new file mode 100644 index 0000000..c644e84 --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/usart_dma.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use heapless::String; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + +#[embassy_executor::task] +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, Irqs, p.GPDMA1_CH0, p.GPDMA1_CH1, config).unwrap(); + + for n in 0u32.. { + let mut s: String<128> = String::new(); + core::write!(&mut s, "Hello DMA World {}!\r\n", n).unwrap(); + + usart.write(s.as_bytes()).await.ok(); + + info!("wrote DMA"); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task())); + }) +} diff --git a/embassy/examples/stm32h5/src/bin/usart_split.rs b/embassy/examples/stm32h5/src/bin/usart_split.rs new file mode 100644 index 0000000..d26c500 --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/usart_split.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::mode::Async; +use embassy_stm32::usart::{Config, Uart, UartRx}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::Channel; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + +static CHANNEL: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, p.GPDMA1_CH0, p.GPDMA1_CH1, config).unwrap(); + unwrap!(usart.blocking_write(b"Type 8 chars to echo!\r\n")); + + let (mut tx, rx) = usart.split(); + + unwrap!(spawner.spawn(reader(rx))); + + loop { + let buf = CHANNEL.receive().await; + info!("writing..."); + unwrap!(tx.write(&buf).await); + } +} + +#[embassy_executor::task] +async fn reader(mut rx: UartRx<'static, Async>) { + let mut buf = [0; 8]; + loop { + info!("reading..."); + unwrap!(rx.read(&mut buf).await); + CHANNEL.send(buf).await; + } +} diff --git a/embassy/examples/stm32h5/src/bin/usb_serial.rs b/embassy/examples/stm32h5/src/bin/usb_serial.rs new file mode 100644 index 0000000..fbcbdb5 --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/usb_serial.rs @@ -0,0 +1,126 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_DRD_FS => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = None; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::BypassDigital, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL125, + divp: Some(PllDiv::DIV2), // 250mhz + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV2; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.apb3_pre = APBPrescaler::DIV4; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.voltage_scale = VoltageScale::Scale0; + config.rcc.mux.usbsel = mux::Usbsel::HSI48; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // 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 compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32h5/src/bin/usb_uac_speaker.rs b/embassy/examples/stm32h5/src/bin/usb_uac_speaker.rs new file mode 100644 index 0000000..8c24fa9 --- /dev/null +++ b/embassy/examples/stm32h5/src/bin/usb_uac_speaker.rs @@ -0,0 +1,381 @@ +#![no_std] +#![no_main] + +use core::cell::{Cell, RefCell}; + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, interrupt, peripherals, timer, usb, Config}; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::signal::Signal; +use embassy_sync::zerocopy_channel; +use embassy_usb::class::uac1; +use embassy_usb::class::uac1::speaker::{self, Speaker}; +use embassy_usb::driver::EndpointError; +use heapless::Vec; +use micromath::F32Ext; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_DRD_FS => usb::InterruptHandler; +}); + +static TIMER: Mutex>>> = + Mutex::new(RefCell::new(None)); + +// A counter signal that is written by the feedback timer, once every `FEEDBACK_REFRESH_PERIOD`. +// At that point, a feedback value is sent to the host. +pub static FEEDBACK_SIGNAL: Signal = Signal::new(); + +// Stereo input +pub const INPUT_CHANNEL_COUNT: usize = 2; + +// This example uses a fixed sample rate of 48 kHz. +pub const SAMPLE_RATE_HZ: u32 = 48_000; +pub const FEEDBACK_COUNTER_TICK_RATE: u32 = 31_250_000; + +// Use 32 bit samples, which allow for a lot of (software) volume adjustment without degradation of quality. +pub const SAMPLE_WIDTH: uac1::SampleWidth = uac1::SampleWidth::Width4Byte; +pub const SAMPLE_WIDTH_BIT: usize = SAMPLE_WIDTH.in_bit(); +pub const SAMPLE_SIZE: usize = SAMPLE_WIDTH as usize; +pub const SAMPLE_SIZE_PER_S: usize = (SAMPLE_RATE_HZ as usize) * INPUT_CHANNEL_COUNT * SAMPLE_SIZE; + +// Size of audio samples per 1 ms - for the full-speed USB frame period of 1 ms. +pub const USB_FRAME_SIZE: usize = SAMPLE_SIZE_PER_S.div_ceil(1000); + +// Select front left and right audio channels. +pub const AUDIO_CHANNELS: [uac1::Channel; INPUT_CHANNEL_COUNT] = [uac1::Channel::LeftFront, uac1::Channel::RightFront]; + +// Factor of two as a margin for feedback (this is an excessive amount) +pub const USB_MAX_PACKET_SIZE: usize = 2 * USB_FRAME_SIZE; +pub const USB_MAX_SAMPLE_COUNT: usize = USB_MAX_PACKET_SIZE / SAMPLE_SIZE; + +// The data type that is exchanged via the zero-copy channel (a sample vector). +pub type SampleBlock = Vec; + +// Feedback is provided in 10.14 format for full-speed endpoints. +pub const FEEDBACK_REFRESH_PERIOD: uac1::FeedbackRefresh = uac1::FeedbackRefresh::Period8Frames; +const FEEDBACK_SHIFT: usize = 14; + +const TICKS_PER_SAMPLE: f32 = (FEEDBACK_COUNTER_TICK_RATE as f32) / (SAMPLE_RATE_HZ as f32); + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +/// Sends feedback messages to the host. +async fn feedback_handler<'d, T: usb::Instance + 'd>( + feedback: &mut speaker::Feedback<'d, usb::Driver<'d, T>>, + feedback_factor: f32, +) -> Result<(), Disconnected> { + let mut packet: Vec = Vec::new(); + + // Collects the fractional component of the feedback value that is lost by rounding. + let mut rest = 0.0_f32; + + loop { + let counter = FEEDBACK_SIGNAL.wait().await; + + packet.clear(); + + let raw_value = counter as f32 * feedback_factor + rest; + let value = raw_value.round(); + rest = raw_value - value; + + let value = value as u32; + + debug!("Feedback value: {}", value); + + packet.push(value as u8).unwrap(); + packet.push((value >> 8) as u8).unwrap(); + packet.push((value >> 16) as u8).unwrap(); + + feedback.write_packet(&packet).await?; + } +} + +/// Handles streaming of audio data from the host. +async fn stream_handler<'d, T: usb::Instance + 'd>( + stream: &mut speaker::Stream<'d, usb::Driver<'d, T>>, + sender: &mut zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) -> Result<(), Disconnected> { + loop { + let mut usb_data = [0u8; USB_MAX_PACKET_SIZE]; + let data_size = stream.read_packet(&mut usb_data).await?; + + let word_count = data_size / SAMPLE_SIZE; + + if word_count * SAMPLE_SIZE == data_size { + // Obtain a buffer from the channel + let samples = sender.send().await; + samples.clear(); + + for w in 0..word_count { + let byte_offset = w * SAMPLE_SIZE; + let sample = u32::from_le_bytes(usb_data[byte_offset..byte_offset + SAMPLE_SIZE].try_into().unwrap()); + + // Fill the sample buffer with data. + samples.push(sample).unwrap(); + } + + sender.send_done(); + } else { + debug!("Invalid USB buffer size of {}, skipped.", data_size); + } + } +} + +/// Receives audio samples from the USB streaming task and can play them back. +#[embassy_executor::task] +async fn audio_receiver_task(mut usb_audio_receiver: zerocopy_channel::Receiver<'static, NoopRawMutex, SampleBlock>) { + loop { + let _samples = usb_audio_receiver.receive().await; + // Use the samples, for example play back via the SAI peripheral. + + // Notify the channel that the buffer is now ready to be reused + usb_audio_receiver.receive_done(); + } +} + +/// Receives audio samples from the host. +#[embassy_executor::task] +async fn usb_streaming_task( + mut stream: speaker::Stream<'static, usb::Driver<'static, peripherals::USB>>, + mut sender: zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) { + loop { + stream.wait_connection().await; + info!("USB connected."); + _ = stream_handler(&mut stream, &mut sender).await; + info!("USB disconnected."); + } +} + +/// Sends sample rate feedback to the host. +/// +/// The `feedback_factor` scales the feedback timer's counter value so that the result is the number of samples that +/// this device played back or "consumed" during one SOF period (1 ms) - in 10.14 format. +/// +/// Ideally, the `feedback_factor` that is calculated below would be an integer for avoiding numerical errors. +/// This is achieved by having `TICKS_PER_SAMPLE` be a power of two. For audio applications at a sample rate of 48 kHz, +/// 24.576 MHz would be one such option. +#[embassy_executor::task] +async fn usb_feedback_task(mut feedback: speaker::Feedback<'static, usb::Driver<'static, peripherals::USB>>) { + let feedback_factor = + ((1 << FEEDBACK_SHIFT) as f32 / TICKS_PER_SAMPLE) / FEEDBACK_REFRESH_PERIOD.frame_count() as f32; + + loop { + feedback.wait_connection().await; + _ = feedback_handler(&mut feedback, feedback_factor).await; + } +} + +#[embassy_executor::task] +async fn usb_task(mut usb_device: embassy_usb::UsbDevice<'static, usb::Driver<'static, peripherals::USB>>) { + usb_device.run().await; +} + +/// Checks for changes on the control monitor of the class. +/// +/// In this case, monitor changes of volume or mute state. +#[embassy_executor::task] +async fn usb_control_task(control_monitor: speaker::ControlMonitor<'static>) { + loop { + control_monitor.changed().await; + + for channel in AUDIO_CHANNELS { + let volume = control_monitor.volume(channel).unwrap(); + info!("Volume changed to {} on channel {}.", volume, channel); + } + } +} + +/// Feedback value measurement and calculation +/// +/// Used for measuring/calculating the number of samples that were received from the host during the +/// `FEEDBACK_REFRESH_PERIOD`. +/// +/// Configured in this example with +/// - a refresh period of 8 ms, and +/// - a tick rate of 42 MHz. +/// +/// This gives an (ideal) counter value of 336.000 for every update of the `FEEDBACK_SIGNAL`. +#[interrupt] +fn TIM5() { + static LAST_TICKS: Mutex> = Mutex::new(Cell::new(0)); + static FRAME_COUNT: Mutex> = Mutex::new(Cell::new(0)); + + critical_section::with(|cs| { + // Read timer counter. + let timer = TIMER.borrow(cs).borrow().as_ref().unwrap().regs_gp32(); + + let status = timer.sr().read(); + + const CHANNEL_INDEX: usize = 0; + if status.ccif(CHANNEL_INDEX) { + let ticks = timer.ccr(CHANNEL_INDEX).read(); + + let frame_count = FRAME_COUNT.borrow(cs); + let last_ticks = LAST_TICKS.borrow(cs); + + frame_count.set(frame_count.get() + 1); + if frame_count.get() >= FEEDBACK_REFRESH_PERIOD.frame_count() { + frame_count.set(0); + FEEDBACK_SIGNAL.signal(ticks.wrapping_sub(last_ticks.get())); + last_ticks.set(ticks); + } + }; + + // Clear trigger interrupt flag. + timer.sr().modify(|r| r.set_tif(false)); + }); +} + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = None; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::BypassDigital, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL125, + divp: Some(PllDiv::DIV2), // 250 Mhz + divq: None, + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL123, + divp: Some(PllDiv::DIV20), // 12.3 Mhz, close to 12.288 MHz for 48 kHz audio + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV2; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.apb3_pre = APBPrescaler::DIV4; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.voltage_scale = VoltageScale::Scale0; + config.rcc.mux.usbsel = mux::Usbsel::HSI48; + config.rcc.mux.sai2sel = mux::Saisel::PLL2_P; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Configure all required buffers in a static way. + debug!("USB packet size is {} byte", USB_MAX_PACKET_SIZE); + static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); + let config_descriptor = CONFIG_DESCRIPTOR.init([0; 256]); + + static BOS_DESCRIPTOR: StaticCell<[u8; 32]> = StaticCell::new(); + let bos_descriptor = BOS_DESCRIPTOR.init([0; 32]); + + const CONTROL_BUF_SIZE: usize = 64; + static CONTROL_BUF: StaticCell<[u8; CONTROL_BUF_SIZE]> = StaticCell::new(); + let control_buf = CONTROL_BUF.init([0; CONTROL_BUF_SIZE]); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(speaker::State::new()); + + let usb_driver = usb::Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Basic USB device configuration + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-audio-speaker example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + let mut builder = embassy_usb::Builder::new( + usb_driver, + config, + config_descriptor, + bos_descriptor, + &mut [], // no msos descriptors + control_buf, + ); + + // Create the UAC1 Speaker class components + let (stream, feedback, control_monitor) = Speaker::new( + &mut builder, + state, + USB_MAX_PACKET_SIZE as u16, + uac1::SampleWidth::Width4Byte, + &[SAMPLE_RATE_HZ], + &AUDIO_CHANNELS, + FEEDBACK_REFRESH_PERIOD, + ); + + // Create the USB device + let usb_device = builder.build(); + + // Establish a zero-copy channel for transferring received audio samples between tasks + static SAMPLE_BLOCKS: StaticCell<[SampleBlock; 2]> = StaticCell::new(); + let sample_blocks = SAMPLE_BLOCKS.init([Vec::new(), Vec::new()]); + + static CHANNEL: StaticCell> = StaticCell::new(); + let channel = CHANNEL.init(zerocopy_channel::Channel::new(sample_blocks)); + let (sender, receiver) = channel.split(); + + // Run a timer for counting between SOF interrupts. + let mut tim5 = timer::low_level::Timer::new(p.TIM5); + tim5.set_tick_freq(Hertz(FEEDBACK_COUNTER_TICK_RATE)); + tim5.set_trigger_source(timer::low_level::TriggerSource::ITR12); // The USB SOF signal. + + const TIMER_CHANNEL: timer::Channel = timer::Channel::Ch1; + tim5.set_input_ti_selection(TIMER_CHANNEL, timer::low_level::InputTISelection::TRC); + tim5.set_input_capture_prescaler(TIMER_CHANNEL, 0); + tim5.set_input_capture_filter(TIMER_CHANNEL, timer::low_level::FilterValue::FCK_INT_N2); + + // Reset all interrupt flags. + tim5.regs_gp32().sr().write(|r| r.0 = 0); + + tim5.enable_channel(TIMER_CHANNEL, true); + tim5.enable_input_interrupt(TIMER_CHANNEL, true); + + tim5.start(); + + TIMER.lock(|p| p.borrow_mut().replace(tim5)); + + // Unmask the TIM5 interrupt. + unsafe { + cortex_m::peripheral::NVIC::unmask(interrupt::TIM5); + } + + // Launch USB audio tasks. + unwrap!(spawner.spawn(usb_control_task(control_monitor))); + unwrap!(spawner.spawn(usb_streaming_task(stream, sender))); + unwrap!(spawner.spawn(usb_feedback_task(feedback))); + unwrap!(spawner.spawn(usb_task(usb_device))); + unwrap!(spawner.spawn(audio_receiver_task(receiver))); +} diff --git a/embassy/examples/stm32h7/.cargo/config.toml b/embassy/examples/stm32h7/.cargo/config.toml new file mode 100644 index 0000000..5f680db --- /dev/null +++ b/embassy/examples/stm32h7/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H743ZITx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32h7/Cargo.toml b/embassy/examples/stm32h7/Cargo.toml new file mode 100644 index 0000000..68a0c3d --- /dev/null +++ b/embassy/examples/stm32h7/Cargo.toml @@ -0,0 +1,75 @@ +[package] +edition = "2021" +name = "embassy-stm32h7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h743bi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h743bi", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/embassy/examples/stm32h7/build.rs b/embassy/examples/stm32h7/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/stm32h7/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/embassy/examples/stm32h7/memory.x b/embassy/examples/stm32h7/memory.x new file mode 100644 index 0000000..e5ab1f6 --- /dev/null +++ b/embassy/examples/stm32h7/memory.x @@ -0,0 +1,14 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 2048K /* BANK_1 + BANK_2 */ + RAM : ORIGIN = 0x24000000, LENGTH = 512K /* SRAM */ + RAM_D3 : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 */ +} + +SECTIONS +{ + .ram_d3 : + { + *(.ram_d3) + } > RAM_D3 +} \ No newline at end of file diff --git a/embassy/examples/stm32h7/src/bin/adc.rs b/embassy/examples/stm32h7/src/bin/adc.rs new file mode 100644 index 0000000..98504dd --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/adc.rs @@ -0,0 +1,60 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // SPI1 cksel defaults to pll1_q + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV8), // 100mhz + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; + } + let mut p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC3); + + adc.set_sample_time(SampleTime::CYCLES32_5); + + let mut vrefint_channel = adc.enable_vrefint(); + + loop { + let vrefint = adc.blocking_read(&mut vrefint_channel); + info!("vrefint: {}", vrefint); + let measured = adc.blocking_read(&mut p.PC0); + info!("measured: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32h7/src/bin/adc_dma.rs b/embassy/examples/stm32h7/src/bin/adc_dma.rs new file mode 100644 index 0000000..0b905d2 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/adc_dma.rs @@ -0,0 +1,76 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, AdcChannel as _, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".ram_d3"] +static mut DMA_BUF: [u16; 2] = [0; 2]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut read_buffer = unsafe { &mut DMA_BUF[..] }; + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // SPI1 cksel defaults to pll1_q + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV8), // 100mhz + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC3); + + let mut dma = p.DMA1_CH1; + let mut vrefint_channel = adc.enable_vrefint().degrade_adc(); + let mut pc0 = p.PC0.degrade_adc(); + + loop { + adc.read( + &mut dma, + [ + (&mut vrefint_channel, SampleTime::CYCLES387_5), + (&mut pc0, SampleTime::CYCLES810_5), + ] + .into_iter(), + &mut read_buffer, + ) + .await; + + let vrefint = read_buffer[0]; + let measured = read_buffer[1]; + info!("vrefint: {}", vrefint); + info!("measured: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32h7/src/bin/blinky.rs b/embassy/examples/stm32h7/src/bin/blinky.rs new file mode 100644 index 0000000..1ee90a8 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PB14, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(500).await; + + info!("low"); + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32h7/src/bin/button_exti.rs b/embassy/examples/stm32h7/src/bin/button_exti.rs new file mode 100644 index 0000000..2a546da --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); + + info!("Press the USER button..."); + + loop { + button.wait_for_rising_edge().await; + info!("Pressed!"); + button.wait_for_falling_edge().await; + info!("Released!"); + } +} diff --git a/embassy/examples/stm32h7/src/bin/camera.rs b/embassy/examples/stm32h7/src/bin/camera.rs new file mode 100644 index 0000000..170a5aa --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/camera.rs @@ -0,0 +1,302 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_stm32::dcmi::{self, *}; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::i2c::I2c; +use embassy_stm32::rcc::{Mco, Mco1Source, McoPrescaler}; +use embassy_stm32::time::khz; +use embassy_stm32::{bind_interrupts, i2c, peripherals, Config}; +use embassy_time::Timer; +use ov7725::*; +use {defmt_rtt as _, panic_probe as _}; + +const WIDTH: usize = 100; +const HEIGHT: usize = 100; + +static mut FRAME: [u32; WIDTH * HEIGHT / 2] = [0u32; WIDTH * HEIGHT / 2]; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; + DCMI => dcmi::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // 100mhz + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + + defmt::info!("Hello World!"); + let mco = Mco::new(p.MCO1, p.PA8, Mco1Source::HSI, McoPrescaler::DIV3); + + let mut led = Output::new(p.PE3, Level::High, Speed::Low); + let cam_i2c = I2c::new( + p.I2C1, + p.PB8, + p.PB9, + Irqs, + p.DMA1_CH1, + p.DMA1_CH2, + khz(100), + Default::default(), + ); + + let mut camera = Ov7725::new(cam_i2c, mco); + + defmt::unwrap!(camera.init().await); + + let manufacturer_id = defmt::unwrap!(camera.read_manufacturer_id().await); + let camera_id = defmt::unwrap!(camera.read_product_id().await); + + defmt::info!("manufacturer: 0x{:x}, pid: 0x{:x}", manufacturer_id, camera_id); + + let config = dcmi::Config::default(); + let mut dcmi = Dcmi::new_8bit( + p.DCMI, p.DMA1_CH0, Irqs, p.PC6, p.PC7, p.PE0, p.PE1, p.PE4, p.PD3, p.PE5, p.PE6, p.PB7, p.PA4, p.PA6, config, + ); + + defmt::info!("attempting capture"); + defmt::unwrap!(dcmi.capture(unsafe { &mut *core::ptr::addr_of_mut!(FRAME) }).await); + + defmt::info!("captured frame: {:x}", unsafe { &*core::ptr::addr_of!(FRAME) }); + + defmt::info!("main loop running"); + loop { + defmt::info!("high"); + led.set_high(); + Timer::after_millis(500).await; + + defmt::info!("low"); + led.set_low(); + Timer::after_millis(500).await; + } +} + +mod ov7725 { + use core::marker::PhantomData; + + use defmt::Format; + use embassy_stm32::rcc::{Mco, McoInstance}; + use embassy_time::Timer; + use embedded_hal_async::i2c::I2c; + + #[repr(u8)] + pub enum RgbFormat { + Gbr422 = 0, + RGB565 = 1, + RGB555 = 2, + RGB444 = 3, + } + pub enum PixelFormat { + Yuv, + ProcessedRawBayer, + Rgb(RgbFormat), + RawBayer, + } + + impl From for u8 { + fn from(raw: PixelFormat) -> Self { + match raw { + PixelFormat::Yuv => 0, + PixelFormat::ProcessedRawBayer => 1, + PixelFormat::Rgb(mode) => 2 | ((mode as u8) << 2), + PixelFormat::RawBayer => 3, + } + } + } + + #[derive(Clone, Copy)] + #[repr(u8)] + #[allow(unused)] + pub enum Register { + Gain = 0x00, + Blue = 0x01, + Red = 0x02, + Green = 0x03, + BAvg = 0x05, + GAvg = 0x06, + RAvg = 0x07, + Aech = 0x08, + Com2 = 0x09, + PId = 0x0a, + Ver = 0x0b, + Com3 = 0x0c, + Com4 = 0x0d, + Com5 = 0x0e, + Com6 = 0x0f, + Aec = 0x10, + ClkRc = 0x11, + Com7 = 0x12, + Com8 = 0x13, + Com9 = 0x14, + Com10 = 0x15, + Reg16 = 0x16, + HStart = 0x17, + HSize = 0x18, + VStart = 0x19, + VSize = 0x1a, + PShift = 0x1b, + MidH = 0x1c, + MidL = 0x1d, + Laec = 0x1f, + Com11 = 0x20, + BdBase = 0x22, + BdMStep = 0x23, + Aew = 0x24, + Aeb = 0x25, + Vpt = 0x26, + Reg28 = 0x28, + HOutSize = 0x29, + EXHCH = 0x2a, + EXHCL = 0x2b, + VOutSize = 0x2c, + Advfl = 0x2d, + Advfh = 0x2e, + Yave = 0x2f, + LumHTh = 0x30, + LumLTh = 0x31, + HRef = 0x32, + DspCtrl4 = 0x67, + DspAuto = 0xac, + } + + const CAM_ADDR: u8 = 0x21; + + #[derive(Format, PartialEq, Eq)] + pub enum Error { + I2c(I2cError), + } + + pub struct Ov7725<'d, Bus: I2c> { + phantom: PhantomData<&'d ()>, + bus: Bus, + } + + impl<'d, Bus> Ov7725<'d, Bus> + where + Bus: I2c, + Bus::Error: Format, + { + pub fn new(bus: Bus, _mco: Mco) -> Self + where + T: McoInstance, + { + Self { + phantom: PhantomData, + bus, + } + } + + pub async fn init(&mut self) -> Result<(), Error> { + Timer::after_millis(500).await; + self.reset_regs().await?; + Timer::after_millis(500).await; + self.set_pixformat().await?; + self.set_resolution().await?; + Ok(()) + } + + pub async fn read_manufacturer_id(&mut self) -> Result> { + Ok(u16::from_le_bytes([ + self.read(Register::MidL).await?, + self.read(Register::MidH).await?, + ])) + } + + pub async fn read_product_id(&mut self) -> Result> { + Ok(u16::from_le_bytes([ + self.read(Register::Ver).await?, + self.read(Register::PId).await?, + ])) + } + + async fn reset_regs(&mut self) -> Result<(), Error> { + self.write(Register::Com7, 0x80).await + } + + async fn set_pixformat(&mut self) -> Result<(), Error> { + self.write(Register::DspCtrl4, 0).await?; + let mut com7 = self.read(Register::Com7).await?; + com7 |= u8::from(PixelFormat::Rgb(RgbFormat::RGB565)); + self.write(Register::Com7, com7).await?; + Ok(()) + } + + async fn set_resolution(&mut self) -> Result<(), Error> { + let horizontal: u16 = super::WIDTH as u16; + let vertical: u16 = super::HEIGHT as u16; + + let h_high = (horizontal >> 2) as u8; + let v_high = (vertical >> 1) as u8; + let h_low = (horizontal & 0x03) as u8; + let v_low = (vertical & 0x01) as u8; + + self.write(Register::HOutSize, h_high).await?; + self.write(Register::VOutSize, v_high).await?; + self.write(Register::EXHCH, h_low | (v_low << 2)).await?; + + self.write(Register::Com3, 0xd1).await?; + + let com3 = self.read(Register::Com3).await?; + let vflip = com3 & 0x80 > 0; + + self.modify(Register::HRef, |reg| reg & 0xbf | if vflip { 0x40 } else { 0x40 }) + .await?; + + if horizontal <= 320 || vertical <= 240 { + self.write(Register::HStart, 0x3f).await?; + self.write(Register::HSize, 0x50).await?; + self.write(Register::VStart, 0x02).await?; // TODO vflip is subtracted in the original code + self.write(Register::VSize, 0x78).await?; + } else { + defmt::panic!("VGA resolutions not yet supported."); + } + + Ok(()) + } + + async fn read(&mut self, register: Register) -> Result> { + let mut buffer = [0u8; 1]; + self.bus + .write_read(CAM_ADDR, &[register as u8], &mut buffer[..1]) + .await + .map_err(Error::I2c)?; + Ok(buffer[0]) + } + + async fn write(&mut self, register: Register, value: u8) -> Result<(), Error> { + self.bus + .write(CAM_ADDR, &[register as u8, value]) + .await + .map_err(Error::I2c) + } + + async fn modify u8>(&mut self, register: Register, f: F) -> Result<(), Error> { + let value = self.read(register).await?; + let value = f(value); + self.write(register, value).await + } + } +} diff --git a/embassy/examples/stm32h7/src/bin/can.rs b/embassy/examples/stm32h7/src/bin/can.rs new file mode 100644 index 0000000..0af11ef --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/can.rs @@ -0,0 +1,98 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::peripherals::*; +use embassy_stm32::{bind_interrupts, can, rcc, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => can::IT0InterruptHandler; + FDCAN1_IT1 => can::IT1InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: embassy_stm32::time::Hertz(25_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.mux.fdcansel = rcc::mux::Fdcansel::HSE; + + let peripherals = embassy_stm32::init(config); + + let mut can = can::CanConfigurator::new(peripherals.FDCAN1, peripherals.PA11, peripherals.PA12, Irqs); + + // 250k bps + can.set_bitrate(250_000); + + //let mut can = can.into_internal_loopback_mode(); + let mut can = can.into_normal_mode(); + + info!("CAN Configured"); + + let mut i = 0; + let mut last_read_ts = embassy_time::Instant::now(); + + loop { + let frame = can::frame::Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = can.write(&frame).await; + + match can.read().await { + Ok(envelope) => { + let (rx_frame, ts) = envelope.parts(); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {:x} {:x} {:x} {:x} --- NEW {}", + rx_frame.data()[0], + rx_frame.data()[1], + rx_frame.data()[2], + rx_frame.data()[3], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + if i > 3 { + break; + } + } + + let (mut tx, mut rx, _props) = can.split(); + // With split + loop { + let frame = can::frame::Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = tx.write(&frame).await; + + match rx.read().await { + Ok(envelope) => { + let (rx_frame, ts) = envelope.parts(); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {:x} {:x} {:x} {:x} --- NEW {}", + rx_frame.data()[0], + rx_frame.data()[1], + rx_frame.data()[2], + rx_frame.data()[3], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i = i.wrapping_add(1); + } +} diff --git a/embassy/examples/stm32h7/src/bin/dac.rs b/embassy/examples/stm32h7/src/bin/dac.rs new file mode 100644 index 0000000..a6f969a --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/dac.rs @@ -0,0 +1,68 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::dac::{DacCh1, Value}; +use embassy_stm32::dma::NoDma; +use embassy_stm32::Config; +use {defmt_rtt as _, panic_probe as _}; + +#[entry] +fn main() -> ! { + info!("Hello World, dude!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // 100mhz + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV8), // 100mhz + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; + } + let p = embassy_stm32::init(config); + + let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); + + loop { + for v in 0..=255 { + dac.set(Value::Bit8(to_sine_wave(v))); + } + } +} + +use micromath::F32Ext; + +fn to_sine_wave(v: u8) -> u8 { + if v >= 128 { + // top half + let r = 3.14 * ((v - 128) as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } else { + // bottom half + let r = 3.14 + 3.14 * (v as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } +} diff --git a/embassy/examples/stm32h7/src/bin/dac_dma.rs b/embassy/examples/stm32h7/src/bin/dac_dma.rs new file mode 100644 index 0000000..3a9887e --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/dac_dma.rs @@ -0,0 +1,158 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dac::{DacCh1, DacCh2, ValueArray}; +use embassy_stm32::pac::timer::vals::Mms; +use embassy_stm32::peripherals::{DAC1, DMA1_CH3, DMA1_CH4, TIM6, TIM7}; +use embassy_stm32::rcc::frequency; +use embassy_stm32::time::Hertz; +use embassy_stm32::timer::low_level::Timer; +use micromath::F32Ext; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // 100mhz + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV8), // 100mhz + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; + } + + // Initialize the board and obtain a Peripherals instance + let p: embassy_stm32::Peripherals = embassy_stm32::init(config); + + // Obtain two independent channels (p.DAC1 can only be consumed once, though!) + let (dac_ch1, dac_ch2) = embassy_stm32::dac::Dac::new(p.DAC1, p.DMA1_CH3, p.DMA1_CH4, p.PA4, p.PA5).split(); + + spawner.spawn(dac_task1(p.TIM6, dac_ch1)).ok(); + spawner.spawn(dac_task2(p.TIM7, dac_ch2)).ok(); +} + +#[embassy_executor::task] +async fn dac_task1(tim: TIM6, mut dac: DacCh1<'static, DAC1, DMA1_CH3>) { + let data: &[u8; 256] = &calculate_array::<256>(); + + info!("TIM6 frequency is {}", frequency::()); + const FREQUENCY: Hertz = Hertz::hz(200); + + // Compute the reload value such that we obtain the FREQUENCY for the sine + let reload: u32 = (frequency::().0 / FREQUENCY.0) / data.len() as u32; + + // Depends on your clock and on the specific chip used, you may need higher or lower values here + if reload < 10 { + error!("Reload value {} below threshold!", reload); + } + + dac.set_trigger(embassy_stm32::dac::TriggerSel::Tim6); + dac.set_triggering(true); + dac.enable(); + + let tim = Timer::new(tim); + tim.regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1)); + tim.regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE)); + tim.regs_basic().cr1().modify(|w| { + w.set_opm(false); + w.set_cen(true); + }); + + debug!( + "TIM6 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}", + frequency::(), + FREQUENCY, + reload, + reload as u16, + data.len() + ); + + // Loop technically not necessary if DMA circular mode is enabled + loop { + info!("Loop DAC1"); + dac.write(ValueArray::Bit8(data), true).await; + } +} + +#[embassy_executor::task] +async fn dac_task2(tim: TIM7, mut dac: DacCh2<'static, DAC1, DMA1_CH4>) { + let data: &[u8; 256] = &calculate_array::<256>(); + + info!("TIM7 frequency is {}", frequency::()); + + const FREQUENCY: Hertz = Hertz::hz(600); + let reload: u32 = (frequency::().0 / FREQUENCY.0) / data.len() as u32; + + if reload < 10 { + error!("Reload value {} below threshold!", reload); + } + + let tim = Timer::new(tim); + tim.regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1)); + tim.regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE)); + tim.regs_basic().cr1().modify(|w| { + w.set_opm(false); + w.set_cen(true); + }); + + dac.set_trigger(embassy_stm32::dac::TriggerSel::Tim7); + dac.set_triggering(true); + dac.enable(); + + debug!( + "TIM7 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}", + frequency::(), + FREQUENCY, + reload, + reload as u16, + data.len() + ); + + dac.write(ValueArray::Bit8(data), true).await; +} + +fn to_sine_wave(v: u8) -> u8 { + if v >= 128 { + // top half + let r = 3.14 * ((v - 128) as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } else { + // bottom half + let r = 3.14 + 3.14 * (v as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } +} + +fn calculate_array() -> [u8; N] { + let mut res = [0; N]; + let mut i = 0; + while i < N { + res[i] = to_sine_wave(i as u8); + i += 1; + } + res +} diff --git a/embassy/examples/stm32h7/src/bin/eth.rs b/embassy/examples/stm32h7/src/bin/eth.rs new file mode 100644 index 0000000..ec3f2c0 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/eth.rs @@ -0,0 +1,133 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, StackResources}; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_stm32::peripherals::ETH; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; +use embassy_time::Timer; +use embedded_io_async::Write; +use rand_core::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + RNG => rng::InterruptHandler; +}); + +type Device = Ethernet<'static, ETH, GenericSMI>; + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + info!("Hello World!"); + + // Generate random seed. + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + static PACKETS: StaticCell> = StaticCell::new(); + // warning: Not all STM32H7 devices have the exact same pins here + // for STM32H747XIH, replace p.PB13 for PG12 + let device = Ethernet::new( + PACKETS.init(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PA1, // ref_clk + p.PA2, // mdio + p.PC1, // eth_mdc + p.PA7, // CRS_DV: Carrier Sense + p.PC4, // RX_D0: Received Bit 0 + p.PC5, // RX_D1: Received Bit 1 + p.PG13, // TX_D0: Transmit Bit 0 + p.PB13, // TX_D1: Transmit Bit 1 + p.PG11, // TX_EN: Transmit Enable + GenericSMI::new(0), + mac_addr, + ); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + // Ensure DHCP configuration is up before trying connect + stack.wait_config_up().await; + + info!("Network task initialized"); + + // Then we can use it! + let mut rx_buffer = [0; 1024]; + let mut tx_buffer = [0; 1024]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + // You need to start a server on the host machine, for example: `nc -l 8000` + let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); + info!("connecting..."); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + info!("connect error: {:?}", e); + Timer::after_secs(1).await; + continue; + } + info!("connected!"); + loop { + let r = socket.write_all(b"Hello\n").await; + if let Err(e) = r { + info!("write error: {:?}", e); + break; + } + Timer::after_secs(1).await; + } + } +} diff --git a/embassy/examples/stm32h7/src/bin/eth_client.rs b/embassy/examples/stm32h7/src/bin/eth_client.rs new file mode 100644 index 0000000..a1558b0 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/eth_client.rs @@ -0,0 +1,132 @@ +#![no_std] +#![no_main] + +use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::StackResources; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_stm32::peripherals::ETH; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; +use embassy_time::Timer; +use embedded_io_async::Write; +use embedded_nal_async::TcpConnect; +use rand_core::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + RNG => rng::InterruptHandler; +}); + +type Device = Ethernet<'static, ETH, GenericSMI>; + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + info!("Hello World!"); + + // Generate random seed. + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + static PACKETS: StaticCell> = StaticCell::new(); + + let device = Ethernet::new( + PACKETS.init(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI::new(0), + mac_addr, + ); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + // Ensure DHCP configuration is up before trying connect + stack.wait_config_up().await; + + info!("Network task initialized"); + + let state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let client = TcpClient::new(stack, &state); + + loop { + // You need to start a server on the host machine, for example: `nc -l 8000` + let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(10, 42, 0, 1), 8000)); + + info!("connecting..."); + let r = client.connect(addr).await; + if let Err(e) = r { + info!("connect error: {:?}", e); + Timer::after_secs(1).await; + continue; + } + let mut connection = r.unwrap(); + info!("connected!"); + loop { + let r = connection.write_all(b"Hello\n").await; + if let Err(e) = r { + info!("write error: {:?}", e); + break; + } + Timer::after_secs(1).await; + } + } +} diff --git a/embassy/examples/stm32h7/src/bin/eth_client_mii.rs b/embassy/examples/stm32h7/src/bin/eth_client_mii.rs new file mode 100644 index 0000000..a352ef4 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/eth_client_mii.rs @@ -0,0 +1,138 @@ +#![no_std] +#![no_main] + +use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::StackResources; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_stm32::peripherals::ETH; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; +use embassy_time::Timer; +use embedded_io_async::Write; +use embedded_nal_async::TcpConnect; +use rand_core::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + RNG => rng::InterruptHandler; +}); + +type Device = Ethernet<'static, ETH, GenericSMI>; + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + info!("Hello World!"); + + // Generate random seed. + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + static PACKETS: StaticCell> = StaticCell::new(); + + let device = Ethernet::new_mii( + PACKETS.init(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PA1, + p.PC3, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PB0, + p.PB1, + p.PG13, + p.PG12, + p.PC2, + p.PE2, + p.PG11, + GenericSMI::new(1), + mac_addr, + ); + info!("Device created"); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + // Ensure DHCP configuration is up before trying connect + stack.wait_config_up().await; + + info!("Network task initialized"); + + let state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let client = TcpClient::new(stack, &state); + + loop { + // You need to start a server on the host machine, for example: `nc -l 8000` + let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 100, 1), 8000)); + + info!("connecting..."); + let r = client.connect(addr).await; + if let Err(e) = r { + info!("connect error: {:?}", e); + Timer::after_secs(1).await; + continue; + } + let mut connection = r.unwrap(); + info!("connected!"); + loop { + let r = connection.write_all(b"Hello\n").await; + if let Err(e) = r { + info!("write error: {:?}", e); + break; + } + Timer::after_secs(1).await; + } + } +} diff --git a/embassy/examples/stm32h7/src/bin/flash.rs b/embassy/examples/stm32h7/src/bin/flash.rs new file mode 100644 index 0000000..4f9f6bb --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/flash.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +use embassy_time::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 Flash!"); + + const ADDR: u32 = 0; // This is the offset into bank 2, the absolute address is 0x8_0000 + + // wait a bit before accessing the flash + Timer::after_millis(300).await; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank2_region; + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.blocking_erase(ADDR, ADDR + 128 * 1024)); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write( + ADDR, + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + )); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!( + &buf[..], + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + ); +} diff --git a/embassy/examples/stm32h7/src/bin/fmc.rs b/embassy/examples/stm32h7/src/bin/fmc.rs new file mode 100644 index 0000000..5e5e6cc --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/fmc.rs @@ -0,0 +1,216 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::fmc::Fmc; +use embassy_stm32::Config; +use embassy_time::{Delay, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // 100mhz + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut core_peri = cortex_m::Peripherals::take().unwrap(); + + // taken from stm32h7xx-hal + core_peri.SCB.enable_icache(); + // See Errata Sheet 2.2.1 + // core_peri.SCB.enable_dcache(&mut core_peri.CPUID); + core_peri.DWT.enable_cycle_counter(); + // ---------------------------------------------------------- + // Configure MPU for external SDRAM + // MPU config for SDRAM write-through + let sdram_size = 32 * 1024 * 1024; + + { + let mpu = core_peri.MPU; + let scb = &mut core_peri.SCB; + let size = sdram_size; + // Refer to ARM®v7-M Architecture Reference Manual ARM DDI 0403 + // Version E.b Section B3.5 + const MEMFAULTENA: u32 = 1 << 16; + + unsafe { + /* Make sure outstanding transfers are done */ + cortex_m::asm::dmb(); + + scb.shcsr.modify(|r| r & !MEMFAULTENA); + + /* Disable the MPU and clear the control register*/ + mpu.ctrl.write(0); + } + + const REGION_NUMBER0: u32 = 0x00; + const REGION_BASE_ADDRESS: u32 = 0xD000_0000; + + const REGION_FULL_ACCESS: u32 = 0x03; + const REGION_CACHEABLE: u32 = 0x01; + const REGION_WRITE_BACK: u32 = 0x01; + const REGION_ENABLE: u32 = 0x01; + + crate::assert_eq!(size & (size - 1), 0, "SDRAM memory region size must be a power of 2"); + crate::assert_eq!(size & 0x1F, 0, "SDRAM memory region size must be 32 bytes or more"); + fn log2minus1(sz: u32) -> u32 { + for i in 5..=31 { + if sz == (1 << i) { + return i - 1; + } + } + crate::panic!("Unknown SDRAM memory region size!"); + } + + //info!("SDRAM Memory Size 0x{:x}", log2minus1(size as u32)); + + // Configure region 0 + // + // Cacheable, outer and inner write-back, no write allocate. So + // reads are cached, but writes always write all the way to SDRAM + unsafe { + mpu.rnr.write(REGION_NUMBER0); + mpu.rbar.write(REGION_BASE_ADDRESS); + mpu.rasr.write( + (REGION_FULL_ACCESS << 24) + | (REGION_CACHEABLE << 17) + | (REGION_WRITE_BACK << 16) + | (log2minus1(size as u32) << 1) + | REGION_ENABLE, + ); + } + + const MPU_ENABLE: u32 = 0x01; + const MPU_DEFAULT_MMAP_FOR_PRIVILEGED: u32 = 0x04; + + // Enable + unsafe { + mpu.ctrl.modify(|r| r | MPU_DEFAULT_MMAP_FOR_PRIVILEGED | MPU_ENABLE); + + scb.shcsr.modify(|r| r | MEMFAULTENA); + + // Ensure MPU settings take effect + cortex_m::asm::dsb(); + cortex_m::asm::isb(); + } + } + + let mut sdram = Fmc::sdram_a12bits_d32bits_4banks_bank2( + p.FMC, + // A0-A11 + p.PF0, + p.PF1, + p.PF2, + p.PF3, + p.PF4, + p.PF5, + p.PF12, + p.PF13, + p.PF14, + p.PF15, + p.PG0, + p.PG1, + // BA0-BA1 + p.PG4, + p.PG5, + // D0-D31 + p.PD14, + p.PD15, + p.PD0, + p.PD1, + p.PE7, + p.PE8, + p.PE9, + p.PE10, + p.PE11, + p.PE12, + p.PE13, + p.PE14, + p.PE15, + p.PD8, + p.PD9, + p.PD10, + p.PH8, + p.PH9, + p.PH10, + p.PH11, + p.PH12, + p.PH13, + p.PH14, + p.PH15, + p.PI0, + p.PI1, + p.PI2, + p.PI3, + p.PI6, + p.PI7, + p.PI9, + p.PI10, + // NBL0 - NBL3 + p.PE0, + p.PE1, + p.PI4, + p.PI5, + p.PH7, // SDCKE1 + p.PG8, // SDCLK + p.PG15, // SDNCAS + p.PH6, // SDNE1 (!CS) + p.PF11, // SDRAS + p.PC0, // SDNWE, change to p.PH5 for EVAL boards + stm32_fmc::devices::is42s32800g_6::Is42s32800g {}, + ); + + let mut delay = Delay; + + let ram_slice = unsafe { + // Initialise controller and SDRAM + let ram_ptr: *mut u32 = sdram.init(&mut delay) as *mut _; + + // Convert raw pointer to slice + core::slice::from_raw_parts_mut(ram_ptr, sdram_size / core::mem::size_of::()) + }; + + // // ---------------------------------------------------------- + // // Use memory in SDRAM + info!("RAM contents before writing: {:x}", ram_slice[..10]); + + ram_slice[0] = 1; + ram_slice[1] = 2; + ram_slice[2] = 3; + ram_slice[3] = 4; + + info!("RAM contents after writing: {:x}", ram_slice[..10]); + + crate::assert_eq!(ram_slice[0], 1); + crate::assert_eq!(ram_slice[1], 2); + crate::assert_eq!(ram_slice[2], 3); + crate::assert_eq!(ram_slice[3], 4); + + info!("Assertions succeeded."); + + loop { + Timer::after_millis(1000).await; + } +} diff --git a/embassy/examples/stm32h7/src/bin/i2c.rs b/embassy/examples/stm32h7/src/bin/i2c.rs new file mode 100644 index 0000000..3bf39eb --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/i2c.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Error, I2c}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +bind_interrupts!(struct Irqs { + I2C2_EV => i2c::EventInterruptHandler; + I2C2_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello world!"); + let p = embassy_stm32::init(Default::default()); + + let mut i2c = I2c::new( + p.I2C2, + p.PB10, + p.PB11, + Irqs, + p.DMA1_CH4, + p.DMA1_CH5, + Hertz(100_000), + Default::default(), + ); + + let mut data = [0u8; 1]; + + match 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/embassy/examples/stm32h7/src/bin/i2c_shared.rs b/embassy/examples/stm32h7/src/bin/i2c_shared.rs new file mode 100644 index 0000000..136b91e --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/i2c_shared.rs @@ -0,0 +1,113 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use defmt::*; +use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{self, I2c}; +use embassy_stm32::mode::Async; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::NoopMutex; +use embassy_time::{Duration, Timer}; +use embedded_hal_1::i2c::I2c as _; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +const TMP117_ADDR: u8 = 0x48; +const TMP117_TEMP_RESULT: u8 = 0x00; + +const SHTC3_ADDR: u8 = 0x70; +const SHTC3_WAKEUP: [u8; 2] = [0x35, 0x17]; +const SHTC3_MEASURE_RH_FIRST: [u8; 2] = [0x5c, 0x24]; +const SHTC3_SLEEP: [u8; 2] = [0xb0, 0x98]; + +static I2C_BUS: StaticCell>>> = StaticCell::new(); + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::task] +async fn temperature(mut i2c: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) { + let mut data = [0u8; 2]; + + loop { + match i2c.write_read(TMP117_ADDR, &[TMP117_TEMP_RESULT], &mut data) { + Ok(()) => { + let temp = f32::from(i16::from_be_bytes(data)) * 7.8125 / 1000.0; + info!("Temperature {}", temp); + } + Err(_) => error!("I2C Error"), + } + + Timer::after(Duration::from_millis(1000)).await; + } +} + +#[embassy_executor::task] +async fn humidity(mut i2c: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) { + let mut data = [0u8; 6]; + + loop { + // Wakeup + match i2c.write(SHTC3_ADDR, &SHTC3_WAKEUP) { + Ok(()) => Timer::after(Duration::from_millis(20)).await, + Err(_) => error!("I2C Error"), + } + + // Measurement + match i2c.write(SHTC3_ADDR, &SHTC3_MEASURE_RH_FIRST) { + Ok(()) => Timer::after(Duration::from_millis(5)).await, + Err(_) => error!("I2C Error"), + } + + // Result + match i2c.read(SHTC3_ADDR, &mut data) { + Ok(()) => Timer::after(Duration::from_millis(5)).await, + Err(_) => error!("I2C Error"), + } + + // Sleep + match i2c.write(SHTC3_ADDR, &SHTC3_SLEEP) { + Ok(()) => { + let (bytes, _) = data.split_at(core::mem::size_of::()); + let rh = f32::from(u16::from_be_bytes(bytes.try_into().unwrap())) * 100.0 / 65536.0; + info!("Humidity: {}", rh); + } + Err(_) => error!("I2C Error"), + } + + Timer::after(Duration::from_millis(1000)).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + let i2c = I2c::new( + p.I2C1, + p.PB8, + p.PB9, + Irqs, + p.DMA1_CH4, + p.DMA1_CH5, + Hertz(100_000), + Default::default(), + ); + let i2c_bus = NoopMutex::new(RefCell::new(i2c)); + let i2c_bus = I2C_BUS.init(i2c_bus); + + // Device 1, using embedded-hal-async compatible driver for TMP117 + let i2c_dev1 = I2cDevice::new(i2c_bus); + spawner.spawn(temperature(i2c_dev1)).unwrap(); + + // Device 2, using embedded-hal-async compatible driver for SHTC3 + let i2c_dev2 = I2cDevice::new(i2c_bus); + spawner.spawn(humidity(i2c_dev2)).unwrap(); +} diff --git a/embassy/examples/stm32h7/src/bin/low_level_timer_api.rs b/embassy/examples/stm32h7/src/bin/low_level_timer_api.rs new file mode 100644 index 0000000..b796996 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/low_level_timer_api.rs @@ -0,0 +1,138 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{AfType, Flex, OutputType, Speed}; +use embassy_stm32::time::{khz, Hertz}; +use embassy_stm32::timer::low_level::{OutputCompareMode, Timer as LLTimer}; +use embassy_stm32::timer::{Channel, Channel1Pin, Channel2Pin, Channel3Pin, Channel4Pin, GeneralInstance32bit4Channel}; +use embassy_stm32::{into_ref, Config, Peripheral}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // 100mhz + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut pwm = SimplePwm32::new(p.TIM5, p.PA0, p.PA1, p.PA2, p.PA3, khz(10)); + let max = pwm.get_max_duty(); + pwm.enable(Channel::Ch1); + + info!("PWM initialized"); + info!("PWM max duty {}", max); + + loop { + pwm.set_duty(Channel::Ch1, 0); + Timer::after_millis(300).await; + pwm.set_duty(Channel::Ch1, max / 4); + Timer::after_millis(300).await; + pwm.set_duty(Channel::Ch1, max / 2); + Timer::after_millis(300).await; + pwm.set_duty(Channel::Ch1, max - 1); + Timer::after_millis(300).await; + } +} +pub struct SimplePwm32<'d, T: GeneralInstance32bit4Channel> { + tim: LLTimer<'d, T>, + _ch1: Flex<'d>, + _ch2: Flex<'d>, + _ch3: Flex<'d>, + _ch4: Flex<'d>, +} + +impl<'d, T: GeneralInstance32bit4Channel> SimplePwm32<'d, T> { + pub fn new( + tim: impl Peripheral

+ 'd, + ch1: impl Peripheral

> + 'd, + ch2: impl Peripheral

> + 'd, + ch3: impl Peripheral

> + 'd, + ch4: impl Peripheral

> + 'd, + freq: Hertz, + ) -> Self { + into_ref!(ch1, ch2, ch3, ch4); + + let af1 = ch1.af_num(); + let af2 = ch2.af_num(); + let af3 = ch3.af_num(); + let af4 = ch4.af_num(); + let mut ch1 = Flex::new(ch1); + let mut ch2 = Flex::new(ch2); + let mut ch3 = Flex::new(ch3); + let mut ch4 = Flex::new(ch4); + ch1.set_as_af_unchecked(af1, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + ch2.set_as_af_unchecked(af2, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + ch3.set_as_af_unchecked(af3, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + ch4.set_as_af_unchecked(af4, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + + let mut this = Self { + tim: LLTimer::new(tim), + _ch1: ch1, + _ch2: ch2, + _ch3: ch3, + _ch4: ch4, + }; + + this.set_frequency(freq); + this.tim.start(); + + let r = this.tim.regs_gp32(); + r.ccmr_output(0) + .modify(|w| w.set_ocm(0, OutputCompareMode::PwmMode1.into())); + r.ccmr_output(0) + .modify(|w| w.set_ocm(1, OutputCompareMode::PwmMode1.into())); + r.ccmr_output(1) + .modify(|w| w.set_ocm(0, OutputCompareMode::PwmMode1.into())); + r.ccmr_output(1) + .modify(|w| w.set_ocm(1, OutputCompareMode::PwmMode1.into())); + + this + } + + pub fn enable(&mut self, channel: Channel) { + self.tim.regs_gp32().ccer().modify(|w| w.set_cce(channel.index(), true)); + } + + pub fn disable(&mut self, channel: Channel) { + self.tim + .regs_gp32() + .ccer() + .modify(|w| w.set_cce(channel.index(), false)); + } + + pub fn set_frequency(&mut self, freq: Hertz) { + self.tim.set_frequency(freq); + } + + pub fn get_max_duty(&self) -> u32 { + self.tim.regs_gp32().arr().read() + } + + pub fn set_duty(&mut self, channel: Channel, duty: u32) { + defmt::assert!(duty < self.get_max_duty()); + self.tim.regs_gp32().ccr(channel.index()).write_value(duty) + } +} diff --git a/embassy/examples/stm32h7/src/bin/mco.rs b/embassy/examples/stm32h7/src/bin/mco.rs new file mode 100644 index 0000000..a6ee276 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/mco.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::rcc::{Mco, Mco1Source, McoPrescaler}; +use embassy_time::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.PB14, Level::High, Speed::Low); + + let _mco = Mco::new(p.MCO1, p.PA8, Mco1Source::HSI, McoPrescaler::DIV8); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(500).await; + + info!("low"); + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32h7/src/bin/multiprio.rs b/embassy/examples/stm32h7/src/bin/multiprio.rs new file mode 100644 index 0000000..b462088 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/multiprio.rs @@ -0,0 +1,150 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_stm32::interrupt; +use embassy_stm32::interrupt::{InterruptExt, Priority}; +use embassy_time::{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_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 + embassy_time::block_for(embassy_time::Duration::from_secs(1)); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!(" [med] done in {} ms", ms); + + Timer::after_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 + embassy_time::block_for(embassy_time::Duration::from_secs(2)); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!("[low] done in {} ms", ms); + + Timer::after_ticks(32983).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[interrupt] +unsafe fn UART4() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn UART5() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_stm32::init(Default::default()); + + // STM32s don’t have any interrupts exclusively for software use, but they can all be triggered by software as well as + // by the peripheral, so we can just use any free interrupt vectors which aren’t used by the rest of your application. + // In this case we’re using UART4 and UART5, but there’s nothing special about them. Any otherwise unused interrupt + // vector would work exactly the same. + + // High-priority executor: UART4, priority level 6 + interrupt::UART4.set_priority(Priority::P6); + let spawner = EXECUTOR_HIGH.start(interrupt::UART4); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: UART5, priority level 7 + interrupt::UART5.set_priority(Priority::P7); + let spawner = EXECUTOR_MED.start(interrupt::UART5); + 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/embassy/examples/stm32h7/src/bin/pwm.rs b/embassy/examples/stm32h7/src/bin/pwm.rs new file mode 100644 index 0000000..a1c53fc --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/pwm.rs @@ -0,0 +1,57 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::khz; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let ch1_pin = PwmPin::new_ch1(p.PA6, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM3, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); + + info!("PWM initialized"); + info!("PWM max duty {}", ch1.max_duty_cycle()); + + loop { + ch1.set_duty_cycle_fully_off(); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 4); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 2); + Timer::after_millis(300).await; + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32h7/src/bin/rng.rs b/embassy/examples/stm32h7/src/bin/rng.rs new file mode 100644 index 0000000..a9ef720 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/rng.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, peripherals, rng, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG, Irqs); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); +} diff --git a/embassy/examples/stm32h7/src/bin/rtc.rs b/embassy/examples/stm32h7/src/bin/rtc.rs new file mode 100644 index 0000000..0adb488 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/rtc.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] + +use chrono::{NaiveDate, NaiveDateTime}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rcc::LsConfig; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.ls = LsConfig::default_lse(); + + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + info!("Got RTC! {:?}", now.and_utc().timestamp()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + // In reality the delay would be much longer + Timer::after_millis(20000).await; + + let then: NaiveDateTime = rtc.now().unwrap().into(); + info!("Got RTC! {:?}", then.and_utc().timestamp()); +} diff --git a/embassy/examples/stm32h7/src/bin/sai.rs b/embassy/examples/stm32h7/src/bin/sai.rs new file mode 100644 index 0000000..95ffe25 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/sai.rs @@ -0,0 +1,187 @@ +//! Daisy Seed rev.7(with PCM3060 codec) +//! https://electro-smith.com/products/daisy-seed +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use grounded::uninit::GroundedArrayCell; +use hal::rcc::*; +use hal::sai::*; +use hal::time::Hertz; +use {defmt_rtt as _, embassy_stm32 as hal, panic_probe as _}; + +const BLOCK_LENGTH: usize = 32; // 32 samples +const HALF_DMA_BUFFER_LENGTH: usize = BLOCK_LENGTH * 2; // 2 channels +const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; // 2 half-blocks +const SAMPLE_RATE: u32 = 48000; + +//DMA buffer must be in special region. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions +#[link_section = ".sram1_bss"] +static mut TX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); +#[link_section = ".sram1_bss"] +static mut RX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = hal::Config::default(); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL200, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV5), + divr: Some(PllDiv::DIV2), + }); + config.rcc.pll3 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV6, + mul: PllMul::MUL295, + divp: Some(PllDiv::DIV16), + divq: Some(PllDiv::DIV4), + divr: Some(PllDiv::DIV32), + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.sai1sel = hal::pac::rcc::vals::Saisel::PLL3_P; + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.hse = Some(Hse { + freq: Hertz::mhz(16), + mode: HseMode::Oscillator, + }); + + let p = hal::init(config); + + let (sub_block_tx, sub_block_rx) = hal::sai::split_subblocks(p.SAI1); + let kernel_clock = hal::rcc::frequency::().0; + let mclk_div = mclk_div_from_u8((kernel_clock / (SAMPLE_RATE * 256)) as u8); + + let mut tx_config = hal::sai::Config::default(); + tx_config.mode = Mode::Master; + tx_config.tx_rx = TxRx::Transmitter; + tx_config.sync_output = true; + tx_config.clock_strobe = ClockStrobe::Falling; + tx_config.master_clock_divider = mclk_div; + tx_config.stereo_mono = StereoMono::Stereo; + tx_config.data_size = DataSize::Data24; + tx_config.bit_order = BitOrder::MsbFirst; + tx_config.frame_sync_polarity = FrameSyncPolarity::ActiveHigh; + tx_config.frame_sync_offset = FrameSyncOffset::OnFirstBit; + tx_config.frame_length = 64; + tx_config.frame_sync_active_level_length = embassy_stm32::sai::word::U7(32); + tx_config.fifo_threshold = FifoThreshold::Quarter; + + let mut rx_config = tx_config.clone(); + rx_config.mode = Mode::Slave; + rx_config.tx_rx = TxRx::Receiver; + rx_config.sync_input = SyncInput::Internal; + rx_config.clock_strobe = ClockStrobe::Rising; + rx_config.sync_output = false; + + let tx_buffer: &mut [u32] = unsafe { + let buf = &mut *core::ptr::addr_of_mut!(TX_BUFFER); + buf.initialize_all_copied(0); + let (ptr, len) = buf.get_ptr_len(); + core::slice::from_raw_parts_mut(ptr, len) + }; + + let mut sai_transmitter = Sai::new_asynchronous_with_mclk( + sub_block_tx, + p.PE5, + p.PE6, + p.PE4, + p.PE2, + p.DMA1_CH0, + tx_buffer, + tx_config, + ); + + let rx_buffer: &mut [u32] = unsafe { + let buf = &mut *core::ptr::addr_of_mut!(RX_BUFFER); + buf.initialize_all_copied(0); + let (ptr, len) = buf.get_ptr_len(); + core::slice::from_raw_parts_mut(ptr, len) + }; + + let mut sai_receiver = Sai::new_synchronous(sub_block_rx, p.PE3, p.DMA1_CH1, rx_buffer, rx_config); + + sai_receiver.start().unwrap(); + + let mut buf = [0u32; HALF_DMA_BUFFER_LENGTH]; + + loop { + sai_receiver.read(&mut buf).await.unwrap(); + sai_transmitter.write(&buf).await.unwrap(); + } +} + +const fn mclk_div_from_u8(v: u8) -> MasterClockDivider { + match v { + 1 => MasterClockDivider::Div1, + 2 => MasterClockDivider::Div2, + 3 => MasterClockDivider::Div3, + 4 => MasterClockDivider::Div4, + 5 => MasterClockDivider::Div5, + 6 => MasterClockDivider::Div6, + 7 => MasterClockDivider::Div7, + 8 => MasterClockDivider::Div8, + 9 => MasterClockDivider::Div9, + 10 => MasterClockDivider::Div10, + 11 => MasterClockDivider::Div11, + 12 => MasterClockDivider::Div12, + 13 => MasterClockDivider::Div13, + 14 => MasterClockDivider::Div14, + 15 => MasterClockDivider::Div15, + 16 => MasterClockDivider::Div16, + 17 => MasterClockDivider::Div17, + 18 => MasterClockDivider::Div18, + 19 => MasterClockDivider::Div19, + 20 => MasterClockDivider::Div20, + 21 => MasterClockDivider::Div21, + 22 => MasterClockDivider::Div22, + 23 => MasterClockDivider::Div23, + 24 => MasterClockDivider::Div24, + 25 => MasterClockDivider::Div25, + 26 => MasterClockDivider::Div26, + 27 => MasterClockDivider::Div27, + 28 => MasterClockDivider::Div28, + 29 => MasterClockDivider::Div29, + 30 => MasterClockDivider::Div30, + 31 => MasterClockDivider::Div31, + 32 => MasterClockDivider::Div32, + 33 => MasterClockDivider::Div33, + 34 => MasterClockDivider::Div34, + 35 => MasterClockDivider::Div35, + 36 => MasterClockDivider::Div36, + 37 => MasterClockDivider::Div37, + 38 => MasterClockDivider::Div38, + 39 => MasterClockDivider::Div39, + 40 => MasterClockDivider::Div40, + 41 => MasterClockDivider::Div41, + 42 => MasterClockDivider::Div42, + 43 => MasterClockDivider::Div43, + 44 => MasterClockDivider::Div44, + 45 => MasterClockDivider::Div45, + 46 => MasterClockDivider::Div46, + 47 => MasterClockDivider::Div47, + 48 => MasterClockDivider::Div48, + 49 => MasterClockDivider::Div49, + 50 => MasterClockDivider::Div50, + 51 => MasterClockDivider::Div51, + 52 => MasterClockDivider::Div52, + 53 => MasterClockDivider::Div53, + 54 => MasterClockDivider::Div54, + 55 => MasterClockDivider::Div55, + 56 => MasterClockDivider::Div56, + 57 => MasterClockDivider::Div57, + 58 => MasterClockDivider::Div58, + 59 => MasterClockDivider::Div59, + 60 => MasterClockDivider::Div60, + 61 => MasterClockDivider::Div61, + 62 => MasterClockDivider::Div62, + 63 => MasterClockDivider::Div63, + _ => panic!(), + } +} diff --git a/embassy/examples/stm32h7/src/bin/sdmmc.rs b/embassy/examples/stm32h7/src/bin/sdmmc.rs new file mode 100644 index 0000000..abe2d4b --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/sdmmc.rs @@ -0,0 +1,63 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::sdmmc::Sdmmc; +use embassy_stm32::time::mhz; +use embassy_stm32::{bind_interrupts, peripherals, sdmmc, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SDMMC1 => sdmmc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV4), // default clock chosen by SDMMCSEL. 200 Mhz + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let mut sdmmc = Sdmmc::new_4bit( + p.SDMMC1, + Irqs, + p.PC12, + p.PD2, + p.PC8, + p.PC9, + p.PC10, + p.PC11, + Default::default(), + ); + + // Should print 400kHz for initialization + info!("Configured clock: {}", sdmmc.clock().0); + + unwrap!(sdmmc.init_card(mhz(25)).await); + + let card = unwrap!(sdmmc.card()); + + info!("Card: {:#?}", Debug2Format(card)); + + loop {} +} diff --git a/embassy/examples/stm32h7/src/bin/signal.rs b/embassy/examples/stm32h7/src/bin/signal.rs new file mode 100644 index 0000000..b73360f --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/signal.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::signal::Signal; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +static SIGNAL: Signal = Signal::new(); + +#[embassy_executor::task] +async fn my_sending_task() { + let mut counter: u32 = 0; + + loop { + Timer::after_secs(1).await; + + SIGNAL.signal(counter); + + counter = counter.wrapping_add(1); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let _p = embassy_stm32::init(Default::default()); + unwrap!(spawner.spawn(my_sending_task())); + + loop { + let received_counter = SIGNAL.wait().await; + + info!("signalled, counter: {}", received_counter); + } +} diff --git a/embassy/examples/stm32h7/src/bin/spi.rs b/embassy/examples/stm32h7/src/bin/spi.rs new file mode 100644 index 0000000..ad4a8aa --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/spi.rs @@ -0,0 +1,71 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; +use core::str::from_utf8; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::mode::Blocking; +use embassy_stm32::time::mhz; +use embassy_stm32::{spi, Config}; +use heapless::String; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn main_task(mut spi: spi::Spi<'static, Blocking>) { + for n in 0u32.. { + let mut write: String<128> = String::new(); + core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); + unsafe { + let result = spi.blocking_transfer_in_place(write.as_bytes_mut()); + if let Err(_) = result { + defmt::panic!("crap"); + } + } + info!("read via spi: {}", from_utf8(write.as_bytes()).unwrap()); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // used by SPI3. 100Mhz. + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + + let mut spi_config = spi::Config::default(); + spi_config.frequency = mhz(1); + + let spi = spi::Spi::new_blocking(p.SPI3, p.PB3, p.PB5, p.PB4, spi_config); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task(spi))); + }) +} diff --git a/embassy/examples/stm32h7/src/bin/spi_bdma.rs b/embassy/examples/stm32h7/src/bin/spi_bdma.rs new file mode 100644 index 0000000..9166fe9 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/spi_bdma.rs @@ -0,0 +1,85 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; +use core::str::from_utf8; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::mode::Async; +use embassy_stm32::time::mhz; +use embassy_stm32::{spi, Config}; +use grounded::uninit::GroundedArrayCell; +use heapless::String; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +// Defined in memory.x +#[link_section = ".ram_d3"] +static mut RAM_D3: GroundedArrayCell = GroundedArrayCell::uninit(); + +#[embassy_executor::task] +async fn main_task(mut spi: spi::Spi<'static, Async>) { + let (read_buffer, write_buffer) = unsafe { + let ram = &mut *core::ptr::addr_of_mut!(RAM_D3); + ram.initialize_all_copied(0); + ( + ram.get_subslice_mut_unchecked(0, 128), + ram.get_subslice_mut_unchecked(128, 128), + ) + }; + + for n in 0u32.. { + let mut write: String<128> = String::new(); + core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); + let read_buffer = &mut read_buffer[..write.len()]; + let write_buffer = &mut write_buffer[..write.len()]; + // copy data to write_buffer which is located in D3 domain, accessable by BDMA + write_buffer.clone_from_slice(write.as_bytes()); + + spi.transfer(read_buffer, write_buffer).await.ok(); + info!("read via spi+dma: {}", from_utf8(read_buffer).unwrap()); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // used by SPI3. 100Mhz. + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + + let mut spi_config = spi::Config::default(); + spi_config.frequency = mhz(1); + + let spi = spi::Spi::new(p.SPI6, p.PA5, p.PA7, p.PA6, p.BDMA_CH1, p.BDMA_CH0, spi_config); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task(spi))); + }) +} diff --git a/embassy/examples/stm32h7/src/bin/spi_dma.rs b/embassy/examples/stm32h7/src/bin/spi_dma.rs new file mode 100644 index 0000000..731c7fe --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/spi_dma.rs @@ -0,0 +1,68 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; +use core::str::from_utf8; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::mode::Async; +use embassy_stm32::time::mhz; +use embassy_stm32::{spi, Config}; +use heapless::String; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn main_task(mut spi: spi::Spi<'static, Async>) { + for n in 0u32.. { + let mut write: String<128> = String::new(); + let mut read = [0; 128]; + core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); + // transfer will slice the &mut read down to &write's actual length. + spi.transfer(&mut read, write.as_bytes()).await.ok(); + info!("read via spi+dma: {}", from_utf8(&read).unwrap()); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // used by SPI3. 100Mhz. + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + + let mut spi_config = spi::Config::default(); + spi_config.frequency = mhz(1); + + let spi = spi::Spi::new(p.SPI3, p.PB3, p.PB5, p.PB4, p.DMA1_CH3, p.DMA1_CH4, spi_config); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task(spi))); + }) +} diff --git a/embassy/examples/stm32h7/src/bin/usart.rs b/embassy/examples/stm32h7/src/bin/usart.rs new file mode 100644 index 0000000..cc49c2f --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/usart.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::usart::{Config, Uart}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn main_task() { + let p = embassy_stm32::init(Default::default()); + + let config = Config::default(); + let mut usart = Uart::new_blocking(p.UART7, p.PF6, p.PF7, config).unwrap(); + + unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.blocking_read(&mut buf)); + unwrap!(usart.blocking_write(&buf)); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task())); + }) +} diff --git a/embassy/examples/stm32h7/src/bin/usart_dma.rs b/embassy/examples/stm32h7/src/bin/usart_dma.rs new file mode 100644 index 0000000..6f340d4 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/usart_dma.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use heapless::String; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + +#[embassy_executor::task] +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, Irqs, p.DMA1_CH0, p.DMA1_CH1, config).unwrap(); + + for n in 0u32.. { + let mut s: String<128> = String::new(); + core::write!(&mut s, "Hello DMA World {}!\r\n", n).unwrap(); + + usart.write(s.as_bytes()).await.ok(); + + info!("wrote DMA"); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task())); + }) +} diff --git a/embassy/examples/stm32h7/src/bin/usart_split.rs b/embassy/examples/stm32h7/src/bin/usart_split.rs new file mode 100644 index 0000000..2bb58be --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/usart_split.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::mode::Async; +use embassy_stm32::usart::{Config, Uart, UartRx}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::Channel; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + +static CHANNEL: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, p.DMA1_CH0, p.DMA1_CH1, config).unwrap(); + unwrap!(usart.blocking_write(b"Type 8 chars to echo!\r\n")); + + let (mut tx, rx) = usart.split(); + + unwrap!(spawner.spawn(reader(rx))); + + loop { + let buf = CHANNEL.receive().await; + info!("writing..."); + unwrap!(tx.write(&buf).await); + } +} + +#[embassy_executor::task] +async fn reader(mut rx: UartRx<'static, Async>) { + let mut buf = [0; 8]; + loop { + info!("reading..."); + unwrap!(rx.read(&mut buf).await); + CHANNEL.send(buf).await; + } +} diff --git a/embassy/examples/stm32h7/src/bin/usb_serial.rs b/embassy/examples/stm32h7/src/bin/usb_serial.rs new file mode 100644 index 0000000..65ae597 --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/usb_serial.rs @@ -0,0 +1,137 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.usbsel = mux::Usbsel::HSI48; + } + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32h7/src/bin/wdg.rs b/embassy/examples/stm32h7/src/bin/wdg.rs new file mode 100644 index 0000000..a4184aa --- /dev/null +++ b/embassy/examples/stm32h7/src/bin/wdg.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::wdg::IndependentWatchdog; +use embassy_time::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); + + wdg.unleash(); + + loop { + Timer::after_secs(1).await; + wdg.pet(); + } +} diff --git a/embassy/examples/stm32h723/.cargo/config.toml b/embassy/examples/stm32h723/.cargo/config.toml new file mode 100644 index 0000000..2e53663 --- /dev/null +++ b/embassy/examples/stm32h723/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H723ZGTx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32h723/Cargo.toml b/embassy/examples/stm32h723/Cargo.toml new file mode 100644 index 0000000..82f3cb9 --- /dev/null +++ b/embassy/examples/stm32h723/Cargo.toml @@ -0,0 +1,69 @@ +[package] +edition = "2021" +name = "embassy-stm32h723-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h723zg to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h723zg", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.2", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/embassy/examples/stm32h723/build.rs b/embassy/examples/stm32h723/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/stm32h723/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/embassy/examples/stm32h723/memory.x b/embassy/examples/stm32h723/memory.x new file mode 100644 index 0000000..aa4c005 --- /dev/null +++ b/embassy/examples/stm32h723/memory.x @@ -0,0 +1,106 @@ +MEMORY +{ + /* This file is intended for parts in the STM32H723 family. (RM0468) */ + /* - FLASH and RAM are mandatory memory sections. */ + /* - The sum of all non-FLASH sections must add to 564k total device RAM. */ + /* - The FLASH section size must match your device, see table below. */ + + /* FLASH */ + /* Select the appropriate FLASH size for your device. */ + /* - STM32H730xB 128K */ + /* - STM32H723xE/725xE 512K */ + /* - STM32H723xG/725xG/733xG/735xG 1M */ + FLASH1 : ORIGIN = 0x08000000, LENGTH = 1M + + /* Data TCM */ + /* - Two contiguous 64KB RAMs. */ + /* - Used for interrupt handlers, stacks and general RAM. */ + /* - Zero wait-states. */ + /* - The DTCM is taken as the origin of the base ram. (See below.) */ + /* This is also where the interrupt table and such will live, */ + /* which is required for deterministic performance. */ + DTCM : ORIGIN = 0x20000000, LENGTH = 128K + + /* Instruction TCM */ + /* - More memory can be assigned to ITCM. See AXI SRAM notes, below. */ + /* - Used for latency-critical interrupt handlers etc. */ + /* - Zero wait-states. */ + ITCM : ORIGIN = 0x00000000, LENGTH = 64K + 0K + + /* AXI SRAM */ + /* - AXISRAM is in D1 and accessible by all system masters except BDMA. */ + /* - Suitable for application data not stored in DTCM. */ + /* - Zero wait-states. */ + /* - The 192k of extra shared RAM is fully allotted to the AXI SRAM by default. */ + /* As a result: 64k (64k + 0k) for ITCM and 320k (128k + 192k) for AXI SRAM. */ + /* This can be re-configured via the TCM_AXI_SHARED[1,0] register when more */ + /* ITCM is required. */ + AXISRAM : ORIGIN = 0x24000000, LENGTH = 128K + 192K + + /* AHB SRAM */ + /* - SRAM1-2 are in D2 and accessible by all system masters except BDMA, LTDC */ + /* and SDMMC1. Suitable for use as DMA buffers. */ + /* - SRAM4 is in D3 and additionally accessible by the BDMA. Used for BDMA */ + /* buffers, for storing application data in lower-power modes. */ + /* - Zero wait-states. */ + SRAM1 : ORIGIN = 0x30000000, LENGTH = 16K + SRAM2 : ORIGIN = 0x30040000, LENGTH = 16K + SRAM4 : ORIGIN = 0x38000000, LENGTH = 16K + + /* Backup SRAM */ + /* Used to store data during low-power sleeps. */ + BSRAM : ORIGIN = 0x38800000, LENGTH = 4K +} + +/* +/* Assign the memory regions defined above for use. */ +/* + +/* Provide the mandatory FLASH and RAM definitions for cortex-m-rt's linker script. */ +REGION_ALIAS(FLASH, FLASH1); +REGION_ALIAS(RAM, DTCM); + +/* The location of the stack can be overridden using the `_stack_start` symbol. */ +/* - Set the stack location at the end of RAM, using all remaining space. */ +_stack_start = ORIGIN(RAM) + LENGTH(RAM); + +/* The location of the .text section can be overridden using the */ +/* `_stext` symbol. By default it will place after .vector_table. */ +/* _stext = ORIGIN(FLASH) + 0x40c; */ + +/* Define sections for placing symbols into the extra memory regions above. */ +/* This makes them accessible from code. */ +/* - ITCM, DTCM and AXISRAM connect to a 64-bit wide bus -> align to 8 bytes. */ +/* - All other memories connect to a 32-bit wide bus -> align to 4 bytes. */ +SECTIONS { + .itcm (NOLOAD) : ALIGN(8) { + *(.itcm .itcm.*); + . = ALIGN(8); + } > ITCM + + .axisram (NOLOAD) : ALIGN(8) { + *(.axisram .axisram.*); + . = ALIGN(8); + } > AXISRAM + + .sram1 (NOLOAD) : ALIGN(4) { + *(.sram1 .sram1.*); + . = ALIGN(4); + } > SRAM1 + + .sram2 (NOLOAD) : ALIGN(4) { + *(.sram2 .sram2.*); + . = ALIGN(4); + } > SRAM2 + + .sram4 (NOLOAD) : ALIGN(4) { + *(.sram4 .sram4.*); + . = ALIGN(4); + } > SRAM4 + + .bsram (NOLOAD) : ALIGN(4) { + *(.bsram .bsram.*); + . = ALIGN(4); + } > BSRAM + +}; diff --git a/embassy/examples/stm32h723/src/bin/spdifrx.rs b/embassy/examples/stm32h723/src/bin/spdifrx.rs new file mode 100644 index 0000000..69ef5cd --- /dev/null +++ b/embassy/examples/stm32h723/src/bin/spdifrx.rs @@ -0,0 +1,165 @@ +//! This example receives inputs on SPDIFRX and outputs on SAI4. +//! +//! Only very few controllers connect the SPDIFRX symbol clock to a SAI peripheral's clock input. +//! However, this is necessary for synchronizing the symbol rates and avoiding glitches. +#![no_std] +#![no_main] + +use defmt::{info, trace}; +use embassy_executor::Spawner; +use embassy_futures::select::{self, select, Either}; +use embassy_stm32::spdifrx::{self, Spdifrx}; +use embassy_stm32::{bind_interrupts, peripherals, sai}; +use grounded::uninit::GroundedArrayCell; +use hal::sai::*; +use {defmt_rtt as _, embassy_stm32 as hal, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPDIF_RX => spdifrx::GlobalInterruptHandler; +}); + +const CHANNEL_COUNT: usize = 2; +const BLOCK_LENGTH: usize = 64; +const HALF_DMA_BUFFER_LENGTH: usize = BLOCK_LENGTH * CHANNEL_COUNT; +const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; // 2 half-blocks + +// DMA buffers must be in special regions. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions +#[link_section = ".sram1"] +static mut SPDIFRX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); + +#[link_section = ".sram4"] +static mut SAI_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut peripheral_config = embassy_stm32::Config::default(); + { + use embassy_stm32::rcc::*; + peripheral_config.rcc.hsi = Some(HSIPrescaler::DIV1); + peripheral_config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV16, + mul: PllMul::MUL200, + divp: Some(PllDiv::DIV2), // 400 MHz + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV2), + }); + peripheral_config.rcc.sys = Sysclk::PLL1_P; + peripheral_config.rcc.ahb_pre = AHBPrescaler::DIV2; + peripheral_config.rcc.apb1_pre = APBPrescaler::DIV2; + peripheral_config.rcc.apb2_pre = APBPrescaler::DIV2; + peripheral_config.rcc.apb3_pre = APBPrescaler::DIV2; + peripheral_config.rcc.apb4_pre = APBPrescaler::DIV2; + + peripheral_config.rcc.mux.spdifrxsel = mux::Spdifrxsel::PLL1_Q; + } + let mut p = embassy_stm32::init(peripheral_config); + + info!("SPDIFRX to SAI4 bridge"); + + // Use SPDIFRX clock for SAI. + // This ensures equal rates of sample production and consumption. + let clk_source = embassy_stm32::pac::rcc::vals::Saiasel::_RESERVED_5; + embassy_stm32::pac::RCC.d3ccipr().modify(|w| { + w.set_sai4asel(clk_source); + }); + + let sai_buffer: &mut [u32] = unsafe { + SAI_BUFFER.initialize_all_copied(0); + let (ptr, len) = SAI_BUFFER.get_ptr_len(); + core::slice::from_raw_parts_mut(ptr, len) + }; + + let spdifrx_buffer: &mut [u32] = unsafe { + SPDIFRX_BUFFER.initialize_all_copied(0); + let (ptr, len) = SPDIFRX_BUFFER.get_ptr_len(); + core::slice::from_raw_parts_mut(ptr, len) + }; + + let mut sai_transmitter = new_sai_transmitter( + &mut p.SAI4, + &mut p.PD13, + &mut p.PC1, + &mut p.PD12, + &mut p.BDMA_CH0, + sai_buffer, + ); + let mut spdif_receiver = new_spdif_receiver(&mut p.SPDIFRX1, &mut p.PD7, &mut p.DMA2_CH7, spdifrx_buffer); + spdif_receiver.start(); + + let mut renew_sai = false; + loop { + let mut buf = [0u32; HALF_DMA_BUFFER_LENGTH]; + + if renew_sai { + renew_sai = false; + trace!("Renew SAI."); + drop(sai_transmitter); + sai_transmitter = new_sai_transmitter( + &mut p.SAI4, + &mut p.PD13, + &mut p.PC1, + &mut p.PD12, + &mut p.BDMA_CH0, + sai_buffer, + ); + } + + match select(spdif_receiver.read(&mut buf), sai_transmitter.wait_write_error()).await { + Either::First(spdif_read_result) => match spdif_read_result { + Ok(_) => (), + Err(spdifrx::Error::RingbufferError(_)) => { + trace!("SPDIFRX ringbuffer error. Renew."); + drop(spdif_receiver); + spdif_receiver = new_spdif_receiver(&mut p.SPDIFRX1, &mut p.PD7, &mut p.DMA2_CH7, spdifrx_buffer); + spdif_receiver.start(); + continue; + } + Err(spdifrx::Error::ChannelSyncError) => { + trace!("SPDIFRX channel sync (left/right assignment) error."); + continue; + } + }, + Either::Second(_) => { + renew_sai = true; + continue; + } + }; + + renew_sai = sai_transmitter.write(&buf).await.is_err(); + } +} + +/// Creates a new SPDIFRX instance for receiving sample data. +/// +/// Used (again) after dropping the SPDIFRX instance, in case of errors (e.g. source disconnect). +fn new_spdif_receiver<'d>( + spdifrx: &'d mut peripherals::SPDIFRX1, + input_pin: &'d mut peripherals::PD7, + dma: &'d mut peripherals::DMA2_CH7, + buf: &'d mut [u32], +) -> Spdifrx<'d, peripherals::SPDIFRX1> { + Spdifrx::new(spdifrx, Irqs, spdifrx::Config::default(), input_pin, dma, buf) +} + +/// Creates a new SAI4 instance for transmitting sample data. +/// +/// Used (again) after dropping the SAI4 instance, in case of errors (e.g. buffer overrun). +fn new_sai_transmitter<'d>( + sai: &'d mut peripherals::SAI4, + sck: &'d mut peripherals::PD13, + sd: &'d mut peripherals::PC1, + fs: &'d mut peripherals::PD12, + dma: &'d mut peripherals::BDMA_CH0, + buf: &'d mut [u32], +) -> Sai<'d, peripherals::SAI4, u32> { + let mut sai_config = hal::sai::Config::default(); + sai_config.slot_count = hal::sai::word::U4(CHANNEL_COUNT as u8); + sai_config.slot_enable = 0xFFFF; // All slots + sai_config.data_size = sai::DataSize::Data32; + sai_config.frame_length = (CHANNEL_COUNT * 32) as u8; + sai_config.master_clock_divider = hal::sai::MasterClockDivider::MasterClockDisabled; + + let (sub_block_tx, _) = hal::sai::split_subblocks(sai); + Sai::new_asynchronous(sub_block_tx, sck, sd, fs, dma, buf, sai_config) +} diff --git a/embassy/examples/stm32h735/.cargo/config.toml b/embassy/examples/stm32h735/.cargo/config.toml new file mode 100644 index 0000000..95536c6 --- /dev/null +++ b/embassy/examples/stm32h735/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H735IGKx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32h735/Cargo.toml b/embassy/examples/stm32h735/Cargo.toml new file mode 100644 index 0000000..a517b97 --- /dev/null +++ b/embassy/examples/stm32h735/Cargo.toml @@ -0,0 +1,61 @@ +[package] +edition = "2021" +name = "embassy-stm32h735-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h735ig", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +embedded-graphics = { version = "0.8.1" } +tinybmp = { version = "0.5" } + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/embassy/examples/stm32h735/build.rs b/embassy/examples/stm32h735/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/stm32h735/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/embassy/examples/stm32h735/memory.x b/embassy/examples/stm32h735/memory.x new file mode 100644 index 0000000..3a70d24 --- /dev/null +++ b/embassy/examples/stm32h735/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 1024K + RAM : ORIGIN = 0x24000000, LENGTH = 320K +} \ No newline at end of file diff --git a/embassy/examples/stm32h735/src/bin/ferris.bmp b/embassy/examples/stm32h735/src/bin/ferris.bmp new file mode 100644 index 0000000000000000000000000000000000000000..7a222ab84ddd9f2707ca8dcebad84b1b39c21818 GIT binary patch literal 6794 zcmb`L33OD|8OQ(JNnU0$OJ>O|nIvR^V8})iwgjaFsH`H2vdNBYA}T_u7=r{^1}IQL zRKN`(6x0McMUzp1q-luKI(W`lj>IPOV3HPUtHrL?CG_66&B`R_wD+9%?zjHl_wKvz zzB@B!%1$SQwlfZ>qS6d{RM4Xj)bvc74X|j%gBqCw_y)L=0Aw&fZDYnyTPsSvKa;4T z@qpl<@ILJNN$8_Wgep#pIF%W3oDN*P1&Fr-oDF^AozTRmfFz_+Uk2jTX^2mtRVDNX z)LBT-WCDrVAetPg)VWY44nVwSAh^UlsI_@iFNQX85R4WR`sfM}r!9d_TMSL&FmSpO zBUmpCLzH%8FA+E(3tL``ZUB_CLqx~1qsO$pt4Mb)^ayA z<{99u_d;))OKY5tM9WMh*rq|3JPT^ed^!xXkwkk<@&Y;>55Qtx3blO>wAMv1Sr;PS z{vh=B#V}e|z+!m}oMQ>Jwv|w)EQQIw5<17j&^VXD>{tb>Z4LA(tDsF=frQk@piNna z{K5jL(^f<0e1hKN25_nCk(mBC^t897JdK33C!y`T23lt|3{=i5Do3(o8`SBQu%~W> zE^QMWY1JqmT!O@mDkS&a413xR7&5lNp1u>#)VEqjul+4{QWbMK5@-o;no`<&o4kTqi3rFS)u=RZvx~v08>bDmuSuaALeGta} z`{2wzfW(~VV9z`ZL%%~XQ8}6Rj;z;V&OU;qoR^S7`#VOBq`Vp!2fPAH|D#Buhdui( z=yHz1nNtJ(fM39r`x>mYH{5m{=A4sAp+{2g30MZa4JSQ}d2b>)_Y@oh-h(OsEg14n z!<_p*Y_w0IeNz5AunhbFPI@HgorSTm9;TuOSPRa`>c{k1GvFBdIgG>Fkvyam)}dXPJ@)~m4*3$c z;eUi>=vT0pU4x_iI+BOqfN{hZu$Fy|`HL1{!QzE@qH;afZD_!bT|2R&@^kEdb`O?3 zc>|mGe}&_3oSPBYs&^!;CwOp0 z|76MaR3My?J5mE$D0dt`F5tRH%H`#76;X+e#ylrED+2MVm%VmH3*j!~`H_l;NQ204 z)a8tvGH>n1?a!0Dn%xa`HT$+LpEg4Bx&tGjI?T5yS}=DJeaPtwcc5HupeHK>vLtzf z29DmtX*oTJ`v48~s$~aR9>L&AMWn4zNg(T}cao$AxT9H4b0BIX(?c5}p*quo9>@%) zOV3#0<_IsqB88jY$f80UA=^R?R2!&dkW%Js5QkXi8pQ-Q3{HB|9TxSF^P7ZNJ3=f> znZZEWMf4;xMhF*@x#BC(L~Wy7M)IS!h;*J8rg3?sqftXhU)TLKRq%O*kr2761~Ggc zAwyi1>cNr8Jl~=f3b|8y)q+BrHH34y#uFl}8~kAwCvxRyKa31@xqezG=6Kg#2qbs? zOrX+}smUKOi&8Tot6Z*`WTT+^*xMm+?~nq$oYUoUt@x0sZSx7?_O*2h&3obC{c~q6 zoV8%?y?0L-{*uSbtp3(5D)q}k>UuJsCf+_m~>XFZ`yzK6NPB5AI)^lknUuTSZ||* z^$++7^Rfx&dw~koRU7uda^jS`#dFzzO)Q+9uXSDaw7O3o+q1Q*n%-SK%ig78nOmi? zzre9^*e+rD76Dt=#q<7FS9SFfx5q0?YS|yw`+V(Zk5pHSFwcLBlS#i(g+ITp7(~I}<#~6%e7!cn9p(>H zmoO@AoOCPxc4;NdjeMc~a=Cm9usRGBqISI_Qt5fLnB=#~miY=LBt=l_0DI(h196@5 zHFVQCY%z#+_nv)Yi%Usk{4!9Sp;@un7NJ^84=Y?**{@ z@FOzmCqq~#$;!Yi3JvO<_>b=H?!PjwR-BNplYddm6>2br&onXB=QdKO$R%~5I7Rb+ z{2xmBJLBexciln0qn1BY!=usXLmdm2xWg1sxZwYmQodx7XGHGRhrXefFX*e$7#=I> zcLuof!X72^2BkbfBey8k{e@Zv5wch@gk^cyM_m|)+AvSBdg-fwe;RKpJRG20`ud-b ztI`idA6=IEhQw8etSZ4!LH2n&yuOervDSbNZ)A@9Lk;4bM~zx#s(K0%6;nASv`A%{lCl(;i}$X*|Ar* zsC?lQ<&~MN58;w!{Dsv{Zg$k%7{)uflqJo4&<-G+ON6y3Ck7&_E6N`yq4kP)67^?R zRG?%GY>Jejh{mmm#fav+O~ENbH10T-+Y#%dr@&tmUZipbrwGxw^sC=Aog=-Nbf<}G zD&2);DL6%l#wBA1Gr#;Fj#6Btu$jdTn=CwSCI(tmuIL^t?7teL?!vs{mTqzY6eh*s zrn0enm^dTLRzw_ literal 0 HcmV?d00001 diff --git a/embassy/examples/stm32h735/src/bin/ltdc.rs b/embassy/examples/stm32h735/src/bin/ltdc.rs new file mode 100644 index 0000000..a36fdef --- /dev/null +++ b/embassy/examples/stm32h735/src/bin/ltdc.rs @@ -0,0 +1,467 @@ +#![no_std] +#![no_main] +#![macro_use] +#![allow(static_mut_refs)] + +/// This example demonstrates the LTDC lcd display peripheral and was tested to run on an stm32h735g-dk (embassy-stm32 feature "stm32h735ig" and probe-rs chip "STM32H735IGKx") +/// Even though the dev kit has 16MB of attached PSRAM this example uses the 320KB of internal AXIS RAM found on the mcu itself to make the example more standalone and portable. +/// For this reason a 256 color lookup table had to be used to keep the memory requirement down to an acceptable level. +/// The example bounces a ferris crab bitmap around the screen while blinking an led on another task +/// +use bouncy_box::BouncyBox; +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::{Duration, Timer}; +use embedded_graphics::draw_target::DrawTarget; +use embedded_graphics::geometry::{OriginDimensions, Point, Size}; +use embedded_graphics::image::Image; +use embedded_graphics::pixelcolor::raw::RawU24; +use embedded_graphics::pixelcolor::Rgb888; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::Rectangle; +use embedded_graphics::Pixel; +use heapless::{Entry, FnvIndexMap}; +use tinybmp::Bmp; +use {defmt_rtt as _, panic_probe as _}; + +const DISPLAY_WIDTH: usize = 480; +const DISPLAY_HEIGHT: usize = 272; +const MY_TASK_POOL_SIZE: usize = 2; + +// the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu +pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; +pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; + +bind_interrupts!(struct Irqs { + LTDC => ltdc::InterruptHandler; +}); + +const NUM_COLORS: usize = 256; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = rcc_setup::stm32h735g_init(); + + // blink the led on another task + let led = Output::new(p.PC3, Level::High, Speed::Low); + unwrap!(spawner.spawn(led_task(led))); + + // numbers from STMicroelectronics/STM32CubeH7 STM32H735G-DK C-based example + const RK043FN48H_HSYNC: u16 = 41; // Horizontal synchronization + const RK043FN48H_HBP: u16 = 13; // Horizontal back porch + const RK043FN48H_HFP: u16 = 32; // Horizontal front porch + const RK043FN48H_VSYNC: u16 = 10; // Vertical synchronization + const RK043FN48H_VBP: u16 = 2; // Vertical back porch + const RK043FN48H_VFP: u16 = 2; // Vertical front porch + + let ltdc_config = LtdcConfiguration { + active_width: DISPLAY_WIDTH as _, + active_height: DISPLAY_HEIGHT as _, + h_back_porch: RK043FN48H_HBP - 11, // -11 from MX_LTDC_Init + h_front_porch: RK043FN48H_HFP, + v_back_porch: RK043FN48H_VBP, + v_front_porch: RK043FN48H_VFP, + h_sync: RK043FN48H_HSYNC, + v_sync: RK043FN48H_VSYNC, + h_sync_polarity: PolarityActive::ActiveLow, + v_sync_polarity: PolarityActive::ActiveLow, + data_enable_polarity: PolarityActive::ActiveHigh, + pixel_clock_polarity: PolarityEdge::FallingEdge, + }; + + info!("init ltdc"); + let mut ltdc = Ltdc::new_with_pins( + p.LTDC, Irqs, p.PG7, p.PC6, p.PA4, p.PG14, p.PD0, p.PD6, p.PA8, p.PE12, p.PA3, p.PB8, p.PB9, p.PB1, p.PB0, + p.PA6, p.PE11, p.PH15, p.PH4, p.PC7, p.PD3, p.PE0, p.PH3, p.PH8, p.PH9, p.PH10, p.PH11, p.PE1, p.PE15, + ); + ltdc.init(<dc_config); + + // we only need to draw on one layer for this example (not to be confused with the double buffer) + info!("enable bottom layer"); + let layer_config = LtdcLayerConfig { + pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel + layer: LtdcLayer::Layer1, + window_x0: 0, + window_x1: DISPLAY_WIDTH as _, + window_y0: 0, + window_y1: DISPLAY_HEIGHT as _, + }; + + let ferris_bmp: Bmp = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap(); + let color_map = build_color_lookup_map(&ferris_bmp); + let clut = build_clut(&color_map); + + // enable the bottom layer with a 256 color lookup table + ltdc.init_layer(&layer_config, Some(&clut)); + + // Safety: the DoubleBuffer controls access to the statically allocated frame buffers + // and it is the only thing that mutates their content + let mut double_buffer = DoubleBuffer::new( + unsafe { FB1.as_mut() }, + unsafe { FB2.as_mut() }, + layer_config, + color_map, + ); + + // this allows us to perform some simple animation for every frame + let mut bouncy_box = BouncyBox::new( + ferris_bmp.bounding_box(), + Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)), + 2, + ); + + loop { + // cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen + double_buffer.clear(); + let position = bouncy_box.next_point(); + let ferris = Image::new(&ferris_bmp, position); + unwrap!(ferris.draw(&mut double_buffer)); + + // perform async dma data transfer to the lcd screen + unwrap!(double_buffer.swap(&mut ltdc).await); + } +} + +/// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work. +fn build_color_lookup_map(bmp: &Bmp) -> FnvIndexMap { + let mut color_map: FnvIndexMap = heapless::FnvIndexMap::new(); + let mut counter: u8 = 0; + + // add black to position 0 + color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap(); + counter += 1; + + for Pixel(_point, color) in bmp.pixels() { + let raw = color.into_storage(); + if let Entry::Vacant(v) = color_map.entry(raw) { + v.insert(counter).expect("more than 256 colors detected"); + counter += 1; + } + } + color_map +} + +/// builds the color look-up table from the color map provided +fn build_clut(color_map: &FnvIndexMap) -> [ltdc::RgbColor; NUM_COLORS] { + let mut clut = [ltdc::RgbColor::default(); NUM_COLORS]; + for (color, index) in color_map.iter() { + let color = Rgb888::from(RawU24::new(*color)); + clut[*index as usize] = ltdc::RgbColor { + red: color.r(), + green: color.g(), + blue: color.b(), + }; + } + + clut +} + +#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)] +async fn led_task(mut led: Output<'static>) { + let mut counter = 0; + loop { + info!("blink: {}", counter); + counter += 1; + + // on + led.set_low(); + Timer::after(Duration::from_millis(50)).await; + + // off + led.set_high(); + Timer::after(Duration::from_millis(450)).await; + } +} + +pub type TargetPixelType = u8; + +// A simple double buffer +pub struct DoubleBuffer { + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + is_buf0: bool, + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, +} + +impl DoubleBuffer { + pub fn new( + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, + ) -> Self { + Self { + buf0, + buf1, + is_buf0: true, + layer_config, + color_map, + } + } + + pub fn current(&mut self) -> (&FnvIndexMap, &mut [TargetPixelType]) { + if self.is_buf0 { + (&self.color_map, self.buf0) + } else { + (&self.color_map, self.buf1) + } + } + + pub async fn swap(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> { + let (_, buf) = self.current(); + let frame_buffer = buf.as_ptr(); + self.is_buf0 = !self.is_buf0; + ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await + } + + /// Clears the buffer + pub fn clear(&mut self) { + let (color_map, buf) = self.current(); + let black = Rgb888::new(0, 0, 0).into_storage(); + let color_index = color_map.get(&black).expect("no black found in the color map"); + + for a in buf.iter_mut() { + *a = *color_index; // solid black + } + } +} + +// Implement DrawTarget for +impl DrawTarget for DoubleBuffer { + type Color = Rgb888; + type Error = (); + + /// Draw a pixel + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + let size = self.size(); + let width = size.width as i32; + let height = size.height as i32; + let (color_map, buf) = self.current(); + + for pixel in pixels { + let Pixel(point, color) = pixel; + + if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height { + let index = point.y * width + point.x; + let raw_color = color.into_storage(); + + match color_map.get(&raw_color) { + Some(x) => { + buf[index as usize] = *x; + } + None => panic!("color not found in color map: {}", raw_color), + }; + } else { + // Ignore invalid points + } + } + + Ok(()) + } +} + +impl OriginDimensions for DoubleBuffer { + /// Return the size of the display + fn size(&self) -> Size { + Size::new( + (self.layer_config.window_x1 - self.layer_config.window_x0) as _, + (self.layer_config.window_y1 - self.layer_config.window_y0) as _, + ) + } +} + +mod rcc_setup { + + use embassy_stm32::rcc::{Hse, HseMode, *}; + use embassy_stm32::time::Hertz; + use embassy_stm32::{Config, Peripherals}; + + /// Sets up clocks for the stm32h735g mcu + /// change this if you plan to use a different microcontroller + pub fn stm32h735g_init() -> Peripherals { + /* + https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/STM32H735G-DK/Examples/GPIO/GPIO_EXTI/Src/main.c + @brief System Clock Configuration + The system Clock is configured as follow : + System Clock source = PLL (HSE) + SYSCLK(Hz) = 520000000 (CPU Clock) + HCLK(Hz) = 260000000 (AXI and AHBs Clock) + AHB Prescaler = 2 + D1 APB3 Prescaler = 2 (APB3 Clock 130MHz) + D2 APB1 Prescaler = 2 (APB1 Clock 130MHz) + D2 APB2 Prescaler = 2 (APB2 Clock 130MHz) + D3 APB4 Prescaler = 2 (APB4 Clock 130MHz) + HSE Frequency(Hz) = 25000000 + PLL_M = 5 + PLL_N = 104 + PLL_P = 1 + PLL_Q = 4 + PLL_R = 2 + VDD(V) = 3.3 + Flash Latency(WS) = 3 + */ + + // setup power and clocks for an stm32h735g-dk run from an external 25 Mhz external oscillator + let mut config = Config::default(); + config.rcc.hse = Some(Hse { + freq: Hertz::mhz(25), + mode: HseMode::Oscillator, + }); + config.rcc.hsi = None; + config.rcc.csi = false; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV5, // PLL_M + mul: PllMul::MUL104, // PLL_N + divp: Some(PllDiv::DIV1), + divq: Some(PllDiv::DIV4), + divr: Some(PllDiv::DIV2), + }); + // numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_ospi.c + // MX_OSPI_ClockConfig + config.rcc.pll2 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV5, // PLL_M + mul: PllMul::MUL80, // PLL_N + divp: Some(PllDiv::DIV5), + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV2), + }); + // numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_lcd.c + // MX_LTDC_ClockConfig + config.rcc.pll3 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV5, // PLL_M + mul: PllMul::MUL160, // PLL_N + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV83), + }); + config.rcc.voltage_scale = VoltageScale::Scale0; + config.rcc.supply_config = SupplyConfig::DirectSMPS; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV2; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.apb3_pre = APBPrescaler::DIV2; + config.rcc.apb4_pre = APBPrescaler::DIV2; + embassy_stm32::init(config) + } +} + +mod bouncy_box { + use embedded_graphics::geometry::Point; + use embedded_graphics::primitives::Rectangle; + + enum Direction { + DownLeft, + DownRight, + UpLeft, + UpRight, + } + + pub struct BouncyBox { + direction: Direction, + child_rect: Rectangle, + parent_rect: Rectangle, + current_point: Point, + move_by: usize, + } + + // This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box + impl BouncyBox { + pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self { + let center_box = parent_rect.center(); + let center_img = child_rect.center(); + let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2); + Self { + direction: Direction::DownRight, + child_rect, + parent_rect, + current_point, + move_by, + } + } + + pub fn next_point(&mut self) -> Point { + let direction = &self.direction; + let img_height = self.child_rect.size.height as i32; + let box_height = self.parent_rect.size.height as i32; + let img_width = self.child_rect.size.width as i32; + let box_width = self.parent_rect.size.width as i32; + let move_by = self.move_by as i32; + + match direction { + Direction::DownLeft => { + self.current_point.x -= move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } + } + Direction::DownRight => { + self.current_point.x += move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } + } + Direction::UpLeft => { + self.current_point.x -= move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } + } + Direction::UpRight => { + self.current_point.x += move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } + } + } + + self.current_point + } + } +} diff --git a/embassy/examples/stm32h755cm4/.cargo/config.toml b/embassy/examples/stm32h755cm4/.cargo/config.toml new file mode 100644 index 0000000..193e6bb --- /dev/null +++ b/embassy/examples/stm32h755cm4/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H755ZITx --catch-hardfault --always-print-stacktrace' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32h755cm4/Cargo.toml b/embassy/examples/stm32h755cm4/Cargo.toml new file mode 100644 index 0000000..1d4d3eb --- /dev/null +++ b/embassy/examples/stm32h755cm4/Cargo.toml @@ -0,0 +1,75 @@ +[package] +edition = "2021" +name = "embassy-stm32h755cm4-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h755zi-cm4 to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm4", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/embassy/examples/stm32h755cm4/build.rs b/embassy/examples/stm32h755cm4/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/stm32h755cm4/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/embassy/examples/stm32h755cm4/memory.x b/embassy/examples/stm32h755cm4/memory.x new file mode 100644 index 0000000..7d60354 --- /dev/null +++ b/embassy/examples/stm32h755cm4/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08100000, LENGTH = 1024K /* BANK_2 */ + RAM : ORIGIN = 0x10000000, LENGTH = 128K /* SRAM1 */ + RAM_D3 : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 */ +} + +SECTIONS +{ + .ram_d3 : + { + *(.ram_d3.shared_data) + *(.ram_d3) + } > RAM_D3 +} \ No newline at end of file diff --git a/embassy/examples/stm32h755cm4/src/bin/blinky.rs b/embassy/examples/stm32h755cm4/src/bin/blinky.rs new file mode 100644 index 0000000..b5c5478 --- /dev/null +++ b/embassy/examples/stm32h755cm4/src/bin/blinky.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::SharedData; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".ram_d3.shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init_secondary(&SHARED_DATA); + info!("Hello World!"); + + let mut led = Output::new(p.PE1, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(250).await; + + info!("low"); + led.set_low(); + Timer::after_millis(250).await; + } +} diff --git a/embassy/examples/stm32h755cm7/.cargo/config.toml b/embassy/examples/stm32h755cm7/.cargo/config.toml new file mode 100644 index 0000000..193e6bb --- /dev/null +++ b/embassy/examples/stm32h755cm7/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H755ZITx --catch-hardfault --always-print-stacktrace' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32h755cm7/Cargo.toml b/embassy/examples/stm32h755cm7/Cargo.toml new file mode 100644 index 0000000..76c88c8 --- /dev/null +++ b/embassy/examples/stm32h755cm7/Cargo.toml @@ -0,0 +1,75 @@ +[package] +edition = "2021" +name = "embassy-stm32h755cm7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h743bi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm7", "time-driver-tim3", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/embassy/examples/stm32h755cm7/build.rs b/embassy/examples/stm32h755cm7/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/stm32h755cm7/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/embassy/examples/stm32h755cm7/memory.x b/embassy/examples/stm32h755cm7/memory.x new file mode 100644 index 0000000..ef88479 --- /dev/null +++ b/embassy/examples/stm32h755cm7/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 1024K /* BANK_1 */ + RAM : ORIGIN = 0x24000000, LENGTH = 512K /* AXIRAM */ + RAM_D3 : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 */ +} + +SECTIONS +{ + .ram_d3 : + { + *(.ram_d3.shared_data) + *(.ram_d3) + } > RAM_D3 +} \ No newline at end of file diff --git a/embassy/examples/stm32h755cm7/src/bin/blinky.rs b/embassy/examples/stm32h755cm7/src/bin/blinky.rs new file mode 100644 index 0000000..94d2226 --- /dev/null +++ b/embassy/examples/stm32h755cm7/src/bin/blinky.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::SharedData; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".ram_d3.shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // 100mhz + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.supply_config = SupplyConfig::DirectSMPS; + } + let p = embassy_stm32::init_primary(config, &SHARED_DATA); + info!("Hello World!"); + + let mut led = Output::new(p.PB14, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(500).await; + + info!("low"); + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32h7b0/.cargo/config.toml b/embassy/examples/stm32h7b0/.cargo/config.toml new file mode 100644 index 0000000..870849a --- /dev/null +++ b/embassy/examples/stm32h7b0/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H7B0VBTx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32h7b0/Cargo.toml b/embassy/examples/stm32h7b0/Cargo.toml new file mode 100644 index 0000000..aba398f --- /dev/null +++ b/embassy/examples/stm32h7b0/Cargo.toml @@ -0,0 +1,74 @@ +[package] +edition = "2021" +name = "embassy-stm32h7b0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7b0vb", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/embassy/examples/stm32h7b0/build.rs b/embassy/examples/stm32h7b0/build.rs new file mode 100644 index 0000000..30691aa --- /dev/null +++ b/embassy/examples/stm32h7b0/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/embassy/examples/stm32h7b0/memory.x b/embassy/examples/stm32h7b0/memory.x new file mode 100644 index 0000000..6eb1bb7 --- /dev/null +++ b/embassy/examples/stm32h7b0/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 128K /* BANK_1 */ + RAM : ORIGIN = 0x24000000, LENGTH = 512K /* SRAM */ +} \ No newline at end of file diff --git a/embassy/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs b/embassy/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs new file mode 100644 index 0000000..dffb740 --- /dev/null +++ b/embassy/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs @@ -0,0 +1,433 @@ +#![no_main] +#![no_std] + +// Tested on weact stm32h7b0 board + w25q64 spi flash + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::ospi::{ + AddressSize, ChipSelectHighTime, DummyCycles, FIFOThresholdLevel, Instance, MemorySize, MemoryType, Ospi, + OspiWidth, TransferConfig, WrapSize, +}; +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // RCC config + let mut config = Config::default(); + info!("START"); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + // Needed for USB + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); + // External oscillator 25MHZ + config.rcc.hse = Some(Hse { + freq: Hertz(25_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV5, + mul: PllMul::MUL112, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV2), + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV2; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.apb3_pre = APBPrescaler::DIV2; + config.rcc.apb4_pre = APBPrescaler::DIV2; + config.rcc.voltage_scale = VoltageScale::Scale0; + } + + // Initialize peripherals + let p = embassy_stm32::init(config); + + let qspi_config = embassy_stm32::ospi::Config { + fifo_threshold: FIFOThresholdLevel::_16Bytes, + memory_type: MemoryType::Micron, + device_size: MemorySize::_8MiB, + chip_select_high_time: ChipSelectHighTime::_1Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 4, + sample_shifting: true, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, + delay_block_bypass: true, + max_transfer: 0, + refresh: 0, + }; + let ospi = embassy_stm32::ospi::Ospi::new_blocking_quadspi( + p.OCTOSPI1, + p.PB2, + p.PD11, + p.PD12, + p.PE2, + p.PD13, + p.PB6, + qspi_config, + ); + + let mut flash = FlashMemory::new(ospi).await; + + let flash_id = flash.read_id(); + info!("FLASH ID: {=[u8]:x}", flash_id); + let mut wr_buf = [0u8; 8]; + for i in 0..8 { + wr_buf[i] = i as u8; + } + let mut rd_buf = [0u8; 8]; + flash.erase_sector(0).await; + flash.write_memory(0, &wr_buf, true).await; + flash.read_memory(0, &mut rd_buf, true); + info!("WRITE BUF: {=[u8]:#X}", wr_buf); + info!("READ BUF: {=[u8]:#X}", rd_buf); + flash.enable_mm().await; + info!("Enabled memory mapped mode"); + + let first_u32 = unsafe { *(0x90000000 as *const u32) }; + assert_eq!(first_u32, 0x03020100); + + let second_u32 = unsafe { *(0x90000004 as *const u32) }; + assert_eq!(second_u32, 0x07060504); + flash.disable_mm().await; + + info!("DONE"); + // Output pin PE3 + let mut led = Output::new(p.PE3, Level::Low, Speed::Low); + + loop { + led.toggle(); + Timer::after_millis(1000).await; + } +} + +const MEMORY_PAGE_SIZE: usize = 8; + +const CMD_QUAD_READ: u8 = 0x6B; + +const CMD_QUAD_WRITE_PG: u8 = 0x32; + +const CMD_READ_ID: u8 = 0x9F; + +const CMD_ENABLE_RESET: u8 = 0x66; +const CMD_RESET: u8 = 0x99; + +const CMD_WRITE_ENABLE: u8 = 0x06; + +const CMD_CHIP_ERASE: u8 = 0xC7; +const CMD_SECTOR_ERASE: u8 = 0x20; +const CMD_BLOCK_ERASE_32K: u8 = 0x52; +const CMD_BLOCK_ERASE_64K: u8 = 0xD8; + +const CMD_READ_SR: u8 = 0x05; +const CMD_READ_CR: u8 = 0x35; + +const CMD_WRITE_SR: u8 = 0x01; +const CMD_WRITE_CR: u8 = 0x31; + +/// Implementation of access to flash chip. +/// Chip commands are hardcoded as it depends on used chip. +/// This implementation is using chip GD25Q64C from Giga Device +pub struct FlashMemory { + ospi: Ospi<'static, I, Blocking>, +} + +impl FlashMemory { + pub async fn new(ospi: Ospi<'static, I, Blocking>) -> Self { + let mut memory = Self { ospi }; + + memory.reset_memory().await; + memory.enable_quad(); + memory + } + + async fn qpi_mode(&mut self) { + // Enter qpi mode + self.exec_command(0x38).await; + + // Set read param + let transaction = TransferConfig { + iwidth: OspiWidth::QUAD, + dwidth: OspiWidth::QUAD, + instruction: Some(0xC0), + ..Default::default() + }; + self.enable_write().await; + self.ospi.blocking_write(&[0x30_u8], transaction).unwrap(); + self.wait_write_finish(); + } + + pub async fn disable_mm(&mut self) { + self.ospi.disable_memory_mapped_mode(); + } + + pub async fn enable_mm(&mut self) { + self.qpi_mode().await; + + let read_config = TransferConfig { + iwidth: OspiWidth::QUAD, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::QUAD, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(0x0B), // Fast read in QPI mode + dummy: DummyCycles::_8, + ..Default::default() + }; + + let write_config = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(0x32), // Write config + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.enable_memory_mapped_mode(read_config, write_config).unwrap(); + } + + fn enable_quad(&mut self) { + let cr = self.read_cr(); + // info!("Read cr: {:x}", cr); + self.write_cr(cr | 0x02); + // info!("Read cr after writing: {:x}", cr); + } + + pub fn disable_quad(&mut self) { + let cr = self.read_cr(); + self.write_cr(cr & (!(0x02))); + } + + async fn exec_command_4(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::QUAD, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_command(&transaction).unwrap(); + } + + async fn exec_command(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + // info!("Excuting command: {:x}", transaction.instruction); + self.ospi.blocking_command(&transaction).unwrap(); + } + + pub async fn reset_memory(&mut self) { + self.exec_command_4(CMD_ENABLE_RESET).await; + self.exec_command_4(CMD_RESET).await; + self.exec_command(CMD_ENABLE_RESET).await; + self.exec_command(CMD_RESET).await; + self.wait_write_finish(); + } + + pub async fn enable_write(&mut self) { + self.exec_command(CMD_WRITE_ENABLE).await; + } + + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::SING, + instruction: Some(CMD_READ_ID as u32), + ..Default::default() + }; + // info!("Reading id: 0x{:X}", transaction.instruction); + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_id_4(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_READ_ID as u32), + ..Default::default() + }; + info!("Reading id: 0x{:X}", transaction.instruction); + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_memory(&mut self, addr: u32, buffer: &mut [u8], use_dma: bool) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_QUAD_READ as u32), + address: Some(addr), + dummy: DummyCycles::_8, + ..Default::default() + }; + if use_dma { + self.ospi.blocking_read(buffer, transaction).unwrap(); + } else { + self.ospi.blocking_read(buffer, transaction).unwrap(); + } + } + + fn wait_write_finish(&mut self) { + while (self.read_sr() & 0x01) != 0 {} + } + + async fn perform_erase(&mut self, addr: u32, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + self.ospi.blocking_command(&transaction).unwrap(); + self.wait_write_finish(); + } + + pub async fn erase_sector(&mut self, addr: u32) { + self.perform_erase(addr, CMD_SECTOR_ERASE).await; + } + + pub async fn erase_block_32k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_32K).await; + } + + pub async fn erase_block_64k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_64K).await; + } + + pub async fn erase_chip(&mut self) { + self.exec_command(CMD_CHIP_ERASE).await; + } + + async fn write_page(&mut self, addr: u32, buffer: &[u8], len: usize, use_dma: bool) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + adwidth: OspiWidth::SING, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_QUAD_WRITE_PG as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + if use_dma { + self.ospi.blocking_write(buffer, transaction).unwrap(); + } else { + self.ospi.blocking_write(buffer, transaction).unwrap(); + } + self.wait_write_finish(); + } + + pub async fn write_memory(&mut self, addr: u32, buffer: &[u8], use_dma: bool) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page(place, chunk, chunk_size, use_dma).await; + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + fn read_register(&mut self, cmd: u8) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::SING, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read w25q64 register: 0x{:x}", buffer[0]); + buffer[0] + } + + fn write_register(&mut self, cmd: u8, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + instruction: Some(cmd as u32), + adsize: AddressSize::_24bit, + adwidth: OspiWidth::NONE, + dwidth: OspiWidth::SING, + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_write(&buffer, transaction).unwrap(); + } + + pub fn read_sr(&mut self) -> u8 { + self.read_register(CMD_READ_SR) + } + + pub fn read_cr(&mut self) -> u8 { + self.read_register(CMD_READ_CR) + } + + pub fn write_sr(&mut self, value: u8) { + self.write_register(CMD_WRITE_SR, value); + } + + pub fn write_cr(&mut self, value: u8) { + self.write_register(CMD_WRITE_CR, value); + } +} diff --git a/embassy/examples/stm32h7rs/.cargo/config.toml b/embassy/examples/stm32h7rs/.cargo/config.toml new file mode 100644 index 0000000..44dbda9 --- /dev/null +++ b/embassy/examples/stm32h7rs/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H7S3L8Hx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32h7rs/Cargo.toml b/embassy/examples/stm32h7rs/Cargo.toml new file mode 100644 index 0000000..1d957e2 --- /dev/null +++ b/embassy/examples/stm32h7rs/Cargo.toml @@ -0,0 +1,73 @@ +[package] +edition = "2021" +name = "embassy-stm32h7rs-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h743bi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7s3l8", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/embassy/examples/stm32h7rs/build.rs b/embassy/examples/stm32h7rs/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32h7rs/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/embassy/examples/stm32h7rs/src/bin/blinky.rs b/embassy/examples/stm32h7rs/src/bin/blinky.rs new file mode 100644 index 0000000..137c585 --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/blinky.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV3, + mul: PllMul::MUL150, + divp: Some(PllDiv::DIV2), + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 600 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 300 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.apb5_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.voltage_scale = VoltageScale::HIGH; + } + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let mut led = Output::new(p.PD10, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(500).await; + + info!("low"); + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32h7rs/src/bin/button_exti.rs b/embassy/examples/stm32h7rs/src/bin/button_exti.rs new file mode 100644 index 0000000..34a08bb --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); + + 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/embassy/examples/stm32h7rs/src/bin/can.rs b/embassy/examples/stm32h7rs/src/bin/can.rs new file mode 100644 index 0000000..0af11ef --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/can.rs @@ -0,0 +1,98 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::peripherals::*; +use embassy_stm32::{bind_interrupts, can, rcc, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => can::IT0InterruptHandler; + FDCAN1_IT1 => can::IT1InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: embassy_stm32::time::Hertz(25_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.mux.fdcansel = rcc::mux::Fdcansel::HSE; + + let peripherals = embassy_stm32::init(config); + + let mut can = can::CanConfigurator::new(peripherals.FDCAN1, peripherals.PA11, peripherals.PA12, Irqs); + + // 250k bps + can.set_bitrate(250_000); + + //let mut can = can.into_internal_loopback_mode(); + let mut can = can.into_normal_mode(); + + info!("CAN Configured"); + + let mut i = 0; + let mut last_read_ts = embassy_time::Instant::now(); + + loop { + let frame = can::frame::Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = can.write(&frame).await; + + match can.read().await { + Ok(envelope) => { + let (rx_frame, ts) = envelope.parts(); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {:x} {:x} {:x} {:x} --- NEW {}", + rx_frame.data()[0], + rx_frame.data()[1], + rx_frame.data()[2], + rx_frame.data()[3], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + if i > 3 { + break; + } + } + + let (mut tx, mut rx, _props) = can.split(); + // With split + loop { + let frame = can::frame::Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = tx.write(&frame).await; + + match rx.read().await { + Ok(envelope) => { + let (rx_frame, ts) = envelope.parts(); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {:x} {:x} {:x} {:x} --- NEW {}", + rx_frame.data()[0], + rx_frame.data()[1], + rx_frame.data()[2], + rx_frame.data()[3], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i = i.wrapping_add(1); + } +} diff --git a/embassy/examples/stm32h7rs/src/bin/i2c.rs b/embassy/examples/stm32h7rs/src/bin/i2c.rs new file mode 100644 index 0000000..31e83cb --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/i2c.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Error, I2c}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +bind_interrupts!(struct Irqs { + I2C2_EV => i2c::EventInterruptHandler; + I2C2_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello world!"); + let p = embassy_stm32::init(Default::default()); + + let mut i2c = I2c::new( + p.I2C2, + p.PB10, + p.PB11, + Irqs, + p.GPDMA1_CH4, + p.GPDMA1_CH5, + Hertz(100_000), + Default::default(), + ); + + let mut data = [0u8; 1]; + + match 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/embassy/examples/stm32h7rs/src/bin/mco.rs b/embassy/examples/stm32h7rs/src/bin/mco.rs new file mode 100644 index 0000000..a6ee276 --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/mco.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::rcc::{Mco, Mco1Source, McoPrescaler}; +use embassy_time::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.PB14, Level::High, Speed::Low); + + let _mco = Mco::new(p.MCO1, p.PA8, Mco1Source::HSI, McoPrescaler::DIV8); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(500).await; + + info!("low"); + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32h7rs/src/bin/multiprio.rs b/embassy/examples/stm32h7rs/src/bin/multiprio.rs new file mode 100644 index 0000000..b462088 --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/multiprio.rs @@ -0,0 +1,150 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_stm32::interrupt; +use embassy_stm32::interrupt::{InterruptExt, Priority}; +use embassy_time::{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_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 + embassy_time::block_for(embassy_time::Duration::from_secs(1)); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!(" [med] done in {} ms", ms); + + Timer::after_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 + embassy_time::block_for(embassy_time::Duration::from_secs(2)); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!("[low] done in {} ms", ms); + + Timer::after_ticks(32983).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[interrupt] +unsafe fn UART4() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn UART5() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_stm32::init(Default::default()); + + // STM32s don’t have any interrupts exclusively for software use, but they can all be triggered by software as well as + // by the peripheral, so we can just use any free interrupt vectors which aren’t used by the rest of your application. + // In this case we’re using UART4 and UART5, but there’s nothing special about them. Any otherwise unused interrupt + // vector would work exactly the same. + + // High-priority executor: UART4, priority level 6 + interrupt::UART4.set_priority(Priority::P6); + let spawner = EXECUTOR_HIGH.start(interrupt::UART4); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: UART5, priority level 7 + interrupt::UART5.set_priority(Priority::P7); + let spawner = EXECUTOR_MED.start(interrupt::UART5); + 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/embassy/examples/stm32h7rs/src/bin/rng.rs b/embassy/examples/stm32h7rs/src/bin/rng.rs new file mode 100644 index 0000000..a9ef720 --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/rng.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, peripherals, rng, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG, Irqs); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); +} diff --git a/embassy/examples/stm32h7rs/src/bin/rtc.rs b/embassy/examples/stm32h7rs/src/bin/rtc.rs new file mode 100644 index 0000000..0adb488 --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/rtc.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] + +use chrono::{NaiveDate, NaiveDateTime}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rcc::LsConfig; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.ls = LsConfig::default_lse(); + + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + info!("Got RTC! {:?}", now.and_utc().timestamp()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + // In reality the delay would be much longer + Timer::after_millis(20000).await; + + let then: NaiveDateTime = rtc.now().unwrap().into(); + info!("Got RTC! {:?}", then.and_utc().timestamp()); +} diff --git a/embassy/examples/stm32h7rs/src/bin/signal.rs b/embassy/examples/stm32h7rs/src/bin/signal.rs new file mode 100644 index 0000000..b73360f --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/signal.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::signal::Signal; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +static SIGNAL: Signal = Signal::new(); + +#[embassy_executor::task] +async fn my_sending_task() { + let mut counter: u32 = 0; + + loop { + Timer::after_secs(1).await; + + SIGNAL.signal(counter); + + counter = counter.wrapping_add(1); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let _p = embassy_stm32::init(Default::default()); + unwrap!(spawner.spawn(my_sending_task())); + + loop { + let received_counter = SIGNAL.wait().await; + + info!("signalled, counter: {}", received_counter); + } +} diff --git a/embassy/examples/stm32h7rs/src/bin/spi.rs b/embassy/examples/stm32h7rs/src/bin/spi.rs new file mode 100644 index 0000000..8d6ccc5 --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/spi.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; +use core::str::from_utf8; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::mode::Blocking; +use embassy_stm32::spi; +use embassy_stm32::time::mhz; +use heapless::String; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn main_task(mut spi: spi::Spi<'static, Blocking>) { + for n in 0u32.. { + let mut write: String<128> = String::new(); + core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); + unsafe { + let result = spi.blocking_transfer_in_place(write.as_bytes_mut()); + if let Err(_) = result { + defmt::panic!("crap"); + } + } + info!("read via spi: {}", from_utf8(write.as_bytes()).unwrap()); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + let p = embassy_stm32::init(Default::default()); + + let mut spi_config = spi::Config::default(); + spi_config.frequency = mhz(1); + + let spi = spi::Spi::new_blocking(p.SPI3, p.PB3, p.PB5, p.PB4, spi_config); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task(spi))); + }) +} diff --git a/embassy/examples/stm32h7rs/src/bin/spi_dma.rs b/embassy/examples/stm32h7rs/src/bin/spi_dma.rs new file mode 100644 index 0000000..cb30535 --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/spi_dma.rs @@ -0,0 +1,46 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; +use core::str::from_utf8; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::mode::Async; +use embassy_stm32::spi; +use embassy_stm32::time::mhz; +use heapless::String; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn main_task(mut spi: spi::Spi<'static, Async>) { + for n in 0u32.. { + let mut write: String<128> = String::new(); + let mut read = [0; 128]; + core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); + // transfer will slice the &mut read down to &write's actual length. + spi.transfer(&mut read, write.as_bytes()).await.ok(); + info!("read via spi+dma: {}", from_utf8(&read).unwrap()); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + let p = embassy_stm32::init(Default::default()); + + let mut spi_config = spi::Config::default(); + spi_config.frequency = mhz(1); + + let spi = spi::Spi::new(p.SPI3, p.PB3, p.PB5, p.PB4, p.GPDMA1_CH0, p.GPDMA1_CH1, spi_config); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task(spi))); + }) +} diff --git a/embassy/examples/stm32h7rs/src/bin/usart.rs b/embassy/examples/stm32h7rs/src/bin/usart.rs new file mode 100644 index 0000000..cc49c2f --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/usart.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::usart::{Config, Uart}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn main_task() { + let p = embassy_stm32::init(Default::default()); + + let config = Config::default(); + let mut usart = Uart::new_blocking(p.UART7, p.PF6, p.PF7, config).unwrap(); + + unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.blocking_read(&mut buf)); + unwrap!(usart.blocking_write(&buf)); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task())); + }) +} diff --git a/embassy/examples/stm32h7rs/src/bin/usart_dma.rs b/embassy/examples/stm32h7rs/src/bin/usart_dma.rs new file mode 100644 index 0000000..c644e84 --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/usart_dma.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use heapless::String; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + +#[embassy_executor::task] +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, Irqs, p.GPDMA1_CH0, p.GPDMA1_CH1, config).unwrap(); + + for n in 0u32.. { + let mut s: String<128> = String::new(); + core::write!(&mut s, "Hello DMA World {}!\r\n", n).unwrap(); + + usart.write(s.as_bytes()).await.ok(); + + info!("wrote DMA"); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task())); + }) +} diff --git a/embassy/examples/stm32h7rs/src/bin/usart_split.rs b/embassy/examples/stm32h7rs/src/bin/usart_split.rs new file mode 100644 index 0000000..d26c500 --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/usart_split.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::mode::Async; +use embassy_stm32::usart::{Config, Uart, UartRx}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::Channel; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + +static CHANNEL: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, p.GPDMA1_CH0, p.GPDMA1_CH1, config).unwrap(); + unwrap!(usart.blocking_write(b"Type 8 chars to echo!\r\n")); + + let (mut tx, rx) = usart.split(); + + unwrap!(spawner.spawn(reader(rx))); + + loop { + let buf = CHANNEL.receive().await; + info!("writing..."); + unwrap!(tx.write(&buf).await); + } +} + +#[embassy_executor::task] +async fn reader(mut rx: UartRx<'static, Async>) { + let mut buf = [0; 8]; + loop { + info!("reading..."); + unwrap!(rx.read(&mut buf).await); + CHANNEL.send(buf).await; + } +} diff --git a/embassy/examples/stm32h7rs/src/bin/usb_serial.rs b/embassy/examples/stm32h7rs/src/bin/usb_serial.rs new file mode 100644 index 0000000..6773f78 --- /dev/null +++ b/embassy/examples/stm32h7rs/src/bin/usb_serial.rs @@ -0,0 +1,140 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_HS => usb::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV12, + mul: PllMul::MUL300, + divp: Some(PllDiv::DIV1), //600 MHz + divq: Some(PllDiv::DIV2), // 300 MHz + divr: Some(PllDiv::DIV2), // 300 MHz + }); + config.rcc.sys = Sysclk::PLL1_P; // 600 MHz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 300 MHz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb5_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.voltage_scale = VoltageScale::HIGH; + config.rcc.mux.usbphycsel = mux::Usbphycsel::HSE; + } + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PM6, p.PM5, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32l0/.cargo/config.toml b/embassy/examples/stm32l0/.cargo/config.toml new file mode 100644 index 0000000..fed9cf9 --- /dev/null +++ b/embassy/examples/stm32l0/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L073RZTx" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32l0/Cargo.toml b/embassy/examples/stm32l0/Cargo.toml new file mode 100644 index 0000000..5cc312a --- /dev/null +++ b/embassy/examples/stm32l0/Cargo.toml @@ -0,0 +1,30 @@ +[package] +edition = "2021" +name = "embassy-stm32l0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32l072cz to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l073rz", "unstable-pac", "time-driver-any", "exti", "memory-x"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +embedded-storage = "0.3.1" +embedded-io = { version = "0.6.0" } +embedded-io-async = { version = "0.6.1" } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +embedded-hal = "0.2.6" +static_cell = { version = "2" } +portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32l0/README.md b/embassy/examples/stm32l0/README.md new file mode 100644 index 0000000..82d2220 --- /dev/null +++ b/embassy/examples/stm32l0/README.md @@ -0,0 +1,24 @@ +# Examples for STM32L0 family +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L073RZ it should be `probe-rs run --chip STM32L073RZTx`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for L073RZ it should be `stm32l073rz`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/embassy/examples/stm32l0/build.rs b/embassy/examples/stm32l0/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32l0/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/embassy/examples/stm32l0/src/bin/adc.rs b/embassy/examples/stm32l0/src/bin/adc.rs new file mode 100644 index 0000000..9dd09bc --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/adc.rs @@ -0,0 +1,40 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::peripherals::ADC1; +use embassy_stm32::{adc, bind_interrupts}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC1_COMP => adc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC1, Irqs); + adc.set_sample_time(SampleTime::CYCLES79_5); + let mut pin = p.PA1; + + let mut vrefint = adc.enable_vref(); + let vrefint_sample = adc.read(&mut vrefint).await; + let convert_to_millivolts = |sample| { + // From https://www.st.com/resource/en/datasheet/stm32l051c6.pdf + // 6.3.3 Embedded internal reference voltage + const VREFINT_MV: u32 = 1224; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + + loop { + let v = adc.read(&mut pin).await; + info!("--> {} - {} mV", v, convert_to_millivolts(v)); + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/stm32l0/src/bin/blinky.rs b/embassy/examples/stm32l0/src/bin/blinky.rs new file mode 100644 index 0000000..caca575 --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PB5, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32l0/src/bin/button.rs b/embassy/examples/stm32l0/src/bin/button.rs new file mode 100644 index 0000000..707486c --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/button.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +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.PB2, Pull::Up); + let mut led1 = Output::new(p.PA5, Level::High, Speed::Low); + let mut led2 = Output::new(p.PB5, Level::High, Speed::Low); + + loop { + if button.is_high() { + info!("high"); + led1.set_high(); + led2.set_low(); + } else { + info!("low"); + led1.set_low(); + led2.set_high(); + } + } +} diff --git a/embassy/examples/stm32l0/src/bin/button_exti.rs b/embassy/examples/stm32l0/src/bin/button_exti.rs new file mode 100644 index 0000000..4945da7 --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/button_exti.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::Pull; +use embassy_stm32::Config; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let config = Config::default(); + let p = embassy_stm32::init(config); + + let mut button = ExtiInput::new(p.PB2, p.EXTI2, Pull::Up); + + 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/embassy/examples/stm32l0/src/bin/dds.rs b/embassy/examples/stm32l0/src/bin/dds.rs new file mode 100644 index 0000000..a54b28a --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/dds.rs @@ -0,0 +1,116 @@ +#![no_std] +#![no_main] + +use core::option::Option::Some; + +use defmt::info; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::rcc::*; +use embassy_stm32::time::hz; +use embassy_stm32::timer::low_level::{Timer as LLTimer, *}; +use embassy_stm32::timer::simple_pwm::PwmPin; +use embassy_stm32::timer::Channel; +use embassy_stm32::{interrupt, pac, Config}; +use panic_probe as _; + +const DDS_SINE_DATA: [u8; 256] = [ + 0x80, 0x83, 0x86, 0x89, 0x8c, 0x8f, 0x92, 0x95, 0x98, 0x9c, 0x9f, 0xa2, 0xa5, 0xa8, 0xab, 0xae, 0xb0, 0xb3, 0xb6, + 0xb9, 0xbc, 0xbf, 0xc1, 0xc4, 0xc7, 0xc9, 0xcc, 0xce, 0xd1, 0xd3, 0xd5, 0xd8, 0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4, + 0xe6, 0xe8, 0xea, 0xec, 0xed, 0xef, 0xf0, 0xf2, 0xf3, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfc, 0xfd, + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfc, 0xfc, 0xfb, + 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf3, 0xf2, 0xf0, 0xef, 0xed, 0xec, 0xea, 0xe8, 0xe6, 0xe4, 0xe2, 0xe0, 0xde, + 0xdc, 0xda, 0xd8, 0xd5, 0xd3, 0xd1, 0xce, 0xcc, 0xc9, 0xc7, 0xc4, 0xc1, 0xbf, 0xbc, 0xb9, 0xb6, 0xb3, 0xb0, 0xae, + 0xab, 0xa8, 0xa5, 0xa2, 0x9f, 0x9c, 0x98, 0x95, 0x92, 0x8f, 0x8c, 0x89, 0x86, 0x83, 0x80, 0x7c, 0x79, 0x76, 0x73, + 0x70, 0x6d, 0x6a, 0x67, 0x63, 0x60, 0x5d, 0x5a, 0x57, 0x54, 0x51, 0x4f, 0x4c, 0x49, 0x46, 0x43, 0x40, 0x3e, 0x3b, + 0x38, 0x36, 0x33, 0x31, 0x2e, 0x2c, 0x2a, 0x27, 0x25, 0x23, 0x21, 0x1f, 0x1d, 0x1b, 0x19, 0x17, 0x15, 0x13, 0x12, + 0x10, 0x0f, 0x0d, 0x0c, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0c, 0x0d, 0x0f, 0x10, 0x12, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, 0x1f, 0x21, 0x23, 0x25, 0x27, 0x2a, 0x2c, + 0x2e, 0x31, 0x33, 0x36, 0x38, 0x3b, 0x3e, 0x40, 0x43, 0x46, 0x49, 0x4c, 0x4f, 0x51, 0x54, 0x57, 0x5a, 0x5d, 0x60, + 0x63, 0x67, 0x6a, 0x6d, 0x70, 0x73, 0x76, 0x79, 0x7c, +]; + +// frequency: 15625/(256/(DDS_INCR/2**24)) = 999,99999Hz +static mut DDS_INCR: u32 = 0x10624DD2; + +// fractional phase accumulator +static mut DDS_AKKU: u32 = 0x00000000; + +#[interrupt] +fn TIM2() { + unsafe { + // get next value of DDS + DDS_AKKU = DDS_AKKU.wrapping_add(DDS_INCR); + let value = (DDS_SINE_DATA[(DDS_AKKU >> 24) as usize] as u16) << 3; + + // set new output compare value + pac::TIM2.ccr(2).modify(|w| w.set_ccr(value)); + + // reset interrupt flag + pac::TIM2.sr().modify(|r| r.set_uif(false)); + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + // configure for 32MHz (HSI16 * 6 / 3) + let mut config = Config::default(); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + div: PllDiv::DIV3, + mul: PllMul::MUL6, + }); + + let p = embassy_stm32::init(config); + + // setup PWM pin in AF mode + let _ch3 = PwmPin::new_ch3(p.PA2, OutputType::PushPull); + + // initialize timer + // we cannot use SimplePWM here because the Time is privately encapsulated + let timer = LLTimer::new(p.TIM2); + + // set counting mode + timer.set_counting_mode(CountingMode::EdgeAlignedUp); + + // set pwm sample frequency + timer.set_frequency(hz(15625)); + + // enable outputs + timer.enable_outputs(); + + // start timer + timer.start(); + + // set output compare mode + timer.set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1); + + // set output compare preload + timer.set_output_compare_preload(Channel::Ch3, true); + + // set output polarity + timer.set_output_polarity(Channel::Ch3, OutputPolarity::ActiveHigh); + + // set compare value + timer.set_compare_value(Channel::Ch3, timer.get_max_compare_value() / 2); + + // enable pwm channel + timer.enable_channel(Channel::Ch3, true); + + // enable timer interrupts + timer.enable_update_interrupt(true); + unsafe { cortex_m::peripheral::NVIC::unmask(interrupt::TIM2) }; + + async { + loop { + embassy_time::Timer::after_millis(5000).await; + } + } + .await; +} diff --git a/embassy/examples/stm32l0/src/bin/flash.rs b/embassy/examples/stm32l0/src/bin/flash.rs new file mode 100644 index 0000000..1865748 --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/flash.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello Flash!"); + + const ADDR: u32 = 0x26000; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.blocking_erase(ADDR, ADDR + 128)); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); +} diff --git a/embassy/examples/stm32l0/src/bin/raw_spawn.rs b/embassy/examples/stm32l0/src/bin/raw_spawn.rs new file mode 100644 index 0000000..29c7e0d --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/raw_spawn.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +use core::mem; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::raw::TaskStorage; +use embassy_executor::Executor; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +async fn run1() { + loop { + info!("BIG INFREQUENT TICK"); + Timer::after_ticks(64000).await; + } +} + +async fn run2() { + loop { + info!("tick"); + Timer::after_ticks(13000).await; + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_stm32::init(Default::default()); + let executor = EXECUTOR.init(Executor::new()); + + let run1_task = TaskStorage::new(); + let run2_task = TaskStorage::new(); + + // Safety: these variables do live forever if main never returns. + let run1_task = unsafe { make_static(&run1_task) }; + let run2_task = unsafe { make_static(&run2_task) }; + + executor.run(|spawner| { + unwrap!(spawner.spawn(run1_task.spawn(|| run1()))); + unwrap!(spawner.spawn(run2_task.spawn(|| run2()))); + }); +} + +unsafe fn make_static(t: &T) -> &'static T { + mem::transmute(t) +} diff --git a/embassy/examples/stm32l0/src/bin/spi.rs b/embassy/examples/stm32l0/src/bin/spi.rs new file mode 100644 index 0000000..8e0cfde --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/spi.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +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, folks!"); + + let mut spi_config = Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new_blocking(p.SPI1, p.PB3, p.PA7, p.PA6, spi_config); + + let mut cs = Output::new(p.PA15, Level::High, Speed::VeryHigh); + + loop { + let mut buf = [0x0Au8; 4]; + cs.set_low(); + unwrap!(spi.blocking_transfer_in_place(&mut buf)); + cs.set_high(); + info!("xfer {=[u8]:x}", buf); + } +} diff --git a/embassy/examples/stm32l0/src/bin/tsc_async.rs b/embassy/examples/stm32l0/src/bin/tsc_async.rs new file mode 100644 index 0000000..dae351c --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/tsc_async.rs @@ -0,0 +1,116 @@ +// Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32L073RZ Nucleo board: +// - Connect a 1000pF capacitor between pin PA0 and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PA1 and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 1 of the TSC on the STM32L073RZ Nucleo board: +// - PA0 as the sampling capacitor, TSC group 1 IO1 (label A0) +// - PA1 as the channel pin, TSC group 1 IO2 (label A1) +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED is turned on when touch is detected (sensor value < 25). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the +// official relevant STM32 datasheets and nucleo-board user manuals to find suitable +// alternative pins. +// +// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to +// the programmer chip. If you try to use these two pins for TSC, you will get strange +// readings, unless you somehow reconfigure/re-wire your nucleo-board. +// No errors or warnings will be emitted, they will just silently not work as expected. +// (see nucleo user manual UM1724, Rev 14, page 25) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let mut pin_group: PinGroupWithRoles = PinGroupWithRoles::default(); + pin_group.set_io1::(context.PA0); + let sensor = pin_group.set_io2::(context.PA1); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let pin_groups: PinGroups = PinGroups { + g1: Some(pin_group.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, tsc_conf, Irqs).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + info!("TSC not ready!"); + return; + } + info!("TSC initialized successfully"); + + // LED2 on the STM32L073RZ nucleo-board (PA5) + let mut led = Output::new(context.PA5, Level::Low, Speed::Low); + + let discharge_delay = 5; // ms + + info!("Starting touch_controller interface"); + loop { + touch_controller.set_active_channels_mask(sensor.pin.into()); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + let group_val = touch_controller.group_get_value(sensor.pin.group()); + info!("Touch value: {}", group_val); + + if group_val < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/stm32l0/src/bin/tsc_blocking.rs b/embassy/examples/stm32l0/src/bin/tsc_blocking.rs new file mode 100644 index 0000000..e1f2463 --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/tsc_blocking.rs @@ -0,0 +1,142 @@ +// Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32L073RZ Nucleo board: +// - Connect a 1000pF capacitor between pin PA0 and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PA1 and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 1 of the TSC on the STM32L073RZ Nucleo board: +// - PA0 as the sampling capacitor, TSC group 1 IO1 (label A0) +// - PA1 as the channel pin, TSC group 1 IO2 (label A1) +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED is turned on when touch is detected (sensor value < 25). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the +// official relevant STM32 datasheets and nucleo-board user manuals to find suitable +// alternative pins. +// +// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to +// the programmer chip. If you try to use these two pins for TSC, you will get strange +// readings, unless you somehow reconfigure/re-wire your nucleo-board. +// No errors or warnings will be emitted, they will just silently not work as expected. +// (see nucleo user manual UM1724, Rev 14, page 25) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut g1: PinGroupWithRoles = PinGroupWithRoles::default(); + g1.set_io1::(context.PA0); + let tsc_sensor = g1.set_io2::(context.PA1); + + let pin_groups: PinGroups = PinGroups { + g1: Some(g1.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + info!("TSC initialized successfully"); + + // LED2 on the STM32L073RZ nucleo-board (PA5) + let mut led = Output::new(context.PA5, Level::High, Speed::Low); + + // smaller sample capacitor discharge faster and can be used with shorter delay. + let discharge_delay = 5; // ms + + // the interval at which the loop polls for new touch sensor values + let polling_interval = 100; // ms + + info!("polling for touch"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.poll_for_acquisition(); + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { + Some(v) => { + info!("sensor value {}", v); + if v < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + } + None => led.set_low(), + } + + Timer::after_millis(polling_interval).await; + } +} + +const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; + +// attempt to read group status and delay when still ongoing +async fn read_touch_value( + touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, + sensor_pin: tsc::IOPin, +) -> Option { + for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { + match touch_controller.group_get_status(sensor_pin.group()) { + GroupStatus::Complete => { + return Some(touch_controller.group_get_value(sensor_pin.group())); + } + GroupStatus::Ongoing => { + // if you end up here a lot, then you prob need to increase discharge_delay + // or consider changing the code to adjust the discharge_delay dynamically + info!("Acquisition still ongoing"); + Timer::after_millis(1).await; + } + } + } + None +} diff --git a/embassy/examples/stm32l0/src/bin/tsc_multipin.rs b/embassy/examples/stm32l0/src/bin/tsc_multipin.rs new file mode 100644 index 0000000..6343de1 --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/tsc_multipin.rs @@ -0,0 +1,209 @@ +// Example of TSC (Touch Sensing Controller) using multiple pins from the same tsc-group. +// +// What is special about using multiple TSC pins as sensor channels from the same TSC group, +// is that only one TSC pin for each TSC group can be acquired and read at the time. +// To control which channel pins are acquired and read, we must write a mask before initiating an +// acquisition. To help manage and abstract all this business away, we can organize our channel +// pins into acquisition banks. Each acquisition bank can contain exactly one channel pin per TSC +// group and it will contain the relevant mask. +// +// This example demonstrates how to: +// 1. Configure multiple channel pins within a single TSC group +// 2. Use the set_active_channels_bank method to switch between sets of different channels (acquisition banks) +// 3. Read and interpret touch values from multiple channels in the same group +// +// Suggested physical setup on STM32L073RZ Nucleo board: +// - Connect a 1000pF capacitor between pin PA0 (label A0) and GND. This is the sampling capacitor for TSC +// group 1. +// - Connect one end of a 1K resistor to pin PA1 (label A1) and leave the other end loose. +// The loose end will act as a touch sensor. +// +// - Connect a 1000pF capacitor between pin PB3 (label D3) and GND. This is the sampling capacitor for TSC +// group 5. +// - Connect one end of another 1K resistor to pin PB4 and leave the other end loose. +// The loose end will act as a touch sensor. +// - Connect one end of another 1K resistor to pin PB6 and leave the other end loose. +// The loose end will act as a touch sensor. +// +// The example uses pins from two TSC groups. +// - PA0 as sampling capacitor, TSC group 1 IO1 (label A0) +// - PA1 as channel, TSC group 1 IO2 (label A1) +// - PB3 as sampling capacitor, TSC group 5 IO1 (label D3) +// - PB4 as channel, TSC group 5 IO2 (label D10) +// - PB6 as channel, TSC group 5 IO3 (label D5) +// +// The pins have been chosen to make it easy to simply add capacitors directly onto the board and +// connect one leg to GND, and to easily add resistors to the board with no special connectors, +// breadboards, special wires or soldering required. All you need is the capacitors and resistors. +// +// The program reads the designated channel pins and adjusts the LED blinking +// pattern based on which sensor(s) are touched: +// - No touch: LED off +// - one sensor touched: Slow blinking +// - two sensors touched: Fast blinking +// - three sensors touched: LED constantly on +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the +// official relevant STM32 datasheets and nucleo-board user manuals to find suitable +// alternative pins. +// +// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to +// the programmer chip. If you try to use these two pins for TSC, you will get strange +// readings, unless you somehow reconfigure/re-wire your nucleo-board. +// No errors or warnings will be emitted, they will just silently not work as expected. +// (see nucleo user manual UM1724, Rev 14, page 25) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); + +const SENSOR_THRESHOLD: u16 = 35; + +async fn acquire_sensors( + touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Async>, + tsc_acquisition_bank: &AcquisitionBank, +) { + touch_controller.set_active_channels_bank(tsc_acquisition_bank); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + let discharge_delay = 5; // ms + Timer::after_millis(discharge_delay).await; +} + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + // ---------- initial configuration of TSC ---------- + let mut pin_group1: PinGroupWithRoles = PinGroupWithRoles::default(); + pin_group1.set_io1::(context.PA0); + let tsc_sensor0 = pin_group1.set_io2(context.PA1); + + let mut pin_group5: PinGroupWithRoles = PinGroupWithRoles::default(); + pin_group5.set_io1::(context.PB3); + let tsc_sensor1 = pin_group5.set_io2(context.PB4); + let tsc_sensor2 = pin_group5.set_io3(context.PB6); + + let config = tsc::Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_16, + ct_pulse_low_length: ChargeTransferPulseCycle::_16, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let pin_groups: PinGroups = PinGroups { + g1: Some(pin_group1.pin_group), + g5: Some(pin_group5.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); + + // ---------- setting up acquisition banks ---------- + // sensor0 and sensor1 in this example belong to different TSC-groups, + // therefore we can acquire and read them both in one go. + let bank1 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g1_pin: Some(tsc_sensor0), + g5_pin: Some(tsc_sensor1), + ..Default::default() + }); + // `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to + // acquire them one at the time. Therefore, we organize them into different acquisition banks. + let bank2 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g5_pin: Some(tsc_sensor2), + ..Default::default() + }); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + + info!("TSC initialized successfully"); + + // LED2 on the STM32L073RZ nucleo-board (PA5) + let mut led = Output::new(context.PA5, Level::High, Speed::Low); + + let mut led_state = false; + + loop { + acquire_sensors(&mut touch_controller, &bank1).await; + let readings1 = touch_controller.get_acquisition_bank_values(&bank1); + acquire_sensors(&mut touch_controller, &bank2).await; + let readings2 = touch_controller.get_acquisition_bank_values(&bank2); + + let mut touched_sensors_count = 0; + for reading in readings1.iter() { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + for reading in readings2.iter() { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + + match touched_sensors_count { + 0 => { + // No sensors touched, turn off the LED + led.set_low(); + led_state = false; + } + 1 => { + // One sensor touched, blink slowly + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(200).await; + } + 2 => { + // Two sensors touched, blink faster + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(50).await; + } + 3 => { + // All three sensors touched, LED constantly on + led.set_high(); + led_state = true; + } + _ => crate::unreachable!(), // This case should never occur with 3 sensors + } + } +} diff --git a/embassy/examples/stm32l0/src/bin/usart_dma.rs b/embassy/examples/stm32l0/src/bin/usart_dma.rs new file mode 100644 index 0000000..74889c8 --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/usart_dma.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USART1 => usart::InterruptHandler; +}); + +#[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, Irqs, p.DMA1_CH2, p.DMA1_CH3, Config::default()).unwrap(); + + usart.write(b"Hello Embassy World!\r\n").await.unwrap(); + info!("wrote Hello, starting echo"); + + let mut buf = [0; 1]; + loop { + usart.read(&mut buf[..]).await.unwrap(); + usart.write(&buf[..]).await.unwrap(); + } +} diff --git a/embassy/examples/stm32l0/src/bin/usart_irq.rs b/embassy/examples/stm32l0/src/bin/usart_irq.rs new file mode 100644 index 0000000..2c96a8b --- /dev/null +++ b/embassy/examples/stm32l0/src/bin/usart_irq.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{BufferedUart, Config}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use embedded_io_async::{Read, Write}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USART2 => usart::BufferedInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hi!"); + + let mut config = Config::default(); + config.baudrate = 9600; + let mut tx_buf = [0u8; 256]; + let mut rx_buf = [0u8; 256]; + let mut usart = BufferedUart::new(p.USART2, Irqs, p.PA3, p.PA2, &mut tx_buf, &mut rx_buf, config).unwrap(); + + usart.write_all(b"Hello Embassy World!\r\n").await.unwrap(); + info!("wrote Hello, starting echo"); + + let mut buf = [0; 4]; + loop { + usart.read_exact(&mut buf[..]).await.unwrap(); + usart.write_all(&buf[..]).await.unwrap(); + } +} diff --git a/embassy/examples/stm32l1/.cargo/config.toml b/embassy/examples/stm32l1/.cargo/config.toml new file mode 100644 index 0000000..9cabd14 --- /dev/null +++ b/embassy/examples/stm32l1/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L151CBxxA" + +[build] +target = "thumbv7m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32l1/Cargo.toml b/embassy/examples/stm32l1/Cargo.toml new file mode 100644 index 0000000..31b6785 --- /dev/null +++ b/embassy/examples/stm32l1/Cargo.toml @@ -0,0 +1,26 @@ +[package] +edition = "2021" +name = "embassy-stm32l1-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32l151cb-a", "time-driver-any", "memory-x"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +embedded-storage = "0.3.1" + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32l1/build.rs b/embassy/examples/stm32l1/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32l1/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/embassy/examples/stm32l1/src/bin/blinky.rs b/embassy/examples/stm32l1/src/bin/blinky.rs new file mode 100644 index 0000000..da6777b --- /dev/null +++ b/embassy/examples/stm32l1/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PA12, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(1000).await; + + info!("low"); + led.set_low(); + Timer::after_millis(1000).await; + } +} diff --git a/embassy/examples/stm32l1/src/bin/flash.rs b/embassy/examples/stm32l1/src/bin/flash.rs new file mode 100644 index 0000000..e9ce4ea --- /dev/null +++ b/embassy/examples/stm32l1/src/bin/flash.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello Flash!"); + + const ADDR: u32 = 0x26000; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.blocking_erase(ADDR, ADDR + 256)); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); +} diff --git a/embassy/examples/stm32l1/src/bin/spi.rs b/embassy/examples/stm32l1/src/bin/spi.rs new file mode 100644 index 0000000..eabf1ba --- /dev/null +++ b/embassy/examples/stm32l1/src/bin/spi.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +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, folks!"); + + let mut spi_config = Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new_blocking(p.SPI1, p.PA5, p.PA7, p.PA6, spi_config); + + let mut cs = Output::new(p.PA4, Level::High, Speed::VeryHigh); + + loop { + let mut buf = [0x0Au8; 4]; + cs.set_low(); + unwrap!(spi.blocking_transfer_in_place(&mut buf)); + cs.set_high(); + info!("xfer {=[u8]:x}", buf); + } +} diff --git a/embassy/examples/stm32l1/src/bin/usart.rs b/embassy/examples/stm32l1/src/bin/usart.rs new file mode 100644 index 0000000..dba79b8 --- /dev/null +++ b/embassy/examples/stm32l1/src/bin/usart.rs @@ -0,0 +1,37 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USART2 => usart::InterruptHandler; +}); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let config = Config::default(); + let mut usart = Uart::new_blocking(p.USART2, p.PA3, p.PA2, config).unwrap(); + let desired_baudrate = 9600; // Default is 115200 and 9600 is used as example + + match usart.set_baudrate(desired_baudrate) { + Ok(_) => info!("Baud rate set to {}", desired_baudrate), + Err(err) => error!("Error setting baudrate to {}: {}", desired_baudrate, err), + } + + unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.blocking_read(&mut buf)); + unwrap!(usart.blocking_write(&buf)); + } +} diff --git a/embassy/examples/stm32l1/src/bin/usb_serial.rs b/embassy/examples/stm32l1/src/bin/usb_serial.rs new file mode 100644 index 0000000..837f7fa --- /dev/null +++ b/embassy/examples/stm32l1/src/bin/usb_serial.rs @@ -0,0 +1,101 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{self, Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_LP => usb::InterruptHandler; + +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + mul: PllMul::MUL6, // PLLVCO = 16*6 = 96Mhz + div: PllDiv::DIV3, // 32Mhz clock (16 * 6 / 3) + }); + config.rcc.sys = Sysclk::PLL1_R; + } + + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-Serial Example"); + config.serial_number = Some("123456"); + + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + let mut usb = builder.build(); + + let usb_fut = usb.run(); + + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + 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/embassy/examples/stm32l4/.cargo/config.toml b/embassy/examples/stm32l4/.cargo/config.toml new file mode 100644 index 0000000..d71fb15 --- /dev/null +++ b/embassy/examples/stm32l4/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +#runner = "probe-rs run --chip STM32L475VGT6" +#runner = "probe-rs run --chip STM32L475VG" +#runner = "probe-rs run --chip STM32L4S5QI" +runner = "probe-rs run --chip STM32L4R5ZITxP" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32l4/Cargo.toml b/embassy/examples/stm32l4/Cargo.toml new file mode 100644 index 0000000..3fde18e --- /dev/null +++ b/embassy/examples/stm32l4/Cargo.toml @@ -0,0 +1,39 @@ +[package] +edition = "2021" +name = "embassy-stm32l4-examples" +version = "0.1.1" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32l4s5vi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4r5zi", "memory-x", "time-driver-any", "exti", "chrono"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net-adin1110 = { version = "0.2.0", path = "../../embassy-net-adin1110" } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "udp", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-io = { version = "0.6.0", features = ["defmt-03"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +chrono = { version = "^0.4", default-features = false } +rand = { version = "0.8.5", default-features = false } +static_cell = "2" + +micromath = "2.0.0" + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32l4/README.md b/embassy/examples/stm32l4/README.md new file mode 100644 index 0000000..e463c18 --- /dev/null +++ b/embassy/examples/stm32l4/README.md @@ -0,0 +1,24 @@ +# Examples for STM32L4 family +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L4R5ZI-P it should be `probe-rs run --chip STM32L4R5ZITxP`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for L4R5ZI-P it should be `stm32l4r5zi`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/embassy/examples/stm32l4/build.rs b/embassy/examples/stm32l4/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32l4/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/embassy/examples/stm32l4/src/bin/adc.rs b/embassy/examples/stm32l4/src/bin/adc.rs new file mode 100644 index 0000000..c557ac6 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/adc.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::adc::{Adc, Resolution}; +use embassy_stm32::Config; +use {defmt_rtt as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.mux.adcsel = mux::Adcsel::SYS; + } + let p = embassy_stm32::init(config); + + let mut adc = Adc::new(p.ADC1); + //adc.enable_vref(); + adc.set_resolution(Resolution::BITS8); + let mut channel = p.PC0; + + loop { + let v = adc.blocking_read(&mut channel); + info!("--> {}", v); + } +} diff --git a/embassy/examples/stm32l4/src/bin/blinky.rs b/embassy/examples/stm32l4/src/bin/blinky.rs new file mode 100644 index 0000000..b55dfd3 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/blinky.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PB14, Level::High, Speed::Low); + + loop { + led.set_high(); + Timer::after_millis(300).await; + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32l4/src/bin/button.rs b/embassy/examples/stm32l4/src/bin/button.rs new file mode 100644 index 0000000..1f32702 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/button.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Input, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[cortex_m_rt::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/embassy/examples/stm32l4/src/bin/button_exti.rs b/embassy/examples/stm32l4/src/bin/button_exti.rs new file mode 100644 index 0000000..34a08bb --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); + + 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/embassy/examples/stm32l4/src/bin/can.rs b/embassy/examples/stm32l4/src/bin/can.rs new file mode 100644 index 0000000..3c4cdac --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/can.rs @@ -0,0 +1,68 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::can::filter::Mask32; +use embassy_stm32::can::{ + Can, Fifo, Frame, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, TxInterruptHandler, +}; +use embassy_stm32::peripherals::CAN1; +use embassy_stm32::{bind_interrupts, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + CAN1_RX0 => Rx0InterruptHandler; + CAN1_RX1 => Rx1InterruptHandler; + CAN1_SCE => SceInterruptHandler; + CAN1_TX => TxInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Config::default()); + + let mut can = Can::new(p.CAN1, p.PA11, p.PA12, Irqs); + + can.modify_filters().enable_bank(0, Fifo::Fifo0, Mask32::accept_all()); + + can.modify_config() + .set_loopback(true) // Receive own frames + .set_silent(true) + .set_bitrate(250_000); + + can.enable().await; + println!("CAN enabled"); + + let mut i = 0; + let mut last_read_ts = embassy_time::Instant::now(); + loop { + let frame = Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + + _ = can.write(&frame).await; + + match can.read().await { + Ok(envelope) => { + let (ts, rx_frame) = (envelope.ts, envelope.frame); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- {}ms", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + delta, + ) + } + Err(err) => error!("Error in frame: {}", err), + } + + Timer::after_millis(250).await; + + i += 1; + if i > 2 { + break; + } + } +} diff --git a/embassy/examples/stm32l4/src/bin/dac.rs b/embassy/examples/stm32l4/src/bin/dac.rs new file mode 100644 index 0000000..fdbf1d3 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/dac.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::dac::{DacCh1, Value}; +use embassy_stm32::dma::NoDma; +use {defmt_rtt as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); + + loop { + for v in 0..=255 { + dac.set(Value::Bit8(to_sine_wave(v))); + } + } +} + +use micromath::F32Ext; + +fn to_sine_wave(v: u8) -> u8 { + if v >= 128 { + // top half + let r = 3.14 * ((v - 128) as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } else { + // bottom half + let r = 3.14 + 3.14 * (v as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } +} diff --git a/embassy/examples/stm32l4/src/bin/dac_dma.rs b/embassy/examples/stm32l4/src/bin/dac_dma.rs new file mode 100644 index 0000000..d01b016 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/dac_dma.rs @@ -0,0 +1,129 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dac::{DacCh1, DacCh2, ValueArray}; +use embassy_stm32::pac::timer::vals::Mms; +use embassy_stm32::peripherals::{DAC1, DMA1_CH3, DMA1_CH4, TIM6, TIM7}; +use embassy_stm32::rcc::frequency; +use embassy_stm32::time::Hertz; +use embassy_stm32::timer::low_level::Timer; +use micromath::F32Ext; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let config = embassy_stm32::Config::default(); + + // Initialize the board and obtain a Peripherals instance + let p: embassy_stm32::Peripherals = embassy_stm32::init(config); + + // Obtain two independent channels (p.DAC1 can only be consumed once, though!) + let (dac_ch1, dac_ch2) = embassy_stm32::dac::Dac::new(p.DAC1, p.DMA1_CH3, p.DMA1_CH4, p.PA4, p.PA5).split(); + + spawner.spawn(dac_task1(p.TIM6, dac_ch1)).ok(); + spawner.spawn(dac_task2(p.TIM7, dac_ch2)).ok(); +} + +#[embassy_executor::task] +async fn dac_task1(tim: TIM6, mut dac: DacCh1<'static, DAC1, DMA1_CH3>) { + let data: &[u8; 256] = &calculate_array::<256>(); + + info!("TIM6 frequency is {}", frequency::()); + const FREQUENCY: Hertz = Hertz::hz(200); + + // Compute the reload value such that we obtain the FREQUENCY for the sine + let reload: u32 = (frequency::().0 / FREQUENCY.0) / data.len() as u32; + + // Depends on your clock and on the specific chip used, you may need higher or lower values here + if reload < 10 { + error!("Reload value {} below threshold!", reload); + } + + dac.set_trigger(embassy_stm32::dac::TriggerSel::Tim6); + dac.set_triggering(true); + dac.enable(); + + let tim = Timer::new(tim); + tim.regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1)); + tim.regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE)); + tim.regs_basic().cr1().modify(|w| { + w.set_opm(false); + w.set_cen(true); + }); + + debug!( + "TIM6 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}", + frequency::(), + FREQUENCY, + reload, + reload as u16, + data.len() + ); + + // Loop technically not necessary if DMA circular mode is enabled + loop { + info!("Loop DAC1"); + dac.write(ValueArray::Bit8(data), true).await; + } +} + +#[embassy_executor::task] +async fn dac_task2(tim: TIM7, mut dac: DacCh2<'static, DAC1, DMA1_CH4>) { + let data: &[u8; 256] = &calculate_array::<256>(); + + info!("TIM7 frequency is {}", frequency::()); + + const FREQUENCY: Hertz = Hertz::hz(600); + let reload: u32 = (frequency::().0 / FREQUENCY.0) / data.len() as u32; + + if reload < 10 { + error!("Reload value {} below threshold!", reload); + } + + let tim = Timer::new(tim); + tim.regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1)); + tim.regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE)); + tim.regs_basic().cr1().modify(|w| { + w.set_opm(false); + w.set_cen(true); + }); + + dac.set_trigger(embassy_stm32::dac::TriggerSel::Tim7); + dac.set_triggering(true); + dac.enable(); + + debug!( + "TIM7 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}", + frequency::(), + FREQUENCY, + reload, + reload as u16, + data.len() + ); + + dac.write(ValueArray::Bit8(data), true).await; +} + +fn to_sine_wave(v: u8) -> u8 { + if v >= 128 { + // top half + let r = 3.14 * ((v - 128) as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } else { + // bottom half + let r = 3.14 + 3.14 * (v as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } +} + +fn calculate_array() -> [u8; N] { + let mut res = [0; N]; + let mut i = 0; + while i < N { + res[i] = to_sine_wave(i as u8); + i += 1; + } + res +} diff --git a/embassy/examples/stm32l4/src/bin/i2c.rs b/embassy/examples/stm32l4/src/bin/i2c.rs new file mode 100644 index 0000000..2861bc0 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/i2c.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::I2c; +use embassy_stm32::time::Hertz; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut i2c = I2c::new_blocking(p.I2C2, p.PB10, p.PB11, Hertz(100_000), Default::default()); + + let mut data = [0u8; 1]; + unwrap!(i2c.blocking_write_read(ADDRESS, &[WHOAMI], &mut data)); + info!("Whoami: {}", data[0]); +} diff --git a/embassy/examples/stm32l4/src/bin/i2c_blocking_async.rs b/embassy/examples/stm32l4/src/bin/i2c_blocking_async.rs new file mode 100644 index 0000000..a014b23 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/i2c_blocking_async.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_embedded_hal::adapter::BlockingAsync; +use embassy_executor::Spawner; +use embassy_stm32::i2c::I2c; +use embassy_stm32::time::Hertz; +use embedded_hal_async::i2c::I2c as I2cTrait; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let i2c = I2c::new_blocking(p.I2C2, p.PB10, p.PB11, Hertz(100_000), Default::default()); + let mut i2c = BlockingAsync::new(i2c); + + let mut data = [0u8; 1]; + unwrap!(i2c.write_read(ADDRESS, &[WHOAMI], &mut data).await); + info!("Whoami: {}", data[0]); +} diff --git a/embassy/examples/stm32l4/src/bin/i2c_dma.rs b/embassy/examples/stm32l4/src/bin/i2c_dma.rs new file mode 100644 index 0000000..794972a --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/i2c_dma.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::I2c; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +bind_interrupts!(struct Irqs { + I2C2_EV => i2c::EventInterruptHandler; + I2C2_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut i2c = I2c::new( + p.I2C2, + p.PB10, + p.PB11, + Irqs, + p.DMA1_CH4, + p.DMA1_CH5, + Hertz(100_000), + Default::default(), + ); + + let mut data = [0u8; 1]; + unwrap!(i2c.write_read(ADDRESS, &[WHOAMI], &mut data).await); + info!("Whoami: {}", data[0]); +} diff --git a/embassy/examples/stm32l4/src/bin/mco.rs b/embassy/examples/stm32l4/src/bin/mco.rs new file mode 100644 index 0000000..36c0029 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/mco.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::rcc::{Mco, McoPrescaler, McoSource}; +use embassy_time::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 _mco = Mco::new(p.MCO, p.PA8, McoSource::HSI, McoPrescaler::DIV1); + + let mut led = Output::new(p.PB14, Level::High, Speed::Low); + + loop { + led.set_high(); + Timer::after_millis(300).await; + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32l4/src/bin/rng.rs b/embassy/examples/stm32l4/src/bin/rng.rs new file mode 100644 index 0000000..14d0e3c --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/rng.rs @@ -0,0 +1,37 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rcc::{Pll, PllMul, PllPreDiv, PllQDiv, PllRDiv, PllSource, Sysclk}; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, peripherals, rng, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL18, + divp: None, + divq: Some(PllQDiv::DIV6), // 48Mhz (16 / 1 * 18 / 6) + divr: Some(PllRDiv::DIV4), // sysclk 72Mhz clock (16 / 1 * 18 / 4) + }); + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG, Irqs); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); +} diff --git a/embassy/examples/stm32l4/src/bin/rtc.rs b/embassy/examples/stm32l4/src/bin/rtc.rs new file mode 100644 index 0000000..f554f0f --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/rtc.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +use chrono::{NaiveDate, NaiveDateTime}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hse = Some(Hse { + freq: Hertz::mhz(8), + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL20, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), // sysclk 80Mhz clock (8 / 1 * 20 / 2) + }); + config.rcc.ls = LsConfig::default_lse(); + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + info!("Got RTC! {:?}", now.and_utc().timestamp()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + // In reality the delay would be much longer + Timer::after_millis(20000).await; + + let then: NaiveDateTime = rtc.now().unwrap().into(); + info!("Got RTC! {:?}", then.and_utc().timestamp()); +} diff --git a/embassy/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/embassy/examples/stm32l4/src/bin/spe_adin1110_http_server.rs new file mode 100644 index 0000000..4a7c01f --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/spe_adin1110_http_server.rs @@ -0,0 +1,441 @@ +#![no_main] +#![no_std] +#![deny(clippy::pedantic)] +#![allow(clippy::doc_markdown)] +#![allow(clippy::missing_errors_doc)] + +// This example works on a ANALOG DEVICE EVAL-ADIN110EBZ board. +// Settings switch S201 "HW CFG": +// - Without SPI CRC: OFF-ON-OFF-OFF-OFF +// - With SPI CRC: ON -ON-OFF-OFF-OFF +// Settings switch S303 "uC CFG": +// - CFG0: On = static ip, Off = Dhcp +// - CFG1: Ethernet `FCS` on TX path: On, Off +// The webserver shows the actual temperature of the onboard i2c temp sensor. + +use core::marker::PhantomData; +use core::sync::atomic::{AtomicI32, Ordering}; + +use defmt::{error, info, println, unwrap, Format}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::select::{select, Either}; +use embassy_futures::yield_now; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources, StaticConfigV4}; +use embassy_net_adin1110::{Device, Runner, ADIN1110}; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::i2c::{self, Config as I2C_Config, I2c}; +use embassy_stm32::mode::Async; +use embassy_stm32::rng::{self, Rng}; +use embassy_stm32::spi::{Config as SPI_Config, Spi}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, exti, pac, peripherals}; +use embassy_time::{Delay, Duration, Ticker, Timer}; +use embedded_hal_async::i2c::I2c as I2cBus; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_io::Write as bWrite; +use embedded_io_async::Write; +use heapless::Vec; +use panic_probe as _; +use rand::RngCore; +use static_cell::StaticCell; + +bind_interrupts!(struct Irqs { + I2C3_EV => i2c::EventInterruptHandler; + I2C3_ER => i2c::ErrorInterruptHandler; + RNG => rng::InterruptHandler; +}); + +// Basic settings +// MAC-address used by the adin1110 +const MAC: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; +// Static IP settings +const IP_ADDRESS: Ipv4Cidr = Ipv4Cidr::new(Ipv4Address::new(192, 168, 1, 5), 24); +// Listen port for the webserver +const HTTP_LISTEN_PORT: u16 = 80; + +pub type SpeSpi = Spi<'static, Async>; +pub type SpeSpiCs = ExclusiveDevice, Delay>; +pub type SpeInt = exti::ExtiInput<'static>; +pub type SpeRst = Output<'static>; +pub type Adin1110T = ADIN1110; +pub type TempSensI2c = I2c<'static, Async>; + +static TEMP: AtomicI32 = AtomicI32::new(0); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + defmt::println!("Start main()"); + + let mut config = embassy_stm32::Config::default(); + { + use embassy_stm32::rcc::*; + // 80Mhz clock (Source: 8 / SrcDiv: 1 * PllMul 20 / ClkDiv 2) + // 80MHz highest frequency for flash 0 wait. + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hse = Some(Hse { + freq: Hertz::mhz(8), + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL20, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), // sysclk 80Mhz clock (8 / 1 * 20 / 2) + }); + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + } + + let dp = embassy_stm32::init(config); + + let reset_status = pac::RCC.bdcr().read().0; + defmt::println!("bdcr before: 0x{:X}", reset_status); + + defmt::println!("Setup IO pins"); + + // Setup LEDs + let _led_uc1_green = Output::new(dp.PC13, Level::Low, Speed::Low); + let mut led_uc2_red = Output::new(dp.PE2, Level::High, Speed::Low); + let led_uc3_yellow = Output::new(dp.PE6, Level::High, Speed::Low); + let led_uc4_blue = Output::new(dp.PG15, Level::High, Speed::Low); + + // Read the uc_cfg switches + let uc_cfg0 = Input::new(dp.PB2, Pull::None); + let uc_cfg1 = Input::new(dp.PF11, Pull::None); + let _uc_cfg2 = Input::new(dp.PG6, Pull::None); + let _uc_cfg3 = Input::new(dp.PG11, Pull::None); + + // Setup I2C pins + let temp_sens_i2c = I2c::new( + dp.I2C3, + dp.PG7, + dp.PG8, + Irqs, + dp.DMA1_CH6, + dp.DMA1_CH7, + Hertz(100_000), + I2C_Config::default(), + ); + + // Setup IO and SPI for the SPE chip + let spe_reset_n = Output::new(dp.PC7, Level::Low, Speed::Low); + let spe_cfg0 = Input::new(dp.PC8, Pull::None); + let spe_cfg1 = Input::new(dp.PC9, Pull::None); + let _spe_ts_capt = Output::new(dp.PC6, Level::Low, Speed::Low); + + let spe_int = exti::ExtiInput::new(dp.PB11, dp.EXTI11, Pull::None); + + let spe_spi_cs_n = Output::new(dp.PB12, Level::High, Speed::High); + let spe_spi_sclk = dp.PB13; + let spe_spi_miso = dp.PB14; + let spe_spi_mosi = dp.PB15; + + // Don't turn the clock to high, clock must fit within the system clock as we get a runtime panic. + let mut spi_config = SPI_Config::default(); + spi_config.frequency = Hertz(25_000_000); + + let spe_spi: SpeSpi = Spi::new( + dp.SPI2, + spe_spi_sclk, + spe_spi_mosi, + spe_spi_miso, + dp.DMA1_CH1, + dp.DMA1_CH2, + spi_config, + ); + let spe_spi = SpeSpiCs::new(spe_spi, spe_spi_cs_n, Delay); + + let cfg0_without_crc = spe_cfg0.is_high(); + let cfg1_spi_mode = spe_cfg1.is_high(); + let uc_cfg1_fcs_en = uc_cfg1.is_low(); + + defmt::println!( + "ADIN1110: CFG SPI-MODE 1-{}, CRC-bit 0-{} FCS-{}", + cfg1_spi_mode, + cfg0_without_crc, + uc_cfg1_fcs_en + ); + + // Check the SPI mode selected with the "HW CFG" dip-switch + if !cfg1_spi_mode { + error!("Driver doesn´t support SPI Protolcol \"OPEN Alliance\".\nplease use the \"Generic SPI\"! Turn On \"HW CFG\": \"SPI_CFG1\""); + loop { + led_uc2_red.toggle(); + Timer::after(Duration::from_hz(10)).await; + } + }; + + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(embassy_net_adin1110::State::<8, 8>::new()); + + let (device, runner) = embassy_net_adin1110::new( + MAC, + state, + spe_spi, + spe_int, + spe_reset_n, + !cfg0_without_crc, + uc_cfg1_fcs_en, + ) + .await; + + // Start task blink_led + unwrap!(spawner.spawn(heartbeat_led(led_uc3_yellow))); + // Start task temperature measurement + unwrap!(spawner.spawn(temp_task(temp_sens_i2c, led_uc4_blue))); + // Start ethernet task + unwrap!(spawner.spawn(ethernet_task(runner))); + + let mut rng = Rng::new(dp.RNG, Irqs); + // Generate random seed + let seed = rng.next_u64(); + + let ip_cfg = if uc_cfg0.is_low() { + println!("Waiting for DHCP..."); + let dhcp4_config = embassy_net::DhcpConfig::default(); + embassy_net::Config::dhcpv4(dhcp4_config) + } else { + embassy_net::Config::ipv4_static(StaticConfigV4 { + address: IP_ADDRESS, + gateway: None, + dns_servers: Vec::new(), + }) + }; + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, ip_cfg, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut mb_buf = [0; 4096]; + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(1))); + + info!("Listening on http://{}:{}...", local_addr, HTTP_LISTEN_PORT); + if let Err(e) = socket.accept(HTTP_LISTEN_PORT).await { + defmt::error!("accept error: {:?}", e); + continue; + } + + loop { + let _n = match socket.read(&mut mb_buf).await { + Ok(0) => { + defmt::info!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + defmt::error!("{:?}", e); + break; + } + }; + led_uc2_red.set_low(); + + let status_line = "HTTP/1.1 200 OK"; + let contents = PAGE; + let length = contents.len(); + + let _ = write!( + &mut mb_buf[..], + "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}\r\n\0" + ); + let loc = mb_buf.iter().position(|v| *v == b'#').unwrap(); + + let temp = TEMP.load(Ordering::Relaxed); + let cel = temp / 1000; + let mcel = temp % 1000; + + info!("{}.{}", cel, mcel); + + let _ = write!(&mut mb_buf[loc..loc + 7], "{cel}.{mcel}"); + + let n = mb_buf.iter().position(|v| *v == 0).unwrap(); + + if let Err(e) = socket.write_all(&mb_buf[..n]).await { + error!("write error: {:?}", e); + break; + } + + led_uc2_red.set_high(); + } + } +} + +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config; + } + yield_now().await; + } +} + +#[embassy_executor::task] +async fn heartbeat_led(mut led: Output<'static>) { + let mut tmr = Ticker::every(Duration::from_hz(3)); + loop { + led.toggle(); + tmr.next().await; + } +} + +// ADT7422 +#[embassy_executor::task] +async fn temp_task(temp_dev_i2c: TempSensI2c, mut led: Output<'static>) -> ! { + let mut tmr = Ticker::every(Duration::from_hz(1)); + let mut temp_sens = ADT7422::new(temp_dev_i2c, 0x48).unwrap(); + + loop { + led.set_low(); + match select(temp_sens.read_temp(), Timer::after_millis(500)).await { + Either::First(i2c_ret) => match i2c_ret { + Ok(value) => { + led.set_high(); + let temp = i32::from(value); + println!("TEMP: {:04x}, {}", temp, temp * 78 / 10); + TEMP.store(temp * 78 / 10, Ordering::Relaxed); + } + Err(e) => defmt::println!("ADT7422: {}", e), + }, + Either::Second(_) => println!("Timeout"), + } + + tmr.next().await; + } +} + +#[embassy_executor::task] +async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +// same panicking *behavior* as `panic-probe` but doesn't print a panic message +// this prevents the panic message being printed *twice* when `defmt::panic` is invoked +#[defmt::panic_handler] +fn panic() -> ! { + cortex_m::asm::udf() +} + +#[allow(non_camel_case_types)] +#[repr(C)] +pub enum Registers { + Temp_MSB = 0x00, + Temp_LSB, + Status, + Cfg, + T_HIGH_MSB, + T_HIGH_LSB, + T_LOW_MSB, + T_LOW_LSB, + T_CRIT_MSB, + T_CRIT_LSB, + T_HYST, + ID, + SW_RESET = 0x2F, +} + +pub struct ADT7422<'d, BUS: I2cBus> { + addr: u8, + phantom: PhantomData<&'d ()>, + bus: BUS, +} + +#[derive(Debug, Format, PartialEq, Eq)] +pub enum Error { + I2c(I2cError), + Address, +} + +impl<'d, BUS> ADT7422<'d, BUS> +where + BUS: I2cBus, + BUS::Error: Format, +{ + pub fn new(bus: BUS, addr: u8) -> Result> { + if !(0x48..=0x4A).contains(&addr) { + return Err(Error::Address); + } + + Ok(Self { + bus, + phantom: PhantomData, + addr, + }) + } + + pub async fn init(&mut self) -> Result<(), Error> { + let mut cfg = 0b000_0000; + // if self.int.is_some() { + // // Set 1 SPS mode + // cfg |= 0b10 << 5; + // } else { + // One shot mode + cfg |= 0b01 << 5; + // } + + self.write_cfg(cfg).await + } + + pub async fn read(&mut self, reg: Registers) -> Result> { + let mut buffer = [0u8; 1]; + self.bus + .write_read(self.addr, &[reg as u8], &mut buffer) + .await + .map_err(Error::I2c)?; + Ok(buffer[0]) + } + + pub async fn write_cfg(&mut self, cfg: u8) -> Result<(), Error> { + let buf = [Registers::Cfg as u8, cfg]; + self.bus.write(self.addr, &buf).await.map_err(Error::I2c) + } + + pub async fn read_temp(&mut self) -> Result> { + let mut buffer = [0u8; 2]; + + // if let Some(int) = &mut self.int { + // // Wait for interrupt + // int.wait_for_low().await.unwrap(); + // } else { + // Start: One shot + let cfg = 0b01 << 5; + self.write_cfg(cfg).await?; + Timer::after_millis(250).await; + self.bus + .write_read(self.addr, &[Registers::Temp_MSB as u8], &mut buffer) + .await + .map_err(Error::I2c)?; + Ok(i16::from_be_bytes(buffer)) + } +} + +// Web page +const PAGE: &str = r#" + + + + + ADIN1110 with Rust + + +

EVAL-ADIN1110EBZ

+
Temp Sensor ADT7422: #00.00 °C
+ +"#; diff --git a/embassy/examples/stm32l4/src/bin/spi.rs b/embassy/examples/stm32l4/src/bin/spi.rs new file mode 100644 index 0000000..5693a37 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/spi.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use {defmt_rtt as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let mut spi_config = Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new_blocking(p.SPI3, p.PC10, p.PC12, p.PC11, spi_config); + + let mut cs = Output::new(p.PE0, Level::High, Speed::VeryHigh); + + loop { + let mut buf = [0x0Au8; 4]; + cs.set_low(); + unwrap!(spi.blocking_transfer_in_place(&mut buf)); + cs.set_high(); + info!("xfer {=[u8]:x}", buf); + } +} diff --git a/embassy/examples/stm32l4/src/bin/spi_blocking_async.rs b/embassy/examples/stm32l4/src/bin/spi_blocking_async.rs new file mode 100644 index 0000000..1f10891 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/spi_blocking_async.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_embedded_hal::adapter::BlockingAsync; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use embedded_hal_async::spi::SpiBus; +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 spi_config = Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let spi = Spi::new_blocking(p.SPI3, p.PC10, p.PC12, p.PC11, spi_config); + + let mut spi = BlockingAsync::new(spi); + + // These are the pins for the Inventek eS-Wifi SPI Wifi Adapter. + + let _boot = Output::new(p.PB12, Level::Low, Speed::VeryHigh); + let _wake = Output::new(p.PB13, Level::Low, Speed::VeryHigh); + let mut reset = Output::new(p.PE8, Level::Low, Speed::VeryHigh); + let mut cs = Output::new(p.PE0, Level::High, Speed::VeryHigh); + let ready = Input::new(p.PE1, Pull::Up); + + cortex_m::asm::delay(100_000); + reset.set_high(); + cortex_m::asm::delay(100_000); + + while ready.is_low() { + info!("waiting for ready"); + } + + let write: [u8; 10] = [0x0A; 10]; + let mut read: [u8; 10] = [0; 10]; + cs.set_low(); + spi.transfer(&mut read, &write).await.ok(); + cs.set_high(); + info!("xfer {=[u8]:x}", read); +} diff --git a/embassy/examples/stm32l4/src/bin/spi_dma.rs b/embassy/examples/stm32l4/src/bin/spi_dma.rs new file mode 100644 index 0000000..946a759 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/spi_dma.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +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 spi_config = Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new(p.SPI3, p.PC10, p.PC12, p.PC11, p.DMA1_CH1, p.DMA1_CH2, spi_config); + + // These are the pins for the Inventek eS-Wifi SPI Wifi Adapter. + + let _boot = Output::new(p.PB12, Level::Low, Speed::VeryHigh); + let _wake = Output::new(p.PB13, Level::Low, Speed::VeryHigh); + let mut reset = Output::new(p.PE8, Level::Low, Speed::VeryHigh); + let mut cs = Output::new(p.PE0, Level::High, Speed::VeryHigh); + let ready = Input::new(p.PE1, Pull::Up); + + cortex_m::asm::delay(100_000); + reset.set_high(); + cortex_m::asm::delay(100_000); + + while ready.is_low() { + info!("waiting for ready"); + } + + let write = [0x0A; 10]; + let mut read = [0; 10]; + cs.set_low(); + spi.transfer(&mut read, &write).await.ok(); + cs.set_high(); + info!("xfer {=[u8]:x}", read); +} diff --git a/embassy/examples/stm32l4/src/bin/tsc_async.rs b/embassy/examples/stm32l4/src/bin/tsc_async.rs new file mode 100644 index 0000000..b9a059e --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/tsc_async.rs @@ -0,0 +1,108 @@ +// Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the async TSC interface +// 3. Waiting for acquisition completion using `pend_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32L4R5ZI-P board: +// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PB5 (D21) and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 2 of the TSC: +// - PB4 (D25) as the sampling capacitor, TSC group 2 IO1 +// - PB5 (D21) as the channel pin, TSC group 2 IO2 +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `pend_for_acquisition`, and reads the value. +// - The LED (connected to PB14) is turned on when touch is detected (sensor value < SENSOR_THRESHOLD). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let mut pin_group: PinGroupWithRoles = PinGroupWithRoles::default(); + // D25 + pin_group.set_io1::(context.PB4); + // D21 + let tsc_sensor = pin_group.set_io2::(context.PB5); + + let pin_groups: PinGroups = PinGroups { + g2: Some(pin_group.pin_group), + ..Default::default() + }; + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, tsc_conf, Irqs).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + info!("TSC not ready!"); + return; + } + info!("TSC initialized successfully"); + + let mut led = Output::new(context.PB14, Level::High, Speed::Low); + + let discharge_delay = 1; // ms + + info!("Starting touch_controller interface"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + let group_val = touch_controller.group_get_value(tsc_sensor.pin.group()); + info!("Touch value: {}", group_val); + + if group_val < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + + Timer::after_millis(100).await; + } +} diff --git a/embassy/examples/stm32l4/src/bin/tsc_blocking.rs b/embassy/examples/stm32l4/src/bin/tsc_blocking.rs new file mode 100644 index 0000000..12084f8 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/tsc_blocking.rs @@ -0,0 +1,147 @@ +// # Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected +// +// This example demonstrates how to use the Touch Sensing Controller (TSC) in blocking mode on an STM32L4R5ZI-P board. +// +// ## This example demonstrates: +// +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// ## Suggested physical setup on STM32L4R5ZI-P board: +// +// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PB5 (D21) and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// ## Pin Configuration: +// +// The example uses two pins from Group 2 of the TSC: +// - PB4 (D25) as the sampling capacitor, TSC group 2 IO1 +// - PB5 (D21) as the channel pin, TSC group 2 IO2 +// +// ## Program Behavior: +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED (connected to PB14) is turned on when touch is detected (sensor value < SENSOR_THRESHOLD). +// - Touch values are logged to the console. +// +// ## Troubleshooting: +// +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 25). +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// - Be aware that for some boards, there might be overlapping concerns between some pins, +// such as UART connections for the programmer. No errors or warnings will be emitted if you +// try to use such a pin for TSC, but you may get strange sensor readings. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. Refer to the official +// STM32L4R5ZI-P datasheet and user manuals for more information on pin configurations and TSC functionality. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut g2: PinGroupWithRoles = PinGroupWithRoles::default(); + // D25 + g2.set_io1::(context.PB4); + // D21 + let tsc_sensor = g2.set_io2::(context.PB5); + + let pin_groups: PinGroups = PinGroups { + g2: Some(g2.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + info!("TSC initialized successfully"); + + let mut led = Output::new(context.PB14, Level::High, Speed::Low); + + // smaller sample capacitor discharge faster and can be used with shorter delay. + let discharge_delay = 5; // ms + + // the interval at which the loop polls for new touch sensor values + let polling_interval = 100; // ms + + info!("polling for touch"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.poll_for_acquisition(); + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { + Some(v) => { + info!("sensor value {}", v); + if v < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + } + None => led.set_low(), + } + + Timer::after_millis(polling_interval).await; + } +} + +const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; + +// attempt to read group status and delay when still ongoing +async fn read_touch_value( + touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, + sensor_pin: tsc::IOPin, +) -> Option { + for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { + match touch_controller.group_get_status(sensor_pin.group()) { + GroupStatus::Complete => { + return Some(touch_controller.group_get_value(sensor_pin.group())); + } + GroupStatus::Ongoing => { + // if you end up here a lot, then you prob need to increase discharge_delay + // or consider changing the code to adjust the discharge_delay dynamically + info!("Acquisition still ongoing"); + Timer::after_millis(1).await; + } + } + } + None +} diff --git a/embassy/examples/stm32l4/src/bin/tsc_multipin.rs b/embassy/examples/stm32l4/src/bin/tsc_multipin.rs new file mode 100644 index 0000000..8fec5dd --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/tsc_multipin.rs @@ -0,0 +1,198 @@ +// # Example of TSC (Touch Sensing Controller) using multiple pins from the same TSC group +// +// This example demonstrates how to use the Touch Sensing Controller (TSC) with multiple pins, including pins from the same TSC group, on an STM32L4R5ZI-P board. +// +// ## Key Concepts +// +// - Only one TSC pin for each TSC group can be acquired and read at a time. +// - To control which channel pins are acquired and read, we must write a mask before initiating an acquisition. +// - We organize channel pins into acquisition banks to manage this process efficiently. +// - Each acquisition bank can contain exactly one channel pin per TSC group and will contain the relevant mask. +// +// ## This example demonstrates how to: +// +// 1. Configure multiple channel pins within a single TSC group +// 2. Use the set_active_channels_bank method to switch between sets of different channels (acquisition banks) +// 3. Read and interpret touch values from multiple channels in the same group +// +// ## Suggested physical setup on STM32L4R5ZI-P board: +// +// - Connect a 1000pF capacitor between pin PB12 (D19) and GND. This is the sampling capacitor for TSC group 1. +// - Connect one end of a 1K resistor to pin PB13 (D18) and leave the other end loose. This will act as a touch sensor. +// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is the sampling capacitor for TSC group 2. +// - Connect one end of a 1K resistor to pin PB5 (D22) and leave the other end loose. This will act as a touch sensor. +// - Connect one end of another 1K resistor to pin PB6 (D71) and leave the other end loose. This will act as a touch sensor. +// +// ## Pin Configuration: +// +// The example uses pins from two TSC groups: +// +// - Group 1: +// - PB12 (D19) as sampling capacitor (TSC group 1 IO1) +// - PB13 (D18) as channel (TSC group 1 IO2) +// - Group 2: +// - PB4 (D25) as sampling capacitor (TSC group 2 IO1) +// - PB5 (D22) as channel (TSC group 2 IO2) +// - PB6 (D71) as channel (TSC group 2 IO3) +// +// The pins have been chosen for their convenient locations on the STM32L4R5ZI-P board, making it easy to add capacitors and resistors directly to the board without special connectors, breadboards, or soldering. +// +// ## Program Behavior: +// +// The program reads the designated channel pins and adjusts the LED (connected to PB14) blinking pattern based on which sensor(s) are touched: +// +// - No touch: LED off +// - One sensor touched: Slow blinking +// - Two sensors touched: Fast blinking +// - Three sensors touched: LED constantly on +// +// ## Troubleshooting: +// +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 20). +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// - Be aware that for some boards there will be overlapping concerns between some pins, for +// example UART connection for the programmer to the MCU and a TSC pin. No errors or warning will +// be emitted if you try to use such a pin for TSC, but you will get strange sensor readings. +// +// Note: Configuration values and sampling capacitor values have been determined experimentally. Optimal values may vary based on your specific hardware setup. Refer to the official STM32L4R5ZI-P datasheet and user manuals for more information on pin configurations and TSC functionality. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); + +const SENSOR_THRESHOLD: u16 = 20; + +async fn acquire_sensors( + touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Async>, + tsc_acquisition_bank: &AcquisitionBank, +) { + touch_controller.set_active_channels_bank(tsc_acquisition_bank); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + let discharge_delay = 1; // ms + Timer::after_millis(discharge_delay).await; +} + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + // ---------- initial configuration of TSC ---------- + let mut g1: PinGroupWithRoles = PinGroupWithRoles::default(); + g1.set_io1::(context.PB12); + let sensor0 = g1.set_io2::(context.PB13); + + let mut g2: PinGroupWithRoles = PinGroupWithRoles::default(); + g2.set_io1::(context.PB4); + let sensor1 = g2.set_io2(context.PB5); + let sensor2 = g2.set_io3(context.PB6); + + let config = tsc::Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_16, + ct_pulse_low_length: ChargeTransferPulseCycle::_16, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let pin_groups: PinGroups = PinGroups { + g1: Some(g1.pin_group), + g2: Some(g2.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); + + // ---------- setting up acquisition banks ---------- + // sensor0 and sensor1 belong to different TSC-groups, therefore we can acquire and + // read them both in one go. + let bank1 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g1_pin: Some(sensor0), + g2_pin: Some(sensor1), + ..Default::default() + }); + // `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to + // acquire them one at the time. We do this by organizing them into different acquisition banks. + let bank2 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g2_pin: Some(sensor2), + ..Default::default() + }); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + + info!("TSC initialized successfully"); + + let mut led = Output::new(context.PB14, Level::High, Speed::Low); + + let mut led_state = false; + + loop { + acquire_sensors(&mut touch_controller, &bank1).await; + let readings1 = touch_controller.get_acquisition_bank_values(&bank1); + acquire_sensors(&mut touch_controller, &bank2).await; + let readings2 = touch_controller.get_acquisition_bank_values(&bank2); + + let mut touched_sensors_count = 0; + for reading in readings1.iter().chain(readings2.iter()) { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + + match touched_sensors_count { + 0 => { + // No sensors touched, turn off the LED + led.set_low(); + led_state = false; + } + 1 => { + // One sensor touched, blink slowly + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(200).await; + } + 2 => { + // Two sensors touched, blink faster + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(50).await; + } + 3 => { + // All three sensors touched, LED constantly on + led.set_high(); + led_state = true; + } + _ => crate::unreachable!(), // This case should never occur with 3 sensors + } + } +} diff --git a/embassy/examples/stm32l4/src/bin/usart.rs b/embassy/examples/stm32l4/src/bin/usart.rs new file mode 100644 index 0000000..d9b3880 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/usart.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART4 => usart::InterruptHandler; +}); + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let config = Config::default(); + let mut usart = Uart::new_blocking(p.UART4, p.PA1, p.PA0, config).unwrap(); + + unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.blocking_read(&mut buf)); + unwrap!(usart.blocking_write(&buf)); + } +} diff --git a/embassy/examples/stm32l4/src/bin/usart_dma.rs b/embassy/examples/stm32l4/src/bin/usart_dma.rs new file mode 100644 index 0000000..b4f7a16 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/usart_dma.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use heapless::String; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART4 => usart::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, Irqs, p.DMA1_CH3, p.DMA1_CH4, config).unwrap(); + + for n in 0u32.. { + let mut s: String<128> = String::new(); + core::write!(&mut s, "Hello DMA World {}!\r\n", n).unwrap(); + + info!("Writing..."); + usart.write(s.as_bytes()).await.ok(); + + info!("wrote DMA"); + } +} diff --git a/embassy/examples/stm32l4/src/bin/usb_serial.rs b/embassy/examples/stm32l4/src/bin/usb_serial.rs new file mode 100644 index 0000000..c3b1211 --- /dev/null +++ b/embassy/examples/stm32l4/src/bin/usb_serial.rs @@ -0,0 +1,132 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + OTG_FS => usb::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), // sysclk 80Mhz (16 / 1 * 10 / 2) + }); + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; + } + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // 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 compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32l5/.cargo/config.toml b/embassy/examples/stm32l5/.cargo/config.toml new file mode 100644 index 0000000..86a145a --- /dev/null +++ b/embassy/examples/stm32l5/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32L552ZETxQ with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L552ZETxQ" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32l5/Cargo.toml b/embassy/examples/stm32l5/Cargo.toml new file mode 100644 index 0000000..2b8a2c0 --- /dev/null +++ b/embassy/examples/stm32l5/Cargo.toml @@ -0,0 +1,35 @@ +[package] +edition = "2021" +name = "embassy-stm32l5-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32l552ze to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "memory-x", "low-power"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +usbd-hid = "0.8.1" + +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +heapless = { version = "0.8", default-features = false } +rand_core = { version = "0.6.3", default-features = false } +embedded-io-async = { version = "0.6.1" } +static_cell = "2" + +[profile.release] +debug = 2 + +[[bin]] +name = "stop" +default-features = ["embassy-stm32/low-power"] diff --git a/embassy/examples/stm32l5/build.rs b/embassy/examples/stm32l5/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32l5/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/embassy/examples/stm32l5/src/bin/button_exti.rs b/embassy/examples/stm32l5/src/bin/button_exti.rs new file mode 100644 index 0000000..e6639d2 --- /dev/null +++ b/embassy/examples/stm32l5/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); + + 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/embassy/examples/stm32l5/src/bin/rng.rs b/embassy/examples/stm32l5/src/bin/rng.rs new file mode 100644 index 0000000..0a644e7 --- /dev/null +++ b/embassy/examples/stm32l5/src/bin/rng.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rcc::{Pll, PllMul, PllPreDiv, PllRDiv, PllSource, Sysclk}; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, peripherals, rng, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.hsi = true; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + // 64Mhz clock (16 / 1 * 8 / 2) + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL8, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), + }); + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG, Irqs); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); +} diff --git a/embassy/examples/stm32l5/src/bin/stop.rs b/embassy/examples/stm32l5/src/bin/stop.rs new file mode 100644 index 0000000..32a736d --- /dev/null +++ b/embassy/examples/stm32l5/src/bin/stop.rs @@ -0,0 +1,61 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{AnyPin, Level, Output, Speed}; +use embassy_stm32::low_power::Executor; +use embassy_stm32::rcc::LsConfig; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::Config; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + Executor::take().run(|spawner| { + unwrap!(spawner.spawn(async_main(spawner))); + }) +} + +#[embassy_executor::task] +async fn async_main(spawner: Spawner) { + let mut config = Config::default(); + config.rcc.ls = LsConfig::default_lsi(); + // when enabled the power-consumption is much higher during stop, but debugging and RTT is working + // if you wan't to measure the power-consumption, or for production: uncomment this line + // config.enable_debug_during_sleep = false; + let p = embassy_stm32::init(config); + + // give the RTC to the executor... + let rtc = Rtc::new(p.RTC, RtcConfig::default()); + static RTC: StaticCell = StaticCell::new(); + let rtc = RTC.init(rtc); + embassy_stm32::low_power::stop_with_rtc(rtc); + + unwrap!(spawner.spawn(blinky(p.PC7.into()))); + unwrap!(spawner.spawn(timeout())); +} + +#[embassy_executor::task] +async fn blinky(led: AnyPin) -> ! { + let mut led = Output::new(led, Level::Low, Speed::Low); + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} + +// when enable_debug_during_sleep is false, it is more difficult to reprogram the MCU +// therefore we block the MCU after 30s to be able to reprogram it easily +#[embassy_executor::task] +async fn timeout() -> ! { + Timer::after_secs(30).await; + loop {} +} diff --git a/embassy/examples/stm32l5/src/bin/usb_ethernet.rs b/embassy/examples/stm32l5/src/bin/usb_ethernet.rs new file mode 100644 index 0000000..095d50c --- /dev/null +++ b/embassy/examples/stm32l5/src/bin/usb_ethernet.rs @@ -0,0 +1,171 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::StackResources; +use embassy_stm32::rng::Rng; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, rng, usb, 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_async::Write; +use rand_core::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +type MyDriver = Driver<'static, embassy_stm32::peripherals::USB>; + +const MTU: usize = 1514; + +bind_interrupts!(struct Irqs { + USB_FS => usb::InterruptHandler; + RNG => rng::InterruptHandler; +}); + +#[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(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + // 80Mhz clock (16 / 1 * 10 / 2) + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), + }); + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; + } + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // 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. + static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new(); + static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new(); + static CONTROL_BUF: StaticCell<[u8; 128]> = StaticCell::new(); + let mut builder = Builder::new( + driver, + config, + &mut CONFIG_DESC.init([0; 256])[..], + &mut BOS_DESC.init([0; 256])[..], + &mut [], // no msos descriptors + &mut CONTROL_BUF.init([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. + static STATE: StaticCell = StaticCell::new(); + let class = CdcNcmClass::new(&mut builder, STATE.init(State::new()), host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + static NET_STATE: StaticCell> = StaticCell::new(); + let (runner, device) = class.into_embassy_net_device::(NET_STATE.init(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + // 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_time::Duration::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/embassy/examples/stm32l5/src/bin/usb_hid_mouse.rs b/embassy/examples/stm32l5/src/bin/usb_hid_mouse.rs new file mode 100644 index 0000000..3f8c52b --- /dev/null +++ b/embassy/examples/stm32l5/src/bin/usb_hid_mouse.rs @@ -0,0 +1,133 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_time::Timer; +use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State}; +use embassy_usb::control::OutResponse; +use embassy_usb::Builder; +use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_FS => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + // 80Mhz clock (16 / 1 * 10 / 2) + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), + }); + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; + } + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("HID mouse example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut request_handler = MyRequestHandler {}; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: MouseReport::desc(), + request_handler: Some(&mut request_handler), + poll_ms: 60, + max_packet_size: 8, + }; + + let mut writer = HidWriter::<_, 5>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let hid_fut = async { + let mut y: i8 = 5; + loop { + Timer::after_millis(500).await; + + y = -y; + let report = MouseReport { + buttons: 0, + x: 0, + y, + wheel: 0, + pan: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + } + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, hid_fut).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} diff --git a/embassy/examples/stm32l5/src/bin/usb_serial.rs b/embassy/examples/stm32l5/src/bin/usb_serial.rs new file mode 100644 index 0000000..a64bda3 --- /dev/null +++ b/embassy/examples/stm32l5/src/bin/usb_serial.rs @@ -0,0 +1,108 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_FS => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + // 80Mhz clock (16 / 1 * 10 / 2) + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), + }); + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Create embassy-usb Config + let config = embassy_usb::Config::new(0xc0de, 0xcafe); + //config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 7]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32u0/.cargo/config.toml b/embassy/examples/stm32u0/.cargo/config.toml new file mode 100644 index 0000000..6883470 --- /dev/null +++ b/embassy/examples/stm32u0/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace stm32u083rctx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip stm32u083rctx" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32u0/Cargo.toml b/embassy/examples/stm32u0/Cargo.toml new file mode 100644 index 0000000..11953ac --- /dev/null +++ b/embassy/examples/stm32u0/Cargo.toml @@ -0,0 +1,29 @@ +[package] +edition = "2021" +name = "embassy-stm32u0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32u083rc to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32u083rc", "memory-x", "unstable-pac", "exti", "chrono"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +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"] } +heapless = { version = "0.8", default-features = false } + +micromath = "2.0.0" +chrono = { version = "0.4.38", default-features = false } + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32u0/build.rs b/embassy/examples/stm32u0/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32u0/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/embassy/examples/stm32u0/src/bin/adc.rs b/embassy/examples/stm32u0/src/bin/adc.rs new file mode 100644 index 0000000..c8252e4 --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/adc.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::adc::{Adc, Resolution}; +use embassy_stm32::Config; +use embassy_time::Duration; +use {defmt_rtt as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.mux.adcsel = mux::Adcsel::SYS; + } + let p = embassy_stm32::init(config); + + let mut adc = Adc::new(p.ADC1); + adc.set_resolution(Resolution::BITS8); + let mut channel = p.PC0; + + loop { + let v = adc.blocking_read(&mut channel); + info!("--> {}", v); + embassy_time::block_for(Duration::from_millis(200)); + } +} diff --git a/embassy/examples/stm32u0/src/bin/blinky.rs b/embassy/examples/stm32u0/src/bin/blinky.rs new file mode 100644 index 0000000..90e479a --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/embassy/examples/stm32u0/src/bin/button.rs b/embassy/examples/stm32u0/src/bin/button.rs new file mode 100644 index 0000000..8017f02 --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/button.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] + +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/embassy/examples/stm32u0/src/bin/button_exti.rs b/embassy/examples/stm32u0/src/bin/button_exti.rs new file mode 100644 index 0000000..34a08bb --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); + + 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/embassy/examples/stm32u0/src/bin/crc.rs b/embassy/examples/stm32u0/src/bin/crc.rs new file mode 100644 index 0000000..d1b545d --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/crc.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::crc::{Config, Crc, InputReverseConfig, PolySize}; +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!"); + + // Setup for: https://crccalc.com/?crc=Life, it never dieWomen are my favorite guy&method=crc32&datatype=ascii&outtype=0 + let mut crc = Crc::new( + p.CRC, + unwrap!(Config::new( + InputReverseConfig::Byte, + true, + PolySize::Width32, + 0xFFFFFFFF, + 0x04C11DB7 + )), + ); + + let output = crc.feed_bytes(b"Life, it never die\nWomen are my favorite guy") ^ 0xFFFFFFFF; + + defmt::assert_eq!(output, 0x33F0E26B); + + cortex_m::asm::bkpt(); +} diff --git a/embassy/examples/stm32u0/src/bin/dac.rs b/embassy/examples/stm32u0/src/bin/dac.rs new file mode 100644 index 0000000..fdbf1d3 --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/dac.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::dac::{DacCh1, Value}; +use embassy_stm32::dma::NoDma; +use {defmt_rtt as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); + + loop { + for v in 0..=255 { + dac.set(Value::Bit8(to_sine_wave(v))); + } + } +} + +use micromath::F32Ext; + +fn to_sine_wave(v: u8) -> u8 { + if v >= 128 { + // top half + let r = 3.14 * ((v - 128) as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } else { + // bottom half + let r = 3.14 + 3.14 * (v as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } +} diff --git a/embassy/examples/stm32u0/src/bin/flash.rs b/embassy/examples/stm32u0/src/bin/flash.rs new file mode 100644 index 0000000..01b80a7 --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/flash.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +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 addr: u32 = 0x40000 - 2 * 1024; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(addr, &mut buf)); + info!("Read: {=[u8]:x}", buf); + info!("Erasing..."); + unwrap!(f.blocking_erase(addr, addr + 2 * 1024)); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(addr, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write( + addr, + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + )); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(addr, &mut buf)); + info!("Read: {=[u8]:x}", buf); +} diff --git a/embassy/examples/stm32u0/src/bin/i2c.rs b/embassy/examples/stm32u0/src/bin/i2c.rs new file mode 100644 index 0000000..2861bc0 --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/i2c.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::I2c; +use embassy_stm32::time::Hertz; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut i2c = I2c::new_blocking(p.I2C2, p.PB10, p.PB11, Hertz(100_000), Default::default()); + + let mut data = [0u8; 1]; + unwrap!(i2c.blocking_write_read(ADDRESS, &[WHOAMI], &mut data)); + info!("Whoami: {}", data[0]); +} diff --git a/embassy/examples/stm32u0/src/bin/rng.rs b/embassy/examples/stm32u0/src/bin/rng.rs new file mode 100644 index 0000000..89445b0 --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/rng.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rcc::mux::Clk48sel; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, peripherals, rng, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG_CRYP => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, // 16 MHz + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL7, // 16 * 7 = 112 MHz + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), // 112 / 2 = 56 MHz + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: false }); // needed for RNG + config.rcc.mux.clk48sel = Clk48sel::HSI48; // needed for RNG (or use MSI or PLLQ if you want) + } + + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG, Irqs); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); +} diff --git a/embassy/examples/stm32u0/src/bin/rtc.rs b/embassy/examples/stm32u0/src/bin/rtc.rs new file mode 100644 index 0000000..72fa0fd --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/rtc.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use chrono::{NaiveDate, NaiveDateTime}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, // 16 MHz + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL7, // 16 * 7 = 112 MHz + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), // 112 / 2 = 56 MHz + }); + config.rcc.ls = LsConfig::default(); + } + + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + info!("Got RTC! {:?}", now.and_utc().timestamp()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + // In reality the delay would be much longer + Timer::after_millis(20000).await; + + let then: NaiveDateTime = rtc.now().unwrap().into(); + info!("Got RTC! {:?}", then.and_utc().timestamp()); +} diff --git a/embassy/examples/stm32u0/src/bin/spi.rs b/embassy/examples/stm32u0/src/bin/spi.rs new file mode 100644 index 0000000..5693a37 --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/spi.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use {defmt_rtt as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let mut spi_config = Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new_blocking(p.SPI3, p.PC10, p.PC12, p.PC11, spi_config); + + let mut cs = Output::new(p.PE0, Level::High, Speed::VeryHigh); + + loop { + let mut buf = [0x0Au8; 4]; + cs.set_low(); + unwrap!(spi.blocking_transfer_in_place(&mut buf)); + cs.set_high(); + info!("xfer {=[u8]:x}", buf); + } +} diff --git a/embassy/examples/stm32u0/src/bin/usart.rs b/embassy/examples/stm32u0/src/bin/usart.rs new file mode 100644 index 0000000..037a5c8 --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/usart.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::usart::{Config, Uart}; +use {defmt_rtt as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let config = Config::default(); + let mut usart = Uart::new_blocking(p.USART2, p.PA3, p.PA2, config).unwrap(); + + unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.blocking_read(&mut buf)); + unwrap!(usart.blocking_write(&buf)); + } +} diff --git a/embassy/examples/stm32u0/src/bin/usb_serial.rs b/embassy/examples/stm32u0/src/bin/usb_serial.rs new file mode 100644 index 0000000..273f406 --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/usb_serial.rs @@ -0,0 +1,109 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + USB_DRD_FS => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, // 16 MHz + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL7, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), // 56 MHz + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; // USB uses ICLK + } + + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Create embassy-usb Config + let config = embassy_usb::Config::new(0xc0de, 0xcafe); + //config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 7]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32u0/src/bin/wdt.rs b/embassy/examples/stm32u0/src/bin/wdt.rs new file mode 100644 index 0000000..f6276e2 --- /dev/null +++ b/embassy/examples/stm32u0/src/bin/wdt.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::wdg::IndependentWatchdog; +use embassy_time::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); + + let mut wdt = IndependentWatchdog::new(p.IWDG, 1_000_000); + wdt.unleash(); + + let mut i = 0; + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + + // Pet watchdog for 5 iterations and then stop. + // MCU should restart in 1 second after the last pet. + if i < 5 { + info!("Petting watchdog"); + wdt.pet(); + } + + i += 1; + } +} diff --git a/embassy/examples/stm32u5/.cargo/config.toml b/embassy/examples/stm32u5/.cargo/config.toml new file mode 100644 index 0000000..bdbd863 --- /dev/null +++ b/embassy/examples/stm32u5/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32U5G9ZJTxQ with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32U5G9ZJTxQ" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32u5/Cargo.toml b/embassy/examples/stm32u5/Cargo.toml new file mode 100644 index 0000000..68a17ce --- /dev/null +++ b/embassy/examples/stm32u5/Cargo.toml @@ -0,0 +1,34 @@ +[package] +edition = "2021" +name = "embassy-stm32u5-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32u5g9zj to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u5g9zj", "time-driver-any", "memory-x" ] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +embedded-graphics = { version = "0.8.1" } +tinybmp = { version = "0.6.0" } + +micromath = "2.0.0" + +[features] +## Use secure registers when TrustZone is enabled +trustzone-secure = ["embassy-stm32/trustzone-secure"] + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32u5/build.rs b/embassy/examples/stm32u5/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32u5/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/embassy/examples/stm32u5/src/bin/blinky.rs b/embassy/examples/stm32u5/src/bin/blinky.rs new file mode 100644 index 0000000..7fe88c1 --- /dev/null +++ b/embassy/examples/stm32u5/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PH7, Level::Low, Speed::Medium); + + loop { + defmt::info!("on!"); + led.set_low(); + Timer::after_millis(200).await; + + defmt::info!("off!"); + led.set_high(); + Timer::after_millis(200).await; + } +} diff --git a/embassy/examples/stm32u5/src/bin/boot.rs b/embassy/examples/stm32u5/src/bin/boot.rs new file mode 100644 index 0000000..23c7f8b --- /dev/null +++ b/embassy/examples/stm32u5/src/bin/boot.rs @@ -0,0 +1,14 @@ +#![no_std] +#![no_main] + +use defmt::*; +use {defmt_rtt as _, embassy_stm32 as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("Hello World!"); + + loop { + //defmt::info!("loop!"); + } +} diff --git a/embassy/examples/stm32u5/src/bin/ferris.bmp b/embassy/examples/stm32u5/src/bin/ferris.bmp new file mode 100644 index 0000000000000000000000000000000000000000..7a222ab84ddd9f2707ca8dcebad84b1b39c21818 GIT binary patch literal 6794 zcmb`L33OD|8OQ(JNnU0$OJ>O|nIvR^V8})iwgjaFsH`H2vdNBYA}T_u7=r{^1}IQL zRKN`(6x0McMUzp1q-luKI(W`lj>IPOV3HPUtHrL?CG_66&B`R_wD+9%?zjHl_wKvz zzB@B!%1$SQwlfZ>qS6d{RM4Xj)bvc74X|j%gBqCw_y)L=0Aw&fZDYnyTPsSvKa;4T z@qpl<@ILJNN$8_Wgep#pIF%W3oDN*P1&Fr-oDF^AozTRmfFz_+Uk2jTX^2mtRVDNX z)LBT-WCDrVAetPg)VWY44nVwSAh^UlsI_@iFNQX85R4WR`sfM}r!9d_TMSL&FmSpO zBUmpCLzH%8FA+E(3tL``ZUB_CLqx~1qsO$pt4Mb)^ayA z<{99u_d;))OKY5tM9WMh*rq|3JPT^ed^!xXkwkk<@&Y;>55Qtx3blO>wAMv1Sr;PS z{vh=B#V}e|z+!m}oMQ>Jwv|w)EQQIw5<17j&^VXD>{tb>Z4LA(tDsF=frQk@piNna z{K5jL(^f<0e1hKN25_nCk(mBC^t897JdK33C!y`T23lt|3{=i5Do3(o8`SBQu%~W> zE^QMWY1JqmT!O@mDkS&a413xR7&5lNp1u>#)VEqjul+4{QWbMK5@-o;no`<&o4kTqi3rFS)u=RZvx~v08>bDmuSuaALeGta} z`{2wzfW(~VV9z`ZL%%~XQ8}6Rj;z;V&OU;qoR^S7`#VOBq`Vp!2fPAH|D#Buhdui( z=yHz1nNtJ(fM39r`x>mYH{5m{=A4sAp+{2g30MZa4JSQ}d2b>)_Y@oh-h(OsEg14n z!<_p*Y_w0IeNz5AunhbFPI@HgorSTm9;TuOSPRa`>c{k1GvFBdIgG>Fkvyam)}dXPJ@)~m4*3$c z;eUi>=vT0pU4x_iI+BOqfN{hZu$Fy|`HL1{!QzE@qH;afZD_!bT|2R&@^kEdb`O?3 zc>|mGe}&_3oSPBYs&^!;CwOp0 z|76MaR3My?J5mE$D0dt`F5tRH%H`#76;X+e#ylrED+2MVm%VmH3*j!~`H_l;NQ204 z)a8tvGH>n1?a!0Dn%xa`HT$+LpEg4Bx&tGjI?T5yS}=DJeaPtwcc5HupeHK>vLtzf z29DmtX*oTJ`v48~s$~aR9>L&AMWn4zNg(T}cao$AxT9H4b0BIX(?c5}p*quo9>@%) zOV3#0<_IsqB88jY$f80UA=^R?R2!&dkW%Js5QkXi8pQ-Q3{HB|9TxSF^P7ZNJ3=f> znZZEWMf4;xMhF*@x#BC(L~Wy7M)IS!h;*J8rg3?sqftXhU)TLKRq%O*kr2761~Ggc zAwyi1>cNr8Jl~=f3b|8y)q+BrHH34y#uFl}8~kAwCvxRyKa31@xqezG=6Kg#2qbs? zOrX+}smUKOi&8Tot6Z*`WTT+^*xMm+?~nq$oYUoUt@x0sZSx7?_O*2h&3obC{c~q6 zoV8%?y?0L-{*uSbtp3(5D)q}k>UuJsCf+_m~>XFZ`yzK6NPB5AI)^lknUuTSZ||* z^$++7^Rfx&dw~koRU7uda^jS`#dFzzO)Q+9uXSDaw7O3o+q1Q*n%-SK%ig78nOmi? zzre9^*e+rD76Dt=#q<7FS9SFfx5q0?YS|yw`+V(Zk5pHSFwcLBlS#i(g+ITp7(~I}<#~6%e7!cn9p(>H zmoO@AoOCPxc4;NdjeMc~a=Cm9usRGBqISI_Qt5fLnB=#~miY=LBt=l_0DI(h196@5 zHFVQCY%z#+_nv)Yi%Usk{4!9Sp;@un7NJ^84=Y?**{@ z@FOzmCqq~#$;!Yi3JvO<_>b=H?!PjwR-BNplYddm6>2br&onXB=QdKO$R%~5I7Rb+ z{2xmBJLBexciln0qn1BY!=usXLmdm2xWg1sxZwYmQodx7XGHGRhrXefFX*e$7#=I> zcLuof!X72^2BkbfBey8k{e@Zv5wch@gk^cyM_m|)+AvSBdg-fwe;RKpJRG20`ud-b ztI`idA6=IEhQw8etSZ4!LH2n&yuOervDSbNZ)A@9Lk;4bM~zx#s(K0%6;nASv`A%{lCl(;i}$X*|Ar* zsC?lQ<&~MN58;w!{Dsv{Zg$k%7{)uflqJo4&<-G+ON6y3Ck7&_E6N`yq4kP)67^?R zRG?%GY>Jejh{mmm#fav+O~ENbH10T-+Y#%dr@&tmUZipbrwGxw^sC=Aog=-Nbf<}G zD&2);DL6%l#wBA1Gr#;Fj#6Btu$jdTn=CwSCI(tmuIL^t?7teL?!vs{mTqzY6eh*s zrn0enm^dTLRzw_ literal 0 HcmV?d00001 diff --git a/embassy/examples/stm32u5/src/bin/flash.rs b/embassy/examples/stm32u5/src/bin/flash.rs new file mode 100644 index 0000000..e4fd6bb --- /dev/null +++ b/embassy/examples/stm32u5/src/bin/flash.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +use embassy_time::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 Flash!"); + + const ADDR: u32 = 0x8_0000; // This is the offset into the third region, the absolute address is 4x32K + 128K + 0x8_0000. + + // wait a bit before accessing the flash + Timer::after_millis(300).await; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.blocking_erase(ADDR, ADDR + 256 * 1024)); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write( + ADDR, + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + )); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!( + &buf[..], + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + ); +} diff --git a/embassy/examples/stm32u5/src/bin/i2c.rs b/embassy/examples/stm32u5/src/bin/i2c.rs new file mode 100644 index 0000000..d5f5d6f --- /dev/null +++ b/embassy/examples/stm32u5/src/bin/i2c.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::i2c::I2c; +use embassy_stm32::time::Hertz; +use {defmt_rtt as _, panic_probe as _}; + +const HTS221_ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut i2c = I2c::new_blocking(p.I2C2, p.PF1, p.PF0, Hertz(100_000), Default::default()); + + let mut data = [0u8; 1]; + unwrap!(i2c.blocking_write_read(HTS221_ADDRESS, &[WHOAMI], &mut data)); + + // HTS221 data sheet is here: https://www.st.com/resource/en/datasheet/hts221.pdf + // 7.1 WHO_AM_I command is x0F which expected response xBC. + info!("Whoami: 0x{:02x}", data[0]); + assert_eq!(0xBC, data[0]); +} diff --git a/embassy/examples/stm32u5/src/bin/ltdc.rs b/embassy/examples/stm32u5/src/bin/ltdc.rs new file mode 100644 index 0000000..bd59a91 --- /dev/null +++ b/embassy/examples/stm32u5/src/bin/ltdc.rs @@ -0,0 +1,461 @@ +#![no_std] +#![no_main] +#![macro_use] +#![allow(static_mut_refs)] + +/// This example was derived from examples\stm32h735\src\bin\ltdc.rs +/// It demonstrates the LTDC lcd display peripheral and was tested on an STM32U5G9J-DK2 demo board (embassy-stm32 feature "stm32u5g9zj" and probe-rs chip "STM32U5G9ZJTxQ") +/// +use bouncy_box::BouncyBox; +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::{Duration, Timer}; +use embedded_graphics::draw_target::DrawTarget; +use embedded_graphics::geometry::{OriginDimensions, Point, Size}; +use embedded_graphics::image::Image; +use embedded_graphics::pixelcolor::raw::RawU24; +use embedded_graphics::pixelcolor::Rgb888; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::Rectangle; +use embedded_graphics::Pixel; +use heapless::{Entry, FnvIndexMap}; +use tinybmp::Bmp; +use {defmt_rtt as _, panic_probe as _}; + +const DISPLAY_WIDTH: usize = 800; +const DISPLAY_HEIGHT: usize = 480; +const MY_TASK_POOL_SIZE: usize = 2; + +// the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu +pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; +pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; + +bind_interrupts!(struct Irqs { + LTDC => ltdc::InterruptHandler; +}); + +const NUM_COLORS: usize = 256; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = rcc_setup::stm32u5g9zj_init(); + + // enable ICACHE + embassy_stm32::pac::ICACHE.cr().write(|w| { + w.set_en(true); + }); + + // blink the led on another task + let led = Output::new(p.PD2, Level::High, Speed::Low); + unwrap!(spawner.spawn(led_task(led))); + + // numbers from STM32U5G9J-DK2.ioc + const RK050HR18H_HSYNC: u16 = 5; // Horizontal synchronization + const RK050HR18H_HBP: u16 = 8; // Horizontal back porch + const RK050HR18H_HFP: u16 = 8; // Horizontal front porch + const RK050HR18H_VSYNC: u16 = 5; // Vertical synchronization + const RK050HR18H_VBP: u16 = 8; // Vertical back porch + const RK050HR18H_VFP: u16 = 8; // Vertical front porch + + // NOTE: all polarities have to be reversed with respect to the STM32U5G9J-DK2 CubeMX parametrization + let ltdc_config = LtdcConfiguration { + active_width: DISPLAY_WIDTH as _, + active_height: DISPLAY_HEIGHT as _, + h_back_porch: RK050HR18H_HBP, + h_front_porch: RK050HR18H_HFP, + v_back_porch: RK050HR18H_VBP, + v_front_porch: RK050HR18H_VFP, + h_sync: RK050HR18H_HSYNC, + v_sync: RK050HR18H_VSYNC, + h_sync_polarity: PolarityActive::ActiveHigh, + v_sync_polarity: PolarityActive::ActiveHigh, + data_enable_polarity: PolarityActive::ActiveHigh, + pixel_clock_polarity: PolarityEdge::RisingEdge, + }; + + info!("init ltdc"); + let mut ltdc_de = Output::new(p.PD6, Level::Low, Speed::High); + let mut ltdc_disp_ctrl = Output::new(p.PE4, Level::Low, Speed::High); + let mut ltdc_bl_ctrl = Output::new(p.PE6, Level::Low, Speed::High); + let mut ltdc = Ltdc::new_with_pins( + p.LTDC, // PERIPHERAL + Irqs, // IRQS + p.PD3, // CLK + p.PE0, // HSYNC + p.PD13, // VSYNC + p.PB9, // B0 + p.PB2, // B1 + p.PD14, // B2 + p.PD15, // B3 + p.PD0, // B4 + p.PD1, // B5 + p.PE7, // B6 + p.PE8, // B7 + p.PC8, // G0 + p.PC9, // G1 + p.PE9, // G2 + p.PE10, // G3 + p.PE11, // G4 + p.PE12, // G5 + p.PE13, // G6 + p.PE14, // G7 + p.PC6, // R0 + p.PC7, // R1 + p.PE15, // R2 + p.PD8, // R3 + p.PD9, // R4 + p.PD10, // R5 + p.PD11, // R6 + p.PD12, // R7 + ); + ltdc.init(<dc_config); + ltdc_de.set_low(); + ltdc_bl_ctrl.set_high(); + ltdc_disp_ctrl.set_high(); + + // we only need to draw on one layer for this example (not to be confused with the double buffer) + info!("enable bottom layer"); + let layer_config = LtdcLayerConfig { + pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel + layer: LtdcLayer::Layer1, + window_x0: 0, + window_x1: DISPLAY_WIDTH as _, + window_y0: 0, + window_y1: DISPLAY_HEIGHT as _, + }; + + let ferris_bmp: Bmp = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap(); + let color_map = build_color_lookup_map(&ferris_bmp); + let clut = build_clut(&color_map); + + // enable the bottom layer with a 256 color lookup table + ltdc.init_layer(&layer_config, Some(&clut)); + + // Safety: the DoubleBuffer controls access to the statically allocated frame buffers + // and it is the only thing that mutates their content + let mut double_buffer = DoubleBuffer::new( + unsafe { FB1.as_mut() }, + unsafe { FB2.as_mut() }, + layer_config, + color_map, + ); + + // this allows us to perform some simple animation for every frame + let mut bouncy_box = BouncyBox::new( + ferris_bmp.bounding_box(), + Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)), + 2, + ); + + loop { + // cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen + double_buffer.clear(); + let position = bouncy_box.next_point(); + let ferris = Image::new(&ferris_bmp, position); + unwrap!(ferris.draw(&mut double_buffer)); + + // perform async dma data transfer to the lcd screen + unwrap!(double_buffer.swap(&mut ltdc).await); + } +} + +/// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work. +fn build_color_lookup_map(bmp: &Bmp) -> FnvIndexMap { + let mut color_map: FnvIndexMap = heapless::FnvIndexMap::new(); + let mut counter: u8 = 0; + + // add black to position 0 + color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap(); + counter += 1; + + for Pixel(_point, color) in bmp.pixels() { + let raw = color.into_storage(); + if let Entry::Vacant(v) = color_map.entry(raw) { + v.insert(counter).expect("more than 256 colors detected"); + counter += 1; + } + } + color_map +} + +/// builds the color look-up table from the color map provided +fn build_clut(color_map: &FnvIndexMap) -> [ltdc::RgbColor; NUM_COLORS] { + let mut clut = [ltdc::RgbColor::default(); NUM_COLORS]; + for (color, index) in color_map.iter() { + let color = Rgb888::from(RawU24::new(*color)); + clut[*index as usize] = ltdc::RgbColor { + red: color.r(), + green: color.g(), + blue: color.b(), + }; + } + + clut +} + +#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)] +async fn led_task(mut led: Output<'static>) { + let mut counter = 0; + loop { + info!("blink: {}", counter); + counter += 1; + + // on + led.set_low(); + Timer::after(Duration::from_millis(50)).await; + + // off + led.set_high(); + Timer::after(Duration::from_millis(450)).await; + } +} + +pub type TargetPixelType = u8; + +// A simple double buffer +pub struct DoubleBuffer { + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + is_buf0: bool, + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, +} + +impl DoubleBuffer { + pub fn new( + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, + ) -> Self { + Self { + buf0, + buf1, + is_buf0: true, + layer_config, + color_map, + } + } + + pub fn current(&mut self) -> (&FnvIndexMap, &mut [TargetPixelType]) { + if self.is_buf0 { + (&self.color_map, self.buf0) + } else { + (&self.color_map, self.buf1) + } + } + + pub async fn swap(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> { + let (_, buf) = self.current(); + let frame_buffer = buf.as_ptr(); + self.is_buf0 = !self.is_buf0; + ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await + } + + /// Clears the buffer + pub fn clear(&mut self) { + let (color_map, buf) = self.current(); + let black = Rgb888::new(0, 0, 0).into_storage(); + let color_index = color_map.get(&black).expect("no black found in the color map"); + + for a in buf.iter_mut() { + *a = *color_index; // solid black + } + } +} + +// Implement DrawTarget for +impl DrawTarget for DoubleBuffer { + type Color = Rgb888; + type Error = (); + + /// Draw a pixel + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + let size = self.size(); + let width = size.width as i32; + let height = size.height as i32; + let (color_map, buf) = self.current(); + + for pixel in pixels { + let Pixel(point, color) = pixel; + + if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height { + let index = point.y * width + point.x; + let raw_color = color.into_storage(); + + match color_map.get(&raw_color) { + Some(x) => { + buf[index as usize] = *x; + } + None => panic!("color not found in color map: {}", raw_color), + }; + } else { + // Ignore invalid points + } + } + + Ok(()) + } +} + +impl OriginDimensions for DoubleBuffer { + /// Return the size of the display + fn size(&self) -> Size { + Size::new( + (self.layer_config.window_x1 - self.layer_config.window_x0) as _, + (self.layer_config.window_y1 - self.layer_config.window_y0) as _, + ) + } +} + +mod rcc_setup { + + use embassy_stm32::time::Hertz; + use embassy_stm32::{rcc, Config, Peripherals}; + + /// Sets up clocks for the stm32u5g9zj mcu + /// change this if you plan to use a different microcontroller + pub fn stm32u5g9zj_init() -> Peripherals { + // setup power and clocks for an STM32U5G9J-DK2 run from an external 16 Mhz external oscillator + let mut config = Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: Hertz(16_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.pll1 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV1, + mul: rcc::PllMul::MUL10, + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV1), + }); + config.rcc.sys = rcc::Sysclk::PLL1_R; // 160 Mhz + config.rcc.pll3 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV4, // PLL_M + mul: rcc::PllMul::MUL125, // PLL_N + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV20), + }); + config.rcc.mux.ltdcsel = rcc::mux::Ltdcsel::PLL3_R; // 25 MHz + embassy_stm32::init(config) + } +} + +mod bouncy_box { + use embedded_graphics::geometry::Point; + use embedded_graphics::primitives::Rectangle; + + enum Direction { + DownLeft, + DownRight, + UpLeft, + UpRight, + } + + pub struct BouncyBox { + direction: Direction, + child_rect: Rectangle, + parent_rect: Rectangle, + current_point: Point, + move_by: usize, + } + + // This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box + impl BouncyBox { + pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self { + let center_box = parent_rect.center(); + let center_img = child_rect.center(); + let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2); + Self { + direction: Direction::DownRight, + child_rect, + parent_rect, + current_point, + move_by, + } + } + + pub fn next_point(&mut self) -> Point { + let direction = &self.direction; + let img_height = self.child_rect.size.height as i32; + let box_height = self.parent_rect.size.height as i32; + let img_width = self.child_rect.size.width as i32; + let box_width = self.parent_rect.size.width as i32; + let move_by = self.move_by as i32; + + match direction { + Direction::DownLeft => { + self.current_point.x -= move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } + } + Direction::DownRight => { + self.current_point.x += move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } + } + Direction::UpLeft => { + self.current_point.x -= move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } + } + Direction::UpRight => { + self.current_point.x += move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } + } + } + + self.current_point + } + } +} diff --git a/embassy/examples/stm32u5/src/bin/rng.rs b/embassy/examples/stm32u5/src/bin/rng.rs new file mode 100644 index 0000000..3a5bce0 --- /dev/null +++ b/embassy/examples/stm32u5/src/bin/rng.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, peripherals, rng}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG, Irqs); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); +} diff --git a/embassy/examples/stm32u5/src/bin/tsc.rs b/embassy/examples/stm32u5/src/bin/tsc.rs new file mode 100644 index 0000000..a85acc4 --- /dev/null +++ b/embassy/examples/stm32u5/src/bin/tsc.rs @@ -0,0 +1,84 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); + +#[cortex_m_rt::exception] +unsafe fn HardFault(_: &cortex_m_rt::ExceptionFrame) -> ! { + cortex_m::peripheral::SCB::sys_reset(); +} + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let config = tsc::Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_2, + ct_pulse_low_length: ChargeTransferPulseCycle::_2, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_4, + max_count_value: MaxCount::_8191, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut g1: PinGroupWithRoles = PinGroupWithRoles::default(); + g1.set_io2::(context.PB13); + g1.set_io3::(context.PB14); + + let mut g2: PinGroupWithRoles = PinGroupWithRoles::default(); + g2.set_io1::(context.PB4); + let sensor0 = g2.set_io2(context.PB5); + + let mut g7: PinGroupWithRoles = PinGroupWithRoles::default(); + g7.set_io2::(context.PE3); + let sensor1 = g7.set_io3(context.PE4); + + let pin_groups: PinGroups = PinGroups { + g1: Some(g1.pin_group), + g2: Some(g2.pin_group), + g7: Some(g7.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); + + let acquisition_bank = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g2_pin: Some(sensor0), + g7_pin: Some(sensor1), + ..Default::default() + }); + + touch_controller.set_active_channels_bank(&acquisition_bank); + + info!("Starting touch_controller interface"); + loop { + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + Timer::after_millis(1).await; + + let status = touch_controller.get_acquisition_bank_status(&acquisition_bank); + + if status.all_complete() { + let read_values = touch_controller.get_acquisition_bank_values(&acquisition_bank); + let group2_reading = read_values.get_group_reading(Group::Two).unwrap(); + let group7_reading = read_values.get_group_reading(Group::Seven).unwrap(); + info!("group 2 value: {}", group2_reading.sensor_value); + info!("group 7 value: {}", group7_reading.sensor_value); + } + } +} diff --git a/embassy/examples/stm32u5/src/bin/usb_hs_serial.rs b/embassy/examples/stm32u5/src/bin/usb_hs_serial.rs new file mode 100644 index 0000000..5549e2c --- /dev/null +++ b/embassy/examples/stm32u5/src/bin/usb_hs_serial.rs @@ -0,0 +1,129 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + OTG_HS => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + use embassy_stm32::time::Hertz; + config.rcc.hse = Some(Hse { + freq: Hertz(16_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, // HSE / 2 = 8MHz + mul: PllMul::MUL60, // 8MHz * 60 = 480MHz + divr: Some(PllDiv::DIV3), // 480MHz / 3 = 160MHz (sys_ck) + divq: Some(PllDiv::DIV10), // 480MHz / 10 = 48MHz (USB) + divp: Some(PllDiv::DIV15), // 480MHz / 15 = 32MHz (USBOTG) + }); + config.rcc.mux.otghssel = mux::Otghssel::PLL1_P; + config.rcc.voltage_range = VoltageScale::RANGE1; + config.rcc.sys = Sysclk::PLL1_R; + } + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32u5/src/bin/usb_serial.rs b/embassy/examples/stm32u5/src/bin/usb_serial.rs new file mode 100644 index 0000000..4bb1a60 --- /dev/null +++ b/embassy/examples/stm32u5/src/bin/usb_serial.rs @@ -0,0 +1,126 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + OTG_HS => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, // 16 MHz + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllDiv::DIV1), // 160 MHz + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.voltage_range = VoltageScale::RANGE1; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.mux.iclksel = mux::Iclksel::HSI48; // USB uses ICLK + } + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/embassy/examples/stm32wb/.cargo/config.toml b/embassy/examples/stm32wb/.cargo/config.toml new file mode 100644 index 0000000..8b6d6d7 --- /dev/null +++ b/embassy/examples/stm32wb/.cargo/config.toml @@ -0,0 +1,10 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32WB55CCUx with your chip as listed in `probe-rs chip list` +# runner = "probe-rs run --chip STM32WB55RGVx --speed 1000 --connect-under-reset" +runner = "teleprobe local run --chip STM32WB55RG --elf" + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32wb/Cargo.toml b/embassy/examples/stm32wb/Cargo.toml new file mode 100644 index 0000000..ecc7239 --- /dev/null +++ b/embassy/examples/stm32wb/Cargo.toml @@ -0,0 +1,56 @@ +[package] +edition = "2021" +name = "embassy-stm32wb-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32wb55rg to your chip name in both dependencies, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] } +embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", features = ["defmt", "stm32wb55rg"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +static_cell = "2" + +[features] +default = ["ble", "mac"] +mac = ["embassy-stm32-wpan/mac", "dep:embassy-net"] +ble = ["embassy-stm32-wpan/ble"] + +[[bin]] +name = "tl_mbox_ble" +required-features = ["ble"] + +[[bin]] +name = "tl_mbox_mac" +required-features = ["mac"] + +[[bin]] +name = "mac_ffd" +required-features = ["mac"] + +[[bin]] +name = "mac_ffd_net" +required-features = ["mac"] + +[[bin]] +name = "eddystone_beacon" +required-features = ["ble"] + +[[bin]] +name = "gatt_server" +required-features = ["ble"] + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32wb/build.rs b/embassy/examples/stm32wb/build.rs new file mode 100644 index 0000000..29b3a9b --- /dev/null +++ b/embassy/examples/stm32wb/build.rs @@ -0,0 +1,11 @@ +use std::error::Error; + +fn main() -> Result<(), Box> { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rerun-if-changed=link.x"); + println!("cargo:rustc-link-arg-bins=-Ttl_mbox.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + + Ok(()) +} diff --git a/embassy/examples/stm32wb/src/bin/blinky.rs b/embassy/examples/stm32wb/src/bin/blinky.rs new file mode 100644 index 0000000..f37e8b1 --- /dev/null +++ b/embassy/examples/stm32wb/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PB0, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(500).await; + + info!("low"); + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32wb/src/bin/button_exti.rs b/embassy/examples/stm32wb/src/bin/button_exti.rs new file mode 100644 index 0000000..2871fd5 --- /dev/null +++ b/embassy/examples/stm32wb/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC4, p.EXTI4, Pull::Up); + + 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/embassy/examples/stm32wb/src/bin/eddystone_beacon.rs b/embassy/examples/stm32wb/src/bin/eddystone_beacon.rs new file mode 100644 index 0000000..3bd8b4a --- /dev/null +++ b/embassy/examples/stm32wb/src/bin/eddystone_beacon.rs @@ -0,0 +1,248 @@ +#![no_std] +#![no_main] + +use core::time::Duration; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32_wpan::hci::host::uart::UartHci; +use embassy_stm32_wpan::hci::host::{AdvertisingFilterPolicy, EncryptionKey, HostHci, OwnAddressType}; +use embassy_stm32_wpan::hci::types::AdvertisingType; +use embassy_stm32_wpan::hci::vendor::command::gap::{AdvertisingDataType, DiscoverableParameters, GapCommands, Role}; +use embassy_stm32_wpan::hci::vendor::command::gatt::GattCommands; +use embassy_stm32_wpan::hci::vendor::command::hal::{ConfigData, HalCommands, PowerLevel}; +use embassy_stm32_wpan::hci::BdAddr; +use embassy_stm32_wpan::lhci::LhciC1DeviceInformationCcrp; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +const BLE_GAP_DEVICE_NAME_LENGTH: u8 = 7; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Mac_802_15_4_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Mac_802_15_4_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let config = Config::default(); + let mut mbox = TlMbox::init(p.IPCC, Irqs, config); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await; + + info!("resetting BLE..."); + mbox.ble_subsystem.reset().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config public address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::public_address(get_bd_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config random address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::random_address(get_random_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config identity root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::identity_root(&get_irk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config encryption root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::encryption_root(&get_erk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config tx power level..."); + mbox.ble_subsystem.set_tx_power_level(PowerLevel::ZerodBm).await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("GATT init..."); + mbox.ble_subsystem.init_gatt().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("GAP init..."); + mbox.ble_subsystem + .init_gap(Role::PERIPHERAL, false, BLE_GAP_DEVICE_NAME_LENGTH) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + // info!("set scan response..."); + // mbox.ble_subsystem.le_set_scan_response_data(&[]).await.unwrap(); + // let response = mbox.ble_subsystem.read().await.unwrap(); + // defmt::info!("{}", response); + + info!("set discoverable..."); + mbox.ble_subsystem + .set_discoverable(&DiscoverableParameters { + advertising_type: AdvertisingType::NonConnectableUndirected, + advertising_interval: Some((Duration::from_millis(250), Duration::from_millis(250))), + address_type: OwnAddressType::Public, + filter_policy: AdvertisingFilterPolicy::AllowConnectionAndScan, + local_name: None, + advertising_data: &[], + conn_interval: (None, None), + }) + .await + .unwrap(); + + let response = mbox.ble_subsystem.read().await; + defmt::info!("{}", response); + + // remove some advertisement to decrease the packet size + info!("delete tx power ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::TxPowerLevel) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("delete conn interval ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::PeripheralConnectionInterval) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("update advertising data..."); + mbox.ble_subsystem + .update_advertising_data(&eddystone_advertising_data()) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("update advertising data type..."); + mbox.ble_subsystem + .update_advertising_data(&[3, AdvertisingDataType::UuidCompleteList16 as u8, 0xaa, 0xfe]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("update advertising data flags..."); + mbox.ble_subsystem + .update_advertising_data(&[ + 2, + AdvertisingDataType::Flags as u8, + (0x02 | 0x04) as u8, // BLE general discoverable, without BR/EDR support + ]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + cortex_m::asm::wfi(); +} + +fn get_bd_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = lhci_info.device_type_id; + bytes[4] = (lhci_info.st_company_id & 0xff) as u8; + bytes[5] = (lhci_info.st_company_id >> 8 & 0xff) as u8; + + BdAddr(bytes) +} + +fn get_random_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = 0; + bytes[4] = 0x6E; + bytes[5] = 0xED; + + BdAddr(bytes) +} + +const BLE_CFG_IRK: [u8; 16] = [ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, +]; +const BLE_CFG_ERK: [u8; 16] = [ + 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, +]; + +fn get_irk() -> EncryptionKey { + EncryptionKey(BLE_CFG_IRK) +} + +fn get_erk() -> EncryptionKey { + EncryptionKey(BLE_CFG_ERK) +} + +fn eddystone_advertising_data() -> [u8; 24] { + const EDDYSTONE_URL: &[u8] = b"www.rust-lang.com"; + + let mut service_data = [0u8; 24]; + let url_len = EDDYSTONE_URL.len(); + + service_data[0] = 6 + url_len as u8; + service_data[1] = AdvertisingDataType::ServiceData as u8; + + // 16-bit eddystone uuid + service_data[2] = 0xaa; + service_data[3] = 0xFE; + + service_data[4] = 0x10; // URL frame type + service_data[5] = 22_i8 as u8; // calibrated TX power at 0m + service_data[6] = 0x03; // eddystone url prefix = https + + service_data[7..(7 + url_len)].copy_from_slice(EDDYSTONE_URL); + + service_data +} diff --git a/embassy/examples/stm32wb/src/bin/gatt_server.rs b/embassy/examples/stm32wb/src/bin/gatt_server.rs new file mode 100644 index 0000000..1cc50e1 --- /dev/null +++ b/embassy/examples/stm32wb/src/bin/gatt_server.rs @@ -0,0 +1,398 @@ +#![no_std] +#![no_main] + +use core::time::Duration; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32_wpan::hci::event::command::{CommandComplete, ReturnParameters}; +use embassy_stm32_wpan::hci::host::uart::{Packet, UartHci}; +use embassy_stm32_wpan::hci::host::{AdvertisingFilterPolicy, EncryptionKey, HostHci, OwnAddressType}; +use embassy_stm32_wpan::hci::types::AdvertisingType; +use embassy_stm32_wpan::hci::vendor::command::gap::{ + AddressType, AuthenticationRequirements, DiscoverableParameters, GapCommands, IoCapability, LocalName, Pin, Role, + SecureConnectionSupport, +}; +use embassy_stm32_wpan::hci::vendor::command::gatt::{ + AddCharacteristicParameters, AddServiceParameters, CharacteristicEvent, CharacteristicPermission, + CharacteristicProperty, EncryptionKeySize, GattCommands, ServiceType, UpdateCharacteristicValueParameters, Uuid, + WriteResponseParameters, +}; +use embassy_stm32_wpan::hci::vendor::command::hal::{ConfigData, HalCommands, PowerLevel}; +use embassy_stm32_wpan::hci::vendor::event::command::VendorReturnParameters; +use embassy_stm32_wpan::hci::vendor::event::{self, AttributeHandle, VendorEvent}; +use embassy_stm32_wpan::hci::{BdAddr, Event}; +use embassy_stm32_wpan::lhci::LhciC1DeviceInformationCcrp; +use embassy_stm32_wpan::sub::ble::Ble; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +const BLE_GAP_DEVICE_NAME_LENGTH: u8 = 7; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Mac_802_15_4_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Mac_802_15_4_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let config = Config::default(); + let mut mbox = TlMbox::init(p.IPCC, Irqs, config); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await; + + info!("resetting BLE..."); + mbox.ble_subsystem.reset().await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("config public address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::public_address(get_bd_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("config random address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::random_address(get_random_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("config identity root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::identity_root(&get_irk()).build()) + .await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("config encryption root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::encryption_root(&get_erk()).build()) + .await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("config tx power level..."); + mbox.ble_subsystem.set_tx_power_level(PowerLevel::ZerodBm).await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("GATT init..."); + mbox.ble_subsystem.init_gatt().await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("GAP init..."); + mbox.ble_subsystem + .init_gap(Role::PERIPHERAL, false, BLE_GAP_DEVICE_NAME_LENGTH) + .await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("set IO capabilities..."); + mbox.ble_subsystem.set_io_capability(IoCapability::DisplayConfirm).await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("set authentication requirements..."); + mbox.ble_subsystem + .set_authentication_requirement(&AuthenticationRequirements { + bonding_required: false, + keypress_notification_support: false, + mitm_protection_required: false, + encryption_key_size_range: (8, 16), + fixed_pin: Pin::Requested, + identity_address_type: AddressType::Public, + secure_connection_support: SecureConnectionSupport::Optional, + }) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("set scan response data..."); + mbox.ble_subsystem.le_set_scan_response_data(b"TXTX").await.unwrap(); + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("set scan response data..."); + mbox.ble_subsystem.le_set_scan_response_data(b"TXTX").await.unwrap(); + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + defmt::info!("initializing services and characteristics..."); + let mut ble_context = init_gatt_services(&mut mbox.ble_subsystem).await.unwrap(); + defmt::info!("{}", ble_context); + + let discovery_params = DiscoverableParameters { + advertising_type: AdvertisingType::ConnectableUndirected, + advertising_interval: Some((Duration::from_millis(100), Duration::from_millis(100))), + address_type: OwnAddressType::Public, + filter_policy: AdvertisingFilterPolicy::AllowConnectionAndScan, + local_name: Some(LocalName::Complete(b"TXTX")), + advertising_data: &[], + conn_interval: (None, None), + }; + + info!("set discoverable..."); + mbox.ble_subsystem.set_discoverable(&discovery_params).await.unwrap(); + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + loop { + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + if let Ok(Packet::Event(event)) = response { + match event { + Event::LeConnectionComplete(_) => { + defmt::info!("connected"); + } + Event::DisconnectionComplete(_) => { + defmt::info!("disconnected"); + ble_context.is_subscribed = false; + mbox.ble_subsystem.set_discoverable(&discovery_params).await.unwrap(); + } + Event::Vendor(vendor_event) => match vendor_event { + VendorEvent::AttReadPermitRequest(read_req) => { + defmt::info!("read request received {}, allowing", read_req); + mbox.ble_subsystem.allow_read(read_req.conn_handle).await + } + VendorEvent::AttWritePermitRequest(write_req) => { + defmt::info!("write request received {}, allowing", write_req); + mbox.ble_subsystem + .write_response(&WriteResponseParameters { + conn_handle: write_req.conn_handle, + attribute_handle: write_req.attribute_handle, + status: Ok(()), + value: write_req.value(), + }) + .await + .unwrap() + } + VendorEvent::GattAttributeModified(attribute) => { + defmt::info!("{}", ble_context); + if attribute.attr_handle.0 == ble_context.chars.notify.0 + 2 { + if attribute.data()[0] == 0x01 { + defmt::info!("subscribed"); + ble_context.is_subscribed = true; + } else { + defmt::info!("unsubscribed"); + ble_context.is_subscribed = false; + } + } + } + _ => {} + }, + _ => {} + } + } + } +} + +fn get_bd_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = lhci_info.device_type_id; + bytes[4] = (lhci_info.st_company_id & 0xff) as u8; + bytes[5] = (lhci_info.st_company_id >> 8 & 0xff) as u8; + + BdAddr(bytes) +} + +fn get_random_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = 0; + bytes[4] = 0x6E; + bytes[5] = 0xED; + + BdAddr(bytes) +} + +const BLE_CFG_IRK: [u8; 16] = [ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, +]; +const BLE_CFG_ERK: [u8; 16] = [ + 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, +]; + +fn get_irk() -> EncryptionKey { + EncryptionKey(BLE_CFG_IRK) +} + +fn get_erk() -> EncryptionKey { + EncryptionKey(BLE_CFG_ERK) +} + +#[derive(defmt::Format)] +pub struct BleContext { + pub service_handle: AttributeHandle, + pub chars: CharHandles, + pub is_subscribed: bool, +} + +#[derive(defmt::Format)] +pub struct CharHandles { + pub read: AttributeHandle, + pub write: AttributeHandle, + pub notify: AttributeHandle, +} + +pub async fn init_gatt_services(ble_subsystem: &mut Ble) -> Result { + let service_handle = gatt_add_service(ble_subsystem, Uuid::Uuid16(0x500)).await?; + + let read = gatt_add_char( + ble_subsystem, + service_handle, + Uuid::Uuid16(0x501), + CharacteristicProperty::READ, + Some(b"Hello from embassy!"), + ) + .await?; + + let write = gatt_add_char( + ble_subsystem, + service_handle, + Uuid::Uuid16(0x502), + CharacteristicProperty::WRITE_WITHOUT_RESPONSE | CharacteristicProperty::WRITE | CharacteristicProperty::READ, + None, + ) + .await?; + + let notify = gatt_add_char( + ble_subsystem, + service_handle, + Uuid::Uuid16(0x503), + CharacteristicProperty::NOTIFY | CharacteristicProperty::READ, + None, + ) + .await?; + + Ok(BleContext { + service_handle, + is_subscribed: false, + chars: CharHandles { read, write, notify }, + }) +} + +async fn gatt_add_service(ble_subsystem: &mut Ble, uuid: Uuid) -> Result { + ble_subsystem + .add_service(&AddServiceParameters { + uuid, + service_type: ServiceType::Primary, + max_attribute_records: 8, + }) + .await; + let response = ble_subsystem.read().await; + defmt::debug!("{}", response); + + if let Ok(Packet::Event(Event::CommandComplete(CommandComplete { + return_params: + ReturnParameters::Vendor(VendorReturnParameters::GattAddService(event::command::GattService { + service_handle, + .. + })), + .. + }))) = response + { + Ok(service_handle) + } else { + Err(()) + } +} + +async fn gatt_add_char( + ble_subsystem: &mut Ble, + service_handle: AttributeHandle, + characteristic_uuid: Uuid, + characteristic_properties: CharacteristicProperty, + default_value: Option<&[u8]>, +) -> Result { + ble_subsystem + .add_characteristic(&AddCharacteristicParameters { + service_handle, + characteristic_uuid, + characteristic_properties, + characteristic_value_len: 32, + security_permissions: CharacteristicPermission::empty(), + gatt_event_mask: CharacteristicEvent::all(), + encryption_key_size: EncryptionKeySize::with_value(7).unwrap(), + is_variable: true, + }) + .await; + let response = ble_subsystem.read().await; + defmt::debug!("{}", response); + + if let Ok(Packet::Event(Event::CommandComplete(CommandComplete { + return_params: + ReturnParameters::Vendor(VendorReturnParameters::GattAddCharacteristic(event::command::GattCharacteristic { + characteristic_handle, + .. + })), + .. + }))) = response + { + if let Some(value) = default_value { + ble_subsystem + .update_characteristic_value(&UpdateCharacteristicValueParameters { + service_handle, + characteristic_handle, + offset: 0, + value, + }) + .await + .unwrap(); + + let response = ble_subsystem.read().await; + defmt::debug!("{}", response); + } + Ok(characteristic_handle) + } else { + Err(()) + } +} diff --git a/embassy/examples/stm32wb/src/bin/mac_ffd.rs b/embassy/examples/stm32wb/src/bin/mac_ffd.rs new file mode 100644 index 0000000..d139aa6 --- /dev/null +++ b/embassy/examples/stm32wb/src/bin/mac_ffd.rs @@ -0,0 +1,186 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32_wpan::mac::commands::{AssociateResponse, ResetRequest, SetRequest, StartRequest}; +use embassy_stm32_wpan::mac::event::MacEvent; +use embassy_stm32_wpan::mac::typedefs::{MacChannel, MacStatus, PanId, PibId, SecurityLevel}; +use embassy_stm32_wpan::sub::mm; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Mac_802_15_4_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Mac_802_15_4_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + core::mem::drop(sys_event); + + let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await; + info!("initialized mac: {}", result); + + info!("resetting"); + mbox.mac_subsystem + .send_command(&ResetRequest { + set_default_pib: true, + ..Default::default() + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("setting extended address"); + let extended_address: u64 = 0xACDE480000000001; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &extended_address as *const _ as *const u8, + pib_attribute: PibId::ExtendedAddress, + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("setting short address"); + let short_address: u16 = 0x1122; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &short_address as *const _ as *const u8, + pib_attribute: PibId::ShortAddress, + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("setting association permit"); + let association_permit: bool = true; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &association_permit as *const _ as *const u8, + pib_attribute: PibId::AssociationPermit, + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("setting TX power"); + let transmit_power: i8 = 2; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &transmit_power as *const _ as *const u8, + pib_attribute: PibId::TransmitPower, + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("starting FFD device"); + mbox.mac_subsystem + .send_command(&StartRequest { + pan_id: PanId([0x1A, 0xAA]), + channel_number: MacChannel::Channel16, + beacon_order: 0x0F, + superframe_order: 0x0F, + pan_coordinator: true, + battery_life_extension: false, + ..Default::default() + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("setting RX on when idle"); + let rx_on_while_idle: bool = true; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &rx_on_while_idle as *const _ as *const u8, + pib_attribute: PibId::RxOnWhenIdle, + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + loop { + let evt = mbox.mac_subsystem.read().await; + if let Ok(evt) = evt { + defmt::info!("parsed mac event"); + defmt::info!("{:#x}", evt); + + match evt { + MacEvent::MlmeAssociateInd(association) => mbox + .mac_subsystem + .send_command(&AssociateResponse { + device_address: association.device_address, + assoc_short_address: [0x33, 0x44], + status: MacStatus::Success, + security_level: SecurityLevel::Unsecure, + ..Default::default() + }) + .await + .unwrap(), + MacEvent::McpsDataInd(data_ind) => { + let payload = data_ind.payload(); + let ref_payload = b"Hello from embassy!"; + info!("{}", payload); + + if payload == ref_payload { + info!("success"); + } else { + info!("ref payload: {}", ref_payload); + } + } + _ => { + defmt::info!("other mac event"); + } + } + } else { + defmt::info!("failed to parse mac event"); + } + } +} diff --git a/embassy/examples/stm32wb/src/bin/mac_ffd_net.rs b/embassy/examples/stm32wb/src/bin/mac_ffd_net.rs new file mode 100644 index 0000000..6a97daf --- /dev/null +++ b/embassy/examples/stm32wb/src/bin/mac_ffd_net.rs @@ -0,0 +1,177 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32_wpan::mac::commands::{ResetRequest, SetRequest, StartRequest}; +use embassy_stm32_wpan::mac::typedefs::{MacChannel, PanId, PibId}; +use embassy_stm32_wpan::mac::{self, Runner}; +use embassy_stm32_wpan::sub::mm; +use embassy_stm32_wpan::TlMbox; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + +#[embassy_executor::task] +async fn run_mac(runner: &'static Runner<'static>) { + runner.run().await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Mac_802_15_4_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Mac_802_15_4_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + core::mem::drop(sys_event); + + let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await; + info!("initialized mac: {}", result); + + info!("resetting"); + mbox.mac_subsystem + .send_command(&ResetRequest { + set_default_pib: true, + ..Default::default() + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("setting extended address"); + let extended_address: u64 = 0xACDE480000000001; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &extended_address as *const _ as *const u8, + pib_attribute: PibId::ExtendedAddress, + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("setting short address"); + let short_address: u16 = 0x1122; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &short_address as *const _ as *const u8, + pib_attribute: PibId::ShortAddress, + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("setting association permit"); + let association_permit: bool = true; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &association_permit as *const _ as *const u8, + pib_attribute: PibId::AssociationPermit, + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("setting TX power"); + let transmit_power: i8 = 2; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &transmit_power as *const _ as *const u8, + pib_attribute: PibId::TransmitPower, + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("starting FFD device"); + mbox.mac_subsystem + .send_command(&StartRequest { + pan_id: PanId([0x1A, 0xAA]), + channel_number: MacChannel::Channel16, + beacon_order: 0x0F, + superframe_order: 0x0F, + pan_coordinator: true, + battery_life_extension: false, + ..Default::default() + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("setting RX on when idle"); + let rx_on_while_idle: bool = true; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &rx_on_while_idle as *const _ as *const u8, + pib_attribute: PibId::RxOnWhenIdle, + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + static TX1: StaticCell<[u8; 127]> = StaticCell::new(); + static TX2: StaticCell<[u8; 127]> = StaticCell::new(); + static TX3: StaticCell<[u8; 127]> = StaticCell::new(); + static TX4: StaticCell<[u8; 127]> = StaticCell::new(); + static TX5: StaticCell<[u8; 127]> = StaticCell::new(); + let tx_queue = [ + TX1.init([0u8; 127]), + TX2.init([0u8; 127]), + TX3.init([0u8; 127]), + TX4.init([0u8; 127]), + TX5.init([0u8; 127]), + ]; + + static RUNNER: StaticCell = StaticCell::new(); + let runner = RUNNER.init(Runner::new(mbox.mac_subsystem, tx_queue)); + + spawner.spawn(run_mac(runner)).unwrap(); + + let (driver, control) = mac::new(runner).await; + + let _ = driver; + let _ = control; +} diff --git a/embassy/examples/stm32wb/src/bin/mac_rfd.rs b/embassy/examples/stm32wb/src/bin/mac_rfd.rs new file mode 100644 index 0000000..9062bdc --- /dev/null +++ b/embassy/examples/stm32wb/src/bin/mac_rfd.rs @@ -0,0 +1,183 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32_wpan::mac::commands::{AssociateRequest, DataRequest, GetRequest, ResetRequest, SetRequest}; +use embassy_stm32_wpan::mac::event::MacEvent; +use embassy_stm32_wpan::mac::typedefs::{ + AddressMode, Capabilities, KeyIdMode, MacAddress, MacChannel, PanId, PibId, SecurityLevel, +}; +use embassy_stm32_wpan::sub::mm; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Mac_802_15_4_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Mac_802_15_4_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + core::mem::drop(sys_event); + + let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await; + info!("initialized mac: {}", result); + + info!("resetting"); + mbox.mac_subsystem + .send_command(&ResetRequest { + set_default_pib: true, + ..Default::default() + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("setting extended address"); + let extended_address: u64 = 0xACDE480000000002; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &extended_address as *const _ as *const u8, + pib_attribute: PibId::ExtendedAddress, + }) + .await + .unwrap(); + defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap()); + + info!("getting extended address"); + mbox.mac_subsystem + .send_command(&GetRequest { + pib_attribute: PibId::ExtendedAddress, + ..Default::default() + }) + .await + .unwrap(); + + { + let evt = mbox.mac_subsystem.read().await.unwrap(); + info!("{:#x}", evt); + + if let MacEvent::MlmeGetCnf(evt) = evt { + if evt.pib_attribute_value_len == 8 { + let value = unsafe { core::ptr::read_unaligned(evt.pib_attribute_value_ptr as *const u64) }; + + info!("value {:#x}", value) + } + } + } + + info!("assocation request"); + let a = AssociateRequest { + channel_number: MacChannel::Channel16, + channel_page: 0, + coord_addr_mode: AddressMode::Short, + coord_address: MacAddress { short: [34, 17] }, + capability_information: Capabilities::ALLOCATE_ADDRESS, + coord_pan_id: PanId([0x1A, 0xAA]), + security_level: SecurityLevel::Unsecure, + key_id_mode: KeyIdMode::Implicite, + key_source: [0; 8], + key_index: 152, + }; + info!("{}", a); + mbox.mac_subsystem.send_command(&a).await.unwrap(); + let short_addr = { + let evt = mbox.mac_subsystem.read().await.unwrap(); + info!("{:#x}", evt); + + if let MacEvent::MlmeAssociateCnf(conf) = evt { + conf.assoc_short_address + } else { + defmt::panic!() + } + }; + + info!("setting short address"); + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &short_addr as *const _ as *const u8, + pib_attribute: PibId::ShortAddress, + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await.unwrap(); + info!("{:#x}", evt); + } + + info!("sending data"); + let data = b"Hello from embassy!"; + mbox.mac_subsystem + .send_command( + DataRequest { + src_addr_mode: AddressMode::Short, + dst_addr_mode: AddressMode::Short, + dst_pan_id: PanId([0x1A, 0xAA]), + dst_address: MacAddress::BROADCAST, + msdu_handle: 0x02, + ack_tx: 0x00, + gts_tx: false, + security_level: SecurityLevel::Unsecure, + ..Default::default() + } + .set_buffer(data), + ) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await.unwrap(); + info!("{:#x}", evt); + } + + loop { + match mbox.mac_subsystem.read().await { + Ok(evt) => info!("{:#x}", evt), + _ => continue, + }; + } +} diff --git a/embassy/examples/stm32wb/src/bin/tl_mbox.rs b/embassy/examples/stm32wb/src/bin/tl_mbox.rs new file mode 100644 index 0000000..4e7f230 --- /dev/null +++ b/embassy/examples/stm32wb/src/bin/tl_mbox.rs @@ -0,0 +1,77 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32_wpan::TlMbox; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Mac_802_15_4_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Mac_802_15_4_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + loop { + let wireless_fw_info = mbox.sys_subsystem.wireless_fw_info(); + match wireless_fw_info { + None => info!("not yet initialized"), + Some(fw_info) => { + let version_major = fw_info.version_major(); + let version_minor = fw_info.version_minor(); + let subversion = fw_info.subversion(); + + let sram2a_size = fw_info.sram2a_size(); + let sram2b_size = fw_info.sram2b_size(); + + info!( + "version {}.{}.{} - SRAM2a {} - SRAM2b {}", + version_major, version_minor, subversion, sram2a_size, sram2b_size + ); + + break; + } + } + + Timer::after_millis(50).await; + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/examples/stm32wb/src/bin/tl_mbox_ble.rs b/embassy/examples/stm32wb/src/bin/tl_mbox_ble.rs new file mode 100644 index 0000000..72a4c18 --- /dev/null +++ b/embassy/examples/stm32wb/src/bin/tl_mbox_ble.rs @@ -0,0 +1,65 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Mac_802_15_4_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Mac_802_15_4_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await; + + info!("starting ble..."); + mbox.ble_subsystem.tl_write(0x0c, &[]).await; + + info!("waiting for ble..."); + let ble_event = mbox.ble_subsystem.tl_read().await; + + info!("ble event: {}", ble_event.payload()); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/examples/stm32wb/src/bin/tl_mbox_mac.rs b/embassy/examples/stm32wb/src/bin/tl_mbox_mac.rs new file mode 100644 index 0000000..9224e62 --- /dev/null +++ b/embassy/examples/stm32wb/src/bin/tl_mbox_mac.rs @@ -0,0 +1,77 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32_wpan::sub::mm; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Mac_802_15_4_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Mac_802_15_4_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + core::mem::drop(sys_event); + + let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await; + info!("initialized mac: {}", result); + + // + // info!("starting ble..."); + // mbox.ble_subsystem.t_write(0x0c, &[]).await; + // + // info!("waiting for ble..."); + // let ble_event = mbox.ble_subsystem.tl_read().await; + // + // info!("ble event: {}", ble_event.payload()); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/examples/stm32wba/.cargo/config.toml b/embassy/examples/stm32wba/.cargo/config.toml new file mode 100644 index 0000000..4774133 --- /dev/null +++ b/embassy/examples/stm32wba/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip STM32WBA52CGUxT" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32wba/Cargo.toml b/embassy/examples/stm32wba/Cargo.toml new file mode 100644 index 0000000..7735dfd --- /dev/null +++ b/embassy/examples/stm32wba/Cargo.toml @@ -0,0 +1,25 @@ +[package] +edition = "2021" +name = "embassy-stm32wba-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wba52cg", "time-driver-any", "memory-x", "exti"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +static_cell = "2" + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32wba/build.rs b/embassy/examples/stm32wba/build.rs new file mode 100644 index 0000000..8fc6faa --- /dev/null +++ b/embassy/examples/stm32wba/build.rs @@ -0,0 +1,10 @@ +use std::error::Error; + +fn main() -> Result<(), Box> { + println!("cargo:rerun-if-changed=link.x"); + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + + Ok(()) +} diff --git a/embassy/examples/stm32wba/src/bin/blinky.rs b/embassy/examples/stm32wba/src/bin/blinky.rs new file mode 100644 index 0000000..0d803b2 --- /dev/null +++ b/embassy/examples/stm32wba/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::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.PB4, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(500).await; + + info!("low"); + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32wba/src/bin/button_exti.rs b/embassy/examples/stm32wba/src/bin/button_exti.rs new file mode 100644 index 0000000..34a08bb --- /dev/null +++ b/embassy/examples/stm32wba/src/bin/button_exti.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::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 mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); + + 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/embassy/examples/stm32wl/.cargo/config.toml b/embassy/examples/stm32wl/.cargo/config.toml new file mode 100644 index 0000000..ee416fc --- /dev/null +++ b/embassy/examples/stm32wl/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32WLE5JCIx" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/embassy/examples/stm32wl/Cargo.toml b/embassy/examples/stm32wl/Cargo.toml new file mode 100644 index 0000000..0182745 --- /dev/null +++ b/embassy/examples/stm32wl/Cargo.toml @@ -0,0 +1,27 @@ +[package] +edition = "2021" +name = "embassy-stm32wl-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32wl55jc-cm4 to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti", "chrono"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-storage = "0.3.1" +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +chrono = { version = "^0.4", default-features = false } + +[profile.release] +debug = 2 diff --git a/embassy/examples/stm32wl/build.rs b/embassy/examples/stm32wl/build.rs new file mode 100644 index 0000000..8cd32d7 --- /dev/null +++ b/embassy/examples/stm32wl/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/embassy/examples/stm32wl/memory.x b/embassy/examples/stm32wl/memory.x new file mode 100644 index 0000000..4590867 --- /dev/null +++ b/embassy/examples/stm32wl/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 256K + SHARED_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128 + RAM (rwx) : ORIGIN = 0x20000080, LENGTH = 64K - 128 +} + +SECTIONS +{ + .shared_data : + { + *(.shared_data) + } > SHARED_RAM +} diff --git a/embassy/examples/stm32wl/src/bin/blinky.rs b/embassy/examples/stm32wl/src/bin/blinky.rs new file mode 100644 index 0000000..ce7d0ec --- /dev/null +++ b/embassy/examples/stm32wl/src/bin/blinky.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::SharedData; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); + info!("Hello World!"); + + let mut led = Output::new(p.PB15, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(500).await; + + info!("low"); + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/embassy/examples/stm32wl/src/bin/button.rs b/embassy/examples/stm32wl/src/bin/button.rs new file mode 100644 index 0000000..8b52044 --- /dev/null +++ b/embassy/examples/stm32wl/src/bin/button.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::SharedData; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); + + let button = Input::new(p.PA0, Pull::Up); + let mut led1 = Output::new(p.PB15, Level::High, Speed::Low); + let mut led2 = Output::new(p.PB9, Level::High, Speed::Low); + + loop { + if button.is_high() { + led1.set_high(); + led2.set_low(); + } else { + led1.set_low(); + led2.set_high(); + } + } +} diff --git a/embassy/examples/stm32wl/src/bin/button_exti.rs b/embassy/examples/stm32wl/src/bin/button_exti.rs new file mode 100644 index 0000000..8dd1a6a --- /dev/null +++ b/embassy/examples/stm32wl/src/bin/button_exti.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::Pull; +use embassy_stm32::SharedData; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); + info!("Hello World!"); + + let mut button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Up); + + 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/embassy/examples/stm32wl/src/bin/flash.rs b/embassy/examples/stm32wl/src/bin/flash.rs new file mode 100644 index 0000000..147f5d2 --- /dev/null +++ b/embassy/examples/stm32wl/src/bin/flash.rs @@ -0,0 +1,45 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +use embassy_stm32::SharedData; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); + info!("Hello Flash!"); + + const ADDR: u32 = 0x36000; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.blocking_erase(ADDR, ADDR + 2048)); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); +} diff --git a/embassy/examples/stm32wl/src/bin/random.rs b/embassy/examples/stm32wl/src/bin/random.rs new file mode 100644 index 0000000..df2ed00 --- /dev/null +++ b/embassy/examples/stm32wl/src/bin/random.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rng::{self, Rng}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, peripherals, SharedData}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + RNG => rng::InterruptHandler; +}); + +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(32_000_000), + mode: HseMode::Bypass, + prescaler: HsePrescaler::DIV1, + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL6, + divp: None, + divq: Some(PllQDiv::DIV2), // PLL1_Q clock (32 / 2 * 6 / 2), used for RNG + divr: Some(PllRDiv::DIV2), // sysclk 48Mhz clock (32 / 2 * 6 / 2) + }); + } + let p = embassy_stm32::init_primary(config, &SHARED_DATA); + + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG, Irqs); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); + + loop {} +} diff --git a/embassy/examples/stm32wl/src/bin/rtc.rs b/embassy/examples/stm32wl/src/bin/rtc.rs new file mode 100644 index 0000000..69a9ddc --- /dev/null +++ b/embassy/examples/stm32wl/src/bin/rtc.rs @@ -0,0 +1,57 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use chrono::{NaiveDate, NaiveDateTime}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{Config, SharedData}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.ls = LsConfig::default_lse(); + config.rcc.hse = Some(Hse { + freq: Hertz(32_000_000), + mode: HseMode::Bypass, + prescaler: HsePrescaler::DIV1, + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL6, + divp: None, + divq: Some(PllQDiv::DIV2), // PLL1_Q clock (32 / 2 * 6 / 2), used for RNG + divr: Some(PllRDiv::DIV2), // sysclk 48Mhz clock (32 / 2 * 6 / 2) + }); + } + let p = embassy_stm32::init_primary(config, &SHARED_DATA); + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + info!("Got RTC! {:?}", now.and_utc().timestamp()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + // In reality the delay would be much longer + Timer::after_millis(20000).await; + + let then: NaiveDateTime = rtc.now().unwrap().into(); + info!("Got RTC! {:?}", then.and_utc().timestamp()); +} diff --git a/embassy/examples/stm32wl/src/bin/uart_async.rs b/embassy/examples/stm32wl/src/bin/uart_async.rs new file mode 100644 index 0000000..ece9b92 --- /dev/null +++ b/embassy/examples/stm32wl/src/bin/uart_async.rs @@ -0,0 +1,67 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{Config, InterruptHandler, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, SharedData}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + USART1 => InterruptHandler; + LPUART1 => InterruptHandler; +}); + +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +/* +Pass Incoming data from LPUART1 to USART1 +Example is written for the LoRa-E5 mini v1.0, +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.sys = embassy_stm32::rcc::Sysclk::HSE; + let p = embassy_stm32::init_primary(config, &SHARED_DATA); + + 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 mut usart1 = Uart::new(p.USART1, p.PB7, p.PB6, Irqs, p.DMA1_CH3, p.DMA1_CH4, config1).unwrap(); + + //RX1/TX1 (LPUART) on LoRa-E5 mini v1.0 + let mut usart2 = Uart::new(p.LPUART1, p.PC0, p.PC1, Irqs, p.DMA1_CH5, p.DMA1_CH6, config2).unwrap(); + + 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(..) => { + //Wasn't able to write + } + } + } + Err(_err) => { + //Ignore eg. framing errors + } + } + } +} diff --git a/embassy/examples/wasm/Cargo.toml b/embassy/examples/wasm/Cargo.toml new file mode 100644 index 0000000..f5dcdc0 --- /dev/null +++ b/embassy/examples/wasm/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "embassy-wasm-example" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["log"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "wasm", ] } + +wasm-logger = "0.2.0" +wasm-bindgen = "0.2" +web-sys = { version = "0.3", features = ["Document", "Element", "HtmlElement", "Node", "Window" ] } +log = "0.4.11" + +[profile.release] +debug = 2 diff --git a/embassy/examples/wasm/README.md b/embassy/examples/wasm/README.md new file mode 100644 index 0000000..3d300d9 --- /dev/null +++ b/embassy/examples/wasm/README.md @@ -0,0 +1,26 @@ +# WASM example + +Examples use a CLI tool named `wasm-pack` to build this example: + +``` +cargo install wasm-pack --version 0.12.1 +``` + +## Building + +To build the example, run: + +``` +wasm-pack build --target web +``` + +## Running + +To run the example, start a webserver server the local folder: + + +``` +python -m http.server +``` + +Then, open a browser at http://127.0.0.1:8000 and watch the ticker print entries to the window. diff --git a/embassy/examples/wasm/index.html b/embassy/examples/wasm/index.html new file mode 100644 index 0000000..05e8b29 --- /dev/null +++ b/embassy/examples/wasm/index.html @@ -0,0 +1,25 @@ + + + + + + + + +

Log

+
+
    +
+
+ + diff --git a/embassy/examples/wasm/src/lib.rs b/embassy/examples/wasm/src/lib.rs new file mode 100644 index 0000000..71cf980 --- /dev/null +++ b/embassy/examples/wasm/src/lib.rs @@ -0,0 +1,28 @@ +use embassy_executor::Spawner; +use embassy_time::Timer; + +#[embassy_executor::task] +async fn ticker() { + let window = web_sys::window().expect("no global `window` exists"); + + let mut counter = 0; + loop { + let document = window.document().expect("should have a document on window"); + let list = document.get_element_by_id("log").expect("should have a log element"); + + let li = document.create_element("li").expect("error creating list item element"); + li.set_text_content(Some(&format!("tick {}", counter))); + + list.append_child(&li).expect("error appending list item"); + log::info!("tick {}", counter); + counter += 1; + + Timer::after_secs(1).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + wasm_logger::init(wasm_logger::Config::default()); + spawner.spawn(ticker()).unwrap(); +} diff --git a/embassy/release/bump-dependency.sh b/embassy/release/bump-dependency.sh new file mode 100755 index 0000000..07511d2 --- /dev/null +++ b/embassy/release/bump-dependency.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# A helper script to bump version dependencies of a crate to a particular version. It does +# not bump the version of the crate itself, only its entries in dependency lists. +# +# Usage (from the embassy repo folder): ./release/bump-dependency.sh embassy-time 0.4.0 +# +# As a sanity check, after running this script, grep for old versions. +# +CRATE=$1 +TARGET_VER=$2 +find . -name "Cargo.toml" | xargs sed -rie "s/($CRATE = \{.*version = \")[0-9]+.[0-9]+.?[0-9]*(\".*)/\1$TARGET_VER\2/g" diff --git a/embassy/rust-toolchain-nightly.toml b/embassy/rust-toolchain-nightly.toml new file mode 100644 index 0000000..6efd989 --- /dev/null +++ b/embassy/rust-toolchain-nightly.toml @@ -0,0 +1,12 @@ +[toolchain] +channel = "nightly-2024-12-10" +components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ] +targets = [ + "thumbv7em-none-eabi", + "thumbv7m-none-eabi", + "thumbv6m-none-eabi", + "thumbv7em-none-eabihf", + "thumbv8m.main-none-eabihf", + "riscv32imac-unknown-none-elf", + "wasm32-unknown-unknown", +] diff --git a/embassy/rust-toolchain.toml b/embassy/rust-toolchain.toml new file mode 100644 index 0000000..704d2e3 --- /dev/null +++ b/embassy/rust-toolchain.toml @@ -0,0 +1,12 @@ +[toolchain] +channel = "1.83" +components = [ "rust-src", "rustfmt", "llvm-tools" ] +targets = [ + "thumbv7em-none-eabi", + "thumbv7m-none-eabi", + "thumbv6m-none-eabi", + "thumbv7em-none-eabihf", + "thumbv8m.main-none-eabihf", + "riscv32imac-unknown-none-elf", + "wasm32-unknown-unknown", +] diff --git a/embassy/rustfmt.toml b/embassy/rustfmt.toml new file mode 100644 index 0000000..3639f43 --- /dev/null +++ b/embassy/rustfmt.toml @@ -0,0 +1,3 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Module" +max_width=120 \ No newline at end of file diff --git a/embassy/tests/link_ram_cortex_m.x b/embassy/tests/link_ram_cortex_m.x new file mode 100644 index 0000000..39a31b5 --- /dev/null +++ b/embassy/tests/link_ram_cortex_m.x @@ -0,0 +1,280 @@ +/* ##### EMBASSY NOTE + Originally from https://github.com/rust-embedded/cortex-m/blob/master/cortex-m-rt/link.x.in + Adjusted to put everything in RAM +*/ + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut __sbss }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol if not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all 4-byte aligned. These alignments are assumed by the RAM initialization + routine. There's also a second benefit: 4-byte aligned boundaries means that you won't see + "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +/* Provides information about the memory layout of the device */ +/* This will be provided by the user (see `memory.x`) or by a Board Support Crate */ +INCLUDE memory.x + +/* # Entry point = reset vector */ +EXTERN(__RESET_VECTOR); +EXTERN(Reset); +ENTRY(Reset); + +/* # Exception vectors */ +/* This is effectively weak aliasing at the linker level */ +/* The user can override any of these aliases by defining the corresponding symbol themselves (cf. + the `exception!` macro) */ +EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */ + +EXTERN(DefaultHandler); + +PROVIDE(NonMaskableInt = DefaultHandler); +EXTERN(HardFaultTrampoline); +PROVIDE(MemoryManagement = DefaultHandler); +PROVIDE(BusFault = DefaultHandler); +PROVIDE(UsageFault = DefaultHandler); +PROVIDE(SecureFault = DefaultHandler); +PROVIDE(SVCall = DefaultHandler); +PROVIDE(DebugMonitor = DefaultHandler); +PROVIDE(PendSV = DefaultHandler); +PROVIDE(SysTick = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultHandler_); +PROVIDE(HardFault = HardFault_); + +/* # Interrupt vectors */ +EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */ + +/* # Pre-initialization function */ +/* If the user overrides this using the `pre_init!` macro or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = DefaultPreInit); + +/* # Sections */ +SECTIONS +{ + PROVIDE(_ram_start = ORIGIN(RAM)); + PROVIDE(_ram_end = ORIGIN(RAM) + LENGTH(RAM)); + PROVIDE(_stack_start = _ram_end); + + /* ## Sections in RAM */ + /* ### Vector table */ + .vector_table ORIGIN(RAM) : + { + __vector_table = .; + + /* Initial Stack Pointer (SP) value. + * We mask the bottom three bits to force 8-byte alignment. + * Despite having an assert for this later, it's possible that a separate + * linker script could override _stack_start after the assert is checked. + */ + LONG(_stack_start & 0xFFFFFFF8); + + /* Reset vector */ + KEEP(*(.vector_table.reset_vector)); /* this is the `__RESET_VECTOR` symbol */ + + /* Exceptions */ + __exceptions = .; /* start of exceptions */ + KEEP(*(.vector_table.exceptions)); /* this is the `__EXCEPTIONS` symbol */ + __eexceptions = .; /* end of exceptions */ + + /* Device specific interrupts */ + KEEP(*(.vector_table.interrupts)); /* this is the `__INTERRUPTS` symbol */ + } > RAM + + PROVIDE(_stext = ADDR(.vector_table) + SIZEOF(.vector_table)); + + /* ### .text */ + .text _stext : + { + __stext = .; + *(.Reset); + + *(.text .text.*); + + /* The HardFaultTrampoline uses the `b` instruction to enter `HardFault`, + so must be placed close to it. */ + *(.HardFaultTrampoline); + *(.HardFault.*); + + . = ALIGN(4); /* Pad .text to the alignment to workaround overlapping load section bug in old lld */ + __etext = .; + } > RAM + + /* ### .rodata */ + .rodata : ALIGN(4) + { + . = ALIGN(4); + __srodata = .; + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + __erodata = .; + } > RAM + + /* ## Sections in RAM */ + /* ### .data */ + .data : ALIGN(4) + { + . = ALIGN(4); + __sdata = .; + __edata = .; /* RAM: By setting __sdata=__edata cortex-m-rt has to copy 0 bytes as .data is already in RAM */ + + *(.data .data.*); + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM + /* Allow sections from user `memory.x` injected using `INSERT AFTER .data` to + * use the .data loading mechanism by pushing __edata. Note: do not change + * output region or load region in those user sections! */ + /* Link from RAM: Disabled, now __sdata == __edata + . = ALIGN(4); + __edata = .; + */ + + /* LMA of .data */ + __sidata = LOADADDR(.data); + + /* ### .gnu.sgstubs + This section contains the TrustZone-M veneers put there by the Arm GNU linker. */ + /* Security Attribution Unit blocks must be 32 bytes aligned. */ + /* Note that this pads the RAM usage to 32 byte alignment. */ + .gnu.sgstubs : ALIGN(32) + { + . = ALIGN(32); + __veneer_base = .; + *(.gnu.sgstubs*) + . = ALIGN(32); + } > RAM + /* Place `__veneer_limit` outside the `.gnu.sgstubs` section because veneers are + * always inserted last in the section, which would otherwise be _after_ the `__veneer_limit` symbol. + */ + . = ALIGN(32); + __veneer_limit = .; + + /* ### .bss */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __sbss = .; + *(.bss .bss.*); + *(COMMON); /* Uninitialized C statics */ + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM + /* Allow sections from user `memory.x` injected using `INSERT AFTER .bss` to + * use the .bss zeroing mechanism by pushing __ebss. Note: do not change + * output region or load region in those user sections! */ + . = ALIGN(4); + __ebss = .; + + /* ### .uninit */ + .uninit (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __suninit = .; + *(.uninit .uninit.*); + . = ALIGN(4); + __euninit = .; + } > RAM + + /* Place the heap right after `.uninit` in RAM */ + PROVIDE(__sheap = __euninit); + + /* ## .got */ + /* Dynamic relocations are unsupported. This section is only used to detect relocatable code in + the input files and raise an error if relocatable code is found */ + .got (NOLOAD) : + { + KEEP(*(.got .got.*)); + } + + /* ## Discarded sections */ + /DISCARD/ : + { + /* Unused exception related info that only wastes space */ + *(.ARM.exidx); + *(.ARM.exidx.*); + *(.ARM.extab.*); + } +} + +/* Do not exceed this mark in the error messages below | */ +/* # Alignment checks */ +ASSERT(ORIGIN(RAM) % 4 == 0, " +ERROR(cortex-m-rt): the start of the RAM region must be 4-byte aligned"); + +ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, " +BUG(cortex-m-rt): .data is not 4-byte aligned"); + +ASSERT(__sidata % 4 == 0, " +BUG(cortex-m-rt): the LMA of .data is not 4-byte aligned"); + +ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, " +BUG(cortex-m-rt): .bss is not 4-byte aligned"); + +ASSERT(__sheap % 4 == 0, " +BUG(cortex-m-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stack_start % 8 == 0, " +ERROR(cortex-m-rt): stack start address is not 8-byte aligned. +If you have set _stack_start, check it's set to an address which is a multiple of 8 bytes. +If you haven't, stack starts at the end of RAM by default. Check that both RAM +origin and length are set to multiples of 8 in the `memory.x` file."); + +/* # Position checks */ + +/* ## .vector_table + * + * If the *start* of exception vectors is not 8 bytes past the start of the + * vector table, then we somehow did not place the reset vector, which should + * live 4 bytes past the start of the vector table. + */ +ASSERT(__exceptions == ADDR(.vector_table) + 0x8, " +BUG(cortex-m-rt): the reset vector is missing"); + +ASSERT(__eexceptions == ADDR(.vector_table) + 0x40, " +BUG(cortex-m-rt): the exception vectors are missing"); + +ASSERT(SIZEOF(.vector_table) > 0x40, " +ERROR(cortex-m-rt): The interrupt vectors are missing. +Possible solutions, from most likely to less likely: +- Link to a svd2rust generated device crate +- Check that you actually use the device/hal/bsp crate in your code +- Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency +may be enabling it) +- Supply the interrupt handlers yourself. Check the documentation for details."); + +/* ## .text */ +ASSERT(ADDR(.vector_table) + SIZEOF(.vector_table) <= _stext, " +ERROR(cortex-m-rt): The .text section can't be placed inside the .vector_table section +Set _stext to an address greater than the end of .vector_table (See output of `nm`)"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(RAM) + LENGTH(RAM), " +ERROR(cortex-m-rt): The .text section must be placed inside the RAM memory. +Set _stext to an address smaller than 'ORIGIN(RAM) + LENGTH(RAM)'"); + +/* # Other checks */ +ASSERT(SIZEOF(.got) == 0, " +ERROR(cortex-m-rt): .got section detected in the input object files +Dynamic relocations are not supported. If you are linking to C code compiled using +the 'cc' crate then modify your build script to compile the C code _without_ +the -fPIC flag. See the documentation of the `cc::Build.pic` method for details."); +/* Do not exceed this mark in the error messages above | */ + +/* Provides weak aliases (cf. PROVIDED) for device specific interrupt handlers */ +/* This will usually be provided by a device crate generated using svd2rust (see `device.x`) */ +INCLUDE device.x diff --git a/embassy/tests/nrf/.cargo/config.toml b/embassy/tests/nrf/.cargo/config.toml new file mode 100644 index 0000000..8f9bccb --- /dev/null +++ b/embassy/tests/nrf/.cargo/config.toml @@ -0,0 +1,11 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +#runner = "teleprobe local run --chip nRF52840_xxAA --elf" +runner = "teleprobe client run" + +[build] +#target = "thumbv6m-none-eabi" +target = "thumbv7em-none-eabi" +#target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,smoltcp=info" diff --git a/embassy/tests/nrf/Cargo.toml b/embassy/tests/nrf/Cargo.toml new file mode 100644 index 0000000..7af3d06 --- /dev/null +++ b/embassy/tests/nrf/Cargo.toml @@ -0,0 +1,114 @@ +[package] +edition = "2021" +name = "embassy-nrf-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +teleprobe-meta = "1" + +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt", ] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } +embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } +embassy-net-enc28j60 = { version = "0.1.0", path = "../../embassy-net-enc28j60", features = ["defmt"] } +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } +static_cell = "2" +perf-client = { path = "../perf-client" } + +defmt = "0.3" +defmt-rtt = "0.4" + +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"] } +portable-atomic = { version = "1.6.0" } + +[features] +nrf51422 = ["embassy-nrf/nrf51", "portable-atomic/unsafe-assume-single-core"] +nrf52832 = ["embassy-nrf/nrf52832", "easydma"] +nrf52833 = ["embassy-nrf/nrf52833", "easydma", "two-uarts"] +nrf52840 = ["embassy-nrf/nrf52840", "easydma", "two-uarts", "embassy-executor/task-arena-size-16384"] +nrf5340 = ["embassy-nrf/nrf5340-app-s", "easydma", "two-uarts"] +nrf9160 = ["embassy-nrf/nrf9160-s", "easydma", "two-uarts"] + +easydma = [] +two-uarts = [] + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 's' +overflow-checks = false + +# BEGIN TESTS +# Generated by gen_test.py. DO NOT EDIT. +[[bin]] +name = "buffered_uart" +path = "src/bin/buffered_uart.rs" +required-features = [ "easydma",] + +[[bin]] +name = "buffered_uart_full" +path = "src/bin/buffered_uart_full.rs" +required-features = [ "easydma",] + +[[bin]] +name = "buffered_uart_halves" +path = "src/bin/buffered_uart_halves.rs" +required-features = [ "two-uarts",] + +[[bin]] +name = "buffered_uart_spam" +path = "src/bin/buffered_uart_spam.rs" +required-features = [ "two-uarts",] + +[[bin]] +name = "ethernet_enc28j60_perf" +path = "src/bin/ethernet_enc28j60_perf.rs" +required-features = [ "nrf52840",] + +[[bin]] +name = "gpio" +path = "src/bin/gpio.rs" +required-features = [] + +[[bin]] +name = "gpiote" +path = "src/bin/gpiote.rs" +required-features = [] + +[[bin]] +name = "spim" +path = "src/bin/spim.rs" +required-features = [ "easydma",] + +[[bin]] +name = "timer" +path = "src/bin/timer.rs" +required-features = [] + +[[bin]] +name = "uart_halves" +path = "src/bin/uart_halves.rs" +required-features = [ "two-uarts",] + +[[bin]] +name = "uart_split" +path = "src/bin/uart_split.rs" +required-features = [ "easydma",] + +[[bin]] +name = "wifi_esp_hosted_perf" +path = "src/bin/wifi_esp_hosted_perf.rs" +required-features = [ "nrf52840",] + +# END TESTS diff --git a/embassy/tests/nrf/build.rs b/embassy/tests/nrf/build.rs new file mode 100644 index 0000000..3c15cf1 --- /dev/null +++ b/embassy/tests/nrf/build.rs @@ -0,0 +1,37 @@ +use std::error::Error; +use std::path::PathBuf; +use std::{env, fs}; + +fn main() -> Result<(), Box> { + let out = PathBuf::from(env::var("OUT_DIR").unwrap()); + + // copy the right memory.x + #[cfg(feature = "nrf51422")] + let memory_x = include_bytes!("memory-nrf51422.x"); + #[cfg(feature = "nrf52832")] + let memory_x = include_bytes!("memory-nrf52832.x"); + #[cfg(feature = "nrf52833")] + let memory_x = include_bytes!("memory-nrf52833.x"); + #[cfg(feature = "nrf52840")] + let memory_x = include_bytes!("memory-nrf52840.x"); + #[cfg(feature = "nrf5340")] + let memory_x = include_bytes!("memory-nrf5340.x"); + #[cfg(feature = "nrf9160")] + let memory_x = include_bytes!("memory-nrf9160.x"); + fs::write(out.join("memory.x"), memory_x).unwrap(); + + // copy main linker script. + fs::write(out.join("link_ram.x"), include_bytes!("../link_ram_cortex_m.x")).unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=link_ram.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + #[cfg(feature = "nrf51422")] + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + #[cfg(not(feature = "nrf51422"))] + println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + println!("cargo:rustc-link-arg-bins=-Tteleprobe.x"); + + Ok(()) +} diff --git a/embassy/tests/nrf/gen_test.py b/embassy/tests/nrf/gen_test.py new file mode 100644 index 0000000..daf7143 --- /dev/null +++ b/embassy/tests/nrf/gen_test.py @@ -0,0 +1,44 @@ +import os +import toml +from glob import glob + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +# ======= load test list +tests = {} +for f in sorted(glob('./src/bin/*.rs')): + name = os.path.splitext(os.path.basename(f))[0] + features = [] + with open(f, 'r') as f: + for line in f: + if line.startswith('// required-features:'): + features = [feature.strip() for feature in line.split(':', 2)[1].strip().split(',')] + + tests[name] = features + +# ========= Update Cargo.toml + +things = { + 'bin': [ + { + 'name': f'{name}', + 'path': f'src/bin/{name}.rs', + 'required-features': features, + } + for name, features in tests.items() + ] +} + +SEPARATOR_START = '# BEGIN TESTS\n' +SEPARATOR_END = '# END TESTS\n' +HELP = '# Generated by gen_test.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 + \ + toml.dumps(things) + SEPARATOR_END + after +with open('Cargo.toml', 'w') as f: + f.write(data) diff --git a/embassy/tests/nrf/memory-nrf51422.x b/embassy/tests/nrf/memory-nrf51422.x new file mode 100644 index 0000000..c140005 --- /dev/null +++ b/embassy/tests/nrf/memory-nrf51422.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 32K +} diff --git a/embassy/tests/nrf/memory-nrf52832.x b/embassy/tests/nrf/memory-nrf52832.x new file mode 100644 index 0000000..c140005 --- /dev/null +++ b/embassy/tests/nrf/memory-nrf52832.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 32K +} diff --git a/embassy/tests/nrf/memory-nrf52833.x b/embassy/tests/nrf/memory-nrf52833.x new file mode 100644 index 0000000..a4baa2d --- /dev/null +++ b/embassy/tests/nrf/memory-nrf52833.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 512K + RAM : ORIGIN = 0x20000000, LENGTH = 64K +} diff --git a/embassy/tests/nrf/memory-nrf52840.x b/embassy/tests/nrf/memory-nrf52840.x new file mode 100644 index 0000000..58900a7 --- /dev/null +++ b/embassy/tests/nrf/memory-nrf52840.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/embassy/tests/nrf/memory-nrf5340.x b/embassy/tests/nrf/memory-nrf5340.x new file mode 100644 index 0000000..58900a7 --- /dev/null +++ b/embassy/tests/nrf/memory-nrf5340.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/embassy/tests/nrf/memory-nrf9160.x b/embassy/tests/nrf/memory-nrf9160.x new file mode 100644 index 0000000..58900a7 --- /dev/null +++ b/embassy/tests/nrf/memory-nrf9160.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/embassy/tests/nrf/src/bin/buffered_uart.rs b/embassy/tests/nrf/src/bin/buffered_uart.rs new file mode 100644 index 0000000..04f3283 --- /dev/null +++ b/embassy/tests/nrf/src/bin/buffered_uart.rs @@ -0,0 +1,82 @@ +// required-features: easydma +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::buffered_uarte::{self, BufferedUarte}; +use embassy_nrf::{peripherals, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD1M; + + let mut tx_buffer = [0u8; 1024]; + let mut rx_buffer = [0u8; 1024]; + + // test teardown + recreate of the buffereduarte works fine. + for _ in 0..2 { + let u = BufferedUarte::new( + &mut peri!(p, UART0), + &mut p.TIMER0, + &mut p.PPI_CH0, + &mut p.PPI_CH1, + &mut p.PPI_GROUP0, + irqs!(UART0_BUFFERED), + &mut peri!(p, PIN_A), + &mut peri!(p, PIN_B), + config.clone(), + &mut rx_buffer, + &mut tx_buffer, + ); + + info!("uarte initialized!"); + + let (mut rx, mut tx) = u.split(); + + const COUNT: usize = 40_000; + + let tx_fut = async { + let mut tx_buf = [0; 215]; + let mut i = 0; + while i < COUNT { + let n = tx_buf.len().min(COUNT - i); + let tx_buf = &mut tx_buf[..n]; + for (j, b) in tx_buf.iter_mut().enumerate() { + *b = (i + j) as u8; + } + let n = unwrap!(tx.write(tx_buf).await); + i += n; + } + }; + let rx_fut = async { + let mut i = 0; + while i < COUNT { + let buf = unwrap!(rx.fill_buf().await); + + for &b in buf { + if b != i as u8 { + panic!("mismatch {} vs {}, index {}", b, i as u8, i); + } + i = i + 1; + } + + let n = buf.len(); + rx.consume(n); + } + }; + + join(rx_fut, tx_fut).await; + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/bin/buffered_uart_full.rs b/embassy/tests/nrf/src/bin/buffered_uart_full.rs new file mode 100644 index 0000000..09353bb --- /dev/null +++ b/embassy/tests/nrf/src/bin/buffered_uart_full.rs @@ -0,0 +1,70 @@ +// required-features: easydma +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_nrf::buffered_uarte::{self, BufferedUarte}; +use embassy_nrf::{peripherals, uarte}; +use embedded_io_async::{Read, Write}; +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::BAUD1M; + + let mut tx_buffer = [0u8; 500]; + let mut rx_buffer = [0u8; 500]; + + let u = BufferedUarte::new( + peri!(p, UART0), + p.TIMER0, + p.PPI_CH0, + p.PPI_CH1, + p.PPI_GROUP0, + irqs!(UART0_BUFFERED), + peri!(p, PIN_A), + peri!(p, PIN_B), + config.clone(), + &mut rx_buffer, + &mut tx_buffer, + ); + + info!("uarte initialized!"); + + let (mut rx, mut tx) = u.split(); + + let mut buf = [0; 500]; + for (j, b) in buf.iter_mut().enumerate() { + *b = j as u8; + } + + // Write 500b. This causes the rx buffer to get exactly full. + unwrap!(tx.write_all(&buf).await); + unwrap!(tx.flush().await); + + // Read those 500b. + unwrap!(rx.read_exact(&mut buf).await); + for (j, b) in buf.iter().enumerate() { + assert_eq!(*b, j as u8); + } + + // The buffer should now be unclogged. Write 500b again. + unwrap!(tx.write_all(&buf).await); + unwrap!(tx.flush().await); + + // Read should work again. + unwrap!(rx.read_exact(&mut buf).await); + for (j, b) in buf.iter().enumerate() { + assert_eq!(*b, j as u8); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/bin/buffered_uart_halves.rs b/embassy/tests/nrf/src/bin/buffered_uart_halves.rs new file mode 100644 index 0000000..bdf5ad7 --- /dev/null +++ b/embassy/tests/nrf/src/bin/buffered_uart_halves.rs @@ -0,0 +1,86 @@ +// required-features: two-uarts +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::buffered_uarte::{self, BufferedUarteRx, BufferedUarteTx}; +use embassy_nrf::{peripherals, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD1M; + + let mut tx_buffer = [0u8; 1024]; + let mut rx_buffer = [0u8; 1024]; + + // test teardown + recreate of the buffereduarte works fine. + for _ in 0..2 { + const COUNT: usize = 40_000; + + let mut tx = BufferedUarteTx::new( + &mut peri!(p, UART1), + irqs!(UART1_BUFFERED), + &mut peri!(p, PIN_A), + config.clone(), + &mut tx_buffer, + ); + + let mut rx = BufferedUarteRx::new( + &mut peri!(p, UART0), + &mut p.TIMER0, + &mut p.PPI_CH0, + &mut p.PPI_CH1, + &mut p.PPI_GROUP0, + irqs!(UART0_BUFFERED), + &mut peri!(p, PIN_B), + config.clone(), + &mut rx_buffer, + ); + + let tx_fut = async { + info!("tx initialized!"); + + let mut tx_buf = [0; 215]; + let mut i = 0; + while i < COUNT { + let n = tx_buf.len().min(COUNT - i); + let tx_buf = &mut tx_buf[..n]; + for (j, b) in tx_buf.iter_mut().enumerate() { + *b = (i + j) as u8; + } + let n = unwrap!(tx.write(tx_buf).await); + i += n; + } + }; + let rx_fut = async { + info!("rx initialized!"); + + let mut i = 0; + while i < COUNT { + let buf = unwrap!(rx.fill_buf().await); + + for &b in buf { + assert_eq!(b, i as u8); + i = i + 1; + } + + let n = buf.len(); + rx.consume(n); + } + }; + + join(rx_fut, tx_fut).await; + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/bin/buffered_uart_spam.rs b/embassy/tests/nrf/src/bin/buffered_uart_spam.rs new file mode 100644 index 0000000..cf9ca50 --- /dev/null +++ b/embassy/tests/nrf/src/bin/buffered_uart_spam.rs @@ -0,0 +1,86 @@ +// required-features: two-uarts +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use core::mem; +use core::ptr::NonNull; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_nrf::buffered_uarte::{self, BufferedUarteRx}; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::ppi::{Event, Ppi, Task}; +use embassy_nrf::uarte::UarteTx; +use embassy_nrf::{pac, peripherals, uarte}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD1M; + + let mut rx_buffer = [0u8; 1024]; + + mem::forget(Output::new(&mut peri!(p, PIN_A), Level::High, OutputDrive::Standard)); + + let mut u = BufferedUarteRx::new( + peri!(p, UART0), + p.TIMER0, + p.PPI_CH0, + p.PPI_CH1, + p.PPI_GROUP0, + irqs!(UART0_BUFFERED), + peri!(p, PIN_B), + config.clone(), + &mut rx_buffer, + ); + + info!("uarte initialized!"); + + // uarte needs some quiet time to start rxing properly. + Timer::after_millis(10).await; + + // Tx spam in a loop. + const NSPAM: usize = 17; + static mut TX_BUF: [u8; NSPAM] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + let _spam = UarteTx::new(peri!(p, UART1), irqs!(UART1), peri!(p, PIN_A), config.clone()); + let spam_peri = pac::UARTE1; + let event = unsafe { Event::new_unchecked(NonNull::new_unchecked(spam_peri.events_endtx().as_ptr())) }; + let task = unsafe { Task::new_unchecked(NonNull::new_unchecked(spam_peri.tasks_starttx().as_ptr())) }; + let mut spam_ppi = Ppi::new_one_to_one(p.PPI_CH2, event, task); + spam_ppi.enable(); + let p = (&raw mut TX_BUF) as *mut u8; + spam_peri.txd().ptr().write_value(p as u32); + spam_peri.txd().maxcnt().write(|w| w.set_maxcnt(NSPAM as _)); + spam_peri.tasks_starttx().write_value(1); + + let mut i = 0; + let mut total = 0; + while total < 256 * 1024 { + let buf = unwrap!(u.fill_buf().await); + //info!("rx {}", buf); + + for &b in buf { + assert_eq!(b, unsafe { TX_BUF[i] }); + + i = i + 1; + if i == NSPAM { + i = 0; + } + } + + // Read bytes have to be explicitly consumed, otherwise fill_buf() will return them again + let n = buf.len(); + u.consume(n); + total += n; + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/bin/ethernet_enc28j60_perf.rs b/embassy/tests/nrf/src/bin/ethernet_enc28j60_perf.rs new file mode 100644 index 0000000..ed58627 --- /dev/null +++ b/embassy/tests/nrf/src/bin/ethernet_enc28j60_perf.rs @@ -0,0 +1,85 @@ +// required-features: nrf52840 +#![no_std] +#![no_main] +teleprobe_meta::target!(b"ak-gwe-r7"); +teleprobe_meta::timeout!(120); + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_net::StackResources; +use embassy_net_enc28j60::Enc28j60; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::rng::Rng; +use embassy_nrf::spim::{self, Spim}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embassy_time::Delay; +use embedded_hal_bus::spi::ExclusiveDevice; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; + RNG => embassy_nrf::rng::InterruptHandler; +}); + +type MyDriver = Enc28j60, Output<'static>, Delay>, Output<'static>>; + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, MyDriver>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("running!"); + + let eth_sck = p.P0_20; + let eth_mosi = p.P0_22; + let eth_miso = p.P0_24; + let eth_cs = p.P0_15; + let eth_rst = p.P0_13; + let _eth_irq = p.P0_12; + + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M16; + let spi = spim::Spim::new(p.SPI3, Irqs, eth_sck, eth_miso, eth_mosi, config); + let cs = Output::new(eth_cs, Level::High, OutputDrive::Standard); + let spi = ExclusiveDevice::new(spi, cs, Delay); + + let rst = Output::new(eth_rst, Level::High, OutputDrive::Standard); + let mac_addr = [2, 3, 4, 5, 6, 7]; + let device = Enc28j60::new(spi, Some(rst), mac_addr); + + let config = embassy_net::Config::dhcpv4(Default::default()); + // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + // }); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + perf_client::run( + stack, + perf_client::Expected { + down_kbps: 200, + up_kbps: 200, + updown_kbps: 150, + }, + ) + .await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/bin/gpio.rs b/embassy/tests/nrf/src/bin/gpio.rs new file mode 100644 index 0000000..4995d24 --- /dev/null +++ b/embassy/tests/nrf/src/bin/gpio.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use defmt::{assert, info}; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; +use embassy_time::Timer; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let input = Input::new(peri!(p, PIN_A), Pull::Up); + let mut output = Output::new(peri!(p, PIN_B), Level::Low, OutputDrive::Standard); + + output.set_low(); + assert!(output.is_set_low()); + Timer::after_millis(10).await; + assert!(input.is_low()); + + output.set_high(); + assert!(output.is_set_high()); + Timer::after_millis(10).await; + assert!(input.is_high()); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/bin/gpiote.rs b/embassy/tests/nrf/src/bin/gpiote.rs new file mode 100644 index 0000000..0700016 --- /dev/null +++ b/embassy/tests/nrf/src/bin/gpiote.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use defmt::{assert, info}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; +use embassy_time::{Duration, Instant, Timer}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let mut input = Input::new(peri!(p, PIN_A), Pull::Up); + let mut output = Output::new(peri!(p, PIN_B), Level::Low, OutputDrive::Standard); + + let fut1 = async { + Timer::after_millis(100).await; + output.set_high(); + }; + let fut2 = async { + let start = Instant::now(); + input.wait_for_high().await; + let dur = Instant::now() - start; + info!("took {} ms", dur.as_millis()); + assert!((Duration::from_millis(90)..Duration::from_millis(110)).contains(&dur)); + }; + + join(fut1, fut2).await; + + let fut1 = async { + Timer::after_millis(100).await; + output.set_low(); + }; + let fut2 = async { + let start = Instant::now(); + input.wait_for_low().await; + let dur = Instant::now() - start; + info!("took {} ms", dur.as_millis()); + assert!((Duration::from_millis(90)..Duration::from_millis(110)).contains(&dur)); + }; + + join(fut1, fut2).await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/bin/spim.rs b/embassy/tests/nrf/src/bin/spim.rs new file mode 100644 index 0000000..c2ec90b --- /dev/null +++ b/embassy/tests/nrf/src/bin/spim.rs @@ -0,0 +1,42 @@ +// required-features: easydma +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_nrf::spim::Spim; +use embassy_nrf::{peripherals, spim}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M1; + let mut spim = Spim::new( + &mut peri!(p, SPIM0), + irqs!(SPIM0), + &mut peri!(p, PIN_X), + &mut peri!(p, PIN_A), // MISO + &mut peri!(p, PIN_B), // MOSI + config.clone(), + ); + let data = [ + 0x42, 0x43, 0x44, 0x45, 0x66, 0x12, 0x23, 0x34, 0x45, 0x19, 0x91, 0xaa, 0xff, 0xa5, 0x5a, 0x77, + ]; + let mut buf = [0u8; 16]; + + buf.fill(0); + spim.blocking_transfer(&mut buf, &data).unwrap(); + assert_eq!(data, buf); + + buf.fill(0); + spim.transfer(&mut buf, &data).await.unwrap(); + assert_eq!(data, buf); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/bin/timer.rs b/embassy/tests/nrf/src/bin/timer.rs new file mode 100644 index 0000000..1ae9dd6 --- /dev/null +++ b/embassy/tests/nrf/src/bin/timer.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use defmt::{assert, info}; +use embassy_executor::Spawner; +use embassy_time::{Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let _p = embassy_nrf::init(Default::default()); + info!("Hello World!"); + + let start = Instant::now(); + Timer::after_millis(100).await; + let end = Instant::now(); + let ms = (end - start).as_millis(); + info!("slept for {} ms", ms); + assert!(ms >= 99); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/bin/uart_halves.rs b/embassy/tests/nrf/src/bin/uart_halves.rs new file mode 100644 index 0000000..f48ea43 --- /dev/null +++ b/embassy/tests/nrf/src/bin/uart_halves.rs @@ -0,0 +1,41 @@ +// required-features: two-uarts +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::uarte::{UarteRx, UarteTx}; +use embassy_nrf::{peripherals, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD1M; + + let mut tx = UarteTx::new(&mut peri!(p, UART0), irqs!(UART0), &mut peri!(p, PIN_A), config.clone()); + let mut rx = UarteRx::new(&mut peri!(p, UART1), irqs!(UART1), &mut peri!(p, PIN_B), config.clone()); + + let data = [ + 0x42, 0x43, 0x44, 0x45, 0x66, 0x12, 0x23, 0x34, 0x45, 0x19, 0x91, 0xaa, 0xff, 0xa5, 0x5a, 0x77, + ]; + + let tx_fut = async { + tx.write(&data).await.unwrap(); + }; + let rx_fut = async { + let mut buf = [0u8; 16]; + rx.read(&mut buf).await.unwrap(); + assert_eq!(data, buf); + }; + join(rx_fut, tx_fut).await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/bin/uart_split.rs b/embassy/tests/nrf/src/bin/uart_split.rs new file mode 100644 index 0000000..70d8b2e --- /dev/null +++ b/embassy/tests/nrf/src/bin/uart_split.rs @@ -0,0 +1,49 @@ +// required-features: easydma +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::uarte::Uarte; +use embassy_nrf::{peripherals, uarte}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD9600; + + let uarte = Uarte::new( + &mut peri!(p, UART0), + irqs!(UART0), + &mut peri!(p, PIN_A), + &mut peri!(p, PIN_B), + config.clone(), + ); + let (mut tx, mut rx) = uarte.split(); + + let data = [ + 0x42, 0x43, 0x44, 0x45, 0x66, 0x12, 0x23, 0x34, 0x45, 0x19, 0x91, 0xaa, 0xff, 0xa5, 0x5a, 0x77, + ]; + + let tx_fut = async { + Timer::after_millis(10).await; + tx.write(&data).await.unwrap(); + }; + let rx_fut = async { + let mut buf = [0u8; 16]; + rx.read(&mut buf).await.unwrap(); + assert_eq!(data, buf); + }; + join(rx_fut, tx_fut).await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/bin/wifi_esp_hosted_perf.rs b/embassy/tests/nrf/src/bin/wifi_esp_hosted_perf.rs new file mode 100644 index 0000000..34fb810 --- /dev/null +++ b/embassy/tests/nrf/src/bin/wifi_esp_hosted_perf.rs @@ -0,0 +1,111 @@ +// required-features: nrf52840 +#![no_std] +#![no_main] +teleprobe_meta::target!(b"nrf52840-dk"); +teleprobe_meta::timeout!(120); + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_net::{Config, StackResources}; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; +use embassy_nrf::rng::Rng; +use embassy_nrf::spim::{self, Spim}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embassy_time::Delay; +use embedded_hal_bus::spi::ExclusiveDevice; +use static_cell::StaticCell; +use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; + RNG => embassy_nrf::rng::InterruptHandler; +}); + +// Test-only wifi network, no internet access! +const WIFI_NETWORK: &str = "EmbassyTest"; +const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; + +#[embassy_executor::task] +async fn wifi_task( + runner: hosted::Runner< + 'static, + ExclusiveDevice, Output<'static>, Delay>, + Input<'static>, + Output<'static>, + >, +) -> ! { + runner.run().await +} + +type MyDriver = hosted::NetDriver<'static>; + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, MyDriver>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_nrf::init(Default::default()); + + let miso = p.P0_28; + let sck = p.P0_29; + let mosi = p.P0_30; + let cs = Output::new(p.P0_31, Level::High, OutputDrive::HighDrive); + let handshake = Input::new(p.P1_01, Pull::Up); + let ready = Input::new(p.P1_04, Pull::None); + let reset = Output::new(p.P1_05, Level::Low, OutputDrive::Standard); + + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M32; + config.mode = spim::MODE_2; // !!! + let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config); + let spi = ExclusiveDevice::new(spi, cs, Delay); + + static STATE: StaticCell = StaticCell::new(); + let (device, mut control, runner) = embassy_net_esp_hosted::new( + STATE.init(embassy_net_esp_hosted::State::new()), + spi, + handshake, + ready, + reset, + ) + .await; + + unwrap!(spawner.spawn(wifi_task(runner))); + + unwrap!(control.init().await); + unwrap!(control.connect(WIFI_NETWORK, WIFI_PASSWORD).await); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + device, + Config::dhcpv4(Default::default()), + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + perf_client::run( + stack, + perf_client::Expected { + down_kbps: 50, + up_kbps: 50, + updown_kbps: 50, + }, + ) + .await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/nrf/src/common.rs b/embassy/tests/nrf/src/common.rs new file mode 100644 index 0000000..ebd332d --- /dev/null +++ b/embassy/tests/nrf/src/common.rs @@ -0,0 +1,117 @@ +#![macro_use] + +use {defmt_rtt as _, panic_probe as _}; + +#[cfg(feature = "nrf52832")] +teleprobe_meta::target!(b"nrf52832-dk"); +#[cfg(feature = "nrf52840")] +teleprobe_meta::target!(b"nrf52840-dk"); +#[cfg(feature = "nrf52833")] +teleprobe_meta::target!(b"nrf52833-dk"); +#[cfg(feature = "nrf5340")] +teleprobe_meta::target!(b"nrf5340-dk"); +#[cfg(feature = "nrf9160")] +teleprobe_meta::target!(b"nrf9160-dk"); +#[cfg(feature = "nrf51422")] +teleprobe_meta::target!(b"nrf51-dk"); + +macro_rules! define_peris { + ($($name:ident = $peri:ident,)* $(@irq $irq_name:ident = $irq_code:tt,)*) => { + #[allow(unused_macros)] + macro_rules! peri { + $( + ($p:expr, $name) => { + $p.$peri + }; + )* + } + #[allow(unused_macros)] + macro_rules! irqs { + $( + ($irq_name) => {{ + embassy_nrf::bind_interrupts!(struct Irqs $irq_code); + Irqs + }}; + )* + ( @ dummy ) => {}; + } + + #[allow(unused)] + #[allow(non_camel_case_types)] + pub mod peris { + $( + pub type $name = embassy_nrf::peripherals::$peri; + )* + } + }; +} + +#[cfg(feature = "nrf51422")] +define_peris!(PIN_A = P0_13, PIN_B = P0_14,); + +#[cfg(feature = "nrf52832")] +define_peris!( + PIN_A = P0_11, PIN_B = P0_12, + PIN_X = P0_13, + UART0 = UARTE0, + SPIM0 = TWISPI0, + @irq UART0 = {UARTE0 => uarte::InterruptHandler;}, + @irq UART0_BUFFERED = {UARTE0 => buffered_uarte::InterruptHandler;}, + @irq SPIM0 = {TWISPI0 => spim::InterruptHandler;}, +); + +#[cfg(feature = "nrf52833")] +define_peris!( + PIN_A = P1_01, PIN_B = P1_02, + PIN_X = P1_03, + UART0 = UARTE0, + UART1 = UARTE1, + SPIM0 = TWISPI0, + @irq UART0 = {UARTE0 => uarte::InterruptHandler;}, + @irq UART1 = {UARTE1 => uarte::InterruptHandler;}, + @irq UART0_BUFFERED = {UARTE0 => buffered_uarte::InterruptHandler;}, + @irq UART1_BUFFERED = {UARTE1 => buffered_uarte::InterruptHandler;}, + @irq SPIM0 = {TWISPI0 => spim::InterruptHandler;}, +); + +#[cfg(feature = "nrf52840")] +define_peris!( + PIN_A = P1_02, PIN_B = P1_03, + PIN_X = P1_04, + UART0 = UARTE0, + UART1 = UARTE1, + SPIM0 = TWISPI0, + @irq UART0 = {UARTE0 => uarte::InterruptHandler;}, + @irq UART1 = {UARTE1 => uarte::InterruptHandler;}, + @irq UART0_BUFFERED = {UARTE0 => buffered_uarte::InterruptHandler;}, + @irq UART1_BUFFERED = {UARTE1 => buffered_uarte::InterruptHandler;}, + @irq SPIM0 = {TWISPI0 => spim::InterruptHandler;}, +); + +#[cfg(feature = "nrf5340")] +define_peris!( + PIN_A = P1_08, PIN_B = P1_09, + PIN_X = P1_10, + UART0 = SERIAL0, + UART1 = SERIAL1, + SPIM0 = SERIAL0, + @irq UART0 = {SERIAL0 => uarte::InterruptHandler;}, + @irq UART1 = {SERIAL1 => uarte::InterruptHandler;}, + @irq UART0_BUFFERED = {SERIAL0 => buffered_uarte::InterruptHandler;}, + @irq UART1_BUFFERED = {SERIAL1 => buffered_uarte::InterruptHandler;}, + @irq SPIM0 = {SERIAL0 => spim::InterruptHandler;}, +); + +#[cfg(feature = "nrf9160")] +define_peris!( + PIN_A = P0_00, PIN_B = P0_01, + PIN_X = P0_02, + UART0 = SERIAL0, + UART1 = SERIAL1, + SPIM0 = SERIAL0, + @irq UART0 = {SERIAL0 => uarte::InterruptHandler;}, + @irq UART1 = {SERIAL1 => uarte::InterruptHandler;}, + @irq UART0_BUFFERED = {SERIAL0 => buffered_uarte::InterruptHandler;}, + @irq UART1_BUFFERED = {SERIAL1 => buffered_uarte::InterruptHandler;}, + @irq SPIM0 = {SERIAL0 => spim::InterruptHandler;}, +); diff --git a/embassy/tests/perf-client/Cargo.toml b/embassy/tests/perf-client/Cargo.toml new file mode 100644 index 0000000..3bbc1b6 --- /dev/null +++ b/embassy/tests/perf-client/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "perf-client" +version = "0.1.0" +edition = "2021" + +[dependencies] +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", ] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +defmt = "0.3.0" diff --git a/embassy/tests/perf-client/src/lib.rs b/embassy/tests/perf-client/src/lib.rs new file mode 100644 index 0000000..4bd9e56 --- /dev/null +++ b/embassy/tests/perf-client/src/lib.rs @@ -0,0 +1,179 @@ +#![no_std] + +use defmt::{assert, *}; +use embassy_futures::join::join; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, Stack}; +use embassy_time::{with_timeout, Duration, Timer}; + +pub struct Expected { + pub down_kbps: usize, + pub up_kbps: usize, + pub updown_kbps: usize, +} + +pub async fn run(stack: Stack<'_>, expected: Expected) { + info!("Waiting for DHCP up..."); + while stack.config_v4().is_none() { + Timer::after_millis(100).await; + } + info!("IP addressing up!"); + + let down = test_download(stack).await; + let up = test_upload(stack).await; + let updown = test_upload_download(stack).await; + + assert!(down > expected.down_kbps); + assert!(up > expected.up_kbps); + assert!(updown > expected.updown_kbps); +} + +const TEST_DURATION: usize = 10; +const IO_BUFFER_SIZE: usize = 1024; +const RX_BUFFER_SIZE: usize = 4096; +const TX_BUFFER_SIZE: usize = 4096; +const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2); +const DOWNLOAD_PORT: u16 = 4321; +const UPLOAD_PORT: u16 = 4322; +const UPLOAD_DOWNLOAD_PORT: u16 = 4323; + +async fn test_download(stack: Stack<'_>) -> usize { + info!("Testing download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let mut rx_buf = [0; IO_BUFFER_SIZE]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("download: {} kB/s", kbps); + kbps +} + +async fn test_upload(stack: Stack<'_>) -> usize { + info!("Testing upload..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let buf = [0; IO_BUFFER_SIZE]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.write(&buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload: {} kB/s", kbps); + kbps +} + +async fn test_upload_download(stack: Stack<'_>) -> usize { + info!("Testing upload+download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let (mut reader, mut writer) = socket.split(); + + let tx_buf = [0; IO_BUFFER_SIZE]; + let mut rx_buf = [0; IO_BUFFER_SIZE]; + let mut total: usize = 0; + let tx_fut = async { + loop { + match writer.write(&tx_buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(_) => {} + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }; + + let rx_fut = async { + loop { + match reader.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }; + + with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut)) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload+download: {} kB/s", kbps); + kbps +} diff --git a/embassy/tests/perf-server/Cargo.toml b/embassy/tests/perf-server/Cargo.toml new file mode 100644 index 0000000..5320390 --- /dev/null +++ b/embassy/tests/perf-server/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "perf-server" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "0.4.17" +pretty_env_logger = "0.4.0" diff --git a/embassy/tests/perf-server/deploy.sh b/embassy/tests/perf-server/deploy.sh new file mode 100755 index 0000000..032e99c --- /dev/null +++ b/embassy/tests/perf-server/deploy.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -euxo pipefail + +HOST=root@192.168.1.3 + +cargo build --release +ssh $HOST -- systemctl stop perf-server +scp target/release/perf-server $HOST:/root +scp perf-server.service $HOST:/etc/systemd/system/ +ssh $HOST -- 'systemctl daemon-reload; systemctl restart perf-server' \ No newline at end of file diff --git a/embassy/tests/perf-server/perf-server.service b/embassy/tests/perf-server/perf-server.service new file mode 100644 index 0000000..c14c5d1 --- /dev/null +++ b/embassy/tests/perf-server/perf-server.service @@ -0,0 +1,16 @@ +[Unit] +Description=perf-server +After=network.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +Restart=always +RestartSec=1 +User=root +ExecStart=/root/perf-server +Environment=RUST_BACKTRACE=1 +Environment=RUST_LOG=info + +[Install] +WantedBy=multi-user.target diff --git a/embassy/tests/perf-server/src/main.rs b/embassy/tests/perf-server/src/main.rs new file mode 100644 index 0000000..f6e7efc --- /dev/null +++ b/embassy/tests/perf-server/src/main.rs @@ -0,0 +1,90 @@ +use std::io::{Read, Write}; +use std::net::{TcpListener, TcpStream}; +use std::thread::spawn; +use std::time::Duration; + +use log::info; + +fn main() { + pretty_env_logger::init(); + spawn(|| rx_listen()); + spawn(|| rxtx_listen()); + tx_listen(); +} + +fn tx_listen() { + info!("tx: listening on 0.0.0.0:4321"); + let listener = TcpListener::bind("0.0.0.0:4321").unwrap(); + loop { + let (socket, addr) = listener.accept().unwrap(); + info!("tx: received connection from: {}", addr); + spawn(|| tx_conn(socket)); + } +} + +fn tx_conn(mut socket: TcpStream) { + socket.set_read_timeout(Some(Duration::from_secs(30))).unwrap(); + socket.set_write_timeout(Some(Duration::from_secs(30))).unwrap(); + + let buf = [0; 1024]; + loop { + if let Err(e) = socket.write_all(&buf) { + info!("tx: failed to write to socket; err = {:?}", e); + return; + } + } +} + +fn rx_listen() { + info!("rx: listening on 0.0.0.0:4322"); + let listener = TcpListener::bind("0.0.0.0:4322").unwrap(); + loop { + let (socket, addr) = listener.accept().unwrap(); + info!("rx: received connection from: {}", addr); + spawn(|| rx_conn(socket)); + } +} + +fn rx_conn(mut socket: TcpStream) { + socket.set_read_timeout(Some(Duration::from_secs(30))).unwrap(); + socket.set_write_timeout(Some(Duration::from_secs(30))).unwrap(); + + let mut buf = [0; 1024]; + loop { + if let Err(e) = socket.read_exact(&mut buf) { + info!("rx: failed to read from socket; err = {:?}", e); + return; + } + } +} + +fn rxtx_listen() { + info!("rxtx: listening on 0.0.0.0:4323"); + let listener = TcpListener::bind("0.0.0.0:4323").unwrap(); + loop { + let (socket, addr) = listener.accept().unwrap(); + info!("rxtx: received connection from: {}", addr); + spawn(|| rxtx_conn(socket)); + } +} + +fn rxtx_conn(mut socket: TcpStream) { + socket.set_read_timeout(Some(Duration::from_secs(30))).unwrap(); + socket.set_write_timeout(Some(Duration::from_secs(30))).unwrap(); + + let mut buf = [0; 1024]; + loop { + match socket.read(&mut buf) { + Ok(n) => { + if let Err(e) = socket.write_all(&buf[..n]) { + info!("rxtx: failed to write to socket; err = {:?}", e); + return; + } + } + Err(e) => { + info!("rxtx: failed to read from socket; err = {:?}", e); + return; + } + } + } +} diff --git a/embassy/tests/riscv32/.cargo/config.toml b/embassy/tests/riscv32/.cargo/config.toml new file mode 100644 index 0000000..58299b5 --- /dev/null +++ b/embassy/tests/riscv32/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.riscv32imac-unknown-none-elf] +runner = "true" + +[build] +target = "riscv32imac-unknown-none-elf" diff --git a/embassy/tests/riscv32/Cargo.toml b/embassy/tests/riscv32/Cargo.toml new file mode 100644 index 0000000..e76e07d --- /dev/null +++ b/embassy/tests/riscv32/Cargo.toml @@ -0,0 +1,46 @@ +[package] +edition = "2021" +name = "embassy-riscv-tests" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +critical-section = { version = "1.1.1", features = ["restore-state-bool"] } +embassy-sync = { version = "0.6.1", path = "../../embassy-sync" } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-riscv32", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +riscv-rt = "0.12.2" +riscv = { version = "0.11.1", features = ["critical-section-single-hart"] } + + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 's' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 's' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/embassy/tests/riscv32/build.rs b/embassy/tests/riscv32/build.rs new file mode 100644 index 0000000..e4a26c4 --- /dev/null +++ b/embassy/tests/riscv32/build.rs @@ -0,0 +1,8 @@ +use std::error::Error; + +fn main() -> Result<(), Box> { + println!("cargo:rustc-link-arg-bins=-Tmemory.x"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + + Ok(()) +} diff --git a/embassy/tests/riscv32/link.x b/embassy/tests/riscv32/link.x new file mode 100644 index 0000000..4076b0c --- /dev/null +++ b/embassy/tests/riscv32/link.x @@ -0,0 +1,214 @@ +/* # EMBASSY notes + This file is a workaround for https://github.com/rust-embedded/riscv/issues/196 + Remove when fixed upstream. +*/ +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol if not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- In this linker script, you may find symbols that look like `${...}` (e.g., `4`). + These are wildcards used by the `build.rs` script to adapt to different target particularities. + Check `build.rs` for more details about these symbols. + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `4`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `4`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(REGION_TEXT)); +PROVIDE(_stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler);; +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > REGION_TEXT + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + } > REGION_TEXT + + .eh_frame : { KEEP(*(.eh_frame)) } > REGION_TEXT + .eh_frame_hdr : { *(.eh_frame_hdr) } > REGION_TEXT + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > REGION_RODATA + + .data : ALIGN(4) + { + _sidata = LOADADDR(.data); + _sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(4); + _edata = .; + } > REGION_DATA AT > REGION_RODATA + + .bss (NOLOAD) : ALIGN(4) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(4); + _ebss = .; + } > REGION_BSS + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > REGION_HEAP + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > REGION_STACK + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } +} + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_RODATA) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_RODATA must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_DATA) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_DATA must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_HEAP) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_HEAP must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_STACK) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_STACK must be 4-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 4 == 0 && _edata % 4 == 0, " +BUG(riscv-rt): .data is not 4-byte aligned"); + +ASSERT(_sidata % 4 == 0, " +BUG(riscv-rt): the LMA of .data is not 4-byte aligned"); + +ASSERT(_sbss % 4 == 0 && _ebss % 4 == 0, " +BUG(riscv-rt): .bss is not 4-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT), " +ERROR(riscv-rt): The .text section must be placed inside the REGION_TEXT region. +Set _stext to an address smaller than 'ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/embassy/tests/riscv32/memory.x b/embassy/tests/riscv32/memory.x new file mode 100644 index 0000000..316d577 --- /dev/null +++ b/embassy/tests/riscv32/memory.x @@ -0,0 +1,14 @@ +MEMORY +{ + ROM : ORIGIN = 0x80000000, LENGTH = 0x00020000 + RAM : ORIGIN = 0x84000000, LENGTH = 0x00008000 +} + +REGION_ALIAS("REGION_TEXT", ROM); +REGION_ALIAS("REGION_RODATA", ROM); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); +REGION_ALIAS("REGION_STACK", RAM); + +_stack_start = ORIGIN(RAM) + LENGTH(RAM) - 4; diff --git a/embassy/tests/riscv32/src/bin/empty.rs b/embassy/tests/riscv32/src/bin/empty.rs new file mode 100644 index 0000000..55a1030 --- /dev/null +++ b/embassy/tests/riscv32/src/bin/empty.rs @@ -0,0 +1,15 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Don't do anything, just make sure it compiles. + loop {} +} diff --git a/embassy/tests/rp/.cargo/config.toml b/embassy/tests/rp/.cargo/config.toml new file mode 100644 index 0000000..4337924 --- /dev/null +++ b/embassy/tests/rp/.cargo/config.toml @@ -0,0 +1,21 @@ +[unstable] +# enabling these breaks the float tests during linking, with intrinsics +# duplicated between embassy-rp and compilter_builtins +#build-std = ["core"] +#build-std-features = ["panic_immediate_abort"] + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "teleprobe client run" +#runner = "teleprobe local run --chip RP2040 --elf" + +rustflags = [ + # Code-size optimizations. + #"-Z", "trap-unreachable=no", + "-C", "no-vectorize-loops", +] + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info" diff --git a/embassy/tests/rp/Cargo.toml b/embassy/tests/rp/Cargo.toml new file mode 100644 index 0000000..8cd4041 --- /dev/null +++ b/embassy/tests/rp/Cargo.toml @@ -0,0 +1,68 @@ +[package] +edition = "2021" +name = "embassy-rp-tests" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +teleprobe-meta = "1.1" + +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", ] } +embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = [ "defmt", "unstable-pac", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram", "rp2040"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet"] } +embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal/"} +cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } +cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } +perf-client = { path = "../perf-client" } + +defmt = "0.3.0" +defmt-rtt = "0.4" + +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" } +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } +panic-probe = { version = "0.3.0", features = ["print-defmt"] } +embedded-io-async = { version = "0.6.1" } +embedded-storage = { version = "0.3" } +static_cell = "2" +portable-atomic = { version = "1.5", features = ["critical-section"] } +pio = "0.2" +pio-proc = "0.2" +rand = { version = "0.8.5", default-features = false } + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 's' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 's' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/embassy/tests/rp/build.rs b/embassy/tests/rp/build.rs new file mode 100644 index 0000000..71c82a7 --- /dev/null +++ b/embassy/tests/rp/build.rs @@ -0,0 +1,17 @@ +use std::error::Error; +use std::path::PathBuf; +use std::{env, fs}; + +fn main() -> Result<(), Box> { + let out = PathBuf::from(env::var("OUT_DIR").unwrap()); + fs::write(out.join("link_ram.x"), include_bytes!("../link_ram_cortex_m.x")).unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=link_ram.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + println!("cargo:rustc-link-arg-bins=-Tteleprobe.x"); + + Ok(()) +} diff --git a/embassy/tests/rp/memory.x b/embassy/tests/rp/memory.x new file mode 100644 index 0000000..bf0041d --- /dev/null +++ b/embassy/tests/rp/memory.x @@ -0,0 +1,4 @@ +/* Provides information about the memory layout of the device */ +MEMORY { + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/embassy/tests/rp/src/bin/adc.rs b/embassy/tests/rp/src/bin/adc.rs new file mode 100644 index 0000000..65c2464 --- /dev/null +++ b/embassy/tests/rp/src/bin/adc.rs @@ -0,0 +1,154 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler, Sample}; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_rp::init(Default::default()); + let _power_reg_pwm_mode = Output::new(p.PIN_23, Level::High); + let _wifi_off = Output::new(p.PIN_25, Level::High); + let mut adc = Adc::new(p.ADC, Irqs, Config::default()); + + { + { + let mut p = Channel::new_pin(&mut p.PIN_26, Pull::Down); + defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); + defmt::assert!(adc.read(&mut p).await.unwrap() < 0b01_0000_0000); + } + { + let mut p = Channel::new_pin(&mut p.PIN_26, Pull::Up); + defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); + defmt::assert!(adc.read(&mut p).await.unwrap() > 0b11_0000_0000); + } + } + // not bothering with async reads from now on + { + { + let mut p = Channel::new_pin(&mut p.PIN_27, Pull::Down); + defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); + } + { + let mut p = Channel::new_pin(&mut p.PIN_27, Pull::Up); + defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); + } + } + { + { + let mut p = Channel::new_pin(&mut p.PIN_28, Pull::Down); + defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); + } + { + let mut p = Channel::new_pin(&mut p.PIN_28, Pull::Up); + defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); + } + } + { + // gp29 is connected to vsys through a 200k/100k divider, + // adding pulls should change the value + let low = { + let mut p = Channel::new_pin(&mut p.PIN_29, Pull::Down); + adc.blocking_read(&mut p).unwrap() + }; + let none = { + let mut p = Channel::new_pin(&mut p.PIN_29, Pull::None); + adc.blocking_read(&mut p).unwrap() + }; + let up = { + let mut p = Channel::new_pin(&mut p.PIN_29, Pull::Up); + adc.blocking_read(&mut p).unwrap() + }; + defmt::assert!(low < none); + defmt::assert!(none < up); + } + { + let temp = convert_to_celsius( + adc.read(&mut Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR)) + .await + .unwrap(), + ); + defmt::assert!(temp > 0.0); + defmt::assert!(temp < 60.0); + } + + // run a bunch of conversions. we'll only check gp29 and the temp + // sensor here for brevity, if those two work the rest will too. + { + // gp29 is connected to vsys through a 200k/100k divider, + // adding pulls should change the value + let mut low = [0u16; 16]; + let mut none = [0u8; 16]; + let mut up = [Sample::default(); 16]; + adc.read_many( + &mut Channel::new_pin(&mut p.PIN_29, Pull::Down), + &mut low, + 1, + &mut p.DMA_CH0, + ) + .await + .unwrap(); + adc.read_many( + &mut Channel::new_pin(&mut p.PIN_29, Pull::None), + &mut none, + 1, + &mut p.DMA_CH0, + ) + .await + .unwrap(); + adc.read_many_raw( + &mut Channel::new_pin(&mut p.PIN_29, Pull::Up), + &mut up, + 1, + &mut p.DMA_CH0, + ) + .await; + defmt::assert!(low.iter().zip(none.iter()).all(|(l, n)| *l >> 4 < *n as u16)); + defmt::assert!(up.iter().all(|s| s.good())); + defmt::assert!(none.iter().zip(up.iter()).all(|(n, u)| (*n as u16) < u.value())); + } + { + let mut temp = [0u16; 16]; + adc.read_many( + &mut Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR), + &mut temp, + 1, + &mut p.DMA_CH0, + ) + .await + .unwrap(); + let temp = temp.map(convert_to_celsius); + defmt::assert!(temp.iter().all(|t| *t > 0.0)); + defmt::assert!(temp.iter().all(|t| *t < 60.0)); + } + { + let mut multi = [0u16; 2]; + let mut channels = [ + Channel::new_pin(&mut p.PIN_26, Pull::Up), + Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR), + ]; + adc.read_many_multichannel(&mut channels, &mut multi, 1, &mut p.DMA_CH0) + .await + .unwrap(); + defmt::assert!(multi[0] > 3_000); + let temp = convert_to_celsius(multi[1]); + defmt::assert!(temp > 0.0 && temp < 60.0); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +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/embassy/tests/rp/src/bin/bootsel.rs b/embassy/tests/rp/src/bin/bootsel.rs new file mode 100644 index 0000000..e88d8bf --- /dev/null +++ b/embassy/tests/rp/src/bin/bootsel.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after_millis(10).await; + + assert_eq!(p.BOOTSEL.is_pressed(), false); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/cyw43-perf.rs b/embassy/tests/rp/src/bin/cyw43-perf.rs new file mode 100644 index 0000000..30e4afb --- /dev/null +++ b/embassy/tests/rp/src/bin/cyw43-perf.rs @@ -0,0 +1,107 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_net::{Config, StackResources}; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::{bind_interrupts, rom_data}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +teleprobe_meta::timeout!(120); + +// Test-only wifi network, no internet access! +const WIFI_NETWORK: &str = "EmbassyTest"; +const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; + +#[embassy_executor::task] +async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + let p = embassy_rp::init(Default::default()); + + // needed for reading the firmware from flash via XIP. + unsafe { + rom_data::flash_exit_xip(); + rom_data::flash_enter_cmd_xip(); + } + + // cyw43 firmware needs to be flashed manually: + // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x101b0000 + // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x101f8000 + let fw = unsafe { core::slice::from_raw_parts(0x101b0000 as *const u8, 230321) }; + let clm = unsafe { core::slice::from_raw_parts(0x101f8000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + // Generate random seed + let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + Config::dhcpv4(Default::default()), + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + panic!("join failed with status={}", err.status); + } + } + } + + perf_client::run( + stack, + perf_client::Expected { + down_kbps: 300, + up_kbps: 300, + updown_kbps: 300, + }, + ) + .await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/dma_copy_async.rs b/embassy/tests/rp/src/bin/dma_copy_async.rs new file mode 100644 index 0000000..7c64bc3 --- /dev/null +++ b/embassy/tests/rp/src/bin/dma_copy_async.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::dma::copy; +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!"); + + // Check `u8` copy + { + let data: [u8; 2] = [0xC0, 0xDE]; + let mut buf = [0; 2]; + unsafe { copy(p.DMA_CH0, &data, &mut buf).await }; + assert_eq!(buf, data); + } + + // Check `u16` copy + { + let data: [u16; 2] = [0xC0BE, 0xDEAD]; + let mut buf = [0; 2]; + unsafe { copy(p.DMA_CH1, &data, &mut buf).await }; + assert_eq!(buf, data); + } + + // Check `u32` copy + { + let data: [u32; 2] = [0xC0BEDEAD, 0xDEADAAFF]; + let mut buf = [0; 2]; + unsafe { copy(p.DMA_CH2, &data, &mut buf).await }; + assert_eq!(buf, data); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/ethernet_w5100s_perf.rs b/embassy/tests/rp/src/bin/ethernet_w5100s_perf.rs new file mode 100644 index 0000000..ae2adfa --- /dev/null +++ b/embassy/tests/rp/src/bin/ethernet_w5100s_perf.rs @@ -0,0 +1,93 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"w5100s-evb-pico"); +teleprobe_meta::timeout!(120); + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::StackResources; +use embassy_net_wiznet::chip::W5100S; +use embassy_net_wiznet::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::SPI0; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::Delay; +use embedded_hal_bus::spi::ExclusiveDevice; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + W5100S, + ExclusiveDevice, Output<'static>, Delay>, + Input<'static>, + Output<'static>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(State::<8, 8>::new()); + let (device, runner) = embassy_net_wiznet::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await + .unwrap(); + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + RESOURCES.init(StackResources::new()), + seed, + ); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + perf_client::run( + stack, + perf_client::Expected { + down_kbps: 500, + up_kbps: 500, + updown_kbps: 300, + }, + ) + .await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/flash.rs b/embassy/tests/rp/src/bin/flash.rs new file mode 100644 index 0000000..310f0d5 --- /dev/null +++ b/embassy/tests/rp/src/bin/flash.rs @@ -0,0 +1,71 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::flash::{Async, ERASE_SIZE, FLASH_BASE}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const ADDR_OFFSET: u32 = 0x8000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after_millis(10).await; + + let mut flash = embassy_rp::flash::Flash::<_, Async, { 2 * 1024 * 1024 }>::new(p.FLASH, p.DMA_CH0); + + // Get JEDEC id + let jedec = defmt::unwrap!(flash.blocking_jedec_id()); + info!("jedec id: 0x{:x}", jedec); + + // Get unique id + let mut uid = [0; 8]; + defmt::unwrap!(flash.blocking_unique_id(&mut uid)); + info!("unique id: {:?}", uid); + + let mut buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET, &mut buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", buf[0..4]); + + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET, ADDR_OFFSET + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.blocking_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.blocking_write(ADDR_OFFSET, &mut buf)); + + defmt::unwrap!(flash.blocking_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"); + } + + let mut buf = [0u32; ERASE_SIZE / 4]; + + defmt::unwrap!(flash.background_read(ADDR_OFFSET, &mut buf)).await; + info!("Contents after write starts with {=u32:x}", buf[0]); + if buf.iter().any(|x| *x != 0xDADADADA) { + defmt::panic!("unexpected"); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/float.rs b/embassy/tests/rp/src/bin/float.rs new file mode 100644 index 0000000..74e5805 --- /dev/null +++ b/embassy/tests/rp/src/bin/float.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::pac; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + embassy_rp::init(Default::default()); + info!("Hello World!"); + + const PI_F: f32 = 3.1415926535f32; + const PI_D: f64 = 3.14159265358979323846f64; + + pac::BUSCTRL + .perfsel(0) + .write(|r| r.set_perfsel(pac::busctrl::vals::Perfsel::ROM)); + + for i in 0..=360 { + let rad_f = (i as f32) * PI_F / 180.0; + info!( + "{}° float: {=f32} / {=f32} / {=f32} / {=f32}", + i, + rad_f, + rad_f - PI_F, + rad_f + PI_F, + rad_f % PI_F + ); + let rad_d = (i as f64) * PI_D / 180.0; + info!( + "{}° double: {=f64} / {=f64} / {=f64} / {=f64}", + i, + rad_d, + rad_d - PI_D, + rad_d + PI_D, + rad_d % PI_D + ); + Timer::after_millis(10).await; + } + + let rom_accesses = pac::BUSCTRL.perfctr(0).read().perfctr(); + // every float operation used here uses at least 10 cycles + defmt::assert!(rom_accesses >= 360 * 12 * 10); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/gpio.rs b/embassy/tests/rp/src/bin/gpio.rs new file mode 100644 index 0000000..e0c3098 --- /dev/null +++ b/embassy/tests/rp/src/bin/gpio.rs @@ -0,0 +1,246 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert, *}; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Flex, Input, Level, Output, OutputOpenDrain, Pull}; +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 a, mut b) = (p.PIN_0, p.PIN_1); + + // Test initial output + { + let b = Input::new(&mut b, Pull::None); + + { + let a = Output::new(&mut a, Level::Low); + delay(); + assert!(b.is_low()); + assert!(!b.is_high()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); + } + { + let mut a = Output::new(&mut a, Level::High); + delay(); + assert!(!b.is_low()); + assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); + + // Test is_set_low / is_set_high + a.set_low(); + delay(); + assert!(b.is_low()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); + + a.set_high(); + delay(); + assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); + + // Test toggle + a.toggle(); + delay(); + assert!(b.is_low()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); + + a.toggle(); + delay(); + assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); + } + } + + // Test input no pull + { + let b = Input::new(&mut b, Pull::None); + // no pull, the status is undefined + + let mut a = Output::new(&mut a, Level::Low); + delay(); + assert!(b.is_low()); + a.set_high(); + delay(); + assert!(b.is_high()); + } + + // Test input pulldown + { + let b = Input::new(&mut b, Pull::Down); + delay(); + assert!(b.is_low()); + + let mut a = Output::new(&mut a, Level::Low); + delay(); + assert!(b.is_low()); + a.set_high(); + delay(); + assert!(b.is_high()); + } + + // Test input pullup + { + let b = Input::new(&mut b, Pull::Up); + delay(); + assert!(b.is_high()); + + let mut a = Output::new(&mut a, Level::Low); + delay(); + assert!(b.is_low()); + a.set_high(); + delay(); + assert!(b.is_high()); + } + + // OUTPUT OPEN DRAIN + { + let mut b = OutputOpenDrain::new(&mut b, Level::High); + let mut a = Flex::new(&mut a); + 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()); + a.set_pull(Pull::Down); + delay(); + assert!(a.is_low()); + + // When an OutputOpenDrain is low, it drives the pin low. + b.set_low(); + a.set_pull(Pull::Up); + delay(); + assert!(a.is_low()); + a.set_pull(Pull::Down); + 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 + // Test initial output + { + //Flex pin configured as input + let mut b = Flex::new(&mut b); + b.set_as_input(); + + { + //Flex pin configured as output + let mut a = Flex::new(&mut a); //Flex pin configured as output + a.set_low(); // Pin state must be set before configuring the pin, thus we avoid unknown state + a.set_as_output(); + delay(); + assert!(b.is_low()); + } + { + //Flex pin configured as output + let mut a = Flex::new(&mut a); + a.set_high(); + a.set_as_output(); + + delay(); + assert!(b.is_high()); + } + } + + // Test input no pull + { + let mut b = Flex::new(&mut b); + b.set_as_input(); // no pull by default. + + let mut a = Flex::new(&mut a); + a.set_low(); + a.set_as_output(); + + delay(); + assert!(b.is_low()); + a.set_high(); + delay(); + assert!(b.is_high()); + } + + // Test input pulldown + { + let mut b = Flex::new(&mut b); + b.set_as_input(); + b.set_pull(Pull::Down); + delay(); + assert!(b.is_low()); + + let mut a = Flex::new(&mut a); + a.set_low(); + a.set_as_output(); + delay(); + assert!(b.is_low()); + a.set_high(); + delay(); + assert!(b.is_high()); + } + + // Test input pullup + { + let mut b = Flex::new(&mut b); + b.set_as_input(); + b.set_pull(Pull::Up); + delay(); + assert!(b.is_high()); + + let mut a = Flex::new(&mut a); + a.set_high(); + a.set_as_output(); + delay(); + assert!(b.is_high()); + a.set_low(); + delay(); + assert!(b.is_low()); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn delay() { + cortex_m::asm::delay(10000); +} diff --git a/embassy/tests/rp/src/bin/gpio_async.rs b/embassy/tests/rp/src/bin/gpio_async.rs new file mode 100644 index 0000000..40a3044 --- /dev/null +++ b/embassy/tests/rp/src/bin/gpio_async.rs @@ -0,0 +1,148 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("embassy-rp gpio_async test"); + + // On the CI device the following pins are connected with each other. + let (mut output_pin, mut input_pin) = (p.PIN_0, p.PIN_1); + + { + info!("test wait_for_high"); + let mut output = Output::new(&mut output_pin, Level::Low); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_low(), "input was expected to be low"); + + let set_high_future = async { + // Allow time for wait_for_high_future to await wait_for_high(). + Timer::after_millis(10).await; + output.set_high(); + }; + let wait_for_high_future = async { + let start = Instant::now(); + input.wait_for_high().await; + assert_duration(start); + }; + join(set_high_future, wait_for_high_future).await; + info!("test wait_for_high: OK\n"); + } + + { + info!("test wait_for_low"); + let mut output = Output::new(&mut output_pin, Level::High); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_high(), "input was expected to be high"); + + let set_low_future = async { + Timer::after_millis(10).await; + output.set_low(); + }; + let wait_for_low_future = async { + let start = Instant::now(); + input.wait_for_low().await; + assert_duration(start); + }; + join(set_low_future, wait_for_low_future).await; + info!("test wait_for_low: OK\n"); + } + + { + info!("test wait_for_rising_edge"); + let mut output = Output::new(&mut output_pin, Level::Low); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_low(), "input was expected to be low"); + + let set_high_future = async { + Timer::after_millis(10).await; + output.set_high(); + }; + let wait_for_rising_edge_future = async { + let start = Instant::now(); + input.wait_for_rising_edge().await; + assert_duration(start); + }; + join(set_high_future, wait_for_rising_edge_future).await; + info!("test wait_for_rising_edge: OK\n"); + } + + { + info!("test wait_for_falling_edge"); + let mut output = Output::new(&mut output_pin, Level::High); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_high(), "input was expected to be high"); + + let set_low_future = async { + Timer::after_millis(10).await; + output.set_low(); + }; + let wait_for_falling_edge_future = async { + let start = Instant::now(); + input.wait_for_falling_edge().await; + assert_duration(start); + }; + join(set_low_future, wait_for_falling_edge_future).await; + info!("test wait_for_falling_edge: OK\n"); + } + + { + info!("test wait_for_any_edge (falling)"); + let mut output = Output::new(&mut output_pin, Level::High); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_high(), "input was expected to be high"); + + let set_low_future = async { + Timer::after_millis(10).await; + output.set_low(); + }; + let wait_for_any_edge_future = async { + let start = Instant::now(); + input.wait_for_any_edge().await; + assert_duration(start); + }; + join(set_low_future, wait_for_any_edge_future).await; + info!("test wait_for_any_edge (falling): OK\n"); + } + + { + info!("test wait_for_any_edge (rising)"); + let mut output = Output::new(&mut output_pin, Level::Low); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_low(), "input was expected to be low"); + + let set_high_future = async { + Timer::after_millis(10).await; + output.set_high(); + }; + let wait_for_any_edge_future = async { + let start = Instant::now(); + input.wait_for_any_edge().await; + assert_duration(start); + }; + join(set_high_future, wait_for_any_edge_future).await; + info!("test wait_for_any_edge (rising): OK\n"); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); + + fn assert_duration(start: Instant) { + let dur = Instant::now() - start; + assert!(dur >= Duration::from_millis(10) && dur < Duration::from_millis(11)); + } +} diff --git a/embassy/tests/rp/src/bin/gpio_multicore.rs b/embassy/tests/rp/src/bin/gpio_multicore.rs new file mode 100644 index 0000000..e9c6f31 --- /dev/null +++ b/embassy/tests/rp/src/bin/gpio_multicore.rs @@ -0,0 +1,67 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{info, unwrap}; +use embassy_executor::Executor; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals::{PIN_0, PIN_1}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<1024> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); +static CHANNEL0: Channel = 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 *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(p.PIN_1)))); + }, + ); + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task(p.PIN_0)))); +} + +#[embassy_executor::task] +async fn core0_task(p: PIN_0) { + info!("CORE0 is running"); + + let mut pin = Output::new(p, Level::Low); + + CHANNEL0.send(()).await; + CHANNEL1.receive().await; + + pin.set_high(); + + CHANNEL1.receive().await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +#[embassy_executor::task] +async fn core1_task(p: PIN_1) { + info!("CORE1 is running"); + + CHANNEL0.receive().await; + + let mut pin = Input::new(p, Pull::Down); + let wait = pin.wait_for_rising_edge(); + + CHANNEL1.send(()).await; + + wait.await; + + CHANNEL1.send(()).await; +} diff --git a/embassy/tests/rp/src/bin/i2c.rs b/embassy/tests/rp/src/bin/i2c.rs new file mode 100644 index 0000000..9615007 --- /dev/null +++ b/embassy/tests/rp/src/bin/i2c.rs @@ -0,0 +1,237 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert_eq, info, panic, unwrap}; +use embassy_embedded_hal::SetConfig; +use embassy_executor::{Executor, Spawner}; +use embassy_rp::clocks::{PllConfig, XoscConfig}; +use embassy_rp::config::Config as rpConfig; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals::{I2C0, I2C1}; +use embassy_rp::{bind_interrupts, i2c, i2c_slave}; +use embedded_hal_1::i2c::Operation; +use embedded_hal_async::i2c::I2c; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _, panic_probe as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<1024> = Stack::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); + +use crate::i2c::AbortReason; + +bind_interrupts!(struct Irqs { + I2C0_IRQ => i2c::InterruptHandler; + I2C1_IRQ => i2c::InterruptHandler; +}); + +const DEV_ADDR: u8 = 0x42; + +#[embassy_executor::task] +async fn device_task(mut dev: i2c_slave::I2cSlave<'static, I2C1>) -> ! { + info!("Device start"); + + let mut count = 0xD0; + + loop { + let mut buf = [0u8; 128]; + match dev.listen(&mut buf).await { + Ok(i2c_slave::Command::GeneralCall(len)) => { + assert_eq!(buf[..len], [0xCA, 0x11], "recieving the general call failed"); + info!("General Call - OK"); + } + Ok(i2c_slave::Command::Read) => { + loop { + match dev.respond_to_read(&[count]).await { + Ok(x) => match x { + i2c_slave::ReadStatus::Done => break, + i2c_slave::ReadStatus::NeedMoreBytes => count += 1, + i2c_slave::ReadStatus::LeftoverBytes(x) => panic!("tried to write {} extra bytes", x), + }, + Err(e) => match e { + embassy_rp::i2c_slave::Error::Abort(AbortReason::Other(n)) => panic!("Other {:b}", n), + _ => panic!("{}", e), + }, + } + } + count += 1; + } + Ok(i2c_slave::Command::Write(len)) => match len { + 1 => { + assert_eq!(buf[..len], [0xAA], "recieving a single byte failed"); + info!("Single Byte Write - OK") + } + 4 => { + assert_eq!(buf[..len], [0xAA, 0xBB, 0xCC, 0xDD], "recieving 4 bytes failed"); + info!("4 Byte Write - OK") + } + 32 => { + assert_eq!( + buf[..len], + [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31 + ], + "recieving 32 bytes failed" + ); + info!("32 Byte Write - OK") + } + _ => panic!("Invalid write length {}", len), + }, + Ok(i2c_slave::Command::WriteRead(len)) => { + info!("device received write read: {:x}", buf[..len]); + match buf[0] { + 0xC2 => { + let resp_buff = [0xD1, 0xD2, 0xD3, 0xD4]; + dev.respond_to_read(&resp_buff).await.unwrap(); + } + 0xC8 => { + let mut resp_buff = [0u8; 32]; + for i in 0..32 { + resp_buff[i] = i as u8; + } + dev.respond_to_read(&resp_buff).await.unwrap(); + // reset count for next round of tests + count = 0xD0; + } + x => panic!("Invalid Write Read {:x}", x), + } + } + Err(e) => match e { + embassy_rp::i2c_slave::Error::Abort(AbortReason::Other(n)) => panic!("Other {:b}", n), + _ => panic!("{}", e), + }, + } + } +} + +async fn controller_task(con: &mut i2c::I2c<'static, I2C0, i2c::Async>) { + info!("Device start"); + + { + let buf = [0xCA, 0x11]; + con.write(0u16, &buf).await.unwrap(); + info!("Controler general call write"); + embassy_futures::yield_now().await; + } + + { + let mut buf = [0u8]; + con.read(DEV_ADDR, &mut buf).await.unwrap(); + assert_eq!(buf, [0xD0], "single byte read failed"); + info!("single byte read - OK"); + embassy_futures::yield_now().await; + } + + { + let mut buf = [0u8; 4]; + con.read(DEV_ADDR, &mut buf).await.unwrap(); + assert_eq!(buf, [0xD1, 0xD2, 0xD3, 0xD4], "single byte read failed"); + info!("4 byte read - OK"); + embassy_futures::yield_now().await; + } + + { + let buf = [0xAA]; + con.write(DEV_ADDR, &buf).await.unwrap(); + info!("Controler single byte write"); + embassy_futures::yield_now().await; + } + + { + let buf = [0xAA, 0xBB, 0xCC, 0xDD]; + con.write(DEV_ADDR, &buf).await.unwrap(); + info!("Controler 4 byte write"); + embassy_futures::yield_now().await; + } + + { + let mut buf = [0u8; 32]; + for i in 0..32 { + buf[i] = i as u8; + } + con.write(DEV_ADDR, &buf).await.unwrap(); + info!("Controler 32 byte write"); + embassy_futures::yield_now().await; + } + + { + let mut buf = [0u8; 4]; + let mut ops = [Operation::Write(&[0xC2]), Operation::Read(&mut buf)]; + con.transaction(DEV_ADDR, &mut ops).await.unwrap(); + assert_eq!(buf, [0xD1, 0xD2, 0xD3, 0xD4], "write_read failed"); + info!("write_read - OK"); + embassy_futures::yield_now().await; + } + + { + let mut buf = [0u8; 32]; + let mut ops = [Operation::Write(&[0xC8]), Operation::Read(&mut buf)]; + con.transaction(DEV_ADDR, &mut ops).await.unwrap(); + assert_eq!( + buf, + [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31 + ], + "write_read of 32 bytes failed" + ); + info!("large write_read - OK") + } + + #[embassy_executor::main] + async fn main(_core0_spawner: Spawner) { + let mut config = rpConfig::default(); + // Configure clk_sys to 48MHz to support 1kHz scl. + // In theory it can go lower, but we won't bother to test below 1kHz. + config.clocks.xosc = Some(XoscConfig { + hz: 12_000_000, + delay_multiplier: 128, + sys_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + }), + usb_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + }), + }); + + let p = embassy_rp::init(config); + info!("Hello World!"); + + let d_sda = p.PIN_19; + let d_scl = p.PIN_18; + let mut config = i2c_slave::Config::default(); + config.addr = DEV_ADDR as u16; + let device = i2c_slave::I2cSlave::new(p.I2C1, d_sda, d_scl, Irqs, config); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(device_task(device)))); + }, + ); + + let c_sda = p.PIN_21; + let c_scl = p.PIN_20; + let mut controller = i2c::I2c::new_async(p.I2C0, c_sda, c_scl, Irqs, Default::default()); + + for freq in [1000, 100_000, 400_000, 1_000_000] { + info!("testing at {}hz", freq); + let mut config = i2c::Config::default(); + config.frequency = freq; + controller.set_config(&config).unwrap(); + controller_task(&mut controller).await; + } + + info!("Test OK"); + cortex_m::asm::bkpt(); + } +} diff --git a/embassy/tests/rp/src/bin/multicore.rs b/embassy/tests/rp/src/bin/multicore.rs new file mode 100644 index 0000000..783ea0f --- /dev/null +++ b/embassy/tests/rp/src/bin/multicore.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{info, unwrap}; +use embassy_executor::Executor; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<1024> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); +static CHANNEL0: Channel = 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 *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task()))); + }, + ); + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +} + +#[embassy_executor::task] +async fn core0_task() { + info!("CORE0 is running"); + let ping = true; + CHANNEL0.send(ping).await; + let pong = CHANNEL1.receive().await; + assert_eq!(ping, pong); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +#[embassy_executor::task] +async fn core1_task() { + info!("CORE1 is running"); + let ping = CHANNEL0.receive().await; + CHANNEL1.send(ping).await; +} diff --git a/embassy/tests/rp/src/bin/pio_irq.rs b/embassy/tests/rp/src/bin/pio_irq.rs new file mode 100644 index 0000000..33cdaaa --- /dev/null +++ b/embassy/tests/rp/src/bin/pio_irq.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Config, InterruptHandler, Pio}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + let Pio { + mut common, + sm0: mut sm, + irq_flags, + .. + } = Pio::new(pio, Irqs); + + let prg = pio_proc::pio_asm!( + "irq set 0", + "irq wait 0", + "irq set 1", + // pause execution here + "irq wait 1", + ); + + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&prg.program), &[]); + sm.set_config(&cfg); + sm.set_enable(true); + + // not using the wait futures on purpose because they clear the irq bits, + // and we want to see in which order they are set. + while !irq_flags.check(0) {} + cortex_m::asm::nop(); + assert!(!irq_flags.check(1)); + irq_flags.clear(0); + cortex_m::asm::nop(); + assert!(irq_flags.check(1)); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/pio_multi_load.rs b/embassy/tests/rp/src/bin/pio_multi_load.rs new file mode 100644 index 0000000..cd28f99 --- /dev/null +++ b/embassy/tests/rp/src/bin/pio_multi_load.rs @@ -0,0 +1,124 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Config, InterruptHandler, LoadError, Pio}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + let Pio { + mut common, + mut sm0, + mut sm1, + mut sm2, + irq_flags, + .. + } = Pio::new(pio, Irqs); + + // load with explicit origin works + let prg1 = pio_proc::pio_asm!( + ".origin 4" + "nop", + "nop", + "nop", + "nop", + "nop", + "nop", + "nop", + "irq 0", + "nop", + "nop", + ); + let loaded1 = common.load_program(&prg1.program); + assert_eq!(loaded1.origin, 4); + assert_eq!(loaded1.wrap.source, 13); + assert_eq!(loaded1.wrap.target, 4); + + // load without origin chooses a free space + let prg2 = pio_proc::pio_asm!("nop", "nop", "nop", "nop", "nop", "nop", "nop", "irq 1", "nop", "nop",); + let loaded2 = common.load_program(&prg2.program); + assert_eq!(loaded2.origin, 14); + assert_eq!(loaded2.wrap.source, 23); + assert_eq!(loaded2.wrap.target, 14); + + // wrapping around the end of program space automatically works + let prg3 = + pio_proc::pio_asm!("nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "irq 2",); + let loaded3 = common.load_program(&prg3.program); + assert_eq!(loaded3.origin, 24); + assert_eq!(loaded3.wrap.source, 3); + assert_eq!(loaded3.wrap.target, 24); + + // check that the programs actually work + { + let mut cfg = Config::default(); + cfg.use_program(&loaded1, &[]); + sm0.set_config(&cfg); + sm0.set_enable(true); + while !irq_flags.check(0) {} + sm0.set_enable(false); + } + { + let mut cfg = Config::default(); + cfg.use_program(&loaded2, &[]); + sm1.set_config(&cfg); + sm1.set_enable(true); + while !irq_flags.check(1) {} + sm1.set_enable(false); + } + { + let mut cfg = Config::default(); + cfg.use_program(&loaded3, &[]); + sm2.set_config(&cfg); + sm2.set_enable(true); + while !irq_flags.check(2) {} + sm2.set_enable(false); + } + + // instruction memory is full now. all loads should fail. + { + let prg = pio_proc::pio_asm!(".origin 0", "nop"); + match common.try_load_program(&prg.program) { + Err(LoadError::AddressInUse(0)) => (), + _ => panic!("program loaded when it shouldn't"), + }; + + let prg = pio_proc::pio_asm!("nop"); + match common.try_load_program(&prg.program) { + Err(LoadError::InsufficientSpace) => (), + _ => panic!("program loaded when it shouldn't"), + }; + } + + // freeing some memory should allow further loads though. + unsafe { + common.free_instr(loaded3.used_memory); + } + { + let prg = pio_proc::pio_asm!(".origin 0", "nop"); + match common.try_load_program(&prg.program) { + Ok(_) => (), + _ => panic!("program didn't loaded when it shouldn"), + }; + + let prg = pio_proc::pio_asm!("nop"); + match common.try_load_program(&prg.program) { + Ok(_) => (), + _ => panic!("program didn't loaded when it shouldn"), + }; + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/pwm.rs b/embassy/tests/rp/src/bin/pwm.rs new file mode 100644 index 0000000..c051970 --- /dev/null +++ b/embassy/tests/rp/src/bin/pwm.rs @@ -0,0 +1,182 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert, assert_eq, assert_ne, *}; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::pwm::{Config, InputMode, Pwm}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // Connections on CI device: 6 -> 9, 7 -> 11 + let (mut p6, mut p7, mut p9, mut p11) = (p.PIN_6, p.PIN_7, p.PIN_9, p.PIN_11); + + let cfg = { + let mut c = Config::default(); + c.divider = 125.into(); + c.top = 10000; + c.compare_a = 5000; + c.compare_b = 5000; + c + }; + + // Test free-running clock + { + let pwm = Pwm::new_free(&mut p.PWM_SLICE3, cfg.clone()); + cortex_m::asm::delay(125); + let ctr = pwm.counter(); + assert!(ctr > 0); + assert!(ctr < 100); + cortex_m::asm::delay(125); + assert!(ctr < pwm.counter()); + } + + for invert_a in [false, true] { + info!("free-running, invert A: {}", invert_a); + let mut cfg = cfg.clone(); + cfg.invert_a = invert_a; + cfg.invert_b = !invert_a; + + // Test output from A + { + let pin1 = Input::new(&mut p9, Pull::None); + let _pwm = Pwm::new_output_a(&mut p.PWM_SLICE3, &mut p6, cfg.clone()); + Timer::after_millis(1).await; + assert_eq!(pin1.is_low(), invert_a); + Timer::after_millis(5).await; + assert_eq!(pin1.is_high(), invert_a); + Timer::after_millis(5).await; + assert_eq!(pin1.is_low(), invert_a); + Timer::after_millis(5).await; + assert_eq!(pin1.is_high(), invert_a); + } + + // Test output from B + { + let pin2 = Input::new(&mut p11, Pull::None); + let _pwm = Pwm::new_output_b(&mut p.PWM_SLICE3, &mut p7, cfg.clone()); + Timer::after_millis(1).await; + assert_ne!(pin2.is_low(), invert_a); + Timer::after_millis(5).await; + assert_ne!(pin2.is_high(), invert_a); + Timer::after_millis(5).await; + assert_ne!(pin2.is_low(), invert_a); + Timer::after_millis(5).await; + assert_ne!(pin2.is_high(), invert_a); + } + + // Test output from A+B + { + let pin1 = Input::new(&mut p9, Pull::None); + let pin2 = Input::new(&mut p11, Pull::None); + let _pwm = Pwm::new_output_ab(&mut p.PWM_SLICE3, &mut p6, &mut p7, cfg.clone()); + Timer::after_millis(1).await; + assert_eq!(pin1.is_low(), invert_a); + assert_ne!(pin2.is_low(), invert_a); + Timer::after_millis(5).await; + assert_eq!(pin1.is_high(), invert_a); + assert_ne!(pin2.is_high(), invert_a); + Timer::after_millis(5).await; + assert_eq!(pin1.is_low(), invert_a); + assert_ne!(pin2.is_low(), invert_a); + Timer::after_millis(5).await; + assert_eq!(pin1.is_high(), invert_a); + assert_ne!(pin2.is_high(), invert_a); + } + } + + // Test level-gated + { + let mut pin2 = Output::new(&mut p11, Level::Low); + let pwm = Pwm::new_input(&mut p.PWM_SLICE3, &mut p7, Pull::None, InputMode::Level, cfg.clone()); + assert_eq!(pwm.counter(), 0); + Timer::after_millis(5).await; + assert_eq!(pwm.counter(), 0); + pin2.set_high(); + Timer::after_millis(1).await; + pin2.set_low(); + let ctr = pwm.counter(); + assert!(ctr >= 1000); + Timer::after_millis(1).await; + assert_eq!(pwm.counter(), ctr); + } + + // Test rising-gated + { + let mut pin2 = Output::new(&mut p11, Level::Low); + let pwm = Pwm::new_input( + &mut p.PWM_SLICE3, + &mut p7, + Pull::None, + InputMode::RisingEdge, + cfg.clone(), + ); + assert_eq!(pwm.counter(), 0); + Timer::after_millis(5).await; + assert_eq!(pwm.counter(), 0); + pin2.set_high(); + Timer::after_millis(1).await; + pin2.set_low(); + assert_eq!(pwm.counter(), 1); + Timer::after_millis(1).await; + assert_eq!(pwm.counter(), 1); + } + + // Test falling-gated + { + let mut pin2 = Output::new(&mut p11, Level::High); + let pwm = Pwm::new_input( + &mut p.PWM_SLICE3, + &mut p7, + Pull::None, + InputMode::FallingEdge, + cfg.clone(), + ); + assert_eq!(pwm.counter(), 0); + Timer::after_millis(5).await; + assert_eq!(pwm.counter(), 0); + pin2.set_low(); + Timer::after_millis(1).await; + pin2.set_high(); + assert_eq!(pwm.counter(), 1); + Timer::after_millis(1).await; + assert_eq!(pwm.counter(), 1); + } + + // pull-down + { + let pin2 = Input::new(&mut p11, Pull::None); + Pwm::new_input( + &mut p.PWM_SLICE3, + &mut p7, + Pull::Down, + InputMode::FallingEdge, + cfg.clone(), + ); + Timer::after_millis(1).await; + assert!(pin2.is_low()); + } + + // pull-up + { + let pin2 = Input::new(&mut p11, Pull::None); + Pwm::new_input( + &mut p.PWM_SLICE3, + &mut p7, + Pull::Up, + InputMode::FallingEdge, + cfg.clone(), + ); + Timer::after_millis(1).await; + assert!(pin2.is_high()); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/spi.rs b/embassy/tests/rp/src/bin/spi.rs new file mode 100644 index 0000000..4b02942 --- /dev/null +++ b/embassy/tests/rp/src/bin/spi.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::spi::{Config, Spi}; +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 clk = p.PIN_2; + let mosi = p.PIN_3; + let miso = p.PIN_4; + + let mut spi = Spi::new_blocking(p.SPI0, clk, mosi, miso, Config::default()); + + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + spi.blocking_transfer(&mut rx_buf, &tx_buf).unwrap(); + assert_eq!(rx_buf, tx_buf); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/spi_async.rs b/embassy/tests/rp/src/bin/spi_async.rs new file mode 100644 index 0000000..efdc80b --- /dev/null +++ b/embassy/tests/rp/src/bin/spi_async.rs @@ -0,0 +1,84 @@ +//! Make sure to connect GPIO pins 3 (`PIN_3`) and 4 (`PIN_4`) together +//! to run this test. +//! +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::spi::{Config, Spi}; +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 clk = p.PIN_2; + let mosi = p.PIN_3; + let miso = p.PIN_4; + + let mut spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + // equal rx & tx buffers + { + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + assert_eq!(rx_buf, tx_buf); + } + + // tx > rx buffer + { + let tx_buf = [7_u8, 8, 9, 10, 11, 12]; + + let mut rx_buf = [0_u8; 3]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + assert_eq!(rx_buf, tx_buf[..3]); + + defmt::info!("tx > rx buffer - OK"); + } + + // we make sure to that clearing FIFO works after the uneven buffers + + // equal rx & tx buffers + { + let tx_buf = [13_u8, 14, 15, 16, 17, 18]; + let mut rx_buf = [0_u8; 6]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + assert_eq!(rx_buf, tx_buf); + + defmt::info!("buffer rx length == tx length - OK"); + } + + // rx > tx buffer + { + let tx_buf = [19_u8, 20, 21]; + let mut rx_buf = [0_u8; 6]; + + // we should have written dummy data to tx buffer to sync clock. + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + + assert_eq!( + rx_buf[..3], + tx_buf, + "only the first 3 TX bytes should have been received in the RX buffer" + ); + assert_eq!(rx_buf[3..], [0, 0, 0], "the rest of the RX bytes should be empty"); + defmt::info!("buffer rx length > tx length - OK"); + } + + // equal rx & tx buffers + { + let tx_buf = [22_u8, 23, 24, 25, 26, 27]; + let mut rx_buf = [0_u8; 6]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + + assert_eq!(rx_buf, tx_buf); + defmt::info!("buffer rx length = tx length - OK"); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/timer.rs b/embassy/tests/rp/src/bin/timer.rs new file mode 100644 index 0000000..be92421 --- /dev/null +++ b/embassy/tests/rp/src/bin/timer.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert, *}; +use embassy_executor::Spawner; +use embassy_time::{Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let _p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let start = Instant::now(); + Timer::after_millis(100).await; + let end = Instant::now(); + let ms = (end - start).as_millis(); + info!("slept for {} ms", ms); + assert!(ms >= 99); + assert!(ms < 110); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/uart.rs b/embassy/tests/rp/src/bin/uart.rs new file mode 100644 index 0000000..6e6e551 --- /dev/null +++ b/embassy/tests/rp/src/bin/uart.rs @@ -0,0 +1,169 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::uart::{Blocking, Config, Error, Instance, Parity, Uart, UartRx}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +fn read(uart: &mut Uart<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + uart.blocking_read(&mut buf)?; + Ok(buf) +} + +fn read1(uart: &mut UartRx<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + uart.blocking_read(&mut buf)?; + Ok(buf) +} + +async fn send(pin: &mut Output<'_>, v: u8, parity: Option) { + pin.set_low(); + Timer::after_millis(1).await; + for i in 0..8 { + if v & (1 << i) == 0 { + pin.set_low(); + } else { + pin.set_high(); + } + Timer::after_millis(1).await; + } + if let Some(b) = parity { + if b { + pin.set_high(); + } else { + pin.set_low(); + } + Timer::after_millis(1).await; + } + pin.set_high(); + Timer::after_millis(1).await; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let (mut tx, mut rx, mut uart) = (p.PIN_0, p.PIN_1, p.UART0); + + { + let config = Config::default(); + let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config); + + // We can't send too many bytes, they have to fit in the FIFO. + // This is because we aren't sending+receiving at the same time. + + let data = [0xC0, 0xDE]; + uart.blocking_write(&data).unwrap(); + assert_eq!(read(&mut uart).unwrap(), data); + } + + info!("test overflow detection"); + { + let config = Config::default(); + let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config); + + let data = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, + ]; + let overflow = [ + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, + ]; + uart.blocking_write(&data).unwrap(); + uart.blocking_write(&overflow).unwrap(); + while uart.busy() {} + + // prefix in fifo is valid + assert_eq!(read(&mut uart).unwrap(), data); + // next received character causes overrun error and is discarded + uart.blocking_write(&[1, 2, 3]).unwrap(); + assert_eq!(read::<1>(&mut uart).unwrap_err(), Error::Overrun); + assert_eq!(read(&mut uart).unwrap(), [2, 3]); + } + + info!("test break detection"); + { + let config = Config::default(); + let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config); + + // break on empty fifo + uart.send_break(20).await; + uart.blocking_write(&[64]).unwrap(); + assert_eq!(read::<1>(&mut uart).unwrap_err(), Error::Break); + assert_eq!(read(&mut uart).unwrap(), [64]); + + // break on partially filled fifo + uart.blocking_write(&[65; 2]).unwrap(); + uart.send_break(20).await; + uart.blocking_write(&[66]).unwrap(); + assert_eq!(read(&mut uart).unwrap(), [65; 2]); + assert_eq!(read::<1>(&mut uart).unwrap_err(), Error::Break); + assert_eq!(read(&mut uart).unwrap(), [66]); + } + + // parity detection. here we bitbang to not require two uarts. + info!("test parity error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + let mut config = Config::default(); + config.baudrate = 1000; + config.parity = Parity::ParityEven; + let mut uart = UartRx::new_blocking(&mut uart, &mut rx, config); + + async fn chr(pin: &mut Output<'_>, v: u8, parity: u8) { + send(pin, v, Some(parity != 0)).await; + } + + // first check that we can send correctly + chr(&mut pin, 64, 1).await; + assert_eq!(read1(&mut uart).unwrap(), [64]); + + // all good, check real errors + chr(&mut pin, 2, 1).await; + chr(&mut pin, 3, 0).await; + chr(&mut pin, 4, 0).await; + chr(&mut pin, 5, 0).await; + assert_eq!(read1(&mut uart).unwrap(), [2, 3]); + assert_eq!(read1::<1>(&mut uart).unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).unwrap(), [5]); + } + + // framing error detection. here we bitbang because there's no other way. + info!("test framing error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + let mut config = Config::default(); + config.baudrate = 1000; + let mut uart = UartRx::new_blocking(&mut uart, &mut rx, config); + + async fn chr(pin: &mut Output<'_>, v: u8, good: bool) { + if good { + send(pin, v, None).await; + } else { + send(pin, v, Some(false)).await; + } + } + + // first check that we can send correctly + chr(&mut pin, 64, true).await; + assert_eq!(read1(&mut uart).unwrap(), [64]); + + // all good, check real errors + chr(&mut pin, 2, true).await; + chr(&mut pin, 3, true).await; + chr(&mut pin, 4, false).await; + chr(&mut pin, 5, true).await; + assert_eq!(read1(&mut uart).unwrap(), [2, 3]); + assert_eq!(read1::<1>(&mut uart).unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).unwrap(), [5]); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/uart_buffered.rs b/embassy/tests/rp/src/bin/uart_buffered.rs new file mode 100644 index 0000000..d68c23c --- /dev/null +++ b/embassy/tests/rp/src/bin/uart_buffered.rs @@ -0,0 +1,254 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert_eq, panic, *}; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config, Error, Instance, Parity}; +use embassy_time::Timer; +use embedded_io_async::{Read, ReadExactError, Write}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART0_IRQ => BufferedInterruptHandler; +}); + +async fn read(uart: &mut BufferedUart<'_, impl Instance>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + match uart.read_exact(&mut buf).await { + Ok(()) => Ok(buf), + // we should not ever produce an Eof condition + Err(ReadExactError::UnexpectedEof) => panic!(), + Err(ReadExactError::Other(e)) => Err(e), + } +} + +async fn read1(uart: &mut BufferedUartRx<'_, impl Instance>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + match uart.read_exact(&mut buf).await { + Ok(()) => Ok(buf), + // we should not ever produce an Eof condition + Err(ReadExactError::UnexpectedEof) => panic!(), + Err(ReadExactError::Other(e)) => Err(e), + } +} + +async fn send(pin: &mut Output<'_>, v: u8, parity: Option) { + pin.set_low(); + Timer::after_millis(1).await; + for i in 0..8 { + if v & (1 << i) == 0 { + pin.set_low(); + } else { + pin.set_high(); + } + Timer::after_millis(1).await; + } + if let Some(b) = parity { + if b { + pin.set_high(); + } else { + pin.set_low(); + } + Timer::after_millis(1).await; + } + pin.set_high(); + Timer::after_millis(1).await; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let (mut tx, mut rx, mut uart) = (p.PIN_0, p.PIN_1, p.UART0); + + { + let config = Config::default(); + let tx_buf = &mut [0u8; 16]; + let rx_buf = &mut [0u8; 16]; + let mut uart = BufferedUart::new(&mut uart, Irqs, &mut tx, &mut rx, tx_buf, rx_buf, config); + + // Make sure we send more bytes than fits in the FIFO, to test the actual + // bufferedUart. + + let data = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + ]; + uart.write_all(&data).await.unwrap(); + info!("Done writing"); + + assert_eq!(read(&mut uart).await.unwrap(), data); + } + + info!("test overflow detection"); + { + let config = Config::default(); + let tx_buf = &mut [0u8; 16]; + let rx_buf = &mut [0u8; 16]; + let mut uart = BufferedUart::new(&mut uart, Irqs, &mut tx, &mut rx, tx_buf, rx_buf, config); + + // Make sure we send more bytes than fits in the FIFO, to test the actual + // bufferedUart. + + let data = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + ]; + let overflow = [ + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, + ]; + // give each block time to settle into the fifo. we want the overrun to occur at a well-defined point. + uart.write_all(&data).await.unwrap(); + uart.blocking_flush().unwrap(); + while uart.busy() {} + uart.write_all(&overflow).await.unwrap(); + uart.blocking_flush().unwrap(); + while uart.busy() {} + + // already buffered/fifod prefix is valid + assert_eq!(read(&mut uart).await.unwrap(), data); + // next received character causes overrun error and is discarded + uart.write_all(&[1, 2, 3]).await.unwrap(); + uart.blocking_flush().unwrap(); + assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Overrun); + assert_eq!(read(&mut uart).await.unwrap(), [2, 3]); + } + + info!("test break detection"); + { + let mut config = Config::default(); + config.baudrate = 1000; + let tx_buf = &mut [0u8; 16]; + let rx_buf = &mut [0u8; 16]; + let mut uart = BufferedUart::new(&mut uart, Irqs, &mut tx, &mut rx, tx_buf, rx_buf, config); + + // break on empty buffer + uart.send_break(20).await; + assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Break); + uart.write_all(&[64]).await.unwrap(); + assert_eq!(read(&mut uart).await.unwrap(), [64]); + + // break on partially filled buffer + uart.write_all(&[65; 2]).await.unwrap(); + uart.send_break(20).await; + uart.write_all(&[66]).await.unwrap(); + assert_eq!(read(&mut uart).await.unwrap(), [65; 2]); + assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Break); + assert_eq!(read(&mut uart).await.unwrap(), [66]); + + // break on full buffer + uart.write_all(&[64; 16]).await.unwrap(); + uart.send_break(20).await; + uart.write_all(&[65]).await.unwrap(); + assert_eq!(read(&mut uart).await.unwrap(), [64; 16]); + assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Break); + assert_eq!(read(&mut uart).await.unwrap(), [65]); + } + + // parity detection. here we bitbang to not require two uarts. + info!("test parity error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + // choose a very slow baud rate to make tests reliable even with O0 + let mut config = Config::default(); + config.baudrate = 1000; + config.parity = Parity::ParityEven; + let rx_buf = &mut [0u8; 16]; + let mut uart = BufferedUartRx::new(&mut uart, Irqs, &mut rx, rx_buf, config); + + async fn chr(pin: &mut Output<'_>, v: u8, parity: u32) { + send(pin, v, Some(parity != 0)).await; + } + + // first check that we can send correctly + chr(&mut pin, 64, 1).await; + assert_eq!(read1(&mut uart).await.unwrap(), [64]); + + // parity on empty buffer + chr(&mut pin, 64, 0).await; + chr(&mut pin, 4, 1).await; + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [4]); + + // parity on partially filled buffer + chr(&mut pin, 64, 1).await; + chr(&mut pin, 32, 1).await; + chr(&mut pin, 64, 0).await; + chr(&mut pin, 65, 0).await; + assert_eq!(read1(&mut uart).await.unwrap(), [64, 32]); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [65]); + + // parity on full buffer + for i in 0..16 { + chr(&mut pin, i, i.count_ones() % 2).await; + } + chr(&mut pin, 64, 0).await; + chr(&mut pin, 65, 0).await; + assert_eq!( + read1(&mut uart).await.unwrap(), + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + ); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [65]); + } + + // framing error detection. here we bitbang because there's no other way. + info!("test framing error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + // choose a very slow baud rate to make tests reliable even with O0 + let mut config = Config::default(); + config.baudrate = 1000; + let rx_buf = &mut [0u8; 16]; + let mut uart = BufferedUartRx::new(&mut uart, Irqs, &mut rx, rx_buf, config); + + async fn chr(pin: &mut Output<'_>, v: u8, good: bool) { + if good { + send(pin, v, None).await; + } else { + send(pin, v, Some(false)).await; + } + } + + // first check that we can send correctly + chr(&mut pin, 64, true).await; + assert_eq!(read1(&mut uart).await.unwrap(), [64]); + + // framing on empty buffer + chr(&mut pin, 64, false).await; + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing); + chr(&mut pin, 65, true).await; + assert_eq!(read1(&mut uart).await.unwrap(), [65]); + + // framing on partially filled buffer + chr(&mut pin, 64, true).await; + chr(&mut pin, 32, true).await; + chr(&mut pin, 64, false).await; + chr(&mut pin, 65, true).await; + assert_eq!(read1(&mut uart).await.unwrap(), [64, 32]); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).await.unwrap(), [65]); + + // framing on full buffer + for i in 0..16 { + chr(&mut pin, i, true).await; + } + chr(&mut pin, 64, false).await; + chr(&mut pin, 65, true).await; + assert_eq!( + read1(&mut uart).await.unwrap(), + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + ); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).await.unwrap(), [65]); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/uart_dma.rs b/embassy/tests/rp/src/bin/uart_dma.rs new file mode 100644 index 0000000..edc8717 --- /dev/null +++ b/embassy/tests/rp/src/bin/uart_dma.rs @@ -0,0 +1,250 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{Async, Config, Error, Instance, InterruptHandler, Parity, Uart, UartRx}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART0_IRQ => InterruptHandler; +}); + +async fn read(uart: &mut Uart<'_, impl Instance, Async>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + uart.read(&mut buf).await?; + Ok(buf) +} + +async fn read1(uart: &mut UartRx<'_, impl Instance, Async>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + uart.read(&mut buf).await?; + Ok(buf) +} + +async fn send(pin: &mut Output<'_>, v: u8, parity: Option) { + pin.set_low(); + Timer::after_millis(1).await; + for i in 0..8 { + if v & (1 << i) == 0 { + pin.set_low(); + } else { + pin.set_high(); + } + Timer::after_millis(1).await; + } + if let Some(b) = parity { + if b { + pin.set_high(); + } else { + pin.set_low(); + } + Timer::after_millis(1).await; + } + pin.set_high(); + Timer::after_millis(1).await; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let (mut tx, mut rx, mut uart) = (p.PIN_0, p.PIN_1, p.UART0); + + // We can't send too many bytes, they have to fit in the FIFO. + // This is because we aren't sending+receiving at the same time. + { + let config = Config::default(); + let mut uart = Uart::new( + &mut uart, + &mut tx, + &mut rx, + Irqs, + &mut p.DMA_CH0, + &mut p.DMA_CH1, + config, + ); + + let data = [0xC0, 0xDE]; + uart.write(&data).await.unwrap(); + + let mut buf = [0; 2]; + uart.read(&mut buf).await.unwrap(); + assert_eq!(buf, data); + } + + info!("test overflow detection"); + { + let config = Config::default(); + let mut uart = Uart::new( + &mut uart, + &mut tx, + &mut rx, + Irqs, + &mut p.DMA_CH0, + &mut p.DMA_CH1, + config, + ); + + uart.blocking_write(&[42; 32]).unwrap(); + uart.blocking_write(&[1, 2, 3]).unwrap(); + uart.blocking_flush().unwrap(); + + // can receive regular fifo contents + assert_eq!(read(&mut uart).await, Ok([42; 16])); + assert_eq!(read(&mut uart).await, Ok([42; 16])); + // receiving the rest fails with overrun + assert_eq!(read::<16>(&mut uart).await, Err(Error::Overrun)); + // new data is accepted, latest overrunning byte first + assert_eq!(read(&mut uart).await, Ok([3])); + uart.blocking_write(&[8, 9]).unwrap(); + Timer::after_millis(1).await; + assert_eq!(read(&mut uart).await, Ok([8, 9])); + } + + info!("test break detection"); + { + let config = Config::default(); + let (mut tx, mut rx) = Uart::new( + &mut uart, + &mut tx, + &mut rx, + Irqs, + &mut p.DMA_CH0, + &mut p.DMA_CH1, + config, + ) + .split(); + + // break before read + tx.send_break(20).await; + tx.write(&[64]).await.unwrap(); + assert_eq!(read1::<1>(&mut rx).await.unwrap_err(), Error::Break); + assert_eq!(read1(&mut rx).await.unwrap(), [64]); + + // break during read + { + let r = read1::<2>(&mut rx); + tx.write(&[2]).await.unwrap(); + tx.send_break(20).await; + tx.write(&[3]).await.unwrap(); + assert_eq!(r.await.unwrap_err(), Error::Break); + assert_eq!(read1(&mut rx).await.unwrap(), [3]); + } + + // break after read + { + let r = read1(&mut rx); + tx.write(&[2]).await.unwrap(); + tx.send_break(20).await; + tx.write(&[3]).await.unwrap(); + assert_eq!(r.await.unwrap(), [2]); + assert_eq!(read1::<1>(&mut rx).await.unwrap_err(), Error::Break); + assert_eq!(read1(&mut rx).await.unwrap(), [3]); + } + } + + // parity detection. here we bitbang to not require two uarts. + info!("test parity error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + // choose a very slow baud rate to make tests reliable even with O0 + let mut config = Config::default(); + config.baudrate = 1000; + config.parity = Parity::ParityEven; + let mut uart = UartRx::new(&mut uart, &mut rx, Irqs, &mut p.DMA_CH0, config); + + async fn chr(pin: &mut Output<'_>, v: u8, parity: u32) { + send(pin, v, Some(parity != 0)).await; + } + + // first check that we can send correctly + chr(&mut pin, 32, 1).await; + assert_eq!(read1(&mut uart).await.unwrap(), [32]); + + // parity error before read + chr(&mut pin, 32, 0).await; + chr(&mut pin, 31, 1).await; + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [31]); + + // parity error during read + { + let r = read1::<2>(&mut uart); + chr(&mut pin, 2, 1).await; + chr(&mut pin, 32, 0).await; + chr(&mut pin, 3, 0).await; + assert_eq!(r.await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [3]); + } + + // parity error after read + { + let r = read1(&mut uart); + chr(&mut pin, 2, 1).await; + chr(&mut pin, 32, 0).await; + chr(&mut pin, 3, 0).await; + assert_eq!(r.await.unwrap(), [2]); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [3]); + } + } + + // framing error detection. here we bitbang because there's no other way. + info!("test framing error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + // choose a very slow baud rate to make tests reliable even with O0 + let mut config = Config::default(); + config.baudrate = 1000; + let mut uart = UartRx::new(&mut uart, &mut rx, Irqs, &mut p.DMA_CH0, config); + + async fn chr(pin: &mut Output<'_>, v: u8, good: bool) { + if good { + send(pin, v, None).await; + } else { + send(pin, v, Some(false)).await; + } + } + + // first check that we can send correctly + chr(&mut pin, 32, true).await; + assert_eq!(read1(&mut uart).await.unwrap(), [32]); + + // parity error before read + chr(&mut pin, 32, false).await; + chr(&mut pin, 31, true).await; + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).await.unwrap(), [31]); + + // parity error during read + { + let r = read1::<2>(&mut uart); + chr(&mut pin, 2, true).await; + chr(&mut pin, 32, false).await; + chr(&mut pin, 3, true).await; + assert_eq!(r.await.unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).await.unwrap(), [3]); + } + + // parity error after read + { + let r = read1(&mut uart); + chr(&mut pin, 2, true).await; + chr(&mut pin, 32, false).await; + chr(&mut pin, 3, true).await; + assert_eq!(r.await.unwrap(), [2]); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).await.unwrap(), [3]); + } + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/rp/src/bin/uart_upgrade.rs b/embassy/tests/rp/src/bin/uart_upgrade.rs new file mode 100644 index 0000000..603e20f --- /dev/null +++ b/embassy/tests/rp/src/bin/uart_upgrade.rs @@ -0,0 +1,58 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{BufferedInterruptHandler, Config, Uart}; +use embedded_io_async::{Read, Write}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART0_IRQ => BufferedInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let (tx, rx, uart) = (p.PIN_0, p.PIN_1, p.UART0); + + let config = Config::default(); + let mut uart = Uart::new_blocking(uart, tx, rx, config); + + // We can't send too many bytes, they have to fit in the FIFO. + // This is because we aren't sending+receiving at the same time. + + let data = [0xC0, 0xDE]; + uart.blocking_write(&data).unwrap(); + + let mut buf = [0; 2]; + uart.blocking_read(&mut buf).unwrap(); + assert_eq!(buf, data); + + let tx_buf = &mut [0u8; 16]; + let rx_buf = &mut [0u8; 16]; + + let mut uart = uart.into_buffered(Irqs, tx_buf, rx_buf); + + // Make sure we send more bytes than fits in the FIFO, to test the actual + // bufferedUart. + + 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, + ]; + uart.write_all(&data).await.unwrap(); + info!("Done writing"); + + let mut buf = [0; 31]; + uart.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, data); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/.cargo/config.toml b/embassy/tests/stm32/.cargo/config.toml new file mode 100644 index 0000000..d943425 --- /dev/null +++ b/embassy/tests/stm32/.cargo/config.toml @@ -0,0 +1,22 @@ +[unstable] +#build-std = ["core"] +#build-std-features = ["panic_immediate_abort"] + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "teleprobe client run" +#runner = "teleprobe local run --chip STM32H7S3L8Hx --elf" + +rustflags = [ + # Code-size optimizations. + #"-Z", "trap-unreachable=no", + "-C", "no-vectorize-loops", +] + +[build] +#target = "thumbv6m-none-eabi" +#target = "thumbv7m-none-eabi" +target = "thumbv7em-none-eabi" +#target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,smoltcp=info" diff --git a/embassy/tests/stm32/Cargo.toml b/embassy/tests/stm32/Cargo.toml new file mode 100644 index 0000000..5ae6878 --- /dev/null +++ b/embassy/tests/stm32/Cargo.toml @@ -0,0 +1,235 @@ +[package] +edition = "2021" +name = "embassy-stm32-tests" +version = "0.1.0" +license = "MIT OR Apache-2.0" +autobins = false + +[features] +stm32c031c6 = ["embassy-stm32/stm32c031c6", "cm0", "not-gpdma"] +stm32f103c8 = ["embassy-stm32/stm32f103c8", "spi-v1", "not-gpdma"] +stm32f207zg = ["embassy-stm32/stm32f207zg", "spi-v1", "chrono", "not-gpdma", "eth", "rng"] +stm32f303ze = ["embassy-stm32/stm32f303ze", "chrono", "not-gpdma"] +stm32f429zi = ["embassy-stm32/stm32f429zi", "spi-v1", "chrono", "eth", "stop", "can", "not-gpdma", "dac", "rng"] +stm32f446re = ["embassy-stm32/stm32f446re", "spi-v1", "chrono", "stop", "can", "not-gpdma", "dac", "sdmmc"] +stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng"] +stm32g071rb = ["embassy-stm32/stm32g071rb", "cm0", "not-gpdma", "dac", "ucpd"] +stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng", "fdcan", "cordic"] +stm32h563zi = ["embassy-stm32/stm32h563zi", "spi-v345", "chrono", "eth", "rng", "fdcan", "hash", "cordic", "stop"] +stm32h753zi = ["embassy-stm32/stm32h753zi", "spi-v345", "chrono", "not-gpdma", "eth", "rng", "fdcan", "hash", "cryp"] +stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "spi-v345", "chrono", "not-gpdma", "eth", "dac", "rng", "fdcan", "hash", "cryp"] +stm32h7a3zi = ["embassy-stm32/stm32h7a3zi", "spi-v345", "not-gpdma", "rng", "fdcan"] +stm32l073rz = ["embassy-stm32/stm32l073rz", "cm0", "not-gpdma", "rng"] +stm32l152re = ["embassy-stm32/stm32l152re", "spi-v1", "chrono", "not-gpdma"] +stm32l496zg = ["embassy-stm32/stm32l496zg", "not-gpdma", "rng"] +stm32l4a6zg = ["embassy-stm32/stm32l4a6zg", "chrono", "not-gpdma", "rng", "hash"] +stm32l4r5zi = ["embassy-stm32/stm32l4r5zi", "chrono", "not-gpdma", "rng"] +stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash"] +stm32u585ai = ["embassy-stm32/stm32u585ai", "spi-v345", "chrono", "rng", "hash", "cordic"] +stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "spi-v345", "chrono", "rng", "hash"] # FIXME: cordic test cause it crash +stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng"] +stm32wba52cg = ["embassy-stm32/stm32wba52cg", "spi-v345", "chrono", "rng", "hash"] +stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono"] +stm32f091rc = ["embassy-stm32/stm32f091rc", "cm0", "not-gpdma", "chrono"] +stm32h503rb = ["embassy-stm32/stm32h503rb", "spi-v345", "rng", "stop"] +stm32h7s3l8 = ["embassy-stm32/stm32h7s3l8", "spi-v345", "rng", "cordic", "hash"] # TODO: fdcan crashes, cryp dma hangs. +stm32u083rc = ["embassy-stm32/stm32u083rc", "cm0", "rng", "chrono"] + +spi-v1 = [] +spi-v345 = [] +cryp = [] +hash = [] +eth = ["embassy-executor/task-arena-size-16384"] +rng = [] +sdmmc = [] +stop = ["embassy-stm32/low-power", "embassy-stm32/low-power-debug-with-sleep"] +chrono = ["embassy-stm32/chrono", "dep:chrono"] +can = [] +fdcan = [] +ble = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/ble"] +mac = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/mac"] +embassy-stm32-wpan = [] +not-gpdma = [] +dac = [] +ucpd = [] +cordic = ["dep:num-traits"] + +cm0 = ["portable-atomic/unsafe-assume-single-core"] + +[dependencies] +teleprobe-meta = "1" + +embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "tick-hz-131_072", "defmt-timestamp-uptime"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "memory-x", "time-driver-any"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", optional = true, features = ["defmt", "stm32wb55rg", "ble"] } +embassy-net = { version = "0.5.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet"] } +perf-client = { path = "../perf-client" } + +defmt = "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" } +embedded-hal-async = { version = "1.0" } +embedded-can = { version = "0.4" } +micromath = "2.0.0" +panic-probe = { version = "0.3.0", features = ["print-defmt"] } +rand_core = { version = "0.6", default-features = false } +rand_chacha = { version = "0.3", default-features = false } +static_cell = "2" +portable-atomic = { version = "1.5", features = [] } + +chrono = { version = "^0.4", default-features = false, optional = true} +sha2 = { version = "0.10.8", default-features = false } +hmac = "0.12.1" +aes-gcm = {version = "0.10.3", default-features = false, features = ["aes", "heapless"] } +num-traits = {version="0.2", default-features = false,features = ["libm"], optional = true} + +# BEGIN TESTS +# Generated by gen_test.py. DO NOT EDIT. +[[bin]] +name = "can" +path = "src/bin/can.rs" +required-features = [ "can",] + +[[bin]] +name = "cordic" +path = "src/bin/cordic.rs" +required-features = [ "rng", "cordic",] + +[[bin]] +name = "cryp" +path = "src/bin/cryp.rs" +required-features = [ "cryp",] + +[[bin]] +name = "dac" +path = "src/bin/dac.rs" +required-features = [ "dac",] + +[[bin]] +name = "dac_l1" +path = "src/bin/dac_l1.rs" +required-features = [ "stm32l152re",] + +[[bin]] +name = "eth" +path = "src/bin/eth.rs" +required-features = [ "eth",] + +[[bin]] +name = "fdcan" +path = "src/bin/fdcan.rs" +required-features = [ "fdcan",] + +[[bin]] +name = "gpio" +path = "src/bin/gpio.rs" +required-features = [] + +[[bin]] +name = "hash" +path = "src/bin/hash.rs" +required-features = [ "hash",] + +[[bin]] +name = "rng" +path = "src/bin/rng.rs" +required-features = [ "rng",] + +[[bin]] +name = "rtc" +path = "src/bin/rtc.rs" +required-features = [ "chrono",] + +[[bin]] +name = "sdmmc" +path = "src/bin/sdmmc.rs" +required-features = [ "sdmmc",] + +[[bin]] +name = "spi" +path = "src/bin/spi.rs" +required-features = [] + +[[bin]] +name = "spi_dma" +path = "src/bin/spi_dma.rs" +required-features = [] + +[[bin]] +name = "stop" +path = "src/bin/stop.rs" +required-features = [ "stop", "chrono",] + +[[bin]] +name = "timer" +path = "src/bin/timer.rs" +required-features = [] + +[[bin]] +name = "ucpd" +path = "src/bin/ucpd.rs" +required-features = [ "ucpd",] + +[[bin]] +name = "usart" +path = "src/bin/usart.rs" +required-features = [] + +[[bin]] +name = "usart_dma" +path = "src/bin/usart_dma.rs" +required-features = [] + +[[bin]] +name = "usart_rx_ringbuffered" +path = "src/bin/usart_rx_ringbuffered.rs" +required-features = [ "not-gpdma",] + +[[bin]] +name = "wpan_ble" +path = "src/bin/wpan_ble.rs" +required-features = [ "ble",] + +[[bin]] +name = "wpan_mac" +path = "src/bin/wpan_mac.rs" +required-features = [ "mac",] + +# END TESTS + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 's' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 's' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/embassy/tests/stm32/build.rs b/embassy/tests/stm32/build.rs new file mode 100644 index 0000000..722671b --- /dev/null +++ b/embassy/tests/stm32/build.rs @@ -0,0 +1,35 @@ +use std::error::Error; +use std::path::PathBuf; +use std::{env, fs}; + +fn main() -> Result<(), Box> { + let out = PathBuf::from(env::var("OUT_DIR").unwrap()); + fs::write(out.join("link_ram.x"), include_bytes!("../link_ram_cortex_m.x")).unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rustc-link-arg-bins=--nmagic"); + + if cfg!(any( + // too little RAM to run from RAM. + feature = "stm32f103c8", // 20 kb + feature = "stm32c031c6", // 6 kb + feature = "stm32l073rz", // 20 kb + feature = "stm32h503rb", // 32 kb + // no VTOR, so interrupts can't work when running from RAM + feature = "stm32f091rc", + )) { + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rerun-if-changed=link.x"); + } else { + println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); + println!("cargo:rerun-if-changed=link_ram.x"); + } + + if cfg!(feature = "stm32wb55rg") { + println!("cargo:rustc-link-arg-bins=-Ttl_mbox.x"); + } + + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + println!("cargo:rustc-link-arg-bins=-Tteleprobe.x"); + + Ok(()) +} diff --git a/embassy/tests/stm32/gen_test.py b/embassy/tests/stm32/gen_test.py new file mode 100644 index 0000000..daf7143 --- /dev/null +++ b/embassy/tests/stm32/gen_test.py @@ -0,0 +1,44 @@ +import os +import toml +from glob import glob + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +# ======= load test list +tests = {} +for f in sorted(glob('./src/bin/*.rs')): + name = os.path.splitext(os.path.basename(f))[0] + features = [] + with open(f, 'r') as f: + for line in f: + if line.startswith('// required-features:'): + features = [feature.strip() for feature in line.split(':', 2)[1].strip().split(',')] + + tests[name] = features + +# ========= Update Cargo.toml + +things = { + 'bin': [ + { + 'name': f'{name}', + 'path': f'src/bin/{name}.rs', + 'required-features': features, + } + for name, features in tests.items() + ] +} + +SEPARATOR_START = '# BEGIN TESTS\n' +SEPARATOR_END = '# END TESTS\n' +HELP = '# Generated by gen_test.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 + \ + toml.dumps(things) + SEPARATOR_END + after +with open('Cargo.toml', 'w') as f: + f.write(data) diff --git a/embassy/tests/stm32/src/bin/can.rs b/embassy/tests/stm32/src/bin/can.rs new file mode 100644 index 0000000..85a5f8d --- /dev/null +++ b/embassy/tests/stm32/src/bin/can.rs @@ -0,0 +1,74 @@ +#![no_std] +#![no_main] + +// required-features: can + +#[path = "../common.rs"] +mod common; +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::can::filter::Mask32; +use embassy_stm32::can::{Fifo, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, TxInterruptHandler}; +use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::peripherals::CAN1; +use embassy_time::Duration; +use {defmt_rtt as _, panic_probe as _}; + +mod can_common; +use can_common::*; + +bind_interrupts!(struct Irqs { + CAN1_RX0 => Rx0InterruptHandler; + CAN1_RX1 => Rx1InterruptHandler; + CAN1_SCE => SceInterruptHandler; + CAN1_TX => TxInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = init(); + info!("Hello World!"); + + let options = TestOptions { + max_latency: Duration::from_micros(1200), + max_buffered: 2, + }; + + let can = peri!(p, CAN); + let tx = peri!(p, CAN_TX); + let mut rx = peri!(p, CAN_RX); + + // The next two lines are a workaround for testing without transceiver. + // To synchronise to the bus the RX input needs to see a high level. + // Use `mem::forget()` to release the borrow on the pin but keep the + // pull-up resistor enabled. + let rx_pin = Input::new(&mut rx, Pull::Up); + core::mem::forget(rx_pin); + + let mut can = embassy_stm32::can::Can::new(can, rx, tx, Irqs); + + info!("Configuring can..."); + + can.modify_filters().enable_bank(0, Fifo::Fifo0, Mask32::accept_all()); + + can.modify_config() + .set_loopback(true) // Receive own frames + .set_silent(true) + // .set_bit_timing(0x001c0003) + .set_bitrate(1_000_000); + + can.enable().await; + + info!("Can configured"); + + run_can_tests(&mut can, &options).await; + + // Test again with a split + let (mut tx, mut rx) = can.split(); + run_split_can_tests(&mut tx, &mut rx, &options).await; + + info!("Test OK"); + + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/can_common.rs b/embassy/tests/stm32/src/bin/can_common.rs new file mode 100644 index 0000000..4e1740a --- /dev/null +++ b/embassy/tests/stm32/src/bin/can_common.rs @@ -0,0 +1,109 @@ +use defmt::{assert, *}; +use embassy_stm32::can; +use embassy_time::{Duration, Instant}; + +#[derive(Clone, Copy, Debug)] +pub struct TestOptions { + pub max_latency: Duration, + pub max_buffered: u8, +} + +pub async fn run_can_tests<'d>(can: &mut can::Can<'d>, options: &TestOptions) { + //pub async fn run_can_tests<'d, T: can::Instance>(can: &mut can::Can<'d, T>, options: &TestOptions) { + let mut i: u8 = 0; + loop { + //let tx_frame = can::frame::Frame::new_standard(0x123, &[i, 0x12 as u8, 0x34 as u8, 0x56 as u8, 0x78 as u8, 0x9A as u8, 0xBC as u8 ]).unwrap(); + let tx_frame = can::frame::Frame::new_standard(0x123, &[i; 1]).unwrap(); + + //info!("Transmitting frame..."); + let tx_ts = Instant::now(); + can.write(&tx_frame).await; + + let (frame, timestamp) = can.read().await.unwrap().parts(); + //info!("Frame received!"); + + // Check data. + assert!(i == frame.data()[0], "{} == {}", i, frame.data()[0]); + + //info!("loopback time {}", timestamp); + //info!("loopback frame {=u8}", frame.data()[0]); + let latency = timestamp.saturating_duration_since(tx_ts); + info!("loopback latency {} us", latency.as_micros()); + + // Theoretical minimum latency is 55us, actual is usually ~80us + const MIN_LATENCY: Duration = Duration::from_micros(50); + // Was failing at 150 but we are not getting a real time stamp. I'm not + // sure if there are other delays + assert!( + MIN_LATENCY <= latency && latency <= options.max_latency, + "{} <= {} <= {}", + MIN_LATENCY, + latency, + options.max_latency + ); + + i += 1; + if i > 5 { + break; + } + } + + // Below here, check that we can receive from both FIFO0 and FIFO1 + // Above we configured FIFO1 for extended ID packets. There are only 3 slots + // in each FIFO so make sure we write enough to fill them both up before reading. + for i in 0..options.max_buffered { + // Try filling up the RX FIFO0 buffers + //let tx_frame = if 0 != (i & 0x01) { + let tx_frame = if i < options.max_buffered / 2 { + info!("Transmitting standard frame {}", i); + can::frame::Frame::new_standard(0x123, &[i; 1]).unwrap() + } else { + info!("Transmitting extended frame {}", i); + can::frame::Frame::new_extended(0x1232344, &[i; 1]).unwrap() + }; + can.write(&tx_frame).await; + } + + // Try and receive all 6 packets + for _i in 0..options.max_buffered { + let (frame, _ts) = can.read().await.unwrap().parts(); + match frame.id() { + embedded_can::Id::Extended(_id) => { + info!("Extended received! {}", frame.data()[0]); + //info!("Extended received! {:x} {} {}", id.as_raw(), frame.data()[0], i); + } + embedded_can::Id::Standard(_id) => { + info!("Standard received! {}", frame.data()[0]); + //info!("Standard received! {:x} {} {}", id.as_raw(), frame.data()[0], i); + } + } + } +} + +pub async fn run_split_can_tests<'d>(tx: &mut can::CanTx<'d>, rx: &mut can::CanRx<'d>, options: &TestOptions) { + for i in 0..options.max_buffered { + // Try filling up the RX FIFO0 buffers + //let tx_frame = if 0 != (i & 0x01) { + let tx_frame = if i < options.max_buffered / 2 { + info!("Transmitting standard frame {}", i); + can::frame::Frame::new_standard(0x123, &[i; 1]).unwrap() + } else { + info!("Transmitting extended frame {}", i); + can::frame::Frame::new_extended(0x1232344, &[i; 1]).unwrap() + }; + tx.write(&tx_frame).await; + } + + // Try and receive all 6 packets + for _i in 0..options.max_buffered { + let (frame, _ts) = rx.read().await.unwrap().parts(); + match frame.id() { + embedded_can::Id::Extended(_id) => { + info!("Extended received! {}", frame.data()[0]); + } + embedded_can::Id::Standard(_id) => { + info!("Standard received! {}", frame.data()[0]); + } + } + } +} diff --git a/embassy/tests/stm32/src/bin/cordic.rs b/embassy/tests/stm32/src/bin/cordic.rs new file mode 100644 index 0000000..879ad56 --- /dev/null +++ b/embassy/tests/stm32/src/bin/cordic.rs @@ -0,0 +1,140 @@ +// required-features: rng, cordic + +// Test Cordic driver, with Q1.31 format, Sin function, at 24 iterations (aka PRECISION = 6), using DMA transfer + +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::cordic::utils; +use embassy_stm32::{bind_interrupts, cordic, peripherals, rng}; +use num_traits::Float; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +/* input value control, can be changed */ + +const INPUT_U32_COUNT: usize = 9; +const INPUT_U8_COUNT: usize = 4 * INPUT_U32_COUNT; + +// Assume first calculation needs 2 arguments, the reset needs 1 argument. +// And all calculation generate 2 results. +const OUTPUT_LENGTH: usize = (INPUT_U32_COUNT - 1) * 2; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let dp = init(); + + // + // use RNG generate random Q1.31 value + // + // we don't generate floating-point value, since not all binary value are valid floating-point value, + // and Q1.31 only accept a fixed range of value. + + let mut rng = rng::Rng::new(dp.RNG, Irqs); + + let mut input_buf_u8 = [0u8; INPUT_U8_COUNT]; + defmt::unwrap!(rng.async_fill_bytes(&mut input_buf_u8).await); + + // convert every [u8; 4] to a u32, for a Q1.31 value + let mut input_q1_31 = unsafe { core::mem::transmute::<[u8; INPUT_U8_COUNT], [u32; INPUT_U32_COUNT]>(input_buf_u8) }; + + // ARG2 for Sin function should be inside [0, 1], set MSB to 0 of a Q1.31 value, will make sure it's no less than 0. + input_q1_31[1] &= !(1u32 << 31); + + // + // CORDIC calculation + // + + let mut output_q1_31 = [0u32; OUTPUT_LENGTH]; + + // setup Cordic driver + let mut cordic = cordic::Cordic::new( + dp.CORDIC, + defmt::unwrap!(cordic::Config::new( + cordic::Function::Sin, + Default::default(), + Default::default(), + )), + ); + + #[cfg(feature = "stm32g491re")] + let (mut write_dma, mut read_dma) = (dp.DMA1_CH4, dp.DMA1_CH5); + + #[cfg(any( + feature = "stm32h563zi", + feature = "stm32u585ai", + feature = "stm32u5a5zj", + feature = "stm32h7s3l8" + ))] + let (mut write_dma, mut read_dma) = (dp.GPDMA1_CH0, dp.GPDMA1_CH1); + + // calculate first result using blocking mode + let cnt0 = defmt::unwrap!(cordic.blocking_calc_32bit(&input_q1_31[..2], &mut output_q1_31, false, false)); + + // calculate rest results using async mode + let cnt1 = defmt::unwrap!( + cordic + .async_calc_32bit( + &mut write_dma, + &mut read_dma, + &input_q1_31[2..], + &mut output_q1_31[cnt0..], + true, + false, + ) + .await + ); + + // all output value length should be the same as our output buffer size + defmt::assert_eq!(cnt0 + cnt1, output_q1_31.len()); + + let mut cordic_result_f64 = [0.0f64; OUTPUT_LENGTH]; + + for (f64_val, u32_val) in cordic_result_f64.iter_mut().zip(output_q1_31) { + *f64_val = utils::q1_31_to_f64(u32_val); + } + + // + // software calculation + // + + let mut software_result_f64 = [0.0f64; OUTPUT_LENGTH]; + + let arg2 = utils::q1_31_to_f64(input_q1_31[1]); + + for (&arg1, res) in input_q1_31 + .iter() + .enumerate() + .filter_map(|(idx, val)| if idx != 1 { Some(val) } else { None }) + .zip(software_result_f64.chunks_mut(2)) + { + let arg1 = utils::q1_31_to_f64(arg1); + + let (raw_res1, raw_res2) = (arg1 * core::f64::consts::PI).sin_cos(); + (res[0], res[1]) = (raw_res1 * arg2, raw_res2 * arg2); + } + + // + // check result are the same + // + + for (cordic_res, software_res) in cordic_result_f64[..cnt0 + cnt1] + .chunks(2) + .zip(software_result_f64.chunks(2)) + { + for (cord_res, soft_res) in cordic_res.iter().zip(software_res.iter()) { + // 2.0.powi(-19) is the max residual error for Sin function, in q1.31 format, with 24 iterations (aka PRECISION = 6) + defmt::assert!((cord_res - soft_res).abs() <= 2.0.powi(-19)); + } + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/cryp.rs b/embassy/tests/stm32/src/bin/cryp.rs new file mode 100644 index 0000000..028775a --- /dev/null +++ b/embassy/tests/stm32/src/bin/cryp.rs @@ -0,0 +1,79 @@ +// required-features: cryp +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use aes_gcm::aead::heapless::Vec; +use aes_gcm::aead::{AeadInPlace, KeyInit}; +use aes_gcm::Aes128Gcm; +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::cryp::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + CRYP => cryp::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p: embassy_stm32::Peripherals = init(); + + const PAYLOAD1: &[u8] = b"payload data 1 ;zdfhzdfhS;GKJASBDG;ASKDJBAL,zdfhzdfhzdfhzdfhvljhb,jhbjhb,sdhsdghsdhsfhsghzdfhzdfhzdfhzdfdhsdthsthsdhsgaadfhhgkdgfuoyguoft6783567"; + const PAYLOAD2: &[u8] = b"payload data 2 ;SKEzdfhzdfhzbhgvljhb,jhbjhb,sdhsdghsdhsfhsghshsfhshstsdthadfhsdfjhsfgjsfgjxfgjzdhgDFghSDGHjtfjtjszftjzsdtjhstdsdhsdhsdhsdhsdthsthsdhsgfh"; + const AAD1: &[u8] = b"additional data 1 stdargadrhaethaethjatjatjaetjartjstrjsfkk;'jopofyuisrteytweTASTUIKFUKIXTRDTEREharhaeryhaterjartjarthaethjrtjarthaetrhartjatejatrjsrtjartjyt1"; + const AAD2: &[u8] = b"additional data 2 stdhthsthsthsrthsrthsrtjdykjdukdyuldadfhsdghsdghsdghsadghjk'hioethjrtjarthaetrhartjatecfgjhzdfhgzdfhzdfghzdfhzdfhzfhjatrjsrtjartjytjfytjfyg"; + + let in_dma = peri!(p, CRYP_IN_DMA); + let out_dma = peri!(p, CRYP_OUT_DMA); + + let mut hw_cryp = Cryp::new(p.CRYP, in_dma, out_dma, Irqs); + let key: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + let mut ciphertext: [u8; PAYLOAD1.len() + PAYLOAD2.len()] = [0; PAYLOAD1.len() + PAYLOAD2.len()]; + let mut plaintext: [u8; PAYLOAD1.len() + PAYLOAD2.len()] = [0; PAYLOAD1.len() + PAYLOAD2.len()]; + let iv: [u8; 12] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + + // Encrypt in hardware using AES-GCM 128-bit in blocking mode. + let aes_gcm = AesGcm::new(&key, &iv); + let mut gcm_encrypt = hw_cryp.start_blocking(&aes_gcm, Direction::Encrypt); + hw_cryp.aad_blocking(&mut gcm_encrypt, AAD1, false); + hw_cryp.aad_blocking(&mut gcm_encrypt, AAD2, true); + hw_cryp.payload_blocking(&mut gcm_encrypt, PAYLOAD1, &mut ciphertext[..PAYLOAD1.len()], false); + hw_cryp.payload_blocking(&mut gcm_encrypt, PAYLOAD2, &mut ciphertext[PAYLOAD1.len()..], true); + let encrypt_tag = hw_cryp.finish_blocking(gcm_encrypt); + + // Decrypt in hardware using AES-GCM 128-bit in async (DMA) mode. + let mut gcm_decrypt = hw_cryp.start(&aes_gcm, Direction::Decrypt).await; + hw_cryp.aad(&mut gcm_decrypt, AAD1, false).await; + hw_cryp.aad(&mut gcm_decrypt, AAD2, true).await; + hw_cryp + .payload(&mut gcm_decrypt, &ciphertext, &mut plaintext, true) + .await; + let decrypt_tag = hw_cryp.finish(gcm_decrypt).await; + + info!("AES-GCM Ciphertext: {:?}", ciphertext); + info!("AES-GCM Plaintext: {:?}", plaintext); + defmt::assert!(PAYLOAD1 == &plaintext[..PAYLOAD1.len()]); + defmt::assert!(PAYLOAD2 == &plaintext[PAYLOAD1.len()..]); + defmt::assert!(encrypt_tag == decrypt_tag); + + // Encrypt in software using AES-GCM 128-bit + let mut payload_vec: Vec = Vec::from_slice(&PAYLOAD1).unwrap(); + payload_vec.extend_from_slice(&PAYLOAD2).unwrap(); + let cipher = Aes128Gcm::new(&key.into()); + let mut aad: Vec = Vec::from_slice(&AAD1).unwrap(); + aad.extend_from_slice(&AAD2).unwrap(); + let _ = cipher.encrypt_in_place(&iv.into(), &aad, &mut payload_vec); + + defmt::assert!(ciphertext == payload_vec[0..ciphertext.len()]); + defmt::assert!(encrypt_tag == payload_vec[ciphertext.len()..ciphertext.len() + encrypt_tag.len()]); + + // Decrypt in software using AES-GCM 128-bit + let _ = cipher.decrypt_in_place(&iv.into(), &aad, &mut payload_vec); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/dac.rs b/embassy/tests/stm32/src/bin/dac.rs new file mode 100644 index 0000000..88e6615 --- /dev/null +++ b/embassy/tests/stm32/src/bin/dac.rs @@ -0,0 +1,76 @@ +#![no_std] +#![no_main] + +// required-features: dac + +#[path = "../common.rs"] +mod common; +use core::f32::consts::PI; + +use common::*; +use defmt::assert; +use embassy_executor::Spawner; +use embassy_stm32::adc::Adc; +use embassy_stm32::dac::{DacCh1, Value}; +use embassy_stm32::dma::NoDma; +use embassy_time::Timer; +use micromath::F32Ext; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Initialize the board and obtain a Peripherals instance + let p: embassy_stm32::Peripherals = init(); + + let adc = peri!(p, ADC); + let dac = peri!(p, DAC); + let dac_pin = peri!(p, DAC_PIN); + let mut adc_pin = unsafe { core::ptr::read(&dac_pin) }; + + let mut dac = DacCh1::new(dac, NoDma, dac_pin); + let mut adc = Adc::new(adc); + + #[cfg(feature = "stm32h755zi")] + let normalization_factor = 256; + #[cfg(any(feature = "stm32f429zi", feature = "stm32f446re", feature = "stm32g071rb"))] + let normalization_factor: i32 = 16; + + dac.set(Value::Bit8(0)); + // Now wait a little to obtain a stable value + Timer::after_millis(30).await; + let offset = adc.blocking_read(&mut adc_pin); + + for v in 0..=255 { + // First set the DAC output value + let dac_output_val = to_sine_wave(v); + dac.set(Value::Bit8(dac_output_val)); + + // Now wait a little to obtain a stable value + Timer::after_millis(30).await; + + // Need to steal the peripherals here because PA4 is obviously in use already + let measured = adc.blocking_read(&mut unsafe { embassy_stm32::Peripherals::steal() }.PA4); + // Calibrate and normalize the measurement to get close to the dac_output_val + let measured_normalized = ((measured as i32 - offset as i32) / normalization_factor) as i16; + + //info!("value / measured: {} / {}", dac_output_val, measured_normalized); + + // The deviations are quite enormous but that does not matter since this is only a quick test + assert!((dac_output_val as i16 - measured_normalized).abs() < 15); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn to_sine_wave(v: u8) -> u8 { + if v >= 128 { + // top half + let r = PI * ((v - 128) as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } else { + // bottom half + let r = PI + PI * (v as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } +} diff --git a/embassy/tests/stm32/src/bin/dac_l1.rs b/embassy/tests/stm32/src/bin/dac_l1.rs new file mode 100644 index 0000000..925db61 --- /dev/null +++ b/embassy/tests/stm32/src/bin/dac_l1.rs @@ -0,0 +1,86 @@ +#![no_std] +#![no_main] + +// required-features: stm32l152re + +#[path = "../common.rs"] +mod common; +use core::f32::consts::PI; + +use common::*; +use defmt::assert; +use embassy_executor::Spawner; +use embassy_stm32::adc::Adc; +use embassy_stm32::dac::{DacCh1, Value}; +use embassy_stm32::dma::NoDma; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use micromath::F32Ext; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC1 => embassy_stm32::adc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Initialize the board and obtain a Peripherals instance + let p: embassy_stm32::Peripherals = init(); + + let adc = peri!(p, ADC); + let dac = peri!(p, DAC); + let dac_pin = peri!(p, DAC_PIN); + let mut adc_pin = unsafe { core::ptr::read(&dac_pin) }; + + let mut dac = DacCh1::new(dac, NoDma, dac_pin); + let mut adc = Adc::new(adc, Irqs); + + #[cfg(feature = "stm32h755zi")] + let normalization_factor = 256; + #[cfg(any( + feature = "stm32f429zi", + feature = "stm32f446re", + feature = "stm32g071rb", + feature = "stm32l152re", + ))] + let normalization_factor: i32 = 16; + + dac.set(Value::Bit8(0)); + // Now wait a little to obtain a stable value + Timer::after_millis(30).await; + let offset = adc.read(&mut adc_pin).await; + + for v in 0..=255 { + // First set the DAC output value + let dac_output_val = to_sine_wave(v); + dac.set(Value::Bit8(dac_output_val)); + + // Now wait a little to obtain a stable value + Timer::after_millis(30).await; + + // Need to steal the peripherals here because PA4 is obviously in use already + let measured = adc.read(&mut unsafe { embassy_stm32::Peripherals::steal() }.PA4).await; + // Calibrate and normalize the measurement to get close to the dac_output_val + let measured_normalized = ((measured as i32 - offset as i32) / normalization_factor) as i16; + + info!("value / measured: {} / {}", dac_output_val, measured_normalized); + + // The deviations are quite enormous but that does not matter since this is only a quick test + assert!((dac_output_val as i16 - measured_normalized).abs() < 15); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn to_sine_wave(v: u8) -> u8 { + if v >= 128 { + // top half + let r = PI * ((v - 128) as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } else { + // bottom half + let r = PI + PI * (v as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } +} diff --git a/embassy/tests/stm32/src/bin/eth.rs b/embassy/tests/stm32/src/bin/eth.rs new file mode 100644 index 0000000..bf1922d --- /dev/null +++ b/embassy/tests/stm32/src/bin/eth.rs @@ -0,0 +1,120 @@ +// required-features: eth +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; +use common::*; +use embassy_executor::Spawner; +use embassy_net::StackResources; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_stm32::peripherals::ETH; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng}; +use rand_core::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +teleprobe_meta::timeout!(120); + +#[cfg(not(any(feature = "stm32h563zi", feature = "stm32f767zi", feature = "stm32f207zg")))] +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + HASH_RNG => rng::InterruptHandler; +}); +#[cfg(any(feature = "stm32h563zi", feature = "stm32f767zi", feature = "stm32f207zg"))] +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + RNG => rng::InterruptHandler; +}); + +type Device = Ethernet<'static, ETH, GenericSMI>; + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = init(); + info!("Hello World!"); + + // Generate random seed. + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Ensure different boards get different MAC + // so running tests concurrently doesn't break (they're all in the same LAN) + #[cfg(feature = "stm32f429zi")] + let n = 1; + #[cfg(feature = "stm32h755zi")] + let n = 2; + #[cfg(feature = "stm32h563zi")] + let n = 3; + #[cfg(feature = "stm32f767zi")] + let n = 4; + #[cfg(feature = "stm32f207zg")] + let n = 5; + #[cfg(feature = "stm32h753zi")] + let n = 6; + + let mac_addr = [0x00, n, 0xDE, 0xAD, 0xBE, 0xEF]; + + // F2 runs out of RAM + #[cfg(feature = "stm32f207zg")] + const PACKET_QUEUE_SIZE: usize = 2; + #[cfg(not(feature = "stm32f207zg"))] + const PACKET_QUEUE_SIZE: usize = 4; + + static PACKETS: StaticCell> = StaticCell::new(); + let device = Ethernet::new( + PACKETS.init(PacketQueue::::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + #[cfg(not(feature = "stm32h563zi"))] + p.PB13, + #[cfg(feature = "stm32h563zi")] + p.PB15, + p.PG11, + GenericSMI::new(0), + mac_addr, + ); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + perf_client::run( + stack, + perf_client::Expected { + down_kbps: 1000, + up_kbps: 1000, + updown_kbps: 1000, + }, + ) + .await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/fdcan.rs b/embassy/tests/stm32/src/bin/fdcan.rs new file mode 100644 index 0000000..83d7eca --- /dev/null +++ b/embassy/tests/stm32/src/bin/fdcan.rs @@ -0,0 +1,142 @@ +#![no_std] +#![no_main] + +// required-features: fdcan + +#[path = "../common.rs"] +mod common; +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::peripherals::*; +use embassy_stm32::{bind_interrupts, can, Config}; +use embassy_time::Duration; +use {defmt_rtt as _, panic_probe as _}; + +mod can_common; +use can_common::*; + +bind_interrupts!(struct Irqs2 { + FDCAN2_IT0 => can::IT0InterruptHandler; + FDCAN2_IT1 => can::IT1InterruptHandler; +}); +bind_interrupts!(struct Irqs1 { + FDCAN1_IT0 => can::IT0InterruptHandler; + FDCAN1_IT1 => can::IT1InterruptHandler; +}); + +#[cfg(feature = "stm32h563zi")] +fn options() -> (Config, TestOptions) { + info!("H563 config"); + ( + config(), + TestOptions { + max_latency: Duration::from_micros(1200), + max_buffered: 3, + }, + ) +} + +#[cfg(any(feature = "stm32h755zi", feature = "stm32h753zi"))] +fn options() -> (Config, TestOptions) { + use embassy_stm32::rcc; + info!("H75 config"); + let mut c = config(); + c.rcc.hse = Some(rcc::Hse { + freq: embassy_stm32::time::Hertz(25_000_000), + mode: rcc::HseMode::Oscillator, + }); + c.rcc.mux.fdcansel = rcc::mux::Fdcansel::HSE; + ( + c, + TestOptions { + max_latency: Duration::from_micros(1200), + max_buffered: 3, + }, + ) +} + +#[cfg(any(feature = "stm32h7a3zi"))] +fn options() -> (Config, TestOptions) { + use embassy_stm32::rcc; + info!("H7a config"); + let mut c = config(); + c.rcc.hse = Some(rcc::Hse { + freq: embassy_stm32::time::Hertz(25_000_000), + mode: rcc::HseMode::Oscillator, + }); + c.rcc.mux.fdcansel = rcc::mux::Fdcansel::HSE; + ( + c, + TestOptions { + max_latency: Duration::from_micros(1200), + max_buffered: 3, + }, + ) +} + +#[cfg(any(feature = "stm32h7s3l8"))] +fn options() -> (Config, TestOptions) { + use embassy_stm32::rcc; + let mut c = config(); + c.rcc.mux.fdcansel = rcc::mux::Fdcansel::HSE; + ( + c, + TestOptions { + max_latency: Duration::from_micros(1200), + max_buffered: 3, + }, + ) +} + +#[cfg(any(feature = "stm32g491re"))] +fn options() -> (Config, TestOptions) { + info!("G4 config"); + ( + config(), + TestOptions { + max_latency: Duration::from_micros(500), + max_buffered: 6, + }, + ) +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + //let peripherals = init(); + + let (config, options) = options(); + let peripherals = init_with_config(config); + + let mut can = can::CanConfigurator::new(peripherals.FDCAN1, peripherals.PB8, peripherals.PB9, Irqs1); + let mut can2 = can::CanConfigurator::new(peripherals.FDCAN2, peripherals.PB12, peripherals.PB13, Irqs2); + + // 250k bps + can.set_bitrate(250_000); + can2.set_bitrate(250_000); + + can.properties().set_extended_filter( + can::filter::ExtendedFilterSlot::_0, + can::filter::ExtendedFilter::accept_all_into_fifo1(), + ); + can2.properties().set_extended_filter( + can::filter::ExtendedFilterSlot::_0, + can::filter::ExtendedFilter::accept_all_into_fifo1(), + ); + + let mut can = can.into_internal_loopback_mode(); + let mut can2 = can2.into_internal_loopback_mode(); + + run_can_tests(&mut can, &options).await; + run_can_tests(&mut can2, &options).await; + + info!("CAN Configured"); + + // Test again with a split + let (mut tx, mut rx, _props) = can.split(); + let (mut tx2, mut rx2, _props) = can2.split(); + run_split_can_tests(&mut tx, &mut rx, &options).await; + run_split_can_tests(&mut tx2, &mut rx2, &options).await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/gpio.rs b/embassy/tests/stm32/src/bin/gpio.rs new file mode 100644 index 0000000..4a2584b --- /dev/null +++ b/embassy/tests/stm32/src/bin/gpio.rs @@ -0,0 +1,227 @@ +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use common::*; +use defmt::assert; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Flex, Input, Level, Output, OutputOpenDrain, Pull, Speed}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = init(); + info!("Hello World!"); + + // Arduino pins D0 and D1 + // They're connected together with a 1K resistor. + let mut a = peri!(p, UART_RX); + let mut b = peri!(p, UART_TX); + + // Test initial output + { + let b = Input::new(&mut b, Pull::None); + + { + let a = Output::new(&mut a, Level::Low, Speed::Low); + delay(); + assert!(b.is_low()); + assert!(!b.is_high()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); + } + { + let mut a = Output::new(&mut a, Level::High, Speed::Low); + delay(); + assert!(!b.is_low()); + assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); + + // Test is_set_low / is_set_high + a.set_low(); + delay(); + assert!(b.is_low()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); + + a.set_high(); + delay(); + assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); + + // Test toggle + a.toggle(); + delay(); + assert!(b.is_low()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); + + a.toggle(); + delay(); + assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); + } + } + + // Test input no pull + { + let b = Input::new(&mut b, Pull::None); + // no pull, the status is undefined + + let mut a = Output::new(&mut a, Level::Low, Speed::Low); + delay(); + assert!(b.is_low()); + a.set_high(); + delay(); + assert!(b.is_high()); + } + + // Test input pulldown + { + let b = Input::new(&mut b, Pull::Down); + delay(); + assert!(b.is_low()); + + let mut a = Output::new(&mut a, Level::Low, Speed::Low); + delay(); + assert!(b.is_low()); + a.set_high(); + delay(); + assert!(b.is_high()); + } + + // Test input pullup + { + let b = Input::new(&mut b, Pull::Up); + delay(); + assert!(b.is_high()); + + let mut a = Output::new(&mut a, Level::Low, Speed::Low); + delay(); + assert!(b.is_low()); + a.set_high(); + delay(); + assert!(b.is_high()); + } + + // Test output open drain + { + let b = Input::new(&mut b, Pull::Down); + // no pull, the status is undefined + + let mut a = OutputOpenDrain::new(&mut a, Level::Low, Speed::Low); + delay(); + assert!(b.is_low()); + a.set_high(); // High-Z output + delay(); + assert!(b.is_low()); + } + + // FLEX + // Test initial output + { + //Flex pin configured as input + let mut b = Flex::new(&mut b); + b.set_as_input(Pull::None); + + { + //Flex pin configured as output + let mut a = Flex::new(&mut a); //Flex pin configured as output + a.set_low(); // Pin state must be set before configuring the pin, thus we avoid unknown state + a.set_as_output(Speed::Low); + delay(); + assert!(b.is_low()); + } + { + //Flex pin configured as output + let mut a = Flex::new(&mut a); + a.set_high(); + a.set_as_output(Speed::Low); + + delay(); + assert!(b.is_high()); + } + } + + // Test input no pull + { + let mut b = Flex::new(&mut b); + b.set_as_input(Pull::None); // no pull, the status is undefined + + let mut a = Flex::new(&mut a); + a.set_low(); + a.set_as_output(Speed::Low); + + delay(); + assert!(b.is_low()); + a.set_high(); + delay(); + assert!(b.is_high()); + } + + // Test input pulldown + { + let mut b = Flex::new(&mut b); + b.set_as_input(Pull::Down); + delay(); + assert!(b.is_low()); + + let mut a = Flex::new(&mut a); + a.set_low(); + a.set_as_output(Speed::Low); + delay(); + assert!(b.is_low()); + a.set_high(); + delay(); + assert!(b.is_high()); + } + + // Test input pullup + { + let mut b = Flex::new(&mut b); + b.set_as_input(Pull::Up); + delay(); + assert!(b.is_high()); + + let mut a = Flex::new(&mut a); + a.set_high(); + a.set_as_output(Speed::Low); + delay(); + assert!(b.is_high()); + a.set_low(); + delay(); + assert!(b.is_low()); + } + + // Test output open drain + { + let mut b = Flex::new(&mut b); + b.set_as_input(Pull::Down); + + let mut a = Flex::new(&mut a); + a.set_low(); + a.set_as_input_output(Speed::Low); + delay(); + assert!(b.is_low()); + a.set_high(); // High-Z output + delay(); + assert!(b.is_low()); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn delay() { + #[cfg(any( + feature = "stm32h755zi", + feature = "stm32h753zi", + feature = "stm32h7a3zi", + feature = "stm32h7s3l8" + ))] + cortex_m::asm::delay(9000); + cortex_m::asm::delay(1000); +} diff --git a/embassy/tests/stm32/src/bin/hash.rs b/embassy/tests/stm32/src/bin/hash.rs new file mode 100644 index 0000000..bdb3c9a --- /dev/null +++ b/embassy/tests/stm32/src/bin/hash.rs @@ -0,0 +1,102 @@ +// required-features: hash +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::dma::NoDma; +use embassy_stm32::hash::*; +use embassy_stm32::{bind_interrupts, hash, peripherals}; +use hmac::{Hmac, Mac}; +use sha2::{Digest, Sha224, Sha256}; +use {defmt_rtt as _, panic_probe as _}; + +type HmacSha256 = Hmac; + +#[cfg(any(feature = "stm32l4a6zg", feature = "stm32h755zi", feature = "stm32h753zi"))] +bind_interrupts!(struct Irqs { + HASH_RNG => hash::InterruptHandler; +}); + +#[cfg(any( + feature = "stm32wba52cg", + feature = "stm32l552ze", + feature = "stm32h563zi", + feature = "stm32h503rb", + feature = "stm32u5a5zj", + feature = "stm32u585ai", + feature = "stm32h7s3l8" +))] +bind_interrupts!(struct Irqs { + HASH => hash::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p: embassy_stm32::Peripherals = init(); + let mut hw_hasher = Hash::new(p.HASH, NoDma, Irqs); + + let test_1: &[u8] = b"as;dfhaslfhas;oifvnasd;nifvnhasd;nifvhndlkfghsd;nvfnahssdfgsdafgsasdfasdfasdfasdfasdfghjklmnbvcalskdjghalskdjgfbaslkdjfgbalskdjgbalskdjbdfhsdfhsfghsfghfgh"; + let test_2: &[u8] = b"fdhalksdjfhlasdjkfhalskdjfhgal;skdjfgalskdhfjgalskdjfglafgadfgdfgdafgaadsfgfgdfgadrgsyfthxfgjfhklhjkfgukhulkvhlvhukgfhfsrghzdhxyfufynufyuszeradrtydyytserr"; + let test_3: &[u8] = b"a.ewtkluGWEBR.KAJRBTA,RMNRBG,FDMGB.kger.tkasjrbt.akrjtba.krjtba.ktmyna,nmbvtyliasd;gdrtba,sfvs.kgjzshd.gkbsr.tksejb.SDkfBSE.gkfgb>ESkfbSE>gkJSBESE>kbSE>fk"; + + // Start an SHA-256 digest. + let mut sha256context = hw_hasher.start(Algorithm::SHA256, DataType::Width8, None); + hw_hasher.update_blocking(&mut sha256context, test_1); + + // Interrupt the SHA-256 digest to compute an SHA-224 digest. + let mut sha224context = hw_hasher.start(Algorithm::SHA224, DataType::Width8, None); + hw_hasher.update_blocking(&mut sha224context, test_3); + let mut sha224_digest_buffer: [u8; 28] = [0; 28]; + let _ = hw_hasher.finish_blocking(sha224context, &mut sha224_digest_buffer); + + // Finish the SHA-256 digest. + hw_hasher.update_blocking(&mut sha256context, test_2); + let mut sha256_digest_buffer: [u8; 32] = [0; 32]; + let _ = hw_hasher.finish_blocking(sha256context, &mut sha256_digest_buffer); + + // Compute the SHA-256 digest in software. + let mut sw_sha256_hasher = Sha256::new(); + sw_sha256_hasher.update(test_1); + sw_sha256_hasher.update(test_2); + let sw_sha256_digest = sw_sha256_hasher.finalize(); + + //Compute the SHA-224 digest in software. + let mut sw_sha224_hasher = Sha224::new(); + sw_sha224_hasher.update(test_3); + let sw_sha224_digest = sw_sha224_hasher.finalize(); + + // Compare the SHA-256 digests. + info!("Hardware SHA-256 Digest: {:?}", sha256_digest_buffer); + info!("Software SHA-256 Digest: {:?}", sw_sha256_digest[..]); + defmt::assert!(sha256_digest_buffer == sw_sha256_digest[..]); + + // Compare the SHA-224 digests. + info!("Hardware SHA-256 Digest: {:?}", sha224_digest_buffer); + info!("Software SHA-256 Digest: {:?}", sw_sha224_digest[..]); + defmt::assert!(sha224_digest_buffer == sw_sha224_digest[..]); + + let hmac_key: [u8; 64] = [0x55; 64]; + + // Compute HMAC in hardware. + let mut sha256hmac_context = hw_hasher.start(Algorithm::SHA256, DataType::Width8, Some(&hmac_key)); + hw_hasher.update_blocking(&mut sha256hmac_context, test_1); + hw_hasher.update_blocking(&mut sha256hmac_context, test_2); + let mut hw_hmac: [u8; 32] = [0; 32]; + hw_hasher.finish_blocking(sha256hmac_context, &mut hw_hmac); + + // Compute HMAC in software. + let mut sw_mac = HmacSha256::new_from_slice(&hmac_key).unwrap(); + sw_mac.update(test_1); + sw_mac.update(test_2); + let sw_hmac = sw_mac.finalize().into_bytes(); + + info!("Hardware HMAC: {:?}", hw_hmac); + info!("Software HMAC: {:?}", sw_hmac[..]); + defmt::assert!(hw_hmac == sw_hmac[..]); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/rng.rs b/embassy/tests/stm32/src/bin/rng.rs new file mode 100644 index 0000000..8438353 --- /dev/null +++ b/embassy/tests/stm32/src/bin/rng.rs @@ -0,0 +1,60 @@ +// required-features: rng +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, peripherals, rng}; +use {defmt_rtt as _, panic_probe as _}; + +#[cfg(any( + feature = "stm32l4a6zg", + feature = "stm32h755zi", + feature = "stm32h753zi", + feature = "stm32f429zi" +))] +bind_interrupts!(struct Irqs { + HASH_RNG => rng::InterruptHandler; +}); +#[cfg(any(feature = "stm32l073rz"))] +bind_interrupts!(struct Irqs { + RNG_LPUART1 => rng::InterruptHandler; +}); +#[cfg(any(feature = "stm32u083rc"))] +bind_interrupts!(struct Irqs { + RNG_CRYP => rng::InterruptHandler; +}); +#[cfg(not(any( + feature = "stm32l4a6zg", + feature = "stm32l073rz", + feature = "stm32h755zi", + feature = "stm32h753zi", + feature = "stm32f429zi", + feature = "stm32u083rc" +)))] +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p: embassy_stm32::Peripherals = init(); + + let mut rng = Rng::new(p.RNG, Irqs); + + let mut buf1 = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf1).await); + info!("random bytes: {:02x}", buf1); + + let mut buf2 = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf2).await); + info!("random bytes: {:02x}", buf2); + + defmt::assert!(buf1 != buf2); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/rtc.rs b/embassy/tests/stm32/src/bin/rtc.rs new file mode 100644 index 0000000..5fe98d8 --- /dev/null +++ b/embassy/tests/stm32/src/bin/rtc.rs @@ -0,0 +1,45 @@ +// required-features: chrono + +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use chrono::{NaiveDate, NaiveDateTime}; +use common::*; +use defmt::assert; +use embassy_executor::Spawner; +use embassy_stm32::rcc::LsConfig; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_time::Timer; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = config(); + config.rcc.ls = LsConfig::default_lse(); + + let p = init_with_config(config); + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + info!("Waiting 5 seconds"); + Timer::after_millis(5000).await; + + let then: NaiveDateTime = rtc.now().unwrap().into(); + let seconds = (then - now).num_seconds(); + + info!("measured = {}", seconds); + + assert!(seconds > 3 && seconds < 7); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/sdmmc.rs b/embassy/tests/stm32/src/bin/sdmmc.rs new file mode 100644 index 0000000..a6bc117 --- /dev/null +++ b/embassy/tests/stm32/src/bin/sdmmc.rs @@ -0,0 +1,141 @@ +// required-features: sdmmc +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use common::*; +use defmt::assert_eq; +use embassy_executor::Spawner; +use embassy_stm32::sdmmc::{DataBlock, Sdmmc}; +use embassy_stm32::time::mhz; +use embassy_stm32::{bind_interrupts, peripherals, sdmmc}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SDIO => sdmmc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let p = init(); + + let (mut sdmmc, mut dma, mut clk, mut cmd, mut d0, mut d1, mut d2, mut d3) = + (p.SDIO, p.DMA2_CH3, p.PC12, p.PD2, p.PC8, p.PC9, p.PC10, p.PC11); + + // Arbitrary block index + let block_idx = 16; + + let mut pattern1 = DataBlock([0u8; 512]); + let mut pattern2 = DataBlock([0u8; 512]); + for i in 0..512 { + pattern1[i] = i as u8; + pattern2[i] = !i as u8; + } + + let mut block = DataBlock([0u8; 512]); + + // ======== Try 4bit. ============== + info!("initializing in 4-bit mode..."); + let mut s = Sdmmc::new_4bit( + &mut sdmmc, + Irqs, + &mut dma, + &mut clk, + &mut cmd, + &mut d0, + &mut d1, + &mut d2, + &mut d3, + Default::default(), + ); + + let mut err = None; + loop { + match s.init_card(mhz(24)).await { + Ok(_) => break, + Err(e) => { + if err != Some(e) { + info!("waiting for card: {:?}", e); + err = Some(e); + } + } + } + } + + let card = unwrap!(s.card()); + + info!("Card: {:#?}", Debug2Format(card)); + info!("Clock: {}", s.clock()); + + info!("writing pattern1..."); + s.write_block(block_idx, &pattern1).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern1); + + info!("writing pattern2..."); + s.write_block(block_idx, &pattern2).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern2); + + drop(s); + + // ======== Try 1bit. ============== + info!("initializing in 1-bit mode..."); + let mut s = Sdmmc::new_1bit( + &mut sdmmc, + Irqs, + &mut dma, + &mut clk, + &mut cmd, + &mut d0, + Default::default(), + ); + + let mut err = None; + loop { + match s.init_card(mhz(24)).await { + Ok(_) => break, + Err(e) => { + if err != Some(e) { + info!("waiting for card: {:?}", e); + err = Some(e); + } + } + } + } + + let card = unwrap!(s.card()); + + info!("Card: {:#?}", Debug2Format(card)); + info!("Clock: {}", s.clock()); + + info!("reading pattern2 written in 4bit mode..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern2); + + info!("writing pattern1..."); + s.write_block(block_idx, &pattern1).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern1); + + info!("writing pattern2..."); + s.write_block(block_idx, &pattern2).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern2); + + drop(s); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/spi.rs b/embassy/tests/stm32/src/bin/spi.rs new file mode 100644 index 0000000..9712a8c --- /dev/null +++ b/embassy/tests/stm32/src/bin/spi.rs @@ -0,0 +1,139 @@ +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use common::*; +use defmt::assert_eq; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::spi::{self, Spi, Word}; +use embassy_stm32::time::Hertz; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = init(); + info!("Hello World!"); + + let mut spi_peri = peri!(p, SPI); + let mut sck = peri!(p, SPI_SCK); + let mut mosi = peri!(p, SPI_MOSI); + let mut miso = peri!(p, SPI_MISO); + + let mut spi_config = spi::Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new_blocking( + &mut spi_peri, + &mut sck, // Arduino D13 + &mut mosi, // Arduino D11 + &mut miso, // Arduino D12 + spi_config, + ); + + test_txrx::(&mut spi); + test_txrx::(&mut spi); + + // Assert the RCC bit gets disabled on drop. + #[cfg(feature = "stm32f429zi")] + defmt::assert!(embassy_stm32::pac::RCC.apb2enr().read().spi1en()); + drop(spi); + #[cfg(feature = "stm32f429zi")] + defmt::assert!(!embassy_stm32::pac::RCC.apb2enr().read().spi1en()); + + // test rx-only configuration + let mut spi = Spi::new_blocking_rxonly(&mut spi_peri, &mut sck, &mut miso, spi_config); + let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); + + test_rx::(&mut spi, &mut mosi_out); + test_rx::(&mut spi, &mut mosi_out); + drop(spi); + drop(mosi_out); + + let mut spi = Spi::new_blocking_txonly(&mut spi_peri, &mut sck, &mut mosi, spi_config); + test_tx::(&mut spi); + test_tx::(&mut spi); + drop(spi); + + let mut spi = Spi::new_blocking_txonly_nosck(&mut spi_peri, &mut mosi, spi_config); + test_tx::(&mut spi); + test_tx::(&mut spi); + drop(spi); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn test_txrx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>) +where + W: core::ops::Not, +{ + let data: [W; 9] = [ + 0x00u8.into(), + 0xFFu8.into(), + 0xAAu8.into(), + 0x55u8.into(), + 0xC0u8.into(), + 0xFFu8.into(), + 0xEEu8.into(), + 0xC0u8.into(), + 0xDEu8.into(), + ]; + + // Arduino pins D11 and D12 (MOSI-MISO) are connected together with a 1K resistor. + // so we should get the data we sent back. + let mut buf = [W::default(); 9]; + spi.blocking_transfer(&mut buf, &data).unwrap(); + assert_eq!(buf, data); + + spi.blocking_transfer_in_place(&mut buf).unwrap(); + assert_eq!(buf, data); + + // Check read/write don't hang. We can't check they transfer the right data + // without fancier test mechanisms. + spi.blocking_write(&buf).unwrap(); + spi.blocking_read(&mut buf).unwrap(); + spi.blocking_write(&buf).unwrap(); + spi.blocking_read(&mut buf).unwrap(); + spi.blocking_write(&buf).unwrap(); + + // Check transfer doesn't break after having done a write, due to garbage in the FIFO + spi.blocking_transfer(&mut buf, &data).unwrap(); + assert_eq!(buf, data); + + // Check zero-length operations, these should be noops. + spi.blocking_transfer::(&mut [], &[]).unwrap(); + spi.blocking_transfer_in_place::(&mut []).unwrap(); + spi.blocking_read::(&mut []).unwrap(); + spi.blocking_write::(&[]).unwrap(); +} + +fn test_rx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>, mosi_out: &mut Output<'_>) +where + W: core::ops::Not, +{ + let mut buf = [W::default(); 9]; + + mosi_out.set_high(); + spi.blocking_read(&mut buf).unwrap(); + assert_eq!(buf, [!W::default(); 9]); + mosi_out.set_low(); + spi.blocking_read(&mut buf).unwrap(); + assert_eq!(buf, [W::default(); 9]); + spi.blocking_read::(&mut []).unwrap(); + spi.blocking_read::(&mut []).unwrap(); +} + +fn test_tx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>) +where + W: core::ops::Not, +{ + let buf = [W::default(); 9]; + + // Test tx-only. Just check it doesn't hang, not much else we can do without using SPI slave. + spi.blocking_write(&buf).unwrap(); + spi.blocking_write::(&[]).unwrap(); + spi.blocking_write(&buf).unwrap(); + spi.blocking_write::(&[]).unwrap(); +} diff --git a/embassy/tests/stm32/src/bin/spi_dma.rs b/embassy/tests/stm32/src/bin/spi_dma.rs new file mode 100644 index 0000000..307409a --- /dev/null +++ b/embassy/tests/stm32/src/bin/spi_dma.rs @@ -0,0 +1,180 @@ +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use common::*; +use defmt::assert_eq; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::mode::Async; +use embassy_stm32::spi::{self, Spi, Word}; +use embassy_stm32::time::Hertz; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = init(); + info!("Hello World!"); + + let mut spi_peri = peri!(p, SPI); + let mut sck = peri!(p, SPI_SCK); + let mut mosi = peri!(p, SPI_MOSI); + let mut miso = peri!(p, SPI_MISO); + let mut tx_dma = peri!(p, SPI_TX_DMA); + let mut rx_dma = peri!(p, SPI_RX_DMA); + + let mut spi_config = spi::Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new( + &mut spi_peri, + &mut sck, // Arduino D13 + &mut mosi, // Arduino D11 + &mut miso, // Arduino D12 + &mut tx_dma, + &mut rx_dma, + spi_config, + ); + + test_txrx::(&mut spi).await; + test_txrx::(&mut spi).await; + drop(spi); + + // test rx-only configuration + let mut spi = Spi::new_rxonly( + &mut spi_peri, + &mut sck, + &mut miso, + // SPIv1/f1 requires txdma even if rxonly. + #[cfg(not(feature = "spi-v345"))] + &mut tx_dma, + &mut rx_dma, + spi_config, + ); + let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); + + test_rx::(&mut spi, &mut mosi_out).await; + test_rx::(&mut spi, &mut mosi_out).await; + drop(spi); + drop(mosi_out); + + let mut spi = Spi::new_txonly(&mut spi_peri, &mut sck, &mut mosi, &mut tx_dma, spi_config); + test_tx::(&mut spi).await; + test_tx::(&mut spi).await; + drop(spi); + + let mut spi = Spi::new_txonly_nosck(&mut spi_peri, &mut mosi, &mut tx_dma, spi_config); + test_tx::(&mut spi).await; + test_tx::(&mut spi).await; + drop(spi); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +async fn test_txrx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>) +where + W: core::ops::Not, +{ + let data: [W; 9] = [ + 0x00u8.into(), + 0xFFu8.into(), + 0xAAu8.into(), + 0x55u8.into(), + 0xC0u8.into(), + 0xFFu8.into(), + 0xEEu8.into(), + 0xC0u8.into(), + 0xDEu8.into(), + ]; + + // Arduino pins D11 and D12 (MOSI-MISO) are connected together with a 1K resistor. + // so we should get the data we sent back. + let mut buf = [W::default(); 9]; + spi.transfer(&mut buf, &data).await.unwrap(); + assert_eq!(buf, data); + + spi.transfer_in_place(&mut buf).await.unwrap(); + assert_eq!(buf, data); + + // Check read/write don't hang. We can't check they transfer the right data + // without fancier test mechanisms. + spi.write(&buf).await.unwrap(); + spi.read(&mut buf).await.unwrap(); + spi.write(&buf).await.unwrap(); + spi.read(&mut buf).await.unwrap(); + spi.write(&buf).await.unwrap(); + + // Check transfer doesn't break after having done a write, due to garbage in the FIFO + spi.transfer(&mut buf, &data).await.unwrap(); + assert_eq!(buf, data); + + // Check zero-length operations, these should be noops. + spi.transfer::(&mut [], &[]).await.unwrap(); + spi.transfer_in_place::(&mut []).await.unwrap(); + spi.read::(&mut []).await.unwrap(); + spi.write::(&[]).await.unwrap(); + spi.blocking_transfer::(&mut [], &[]).unwrap(); + spi.blocking_transfer_in_place::(&mut []).unwrap(); + spi.blocking_read::(&mut []).unwrap(); + spi.blocking_write::(&[]).unwrap(); + + // Check mixing blocking with async. + spi.blocking_transfer(&mut buf, &data).unwrap(); + assert_eq!(buf, data); + spi.transfer(&mut buf, &data).await.unwrap(); + assert_eq!(buf, data); + spi.blocking_write(&buf).unwrap(); + spi.transfer(&mut buf, &data).await.unwrap(); + assert_eq!(buf, data); + spi.blocking_read(&mut buf).unwrap(); + spi.blocking_write(&buf).unwrap(); + spi.write(&buf).await.unwrap(); + spi.read(&mut buf).await.unwrap(); + spi.blocking_write(&buf).unwrap(); + spi.blocking_read(&mut buf).unwrap(); + spi.write(&buf).await.unwrap(); +} + +async fn test_rx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>, mosi_out: &mut Output<'_>) +where + W: core::ops::Not, +{ + let mut buf = [W::default(); 9]; + + mosi_out.set_high(); + spi.read(&mut buf).await.unwrap(); + assert_eq!(buf, [!W::default(); 9]); + spi.blocking_read(&mut buf).unwrap(); + assert_eq!(buf, [!W::default(); 9]); + spi.read(&mut buf).await.unwrap(); + assert_eq!(buf, [!W::default(); 9]); + spi.read(&mut buf).await.unwrap(); + assert_eq!(buf, [!W::default(); 9]); + spi.blocking_read(&mut buf).unwrap(); + assert_eq!(buf, [!W::default(); 9]); + spi.blocking_read(&mut buf).unwrap(); + assert_eq!(buf, [!W::default(); 9]); + mosi_out.set_low(); + spi.read(&mut buf).await.unwrap(); + assert_eq!(buf, [W::default(); 9]); + spi.read::(&mut []).await.unwrap(); + spi.blocking_read::(&mut []).unwrap(); +} + +async fn test_tx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>) +where + W: core::ops::Not, +{ + let buf = [W::default(); 9]; + + // Test tx-only. Just check it doesn't hang, not much else we can do without using SPI slave. + spi.blocking_write(&buf).unwrap(); + spi.write(&buf).await.unwrap(); + spi.blocking_write(&buf).unwrap(); + spi.blocking_write(&buf).unwrap(); + spi.write(&buf).await.unwrap(); + spi.write(&buf).await.unwrap(); + spi.write::(&[]).await.unwrap(); + spi.blocking_write::(&[]).unwrap(); +} diff --git a/embassy/tests/stm32/src/bin/stop.rs b/embassy/tests/stm32/src/bin/stop.rs new file mode 100644 index 0000000..772bc52 --- /dev/null +++ b/embassy/tests/stm32/src/bin/stop.rs @@ -0,0 +1,80 @@ +// required-features: stop,chrono + +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use chrono::NaiveDate; +use common::*; +use cortex_m_rt::entry; +use embassy_executor::Spawner; +use embassy_stm32::low_power::{stop_ready, stop_with_rtc, Executor, StopMode}; +use embassy_stm32::rcc::LsConfig; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::Config; +use embassy_time::Timer; +use static_cell::StaticCell; + +#[entry] +fn main() -> ! { + Executor::take().run(|spawner| { + unwrap!(spawner.spawn(async_main(spawner))); + }); +} + +#[embassy_executor::task] +async fn task_1() { + for _ in 0..9 { + info!("task 1: waiting for 500ms..."); + defmt::assert!(stop_ready(StopMode::Stop2)); + Timer::after_millis(500).await; + } +} + +#[embassy_executor::task] +async fn task_2() { + for _ in 0..5 { + info!("task 2: waiting for 1000ms..."); + defmt::assert!(stop_ready(StopMode::Stop2)); + Timer::after_millis(1000).await; + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +#[embassy_executor::task] +async fn async_main(spawner: Spawner) { + let _ = config(); + + let mut config = Config::default(); + config.rcc.ls = LsConfig::default_lse(); + + // System Clock seems cannot be greater than 16 MHz + #[cfg(any(feature = "stm32h563zi", feature = "stm32h503rb"))] + { + use embassy_stm32::rcc::HSIPrescaler; + config.rcc.hsi = Some(HSIPrescaler::DIV4); // 64 MHz HSI will need a /4 + } + + let p = init_with_config(config); + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + static RTC: StaticCell = StaticCell::new(); + let rtc = RTC.init(rtc); + + stop_with_rtc(rtc); + + spawner.spawn(task_1()).unwrap(); + spawner.spawn(task_2()).unwrap(); +} diff --git a/embassy/tests/stm32/src/bin/timer.rs b/embassy/tests/stm32/src/bin/timer.rs new file mode 100644 index 0000000..8719e76 --- /dev/null +++ b/embassy/tests/stm32/src/bin/timer.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use common::*; +use defmt::assert; +use embassy_executor::Spawner; +use embassy_time::{Instant, Timer}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let _p = init(); + info!("Hello World!"); + + let start = Instant::now(); + Timer::after_millis(100).await; + let end = Instant::now(); + let ms = (end - start).as_millis(); + info!("slept for {} ms", ms); + assert!(ms >= 99); + assert!(ms < 110); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/ucpd.rs b/embassy/tests/stm32/src/bin/ucpd.rs new file mode 100644 index 0000000..bd7b35d --- /dev/null +++ b/embassy/tests/stm32/src/bin/ucpd.rs @@ -0,0 +1,120 @@ +// required-features: ucpd +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use common::*; +use defmt::{assert, assert_eq}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::ucpd::{self, CcPhy, CcPull, CcSel, CcVState, RxError, Ucpd}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; + +bind_interrupts!(struct Irqs { + UCPD1_2 => ucpd::InterruptHandler, ucpd::InterruptHandler; +}); + +static SRC_TO_SNK: [u8; 6] = [0, 1, 2, 3, 4, 5]; +static SNK_TO_SRC: [u8; 4] = [9, 8, 7, 6]; + +async fn wait_for_vstate(cc_phy: &mut CcPhy<'_, T>, vstate: CcVState) { + let (mut cc1, mut _cc2) = cc_phy.vstate(); + while cc1 != vstate { + (cc1, _cc2) = cc_phy.wait_for_vstate_change().await; + } +} + +async fn source( + mut ucpd: Ucpd<'static, peripherals::UCPD1>, + rx_dma: peripherals::DMA1_CH1, + tx_dma: peripherals::DMA1_CH2, +) { + debug!("source: setting default current pull-up"); + ucpd.cc_phy().set_pull(CcPull::SourceDefaultUsb); + + // Wait for default sink. + debug!("source: wait for sink"); + wait_for_vstate(ucpd.cc_phy(), CcVState::LOW).await; + + // Advertise a higher current by changing the pull-up resistor. + debug!("source: sink detected, setting 3.0A current pull-up"); + ucpd.cc_phy().set_pull(CcPull::Source3_0A); + + let (_, mut pd_phy) = ucpd.split_pd_phy(rx_dma, tx_dma, CcSel::CC1); + + // Listen for an incoming message + debug!("source: wait for message from sink"); + let mut snk_to_src_buf = [0_u8; 30]; + let n = unwrap!(pd_phy.receive(snk_to_src_buf.as_mut()).await); + assert_eq!(n, SNK_TO_SRC.len()); + assert_eq!(&snk_to_src_buf[..n], SNK_TO_SRC.as_slice()); + + // Send message + debug!("source: message received, sending message"); + unwrap!(pd_phy.transmit(SRC_TO_SNK.as_slice()).await); + + // Wait for hard-reset + debug!("source: message sent, waiting for hard-reset"); + assert!(matches!( + pd_phy.receive(snk_to_src_buf.as_mut()).await, + Err(RxError::HardReset) + )); +} + +async fn sink( + mut ucpd: Ucpd<'static, peripherals::UCPD2>, + rx_dma: peripherals::DMA1_CH3, + tx_dma: peripherals::DMA1_CH4, +) { + debug!("sink: setting pull down"); + ucpd.cc_phy().set_pull(CcPull::Sink); + + // Wait for default source. + debug!("sink: waiting for default vstate"); + wait_for_vstate(ucpd.cc_phy(), CcVState::LOW).await; + + // Wait higher current pull-up. + //debug!("sink: source default vstate detected, waiting for 3.0A vstate"); + //wait_for_vstate(ucpd.cc_phy(), CcVState::HIGHEST).await; + //debug!("sink: source 3.0A vstate detected"); + // TODO: not working yet, why? no idea, replace with timer for now + Timer::after_millis(100).await; + + let (_, mut pd_phy) = ucpd.split_pd_phy(rx_dma, tx_dma, CcSel::CC1); + + // Send message + debug!("sink: sending message"); + unwrap!(pd_phy.transmit(SNK_TO_SRC.as_slice()).await); + + // Listen for an incoming message + debug!("sink: message sent, waiting for message from source"); + let mut src_to_snk_buf = [0_u8; 30]; + let n = unwrap!(pd_phy.receive(src_to_snk_buf.as_mut()).await); + assert_eq!(n, SRC_TO_SNK.len()); + assert_eq!(&src_to_snk_buf[..n], SRC_TO_SNK.as_slice()); + + // Send hard reset + debug!("sink: message received, sending hard-reset"); + unwrap!(pd_phy.transmit_hardreset().await); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = init(); + info!("Hello World!"); + + // Wire between PD0 and PA8 + let ucpd1 = Ucpd::new(p.UCPD1, Irqs {}, p.PA8, p.PB15, Default::default()); + let ucpd2 = Ucpd::new(p.UCPD2, Irqs {}, p.PD0, p.PD2, Default::default()); + + join( + source(ucpd1, p.DMA1_CH1, p.DMA1_CH2), + sink(ucpd2, p.DMA1_CH3, p.DMA1_CH4), + ) + .await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/usart.rs b/embassy/tests/stm32/src/bin/usart.rs new file mode 100644 index 0000000..2f601ad --- /dev/null +++ b/embassy/tests/stm32/src/bin/usart.rs @@ -0,0 +1,115 @@ +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use common::*; +use defmt::{assert, assert_eq, unreachable}; +use embassy_executor::Spawner; +use embassy_stm32::usart::{Config, ConfigError, Error, Uart}; +use embassy_time::{block_for, Duration, Instant}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = init(); + info!("Hello World!"); + + // Arduino pins D0 and D1 + // They're connected together with a 1K resistor. + let mut usart = peri!(p, UART); + let mut rx = peri!(p, UART_RX); + let mut tx = peri!(p, UART_TX); + + { + let config = Config::default(); + let mut usart = Uart::new_blocking(&mut usart, &mut rx, &mut tx, config).unwrap(); + + // We can't send too many bytes, they have to fit in the FIFO. + // This is because we aren't sending+receiving at the same time. + + let data = [0xC0, 0xDE]; + usart.blocking_write(&data).unwrap(); + + let mut buf = [0; 2]; + usart.blocking_read(&mut buf).unwrap(); + assert_eq!(buf, data); + + // Test flush doesn't hang. + usart.blocking_write(&data).unwrap(); + usart.blocking_flush().unwrap(); + + // Test flush doesn't hang if there's nothing to flush + usart.blocking_flush().unwrap(); + } + + // Test error handling with with an overflow error + { + let config = Config::default(); + let mut usart = Uart::new_blocking(&mut usart, &mut rx, &mut tx, config).unwrap(); + + // Send enough bytes to fill the RX FIFOs off all USART versions. + let data = [0; 64]; + usart.blocking_write(&data).unwrap(); + usart.blocking_flush().unwrap(); + + // USART can still take up to 1 bit time (?) to receive the last byte + // that we just flushed, so wait a bit. + // otherwise, we might clear the overrun flag from an *earlier* byte and + // it gets set again when receiving the last byte is done. + block_for(Duration::from_millis(1)); + + // The error should be reported first. + let mut buf = [0; 1]; + let err = usart.blocking_read(&mut buf); + assert_eq!(err, Err(Error::Overrun)); + + // At least the first data byte should still be available on all USART versions. + usart.blocking_read(&mut buf).unwrap(); + assert_eq!(buf[0], data[0]); + } + + // Test that baudrate divider is calculated correctly. + // Do it by comparing the time it takes to send a known number of bytes. + for baudrate in [300, 9600, 115200, 250_000, 337_934, 1_000_000, 2_000_000] { + info!("testing baudrate {}", baudrate); + + let mut config = Config::default(); + config.baudrate = baudrate; + let mut usart = match Uart::new_blocking(&mut usart, &mut rx, &mut tx, config) { + Ok(x) => x, + Err(ConfigError::BaudrateTooHigh) => { + info!("baudrate too high"); + assert!(baudrate >= 1_000_000); + continue; + } + Err(ConfigError::BaudrateTooLow) => { + info!("baudrate too low"); + assert!(baudrate <= 300); + continue; + } + Err(_) => unreachable!(), + }; + + let n = (baudrate as usize / 100).max(64); + + let start = Instant::now(); + for _ in 0..n { + usart.blocking_write(&[0x00]).unwrap(); + } + usart.blocking_flush().unwrap(); + let dur = Instant::now() - start; + let want_dur = Duration::from_micros(n as u64 * 10 * 1_000_000 / (baudrate as u64)); + let fuzz = want_dur / 5; + if dur < want_dur - fuzz || dur > want_dur + fuzz { + defmt::panic!( + "bad duration for baudrate {}: got {:?} want {:?}", + baudrate, + dur, + want_dur + ); + } + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/usart_dma.rs b/embassy/tests/stm32/src/bin/usart_dma.rs new file mode 100644 index 0000000..a344983 --- /dev/null +++ b/embassy/tests/stm32/src/bin/usart_dma.rs @@ -0,0 +1,73 @@ +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use common::*; +use defmt::assert_eq; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usart::{Config, Uart}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = init(); + info!("Hello World!"); + + // Arduino pins D0 and D1 + // They're connected together with a 1K resistor. + let usart = peri!(p, UART); + let rx = peri!(p, UART_RX); + let tx = peri!(p, UART_TX); + let rx_dma = peri!(p, UART_RX_DMA); + let tx_dma = peri!(p, UART_TX_DMA); + let irq = irqs!(UART); + + let config = Config::default(); + let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config).unwrap(); + + const LEN: usize = 128; + let mut tx_buf = [0; LEN]; + let mut rx_buf = [0; LEN]; + + let (mut tx, mut rx) = usart.split(); + + for n in 0..42 { + for i in 0..LEN { + tx_buf[i] = (i ^ n) as u8; + } + + let tx_fut = async { + tx.write(&tx_buf).await.unwrap(); + }; + let rx_fut = async { + rx.read(&mut rx_buf).await.unwrap(); + }; + + // note: rx needs to be polled first, to workaround this bug: + // https://github.com/embassy-rs/embassy/issues/1426 + join(rx_fut, tx_fut).await; + + assert_eq!(tx_buf, rx_buf); + } + + // Test flush doesn't hang. Check multiple combinations of async+blocking. + tx.write(&tx_buf).await.unwrap(); + tx.flush().await.unwrap(); + tx.flush().await.unwrap(); + + tx.write(&tx_buf).await.unwrap(); + tx.blocking_flush().unwrap(); + tx.flush().await.unwrap(); + + tx.blocking_write(&tx_buf).unwrap(); + tx.blocking_flush().unwrap(); + tx.flush().await.unwrap(); + + tx.blocking_write(&tx_buf).unwrap(); + tx.flush().await.unwrap(); + tx.blocking_flush().unwrap(); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/bin/usart_rx_ringbuffered.rs b/embassy/tests/stm32/src/bin/usart_rx_ringbuffered.rs new file mode 100644 index 0000000..83c0887 --- /dev/null +++ b/embassy/tests/stm32/src/bin/usart_rx_ringbuffered.rs @@ -0,0 +1,110 @@ +// required-features: not-gpdma + +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use common::*; +use defmt::{assert_eq, panic}; +use embassy_executor::Spawner; +use embassy_stm32::mode::Async; +use embassy_stm32::usart::{Config, DataBits, Parity, RingBufferedUartRx, StopBits, Uart, UartTx}; +use embassy_time::Timer; +use rand_chacha::ChaCha8Rng; +use rand_core::{RngCore, SeedableRng}; + +const DMA_BUF_SIZE: usize = 256; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = init(); + info!("Hello World!"); + + // Arduino pins D0 and D1 + // They're connected together with a 1K resistor. + let usart = peri!(p, UART); + let rx = peri!(p, UART_RX); + let tx = peri!(p, UART_TX); + let rx_dma = peri!(p, UART_RX_DMA); + let tx_dma = peri!(p, UART_TX_DMA); + let irq = irqs!(UART); + + // To run this test, use the saturating_serial test utility to saturate the serial port + + let mut config = Config::default(); + // this is the fastest we can go without tuning RCC + // some chips have default pclk=8mhz, and uart can run at max pclk/16 + config.baudrate = 500_000; + config.data_bits = DataBits::DataBits8; + config.stop_bits = StopBits::STOP1; + config.parity = Parity::ParityNone; + + let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config).unwrap(); + let (tx, rx) = usart.split(); + static mut DMA_BUF: [u8; DMA_BUF_SIZE] = [0; DMA_BUF_SIZE]; + let rx = rx.into_ring_buffered(unsafe { &mut *core::ptr::addr_of_mut!(DMA_BUF) }); + + info!("Spawning tasks"); + spawner.spawn(transmit_task(tx)).unwrap(); + spawner.spawn(receive_task(rx)).unwrap(); +} + +#[embassy_executor::task] +async fn transmit_task(mut tx: UartTx<'static, Async>) { + // workaround https://github.com/embassy-rs/embassy/issues/1426 + Timer::after_millis(100).await; + + let mut rng = ChaCha8Rng::seed_from_u64(1337); + + info!("Starting random transmissions into void..."); + + let mut i: u8 = 0; + loop { + let mut buf = [0; 256]; + let len = 1 + (rng.next_u32() as usize % buf.len()); + for b in &mut buf[..len] { + *b = i; + i = i.wrapping_add(1); + } + + tx.write(&buf[..len]).await.unwrap(); + Timer::after_micros((rng.next_u32() % 1000) as _).await; + } +} + +#[embassy_executor::task] +async fn receive_task(mut rx: RingBufferedUartRx<'static>) { + info!("Ready to receive..."); + + let mut rng = ChaCha8Rng::seed_from_u64(1337); + + let mut i = 0; + let mut expected = 0; + loop { + let mut buf = [0; 256]; + let max_len = 1 + (rng.next_u32() as usize % buf.len()); + let received = match rx.read(&mut buf[..max_len]).await { + Ok(r) => r, + Err(e) => { + panic!("Test fail! read error: {:?}", e); + } + }; + + for byte in &buf[..received] { + assert_eq!(*byte, expected); + expected = expected.wrapping_add(1); + } + + if received < max_len { + Timer::after_micros((rng.next_u32() % 1000) as _).await; + } + + i += received; + + if i > 100000 { + info!("Test OK!"); + cortex_m::asm::bkpt(); + } + } +} diff --git a/embassy/tests/stm32/src/bin/wpan_ble.rs b/embassy/tests/stm32/src/bin/wpan_ble.rs new file mode 100644 index 0000000..fde1dfa --- /dev/null +++ b/embassy/tests/stm32/src/bin/wpan_ble.rs @@ -0,0 +1,252 @@ +// required-features: ble + +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use core::time::Duration; + +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32_wpan::hci::host::uart::UartHci; +use embassy_stm32_wpan::hci::host::{AdvertisingFilterPolicy, EncryptionKey, HostHci, OwnAddressType}; +use embassy_stm32_wpan::hci::types::AdvertisingType; +use embassy_stm32_wpan::hci::vendor::command::gap::{AdvertisingDataType, DiscoverableParameters, GapCommands, Role}; +use embassy_stm32_wpan::hci::vendor::command::gatt::GattCommands; +use embassy_stm32_wpan::hci::vendor::command::hal::{ConfigData, HalCommands, PowerLevel}; +use embassy_stm32_wpan::hci::BdAddr; +use embassy_stm32_wpan::lhci::LhciC1DeviceInformationCcrp; +use embassy_stm32_wpan::sub::mm; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +const BLE_GAP_DEVICE_NAME_LENGTH: u8 = 7; + +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let mut config = config(); + config.rcc = WPAN_DEFAULT; + + let p = init_with_config(config); + info!("Hello World!"); + + let config = Config::default(); + let mut mbox = TlMbox::init(p.IPCC, Irqs, config); + + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + let fw_info = mbox.sys_subsystem.wireless_fw_info().unwrap(); + let version_major = fw_info.version_major(); + let version_minor = fw_info.version_minor(); + let subversion = fw_info.subversion(); + + let sram2a_size = fw_info.sram2a_size(); + let sram2b_size = fw_info.sram2b_size(); + + info!( + "version {}.{}.{} - SRAM2a {} - SRAM2b {}", + version_major, version_minor, subversion, sram2a_size, sram2b_size + ); + + let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await; + + info!("resetting BLE..."); + mbox.ble_subsystem.reset().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config public address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::public_address(get_bd_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config random address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::random_address(get_random_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config identity root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::identity_root(&get_irk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config encryption root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::encryption_root(&get_erk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config tx power level..."); + mbox.ble_subsystem.set_tx_power_level(PowerLevel::ZerodBm).await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("GATT init..."); + mbox.ble_subsystem.init_gatt().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("GAP init..."); + mbox.ble_subsystem + .init_gap(Role::PERIPHERAL, false, BLE_GAP_DEVICE_NAME_LENGTH) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + // info!("set scan response..."); + // mbox.ble_subsystem.le_set_scan_response_data(&[]).await.unwrap(); + // let response = mbox.ble_subsystem.read().await.unwrap(); + // info!("{}", response); + + info!("set discoverable..."); + mbox.ble_subsystem + .set_discoverable(&DiscoverableParameters { + advertising_type: AdvertisingType::NonConnectableUndirected, + advertising_interval: Some((Duration::from_millis(250), Duration::from_millis(250))), + address_type: OwnAddressType::Public, + filter_policy: AdvertisingFilterPolicy::AllowConnectionAndScan, + local_name: None, + advertising_data: &[], + conn_interval: (None, None), + }) + .await + .unwrap(); + + let response = mbox.ble_subsystem.read().await; + info!("{}", response); + + // remove some advertisement to decrease the packet size + info!("delete tx power ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::TxPowerLevel) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("delete conn interval ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::PeripheralConnectionInterval) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("update advertising data..."); + mbox.ble_subsystem + .update_advertising_data(&eddystone_advertising_data()) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("update advertising data type..."); + mbox.ble_subsystem + .update_advertising_data(&[3, AdvertisingDataType::UuidCompleteList16 as u8, 0xaa, 0xfe]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("update advertising data flags..."); + mbox.ble_subsystem + .update_advertising_data(&[ + 2, + AdvertisingDataType::Flags as u8, + (0x02 | 0x04) as u8, // BLE general discoverable, without BR/EDR support + ]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn get_bd_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = lhci_info.device_type_id; + bytes[4] = (lhci_info.st_company_id & 0xff) as u8; + bytes[5] = (lhci_info.st_company_id >> 8 & 0xff) as u8; + + BdAddr(bytes) +} + +fn get_random_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = 0; + bytes[4] = 0x6E; + bytes[5] = 0xED; + + BdAddr(bytes) +} + +const BLE_CFG_IRK: [u8; 16] = [ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, +]; +const BLE_CFG_ERK: [u8; 16] = [ + 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, +]; + +fn get_irk() -> EncryptionKey { + EncryptionKey(BLE_CFG_IRK) +} + +fn get_erk() -> EncryptionKey { + EncryptionKey(BLE_CFG_ERK) +} + +fn eddystone_advertising_data() -> [u8; 24] { + const EDDYSTONE_URL: &[u8] = b"www.rust-lang.com"; + + let mut service_data = [0u8; 24]; + let url_len = EDDYSTONE_URL.len(); + + service_data[0] = 6 + url_len as u8; + service_data[1] = AdvertisingDataType::ServiceData as u8; + + // 16-bit eddystone uuid + service_data[2] = 0xaa; + service_data[3] = 0xFE; + + service_data[4] = 0x10; // URL frame type + service_data[5] = 22_i8 as u8; // calibrated TX power at 0m + service_data[6] = 0x03; // eddystone url prefix = https + + service_data[7..(7 + url_len)].copy_from_slice(EDDYSTONE_URL); + + service_data +} diff --git a/embassy/tests/stm32/src/bin/wpan_mac.rs b/embassy/tests/stm32/src/bin/wpan_mac.rs new file mode 100644 index 0000000..b65ace4 --- /dev/null +++ b/embassy/tests/stm32/src/bin/wpan_mac.rs @@ -0,0 +1,127 @@ +// required-features: mac + +#![no_std] +#![no_main] +#[path = "../common.rs"] +mod common; + +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32_wpan::mac::commands::{AssociateRequest, GetRequest, ResetRequest, SetRequest}; +use embassy_stm32_wpan::mac::event::MacEvent; +use embassy_stm32_wpan::mac::typedefs::{ + AddressMode, Capabilities, KeyIdMode, MacAddress, MacChannel, PanId, PibId, SecurityLevel, +}; +use embassy_stm32_wpan::sub::mm; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let mut config = config(); + config.rcc = WPAN_DEFAULT; + + let p = init_with_config(config); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + core::mem::drop(sys_event); + + let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await; + info!("initialized mac: {}", result); + + info!("resetting"); + mbox.mac_subsystem + .send_command(&ResetRequest { + set_default_pib: true, + ..Default::default() + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await.unwrap(); + info!("{:#x}", evt); + } + + info!("setting extended address"); + let extended_address: u64 = 0xACDE480000000002; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &extended_address as *const _ as *const u8, + pib_attribute: PibId::ExtendedAddress, + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await.unwrap(); + info!("{:#x}", evt); + } + + info!("getting extended address"); + mbox.mac_subsystem + .send_command(&GetRequest { + pib_attribute: PibId::ExtendedAddress, + ..Default::default() + }) + .await + .unwrap(); + + { + let evt = mbox.mac_subsystem.read().await.unwrap(); + info!("{:#x}", evt); + + if let MacEvent::MlmeGetCnf(evt) = evt { + if evt.pib_attribute_value_len == 8 { + let value = unsafe { core::ptr::read_unaligned(evt.pib_attribute_value_ptr as *const u64) }; + + info!("value {:#x}", value) + } + } + } + + info!("assocation request"); + let a = AssociateRequest { + channel_number: MacChannel::Channel16, + channel_page: 0, + coord_addr_mode: AddressMode::Short, + coord_address: MacAddress { short: [34, 17] }, + capability_information: Capabilities::ALLOCATE_ADDRESS, + coord_pan_id: PanId([0x1A, 0xAA]), + security_level: SecurityLevel::Unsecure, + key_id_mode: KeyIdMode::Implicite, + key_source: [0; 8], + key_index: 152, + }; + info!("{}", a); + mbox.mac_subsystem.send_command(&a).await.unwrap(); + let short_addr = if let MacEvent::MlmeAssociateCnf(conf) = mbox.mac_subsystem.read().await.unwrap() { + conf.assoc_short_address + } else { + defmt::panic!() + }; + + info!("{}", short_addr); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/embassy/tests/stm32/src/common.rs b/embassy/tests/stm32/src/common.rs new file mode 100644 index 0000000..935a41e --- /dev/null +++ b/embassy/tests/stm32/src/common.rs @@ -0,0 +1,719 @@ +#![macro_use] + +pub use defmt::*; +#[allow(unused)] +use embassy_stm32::rcc::*; +#[allow(unused)] +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; +use {defmt_rtt as _, panic_probe as _}; + +#[cfg(feature = "stm32f103c8")] +teleprobe_meta::target!(b"bluepill-stm32f103c8"); +#[cfg(feature = "stm32g491re")] +teleprobe_meta::target!(b"nucleo-stm32g491re"); +#[cfg(feature = "stm32g071rb")] +teleprobe_meta::target!(b"nucleo-stm32g071rb"); +#[cfg(feature = "stm32f429zi")] +teleprobe_meta::target!(b"nucleo-stm32f429zi"); +#[cfg(feature = "stm32f446re")] +teleprobe_meta::target!(b"weact-stm32f446re"); +#[cfg(feature = "stm32wb55rg")] +teleprobe_meta::target!(b"nucleo-stm32wb55rg"); +#[cfg(feature = "stm32h755zi")] +teleprobe_meta::target!(b"nucleo-stm32h755zi"); +#[cfg(feature = "stm32h753zi")] +teleprobe_meta::target!(b"nucleo-stm32h753zi"); +#[cfg(feature = "stm32h7a3zi")] +teleprobe_meta::target!(b"nucleo-stm32h7a3zi"); +#[cfg(feature = "stm32u585ai")] +teleprobe_meta::target!(b"iot-stm32u585ai"); +#[cfg(feature = "stm32u5a5zj")] +teleprobe_meta::target!(b"nucleo-stm32u5a5zj"); +#[cfg(feature = "stm32h563zi")] +teleprobe_meta::target!(b"nucleo-stm32h563zi"); +#[cfg(feature = "stm32c031c6")] +teleprobe_meta::target!(b"nucleo-stm32c031c6"); +#[cfg(feature = "stm32l073rz")] +teleprobe_meta::target!(b"nucleo-stm32l073rz"); +#[cfg(feature = "stm32l152re")] +teleprobe_meta::target!(b"nucleo-stm32l152re"); +#[cfg(feature = "stm32l4a6zg")] +teleprobe_meta::target!(b"nucleo-stm32l4a6zg"); +#[cfg(feature = "stm32l4r5zi")] +teleprobe_meta::target!(b"nucleo-stm32l4r5zi"); +#[cfg(feature = "stm32l552ze")] +teleprobe_meta::target!(b"nucleo-stm32l552ze"); +#[cfg(feature = "stm32f767zi")] +teleprobe_meta::target!(b"nucleo-stm32f767zi"); +#[cfg(feature = "stm32f207zg")] +teleprobe_meta::target!(b"nucleo-stm32f207zg"); +#[cfg(feature = "stm32f303ze")] +teleprobe_meta::target!(b"nucleo-stm32f303ze"); +#[cfg(feature = "stm32l496zg")] +teleprobe_meta::target!(b"nucleo-stm32l496zg"); +#[cfg(feature = "stm32wl55jc")] +teleprobe_meta::target!(b"nucleo-stm32wl55jc"); +#[cfg(feature = "stm32wba52cg")] +teleprobe_meta::target!(b"nucleo-stm32wba52cg"); +#[cfg(feature = "stm32f091rc")] +teleprobe_meta::target!(b"nucleo-stm32f091rc"); +#[cfg(feature = "stm32h503rb")] +teleprobe_meta::target!(b"nucleo-stm32h503rb"); +#[cfg(feature = "stm32h7s3l8")] +teleprobe_meta::target!(b"nucleo-stm32h7s3l8"); +#[cfg(feature = "stm32u083rc")] +teleprobe_meta::target!(b"nucleo-stm32u083rc"); + +macro_rules! define_peris { + ($($name:ident = $peri:ident,)* $(@irq $irq_name:ident = $irq_code:tt,)*) => { + #[allow(unused_macros)] + macro_rules! peri { + $( + ($p:expr, $name) => { + $p.$peri + }; + )* + } + #[allow(unused_macros)] + macro_rules! irqs { + $( + ($irq_name) => {{ + embassy_stm32::bind_interrupts!(struct Irqs $irq_code); + Irqs + }}; + )* + } + + #[allow(unused)] + #[allow(non_camel_case_types)] + pub mod peris { + $( + pub type $name = embassy_stm32::peripherals::$peri; + )* + } + }; +} + +#[cfg(feature = "stm32f091rc")] +define_peris!( + UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA1_CH4, UART_RX_DMA = DMA1_CH5, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32f103c8")] +define_peris!( + UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA1_CH4, UART_RX_DMA = DMA1_CH5, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32g491re")] +define_peris!( + UART = USART1, UART_TX = PC4, UART_RX = PC5, UART_TX_DMA = DMA1_CH1, UART_RX_DMA = DMA1_CH2, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH1, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32g071rb")] +define_peris!( + UART = USART1, UART_TX = PC4, UART_RX = PC5, UART_TX_DMA = DMA1_CH1, UART_RX_DMA = DMA1_CH2, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH1, SPI_RX_DMA = DMA1_CH2, + ADC = ADC1, DAC = DAC1, DAC_PIN = PA4, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32f429zi")] +define_peris!( + UART = USART6, UART_TX = PG14, UART_RX = PG9, UART_TX_DMA = DMA2_CH6, UART_RX_DMA = DMA2_CH1, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA2_CH3, SPI_RX_DMA = DMA2_CH2, + ADC = ADC1, DAC = DAC1, DAC_PIN = PA4, + CAN = CAN1, CAN_RX = PD0, CAN_TX = PD1, + @irq UART = {USART6 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32f446re")] +define_peris!( + UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA2_CH7, UART_RX_DMA = DMA2_CH5, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA2_CH3, SPI_RX_DMA = DMA2_CH2, + ADC = ADC1, DAC = DAC1, DAC_PIN = PA4, + CAN = CAN1, CAN_RX = PA11, CAN_TX = PA12, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32wb55rg")] +define_peris!( + UART = LPUART1, UART_TX = PA2, UART_RX = PA3, UART_TX_DMA = DMA1_CH1, UART_RX_DMA = DMA1_CH2, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH1, SPI_RX_DMA = DMA1_CH2, + @irq UART = {LPUART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(any(feature = "stm32h755zi", feature = "stm32h753zi"))] +define_peris!( + CRYP_IN_DMA = DMA1_CH0, CRYP_OUT_DMA = DMA1_CH1, + UART = USART1, UART_TX = PB6, UART_RX = PB7, UART_TX_DMA = DMA1_CH0, UART_RX_DMA = DMA1_CH1, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PB5, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH0, SPI_RX_DMA = DMA1_CH1, + ADC = ADC1, DAC = DAC1, DAC_PIN = PA4, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32h7a3zi")] +define_peris!( + UART = USART1, UART_TX = PB6, UART_RX = PB7, UART_TX_DMA = DMA1_CH0, UART_RX_DMA = DMA1_CH1, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH0, SPI_RX_DMA = DMA1_CH1, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32u585ai")] +define_peris!( + UART = USART3, UART_TX = PD8, UART_RX = PD9, UART_TX_DMA = GPDMA1_CH0, UART_RX_DMA = GPDMA1_CH1, + SPI = SPI1, SPI_SCK = PE13, SPI_MOSI = PE15, SPI_MISO = PE14, SPI_TX_DMA = GPDMA1_CH0, SPI_RX_DMA = GPDMA1_CH1, + @irq UART = {USART3 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32u5a5zj")] +define_peris!( + UART = LPUART1, UART_TX = PG7, UART_RX = PG8, UART_TX_DMA = GPDMA1_CH0, UART_RX_DMA = GPDMA1_CH1, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = GPDMA1_CH0, SPI_RX_DMA = GPDMA1_CH1, + @irq UART = {LPUART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32h563zi")] +define_peris!( + UART = LPUART1, UART_TX = PB6, UART_RX = PB7, UART_TX_DMA = GPDMA1_CH0, UART_RX_DMA = GPDMA1_CH1, + SPI = SPI4, SPI_SCK = PE12, SPI_MOSI = PE14, SPI_MISO = PE13, SPI_TX_DMA = GPDMA1_CH0, SPI_RX_DMA = GPDMA1_CH1, + @irq UART = {LPUART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32h503rb")] +define_peris!( + UART = USART1, UART_TX = PB14, UART_RX = PB15, UART_TX_DMA = GPDMA1_CH0, UART_RX_DMA = GPDMA1_CH1, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = GPDMA1_CH0, SPI_RX_DMA = GPDMA1_CH1, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32c031c6")] +define_peris!( + UART = USART1, UART_TX = PB6, UART_RX = PB7, UART_TX_DMA = DMA1_CH1, UART_RX_DMA = DMA1_CH2, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH1, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32l496zg")] +define_peris!( + UART = USART3, UART_TX = PD8, UART_RX = PD9, UART_TX_DMA = DMA1_CH2, UART_RX_DMA = DMA1_CH3, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART3 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32l4a6zg")] +define_peris!( + UART = USART3, UART_TX = PD8, UART_RX = PD9, UART_TX_DMA = DMA1_CH2, UART_RX_DMA = DMA1_CH3, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART3 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32l4r5zi")] +define_peris!( + UART = USART3, UART_TX = PD8, UART_RX = PD9, UART_TX_DMA = DMA1_CH1, UART_RX_DMA = DMA1_CH2, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH1, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART3 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32l073rz")] +define_peris!( + UART = USART4, UART_TX = PA0, UART_RX = PA1, UART_TX_DMA = DMA1_CH3, UART_RX_DMA = DMA1_CH2, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART4_5 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32l152re")] +define_peris!( + UART = USART3, UART_TX = PB10, UART_RX = PB11, UART_TX_DMA = DMA1_CH2, UART_RX_DMA = DMA1_CH3, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, + ADC = ADC1, DAC = DAC1, DAC_PIN = PA4, + @irq UART = {USART3 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32l552ze")] +define_peris!( + UART = USART3, UART_TX = PD8, UART_RX = PD9, UART_TX_DMA = DMA1_CH1, UART_RX_DMA = DMA1_CH2, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH1, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART3 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32f767zi")] +define_peris!( + UART = USART6, UART_TX = PG14, UART_RX = PG9, UART_TX_DMA = DMA2_CH6, UART_RX_DMA = DMA2_CH1, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA2_CH3, SPI_RX_DMA = DMA2_CH2, + @irq UART = {USART6 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32f207zg")] +define_peris!( + UART = USART6, UART_TX = PG14, UART_RX = PG9, UART_TX_DMA = DMA2_CH6, UART_RX_DMA = DMA2_CH1, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA2_CH3, SPI_RX_DMA = DMA2_CH2, + @irq UART = {USART6 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32f303ze")] +define_peris!( + UART = USART1, UART_TX = PC4, UART_RX = PC5, UART_TX_DMA = DMA1_CH4, UART_RX_DMA = DMA1_CH5, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32wl55jc")] +define_peris!( + UART = USART1, UART_TX = PB6, UART_RX = PB7, UART_TX_DMA = DMA1_CH4, UART_RX_DMA = DMA1_CH5, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32wba52cg")] +define_peris!( + UART = LPUART1, UART_TX = PB5, UART_RX = PA10, UART_TX_DMA = GPDMA1_CH0, UART_RX_DMA = GPDMA1_CH1, + SPI = SPI1, SPI_SCK = PB4, SPI_MOSI = PA15, SPI_MISO = PB3, SPI_TX_DMA = GPDMA1_CH0, SPI_RX_DMA = GPDMA1_CH1, + @irq UART = {LPUART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32h7s3l8")] +define_peris!( + CRYP_IN_DMA = GPDMA1_CH0, CRYP_OUT_DMA = GPDMA1_CH1, + UART = USART1, UART_TX = PB14, UART_RX = PA10, UART_TX_DMA = GPDMA1_CH0, UART_RX_DMA = GPDMA1_CH1, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PB5, SPI_MISO = PA6, SPI_TX_DMA = GPDMA1_CH0, SPI_RX_DMA = GPDMA1_CH1, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); +#[cfg(feature = "stm32u083rc")] +define_peris!( + UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA1_CH1, UART_RX_DMA = DMA1_CH2, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH1, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, +); + +pub fn config() -> Config { + #[allow(unused_mut)] + let mut config = Config::default(); + + #[cfg(feature = "stm32c031c6")] + { + config.rcc.hsi = Some(Hsi { + sys_div: HsiSysDiv::DIV1, // 48Mhz + ker_div: HsiKerDiv::DIV3, // 16Mhz + }); + config.rcc.sys = Sysclk::HSISYS; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV1; + } + + #[cfg(feature = "stm32g071rb")] + { + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL16, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV4), // 16 / 1 * 16 / 4 = 64 Mhz + }); + config.rcc.sys = Sysclk::PLL1_R; + } + #[cfg(feature = "stm32wb55rg")] + { + config.rcc = embassy_stm32::rcc::WPAN_DEFAULT; + } + + #[cfg(feature = "stm32f091rc")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL6, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV1; + } + #[cfg(feature = "stm32f103c8")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + } + #[cfg(feature = "stm32f207zg")] + { + // By default, HSE on the board comes from a 8 MHz clock signal (not a crystal) + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + // PLL uses HSE as the clock source + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + // 8 MHz clock source / 8 = 1 MHz PLL input + prediv: unwrap!(PllPreDiv::try_from(8)), + // 1 MHz PLL input * 240 = 240 MHz PLL VCO + mul: unwrap!(PllMul::try_from(240)), + // 240 MHz PLL VCO / 2 = 120 MHz main PLL output + divp: Some(PllPDiv::DIV2), + // 240 MHz PLL VCO / 5 = 48 MHz PLL48 output + divq: Some(PllQDiv::DIV5), + divr: None, + }); + // System clock comes from PLL (= the 120 MHz main PLL output) + config.rcc.sys = Sysclk::PLL1_P; + // 120 MHz / 4 = 30 MHz APB1 frequency + config.rcc.apb1_pre = APBPrescaler::DIV4; + // 120 MHz / 2 = 60 MHz APB2 frequency + config.rcc.apb2_pre = APBPrescaler::DIV2; + } + + #[cfg(feature = "stm32f303ze")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + } + + #[cfg(feature = "stm32f429zi")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL180, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 180 / 2 = 180Mhz. + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + + #[cfg(feature = "stm32f446re")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL168, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 168 / 2 = 168 Mhz. + divq: Some(PllQDiv::DIV7), // 8mhz / 4 * 168 / 7 = 48 Mhz. + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + + #[cfg(feature = "stm32f767zi")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL216, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 216 / 2 = 216Mhz. + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + + #[cfg(feature = "stm32h563zi")] + { + config.rcc.hsi = None; + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::BypassDigital, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL125, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV1; + config.rcc.apb2_pre = APBPrescaler::DIV1; + config.rcc.apb3_pre = APBPrescaler::DIV1; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.voltage_scale = VoltageScale::Scale0; + } + + #[cfg(feature = "stm32h503rb")] + { + config.rcc.hsi = None; + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV6, + mul: PllMul::MUL125, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV1; + config.rcc.apb2_pre = APBPrescaler::DIV1; + config.rcc.apb3_pre = APBPrescaler::DIV1; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.voltage_scale = VoltageScale::Scale0; + } + + #[cfg(feature = "stm32g491re")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV6, + mul: PllMul::MUL85, + divp: None, + divq: Some(PllQDiv::DIV8), // 42.5 Mhz for fdcan. + divr: Some(PllRDiv::DIV2), // Main system clock at 170 MHz + }); + config.rcc.mux.fdcansel = mux::Fdcansel::PLL1_Q; + config.rcc.sys = Sysclk::PLL1_R; + } + + #[cfg(any(feature = "stm32h755zi", feature = "stm32h753zi"))] + { + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // SPI1 cksel defaults to pll1_q + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV8), // 100mhz + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; + #[cfg(any(feature = "stm32h755zi"))] + { + config.rcc.supply_config = SupplyConfig::DirectSMPS; + } + } + + #[cfg(any(feature = "stm32h7a3zi"))] + { + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL35, + divp: Some(PllDiv::DIV2), // 280 Mhz + divq: Some(PllDiv::DIV8), // SPI1 cksel defaults to pll1_q + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL35, + divp: Some(PllDiv::DIV8), // 70 Mhz + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 280 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV1; // 280 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 140 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 140 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 140 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 140 Mhz + config.rcc.voltage_scale = VoltageScale::Scale0; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; + } + + #[cfg(any(feature = "stm32l496zg", feature = "stm32l4a6zg", feature = "stm32l4r5zi"))] + { + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL18, + divp: None, + divq: Some(PllQDiv::DIV6), // 48Mhz (16 / 1 * 18 / 6) + divr: Some(PllRDiv::DIV4), // sysclk 72Mhz clock (16 / 1 * 18 / 4) + }); + } + + #[cfg(feature = "stm32wl55jc")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(32_000_000), + mode: HseMode::Bypass, + prescaler: HsePrescaler::DIV1, + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL6, + divp: None, + divq: Some(PllQDiv::DIV2), // PLL1_Q clock (32 / 2 * 6 / 2), used for RNG + divr: Some(PllRDiv::DIV2), // sysclk 48Mhz clock (32 / 2 * 6 / 2) + }); + } + + #[cfg(any(feature = "stm32l552ze"))] + { + config.rcc.hsi = true; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + // 110Mhz clock (16 / 4 * 55 / 2) + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL55, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), + }); + } + + #[cfg(any(feature = "stm32u585ai", feature = "stm32u5a5zj"))] + { + config.rcc.hsi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, // 16 MHz + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllDiv::DIV1), // 160 MHz + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.voltage_range = VoltageScale::RANGE1; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + } + + #[cfg(feature = "stm32wba52cg")] + { + config.rcc.sys = Sysclk::HSI; + config.rcc.mux.rngsel = mux::Rngsel::HSI; + } + + #[cfg(feature = "stm32l073rz")] + { + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + mul: PllMul::MUL4, + div: PllDiv::DIV2, // 32Mhz clock (16 * 4 / 2) + }); + config.rcc.sys = Sysclk::PLL1_R; + } + + #[cfg(any(feature = "stm32l152re"))] + { + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + mul: PllMul::MUL4, + div: PllDiv::DIV2, // 32Mhz clock (16 * 4 / 2) + }); + config.rcc.sys = Sysclk::PLL1_R; + } + #[cfg(any(feature = "stm32h7s3l8"))] + { + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV3, + mul: PllMul::MUL150, + divp: Some(PllDiv::DIV2), // 600Mhz + divq: Some(PllDiv::DIV25), // 48Mhz + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 600 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 300 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.apb5_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.voltage_scale = VoltageScale::HIGH; + config.rcc.mux.spi1sel = mux::Spi123sel::PLL1_Q; + } + #[cfg(any(feature = "stm32u083rc"))] + { + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, // 16 MHz + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL7, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), // 56 MHz + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; // USB uses ICLK + } + + config +} + +#[allow(unused)] +pub fn init() -> embassy_stm32::Peripherals { + init_with_config(config()) +} + +#[allow(unused)] +pub fn init_with_config(config: Config) -> embassy_stm32::Peripherals { + #[cfg(any(feature = "stm32wl55jc", feature = "stm32h755zi"))] + { + // Not in shared memory, but we're not running the second core, so it's fine + static SHARED_DATA: core::mem::MaybeUninit = core::mem::MaybeUninit::uninit(); + embassy_stm32::init_primary(config, &SHARED_DATA) + } + + #[cfg(not(any(feature = "stm32wl55jc", feature = "stm32h755zi")))] + embassy_stm32::init(config) +} diff --git a/embassy/tests/utils/Cargo.toml b/embassy/tests/utils/Cargo.toml new file mode 100644 index 0000000..7b54a4f --- /dev/null +++ b/embassy/tests/utils/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "test-utils" +version = "0.1.0" +edition = "2021" + +[dependencies] +rand = "0.8" +serial = "0.4" diff --git a/embassy/tests/utils/src/bin/saturate_serial.rs b/embassy/tests/utils/src/bin/saturate_serial.rs new file mode 100644 index 0000000..18ca12f --- /dev/null +++ b/embassy/tests/utils/src/bin/saturate_serial.rs @@ -0,0 +1,53 @@ +use std::path::Path; +use std::time::Duration; +use std::{env, io, process, thread}; + +use rand::random; +use serial::SerialPort; + +pub fn main() { + if let Some(port_name) = env::args().nth(1) { + let idles = env::args().position(|x| x == "--idles").is_some(); + + println!("Saturating port {:?} with 115200 8N1", port_name); + println!("Idles: {}", idles); + println!("Process ID: {}", process::id()); + let mut port = serial::open(&port_name).unwrap(); + if saturate(&mut port, idles).is_err() { + eprintln!("Unable to saturate port"); + } + } else { + let path = env::args().next().unwrap(); + let basepath = Path::new(&path).with_extension(""); + let basename = basepath.file_name().unwrap(); + eprintln!("USAGE: {} ", basename.to_string_lossy()); + } +} + +fn saturate(port: &mut T, idles: bool) -> io::Result<()> { + port.reconfigure(&|settings| { + settings.set_baud_rate(serial::Baud115200)?; + settings.set_char_size(serial::Bits8); + settings.set_parity(serial::ParityNone); + settings.set_stop_bits(serial::Stop1); + Ok(()) + })?; + + let mut written = 0; + loop { + let len = random::() % 0x1000; + let buf: Vec = (written..written + len).map(|x| x as u8).collect(); + + port.write_all(&buf)?; + + if idles { + let micros = (random::() % 1000) as u64; + println!("Sleeping {}us", micros); + port.flush().unwrap(); + thread::sleep(Duration::from_micros(micros)); + } + + written += len; + println!("Written: {}", written); + } +} diff --git a/embedded-nal/.github/workflows/ci.yml b/embedded-nal/.github/workflows/ci.yml new file mode 100644 index 0000000..4ff6c27 --- /dev/null +++ b/embedded-nal/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +on: + push: + branches: [staging, trying, master] + pull_request: + +name: Continuous integration + +jobs: + ci-linux: + runs-on: ubuntu-latest + strategy: + matrix: + # All generated code should be running on stable now + rust: [stable] + + # The default target we're compiling on and for + TARGET: + [x86_64-unknown-linux-gnu, thumbv6m-none-eabi, thumbv7m-none-eabi] + + include: + # Test MSRV + - rust: 1.77.0 + TARGET: x86_64-unknown-linux-gnu + + # Test nightly but don't fail + - rust: nightly + experimental: true + TARGET: x86_64-unknown-linux-gnu + + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.TARGET }} + - uses: actions-rs/cargo@v1 + with: + command: check + args: --target=${{ matrix.TARGET }} + + ci-linux-async: + runs-on: ubuntu-latest + strategy: + matrix: + rust: [stable] + TARGET: + [x86_64-unknown-linux-gnu, thumbv6m-none-eabi, thumbv7m-none-eabi] + + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.TARGET }} + - run: cargo check --target=${{ matrix.TARGET }} + working-directory: embedded-nal-async + - run: cargo test --target=${{ matrix.TARGET }} + if: contains(matrix.TARGET, 'linux') + working-directory: embedded-nal-async diff --git a/embedded-nal/.github/workflows/clippy.yml b/embedded-nal/.github/workflows/clippy.yml new file mode 100644 index 0000000..456f3fa --- /dev/null +++ b/embedded-nal/.github/workflows/clippy.yml @@ -0,0 +1,27 @@ +on: + push: + branches: [ staging, trying, master ] + pull_request: + +name: Clippy check +jobs: + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + clippy_check_async: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + with: + components: clippy + - run: cargo clippy + working-directory: embedded-nal-async diff --git a/embedded-nal/.github/workflows/rustfmt.yml b/embedded-nal/.github/workflows/rustfmt.yml new file mode 100644 index 0000000..e9e07a1 --- /dev/null +++ b/embedded-nal/.github/workflows/rustfmt.yml @@ -0,0 +1,29 @@ +on: + push: + branches: [staging, trying, master] + pull_request: + +name: Code formatting check + +jobs: + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - run: cargo fmt --all -- --check + + fmt-async: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustfmt + - run: cargo fmt --all -- --check + working-directory: embedded-nal-async diff --git a/embedded-nal/.gitignore b/embedded-nal/.gitignore new file mode 100644 index 0000000..45fad16 --- /dev/null +++ b/embedded-nal/.gitignore @@ -0,0 +1,4 @@ +/target +/embedded-nal-async/target +**/*.rs.bk +Cargo.lock diff --git a/embedded-nal/CHANGELOG.md b/embedded-nal/CHANGELOG.md new file mode 100644 index 0000000..41f13bc --- /dev/null +++ b/embedded-nal/CHANGELOG.md @@ -0,0 +1,99 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +No unreleased changes yet + +## [0.9.0] - 2024-09-20 + +- Bump MSRV to 1.77.0 for `ip_in_core`. +- Removed the `no-std-net` and `ip_in_core` features, `ip_in_core` is now the default. + +## [0.8.0] - 2023-11-10 + +- Bump MSRV to 1.60.0 (required for Edition 2021) +- Switch to Edition 2021 +- [breaking] `Dns::get_host_by_address` now uses `&mut [u8]` instead of `heapless::String`. + +## [0.7.0] - 2023-06-21 + +- Add blanket impls of all the traits for mutable references. +- Bump dependency version of `no-std-net` to `v0.6`. +- Bump MSRV to 1.53.0 due to `no-std-net`'s use of or-patterns. +- Added support for `core::net` with the `ip_in_core` feature. +- [breaking] New TCP error enumerations added for identifying TCP-related connection errors +- [breaking] Removed the `TcpClientStack::is_connected` API + +## [0.6.0] - 2021-05-25 + +- Changed self references in dns stack methods to mutable, to follow the network stack implementations. + +## [0.5.0] - 2021-05-20 + +### Changed + +- Bump dependency version of `heapless` to `v0.7.0` to utilize const generics. +- Bump MSRV to 1.51.0 to get `min_const_generics` for `heapless`. + +## [0.4.0] - 2021-03-05 + +### Changed + +- Changed [`Dns`](./src/dns.rs) methods to return `nb::Result<..>` to allow non-blocking implementations. +- Bump dependency version of `heapless` to `v0.6.1` to address security issue of sub-dependency. +- Bump dependency version of `no-std-net` to `v0.5`. +- Bump MSRV to 1.46.0 to get `const-fn` for `no-std-net`. + +## [0.3.0] - 2021-02-15 + +### Added + +- New optional struct [`SharedNal`](./src/stack/share.rs) that can share a single underlying implementation among several users within a thread. + +### Changed + +- Changed the names of `UdpClient`/`TcpClient` to `UdpClientStack`/`TcpClientStack` +- Changed the names of `UdpServer`/`TcpServer` to `UdpFullStack`/`TcpFullStack` +- Changed the method names `Dns::gethostbyname`/`Dns::gethostbyaddr` to `Dns::get_host_by_name`/`Dns::get_host_by_address` +- Changed self references in all network stack methods to mutable, with the intent of handling sharing in a different layer (see [#43](https://github.com/rust-embedded-community/embedded-nal/issues/43)). + +## [0.2.0] - 2020-12-02 + +### Added + +- Added a new `UdpServer` trait with server-specific methods +- Added a new `TcpServer` trait with server-specific methods + +### Changed + +- Changed the `UdpStack::receive` method to return the packet sender address, along with the packet length +- Changed the name of `UdpStack` to `UdpClient` +- Changed name of `TcpStack` to `TcpClient` +- Changed the `TcpCStack::connect()` function to return an `nb::Result` +- Renamed `open()` functions to `socket()` for both stacks +- Renamed `read()` and `write()` functions to `send()` and `receive()` respectively +- Updated `UdpStack::connect()` to modify an existing socket + +### Removed + +- Removed `Mode` enum, implementations should instead use `nb::WouldBlock` + +## [0.1.0] - 2020-08-26 + +Initial release to crates.io. + +[Unreleased]: https://github.com/rust-embedded-community/embedded-nal/compare/v0.9.0...HEAD +[0.9.0]: https://github.com/rust-embedded-community/embedded-nal/compare/v0.8.0...v0.9.0 +[0.8.0]: https://github.com/rust-embedded-community/embedded-nal/compare/v0.7.0...v0.8.0 +[0.7.0]: https://github.com/rust-embedded-community/embedded-nal/compare/v0.6.0...v0.7.0 +[0.6.0]: https://github.com/rust-embedded-community/embedded-nal/compare/v0.5.0...v0.6.0 +[0.5.0]: https://github.com/rust-embedded-community/embedded-nal/compare/v0.4.0...v0.5.0 +[0.4.0]: https://github.com/rust-embedded-community/embedded-nal/compare/v0.3.0...v0.4.0 +[0.3.0]: https://github.com/rust-embedded-community/embedded-nal/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/rust-embedded-community/embedded-nal/compare/v0.1.0...v0.2.0 +[0.1.0]: https://github.com/rust-embedded-community/embedded-nal/releases/tag/v0.1.0 diff --git a/embedded-nal/Cargo.toml b/embedded-nal/Cargo.toml new file mode 100644 index 0000000..b368201 --- /dev/null +++ b/embedded-nal/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "embedded-nal" +version = "0.9.0" +authors = [ + "Jonathan 'theJPster' Pallant ", + "Mathias Koch ", + "Diego Barrios Romero ", + "Ryan Summers ", +] +edition = "2021" +description = "A Network Abstraction Layer (NAL) for Embedded Systems" +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-embedded-community/embedded-nal" +homepage = "https://github.com/rust-embedded-community/embedded-nal" +documentation = "https://docs.rs/embedded-nal" +readme = "README.md" +keywords = ["network"] +categories = ["embedded", "hardware-support", "no-std", "network-programming"] + +[dependencies] +nb = "1" diff --git a/embedded-nal/LICENSE-APACHE b/embedded-nal/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/embedded-nal/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/embedded-nal/LICENSE-MIT b/embedded-nal/LICENSE-MIT new file mode 100644 index 0000000..5a9afed --- /dev/null +++ b/embedded-nal/LICENSE-MIT @@ -0,0 +1,27 @@ +Copyright (c) 2020 Jonathan 'theJPster' Pallant +Copyright (c) 2020 Mathias Koch +Copyright (c) 2020 Diego Barrios Romero + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/embedded-nal/README.md b/embedded-nal/README.md new file mode 100644 index 0000000..0e9630c --- /dev/null +++ b/embedded-nal/README.md @@ -0,0 +1,55 @@ +# embedded-nal + +> An Embedded Network Abstraction Layer + +This crate defines a simple set of traits that can be implemented by almost any TCP/IP stack. This might be an on-chip stack like smoltcp, or it could be an off-chip TCP/IP stack on an AT modem. + +## [API reference] + +[API reference]: https://docs.rs/embedded-nal + +## How-to: add a new trait + +This is the suggested approach to adding a new trait to `embedded-nal` + +### Research / Discussion + +Ideally, before proposing a new trait, or set of traits, you should check for an existing issue +suggesting the need for the trait, as well as any related works / use cases / requirements that +are useful to consider in the design of the trait. + +These issues will be labeled as `discussion` in the issue tracker. + +### Implementation / Demonstration + +Proposed traits should then be implemented and demonstrated, either by forking `embedded-nal` or by creating a new crate with the intent of integrating this into `embedded-nal` once the traits have stabilized. You may find [cargo workspaces](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html) and [patch](https://doc.rust-lang.org/edition-guide/rust-2018/cargo-and-crates-io/replacing-dependencies-with-patch.html) useful for the forking approach. + +### Proposing a trait + +Once the trait has been demonstrated a PR should be opened to merge the new trait(s) into `embedded-nal`. This should include a link to the previous discussion issue. + +If there is determined to be more than one alternative then there should be further discussion to +try to single out the best option. Once there is consensus this will be merged into the `embedded-nal` repository. + +These issues / PRs will be labeled as `proposal`s in the issue tracker. + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.77.0 and up. It _might_ +compile with older versions but that may change in any new patch release. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/embedded-nal/embedded-nal-async/CHANGELOG.md b/embedded-nal/embedded-nal-async/CHANGELOG.md new file mode 100644 index 0000000..0ab7b52 --- /dev/null +++ b/embedded-nal/embedded-nal-async/CHANGELOG.md @@ -0,0 +1,61 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +No unreleased changes yet + +## [0.8.0] - 2024-09-20 + +- Removed the `ip_in_core` feature, this is now the default. + +## [0.7.1] - 2023-11-28 + +- Use `feature()` on nightly toolchains only. This adds support for 1.75 beta and stable. + +## [0.7.0] - 2023-11-10 + +- [breaking] `Dns::get_host_by_address` now uses `&mut [u8]` instead of `heapless::String`. +- [breaking] Remove unneeded `where Self: 'a` bound in `TcpClient::connect`. +- Bumped to `embedded-nal` 0.8 + +## [0.6.0] - 2023-10-03 + +- Bumped to `embedded-io-async` 0.6 + +## [0.5.0] - 2023-08-07 + +- Let `&T` for `T: Dns` implement `Dns` +- Bumped to `embedded-nal` 0.7 +- Bumped to `embedded-io-async` 0.5 + +## [0.4.0] - 2023-01-27 + +- Add traits for UDP + +## [0.3.0] - 2022-11-25 + +- Bump `embedded-io` dependency to `0.4` +- Switch all traits to use [`async_fn_in_trait`](https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html) (AFIT). Requires `nightly-2022-11-22` or newer. + +## [0.2.0] - 2022-08-03 + +TcpClient trait for creating shared async TCP/IP stack implementations. +Remove TcpClientStack, TcpFullStack and UDP traits pending traits that support shared use. + +## [0.1.0] - 2022-05-04 + +Initial release to crates.io. + +[Unreleased]: https://github.com/rust-embedded-community/embedded-nal/compare/embedded-nal-async-v0.8.0...HEAD +[0.8.0]: https://github.com/rust-embedded-community/embedded-nal/compare/embedded-nal-async-v0.7.1...embedded-nal-async-v0.8.0 +[0.7.1]: https://github.com/rust-embedded-community/embedded-nal/compare/embedded-nal-async-v0.7.0...embedded-nal-async-v0.7.1 +[0.7.0]: https://github.com/rust-embedded-community/embedded-nal/compare/embedded-nal-async-v0.6.0...embedded-nal-async-v0.7.0 +[0.6.0]: https://github.com/rust-embedded-community/embedded-nal/compare/embedded-nal-async-v0.5.0...embedded-nal-async-v0.6.0 +[0.5.0]: https://github.com/rust-embedded-community/embedded-nal/compare/embedded-nal-async-v0.4.0...embedded-nal-async-v0.5.0 +[0.4.0]: https://github.com/rust-embedded-community/embedded-nal/compare/embedded-nal-async-v0.3.0...embedded-nal-async-v0.4.0 +[0.1.0]: https://github.com/rust-embedded-community/embedded-nal/releases/tag/embedded-nal-async-v0.1.0 diff --git a/embedded-nal/embedded-nal-async/Cargo.toml b/embedded-nal/embedded-nal-async/Cargo.toml new file mode 100644 index 0000000..38871cd --- /dev/null +++ b/embedded-nal/embedded-nal-async/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "embedded-nal-async" +version = "0.8.0" +edition = "2021" +description = "An Async Network Abstraction Layer (NAL) for Embedded Systems" +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-embedded-community/embedded-nal" +homepage = "https://github.com/rust-embedded-community/embedded-nal" +documentation = "https://docs.rs/embedded-nal-async" +readme = "README.md" +keywords = ["network"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] + +[dependencies] +embedded-nal = { version = "0.9.0", path = "../" } +embedded-io-async = { version = "0.6.0" } diff --git a/embedded-nal/embedded-nal-async/README.md b/embedded-nal/embedded-nal-async/README.md new file mode 100644 index 0000000..25b8248 --- /dev/null +++ b/embedded-nal/embedded-nal-async/README.md @@ -0,0 +1,30 @@ + + +# `embedded-nal-async` + +An asynchronous Nardware Abstraction Layer (NAL) for embedded systems. + +This crate contains asynchronous versions of the [`embedded-nal`] traits and shares its scope and [design goals]. +The purpose of this crate is to iterate over these trait versions before integrating them into [`embedded-nal`]. + +**NOTE** These traits are still experimental. At least one breaking change to this crate is expected in the future (changing from GATs to `async fn`), but there might be more. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/embedded-nal/embedded-nal-async/src/dns.rs b/embedded-nal/embedded-nal-async/src/dns.rs new file mode 100644 index 0000000..6ff5327 --- /dev/null +++ b/embedded-nal/embedded-nal-async/src/dns.rs @@ -0,0 +1,62 @@ +use core::net::IpAddr; +use embedded_nal::AddrType; + +/// This trait is an extension trait for [`TcpStack`] and [`UdpStack`] for dns +/// resolutions. It does not handle every DNS record type, but is meant as an +/// embedded alternative to [`ToSocketAddrs`], and is as such meant to resolve +/// an ip address from a hostname, or a hostname from an ip address. This means +/// that it only deals in host address records `A` (IPv4) and `AAAA` (IPv6). +/// +/// [`TcpStack`]: crate::trait@TcpStack +/// [`UdpStack`]: crate::trait@UdpStack +/// [`ToSocketAddrs`]: +/// https://doc.rust-lang.org/std/net/trait.ToSocketAddrs.html +pub trait Dns { + /// The type returned when we have an error + type Error: core::fmt::Debug; + + /// Resolve the first ip address of a host, given its hostname and a desired + /// address record type to look for + async fn get_host_by_name( + &self, + host: &str, + addr_type: AddrType, + ) -> Result; + + /// Resolve the hostname of a host, given its ip address. + /// + /// The hostname is stored at the beginning of `result`, the length is returned. + /// + /// If the buffer is too small to hold the domain name, an error should be returned. + /// + /// **Note**: A fully qualified domain name (FQDN), has a maximum length of + /// 255 bytes according to [`rfc1035`]. Therefore, you can pass a 255-byte long + /// buffer to guarantee it'll always be large enough. + /// + /// [`rfc1035`]: https://tools.ietf.org/html/rfc1035 + async fn get_host_by_address( + &self, + addr: IpAddr, + result: &mut [u8], + ) -> Result; +} + +impl Dns for &T { + type Error = T::Error; + + async fn get_host_by_name( + &self, + host: &str, + addr_type: AddrType, + ) -> Result { + T::get_host_by_name(self, host, addr_type).await + } + + async fn get_host_by_address( + &self, + addr: IpAddr, + result: &mut [u8], + ) -> Result { + T::get_host_by_address(self, addr, result).await + } +} diff --git a/embedded-nal/embedded-nal-async/src/lib.rs b/embedded-nal/embedded-nal-async/src/lib.rs new file mode 100644 index 0000000..2a8e390 --- /dev/null +++ b/embedded-nal/embedded-nal-async/src/lib.rs @@ -0,0 +1,14 @@ +//! # embedded-nal-async - An async Network Abstraction Layer for Embedded Systems + +#![no_std] +#![allow(async_fn_in_trait)] +#![deny(missing_docs)] +#![deny(unsafe_code)] + +mod dns; +mod stack; + +pub use dns::Dns; +pub use embedded_nal::AddrType; +pub use stack::TcpConnect; +pub use stack::{ConnectedUdp, UdpStack, UnconnectedUdp}; diff --git a/embedded-nal/embedded-nal-async/src/stack/mod.rs b/embedded-nal/embedded-nal-async/src/stack/mod.rs new file mode 100644 index 0000000..65800db --- /dev/null +++ b/embedded-nal/embedded-nal-async/src/stack/mod.rs @@ -0,0 +1,5 @@ +mod tcp; +mod udp; + +pub use tcp::TcpConnect; +pub use udp::{ConnectedUdp, UdpStack, UnconnectedUdp}; diff --git a/embedded-nal/embedded-nal-async/src/stack/tcp.rs b/embedded-nal/embedded-nal-async/src/stack/tcp.rs new file mode 100644 index 0000000..3549c43 --- /dev/null +++ b/embedded-nal/embedded-nal-async/src/stack/tcp.rs @@ -0,0 +1,35 @@ +use core::net::SocketAddr; + +/// This trait is implemented by TCP/IP stacks. The trait allows the underlying driver to +/// construct multiple connections that implement the I/O traits from embedded-io-async. +/// +/// The associated connection type should close the connection when dropped. +pub trait TcpConnect { + /// Error type returned on connect failure. + type Error: embedded_io_async::Error; + + /// Type holding state of a TCP connection. Should close the connection when dropped. + type Connection<'a>: embedded_io_async::Read + + embedded_io_async::Write + where + Self: 'a; + + /// Connect to the given remote host and port. + /// + /// Returns `Ok` if the connection was successful. + async fn connect<'a>(&'a self, remote: SocketAddr) + -> Result, Self::Error>; +} + +impl TcpConnect for &T { + type Error = T::Error; + + type Connection<'a> = T::Connection<'a> where Self: 'a; + + async fn connect<'a>( + &'a self, + remote: SocketAddr, + ) -> Result, Self::Error> { + T::connect(self, remote).await + } +} diff --git a/embedded-nal/embedded-nal-async/src/stack/udp.rs b/embedded-nal/embedded-nal-async/src/stack/udp.rs new file mode 100644 index 0000000..d425846 --- /dev/null +++ b/embedded-nal/embedded-nal-async/src/stack/udp.rs @@ -0,0 +1,199 @@ +//! Traits for using UDP on embedded devices +//! +//! ## Notes for implementers +//! +//! * At several places, the APIs expect to provide a local address. Backends that can not obtain +//! it, such as some AT-command based stacks, may pretend to have performed some form of network address +//! translation, and present invalid addresses as the local address. +//! +//! * Implementing [`UdpStack::UniquelyBound`] and [`UdpStack::MultiplyBound`] unconnected sockets +//! separately allows discarding the local addresses in the bound case. With LTO enabled, all the +//! overhead compared with a third trait variant between [ConnectedUdp] and [UnconnectedUdp] (in +//! which the local address is static but the remote address is flexible) should optimized out. +//! Implementing `UniquelyBound` and `MultiplyBound` with the same type is expected to be a +//! common choice. + +use core::net::SocketAddr; + +/// This trait is implemented by UDP sockets. +/// +/// The socket it represents is both bound (has a local IP address, port and interface) and +/// connected (has a remote IP address and port). +/// +/// The term "connected" here refers to the semantics of POSIX datagram sockets, through which datagrams +/// are sent and received without having a remote address per call. It does not imply any process +/// of establishing a connection (which is absent in UDP). While there is typically no POSIX +/// `bind()` call in the creation of such sockets, these are implicitly bound to a suitable local +/// address at connect time. +pub trait ConnectedUdp { + /// Error type returned by send and receive operations. + type Error: embedded_io_async::Error; + + /// Send the provided data to the connected peer + async fn send(&mut self, data: &[u8]) -> Result<(), Self::Error>; + + /// Receive a datagram into the provided buffer. + /// + /// If the received datagram exceeds the buffer's length, it is received regardless, and the + /// remaining bytes are discarded. The full datagram size is still indicated in the result, + /// allowing the recipient to detect that truncation. + /// + /// ## Compatibility note + /// + /// This deviates from the sync/nb equivalent trait in that it describes the overflow behavior + /// (a possibility not considered there). The name deviates from the original `receive()` to + /// make room for a version that is more zero-copy friendly. + async fn receive_into(&mut self, buffer: &mut [u8]) -> Result; + + // WIP to allow zero-copy operation + // The plain receive is simple and can be provided -- implementations that don't populate + // receive calls from scatter-gather can just return a slice of the raw data instead, and rely + // on the socket still being exclusively owned. receive_oned is harder as providing it requires + // alloc. + // + // async fn receive(&mut self, buffer: &mut [u8]) -> utput = Result + '_, Self::Error>; + // async fn receive_owned(&mut self) -> Result + 'static, Self::Error>; +} + +/// This trait is implemented by UDP sockets. +/// +/// The socket it represents is not necessarily bound (may not have a single local IP address, port +/// and interface), and is typically not connected (has no remote IP address and port). Both are +/// addresses are explicitly given in every call. +/// +/// If there were constraints in place at socket creation time (typically on the local side), the +/// caller MUST pass in the same (or compatible) values, MAY and pass in unspecified values where +/// applicable. The implementer MAY check them for compatibility, and SHOULD do that in debug mode. +pub trait UnconnectedUdp { + /// Error type returned by send and receive operations. + type Error: embedded_io_async::Error; + + /// Send the provided data to a peer + /// + /// ## Sending initial messages + /// + /// The local address can be left unspecified by leaving any of its component zero -- that + /// gives the "any" address (`[::]` / `0.0.0.0`), the uncspecified port (0) or the unspecified + /// zone identifier (0). Unless the operating system provides facilities exceeding this crate's traits for + /// enumerating local interfaces and addresses, this is the only way to initiate outbound + /// traffic. + /// + /// ## Responding to messages + /// + /// Users who have previously received data from a peer and want to respond have a choice of + /// sending from the address to which the original datagram was addressed, or from an unbound + /// address. Both are valid choices in some situations, and the right choice depends on the + /// protocol used. + /// + /// Note that users of sockets created through [`UdpStack::bind_single()`] should always pass + /// in that single address -- even though they've made their intention clear at construction. + /// They can pass either the one obtained at socket creation time, or the one obtained at + /// receive time; these should be equal. This allows implementations of the trait to use a + /// single kind of socket for both sockets bound to a single and sockets bound to multiple + /// addresses. + async fn send( + &mut self, + local: SocketAddr, + remote: SocketAddr, + data: &[u8], + ) -> Result<(), Self::Error>; + + /// Receive a datagram into the provided buffer. + /// + /// If the received datagram exceeds the buffer's length, it is received regardless, and the + /// remaining bytes are discarded. The full datagram size is still indicated in the result, + /// allowing the recipient to detect that truncation. + /// + /// The local and remote address are given, in that order, in the result along with the number + /// of bytes. + async fn receive_into( + &mut self, + buffer: &mut [u8], + ) -> Result<(usize, SocketAddr, SocketAddr), Self::Error>; +} + +/// This trait is implemented by UDP/IP stacks. The trait allows the underlying driver to +/// construct multiple connections that implement the I/O traits from embedded-io-async. +/// +/// Note that stacks with exotic connection creation methods may still not implement this, yet have +/// objects that implement [`ConnectedUdp`] or similar. +pub trait UdpStack { + /// Error type returned on socket creation failure. + type Error: embedded_io_async::Error; + + /// Eventual socket return type of the [`.connect()`] method + type Connected: ConnectedUdp; + /// Eventual socket return type of the [`.bind_single()`] method + type UniquelyBound: UnconnectedUdp; + /// Eventual return type of the [`.bind_multiple()`] method + type MultiplyBound: UnconnectedUdp; + + /// Create a socket that has a fixed remote address. + /// + /// The local address is chosen automatically. + /// + /// There is a provided implementation that implements this from the maximally unspecified + /// local address and [`.connect_from()`], but may be provided more efficiently by + /// implementers. + async fn connect( + &self, + remote: SocketAddr, + ) -> Result<(SocketAddr, Self::Connected), Self::Error> { + use core::net::{Ipv4Addr, Ipv6Addr, SocketAddr::*, SocketAddrV4, SocketAddrV6}; + + let local = match remote { + V4(_) => V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), + V6(_) => V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0)), + }; + self.connect_from(local, remote).await + } + + /// Create a socket that has a fixed remote address. + /// + /// The local address is given explicitly, but may be partially unspecified; it is fixed by the + /// network stack at connection time. The full local address is returned along with the + /// connected socket, primarily for debugging purposes. + async fn connect_from( + &self, + local: SocketAddr, + remote: SocketAddr, + ) -> Result<(SocketAddr, Self::Connected), Self::Error>; + + /// Create a socket that has a fixed local address. + /// + /// Note that the giving an unspecified address here is *not* the same as a POSIX `bind()` -- + /// if the underlying stack supports multiple local addresses, it will pick *one* of the + /// applicable addresses, rather than binding to all of them. + /// + /// The full local address is returned along with the bound socket; it may then be passed on to + /// other protocols for advertising purposes. + async fn bind_single( + &self, + local: SocketAddr, + ) -> Result<(SocketAddr, Self::UniquelyBound), Self::Error>; + + /// Create a socket that has no single fixed local address. + /// + /// The IP address part of the local address is typically left unspecified, and the port is + /// given. There are use cases for other constellations, and this interface does not rule out + /// that they can be used, but they are rare (e.g. using the same IP address on different + /// network interfaces, and listening to datagrams arriving at any of them) or not well + /// supported by operating systems (e.g., binding to all ports at the same is not possible on + /// POSIX systems, where giving port 0 to a bind makes the OS pick *some* suitable port). + /// + /// Caveats: + /// + /// * There is currently no way to pass in a local address that has an unspecified address + /// family (which would effectively create a single socket that servers both IPv4 and IPv6); + /// it is not specified whether stacks that use V6MAPPED IPv4 addresses could simply used + /// that mechanism. + /// + /// * It is currently not specified whether this mechanism can be used to join multicast + /// groups. + /// + /// * There is currently no hybrid binding that allows emulating what POSIX systems do when + /// binding to `[::]:0`, that is, picking some available port but then still leaving the + /// interface and IP address unspecified. + async fn bind_multiple(&self, local: SocketAddr) -> Result; +} diff --git a/embedded-nal/rustfmt.toml b/embedded-nal/rustfmt.toml new file mode 100644 index 0000000..6eed6d0 --- /dev/null +++ b/embedded-nal/rustfmt.toml @@ -0,0 +1,2 @@ +hard_tabs = true + diff --git a/embedded-nal/src/dns.rs b/embedded-nal/src/dns.rs new file mode 100644 index 0000000..7470421 --- /dev/null +++ b/embedded-nal/src/dns.rs @@ -0,0 +1,75 @@ +use core::net::IpAddr; + +/// This is the host address type to be returned by `gethostbyname`. +/// +/// An IPv4 address type always looks for `A` records, while IPv6 address type +/// will look for `AAAA` records +#[derive(Clone, Debug, PartialEq)] +pub enum AddrType { + /// Result is `A` record + IPv4, + /// Result is `AAAA` record + IPv6, + /// Result is either a `A` record, or a `AAAA` record + Either, +} + +/// This trait is an extension trait for [`TcpStack`] and [`UdpStack`] for dns +/// resolutions. It does not handle every DNS record type, but is meant as an +/// embedded alternative to [`ToSocketAddrs`], and is as such meant to resolve +/// an ip address from a hostname, or a hostname from an ip address. This means +/// that it only deals in host address records `A` (IPv4) and `AAAA` (IPv6). +/// +/// [`TcpStack`]: crate::trait@TcpStack +/// [`UdpStack`]: crate::trait@UdpStack +/// [`ToSocketAddrs`]: +/// https://doc.rust-lang.org/std/net/trait.ToSocketAddrs.html +pub trait Dns { + /// The type returned when we have an error + type Error: core::fmt::Debug; + + /// Resolve the first ip address of a host, given its hostname and a desired + /// address record type to look for + fn get_host_by_name( + &mut self, + hostname: &str, + addr_type: AddrType, + ) -> nb::Result; + + /// Resolve the hostname of a host, given its ip address. + /// + /// The hostname is stored at the beginning of `result`, the length is returned. + /// + /// If the buffer is too small to hold the domain name, an error should be returned. + /// + /// **Note**: A fully qualified domain name (FQDN), has a maximum length of + /// 255 bytes according to [`rfc1035`]. Therefore, you can pass a 255-byte long + /// buffer to guarantee it'll always be large enough. + /// + /// [`rfc1035`]: https://tools.ietf.org/html/rfc1035 + fn get_host_by_address( + &mut self, + addr: IpAddr, + result: &mut [u8], + ) -> nb::Result; +} + +impl Dns for &mut T { + type Error = T::Error; + + fn get_host_by_name( + &mut self, + hostname: &str, + addr_type: AddrType, + ) -> nb::Result { + T::get_host_by_name(self, hostname, addr_type) + } + + fn get_host_by_address( + &mut self, + addr: IpAddr, + result: &mut [u8], + ) -> nb::Result { + T::get_host_by_address(self, addr, result) + } +} diff --git a/embedded-nal/src/lib.rs b/embedded-nal/src/lib.rs new file mode 100644 index 0000000..5cf4aa8 --- /dev/null +++ b/embedded-nal/src/lib.rs @@ -0,0 +1,15 @@ +//! # embedded-nal - A Network Abstraction Layer for Embedded Systems +#![no_std] +#![deny(missing_docs)] +#![deny(unsafe_code)] + +mod dns; +mod stack; + +pub use nb; + +pub use dns::{AddrType, Dns}; +pub use stack::{ + SharableStack, SharedStack, TcpClientStack, TcpError, TcpErrorKind, TcpFullStack, + UdpClientStack, UdpFullStack, +}; diff --git a/embedded-nal/src/stack/mod.rs b/embedded-nal/src/stack/mod.rs new file mode 100644 index 0000000..f86c594 --- /dev/null +++ b/embedded-nal/src/stack/mod.rs @@ -0,0 +1,7 @@ +mod share; +mod tcp; +mod udp; + +pub use share::{SharableStack, SharedStack}; +pub use tcp::{TcpClientStack, TcpError, TcpErrorKind, TcpFullStack}; +pub use udp::{UdpClientStack, UdpFullStack}; diff --git a/embedded-nal/src/stack/share.rs b/embedded-nal/src/stack/share.rs new file mode 100644 index 0000000..c8caec8 --- /dev/null +++ b/embedded-nal/src/stack/share.rs @@ -0,0 +1,143 @@ +use crate::{nb, TcpClientStack, TcpFullStack, UdpClientStack, UdpFullStack}; +use core::cell::RefCell; +use core::net::SocketAddr; + +/// Sharable wrapper for a network stack implementation. +/// +/// An implementation of the stack traits that can contain (and provide provide +/// single-threaded shared access to) another stack implementation. A direct +/// implementation can only be used when owned or with a mutable reference. +/// This implementation will store another implementation internally, and yield +/// an arbitrary number of shared references to it, which themselves implement +/// the stack traits. +/// +/// ``` +/// use embedded_nal::SharableStack; +/// use core::net::{SocketAddr, SocketAddrV4, Ipv4Addr}; +/// # use embedded_nal::{UdpClientStack, nb}; +/// # struct SomeNalDriver {} +/// # impl SomeNalDriver { +/// # fn new() -> Self { Self {} } +/// # } +/// # impl UdpClientStack for SomeNalDriver { +/// # type Error = (); +/// # type UdpSocket = (); +/// # fn socket(&mut self) -> Result { +/// # Ok(()) +/// # } +/// # fn connect( +/// # &mut self, +/// # socket: &mut Self::UdpSocket, +/// # remote: SocketAddr, +/// # ) -> Result<(), Self::Error> { +/// # Ok(()) +/// # } +/// # fn send(&mut self, socket: &mut Self::UdpSocket, buffer: &[u8]) -> nb::Result<(), Self::Error> { +/// # Ok(()) +/// # } +/// # fn receive( +/// # &mut self, +/// # socket: &mut Self::UdpSocket, +/// # buffer: &mut [u8], +/// # ) -> nb::Result<(usize, SocketAddr), Self::Error> { +/// # Ok((0, SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 0)))) +/// # } +/// # fn close(&mut self, socket: Self::UdpSocket) -> Result<(), Self::Error> { +/// # Ok(()) +/// # } +/// # } +/// let mut driver = SomeNalDriver::new(); +/// // Driver can only be used in one place at a time. +/// let mut sharable_driver = SharableStack::new(driver); +/// // Sharable driver can't do anything on its own, but it can create many usable copies. +/// let mut shared_driver0 = sharable_driver.acquire(); +/// let mut shared_driver1 = sharable_driver.acquire(); +/// // These shared copies can be passed around to other parts of an application's code, and used +/// // independently. +/// let mut socket0 = shared_driver0.socket()?; +/// shared_driver0.connect(&mut socket0, SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080))); +/// // ... +/// +/// // ... and somewhere else +/// let mut socket1 = shared_driver1.socket()?; +/// shared_driver1.connect(&mut socket1, SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8443))); +/// // ... +/// # Ok::<(), ()>(()) +/// ``` +pub struct SharableStack { + stack: RefCell, +} + +impl SharableStack { + /// Create a new SharedStack that contains and uses some other stack implementation. + pub fn new(stack: T) -> Self { + SharableStack { + stack: RefCell::new(stack), + } + } + + /// Returns a shared reference to the driver that can be used as a first-class implementation. + pub fn acquire(&self) -> SharedStack { + SharedStack { stack: &self.stack } + } +} + +/// Single-thread shared reference to an internal network stack implementation. +/// +/// This can only be created by calling [`SharableStack::acquire()`] +pub struct SharedStack<'a, T> { + stack: &'a RefCell, +} + +macro_rules! forward { + ($func:ident($($v:ident: $IT:ty),*) -> $T:ty) => { + fn $func(&mut self, $($v: $IT),*) -> $T { + self.stack.borrow_mut().$func($($v),*) + } + } +} + +impl<'a, T> UdpClientStack for SharedStack<'a, T> +where + T: UdpClientStack, +{ + type Error = T::Error; + type UdpSocket = T::UdpSocket; + + forward! {socket() -> Result} + forward! {connect(socket: &mut Self::UdpSocket, address: SocketAddr) -> Result<(), Self::Error>} + forward! {send(socket: &mut Self::UdpSocket, data: &[u8]) -> Result<(), nb::Error<::Error>>} + forward! {receive(socket: &mut Self::UdpSocket, data: &mut [u8]) -> Result<(usize, SocketAddr), nb::Error<::Error>>} + forward! {close(socket: Self::UdpSocket) -> Result<(), Self::Error>} +} + +impl<'a, T> UdpFullStack for SharedStack<'a, T> +where + T: UdpFullStack, +{ + forward! {bind(socket: &mut Self::UdpSocket, local_port: u16) -> Result<(), Self::Error>} + forward! {send_to(socket: &mut Self::UdpSocket, remote: SocketAddr, buffer: &[u8]) -> Result<(), nb::Error<::Error>>} +} + +impl<'a, T> TcpClientStack for SharedStack<'a, T> +where + T: TcpClientStack, +{ + type TcpSocket = T::TcpSocket; + type Error = T::Error; + + forward! {socket() -> Result} + forward! {connect(socket: &mut Self::TcpSocket, address: SocketAddr) -> Result<(), nb::Error<::Error>>} + forward! {send(socket: &mut Self::TcpSocket, data: &[u8]) -> Result::Error>>} + forward! {receive(socket: &mut Self::TcpSocket, data: &mut [u8]) -> Result::Error>>} + forward! {close(socket: Self::TcpSocket) -> Result<(), Self::Error>} +} + +impl<'a, T> TcpFullStack for SharedStack<'a, T> +where + T: TcpFullStack, +{ + forward! {bind(socket: &mut Self::TcpSocket, port: u16) -> Result<(), ::Error>} + forward! {listen(socket: &mut Self::TcpSocket) -> Result<(), ::Error>} + forward! {accept(socket: &mut Self::TcpSocket) -> Result<(::TcpSocket, SocketAddr), nb::Error<::Error>>} +} diff --git a/embedded-nal/src/stack/tcp.rs b/embedded-nal/src/stack/tcp.rs new file mode 100644 index 0000000..d28f76f --- /dev/null +++ b/embedded-nal/src/stack/tcp.rs @@ -0,0 +1,134 @@ +use core::net::SocketAddr; + +/// Represents specific errors encountered during TCP operations. +#[non_exhaustive] +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum TcpErrorKind { + /// The socket has been closed in the direction in which the failing operation was attempted. + PipeClosed, + + /// Some other error has occurred. + Other, +} + +/// Methods to resolve errors into identifiable, actionable codes on the client side. +pub trait TcpError: core::fmt::Debug { + /// Determines the kind of error that occurred. + fn kind(&self) -> TcpErrorKind; +} + +/// This trait is implemented by TCP/IP stacks. You could, for example, have an implementation +/// which knows how to send AT commands to an ESP8266 WiFi module. You could have another implementation +/// which knows how to driver the Rust Standard Library's `std::net` module. Given this trait, you can +/// write a portable HTTP client which can work with either implementation. +pub trait TcpClientStack { + /// The type returned when we create a new TCP socket + type TcpSocket; + /// The type returned when we have an error + type Error: TcpError; + + /// Open a socket for usage as a TCP client. + /// + /// The socket must be connected before it can be used. + /// + /// Returns `Ok(socket)` if the socket was successfully created. + fn socket(&mut self) -> Result; + + /// Connect to the given remote host and port. + /// + /// Returns `Ok` if the connection was successful. Otherwise, if the connection could not be + /// completed immediately, this function should return [`nb::Error::WouldBlock`]. + fn connect( + &mut self, + socket: &mut Self::TcpSocket, + remote: SocketAddr, + ) -> nb::Result<(), Self::Error>; + + /// Write to the stream. + /// + /// Returns the number of bytes written (which may be less than `buffer.len()`) or an error. + fn send( + &mut self, + socket: &mut Self::TcpSocket, + buffer: &[u8], + ) -> nb::Result; + + /// Receive data from the stream. + /// + /// Returns `Ok(n)`, which means `n` bytes of data have been received and + /// they have been placed in `&buffer[0..n]`, or an error. If a packet has + /// not been received when called, then [`nb::Error::WouldBlock`] + /// should be returned. + fn receive( + &mut self, + socket: &mut Self::TcpSocket, + buffer: &mut [u8], + ) -> nb::Result; + + /// Close an existing TCP socket. + fn close(&mut self, socket: Self::TcpSocket) -> Result<(), Self::Error>; +} + +/// This trait is implemented by TCP/IP stacks that expose TCP server functionality. TCP servers +/// may listen for connection requests to establish multiple unique TCP connections with various +/// clients. +pub trait TcpFullStack: TcpClientStack { + /// Create a new TCP socket and bind it to the specified local port. + /// + /// Returns `Ok` when a socket is successfully bound to the specified local port. Otherwise, an + /// `Err(e)` variant is returned. + fn bind(&mut self, socket: &mut Self::TcpSocket, local_port: u16) -> Result<(), Self::Error>; + + /// Begin listening for connection requests on a previously-bound socket. + /// + /// Returns `Ok` if the socket was successfully transitioned to the listening state. Otherwise, + /// an `Err(e)` variant is returned. + fn listen(&mut self, socket: &mut Self::TcpSocket) -> Result<(), Self::Error>; + + /// Accept an active connection request on a listening socket. + /// + /// Returns `Ok(connection)` if a new connection was created. If no pending connections are + /// available, this function should return [`nb::Error::WouldBlock`]. + fn accept( + &mut self, + socket: &mut Self::TcpSocket, + ) -> nb::Result<(Self::TcpSocket, SocketAddr), Self::Error>; +} + +impl TcpClientStack for &mut T { + type Error = T::Error; + + type TcpSocket = T::TcpSocket; + + fn socket(&mut self) -> Result { + T::socket(self) + } + + fn connect( + &mut self, + socket: &mut Self::TcpSocket, + remote: SocketAddr, + ) -> nb::Result<(), Self::Error> { + T::connect(self, socket, remote) + } + + fn send( + &mut self, + socket: &mut Self::TcpSocket, + buffer: &[u8], + ) -> nb::Result { + T::send(self, socket, buffer) + } + + fn receive( + &mut self, + socket: &mut Self::TcpSocket, + buffer: &mut [u8], + ) -> nb::Result { + T::receive(self, socket, buffer) + } + + fn close(&mut self, socket: Self::TcpSocket) -> Result<(), Self::Error> { + T::close(self, socket) + } +} diff --git a/embedded-nal/src/stack/udp.rs b/embedded-nal/src/stack/udp.rs new file mode 100644 index 0000000..c45e6fb --- /dev/null +++ b/embedded-nal/src/stack/udp.rs @@ -0,0 +1,110 @@ +use core::net::SocketAddr; + +/// This trait is implemented by UDP/IP stacks. You could, for example, have +/// an implementation which knows how to send AT commands to an ESP8266 WiFi +/// module. You could have another implementation which knows how to driver the +/// Rust Standard Library's `std::net` module. Given this trait, you can how +/// write a portable CoAP client which can work with either implementation. +pub trait UdpClientStack { + /// The type returned when we create a new UDP socket + type UdpSocket; + /// The type returned when we have an error + type Error: core::fmt::Debug; + + /// Allocate a socket for further use. + fn socket(&mut self) -> Result; + + /// Connect a UDP socket with a peer using a dynamically selected port. + /// + /// Selects a port number automatically and initializes for read/writing. + fn connect( + &mut self, + socket: &mut Self::UdpSocket, + remote: SocketAddr, + ) -> Result<(), Self::Error>; + + /// Send a datagram to the remote host. + /// + /// The remote host used is either the one specified in `UdpStack::connect` + /// or the last one used in `UdpServerStack::write_to`. + fn send(&mut self, socket: &mut Self::UdpSocket, buffer: &[u8]) -> nb::Result<(), Self::Error>; + + /// Read a datagram the remote host has sent to us. + /// + /// Returns `Ok((n, remote))`, which means a datagram of size `n` has been + /// received from `remote` and been placed in `&buffer[0..n]`, or an error. + /// If a packet has not been received when called, then [`nb::Error::WouldBlock`] + /// should be returned. + fn receive( + &mut self, + socket: &mut Self::UdpSocket, + buffer: &mut [u8], + ) -> nb::Result<(usize, SocketAddr), Self::Error>; + + /// Close an existing UDP socket. + fn close(&mut self, socket: Self::UdpSocket) -> Result<(), Self::Error>; +} + +/// This trait is implemented by UDP/IP stacks. It provides the ability to +/// listen for packets on a specified port and send replies. +pub trait UdpFullStack: UdpClientStack { + /// Bind a UDP socket with a specified port + fn bind(&mut self, socket: &mut Self::UdpSocket, local_port: u16) -> Result<(), Self::Error>; + + /// Send a packet to a remote host/port. + fn send_to( + &mut self, + socket: &mut Self::UdpSocket, + remote: SocketAddr, + buffer: &[u8], + ) -> nb::Result<(), Self::Error>; +} + +impl UdpClientStack for &mut T { + type Error = T::Error; + + type UdpSocket = T::UdpSocket; + + fn socket(&mut self) -> Result { + T::socket(self) + } + + fn connect( + &mut self, + socket: &mut Self::UdpSocket, + remote: SocketAddr, + ) -> Result<(), Self::Error> { + T::connect(self, socket, remote) + } + + fn send(&mut self, socket: &mut Self::UdpSocket, buffer: &[u8]) -> nb::Result<(), Self::Error> { + T::send(self, socket, buffer) + } + + fn receive( + &mut self, + socket: &mut Self::UdpSocket, + buffer: &mut [u8], + ) -> nb::Result<(usize, SocketAddr), Self::Error> { + T::receive(self, socket, buffer) + } + + fn close(&mut self, socket: Self::UdpSocket) -> Result<(), Self::Error> { + T::close(self, socket) + } +} + +impl UdpFullStack for &mut T { + fn bind(&mut self, socket: &mut Self::UdpSocket, local_port: u16) -> Result<(), Self::Error> { + T::bind(self, socket, local_port) + } + + fn send_to( + &mut self, + socket: &mut Self::UdpSocket, + remote: SocketAddr, + buffer: &[u8], + ) -> nb::Result<(), Self::Error> { + T::send_to(self, socket, remote, buffer) + } +} diff --git a/memory.x b/memory.x new file mode 100644 index 0000000..ef19dff --- /dev/null +++ b/memory.x @@ -0,0 +1,17 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + + /* Pick one of the two options for RAM layout */ + + /* OPTION A: Use all RAM banks as one big block */ + /* Reasonable, unless you are doing something */ + /* really particular with DMA or other concurrent */ + /* access that would benefit from striping */ + RAM : ORIGIN = 0x20000000, LENGTH = 264K + + /* OPTION B: Keep the unstriped sections separate */ + /* RAM: ORIGIN = 0x20000000, LENGTH = 256K */ + /* SCRATCH_A: ORIGIN = 0x20040000, LENGTH = 4K */ + /* SCRATCH_B: ORIGIN = 0x20041000, LENGTH = 4K */ +} diff --git a/rp-hal/.github/workflows/on_target_tests.yml b/rp-hal/.github/workflows/on_target_tests.yml new file mode 100644 index 0000000..a3ec108 --- /dev/null +++ b/rp-hal/.github/workflows/on_target_tests.yml @@ -0,0 +1,70 @@ +on: [push, pull_request] +name: Check on-target-tests +env: + PACKAGE: on-target-tests +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: thumbv6m-none-eabi + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-11-14 + target: thumbv6m-none-eabi + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps + run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.79 + target: thumbv6m-none-eabi + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build on-target-tests (on MSRV) + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format + run: cd ${PACKAGE} && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: thumbv6m-none-eabi + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy diff --git a/rp-hal/.github/workflows/rp2040_hal.yml b/rp-hal/.github/workflows/rp2040_hal.yml new file mode 100644 index 0000000..a0c9570 --- /dev/null +++ b/rp-hal/.github/workflows/rp2040_hal.yml @@ -0,0 +1,99 @@ +on: [push, pull_request] +name: Check rp2040-hal +env: + PACKAGE: rp2040-hal + TARGET: thumbv6m-none-eabi +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build rp2040-hal + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} + - name: Build rp2040-hal-macros + run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature + test: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Test rp2040-hal + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --tests --features critical-section-impl + - name: Test rp2040-hal docs + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --doc --features critical-section-impl + - name: Test rp2040-hal-macros + run: cd ${PACKAGE}-macros && cargo hack test --optional-deps --tests --each-feature + - name: Test rp2040-hal-macros docs + run: cd ${PACKAGE}-macros && cargo hack test --optional-deps --doc --each-feature + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-11-14 + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps on rp2040-hal + run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature --target=${TARGET} + - name: Run cargo-udeps on rp2040-hal-macros + run: cd ${PACKAGE}-macros && cargo hack udeps --optional-deps --each-feature + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.79 + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Use older version of regex + run: cd ${PACKAGE}-examples && cargo update -p regex --precise 1.9.3 + - name: Build rp2040-hal (on MSRV) + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} + - name: Build rp2040-hal-macros (on MSRV) + run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format of rp2040-hal + run: cd ${PACKAGE} && cargo fmt -- --check + - name: Check format of rp2040-hal-macros + run: cd ${PACKAGE}-macros && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy --target=${TARGET} diff --git a/rp-hal/.github/workflows/rp2040_hal_examples.yml b/rp-hal/.github/workflows/rp2040_hal_examples.yml new file mode 100644 index 0000000..59b29c2 --- /dev/null +++ b/rp-hal/.github/workflows/rp2040_hal_examples.yml @@ -0,0 +1,72 @@ +on: [push, pull_request] +name: Check rp2040-hal-examples +env: + PACKAGE: rp2040-hal-examples + TARGET: thumbv6m-none-eabi +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Build + run: cd ${PACKAGE} && cargo build + - name: Install picotool + run: | + sudo apt-get install -y debian-archive-keyring + sudo sh -c "echo 'deb [signed-by=/usr/share/keyrings/debian-archive-keyring.gpg] http://deb.debian.org/debian testing main contrib non-free' >/etc/apt/sources.list.d/debian.list" + sudo apt-get update + sudo apt-get install -y libxml2-utils picotool + - name: Test picotool + run: picotool info ${PACKAGE}/target/${TARGET}/debug/binary_info_demo -t elf -a | tee /dev/stderr | grep -q "rp2040-hal Binary Info Example" + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-11-14 + target: ${{ env.TARGET }} + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps + run: cd ${PACKAGE} && cargo udeps + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.79 + target: ${{ env.TARGET }} + - name: Use older version of regex + run: cd ${PACKAGE} && cargo update -p regex --precise 1.9.3 + - name: Build on MSRV + run: cd ${PACKAGE} && cargo build + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format + run: cd ${PACKAGE} && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy diff --git a/rp-hal/.github/workflows/rp235x_hal_arm.yml b/rp-hal/.github/workflows/rp235x_hal_arm.yml new file mode 100644 index 0000000..acce8e2 --- /dev/null +++ b/rp-hal/.github/workflows/rp235x_hal_arm.yml @@ -0,0 +1,99 @@ +on: [push, pull_request] +name: Check rp235x-hal on Arm +env: + PACKAGE: rp235x-hal + TARGET: thumbv8m.main-none-eabihf +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build rp235x-hal + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} + - name: Build rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature + test: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Test rp235x-hal + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --tests --features critical-section-impl + - name: Test rp235x-hal docs + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --doc --features critical-section-impl + - name: Test rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack test --optional-deps --tests --each-feature + - name: Test rp235x-hal-macros docs + run: cd ${PACKAGE}-macros && cargo hack test --optional-deps --doc --each-feature + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-11-14 + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps on rp235x-hal + run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature --target=${TARGET} + - name: Run cargo-udeps on rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack udeps --optional-deps --each-feature + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.79 + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Use older version of regex + run: cd ${PACKAGE}-examples && cargo update -p regex --precise 1.9.3 + - name: Build rp235x-hal (on MSRV) + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} + - name: Build rp235x-hal-macros (on MSRV) + run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format of rp235x-hal + run: cd ${PACKAGE} && cargo fmt -- --check + - name: Check format of rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy --target=${TARGET} diff --git a/rp-hal/.github/workflows/rp235x_hal_examples_arm.yml b/rp-hal/.github/workflows/rp235x_hal_examples_arm.yml new file mode 100644 index 0000000..6a8c6f4 --- /dev/null +++ b/rp-hal/.github/workflows/rp235x_hal_examples_arm.yml @@ -0,0 +1,72 @@ +on: [push, pull_request] +name: Check rp235x-hal-examples on Arm +env: + PACKAGE: rp235x-hal-examples + TARGET: thumbv8m.main-none-eabihf +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Build + run: cd ${PACKAGE} && cargo build --target=${TARGET} + - name: Install picotool + run: | + sudo apt-get install -y debian-archive-keyring + sudo sh -c "echo 'deb [signed-by=/usr/share/keyrings/debian-archive-keyring.gpg] http://deb.debian.org/debian testing main contrib non-free' >/etc/apt/sources.list.d/debian.list" + sudo apt-get update + sudo apt-get install -y libxml2-utils picotool + - name: Test picotool + run: picotool info ${PACKAGE}/target/${TARGET}/debug/binary_info_demo -t elf -a | tee /dev/stderr | grep -q RP2350 + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-11-14 + target: ${{ env.TARGET }} + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps + run: cd ${PACKAGE} && cargo udeps --target=${TARGET} + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.79 + target: ${{ env.TARGET }} + - name: Use older version of regex + run: cd ${PACKAGE} && cargo update -p regex --precise 1.9.3 + - name: Build on MSRV + run: cd ${PACKAGE} && cargo build --target=${TARGET} + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format + run: cd ${PACKAGE} && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy --target=${TARGET} diff --git a/rp-hal/.github/workflows/rp235x_hal_examples_riscv.yml b/rp-hal/.github/workflows/rp235x_hal_examples_riscv.yml new file mode 100644 index 0000000..4ae4a44 --- /dev/null +++ b/rp-hal/.github/workflows/rp235x_hal_examples_riscv.yml @@ -0,0 +1,66 @@ +on: [push, pull_request] +name: Check rp235x-hal-examples on RISC-V +env: + PACKAGE: rp235x-hal-examples + TARGET: riscv32imac-unknown-none-elf +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Build + run: | + cd ${PACKAGE} + cat riscv_examples.txt | while read example; do + echo "Building $example" + cargo build --target=${TARGET} --bin $example + done + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.79 + target: ${{ env.TARGET }} + - name: Use older version of regex + run: cd ${PACKAGE} && cargo update -p regex --precise 1.9.3 + - name: Build on MSRV + run: | + cd ${PACKAGE} + cat riscv_examples.txt | while read example; do + echo "Building $example" + cargo build --target=${TARGET} --bin $example + done + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format + run: cd ${PACKAGE} && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + components: clippy + - name: Run cargo clippy + run: | + cd ${PACKAGE} + cat riscv_examples.txt | while read example; do + echo "Checking $example" + cargo clippy --target=${TARGET} --bin $example + done diff --git a/rp-hal/.github/workflows/rp235x_hal_riscv.yml b/rp-hal/.github/workflows/rp235x_hal_riscv.yml new file mode 100644 index 0000000..c738041 --- /dev/null +++ b/rp-hal/.github/workflows/rp235x_hal_riscv.yml @@ -0,0 +1,99 @@ +on: [push, pull_request] +name: Check rp235x-hal on RISC-V +env: + PACKAGE: rp235x-hal + TARGET: riscv32imac-unknown-none-elf +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build rp235x-hal + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} + - name: Build rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature + test: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Test rp235x-hal + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --tests --features critical-section-impl + - name: Test rp235x-hal docs + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --doc --features critical-section-impl + - name: Test rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack test --optional-deps --tests --each-feature + - name: Test rp235x-hal-macros docs + run: cd ${PACKAGE}-macros && cargo hack test --optional-deps --doc --each-feature + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-11-14 + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps on rp235x-hal + run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature --target=${TARGET} + - name: Run cargo-udeps on rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack udeps --optional-deps --each-feature + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.79 + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Use older version of regex + run: cd ${PACKAGE}-examples && cargo update -p regex --precise 1.9.3 + - name: Build rp235x-hal (on MSRV) + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} + - name: Build rp235x-hal-macros (on MSRV) + run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format of rp235x-hal + run: cd ${PACKAGE} && cargo fmt -- --check + - name: Check format of rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy --target=${TARGET} diff --git a/rp-hal/.github/workflows/rp_binary_info.yml b/rp-hal/.github/workflows/rp_binary_info.yml new file mode 100644 index 0000000..f81deac --- /dev/null +++ b/rp-hal/.github/workflows/rp_binary_info.yml @@ -0,0 +1,77 @@ +on: [push, pull_request] +name: Check rp-binary-info +env: + PACKAGE: rp-binary-info +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature + test: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Test + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --tests + - name: Test docs + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --doc + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-11-14 + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps + run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.79 + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build on MSRV + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format + run: cd ${PACKAGE} && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy diff --git a/rp-hal/.github/workflows/rp_hal_common.yml b/rp-hal/.github/workflows/rp_hal_common.yml new file mode 100644 index 0000000..b8b4d79 --- /dev/null +++ b/rp-hal/.github/workflows/rp_hal_common.yml @@ -0,0 +1,77 @@ +on: [push, pull_request] +name: Check rp-hal-common +env: + PACKAGE: rp-hal-common +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature + test: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Test + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --tests + - name: Test docs + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --doc + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-11-14 + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps + run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.79 + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build on MSRV + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format + run: cd ${PACKAGE} && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy diff --git a/rp-hal/.gitignore b/rp-hal/.gitignore new file mode 100644 index 0000000..ef0f5ea --- /dev/null +++ b/rp-hal/.gitignore @@ -0,0 +1,5 @@ +.idea/ +target +Cargo.lock +.vscode +*.orig diff --git a/rp-hal/CODE_OF_CONDUCT.md b/rp-hal/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..1da6c3d --- /dev/null +++ b/rp-hal/CODE_OF_CONDUCT.md @@ -0,0 +1,39 @@ +# The Rust Code of Conduct + +## Conduct + +**Contact**: [rp-rs team][team] + +* We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. +* On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all. +* Please be kind and courteous. There's no need to be mean or rude. +* Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. +* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. +* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the [Citizen Code of Conduct](http://citizencodeofconduct.org/); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups. +* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the [rp-rs team][team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back. +* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. + +## Moderation + +These are the policies for upholding our community's standards of conduct. + +1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.) +2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed. +3. Moderators will first respond to such remarks with a warning. +4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off. +5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. +6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology. +7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, **in private**. Complaints about bans in-channel are not allowed. +8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others. + +In the Rust community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. + +And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. + +The enforcement policies listed above apply to all official embedded rp-rs venues; including official Matrix channels (##rp-rs) and GitHub repositories under rp-rs. + +*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).* + +*Text kindly borrowed from the Rust Embedded Working Group* + +[team]: https://github.com/orgs/rp-rs/people diff --git a/rp-hal/LICENSE-APACHE b/rp-hal/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rp-hal/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp-hal/LICENSE-MIT b/rp-hal/LICENSE-MIT new file mode 100644 index 0000000..6e052e3 --- /dev/null +++ b/rp-hal/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp-hal/NOTICE b/rp-hal/NOTICE new file mode 100644 index 0000000..790ecb1 --- /dev/null +++ b/rp-hal/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp-hal/README.md b/rp-hal/README.md new file mode 100644 index 0000000..ee8109e --- /dev/null +++ b/rp-hal/README.md @@ -0,0 +1,312 @@ + +
+

+ + Logo + + +

rp-hal

+ +

+ Rust support for the "Raspberry Silicon" family of microcontrollers +
+ Explore the API docs » +
+
+ View Demos + · + Report a Bug + · + Chat on Matrix +

+

+ + + +
+

Table of Contents

+
    +
  1. Getting Started
  2. +
  3. Programming
  4. +
  5. Roadmap
  6. +
  7. Contributing
  8. +
  9. License
  10. +
  11. Contact
  12. +
  13. Acknowledgements
  14. +
+
+ + + +## Getting Started + +So, you want to program your new Raspberry Silicon microcontroller, using the +Rust programming language. You've come to the right place! + +This repository is `rp-hal` - a collection of high-level drivers for the +Raspberry Silicon RP2040 microcontroller and various associated boards, like +the Raspberry Pi Pico and the Adafruit Feather RP2040. + +If you want to write an application for Raspberry Silicon, check out our +[RP2040 Project Template](https://github.com/rp-rs/rp2040-project-template). + +If you want to write code that uses the Raspberry Silicon PIO State Machines, +check out [pio-rs](https://github.com/rp-rs/pio-rs). You can even compile PIO +programs at run-time, on the RP2040 itself! + +If you want to try out some examples on one of our supported boards, check out +the list of [*Board Support Packages*][BSPs], and click through to see the various +examples for each board. + +Before trying any of the examples, please ensure you have the latest stable +version of Rust installed, along with the right target support: + +```sh +rustup self update +rustup update stable +rustup target add thumbv6m-none-eabi +``` + +You may also want to install these helpful tools: + +```sh +# Useful to creating UF2 images for the RP2040 USB Bootloader +cargo install elf2uf2-rs --locked +# Useful for flashing over the SWD pins using a supported JTAG probe +cargo install --locked probe-rs-tools +``` + +## Packages + +There is a _Hardware Abstraction Layer_ (or HAL) crate for the RP2040 chip, +and _Board Support Package_ crates for a number of RP2040 based PCBs. If you +are writing code that should run on any microcontroller, consider using the +generic Rust Embedded Working Group's [Embedded HAL]. + +If you are writing code that should work on any RP2040 device, use the _HAL_ +crate. If you are running code on a specific board, use the appropriate _BSP_ +crate (which will include the _HAL_ crate for you). Please note, you cannot +depend on multiple _BSP_ crates; you have to pick one, or use [Cargo Features] +to select one at build time. + +Each BSP includes some examples to show off the features of that particular board. + +[Cargo Workspace]: https://doc.rust-lang.org/cargo/reference/workspaces.html +[Embedded HAL]: https://github.com/rust-embedded/embedded-hal +[Cargo Features]: https://doc.rust-lang.org/cargo/reference/features.html + +### [rp2040-hal] - The HAL for the [Raspberry Silicon RP2040] + +You should include this crate in your project if you want to write a driver or +library that runs on the [Raspberry Silicon RP2040], or if you are writing a Board +Support Package (see later on). + +The crate provides high-level drivers for the RP2040's internal peripherals, +such as the SPI Controller and the I²C Controller. It doesn't know anything +about how your particular board is wired up (such as what each IO pin of the +RP2040 is connected to). + +There are examples in this crate to show how to use various peripherals +(GPIO, I²C, SPI, UART, etc) but note that the pin-outs may not match any +particular board. + +### [BSPs] - Board support packages + +There are BSPs for various boards based on the RP2040 available in +a [separate repository][BSPs]. + +[rp2040-hal]: https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal +[Raspberry Silicon RP2040]: https://www.raspberrypi.org/products/rp2040/ +[BSPs]: https://github.com/rp-rs/rp-hal-boards/ + + +## Programming + +Rust generates standard Arm ELF files, which you can load onto your Raspberry Pi +Silicon device with your favourite Arm flashing/debugging tool. In addition, the +RP2040 contains a ROM bootloader which appears as a Mass Storage Device over USB +that accepts UF2 format images. You can use the `elf2uf2-rs` package to convert +the Arm ELF file to a UF2 format image. + +For boards with USB Device support like the Raspberry Pi Pico, we recommend you +use the UF2 process. + +The RP2040 contains two Cortex-M0+ processors, which execute Thumb-2 encoded +ARMv6-M instructions. There are no operating-specific features in the binaries +produced - they are for 'bare-metal' systems. For compatibility with other Arm +code (e.g. as produced by GCC), Rust uses the *Arm Embedded-Application Binary +Interface* standard or EABI. Therefore, any Rust code for the RP2040 should be +compiled with the target *`thumbv6m-none-eabi`*. + +More details can be found in the [Project Template]. + +### Linker flags + +Besides the correct target, which mainly defines the instruction set, +it's also necessary to use a certain memory layout compatible with +the rp2040. To achieve that, rustc must be called with appropriate +linker flags. In the [Project Template], those flags are defined in +[`.cargo/config.toml`](https://github.com/rp-rs/rp2040-project-template/blob/main/.cargo/config.toml). +Another necessary file is +[`memory.x`](https://github.com/rp-rs/rp2040-project-template/blob/main/memory.x). + +More detailed information on how the linker flags work can be found in +[the cortex_m_rt docs](https://docs.rs/cortex-m-rt/latest/cortex_m_rt/). + +In most cases, it should be sufficient to use the example files from the +[Project Template]. + +### Loading a UF2 over USB + +*Step 1* - Install [`elf2uf2-rs`](https://github.com/JoNil/elf2uf2-rs): + +```console +$ cargo install elf2uf2-rs --locked +``` + +*Step 2* - Make sure your .cargo/config contains the following (it should by +default if you are working in this repository): + +```toml +[target.thumbv6m-none-eabi] +runner = "elf2uf2-rs -d" +``` + +The `thumbv6m-none-eabi` target may be replaced by the all-Arm wildcard +`'cfg(all(target_arch = "arm", target_os = "none"))'`. + +*Step 3* - Boot your RP2040 into "USB Bootloader mode", typically by rebooting +whilst holding some kind of "Boot Select" button. On Linux, you will also need +to 'mount' the device, like you would a USB Thumb Drive. + +*Step 4* - Use `cargo run`, which will compile the code and started the +specified 'runner'. As the 'runner' is the elf2uf2-rs tool, it will build a UF2 +file and copy it to your RP2040. + +```console +$ cargo run --release --features "critical-section-impl,rt,defmt" --example pwm_blink +``` + +(The `pwm_blink` example doesn't need all these feature flags. They are listed here +so you can use the same command for all examples.) + +### Loading with probe-rs +[probe-rs](https://github.com/probe-rs/probe-rs) is a library and a +command-line tool which can flash a wide variety of microcontrollers +using a wide variety of debug/JTAG probes. Unlike using, say, OpenOCD, +probe-rs can autodetect your debug probe, which can make it easier to use. + +*Step 1* - Install `probe-rs`: + +```console +$ cargo install --locked probe-rs-tools +``` + +Alternatively, follow the installation instructions on https://probe.rs/. + +*Step 2* - Make sure your .cargo/config contains the following: + +```toml +[target.thumbv6m-none-eabi] +runner = "probe-rs run --chip RP2040" +``` + +*Step 3* - Connect your USB JTAG/debug probe (such as a Raspberry Pi Pico +running [this firmware](https://github.com/majbthrd/DapperMime)) to the SWD +programming pins on your RP2040 board. Check the probe has been found by +running: + +```console +$ probe-rs list +The following debug probes were found: +[0]: J-Link (J-Link) (VID: 1366, PID: 0101, Serial: 000099999999, JLink) +``` + +There is a SEGGER J-Link connected in the example above - the message you see +will reflect the probe you have connected. + +*Step 4* - Use `cargo run`, which will compile the code and start the specified +'runner'. As the 'runner' is the `probe-rs` tool, it will connect to the +RP2040 via the first probe it finds, and install your firmware into the Flash +connected to the RP2040. + +```console +$ cargo run --release --example pwm_blink +``` + +### Loading with picotool + +As ELF files produced by compiling Rust code are completely compatible with ELF +files produced by compiling C or C++ code, you can also use the Raspberry Pi +tool [picotool](https://github.com/raspberrypi/picotool). The only thing to be +aware of is that picotool expects your ELF files to have a `.elf` extension, and +by default Rust does not give the ELF files any extension. You can fix this by +simply renaming the file. + +Also of note is that the special +[pico-sdk](https://github.com/raspberrypi/pico-sdk) macros which hide +information in the ELF file in a way that `picotool info` can read it out, are +not supported in Rust. An alternative is TBC. + +[Project Template]: https://github.com/rp-rs/rp2040-project-template + + +## Roadmap + +NOTE These packages are under active development. As such, it is likely to +remain volatile until a 1.0.0 release. + +See the [open issues](https://github.com/rp-rs/rp-hal/issues) for a list of +proposed features (and known issues). + + +## Contributing + +Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +The steps are: + +1. Fork the Project by clicking the 'Fork' button at the top of the page. +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Make some changes to the code or documentation. +4. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +5. Push to the Feature Branch (`git push origin feature/AmazingFeature`) +6. Create a [New Pull Request](https://github.com/rp-rs/rp-hal/pulls) +7. An admin will review the Pull Request and discuss any changes that may be required. +8. Once everyone is happy, the Pull Request can be merged by an admin, and your work is part of our project! + + +## Code of Conduct + +Contribution to this crate is organized under the terms of the [Rust Code of +Conduct][CoC], and the maintainer of this crate, the [rp-rs team], promises +to intervene to uphold that code of conduct. + +[CoC]: CODE_OF_CONDUCT.md +[rp-rs team]: https://github.com/orgs/rp-rs/people + + +## License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + + +## Contact + +Raise an issue: [https://github.com/rp-rs/rp-hal/issues](https://github.com/rp-rs/rp-hal/issues) +Chat to us on Matrix: [#rp-rs:matrix.org](https://matrix.to/#/#rp-rs:matrix.org) + + +## Acknowledgements + +* [Othneil Drew's README template](https://github.com/othneildrew) +* [Rust Embedded Working Group](https://github.com/rust-embedded) +* [Raspberry Pi](https://raspberrypi.org) and the [Pico SDK](https://github.com/raspberrypi/pico-sdk) diff --git a/rp-hal/clippy.toml b/rp-hal/clippy.toml new file mode 100644 index 0000000..cda8d17 --- /dev/null +++ b/rp-hal/clippy.toml @@ -0,0 +1 @@ +avoid-breaking-exported-api = false diff --git a/rp-hal/format.bat b/rp-hal/format.bat new file mode 100644 index 0000000..44d08a1 --- /dev/null +++ b/rp-hal/format.bat @@ -0,0 +1,10 @@ +rem Formats all the files in the repo + +cargo fmt --manifest-path rp2040-hal\Cargo.toml +cargo fmt --manifest-path rp2040-hal-macros\Cargo.toml +cargo fmt --manifest-path rp2040-hal-examples\Cargo.toml +cargo fmt --manifest-path on-target-tests\Cargo.toml +cargo fmt --manifest-path rp235x-hal\Cargo.toml +cargo fmt --manifest-path rp235x-hal-macros\Cargo.toml +cargo fmt --manifest-path rp235x-hal-examples\Cargo.toml +cargo fmt --manifest-path rp-hal-common\Cargo.toml diff --git a/rp-hal/format.sh b/rp-hal/format.sh new file mode 100755 index 0000000..ecf8adb --- /dev/null +++ b/rp-hal/format.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# Formats all the files in the repo + +cargo fmt --manifest-path rp2040-hal/Cargo.toml +cargo fmt --manifest-path rp2040-hal-macros/Cargo.toml +cargo fmt --manifest-path rp2040-hal-examples/Cargo.toml +cargo fmt --manifest-path on-target-tests/Cargo.toml +cargo fmt --manifest-path rp235x-hal/Cargo.toml +cargo fmt --manifest-path rp235x-hal-macros/Cargo.toml +cargo fmt --manifest-path rp235x-hal-examples/Cargo.toml +cargo fmt --manifest-path rp-hal-common/Cargo.toml diff --git a/rp-hal/on-target-tests/.cargo/config.toml b/rp-hal/on-target-tests/.cargo/config.toml new file mode 100644 index 0000000..e548cb6 --- /dev/null +++ b/rp-hal/on-target-tests/.cargo/config.toml @@ -0,0 +1,37 @@ +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +# Set the default target to match the Cortex-M0+ in the RP2040 +target = "thumbv6m-none-eabi" + +# Target specific options +[target.thumbv6m-none-eabi] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as the linker +# script. This is usually provided by the cortex-m-rt crate, and by default +# the version in that crate will include a file called `memory.x` which +# describes the particular memory layout for your specific chip. +# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't +# have SIMD) +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] + +# This runner will make a UF2 file and then copy it to a mounted RP2040 in USB +# Bootloader mode: +runner = "elf2uf2-rs -d" + +# This runner will find a supported SWD debug probe and flash your RP2040 over +# SWD: +# runner = "probe-rs run --chip RP2040" diff --git a/rp-hal/on-target-tests/.gitignore b/rp-hal/on-target-tests/.gitignore new file mode 100644 index 0000000..6ccfefb --- /dev/null +++ b/rp-hal/on-target-tests/.gitignore @@ -0,0 +1,14 @@ +**/*.rs.bk +.#* +.gdb_history +Cargo.lock +target/ + +# editor files +.vscode/* +!.vscode/*.md +!.vscode/*.svd +!.vscode/launch.json +!.vscode/tasks.json +!.vscode/extensions.json +!.vscode/settings.json diff --git a/rp-hal/on-target-tests/Cargo.toml b/rp-hal/on-target-tests/Cargo.toml new file mode 100644 index 0000000..50a734b --- /dev/null +++ b/rp-hal/on-target-tests/Cargo.toml @@ -0,0 +1,78 @@ +[package] +authors = ["The rp-rs Developers"] +description = "Test cases that run on an RP2040" +edition = "2021" +license = "MIT OR Apache-2.0" +name = "on-target-tests" +publish = false +readme = "README.md" +repository = "https://github.com/rp-rs/rp-hal" +version = "0.1.0" + +[[test]] +harness = false +name = "dma_m2m_u8" + +[[test]] +harness = false +name = "dma_m2m_u16" + +[[test]] +harness = false +name = "dma_m2m_u32" + +[[test]] +harness = false +name = "dma_spi_loopback_u8" + +[[test]] +harness = false +name = "dma_spi_loopback_u16" + +[[test]] +harness = false +name = "dma_dyn" + +[[test]] +harness = false +name = "i2c_loopback" + +[[test]] +harness = false +name = "i2c_loopback_async" + +[[test]] +harness = false +name = "gpio" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +critical-section = "1.2.0" +defmt = "0.3" +defmt-rtt = "0.4" +defmt-test = "0.3.1" +embedded-hal = "1.0.0" +embedded-hal-async = "1.0.0" +embedded_hal_0_2 = {package = "embedded-hal", version = "0.2.5", features = ["unproven"]} +fugit = "0.3.6" +futures = {version = "0.3.30", default-features = false, features = ["async-await"]} +heapless = {version = "0.8.0", features = ["portable-atomic-critical-section", "defmt-03"]} +i2c-write-iter = {version = "1.0.0", features = ["async"]} +itertools = {version = "0.12.0", default-features = false} +once_cell = { version = "1.19.0", default-features = false, features = ["critical-section"] } +panic-probe = {version = "0.3", features = ["print-defmt"]} +rp2040-boot2 = "0.3.0" +rp2040-hal = {path = "../rp2040-hal", features = ["critical-section-impl", "defmt", "rt", "i2c-write-iter"]} + +[profile.dev] +codegen-units = 1 +incremental = false +lto = 'fat' +opt-level = 's' + +[profile.test] +codegen-units = 1 +incremental = false +lto = 'fat' +opt-level = 's' diff --git a/rp-hal/on-target-tests/LICENSE-APACHE b/rp-hal/on-target-tests/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rp-hal/on-target-tests/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp-hal/on-target-tests/LICENSE-MIT b/rp-hal/on-target-tests/LICENSE-MIT new file mode 100644 index 0000000..6e052e3 --- /dev/null +++ b/rp-hal/on-target-tests/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp-hal/on-target-tests/NOTICE b/rp-hal/on-target-tests/NOTICE new file mode 100644 index 0000000..790ecb1 --- /dev/null +++ b/rp-hal/on-target-tests/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp-hal/on-target-tests/README.md b/rp-hal/on-target-tests/README.md new file mode 100644 index 0000000..6fe00d3 --- /dev/null +++ b/rp-hal/on-target-tests/README.md @@ -0,0 +1,52 @@ +# Target tests for rp2040-hal + +This project is for running tests of rp2040-hal against real hardware via knurling-rs tools + +Adding a test: +- Add a new Rust program to tests (eg tests/my_new_test.rs) +- Add a new [[test]] to the Cargo.toml + +Running all tests: +Linux (and any other Unix-likes where probe-rs are supported): +```system +cd on-target-tests +./run_tests.sh +``` +Windows +```system +cd on-target-tests +run_tests.bat +``` + +To run a specific test (to make developing tests faster) + +```system +CARGO_TARGET_THUMBV6M_NONE_EABI_RUNNER="probe-rs run" cargo test -p on-target-tests --test my_new_test -- --chip rp2040 +``` + +## Prerequisites + +Some of the tests need connections between specific pins. + +Currently, the following connections are required: + +- Connect GPIO 4 to GPIO 7 (pins 6 and 10 an a Pico) for the SPI loopback tests +- Connect GPIO 0 to GPIO 2 (pins 1 and 4 on a Pico) and + connect GPIO 1 to GPIO 3 (pins 2 and 5 on a Pico) for the I2C loopback tests + +If you add tests that need some hardware setup, make sure that they are +compatible to the existing on-target tests, so all tests can be run with +a single configuration. + +## License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + diff --git a/rp-hal/on-target-tests/memory.x b/rp-hal/on-target-tests/memory.x new file mode 100644 index 0000000..4077aab --- /dev/null +++ b/rp-hal/on-target-tests/memory.x @@ -0,0 +1,15 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; diff --git a/rp-hal/on-target-tests/run_tests.bat b/rp-hal/on-target-tests/run_tests.bat new file mode 100755 index 0000000..f8fcfa6 --- /dev/null +++ b/rp-hal/on-target-tests/run_tests.bat @@ -0,0 +1,6 @@ +@rem Keep running tests even if one of them fails +@rem We need to specify environment variables here to control build since we aren't able to override them in Cargo.toml + +@SET "CARGO_TARGET_THUMBV6M_NONE_EABI_RUNNER=probe-rs run" + +cargo test --no-fail-fast -- --chip rp2040 diff --git a/rp-hal/on-target-tests/run_tests.sh b/rp-hal/on-target-tests/run_tests.sh new file mode 100755 index 0000000..326e799 --- /dev/null +++ b/rp-hal/on-target-tests/run_tests.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +# Keep running tests even if one of them fails +# We need to specify probe-rs as our runner via environment variables here +# to control build since we aren't able to override them in config.toml +CARGO_TARGET_THUMBV6M_NONE_EABI_RUNNER="probe-rs run" cargo test --no-fail-fast -- --chip rp2040 diff --git a/rp-hal/on-target-tests/tests/dma_dyn.rs b/rp-hal/on-target-tests/tests/dma_dyn.rs new file mode 100644 index 0000000..26a486c --- /dev/null +++ b/rp-hal/on-target-tests/tests/dma_dyn.rs @@ -0,0 +1,205 @@ +#![no_std] +#![no_main] +#![cfg(test)] + +use crate::hal::dma::DynChannels; +use defmt_rtt as _; // defmt transport +use defmt_test as _; +use panic_probe as _; +use rp2040_hal as hal; // memory layout // panic handler + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +struct State { + channels: DynChannels, +} + +mod testdata { + #[allow(dead_code)] + pub const ARRAY_U8: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + #[allow(dead_code)] + pub const ARRAY_U16: [u16; 10] = [270, 271, 272, 273, 274, 275, 276, 277, 278, 279]; + #[allow(dead_code)] + pub const ARRAY_U32: [u32; 10] = [ + 65571, 65572, 65573, 65574, 65575, 65576, 65577, 65578, 65579, 65580, + ]; + #[allow(dead_code)] + pub const ARRAY_I8: [i8; 10] = [-1, -2, -3, -4, -5, -6, -7, -8, -9, -10]; + #[allow(dead_code)] + pub const ARRAY_I16: [i16; 10] = [-270, -271, -272, -273, -274, -275, -276, -277, -278, -279]; + #[allow(dead_code)] + pub const ARRAY_I32: [i32; 10] = [ + -65571, -65572, -65573, -65574, -65575, -65576, -65577, -65578, -65579, -65580, + ]; +} + +#[defmt_test::tests] +mod tests { + use crate::testdata; + use crate::State; + use crate::XTAL_FREQ_HZ; + use defmt::assert_eq; + use defmt_rtt as _; + use panic_probe as _; + use rp2040_hal as hal; + + use hal::{clocks::init_clocks_and_plls, pac, watchdog::Watchdog}; + + use rp2040_hal::dma::DMAExt; + + #[init] + fn setup() -> State { + unsafe { + hal::sio::spinlock_reset(); + } + let mut pac = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let _clocks = init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let dma = pac.DMA.dyn_split(&mut pac.RESETS); + + State { channels: dma } + } + + #[test] + fn dyn_m2m_u8(state: &mut State) { + let ch0 = state + .channels + .ch0 + .take() + .expect("Could not take Dyn DMA channel"); + let rx_buffer = cortex_m::singleton!(: [u8; 10] = [0; 10]).unwrap(); + let tx_buffer = cortex_m::singleton!(: [u8; 10] = testdata::ARRAY_U8).unwrap(); + let tx_transfer = hal::dma::single_buffer::Config::new(ch0, tx_buffer, rx_buffer); + let tx_started = tx_transfer.start(); + let (newch0, tx_buffer, rx_buffer) = tx_started.wait(); + let first = tx_buffer.iter(); + let second = rx_buffer.iter(); + for (x, y) in first.zip(second) { + assert_eq!(x, y); + } + state.channels.ch0.replace(newch0); + } + + #[test] + fn dyn_m2m_u16(state: &mut State) { + let ch0 = state + .channels + .ch0 + .take() + .expect("Could not take Dyn DMA channel"); + let rx_buffer = cortex_m::singleton!(: [u16; 10] = [0; 10]).unwrap(); + let tx_buffer = cortex_m::singleton!(: [u16; 10] = testdata::ARRAY_U16).unwrap(); + let tx_transfer = hal::dma::single_buffer::Config::new(ch0, tx_buffer, rx_buffer); + let tx_started = tx_transfer.start(); + let (newch0, tx_buffer, rx_buffer) = tx_started.wait(); + let first = tx_buffer.iter(); + let second = rx_buffer.iter(); + for (x, y) in first.zip(second) { + assert_eq!(x, y); + } + state.channels.ch0.replace(newch0); + } + + #[test] + fn dyn_m2m_u32(state: &mut State) { + let ch0 = state + .channels + .ch0 + .take() + .expect("Could not take Dyn DMA channel"); + let rx_buffer = cortex_m::singleton!(: [u32; 10] = [0; 10]).unwrap(); + let tx_buffer = cortex_m::singleton!(: [u32; 10] = testdata::ARRAY_U32).unwrap(); + let tx_transfer = hal::dma::single_buffer::Config::new(ch0, tx_buffer, rx_buffer); + let tx_started = tx_transfer.start(); + let (newch0, tx_buffer, rx_buffer) = tx_started.wait(); + let first = tx_buffer.iter(); + let second = rx_buffer.iter(); + for (x, y) in first.zip(second) { + assert_eq!(x, y); + } + state.channels.ch0.replace(newch0); + } + + #[test] + fn dyn_m2m_i8(state: &mut State) { + let ch0 = state + .channels + .ch0 + .take() + .expect("Could not take Dyn DMA channel"); + let rx_buffer = cortex_m::singleton!(: [i8; 10] = [0; 10]).unwrap(); + let tx_buffer = cortex_m::singleton!(: [i8; 10] = testdata::ARRAY_I8).unwrap(); + let tx_transfer = hal::dma::single_buffer::Config::new(ch0, tx_buffer, rx_buffer); + let tx_started = tx_transfer.start(); + let (newch0, tx_buffer, rx_buffer) = tx_started.wait(); + let first = tx_buffer.iter(); + let second = rx_buffer.iter(); + for (x, y) in first.zip(second) { + assert_eq!(x, y); + } + state.channels.ch0.replace(newch0); + } + + #[test] + fn dyn_m2m_i16(state: &mut State) { + let ch0 = state + .channels + .ch0 + .take() + .expect("Could not take Dyn DMA channel"); + let rx_buffer = cortex_m::singleton!(: [i16; 10] = [0; 10]).unwrap(); + let tx_buffer = cortex_m::singleton!(: [i16; 10] = testdata::ARRAY_I16).unwrap(); + let tx_transfer = hal::dma::single_buffer::Config::new(ch0, tx_buffer, rx_buffer); + let tx_started = tx_transfer.start(); + let (newch0, tx_buffer, rx_buffer) = tx_started.wait(); + let first = tx_buffer.iter(); + let second = rx_buffer.iter(); + for (x, y) in first.zip(second) { + assert_eq!(x, y); + } + state.channels.ch0.replace(newch0); + } + + #[test] + fn dyn_m2m_i32(state: &mut State) { + let ch0 = state + .channels + .ch0 + .take() + .expect("Could not take Dyn DMA channel"); + let rx_buffer = cortex_m::singleton!(: [i32; 10] = [0; 10]).unwrap(); + let tx_buffer = cortex_m::singleton!(: [i32; 10] = testdata::ARRAY_I32).unwrap(); + let tx_transfer = hal::dma::single_buffer::Config::new(ch0, tx_buffer, rx_buffer); + let tx_started = tx_transfer.start(); + let (newch0, tx_buffer, rx_buffer) = tx_started.wait(); + let first = tx_buffer.iter(); + let second = rx_buffer.iter(); + for (x, y) in first.zip(second) { + assert_eq!(x, y); + } + state.channels.ch0.replace(newch0); + } +} diff --git a/rp-hal/on-target-tests/tests/dma_m2m_u16.rs b/rp-hal/on-target-tests/tests/dma_m2m_u16.rs new file mode 100644 index 0000000..e6ae1f3 --- /dev/null +++ b/rp-hal/on-target-tests/tests/dma_m2m_u16.rs @@ -0,0 +1,99 @@ +#![no_std] +#![no_main] +#![cfg(test)] + +use crate::hal::dma::Channels; +use defmt_rtt as _; // defmt transport +use defmt_test as _; +use panic_probe as _; +use rp2040_hal as hal; // memory layout // panic handler + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +struct State { + channels: Option, +} + +mod testdata { + #[allow(dead_code)] + pub const ARRAY_U8: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + #[allow(dead_code)] + pub const ARRAY_U16: [u16; 10] = [270, 271, 272, 273, 274, 275, 276, 277, 278, 279]; + #[allow(dead_code)] + pub const ARRAY_U32: [u32; 10] = [ + 65571, 65572, 65573, 65574, 65575, 65576, 65577, 65578, 65579, 65580, + ]; + #[allow(dead_code)] + pub const ARRAY_U64: [u64; 10] = [ + 65571, 65572, 65573, 65574, 65575, 65576, 65577, 65578, 65579, 65580, + ]; +} + +#[defmt_test::tests] +mod tests { + use crate::testdata; + use crate::State; + use crate::XTAL_FREQ_HZ; + use defmt::assert_eq; + use defmt_rtt as _; + use panic_probe as _; + use rp2040_hal as hal; + + use hal::{clocks::init_clocks_and_plls, pac, watchdog::Watchdog}; + + use rp2040_hal::dma::DMAExt; + + #[init] + fn setup() -> State { + unsafe { + hal::sio::spinlock_reset(); + } + let mut pac = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let _clocks = init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let dma = pac.DMA.split(&mut pac.RESETS); + + State { + channels: Some(dma), + } + } + + #[test] + fn dma_mem2mem_u16(state: &mut State) { + if let Some(dma) = state.channels.take() { + let rx_buffer = cortex_m::singleton!(: [u16; 10] = [0; 10]).unwrap(); + let tx_buffer = cortex_m::singleton!(: [u16; 10] = testdata::ARRAY_U16).unwrap(); + let tx_transfer = hal::dma::single_buffer::Config::new(dma.ch0, tx_buffer, rx_buffer); + let tx_started = tx_transfer.start(); + let (_ch0, tx_buffer, rx_buffer) = tx_started.wait(); + let first = tx_buffer.iter(); + let second = rx_buffer.iter(); + for (x, y) in first.zip(second) { + assert_eq!(x, y); + } + } + } +} diff --git a/rp-hal/on-target-tests/tests/dma_m2m_u32.rs b/rp-hal/on-target-tests/tests/dma_m2m_u32.rs new file mode 100644 index 0000000..7ea3444 --- /dev/null +++ b/rp-hal/on-target-tests/tests/dma_m2m_u32.rs @@ -0,0 +1,99 @@ +#![no_std] +#![no_main] +#![cfg(test)] + +use crate::hal::dma::Channels; +use defmt_rtt as _; // defmt transport +use defmt_test as _; +use panic_probe as _; +use rp2040_hal as hal; // memory layout // panic handler + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +struct State { + channels: Option, +} + +mod testdata { + #[allow(dead_code)] + pub const ARRAY_U8: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + #[allow(dead_code)] + pub const ARRAY_U16: [u16; 10] = [270, 271, 272, 273, 274, 275, 276, 277, 278, 279]; + #[allow(dead_code)] + pub const ARRAY_U32: [u32; 10] = [ + 65571, 65572, 65573, 65574, 65575, 65576, 65577, 65578, 65579, 65580, + ]; + #[allow(dead_code)] + pub const ARRAY_U64: [u64; 10] = [ + 65571, 65572, 65573, 65574, 65575, 65576, 65577, 65578, 65579, 65580, + ]; +} + +#[defmt_test::tests] +mod tests { + use crate::testdata; + use crate::State; + use crate::XTAL_FREQ_HZ; + use defmt::assert_eq; + use defmt_rtt as _; + use panic_probe as _; + use rp2040_hal as hal; + + use hal::{clocks::init_clocks_and_plls, pac, watchdog::Watchdog}; + + use rp2040_hal::dma::DMAExt; + + #[init] + fn setup() -> State { + unsafe { + hal::sio::spinlock_reset(); + } + let mut pac = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let _clocks = init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let dma = pac.DMA.split(&mut pac.RESETS); + + State { + channels: Some(dma), + } + } + + #[test] + fn dma_mem2mem_u32(state: &mut State) { + if let Some(dma) = state.channels.take() { + let rx_buffer = cortex_m::singleton!(: [u32; 10] = [0; 10]).unwrap(); + let tx_buffer = cortex_m::singleton!(: [u32; 10] = testdata::ARRAY_U32).unwrap(); + let tx_transfer = hal::dma::single_buffer::Config::new(dma.ch0, tx_buffer, rx_buffer); + let tx_started = tx_transfer.start(); + let (_ch0, tx_buffer, rx_buffer) = tx_started.wait(); + let first = tx_buffer.iter(); + let second = rx_buffer.iter(); + for (x, y) in first.zip(second) { + assert_eq!(x, y); + } + } + } +} diff --git a/rp-hal/on-target-tests/tests/dma_m2m_u8.rs b/rp-hal/on-target-tests/tests/dma_m2m_u8.rs new file mode 100644 index 0000000..7d8d5ea --- /dev/null +++ b/rp-hal/on-target-tests/tests/dma_m2m_u8.rs @@ -0,0 +1,99 @@ +#![no_std] +#![no_main] +#![cfg(test)] + +use crate::hal::dma::Channels; +use defmt_rtt as _; // defmt transport +use defmt_test as _; +use panic_probe as _; +use rp2040_hal as hal; // memory layout // panic handler + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +struct State { + channels: Option, +} + +mod testdata { + #[allow(dead_code)] + pub const ARRAY_U8: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + #[allow(dead_code)] + pub const ARRAY_U16: [u16; 10] = [270, 271, 272, 273, 274, 275, 276, 277, 278, 279]; + #[allow(dead_code)] + pub const ARRAY_U32: [u32; 10] = [ + 65571, 65572, 65573, 65574, 65575, 65576, 65577, 65578, 65579, 65580, + ]; + #[allow(dead_code)] + pub const ARRAY_U64: [u64; 10] = [ + 65571, 65572, 65573, 65574, 65575, 65576, 65577, 65578, 65579, 65580, + ]; +} + +#[defmt_test::tests] +mod tests { + use crate::testdata; + use crate::State; + use crate::XTAL_FREQ_HZ; + use defmt::assert_eq; + use defmt_rtt as _; + use panic_probe as _; + use rp2040_hal as hal; + + use hal::{clocks::init_clocks_and_plls, pac, watchdog::Watchdog}; + + use rp2040_hal::dma::DMAExt; + + #[init] + fn setup() -> State { + unsafe { + hal::sio::spinlock_reset(); + } + let mut pac = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let _clocks = init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let dma = pac.DMA.split(&mut pac.RESETS); + + State { + channels: Some(dma), + } + } + + #[test] + fn dma_mem2mem_u8(state: &mut State) { + if let Some(dma) = state.channels.take() { + let rx_buffer = cortex_m::singleton!(: [u8; 10] = [0; 10]).unwrap(); + let tx_buffer = cortex_m::singleton!(: [u8; 10] = testdata::ARRAY_U8).unwrap(); + let tx_transfer = hal::dma::single_buffer::Config::new(dma.ch0, tx_buffer, rx_buffer); + let tx_started = tx_transfer.start(); + let (_ch0, tx_buffer, rx_buffer) = tx_started.wait(); + let first = tx_buffer.iter(); + let second = rx_buffer.iter(); + for (x, y) in first.zip(second) { + assert_eq!(x, y); + } + } + } +} diff --git a/rp-hal/on-target-tests/tests/dma_spi_loopback_u16.rs b/rp-hal/on-target-tests/tests/dma_spi_loopback_u16.rs new file mode 100644 index 0000000..5062e95 --- /dev/null +++ b/rp-hal/on-target-tests/tests/dma_spi_loopback_u16.rs @@ -0,0 +1,136 @@ +//! This test needs a connection between GPIO 4 and GPIO 7 (pins 6 and 10 an a Pico) + +#![no_std] +#![no_main] +#![cfg(test)] + +use crate::hal::dma::Channels; +use defmt_rtt as _; // defmt transport +use defmt_test as _; +use hal::gpio::{self, Pin}; +use panic_probe as _; +use rp2040_hal as hal; // memory layout // panic handler +use rp2040_hal::pac::SPI0; +use rp2040_hal::spi; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +type MISO = Pin; +type MOSI = Pin; +type SCLK = Pin; +struct State { + channels: Option, + spi: Option>, +} + +mod testdata { + #[allow(dead_code)] + pub const ARRAY_U8: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + #[allow(dead_code)] + pub const ARRAY_U16: [u16; 10] = [270, 271, 272, 273, 274, 275, 276, 277, 278, 279]; + #[allow(dead_code)] + pub const ARRAY_U32: [u32; 10] = [ + 65571, 65572, 65573, 65574, 65575, 65576, 65577, 65578, 65579, 65580, + ]; + #[allow(dead_code)] + pub const ARRAY_U64: [u64; 10] = [ + 65571, 65572, 65573, 65574, 65575, 65576, 65577, 65578, 65579, 65580, + ]; +} + +#[defmt_test::tests] +mod tests { + use crate::testdata; + use crate::State; + use crate::XTAL_FREQ_HZ; + use defmt::assert_eq; + use defmt_rtt as _; + use fugit::RateExtU32; + use hal::{clocks::init_clocks_and_plls, pac, watchdog::Watchdog}; + use panic_probe as _; + use rp2040_hal as hal; + use rp2040_hal::dma::{bidirectional, DMAExt}; + use rp2040_hal::Clock; + + #[init] + fn setup() -> State { + unsafe { + hal::sio::spinlock_reset(); + } + let mut pac = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let clocks = init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let dma = pac.DMA.split(&mut pac.RESETS); + + // Setup the pins. + let sio = hal::sio::Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // These are implicitly used by the spi driver if they are in the correct mode + let spi_sclk = pins.gpio6.reconfigure(); + let spi_mosi = pins.gpio7.reconfigure(); + let spi_miso = pins.gpio4.reconfigure(); + let spi = hal::spi::Spi::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); + + // Exchange the uninitialised SPI driver for an initialised one + let spi = spi.init( + &mut pac.RESETS, + clocks.peripheral_clock.freq(), + 16_000_000u32.Hz(), + &embedded_hal::spi::MODE_0, + ); + + State { + channels: Some(dma), + spi: Some(spi), + } + } + + #[test] + fn dma_spi_loopback_u16(state: &mut State) { + if let Some(dma) = state.channels.take() { + if let Some(spi) = state.spi.take() { + let rx_buf = cortex_m::singleton!(: [u16; 10] = [0; 10]).unwrap(); + let tx_buf = cortex_m::singleton!(: [u16; 10] = testdata::ARRAY_U16).unwrap(); + + let transfer = + bidirectional::Config::new((dma.ch0, dma.ch1), tx_buf, spi, rx_buf).start(); + let ((_ch0, _ch1), tx_buf, _spi, rx_buf) = transfer.wait(); + + let first = tx_buf.iter(); + let second = rx_buf.iter(); + for (x, y) in first.zip(second) { + assert_eq!(x, y); + } + } + } + } +} diff --git a/rp-hal/on-target-tests/tests/dma_spi_loopback_u8.rs b/rp-hal/on-target-tests/tests/dma_spi_loopback_u8.rs new file mode 100644 index 0000000..20b0f26 --- /dev/null +++ b/rp-hal/on-target-tests/tests/dma_spi_loopback_u8.rs @@ -0,0 +1,137 @@ +//! This test needs a connection between GPIO 4 and GPIO 7 (pins 6 and 10 an a Pico) + +#![no_std] +#![no_main] +#![cfg(test)] + +use crate::hal::dma::Channels; +use defmt_rtt as _; // defmt transport +use defmt_test as _; +use panic_probe as _; +use rp2040_hal as hal; // memory layout // panic handler +use rp2040_hal::gpio::{self, Pin}; +use rp2040_hal::pac::SPI0; +use rp2040_hal::spi; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +type MISO = Pin; +type MOSI = Pin; +type SCLK = Pin; +struct State { + channels: Option, + spi: Option>, +} + +mod testdata { + #[allow(dead_code)] + pub const ARRAY_U8: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + #[allow(dead_code)] + pub const ARRAY_U16: [u16; 10] = [270, 271, 272, 273, 274, 275, 276, 277, 278, 279]; + #[allow(dead_code)] + pub const ARRAY_U32: [u32; 10] = [ + 65571, 65572, 65573, 65574, 65575, 65576, 65577, 65578, 65579, 65580, + ]; + #[allow(dead_code)] + pub const ARRAY_U64: [u64; 10] = [ + 65571, 65572, 65573, 65574, 65575, 65576, 65577, 65578, 65579, 65580, + ]; +} + +#[defmt_test::tests] +mod tests { + use crate::testdata; + use crate::State; + use crate::XTAL_FREQ_HZ; + use defmt::assert_eq; + use defmt_rtt as _; + use fugit::RateExtU32; + use hal::{clocks::init_clocks_and_plls, pac, watchdog::Watchdog}; + use panic_probe as _; + use rp2040_hal as hal; + use rp2040_hal::dma::bidirectional; + use rp2040_hal::dma::DMAExt; + use rp2040_hal::Clock; + + #[init] + fn setup() -> State { + unsafe { + hal::sio::spinlock_reset(); + } + let mut pac = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let clocks = init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let dma = pac.DMA.split(&mut pac.RESETS); + + // Setup the pins. + let sio = hal::sio::Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Set up our SPI pins into the correct mode + let spi_sclk = pins.gpio6.reconfigure(); + let spi_mosi = pins.gpio7.reconfigure(); + let spi_miso = pins.gpio4.reconfigure(); + let spi = hal::spi::Spi::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); + + // Exchange the uninitialised SPI driver for an initialised one + let spi = spi.init( + &mut pac.RESETS, + clocks.peripheral_clock.freq(), + 16_000_000u32.Hz(), + &embedded_hal::spi::MODE_0, + ); + + State { + channels: Some(dma), + spi: Some(spi), + } + } + + #[test] + fn dma_spi_loopback_u8(state: &mut State) { + if let Some(dma) = state.channels.take() { + if let Some(spi) = state.spi.take() { + let rx_buf = cortex_m::singleton!(: [u8; 10] = [0; 10]).unwrap(); + let tx_buf = cortex_m::singleton!(: [u8; 10] = testdata::ARRAY_U8).unwrap(); + + let transfer = + bidirectional::Config::new((dma.ch0, dma.ch1), tx_buf, spi, rx_buf).start(); + let ((_ch0, _ch1), tx_buf, _spi, rx_buf) = transfer.wait(); + + let first = tx_buf.iter(); + let second = rx_buf.iter(); + for (x, y) in first.zip(second) { + assert_eq!(x, y); + } + } + } + } +} diff --git a/rp-hal/on-target-tests/tests/gpio.rs b/rp-hal/on-target-tests/tests/gpio.rs new file mode 100644 index 0000000..d3930d8 --- /dev/null +++ b/rp-hal/on-target-tests/tests/gpio.rs @@ -0,0 +1,163 @@ +#![no_std] +#![no_main] +#![cfg(test)] + +use defmt_rtt as _; // defmt transport +use defmt_test as _; +use panic_probe as _; +use rp2040_hal as hal; // memory layout // panic handler + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[defmt_test::tests] +mod tests { + use crate::hal; + use crate::hal::clocks::init_clocks_and_plls; + use crate::hal::pac; + use crate::XTAL_FREQ_HZ; + use hal::watchdog::Watchdog; + use rp2040_hal::gpio::{PinGroup, PinState}; + + #[init] + fn setup() -> () { + unsafe { + hal::sio::spinlock_reset(); + } + let mut pac = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let _clocks = init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let sio = hal::Sio::new(pac.SIO); + + let _pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + } + + #[test] + fn check_ie() { + // Safety: Test cases do not run in parallel + let pac = unsafe { pac::Peripherals::steal() }; + for id in 0..=29 { + assert!(pac.PADS_BANK0.gpio(id).read().ie().bit_is_clear()); + } + } + + #[test] + fn check_ie_gets_enabled() { + // Safety: Test cases do not run in parallel + let pac = unsafe { pac::Peripherals::steal() }; + for id in 0..=29 { + let pin = unsafe { + hal::gpio::new_pin(hal::gpio::DynPinId { + bank: hal::gpio::DynBankId::Bank0, + num: id as u8, + }) + }; + let pin = pin + .try_into_function::() + .ok() + .unwrap(); + assert!(pac.PADS_BANK0.gpio(id).read().ie().bit_is_set()); + let pin = pin + .try_into_function::() + .ok() + .unwrap(); + assert!(pac.PADS_BANK0.gpio(id).read().ie().bit_is_clear()); + let pin = pin + .try_into_function::() + .ok() + .unwrap(); + assert!(pac.PADS_BANK0.gpio(id).read().ie().bit_is_set()); + let _pin = pin + .try_into_function::() + .ok() + .unwrap(); + assert!(pac.PADS_BANK0.gpio(id).read().ie().bit_is_clear()); + } + } + + #[test] + fn check_pin_groups() { + // Safety: Test cases do not run in parallel + let mut pac = unsafe { pac::Peripherals::steal() }; + let sio = hal::Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // GPIO (0 <=> 2) and (1 <=> 3) connected together + let mut group = PinGroup::new() + .add_pin(pins.gpio0.into_push_pull_output()) + .add_pin(pins.gpio1.into_push_pull_output()) + .add_pin(pins.gpio2.into_bus_keep_input()) + .add_pin(pins.gpio3.into_bus_keep_input()); + + group.set(PinState::Low); + cortex_m::asm::delay(10); + assert_eq!(group.read(), 0b0000); + group.set(PinState::High); + cortex_m::asm::delay(10); + assert_eq!(group.read(), 0b1111); + group.set(PinState::Low); + cortex_m::asm::delay(10); + assert_eq!(group.read(), 0b0000); + + group.set(PinState::Low); + group.toggle(); + cortex_m::asm::delay(10); + assert_eq!(group.read(), 0b1111); + group.toggle(); + cortex_m::asm::delay(10); + assert_eq!(group.read(), 0b0000); + group.toggle(); + cortex_m::asm::delay(10); + assert_eq!(group.read(), 0b1111); + + group.set_u32(0b0001); + cortex_m::asm::delay(10); + assert_eq!(group.read(), 0b0101); + group.set_u32(0b0010); + cortex_m::asm::delay(10); + assert_eq!(group.read(), 0b1010); + } + + #[test] + fn read_adc() { + use embedded_hal_0_2::adc::OneShot; + + // Safety: Test cases do not run in parallel + let mut pac = unsafe { pac::Peripherals::steal() }; + let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS); + let mut temp_sensor = hal::adc::Adc::take_temp_sensor(&mut adc).unwrap(); + let _temperature: u16 = adc.read(&mut temp_sensor).unwrap(); + } +} diff --git a/rp-hal/on-target-tests/tests/i2c_loopback.rs b/rp-hal/on-target-tests/tests/i2c_loopback.rs new file mode 100644 index 0000000..bad66a8 --- /dev/null +++ b/rp-hal/on-target-tests/tests/i2c_loopback.rs @@ -0,0 +1,128 @@ +//! This test needs a connection between: +//! +//! | from GPIO (pico Pin) | to GPIO (pico Pin) | +//! | -------------------- | ------------------ | +//! | 0 (1) | 2 (4) | +//! | 1 (2) | 3 (5) | + +#![no_std] +#![no_main] +#![cfg(test)] + +use defmt_rtt as _; // defmt transport +use defmt_test as _; +use panic_probe as _; +use rp2040_hal as hal; // memory layout // panic handler + +use hal::pac::interrupt; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +pub mod i2c_tests; + +#[interrupt] +unsafe fn I2C1_IRQ() { + i2c_tests::blocking::peripheral_handler(); +} + +#[defmt_test::tests] +mod tests { + use crate::i2c_tests::{self, blocking::State, ADDR_10BIT, ADDR_7BIT}; + + #[init] + fn setup() -> State { + i2c_tests::blocking::setup(super::XTAL_FREQ_HZ, ADDR_7BIT) + } + + #[test] + fn write(state: &mut State) { + i2c_tests::blocking::write(state, ADDR_7BIT); + i2c_tests::blocking::write(state, ADDR_10BIT); + } + + #[test] + fn write_iter(state: &mut State) { + i2c_tests::blocking::write_iter(state, ADDR_7BIT); + i2c_tests::blocking::write_iter(state, ADDR_10BIT); + } + + #[test] + fn write_iter_read(state: &mut State) { + i2c_tests::blocking::write_iter_read(state, ADDR_7BIT, 1..=1); + i2c_tests::blocking::write_iter_read(state, ADDR_10BIT, 2..=2); + } + + #[test] + fn write_read(state: &mut State) { + i2c_tests::blocking::write_read(state, ADDR_7BIT, 1..=1); + i2c_tests::blocking::write_read(state, ADDR_10BIT, 2..=2); + } + + #[test] + fn read(state: &mut State) { + i2c_tests::blocking::read(state, ADDR_7BIT, 0..=0); + i2c_tests::blocking::read(state, ADDR_10BIT, 1..=1); + } + + #[test] + fn transactions_read(state: &mut State) { + i2c_tests::blocking::transactions_read(state, ADDR_7BIT, 0..=0); + i2c_tests::blocking::transactions_read(state, ADDR_10BIT, 1..=1); + } + + #[test] + fn transactions_write(state: &mut State) { + i2c_tests::blocking::transactions_write(state, ADDR_7BIT); + i2c_tests::blocking::transactions_write(state, ADDR_10BIT); + } + + #[test] + fn transactions_read_write(state: &mut State) { + i2c_tests::blocking::transactions_read_write(state, ADDR_7BIT, 1..=1); + i2c_tests::blocking::transactions_read_write(state, ADDR_10BIT, 2..=2); + } + + #[test] + fn transactions_write_read(state: &mut State) { + i2c_tests::blocking::transactions_write_read(state, ADDR_7BIT, 1..=1); + i2c_tests::blocking::transactions_write_read(state, ADDR_10BIT, 2..=2); + } + + #[test] + fn transaction(state: &mut State) { + i2c_tests::blocking::transaction(state, ADDR_7BIT, 7..=9); + i2c_tests::blocking::transaction(state, ADDR_10BIT, 7..=9); + } + + #[test] + fn transactions_iter(state: &mut State) { + i2c_tests::blocking::transactions_iter(state, ADDR_7BIT, 1..=1); + i2c_tests::blocking::transactions_iter(state, ADDR_10BIT, 2..=2); + } + + #[test] + fn embedded_hal(state: &mut State) { + i2c_tests::blocking::embedded_hal(state, ADDR_7BIT, 2..=2); + i2c_tests::blocking::embedded_hal(state, ADDR_10BIT, 2..=7); + } + + // Sad paths: + // invalid tx buf on write + // invalid rx buf on read + // + // invalid (rx/tx) buf in transactions + // + // Peripheral Nack + // + // Arbritration conflict +} diff --git a/rp-hal/on-target-tests/tests/i2c_loopback_async.rs b/rp-hal/on-target-tests/tests/i2c_loopback_async.rs new file mode 100644 index 0000000..b8d4543 --- /dev/null +++ b/rp-hal/on-target-tests/tests/i2c_loopback_async.rs @@ -0,0 +1,82 @@ +//! This test needs a connection between: +//! +//! | from GPIO (pico Pin) | to GPIO (pico Pin) | +//! | -------------------- | ------------------ | +//! | 0 (1) | 2 (4) | +//! | 1 (2) | 3 (5) | + +#![no_std] +#![no_main] +#![cfg(test)] + +use defmt_rtt as _; // defmt transport +use defmt_test as _; +use panic_probe as _; +use rp2040_hal as hal; // memory layout // panic handler + +use hal::{async_utils::AsyncPeripheral, pac::interrupt}; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +pub mod i2c_tests; + +#[interrupt] +unsafe fn I2C0_IRQ() { + i2c_tests::Controller::on_interrupt(); +} + +#[interrupt] +unsafe fn I2C1_IRQ() { + i2c_tests::Target::on_interrupt(); +} + +#[defmt_test::tests] +mod tests { + use crate::i2c_tests::{ + non_blocking::{self, run_test, State}, + ADDR_10BIT, ADDR_7BIT, + }; + + #[init] + fn setup() -> State { + non_blocking::setup(super::XTAL_FREQ_HZ, ADDR_7BIT) + } + + #[test] + fn embedded_hal(state: &mut State) { + run_test(non_blocking::embedded_hal(state, ADDR_7BIT, 2..=2)); + run_test(non_blocking::embedded_hal(state, ADDR_10BIT, 2..=7)); + } + + #[test] + fn transactions_iter(state: &mut State) { + run_test(non_blocking::transaction(state, ADDR_7BIT, 7..=9)); + run_test(non_blocking::transaction(state, ADDR_10BIT, 7..=14)); + } + + #[test] + fn i2c_write_iter(state: &mut State) { + run_test(non_blocking::transaction_iter(state, ADDR_7BIT)); + run_test(non_blocking::transaction_iter(state, ADDR_10BIT)); + } + + // Sad paths: + // invalid tx buf on write + // invalid rx buf on read + // + // invalid (rx/tx) buf in transactions + // + // Peripheral Nack + // + // Arbritration conflict +} diff --git a/rp-hal/on-target-tests/tests/i2c_tests/blocking.rs b/rp-hal/on-target-tests/tests/i2c_tests/blocking.rs new file mode 100644 index 0000000..13d731c --- /dev/null +++ b/rp-hal/on-target-tests/tests/i2c_tests/blocking.rs @@ -0,0 +1,530 @@ +use core::{cell::RefCell, ops::RangeInclusive}; + +use critical_section::Mutex; +use fugit::{HertzU32, RateExtU32}; + +use rp2040_hal::{ + self as hal, + clocks::init_clocks_and_plls, + gpio::{FunctionI2C, Pin, PullUp}, + i2c::{Error, ValidAddress}, + pac, + watchdog::Watchdog, + Clock, Timer, +}; + +use super::{Controller, FIFOBuffer, Generator, MutexCell, Target, TargetState}; + +pub struct State { + controller: Option, + timer: hal::Timer, + resets: hal::pac::RESETS, + ref_clock_freq: HertzU32, +} + +static TARGET: MutexCell> = Mutex::new(RefCell::new(None)); + +static PAYLOAD: MutexCell = MutexCell::new(RefCell::new(TargetState::new())); +static TIMER: MutexCell> = MutexCell::new(RefCell::new(None)); + +macro_rules! assert_vec_eq { + ($e:expr) => { + critical_section::with(|cs| { + let v = &mut PAYLOAD.borrow_ref_mut(cs).vec; + assert_eq!(*v, $e, "FIFO"); + v.clear(); + }); + }; +} +macro_rules! assert_restart_count { + ($e:expr) => {{ + let restart_cnt: u32 = critical_section::with(|cs| PAYLOAD.borrow_ref(cs).restart_cnt); + defmt::assert!( + $e.contains(&restart_cnt), + "restart count out of range {} ∉ {}", + restart_cnt, + $e + ); + }}; +} + +pub fn setup(xtal_freq_hz: u32, addr: T) -> State { + unsafe { + hal::sio::spinlock_reset(); + } + let mut pac = pac::Peripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let clocks = init_clocks_and_plls( + xtal_freq_hz, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let mut sio = hal::Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let ctrl_sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio0.reconfigure(); + let ctrl_scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio1.reconfigure(); + + let trg_sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio2.reconfigure(); + let trg_scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio3.reconfigure(); + + let i2c_ctrl = hal::I2C::new_controller( + pac.I2C0, + ctrl_sda_pin, + ctrl_scl_pin, + 400.kHz(), + &mut pac.RESETS, + clocks.system_clock.freq(), + ); + let i2c_target = hal::I2C::new_peripheral_event_iterator( + pac.I2C1, + trg_sda_pin, + trg_scl_pin, + &mut pac.RESETS, + addr, + ); + + critical_section::with(|cs| TARGET.replace(cs, Some(i2c_target))); + + static mut STACK: rp2040_hal::multicore::Stack<10240> = rp2040_hal::multicore::Stack::new(); + unsafe { + // delegate I2C1 irqs to core 1 + hal::multicore::Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo) + .cores() + .get_mut(1) + .expect("core 1 is not available") + .spawn(&mut STACK.mem, || { + pac::NVIC::unpend(hal::pac::Interrupt::I2C1_IRQ); + pac::NVIC::unmask(hal::pac::Interrupt::I2C1_IRQ); + + loop { + cortex_m::asm::wfi() + } + }) + .expect("failed to start second core."); + } + + State { + controller: Some(i2c_ctrl), + timer, + resets: pac.RESETS, + ref_clock_freq: clocks.system_clock.freq(), + } +} + +pub fn reset(state: &mut State, addr: T, throttling: bool) -> &mut Controller { + // reset controller + let (i2c, (sda, scl)) = state + .controller + .take() + .expect("State contains a controller") + .free(&mut state.resets); + + // TARGET is shared with core1. Therefore this needs to happen in a cross-core + // critical-section. + critical_section::with(|cs| { + // reset peripheral + let (i2c, (sda, scl)) = TARGET + .replace(cs, None) + .expect("State contains a target") + .free(&mut state.resets); + + // reset payload storage + PAYLOAD.replace_with(cs, |_| TargetState::new()); + + // remove timer/disable throttling + TIMER.replace(cs, throttling.then_some(state.timer)); + + // + TARGET.replace( + cs, + Some(hal::I2C::new_peripheral_event_iterator( + i2c, + sda, + scl, + &mut state.resets, + addr, + )), + ); + }); + + state.controller = Some(hal::I2C::new_controller( + i2c, + sda, + scl, + 400.kHz(), + &mut state.resets, + state.ref_clock_freq, + )); + state + .controller + .as_mut() + .expect("State contains a controller") +} + +/// Wait for the expected count of Stop event to ensure the target side has finished processing +/// requests. +/// +/// If a test ends with a write command, there is a risk that the test will check the content of +/// the shared buffer while the target handler hasn't finished processing its fifo. +pub fn wait_stop_count(stop_cnt: u32) { + while critical_section::with(|cs| PAYLOAD.borrow_ref(cs).stop_cnt) < stop_cnt { + cortex_m::asm::wfe(); + } + defmt::flush(); +} + +pub fn peripheral_handler() { + critical_section::with(|cs| { + let Some(ref mut target) = *TARGET.borrow_ref_mut(cs) else { + return; + }; + + let mut timer = TIMER.borrow_ref_mut(cs); + + while let Some(evt) = target.next_event() { + if let Some(t) = timer.as_mut() { + use embedded_hal_0_2::blocking::delay::DelayUs; + t.delay_us(50); + } + + super::target_handler( + target, + evt, + &mut *PAYLOAD.borrow_ref_mut(cs), + timer.is_some(), + ); + } + }) +} + +pub fn write(state: &mut State, addr: T) { + use embedded_hal_0_2::blocking::i2c::Write; + let controller = reset(state, addr, false); + + let samples: FIFOBuffer = Generator::seq().take(25).collect(); + assert_eq!(controller.write(addr, &samples).is_ok(), true); + wait_stop_count(1); + + assert_restart_count!((0..=0)); + assert_vec_eq!(samples); +} +pub fn write_iter(state: &mut State, addr: T) { + let controller = reset(state, addr, false); + + let samples: FIFOBuffer = Generator::seq().take(25).collect(); + controller + .write_iter(addr, samples.iter().cloned()) + .expect("Successful write_iter"); + wait_stop_count(1); + + assert_restart_count!((0..=0)); + assert_vec_eq!(samples); +} + +pub fn write_iter_read( + state: &mut State, + addr: T, + restart_count: RangeInclusive, +) { + let controller = reset(state, addr, false); + + let samples_seq: FIFOBuffer = Generator::seq().take(25).collect(); + let samples_fib: FIFOBuffer = Generator::fib().take(25).collect(); + let mut v = [0u8; 25]; + controller + .write_iter_read(addr, samples_fib.iter().cloned(), &mut v) + .expect("Successful write_iter_read"); + wait_stop_count(1); + + assert_restart_count!(restart_count); + assert_eq!(v, samples_seq); + assert_vec_eq!(samples_fib); +} + +pub fn write_read(state: &mut State, addr: T, restart_count: RangeInclusive) { + use embedded_hal_0_2::blocking::i2c::WriteRead; + let controller = reset(state, addr, false); + + let samples_seq: FIFOBuffer = Generator::seq().take(25).collect(); + let samples_fib: FIFOBuffer = Generator::fib().take(25).collect(); + let mut v = [0u8; 25]; + controller + .write_read(addr, &samples_fib, &mut v) + .expect("successfully write_read"); + wait_stop_count(1); + + assert_restart_count!(restart_count); + assert_eq!(v, samples_seq); + assert_vec_eq!(samples_fib); +} + +pub fn read(state: &mut State, addr: T, restart_count: RangeInclusive) { + use embedded_hal_0_2::blocking::i2c::Read; + let controller = reset(state, addr, false); + + let mut v = [0u8; 25]; + controller.read(addr, &mut v).expect("successfully read"); + wait_stop_count(1); + + let samples: FIFOBuffer = Generator::fib().take(25).collect(); + assert_restart_count!(restart_count); + assert_eq!(v, samples); + assert_vec_eq!([]); +} + +pub fn transactions_read( + state: &mut State, + addr: T, + restart_count: RangeInclusive, +) { + use embedded_hal::i2c::{I2c, Operation}; + let controller = reset(state, addr, false); + + let mut v = [0u8; 25]; + controller + .transaction(addr, &mut [Operation::Read(&mut v)]) + .expect("successfully write_read"); + wait_stop_count(1); + + let samples: FIFOBuffer = Generator::fib().take(25).collect(); + assert_restart_count!(restart_count); + assert_eq!(v, samples); + assert_vec_eq!([]); +} + +pub fn transactions_write(state: &mut State, addr: T) { + use embedded_hal::i2c::{I2c, Operation}; + let controller = reset(state, addr, false); + + let samples: FIFOBuffer = Generator::seq().take(25).collect(); + controller + .transaction(addr, &mut [Operation::Write(&samples)]) + .expect("successfully write_read"); + wait_stop_count(1); + + assert_restart_count!((0..=0)); + assert_vec_eq!(samples); +} + +pub fn transactions_read_write( + state: &mut State, + addr: T, + restart_count: RangeInclusive, +) { + use embedded_hal::i2c::{I2c, Operation}; + let controller = reset(state, addr, true); + + let samples_seq: FIFOBuffer = Generator::seq().take(25).collect(); + let samples_fib: FIFOBuffer = Generator::fib().take(25).collect(); + let mut v = [0u8; 25]; + controller + .transaction( + addr, + &mut [Operation::Read(&mut v), Operation::Write(&samples_seq)], + ) + .expect("successfully write_read"); + wait_stop_count(1); + + assert_restart_count!(restart_count); + assert_eq!(v, samples_fib); + assert_vec_eq!(samples_seq); +} + +pub fn transactions_write_read( + state: &mut State, + addr: T, + restart_count: RangeInclusive, +) { + use embedded_hal::i2c::{I2c, Operation}; + let controller = reset(state, addr, false); + + let samples_seq: FIFOBuffer = Generator::seq().take(25).collect(); + let mut v = [0u8; 25]; + + controller + .transaction( + addr, + &mut [Operation::Write(&samples_seq), Operation::Read(&mut v)], + ) + .expect("successfully write_read"); + wait_stop_count(1); + + assert_restart_count!(restart_count); + assert_eq!(v, samples_seq); + assert_vec_eq!(samples_seq); +} + +pub fn transaction( + state: &mut State, + addr: T, + restart_count: RangeInclusive, +) { + // Throttling is important for this test as it also ensures that the Target implementation + // does not "waste" bytes that would be discarded otherwise. + // + // One down side of this is that the Target implementation is unable to detect restarts + // between consicutive write operations + use embedded_hal::i2c::{I2c, Operation}; + let controller = reset(state, addr, true); + + let mut v = ([0u8; 14], [0u8; 25], [0u8; 25], [0u8; 14], [0u8; 14]); + let samples: FIFOBuffer = Generator::seq().take(25).collect(); + controller + .transaction( + addr, + &mut [ + Operation::Write(&samples), // goes to v2 + Operation::Read(&mut v.0), + Operation::Read(&mut v.1), + Operation::Read(&mut v.2), + Operation::Write(&samples), // goes to v3 + Operation::Read(&mut v.3), + Operation::Write(&samples), // goes to v4 + Operation::Write(&samples), // remains in buffer + Operation::Write(&samples), // remains in buffer + Operation::Read(&mut v.4), + ], + ) + .expect("successfully write_read"); + wait_stop_count(1); + + // There are 14restarts in this sequence but because of latency in the target handling, it + // may only detect 7. + assert_restart_count!(restart_count); + + // assert writes + let e: FIFOBuffer = itertools::chain!( + samples.iter(), + samples.iter(), + samples.iter(), + samples.iter(), + samples.iter(), + ) + .cloned() + .collect(); + assert_vec_eq!(e); + + // assert reads + let g: FIFOBuffer = Generator::seq().take(92).collect(); + let h: FIFOBuffer = itertools::chain!( + v.0.into_iter(), + v.1.into_iter(), + v.2.into_iter(), + v.3.into_iter(), + v.4.into_iter() + ) + .collect(); + assert_eq!(g, h); +} + +pub fn transactions_iter( + state: &mut State, + addr: T, + restart_count: RangeInclusive, +) { + use embedded_hal::i2c::{I2c, Operation}; + let controller = reset(state, addr, false); + + let samples: FIFOBuffer = Generator::seq().take(25).collect(); + let mut v = [0u8; 25]; + controller + .transaction( + addr, + &mut [Operation::Write(&samples), Operation::Read(&mut v)], + ) + .expect("successfully write_read"); + wait_stop_count(1); + + assert_restart_count!(restart_count); + assert_eq!(v, samples); + assert_vec_eq!(samples); +} + +pub fn embedded_hal( + state: &mut State, + addr: T, + restart_count: RangeInclusive, +) { + // Throttling is important for this test as it also ensures that the Target implementation + // does not "waste" bytes that would be discarded otherwise. + // + // One down side of this is that the Target implementation is unable to detect restarts + // between consicutive write operations + use embedded_hal::i2c::I2c; + let controller = reset(state, addr, true); + + let samples1: FIFOBuffer = Generator::seq().take(25).collect(); + let samples2: FIFOBuffer = Generator::fib().take(14).collect(); + let mut v = ([0; 14], [0; 25], [0; 25], [0; 14], [0; 14]); + + let mut case = || { + controller.write(addr, &samples1)?; + wait_stop_count(1); + controller.read(addr, &mut v.0)?; + wait_stop_count(2); + controller.read(addr, &mut v.1)?; + wait_stop_count(3); + controller.read(addr, &mut v.2)?; + wait_stop_count(4); + controller.write_read(addr, &samples2, &mut v.3)?; + wait_stop_count(5); + controller.write(addr, &samples2)?; + wait_stop_count(6); + controller.write(addr, &samples1)?; + wait_stop_count(7); + controller.write_read(addr, &samples1, &mut v.4)?; + wait_stop_count(8); + Ok::<(), Error>(()) + }; + case().expect("Successful test"); + + // There are 14restarts in this sequence but because of latency in the target handling, it + // may only detect 7. + assert_restart_count!(restart_count); + + // assert writes + let e: FIFOBuffer = itertools::chain!( + Generator::seq().take(25), + Generator::fib().take(14), + Generator::fib().take(14), + Generator::seq().take(25), + Generator::seq().take(25), + ) + .collect(); + assert_vec_eq!(e); + + // assert reads + let g: FIFOBuffer = itertools::chain!( + Generator::seq().take(64), + Generator::fib().take(14), + Generator::seq().take(14) + ) + .collect(); + let h: FIFOBuffer = itertools::chain!( + v.0.into_iter(), + v.1.into_iter(), + v.2.into_iter(), + v.3.into_iter(), + v.4.into_iter() + ) + .collect(); + assert_eq!(g, h); +} diff --git a/rp-hal/on-target-tests/tests/i2c_tests/mod.rs b/rp-hal/on-target-tests/tests/i2c_tests/mod.rs new file mode 100644 index 0000000..7cd1c02 --- /dev/null +++ b/rp-hal/on-target-tests/tests/i2c_tests/mod.rs @@ -0,0 +1,127 @@ +use core::cell::RefCell; + +use critical_section::Mutex; +use rp2040_hal::{ + self as hal, + gpio::{ + bank0::{Gpio0, Gpio1, Gpio2, Gpio3}, + FunctionI2C, Pin, PullUp, + }, + i2c::peripheral::Event, +}; + +pub mod blocking; +pub mod non_blocking; +pub mod test_executor; + +pub const ADDR_7BIT: u8 = 0x2c; +pub const ADDR_10BIT: u16 = 0x12c; + +type P = (Pin, Pin); +pub type Target = hal::I2C, hal::i2c::Peripheral>; +pub type Controller = hal::I2C, hal::i2c::Controller>; +type MutexCell = Mutex>; +type FIFOBuffer = heapless::Vec; + +#[derive(Debug, defmt::Format, Default)] +pub struct TargetState { + first: bool, + gen: Generator, + vec: FIFOBuffer, + restart_cnt: u32, + stop_cnt: u32, +} +impl TargetState { + pub const fn new() -> TargetState { + TargetState { + first: true, + gen: Generator::fib(), + vec: FIFOBuffer::new(), + restart_cnt: 0, + stop_cnt: 0, + } + } +} + +#[derive(Debug, defmt::Format, Clone, Copy)] +pub enum Generator { + Sequence(u8), + Fibonacci(u8, u8), +} +impl Generator { + const fn fib() -> Generator { + Generator::Fibonacci(0, 1) + } + const fn seq() -> Generator { + Generator::Sequence(0) + } + fn switch(&mut self) { + *self = if matches!(self, Generator::Sequence(_)) { + Generator::Fibonacci(0, 1) + } else { + Generator::Sequence(0) + }; + } +} +impl Default for Generator { + fn default() -> Self { + Generator::Sequence(0) + } +} +impl Iterator for Generator { + type Item = u8; + + fn next(&mut self) -> Option { + let out; + match self { + Generator::Sequence(prev) => { + (out, *prev) = (*prev, prev.wrapping_add(1)); + } + Generator::Fibonacci(a, b) => { + out = *a; + (*a, *b) = (*b, a.wrapping_add(*b)); + } + } + Some(out) + } +} + +fn target_handler( + target: &mut Target, + evt: rp2040_hal::i2c::peripheral::Event, + payload: &mut TargetState, + throttle: bool, +) { + let TargetState { + first, + gen, + vec, + restart_cnt, + stop_cnt, + } = payload; + match evt { + Event::Start => *first = true, + Event::Restart => *restart_cnt += 1, + Event::TransferRead => { + let n = throttle.then_some(1).unwrap_or(target.tx_fifo_available()); + let v: FIFOBuffer = gen.take(n.into()).collect(); + target.write(&v); + } + Event::TransferWrite => { + if *first { + gen.switch(); + *first = false; + } + // when throttling, treat 1 byte at a time + let max = throttle + .then_some(1) + .unwrap_or_else(|| target.rx_fifo_used().into()); + vec.extend(target.take(max)); + } + Event::Stop => { + *stop_cnt += 1; + // notify the other core a stop was detected + cortex_m::asm::sev(); + } + } +} diff --git a/rp-hal/on-target-tests/tests/i2c_tests/non_blocking.rs b/rp-hal/on-target-tests/tests/i2c_tests/non_blocking.rs new file mode 100644 index 0000000..87cc111 --- /dev/null +++ b/rp-hal/on-target-tests/tests/i2c_tests/non_blocking.rs @@ -0,0 +1,390 @@ +use core::{ + cell::RefCell, + future::Future, + ops::{Deref, RangeInclusive}, + task::Poll, +}; + +use fugit::{HertzU32, RateExtU32}; +use futures::FutureExt; +use heapless::Vec; + +use rp2040_hal::{ + self as hal, + clocks::init_clocks_and_plls, + gpio::{FunctionI2C, Pin, PullUp}, + i2c::{Error, ValidAddress}, + pac, + watchdog::Watchdog, + Clock, +}; + +use super::{Controller, FIFOBuffer, Generator, Target, TargetState}; + +pub struct State { + controller: Option, + target: Option, + resets: hal::pac::RESETS, + ref_clock_freq: HertzU32, + payload: RefCell, +} + +pub fn run_test(f: impl Future) { + super::test_executor::execute(f); +} +async fn wait_with(payload: &RefCell, mut f: impl FnMut(&TargetState) -> bool) { + while f(payload.borrow().deref()) { + let mut done = false; + core::future::poll_fn(|cx| { + cx.waker().wake_by_ref(); + if !done { + done = true; + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + } +} + +pub fn setup(xtal_freq_hz: u32, addr: T) -> State { + unsafe { + hal::sio::spinlock_reset(); + } + let mut pac = pac::Peripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let clocks = init_clocks_and_plls( + xtal_freq_hz, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let ctrl_sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio0.reconfigure(); + let ctrl_scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio1.reconfigure(); + + let trg_sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio2.reconfigure(); + let trg_scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio3.reconfigure(); + + let i2c_ctrl = hal::I2C::new_controller( + pac.I2C0, + ctrl_sda_pin, + ctrl_scl_pin, + 400.kHz(), + &mut pac.RESETS, + clocks.system_clock.freq(), + ); + let i2c_target = hal::I2C::new_peripheral_event_iterator( + pac.I2C1, + trg_sda_pin, + trg_scl_pin, + &mut pac.RESETS, + addr, + ); + + unsafe { + pac::NVIC::unpend(hal::pac::Interrupt::I2C0_IRQ); + pac::NVIC::unmask(hal::pac::Interrupt::I2C0_IRQ); + pac::NVIC::unpend(hal::pac::Interrupt::I2C1_IRQ); + pac::NVIC::unmask(hal::pac::Interrupt::I2C1_IRQ); + } + + State { + controller: Some(i2c_ctrl), + target: Some(i2c_target), + resets: pac.RESETS, + ref_clock_freq: clocks.system_clock.freq(), + payload: RefCell::new(TargetState::new()), + } +} + +pub fn reset(state: &mut State, addr: T) { + // reset controller + let (i2c, (sda, scl)) = state + .controller + .take() + .expect("controller's missing.") + .free(&mut state.resets); + + let (i2c_t, (sda_t, scl_t)) = state + .target + .take() + .expect("target's missing") + .free(&mut state.resets); + + state.payload.replace(Default::default()); + + state.target = Some(hal::I2C::new_peripheral_event_iterator( + i2c_t, + sda_t, + scl_t, + &mut state.resets, + addr, + )); + + state.controller = Some(hal::I2C::new_controller( + i2c, + sda, + scl, + 400.kHz(), + &mut state.resets, + state.ref_clock_freq, + )); +} + +pub async fn target_handler(payload: &RefCell, target: &mut Target) -> (u32, u32) { + loop { + let evt = target.wait_next().await; + + super::target_handler(target, evt, &mut *payload.borrow_mut(), false); + } +} + +async fn embedded_hal_case( + controller: &mut Controller, + addr: A, + v: &mut ([u8; 25], [u8; 25], [u8; 25], [u8; 14], [u8; 14]), + payload: &RefCell, +) -> Result<(), Error> { + use embedded_hal_async::i2c::I2c; + let sample1: FIFOBuffer = Generator::seq().take(25).collect(); + let sample2: FIFOBuffer = Generator::fib().take(14).collect(); + + // we need to wait for stop to be registered between each operations otherwise we have no + // way to know when the Target side has finished processing the last request. + controller.write(addr, &sample1).await?; + wait_with(payload, |p| p.stop_cnt != 1).await; + + controller.read(addr, &mut v.0).await?; + wait_with(payload, |p| p.stop_cnt != 2).await; + + controller.read(addr, &mut v.1).await?; + wait_with(payload, |p| p.stop_cnt != 3).await; + + controller.read(addr, &mut v.2).await?; + wait_with(payload, |p| p.stop_cnt != 4).await; + + controller.write_read(addr, &sample2, &mut v.3).await?; + wait_with(payload, |p| p.stop_cnt != 5).await; + + controller.write(addr, &sample2).await?; + wait_with(payload, |p| p.stop_cnt != 6).await; + + controller.write(addr, &sample1).await?; + wait_with(payload, |p| p.stop_cnt != 7).await; + + controller.write_read(addr, &sample1, &mut v.4).await?; + wait_with(payload, |p| p.stop_cnt != 8).await; + Ok::<(), Error>(()) +} +pub async fn embedded_hal( + state: &mut State, + addr: T, + restart_count: RangeInclusive, +) { + // Throttling is important for this test as it also ensures that the Target implementation + // does not "waste" bytes that would be discarded otherwise. + // + // One down side of this is that the Target implementation is unable to detect restarts + // between consicutive write operations + reset(state, addr); + + // Test + let mut v = Default::default(); + let ctrl = embedded_hal_case( + state.controller.as_mut().expect("controller's missing."), + addr, + &mut v, + &state.payload, + ); + let trgt = target_handler( + &state.payload, + state.target.as_mut().take().expect("target’s missing"), + ); + futures::select_biased! { + r = ctrl.fuse() => r.expect("Controller test success"), + _ = trgt.fuse() => {} + } + + // Validate + + // There are 14restarts in this sequence but because of latency in the target handling, it + // may only detect 7. + let actual_restart_count = state.payload.borrow().restart_cnt; + assert!( + restart_count.contains(&actual_restart_count), + "restart count out of range {} ∉ {:?}", + actual_restart_count, + restart_count + ); + + // assert writes + let sample1: FIFOBuffer = Generator::seq().take(25).collect(); + let sample2: FIFOBuffer = Generator::fib().take(14).collect(); + let e: FIFOBuffer = itertools::chain!( + sample1.iter(), + sample2.iter(), + sample2.iter(), + sample1.iter(), + sample1.iter(), + ) + .cloned() + .collect(); + assert_eq!(state.payload.borrow().vec, e); + // assert reads + let g: FIFOBuffer = itertools::chain!( + Generator::fib().take(25), + Generator::fib().skip(25 + 7).take(25), + Generator::fib().skip(2 * (25 + 7)).take(25), + Generator::seq().take(14), + Generator::fib().take(14) + ) + .collect(); + let h: FIFOBuffer = itertools::chain!( + v.0.into_iter(), + v.1.into_iter(), + v.2.into_iter(), + v.3.into_iter(), + v.4.into_iter() + ) + .collect(); + assert_eq!(g, h); +} + +pub async fn transaction( + state: &mut State, + addr: A, + restart_count: RangeInclusive, +) { + use embedded_hal::i2c::Operation; + use embedded_hal_async::i2c::I2c; + reset(state, addr); + + // Throttling is important for this test as it also ensures that the Target implementation + // does not "waste" bytes that would be discarded otherwise. + // + // One down side of this is that the Target implementation is unable to detect restarts + // between consicutive write operations + let sample1: Vec = Generator::seq().take(25).collect(); + let sample2: Vec = Generator::fib().take(14).collect(); + + // Test + let mut v: ([u8; 25], [u8; 25], [u8; 25], [u8; 14], [u8; 14]) = Default::default(); + let mut ops = [ + Operation::Write(&sample1), // goes to v2 + Operation::Read(&mut v.0), + Operation::Read(&mut v.1), + Operation::Read(&mut v.2), + Operation::Write(&sample2), // goes to v3 + Operation::Read(&mut v.3), + Operation::Write(&sample2), // goes to v4 + Operation::Write(&sample1), // remains in buffer + Operation::Write(&sample1), // remains in buffer + Operation::Read(&mut v.4), + ]; + + let case = async { + state + .controller + .as_mut() + .expect("controller's missing.") + .transaction(addr, &mut ops) + .await + .expect("Controller test success"); + wait_with(&state.payload, |p| p.stop_cnt != 1).await; + }; + futures::select_biased! { + _ = case.fuse() => {} + _ = target_handler( + &state.payload, + state.target.as_mut().take().expect("target’s missing"), + ).fuse() => {} + } + + // Validate + + // There are 14restarts in this sequence but because of latency in the target handling, it + // may only detect 7. + let actual_restart_count = state.payload.borrow().restart_cnt; + assert!( + restart_count.contains(&actual_restart_count), + "restart count out of range {} ∉ {:?}", + actual_restart_count, + restart_count + ); + // assert writes + let e: FIFOBuffer = itertools::chain!( + Generator::seq().take(25), + Generator::fib().take(14), + Generator::fib().take(14), + Generator::seq().take(25), + Generator::seq().take(25), + ) + .collect(); + assert_eq!(e, state.payload.borrow().vec); + // assert reads + let g: FIFOBuffer = itertools::chain!( + Generator::fib().take(25), + Generator::fib().skip(32).take(25), + Generator::fib().skip(64).take(25), + Generator::fib().skip(96).take(14), + Generator::fib().skip(112).take(14), + ) + .collect(); + let h: FIFOBuffer = itertools::chain!( + v.0.into_iter(), + v.1.into_iter(), + v.2.into_iter(), + v.3.into_iter(), + v.4.into_iter() + ) + .collect(); + assert_eq!(g, h); +} + +pub async fn transaction_iter(state: &mut State, addr: A) { + use i2c_write_iter::non_blocking::I2cIter; + reset(state, addr); + + let samples: FIFOBuffer = Generator::seq().take(25).collect(); + let controller = state.controller.as_mut().expect("controller's missing."); + let case = async { + controller + .transaction_iter( + addr, + [i2c_write_iter::Operation::WriteIter( + samples.iter().cloned(), + )], + ) + .await + .expect("Successful write_iter"); + wait_with(&state.payload, |p| p.stop_cnt != 1).await; + }; + + futures::select_biased! { + _ = case.fuse() => {} + _ = target_handler( + &state.payload, + state.target.as_mut().take().expect("target’s missing"), + ).fuse() => {} + } + + assert_eq!(samples, state.payload.borrow().vec); +} diff --git a/rp-hal/on-target-tests/tests/i2c_tests/test_executor.rs b/rp-hal/on-target-tests/tests/i2c_tests/test_executor.rs new file mode 100644 index 0000000..dfd1328 --- /dev/null +++ b/rp-hal/on-target-tests/tests/i2c_tests/test_executor.rs @@ -0,0 +1,79 @@ +//! Simplistic test executor +//! +//! Compared to a real executor, this has some limitations: +//! +//! - Can only run to completion (like block_on, but without busy polling) +//! - Can't spawn additional tasks +//! - Must not be called multiple times concurrently + +use core::{ + future::Future, + pin::{self, Pin}, + ptr, + sync::atomic::{AtomicBool, Ordering}, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +}; + +use once_cell::sync::OnceCell; + +static WOKE: AtomicBool = AtomicBool::new(false); +static POLLING: AtomicBool = AtomicBool::new(false); + +static VTABLE: RawWakerVTable = RawWakerVTable::new(clone_fn, wake_fn, wake_fn, drop_fn); + +fn wake_fn(_data: *const ()) { + if !POLLING.load(Ordering::Relaxed) { + defmt::info!("waker called while not polling"); + } + WOKE.store(true, Ordering::Relaxed); +} + +fn clone_fn(data: *const ()) -> RawWaker { + RawWaker::new(data, &VTABLE) +} + +fn drop_fn(_data: *const ()) {} + +fn context() -> Context<'static> { + static WAKER: OnceCell = OnceCell::new(); + // Safety: The functions in the vtable of this executor only modify static atomics. + let waker = + WAKER.get_or_init(|| unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &VTABLE)) }); + + // Starting from rust 1.82.0, this could be used: + // static WAKER: Waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &VTABLE)) }; + // (stabilized by https://github.com/rust-lang/rust/pull/128228) + + Context::from_waker(waker) +} + +/// Run future to completion +/// +/// poll() will only be called when the waker was invoked, so this is suitable to test +/// if the waker is properly triggered from an interrupt. +/// +/// This won't work as expected of multiple calls to `execute` happen concurrently. +/// +/// (Calling this function from multiple threads concurrently doesn't violate any +/// safety guarantees, but wakers may wake the wrong task, making futures stall.) +pub fn execute(future: impl Future) -> T { + let mut pinned: Pin<&mut _> = pin::pin!(future); + if WOKE.load(Ordering::Relaxed) { + defmt::info!("woken before poll - ignoring"); + } + POLLING.store(true, Ordering::Relaxed); + loop { + WOKE.store(false, Ordering::Relaxed); + if let Poll::Ready(result) = pinned.as_mut().poll(&mut context()) { + WOKE.store(false, Ordering::Relaxed); + POLLING.store(false, Ordering::Relaxed); + break result; + } + while !WOKE.load(Ordering::Relaxed) { + // In a real executor, there should be a WFI/WFE or similar here, to avoid + // busy looping. + // As this is only a test executor, we don't care. + core::hint::spin_loop(); + } + } +} diff --git a/rp-hal/rp-binary-info/Cargo.toml b/rp-hal/rp-binary-info/Cargo.toml new file mode 100644 index 0000000..f240a6d --- /dev/null +++ b/rp-hal/rp-binary-info/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rp-binary-info" +version = "0.1.0" +edition = "2021" +authors = ["The rp-rs Developers"] +homepage = "https://github.com/rp-rs/rp-hal" +description = "Code and types for creating Picotool compatible Binary Info metadata" +license = "MIT OR Apache-2.0" +rust-version = "1.79" +repository = "https://github.com/rp-rs/rp-hal" +categories = ["embedded", "hardware-support", "no-std", "no-std::no-alloc"] +keywords = ["embedded", "raspberry-pi", "rp2040", "rp2350", "picotool"] + +[features] +binary-info = [] + +[dependencies] diff --git a/rp-hal/rp-binary-info/LICENSE-APACHE b/rp-hal/rp-binary-info/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rp-hal/rp-binary-info/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp-hal/rp-binary-info/LICENSE-MIT b/rp-hal/rp-binary-info/LICENSE-MIT new file mode 100644 index 0000000..6e052e3 --- /dev/null +++ b/rp-hal/rp-binary-info/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp-hal/rp-binary-info/NOTICE b/rp-hal/rp-binary-info/NOTICE new file mode 100644 index 0000000..790ecb1 --- /dev/null +++ b/rp-hal/rp-binary-info/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp-hal/rp-binary-info/README.md b/rp-hal/rp-binary-info/README.md new file mode 100644 index 0000000..68bcfcb --- /dev/null +++ b/rp-hal/rp-binary-info/README.md @@ -0,0 +1,15 @@ +# `rp-binary-info` + +Code and types for creating [Picotool](https://github.com/raspberrypi/picotool) compatible "Binary Info" metadata. + +# License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/rp-hal/rp-binary-info/src/consts.rs b/rp-hal/rp-binary-info/src/consts.rs new file mode 100644 index 0000000..c8270c0 --- /dev/null +++ b/rp-hal/rp-binary-info/src/consts.rs @@ -0,0 +1,31 @@ +//! Constants for binary info + +/// All Raspberry Pi specified IDs have this tag. +/// +/// You can create your own for custom fields. +pub const TAG_RASPBERRY_PI: u16 = super::make_tag(b"RP"); + +/// Used to note the program name - use with StringEntry +pub const ID_RP_PROGRAM_NAME: u32 = 0x02031c86; +/// Used to note the program version - use with StringEntry +pub const ID_RP_PROGRAM_VERSION_STRING: u32 = 0x11a9bc3a; +/// Used to note the program build date - use with StringEntry +pub const ID_RP_PROGRAM_BUILD_DATE_STRING: u32 = 0x9da22254; +/// Used to note the size of the binary - use with IntegerEntry +pub const ID_RP_BINARY_END: u32 = 0x68f465de; +/// Used to note a URL for the program - use with StringEntry +pub const ID_RP_PROGRAM_URL: u32 = 0x1856239a; +/// Used to note a description of the program - use with StringEntry +pub const ID_RP_PROGRAM_DESCRIPTION: u32 = 0xb6a07c19; +/// Used to note some feature of the program - use with StringEntry +pub const ID_RP_PROGRAM_FEATURE: u32 = 0xa1f4b453; +/// Used to note some whether this was a Debug or Release build - use with StringEntry +pub const ID_RP_PROGRAM_BUILD_ATTRIBUTE: u32 = 0x4275f0d3; +/// Used to note the Pico SDK version used - use with StringEntry +pub const ID_RP_SDK_VERSION: u32 = 0x5360b3ab; +/// Used to note which board this program targets - use with StringEntry +pub const ID_RP_PICO_BOARD: u32 = 0xb63cffbb; +/// Used to note which `boot2` image this program uses - use with StringEntry +pub const ID_RP_BOOT2_NAME: u32 = 0x7f8882e1; + +// End of file diff --git a/rp-hal/rp-binary-info/src/lib.rs b/rp-hal/rp-binary-info/src/lib.rs new file mode 100644 index 0000000..6d20a24 --- /dev/null +++ b/rp-hal/rp-binary-info/src/lib.rs @@ -0,0 +1,251 @@ +//! Code and types for creating Picotool compatible "Binary Info" metadata +//! +//! ## Example usage +//! +//! Enable the Cargo feature `binary-info`. +//! +//! Add this to your linker script (usually named `memory.x`): +//! +//! ```not_rust,ignore +//! SECTIONS { +//! /* ### Boot ROM info +//! * +//! * Goes after .vector_table, to keep it in the first 512 bytes of flash, +//! * where picotool can find it +//! */ +//! .boot_info : ALIGN(4) +//! { +//! KEEP(*(.boot_info)); +//! } > FLASH +//! +//! } INSERT AFTER .vector_table; +//! +//! /* move .text to start /after/ the boot info */ +//! _stext = ADDR(.boot_info) + SIZEOF(.boot_info); +//! +//! SECTIONS { +//! /* ### Picotool 'Binary Info' Entries +//! * +//! * Picotool looks through this block (as we have pointers to it in our +//! * header) to find interesting information. +//! */ +//! .bi_entries : ALIGN(4) +//! { +//! /* We put this in the header */ +//! __bi_entries_start = .; +//! /* Here are the entries */ +//! KEEP(*(.bi_entries)); +//! /* Keep this block a nice round size */ +//! . = ALIGN(4); +//! /* We put this in the header */ +//! __bi_entries_end = .; +//! } > FLASH +//! } INSERT AFTER .text; +//! ``` +//! +//! Then, add this to your Rust code: +//! +//! ``` +//! #[link_section = ".bi_entries"] +//! #[used] +//! pub static PICOTOOL_ENTRIES: [rp_binary_info::EntryAddr; 3] = [ +//! rp_binary_info::rp_program_name!(c"Program Name Here"), +//! rp_binary_info::rp_cargo_version!(), +//! rp_binary_info::int!( +//! rp_binary_info::make_tag(b"JP"), +//! 0x0000_0001, +//! 0x12345678 +//! ), +//! ]; +//! ``` +//! +//! ## Cargo features +//! +//! The `binary-info` Cargo feature enables emitting the main `PICOTOOL_HEADER` +//! static, which is what Picotool looks for to discover the binary info. +//! +//! It is optional to allow you to emit the static yourself differently, for e.g. +//! compatibility with different linker scripts, while still allowing using the +//! rest of the utilities in the crate to format the info. + +#![no_std] +#![warn(missing_docs)] + +pub mod consts; + +mod types; +pub use types::*; + +#[macro_use] +mod macros; + +extern "C" { + /// The linker script sets this symbol to have the address of the first + /// entry in the `.bi_entries` section. + static __bi_entries_start: EntryAddr; + /// The linker script sets this symbol to have the address just past the + /// last entry in the `.bi_entries` section. + static __bi_entries_end: EntryAddr; + /// The linker script sets this symbol to have the address of the first + /// entry in the `.data` section. + static __sdata: u32; + /// The linker script sets this symbol to have the address just past the + /// first entry in the `.data` section. + static __edata: u32; + /// The linker script sets this symbol to have the address of the + /// initialisation data for the first entry in the `.data` section (i.e. a + /// flash address, not a RAM address). + static __sidata: u32; +} + +/// Picotool can find this block in our ELF file and report interesting +/// metadata. +/// +/// The data here tells picotool the start and end flash addresses of our +/// metadata. +#[link_section = ".boot_info"] +#[cfg(feature = "binary-info")] +#[used] +#[allow(unused_unsafe)] // addr_of! is safe since rust 1.82.0 +pub static PICOTOOL_HEADER: Header = unsafe { + Header::new( + core::ptr::addr_of!(__bi_entries_start), + core::ptr::addr_of!(__bi_entries_end), + &MAPPING_TABLE, + ) +}; + +/// This tells picotool how to convert RAM addresses back into Flash addresses +pub static MAPPING_TABLE: [MappingTableEntry; 2] = [ + // This is the entry for .data + #[allow(unused_unsafe)] + MappingTableEntry { + source_addr_start: unsafe { core::ptr::addr_of!(__sidata) }, + dest_addr_start: unsafe { core::ptr::addr_of!(__sdata) }, + dest_addr_end: unsafe { core::ptr::addr_of!(__edata) }, + }, + // This is the terminating marker + MappingTableEntry::null(), +]; + +/// Create a 'Binary Info' entry containing the program name +/// +/// This is well-known to picotool, and will be displayed if you run `picotool info`. +/// +/// * Tag: [`consts::TAG_RASPBERRY_PI`] +/// * ID: [`consts::ID_RP_PROGRAM_NAME`] +pub const fn rp_program_name(name: &'static core::ffi::CStr) -> StringEntry { + StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_PROGRAM_NAME, name) +} + +/// Create a 'Binary Info' entry containing the program version. +/// +/// * Tag: [`consts::TAG_RASPBERRY_PI`] +/// * Id: [`consts::ID_RP_PROGRAM_VERSION_STRING`] +pub const fn rp_program_version(name: &'static core::ffi::CStr) -> StringEntry { + StringEntry::new( + consts::TAG_RASPBERRY_PI, + consts::ID_RP_PROGRAM_VERSION_STRING, + name, + ) +} + +/// Create a 'Binary Info' entry with a URL +/// +/// * Tag: [`consts::TAG_RASPBERRY_PI`] +/// * Id: [`consts::ID_RP_PROGRAM_URL`] +pub const fn rp_program_url(url: &'static core::ffi::CStr) -> StringEntry { + StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_PROGRAM_URL, url) +} + +/// Create a 'Binary Info' with the program build date +/// +/// * Tag: [`consts::TAG_RASPBERRY_PI`] +/// * Id: [`consts::ID_RP_PROGRAM_BUILD_DATE_STRING`] +pub const fn rp_program_build_date_string(value: &'static core::ffi::CStr) -> StringEntry { + StringEntry::new( + consts::TAG_RASPBERRY_PI, + consts::ID_RP_PROGRAM_BUILD_DATE_STRING, + value, + ) +} + +/// Create a 'Binary Info' with the size of the binary +/// +/// * Tag: [`consts::TAG_RASPBERRY_PI`] +/// * Id: [`consts::ID_RP_BINARY_END`] +pub const fn rp_binary_end(value: u32) -> IntegerEntry { + IntegerEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_BINARY_END, value) +} + +/// Create a 'Binary Info' with a description of the program +/// +/// * Tag: [`consts::TAG_RASPBERRY_PI`] +/// * Id: [`consts::ID_RP_PROGRAM_DESCRIPTION`] +pub const fn rp_program_description(value: &'static core::ffi::CStr) -> StringEntry { + StringEntry::new( + consts::TAG_RASPBERRY_PI, + consts::ID_RP_PROGRAM_DESCRIPTION, + value, + ) +} + +/// Create a 'Binary Info' with some feature of the program +/// +/// * Tag: [`consts::TAG_RASPBERRY_PI`] +/// * Id: [`consts::ID_RP_PROGRAM_FEATURE`] +pub const fn rp_program_feature(value: &'static core::ffi::CStr) -> StringEntry { + StringEntry::new( + consts::TAG_RASPBERRY_PI, + consts::ID_RP_PROGRAM_FEATURE, + value, + ) +} + +/// Create a 'Binary Info' with some whether this was a Debug or Release build +/// +/// * Tag: [`consts::TAG_RASPBERRY_PI`] +/// * Id: [`consts::ID_RP_PROGRAM_BUILD_ATTRIBUTE`] +pub const fn rp_program_build_attribute(value: &'static core::ffi::CStr) -> StringEntry { + StringEntry::new( + consts::TAG_RASPBERRY_PI, + consts::ID_RP_PROGRAM_BUILD_ATTRIBUTE, + value, + ) +} + +/// Create a 'Binary Info' with the Pico SDK version used +/// +/// * Tag: [`consts::TAG_RASPBERRY_PI`] +/// * Id: [`consts::ID_RP_SDK_VERSION`] +pub const fn rp_sdk_version(value: &'static core::ffi::CStr) -> StringEntry { + StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_SDK_VERSION, value) +} + +/// Create a 'Binary Info' with which board this program targets +/// +/// * Tag: [`consts::TAG_RASPBERRY_PI`] +/// * Id: [`consts::ID_RP_PICO_BOARD`] +pub const fn rp_pico_board(value: &'static core::ffi::CStr) -> StringEntry { + StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_PICO_BOARD, value) +} + +/// Create a 'Binary Info' with which `boot2` image this program uses +/// +/// * Tag: [`consts::TAG_RASPBERRY_PI`] +/// * Id: [`consts::ID_RP_BOOT2_NAME`] +pub const fn rp_boot2_name(value: &'static core::ffi::CStr) -> StringEntry { + StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_BOOT2_NAME, value) +} + +/// Create a tag from two ASCII letters. +/// +/// ``` +/// let tag = rp_binary_info::make_tag(b"RP"); +/// assert_eq!(tag, 0x5052); +/// ``` +pub const fn make_tag(c: &[u8; 2]) -> u16 { + u16::from_le_bytes(*c) +} + +// End of file diff --git a/rp-hal/rp-binary-info/src/macros.rs b/rp-hal/rp-binary-info/src/macros.rs new file mode 100644 index 0000000..7431cf4 --- /dev/null +++ b/rp-hal/rp-binary-info/src/macros.rs @@ -0,0 +1,169 @@ +//! Handy macros for making Binary Info entries + +/// Generate a static item containing the given environment variable, +/// and return its [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! env { + ($tag:expr, $id:expr, $env_var_name:expr) => { + $crate::str!($tag, $id, { + let value = concat!(env!($env_var_name), "\0"); + // # Safety + // + // We used `concat!` to null-terminate on the line above. + let value_cstr = + unsafe { core::ffi::CStr::from_bytes_with_nul_unchecked(value.as_bytes()) }; + value_cstr + }) + }; +} + +/// Generate a static item containing the given string, and return its +/// [`EntryAddr`](super::EntryAddr). +/// +/// You must pass a numeric tag, a numeric ID, and `&CStr` (which is always +/// null-terminated). +#[macro_export] +macro_rules! str { + ($tag:expr, $id:expr, $str:expr) => {{ + static ENTRY: $crate::StringEntry = $crate::StringEntry::new($tag, $id, $str); + ENTRY.addr() + }}; +} + +/// Generate a static item containing the given string, and return its +/// [`EntryAddr`](super::EntryAddr). +/// +/// You must pass a numeric tag, a numeric ID, and `&CStr` (which is always +/// null-terminated). +#[macro_export] +macro_rules! int { + ($tag:expr, $id:expr, $int:expr) => {{ + static ENTRY: $crate::IntegerEntry = $crate::IntegerEntry::new($tag, $id, $int); + ENTRY.addr() + }}; +} + +/// Generate a static item containing the program name, and return its +/// [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! rp_program_name { + ($name:expr) => { + $crate::str!( + $crate::consts::TAG_RASPBERRY_PI, + $crate::consts::ID_RP_PROGRAM_NAME, + $name + ) + }; +} + +/// Generate a static item containing the `CARGO_BIN_NAME` as the program name, +/// and return its [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! rp_cargo_bin_name { + () => { + $crate::env!( + $crate::consts::TAG_RASPBERRY_PI, + $crate::consts::ID_RP_PROGRAM_NAME, + "CARGO_BIN_NAME" + ) + }; +} + +/// Generate a static item containing the program version, and return its +/// [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! rp_program_version { + ($version:expr) => {{ + $crate::str!( + $crate::consts::TAG_RASPBERRY_PI, + $crate::consts::ID_RP_PROGRAM_VERSION, + $version + ) + }}; +} + +/// Generate a static item containing the `CARGO_PKG_VERSION` as the program +/// version, and return its [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! rp_cargo_version { + () => { + $crate::env!( + $crate::consts::TAG_RASPBERRY_PI, + $crate::consts::ID_RP_PROGRAM_VERSION_STRING, + "CARGO_PKG_VERSION" + ) + }; +} + +/// Generate a static item containing the program URL, and return its +/// [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! rp_program_url { + ($url:expr) => { + $crate::str!( + $crate::consts::TAG_RASPBERRY_PI, + $crate::consts::ID_RP_PROGRAM_URL, + $url + ) + }; +} + +/// Generate a static item containing the `CARGO_PKG_HOMEPAGE` as the program URL, +/// and return its [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! rp_cargo_homepage_url { + () => { + $crate::env!( + $crate::consts::TAG_RASPBERRY_PI, + $crate::consts::ID_RP_PROGRAM_URL, + "CARGO_PKG_HOMEPAGE" + ) + }; +} + +/// Generate a static item containing the program description, and return its +/// [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! rp_program_description { + ($description:expr) => { + $crate::str!( + $crate::consts::TAG_RASPBERRY_PI, + $crate::consts::ID_RP_PROGRAM_DESCRIPTION, + $description + ) + }; +} + +/// Generate a static item containing whether this is a debug or a release +/// build, and return its [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! rp_program_build_attribute { + () => { + $crate::str!( + $crate::consts::TAG_RASPBERRY_PI, + $crate::consts::ID_RP_PROGRAM_BUILD_ATTRIBUTE, + { + if cfg!(debug_assertions) { + c"debug" + } else { + c"release" + } + } + ) + }; +} + +/// Generate a static item containing the specific board this program runs on, +/// and return its [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! rp_pico_board { + ($board:expr) => { + $crate::str!( + $crate::consts::TAG_RASPBERRY_PI, + $crate::consts::ID_RP_PICO_BOARD, + $board + ) + }; +} + +// End of file diff --git a/rp-hal/rp-binary-info/src/types.rs b/rp-hal/rp-binary-info/src/types.rs new file mode 100644 index 0000000..d2b192e --- /dev/null +++ b/rp-hal/rp-binary-info/src/types.rs @@ -0,0 +1,192 @@ +//! Types for the Binary Info system + +/// This is the 'Binary Info' header block that `picotool` looks for in your UF2 +/// file/ELF file/Pico in Bootloader Mode to give you useful metadata about your +/// program. +/// +/// It should be placed in the first 4096 bytes of flash, so use your `memory.x` +/// to insert a section between `.text` and `.vector_table` and put a static +/// value of this type in that section. +#[repr(C)] +pub struct Header { + /// Must be equal to Picotool::MARKER_START + marker_start: u32, + /// The first in our table of pointers to Entries + entries_start: *const EntryAddr, + /// The last in our table of pointers to Entries + entries_end: *const EntryAddr, + /// The first entry in a null-terminated RAM/Flash mapping table + mapping_table: *const MappingTableEntry, + /// Must be equal to Picotool::MARKER_END + marker_end: u32, +} + +impl Header { + /// This is the `BINARY_INFO_MARKER_START` magic value from `picotool` + const MARKER_START: u32 = 0x7188ebf2; + /// This is the `BINARY_INFO_MARKER_END` magic value from `picotool` + const MARKER_END: u32 = 0xe71aa390; + + /// Create a new `picotool` compatible header. + /// + /// * `entries_start` - the first [`EntryAddr`] in the table + /// * `entries_end` - the last [`EntryAddr`] in the table + /// * `mapping_table` - the RAM/Flash address mapping table + pub const fn new( + entries_start: *const EntryAddr, + entries_end: *const EntryAddr, + mapping_table: &'static [MappingTableEntry], + ) -> Self { + let mapping_table = mapping_table.as_ptr(); + Self { + marker_start: Self::MARKER_START, + entries_start, + entries_end, + mapping_table, + marker_end: Self::MARKER_END, + } + } +} + +// We need this as rustc complains that is is unsafe to share `*const u32` +// pointers between threads. We only allow these to be created with static +// data, so this is OK. +unsafe impl Sync for Header {} + +/// This is a reference to an entry. It's like a `&dyn` ref to some type `T: +/// Entry`, except that the run-time type information is encoded into the +/// Entry itself in very specific way. +#[repr(transparent)] +pub struct EntryAddr(*const u32); + +// We need this as rustc complains that is is unsafe to share `*const u32` +// pointers between threads. We only allow these to be created with static +// data, so this is OK. +unsafe impl Sync for EntryAddr {} + +/// Allows us to tell picotool where values are in the UF2 given their run-time +/// address. +/// +/// The most obvious example is RAM variables, which must be found in the +/// `.data` section of the UF2. +#[repr(C)] +pub struct MappingTableEntry { + /// The start address in RAM (or wherever the address picotool finds will + /// point) + pub source_addr_start: *const u32, + /// The start address in flash (or whever the data actually lives in the + /// ELF) + pub dest_addr_start: *const u32, + /// The end address in flash + pub dest_addr_end: *const u32, +} + +impl MappingTableEntry { + /// Generate a null entry to mark the end of the list + pub const fn null() -> MappingTableEntry { + MappingTableEntry { + source_addr_start: core::ptr::null(), + dest_addr_start: core::ptr::null(), + dest_addr_end: core::ptr::null(), + } + } +} + +// We need this as rustc complains that is is unsafe to share `*const u32` +// pointers between threads. We only allow these to be created with static +// data, so this is OK. +unsafe impl Sync for MappingTableEntry {} + +/// This is the set of data types that `picotool` supports. +#[repr(u16)] +pub enum DataType { + /// Raw data + Raw = 1, + /// Data with a size + SizedData = 2, + /// A list of binary data + BinaryInfoListZeroTerminated = 3, + /// A BSON encoded blob + Bson = 4, + /// An Integer with an ID + IdAndInt = 5, + /// A string with an Id + IdAndString = 6, + /// A block device + BlockDevice = 7, + /// GPIO pins, with their function + PinsWithFunction = 8, + /// GPIO pins, with their name + PinsWithName = 9, + /// GPIO pins, with multiple names? + PinsWithNames = 10, +} + +/// All Entries start with this common header +#[repr(C)] +struct EntryCommon { + data_type: DataType, + tag: u16, +} + +/// An entry which contains both an ID (e.g. `ID_RP_PROGRAM_NAME`) and a pointer +/// to a null-terminated string. +#[repr(C)] +pub struct StringEntry { + header: EntryCommon, + id: u32, + value: *const core::ffi::c_char, +} + +impl StringEntry { + /// Create a new `StringEntry` + pub const fn new(tag: u16, id: u32, value: &'static core::ffi::CStr) -> StringEntry { + StringEntry { + header: EntryCommon { + data_type: DataType::IdAndString, + tag, + }, + id, + value: value.as_ptr(), + } + } + + /// Get this entry's address + pub const fn addr(&self) -> EntryAddr { + EntryAddr(self as *const Self as *const u32) + } +} + +// We need this as rustc complains that is is unsafe to share `*const +// core::ffi::c_char` pointers between threads. We only allow these to be +// created with static string slices, so it's OK. +unsafe impl Sync for StringEntry {} + +/// An entry which contains both an ID (e.g. `ID_RP_BINARY_END`) and an integer. +#[repr(C)] +pub struct IntegerEntry { + header: EntryCommon, + id: u32, + value: u32, +} + +impl IntegerEntry { + /// Create a new `StringEntry` + pub const fn new(tag: u16, id: u32, value: u32) -> IntegerEntry { + IntegerEntry { + header: EntryCommon { + data_type: DataType::IdAndInt, + tag, + }, + id, + value, + } + } + + /// Get this entry's address + pub const fn addr(&self) -> EntryAddr { + EntryAddr(self as *const Self as *const u32) + } +} + +// End of file diff --git a/rp-hal/rp-hal-common/Cargo.toml b/rp-hal/rp-hal-common/Cargo.toml new file mode 100644 index 0000000..142f007 --- /dev/null +++ b/rp-hal/rp-hal-common/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["The rp-rs Developers"] +description = "Shared HAL code for the Raspberry Pi microcontrollers" +edition = "2021" +homepage = "https://github.com/rp-rs/rp-hal" +license = "MIT OR Apache-2.0" +name = "rp-hal-common" +repository = "https://github.com/rp-rs/rp-hal" +rust-version = "1.79" +version = "0.1.0" + +# DO NOT LIST ANY PAC CRATES OR ARCHITECTURE CRATES HERE +[dependencies] +fugit = "0.3.7" diff --git a/rp-hal/rp-hal-common/LICENSE-APACHE b/rp-hal/rp-hal-common/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rp-hal/rp-hal-common/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp-hal/rp-hal-common/LICENSE-MIT b/rp-hal/rp-hal-common/LICENSE-MIT new file mode 100644 index 0000000..6e052e3 --- /dev/null +++ b/rp-hal/rp-hal-common/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp-hal/rp-hal-common/NOTICE b/rp-hal/rp-hal-common/NOTICE new file mode 100644 index 0000000..790ecb1 --- /dev/null +++ b/rp-hal/rp-hal-common/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp-hal/rp-hal-common/README.md b/rp-hal/rp-hal-common/README.md new file mode 100644 index 0000000..f9f4e09 --- /dev/null +++ b/rp-hal/rp-hal-common/README.md @@ -0,0 +1,15 @@ +# `rp-hal-common` + +Code and types useful to both rp2040-hal and rp235x-hal. + +# License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/rp-hal/rp-hal-common/src/lib.rs b/rp-hal/rp-hal-common/src/lib.rs new file mode 100644 index 0000000..7c41ebe --- /dev/null +++ b/rp-hal/rp-hal-common/src/lib.rs @@ -0,0 +1,11 @@ +//! Common HAL code +//! +//! This library contains types and functions which are shared between the +//! RP2040 HAL and the RP235x HAL. +//! +//! You shouldn't include anything here which requires either the `cortex-m` +//! crate, or a PAC. + +#![no_std] + +pub mod uart; diff --git a/rp-hal/rp-hal-common/src/uart/common_configs.rs b/rp-hal/rp-hal-common/src/uart/common_configs.rs new file mode 100644 index 0000000..0e5bdba --- /dev/null +++ b/rp-hal/rp-hal-common/src/uart/common_configs.rs @@ -0,0 +1,45 @@ +//! Common UART configurations + +use fugit::HertzU32; + +use super::{DataBits, StopBits, UartConfig}; + +/// 9600 baud, 8 data bits, no parity, 1 stop bit +pub const _9600_8_N_1: UartConfig = UartConfig { + baudrate: HertzU32::from_raw(9600), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, +}; + +/// 19200 baud, 8 data bits, no parity, 1 stop bit +pub const _19200_8_N_1: UartConfig = UartConfig { + baudrate: HertzU32::from_raw(19200), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, +}; + +/// 38400 baud, 8 data bits, no parity, 1 stop bit +pub const _38400_8_N_1: UartConfig = UartConfig { + baudrate: HertzU32::from_raw(38400), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, +}; + +/// 57600 baud, 8 data bits, no parity, 1 stop bit +pub const _57600_8_N_1: UartConfig = UartConfig { + baudrate: HertzU32::from_raw(57600), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, +}; + +/// 115200 baud, 8 data bits, no parity, 1 stop bit +pub const _115200_8_N_1: UartConfig = UartConfig { + baudrate: HertzU32::from_raw(115200), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, +}; diff --git a/rp-hal/rp-hal-common/src/uart/mod.rs b/rp-hal/rp-hal-common/src/uart/mod.rs new file mode 100644 index 0000000..3a4918a --- /dev/null +++ b/rp-hal/rp-hal-common/src/uart/mod.rs @@ -0,0 +1,6 @@ +//! Shared code and types for Raspberry Pi Silicon UARTS + +pub mod common_configs; +mod utils; + +pub use utils::*; diff --git a/rp-hal/rp-hal-common/src/uart/utils.rs b/rp-hal/rp-hal-common/src/uart/utils.rs new file mode 100644 index 0000000..a9ad7cb --- /dev/null +++ b/rp-hal/rp-hal-common/src/uart/utils.rs @@ -0,0 +1,88 @@ +//! Useful UART types + +use fugit::HertzU32; + +/// Data bits +pub enum DataBits { + /// 5 bits + Five, + /// 6 bits + Six, + /// 7 bits + Seven, + /// 8 bits + Eight, +} + +/// Stop bits +pub enum StopBits { + /// 1 bit + One, + /// 2 bits + Two, +} + +/// Parity +/// +/// The "none" state of parity is represented with the Option type (None). +pub enum Parity { + /// Odd parity + Odd, + /// Even parity + Even, +} + +/// A struct holding the configuration for an UART device. +/// +/// The `Default` implementation implements the following values: +/// ```ignore +/// # // can't actually create this with the non_exhaustive attribute +/// UartConfig { +/// baudrate: Baud(115_200), +/// data_bits: DataBits::Eight, +/// stop_bits: StopBits::One, +/// parity: None, +///} +/// ``` +#[non_exhaustive] +pub struct UartConfig { + /// The baudrate the uart will run at. + pub baudrate: HertzU32, + + /// The amount of data bits the uart should be configured to. + pub data_bits: DataBits, + + /// The amount of stop bits the uart should be configured to. + pub stop_bits: StopBits, + + /// The parity that this uart should have + pub parity: Option, +} + +impl UartConfig { + /// Create a new instance of UartConfig + pub const fn new( + baudrate: HertzU32, + data_bits: DataBits, + parity: Option, + stop_bits: StopBits, + ) -> UartConfig { + UartConfig { + baudrate, + data_bits, + stop_bits, + parity, + } + } +} + +impl Default for UartConfig { + fn default() -> Self { + Self { + baudrate: HertzU32::from_raw(115_200), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, + } + } +} diff --git a/rp-hal/rp2040-hal-examples/.cargo/config.toml b/rp-hal/rp2040-hal-examples/.cargo/config.toml new file mode 100644 index 0000000..e548cb6 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/.cargo/config.toml @@ -0,0 +1,37 @@ +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +# Set the default target to match the Cortex-M0+ in the RP2040 +target = "thumbv6m-none-eabi" + +# Target specific options +[target.thumbv6m-none-eabi] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as the linker +# script. This is usually provided by the cortex-m-rt crate, and by default +# the version in that crate will include a file called `memory.x` which +# describes the particular memory layout for your specific chip. +# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't +# have SIMD) +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] + +# This runner will make a UF2 file and then copy it to a mounted RP2040 in USB +# Bootloader mode: +runner = "elf2uf2-rs -d" + +# This runner will find a supported SWD debug probe and flash your RP2040 over +# SWD: +# runner = "probe-rs run --chip RP2040" diff --git a/rp-hal/rp2040-hal-examples/Cargo.toml b/rp-hal/rp2040-hal-examples/Cargo.toml new file mode 100644 index 0000000..5393bad --- /dev/null +++ b/rp-hal/rp2040-hal-examples/Cargo.toml @@ -0,0 +1,47 @@ +[package] +authors = ["The rp-rs Developers"] +categories = ["embedded", "hardware-support", "no-std", "no-std::no-alloc"] +description = "Examples for the rp2040-hal crate" +edition = "2021" +homepage = "https://github.com/rp-rs/rp-hal" +keywords = ["embedded", "hal", "raspberry-pi", "rp2040", "embedded-hal"] +license = "MIT OR Apache-2.0" +name = "rp2040-hal-examples" +repository = "https://github.com/rp-rs/rp-hal" +rust-version = "1.79" +version = "0.1.0" + +[dependencies] +cortex-m = "0.7.2" +cortex-m-rt = "0.7" +cortex-m-rtic = "1.1.4" +critical-section = {version = "1.2.0"} +defmt = "0.3" +defmt-rtt = "0.4.0" +dht-sensor = "0.2.1" +embedded-alloc = "0.5.1" +embedded-hal = "1.0.0" +embedded-hal-async = "1.0.0" +embedded-hal-bus = {version = "0.2.0", features = ["defmt-03"]} +embedded-io = "0.6.1" +embedded_hal_0_2 = {package = "embedded-hal", version = "0.2.5", features = ["unproven"]} +fugit = "0.3.6" +futures = {version = "0.3.30", default-features = false, features = ["async-await"]} +hd44780-driver = "0.4.0" +nb = "1.0" +panic-halt = "0.2.0" +panic-probe = {version = "0.3.1", features = ["print-defmt"]} +pio = "0.2.0" +pio-proc = "0.2.0" +# We aren't using this, but embedded-hal-bus 0.2 unconditionally requires atomics. +# Should be fixed in e-h-b 0.3 via https://github.com/rust-embedded/embedded-hal/pull/607 +portable-atomic = {version = "1.7.0", features = ["critical-section"]} +rp2040-boot2 = "0.3.0" +rp2040-hal = {path = "../rp2040-hal", version = "0.10.0", features = ["binary-info", "critical-section-impl", "rt", "defmt"]} +static_cell = "2.1.0" + +[target.'cfg( target_arch = "arm" )'.dependencies] +embassy-executor = {version = "0.5", features = ["arch-cortex-m", "executor-thread"]} + +[lints.clippy] +too_long_first_doc_paragraph = "allow" diff --git a/rp-hal/rp2040-hal-examples/LICENSE-APACHE b/rp-hal/rp2040-hal-examples/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp-hal/rp2040-hal-examples/LICENSE-MIT b/rp-hal/rp2040-hal-examples/LICENSE-MIT new file mode 100644 index 0000000..6e052e3 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp-hal/rp2040-hal-examples/NOTICE b/rp-hal/rp2040-hal-examples/NOTICE new file mode 100644 index 0000000..790ecb1 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp-hal/rp2040-hal-examples/README.md b/rp-hal/rp2040-hal-examples/README.md new file mode 100644 index 0000000..f10ad6a --- /dev/null +++ b/rp-hal/rp2040-hal-examples/README.md @@ -0,0 +1,149 @@ + +
+

+ + Logo + + +

rp-hal

+ +

+ Rust Examples for the Raspberry Silicon RP2040 Microcontroller +
+ Explore the API docs » +
+
+ View more Demos + · + Report a Bug + · + Chat on Matrix +

+

+ + + + +
+

Table of Contents

+
    +
  1. Introduction
  2. +
  3. Getting Started
  4. +
  5. Roadmap
  6. +
  7. Contributing
  8. +
  9. License
  10. +
  11. Contact
  12. +
  13. Acknowledgements
  14. +
+
+ + +## Introduction + +The `rp2040-hal` package is a library crate of high-level Rust drivers for the +Raspberry Silicon RP2040 microcontroller. This folder contains a collection of +non-board specific example programs for you to study. + +We also provide a series of [*Board Support Package* (BSP) crates][BSPs], which +take the HAL crate and pre-configure the pins according to a specific PCB +design. If you are using one of the supported boards, you should use one of +those crates in preference, and return here to see documentation about specific +peripherals on the RP2040 and how to use them. See the `boards` folder in +https://github.com/rp-rs/rp-hal-boards/ for more details. + +[BSPs]: https://github.com/rp-rs/rp-hal-boards/ + + +## Getting Started + +To build all the examples, first grab a copy of the source code: + +```console +$ git clone https://github.com/rp-rs/rp-hal.git +``` + +Then use `rustup` to grab the Rust Standard Library for the appropriate target and our preferred flashing tool: + +```console +$ rustup target add thumbv6m-none-eabi +``` + +Then you can build the examples: + +```console +$ cd rp2040-hal-examples +$ cargo build + Compiling rp2040-hal-examples v0.1.0 (/home/user/rp-hal/rp2040-hal-examples) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.53s +$ cargo build --bin dormant_sleep + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s +$ file ./target/thumbv6m-none-eabi/debug/dormant_sleep +./target/thumbv6m-none-eabi/debug/dormant_sleep: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped +``` + +You can also 'run' an example, which will invoke [`elf2uf2-rs`] to copy it to an +RP2040's virtual USB Mass Storage Device (which it puts over the USB port when +in its ROM bootloader). You should install that if you don't have it already: + +```console +$ cargo install elf2uf2-rs --locked +$ cd rp2040-hal-examples +$ cargo run --bin dormant_sleep + Compiling rp2040-hal v0.10.0 (/home/user/rp-hal/rp2040-hal) + Compiling rp2040-hal-examples v0.1.0 (/home/user/rp-hal/rp2040-hal-examples) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.62s + Running `elf2uf2-rs -d target/thumbv6m-none-eabi/debug/dormant_sleep` +Found pico uf2 disk /media/user/RP2040 +Transfering program to pico +88.50 KB / 88.50 KB [=====================================] 100.00 % 430.77 KB/s +$ +``` + +[`elf2uf2-rs`]: https://github.com/JoNil/elf2uf2-rs + + +## Roadmap + +NOTE The HAL is under active development, and so are these examples. As such, it +is likely to remain volatile until a 1.0.0 release. + +See the [open issues](https://github.com/rp-rs/rp-hal/issues) for a list of +proposed features (and known issues). + + +## Contributing + +Contributions are what make the open source community such an amazing place to +be learn, inspire, and create. Any contributions you make are **greatly +appreciated**. + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + + + +## License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + + +## Contact + +* Project Link: [https://github.com/rp-rs/rp-hal/issues](https://github.com/rp-rs/rp-hal/issues) +* Matrix: [#rp-rs:matrix.org](https://matrix.to/#/#rp-rs:matrix.org) + + +## Acknowledgements + +* [Othneil Drew's README template](https://github.com/othneildrew) diff --git a/rp-hal/rp2040-hal-examples/build.rs b/rp-hal/rp2040-hal-examples/build.rs new file mode 100644 index 0000000..b2dd9ec --- /dev/null +++ b/rp-hal/rp2040-hal-examples/build.rs @@ -0,0 +1,14 @@ +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put the linker script somewhere the linker can find it + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let memory_x = include_bytes!("memory.x"); + let mut f = File::create(out.join("memory.x")).unwrap(); + f.write_all(memory_x).unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/rp-hal/rp2040-hal-examples/memory.x b/rp-hal/rp2040-hal-examples/memory.x new file mode 100644 index 0000000..10c6cfa --- /dev/null +++ b/rp-hal/rp2040-hal-examples/memory.x @@ -0,0 +1,83 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + /* + * Here we assume you have 2048 KiB of Flash. This is what the Pi Pico + * has, but your board may have more or less Flash and you should adjust + * this value to suit. + */ + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + /* + * RAM consists of 4 banks, SRAM0-SRAM3, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + /* + * RAM banks 4 and 5 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k + + /* SRAM banks 0-3 can also be accessed directly. However, those ranges + alias with the RAM mapping, above. So don't use them at the same time! + SRAM0 : ORIGIN = 0x21000000, LENGTH = 64k + SRAM1 : ORIGIN = 0x21010000, LENGTH = 64k + SRAM2 : ORIGIN = 0x21020000, LENGTH = 64k + SRAM3 : ORIGIN = 0x21030000, LENGTH = 64k + */ +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader + * + * An executable block of code which sets up the QSPI interface for + * 'Execute-In-Place' (or XIP) mode. Also sends chip-specific commands to + * the external flash chip. + * + * Must go at the start of external flash, where the Boot ROM expects it. + */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 512 bytes of flash, + * where picotool can find it + */ + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/rp-hal/rp2040-hal-examples/src/bin/adc.rs b/rp-hal/rp2040-hal-examples/src/bin/adc.rs new file mode 100644 index 0000000..7e9c34b --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/adc.rs @@ -0,0 +1,127 @@ +//! # ADC Example +//! +//! This application demonstrates how to read ADC samples from the temperature +//! sensor and pin and output them to the UART on pins 1 and 2 at 115200 baud. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use core::fmt::Write; +// Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +use embedded_hal_0_2::adc::OneShot; +use hal::fugit::RateExtU32; +use rp2040_hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then prints the temperature +/// in an infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Write to the UART + uart.write_full_blocking(b"ADC example\r\n"); + + // Enable ADC + let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS); + + // Enable the temperature sense channel + let mut temperature_sensor = adc.take_temp_sensor().unwrap(); + + // Configure GPIO26 as an ADC input + let mut adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26).unwrap(); + loop { + // Read the raw ADC counts from the temperature sensor channel. + let temp_sens_adc_counts: u16 = adc.read(&mut temperature_sensor).unwrap(); + let pin_adc_counts: u16 = adc.read(&mut adc_pin_0).unwrap(); + writeln!( + uart, + "ADC readings: Temperature: {temp_sens_adc_counts:02} Pin: {pin_adc_counts:02}\r\n" + ) + .unwrap(); + delay.delay_ms(1000); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/adc_fifo_dma.rs b/rp-hal/rp2040-hal-examples/src/bin/adc_fifo_dma.rs new file mode 100644 index 0000000..505b5ec --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/adc_fifo_dma.rs @@ -0,0 +1,187 @@ +//! # ADC FIFO DMA Example +//! +//! This application demonstrates how to read ADC samples in free-running mode, +//! and reading them from the FIFO by using a DMA transfer. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use core::fmt::Write; +use cortex_m::singleton; +use fugit::RateExtU32; +use hal::dma::{single_buffer, DMAExt}; +use rp2040_hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then prints the temperature +/// in an infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Write to the UART + uart.write_full_blocking(b"ADC FIFO DMA example\r\n"); + + // Initialize DMA + let dma = pac.DMA.split(&mut pac.RESETS); + + // Enable ADC + let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS); + + // Enable the temperature sense channel + let mut temperature_sensor = adc.take_temp_sensor().unwrap(); + + // Configure GPIO26 as an ADC input + let adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); + + // we'll capture 1000 samples in total (500 per channel) + // NOTE: when calling `shift_8bit` below, the type here must be changed from `u16` to `u8` + let buf_for_samples = singleton!(: [u16; 1000] = [0; 1000]).unwrap(); + + // Configure free-running mode: + let mut adc_fifo = adc + .build_fifo() + // Set clock divider to target a sample rate of 1000 samples per second (1ksps). + // The value was calculated by `(48MHz / 1ksps) - 1 = 47999.0`. + // Please check the `clock_divider` method documentation for details. + .clock_divider(47999, 0) + // sample the temperature sensor first + .set_channel(&mut temperature_sensor) + // then alternate between GPIO26 and the temperature sensor + .round_robin((&adc_pin_0, &temperature_sensor)) + // Uncomment this line to produce 8-bit samples, instead of 12 bit (lower bits are discarded) + //.shift_8bit() + // Enable DMA transfers for the FIFO + .enable_dma() + // Create the FIFO, but don't start it just yet + .start_paused(); + + // Start a DMA transfer (must happen before resuming the ADC FIFO) + let dma_transfer = + single_buffer::Config::new(dma.ch0, adc_fifo.dma_read_target(), buf_for_samples).start(); + + // Resume the FIFO to start capturing + adc_fifo.resume(); + + // initialize a timer, to measure the total sampling time (printed below) + let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + // NOTE: in a real-world program, instead of calling `wait` now, you would probably: + // 1. Enable one of the DMA interrupts for the channel (e.g. `dma.ch0.enable_irq0()`) + // 2. Set up a handler for the respective `DMA_IRQ_*` interrupt + // 3. Call `wait` only within that interrupt, which will be fired once the transfer is complete. + + // the DMA unit takes care of shuffling data from the FIFO into the buffer. + // We just sit here and wait... 😴 + let (_ch, _adc_read_target, buf_for_samples) = dma_transfer.wait(); + + // ^^^ the three results here (channel, adc::DmaReadTarget, write target) can be reused + // right away to start another transfer. + + let time_taken = timer.get_counter(); + + uart.write_full_blocking(b"Done sampling, printing results:\r\n"); + + // Stop free-running mode (the returned `adc` can be reused for future captures) + let _adc = adc_fifo.stop(); + + // Print the measured values + for i in 0..500 { + writeln!( + uart, + "Temp:\t{}\tPin\t{}\r", + buf_for_samples[i * 2], + buf_for_samples[i * 2 + 1] + ) + .unwrap(); + } + + writeln!(uart, "Sampling took: {}\r", time_taken).unwrap(); + + loop { + delay.delay_ms(1000); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/adc_fifo_irq.rs b/rp-hal/rp2040-hal-examples/src/bin/adc_fifo_irq.rs new file mode 100644 index 0000000..ee4c905 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/adc_fifo_irq.rs @@ -0,0 +1,185 @@ +//! # ADC FIFO Interrupt Example +//! +//! This application demonstrates how to read ADC samples in free-running mode, +//! using the FIFO interrupt. +//! +//! It utilizes `rtic` (cortex-m-rtic crate) to safely share peripheral access between +//! initialization code and interrupt handlers. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +use panic_halt as _; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +#[rtic::app(device = rp2040_hal::pac)] +mod app { + use core::fmt::Write; + use fugit::RateExtU32; + use hal::Clock; + use rp2040_hal as hal; + + /// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust + /// if your board has a different frequency + const XTAL_FREQ_HZ: u32 = 12_000_000u32; + + // This example will capture 1000 samples to `shared.buf`. + // When it is done, it will stop the ADC, set `shared.done` to true, + // print the result and loop forever. + + const SAMPLE_COUNT: usize = 1000; + + type Uart = hal::uart::UartPeripheral< + hal::uart::Enabled, + hal::pac::UART0, + ( + hal::gpio::Pin, + hal::gpio::Pin, + ), + >; + + #[shared] + struct Shared { + done: bool, + buf: [u16; SAMPLE_COUNT], + uart: Uart, + } + + #[local] + struct Local { + adc_fifo: Option>, + } + + #[init(local = [adc: Option = None])] + fn init(c: init::Context) -> (Shared, Local, init::Monotonics) { + // Soft-reset does not release the hardware spinlocks + // Release them now to avoid a deadlock after debug or watchdog reset + unsafe { + hal::sio::spinlock_reset(); + } + + let mut resets = c.device.RESETS; + let mut watchdog = hal::Watchdog::new(c.device.WATCHDOG); + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + c.device.XOSC, + c.device.CLOCKS, + c.device.PLL_SYS, + c.device.PLL_USB, + &mut resets, + &mut watchdog, + ) + .unwrap(); + let sio = hal::Sio::new(c.device.SIO); + let pins = hal::gpio::Pins::new( + c.device.IO_BANK0, + c.device.PADS_BANK0, + sio.gpio_bank0, + &mut resets, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let uart = hal::uart::UartPeripheral::new(c.device.UART0, uart_pins, &mut resets) + .enable( + hal::uart::UartConfig::new( + 115200.Hz(), + hal::uart::DataBits::Eight, + None, + hal::uart::StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // the ADC is put into a local, to gain static lifetime + *c.local.adc = Some(hal::Adc::new(c.device.ADC, &mut resets)); + let adc = c.local.adc.as_mut().unwrap(); + + let mut adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); + + uart.write_full_blocking(b"ADC FIFO interrupt example\r\n"); + + let adc_fifo = adc + .build_fifo() + // Set clock divider to target a sample rate of 1000 samples per second (1ksps). + // The value was calculated by `(48MHz / 1ksps) - 1 = 47999.0`. + // Please check the `clock_divider` method documentation for details. + .clock_divider(47999, 0) + .set_channel(&mut adc_pin_0) + .enable_interrupt(1) + .start(); + + ( + Shared { + done: false, + buf: [0; SAMPLE_COUNT], + uart, + }, + Local { + adc_fifo: Some(adc_fifo), + }, + init::Monotonics(), + ) + } + + #[idle(shared = [done, buf, uart])] + fn idle(mut c: idle::Context) -> ! { + loop { + let finished = (&mut c.shared.done, &mut c.shared.buf, &mut c.shared.uart).lock( + |done, buf, uart| { + if *done { + for sample in buf { + writeln!(uart, "Sample: {}\r", sample).unwrap(); + } + writeln!(uart, "All done, going to sleep 😴\r").unwrap(); + true + } else { + false + } + }, + ); + + if finished { + break; + } + } + + #[allow(clippy::empty_loop)] + loop {} + } + + #[task( + binds = ADC_IRQ_FIFO, + priority = 1, + shared = [done, buf], + local = [adc_fifo, counter: usize = 0] + )] + fn adc_irq_fifo(mut c: adc_irq_fifo::Context) { + let sample = c.local.adc_fifo.as_mut().unwrap().read(); + let i = *c.local.counter; + c.shared.buf.lock(|buf| buf[i] = sample); + *c.local.counter += 1; + + if *c.local.counter == SAMPLE_COUNT { + c.local.adc_fifo.take().unwrap().stop(); + c.shared.done.lock(|done| *done = true); + } + } +} diff --git a/rp-hal/rp2040-hal-examples/src/bin/adc_fifo_poll.rs b/rp-hal/rp2040-hal-examples/src/bin/adc_fifo_poll.rs new file mode 100644 index 0000000..1c2d04a --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/adc_fifo_poll.rs @@ -0,0 +1,194 @@ +//! # ADC FIFO Example +//! +//! This application demonstrates how to read ADC samples in free-running mode, +//! and reading them from the FIFO by polling the fifo's `len()`. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use core::fmt::Write; +use fugit::RateExtU32; +use rp2040_hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then prints the temperature +/// in an infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Write to the UART + uart.write_full_blocking(b"ADC FIFO poll example\r\n"); + + // Enable ADC + let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS); + + // Enable the temperature sense channel + let mut temperature_sensor = adc.take_temp_sensor().unwrap(); + + // Configure GPIO26 as an ADC input + let adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); + + // Configure free-running mode: + let mut adc_fifo = adc + .build_fifo() + // Set clock divider to target a sample rate of 1000 samples per second (1ksps). + // The value was calculated by `(48MHz / 1ksps) - 1 = 47999.0`. + // Please check the `clock_divider` method documentation for details. + .clock_divider(47999, 0) + // sample the temperature sensor first + .set_channel(&mut temperature_sensor) + // then alternate between GPIO26 and the temperature sensor + .round_robin((&adc_pin_0, &temperature_sensor)) + // Uncomment this line to produce 8-bit samples, instead of 12 bit (lower bits are discarded) + //.shift_8bit() + // start sampling + .start(); + + // we'll capture 1000 samples in total (500 per channel) + let mut temp_samples = [0; 500]; + let mut pin_samples = [0; 500]; + let mut i = 0; + + // initialize a timer, to measure the total sampling time (printed below) + let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + loop { + // busy-wait until the FIFO contains at least two samples: + while adc_fifo.len() < 2 {} + + // fetch two values from the fifo + let temp_result = adc_fifo.read(); + let pin_result = adc_fifo.read(); + + // uncomment this line, to trigger an "underrun" condition + //let _extra_sample = adc_fifo.read(); + + if adc_fifo.is_over() { + // samples were pushed into the fifo faster they were read + uart.write_full_blocking(b"FIFO overrun!\r\n"); + } + if adc_fifo.is_under() { + // we tried to read samples more quickly than they were pushed into the fifo + uart.write_full_blocking(b"FIFO underrun!\r\n"); + } + + temp_samples[i] = temp_result; + pin_samples[i] = pin_result; + + i += 1; + + // uncomment this line to trigger an "overrun" condition + //delay.delay_ms(1000); + + if i == 500 { + break; + } + } + + let time_taken = timer.get_counter(); + + uart.write_full_blocking(b"Done sampling, printing results:\r\n"); + + // Stop free-running mode (the returned `adc` can be reused for future captures) + let _adc = adc_fifo.stop(); + + // Print the measured values + for i in 0..500 { + writeln!( + uart, + "Temp:\t{}\tPin\t{}\r", + temp_samples[i], pin_samples[i] + ) + .unwrap(); + } + + writeln!(uart, "Sampling took: {}\r", time_taken).unwrap(); + + loop { + delay.delay_ms(1000); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/alloc.rs b/rp-hal/rp2040-hal-examples/src/bin/alloc.rs new file mode 100644 index 0000000..2077310 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/alloc.rs @@ -0,0 +1,123 @@ +//! # Alloc Example +//! +//! Uses alloc to create a Vec. +//! +//! This will blink an LED attached to GP25, which is the pin the Pico uses for +//! the on-board LED. It may need to be adapted to your particular board layout +//! and/or pin assignment. +//! +//! While blinking the LED, it will continuously push to a `Vec`, which will +//! eventually lead to a panic due to an out of memory condition. +//! +//! See the top-level `README.md` file for Copyright and licence details. + +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::vec::Vec; +use embedded_alloc::Heap; + +// The macro for our start-up function +use cortex_m_rt::entry; + +#[global_allocator] +static ALLOCATOR: Heap = Heap::empty(); + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Some traits we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, then blinks the LED in an +/// infinite loop where the duration indicates how many items were allocated. +#[entry] +fn main() -> ! { + { + use core::mem::MaybeUninit; + const HEAP_SIZE: usize = 1024; + static mut HEAP: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { ALLOCATOR.init(core::ptr::addr_of_mut!(HEAP) as usize, HEAP_SIZE) } + } + + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let mut timer = rp2040_hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + + let mut xs = Vec::new(); + xs.push(1); + + // Blink the LED at 1 Hz + loop { + led_pin.set_high().unwrap(); + let len = xs.len() as u32; + timer.delay_ms(100 * len); + xs.push(1); + led_pin.set_low().unwrap(); + timer.delay_ms(100 * len); + xs.push(1); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/binary_info_demo.rs b/rp-hal/rp2040-hal-examples/src/bin/binary_info_demo.rs new file mode 100644 index 0000000..f0b7523 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/binary_info_demo.rs @@ -0,0 +1,109 @@ +//! # GPIO 'Blinky' Example, with Binary Info +//! +//! This application demonstrates how to control a GPIO pin on the RP2040, and +//! includes some picotool-compatible metadata. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +use hal::binary_info; + +// Some traits we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut timer = rp2040_hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + loop { + led_pin.set_high().unwrap(); + timer.delay_ms(500); + led_pin.set_low().unwrap(); + timer.delay_ms(500); + } +} + +/// This is a list of references to our table entries +/// +/// They must be in the `.bi_entries` section as we tell picotool the start and +/// end addresses of that section. +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [binary_info::EntryAddr; 7] = [ + binary_info::rp_program_name!(c"rp2040-hal Binary Info Example"), + binary_info::rp_cargo_version!(), + binary_info::rp_program_description!(c"A GPIO blinky with extra metadata."), + binary_info::rp_program_url!(c"https://github.com/rp-rs/rp-hal"), + binary_info::rp_program_build_attribute!(), + binary_info::rp_pico_board!(c"pico"), + // An example with a non-Raspberry-Pi tag + binary_info::int!(binary_info::make_tag(b"JP"), 0x0000_0001, 0x12345678), +]; + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/blinky.rs b/rp-hal/rp2040-hal-examples/src/bin/blinky.rs new file mode 100644 index 0000000..0fcc411 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/blinky.rs @@ -0,0 +1,89 @@ +//! # GPIO 'Blinky' Example +//! +//! This application demonstrates how to control a GPIO pin on the RP2040. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Some traits we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut timer = rp2040_hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + loop { + led_pin.set_high().unwrap(); + timer.delay_ms(500); + led_pin.set_low().unwrap(); + timer.delay_ms(500); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/dht11.rs b/rp-hal/rp2040-hal-examples/src/bin/dht11.rs new file mode 100644 index 0000000..0be04b1 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/dht11.rs @@ -0,0 +1,100 @@ +//! # DHT11 Example +//! +//! This application demonstrates how to read a DHT11 sensor on the RP2040. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! In this example, the DHT11 data pin should be connected to GPIO28. +//! +//! NOTE: The DHT11 driver only works reliably when compiled in release mode. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Some traits we need +use embedded_hal::digital::OutputPin; +use hal::Clock; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +use dht_sensor::{dht11, DhtReading}; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, assigns GPIO 28 to the +/// DHT11 driver, and takes a single measurement. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // Use GPIO 28 as an InOutPin + let mut pin = hal::gpio::InOutPin::new(pins.gpio28); + let _ = pin.set_high(); + + // Perform a sensor reading + let _measurement = dht11::Reading::read(&mut delay, &mut pin); + + // In this case, we just ignore the result. A real application + // would do something with the measurement. + + loop { + cortex_m::asm::wfi(); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/dormant_sleep.rs b/rp-hal/rp2040-hal-examples/src/bin/dormant_sleep.rs new file mode 100644 index 0000000..f2108a2 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/dormant_sleep.rs @@ -0,0 +1,298 @@ +//! # DORMANT low-power mode example +//! +//! This application demonstrates how to enter and exit the RP2040's lowest-power DORMANT mode +//! where all clocks and PLLs are stopped. +//! +//! Pulling GPIO 14 low (e.g. via a debounced momentary-contact button) alternately wakes the +//! RP2040 from DORMANT mode and a regular WFI sleep. A LED attached to GPIO 25 (the onboard LED +//! on the Raspberry Pi Pico) pulses once before entering DORMANT mode and twice before entering WFI sleep. +//! +//! Note: DORMANT mode breaks the debug connection. You may need to power cycle while pressing the +//! BOOTSEL button to regain debug access to the pico. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +#[allow(unused_imports)] +use panic_halt as _; + +use rp2040_hal as hal; + +use core::{cell::RefCell, ops::DerefMut}; + +use critical_section::Mutex; + +use embedded_hal::digital::StatefulOutputPin; + +use fugit::RateExtU32; + +use hal::{ + clocks::{ClockError, ClocksManager, InitError, StoppableClock}, + gpio, + gpio::{Interrupt::EdgeLow, Pins}, + pac, + pac::{interrupt, CLOCKS, PLL_SYS, PLL_USB, RESETS, XOSC}, + pll::{ + common_configs::{PLL_SYS_125MHZ, PLL_USB_48MHZ}, + setup_pll_blocking, start_pll_blocking, Disabled, Locked, PhaseLockedLoop, + }, + rosc::RingOscillator, + sio::Sio, + watchdog::Watchdog, + xosc::{setup_xosc_blocking, CrystalOscillator, Stable, Unstable}, + Clock, +}; + +use nb::block; + +type ClocksAndPlls = ( + ClocksManager, + CrystalOscillator, + PhaseLockedLoop, + PhaseLockedLoop, +); + +type RestartedClockAndPlls = ( + CrystalOscillator, + PhaseLockedLoop, + PhaseLockedLoop, +); + +/// The button input. +type ButtonPin = gpio::Pin; + +/// Devices shared between the foreground code and interrupt handlers. +static GLOBAL_DEVICES: Mutex>> = Mutex::new(RefCell::new(None)); + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency. +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[rp2040_hal::entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + let sio = Sio::new(pac.SIO); + + // Configure the clocks + let (mut clocks, mut xosc, mut pll_sys, mut pll_usb) = init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // Disable ring oscillator to maximise power savings - optional + let rosc = RingOscillator::new(pac.ROSC).initialize(); + rosc.disable(); + + // Set the pins to their default state + let pins = Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO 25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + + // Configure GPIO 14 as an input that wakes the RP2040 from a sleep state + let button_pin = pins.gpio14.reconfigure(); + button_pin.set_dormant_wake_enabled(EdgeLow, true); + button_pin.set_interrupt_enabled(EdgeLow, true); + + critical_section::with(|cs| { + GLOBAL_DEVICES.borrow(cs).replace(Some(button_pin)); + }); + + unsafe { + pac::NVIC::unmask(pac::Interrupt::IO_IRQ_BANK0); + } + + let mut use_dormant = true; + loop { + if use_dormant { + pulse(&mut led_pin, 1); + + let (disabled_pll_sys, disabled_pll_usb) = + prepare_clocks_and_plls_for_dormancy(&mut xosc, &mut clocks, pll_sys, pll_usb); + + // Stop the crystal oscillator and enter the RP2040's dormant state + let unstable_xosc = unsafe { xosc.dormant() }; + + match restart_clocks_and_plls( + &mut clocks, + unstable_xosc, + disabled_pll_sys, + disabled_pll_usb, + &mut pac.RESETS, + ) { + Ok((stable_xosc, stable_pll_sys, stable_pll_usb)) => { + xosc = stable_xosc; + pll_sys = stable_pll_sys; + pll_usb = stable_pll_usb; + } + Err(_) => { + panic!(); + } + } + + // Clear dormant wake interrupt status to enable wake next time + critical_section::with(|cs| { + let mut global_devices = GLOBAL_DEVICES.borrow(cs).borrow_mut(); + if let Some(ref mut trigger_pin) = global_devices.deref_mut() { + trigger_pin.clear_interrupt(EdgeLow); + } else { + panic!(); + }; + }); + } else { + pulse(&mut led_pin, 2); + + // Enter the regular RP2040 sleep state: clocks and PLLs stay running + cortex_m::asm::wfi(); + } + + use_dormant = !use_dormant; + } +} + +/// Pulse an LED-connected pin the specified number of times. +fn pulse(pin: &mut P, count: u32) { + const LED_PULSE_CYCLES: u32 = 2_000_000; + + for i in 0..count * 2 { + let _ = pin.toggle(); + // 1:10 duty cycle + cortex_m::asm::delay(LED_PULSE_CYCLES + (i % 2) * 9 * LED_PULSE_CYCLES); + } +} + +/// Initialize clocks and PLLs in much the same way as rp2040-hal::clocks::init_clocks_and_plls(). +/// Returns the crystal oscillator and the PLLs so we can reconfigure them later. +fn init_clocks_and_plls( + xosc_crystal_freq: u32, + xosc_dev: XOSC, + clocks_dev: CLOCKS, + pll_sys_dev: PLL_SYS, + pll_usb_dev: PLL_USB, + resets: &mut RESETS, + watchdog: &mut Watchdog, +) -> Result { + let xosc = setup_xosc_blocking(xosc_dev, xosc_crystal_freq.Hz()).unwrap(); + + // Configure watchdog tick generation to tick over every microsecond + watchdog.enable_tick_generation((xosc_crystal_freq / 1_000_000) as u8); + + let mut clocks = ClocksManager::new(clocks_dev); + + let pll_sys = setup_pll_blocking( + pll_sys_dev, + xosc.operating_frequency(), + PLL_SYS_125MHZ, + &mut clocks, + resets, + ) + .map_err(InitError::PllError)?; + let pll_usb = setup_pll_blocking( + pll_usb_dev, + xosc.operating_frequency(), + PLL_USB_48MHZ, + &mut clocks, + resets, + ) + .map_err(InitError::PllError)?; + + clocks + .init_default(&xosc, &pll_sys, &pll_usb) + .map_err(InitError::ClockError)?; + + Ok((clocks, xosc, pll_sys, pll_usb)) +} + +/// Switch clocks to the crystal oscillator or disable them as appropriate, and stop PLLs so +/// that we're ready to go dormant. +fn prepare_clocks_and_plls_for_dormancy( + xosc: &mut CrystalOscillator, + clocks: &mut ClocksManager, + pll_sys: PhaseLockedLoop, + pll_usb: PhaseLockedLoop, +) -> ( + PhaseLockedLoop, + PhaseLockedLoop, +) { + // switch system clock from pll_sys to xosc so that we can stop the system PLL + nb::block!(clocks.system_clock.reset_source_await()).unwrap(); + + clocks.usb_clock.disable(); + clocks.adc_clock.disable(); + + clocks + .rtc_clock + .configure_clock(xosc, 46875u32.Hz()) + .unwrap(); + clocks + .peripheral_clock + .configure_clock(&clocks.system_clock, clocks.system_clock.freq()) + .unwrap(); + + (pll_sys.disable(), pll_usb.disable()) +} + +/// Restart the PLLs and start/reconfigure the clocks back to how they were before going dormant. +fn restart_clocks_and_plls( + clocks: &mut ClocksManager, + unstable_xosc: CrystalOscillator, + disabled_pll_sys: PhaseLockedLoop, + disabled_pll_usb: PhaseLockedLoop, + resets: &mut RESETS, +) -> Result { + // Wait for the restarted XOSC to stabilise + let stable_xosc_token = block!(unstable_xosc.await_stabilization()).unwrap(); + let xosc = unstable_xosc.get_stable(stable_xosc_token); + + let pll_sys = start_pll_blocking(disabled_pll_sys, resets).unwrap(); + let pll_usb = start_pll_blocking(disabled_pll_usb, resets).unwrap(); + + clocks + .init_default(&xosc, &pll_sys, &pll_usb) + .map(|_| (xosc, pll_sys, pll_usb)) +} + +#[interrupt] +fn IO_IRQ_BANK0() { + critical_section::with(|cs| { + let mut global_devices = GLOBAL_DEVICES.borrow(cs).borrow_mut(); + if let Some(ref mut button_pin) = global_devices.deref_mut() { + // Check if the interrupt source is from the push button going from high-to-low. + // Note: this will always be true in this example, as that is the only enabled GPIO interrupt source + if button_pin.interrupt_status(EdgeLow) { + // Our interrupt doesn't clear itself. + // Do that now so we don't immediately jump back to this interrupt handler. + button_pin.clear_interrupt(EdgeLow); + } + } else { + panic!(); + }; + }); +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/gpio_dyn_pin_array.rs b/rp-hal/rp2040-hal-examples/src/bin/gpio_dyn_pin_array.rs new file mode 100644 index 0000000..292850a --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/gpio_dyn_pin_array.rs @@ -0,0 +1,133 @@ +//! # GPIO Dynamic Pin Type Mode Example +//! +//! This application demonstrates how to put GPIO pins into their Dynamic Pin type on the RP2040. +//! +//! Usually, the type of each pin is different (which allows the type system to catch misuse). +//! But this stops you storing the pins in an array, or allowing a struct to take any pin. +//! This mode is also referred to as "Erased", "Downgraded", "Degraded", or "Dynamic". +//! +//! In order to see the result of this program, you will need to put LEDs and a current limiting +//! resistor on each of GPIO 2, 3, 4, 5. +//! The other side of the LED + resistor pair should be connected to GND. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +use hal::gpio::{DynPinId, FunctionSioOutput, Pin, PullNone, PullUp}; +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Some traits we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // We will use the RP2040 timer peripheral as our delay source + let mut timer = rp2040_hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // To put pins into an array we have to convert them to Dynamically Typed pins. + // This means they'll carry their pin and bank numbers around with them at run time, + // rather than relying on the Type of the pin to track that. + let mut pinarray: [Pin; 4] = [ + pins.gpio2 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + pins.gpio3 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + pins.gpio4 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + pins.gpio5 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + ]; + + // Also set a pin as a dynamic input. We won't use this, it is just to demonstrate that + // pins can have other functions and still be Dynamically typed. + let _in_pin = pins.gpio23.into_floating_input().into_dyn_pin(); + + // You can also let the target type set the pin mode, using the type system to guide it. + // Once again, we're not going to use this array. The only reason it is here is to demonstrate a less verbose way to set pin modes + let mut _type_coerce: [Pin; 1] = + [pins.gpio22.reconfigure().into_dyn_pin()]; + + // Light one LED at a time. Start at GPIO2 and go through to GPIO5, then reverse. + loop { + for led in pinarray.iter_mut() { + led.set_high().unwrap(); + timer.delay_ms(50); + led.set_low().unwrap(); + } + for led in pinarray.iter_mut().rev() { + led.set_high().unwrap(); + timer.delay_ms(50); + led.set_low().unwrap(); + } + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/gpio_in_out.rs b/rp-hal/rp2040-hal-examples/src/bin/gpio_in_out.rs new file mode 100644 index 0000000..2b25b64 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/gpio_in_out.rs @@ -0,0 +1,93 @@ +//! # GPIO In/Out Example +//! +//! This application demonstrates how to control GPIO pins on the RP2040. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Some traits we need +use embedded_hal::digital::InputPin; +use embedded_hal::digital::OutputPin; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let _clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO 25 as an output + let mut out_pin = pins.gpio25.into_push_pull_output(); + + // Configure GPIO 23 as an input + let mut in_pin = pins.gpio23.into_pull_down_input(); + + // Output is the opposite of the input + loop { + if in_pin.is_low().unwrap() { + out_pin.set_high().unwrap(); + } else { + out_pin.set_low().unwrap(); + } + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/gpio_irq_example.rs b/rp-hal/rp2040-hal-examples/src/bin/gpio_irq_example.rs new file mode 100644 index 0000000..dd70e1e --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/gpio_irq_example.rs @@ -0,0 +1,186 @@ +//! # GPIO IRQ Example +//! +//! This application demonstrates use of GPIO Interrupts. +//! It is also intended as a general introduction to interrupts with RP2040. +//! +//! Each GPIO can be triggered on the input being high (LevelHigh), being low (LevelLow) +//! starting high and then going low (EdgeLow) or starting low and becoming high (EdgeHigh) +//! +//! In this example, we trigger on EdgeLow. Our input pin configured to be pulled to the high logic-level +//! via an internal pullup resistor. This resistor is quite weak, so you can bring the logic level back to low +//! via an external jumper wire or switch. +//! Whenever we see the edge transition, we will toggle the output on GPIO25 - this is the LED pin on a Pico. +//! +//! Note that this demo does not perform any [software debouncing](https://en.wikipedia.org/wiki/Switch#Contact_bounce). +//! You can fix that through hardware, or you could disable the button interrupt in the interrupt and re-enable it +//! some time later using one of the Alarms of the Timer peripheral - this is left as an exercise for the reader. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Some traits we need +use embedded_hal::digital::StatefulOutputPin; + +// Our interrupt macro +use hal::pac::interrupt; + +// Some short-cuts to useful types +use core::cell::RefCell; +use critical_section::Mutex; +use rp2040_hal::gpio; + +// The GPIO interrupt type we're going to generate +use rp2040_hal::gpio::Interrupt::EdgeLow; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +// Pin types quickly become very long! +// We'll create some type aliases using `type` to help with that + +/// This pin will be our output - it will drive an LED if you run this on a Pico +type LedPin = gpio::Pin; + +/// This pin will be our interrupt source. +/// It will trigger an interrupt if pulled to ground (via a switch or jumper wire) +type ButtonPin = gpio::Pin; + +/// Since we're always accessing these pins together we'll store them in a tuple. +/// Giving this tuple a type alias means we won't need to use () when putting them +/// inside an Option. That will be easier to read. +type LedAndButton = (LedPin, ButtonPin); + +/// This how we transfer our Led and Button pins into the Interrupt Handler. +/// We'll have the option hold both using the LedAndButton type. +/// This will make it a bit easier to unpack them later. +static GLOBAL_PINS: Mutex>> = Mutex::new(RefCell::new(None)); + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let _clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO 25 as an output to drive our LED. + // we can use reconfigure() instead of into_pull_up_input() + // since the variable we're pushing it into has that type + let led = pins.gpio25.reconfigure(); + + // Set up the GPIO pin that will be our input + let in_pin = pins.gpio26.reconfigure(); + + // Trigger on the 'falling edge' of the input pin. + // This will happen as the button is being pressed + in_pin.set_interrupt_enabled(EdgeLow, true); + + // Give away our pins by moving them into the `GLOBAL_PINS` variable. + // We won't need to access them in the main thread again + critical_section::with(|cs| { + GLOBAL_PINS.borrow(cs).replace(Some((led, in_pin))); + }); + + // Unmask the IO_BANK0 IRQ so that the NVIC interrupt controller + // will jump to the interrupt function when the interrupt occurs. + // We do this last so that the interrupt can't go off while + // it is in the middle of being configured + unsafe { + pac::NVIC::unmask(pac::Interrupt::IO_IRQ_BANK0); + } + + loop { + // interrupts handle everything else in this example. + cortex_m::asm::wfi(); + } +} + +#[allow(static_mut_refs)] // See https://github.com/rust-embedded/cortex-m/pull/561 +#[interrupt] +fn IO_IRQ_BANK0() { + // The `#[interrupt]` attribute covertly converts this to `&'static mut Option` + static mut LED_AND_BUTTON: Option = None; + + // This is one-time lazy initialisation. We steal the variables given to us + // via `GLOBAL_PINS`. + if LED_AND_BUTTON.is_none() { + critical_section::with(|cs| { + *LED_AND_BUTTON = GLOBAL_PINS.borrow(cs).take(); + }); + } + + // Need to check if our Option contains our pins + if let Some(gpios) = LED_AND_BUTTON { + // borrow led and button by *destructuring* the tuple + // these will be of type `&mut LedPin` and `&mut ButtonPin`, so we don't have + // to move them back into the static after we use them + let (led, button) = gpios; + // Check if the interrupt source is from the pushbutton going from high-to-low. + // Note: this will always be true in this example, as that is the only enabled GPIO interrupt source + if button.interrupt_status(EdgeLow) { + // toggle can't fail, but the embedded-hal traits always allow for it + // we can discard the return value by assigning it to an unnamed variable + let _ = led.toggle(); + + // Our interrupt doesn't clear itself. + // Do that now so we don't immediately jump back to this interrupt handler. + button.clear_interrupt(EdgeLow); + } + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/i2c.rs b/rp-hal/rp2040-hal-examples/src/bin/i2c.rs new file mode 100644 index 0000000..e0d509b --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/i2c.rs @@ -0,0 +1,106 @@ +//! # I²C Example +//! +//! This application demonstrates how to talk to I²C devices with an RP2040. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use embedded_hal_0_2::blocking::i2c::Write; +use hal::fugit::RateExtU32; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access and a gpio related types. +use hal::{ + gpio::{FunctionI2C, Pin}, + pac, +}; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then performs a single I²C +/// write to a fixed address. +#[rp2040_hal::entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let sda_pin: Pin<_, FunctionI2C, _> = pins.gpio18.reconfigure(); + let scl_pin: Pin<_, FunctionI2C, _> = pins.gpio19.reconfigure(); + // let not_an_scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio20.reconfigure(); + + // Create the I²C drive, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let mut i2c = hal::I2C::i2c1( + pac.I2C1, + sda_pin, + scl_pin, // Try `not_an_scl_pin` here + 400.kHz(), + &mut pac.RESETS, + &clocks.system_clock, + ); + + // Write three bytes to the I²C device with 7-bit address 0x2C + i2c.write(0x2Cu8, &[1, 2, 3]).unwrap(); + + // Demo finish - just loop until reset + + loop { + cortex_m::asm::wfi(); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/i2c_async.rs b/rp-hal/rp2040-hal-examples/src/bin/i2c_async.rs new file mode 100644 index 0000000..9e490e7 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/i2c_async.rs @@ -0,0 +1,127 @@ +//! # I²C Example +//! +//! This application demonstrates how to talk to I²C devices with an RP2040. +//! in an Async environment. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use hal::{ + fugit::RateExtU32, + gpio::bank0::{Gpio20, Gpio21}, + i2c::Controller, + I2C, +}; + +// Import required types & traits. +use embassy_executor::Executor; +use embedded_hal_async::i2c::I2c; +use hal::{ + gpio::{FunctionI2C, Pin, PullUp}, + pac::{self, interrupt}, + Clock, +}; +use static_cell::StaticCell; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Bind the interrupt handler with the peripheral +#[interrupt] +unsafe fn I2C0_IRQ() { + use hal::async_utils::AsyncPeripheral; + I2C::::on_interrupt(); +} + +/// The function configures the RP2040 peripherals, then performs a single I²C +/// write to a fixed address. +#[embassy_executor::task] +async fn demo() { + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio20.reconfigure(); + let scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio21.reconfigure(); + + // Create the I²C drive, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let mut i2c = hal::I2C::new_controller( + pac.I2C0, + sda_pin, + scl_pin, + 400.kHz(), + &mut pac.RESETS, + clocks.system_clock.freq(), + ); + + // Unmask the interrupt in the NVIC to let the core wake up & enter the interrupt handler. + // Each core has its own NVIC so these needs to executed from the core where the IRQ are + // expected. + unsafe { + pac::NVIC::unpend(hal::pac::Interrupt::I2C0_IRQ); + pac::NVIC::unmask(hal::pac::Interrupt::I2C0_IRQ); + } + + // Asynchronously write three bytes to the I²C device with 7-bit address 0x2C + i2c.write(0x76u8, &[1, 2, 3]).await.unwrap(); + + // Demo finish - just loop until reset + core::future::pending().await +} + +/// Entry point to our bare-metal application. +#[rp2040_hal::entry] +fn main() -> ! { + static EXECUTOR: StaticCell = StaticCell::new(); + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| spawner.spawn(demo()).unwrap()); +} diff --git a/rp-hal/rp2040-hal-examples/src/bin/i2c_async_cancelled.rs b/rp-hal/rp2040-hal-examples/src/bin/i2c_async_cancelled.rs new file mode 100644 index 0000000..90ac5a0 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/i2c_async_cancelled.rs @@ -0,0 +1,151 @@ +//! # I²C Example +//! +//! This application demonstrates how to talk to I²C devices with an RP2040. +//! in an Async environment. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +use core::task::Poll; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +//use panic_halt as _; + +use embedded_hal_async::i2c::I2c; +use futures::FutureExt; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use hal::{ + fugit::RateExtU32, + gpio::bank0::{Gpio20, Gpio21}, + i2c::Controller, + pac::interrupt, + I2C, +}; + +// Import required types & traits. +use hal::{ + gpio::{FunctionI2C, Pin, PullUp}, + pac, Clock, +}; + +use defmt_rtt as _; +use embassy_executor::Executor; +use panic_probe as _; +use static_cell::StaticCell; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[interrupt] +unsafe fn I2C0_IRQ() { + use hal::async_utils::AsyncPeripheral; + I2C::::on_interrupt(); +} + +/// The function configures the RP2040 peripherals, then performs a single I²C +/// write to a fixed address. +#[embassy_executor::task] +async fn demo() { + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio20.reconfigure(); + let scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio21.reconfigure(); + + // Create the I²C drive, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let mut i2c = hal::I2C::new_controller( + pac.I2C0, + sda_pin, + scl_pin, + 400.kHz(), + &mut pac.RESETS, + clocks.system_clock.freq(), + ); + + // Unmask the interrupt in the NVIC to let the core wake up & enter the interrupt handler. + unsafe { + pac::NVIC::unpend(hal::pac::Interrupt::I2C0_IRQ); + pac::NVIC::unmask(hal::pac::Interrupt::I2C0_IRQ); + } + + let mut cnt = 0; + let timeout = core::future::poll_fn(|cx| { + cx.waker().wake_by_ref(); + if cnt == 1 { + Poll::Ready(()) + } else { + cnt += 1; + Poll::Pending + } + }); + + let mut v = [0; 32]; + v.iter_mut().enumerate().for_each(|(i, v)| *v = i as u8); + + // Asynchronously write three bytes to the I²C device with 7-bit address 0x2C + futures::select_biased! { + r = i2c.write(0x76u8, &v).fuse() => r.unwrap(), + _ = timeout.fuse() => { + defmt::info!("Timed out."); + } + } + i2c.write(0x76u8, &v).await.unwrap(); + + // Demo finish - just loop until reset + core::future::pending().await +} + +/// Entry point to our bare-metal application. +#[rp2040_hal::entry] +fn main() -> ! { + static EXECUTOR: StaticCell = StaticCell::new(); + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| spawner.spawn(demo()).unwrap()); +} diff --git a/rp-hal/rp2040-hal-examples/src/bin/lcd_display.rs b/rp-hal/rp2040-hal-examples/src/bin/lcd_display.rs new file mode 100644 index 0000000..aebc15b --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/lcd_display.rs @@ -0,0 +1,118 @@ +//! # LCD Display Example +//! +//! In this example, the RP2040 is configured to drive a small two-line +//! alphanumeric LCD using the +//! [HD44780](https://crates.io/crates/hd44780-driver) driver. +//! +//! It drives the LCD by pushing data out of six GPIO pins. It may need to be +//! adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Our LCD driver +use hd44780_driver as hd44780; + +// Some traits we need +use rp2040_hal::clocks::Clock; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, writes to the LCD, then goes +/// to sleep. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Create the LCD driver from some GPIO pins + let mut lcd = hd44780::HD44780::new_4bit( + pins.gpio16.into_push_pull_output(), // Register Select + pins.gpio17.into_push_pull_output(), // Enable + pins.gpio18.into_push_pull_output(), // d4 + pins.gpio19.into_push_pull_output(), // d5 + pins.gpio20.into_push_pull_output(), // d6 + pins.gpio21.into_push_pull_output(), // d7 + &mut delay, + ) + .unwrap(); + + // Clear the screen + lcd.reset(&mut delay).unwrap(); + lcd.clear(&mut delay).unwrap(); + + // Write to the top line + lcd.write_str("rp-hal on", &mut delay).unwrap(); + + // Move the cursor + lcd.set_cursor_pos(40, &mut delay).unwrap(); + + // Write more more text + lcd.write_str("HD44780!", &mut delay).unwrap(); + + // Do nothing - we're finished + loop { + cortex_m::asm::wfi(); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/mem_to_mem_dma.rs b/rp-hal/rp2040-hal-examples/src/bin/mem_to_mem_dma.rs new file mode 100644 index 0000000..85891c4 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/mem_to_mem_dma.rs @@ -0,0 +1,80 @@ +//! # Memory to memory DMA transfer Example +//! +//! This application demonstrates how to use DMA to transfer data from memory to memory buffers. +//! +//! See the top-level `README.md` file for Copyright and licence details. +#![no_std] +#![no_main] + +use cortex_m::singleton; +use cortex_m_rt::entry; +use embedded_hal::digital::OutputPin; +use hal::dma::{single_buffer, DMAExt}; +use hal::pac; +use panic_halt as _; +use rp2040_hal as hal; +use rp2040_hal::clocks::Clock; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Setup clocks and the watchdog. + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // Setup the pins. + let sio = hal::sio::Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Initialize DMA. + let dma = pac.DMA.split(&mut pac.RESETS); + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // Use DMA to transfer some bytes (single buffering). + let tx_buf = singleton!(: [u8; 16] = [0x42; 16]).unwrap(); + let rx_buf = singleton!(: [u8; 16] = [0; 16]).unwrap(); + + // Use a single_buffer to read from tx_buf and write into rx_buf + let transfer = single_buffer::Config::new(dma.ch0, tx_buf, rx_buf).start(); + // Wait for both DMA channels to finish + let (_ch, tx_buf, rx_buf) = transfer.wait(); + + // Compare buffers to see if the data was transferred correctly + // Slow blink on success, fast on failure + let delay_ms = if tx_buf == rx_buf { 1000 } else { 100 }; + + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(delay_ms); + led_pin.set_low().unwrap(); + delay.delay_ms(delay_ms); + } +} diff --git a/rp-hal/rp2040-hal-examples/src/bin/multicore_fifo_blink.rs b/rp-hal/rp2040-hal-examples/src/bin/multicore_fifo_blink.rs new file mode 100644 index 0000000..102fae4 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/multicore_fifo_blink.rs @@ -0,0 +1,175 @@ +//! # Multicore FIFO + GPIO 'Blinky' Example +//! +//! This application demonstrates FIFO communication between the CPU cores on the RP2040. +//! Core 0 will calculate and send a delay value to Core 1, which will then wait that long +//! before toggling the LED. +//! Core 0 will wait for Core 1 to complete this task and send an acknowledgement value. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +use hal::clocks::Clock; +use hal::multicore::{Multicore, Stack}; +use hal::sio::Sio; +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Some traits we need +use embedded_hal::digital::StatefulOutputPin; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Value to indicate that Core 1 has completed its task +const CORE1_TASK_COMPLETE: u32 = 0xEE; + +/// Stack for core 1 +/// +/// Core 0 gets its stack via the normal route - any memory not used by static values is +/// reserved for stack and initialised by cortex-m-rt. +/// To get the same for Core 1, we would need to compile everything separately and +/// modify the linker file for both programs, and that's quite annoying. +/// So instead, core1.spawn takes a [usize] which gets used for the stack. +/// NOTE: We use the `Stack` struct here to ensure that it has 32-byte alignment, which allows +/// the stack guard to take up the least amount of usable RAM. +static CORE1_STACK: Stack<4096> = Stack::new(); + +fn core1_task(sys_freq: u32) -> ! { + let mut pac = unsafe { pac::Peripherals::steal() }; + let core = unsafe { pac::CorePeripherals::steal() }; + + let mut sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let mut led_pin = pins.gpio25.into_push_pull_output(); + let mut delay = cortex_m::delay::Delay::new(core.SYST, sys_freq); + loop { + let input = sio.fifo.read(); + if let Some(word) = input { + delay.delay_ms(word); + led_pin.toggle().unwrap(); + sio.fifo.write_blocking(CORE1_TASK_COMPLETE); + }; + } +} + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let sys_freq = clocks.system_clock.freq().to_Hz(); + + // The single-cycle I/O block controls our GPIO pins + let mut sio = hal::sio::Sio::new(pac.SIO); + + let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo); + let cores = mc.cores(); + let core1 = &mut cores[1]; + let _test = core1.spawn(CORE1_STACK.take().unwrap(), move || core1_task(sys_freq)); + + /// How much we adjust the LED period every cycle + const LED_PERIOD_INCREMENT: i32 = 2; + + /// The minimum LED toggle interval we allow for. + const LED_PERIOD_MIN: i32 = 0; + + /// The maximum LED toggle interval period we allow for. Keep it reasonably short so it's easy to see. + const LED_PERIOD_MAX: i32 = 100; + + // Our current LED period. It starts at the shortest period, which is the highest blink frequency + let mut led_period: i32 = LED_PERIOD_MIN; + + // The direction we're incrementing our LED period. + // Since we start at the minimum value, start by counting up + let mut count_up = true; + + loop { + if count_up { + // Increment our period + led_period += LED_PERIOD_INCREMENT; + + // Change direction of increment if we hit the limit + if led_period > LED_PERIOD_MAX { + led_period = LED_PERIOD_MAX; + count_up = false; + } + } else { + // Decrement our period + led_period -= LED_PERIOD_INCREMENT; + + // Change direction of increment if we hit the limit + if led_period < LED_PERIOD_MIN { + led_period = LED_PERIOD_MIN; + count_up = true; + } + } + + // It should not be possible for led_period to go negative, but let's ensure that. + if led_period < 0 { + led_period = 0; + } + + // Send the new delay time to Core 1. We convert it + sio.fifo.write(led_period as u32); + + // Sleep until Core 1 sends a message to tell us it is done + let ack = sio.fifo.read_blocking(); + if ack != CORE1_TASK_COMPLETE { + // In a real application you might want to handle the case + // where the CPU sent the wrong message - we're going to + // ignore it here. + } + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/multicore_polyblink.rs b/rp-hal/rp2040-hal-examples/src/bin/multicore_polyblink.rs new file mode 100644 index 0000000..2eaa8c1 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/multicore_polyblink.rs @@ -0,0 +1,131 @@ +//! # Multicore Blinking Example +//! +//! This application blinks two LEDs on GPIOs 2 and 3 at different rates (3Hz +//! and 4Hz respectively.) +//! +//! See the top-level `README.md` file for Copyright and licence details. + +#![no_std] +#![no_main] + +use cortex_m::delay::Delay; + +use hal::clocks::Clock; +use hal::gpio::Pins; +use hal::multicore::{Multicore, Stack}; +use hal::sio::Sio; +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Some traits we need +use embedded_hal::digital::StatefulOutputPin; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// The frequency at which core 0 will blink its LED (Hz). +const CORE0_FREQ: u32 = 3; +/// The frequency at which core 1 will blink its LED (Hz). +const CORE1_FREQ: u32 = 4; +/// The delay between each toggle of core 0's LED (us). +const CORE0_DELAY: u32 = 1_000_000 / CORE0_FREQ; +/// The delay between each toggle of core 1's LED (us). +const CORE1_DELAY: u32 = 1_000_000 / CORE1_FREQ; + +/// Stack for core 1 +/// +/// Core 0 gets its stack via the normal route - any memory not used by static +/// values is reserved for stack and initialised by cortex-m-rt. +/// To get the same for Core 1, we would need to compile everything separately +/// and modify the linker file for both programs, and that's quite annoying. +/// So instead, core1.spawn takes a [usize] which gets used for the stack. +/// NOTE: We use the `Stack` struct here to ensure that it has 32-byte +/// alignment, which allows the stack guard to take up the least amount of +/// usable RAM. +static CORE1_STACK: Stack<4096> = Stack::new(); + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // Set up the GPIO pins + let mut sio = Sio::new(pac.SIO); + let pins = Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + let mut led1 = pins.gpio2.into_push_pull_output(); + let mut led2 = pins.gpio3.into_push_pull_output(); + + // Set up the delay for the first core. + let sys_freq = clocks.system_clock.freq().to_Hz(); + let mut delay = Delay::new(core.SYST, sys_freq); + + // Start up the second core to blink the second LED + let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo); + let cores = mc.cores(); + let core1 = &mut cores[1]; + core1 + .spawn(CORE1_STACK.take().unwrap(), move || { + // Get the second core's copy of the `CorePeripherals`, which are per-core. + // Unfortunately, `cortex-m` doesn't support this properly right now, + // so we have to use `steal`. + let core = unsafe { pac::CorePeripherals::steal() }; + // Set up the delay for the second core. + let mut delay = Delay::new(core.SYST, sys_freq); + // Blink the second LED. + loop { + led2.toggle().unwrap(); + delay.delay_us(CORE1_DELAY) + } + }) + .unwrap(); + + // Blink the first LED. + loop { + led1.toggle().unwrap(); + delay.delay_us(CORE0_DELAY) + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/pio_blink.rs b/rp-hal/rp2040-hal-examples/src/bin/pio_blink.rs new file mode 100644 index 0000000..67ac677 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/pio_blink.rs @@ -0,0 +1,79 @@ +//! This example toggles the GPIO25 pin, using a PIO program. +//! +//! If a LED is connected to that pin, like on a Pico board, the LED should blink. +#![no_std] +#![no_main] + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pac; +use hal::pio::PIOExt; +use hal::Sio; +use panic_halt as _; +use rp2040_hal as hal; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then blinks an LED using the PIO peripheral. +#[rp2040_hal::entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // Define some simple PIO program. + const MAX_DELAY: u8 = 31; + let mut a = pio::Assembler::<32>::new(); + let mut wrap_target = a.label(); + let mut wrap_source = a.label(); + // Set pin as Out + a.set(pio::SetDestination::PINDIRS, 1); + // Define begin of program loop + a.bind(&mut wrap_target); + // Set pin low + a.set_with_delay(pio::SetDestination::PINS, 0, MAX_DELAY); + // Set pin high + a.set_with_delay(pio::SetDestination::PINS, 1, MAX_DELAY); + // Define end of program loop + a.bind(&mut wrap_source); + // The labels wrap_target and wrap_source, as set above, + // define a loop which is executed repeatedly by the PIO + // state machine. + let program = a.assemble_with_wrap(wrap_source, wrap_target); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program).unwrap(); + let (int, frac) = (0, 0); // as slow as possible (0 is interpreted as 65536) + let (sm, _, _) = rp2040_hal::pio::PIOBuilder::from_installed_program(installed) + .set_pins(led_pin_id, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + sm.start(); + + // PIO runs in background, independently from CPU + loop { + cortex_m::asm::wfi(); + } +} diff --git a/rp-hal/rp2040-hal-examples/src/bin/pio_dma.rs b/rp-hal/rp2040-hal-examples/src/bin/pio_dma.rs new file mode 100644 index 0000000..a3857e7 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/pio_dma.rs @@ -0,0 +1,117 @@ +//! This example shows how to read from and write to PIO using DMA. +//! +//! If a LED is connected to that pin, like on a Pico board, it will continuously output "HELLO +//! WORLD" in morse code. The example also tries to read the data back. If reading the data fails, +//! the message will only be shown once, and then the LED remains dark. +//! +//! See the top-level `README.md` file for Copyright and licence details. +#![no_std] +#![no_main] + +use cortex_m::singleton; +use cortex_m_rt::entry; +use hal::dma::{double_buffer, single_buffer, DMAExt}; +use hal::gpio::{FunctionPio0, Pin}; +use hal::pac; +use hal::pio::PIOExt; +use hal::sio::Sio; +use panic_halt as _; +use rp2040_hal as hal; + +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +#[entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // HELLO WORLD in morse code: + // .... . .-.. .-.. --- / .-- --- .-. .-.. -.. + #[allow(clippy::unusual_byte_groupings)] + let message = [ + 0b10101010_00100010_11101010_00101110, + 0b10100011_10111011_10000000_10111011, + 0b10001110_11101110_00101110_10001011, + 0b10101000_11101010_00000000_00000000, + ]; + + // Define a PIO program which reads data from the TX FIFO bit by bit, configures the LED + // according to the data, and then writes the data back to the RX FIFO. + let program = pio_proc::pio_asm!( + ".wrap_target", + " out x, 1", + " mov pins, x", + " in x, 1 [13]", + ".wrap" + ); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program.program).unwrap(); + let (mut sm, rx, tx) = rp2040_hal::pio::PIOBuilder::from_installed_program(installed) + .out_pins(led_pin_id, 1) + .clock_divisor_fixed_point(0, 0) // as slow as possible (0 is interpreted as 65536) + .autopull(true) + .autopush(true) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]); + sm.start(); + + let dma = pac.DMA.split(&mut pac.RESETS); + + // Transfer a single message via DMA. + let tx_buf = singleton!(: [u32; 4] = message).unwrap(); + let rx_buf = singleton!(: [u32; 4] = [0; 4]).unwrap(); + let tx_transfer = single_buffer::Config::new(dma.ch0, tx_buf, tx).start(); + let rx_transfer = single_buffer::Config::new(dma.ch1, rx, rx_buf).start(); + let (ch0, tx_buf, tx) = tx_transfer.wait(); + let (ch1, rx, rx_buf) = rx_transfer.wait(); + for i in 0..rx_buf.len() { + if rx_buf[i] != tx_buf[i] { + // The data did not match, abort. + #[allow(clippy::empty_loop)] + loop {} + } + } + + // Chain some buffers together for continuous transfers + let tx_buf2 = singleton!(: [u32; 4] = message).unwrap(); + let rx_buf2 = singleton!(: [u32; 4] = [0; 4]).unwrap(); + let tx_transfer = double_buffer::Config::new((ch0, ch1), tx_buf, tx).start(); + let mut tx_transfer = tx_transfer.read_next(tx_buf2); + let rx_transfer = double_buffer::Config::new((dma.ch2, dma.ch3), rx, rx_buf).start(); + let mut rx_transfer = rx_transfer.write_next(rx_buf2); + loop { + // When a transfer is done we immediately enqueue the buffers again. + if tx_transfer.is_done() { + let (tx_buf, next_tx_transfer) = tx_transfer.wait(); + tx_transfer = next_tx_transfer.read_next(tx_buf); + } + if rx_transfer.is_done() { + let (rx_buf, next_rx_transfer) = rx_transfer.wait(); + for i in 0..rx_buf.len() { + if rx_buf[i] != message[i] { + // The data did not match, abort. + #[allow(clippy::empty_loop)] + loop {} + } + } + rx_transfer = next_rx_transfer.write_next(rx_buf); + } + } +} diff --git a/rp-hal/rp2040-hal-examples/src/bin/pio_proc_blink.rs b/rp-hal/rp2040-hal-examples/src/bin/pio_proc_blink.rs new file mode 100644 index 0000000..75cdebc --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/pio_proc_blink.rs @@ -0,0 +1,67 @@ +//! This example toggles the GPIO25 pin, using a PIO program compiled via pio_proc::pio!(). +//! +//! If a LED is connected to that pin, like on a Pico board, the LED should blink. +#![no_std] +#![no_main] + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pac; +use hal::pio::PIOExt; +use hal::Sio; +use panic_halt as _; +use rp2040_hal as hal; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[rp2040_hal::entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // Define some simple PIO program. + let program = pio_proc::pio_asm!( + ".wrap_target", + "set pins, 1 [31]", + "set pins, 0 [31]", + ".wrap" + ); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program.program).unwrap(); + let (int, frac) = (0, 0); // as slow as possible (0 is interpreted as 65536) + let (mut sm, _, _) = rp2040_hal::pio::PIOBuilder::from_installed_program(installed) + .set_pins(led_pin_id, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]); + sm.start(); + + // PIO runs in background, independently from CPU + loop { + cortex_m::asm::wfi(); + } +} diff --git a/rp-hal/rp2040-hal-examples/src/bin/pio_side_set.rs b/rp-hal/rp2040-hal-examples/src/bin/pio_side_set.rs new file mode 100644 index 0000000..4d077f1 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/pio_side_set.rs @@ -0,0 +1,86 @@ +//! This example toggles the GPIO25 pin, using a PIO program compiled via pio_proc::pio!(). +//! +//! If a LED is connected to that pin, like on a Pico board, the LED should blink. +//! +//! This example makes use of side setting. +#![no_std] +#![no_main] + +use rp2040_hal as hal; + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pio::PIOExt; +use hal::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // Define some simple PIO program. + let program = pio_proc::pio_asm!( + ".side_set 1", // each instruction must set 1 bit + ".wrap_target", + " nop side 1 [15]", + " nop side 0 [15]", + ".wrap", + ); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program.program).unwrap(); + let (int, frac) = (0, 0); // as slow as possible (0 is interpreted as 65536) + let (mut sm, _, _) = hal::pio::PIOBuilder::from_installed_program(installed) + .side_set_pin_base(led_pin_id) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]); + sm.start(); + + // PIO runs in background, independently from CPU + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PIO Side-set Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/pio_synchronized.rs b/rp-hal/rp2040-hal-examples/src/bin/pio_synchronized.rs new file mode 100644 index 0000000..4505df1 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/pio_synchronized.rs @@ -0,0 +1,104 @@ +//! This example toggles the GPIO0 and GPIO1 pins, with each controlled from a +//! separate PIO state machine. +//! +//! Despite running in separate state machines, the clocks are synchronized at +//! the rise and fall times will be simultaneous. +#![no_std] +#![no_main] + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pac; +use hal::pio::PIOExt; +use hal::Sio; +use panic_halt as _; +use rp2040_hal as hal; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[rp2040_hal::entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure pins for Pio0. + let gp0: Pin<_, FunctionPio0, _> = pins.gpio0.into_function(); + let gp1: Pin<_, FunctionPio0, _> = pins.gpio1.into_function(); + + // PIN id for use inside of PIO + let pin0 = gp0.id().num; + let pin1 = gp1.id().num; + + // Define some simple PIO program. + let program = pio_proc::pio_asm!( + " +.wrap_target + set pins, 1 [31] + set pins, 0 [31] +.wrap + " + ); + + // Initialize and start PIO + let (mut pio, sm0, sm1, _, _) = pac.PIO0.split(&mut pac.RESETS); + // I'm "measuring" the phase offset between the two pins by connecting + // then through a LED. If there is a clock offset, there will be a + // short time with a voltage between the pins, so the LED will flash up. + // With a slow clock this is not visible, so use a reasonably fast clock. + let (int, frac) = (256, 0); + + let installed = pio.install(&program.program).unwrap(); + let (mut sm0, _, _) = rp2040_hal::pio::PIOBuilder::from_installed_program( + // Safety: We won't uninstall the program, ever + unsafe { installed.share() }, + ) + .set_pins(pin0, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm0.set_pindirs([(pin0, hal::pio::PinDir::Output)]); + + let (mut sm1, _, _) = rp2040_hal::pio::PIOBuilder::from_installed_program(installed) + .set_pins(pin1, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm1); + // The GPIO pin needs to be configured as an output. + sm1.set_pindirs([(pin1, hal::pio::PinDir::Output)]); + + // Start both SMs at the same time + let group = sm0.with(sm1).sync().start(); + cortex_m::asm::delay(10_000_000); + + // Stop both SMs at the same time + let group = group.stop(); + cortex_m::asm::delay(10_000_000); + + // Start them again and extract the individual state machines + let (sm1, sm2) = group.start().free(); + cortex_m::asm::delay(10_000_000); + + // Stop the two state machines separately + let _sm1 = sm1.stop(); + cortex_m::asm::delay(10_000_000); + let _sm2 = sm2.stop(); + + loop { + cortex_m::asm::wfi(); + } +} diff --git a/rp-hal/rp2040-hal-examples/src/bin/pwm_blink.rs b/rp-hal/rp2040-hal-examples/src/bin/pwm_blink.rs new file mode 100644 index 0000000..08e5628 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/pwm_blink.rs @@ -0,0 +1,121 @@ +//! # PWM Blink Example +//! +//! If you have an LED connected to pin 25, it will fade the LED using the PWM +//! peripheral. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use embedded_hal::pwm::SetDutyCycle; +use rp2040_hal::clocks::Clock; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// The minimum PWM value (i.e. LED brightness) we want +const LOW: u16 = 0; + +/// The maximum PWM value (i.e. LED brightness) we want +const HIGH: u16 = 25000; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then fades the LED in an +/// infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // Init PWMs + let mut pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM4 + let pwm = &mut pwm_slices.pwm4; + pwm.set_ph_correct(); + pwm.enable(); + + // Output channel B on PWM4 to GPIO 25 + let channel = &mut pwm.channel_b; + channel.output_to(pins.gpio25); + + // Infinite loop, fading LED up and down + loop { + // Ramp brightness up + for i in LOW..=HIGH { + delay.delay_us(8); + let _ = channel.set_duty_cycle(i); + } + + // Ramp brightness down + for i in (LOW..=HIGH).rev() { + delay.delay_us(8); + let _ = channel.set_duty_cycle(i); + } + + delay.delay_ms(500); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/pwm_blink_embedded_hal_1.rs b/rp-hal/rp2040-hal-examples/src/bin/pwm_blink_embedded_hal_1.rs new file mode 100644 index 0000000..81fa69d --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/pwm_blink_embedded_hal_1.rs @@ -0,0 +1,121 @@ +//! # PWM Blink Example +//! +//! If you have an LED connected to pin 25, it will fade the LED using the PWM +//! peripheral. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use embedded_hal::pwm::SetDutyCycle; +use rp2040_hal::clocks::Clock; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// The minimum PWM value (i.e. LED brightness) we want +const LOW: u16 = 0; + +/// The maximum PWM value (i.e. LED brightness) we want +const HIGH: u16 = 25000; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then fades the LED in an +/// infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // Init PWMs + let mut pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM4 + let pwm = &mut pwm_slices.pwm4; + pwm.set_ph_correct(); + pwm.enable(); + + // Output channel B on PWM4 to GPIO 25 + let channel = &mut pwm.channel_b; + channel.output_to(pins.gpio25); + + // Infinite loop, fading LED up and down + loop { + // Ramp brightness up + for i in LOW..=HIGH { + delay.delay_us(8); + channel.set_duty_cycle(i).unwrap(); + } + + // Ramp brightness down + for i in (LOW..=HIGH).rev() { + delay.delay_us(8); + channel.set_duty_cycle(i).unwrap(); + } + + delay.delay_ms(500); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/pwm_irq_input.rs b/rp-hal/rp2040-hal-examples/src/bin/pwm_irq_input.rs new file mode 100644 index 0000000..9cb30e4 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/pwm_irq_input.rs @@ -0,0 +1,219 @@ +//! # PWM IRQ Input Example +//! +//! Read a 5V 50Hz PWM servo input signal from gpio pin 1 and turn the LED on when +//! the input signal is high ( > 1600 us duty pulse width ) and off when low ( < 1400 us ). +//! +//! This signal is commonly used with radio control model systems and small servos. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use embedded_hal::digital::OutputPin; + +// Our interrupt macro +use hal::pac::interrupt; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Shorter alias for gpio and pwm modules +use hal::gpio; +use hal::pwm; + +// Some short-cuts to useful types for sharing data with the interrupt handlers +use core::cell::RefCell; +use critical_section::Mutex; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// 50 Hz PWM servo signals have a pulse width between 1000 us and 2000 us with +/// 1500 us as the centre point. us is the abbreviation for micro seconds. +/// +/// The PWM threshold value for turning off the LED in us +const LOW_US: u16 = 1475; + +/// The PWM threshold value for turning on the LED in us +const HIGH_US: u16 = 1525; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Pin types quickly become very long! +/// We'll create some type aliases using `type` to help with that +/// +/// This pin will be our output - it will drive an LED if you run this on a Pico +type LedPin = gpio::Pin, gpio::PullNone>; + +/// This pin will be our input for a 50 Hz servo PWM signal +type InputPwmPin = gpio::Pin; + +/// This will be our PWM Slice - it will interpret the PWM signal from the pin +type PwmSlice = pwm::Slice; + +/// Since we're always accessing these pins together we'll store them in a tuple. +/// Giving this tuple a type alias means we won't need to use () when putting them +/// inside an Option. That will be easier to read. +type LedInputAndPwm = (LedPin, InputPwmPin, PwmSlice); + +/// This how we transfer our LED pin, input pin and PWM slice into the Interrupt Handler. +/// We'll have the option hold both using the LedAndInput type. +/// This will make it a bit easier to unpack them later. +static GLOBAL_PINS: Mutex>> = Mutex::new(RefCell::new(None)); + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then fades the LED in an +/// infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Init PWMs + let pwm_slices = pwm::Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM0 slice + // The PWM slice clock should only run when the input is high (InputHighRunning) + let mut pwm: pwm::Slice<_, pwm::InputHighRunning> = pwm_slices.pwm0.into_mode(); + + // Divide the 125 MHz system clock by 125 to give a 1 MHz PWM slice clock (1 us per tick) + pwm.set_div_int(125); + pwm.enable(); + + // Connect to GPI O1 as the input to channel B on PWM0 + let input_pin = pins.gpio1.reconfigure(); + let channel = &mut pwm.channel_b; + channel.set_enabled(true); + + // Enable an interrupt whenever GPI O1 goes from high to low (the end of a pulse) + input_pin.set_interrupt_enabled(gpio::Interrupt::EdgeLow, true); + + // Configure GPIO 25 as an output to drive our LED. + // we can use reconfigure() instead of into_pull_up_input() + // since the variable we're pushing it into has that type + let led = pins.gpio25.reconfigure(); + + // Give away our pins by moving them into the `GLOBAL_PINS` variable. + // We won't need to access them in the main thread again + critical_section::with(|cs| { + GLOBAL_PINS.borrow(cs).replace(Some((led, input_pin, pwm))); + }); + + // Unmask the IO_BANK0 IRQ so that the NVIC interrupt controller + // will jump to the interrupt function when the interrupt occurs. + // We do this last so that the interrupt can't go off while + // it is in the middle of being configured + unsafe { + pac::NVIC::unmask(pac::Interrupt::IO_IRQ_BANK0); + } + + loop { + // interrupts handle everything else in this example. + cortex_m::asm::wfi(); + } +} + +#[allow(static_mut_refs)] // See https://github.com/rust-embedded/cortex-m/pull/561 +#[interrupt] +fn IO_IRQ_BANK0() { + // The `#[interrupt]` attribute covertly converts this to `&'static mut Option` + static mut LED_INPUT_AND_PWM: Option = None; + + // This is one-time lazy initialisation. We steal the variables given to us + // via `GLOBAL_PINS`. + if LED_INPUT_AND_PWM.is_none() { + critical_section::with(|cs| { + *LED_INPUT_AND_PWM = GLOBAL_PINS.borrow(cs).take(); + }); + } + + // Need to check if our Option contains our pins and pwm slice + // borrow led, input and pwm by *destructuring* the tuple + // these will be of type `&mut LedPin`, `&mut InputPwmPin` and `&mut PwmSlice`, so we + // don't have to move them back into the static after we use them + if let Some((led, input, pwm)) = LED_INPUT_AND_PWM { + // Check if the interrupt source is from the input pin going from high-to-low. + // Note: this will always be true in this example, as that is the only enabled GPIO interrupt source + if input.interrupt_status(gpio::Interrupt::EdgeLow) { + // Read the width of the last pulse from the PWM Slice counter + let pulse_width_us = pwm.get_counter(); + + // if the PWM signal indicates low, turn off the LED + if pulse_width_us < LOW_US { + // set_low can't fail, but the embedded-hal traits always allow for it + // we can discard the Result + let _ = led.set_low(); + } + // if the PWM signal indicates high, turn on the LED + else if pulse_width_us > HIGH_US { + // set_high can't fail, but the embedded-hal traits always allow for it + // we can discard the Result + let _ = led.set_high(); + } + + // If the PWM signal was in the dead-zone between LOW and HIGH, don't change the LED's + // state. The dead-zone avoids the LED flickering rapidly when receiving a signal close + // to the mid-point, 1500 us in this case. + + // Reset the pwm counter back to 0, ready for the next pulse + pwm.set_counter(0); + + // Our interrupt doesn't clear itself. + // Do that now so we don't immediately jump back to this interrupt handler. + input.clear_interrupt(gpio::Interrupt::EdgeLow); + } + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/rom_funcs.rs b/rp-hal/rp2040-hal-examples/src/bin/rom_funcs.rs new file mode 100644 index 0000000..dc2b984 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/rom_funcs.rs @@ -0,0 +1,192 @@ +//! # 'ROM Functions' Example +//! +//! This application demonstrates how to call functions in the RP2040's boot ROM. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Some traits we need +use core::fmt::Write; +use hal::fugit::RateExtU32; +use hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Our Cortex-M systick goes from this value down to zero. For our timer maths +/// to work, this value must be of the form `2**N - 1`. +const SYSTICK_RELOAD: u32 = 0x00FF_FFFF; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then writes to the UART in +/// an infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let mut core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from RP2040) on pin 1 (GPIO0) + pins.gpio0.into_function::(), + // UART RX (characters received by RP2040) on pin 2 (GPIO1) + pins.gpio1.into_function::(), + ); + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + writeln!(uart, "ROM Copyright: {}", hal::rom_data::copyright_string()).unwrap(); + writeln!( + uart, + "ROM Git Revision: 0x{:x}", + hal::rom_data::git_revision() + ) + .unwrap(); + + // Some ROM functions are exported directly, so we can just call them + writeln!( + uart, + "popcount32(0xF000_0001) = {}", + hal::rom_data::popcount32(0xF000_0001) + ) + .unwrap(); + + // Try to hide the numbers from the compiler so it is forced to do the maths + let x = hal::rom_data::popcount32(0xFF) as f32; // 8 + let y = hal::rom_data::popcount32(0xFFF) as f32; // 12 + + // Use systick as a count-down timer + core.SYST.set_reload(SYSTICK_RELOAD); + core.SYST.clear_current(); + core.SYST.enable_counter(); + + // Do some simple sums + let start_soft = cortex_m::peripheral::SYST::get_current(); + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + let soft_result = x * y; + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + let end_soft = cortex_m::peripheral::SYST::get_current(); + + writeln!( + uart, + "{} x {} = {} in {} systicks (doing soft-float maths)", + x, + y, + soft_result, + calc_delta(start_soft, end_soft) + ) + .unwrap(); + + // Some functions require a look-up in a table. First we do the lookup and + // find the function pointer in ROM (you only want to do this once per + // function). + let fmul = hal::rom_data::float_funcs::fmul::ptr(); + + // Then we can call the function whenever we want + let start_rom = cortex_m::peripheral::SYST::get_current(); + let rom_result = fmul(x, y); + let end_rom = cortex_m::peripheral::SYST::get_current(); + + writeln!( + uart, + "{} x {} = {} in {} systicks (using the ROM)", + x, + y, + rom_result, + calc_delta(start_rom, end_rom) + ) + .unwrap(); + + // Now just spin (whilst the UART does its thing) + for _ in 0..1_000_000 { + cortex_m::asm::nop(); + } + + // Reboot back into USB mode (no activity, both interfaces enabled) + rp2040_hal::rom_data::reset_to_usb_boot(0, 0); + + // In case the reboot fails + loop { + cortex_m::asm::wfi(); + } +} + +/// Calculate the number of systicks elapsed between two counter readings. +/// +/// Note: SYSTICK starts at `SYSTICK_RELOAD` and counts down towards zero, so +/// these comparisons might appear to be backwards. +/// +/// ``` +/// assert_eq!(1, calc_delta(SYSTICK_RELOAD, SYSTICK_RELOAD - 1)); +/// assert_eq!(2, calc_delta(0, SYSTICK_RELOAD - 1)); +/// ``` +fn calc_delta(start: u32, end: u32) -> u32 { + if start < end { + (start.wrapping_sub(end)) & SYSTICK_RELOAD + } else { + start - end + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/rosc_as_system_clock.rs b/rp-hal/rp2040-hal-examples/src/bin/rosc_as_system_clock.rs new file mode 100644 index 0000000..d0c7a10 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/rosc_as_system_clock.rs @@ -0,0 +1,334 @@ +//! # ROSC as system clock Example +//! +//! This application demonstrates how to use the ROSC as the system clock on the RP2040. +//! +//! It shows setting the frequency of the ROSC to a measured known frequency, and contains +//! helper functions to configure the ROSC drive strength to reach a desired target frequency. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +use embedded_hal::digital::OutputPin; +use fugit::{HertzU32, RateExtU32}; +use hal::clocks::{Clock, ClockSource, ClocksManager, StoppableClock}; +use hal::pac::rosc::ctrl::FREQ_RANGE_A; +use hal::pac::{CLOCKS, ROSC}; +use hal::rosc::RingOscillator; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function attempts to find appropriate settings for the RingOscillator to reach a target +/// frequency, and then logs the actual attained frequency. +/// +/// The main reasons you'd want to use this is for power-saving in applications where precise +/// timings are not critical (you don't need to use USB peripherals for example). +/// Using the ROSC as the system clock allows under-clocking or over-clocking rp2040, and +/// it also can allow fast waking from a dormant state on the order of µs, which the XOSC cannot do. +/// +/// A motivating application for this was a flir lepton thermal camera module which +/// makes thermal images available via SPI at a rate of around 9Hz. Using the rp2040s ROSC, we +/// are able to clock out the thermal image via SPI and then enter dormant mode until the next vsync +/// interrupt wakes us again, saving some power. +#[rp2040_hal::entry] +fn main() -> ! { + // Set target rosc frequency to 150Mhz + // Setting frequencies can be a matter of a bit of trial and error to see what + // actual frequencies you can easily hit. In practice, the lowest achieved with this method + // is around 32Mhz, and it seems to be able to ramp up to around 230Mhz + let desired_rosc_freq: HertzU32 = 150_000_000.Hz(); + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + led_pin.set_low().unwrap(); + + // Setup the crystal oscillator to do accurate measurements against + let xosc = hal::xosc::setup_xosc_blocking(pac.XOSC, XTAL_FREQ_HZ.Hz()).unwrap(); + + // Find appropriate settings for the desired ring oscillator frequency. + let measured_rosc_frequency = + find_target_rosc_frequency(&pac.ROSC, &pac.CLOCKS, desired_rosc_freq); + let rosc = RingOscillator::new(pac.ROSC); + + // Now initialise the ROSC with the reached frequency and set it as the system clock. + let rosc = rosc.initialize_with_freq(measured_rosc_frequency); + + let mut clocks = ClocksManager::new(pac.CLOCKS); + clocks + .system_clock + .configure_clock(&rosc, rosc.get_freq()) + .unwrap(); + + clocks + .peripheral_clock + .configure_clock(&clocks.system_clock, clocks.system_clock.freq()) + .unwrap(); + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // Now we can disable the crystal oscillator and run off the ring oscillator, for power savings. + let _xosc_disabled = xosc.disable(); + // You may also wish to disable other clocks/peripherals that you don't need. + clocks.usb_clock.disable(); + clocks.gpio_output0_clock.disable(); + clocks.gpio_output1_clock.disable(); + clocks.gpio_output2_clock.disable(); + clocks.gpio_output3_clock.disable(); + clocks.adc_clock.disable(); + clocks.rtc_clock.disable(); + + // Check that desired frequency is close to the frequency speed. + // If it is, turn the LED on. If not, blink the LED. + let got_to_within_1_mhz_of_target = desired_rosc_freq + .to_kHz() + .abs_diff(measured_rosc_frequency.to_kHz()) + < 1000; + + if got_to_within_1_mhz_of_target { + // Now it's possible to easily take the ROSC dormant, to be woken by an external interrupt. + led_pin.set_high().unwrap(); + loop { + cortex_m::asm::wfi(); + } + } else { + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(500); + led_pin.set_low().unwrap(); + delay.delay_ms(500); + } + } +} + +/// Measure the actual speed of the ROSC at the current freq_range and drive strength config +fn rosc_frequency_count_hz(clocks: &CLOCKS) -> HertzU32 { + // Wait for the frequency counter to be ready + while clocks.fc0_status().read().running().bit_is_set() { + cortex_m::asm::nop(); + } + + // Set the speed of the reference clock in kHz. + clocks + .fc0_ref_khz() + .write(|w| unsafe { w.fc0_ref_khz().bits(XTAL_FREQ_HZ / 1000) }); + + // Corresponds to a 1ms test time, which seems to give good enough accuracy + clocks + .fc0_interval() + .write(|w| unsafe { w.fc0_interval().bits(10) }); + + // We don't really care about the min/max, so these are just set to min/max values. + clocks + .fc0_min_khz() + .write(|w| unsafe { w.fc0_min_khz().bits(0) }); + clocks + .fc0_max_khz() + .write(|w| unsafe { w.fc0_max_khz().bits(0xffffffff) }); + + // To measure rosc directly we use the value 0x03. + clocks + .fc0_src() + .write(|w| unsafe { w.fc0_src().bits(0x03) }); + + // Wait until the measurement is ready + while clocks.fc0_status().read().done().bit_is_clear() { + cortex_m::asm::nop(); + } + + let speed_hz = clocks.fc0_result().read().khz().bits() * 1000; + speed_hz.Hz() +} + +/// Resets ROSC frequency range and stages drive strength, then increases the frequency range, +/// drive strength bits, and finally divider in order to try to come close to the desired target +/// frequency, returning the final measured ROSC frequency attained. +fn find_target_rosc_frequency( + rosc: &ROSC, + clocks: &CLOCKS, + target_frequency: HertzU32, +) -> HertzU32 { + reset_rosc_operating_frequency(rosc); + let mut div = 1; + let mut measured_rosc_frequency; + loop { + measured_rosc_frequency = rosc_frequency_count_hz(clocks); + // If it has overshot the target frequency, increase the divider and continue. + if measured_rosc_frequency > target_frequency { + div += 1; + set_rosc_div(rosc, div); + } else { + break; + } + } + loop { + measured_rosc_frequency = rosc_frequency_count_hz(clocks); + if measured_rosc_frequency > target_frequency { + // And probably want to step it down a notch? + break; + } + let can_increase = increase_drive_strength(rosc); + if !can_increase { + let can_increase_range = increase_freq_range(rosc); + if !can_increase_range { + break; + } + } + } + measured_rosc_frequency +} + +fn set_rosc_div(rosc: &ROSC, div: u32) { + assert!(div <= 32); + let div = if div == 32 { 0 } else { div }; + rosc.div().write(|w| unsafe { w.bits(0xaa0 + div) }); +} + +fn reset_rosc_operating_frequency(rosc: &ROSC) { + // Set divider to 1 + set_rosc_div(rosc, 1); + rosc.ctrl().write(|w| w.freq_range().low()); + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); +} + +fn read_freq_stage(rosc: &ROSC, stage: u8) -> u8 { + match stage { + 0 => rosc.freqa().read().ds0().bits(), + 1 => rosc.freqa().read().ds1().bits(), + 2 => rosc.freqa().read().ds2().bits(), + 3 => rosc.freqa().read().ds3().bits(), + 4 => rosc.freqb().read().ds4().bits(), + 5 => rosc.freqb().read().ds5().bits(), + 6 => rosc.freqb().read().ds6().bits(), + 7 => rosc.freqb().read().ds7().bits(), + _ => panic!("invalid frequency drive strength stage"), + } +} + +/// Increase the ROSC drive strength bits for the current freq_range +fn increase_drive_strength(rosc: &ROSC) -> bool { + const MAX_STAGE_DRIVE: u8 = 3; + // Assume div is 1, and freq_range is high + let mut stages: [u8; 8] = [0; 8]; + for (stage_index, stage) in stages.iter_mut().enumerate() { + *stage = read_freq_stage(rosc, stage_index as u8) + } + let num_stages_at_drive_level = match rosc.ctrl().read().freq_range().variant() { + Some(FREQ_RANGE_A::LOW) => 8, + Some(FREQ_RANGE_A::MEDIUM) => 6, + Some(FREQ_RANGE_A::HIGH) => 4, + Some(FREQ_RANGE_A::TOOHIGH) => panic!("Don't use TOOHIGH freq_range"), + None => { + // Start out at initial unset drive stage + return false; + } + }; + let mut next_i = 0; + for (index, x) in stages[0..num_stages_at_drive_level].windows(2).enumerate() { + if x[1] < x[0] { + next_i = index + 1; + break; + } + } + if stages[next_i] < MAX_STAGE_DRIVE { + stages[next_i] += 1; + let min = *stages[0..num_stages_at_drive_level] + .iter() + .min() + .unwrap_or(&0); + for stage in &mut stages[num_stages_at_drive_level..] { + *stage = min; + } + write_freq_stages(rosc, &stages); + true + } else { + false + } +} + +/// Sets the `freqa` and `freqb` ROSC drive strength stage registers. +fn write_freq_stages(rosc: &ROSC, stages: &[u8; 8]) { + let passwd: u32 = 0x9696 << 16; + let mut freq_a = passwd; + let mut freq_b = passwd; + for (stage_index, stage) in stages.iter().enumerate().take(4) { + freq_a |= ((*stage & 0x07) as u32) << (stage_index * 4); + } + for (stage_index, stage) in stages.iter().enumerate().skip(4) { + freq_b |= ((*stage & 0x07) as u32) << ((stage_index - 4) * 4); + } + rosc.freqa().write(|w| unsafe { w.bits(freq_a) }); + rosc.freqb().write(|w| unsafe { w.bits(freq_b) }); +} + +/// Increase the rosc frequency range up to the next step. +/// Returns a boolean to indicate whether the frequency was increased. +fn increase_freq_range(rosc: &ROSC) -> bool { + match rosc.ctrl().read().freq_range().variant() { + None => { + // Initial unset frequency range, move to LOW frequency range + rosc.ctrl().write(|w| w.freq_range().low()); + // Reset all the drive strength bits. + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); + true + } + Some(FREQ_RANGE_A::LOW) => { + // Transition from LOW to MEDIUM frequency range + rosc.ctrl().write(|w| w.freq_range().medium()); + // Reset all the drive strength bits. + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); + true + } + Some(FREQ_RANGE_A::MEDIUM) => { + // Transition from MEDIUM to HIGH frequency range + rosc.ctrl().write(|w| w.freq_range().high()); + // Reset all the drive strength bits. + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); + true + } + Some(FREQ_RANGE_A::HIGH) | Some(FREQ_RANGE_A::TOOHIGH) => { + // Already in the HIGH frequency range, and can't increase + false + } + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/rtc_irq_example.rs b/rp-hal/rp2040-hal-examples/src/bin/rtc_irq_example.rs new file mode 100644 index 0000000..237febe --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/rtc_irq_example.rs @@ -0,0 +1,181 @@ +//! # RTC IRQ Example +//! +//! This application demonstrates use of RTC Interrupts. +//! It is also intended as a general introduction to interrupts with RP2040. +//! +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access and to the gpio and rtc modules. +use hal::{gpio, pac, rtc}; + +// Some traits we need +use embedded_hal::digital::StatefulOutputPin; + +// Our interrupt macro +use hal::pac::interrupt; + +// Some short-cuts to useful types +use core::cell::RefCell; +use critical_section::Mutex; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +// Pin types quickly become very long! +// We'll create some type aliases using `type` to help with that + +/// This pin will be our output - it will drive an LED if you run this on a Pico +type LedPin = gpio::Pin; + +/// Since we're always accessing the pin and the rtc together we'll store them in a tuple. +/// Giving this tuple a type alias means we won't need to use () when putting them +/// inside an Option. That will be easier to read. +type LedAndRtc = (LedPin, rtc::RealTimeClock); + +/// This how we transfer our Led pin and RTC into the Interrupt Handler. +/// We'll have the option hold both using the LedAndRtc type. +/// This will make it a bit easier to unpack them later. +static GLOBAL_SHARED: Mutex>> = Mutex::new(RefCell::new(None)); + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, sets up the RTC irq then goes into sleep in an +/// infinite loop. If there is an LED connected to that pin, it will toggle very minute (1/60 Hz). +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO 25 as an output to drive our LED. + // we can use reconfigure() instead of into_pull_up_input() + // since the variable we're pushing it into has that type + let led = pins.gpio25.reconfigure(); + + // Prepare the RTC for the example using the 1/1/0 (Day/Month/Year) at 0:00:00 as the initial + // day and time (it may not have been a Monday but it doesn't matter for this example). + let mut rtc = hal::rtc::RealTimeClock::new( + pac.RTC, + clocks.rtc_clock, + &mut pac.RESETS, + rtc::DateTime { + year: 0, + month: 1, + day: 1, + day_of_week: rtc::DayOfWeek::Monday, + hour: 0, + minute: 0, + second: 0, + }, + ) + .unwrap(); + + // Trigger the IRQ every time a minute starts. + rtc.schedule_alarm(rtc::DateTimeFilter::default().second(0)); + rtc.enable_interrupt(); + + // Give away our pin and rtc by moving them into the `GLOBAL_SHARED` variable. + // We won't need to access them in the main thread again + critical_section::with(|cs| { + GLOBAL_SHARED.borrow(cs).replace(Some((led, rtc))); + }); + + // Unmask the RTC IRQ so that the NVIC interrupt controller + // will jump to the interrupt function when the interrupt occurs. + // We do this last so that the interrupt can't go off while + // it is in the middle of being configured + unsafe { + pac::NVIC::unmask(pac::Interrupt::RTC_IRQ); + } + + loop { + // interrupts handle everything else in this example. + cortex_m::asm::wfi(); + } +} + +#[allow(non_snake_case)] +#[allow(static_mut_refs)] // See https://github.com/rust-embedded/cortex-m/pull/561 +#[interrupt] +fn RTC_IRQ() { + // The `#[interrupt]` attribute covertly converts this to `&'static mut Option` + static mut LED_AND_RTC: Option = None; + + // This is one-time lazy initialisation. We steal the variables given to us + // via `GLOBAL_SHARED`. + if LED_AND_RTC.is_none() { + critical_section::with(|cs| { + *LED_AND_RTC = GLOBAL_SHARED.borrow(cs).take(); + }); + } + + // Need to check if our Option contains our pins + // LED_AND_RTC is an `&'static mut Option` thanks to the interrupt macro's magic. + // The pattern binding mode handles an ergonomic conversion of the match from `if let Some(led_and_rtc)` + // to `if let Some(ref mut led_and_rtc)`. + // + // https://doc.rust-lang.org/reference/patterns.html#binding-modes + if let Some(led_and_rtc) = LED_AND_RTC { + // borrow led and rtc by *destructuring* the tuple + // these will be of type `&mut LedPin` and `&mut RealTimeClock`, so we don't have + // to move them back into the static after we use them + let (led, rtc) = led_and_rtc; + + // Toggle the led + let _ = led.toggle(); + + // clear the interrupt flag so that it stops firing for now and can be triggered again. + rtc.clear_interrupt(); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/rtc_sleep_example.rs b/rp-hal/rp2040-hal-examples/src/bin/rtc_sleep_example.rs new file mode 100644 index 0000000..0f13056 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/rtc_sleep_example.rs @@ -0,0 +1,167 @@ +//! # RTC Sleep Example +//! +//! This application demonstrates use of RTC Interrupt to wake from deepsleep. +//! It is also intended as a general introduction to interrupts and RTC with RP2040. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access and to the gpio and rtc modules. +use hal::{clocks::ClockGate, gpio, pac, rtc}; + +// Some traits we need +use embedded_hal::digital::StatefulOutputPin; + +// Our interrupt macro +use hal::pac::interrupt; + +// Some short-cuts to useful types +use core::cell::RefCell; +use critical_section::Mutex; + +// Time & clock traits +use fugit::{HertzU32, RateExtU32}; +use hal::Clock; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: HertzU32 = HertzU32::Hz(12_000_000u32); + +/// This how we transfer our RTC instance into the Interrupt Handler. +static GLOBAL_SHARED: Mutex>> = Mutex::new(RefCell::new(None)); + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let mut core = pac::CorePeripherals::take().unwrap(); + + // Configure the clocks + let mut clocks = hal::clocks::ClocksManager::new(pac.CLOCKS); + // First, enable and wait for xosc to be stable. + let xosc = hal::xosc::setup_xosc_blocking(pac.XOSC, XTAL_FREQ_HZ).unwrap(); + + // use xosc at 12MHz for clk_ref -> clk_sys -> clk_peri + clocks + .reference_clock + .configure_clock(&xosc, XTAL_FREQ_HZ) + .unwrap(); + clocks + .system_clock + .configure_clock(&clocks.reference_clock, XTAL_FREQ_HZ) + .unwrap(); + clocks + .peripheral_clock + .configure_clock(&clocks.system_clock, XTAL_FREQ_HZ) + .unwrap(); + // use xosc at 12MHz/256 for clk_rtc + clocks.rtc_clock.configure_clock(&xosc, 46875.Hz()).unwrap(); + // Only leave the rtc's clock enabled while in deep sleep. + let mut config = ClockGate::default(); + config.set_rtc_rtc(true); + clocks.configure_sleep_enable(config); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO 25 as an output to drive our LED. + let mut led = pins.gpio25.into_push_pull_output(); + + // Prepare the RTC for the example using the 1/1/0 (Day/Month/Year) at 0:00:00 as the initial + // day and time (it may not have been a Monday but it doesn't matter for this example.). + let mut rtc = hal::rtc::RealTimeClock::new( + pac.RTC, + clocks.rtc_clock, + &mut pac.RESETS, + rtc::DateTime { + year: 0, + month: 1, + day: 1, + day_of_week: rtc::DayOfWeek::Monday, + hour: 0, + minute: 0, + second: 0, + }, + ) + .unwrap(); + + // Trigger the IRQ every time a minute starts. + rtc.schedule_alarm(rtc::DateTimeFilter::default().second(0)); + // Let the alarm trigger an interrupt in the NVIC. + rtc.enable_interrupt(); + + // Give away our rtc by moving them into the `GLOBAL_SHARED` variable. + // We won't need to access it in the main thread again + critical_section::with(|cs| { + GLOBAL_SHARED.borrow(cs).replace(Some(rtc)); + }); + + // Let the core enter deep-sleep while waiting on wfi + core.SCB.set_sleepdeep(); + + // Unmask the RTC IRQ so that the NVIC interrupt controller + // will jump to the interrupt function when the interrupt occurs. + // We do this last so that the interrupt can't go off while + // it is in the middle of being configured + unsafe { + pac::NVIC::unmask(pac::Interrupt::RTC_IRQ); + } + + loop { + // Wait to be awaken by an interrupt + cortex_m::asm::wfi(); + + // Toggle the led + let _ = led.toggle(); + } +} + +#[allow(non_snake_case)] +#[interrupt] +fn RTC_IRQ() { + critical_section::with(|cs| { + // borrow the content of the Mutexed RefCell. + let mut maybe_rtc = GLOBAL_SHARED.borrow_ref_mut(cs); + + // borrow the content of the Option + if let Some(rtc) = maybe_rtc.as_mut() { + // clear the interrupt flag so that it stops firing for now and can be triggered again. + rtc.clear_interrupt(); + } + }); +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/spi.rs b/rp-hal/rp2040-hal-examples/src/bin/spi.rs new file mode 100644 index 0000000..a7a76ff --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/spi.rs @@ -0,0 +1,127 @@ +//! # SPI Example +//! +//! This application demonstrates how to use the SPI Driver to talk to a remote +//! SPI device. +//! +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use cortex_m::prelude::*; +use hal::clocks::Clock; +use hal::fugit::RateExtU32; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then performs some example +/// SPI transactions, then goes to sleep. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Set up our SPI pins so they can be used by the SPI driver + let spi_mosi = pins.gpio7.into_function::(); + let spi_miso = pins.gpio4.into_function::(); + let spi_sclk = pins.gpio6.into_function::(); + let spi = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); + + // Exchange the uninitialised SPI driver for an initialised one + let mut spi = spi.init( + &mut pac.RESETS, + clocks.peripheral_clock.freq(), + 16.MHz(), + embedded_hal::spi::MODE_0, + ); + + // Write out 0, ignore return value + if spi.write(&[0]).is_ok() { + // SPI write was successful + }; + + // write 50, then check the return + let send_success = spi.send(50); + match send_success { + Ok(_) => { + // We succeeded, check the read value + if let Ok(_x) = spi.read() { + // We got back `x` in exchange for the 0x50 we sent. + }; + } + Err(_) => todo!(), + } + + // Do a read+write at the same time. Data in `buffer` will be replaced with + // the data read from the SPI device. + let mut buffer: [u8; 4] = [1, 2, 3, 4]; + let transfer_success = spi.transfer(&mut buffer); + #[allow(clippy::single_match)] + match transfer_success { + Ok(_) => {} // Handle success + Err(_) => {} // handle errors + }; + + loop { + cortex_m::asm::wfi(); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/spi_dma.rs b/rp-hal/rp2040-hal-examples/src/bin/spi_dma.rs new file mode 100644 index 0000000..8a9f59a --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/spi_dma.rs @@ -0,0 +1,108 @@ +//! # SPI DMA Example +//! +//! This application demonstrates how to use DMA for SPI transfers. +//! +//! The application expects the MISO and MOSI pins to be wired together so that it is able to check +//! whether the data was sent and received correctly. +//! +//! See the top-level `README.md` file for Copyright and licence details. +#![no_std] +#![no_main] + +use cortex_m::singleton; +use cortex_m_rt::entry; +use embedded_hal::digital::OutputPin; +use hal::dma::{bidirectional, DMAExt}; +use hal::fugit::RateExtU32; +use hal::pac; +use panic_halt as _; +use rp2040_hal as hal; +use rp2040_hal::clocks::Clock; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Setup clocks and the watchdog. + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // Setup the pins. + let sio = hal::sio::Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Set up our SPI pins into the correct mode + let spi_mosi = pins.gpio7.into_function::(); + let spi_miso = pins.gpio4.into_function::(); + let spi_sclk = pins.gpio6.into_function::(); + let spi = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); + + // Exchange the uninitialised SPI driver for an initialised one + let spi = spi.init( + &mut pac.RESETS, + clocks.peripheral_clock.freq(), + 16_000_000u32.Hz(), + embedded_hal::spi::MODE_0, + ); + + // Initialize DMA. + let dma = pac.DMA.split(&mut pac.RESETS); + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // Use DMA to transfer some bytes (single buffering). + let tx_buf = singleton!(: [u8; 16] = [0x42; 16]).unwrap(); + let rx_buf = singleton!(: [u8; 16] = [0; 16]).unwrap(); + + // Use BidirectionalConfig to simultaneously write to spi from tx_buf and read into rx_buf + let transfer = bidirectional::Config::new((dma.ch0, dma.ch1), tx_buf, spi, rx_buf).start(); + // Wait for both DMA channels to finish + let ((_ch0, _ch1), tx_buf, _spi, rx_buf) = transfer.wait(); + + // Compare buffers to see if the data was transferred correctly + for i in 0..rx_buf.len() { + if rx_buf[i] != tx_buf[i] { + // Fast blink on error + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(100); + led_pin.set_low().unwrap(); + delay.delay_ms(100); + } + } + } + + // Slow blink on success + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(500); + led_pin.set_low().unwrap(); + delay.delay_ms(500); + } +} diff --git a/rp-hal/rp2040-hal-examples/src/bin/spi_eh_bus.rs b/rp-hal/rp2040-hal-examples/src/bin/spi_eh_bus.rs new file mode 100644 index 0000000..456de17 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/spi_eh_bus.rs @@ -0,0 +1,307 @@ +//! # SPI Bus example +//! +//! This application demonstrates how to construct a simple SPI Driver and +//! configure rp2040-hal's SPI peripheral to access it by utilising +//! [`ExclusiveDevice`] from [`embedded-hal-bus`]. +//! +//! [`ExclusiveDevice`]: +//! https://docs.rs/embedded-hal-bus/latest/embedded_hal_bus/spi/struct.ExclusiveDevice.html +//! [`embedded-hal-bus`]: https://crates.io/crates/embedded-hal-bus +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. +//! +//! ## Glossary +//! +//! * *SPI Bus* - a shared bus consisting of three shared wires (Clock, COPI and +//! CIPO) and a unique 'Chip Select' wire for each device on the bus. An *SPI +//! Bus* typically only has one *SPI Controller* which is 'driving' the bus +//! (specifically it is driving the clock signal and the *COPI* data line). +//! * *COPI* - One of the data lines on an *SPI Bus*. Stands for *Controller +//! Out, Peripheral In*, but we use the term *SPI Device* instead of *SPI +//! Peripheral*. +//! * *CIPO* - One of the data lines on an *SPI Bus*. Stands for *Controller In, +//! Peripheral Out*, but we use the term *SPI Device* instead of *SPI +//! Peripheral*. +//! * *SPI Controller* - a block of silicon within the RP2040 designed for +//! driving an *SPI Bus*. +//! * *SPI Device* - a device on the *SPI Bus*; has its own unique chip select +//! signal. +//! * *Device Driver* - a Rust type (and/or a value of that type) which +//! represents a particular *SPI Device*, such as an Bosch BMA400 +//! accelerometer chip, or an ST7789 LCD controller chip. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some types/traits we need +use core::fmt::Write; +use embedded_hal::spi::Operation; +use embedded_hal::spi::SpiDevice; +use embedded_hal_bus::spi::ExclusiveDevice; +use hal::clocks::Clock; +use hal::fugit::RateExtU32; +use hal::gpio::PinState; +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// The ways our device driver might fail. +/// +/// When things go wrong, we could return `Err(())`` but that wouldn't help +/// anyone troubleshoot so we'll create an error type with additional info to +/// return. +#[derive(Copy, Clone, Debug)] +pub enum MyError { + /// We got an error from the *SPI Device* + Spi(E), + // Add other errors for your driver here. +} + +/// Our *Device Driver* type. +/// +/// This is an example of a device driver that manages some kind of *SPI +/// Device*. +/// +/// It is not a driver for any real device - it's here as an example of how to +/// write such a driver. You would typically get real drivers from a library +/// crate but we've pasted it into the example so you can see it. +/// +/// We need to use a generic here (`SD`), because we want our example +/// driver to work for any other microcontroller that implements the +/// embedded-hal SPI traits - specifically the `embedded_hal::spi::SpiDevice` +/// trait that represents access to a unique device on the bus. +pub struct MySpiDeviceDriver { + spi_dev: SD, +} + +impl MySpiDeviceDriver +where + SD: SpiDevice, +{ + /// Construct a new instance of our *Device Driver*. + /// + /// Takes ownership of a value representing a unique device on the *SPI + /// Bus*. + pub fn new(spi_dev: SD) -> Self { + Self { spi_dev } + } + + /// Write to our hypothetical device. + /// + /// We imagine our device has a register at `0x20`, that accepts a `u8` + /// value. + pub fn set_value(&mut self, value: u8) -> Result<(), MyError> { + self.spi_dev + .transaction(&mut [Operation::Write(&[0x20, value])]) + .map_err(MyError::Spi)?; + + Ok(()) + } + + /// Read from our hypothetical device. + /// + /// We imagine our device has a register at `0x90`, that we can read a 2 + /// byte integer value from. + pub fn get_value(&mut self) -> Result> { + let mut buf = [0u8; 2]; + self.spi_dev + .transaction(&mut [Operation::Write(&[0x90]), Operation::Read(&mut buf)]) + .map_err(MyError::Spi)?; + + Ok(u16::from_le_bytes(buf)) + } +} + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls +/// this function as soon as all global variables and the spinlock are +/// initialised. +/// +/// The function configures the RP2040 peripherals, then performs some example +/// SPI transactions, then goes to sleep. +#[rp2040_hal::entry] +fn main() -> ! { + // ======================================================================== + // Some things that pretty much every rp2040-hal program will do + // ======================================================================== + + // Grab our singleton objects + let mut p = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(p.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + p.XOSC, + p.CLOCKS, + p.PLL_SYS, + p.PLL_USB, + &mut p.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(p.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new(p.IO_BANK0, p.PADS_BANK0, sio.gpio_bank0, &mut p.RESETS); + + // ======================================================================== + // Construct a timer object, which we will need later. + // ======================================================================== + + let timer = hal::Timer::new(p.TIMER, &mut p.RESETS, &clocks); + + // ======================================================================== + // Set up a UART so we can print out the values we read using our *Device + // Driver*: + // ======================================================================== + + // How we want our UART to be configured + let uart_config = UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One); + + // The pins we want to use for our UART. + let uart_pins = ( + // UART TX (characters sent from RP2040) on pin 1 (GPIO0) + pins.gpio0.into_function(), + // UART RX (characters received by RP2040) on pin 2 (GPIO1) + pins.gpio1.into_function(), + ); + + // Create new, uninitialized UART driver with one of the two UART objects + // from our PAC, and our UART pins. We need temporary access to the RESETS + // object to reset the UART. + let uart = hal::uart::UartPeripheral::new(p.UART0, uart_pins, &mut p.RESETS); + + // Swap the uninitialised UART driver for an initialised one, passing in the + // desired configuration. We need to know the internal UART clock frequency + // to set up the baud rate dividers correctly. + let mut uart = uart + .enable(uart_config, clocks.peripheral_clock.freq()) + .unwrap(); + + // ======================================================================== + // Set up our *SPI Bus* and one *SPI Device* on the bus, and then build our + // *Device Driver*: + // ======================================================================== + + // Set up our SPI pins so they can be used by the *SPI Bus* driver. + let spi_copi = pins.gpio7.into_function::(); + let spi_cipo = pins.gpio4.into_function::(); + let spi_sclk = pins.gpio6.into_function::(); + + // Create new, uninitialized *SPI Bus* driver with one of the two *SPI* + // objects from our PAC, plus our data/clock pins for the *SPI Bus*. + // + // We've stubbed out most of the type parameters because the compiler can + // work them out for us. The only one we need to specify is the size of a + // word sent over the *SPI Bus* to our device - and we picked 8 bits (a + // byte). + let spi_bus = hal::spi::Spi::<_, _, _, 8>::new(p.SPI0, (spi_copi, spi_cipo, spi_sclk)); + + // Exchange the uninitialised *SPI Bus* driver for an initialised one, by + // passing in the extra bus parameters required. + // + // We need temporary access to the RESETS object to reset the SPI + // peripheral, and we need to know the internal clock speed in order to set + // up the clock dividers correctly. + let spi_bus = spi_bus.init( + &mut p.RESETS, + clocks.peripheral_clock.freq(), + 16.MHz(), + embedded_hal::spi::MODE_0, + ); + + // The Chip Select pin. Every *SPI Device* has a unique chip select signal, + // and when this pin goes low, it uniquely selects our desired + // (hypothetical) *SPI Device* on the *SPI Bus*. + let spi_cs = pins.gpio8.into_push_pull_output_in_state(PinState::High); + + // Create an object which represents our (hypothetical) *SPI Device* on the + // *SPI Bus*. + // + // We are the only task talking to this *SPI Bus*, so we can use + // `ExclusiveDevice` here. If we had multiple tasks accessing the same bus + // (e.g. you had both an accelerometer and a pressure sensor on the same + // bus), we would need to use a different interface to ensure that we have + // exclusive access to this bus (via `AtomicDevice` or + // `CriticalSectionDevice`, for example). + // + // See https://docs.rs/embedded-hal-bus/0.2.0/embedded_hal_bus/spi for a + // list of options. + // + // We can safely unwrap here, because the only possible failure is CS + // assertion failure and our CS pin is infallible. + // + // Note that it also takes ownership of our timer object so that it has a + // way of measuring elapsed time. This is required so it can handle `Delay` + // transactions that can put small pauses inbetween say, a `Write` + // transaction and a `Read` transaction (which some SPI devices require + // because they're a bit sluggish and take time to process things). + let excl_spi_dev = ExclusiveDevice::new(spi_bus, spi_cs, timer).unwrap(); + + // Now that we've constructed a value of type `ExclusiveDevice` (which + // implements the embedded-hal `SpiDevice` traits) we can finally construct + // our device driver. + let mut driver = MySpiDeviceDriver::new(excl_spi_dev); + + // ======================================================================== + // Now let's use our device driver. + // ======================================================================== + + // Let's write to the device + match driver.set_value(10) { + Ok(_) => { + // Do something on success + _ = writeln!(uart, "Wrote to device OK!"); + } + Err(e) => { + // Do something on failure + _ = writeln!(uart, "Device write error: {:?}", e); + } + } + + // Let's read from the device + match driver.get_value() { + Ok(value) => { + // Do something on success + _ = writeln!(uart, "Read value: {}", value); + } + Err(e) => { + // Do something on failure + _ = writeln!(uart, "Device write error: {:?}", e); + } + } + + // We're done, so just sleep in an infinite loop. Your program should + // probably do something more useful. + loop { + cortex_m::asm::wfi(); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/uart.rs b/rp-hal/rp2040-hal-examples/src/bin/uart.rs new file mode 100644 index 0000000..bb29122 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/uart.rs @@ -0,0 +1,109 @@ +//! # UART Example +//! +//! This application demonstrates how to use the UART Driver to talk to a serial +//! connection. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Some traits we need +use core::fmt::Write; +use hal::fugit::RateExtU32; +use rp2040_hal::clocks::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then writes to the UART in +/// an infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from RP2040) on pin 1 (GPIO0) + pins.gpio0.into_function(), + // UART RX (characters received by RP2040) on pin 2 (GPIO1) + pins.gpio1.into_function(), + ); + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + uart.write_full_blocking(b"UART example\r\n"); + + let mut value = 0u32; + loop { + writeln!(uart, "value: {value:02}\r").unwrap(); + delay.delay_ms(1000); + value += 1 + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/uart_dma.rs b/rp-hal/rp2040-hal-examples/src/bin/uart_dma.rs new file mode 100644 index 0000000..77f65d0 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/uart_dma.rs @@ -0,0 +1,146 @@ +//! # UART DMA Example +//! +//! This application demonstrates how to use the UART peripheral with the +//! DMA controller. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +use cortex_m::singleton; +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::{dma::DMAExt, pac}; + +// Some traits we need +use hal::fugit::RateExtU32; +use rp2040_hal::clocks::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then writes to the UART in +/// an infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from RP2040) on pin 1 (GPIO0) + pins.gpio0.into_function(), + // UART RX (characters received by RP2040) on pin 2 (GPIO1) + pins.gpio1.into_function(), + ); + let uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + // Initialize DMA. + let dma = pac.DMA.split(&mut pac.RESETS); + + // The `write_full_blocking` calls in this example are only used to provide some structure to the + // output. They are not required for using the UART with DMA. + uart.write_full_blocking(b"\r\n\r\nUART DMA echo example\r\n\r\n"); + + // In order to use DMA we need to split the UART into a RX (receive) and TX (transmit) pair + let (rx, tx) = uart.split(); + + // We can still write to the tx side of the UART after splitting + tx.write_full_blocking(b"Regular UART write\r\n"); + + // And we can DMA from a buffer into the UART + let teststring = b"DMA UART write\r\n"; + let tx_transfer = hal::dma::single_buffer::Config::new(dma.ch0, teststring, tx).start(); + + // The actual transfer happens in the background, asynchronously. So the CPU could do something + // else here: + // + // while !tx_transfer.is_done() { + // // do something else + // } + + // Wait for the DMA transfer to finish so we can reuse the tx and the dma channel + let (ch0, _teststring, tx) = tx_transfer.wait(); + + // Let's test DMA RX into a buffer. + tx.write_full_blocking(b"Waiting for you to type 5 letters...\r\n"); + let rx_buf = singleton!(: [u8; 5] = [0; 5]).unwrap(); + let rx_transfer = hal::dma::single_buffer::Config::new(ch0, rx, rx_buf).start(); + let (ch0, rx, rx_buf) = rx_transfer.wait(); + + // Echo back the 5 characters the user typed + tx.write_full_blocking(b"You wrote \""); + tx.write_full_blocking(rx_buf); + tx.write_full_blocking(b"\"\r\n"); + + // Now just keep echoing anything that is received back out of TX + tx.write_full_blocking(b"Now echoing any character you write...\r\n"); + let _tx_transfer = hal::dma::single_buffer::Config::new(ch0, rx, tx).start(); + + loop { + // Everything should be handled by DMA, nothing else to do. The CPU is free to do other tasks. Here, we just wait. + delay.delay_ms(1000); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/uart_loopback.rs b/rp-hal/rp2040-hal-examples/src/bin/uart_loopback.rs new file mode 100644 index 0000000..fd41b5e --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/uart_loopback.rs @@ -0,0 +1,429 @@ +//! # UART Loopback Example +//! +//! This application tests handling UART errors. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. We assume you have connected GP0 to a TTL UART on your host +//! computer at 115200 baud. We assume that GP1 is connected to GP4, which is +//! our UART loopback connection. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use core::fmt::Write; +use embedded_hal::delay::DelayNs; +use hal::fugit::RateExtU32; +use rp2040_hal::clocks::Clock; + +// UART related types +use hal::uart::{DataBits, Parity, StopBits, UartConfig}; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Some sample text we can send. +/// +/// It's the same length as the UART FIFO, deliberately. +static SAMPLE32: [u8; 32] = *b"abcdefghijklmnopqrstuvwxyz012345"; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then writes to the UART in +/// an infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut delay = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart0_pins = ( + // UART TX (characters sent from RP2350) + pins.gpio0.into_function(), + // UART RX (characters received by RP2350) + pins.gpio1.into_pull_up_input().into_function(), + ); + let mut uart0 = hal::uart::UartPeripheral::new(pac.UART0, uart0_pins, &mut pac.RESETS) + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Even), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart0.set_fifos(true); + + let uart1_pins = ( + // UART TX (characters sent from RP2350) + pins.gpio4.into_function(), + // UART RX (characters received by RP2350) + pins.gpio5.into_pull_up_input().into_function(), + ); + let mut uart1 = hal::uart::UartPeripheral::new(pac.UART1, uart1_pins, &mut pac.RESETS) + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Even), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart1.set_fifos(true); + + let mut buffer = [0u8; 128]; + + // ====================================================================== + // Single byte read/write + // ====================================================================== + + let sample = &SAMPLE32[0..1]; + _ = writeln!(uart0, "** Testing single byte write/read"); + uart1.write_full_blocking(sample); + delay.delay_ms(100); + uart0 + .read_full_blocking(&mut buffer[0..sample.len()]) + .unwrap(); + if &buffer[0..sample.len()] != sample { + _ = writeln!( + uart0, + "Failed:\n{:02x?} !=\n{:02x?}", + &buffer[0..sample.len()], + sample + ); + panic!("Test failed"); + } + _ = writeln!(uart0, "Tested OK"); + + // ====================================================================== + // FIFO backed read/write + // ====================================================================== + + let sample = &SAMPLE32[..]; + _ = writeln!(uart0, "** Testing FIFO write/read"); + uart1.write_full_blocking(sample); + delay.delay_ms(100); + uart0 + .read_full_blocking(&mut buffer[0..sample.len()]) + .unwrap(); + if &buffer[0..sample.len()] != sample { + _ = writeln!( + uart0, + "Failed:\n{:02x?} !=\n{:02x?}", + &buffer[0..sample.len()], + sample + ); + panic!("Test failed"); + } + _ = writeln!(uart0, "Tested OK"); + + // ====================================================================== + // FIFO overflow read/write + // + // Note: The Arm Primecell PL022 UART that Raspberry Pi uses has a 32-byte + // FIFO. We're about to overflow that FIFO. + // ====================================================================== + + let sample = &SAMPLE32[..]; + _ = writeln!(uart0, "** Testing FIFO overflow write/read"); + _ = writeln!(uart0, "Sending {} bytes...", sample.len() + 1); + // send 32 bytes to the receiving FIFO + uart1.write_full_blocking(sample); + // Now send one more byte to overflow the receiving FIFO. This byte is lost + // to the wind. + // + // NB: It fits into the TX FIFO because this is a 'blocking' call that + // waited for FIFO space. + uart1.write_full_blocking(&[0x00]); + // Let the TX FIFO drain. + delay.delay_ms(100); + // the first 32 bytes should read fine + uart0 + .read_full_blocking(&mut buffer[0..sample.len()]) + .unwrap(); + if &buffer[0..sample.len()] != sample { + _ = writeln!( + uart0, + "Failed:\n{:02x?} !=\n{:02x?}", + &buffer[0..sample.len()], + sample + ); + panic!("Test failed"); + } + _ = writeln!(uart0, "Got first 32 bytes OK..."); + + _ = writeln!( + uart0, + "I now want to see Overrun([]), WouldBlock, WouldBlock" + ); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + // Now send two more bytes - the first will also be flagged with an overrun error. + _ = writeln!(uart0, "Sending two more bytes..."); + uart1.write_full_blocking(&[0x01, 0x02]); + // let them transfer over + delay.delay_ms(100); + // annoyingly we see the overrun error again, then our data + _ = writeln!(uart0, "I want to see Overrun([1]), Ok(1), WouldBlock"); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + + // ====================================================================== + // FIFO read/write with parity error + // ====================================================================== + + _ = writeln!(uart0, "** Testing FIFO read with parity errors"); + // Send three bytes with correct parity + uart1.write_full_blocking(&[0x00, 0x01, 0x02]); + delay.delay_ms(100); + // send one with bad settings + uart1 = uart1 + .disable() + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Odd), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart1.write_full_blocking(&[0x03]); + delay.delay_ms(100); + // send three more with good parity + uart1 = uart1 + .disable() + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Even), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart1.write_full_blocking(&[0x04, 0x05, 0x06]); + delay.delay_ms(100); + + _ = writeln!(uart0, "I want to see Parity error [0, 1, 2]"); + match uart0.read_raw(&mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + _ = writeln!(uart0, "I want to see RX: [4, 5, 6]"); + match uart0.read_raw(&mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + _ = writeln!(uart0, "I want to see WouldBlock"); + match uart0.read_raw(&mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + + // ====================================================================== + // FIFO backed read/write with embedded_io traits. + // ====================================================================== + + let sample = &SAMPLE32[..]; + _ = writeln!(uart0, "** Testing FIFO write/read with embedded-io"); + + embedded_io::Write::write_all(&mut uart1, sample).unwrap(); + delay.delay_ms(100); + + embedded_io::Read::read_exact(&mut uart0, &mut buffer[0..sample.len()]).unwrap(); + if &buffer[0..sample.len()] != sample { + _ = writeln!( + uart0, + "Failed:\n{:02x?} !=\n{:02x?}", + &buffer[0..sample.len()], + sample + ); + panic!("Test failed"); + } + _ = writeln!(uart0, "Tested OK"); + + // ====================================================================== + // FIFO read/write with parity error using embedded-io + // ====================================================================== + + _ = writeln!(uart0, "** Testing FIFO read with parity errors"); + // Send three bytes with correct parity + uart1.write_full_blocking(&[0x00, 0x01, 0x02]); + delay.delay_ms(100); + // send one with bad settings + uart1 = uart1 + .disable() + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Odd), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart1.write_full_blocking(&[0x03]); + delay.delay_ms(100); + // send three more with good parity + uart1 = uart1 + .disable() + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Even), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart1.write_full_blocking(&[0x04, 0x05, 0x06]); + delay.delay_ms(100); + + _ = writeln!(uart0, "I want to see RX: [0, 1, 2]"); + match embedded_io::Read::read(&mut uart0, &mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + _ = writeln!(uart0, "I want to see ParityError"); + match embedded_io::Read::read(&mut uart0, &mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + + _ = writeln!(uart0, "I want to see RX: [4, 5, 6]"); + match embedded_io::Read::read(&mut uart0, &mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + + _ = writeln!(uart0, "I want to see RX ready: false"); + match embedded_io::ReadReady::read_ready(&mut uart0) { + Ok(ready) => { + _ = writeln!(uart0, "RX ready: {}", ready); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + + // ====================================================================== + // Tests complete + // ====================================================================== + + _ = writeln!(uart0, "Tests complete. Review output for correctness."); + + // Reboot back into USB mode (no activity, both interfaces enabled) + rp2040_hal::rom_data::reset_to_usb_boot(0, 0); + + // In case the reboot fails + loop { + cortex_m::asm::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"UART Loopback Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + // wait about 1s for UART FIFOs to flush + for _ in 0..75_000_000 { + cortex_m::asm::nop(); + } + // Reboot back into USB mode (no activity, both interfaces enabled) + rp2040_hal::rom_data::reset_to_usb_boot(0, 0); + + // In case the reboot fails + loop { + cortex_m::asm::wfi(); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/vector_table.rs b/rp-hal/rp2040-hal-examples/src/bin/vector_table.rs new file mode 100644 index 0000000..4532398 --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/vector_table.rs @@ -0,0 +1,182 @@ +//! # RAM Vector Table example +//! +//! This application demonstrates how to create a new Interrupt Vector Table in RAM. +//! To demonstrate the extra utility of this, we also replace an entry in the Vector Table +//! with a new one. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate +use hal::pac; +use static_cell::ConstStaticCell; + +// Some traits we need +use core::cell::RefCell; +use critical_section::Mutex; +use embedded_hal::digital::StatefulOutputPin; +use hal::fugit::MicrosDurationU32; +use pac::interrupt; +use rp2040_hal::clocks::Clock; +use rp2040_hal::timer::Alarm; +use rp2040_hal::vector_table::VectorTable; + +// Memory that will hold our vector table in RAM +static RAM_VTABLE: ConstStaticCell = ConstStaticCell::new(VectorTable::new()); + +// Give our LED and Alarm a type alias to make it easier to refer to them +type LedAndAlarm = ( + hal::gpio::Pin, + hal::timer::Alarm0, +); + +// Place our LED and Alarm type in a static variable, so we can access it from interrupts +static LED_AND_ALARM: Mutex>> = Mutex::new(RefCell::new(None)); + +// Period that each of the alarms will be set for - 1 second and 300ms respectively +const SLOW_BLINK_INTERVAL_US: MicrosDurationU32 = MicrosDurationU32::secs(1); +const FAST_BLINK_INTERVAL_US: MicrosDurationU32 = MicrosDurationU32::millis(300); + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Need to make a reference to the Peripheral Base at this scope to avoid confusing the borrow checker + let ppb = &mut pac.PPB; + + let ram_vtable = RAM_VTABLE.take(); + // Copy the vector table that cortex_m_rt produced into the RAM vector table + ram_vtable.init(ppb); + // Replace the function that is called on Alarm0 interrupts with a new one + ram_vtable.register_handler(pac::Interrupt::TIMER_IRQ_0 as usize, timer_irq0_replacement); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // Create simple delay + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let led_pin = pins.gpio25.into_push_pull_output(); + + let mut timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + critical_section::with(|cs| { + let mut alarm = timer.alarm_0().unwrap(); + // Schedule an alarm in 1 second + let _ = alarm.schedule(SLOW_BLINK_INTERVAL_US); + // Enable generating an interrupt on alarm + alarm.enable_interrupt(); + // Move alarm into ALARM, so that it can be accessed from interrupts + LED_AND_ALARM.borrow(cs).replace(Some((led_pin, alarm))); + }); + // Unmask the timer0 IRQ so that it will generate an interrupt + unsafe { + pac::NVIC::unmask(pac::Interrupt::TIMER_IRQ_0); + } + + // After 5 seconds, switch to our modified vector rable + delay.delay_ms(5000); + unsafe { + critical_section::with(|_| { + ram_vtable.activate(ppb); + }); + } + + loop { + // Wait for an interrupt to fire before doing any more work + cortex_m::asm::wfi(); + } +} + +// Regular interrupt handler for Alarm0. The `interrupt` macro will perform some transformations to ensure +// that this interrupt entry ends up in the vector table. +#[interrupt] +fn TIMER_IRQ_0() { + critical_section::with(|cs| { + // Temporarily take our LED_AND_ALARM + let ledalarm = LED_AND_ALARM.borrow(cs).take(); + if let Some((mut led, mut alarm)) = ledalarm { + // Clear the alarm interrupt or this interrupt service routine will keep firing + alarm.clear_interrupt(); + // Schedule a new alarm after SLOW_BLINK_INTERVAL_US have passed (1 second) + let _ = alarm.schedule(SLOW_BLINK_INTERVAL_US); + // Blink the LED so we know we hit this interrupt + led.toggle().unwrap(); + // Return LED_AND_ALARM into our static variable + LED_AND_ALARM + .borrow(cs) + .replace_with(|_| Some((led, alarm))); + } + }); +} + +// This is the function we will use to replace TIMER_IRQ_0 in our RAM Vector Table +extern "C" fn timer_irq0_replacement() { + critical_section::with(|cs| { + let ledalarm = LED_AND_ALARM.borrow(cs).take(); + if let Some((mut led, mut alarm)) = ledalarm { + // Clear the alarm interrupt or this interrupt service routine will keep firing + alarm.clear_interrupt(); + // Schedule a new alarm after FAST_BLINK_INTERVAL_US have passed (300 milliseconds) + let _ = alarm.schedule(FAST_BLINK_INTERVAL_US); + led.toggle().unwrap(); + // Return LED_AND_ALARM into our static variable + LED_AND_ALARM + .borrow(cs) + .replace_with(|_| Some((led, alarm))); + } + }); +} + +// End of file diff --git a/rp-hal/rp2040-hal-examples/src/bin/watchdog.rs b/rp-hal/rp2040-hal-examples/src/bin/watchdog.rs new file mode 100644 index 0000000..139100f --- /dev/null +++ b/rp-hal/rp2040-hal-examples/src/bin/watchdog.rs @@ -0,0 +1,111 @@ +//! # Watchdog Example +//! +//! This application demonstrates how to use the RP2040 Watchdog. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Some traits we need +use embedded_hal::digital::OutputPin; +use hal::fugit::ExtU32; +use rp2040_hal::clocks::Clock; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then toggles a GPIO pin in +/// an infinite loop. After a period of time, the watchdog will kick in to reset +/// the CPU. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure an LED so we can show the current state of the watchdog + let mut led_pin = pins.gpio25.into_push_pull_output(); + + // Set the LED high for 2 seconds so we know when we're about to start the watchdog + led_pin.set_high().unwrap(); + delay.delay_ms(2000); + + // Set to watchdog to reset if it's not reloaded within 1.05 seconds, and start it + watchdog.start(1_050.millis()); + + // Blink once a second for 5 seconds, refreshing the watchdog timer once a second to avoid a reset + for _ in 1..=5 { + led_pin.set_low().unwrap(); + delay.delay_ms(500); + led_pin.set_high().unwrap(); + delay.delay_ms(500); + watchdog.feed(); + } + + // Blink 5 times per second, not feeding the watchdog. + // The processor should reset in 1.05 seconds, or 5 blinks time + loop { + led_pin.set_low().unwrap(); + delay.delay_ms(100); + led_pin.set_high().unwrap(); + delay.delay_ms(100); + } +} + +// End of file diff --git a/rp-hal/rp2040-hal-macros/.gitignore b/rp-hal/rp2040-hal-macros/.gitignore new file mode 100644 index 0000000..ff47c2d --- /dev/null +++ b/rp-hal/rp2040-hal-macros/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/rp-hal/rp2040-hal-macros/Cargo.toml b/rp-hal/rp2040-hal-macros/Cargo.toml new file mode 100644 index 0000000..f55c478 --- /dev/null +++ b/rp-hal/rp2040-hal-macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors = ["The rp-rs Developers"] +description = "Macros used by rp2040-hal" +edition = "2021" +license = "MIT OR Apache-2.0" +name = "rp2040-hal-macros" +readme = "README.md" +repository = "https://github.com/rp-rs/rp-hal" +version = "0.1.0" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = {version = "2.0", features = ["full"]} diff --git a/rp-hal/rp2040-hal-macros/LICENSE-APACHE b/rp-hal/rp2040-hal-macros/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rp-hal/rp2040-hal-macros/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp-hal/rp2040-hal-macros/LICENSE-MIT b/rp-hal/rp2040-hal-macros/LICENSE-MIT new file mode 100644 index 0000000..6e052e3 --- /dev/null +++ b/rp-hal/rp2040-hal-macros/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp-hal/rp2040-hal-macros/NOTICE b/rp-hal/rp2040-hal-macros/NOTICE new file mode 100644 index 0000000..790ecb1 --- /dev/null +++ b/rp-hal/rp2040-hal-macros/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp-hal/rp2040-hal-macros/README.md b/rp-hal/rp2040-hal-macros/README.md new file mode 100644 index 0000000..dc58797 --- /dev/null +++ b/rp-hal/rp2040-hal-macros/README.md @@ -0,0 +1,21 @@ +# `rp2040-hal-macros` + +Macros used by rp2040-hal. + +## Entry macro + +Extension of the `cortex-m-rt` `#[entry]` with rp2040 specific initialization code. + +Currently, it just unlocks all spinlocks before calling the entry function. + +# License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/rp-hal/rp2040-hal-macros/src/lib.rs b/rp-hal/rp2040-hal-macros/src/lib.rs new file mode 100644 index 0000000..f8e9c71 --- /dev/null +++ b/rp-hal/rp2040-hal-macros/src/lib.rs @@ -0,0 +1,59 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::{parse, parse_macro_input, Item, ItemFn, Stmt}; + +#[proc_macro_attribute] +pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { + let mut f = parse_macro_input!(input as ItemFn); + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + + let clear_locks: TokenStream = quote!(unsafe { + const SIO_BASE: u32 = 0xd0000000; + const SPINLOCK0_PTR: *mut u32 = (SIO_BASE + 0x100) as *mut u32; + const SPINLOCK_COUNT: usize = 32; + for i in 0..SPINLOCK_COUNT { + SPINLOCK0_PTR.wrapping_add(i).write_volatile(1); + } + }) + .into(); + let clear_locks = parse_macro_input!(clear_locks as Stmt); + + // statics must stay first so cortex_m_rt::entry still finds them + let stmts = insert_after_static(f.block.stmts, clear_locks); + f.block.stmts = stmts; + + quote!( + #[::cortex_m_rt::entry] + #f + ) + .into() +} + +/// Insert new statements after initial block of statics +fn insert_after_static(stmts: impl IntoIterator, insert: Stmt) -> Vec { + let mut istmts = stmts.into_iter(); + let mut stmts = vec![]; + for stmt in istmts.by_ref() { + match stmt { + Stmt::Item(Item::Static(var)) => { + stmts.push(Stmt::Item(Item::Static(var))); + } + _ => { + stmts.push(insert); + stmts.push(stmt); + break; + } + } + } + stmts.extend(istmts); + + stmts +} diff --git a/rp-hal/rp2040-hal/.gitignore b/rp-hal/rp2040-hal/.gitignore new file mode 100644 index 0000000..ff47c2d --- /dev/null +++ b/rp-hal/rp2040-hal/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/rp-hal/rp2040-hal/CHANGELOG.md b/rp-hal/rp2040-hal/CHANGELOG.md new file mode 100644 index 0000000..d5ff3ac --- /dev/null +++ b/rp-hal/rp2040-hal/CHANGELOG.md @@ -0,0 +1,422 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### MSRV + +The Minimum-Supported Rust Version (MSRV) for the next release is 1.79 + +### Added + +- Support for *binary info*, which is metadata that `picotool` can read from your binary. +- Bump MSRV to 1.77, because *binary info* examples need C-Strings. +- Bump MSRV to 1.79 to enable inline\_const, used for static asserts. + +### Fixed + +- Let UART embedded\_io::Write::write return some bytes were written. +- Fix unsoundness in definition of stack for spawning core1. + +## [0.10.0] - 2024-03-10 + +### Added + +- Implement i2c-write-iter traits - #765 @ithinuel +- Add categories and keywords to Cargo.toml - #769 @rursprung +- Add getters to the various pad overrides - #768 @ithinuel +- Add an example for using embedded-alloc - #306 @umgefahren @jannic +- Introduce async.await i2c implementation - #747 @ithinuel +- Support embedded\_hal 1.0.0 - #734 #736 #753 @jannic @jonathanpallant +- Implement defmt formatting and Debug for clocks::InitError - #751 @jannic +- Implement embedded-io Read + Write for UartPeripheral - #727 @Sympatron +- Add from\_installed\_program with correct shift direction - #715 @jannic +- Add derive(defmt::Format) to i2c::peripheral::I2CEvent - #726 @ithinuel +- Make PIO IRQ IDs into ZSTs - #723 @9ary +- Add RTC examples & expand RealTimeClock & ClockManager capabilities - #676 @ithinuel +- Allow to use ADC free-running mode without FIFO - #739 @jannic +- Add on-chip voltage regulator (VREG) voltage setting function - #757 @AkiyukiOkayasu +- Support for entering and exiting DORMANT mode - #701 @andrewh42 + +### Changed + +- Update lower VCO frequency limit according to datasheet update - #773 @ithinuel +- Bump MSRV to 1.75 - #761 @ithinuel +- Move on-target-tests back to the work space - #762 @ithinuel +- Set startup\_delay\_multiplier of XOSC to 64, and make it configurable. + This should increase compatibility with boards where the oscillator starts up + more slowly than on the Raspberry Pico. - #746 @jannic +- Replace asm macros by rust macros - #730 @jannic +- Update usb-device implementation - #584 @ithinuel +- Update rp2040-pac to v0.6.0 and apply required changes - #770 @AkiyukiOkayasu +- Some reorganization of ADC code, making sure that AdcPin can only + be created for pins that can actually be used as ADC channels - #739 @jannic +- Breaking change: Clear the input-enable flag of all pins on bank 0 in `Pins::new`. + They will automatically be enabled when setting a pin function, so most users + won't be affected by that change. Notable exception: If you rely on the fact that + PIO can read all pins as input even if the pin is not configured to the PIO function, + you may need to set the input-enable flag manually. - #755 @jannic + +### Fixed + +- Properly report UART break conditions - #712 @jannic +- Ensure that i2c pins have PullUp activated - #708 @jannic +- PWM: Set TOP to 0xfffe by default and fix get\_max\_duty - #744 @jannic +- Add missing ValidFunction implementation for DynFunction - #738 @ithinuel +- Fix RealTimeClock & UsbBus ownership - #725 @jnthbdn +- Make Spi::free also free up the pins - #719 @SCingolani +- Add safety comments to unsafe rom function - #721 @jannic +- Several cleanups and documentation updates - #716 #718 #720 #743 #763 #767 #776 #777 #778 #779 @jannic @ithinuel + +## [0.9.1] - 2023-11-12 + +### Added + +- Add function to enable multiple PWM slices at the same time - #668 @jlpettersson +- Add uart\_is\_busy() method - #711 @Rahix +- Re-export fugit - #573 @jannic +- Add function to clear PIO fifos - #670 @ThadHouse +- Implement WriteIter and WriteIterRead - #703 @thejpster +- Implement InputPin for all pin configurations - #689 @jannic +- Provide functions to access watchdog scratch registers - #685 @jannic +- Implement reset() and halt() functions - #613 @jannic + +### Fixed + +- Fix USB PLL's VCO frequency according to updated datasheet - #688 @ithinuel, @jannic +- Fix UART transmit\_flushed method - #713 @jannic +- Fix calculation of pll frequency for refdiv != 1 - #691 @vinsynth + +### Changed + +- Improve documentation - #692 #696 #697 #698 #699 #702 #704 #709 #714 @9names @fu5ha @ithinuel @jannic +- Migrate to eh1\_0 rc 1 - #681 @ithinuel + +## [0.9.0] - 2023-09-01 + +### MSRV + +The Minimum-Supported Rust Version (MSRV) for this release is 1.64 + +### Fixed + +- With rust nightly and beta 1.70.0, the multi-core spawn code could get miscompiled + due to undefined behavior in our code. This was first found in embassy (which + uses similar code) and reported to us by @Dirbaio. - #612 @ithinuel +- Fixed embedded-hal 1.0-alpha implementation of SPI - #611 @tomgilligan +- Fixed the on-target tests - #601 @jannic +- Fix calculation of MPU RBAR value - #653 @9names @jannic +- Fix of usb device address mask - #639 @mrdxxm +- Fix on target tests - #624 @jannic +- Make sure clocks are initialized before creating a Timer - #618 @jannic +- Mark ReadTarget and WriteTarget as unsafe - #621 @jannic +- Fix typo in rom_data.rs - #675 @jlpettersson + +### Changed + +- multicore: remove the requirement on the closure to never return - #594 @ithinuel +- Updated dependency on rp2040-boot2 to version 0.3.0. - @jannic + This doubles the flash access speed to the value used by the C SDK by + default. So it should usually be safe. However, if you are overclocking + the RP2040, you might need to lower the flash speed accordingly. +- Doc: Several improvements have been made to documentation: #607 #597 #661 #633 #632 #629 #679 +- DMA: Check for valid word sizes at compile time - #600 @jannic +- Use an enum for core identification. - @ithinuel +- Merge DynPin and Pin into Pin. The type class used in Pin now have a runtime variant allowing for + the creation of uniform array of pins (eg: `[Pin]`). - @ithinuel +- Fix miss defined ValidPinMode bound allowing any Bank0 pin to be Xip and any Qspi pin to be any + other function (except for clock). - @ithinuel +- Use `let _ =` to ignore result rather than `.ok();` as this gives a false sense the result is + checked. - @ithinuel +- Reduce code repetition in i2c modules. - @ithinuel +- Rename `DontInvert` to `Normal`. - @ithinuel +- Prevent the creation of multiple instances of `adc::TempSensor` - @ithinuel +- Update dependency on rp2040-pac to 0.5.0 - #662 @jannic +- Migrate rp2040-hal to edition 2021 - #651 @ithinuel +- Fix lifetimes and mutability of get_buf and get_buf_mut - #649 @adrianparvino +- Rename dma::SingleChannel::listen_irq* to enable_irq* - #648 @nilclass +- Update embedded-hal alpha support to version 1.0.0-alpha.11 - #642 @jannic + +### Added + +- timer::Timer implements the embedded-hal delay traits and Copy/Clone - #614 @ithinuel @jannic +- DMA: Allow access to the DMA engine's byteswapping feature - #603 @Gip-Gip +- Added `AdcPin` wrapper to disable digital function for ADC operations - @ithinuel +- Added `Sealed` supertrait to `PIOExt` - @ithinuel +- Added pins to `Spi` to fix inconsistencies in gpio bounds in peripheral (i2c, uart, spi) - @ithinuel +- Added `sio::Sio::read_bank0() -> u32` to provide single instruction multiple io read. +- Implement WriteTarget for PWM top and cc registers. - #646 @mBornand +- Add the ability to initialise the ring oscillator with a known frequency - #640 @hardiesoft +- Add ADC free-running mode & FIFO - #626 @nilclass +- Add DMA support for free-running ADC capture - #636 @nilclass +- Make SPI set_format accept frame format - #653 @NelsonAPenn + +## [0.8.1] - 2023-05-05 + +### Added + +- Re-enabled implementations of traits from embedded-hal-nb 1.0.0-alpha.1 - #569 @jannic +- PIO: Added `set_mov_status_config` to `PIOBuilder` - #566 @Gip-gip +- memory.x: Added SRAM4 and SRAM5 blocks - #578 @jannic +- DMA: Added memory-to-memory example - #579 @jlpettersson + +### Changed + +- pwm::Slice::has_overflown() returns the raw interrupt flag, without masking/forcing. - #562 @jannic +- Update embedded-hal alpha support to version 1.0.0-alpha.10 - #582 @jannic +- Update embedded-hal-nb alpha support to version 1.0.0-alpha.2 - #582 @jannic +- DMA: Fixed an issue where `check_irq0` would check `irq1` on channel 1 - #580 @jlpettersson +- Serial: Fixed possible overflow of the baudrate calculation - #583 @ArchUsr64 +- Doc: Several improvements have been made to documentation: #567 #570 #574 #575 #577 #586 +- CI: A few improvements have been made to CI pipelines: #555 #587 #588 + +## [0.8.0] - 2023-02-16 + +### Added + +- Add SPI slave support - @zaksabeast +- Add set_base_1and0 to interpolator - @tomgilligan +- Add DMA support, with implementations for SPI, PIO, UART - @9names, @mgottschlag, @Nashenas88 +- Add alarm cancellation feature - @MuratUrsavas +- Add Pin::id() - @jannic +- Add interrupt functions for DynPin - @larsarv + +### Changed + +- Several documentation improvements - @nmattia, @tianrking +- CI improvements - @ithinuel, @jannic +- Marked several traits as Sealed - @jannic +- Removed `pwm::PwmPinToken` and add PWM IRQ Input example - @dlkj + +## [0.7.0] - 2022-12-11 + +### MSRV + +The Minimum-Supported Rust Version (MSRV) for this release is 1.62 + +### Changed + +- Fixed: First frame is getting lost on a USB-CDC device - @MuratUrsavas +- Moved BSP crates to separate repo at https://github.com/rp-rs/rp-hal-boards - @jannic +- Avoid losing USB status events by reading ints rather than sie_status in poll - @ithinuel +- Allow setting clock divisors on running state machines - @jannic +- Remove unnecessary `mut` from `static mut LOCK_OWNER: AtomicU8` in critical section impl - @zachs18 +- Update dependency on rp2040-pac to 0.4.0 - @jannic +- Update embedded-hal alpha support to version 1.0.0-alpha.9 - @jannic + (Non-blocking traits were moved to embedded-hal-nb, which is not yet supported) +- Implement UartConfig::new constructor method - @jannic +- Deprecate uart::common_configs - @jannic +- Fix spelling error in UART error discarded field - @Sizurka +- Fix contents of UART error discarded field - @Sizurka +- Fix watchdog counter load value - @Sizurka +- Fix concurrent accesses to sm_execctrl and sm_instr when sideset isn't optional - @ithinuel +- pio: Move interrupt related (en|dis)abling/forcing methods to the statemachine - @ithinuel +- Mark Timer & Alarm* Send and Sync - @ithinuel +- The feature critical-section-impl is now enabled by default from the BSP crates - @jannic +- Simplify signature of Alarm::schedule, removing generic parameter - @ithinuel +- USB: Use the dedicated write_bitmask_* functions - @ithinuel + +### Added + +- Add docs.rs metadata - @icedrocket +- Implement embedded-hal alpha SPI traits - @ptpaterson +- Add derive(Debug) and derive(defmt::Format) to error types - @9names +- Add ability to modify installed PIO program wrap bounds - @davidcole1340 +- Add rtic-monotonic support for timer & alarms (feature gated) - @ithinuel +- Add SPI is_busy function - @papyDoctor +- Add set_fifos/set_rx_watermark/set_tx_watermark - @papyDoctor +- Add a method to allow setting the PIO's clock divisor without floats - @ithinuel +- Use TimerInstant in Timer::GetCounter & add Alarm::schedule_at - @ithinuel + +### Removed + +- Removed support for critical-section 0.2 (was already deprecated) - @jannic + +## [0.6.1] - 2022-11-30 + +### Changed + +- Upgraded dependency on critical-section 0.2 to 0.2.8 - @jannic + (There is also a dependency on version 1.0.0) +- Remove critical-section impl for version 0.2 - @jannic + Both 0.2.8 and 1.x use the same symbols internally to implement the + critical sections, so one impl is sufficient, and having both causes + compilation errors + +## [0.6.0] - 2022-08-26 + +### Added + +- Documentation Example for the bsp_pin! macro - @ hmvp +- Timer: Documentation & doc examples for Timers - @9names +- Add suspend, resume and remote wakeup support. - @ithinuel & @jannic +- `rp2040-e5` feature enabling the workaround for errata 5 on the USB device peripheral. - @ithinuel +- NonPwmPinMode for gpio::Disabled - @FlorianUekermann +- RAM-based interrupt vector tables - @9names +- Support for critical-section 1.0.0. + Critical-section 0.2 is still supported (ie. a custom-impl is provided, compatible + with the 1.0.0 implementation), to avoid a breaking change. It will be removed + later. - @jannic +- Add support for the Interpolator. @fenax + +### Changed + +- Update dev dependencies on cortex-m-rtic to 1.1.2 - @jannic +- Use correct interrupt names in `timer::alarms` - @hmvp +- Update embedded-hal alpha support to version 1.0.0-alpha.8 - @jannic +- Fix PIO rx fifo status - @jannic +- Implement `From<&SomeClock> for Hertz` instead of `From for Hertz` + for the clocks in `rp2040_hal::clocks`. - @jannic +- Fix i2c example using the wrong clock. - @jannic +- Fix duty cycle handing on disabled pwm channels. - @jannic +- GPIO IRQ example: add check for interrupt source - @9names +- Align USB synchronisation requirements with the manual & pico-sdk - @ithinuel +- Update dependencies on usb-device to 0.2.9 - @ithinuel +- Use wfi in otherwise empty infinite loops in examples. - @jannic +- Use generic bootloader in examples - @jannic & @ithinuel +- Use `rp2040-hal`'s entry function. - @ithinuel +- Migrate from `embedded-time` to `fugit` - @ithinuel +- Fix PIO's `set_pins` and `set_pindirs` when `out_sticky` is set. - @jannic & @ithinuel +- Clarify usage of boot2 section - @a-gavin + +### Removed + +- Unnecessary cortex_m::interrupt::free in timer.rs - @jannic +- Unused embassy-traits deps - @9names + +## [0.5.0] - 2022-06-13 + +### MSRV + +The Minimum-Supported Rust Version (MSRV) for this release is 1.61 + +### Added + +- RP2040 specific #[entry] macro that releases spinlocks - @jannic +- Start multiple state machines in sync with each other - @astraw +- Unsafe fn for freeing all spinlocks when you can't use the RP2040 entry macro (eg RTIC) - @9names +- Optional feature for enabling defmt formatting for i2c errors - @ithinuel +- Accessor for getting the offset of an installed PIO program - @fenax + +### Changed + +- Use thread send safe UART* marker when splitting, improves UART ergonmics - @marius-meissner +- Improve performance for hardware division intrinsics. Internal intrinsics cleanup - @Sizurka +- Provide a better alarm abstraction - @ithinuel +- Update Multicore::spawn to be able to take a closure without requiring alloc. + Improve Multicore ergonomics and add example for how to use new API - @Liamolucko +- Allow PIO program to be 32 instructions long, was previously limited to 31 - @jannic +- Fix Typos - @mqy, @danbev +- Replace generic pio::Tx::write with write_u8_replicated, write_u16_replicated, and update + write to take a u32. The generic version was too easy to misuse. - @9names + +### Removed + +- I2c async driver. Use new one at https://github.com/ithinuel/rp2040-async-i2c/ - @ithinuel +- Unused fields from UartPeripheral and Reader - @jannic + +## [0.4.0] - 2022-03-09 + +### Added + +- ROM function caching +- ROM version lookup function +- Compiler intrinsics for ROM functions +- Compiler intrinsics for hardware divider +- Document bsp_pins! macro +- UART IRQ examples +- PIO side-set example +- Stopped PIO state machines can change their clock divider +- Added HAL IRQ example + +### Changed + +- Rewrite UART driver to own its pins +- Improve UART defaults +- Fix repeated-read in i2c embassy driver +- Fix bug in i2c peripheral state machine +- Fix race condition in alarm +- Fix safety bugs in hardware divider +- Enable watchdog reset trigger bits when watchdog enabled +- Update spinlocks to use new PAC API +- Use generics to improve spinlock implementation +- Update critical_section to use new spinlock implementation +- Update embedded-hal alpha support to version 1.0.0-alpha.7 +- Avoid 64-bit division in clock calculations +- Update pio and pio-proc to 0.2.0 + +## [0.3.0] - 2021-12-19 + +### MSRV + +The Minimum-Supported Rust Version (MSRV) for this release is 1.54. + +### Added + +- A README! +- Implementation of the `critical-section` API +- Embedded HAL 1.0-alpha6 support +- I²C Controller and Peripheral support +- Multi-core support, including spin-locks and SIO FIFO +- RTC support +- USB Device support +- Timer support +- PIO support +- Implementation of `rng_core::RngCore` for `RingOscillator` +- ADC example +- GPIO Interrupt support +- Multi-core FIFO example +- PIO LED Blinky example +- ROM Functions example +- SPI example +- Watchdog example +- ADC documentation +- Lots of bug fixes :) + +### Changed + +- Modified PIO API for better ergonomics +- Updated PAC to 0.2.0 +- Exported common driver structs from top-level (e.g. it's now `Sio`, not `sio::Sio`) + +## [0.2.0] - 2021-08-14 + +### Added + +- Updated version with support for: + - Ring Oscillator + - Crystal Oscillator + - Plls + - Watchdog + - Clocks + - GPIO + - PWM + - ADC + - SPI + - I²C + - Resets + - UART + - Hardware divide/modulo + +## [0.1.0] - 2021-01-21 + +- Initial release + +[Unreleased]: https://github.com/rp-rs/rp-hal/compare/v0.10.0...HEAD +[0.10.0]: https://github.com/rp-rs/rp-hal/compare/v0.9.1...v0.10.0 +[0.9.1]: https://github.com/rp-rs/rp-hal/compare/v0.9.0...v0.9.1 +[0.9.0]: https://github.com/rp-rs/rp-hal/compare/v0.8.1...v0.9.0 +[0.8.1]: https://github.com/rp-rs/rp-hal/compare/v0.8.0...v0.8.1 +[0.8.0]: https://github.com/rp-rs/rp-hal/compare/v0.7.0...v0.8.0 +[0.7.0]: https://github.com/rp-rs/rp-hal/compare/v0.6.0...v0.7.0 +[0.6.1]: https://github.com/rp-rs/rp-hal/compare/v0.6.0...v0.6.1 +[0.6.0]: https://github.com/rp-rs/rp-hal/compare/v0.5.0...v0.6.0 +[0.5.0]: https://github.com/rp-rs/rp-hal/compare/v0.4.0...v0.5.0 +[0.4.0]: https://github.com/rp-rs/rp-hal/compare/v0.3.0...v0.4.0 +[0.3.0]: https://github.com/rp-rs/rp-hal/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/rp-rs/rp-hal/compare/v0.1.0...v0.2.0 +[0.1.0]: https://github.com/rp-rs/rp-hal/releases/tag/v0.1.0 diff --git a/rp-hal/rp2040-hal/Cargo.toml b/rp-hal/rp2040-hal/Cargo.toml new file mode 100644 index 0000000..074d184 --- /dev/null +++ b/rp-hal/rp2040-hal/Cargo.toml @@ -0,0 +1,100 @@ +[package] +authors = ["The rp-rs Developers"] +categories = ["embedded", "hardware-support", "no-std", "no-std::no-alloc"] +description = "A Rust Embedded-HAL impl for the rp2040 microcontroller" +edition = "2021" +homepage = "https://github.com/rp-rs/rp-hal" +keywords = ["embedded", "hal", "raspberry-pi", "rp2040", "embedded-hal"] +license = "MIT OR Apache-2.0" +name = "rp2040-hal" +repository = "https://github.com/rp-rs/rp-hal" +rust-version = "1.79" +version = "0.10.0" + +[package.metadata.docs.rs] +features = ["rt", "rom-v2-intrinsics", "defmt", "rtic-monotonic"] +targets = ["thumbv6m-none-eabi"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# Non-optional dependencies. Keep these sorted by name. +bitfield = {version = "0.14.0"} +cortex-m = "0.7.2" +critical-section = {version = "1.2.0"} +embedded-dma = "0.2.0" +embedded-hal = "1.0.0" +embedded-hal-async = "1.0.0" +embedded-hal-nb = "1.0.0" +embedded-io = "0.6.1" +embedded_hal_0_2 = {package = "embedded-hal", version = "0.2.5", features = ["unproven"]} +frunk = {version = "0.4.1", default-features = false} +fugit = "0.3.6" +itertools = {version = "0.10.1", default-features = false} +nb = "1.0" +paste = "1.0" +pio = "0.2.0" +rand_core = "0.6.3" +rp-binary-info = { version = "0.1.0", path = "../rp-binary-info" } +rp-hal-common = {version="0.1.0", path="../rp-hal-common"} +rp2040-hal-macros = {version = "0.1.0", path = "../rp2040-hal-macros"} +rp2040-pac = {version = "0.6.0", features = ["critical-section"]} +usb-device = "0.3" +vcell = "0.1" +void = {version = "1.0.2", default-features = false} + +# Optional dependencies. Keep these sorted by name. +chrono = {version = "0.4", default-features = false, optional = true} +defmt = {version = ">=0.2.0, <0.4", optional = true} +i2c-write-iter = {version = "1.0.0", features = ["async"], optional = true} +rtic-monotonic = {version = "1.0.0", optional = true} + +[dev-dependencies] +# Non-optional dependencies. Keep these sorted by name. +pio-proc = "0.2.0" +rand = {version = "0.8.5", default-features = false} + +# Optional dependencies. Keep these sorted by name. +# None + +[features] +# Minimal startup / runtime for Cortex-M microcontrollers +rt = ["rp2040-pac/rt"] + +# Memoize(cache) ROM function pointers on first use to improve performance +rom-func-cache = [] + +# Disable automatic mapping of language features (like floating point math) to ROM functions +disable-intrinsics = [] + +# This enables ROM functions for f64 math that were not present in the earliest RP2040s +rom-v2-intrinsics = [] + +# This enables a fix for USB errata 5: USB device fails to exit RESET state on busy USB bus. +# Only required for RP2040 B0 and RP2040 B1, but it also works for RP2040 B2 and above +# **Note that the workaround takes control of pin 15 (bank0) during usb reset so the bank needs +# to be taken out of reset before calling `UsbBus::new`**. +# Using `let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS);` +# is enough to take the Bank 0 out of reset. +rp2040-e5 = [] + +# critical section that is safe for multicore use +critical-section-impl = ["critical-section/restore-state-u8"] + +# Add conversion functions between chrono types and the rp2040-hal specific DateTime type +chrono = ["dep:chrono"] + +# Implement `defmt::Format` for several types. +defmt = ["dep:defmt"] + +# Implement `rtic_monotonic::Monotonic` based on the RP2040 timer peripheral +rtic-monotonic = ["dep:rtic-monotonic"] + +# Implement `i2c-write-iter` traits +i2c-write-iter = ["dep:i2c-write-iter"] + +# Add a binary-info header block containing picotool-compatible metadata. +# +# Requires 'rt' so that the vector table is correctly sized and therefore the +# header is within reach of picotool. +binary-info = ["rt", "rp-binary-info/binary-info"] diff --git a/rp-hal/rp2040-hal/LICENSE-APACHE b/rp-hal/rp2040-hal/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rp-hal/rp2040-hal/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp-hal/rp2040-hal/LICENSE-MIT b/rp-hal/rp2040-hal/LICENSE-MIT new file mode 100644 index 0000000..6e052e3 --- /dev/null +++ b/rp-hal/rp2040-hal/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp-hal/rp2040-hal/NOTICE b/rp-hal/rp2040-hal/NOTICE new file mode 100644 index 0000000..790ecb1 --- /dev/null +++ b/rp-hal/rp2040-hal/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp-hal/rp2040-hal/README.md b/rp-hal/rp2040-hal/README.md new file mode 100644 index 0000000..ac961c0 --- /dev/null +++ b/rp-hal/rp2040-hal/README.md @@ -0,0 +1,132 @@ + +
+

+ + Logo + + +

rp-hal

+ +

+ High-level Rust drivers for the Raspberry Silicon RP2040 Microcontroller +
+ Explore the API docs » +
+
+ View Demos + · + Report a Bug + · + Chat on Matrix +

+

+ + + + +
+

Table of Contents

+
    +
  1. Introduction
  2. +
  3. Getting Started
  4. +
  5. Roadmap
  6. +
  7. Contributing
  8. +
  9. License
  10. +
  11. Contact
  12. +
  13. Acknowledgements
  14. +
+
+ + +## Introduction + +This is the `rp2040-hal` package - a library crate of high-level Rust drivers +for the Raspberry Silicon RP2040 microcontroller, along with a collection of +non-board specific example programs for you to study. You should use this crate +in your application if you want to write code for the RP2040 microcontroller. +The *HAL* in the name standards for *Hardware Abstraction Layer*, and comes from +the fact that many of the drivers included implement the generic +hardware-abstraction interfaces defined in the Rust Embedded Working Group's +[embedded-hal](https://github.com/rust-embedded/embedded-hal) crate. + +We also provide a series of [*Board Support Package* (BSP) crates][BSPs], which take +this HAL crate and pre-configure the pins according to a specific PCB design. If +you are using one of the supported boards, you should use one of those crates in +preference, and return here to see documentation about specific peripherals on +the RP2040 and how to use them. See the `boards` folder in +https://github.com/rp-rs/rp-hal-boards/ for more details. + +[BSPs]: https://github.com/rp-rs/rp-hal-boards/ + + +## Getting Started + +To include this crate in your project, amend your `Cargo.toml` file to include + +```toml +rp2040-hal = "0.10.0" +``` + +To obtain a copy of the source code (e.g. if you want to propose a bug-fix or +new feature, or simply to study the code), run: + +```console +$ git clone https://github.com/rp-rs/rp-hal.git +``` + +For details on how to program an RP2040 microcontroller, see the [top-level +rp-hal README](https://github.com/rp-rs/rp-hal/). To see this HAL in use, +see either the examples in this repository, or the examples included with each +BSP. + + +## Roadmap + +NOTE This HAL is under active development. As such, it is likely to remain +volatile until a 1.0.0 release. + +See the [open issues](https://github.com/rp-rs/rp-hal/issues) for a list of +proposed features (and known issues). + +### Implemented traits + +This crate aims to implement all traits from embedded-hal, both version +0.2 and 1.0. They can be used at the same time, so you can upgrade drivers +incrementally. + + +## Contributing + +Contributions are what make the open source community such an amazing place to +be learn, inspire, and create. Any contributions you make are **greatly +appreciated**. + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + + +## License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + + +## Contact + +* Project Link: [https://github.com/rp-rs/rp-hal/issues](https://github.com/rp-rs/rp-hal/issues) +* Matrix: [#rp-rs:matrix.org](https://matrix.to/#/#rp-rs:matrix.org) + + +## Acknowledgements + +* [Othneil Drew's README template](https://github.com/othneildrew) diff --git a/rp-hal/rp2040-hal/src/adc.rs b/rp-hal/rp2040-hal/src/adc.rs new file mode 100644 index 0000000..835ee7c --- /dev/null +++ b/rp-hal/rp2040-hal/src/adc.rs @@ -0,0 +1,925 @@ +//! Analog-Digital Converter (ADC) +//! +//! See [Chapter 4 Section 9](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) of the datasheet for more details +//! +//! ## Usage +//! +//! Capture ADC reading from a pin: + +//! ```no_run +//! // Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +//! use embedded_hal_0_2::adc::OneShot; +//! use rp2040_hal::{adc::Adc, adc::AdcPin, gpio::Pins, pac, Sio}; +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Configure one of the pins as an ADC input +//! let mut adc_pin_0 = AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); +//! // Read the ADC counts from the ADC channel +//! let pin_adc_counts: u16 = adc.read(&mut adc_pin_0).unwrap(); +//! ``` +//! +//! Capture ADC reading from temperature sensor. Note that this needs conversion to be a real-world temperature. +//! +//! ```no_run +//! // Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +//! use embedded_hal_0_2::adc::OneShot; +//! use rp2040_hal::{adc::Adc, gpio::Pins, pac, Sio}; +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Enable the temperature sensor +//! let mut temperature_sensor = adc.take_temp_sensor().unwrap(); +//! // Read the ADC counts from the ADC channel +//! let temperature_adc_counts: u16 = adc.read(&mut temperature_sensor).unwrap(); +//! ``` +//! +//! See [examples/adc.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal-examples/src/bin/adc.rs) and +//! [pimoroni_pico_explorer_showcase.rs](https://github.com/rp-rs/rp-hal-boards/tree/main/boards/pimoroni-pico-explorer/examples/pimoroni_pico_explorer_showcase.rs) for more complete examples +//! +//! ### Free running mode with FIFO +//! +//! In free-running mode the ADC automatically captures samples in regular intervals. +//! The samples are written to a FIFO, from which they can be retrieved. +//! +//! ```no_run +//! use rp2040_hal::{adc::Adc, gpio::Pins, pac, Sio}; +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Enable the temperature sensor +//! let mut temperature_sensor = adc.take_temp_sensor().unwrap(); +//! +//! // Configure & start capturing to the fifo: +//! let mut fifo = adc.build_fifo() +//! .clock_divider(0, 0) // sample as fast as possible (500ksps. This is the default) +//! .set_channel(&mut temperature_sensor) +//! .start(); +//! +//! loop { +//! if fifo.len() > 0 { +//! // Read one captured ADC sample from the FIFO: +//! let temperature_adc_counts: u16 = fifo.read(); +//! } +//! } +//! ``` +//! See [examples/adc_fifo_poll.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal-examples/src/bin/adc_fifo_poll.rs) for a more complete example. +//! +//! ### Using DMA +//! +//! When the ADC is in free-running mode, it's possible to use DMA to transfer data from the FIFO elsewhere, without having to read the FIFO manually. +//! +//! This requires a number of steps: +//! 1. Build an `AdcFifo`, with DMA enabled ([`AdcFifoBuilder::enable_dma`]) +//! 2. Use [`AdcFifoBuilder::prepare`] instead of [`AdcFifoBuilder::start`], so that the FIFO is created in `paused` state +//! 3. Start a DMA transfer ([`dma::single_buffer::Transfer`], [`dma::double_buffer::Transfer`], ...), using the [`AdcFifo::dma_read_target`] as the source (`from` parameter) +//! 4. Finally unpause the FIFO by calling [`AdcFifo::resume`], to start capturing +//! +//! Example: +//! ```no_run +//! use rp2040_hal::{adc::Adc, gpio::Pins, pac, Sio, dma::{single_buffer, DMAExt}}; +//! use cortex_m::singleton; +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS); +//! let dma = peripherals.DMA.split(&mut peripherals.RESETS); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Enable the temperature sensor +//! let mut temperature_sensor = adc.take_temp_sensor().unwrap(); +//! +//! // Configure & start capturing to the fifo: +//! let mut fifo = adc.build_fifo() +//! .clock_divider(0, 0) // sample as fast as possible (500ksps. This is the default) +//! .set_channel(&mut temperature_sensor) +//! .enable_dma() +//! .prepare(); +//! +//! // Set up a buffer, where the samples should be written: +//! let buf = singleton!(: [u16; 500] = [0; 500]).unwrap(); +//! +//! // Start DMA transfer +//! let transfer = single_buffer::Config::new(dma.ch0, fifo.dma_read_target(), buf).start(); +//! +//! // Resume the FIFO to start capturing +//! fifo.resume(); +//! +//! // Wait for the transfer to complete: +//! let (ch, adc_read_target, buf) = transfer.wait(); +//! +//! // do something with `buf` (it now contains 500 samples read from the ADC) +//! //... +//! +//! ``` +//! //! See [examples/adc_fifo_dma.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal-examples/src/bin/adc_fifo_dma.rs) for a more complete example. +//! +//! ### Free running mode without FIFO +//! +//! While free-running mode is usually used in combination with a FIFO, there are +//! use cases where it can be used without. For example, if you want to be able to +//! get the latest available sample at any point in time, and without waiting 96 ADC clock +//! cycles (2µs). +//! +//! In this case, you can just enable free-running mode on it's own. The ADC will +//! continuously do ADC conversions. The ones not read will just be discarded, but it's +//! always possible to read the latest value, without additional delay: +//! +//! ```no_run +//! use rp2040_hal::{adc::Adc, adc::AdcPin, gpio::Pins, pac, Sio}; +//! // Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +//! use embedded_hal_0_2::adc::OneShot; +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Configure one of the pins as an ADC input +//! let mut adc_pin_0 = AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); +//! // Enable free-running mode +//! adc.free_running(&adc_pin_0); +//! // Read the ADC counts from the ADC channel whenever necessary +//! loop { +//! let pin_adc_counts: u16 = adc.read_single(); +//! // Do time critical stuff +//! } +//! ``` + +use core::convert::Infallible; +use core::marker::PhantomData; +// Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +use embedded_hal_0_2::adc::{Channel, OneShot}; + +use crate::{ + dma, + gpio::{ + bank0::{Gpio26, Gpio27, Gpio28, Gpio29}, + AnyPin, DynBankId, DynPinId, Function, OutputEnableOverride, Pin, PullType, ValidFunction, + }, + pac::{dma::ch::ch_ctrl_trig::TREQ_SEL_A, ADC, RESETS}, + resets::SubsystemReset, + typelevel::Sealed, +}; + +const TEMPERATURE_SENSOR_CHANNEL: u8 = 4; + +/// The pin was invalid for the requested operation +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InvalidPinError; + +/// A pin locked in use with the ADC. +pub struct AdcPin

+where + P: AnyPin, +{ + pin: P, + saved_output_disable: bool, + saved_input_enable: bool, +} + +impl

AdcPin

+where + P: AnyPin, +{ + /// Captures the pin to be used with an ADC and disables its digital circuitry. + pub fn new(pin: P) -> Result { + let pin_id = pin.borrow().id(); + if (26..=29).contains(&pin_id.num) && pin_id.bank == DynBankId::Bank0 { + let mut p = pin.into(); + let (od, ie) = (p.get_output_disable(), p.get_input_enable()); + p.set_output_enable_override(OutputEnableOverride::Disable); + p.set_input_enable(false); + Ok(Self { + pin: P::from(p), + saved_output_disable: od, + saved_input_enable: ie, + }) + } else { + Err(InvalidPinError) + } + } + + /// Release the pin and restore its digital circuitry's state. + pub fn release(self) -> P { + let mut p = self.pin.into(); + p.set_output_disable(self.saved_output_disable); + p.set_input_enable(self.saved_input_enable); + P::from(p) + } + + /// Returns the ADC channel of this AdcPin. + pub fn channel(&self) -> u8 { + let pin_id = self.pin.borrow().id(); + // Self::new() makes sure that this is a valid channel number + pin_id.num - 26 + } +} + +/// Trait for entities that can be used as ADC channels. +/// +/// This is implemented by [`AdcPin`] and by [`TempSense`]. +/// The trait is sealed and can't be implemented in other crates. +pub trait AdcChannel: Sealed { + /// Get the channel id used to configure the ADC peripheral. + fn channel(&self) -> u8; +} + +impl Sealed for AdcPin

{} +impl AdcChannel for AdcPin

{ + fn channel(&self) -> u8 { + self.channel() + } +} + +impl Sealed for TempSense {} +impl AdcChannel for TempSense { + fn channel(&self) -> u8 { + TEMPERATURE_SENSOR_CHANNEL + } +} + +macro_rules! channel { + ($pin:ident, $channel:expr) => { + impl Channel for AdcPin> + where + $pin: crate::gpio::ValidFunction, + { + type ID = u8; // ADC channels are identified numerically + + fn channel() -> u8 { + $channel + } + } + }; +} + +channel!(Gpio26, 0); +channel!(Gpio27, 1); +channel!(Gpio28, 2); +channel!(Gpio29, 3); + +impl Channel for AdcPin> +where + DynPinId: crate::gpio::ValidFunction, +{ + type ID = (); // ADC channels are identified at run time + fn channel() {} +} + +/// Internal temperature sensor type +pub struct TempSense { + __private: (), +} + +impl Channel for TempSense { + type ID = u8; // ADC channels are identified numerically + + fn channel() -> u8 { + TEMPERATURE_SENSOR_CHANNEL + } +} + +/// Analog to Digital Convertor (ADC). +/// +/// Represents an ADC within the RP2040. Each ADC has multiple channels, and each +/// channel is either associated with a specific GPIO pin or attached to the internal +/// temperature sensor. You should put the relevant pin into ADC mode by creating an +/// [`AdcPin`] object with it, or you can put the ADC into `Temperature Sensing Mode` +/// by calling [`Adc::take_temp_sensor()`]. Either way, the resulting objects can be +/// passed to the [`OneShot::read()`][a] trait method to actually do the read. +/// +/// [a]: embedded_hal_0_2::adc::OneShot::read +pub struct Adc { + device: ADC, +} + +impl Adc { + /// Create new adc struct and bring up adc + pub fn new(device: ADC, resets: &mut RESETS) -> Self { + device.reset_bring_down(resets); + device.reset_bring_up(resets); + + // Enable adc + device.cs().write(|w| w.en().set_bit()); + + // Wait for adc ready + while !device.cs().read().ready().bit_is_set() {} + + Self { device } + } + + /// Free underlying register block + pub fn free(self) -> ADC { + self.device + } + + /// Read the most recently sampled ADC value + /// + /// This function does not wait for the current conversion to finish. + /// If a conversion is still in progress, it returns the result of the + /// previous one. + /// + /// It also doesn't trigger a new conversion. + pub fn read_single(&self) -> u16 { + self.device.result().read().result().bits() + } + + /// Enable temperature sensor, returns a channel to use. + /// + /// This can only be done once before calling [`Adc::disable_temp_sensor()`]. If the sensor has already + /// been enabled, this method will panic. + #[deprecated( + note = "This method may panic, use `take_temp_sensor()` instead.", + since = "0.9.0" + )] + pub fn enable_temp_sensor(&mut self) -> TempSense { + self.take_temp_sensor() + .expect("Temp sensor is already enabled.") + } + + /// Enable temperature sensor, returns a channel to use + /// + /// If the sensor has already been enabled, this method returns `None`. + pub fn take_temp_sensor(&mut self) -> Option { + let mut disabled = false; + self.device.cs().modify(|r, w| { + disabled = r.ts_en().bit_is_clear(); + // if bit was already set, this is a nop + w.ts_en().set_bit() + }); + disabled.then_some(TempSense { __private: () }) + } + + /// Disable temperature sensor, consumes channel + pub fn disable_temp_sensor(&mut self, _: TempSense) { + self.device.cs().modify(|_, w| w.ts_en().clear_bit()); + } + + /// Start configuring free-running mode, and set up the FIFO + /// + /// The [`AdcFifoBuilder`] returned by this method can be used + /// to configure capture options, like sample rate, channels to + /// capture from etc. + /// + /// Capturing is started by calling [`AdcFifoBuilder::start`], which + /// returns an [`AdcFifo`] to read from. + pub fn build_fifo(&mut self) -> AdcFifoBuilder<'_, u16> { + AdcFifoBuilder { + adc: self, + marker: PhantomData, + } + } + + /// Enable free-running mode by setting the start_many flag. + pub fn free_running(&mut self, pin: &dyn AdcChannel) { + self.device + .cs() + .modify(|_, w| w.ainsel().variant(pin.channel()).start_many().set_bit()); + } + + /// Disable free-running mode by unsetting the start_many flag. + pub fn stop(&mut self) { + self.device.cs().modify(|_, w| w.start_many().clear_bit()); + } + + fn inner_read(&mut self, chan: u8) -> u16 { + self.wait_ready(); + + self.device + .cs() + .modify(|_, w| unsafe { w.ainsel().bits(chan).start_once().set_bit() }); + + self.wait_ready(); + + self.read_single() + } + + /// Wait for the ADC to become ready. + /// + /// Also returns immediately if start_many is set, to avoid indefinite blocking. + pub fn wait_ready(&self) { + while !self.is_ready_or_free_running() { + cortex_m::asm::nop(); + } + } + + fn is_ready_or_free_running(&self) -> bool { + let cs = self.device.cs().read(); + cs.ready().bit_is_set() || cs.start_many().bit_is_set() + } + + /// Returns true if the ADC is ready for the next conversion. + /// + /// This implies that any previous conversion has finished. + pub fn is_ready(&self) -> bool { + self.device.cs().read().ready().bit_is_set() + } +} + +// Implementation for TempSense and type-checked pins +impl OneShot for Adc +where + WORD: From, + SRC: Channel, +{ + type Error = Infallible; + + fn read(&mut self, _pin: &mut SRC) -> nb::Result { + let chan = SRC::channel(); + + Ok(self.inner_read(chan).into()) + } +} + +// Implementation for dyn-pins +impl OneShot>> for Adc +where + WORD: From, + F: Function, + M: PullType, + DynPinId: ValidFunction, + AdcPin>: Channel, +{ + type Error = Infallible; + + fn read(&mut self, pin: &mut AdcPin>) -> nb::Result { + Ok(self.inner_read(pin.channel()).into()) + } +} + +/// Used to configure & build an [`AdcFifo`] +/// +/// See [`Adc::build_fifo`] for details, as well as the `adc_fifo_*` [examples](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal-examples). +pub struct AdcFifoBuilder<'a, Word> { + adc: &'a mut Adc, + marker: PhantomData, +} + +impl<'a, Word> AdcFifoBuilder<'a, Word> { + /// Manually set clock divider to control sample rate + /// + /// The ADC is tied to the USB clock, normally running at 48MHz. + /// ADC conversion happens at 96 cycles per sample, so with the dividers + /// both set to 0 (the default) the sample rate will be `48MHz / 96 = 500ksps`. + /// + /// Setting the `int` and / or `frac` dividers will hold off between + /// samples, leading to an effective rate of: + /// + /// ```text + /// rate = 48MHz / (1 + int + (frac / 256)) + /// ``` + /// + /// To determine the required `int` and `frac` values for a given target rate, + /// use these equations: + /// + /// ```text + /// int = floor((48MHz / rate) - 1) + /// frac = round(256 * ((48MHz / rate) - 1 - int)) + /// ``` + /// + /// Some examples: + /// + /// | Target rate | `int` | `frac` | + /// |-------------|---------|--------| + /// | 1000sps | `47999` | `0` | + /// | 1024sps | `46874` | `0` | + /// | 1337sps | `35900` | `70` | + /// | 4096sps | `11717` | `192` | + /// | 96ksps | `499` | `0` | + /// + /// Since each conversion takes 96 cycles, setting `int` to anything below 96 does + /// not make a difference, and leads to the same result as setting it to 0. + /// + /// The lowest possible rate is 732.41Hz, attainable by setting `int = 0xFFFF, frac = 0xFF`. + /// + /// For more details, please refer to section 4.9.2.2 in the RP2040 datasheet. + pub fn clock_divider(self, int: u16, frac: u8) -> Self { + self.adc + .device + .div() + .modify(|_, w| unsafe { w.int().bits(int).frac().bits(frac) }); + self + } + + /// Select ADC input channel to sample from + /// + /// If round-robin mode is used, this will only affect the first sample. + /// + /// The given `pin` can either be one of the ADC inputs (GPIO26-28) or the + /// internal temperature sensor (retrieved via [`Adc::take_temp_sensor`]). + pub fn set_channel(self, pin: &mut P) -> Self { + self.adc + .device + .cs() + .modify(|_, w| unsafe { w.ainsel().bits(pin.channel()) }); + self + } + + /// Set channels to use for round-robin mode + /// + /// Takes a tuple of channels, like `(&mut adc_pin, &mut temp_sense)`. + /// + /// **NOTE:** *The order in which the channels are specified has no effect! + /// Channels are always sampled in increasing order, by their channel number (Channel 0, Channel 1, ...).* + pub fn round_robin>(self, selected_channels: T) -> Self { + let RoundRobin(bits) = selected_channels.into(); + self.adc + .device + .cs() + .modify(|_, w| unsafe { w.rrobin().bits(bits) }); + self + } + + /// Enable the FIFO interrupt ([`ADC_IRQ_FIFO`](crate::pac::Interrupt::ADC_IRQ_FIFO)) + /// + /// It will be triggered whenever there are at least `threshold` samples waiting in the FIFO. + pub fn enable_interrupt(self, threshold: u8) -> Self { + self.adc.device.inte().modify(|_, w| w.fifo().set_bit()); + self.adc + .device + .fcs() + .modify(|_, w| unsafe { w.thresh().bits(threshold) }); + self + } + + /// Shift values to produce 8 bit samples (discarding the lower 4 bits). + /// + /// Normally the ADC uses 12 bits of precision, packed into a u16. + /// Shifting the values loses some precision, but produces smaller samples. + /// + /// When this method has been called, the resulting fifo's `read` method returns u8. + pub fn shift_8bit(self) -> AdcFifoBuilder<'a, u8> { + self.adc.device.fcs().modify(|_, w| w.shift().set_bit()); + AdcFifoBuilder { + adc: self.adc, + marker: PhantomData, + } + } + + /// Enable DMA for the FIFO. + /// + /// This must be called to be able to transfer data from the ADC using a DMA transfer. + /// + /// **NOTE:** *this method sets the FIFO interrupt threshold to `1`, which is required for DMA transfers to work. + /// The threshold is the same one as set by [`AdcFifoBuilder::enable_interrupt`]. If you want to enable FIFO + /// interrupts, but also use DMA, the `threshold` parameter passed to `enable_interrupt` *must* be set to `1` as well.* + pub fn enable_dma(self) -> Self { + self.adc + .device + .fcs() + .modify(|_, w| unsafe { w.dreq_en().set_bit().thresh().bits(1) }); + self + } + + /// Enable ADC FIFO and start free-running conversion + /// + /// Use the returned [`AdcFifo`] instance to access the captured data. + /// + /// To stop capturing, call [`AdcFifo::stop`]. + /// + /// Note: if you plan to use the FIFO for DMA transfers, [`AdcFifoBuilder::prepare`] instead. + pub fn start(self) -> AdcFifo<'a, Word> { + self.adc.device.fcs().modify(|_, w| w.en().set_bit()); + self.adc.device.cs().modify(|_, w| w.start_many().set_bit()); + AdcFifo { + adc: self.adc, + marker: PhantomData, + } + } + + /// Enable ADC FIFO, but do not start conversion yet + /// + /// Same as [`AdcFifoBuilder::start`], except the FIFO is initially paused. + /// + /// Use [`AdcFifo::resume`] to start conversion. + pub fn start_paused(self) -> AdcFifo<'a, Word> { + self.adc.device.fcs().modify(|_, w| w.en().set_bit()); + self.adc + .device + .cs() + .modify(|_, w| w.start_many().clear_bit()); + AdcFifo { + adc: self.adc, + marker: PhantomData, + } + } + + /// Alias for [`AdcFifoBuilder::start_paused`]. + #[deprecated(note = "Use `start_paused()` instead.", since = "0.10.0")] + pub fn prepare(self) -> AdcFifo<'a, Word> { + self.start_paused() + } +} + +/// Represents the ADC fifo +/// +/// Constructed by [`AdcFifoBuilder::start`], which is accessible through [`Adc::build_fifo`]. +/// +pub struct AdcFifo<'a, Word> { + adc: &'a mut Adc, + marker: PhantomData, +} + +impl<'a, Word> AdcFifo<'a, Word> { + #[allow(clippy::len_without_is_empty)] + /// Returns the number of elements currently in the fifo + pub fn len(&mut self) -> u8 { + self.adc.device.fcs().read().level().bits() + } + + /// Check if there was a fifo overrun + /// + /// An overrun happens when the fifo is filled up faster than `read` is called to consume it. + /// + /// This function also clears the `over` bit if it was set. + pub fn is_over(&mut self) -> bool { + let over = self.adc.device.fcs().read().over().bit(); + if over { + self.adc + .device + .fcs() + .modify(|_, w| w.over().clear_bit_by_one()); + } + over + } + + /// Check if there was a fifo underrun + /// + /// An underrun happens when `read` is called on an empty fifo (`len() == 0`). + /// + /// This function also clears the `under` bit if it was set. + pub fn is_under(&mut self) -> bool { + let under = self.adc.device.fcs().read().under().bit(); + if under { + self.adc + .device + .fcs() + .modify(|_, w| w.under().clear_bit_by_one()); + } + under + } + + /// Read the most recently sampled ADC value + /// + /// Returns the most recently sampled value, bypassing the FIFO. + /// + /// This can be used if you want to read samples occasionally, but don't + /// want to incur the 96 cycle delay of a one-off read. + /// + /// Example: + /// ```ignore + /// // start continuously sampling values: + /// let mut fifo = adc.build_fifo().set_channel(&mut adc_pin).start(); + /// + /// loop { + /// do_something_timing_critical(); + /// + /// // read the most recent value: + /// if fifo.read_single() > THRESHOLD { + /// led.set_high().unwrap(); + /// } else { + /// led.set_low().unwrap(); + /// } + /// } + /// + /// // stop sampling, when it's no longer needed + /// fifo.stop(); + /// ``` + /// + /// Note that when round-robin sampling is used, there is no way + /// to tell from which channel this sample came. + pub fn read_single(&mut self) -> u16 { + self.adc.read_single() + } + + /// Returns `true` if conversion is currently paused. + /// + /// While paused, no samples will be added to the FIFO. + /// + /// There may be existing samples in the FIFO though, or a conversion may still be in progress. + pub fn is_paused(&mut self) -> bool { + self.adc.device.cs().read().start_many().bit_is_clear() + } + + /// Temporarily pause conversion + /// + /// This method stops ADC conversion, but leaves everything else configured. + /// + /// No new samples are captured until [`AdcFifo::resume`] is called. + /// + /// Note that existing samples can still be read from the FIFO, and can possibly + /// cause interrupts and DMA transfer progress until the FIFO is emptied. + pub fn pause(&mut self) { + self.adc + .device + .cs() + .modify(|_, w| w.start_many().clear_bit()); + } + + /// Resume conversion after it was paused + /// + /// There are two situations when it makes sense to use this method: + /// - After having called [`AdcFifo::pause`] on an AdcFifo + /// - If the FIFO was initialized using [`AdcFifoBuilder::prepare`]. + /// + /// Calling this method when conversion is already running has no effect. + pub fn resume(&mut self) { + self.adc.device.cs().modify(|_, w| w.start_many().set_bit()); + } + + /// Clears the FIFO, removing all values + /// + /// Reads and discards values from the FIFO until it is empty. + /// + /// This only makes sense to use while the FIFO is paused (see [`AdcFifo::pause`]). + pub fn clear(&mut self) { + while self.len() > 0 { + self.read_from_fifo(); + } + } + + /// Stop capturing in free running mode. + /// + /// Resets all capture options that can be set via [`AdcFifoBuilder`] to + /// their defaults. + /// + /// Returns the underlying [`Adc`], to be reused. + pub fn stop(mut self) -> &'a mut Adc { + // stop capture and clear channel selection + self.adc + .device + .cs() + .modify(|_, w| unsafe { w.start_many().clear_bit().rrobin().bits(0).ainsel().bits(0) }); + // disable fifo interrupt + self.adc.device.inte().modify(|_, w| w.fifo().clear_bit()); + // Wait for one more conversion, then drain remaining values from fifo. + // This MUST happen *after* the interrupt is disabled, but + // *before* `thresh` is modified. Otherwise if `INTS.FIFO = 1`, + // the interrupt will be fired one more time. + // The only way to clear `INTS.FIFO` is for `FCS.LEVEL` to go + // below `FCS.THRESH`, which requires `FCS.THRESH` not to be 0. + while self.adc.device.cs().read().ready().bit_is_clear() {} + self.clear(); + // disable fifo, reset threshold to 0 and disable DMA + self.adc + .device + .fcs() + .modify(|_, w| unsafe { w.en().clear_bit().thresh().bits(0).dreq_en().clear_bit() }); + // reset clock divider + self.adc + .device + .div() + .modify(|_, w| unsafe { w.int().bits(0).frac().bits(0) }); + self.adc + } + + /// Block until a ADC_IRQ_FIFO interrupt occurs + /// + /// Interrupts must be enabled ([`AdcFifoBuilder::enable_interrupt`]), or else this methods blocks forever. + pub fn wait_for_interrupt(&mut self) { + while self.adc.device.intr().read().fifo().bit_is_clear() {} + } + + fn read_from_fifo(&mut self) -> u16 { + self.adc.device.fifo().read().val().bits() + } + + /// Returns a read-target for initiating DMA transfers + /// + /// The [`DmaReadTarget`] returned by this function can be used to initiate DMA transfers + /// reading from the ADC. + pub fn dma_read_target(&self) -> DmaReadTarget { + DmaReadTarget(self.adc.device.fifo().as_ptr() as u32, PhantomData) + } + + /// Trigger a single conversion + /// + /// Ignored when in [`Adc::free_running`] mode. + pub fn trigger(&mut self) { + self.adc.device.cs().modify(|_, w| w.start_once().set_bit()); + } + + /// Check if ADC is ready for the next conversion trigger + /// + /// Not useful when in [`Adc::free_running`] mode. + pub fn is_ready(&self) -> bool { + self.adc.device.cs().read().ready().bit_is_set() + } +} + +impl AdcFifo<'_, u16> { + /// Read a single value from the fifo (u16 version, not shifted) + pub fn read(&mut self) -> u16 { + self.read_from_fifo() + } +} + +impl AdcFifo<'_, u8> { + /// Read a single value from the fifo (u8 version, shifted) + /// + /// Also see [`AdcFifoBuilder::shift_8bit`]. + pub fn read(&mut self) -> u8 { + self.read_from_fifo() as u8 + } +} + +/// Represents a [`dma::ReadTarget`] for the [`AdcFifo`] +/// +/// If [`AdcFifoBuilder::shift_8bit`] was called when constructing the FIFO, +/// `Word` will be `u8`, otherwise it will be `u16`. +/// +pub struct DmaReadTarget(u32, PhantomData); + +/// Safety: rx_address_count points to a register which is always a valid +/// read target. +unsafe impl dma::ReadTarget for DmaReadTarget { + type ReceivedWord = Word; + + fn rx_treq() -> Option { + Some(TREQ_SEL_A::ADC.into()) + } + + fn rx_address_count(&self) -> (u32, u32) { + (self.0, u32::MAX) + } + + fn rx_increment(&self) -> bool { + false + } +} + +impl dma::EndlessReadTarget for DmaReadTarget {} + +/// Internal struct representing values for the `CS.RROBIN` register. +/// +/// See [`AdcFifoBuilder::round_robin`], for usage example. +pub struct RoundRobin(u8); + +impl From<&PIN> for RoundRobin { + fn from(pin: &PIN) -> Self { + Self(1 << pin.channel()) + } +} + +impl From<(&A, &B)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, +{ + fn from(pins: (&A, &B)) -> Self { + Self(1 << pins.0.channel() | 1 << pins.1.channel()) + } +} + +impl From<(&A, &B, &C)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, + C: AdcChannel, +{ + fn from(pins: (&A, &B, &C)) -> Self { + Self(1 << pins.0.channel() | 1 << pins.1.channel() | 1 << pins.2.channel()) + } +} + +impl From<(&A, &B, &C, &D)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, + C: AdcChannel, + D: AdcChannel, +{ + fn from(pins: (&A, &B, &C, &D)) -> Self { + Self( + 1 << pins.0.channel() + | 1 << pins.1.channel() + | 1 << pins.2.channel() + | 1 << pins.3.channel(), + ) + } +} + +impl From<(&A, &B, &C, &D, &E)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, + C: AdcChannel, + D: AdcChannel, + E: AdcChannel, +{ + fn from(pins: (&A, &B, &C, &D, &E)) -> Self { + Self( + 1 << pins.0.channel() + | 1 << pins.1.channel() + | 1 << pins.2.channel() + | 1 << pins.3.channel() + | 1 << pins.4.channel(), + ) + } +} diff --git a/rp-hal/rp2040-hal/src/arch.rs b/rp-hal/rp2040-hal/src/arch.rs new file mode 100644 index 0000000..4360d4b --- /dev/null +++ b/rp-hal/rp2040-hal/src/arch.rs @@ -0,0 +1,67 @@ +//! Portable in-line assembly +//! +//! On the RP235x, this is useful to write code portable between ARM and RISC-V cores. +//! While there's no such choice on the RP2040, providing the same functions helps writing +//! code that works on both RP2040 and RP235x. + +#[cfg(all(target_arch = "arm", target_os = "none"))] +mod inner { + pub use cortex_m::asm::{delay, dsb, nop, sev, wfe, wfi}; + pub use cortex_m::interrupt::{disable as interrupt_disable, enable as interrupt_enable}; + + /// Are interrupts current enabled? + pub fn interrupts_enabled() -> bool { + cortex_m::register::primask::read().is_active() + } + + /// Run the closure without interrupts + /// + /// No critical-section token because we haven't blocked the second core + pub fn interrupt_free(f: F) -> T + where + F: FnOnce() -> T, + { + let active = interrupts_enabled(); + if active { + interrupt_disable(); + } + let t = f(); + if active { + unsafe { + interrupt_enable(); + } + } + t + } +} + +#[cfg(not(all(any(target_arch = "arm", target_arch = "riscv32"), target_os = "none")))] +mod inner { + /// Placeholder function to disable interrupts + pub fn interrupt_disable() {} + /// Placeholder function to enable interrupts + pub fn interrupt_enable() {} + /// Placeholder function to check if interrupts are enabled + pub fn interrupts_enabled() -> bool { + false + } + /// Placeholder function to wait for an event + pub fn wfe() {} + /// Placeholder function to do nothing + pub fn nop() {} + /// Placeholder function to emit a data synchronisation barrier + pub fn dsb() {} + /// Placeholder function to run a closure with interrupts disabled + pub fn interrupt_free(f: F) -> T + where + F: FnOnce() -> T, + { + f() + } + /// Placeholder function to wait for some clock cycles + pub fn delay(_: u32) {} + /// Placeholder function to emit an event + pub fn sev() {} +} + +pub use inner::*; diff --git a/rp-hal/rp2040-hal/src/async_utils.rs b/rp-hal/rp2040-hal/src/async_utils.rs new file mode 100644 index 0000000..11f8276 --- /dev/null +++ b/rp-hal/rp2040-hal/src/async_utils.rs @@ -0,0 +1,143 @@ +//! Commonly used in async implementations. + +use core::{marker::PhantomData, task::Poll}; + +pub(crate) mod sealed { + use core::{cell::Cell, task::Waker}; + use critical_section::Mutex; + + pub trait Wakeable { + /// Returns the waker associated with driver instance. + fn waker() -> &'static IrqWaker; + } + + /// This type wraps a `Waker` in a `Mutex>`. + /// + /// While `critical_section::Mutex` intregrates nicely with RefCell, RefCell adds a borrow + /// counter that is not necessary for this usecase. + /// + /// This type is kept sealed to prevent user from mistakenly messing with the waker such as + /// clearing it while the driver is parked. + pub struct IrqWaker { + waker: Mutex>>, + } + + impl Default for IrqWaker { + fn default() -> Self { + Self::new() + } + } + + impl IrqWaker { + pub const fn new() -> Self { + Self { + waker: Mutex::new(Cell::new(None)), + } + } + pub fn wake(&self) { + critical_section::with(|cs| { + if let Some(waker) = self.waker.borrow(cs).take() { + Waker::wake(waker); + } + }); + } + pub fn register(&self, waker: &Waker) { + critical_section::with(|cs| { + self.waker.borrow(cs).replace(Some(waker.clone())); + }); + } + pub fn clear(&self) { + critical_section::with(|cs| { + self.waker.borrow(cs).take(); + }); + } + } +} + +/// Marks driver instances that can be bound to an interrupt to wake async tasks. +pub trait AsyncPeripheral: sealed::Wakeable { + /// Signals the driver of an interrupt. + fn on_interrupt(); +} + +#[must_use = "Future do nothing unless they are polled on."] +pub(crate) struct CancellablePollFn<'periph, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + CancelFn: FnMut(&mut Periph), +{ + periph: &'periph mut Periph, + poll: PFn, + enable_irq: EnIrqFn, + cancel: CancelFn, + done: bool, + // captures F's return type. + phantom: PhantomData, +} +impl<'p, Periph, PFn, EnIrqFn, CancelFn, OutputTy> + CancellablePollFn<'p, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + PFn: FnMut(&mut Periph) -> Poll, + EnIrqFn: FnMut(&mut Periph), + CancelFn: FnMut(&mut Periph), +{ + pub(crate) fn new( + periph: &'p mut Periph, + poll: PFn, + enable_irq: EnIrqFn, + cancel: CancelFn, + ) -> Self { + Self { + periph, + poll, + enable_irq, + cancel, + done: false, + phantom: PhantomData, + } + } +} + +impl core::future::Future + for CancellablePollFn<'_, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + PFn: FnMut(&mut Periph) -> Poll, + EnIrqFn: FnMut(&mut Periph), + CancelFn: FnMut(&mut Periph), +{ + type Output = OutputTy; + + fn poll(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll { + // SAFETY: We are not moving anything. + let Self { + ref mut periph, + poll: ref mut is_ready, + enable_irq: ref mut setup_flags, + ref mut done, + .. + } = unsafe { self.get_unchecked_mut() }; + let r = (is_ready)(periph); + if r.is_pending() { + Periph::waker().register(cx.waker()); + (setup_flags)(periph); + } else { + *done = true; + } + r + } +} +impl Drop + for CancellablePollFn<'_, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + CancelFn: FnMut(&mut Periph), +{ + fn drop(&mut self) { + if !self.done { + Periph::waker().clear(); + (self.cancel)(self.periph); + } + } +} diff --git a/rp-hal/rp2040-hal/src/atomic_register_access.rs b/rp-hal/rp2040-hal/src/atomic_register_access.rs new file mode 100644 index 0000000..b196ac6 --- /dev/null +++ b/rp-hal/rp2040-hal/src/atomic_register_access.rs @@ -0,0 +1,40 @@ +//! Provide atomic access to peripheral registers +//! +//! This feature is not available for all peripherals. +//! See [section 2.1.2 of the RP2040 datasheet][section_2_1_2] for details. +//! +//! [section_2_1_2]: https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#atomic-rwtype + +use core::ptr::write_volatile; + +/// Perform atomic bitmask set operation on register +/// +/// See [section 2.1.2 of the RP2040 datasheet][section_2_1_2] for details. +/// +/// [section_2_1_2]: https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#atomic-rwtype +/// +/// # Safety +/// +/// In addition to the requirements of [core::ptr::write_volatile], +/// `register` must point to a register providing atomic aliases. +#[inline] +pub(crate) unsafe fn write_bitmask_set(register: *mut u32, bits: u32) { + let alias = (register as usize + 0x2000) as *mut u32; + write_volatile(alias, bits); +} + +/// Perform atomic bitmask clear operation on register +/// +/// See [section 2.1.2 of the RP2040 datasheet][section_2_1_2] for details. +/// +/// [section_2_1_2]: https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#atomic-rwtype +/// +/// # Safety +/// +/// In addition to the requirements of [core::ptr::write_volatile], +/// `register` must point to a register providing atomic aliases. +#[inline] +pub(crate) unsafe fn write_bitmask_clear(register: *mut u32, bits: u32) { + let alias = (register as usize + 0x3000) as *mut u32; + write_volatile(alias, bits); +} diff --git a/rp-hal/rp2040-hal/src/clocks/clock_sources.rs b/rp-hal/rp2040-hal/src/clocks/clock_sources.rs new file mode 100644 index 0000000..8189d75 --- /dev/null +++ b/rp-hal/rp2040-hal/src/clocks/clock_sources.rs @@ -0,0 +1,89 @@ +//! Available clocks + +use super::*; +use crate::{ + gpio::{ + bank0::{Gpio20, Gpio22}, + FunctionClock, Pin, PullNone, PullType, + }, + rosc::{Enabled, RingOscillator}, +}; + +pub(crate) type PllSys = PhaseLockedLoop; +impl Sealed for PllSys {} +impl ClockSource for PllSys { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +pub(crate) type PllUsb = PhaseLockedLoop; +impl Sealed for PllUsb {} +impl ClockSource for PllUsb { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +impl ClockSource for UsbClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +impl ClockSource for AdcClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +impl ClockSource for RtcClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +impl ClockSource for SystemClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +impl ClockSource for ReferenceClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +pub(crate) type Xosc = CrystalOscillator; +impl Sealed for Xosc {} +impl ClockSource for Xosc { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +pub(crate) type Rosc = RingOscillator; +impl Sealed for Rosc {} +// We are assuming the second output is never phase shifted (see 2.17.4) +impl ClockSource for RingOscillator { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +// GPIN0 +pub(crate) type GPin0 = Pin; +impl ClockSource for GPin0 { + fn get_freq(&self) -> HertzU32 { + todo!() + } +} + +// GPIN1 +pub(crate) type GPin1 = Pin; +impl ClockSource for Pin { + fn get_freq(&self) -> HertzU32 { + todo!() + } +} diff --git a/rp-hal/rp2040-hal/src/clocks/macros.rs b/rp-hal/rp2040-hal/src/clocks/macros.rs new file mode 100644 index 0000000..a193fb0 --- /dev/null +++ b/rp-hal/rp2040-hal/src/clocks/macros.rs @@ -0,0 +1,431 @@ +macro_rules! clocks { + ( + $( + $(#[$attr:meta])* + struct $name:ident { + init_freq: $init_freq:expr, + reg: $reg:ident, + $(src: {$($src:ident: $src_variant:ident),*},)? + auxsrc: {$($auxsrc:ident: $aux_variant:ident),*} + $(, div: $div:tt)? + } + )* + + ) => { + + $crate::paste::paste!{ + /// Abstraction layer providing Clock Management. + pub struct ClocksManager { + clocks: CLOCKS, + $( + #[doc = "`" $name "` field"] + pub [<$name:snake>]: $name, + )* + } + + impl ClocksManager { + /// Exchanges CLOCKS block against Self. + pub fn new(mut clocks_block: CLOCKS) -> Self { + // Disable resus that may be enabled from previous software + unsafe { + clocks_block.clk_sys_resus_ctrl().write_with_zero(|w| w); + } + + let shared_clocks = ShareableClocks::new(&mut clocks_block); + ClocksManager { + clocks: clocks_block, + $( + [<$name:snake>]: $name { + shared_dev: shared_clocks, + frequency: $init_freq.Hz(), + }, + )* + } + } + } + } + + $( + clock!( + $(#[$attr])* + struct $name { + reg: $reg, + $(src: {$($src: $src_variant),*},)? + auxsrc: {$($auxsrc: $aux_variant),*} + $(, div: $div )? + } + ); + )* + }; +} + +macro_rules! clock { + { + $(#[$attr:meta])* + struct $name:ident { + reg: $reg:ident, + src: {$($src:ident: $src_variant:ident),*}, + auxsrc: {$($auxsrc:ident: $aux_variant:ident),*} + } + } => { + base_clock!{ + $(#[$attr])* + ($name, $reg, auxsrc={$($auxsrc: $aux_variant),*}) + } + + divisible_clock!($name, $reg); + + $crate::paste::paste!{ + $(impl ValidSrc<$name> for $src { + fn is_aux(&self) -> bool{ + false + } + fn variant(&self) -> [<$reg:camel SrcType>] { + [<$reg:camel SrcType>]::Src($crate::pac::clocks::[<$reg _ctrl>]::SRC_A::$src_variant) + } + })* + + impl GlitchlessClock for $name { + type Clock = Self; + + fn await_select(&self, clock_token: &ChangingClockToken) -> nb::Result<(), Infallible> { + let shared_dev = unsafe { self.shared_dev.get() }; + + let selected = shared_dev.[<$reg _selected>]().read().bits(); + if selected != 1 << clock_token.clock_nr { + return Err(nb::Error::WouldBlock); + } + + Ok(()) + } + } + + #[doc = "Holds register value for ClockSource for `" $name "`"] + pub enum [<$reg:camel SrcType>] { + #[doc = "Contains a valid clock source register value that is to be used to set a clock as glitchless source for `" $name "`"] + Src($crate::pac::clocks::[<$reg _ctrl>]::SRC_A), + #[doc = "Contains a valid clock source register value that is to be used to set a clock as aux source for `" $name "`"] + Aux($crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A) + } + + impl [<$reg:camel SrcType>] { + fn get_clock_id(&self) -> u8 { + match self { + Self::Src(v) => *v as u8, + Self::Aux(v) => *v as u8, + } + } + + fn unwrap_src(&self) -> $crate::pac::clocks::[<$reg _ctrl>]::SRC_A{ + match self { + Self::Src(v) => *v, + Self::Aux(_) => panic!(), + } + } + + fn unwrap_aux(&self) -> $crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A { + match self { + Self::Src(_) => panic!(), + Self::Aux(v) => *v + } + } + } + + impl $name { + /// Reset clock back to its reset source + pub fn reset_source_await(&mut self) -> nb::Result<(), Infallible> { + let shared_dev = unsafe { self.shared_dev.get() }; + + shared_dev.[<$reg _ctrl>]().modify(|_, w| { + w.src().variant(self.get_default_clock_source()) + }); + + use fugit::RateExtU32; + self.frequency = 12.MHz(); //TODO Get actual clock source.. Most likely 12 MHz though + + self.await_select(&ChangingClockToken{clock_nr:0, clock: PhantomData::}) + } + + fn set_src>(&mut self, src: &S)-> ChangingClockToken<$name> { + let shared_dev = unsafe { self.shared_dev.get() }; + + shared_dev.[<$reg _ctrl>]().modify(|_,w| { + w.src().variant(src.variant().unwrap_src()) + }); + + ChangingClockToken { + clock: PhantomData::<$name>, + clock_nr: src.variant().get_clock_id(), + } + } + + fn set_self_aux_src(&mut self) -> ChangingClockToken<$name> { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.src().variant(self.get_aux_source()) + }); + + ChangingClockToken{ + clock: PhantomData::<$name>, + clock_nr: $crate::pac::clocks::clk_ref_ctrl::SRC_A::CLKSRC_CLK_REF_AUX as u8, + } + } + } + + impl Clock for $name { + type Variant = [<$reg:camel SrcType>]; + + #[doc = "Get operating frequency for `" $name "`"] + fn freq(&self) -> HertzU32 { + self.frequency + } + + #[doc = "Configure `" $name "`"] + fn configure_clock>(&mut self, src: &S, freq: HertzU32) -> Result<(), ClockError>{ + let src_freq: HertzU32 = src.get_freq().into(); + + if freq.gt(&src_freq){ + return Err(ClockError::CantIncreaseFreq); + } + + let div = fractional_div(src_freq.to_Hz(), freq.to_Hz()).ok_or(ClockError::FrequencyTooLow)?; + + // If increasing divisor, set divisor before source. Otherwise set source + // before divisor. This avoids a momentary overspeed when e.g. switching + // to a faster source and increasing divisor to compensate. + if div > self.get_div() { + self.set_div(div); + } + + + // Set aux mux first, and then glitchless mux if this self has one + let token = if src.is_aux() { + // If switching a glitchless slice (ref or sys) to an aux source, switch + // away from aux *first* to avoid passing glitches when changing aux mux. + // Assume (!!!) glitchless source 0 is no faster than the aux source. + nb::block!(self.reset_source_await()).unwrap(); + + self.set_aux(src); + self.set_self_aux_src() + } else { + self.set_src(src) + }; + + nb::block!(self.await_select(&token)).unwrap(); + + + // Now that the source is configured, we can trust that the user-supplied + // divisor is a safe value. + self.set_div(div); + + // Store the configured frequency + use fugit::RateExtU32; + self.frequency = fractional_div(src_freq.to_Hz(), div).ok_or(ClockError::FrequencyTooHigh)?.Hz(); + + Ok(()) + } + } + } + }; + { + $( #[$attr:meta])* + struct $name:ident { + reg: $reg:ident, + auxsrc: {$($auxsrc:ident: $variant:ident),*}, + div: false + } + } => { + base_clock!{ + $(#[$attr])* + ($name, $reg, auxsrc={$($auxsrc: $variant),*}) + } + + // Just to match proper divisible clocks so we don't have to do something special in configure function + impl ClockDivision for $name { + fn set_div(&mut self, _: u32) {} + fn get_div(&self) -> u32 {1} + } + + stoppable_clock!($name, $reg); + }; + { + $( #[$attr:meta])* + struct $name:ident { + reg: $reg:ident, + auxsrc: {$($auxsrc:ident: $variant:ident),*} + } + } => { + base_clock!{ + $(#[$attr])* + ($name, $reg, auxsrc={$($auxsrc: $variant),*}) + } + + divisible_clock!($name, $reg); + stoppable_clock!($name, $reg); + }; +} + +macro_rules! divisible_clock { + ($name:ident, $reg:ident) => { + $crate::paste::paste! { + impl ClockDivision for $name { + fn set_div(&mut self, div: u32) { + unsafe { self.shared_dev.get() }.[<$reg _div>]().modify(|_, w| unsafe { + w.bits(div); + w + }); + } + fn get_div(&self) -> u32 { + unsafe { self.shared_dev.get() }.[<$reg _div>]().read().bits() + } + } + } + }; +} + +macro_rules! stoppable_clock { + ($name:ident, $reg:ident) => { + $crate::paste::paste!{ + #[doc = "Holds register value for ClockSource for `" $name "`"] + pub enum [<$reg:camel SrcType>] { + #[doc = "Contains a valid clock source register value that is to be used to set a clock as aux source for `" $name "`"] + Aux($crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A) + } + + impl [<$reg:camel SrcType>] { + fn unwrap_aux(&self) -> $crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A { + match self { + Self::Aux(v) => *v + } + } + } + + impl StoppableClock for $name { + /// Enable the clock + fn enable(&mut self) { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.enable().set_bit() + }); + } + + /// Disable the clock cleanly + fn disable(&mut self) { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.enable().clear_bit() + }); + } + + /// Disable the clock asynchronously + fn kill(&mut self) { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.kill().set_bit() + }); + } + } + + impl Clock for $name { + type Variant = [<$reg:camel SrcType>]; + + #[doc = "Get operating frequency for `" $name "`"] + fn freq(&self) -> HertzU32 { + self.frequency + } + + #[doc = "Configure `" $name "`"] + fn configure_clock>(&mut self, src: &S, freq: HertzU32) -> Result<(), ClockError>{ + let src_freq: HertzU32 = src.get_freq().into(); + + if freq.gt(&src_freq){ + return Err(ClockError::CantIncreaseFreq); + } + + let div = fractional_div(src_freq.to_Hz(), freq.to_Hz()).ok_or(ClockError::FrequencyTooLow)?; + + // If increasing divisor, set divisor before source. Otherwise set source + // before divisor. This avoids a momentary overspeed when e.g. switching + // to a faster source and increasing divisor to compensate. + if div > self.get_div() { + self.set_div(div); + } + + // If no glitchless mux, cleanly stop the clock to avoid glitches + // propagating when changing aux mux. Note it would be a really bad idea + // to do this on one of the glitchless clocks (clk_sys, clk_ref). + + // Disable clock. On clk_ref and clk_sys this does nothing, + // all other clocks have the ENABLE bit in the same position. + self.disable(); + if self.frequency > HertzU32::Hz(0) { + // Delay for 3 cycles of the target clock, for ENABLE propagation. + // Note XOSC_COUNT is not helpful here because XOSC is not + // necessarily running, nor is timer... so, 3 cycles per loop: + let sys_freq = 125_000_000; // TODO get actual sys_clk frequency + let delay_cyc = sys_freq / self.frequency.to_Hz() + 1u32; + cortex_m::asm::delay(delay_cyc); + } + + // Set aux mux first, and then glitchless mux if this self has one + self.set_aux(src); + + // Enable clock. On clk_ref and clk_sys this does nothing, + // all other clocks have the ENABLE bit in the same posi + self.enable(); + + // Now that the source is configured, we can trust that the user-supplied + // divisor is a safe value. + self.set_div(div); + + // Store the configured frequency + use fugit::RateExtU32; + self.frequency = fractional_div(src_freq.to_Hz(), div).ok_or(ClockError::FrequencyTooHigh)?.Hz(); + + Ok(()) + } + } + } + }; +} + +macro_rules! base_clock { + { + $(#[$attr:meta])* + ($name:ident, $reg:ident, auxsrc={$($auxsrc:ident: $variant:ident),*}) + } => { + $crate::paste::paste!{ + + $(impl ValidSrc<$name> for $auxsrc { + + fn is_aux(&self) -> bool{ + true + } + fn variant(&self) -> [<$reg:camel SrcType>] { + [<$reg:camel SrcType>]::Aux($crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A::$variant) + } + })* + + $(#[$attr])* + pub struct $name { + shared_dev: ShareableClocks, + frequency: HertzU32, + } + + impl $name { + fn set_aux>(&mut self, src: &S) { + let shared_dev = unsafe { self.shared_dev.get() }; + + shared_dev.[<$reg _ctrl>]().modify(|_,w| { + w.auxsrc().variant(src.variant().unwrap_aux()) + }); + } + } + + impl Sealed for $name {} + + impl From<&$name> for HertzU32 + { + fn from(value: &$name) -> HertzU32 { + value.frequency + } + } + } + }; +} diff --git a/rp-hal/rp2040-hal/src/clocks/mod.rs b/rp-hal/rp2040-hal/src/clocks/mod.rs new file mode 100644 index 0000000..f2c4c88 --- /dev/null +++ b/rp-hal/rp2040-hal/src/clocks/mod.rs @@ -0,0 +1,608 @@ +//! Clocks (CLOCKS) +//! +//! +//! +//! ## Usage simple +//! ```no_run +//! use rp2040_hal::{clocks::init_clocks_and_plls, watchdog::Watchdog, pac}; +//! +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! let mut clocks = init_clocks_and_plls(XOSC_CRYSTAL_FREQ, peripherals.XOSC, peripherals.CLOCKS, peripherals.PLL_SYS, peripherals.PLL_USB, &mut peripherals.RESETS, &mut watchdog).ok().unwrap(); +//! ``` +//! +//! ## Usage extended +//! ```no_run +//! use fugit::RateExtU32; +//! use rp2040_hal::{clocks::{Clock, ClocksManager, ClockSource, InitError}, gpio::Pins, pac, pll::{common_configs::{PLL_SYS_125MHZ, PLL_USB_48MHZ}, setup_pll_blocking}, Sio, watchdog::Watchdog, xosc::setup_xosc_blocking}; +//! +//! # fn func() -> Result<(), InitError> { +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! +//! // Enable the xosc +//! let xosc = setup_xosc_blocking(peripherals.XOSC, XOSC_CRYSTAL_FREQ.Hz()).map_err(InitError::XoscErr)?; +//! +//! // Start tick in watchdog +//! watchdog.enable_tick_generation((XOSC_CRYSTAL_FREQ / 1_000_000) as u8); +//! +//! let mut clocks = ClocksManager::new(peripherals.CLOCKS); +//! +//! // Configure PLLs +//! // REF FBDIV VCO POSTDIV +//! // PLL SYS: 12 / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz +//! // PLL USB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz +//! let pll_sys = setup_pll_blocking(peripherals.PLL_SYS, xosc.operating_frequency().into(), PLL_SYS_125MHZ, &mut clocks, &mut peripherals.RESETS).map_err(InitError::PllError)?; +//! let pll_usb = setup_pll_blocking(peripherals.PLL_USB, xosc.operating_frequency().into(), PLL_USB_48MHZ, &mut clocks, &mut peripherals.RESETS).map_err(InitError::PllError)?; +//! +//! // Configure clocks +//! // CLK_REF = XOSC (12MHz) / 1 = 12MHz +//! clocks.reference_clock.configure_clock(&xosc, xosc.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK SYS = PLL SYS (125MHz) / 1 = 125MHz +//! clocks.system_clock.configure_clock(&pll_sys, pll_sys.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK USB = PLL USB (48MHz) / 1 = 48MHz +//! clocks.usb_clock.configure_clock(&pll_usb, pll_usb.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz +//! clocks.adc_clock.configure_clock(&pll_usb, pll_usb.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz +//! clocks.rtc_clock.configure_clock(&pll_usb, 46875u32.Hz()).map_err(InitError::ClockError)?; +//! +//! // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable +//! // Normally choose clk_sys or clk_usb +//! clocks.peripheral_clock.configure_clock(&clocks.system_clock, clocks.system_clock.freq()).map_err(InitError::ClockError)?; +//! # Ok(()) +//! # } +//! ``` +//! +//! See [Chapter 2 Section 15](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details +use core::{convert::Infallible, marker::PhantomData}; +use fugit::{HertzU32, RateExtU32}; + +use crate::{ + pac::{self, CLOCKS, PLL_SYS, PLL_USB, RESETS, XOSC}, + pll::{ + common_configs::{PLL_SYS_125MHZ, PLL_USB_48MHZ}, + setup_pll_blocking, Error as PllError, Locked, PhaseLockedLoop, + }, + typelevel::Sealed, + watchdog::Watchdog, + xosc::{setup_xosc_blocking, CrystalOscillator, Error as XoscError, Stable}, +}; + +#[macro_use] +mod macros; +mod clock_sources; + +use clock_sources::PllSys; + +use self::clock_sources::{GPin0, GPin1, PllUsb, Rosc, Xosc}; + +bitfield::bitfield! { + /// Bit field mapping clock enable bits. + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Default)] + pub struct ClockGate(u64); + /// Clock gate to the clock controller. + pub sys_clock, set_sys_clock: 0; + /// Clock gate to the adc analog logic. + pub adc_adc, set_adc_adc: 1; + /// Clock gate to the adc peripheral. + pub sys_adc, set_sys_adc: 2; + /// Clock gate the memory bus controller. + pub sys_busctrl, set_sys_busctrl: 3; + /// Clock gate the memory bus fabric. + pub sys_busfabric, set_sys_busfabric: 4; + /// Clock gate the dma controller. + pub sys_dma, set_sys_dma: 5; + /// Clock gate I2C0. + pub sys_i2c0, set_sys_i2c0: 6; + /// Clock gate I2C1. + pub sys_i2c1, set_sys_i2c1: 7; + /// Clock gate the IO controller. + pub sys_io, set_sys_io: 8; + /// Clock gate the JTAG peripheral. + pub sys_jtag, set_sys_jtag: 9; + /// Clock gate the voltage regulator and reset controller. + pub sys_vreg_and_chip_reset, set_sys_vreg_and_chip_reset: 10; + /// Clock gate pad controller. + pub sys_pads, set_sys_pads: 11; + /// Clock gate PIO0 peripheral. + pub sys_pio0, set_sys_pio0: 12; + /// Clock gate PIO1 peripheral. + pub sys_pio1, set_sys_pio1: 13; + /// Clock gate . + pub sys_pll_sys, set_sys_pll_sys: 14; + /// Clock gate . + pub sys_pll_usb, set_sys_pll_usb: 15; + /// Clock gate the power state machine. + pub sys_psm, set_sys_psm: 16; + /// Clock gate PWM peripheral. + pub sys_pwm, set_sys_pwm: 17; + /// Clock gate the reset controller. + pub sys_resets, set_sys_resets: 18; + /// Clock gate the ROM. + pub sys_rom, set_sys_rom: 19; + /// Clock gate the ROSC controller (not the rosc itself). + pub sys_rosc, set_sys_rosc: 20; + /// Clock gate the RTC internal clock. + pub rtc_rtc, set_rtc_rtc: 21; + /// Clock gate the RTC peripheral. + pub sys_rtc, set_sys_rtc: 22; + /// Clock gate the SIO controller. + pub sys_sio, set_sys_sio: 23; + /// Clock gate SPI0's baud generation. + pub peri_spi0, set_peri_spi0: 24; + /// Clock gate SPI0's controller.. + pub sys_spi0, set_sys_spi0: 25; + /// Clock gate SPI1's baud generation. + pub peri_spi1, set_peri_spi1: 26; + /// Clock gate SPI1's controller.. + pub sys_spi1, set_sys_spi1: 27; + /// Clock gate SRAM0. + pub sys_sram0, set_sys_sram0: 28; + /// Clock gate SRAM1. + pub sys_sram1, set_sys_sram1: 29; + /// Clock gate SRAM2. + pub sys_sram2, set_sys_sram2: 30; + /// Clock gate SRAM3. + pub sys_sram3, set_sys_sram3: 31; + + /// Clock gate SRAM4. + pub sys_sram4, set_sys_sram4: 32; + /// Clock gate SRAM5. + pub sys_sram5, set_sys_sram5: 33; + /// Clock gate the system configuration controller. + pub sys_syscfg, set_sys_syscfg: 34; + /// Clock gate the system information peripheral. + pub sys_sysinfo, set_sys_sysinfo: 35; + /// Clock gate the test bench manager. + pub sys_tbman, set_sys_tbman: 36; + /// Clock gate the Timer peripheral. + pub sys_timer, set_sys_timer: 37; + /// Clock gate UART0's baud generation. + pub peri_uart0, set_peri_uart0: 38; + /// Clock gate UART0's controller. + pub sys_uart0, set_sys_uart0: 39; + /// Clock gate UART1's baud generation. + pub peri_uart1, set_peri_uart1: 40; + /// Clock gate UART1's controller. + pub sys_uart1, set_sys_uart1: 41; + /// Clock gate the USB controller. + pub sys_usbctrl, set_sys_usbctrl: 42; + /// Clock gate the USB logic. + pub usb_usbctrl, set_usb_usbctrl: 43; + /// Clock gate the Watchdog controller. + pub sys_watchdog, set_sys_watchdog: 44; + /// .Clock gate the XIP controller. + pub sys_xip, set_sys_xip: 45; + /// Clock gate the XOSC controller (not xosc itself). + pub sys_xosc, set_sys_xosc: 46; +} +impl core::fmt::Debug for ClockGate { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ClockGate") + .field("sys_clock", &self.sys_clock()) + .field("adc_adc", &self.adc_adc()) + .field("sys_adc", &self.sys_adc()) + .field("sys_busctrl", &self.sys_busctrl()) + .field("sys_busfabric", &self.sys_busfabric()) + .field("sys_dma", &self.sys_dma()) + .field("sys_i2c0", &self.sys_i2c0()) + .field("sys_i2c1", &self.sys_i2c1()) + .field("sys_io", &self.sys_io()) + .field("sys_jtag", &self.sys_jtag()) + .field("sys_vreg_and_chip_reset", &self.sys_vreg_and_chip_reset()) + .field("sys_pads", &self.sys_pads()) + .field("sys_pio0", &self.sys_pio0()) + .field("sys_pio1", &self.sys_pio1()) + .field("sys_pll_sys", &self.sys_pll_sys()) + .field("sys_pll_usb", &self.sys_pll_usb()) + .field("sys_psm", &self.sys_psm()) + .field("sys_pwm", &self.sys_pwm()) + .field("sys_resets", &self.sys_resets()) + .field("sys_rom", &self.sys_rom()) + .field("sys_rosc", &self.sys_rosc()) + .field("rtc_rtc", &self.rtc_rtc()) + .field("sys_rtc", &self.sys_rtc()) + .field("sys_sio", &self.sys_sio()) + .field("peri_spi0", &self.peri_spi0()) + .field("sys_spi0", &self.sys_spi0()) + .field("peri_spi1", &self.peri_spi1()) + .field("sys_spi1", &self.sys_spi1()) + .field("sys_sram0", &self.sys_sram0()) + .field("sys_sram1", &self.sys_sram1()) + .field("sys_sram2", &self.sys_sram2()) + .field("sys_sram3", &self.sys_sram3()) + .field("sys_sram4", &self.sys_sram4()) + .field("sys_syscfg", &self.sys_syscfg()) + .field("sys_sysinfo", &self.sys_sysinfo()) + .field("sys_tbman", &self.sys_tbman()) + .field("sys_timer", &self.sys_timer()) + .field("peri_uart0", &self.peri_uart0()) + .field("sys_uart0", &self.sys_uart0()) + .field("peri_uart1", &self.peri_uart1()) + .field("sys_uart1", &self.sys_uart1()) + .field("sys_usbctrl", &self.sys_usbctrl()) + .field("usb_usbctrl", &self.usb_usbctrl()) + .field("sys_watchdog", &self.sys_watchdog()) + .field("sys_xip", &self.sys_xip()) + .field("sys_xosc", &self.sys_xosc()) + .finish() + } +} + +#[derive(Copy, Clone)] +/// Provides refs to the CLOCKS block. +struct ShareableClocks { + _internal: (), +} + +impl ShareableClocks { + fn new(_clocks: &mut CLOCKS) -> Self { + ShareableClocks { _internal: () } + } + + unsafe fn get(&self) -> &pac::clocks::RegisterBlock { + &*CLOCKS::ptr() + } +} + +/// Something when wrong setting up the clock +#[non_exhaustive] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClockError { + /// The frequency desired is higher than the source frequency + CantIncreaseFreq, + /// The desired frequency is to high (would overflow an u32) + FrequencyTooHigh, + /// The desired frequency is too low (divider can't reach the desired value) + FrequencyTooLow, +} + +/// For clocks +pub trait Clock: Sealed + Sized { + /// Enum with valid source clocks register values for `Clock` + type Variant; + + /// Get operating frequency + fn freq(&self) -> HertzU32; + + /// Configure this clock based on a clock source and desired frequency + fn configure_clock>( + &mut self, + src: &S, + freq: HertzU32, + ) -> Result<(), ClockError>; +} + +/// For clocks with a divider +trait ClockDivision { + /// Set integer divider value. + fn set_div(&mut self, div: u32); + /// Get integer diveder value. + fn get_div(&self) -> u32; +} + +/// Clock with glitchless source +trait GlitchlessClock { + /// Self type to hand to ChangingClockToken + type Clock: Clock; + + /// Await switching clock sources without glitches. Needs a token that is returned when setting + fn await_select( + &self, + clock_token: &ChangingClockToken, + ) -> nb::Result<(), Infallible>; +} + +/// Token which can be used to await the glitchless switch +pub struct ChangingClockToken { + clock_nr: u8, + clock: PhantomData, +} + +/// For clocks that can be disabled +pub trait StoppableClock: Sealed { + /// Enables the clock. + fn enable(&mut self); + + /// Disables the clock. + fn disable(&mut self); + + /// Kills the clock. + fn kill(&mut self); +} + +/// Trait for things that can be used as clock source +pub trait ClockSource: Sealed { + /// Get the operating frequency for this source + /// + /// Used to determine the divisor + fn get_freq(&self) -> HertzU32; +} + +/// Trait to constrain which ClockSource is valid for which Clock +pub trait ValidSrc: Sealed + ClockSource { + /// Is this a ClockSource for src or aux? + fn is_aux(&self) -> bool; + /// Get register value for this ClockSource + fn variant(&self) -> C::Variant; +} + +clocks! { + /// GPIO Output 0 Clock + struct GpioOutput0Clock { + init_freq: 0, + reg: clk_gpout0, + auxsrc: {PllSys:CLKSRC_PLL_SYS, GPin0:CLKSRC_GPIN0, GPin1:CLKSRC_GPIN1, PllUsb:CLKSRC_PLL_USB, Rosc: ROSC_CLKSRC, Xosc: XOSC_CLKSRC, SystemClock: CLK_SYS, UsbClock: CLK_USB, AdcClock: CLK_ADC, RtcClock: CLK_RTC, ReferenceClock:CLK_REF} + } + /// GPIO Output 1 Clock + struct GpioOutput1Clock { + init_freq: 0, + reg: clk_gpout1, + auxsrc: {PllSys:CLKSRC_PLL_SYS, GPin0:CLKSRC_GPIN0, GPin1:CLKSRC_GPIN1, PllUsb:CLKSRC_PLL_USB, Rosc: ROSC_CLKSRC, Xosc: XOSC_CLKSRC, SystemClock: CLK_SYS, UsbClock: CLK_USB, AdcClock: CLK_ADC, RtcClock: CLK_RTC, ReferenceClock:CLK_REF} + } + /// GPIO Output 2 Clock + struct GpioOutput2Clock { + init_freq: 0, + reg: clk_gpout2, + auxsrc: {PllSys:CLKSRC_PLL_SYS, GPin0:CLKSRC_GPIN0, GPin1:CLKSRC_GPIN1, PllUsb:CLKSRC_PLL_USB, Rosc: ROSC_CLKSRC_PH, Xosc: XOSC_CLKSRC, SystemClock: CLK_SYS, UsbClock: CLK_USB, AdcClock: CLK_ADC, RtcClock: CLK_RTC, ReferenceClock:CLK_REF} + } + /// GPIO Output 3 Clock + struct GpioOutput3Clock { + init_freq: 0, + reg: clk_gpout3, + auxsrc: {PllSys:CLKSRC_PLL_SYS, GPin0:CLKSRC_GPIN0, GPin1:CLKSRC_GPIN1, PllUsb:CLKSRC_PLL_USB, Rosc: ROSC_CLKSRC_PH, Xosc: XOSC_CLKSRC, SystemClock: CLK_SYS, UsbClock: CLK_USB, AdcClock: CLK_ADC, RtcClock: CLK_RTC, ReferenceClock:CLK_REF} + } + /// Reference Clock + struct ReferenceClock { + init_freq: 12_000_000, // Starts from ROSC which actually varies with input voltage etc, but 12 MHz seems to be a good value + reg: clk_ref, + src: {Rosc: ROSC_CLKSRC_PH, Xosc:XOSC_CLKSRC}, + auxsrc: {PllUsb:CLKSRC_PLL_USB, GPin0:CLKSRC_GPIN0, GPin1:CLKSRC_GPIN1} + } + /// System Clock + struct SystemClock { + init_freq: 12_000_000, // ref_clk is 12 MHz + reg: clk_sys, + src: {ReferenceClock: CLK_REF}, + auxsrc: {PllSys: CLKSRC_PLL_SYS, PllUsb:CLKSRC_PLL_USB, Rosc: ROSC_CLKSRC, Xosc: XOSC_CLKSRC,GPin0:CLKSRC_GPIN0, GPin1:CLKSRC_GPIN1} + } + /// Peripheral Clock + struct PeripheralClock { + init_freq: 12_000_000, // sys_clk is 12 MHz + reg: clk_peri, + auxsrc: {SystemClock: CLK_SYS, PllSys: CLKSRC_PLL_SYS, PllUsb:CLKSRC_PLL_USB, Rosc: ROSC_CLKSRC_PH, Xosc: XOSC_CLKSRC,GPin0:CLKSRC_GPIN0, GPin1:CLKSRC_GPIN1 }, + div: false + } + /// USB Clock + struct UsbClock { + init_freq: 0, + reg: clk_usb, + auxsrc: {PllUsb:CLKSRC_PLL_USB,PllSys: CLKSRC_PLL_SYS, Rosc: ROSC_CLKSRC_PH, Xosc: XOSC_CLKSRC,GPin0:CLKSRC_GPIN0, GPin1:CLKSRC_GPIN1} + } + /// Adc Clock + struct AdcClock { + init_freq: 0, + reg: clk_adc, + auxsrc: {PllUsb:CLKSRC_PLL_USB,PllSys: CLKSRC_PLL_SYS, Rosc: ROSC_CLKSRC_PH, Xosc: XOSC_CLKSRC,GPin0:CLKSRC_GPIN0, GPin1:CLKSRC_GPIN1} + } + /// RTC Clock + struct RtcClock { + init_freq: 0, + reg: clk_rtc, + auxsrc: {PllUsb:CLKSRC_PLL_USB,PllSys: CLKSRC_PLL_SYS, Rosc: ROSC_CLKSRC_PH, Xosc: XOSC_CLKSRC,GPin0:CLKSRC_GPIN0, GPin1:CLKSRC_GPIN1} + } +} + +impl SystemClock { + fn get_default_clock_source(&self) -> pac::clocks::clk_sys_ctrl::SRC_A { + pac::clocks::clk_sys_ctrl::SRC_A::CLK_REF + } + + fn get_aux_source(&self) -> pac::clocks::clk_sys_ctrl::SRC_A { + pac::clocks::clk_sys_ctrl::SRC_A::CLKSRC_CLK_SYS_AUX + } +} + +impl ReferenceClock { + fn get_default_clock_source(&self) -> pac::clocks::clk_ref_ctrl::SRC_A { + pac::clocks::clk_ref_ctrl::SRC_A::ROSC_CLKSRC_PH + } + + fn get_aux_source(&self) -> pac::clocks::clk_ref_ctrl::SRC_A { + pac::clocks::clk_ref_ctrl::SRC_A::CLKSRC_CLK_REF_AUX + } +} + +impl ClocksManager { + /// Initialize the clocks to a sane default + pub fn init_default( + &mut self, + xosc: &CrystalOscillator, + pll_sys: &PhaseLockedLoop, + pll_usb: &PhaseLockedLoop, + ) -> Result<(), ClockError> { + // Configure clocks + // CLK_REF = XOSC (12MHz) / 1 = 12MHz + self.reference_clock + .configure_clock(xosc, xosc.get_freq())?; + + // CLK SYS = PLL SYS (125MHz) / 1 = 125MHz + self.system_clock + .configure_clock(pll_sys, pll_sys.get_freq())?; + + // CLK USB = PLL USB (48MHz) / 1 = 48MHz + self.usb_clock + .configure_clock(pll_usb, pll_usb.get_freq())?; + + // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz + self.adc_clock + .configure_clock(pll_usb, pll_usb.get_freq())?; + + // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz + self.rtc_clock.configure_clock(pll_usb, 46875u32.Hz())?; + + // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable + // Normally choose clk_sys or clk_usb + self.peripheral_clock + .configure_clock(&self.system_clock, self.system_clock.freq()) + } + + /// Configure the clocks staying ON during deep-sleep. + pub fn configure_sleep_enable(&mut self, clock_gate: ClockGate) { + self.clocks + .sleep_en0() + .write(|w| unsafe { w.bits(clock_gate.0 as u32) }); + self.clocks + .sleep_en1() + .write(|w| unsafe { w.bits((clock_gate.0 >> 32) as u32) }); + } + + /// Read the clock gate configuration while the device is in its (deep) sleep state. + pub fn sleep_enable(&self) -> ClockGate { + ClockGate( + (u64::from(self.clocks.sleep_en1().read().bits()) << 32) + | u64::from(self.clocks.sleep_en0().read().bits()), + ) + } + + /// Read the clock gate configuration while the device is in its wake state. + pub fn wake_enable(&self) -> ClockGate { + ClockGate( + (u64::from(self.clocks.wake_en1().read().bits()) << 32) + | u64::from(self.clocks.wake_en0().read().bits()), + ) + } + + /// Releases the CLOCKS block + pub fn free(self) -> CLOCKS { + self.clocks + } +} + +/// Possible init errors +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InitError { + /// Something went wrong setting up the Xosc + XoscErr(XoscError), + /// Something went wrong setting up the Pll + PllError(PllError), + /// Something went wrong setting up the Clocks + ClockError(ClockError), +} + +/// Initialize the clocks and plls according to the reference implementation +pub fn init_clocks_and_plls( + xosc_crystal_freq: u32, + xosc_dev: XOSC, + clocks_dev: CLOCKS, + pll_sys_dev: PLL_SYS, + pll_usb_dev: PLL_USB, + resets: &mut RESETS, + watchdog: &mut Watchdog, +) -> Result { + let xosc = setup_xosc_blocking(xosc_dev, xosc_crystal_freq.Hz()).map_err(InitError::XoscErr)?; + + // Configure watchdog tick generation to tick over every microsecond + watchdog.enable_tick_generation((xosc_crystal_freq / 1_000_000) as u8); + + let mut clocks = ClocksManager::new(clocks_dev); + + let pll_sys = setup_pll_blocking( + pll_sys_dev, + xosc.operating_frequency(), + PLL_SYS_125MHZ, + &mut clocks, + resets, + ) + .map_err(InitError::PllError)?; + let pll_usb = setup_pll_blocking( + pll_usb_dev, + xosc.operating_frequency(), + PLL_USB_48MHZ, + &mut clocks, + resets, + ) + .map_err(InitError::PllError)?; + + clocks + .init_default(&xosc, &pll_sys, &pll_usb) + .map_err(InitError::ClockError)?; + Ok(clocks) +} + +// Calculates (numerator<<8)/denominator, avoiding 64bit division +// Returns None if the result would not fit in 32 bit. +fn fractional_div(numerator: u32, denominator: u32) -> Option { + if denominator.eq(&numerator) { + return Some(1 << 8); + } + + let div_int = numerator / denominator; + if div_int >= 1 << 24 { + return None; + } + + let div_rem = numerator - (div_int * denominator); + + let div_frac = if div_rem < 1 << 24 { + // div_rem is small enough to shift it by 8 bits without overflow + (div_rem << 8) / denominator + } else { + // div_rem is too large. Shift denominator right, instead. + // As 1<<24 < div_rem < denominator, relative error caused by the + // lost lower 8 bits of denominator is smaller than 2^-16 + (div_rem) / (denominator >> 8) + }; + + Some((div_int << 8) + div_frac) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fractional_div() { + // easy values + assert_eq!(fractional_div(1, 1), Some(1 << 8)); + + // typical values + assert_eq!(fractional_div(125_000_000, 48_000_000), Some(666)); + assert_eq!(fractional_div(48_000_000, 46875), Some(1024 << 8)); + + // resulting frequencies + assert_eq!( + fractional_div( + 125_000_000, + fractional_div(125_000_000, 48_000_000).unwrap() + ), + Some(48_048_048) + ); + assert_eq!( + fractional_div(48_000_000, fractional_div(48_000_000, 46875).unwrap()), + Some(46875) + ); + + // not allowed in src/clocks/mod.rs, but should still deliver correct results + assert_eq!(fractional_div(1, 2), Some(128)); + assert_eq!(fractional_div(1, 256), Some(1)); + assert_eq!(fractional_div(1, 257), Some(0)); + + // borderline cases + assert_eq!(fractional_div((1 << 24) - 1, 1), Some(((1 << 24) - 1) << 8)); + assert_eq!(fractional_div(1 << 24, 1), None); + assert_eq!(fractional_div(1 << 24, 2), Some(1 << (23 + 8))); + assert_eq!(fractional_div(1 << 24, (1 << 24) + 1), Some(1 << 8)); + assert_eq!(fractional_div(u32::MAX, u32::MAX), Some(1 << 8)); + } +} diff --git a/rp-hal/rp2040-hal/src/critical_section_impl.rs b/rp-hal/rp2040-hal/src/critical_section_impl.rs new file mode 100644 index 0000000..a121059 --- /dev/null +++ b/rp-hal/rp2040-hal/src/critical_section_impl.rs @@ -0,0 +1,91 @@ +use core::sync::atomic::{AtomicU8, Ordering}; + +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 = crate::Sio::core() as u8 + 1_u8; + // 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) = crate::sio::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 + crate::sio::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(); + } + } + } +} diff --git a/rp-hal/rp2040-hal/src/dma/bidirectional.rs b/rp-hal/rp2040-hal/src/dma/bidirectional.rs new file mode 100644 index 0000000..c54c44f --- /dev/null +++ b/rp-hal/rp2040-hal/src/dma/bidirectional.rs @@ -0,0 +1,160 @@ +//! Bidirectional DMA transfers + +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::{ + single_channel::{ChannelConfig, SingleChannel}, + Pace, ReadTarget, WriteTarget, +}; + +/// DMA configuration for sending and receiving data simultaneously +pub struct Config +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + ch: (CH1, CH2), + from: FROM, + bidi: BIDI, + to: TO, + from_pace: Pace, + to_pace: Pace, + bswap: bool, +} + +impl Config +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + /// Create a DMA configuration for sending and receiving data simultaneously + pub fn new(ch: (CH1, CH2), from: FROM, bidi: BIDI, to: TO) -> Config { + Config { + ch, + from, + bidi, + to, + bswap: false, + from_pace: Pace::PreferSink, + to_pace: Pace::PreferSink, + } + } + + #[allow(clippy::wrong_self_convention)] + /// Set the transfer pacing for the DMA transfer from the source + pub fn from_pace(&mut self, pace: Pace) { + self.from_pace = pace; + } + + #[allow(clippy::wrong_self_convention)] + /// Set the transfer pacing for the DMA transfer to the target + pub fn to_pace(&mut self, pace: Pace) { + self.to_pace = pace; + } + + /// Enable/disable byteswapping for the DMA transfers, default value is false. + /// + /// For byte data, this has no effect. For halfword data, the two bytes of + /// each halfword are swapped. For word data, the four bytes of each word + /// are swapped to reverse order. + /// + /// This is a convenient way to change the (half-)words' byte endianness on the fly. + pub fn bswap(&mut self, bswap: bool) { + self.bswap = bswap; + } + + /// Start the DMA transfer + pub fn start(mut self) -> Transfer { + cortex_m::asm::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the DMA channel and start it. + self.ch.0.config( + &self.from, + &mut self.bidi, + self.from_pace, + self.bswap, + None, + false, + ); + self.ch.1.config( + &self.bidi, + &mut self.to, + self.to_pace, + self.bswap, + None, + false, + ); + self.ch.0.start_both(&mut self.ch.1); + + Transfer { + ch: self.ch, + from: self.from, + bidi: self.bidi, + to: self.to, + } + } +} + +/// Instance of a bidirectional DMA transfer +pub struct Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + ch: (CH1, CH2), + from: FROM, + bidi: BIDI, + to: TO, +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + /// Check if an interrupt is pending for either channel and clear the corresponding pending bit + pub fn check_irq0(&mut self) -> bool { + let a = self.ch.0.check_irq0(); + let b = self.ch.1.check_irq0(); + a | b + } + + /// Check if an interrupt is pending for either channel and clear the corresponding pending bit + pub fn check_irq1(&mut self) -> bool { + let a = self.ch.0.check_irq1(); + let b = self.ch.1.check_irq1(); + a | b + } + + /// Check if the transfer is completed + pub fn is_done(&self) -> bool { + let a = self.ch.1.ch().ch_ctrl_trig().read().busy().bit_is_set(); + let b = self.ch.0.ch().ch_ctrl_trig().read().busy().bit_is_set(); + !(a | b) + } + + /// Block until transfer is complete + pub fn wait(self) -> ((CH1, CH2), FROM, BIDI, TO) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + cortex_m::asm::dsb(); + compiler_fence(Ordering::SeqCst); + + // TODO: Use a tuple type? + ((self.ch.0, self.ch.1), self.from, self.bidi, self.to) + } +} diff --git a/rp-hal/rp2040-hal/src/dma/double_buffer.rs b/rp-hal/rp2040-hal/src/dma/double_buffer.rs new file mode 100644 index 0000000..075140f --- /dev/null +++ b/rp-hal/rp2040-hal/src/dma/double_buffer.rs @@ -0,0 +1,316 @@ +//! Double-buffered DMA Transfers + +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::{ + single_channel::ChannelConfig, single_channel::SingleChannel, EndlessReadTarget, + EndlessWriteTarget, Pace, ReadTarget, WriteTarget, +}; + +/// Configuration for double-buffered DMA transfer +pub struct Config { + ch: (CH1, CH2), + from: FROM, + to: TO, + bswap: bool, + pace: Pace, +} + +impl Config +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Create a new configuration for double-buffered DMA transfer + pub fn new(ch: (CH1, CH2), from: FROM, to: TO) -> Config { + Config { + ch, + from, + to, + bswap: false, + pace: Pace::PreferSource, + } + } + + /// Sets the (preferred) pace for the DMA transfers. + /// + /// Usually, the code will automatically configure the correct pace, but + /// peripheral-to-peripheral transfers require the user to manually select whether the source + /// or the sink shall be queried for the pace signal. + pub fn pace(&mut self, pace: Pace) { + self.pace = pace; + } + + /// Enable/disable byteswapping for the DMA transfers, default value is false. + /// + /// For byte data, this has no effect. For halfword data, the two bytes of + /// each halfword are swapped. For word data, the four bytes of each word + /// are swapped to reverse order. + /// + /// This is a convenient way to change the (half-)words' byte endianness on the fly. + pub fn bswap(&mut self, bswap: bool) { + self.bswap = bswap; + } + + /// Start the DMA transfer + pub fn start(mut self) -> Transfer { + // TODO: Do we want to call any callbacks to configure source/sink? + + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + cortex_m::asm::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the DMA channel and start it. + self.ch + .0 + .config(&self.from, &mut self.to, self.pace, self.bswap, None, true); + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: (), + second_ch: false, + } + } +} + +/// State for a double-buffered read +pub struct ReadNext(BUF); +/// State for a double-buffered write +pub struct WriteNext(BUF); + +/// Instance of a double-buffered DMA transfer +pub struct Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + ch: (CH1, CH2), + from: FROM, + to: TO, + pace: Pace, + bswap: bool, + state: STATE, + second_ch: bool, +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Check if an interrupt is pending for the active channel and clear the corresponding pending bit + pub fn check_irq0(&mut self) -> bool { + if self.second_ch { + self.ch.1.check_irq0() + } else { + self.ch.0.check_irq0() + } + } + + /// Check if an interrupt is pending for the active channel and clear the corresponding pending bit + pub fn check_irq1(&mut self) -> bool { + if self.second_ch { + self.ch.1.check_irq1() + } else { + self.ch.0.check_irq1() + } + } + + /// Check if the transfer is completed + pub fn is_done(&self) -> bool { + if self.second_ch { + !self.ch.1.ch().ch_ctrl_trig().read().busy().bit_is_set() + } else { + !self.ch.0.ch().ch_ctrl_trig().read().busy().bit_is_set() + } + } +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget + EndlessWriteTarget, +{ + /// Block until transfer completed + pub fn wait(self) -> (CH1, CH2, FROM, TO) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + cortex_m::asm::dsb(); + compiler_fence(Ordering::SeqCst); + + // TODO: Use a tuple type? + (self.ch.0, self.ch.1, self.from, self.to) + } +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget + EndlessWriteTarget, +{ + /// Perform the next read of a double-buffered sequence + pub fn read_next>( + mut self, + buf: BUF, + ) -> Transfer> { + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + cortex_m::asm::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the _other_ DMA channel, but do not start it yet. + if self.second_ch { + self.ch + .0 + .config(&buf, &mut self.to, self.pace, self.bswap, None, false); + } else { + self.ch + .1 + .config(&buf, &mut self.to, self.pace, self.bswap, None, false); + } + + // Chain the first channel to the second. + if self.second_ch { + self.ch.1.set_chain_to_enabled(&mut self.ch.0); + } else { + self.ch.0.set_chain_to_enabled(&mut self.ch.1); + } + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: ReadNext(buf), + second_ch: self.second_ch, + } + } +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget + EndlessReadTarget, + TO: WriteTarget, +{ + /// Perform the next write of a double-buffered sequence + pub fn write_next>( + mut self, + mut buf: BUF, + ) -> Transfer> { + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + cortex_m::asm::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the _other_ DMA channel, but do not start it yet. + if self.second_ch { + self.ch + .0 + .config(&self.from, &mut buf, self.pace, self.bswap, None, false); + } else { + self.ch + .1 + .config(&self.from, &mut buf, self.pace, self.bswap, None, false); + } + + // Chain the first channel to the second. + if self.second_ch { + self.ch.1.set_chain_to_enabled(&mut self.ch.0); + } else { + self.ch.0.set_chain_to_enabled(&mut self.ch.1); + } + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: WriteNext(buf), + second_ch: self.second_ch, + } + } +} + +impl Transfer> +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget + EndlessWriteTarget, + NEXT: ReadTarget, +{ + /// Block until the the transfer is complete + pub fn wait(self) -> (FROM, Transfer) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + cortex_m::asm::dsb(); + compiler_fence(Ordering::SeqCst); + + // Invert second_ch as now the other channel is the "active" channel. + ( + self.from, + Transfer { + ch: self.ch, + from: self.state.0, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: (), + second_ch: !self.second_ch, + }, + ) + } +} + +impl Transfer> +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget + EndlessReadTarget, + TO: WriteTarget, + NEXT: WriteTarget, +{ + /// Block until transfer is complete + pub fn wait(self) -> (TO, Transfer) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + cortex_m::asm::dsb(); + compiler_fence(Ordering::SeqCst); + + // Invert second_ch as now the other channel is the "active" channel. + ( + self.to, + Transfer { + ch: self.ch, + from: self.from, + to: self.state.0, + pace: self.pace, + bswap: self.bswap, + state: (), + second_ch: !self.second_ch, + }, + ) + } +} diff --git a/rp-hal/rp2040-hal/src/dma/mod.rs b/rp-hal/rp2040-hal/src/dma/mod.rs new file mode 100644 index 0000000..1b79a3a --- /dev/null +++ b/rp-hal/rp2040-hal/src/dma/mod.rs @@ -0,0 +1,327 @@ +//! Direct memory access (DMA). +//! +//! The DMA unit of the RP2040 seems very simplistic at first when compared to other MCUs. For +//! example, the individual DMA channels do not support chaining multiple buffers. However, within +//! certain limits, the DMA engine supports a wide range of transfer and buffer types, often by +//! combining multiple DMA channels: +//! +//! * Simple RX/TX transfers filling a single buffer or transferring data from one peripheral to +//! another. +//! * RX/TX transfers that use multiple chained buffers: These transfers require two channels to +//! be combined, where the first DMA channel configures the second DMA channel. An example for +//! this transfer type can be found in the datasheet. +//! * Repeated transfers from/to a set of buffers: By allocating one channel per buffer and +//! chaining the channels together, continuous transfers to a set of ring buffers can be +//! achieved. Note, however, that the MCU manually needs to reconfigure the DMA units unless the +//! buffer addresses and sizes are aligned, in which case the ring buffer functionality of the +//! DMA engine can be used. Even then, however, at least two DMA channels are required as a +//! channel cannot be chained to itself. +//! +//! This API tries to provide three types of buffers: Single buffers, double-buffered transfers +//! where the user can specify the next buffer while the previous is being transferred, and +//! automatic continuous ring buffers consisting of two aligned buffers being read or written +//! alternatingly. + +use core::marker::PhantomData; +use embedded_dma::{ReadBuffer, WriteBuffer}; + +use crate::{ + pac::{self, DMA}, + resets::SubsystemReset, + typelevel::Sealed, +}; +// Export these types for easier use by external code +pub use crate::dma::single_channel::SingleChannel; + +// Bring in our submodules +pub mod bidirectional; +pub mod double_buffer; +pub mod single_buffer; +mod single_channel; + +/// DMA unit. +pub trait DMAExt: Sealed { + /// Splits the DMA unit into its individual channels. + fn split(self, resets: &mut pac::RESETS) -> Channels; + /// Splits the DMA unit into its individual channels with runtime ownership + fn dyn_split(self, resets: &mut pac::RESETS) -> DynChannels; +} + +/// DMA channel. +pub struct Channel { + _phantom: PhantomData, +} + +/// DMA channel identifier. +pub trait ChannelIndex: Sealed { + /// Numerical index of the DMA channel (0..11). + fn id() -> u8; +} + +macro_rules! channels { + ( + $($CHX:ident: ($chX:ident, $x:expr),)+ + ) => { + impl DMAExt for DMA { + fn split(self, resets: &mut pac::RESETS) -> Channels { + self.reset_bring_down(resets); + self.reset_bring_up(resets); + + Channels { + $( + $chX: Channel { + _phantom: PhantomData, + }, + )+ + } + } + + fn dyn_split(self, resets: &mut pac::RESETS) -> DynChannels{ + self.reset_bring_down(resets); + self.reset_bring_up(resets); + + DynChannels { + $( + $chX: Some(Channel { + _phantom: PhantomData, + }), + )+ + } + } + } + + impl Sealed for DMA {} + + /// Set of DMA channels. + pub struct Channels { + $( + /// DMA channel. + pub $chX: Channel<$CHX>, + )+ + } + $( + /// DMA channel identifier. + pub struct $CHX; + impl ChannelIndex for $CHX { + fn id() -> u8 { + $x + } + } + + impl Sealed for $CHX {} + )+ + + /// Set of DMA channels with runtime ownership. + pub struct DynChannels { + $( + /// DMA channel. + pub $chX: Option>, + )+ + } + } +} + +channels! { + CH0: (ch0, 0), + CH1: (ch1, 1), + CH2: (ch2, 2), + CH3: (ch3, 3), + CH4: (ch4, 4), + CH5: (ch5, 5), + CH6: (ch6, 6), + CH7: (ch7, 7), + CH8: (ch8, 8), + CH9: (ch9, 9), + CH10:(ch10, 10), + CH11:(ch11, 11), +} + +trait ChannelRegs { + unsafe fn ptr() -> *const pac::dma::CH; + fn regs(&self) -> &pac::dma::CH; +} + +impl ChannelRegs for Channel { + unsafe fn ptr() -> *const pac::dma::CH { + (*pac::DMA::ptr()).ch(CH::id() as usize) + } + + fn regs(&self) -> &pac::dma::CH { + unsafe { &*Self::ptr() } + } +} + +/// Trait which is implemented by anything that can be read via DMA. +/// +/// # Safety +/// +/// The implementing type must be safe to use for DMA reads. This means: +/// +/// - The range returned by rx_address_count must point to a valid address, +/// and if rx_increment is true, count must fit into the allocated buffer. +/// - As long as no `&mut self` method is called on the implementing object: +/// - `rx_address_count` must always return the same value, if called multiple +/// times. +/// - The memory specified by the pointer and size returned by `rx_address_count` +/// must not be freed during the transfer it is used in as long as `self` is not dropped. +pub unsafe trait ReadTarget { + /// Type which is transferred in a single DMA transfer. + type ReceivedWord; + + /// Returns the DREQ number for this data source (`None` for memory buffers). + fn rx_treq() -> Option; + + /// Returns the address and the maximum number of words that can be transferred from this data + /// source in a single DMA operation. + /// + /// For peripherals, the count should likely be u32::MAX. If a data source implements + /// EndlessReadTarget, it is suitable for infinite transfers from or to ring buffers. Note that + /// ring buffers designated for endless transfers, but with a finite buffer size, should return + /// the size of their individual buffers here. + /// + /// # Safety + /// + /// This function has the same safety guarantees as `ReadBuffer::read_buffer`. + fn rx_address_count(&self) -> (u32, u32); + + /// Returns whether the address shall be incremented after each transfer. + fn rx_increment(&self) -> bool; +} + +/// Marker which signals that `rx_address_count()` can be called multiple times. +/// +/// The DMA code will never call `rx_address_count()` to request more than two buffers to configure +/// two DMA channels. In the case of peripherals, the function can always return the same values. +pub trait EndlessReadTarget: ReadTarget {} + +/// Safety: ReadBuffer and ReadTarget have the same safety requirements. +unsafe impl ReadTarget for B { + type ReceivedWord = ::Word; + + fn rx_treq() -> Option { + None + } + + fn rx_address_count(&self) -> (u32, u32) { + let (ptr, len) = unsafe { self.read_buffer() }; + (ptr as u32, len as u32) + } + + fn rx_increment(&self) -> bool { + true + } +} + +/// Trait which is implemented by anything that can be written via DMA. +/// +/// # Safety +/// +/// The implementing type must be safe to use for DMA writes. This means: +/// +/// - The range returned by tx_address_count must point to a valid address, +/// and if tx_increment is true, count must fit into the allocated buffer. +/// - As long as no other `&mut self` method is called on the implementing object: +/// - `tx_address_count` must always return the same value, if called multiple +/// times. +/// - The memory specified by the pointer and size returned by `tx_address_count` +/// must not be freed during the transfer it is used in as long as `self` is not dropped. +pub unsafe trait WriteTarget { + /// Type which is transferred in a single DMA transfer. + type TransmittedWord; + + /// Returns the DREQ number for this data sink (`None` for memory buffers). + fn tx_treq() -> Option; + + /// Returns the address and the maximum number of words that can be transferred from this data + /// source in a single DMA operation. + /// + /// See `ReadTarget::rx_address_count` for a complete description of the semantics of this + /// function. + fn tx_address_count(&mut self) -> (u32, u32); + + /// Returns whether the address shall be incremented after each transfer. + fn tx_increment(&self) -> bool; +} + +/// Marker which signals that `tx_address_count()` can be called multiple times. +/// +/// The DMA code will never call `tx_address_count()` to request more than two buffers to configure +/// two DMA channels. In the case of peripherals, the function can always return the same values. +pub trait EndlessWriteTarget: WriteTarget {} + +/// Safety: WriteBuffer and WriteTarget have the same safety requirements. +unsafe impl WriteTarget for B { + type TransmittedWord = ::Word; + + fn tx_treq() -> Option { + None + } + + fn tx_address_count(&mut self) -> (u32, u32) { + let (ptr, len) = unsafe { self.write_buffer() }; + (ptr as u32, len as u32) + } + + fn tx_increment(&self) -> bool { + true + } +} + +/// Pacing for DMA transfers. +/// +/// Generally, while memory-to-memory DMA transfers can operate at maximum possible throughput, +/// transfers involving peripherals commonly have to wait for data to be available or for available +/// space in write queues. This type defines whether the sink or the source shall pace the transfer +/// for peripheral-to-peripheral transfers. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pace { + /// The DREQ signal from the source is used, if available. If not, the sink's DREQ signal is + /// used. + PreferSource, + /// The DREQ signal from the sink is used, if available. If not, the source's DREQ signal is + /// used. + PreferSink, + // TODO: Timers? +} + +/// Error during DMA configuration. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DMAError { + /// Buffers were not aligned to their size even though they needed to be. + Alignment, + /// An illegal configuration (i.e., buffer sizes not suitable for a memory-to-memory transfer) + /// was specified. + IllegalConfig, +} + +/// Constraint on transfer size types +pub trait TransferSize: Sealed { + /// Actual type of transfer + type Type; +} + +/// DMA transfer in bytes (u8) +#[derive(Debug, Copy, Clone)] +pub struct Byte; +/// DMA transfer in half words (u16) +#[derive(Debug, Copy, Clone)] +pub struct HalfWord; +/// DMA transfer in words (u32) +#[derive(Debug, Copy, Clone)] +pub struct Word; + +impl Sealed for Byte {} +impl Sealed for HalfWord {} +impl Sealed for Word {} + +impl TransferSize for Byte { + type Type = u8; +} +impl TransferSize for HalfWord { + type Type = u16; +} +impl TransferSize for Word { + type Type = u32; +} diff --git a/rp-hal/rp2040-hal/src/dma/single_buffer.rs b/rp-hal/rp2040-hal/src/dma/single_buffer.rs new file mode 100644 index 0000000..17d3d38 --- /dev/null +++ b/rp-hal/rp2040-hal/src/dma/single_buffer.rs @@ -0,0 +1,149 @@ +//! Single-buffered or peripheral-peripheral DMA Transfers + +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::{ + single_channel::ChannelConfig, single_channel::SingleChannel, Pace, ReadTarget, WriteTarget, +}; + +/// Configuration for single-buffered DMA transfer +pub struct Config { + ch: CH, + from: FROM, + to: TO, + pace: Pace, + bswap: bool, +} + +impl Config +where + CH: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Create a new configuration for single-buffered DMA transfer + pub fn new(ch: CH, from: FROM, to: TO) -> Config { + Config { + ch, + from, + to, + pace: Pace::PreferSource, + bswap: false, + } + } + + /// Sets the (preferred) pace for the DMA transfers. + /// + /// Usually, the code will automatically configure the correct pace, but + /// peripheral-to-peripheral transfers require the user to manually select whether the source + /// or the sink shall be queried for the pace signal. + pub fn pace(&mut self, pace: Pace) { + self.pace = pace; + } + + /// Enable/disable byteswapping for the DMA transfers, default value is false. + /// + /// For byte data, this has no effect. For halfword data, the two bytes of + /// each halfword are swapped. For word data, the four bytes of each word + /// are swapped to reverse order. + /// + /// This is a convenient way to change the (half-)words' byte endianness on the fly. + pub fn bswap(&mut self, bswap: bool) { + self.bswap = bswap; + } + + /// Start the DMA transfer + pub fn start(mut self) -> Transfer { + // TODO: Do we want to call any callbacks to configure source/sink? + + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + cortex_m::asm::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the DMA channel and start it. + self.ch + .config(&self.from, &mut self.to, self.pace, self.bswap, None, true); + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + } + } +} + +// TODO: Drop for most of these structs +/// Instance of a single-buffered DMA transfer +pub struct Transfer { + ch: CH, + from: FROM, + to: TO, +} + +impl Transfer +where + CH: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + pub fn check_irq0(&mut self) -> bool { + self.ch.check_irq0() + } + + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + pub fn check_irq1(&mut self) -> bool { + self.ch.check_irq1() + } + + /// Check if the transfer has completed. + pub fn is_done(&self) -> bool { + !self.ch.ch().ch_ctrl_trig().read().busy().bit_is_set() + } + + /// Block until the transfer is complete, returning the channel and targets + pub fn wait(self) -> (CH, FROM, TO) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + cortex_m::asm::dsb(); + compiler_fence(Ordering::SeqCst); + + (self.ch, self.from, self.to) + } + + /// Aborts the current transfer, returning the channel and targets + pub fn abort(mut self) -> (CH, FROM, TO) { + let irq0_was_enabled = self.ch.is_enabled_irq0(); + let irq1_was_enabled = self.ch.is_enabled_irq1(); + self.ch.disable_irq0(); + self.ch.disable_irq1(); + + let chan_abort = unsafe { &*crate::pac::DMA::ptr() }.chan_abort(); + let abort_mask = (1 << self.ch.id()) as u16; + + chan_abort.write(|w| unsafe { w.chan_abort().bits(abort_mask) }); + + while chan_abort.read().chan_abort().bits() != 0 {} + + while !self.is_done() {} + + self.ch.check_irq0(); + self.ch.check_irq1(); + + if irq0_was_enabled { + self.ch.enable_irq0(); + } + + if irq1_was_enabled { + self.ch.enable_irq1(); + } + + // Make sure that memory contents reflect what the user intended. + cortex_m::asm::dsb(); + compiler_fence(Ordering::SeqCst); + + (self.ch, self.from, self.to) + } +} diff --git a/rp-hal/rp2040-hal/src/dma/single_channel.rs b/rp-hal/rp2040-hal/src/dma/single_channel.rs new file mode 100644 index 0000000..a380425 --- /dev/null +++ b/rp-hal/rp2040-hal/src/dma/single_channel.rs @@ -0,0 +1,259 @@ +use crate::pac::DMA; + +use super::{Channel, ChannelIndex, Pace, ReadTarget, WriteTarget}; +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + dma::ChannelRegs, + typelevel::Sealed, +}; +use core::mem; + +/// Trait which implements low-level functionality for transfers using a single DMA channel. +pub trait SingleChannel: Sealed { + /// Returns the registers associated with this DMA channel. + /// + /// In the case of channel pairs, this returns the first channel. + fn ch(&self) -> &crate::pac::dma::CH; + /// Returns the index of the DMA channel. + fn id(&self) -> u8; + + #[deprecated(note = "Renamed to enable_irq0")] + /// Enables the DMA_IRQ_0 signal for this channel. + fn listen_irq0(&mut self) { + self.enable_irq0(); + } + + /// Enables the DMA_IRQ_0 signal for this channel. + fn enable_irq0(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_set((*DMA::ptr()).inte0().as_ptr(), 1 << self.id()); + } + } + + /// Check if the DMA_IRQ_0 signal for this channel is enabled. + fn is_enabled_irq0(&mut self) -> bool { + unsafe { ((*DMA::ptr()).inte0().read().bits() & (1 << self.id())) != 0 } + } + + #[deprecated(note = "Renamed to disable_irq0")] + /// Disables the DMA_IRQ_0 signal for this channel. + fn unlisten_irq0(&mut self) { + self.disable_irq0(); + } + + /// Disables the DMA_IRQ_0 signal for this channel. + fn disable_irq0(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_clear((*DMA::ptr()).inte0().as_ptr(), 1 << self.id()); + } + } + + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + fn check_irq0(&mut self) -> bool { + // Safety: The following is race-free as we only ever clear the bit for this channel. + // Nobody else modifies that bit. + unsafe { + let status = (*DMA::ptr()).ints0().read().bits(); + if (status & (1 << self.id())) != 0 { + // Clear the interrupt. + (*DMA::ptr()).ints0().write(|w| w.bits(1 << self.id())); + true + } else { + false + } + } + } + + #[deprecated(note = "Renamed to enable_irq1")] + /// Enables the DMA_IRQ_1 signal for this channel. + fn listen_irq1(&mut self) { + self.enable_irq1(); + } + + /// Enables the DMA_IRQ_1 signal for this channel. + fn enable_irq1(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_set((*DMA::ptr()).inte1().as_ptr(), 1 << self.id()); + } + } + + /// Check if the DMA_IRQ_1 signal for this channel is enabled. + fn is_enabled_irq1(&mut self) -> bool { + unsafe { ((*DMA::ptr()).inte1().read().bits() & (1 << self.id())) != 0 } + } + + #[deprecated(note = "Renamed to disable_irq1")] + /// Disables the DMA_IRQ_1 signal for this channel. + fn unlisten_irq1(&mut self) { + self.disable_irq1(); + } + + /// Disables the DMA_IRQ_1 signal for this channel. + fn disable_irq1(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_clear((*DMA::ptr()).inte1().as_ptr(), 1 << self.id()); + } + } + + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + fn check_irq1(&mut self) -> bool { + // Safety: The following is race-free as we only ever clear the bit for this channel. + // Nobody else modifies that bit. + unsafe { + let status = (*DMA::ptr()).ints1().read().bits(); + if (status & (1 << self.id())) != 0 { + // Clear the interrupt. + (*DMA::ptr()).ints1().write(|w| w.bits(1 << self.id())); + true + } else { + false + } + } + } +} + +impl SingleChannel for Channel { + fn ch(&self) -> &crate::pac::dma::CH { + self.regs() + } + + fn id(&self) -> u8 { + CH::id() + } +} + +impl Sealed for Channel {} + +impl SingleChannel for (Channel, Channel) { + fn ch(&self) -> &crate::pac::dma::CH { + self.0.regs() + } + + fn id(&self) -> u8 { + CH1::id() + } +} + +pub(crate) trait ChannelConfig { + fn config( + &mut self, + from: &FROM, + to: &mut TO, + pace: Pace, + bswap: bool, + chain_to: Option, + start: bool, + ) where + FROM: ReadTarget, + TO: WriteTarget; + + fn set_chain_to_enabled(&mut self, other: &mut CH); + fn start(&mut self); + fn start_both(&mut self, other: &mut CH); +} + +// RP2040's DMA engine only works with certain word sizes. Make sure that other +// word sizes will fail to compile. +struct IsValidWordSize { + w: core::marker::PhantomData, +} + +impl IsValidWordSize { + const OK: usize = { + match mem::size_of::() { + 1 | 2 | 4 => 0, // ok + _ => panic!("Unsupported DMA word size"), + } + }; +} + +impl ChannelConfig for CH { + fn config( + &mut self, + from: &FROM, + to: &mut TO, + pace: Pace, + bswap: bool, + chain_to: Option, + start: bool, + ) where + FROM: ReadTarget, + TO: WriteTarget, + { + // Configure the DMA channel. + let _ = IsValidWordSize::::OK; + + let (src, src_count) = from.rx_address_count(); + let src_incr = from.rx_increment(); + let (dest, dest_count) = to.tx_address_count(); + let dest_incr = to.tx_increment(); + const TREQ_UNPACED: u8 = 0x3f; + let treq = match pace { + Pace::PreferSource => FROM::rx_treq().or_else(TO::tx_treq).unwrap_or(TREQ_UNPACED), + Pace::PreferSink => TO::tx_treq().or_else(FROM::rx_treq).unwrap_or(TREQ_UNPACED), + }; + let len = u32::min(src_count, dest_count); + self.ch().ch_al1_ctrl().write(|w| unsafe { + w.data_size().bits(mem::size_of::() as u8 >> 1); + w.incr_read().bit(src_incr); + w.incr_write().bit(dest_incr); + w.treq_sel().bits(treq); + w.bswap().bit(bswap); + w.chain_to().bits(chain_to.unwrap_or_else(|| self.id())); + w.en().bit(true); + w + }); + self.ch().ch_read_addr().write(|w| unsafe { w.bits(src) }); + self.ch().ch_trans_count().write(|w| unsafe { w.bits(len) }); + if start { + self.ch() + .ch_al2_write_addr_trig() + .write(|w| unsafe { w.bits(dest) }); + } else { + self.ch().ch_write_addr().write(|w| unsafe { w.bits(dest) }); + } + } + + fn set_chain_to_enabled(&mut self, other: &mut CH2) { + // We temporarily pause the channel when setting CHAIN_TO, to prevent any race condition + // that could occur, as we need to check afterwards whether the channel was successfully + // chained to this channel or whether this channel was already completed. If we did not + // pause this channel, we could get into a situation where both channels completed in quick + // succession, yet we did not notice, as the situation is not distinguishable from one + // where the second channel was not started at all. + + self.ch().ch_al1_ctrl().modify(|_, w| unsafe { + w.chain_to().bits(other.id()); + w.en().clear_bit(); + w + }); + if self.ch().ch_al1_ctrl().read().busy().bit_is_set() { + // This channel is still active, so just continue. + self.ch().ch_al1_ctrl().modify(|_, w| w.en().set_bit()); + } else { + // This channel has already finished, so just start the other channel directly. + other.start(); + } + } + + fn start(&mut self) { + // Safety: The write does not interfere with any other writes, it only affects this + // channel. + unsafe { &*crate::pac::DMA::ptr() } + .multi_chan_trigger() + .write(|w| unsafe { w.bits(1 << self.id()) }); + } + + fn start_both(&mut self, other: &mut CH2) { + // Safety: The write does not interfere with any other writes, it only affects this + // channel and other (which we have an exclusive borrow of). + let channel_flags = 1 << self.id() | 1 << other.id(); + unsafe { &*crate::pac::DMA::ptr() } + .multi_chan_trigger() + .write(|w| unsafe { w.bits(channel_flags) }); + } +} diff --git a/rp-hal/rp2040-hal/src/float/add_sub.rs b/rp-hal/rp2040-hal/src/float/add_sub.rs new file mode 100644 index 0000000..06c987a --- /dev/null +++ b/rp-hal/rp2040-hal/src/float/add_sub.rs @@ -0,0 +1,89 @@ +use super::{Float, Int}; +use crate::rom_data; + +trait ROMAdd { + fn rom_add(self, b: Self) -> Self; +} + +impl ROMAdd for f32 { + fn rom_add(self, b: Self) -> Self { + rom_data::float_funcs::fadd(self, b) + } +} + +impl ROMAdd for f64 { + fn rom_add(self, b: Self) -> Self { + rom_data::double_funcs::dadd(self, b) + } +} + +fn add(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_not_finite() { + let class_a = a.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK); + let class_b = b.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK); + + if class_a == F::Int::ZERO && class_b == F::Int::ZERO { + // inf + inf = inf + return a; + } + if class_a == F::SIGN_MASK && class_b == F::SIGN_MASK { + // -inf + (-inf) = -inf + return a; + } + + // Sign mismatch, or either is NaN already + return F::NAN; + } + + // [-]inf/NaN + X = [-]inf/NaN + return a; + } + + if b.is_not_finite() { + // X + [-]inf/NaN = [-]inf/NaN + return b; + } + + a.rom_add(b) +} + +intrinsics! { + #[alias = __addsf3vfp] + #[aeabi = __aeabi_fadd] + extern "C" fn __addsf3(a: f32, b: f32) -> f32 { + add(a, b) + } + + #[bootrom_v2] + #[alias = __adddf3vfp] + #[aeabi = __aeabi_dadd] + extern "C" fn __adddf3(a: f64, b: f64) -> f64 { + add(a, b) + } + + // The ROM just implements subtraction the same way, so just do it here + // and save the work of implementing more complicated NaN/inf handling. + + #[alias = __subsf3vfp] + #[aeabi = __aeabi_fsub] + extern "C" fn __subsf3(a: f32, b: f32) -> f32 { + add(a, -b) + } + + #[bootrom_v2] + #[alias = __subdf3vfp] + #[aeabi = __aeabi_dsub] + extern "C" fn __subdf3(a: f64, b: f64) -> f64 { + add(a, -b) + } + + extern "aapcs" fn __aeabi_frsub(a: f32, b: f32) -> f32 { + add(b, -a) + } + + #[bootrom_v2] + extern "aapcs" fn __aeabi_drsub(a: f64, b: f64) -> f64 { + add(b, -a) + } +} diff --git a/rp-hal/rp2040-hal/src/float/cmp.rs b/rp-hal/rp2040-hal/src/float/cmp.rs new file mode 100644 index 0000000..f3b16de --- /dev/null +++ b/rp-hal/rp2040-hal/src/float/cmp.rs @@ -0,0 +1,198 @@ +use super::Float; +use crate::rom_data; + +trait ROMCmp { + fn rom_cmp(self, b: Self) -> i32; +} + +impl ROMCmp for f32 { + fn rom_cmp(self, b: Self) -> i32 { + rom_data::float_funcs::fcmp(self, b) + } +} + +impl ROMCmp for f64 { + fn rom_cmp(self, b: Self) -> i32 { + rom_data::double_funcs::dcmp(self, b) + } +} + +fn le_abi(a: F, b: F) -> i32 { + if a.is_nan() || b.is_nan() { + 1 + } else { + a.rom_cmp(b) + } +} + +fn ge_abi(a: F, b: F) -> i32 { + if a.is_nan() || b.is_nan() { + -1 + } else { + a.rom_cmp(b) + } +} + +intrinsics! { + #[slower_than_default] + #[bootrom_v2] + #[alias = __eqsf2, __ltsf2, __nesf2] + extern "C" fn __lesf2(a: f32, b: f32) -> i32 { + le_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __eqdf2, __ltdf2, __nedf2] + extern "C" fn __ledf2(a: f64, b: f64) -> i32 { + le_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __gtsf2] + extern "C" fn __gesf2(a: f32, b: f32) -> i32 { + ge_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __gtdf2] + extern "C" fn __gedf2(a: f64, b: f64) -> i32 { + ge_abi(a, b) + } + + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmple(a: f32, b: f32) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpge(a: f32, b: f32) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpeq(a: f32, b: f32) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmplt(a: f32, b: f32) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpgt(a: f32, b: f32) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmple(a: f64, b: f64) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpge(a: f64, b: f64) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpeq(a: f64, b: f64) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmplt(a: f64, b: f64) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpgt(a: f64, b: f64) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gesf2vfp(a: f32, b: f32) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gedf2vfp(a: f64, b: f64) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gtsf2vfp(a: f32, b: f32) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gtdf2vfp(a: f64, b: f64) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ltsf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ltdf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __lesf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ledf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __nesf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) != 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __nedf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) != 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __eqsf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __eqdf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) == 0) as i32 + } +} diff --git a/rp-hal/rp2040-hal/src/float/conv.rs b/rp-hal/rp2040-hal/src/float/conv.rs new file mode 100644 index 0000000..40678fc --- /dev/null +++ b/rp-hal/rp2040-hal/src/float/conv.rs @@ -0,0 +1,154 @@ +use super::Float; +use crate::rom_data; + +// Some of these are also not connected in the Pico SDK. This is probably +// because the ROM version actually does a fixed point conversion, just with +// the fractional width set to zero. + +intrinsics! { + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_i2f] + extern "C" fn __floatsisf(i: i32) -> f32 { + rom_data::float_funcs::int_to_float(i) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_i2d] + extern "C" fn __floatsidf(i: i32) -> f64 { + rom_data::double_funcs::int_to_double(i) + } + + // Questionable gain + #[aeabi = __aeabi_l2f] + extern "C" fn __floatdisf(i: i64) -> f32 { + rom_data::float_funcs::int64_to_float(i) + } + + #[bootrom_v2] + #[aeabi = __aeabi_l2d] + extern "C" fn __floatdidf(i: i64) -> f64 { + rom_data::double_funcs::int64_to_double(i) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_ui2f] + extern "C" fn __floatunsisf(i: u32) -> f32 { + rom_data::float_funcs::uint_to_float(i) + } + + // Questionable gain + #[bootrom_v2] + #[aeabi = __aeabi_ui2d] + extern "C" fn __floatunsidf(i: u32) -> f64 { + rom_data::double_funcs::uint_to_double(i) + } + + // Questionable gain + #[bootrom_v2] + #[aeabi = __aeabi_ul2f] + extern "C" fn __floatundisf(i: u64) -> f32 { + rom_data::float_funcs::uint64_to_float(i) + } + + #[bootrom_v2] + #[aeabi = __aeabi_ul2d] + extern "C" fn __floatundidf(i: u64) -> f64 { + rom_data::double_funcs::uint64_to_double(i) + } + + + // The Pico SDK does some optimization here (e.x. fast paths for zero and + // one), but we can just directly connect it. + #[aeabi = __aeabi_f2iz] + extern "C" fn __fixsfsi(f: f32) -> i32 { + rom_data::float_funcs::float_to_int(f) + } + + #[bootrom_v2] + #[aeabi = __aeabi_f2lz] + extern "C" fn __fixsfdi(f: f32) -> i64 { + rom_data::float_funcs::float_to_int64(f) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2iz] + extern "C" fn __fixdfsi(f: f64) -> i32 { + rom_data::double_funcs::double_to_int(f) + } + + // Like with the 32 bit version, there's optimization that we just + // skip. + #[bootrom_v2] + #[aeabi = __aeabi_d2lz] + extern "C" fn __fixdfdi(f: f64) -> i64 { + rom_data::double_funcs::double_to_int64(f) + } + + #[slower_than_default] + #[aeabi = __aeabi_f2uiz] + extern "C" fn __fixunssfsi(f: f32) -> u32 { + rom_data::float_funcs::float_to_uint(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_f2ulz] + extern "C" fn __fixunssfdi(f: f32) -> u64 { + rom_data::float_funcs::float_to_uint64(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2uiz] + extern "C" fn __fixunsdfsi(f: f64) -> u32 { + rom_data::double_funcs::double_to_uint(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2ulz] + extern "C" fn __fixunsdfdi(f: f64) -> u64 { + rom_data::double_funcs::double_to_uint64(f) + } + + #[bootrom_v2] + #[alias = __extendsfdf2vfp] + #[aeabi = __aeabi_f2d] + extern "C" fn __extendsfdf2(f: f32) -> f64 { + if f.is_not_finite() { + return f64::from_repr( + // Not finite + f64::EXPONENT_MASK | + // Preserve NaN or inf + ((f.repr() & f32::SIGNIFICAND_MASK) as u64) | + // Preserve sign + ((f.repr() & f32::SIGN_MASK) as u64) << (f64::BITS-f32::BITS) + ); + } + rom_data::float_funcs::float_to_double(f) + } + + #[bootrom_v2] + #[alias = __truncdfsf2vfp] + #[aeabi = __aeabi_d2f] + extern "C" fn __truncdfsf2(f: f64) -> f32 { + if f.is_not_finite() { + let mut repr: u32 = + // Not finite + f32::EXPONENT_MASK | + // Preserve sign + ((f.repr() & f64::SIGN_MASK) >> (f64::BITS-f32::BITS)) as u32; + // Set NaN + if (f.repr() & f64::SIGNIFICAND_MASK) != 0 { + repr |= 1; + } + return f32::from_repr(repr); + } + rom_data::double_funcs::double_to_float(f) + } +} diff --git a/rp-hal/rp2040-hal/src/float/div.rs b/rp-hal/rp2040-hal/src/float/div.rs new file mode 100644 index 0000000..af74545 --- /dev/null +++ b/rp-hal/rp2040-hal/src/float/div.rs @@ -0,0 +1,136 @@ +use super::Float; +use crate::{pac, rom_data}; + +// Make sure this stays as a separate call, because when it's inlined the +// compiler will move the save of the registers used to contain the divider +// state into the function prologue. That save and restore (push/pop) takes +// longer than the actual division, so doing it in the common case where +// they are not required wastes a lot of time. +#[inline(never)] +#[cold] +fn save_divider_and_call(f: F) -> R +where + F: FnOnce() -> R, +{ + let sio = unsafe { &(*pac::SIO::ptr()) }; + + // Since we can't save the signed-ness of the calculation, we have to make + // sure that there's at least an 8 cycle delay before we read the result. + // The Pico SDK ensures this by using a 6 cycle push and two 1 cycle reads. + // Since we can't be sure the Rust implementation will optimize to the same, + // just use an explicit wait. + while !sio.div_csr().read().ready().bit() {} + + // Read the quotient last, since that's what clears the dirty flag + let dividend = sio.div_udividend().read().bits(); + let divisor = sio.div_udivisor().read().bits(); + let remainder = sio.div_remainder().read().bits(); + let quotient = sio.div_quotient().read().bits(); + + // If we get interrupted here (before a write sets the DIRTY flag) its fine, since + // we have the full state, so the interruptor doesn't have to restore it. Once the + // write happens and the DIRTY flag is set, the interruptor becomes responsible for + // restoring our state. + let result = f(); + + // If we are interrupted here, then the interruptor will start an incorrect calculation + // using a wrong divisor, but we'll restore the divisor and result ourselves correctly. + // This sets DIRTY, so any interruptor will save the state. + sio.div_udividend().write(|w| unsafe { w.bits(dividend) }); + // If we are interrupted here, the the interruptor may start the calculation using + // incorrectly signed inputs, but we'll restore the result ourselves. + // This sets DIRTY, so any interruptor will save the state. + sio.div_udivisor().write(|w| unsafe { w.bits(divisor) }); + // If we are interrupted here, the interruptor will have restored everything but the + // quotient may be wrongly signed. If the calculation started by the above writes is + // still ongoing it is stopped, so it won't replace the result we're restoring. + // DIRTY and READY set, but only DIRTY matters to make the interruptor save the state. + sio.div_remainder().write(|w| unsafe { w.bits(remainder) }); + // State fully restored after the quotient write. This sets both DIRTY and READY, so + // whatever we may have interrupted can read the result. + sio.div_quotient().write(|w| unsafe { w.bits(quotient) }); + + result +} + +fn save_divider(f: F) -> R +where + F: FnOnce() -> R, +{ + let sio = unsafe { &(*pac::SIO::ptr()) }; + if !sio.div_csr().read().dirty().bit() { + // Not dirty, so nothing is waiting for the calculation. So we can just + // issue it directly without a save/restore. + f() + } else { + save_divider_and_call(f) + } +} + +trait ROMDiv { + fn rom_div(self, b: Self) -> Self; +} + +impl ROMDiv for f32 { + fn rom_div(self, b: Self) -> Self { + // ROM implementation uses the hardware divider, so we have to save it + save_divider(|| rom_data::float_funcs::fdiv(self, b)) + } +} + +impl ROMDiv for f64 { + fn rom_div(self, b: Self) -> Self { + // ROM implementation uses the hardware divider, so we have to save it + save_divider(|| rom_data::double_funcs::ddiv(self, b)) + } +} + +fn div(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_not_finite() { + // inf/NaN / inf/NaN = NaN + return F::NAN; + } + + if b.is_zero() { + // inf/NaN / 0 = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // [+/-]inf/NaN / (-X) = [-/+]inf/NaN + a.negate() + } else { + // [-]inf/NaN / X = [-]inf/NaN + a + }; + } + + if b.is_nan() { + // X / NaN = NaN + return b; + } + + // ROM handles X / 0 = [-]inf and X / [-]inf = [-]0, so we only + // need to catch 0 / 0 + if b.is_zero() && a.is_zero() { + return F::NAN; + } + + a.rom_div(b) +} + +intrinsics! { + #[alias = __divsf3vfp] + #[aeabi = __aeabi_fdiv] + extern "C" fn __divsf3(a: f32, b: f32) -> f32 { + div(a, b) + } + + #[bootrom_v2] + #[alias = __divdf3vfp] + #[aeabi = __aeabi_ddiv] + extern "C" fn __divdf3(a: f64, b: f64) -> f64 { + div(a, b) + } +} diff --git a/rp-hal/rp2040-hal/src/float/functions.rs b/rp-hal/rp2040-hal/src/float/functions.rs new file mode 100644 index 0000000..4ed8333 --- /dev/null +++ b/rp-hal/rp2040-hal/src/float/functions.rs @@ -0,0 +1,236 @@ +use crate::float::{Float, Int}; +use crate::rom_data; + +trait ROMFunctions { + fn sqrt(self) -> Self; + fn ln(self) -> Self; + fn exp(self) -> Self; + fn sin(self) -> Self; + fn cos(self) -> Self; + fn tan(self) -> Self; + fn atan2(self, y: Self) -> Self; + + fn to_trig_range(self) -> Self; +} + +impl ROMFunctions for f32 { + fn sqrt(self) -> Self { + rom_data::float_funcs::fsqrt(self) + } + + fn ln(self) -> Self { + rom_data::float_funcs::fln(self) + } + + fn exp(self) -> Self { + rom_data::float_funcs::fexp(self) + } + + fn sin(self) -> Self { + rom_data::float_funcs::fsin(self) + } + + fn cos(self) -> Self { + rom_data::float_funcs::fcos(self) + } + + fn tan(self) -> Self { + rom_data::float_funcs::ftan(self) + } + + fn atan2(self, y: Self) -> Self { + rom_data::float_funcs::fatan2(self, y) + } + + fn to_trig_range(self) -> Self { + // -128 < X < 128, logic from the Pico SDK + let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS; + if exponent < 134 { + self + } else { + self % (core::f32::consts::PI * 2.0) + } + } +} + +impl ROMFunctions for f64 { + fn sqrt(self) -> Self { + rom_data::double_funcs::dsqrt(self) + } + + fn ln(self) -> Self { + rom_data::double_funcs::dln(self) + } + + fn exp(self) -> Self { + rom_data::double_funcs::dexp(self) + } + + fn sin(self) -> Self { + rom_data::double_funcs::dsin(self) + } + + fn cos(self) -> Self { + rom_data::double_funcs::dcos(self) + } + fn tan(self) -> Self { + rom_data::double_funcs::dtan(self) + } + + fn atan2(self, y: Self) -> Self { + rom_data::double_funcs::datan2(self, y) + } + + fn to_trig_range(self) -> Self { + // -1024 < X < 1024, logic from the Pico SDK + let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS; + if exponent < 1033 { + self + } else { + self % (core::f64::consts::PI * 2.0) + } + } +} + +fn is_negative_nonzero_or_nan(f: F) -> bool { + let repr = f.repr(); + if (repr & F::SIGN_MASK) != F::Int::ZERO { + // Negative, so anything other than exactly zero + return (repr & (!F::SIGN_MASK)) != F::Int::ZERO; + } + // NaN + (repr & (F::EXPONENT_MASK | F::SIGNIFICAND_MASK)) > F::EXPONENT_MASK +} + +fn sqrt(f: F) -> F { + if is_negative_nonzero_or_nan(f) { + F::NAN + } else { + f.sqrt() + } +} + +fn ln(f: F) -> F { + if is_negative_nonzero_or_nan(f) { + F::NAN + } else { + f.ln() + } +} + +fn exp(f: F) -> F { + if f.is_nan() { + F::NAN + } else { + f.exp() + } +} + +fn sin(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().sin() + } +} + +fn cos(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().cos() + } +} + +fn tan(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().tan() + } +} + +fn atan2(x: F, y: F) -> F { + if x.is_nan() || y.is_nan() { + F::NAN + } else { + x.to_trig_range().atan2(y) + } +} + +// Name collisions +mod intrinsics { + intrinsics! { + extern "C" fn sqrtf(f: f32) -> f32 { + super::sqrt(f) + } + + #[bootrom_v2] + extern "C" fn sqrt(f: f64) -> f64 { + super::sqrt(f) + } + + extern "C" fn logf(f: f32) -> f32 { + super::ln(f) + } + + #[bootrom_v2] + extern "C" fn log(f: f64) -> f64 { + super::ln(f) + } + + extern "C" fn expf(f: f32) -> f32 { + super::exp(f) + } + + #[bootrom_v2] + extern "C" fn exp(f: f64) -> f64 { + super::exp(f) + } + + #[slower_than_default] + extern "C" fn sinf(f: f32) -> f32 { + super::sin(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn sin(f: f64) -> f64 { + super::sin(f) + } + + #[slower_than_default] + extern "C" fn cosf(f: f32) -> f32 { + super::cos(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn cos(f: f64) -> f64 { + super::cos(f) + } + + #[slower_than_default] + extern "C" fn tanf(f: f32) -> f32 { + super::tan(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn tan(f: f64) -> f64 { + super::tan(f) + } + + // Questionable gain + #[bootrom_v2] + extern "C" fn atan2f(a: f32, b: f32) -> f32 { + super::atan2(a, b) + } + + // Questionable gain + #[bootrom_v2] + extern "C" fn atan2(a: f64, b: f64) -> f64 { + super::atan2(a, b) + } + } +} diff --git a/rp-hal/rp2040-hal/src/float/mod.rs b/rp-hal/rp2040-hal/src/float/mod.rs new file mode 100644 index 0000000..ad5df5f --- /dev/null +++ b/rp-hal/rp2040-hal/src/float/mod.rs @@ -0,0 +1,155 @@ +use core::ops; + +use crate::typelevel::Sealed; + +// Borrowed and simplified from compiler-builtins so we can use bit ops +// on floating point without macro soup. +pub trait Int: + Copy + + core::fmt::Debug + + PartialEq + + PartialOrd + + ops::AddAssign + + ops::SubAssign + + ops::BitAndAssign + + ops::BitOrAssign + + ops::BitXorAssign + + ops::ShlAssign + + ops::ShrAssign + + ops::Add + + ops::Sub + + ops::Div + + ops::Shl + + ops::Shr + + ops::BitOr + + ops::BitXor + + ops::BitAnd + + ops::Not + + Sealed +{ + const ZERO: Self; +} + +macro_rules! int_impl { + ($ty:ty) => { + impl Int for $ty { + const ZERO: Self = 0; + } + + impl Sealed for $ty {} + }; +} + +int_impl!(u32); +int_impl!(u64); + +pub trait Float: + Copy + + core::fmt::Debug + + PartialEq + + PartialOrd + + ops::AddAssign + + ops::MulAssign + + ops::Add + + ops::Sub + + ops::Div + + ops::Rem + + Sealed +{ + /// A uint of the same with as the float + type Int: Int; + + /// NaN representation for the float + const NAN: Self; + + /// The bitwidth of the float type + const BITS: u32; + + /// The bitwidth of the significand + const SIGNIFICAND_BITS: u32; + + /// A mask for the sign bit + const SIGN_MASK: Self::Int; + + /// A mask for the significand + const SIGNIFICAND_MASK: Self::Int; + + /// A mask for the exponent + const EXPONENT_MASK: Self::Int; + + /// Returns `self` transmuted to `Self::Int` + fn repr(self) -> Self::Int; + + /// Returns a `Self::Int` transmuted back to `Self` + fn from_repr(a: Self::Int) -> Self; + + /// Return a sign swapped `self` + fn negate(self) -> Self; + + /// Returns true if `self` is either NaN or infinity + fn is_not_finite(self) -> bool { + (self.repr() & Self::EXPONENT_MASK) == Self::EXPONENT_MASK + } + + /// Returns true if `self` is infinity + #[allow(unused)] + fn is_infinity(self) -> bool { + (self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) == Self::EXPONENT_MASK + } + + /// Returns true if `self is NaN + fn is_nan(self) -> bool { + (self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) > Self::EXPONENT_MASK + } + + /// Returns true if `self` is negative + fn is_sign_negative(self) -> bool { + (self.repr() & Self::SIGN_MASK) != Self::Int::ZERO + } + + /// Returns true if `self` is zero (either sign) + fn is_zero(self) -> bool { + (self.repr() & (Self::SIGNIFICAND_MASK | Self::EXPONENT_MASK)) == Self::Int::ZERO + } +} + +macro_rules! float_impl { + ($ty:ident, $ity:ident, $bits:expr, $significand_bits:expr) => { + impl Float for $ty { + type Int = $ity; + + const NAN: Self = <$ty>::NAN; + + const BITS: u32 = $bits; + const SIGNIFICAND_BITS: u32 = $significand_bits; + + const SIGN_MASK: Self::Int = 1 << (Self::BITS - 1); + const SIGNIFICAND_MASK: Self::Int = (1 << Self::SIGNIFICAND_BITS) - 1; + const EXPONENT_MASK: Self::Int = !(Self::SIGN_MASK | Self::SIGNIFICAND_MASK); + + fn repr(self) -> Self::Int { + self.to_bits() + } + + fn from_repr(a: Self::Int) -> Self { + Self::from_bits(a) + } + + fn negate(self) -> Self { + -self + } + } + + impl Sealed for $ty {} + }; +} + +float_impl!(f32, u32, 32, 23); +float_impl!(f64, u64, 64, 52); + +mod add_sub; +mod cmp; +mod conv; +mod div; +mod functions; +mod mul; diff --git a/rp-hal/rp2040-hal/src/float/mul.rs b/rp-hal/rp2040-hal/src/float/mul.rs new file mode 100644 index 0000000..aab1a87 --- /dev/null +++ b/rp-hal/rp2040-hal/src/float/mul.rs @@ -0,0 +1,67 @@ +use super::Float; +use crate::rom_data; + +trait ROMMul { + fn rom_mul(self, b: Self) -> Self; +} + +impl ROMMul for f32 { + fn rom_mul(self, b: Self) -> Self { + rom_data::float_funcs::fmul(self, b) + } +} + +impl ROMMul for f64 { + fn rom_mul(self, b: Self) -> Self { + rom_data::double_funcs::dmul(self, b) + } +} + +fn mul(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_zero() { + // [-]inf/NaN * 0 = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // [+/-]inf/NaN * (-X) = [-/+]inf/NaN + a.negate() + } else { + // [-]inf/NaN * X = [-]inf/NaN + a + }; + } + + if b.is_not_finite() { + if a.is_zero() { + // 0 * [-]inf/NaN = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // (-X) * [+/-]inf/NaN = [-/+]inf/NaN + b.negate() + } else { + // X * [-]inf/NaN = [-]inf/NaN + b + }; + } + + a.rom_mul(b) +} + +intrinsics! { + #[alias = __mulsf3vfp] + #[aeabi = __aeabi_fmul] + extern "C" fn __mulsf3(a: f32, b: f32) -> f32 { + mul(a, b) + } + + #[bootrom_v2] + #[alias = __muldf3vfp] + #[aeabi = __aeabi_dmul] + extern "C" fn __muldf3(a: f64, b: f64) -> f64 { + mul(a, b) + } +} diff --git a/rp-hal/rp2040-hal/src/gpio/func.rs b/rp-hal/rp2040-hal/src/gpio/func.rs new file mode 100644 index 0000000..1c9f0d9 --- /dev/null +++ b/rp-hal/rp2040-hal/src/gpio/func.rs @@ -0,0 +1,212 @@ +use core::marker::PhantomData; + +use paste::paste; + +use super::pin::DynBankId; + +pub(crate) mod func_sealed { + use super::DynFunction; + + pub trait Function { + fn from(f: DynFunction) -> Self; + fn as_dyn(&self) -> DynFunction; + } +} + +/// Type-level `enum` for pin function. +pub trait Function: func_sealed::Function {} + +/// Describes the function currently assigned to a pin with a dynamic type. +/// +/// A 'pin' on the RP2040 can be connected to different parts of the chip +/// internally - for example, it could be configured as a GPIO pin and connected +/// to the SIO block, or it could be configured as a UART pin and connected to +/// the UART block. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DynFunction { + /// The 'XIP' (or Execute-in-place) function, which means talking to the QSPI Flash. + Xip, + /// The 'SPI' (or serial-peripheral-interface) function. + Spi, + /// The 'UART' (or serial-port) function. + Uart, + /// The 'I2C' (or inter-integrated circuit) function. This is sometimes also called TWI (for + /// two-wire-interface). + I2c, + /// The 'PWM' (or pulse-width-modulation) function. + Pwm, + /// The 'SIO' (or single-cycle input-output) function. This is the function to use for + /// 'manually' controlling the GPIO. + Sio(DynSioConfig), + /// The 'PIO' (or programmable-input-output) function for the PIO0 peripheral block. + Pio0, + /// The 'PIO' (or programmable-input-output) function for the PIO1 peripheral block. + Pio1, + /// The 'Clock' function. This can be used to input or output clock references to or from the + /// rp2040. + Clock, + /// The 'USB' function. Only VBUS detect, VBUS enable and overcurrent detect are configurable. + /// Other USB io have dedicated pins. + Usb, + /// The 'Null' function for unused pins. + Null, +} + +/// Value-level `enum` for SIO configuration. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DynSioConfig { + /// Pin is configured as Input. + Input, + /// Pin is configured as Output. + Output, +} + +impl Function for DynFunction {} +impl func_sealed::Function for DynFunction { + #[inline] + fn from(f: DynFunction) -> Self { + f + } + + #[inline] + fn as_dyn(&self) -> DynFunction { + *self + } +} + +macro_rules! pin_func { + ($($fn:ident $(as $alias:ident)?),*) => { + $(paste! { + /// Type-level `variant` for pin [`Function`]. + #[derive(Debug)] + pub struct [](pub(super) ()); + impl Function for [] {} + impl func_sealed::Function for [] { + #[inline] + fn from(_f: DynFunction) -> Self { + Self(()) + } + #[inline] + fn as_dyn(&self) -> DynFunction { + DynFunction::[<$fn>] + } + } + $( + #[doc = "Alias to [`Function" $fn "`]."] + pub type [] = []; + )? + })* + }; +} +pin_func!(Xip, Spi, Uart, I2c as I2C, Pwm, Pio0, Pio1, Clock, Usb, Null); + +//============================================================================== +// SIO sub-types +//============================================================================== + +/// Type-level `variant` for pin [`Function`]. +#[derive(Debug)] +pub struct FunctionSio(PhantomData); +impl Function for FunctionSio {} +impl func_sealed::Function for FunctionSio { + fn from(_f: DynFunction) -> Self { + FunctionSio(PhantomData) + } + fn as_dyn(&self) -> DynFunction { + DynFunction::Sio(C::DYN) + } +} +/// Alias to [`FunctionSio`]. +pub type FunctionSioInput = FunctionSio; +/// Alias to [`FunctionSio`]. +pub type FunctionSioOutput = FunctionSio; + +/// Type-level `enum` for SIO configuration. +pub trait SioConfig { + #[allow(missing_docs)] + const DYN: DynSioConfig; +} + +/// Type-level `variant` for SIO configuration. +#[derive(Debug)] +pub enum SioInput {} +impl SioConfig for SioInput { + #[allow(missing_docs)] + const DYN: DynSioConfig = DynSioConfig::Input; +} +/// Type-level `variant` for SIO configuration. +#[derive(Debug)] +pub enum SioOutput {} +impl SioConfig for SioOutput { + #[allow(missing_docs)] + const DYN: DynSioConfig = DynSioConfig::Output; +} + +//============================================================================== +// Pin to function mapping +//============================================================================== + +/// Error type for invalid function conversion. +pub struct InvalidFunction; + +/// Marker of valid pin -> function combination. +/// +/// Where `impl ValidFunction for I` reads as `F is a valid function implemented for the pin I`. +pub trait ValidFunction: super::pin::PinId {} + +impl DynFunction { + pub(crate) fn is_valid(&self, id: &P) -> bool { + use DynBankId::*; + use DynFunction::*; + + let dyn_pin = id.as_dyn(); + match (self, dyn_pin.bank, dyn_pin.num) { + (Xip, Bank0, _) => false, + (Clock, _, 0..=19 | 26..=29) => false, + (_, Bank0, 0..=29) => true, + + (Xip | Sio(_), Qspi, 0..=5) => true, + (_, Qspi, 0..=5) => false, + + _ => unreachable!(), + } + } +} +impl ValidFunction for P {} +macro_rules! pin_valid_func { + ($bank:ident as $prefix:ident, [$head:ident $(, $func:ident)*], [$($name:tt),+]) => { + pin_valid_func!($bank as $prefix, [$($func),*], [$($name),+]); + paste::paste!{$( + impl ValidFunction<[]> for super::pin::[<$bank:lower>]::[<$prefix $name>] {} + )+} + }; + ($bank:ident as $prefix:ident, [], [$($name:tt),+]) => {}; +} +pin_valid_func!( + bank0 as Gpio, + [Spi, Uart, I2c, Pwm, Pio0, Pio1, Usb, Null], + [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29 + ] +); +pin_valid_func!(bank0 as Gpio, [Clock], [20, 21, 22, 23, 24, 25]); +pin_valid_func!(qspi as Qspi, [Xip, Null], [Sclk, Sd0, Sd1, Sd2, Sd3, Ss]); + +macro_rules! pin_valid_func_sio { + ($bank:ident as $prefix:ident, [$($name:tt),+]) => { + paste::paste!{$( + impl ValidFunction> for super::pin::[<$bank:lower>]::[<$prefix $name>] {} + )+} + }; +} +pin_valid_func_sio!( + bank0 as Gpio, + [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29 + ] +); +pin_valid_func_sio!(qspi as Qspi, [Sclk, Sd0, Sd1, Sd2, Sd3, Ss]); diff --git a/rp-hal/rp2040-hal/src/gpio/mod.rs b/rp-hal/rp2040-hal/src/gpio/mod.rs new file mode 100644 index 0000000..74b4f7a --- /dev/null +++ b/rp-hal/rp2040-hal/src/gpio/mod.rs @@ -0,0 +1,1590 @@ +//! General Purpose Input and Output (GPIO) +//! +//! ## Basic usage +//! ```no_run +//! use embedded_hal::digital::{InputPin, OutputPin}; +//! use rp2040_hal::{clocks::init_clocks_and_plls, gpio::Pins, watchdog::Watchdog, pac, Sio}; +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! let mut clocks = init_clocks_and_plls(XOSC_CRYSTAL_FREQ, peripherals.XOSC, peripherals.CLOCKS, peripherals.PLL_SYS, peripherals.PLL_USB, &mut peripherals.RESETS, &mut watchdog).ok().unwrap(); +//! +//! let mut pac = pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(pac.SIO); +//! let pins = rp2040_hal::gpio::Pins::new(pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut pac.RESETS); +//! // Set a pin to drive output +//! let mut output_pin = pins.gpio25.into_push_pull_output(); +//! // Drive output to 3.3V +//! output_pin.set_high().unwrap(); +//! // Drive output to 0V +//! output_pin.set_low().unwrap(); +//! // Set a pin to input +//! let mut input_pin = pins.gpio24.into_floating_input(); +//! // pinstate will be true if the pin is above 2V +//! let pinstate = input_pin.is_high().unwrap(); +//! // pinstate_low will be true if the pin is below 1.15V +//! let pinstate_low = input_pin.is_low().unwrap(); +//! // you'll want to pull-up or pull-down a switch if it's not done externally +//! let button_pin = pins.gpio23.into_pull_down_input(); +//! let button2_pin = pins.gpio22.into_pull_up_input(); +//! ``` +//! See [examples/gpio_in_out.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal-examples/src/bin/gpio_in_out.rs) for a more practical example + +// Design Notes: +// +// - The user must not be able to instantiate by themselves nor obtain an instance of the Type-level +// structure. +// - non-typestated features (overrides, irq configuration, pads' output disable, pad's input +// enable, drive strength, schmitt, slew rate, sio's in sync bypass) are considered somewhat +// advanced usage of the pin (relative to reading/writing a gpio) and it is the responsibility of +// the user to make sure these are in a correct state when converting and passing the pin around. + +pub use embedded_hal::digital::PinState; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + pac, + sio::Sio, + typelevel::{self, Sealed}, +}; + +mod func; +pub(crate) mod pin; +mod pin_group; +mod pull; + +pub use func::*; +pub use pin::{DynBankId, DynPinId, PinId}; +pub use pin_group::PinGroup; +pub use pull::*; + +/// The amount of current that a pin can drive when used as an output. +#[allow(clippy::enum_variant_names)] +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum OutputDriveStrength { + /// 2 mA + TwoMilliAmps, + /// 4 mA + FourMilliAmps, + /// 8 mA + EightMilliAmps, + /// 12 mA + TwelveMilliAmps, +} + +/// The slew rate of a pin when used as an output. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum OutputSlewRate { + /// Slew slow + Slow, + /// Slew fast + Fast, +} + +/// Interrupt kind. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum Interrupt { + /// While low + LevelLow, + /// While high + LevelHigh, + /// On falling edge + EdgeLow, + /// On rising edge + EdgeHigh, +} +impl Interrupt { + fn mask(&self) -> u32 { + match self { + Interrupt::LevelLow => 0b0001, + Interrupt::LevelHigh => 0b0010, + Interrupt::EdgeLow => 0b0100, + Interrupt::EdgeHigh => 0b1000, + } + } +} + +/// Interrupt override state. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum InterruptOverride { + /// Don't invert the interrupt. + Normal = 0, + /// Invert the interrupt. + Invert = 1, + /// Drive interrupt low. + AlwaysLow = 2, + /// Drive interrupt high. + AlwaysHigh = 3, +} + +/// Input override state. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum InputOverride { + /// Don't invert the peripheral input. + Normal = 0, + /// Invert the peripheral input. + Invert = 1, + /// Drive peripheral input low. + AlwaysLow = 2, + /// Drive peripheral input high. + AlwaysHigh = 3, +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +/// Output enable override state. +pub enum OutputEnableOverride { + /// Use the original output enable signal from selected peripheral. + Normal = 0, + /// Invert the output enable signal from selected peripheral. + Invert = 1, + /// Disable output. + Disable = 2, + /// Enable output. + Enable = 3, +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +/// Output override state. +pub enum OutputOverride { + /// Use the original output signal from selected peripheral. + DontInvert = 0, + /// Invert the output signal from selected peripheral. + Invert = 1, + /// Drive output low. + AlwaysLow = 2, + /// Drive output high. + AlwaysHigh = 3, +} + +/// Represents a pin, with a given ID (e.g. Gpio3), a given function (e.g. FunctionUart) and a given pull type +/// (e.g. pull-down). +#[derive(Debug)] +pub struct Pin { + id: I, + function: F, + pull_type: P, +} + +/// Create a new pin instance. +/// +/// # Safety +/// The uniqueness of the pin is not verified. User must make sure no other instance of that specific +/// pin exists at the same time. +pub unsafe fn new_pin(id: DynPinId) -> Pin { + use pac::io_bank0::gpio::gpio_ctrl::FUNCSEL_A; + use pin::pin_sealed::PinIdOps; + + let funcsel = id + .io_ctrl() + .read() + .funcsel() + .variant() + .expect("Invalid funcsel read from register."); + let function = match funcsel { + FUNCSEL_A::JTAG => DynFunction::Xip, + FUNCSEL_A::SPI => DynFunction::Spi, + FUNCSEL_A::UART => DynFunction::Uart, + FUNCSEL_A::I2C => DynFunction::I2c, + FUNCSEL_A::PWM => DynFunction::Pwm, + FUNCSEL_A::SIO => { + let mask = id.mask(); + let cfg = if id.sio_oe().read().bits() & mask == mask { + DynSioConfig::Output + } else { + DynSioConfig::Input + }; + DynFunction::Sio(cfg) + } + FUNCSEL_A::PIO0 => DynFunction::Pio0, + FUNCSEL_A::PIO1 => DynFunction::Pio1, + FUNCSEL_A::CLOCK => DynFunction::Clock, + FUNCSEL_A::USB => DynFunction::Usb, + FUNCSEL_A::NULL => DynFunction::Null, + }; + let pad = id.pad_ctrl().read(); + let pull_type = match (pad.pue().bit_is_set(), pad.pde().bit_is_set()) { + (true, true) => DynPullType::BusKeep, + (true, false) => DynPullType::Up, + (false, true) => DynPullType::Down, + (false, false) => DynPullType::None, + }; + + Pin { + id, + function, + pull_type, + } +} + +impl Pin { + /// Pin ID. + pub fn id(&self) -> DynPinId { + self.id.as_dyn() + } + + /// # Safety + /// This method does not check if the pin is actually configured as the target function or pull + /// mode. This may lead to inconsistencies between the type-state and the actual state of the + /// pin's configuration. + pub unsafe fn into_unchecked(self) -> Pin { + Pin { + id: self.id, + function: F2::from(self.function.as_dyn()), + pull_type: P2::from(self.pull_type.as_dyn()), + } + } + + /// Convert the pin from one state to the other. + pub fn reconfigure(self) -> Pin + where + F2: func::Function, + P2: PullType, + I: func::ValidFunction, + { + self.into_function().into_pull_type() + } + + /// Convert the pin function. + #[deprecated( + note = "Misleading name `mode` when it changes the `function`. Please use `into_function` instead.", + since = "0.9.0" + )] + pub fn into_mode(self) -> Pin + where + F2: func::Function, + I: func::ValidFunction, + { + self.into_function() + } + + /// Convert the pin function. + pub fn into_function(self) -> Pin + where + F2: func::Function, + I: func::ValidFunction, + { + // Thanks to type-level validation, we know F2 is valid for I + let prev_function = self.function.as_dyn(); + let function = F2::from(prev_function); + let new_function = function.as_dyn(); + + if prev_function != new_function { + pin::set_function(&self.id, new_function); + } + + Pin { + function, + id: self.id, + pull_type: self.pull_type, + } + } + + /// Convert the pin pull type. + pub fn into_pull_type(self) -> Pin { + let prev_pull_type = self.pull_type.as_dyn(); + let pull_type = M2::from(prev_pull_type); + let new_pull_type = pull_type.as_dyn(); + + if prev_pull_type != new_pull_type { + pin::set_pull_type(&self.id, new_pull_type); + } + + Pin { + pull_type, + id: self.id, + function: self.function, + } + } + + /// Erase the Pin ID type check. + pub fn into_dyn_pin(self) -> Pin { + Pin { + id: self.id.as_dyn(), + function: self.function, + pull_type: self.pull_type, + } + } + + /// Get the pin's pull type. + pub fn pull_type(&self) -> DynPullType { + self.pull_type.as_dyn() + } + + //============================================================================== + // Typical pin conversions. + //============================================================================== + + /// Disable the pin and set it to float + #[inline] + pub fn into_floating_disabled(self) -> Pin + where + I: ValidFunction, + { + self.reconfigure() + } + + /// Disable the pin and set it to pull down + #[inline] + pub fn into_pull_down_disabled(self) -> Pin + where + I: ValidFunction, + { + self.reconfigure() + } + + /// Disable the pin and set it to pull up + #[inline] + pub fn into_pull_up_disabled(self) -> Pin + where + I: ValidFunction, + { + self.reconfigure() + } + + /// Configure the pin to operate as a floating input + #[inline] + pub fn into_floating_input(self) -> Pin, PullNone> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a pulled down input + #[inline] + pub fn into_pull_down_input(self) -> Pin, PullDown> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a pulled up input + #[inline] + pub fn into_pull_up_input(self) -> Pin, PullUp> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a bus keep input + #[inline] + pub fn into_bus_keep_input(self) -> Pin, PullBusKeep> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a push-pull output. + /// + /// If you want to specify the initial pin state, use [`Pin::into_push_pull_output_in_state`]. + #[inline] + pub fn into_push_pull_output(self) -> Pin, P> + where + I: ValidFunction>, + { + self.into_function() + } + + /// Configure the pin to operate as a push-pull output, specifying an initial + /// state which is applied immediately. + #[inline] + pub fn into_push_pull_output_in_state( + mut self, + state: PinState, + ) -> Pin, P> + where + I: ValidFunction>, + { + match state { + PinState::High => self._set_high(), + PinState::Low => self._set_low(), + } + self.into_push_pull_output() + } + + /// Configure the pin to operate as a readable push pull output. + /// + /// If you want to specify the initial pin state, use [`Pin::into_readable_output_in_state`]. + #[inline] + #[deprecated( + note = "All gpio are readable, use `.into_push_pull_output()` instead.", + since = "0.9.0" + )] + pub fn into_readable_output(self) -> Pin, P> + where + I: ValidFunction>, + { + self.into_function() + } + + /// Configure the pin to operate as a readable push pull output, specifying an initial + /// state which is applied immediately. + #[inline] + #[deprecated( + note = "All gpio are readable, use `.into_push_pull_output_in_state()` instead.", + since = "0.9.0" + )] + pub fn into_readable_output_in_state(self, state: PinState) -> Pin, P> + where + I: ValidFunction>, + { + self.into_push_pull_output_in_state(state) + } + + //============================================================================== + // methods available for all pins. + //============================================================================== + + // ======================= + // Pad related methods + + /// Get the current drive strength of the pin. + #[inline] + pub fn get_drive_strength(&self) -> OutputDriveStrength { + use pac::pads_bank0::gpio::DRIVE_A; + match self.id.pad_ctrl().read().drive().variant() { + DRIVE_A::_2M_A => OutputDriveStrength::TwoMilliAmps, + DRIVE_A::_4M_A => OutputDriveStrength::FourMilliAmps, + DRIVE_A::_8M_A => OutputDriveStrength::EightMilliAmps, + DRIVE_A::_12M_A => OutputDriveStrength::TwelveMilliAmps, + } + } + + /// Set the drive strength for the pin. + #[inline] + pub fn set_drive_strength(&mut self, strength: OutputDriveStrength) { + use pac::pads_bank0::gpio::DRIVE_A; + let variant = match strength { + OutputDriveStrength::TwoMilliAmps => DRIVE_A::_2M_A, + OutputDriveStrength::FourMilliAmps => DRIVE_A::_4M_A, + OutputDriveStrength::EightMilliAmps => DRIVE_A::_8M_A, + OutputDriveStrength::TwelveMilliAmps => DRIVE_A::_12M_A, + }; + self.id.pad_ctrl().modify(|_, w| w.drive().variant(variant)) + } + + /// Get the slew rate for the pin. + #[inline] + pub fn get_slew_rate(&self) -> OutputSlewRate { + if self.id.pad_ctrl().read().slewfast().bit_is_set() { + OutputSlewRate::Fast + } else { + OutputSlewRate::Slow + } + } + + /// Set the slew rate for the pin. + #[inline] + pub fn set_slew_rate(&mut self, rate: OutputSlewRate) { + self.id + .pad_ctrl() + .modify(|_, w| w.slewfast().bit(OutputSlewRate::Fast == rate)); + } + + /// Get whether the schmitt trigger (hysteresis) is enabled. + #[inline] + pub fn get_schmitt_enabled(&self) -> bool { + self.id.pad_ctrl().read().schmitt().bit_is_set() + } + + /// Enable/Disable the schmitt trigger. + #[inline] + pub fn set_schmitt_enabled(&self, enable: bool) { + self.id.pad_ctrl().modify(|_, w| w.schmitt().bit(enable)); + } + + /// Get the state of the digital output circuitry of the pad. + #[inline] + pub fn get_output_disable(&mut self) -> bool { + self.id.pad_ctrl().read().od().bit_is_set() + } + + /// Set the digital output circuitry of the pad. + #[inline] + pub fn set_output_disable(&mut self, disable: bool) { + self.id.pad_ctrl().modify(|_, w| w.od().bit(disable)); + } + + /// Get the state of the digital input circuitry of the pad. + #[inline] + pub fn get_input_enable(&mut self) -> bool { + self.id.pad_ctrl().read().ie().bit_is_set() + } + + /// Set the digital input circuitry of the pad. + #[inline] + pub fn set_input_enable(&mut self, enable: bool) { + self.id.pad_ctrl().modify(|_, w| w.ie().bit(enable)); + } + + // ======================= + // IO related methods + + /// Get the input override. + #[inline] + pub fn get_input_override(&self) -> InputOverride { + use pac::io_bank0::gpio::gpio_ctrl::INOVER_A; + match self.id.io_ctrl().read().inover().variant() { + INOVER_A::NORMAL => InputOverride::Normal, + INOVER_A::INVERT => InputOverride::Invert, + INOVER_A::LOW => InputOverride::AlwaysLow, + INOVER_A::HIGH => InputOverride::AlwaysHigh, + } + } + + /// Set the input override. + #[inline] + pub fn set_input_override(&mut self, override_value: InputOverride) { + use pac::io_bank0::gpio::gpio_ctrl::INOVER_A; + let variant = match override_value { + InputOverride::Normal => INOVER_A::NORMAL, + InputOverride::Invert => INOVER_A::INVERT, + InputOverride::AlwaysLow => INOVER_A::LOW, + InputOverride::AlwaysHigh => INOVER_A::HIGH, + }; + self.id.io_ctrl().modify(|_, w| w.inover().variant(variant)); + } + + /// Get the output enable override. + #[inline] + pub fn get_output_enable_override(&self) -> OutputEnableOverride { + use pac::io_bank0::gpio::gpio_ctrl::OEOVER_A; + match self.id.io_ctrl().read().oeover().variant() { + OEOVER_A::NORMAL => OutputEnableOverride::Normal, + OEOVER_A::INVERT => OutputEnableOverride::Invert, + OEOVER_A::DISABLE => OutputEnableOverride::Disable, + OEOVER_A::ENABLE => OutputEnableOverride::Enable, + } + } + + /// Set the output enable override. + #[inline] + pub fn set_output_enable_override(&mut self, override_value: OutputEnableOverride) { + use pac::io_bank0::gpio::gpio_ctrl::OEOVER_A; + let variant = match override_value { + OutputEnableOverride::Normal => OEOVER_A::NORMAL, + OutputEnableOverride::Invert => OEOVER_A::INVERT, + OutputEnableOverride::Disable => OEOVER_A::DISABLE, + OutputEnableOverride::Enable => OEOVER_A::ENABLE, + }; + self.id.io_ctrl().modify(|_, w| w.oeover().variant(variant)); + } + + /// Get the output override. + #[inline] + pub fn get_output_override(&self) -> OutputOverride { + use pac::io_bank0::gpio::gpio_ctrl::OUTOVER_A; + match self.id.io_ctrl().read().outover().variant() { + OUTOVER_A::NORMAL => OutputOverride::DontInvert, + OUTOVER_A::INVERT => OutputOverride::Invert, + OUTOVER_A::LOW => OutputOverride::AlwaysLow, + OUTOVER_A::HIGH => OutputOverride::AlwaysHigh, + } + } + + /// Set the output override. + #[inline] + pub fn set_output_override(&mut self, override_value: OutputOverride) { + use pac::io_bank0::gpio::gpio_ctrl::OUTOVER_A; + let variant = match override_value { + OutputOverride::DontInvert => OUTOVER_A::NORMAL, + OutputOverride::Invert => OUTOVER_A::INVERT, + OutputOverride::AlwaysLow => OUTOVER_A::LOW, + OutputOverride::AlwaysHigh => OUTOVER_A::HIGH, + }; + self.id + .io_ctrl() + .modify(|_, w| w.outover().variant(variant)); + } + + /// Get the interrupt override. + #[inline] + pub fn get_interrupt_override(&self) -> InterruptOverride { + use pac::io_bank0::gpio::gpio_ctrl::IRQOVER_A; + match self.id.io_ctrl().read().irqover().variant() { + IRQOVER_A::NORMAL => InterruptOverride::Normal, + IRQOVER_A::INVERT => InterruptOverride::Invert, + IRQOVER_A::LOW => InterruptOverride::AlwaysLow, + IRQOVER_A::HIGH => InterruptOverride::AlwaysHigh, + } + } + + /// Set the interrupt override. + #[inline] + pub fn set_interrupt_override(&mut self, override_value: InterruptOverride) { + use pac::io_bank0::gpio::gpio_ctrl::IRQOVER_A; + let variant = match override_value { + InterruptOverride::Normal => IRQOVER_A::NORMAL, + InterruptOverride::Invert => IRQOVER_A::INVERT, + InterruptOverride::AlwaysLow => IRQOVER_A::LOW, + InterruptOverride::AlwaysHigh => IRQOVER_A::HIGH, + }; + self.id + .io_ctrl() + .modify(|_, w| w.irqover().variant(variant)); + } + + // ======================= + // SIO related methods + + #[inline] + #[allow(clippy::bool_comparison)] // more explicit this way + pub(crate) fn _is_low(&self) -> bool { + let mask = self.id.mask(); + self.id.sio_in().read().bits() & mask == 0 + } + + #[inline] + #[allow(clippy::bool_comparison)] // more explicit this way + pub(crate) fn _is_high(&self) -> bool { + !self._is_low() + } + + #[inline] + pub(crate) fn _set_low(&mut self) { + let mask = self.id.mask(); + self.id + .sio_out_clr() + .write(|w| unsafe { w.gpio_out_clr().bits(mask) }); + } + + #[inline] + pub(crate) fn _set_high(&mut self) { + let mask = self.id.mask(); + self.id + .sio_out_set() + .write(|w| unsafe { w.gpio_out_set().bits(mask) }); + } + + #[inline] + pub(crate) fn _toggle(&mut self) { + let mask = self.id.mask(); + self.id + .sio_out_xor() + .write(|w| unsafe { w.gpio_out_xor().bits(mask) }); + } + + #[inline] + pub(crate) fn _is_set_low(&self) -> bool { + let mask = self.id.mask(); + self.id.sio_out().read().bits() & mask == 0 + } + + #[inline] + pub(crate) fn _is_set_high(&self) -> bool { + !self._is_set_low() + } + + // ======================= + // Interrupt related methods + + /// Clear interrupt. + #[inline] + pub fn clear_interrupt(&mut self, interrupt: Interrupt) { + let (reg, offset) = self.id.intr(); + let mask = interrupt.mask(); + reg.write(|w| unsafe { w.bits(mask << offset) }); + } + + /// Interrupt status. + #[inline] + pub fn interrupt_status(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.proc_ints(Sio::core()); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Is interrupt enabled. + #[inline] + pub fn is_interrupt_enabled(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.proc_inte(Sio::core()); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Enable or disable interrupt. + #[inline] + pub fn set_interrupt_enabled(&self, interrupt: Interrupt, enabled: bool) { + let (reg, offset) = self.id.proc_inte(Sio::core()); + let mask = interrupt.mask(); + unsafe { + if enabled { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Is interrupt forced. + #[inline] + pub fn is_interrupt_forced(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.proc_intf(Sio::core()); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Force or release interrupt. + #[inline] + pub fn set_interrupt_forced(&self, interrupt: Interrupt, forced: bool) { + let (reg, offset) = self.id.proc_intf(Sio::core()); + let mask = interrupt.mask(); + unsafe { + if forced { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Dormant wake status. + #[inline] + pub fn dormant_wake_status(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.dormant_wake_ints(); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Is dormant wake enabled. + #[inline] + pub fn is_dormant_wake_enabled(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.dormant_wake_inte(); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Enable or disable dormant wake. + #[inline] + pub fn set_dormant_wake_enabled(&self, interrupt: Interrupt, enabled: bool) { + let (reg, offset) = self.id.dormant_wake_inte(); + let mask = interrupt.mask(); + unsafe { + if enabled { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Is dormant wake forced. + #[inline] + pub fn is_dormant_wake_forced(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.dormant_wake_intf(); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Force dormant wake. + #[inline] + pub fn set_dormant_wake_forced(&mut self, interrupt: Interrupt, forced: bool) { + let (reg, offset) = self.id.dormant_wake_intf(); + let mask = interrupt.mask(); + unsafe { + if forced { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Return a wrapper that implements InputPin. + /// + /// This allows to read from the pin independent of the selected function. + /// Depending on the pad configuration, reading from the pin may not return a + /// meaningful result. + /// + /// Calling this function does not set the pad's input enable bit. + pub fn as_input(&self) -> AsInputPin { + AsInputPin(self) + } +} +impl Pin, P> { + /// Is bypass enabled + #[inline] + pub fn is_sync_bypass(&self) -> bool { + let mask = self.id.mask(); + self.id.proc_in_by_pass().read().bits() & mask == mask + } + + /// Bypass the input sync stages. + /// + /// This saves two clock cycles in the input signal's path at the risks of introducing metastability. + #[inline] + pub fn set_sync_bypass(&mut self, bypass: bool) { + let mask = self.id.mask(); + let reg = self.id.proc_in_by_pass(); + unsafe { + if bypass { + write_bitmask_set(reg.as_ptr(), mask); + } else { + write_bitmask_clear(reg.as_ptr(), mask); + } + } + } +} +impl Pin { + /// Try to return to a type-checked pin id. + /// + /// This method may fail if the target pin id differs from the current dynamic one. + pub fn try_into_pin(self) -> Result, Self> { + if P2::ID == self.id { + Ok(Pin { + id: P2::new(), + function: self.function, + pull_type: self.pull_type, + }) + } else { + Err(self) + } + } + + /// Try to change the pin's function. + pub fn try_into_function(self) -> Result, Pin> + where + F2: func::Function, + { + // Thanks to type-level validation, we know F2 is valid for I + let prev_function = self.function.as_dyn(); + let function = F2::from(prev_function); + let function_as_dyn = function.as_dyn(); + + use func_sealed::Function; + if function_as_dyn.is_valid(&self.id) { + if function_as_dyn != prev_function.as_dyn() { + pin::set_function(&self.id, function_as_dyn); + } + Ok(Pin { + function, + id: self.id, + pull_type: self.pull_type, + }) + } else { + Err(self) + } + } +} +impl Pin { + /// Try to set the pin's function. + /// + /// This method may fail if the requested function is not supported by the pin, eg `FunctionXiP` + /// on a gpio from `Bank0`. + pub fn try_set_function(&mut self, function: DynFunction) -> Result<(), func::InvalidFunction> { + use func_sealed::Function; + if !function.is_valid(&self.id) { + return Err(func::InvalidFunction); + } else if function != self.function.as_dyn() { + pin::set_function(&self.id, function); + self.function = function; + } + Ok(()) + } + + /// Gets the pin's function. + pub fn function(&self) -> DynFunction { + use func_sealed::Function; + self.function.as_dyn() + } +} + +impl Pin { + /// Set the pin's pull type. + pub fn set_pull_type(&mut self, pull_type: DynPullType) { + if pull_type != self.pull_type { + pin::set_pull_type(&self.id, pull_type); + self.pull_type = pull_type; + } + } +} + +/// Wrapper providing input pin functions for GPIO pins independent of the configured mode. +pub struct AsInputPin<'a, I: PinId, F: func::Function, P: PullType>(&'a Pin); + +//============================================================================== +// Embedded-HAL +//============================================================================== + +/// GPIO error type. +pub type Error = core::convert::Infallible; + +impl embedded_hal_0_2::digital::v2::OutputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn set_low(&mut self) -> Result<(), Self::Error> { + self._set_low(); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self._set_high(); + Ok(()) + } +} + +/// Deprecated: Instead of implicitly implementing InputPin for function SioOutput, +/// use `pin.as_input()` to get access to input values independent of the selected function. +impl embedded_hal_0_2::digital::v2::InputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn is_high(&self) -> Result { + Ok(self._is_high()) + } + + fn is_low(&self) -> Result { + Ok(self._is_low()) + } +} + +impl embedded_hal_0_2::digital::v2::InputPin + for AsInputPin<'_, I, F, P> +{ + type Error = core::convert::Infallible; + + fn is_high(&self) -> Result { + Ok(self.0._is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.0._is_low()) + } +} + +impl embedded_hal_0_2::digital::v2::StatefulOutputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + fn is_set_high(&self) -> Result { + Ok(self._is_set_high()) + } + + fn is_set_low(&self) -> Result { + Ok(self._is_set_low()) + } +} + +impl embedded_hal_0_2::digital::v2::ToggleableOutputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn toggle(&mut self) -> Result<(), Self::Error> { + self._toggle(); + Ok(()) + } +} +impl embedded_hal_0_2::digital::v2::InputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn is_high(&self) -> Result { + Ok(self._is_high()) + } + + fn is_low(&self) -> Result { + Ok(self._is_low()) + } +} + +//============================================================================== +// Pins +//============================================================================== + +/// Default type state of a pin after reset of the pads, io and sio. +pub trait DefaultTypeState: crate::typelevel::Sealed { + /// Default function. + type Function: Function; + /// Default pull type. + type PullType: PullType; +} + +// Clear input enable for pins of bank0. +// Pins 26-29 are ADC pins. If the pins are connected to an analog input, +// the signal level may not be valid for a digital input. Therefore, input +// should be disabled by default. +// For the other GPIO pins, the same setting is applied for consistency. +macro_rules! reset_ie { + ( Bank0, $pads:ident ) => { + for id in (0..=29) { + $pads.gpio(id).modify(|_, w| w.ie().clear_bit()); + } + }; + ( Qspi, $pads:ident ) => {}; +} + +macro_rules! gpio { + ( $bank:ident:$prefix:ident, [ $(($id:expr, $pull_type:ident, $func:ident)),* ] ) => { + paste::paste!{ + #[doc = "Pin bank " [<$bank>] ] + pub mod [<$bank:snake>] { + use $crate::pac::{[],[]}; + use crate::sio::[]; + use super::{Pin, pin, pull, func}; + $(pub use super::pin::[<$bank:lower>]::[<$prefix $id>];)* + + $( + impl super::DefaultTypeState for [<$prefix $id>] { + type Function = super::[]; + type PullType = super::[]; + } + )* + gpio!(struct: $bank $prefix $([<$prefix $id>], $id, $func, $pull_type),*); + + impl Pins { + /// Take ownership of the PAC peripherals and SIO slice and split it into discrete [`Pin`]s + /// + /// This clears the input-enable flag for all Bank0 pads. + pub fn new(io : [], pads: [], sio: [], reset : &mut $crate::pac::RESETS) -> Self { + use $crate::resets::SubsystemReset; + pads.reset_bring_down(reset); + io.reset_bring_down(reset); + + { + use $crate::gpio::pin::DynBankId; + // SAFETY: this function owns the whole bank that will be affected. + let sio = unsafe { &*$crate::pac::SIO::PTR }; + if DynBankId::$bank == DynBankId::Bank0 { + sio.gpio_oe().reset(); + sio.gpio_out().reset(); + } else { + sio.gpio_hi_oe().reset(); + sio.gpio_hi_out().reset(); + } + } + + io.reset_bring_up(reset); + pads.reset_bring_up(reset); + reset_ie!($bank, pads); + gpio!(members: io, pads, sio, $(([<$prefix $id>], $func, $pull_type)),+) + } + } + } + } + }; + (struct: $bank:ident $prefix:ident $($PXi:ident, $id:expr, $func:ident, $pull_type:ident),*) => { + paste::paste!{ + /// Collection of all the individual [`Pin`]s + #[derive(Debug)] + pub struct Pins { + _io: [], + _pads: [], + _sio: [], + $( + #[doc = "Pin " [<$PXi>] ] + pub [<$PXi:snake>]: Pin]::[<$prefix $id>] , func::[], pull::[]>, + )* + } + } + }; + (members: $io:ident, $pads:ident, $sio:ident, $(($PXi:ident, $func:ident, $pull_type:ident)),+) => { + paste::paste!{ + Self { + _io: $io, + _pads: $pads, + _sio: $sio, + $( + [<$PXi:snake>]: Pin { + id: [<$PXi>] (()), + function: func::[] (()), + pull_type: pull::[] (()) + }, + )+ + } + } + }; +} + +gpio!( + Bank0: Gpio, + [ + (0, Down, Null), + (1, Down, Null), + (2, Down, Null), + (3, Down, Null), + (4, Down, Null), + (5, Down, Null), + (6, Down, Null), + (7, Down, Null), + (8, Down, Null), + (9, Down, Null), + (10, Down, Null), + (11, Down, Null), + (12, Down, Null), + (13, Down, Null), + (14, Down, Null), + (15, Down, Null), + (16, Down, Null), + (17, Down, Null), + (18, Down, Null), + (19, Down, Null), + (20, Down, Null), + (21, Down, Null), + (22, Down, Null), + (23, Down, Null), + (24, Down, Null), + (25, Down, Null), + (26, Down, Null), + (27, Down, Null), + (28, Down, Null), + (29, Down, Null) + ] +); + +gpio!( + Qspi: Qspi, + [ + (Sclk, Down, Null), + (Ss, Up, Null), + (Sd0, None, Null), + (Sd1, None, Null), + (Sd2, None, Null), + (Sd3, None, Null) + ] +); + +pub use bank0::Pins; + +//============================================================================== +// AnyPin +//============================================================================== + +/// Type class for [`Pin`] types. +/// +/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for +/// [`Pin`] types. See the `AnyKind` documentation for more details on the +/// pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +/// [type class]: crate::typelevel#type-classes +pub trait AnyPin: Sealed +where + Self: typelevel::Sealed, + Self: typelevel::Is>, +{ + /// [`PinId`] of the corresponding [`Pin`] + type Id: PinId; + /// [`func::Function`] of the corresponding [`Pin`] + type Function: func::Function; + /// [`PullType`] of the corresponding [`Pin`] + type Pull: PullType; +} + +impl Sealed for Pin +where + I: PinId, + F: func::Function, + P: PullType, +{ +} + +impl AnyPin for Pin +where + I: PinId, + F: func::Function, + P: PullType, +{ + type Id = I; + type Function = F; + type Pull = P; +} + +/// Type alias to recover the specific [`Pin`] type from an implementation of [`AnyPin`]. +/// +/// See the [`AnyKind`] documentation for more details on the pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +pub type SpecificPin

= Pin<

::Id,

::Function,

::Pull>; + +//============================================================================== +// bsp_pins helper macro +//============================================================================== + +/// Helper macro to give meaningful names to GPIO pins +/// +/// The normal [`Pins`] struct names each [`Pin`] according to its [`PinId`]. +/// However, BSP authors would prefer to name each [`Pin`] according to its +/// function. This macro defines a new `Pins` struct with custom field names +/// for each [`Pin`]. +/// +/// # Example +/// Calling the macro like this: +/// ```rust +/// use rp2040_hal::bsp_pins; +/// bsp_pins! { +/// #[cfg(feature = "gpio")] +/// Gpio0 { +/// /// Doc gpio0 +/// name: gpio0, +/// aliases: { FunctionPio0, PullNone: PioPin } +/// }, +/// Gpio1 { +/// name: led, +/// aliases: { FunctionPwm, PullDown: LedPwm } +/// }, +/// } +/// ``` +/// +/// Is roughly equivalent to the following source code (excluding the docs strings below): +/// ``` +/// use ::rp2040_hal as hal; +/// use hal::gpio; +/// pub struct Pins { +/// /// Doc gpio0 +/// #[cfg(feature = "gpio")] +/// pub gpio0: gpio::Pin< +/// gpio::bank0::Gpio0, +/// ::Function, +/// ::PullType, +/// >, +/// pub led: gpio::Pin< +/// gpio::bank0::Gpio1, +/// ::Function, +/// ::PullType, +/// >, +/// } +/// impl Pins { +/// #[inline] +/// pub fn new( +/// io: hal::pac::IO_BANK0, +/// pads: hal::pac::PADS_BANK0, +/// sio: hal::sio::SioGpioBank0, +/// reset: &mut hal::pac::RESETS, +/// ) -> Self { +/// let mut pins = gpio::Pins::new(io, pads, sio, reset); +/// Self { +/// #[cfg(feature = "gpio")] +/// gpio0: pins.gpio0, +/// led: pins.gpio1, +/// } +/// } +/// } +/// pub type PioPin = gpio::Pin; +/// pub type LedPwm = gpio::Pin; +/// ``` +#[macro_export] +macro_rules! bsp_pins { + ( + $( + $( #[$id_cfg:meta] )* + $Id:ident { + $( #[$name_doc:meta] )* + name: $name:ident $(,)? + $( + aliases: { + $( + $( #[$alias_cfg:meta] )* + $Function:ty, $PullType:ident: $Alias:ident + ),+ + } + )? + } $(,)? + )+ + ) => { + $crate::paste::paste! { + + /// BSP replacement for the HAL + /// [`Pins`](rp2040_hal::gpio::Pins) type + /// + /// This type is intended to provide more meaningful names for the + /// given pins. + /// + /// To enable specific functions of the pins you can use the + /// [rp2040_hal::gpio::pin::Pin::into_function] function with + /// one of: + /// - [rp2040_hal::gpio::FunctionI2C] + /// - [rp2040_hal::gpio::FunctionPwm] + /// - [rp2040_hal::gpio::FunctionSpi] + /// - [rp2040_hal::gpio::FunctionXip] + /// - [rp2040_hal::gpio::FunctionPio0] + /// - [rp2040_hal::gpio::FunctionPio1] + /// - [rp2040_hal::gpio::FunctionUart] + /// + /// like this: + ///```no_run + /// use rp2040_hal::{pac, gpio::{bank0::Gpio12, Pin, Pins}, sio::Sio}; + /// + /// let mut peripherals = pac::Peripherals::take().unwrap(); + /// let sio = Sio::new(peripherals.SIO); + /// let pins = Pins::new(peripherals.IO_BANK0,peripherals.PADS_BANK0,sio.gpio_bank0, &mut peripherals.RESETS); + /// + /// let _spi_sclk = pins.gpio2.into_function::(); + /// let _spi_mosi = pins.gpio3.into_function::(); + /// let _spi_miso = pins.gpio4.into_function::(); + ///``` + /// + /// **See also [rp2040_hal::gpio] for more in depth information about this**! + pub struct Pins { + $( + $( #[$id_cfg] )* + $( #[$name_doc] )* + pub $name: $crate::gpio::Pin< + $crate::gpio::bank0::$Id, + <$crate::gpio::bank0::$Id as $crate::gpio::DefaultTypeState>::Function, + <$crate::gpio::bank0::$Id as $crate::gpio::DefaultTypeState>::PullType, + >, + )+ + } + + impl Pins { + /// Take ownership of the PAC [`PORT`] and split it into + /// discrete [`Pin`]s. + /// + /// This struct serves as a replacement for the HAL [`Pins`] + /// struct. It is intended to provide more meaningful names for + /// each [`Pin`] in a BSP. Any [`Pin`] not defined by the BSP is + /// dropped. + /// + /// [`Pin`](rp2040_hal::gpio::Pin) + /// [`Pins`](rp2040_hal::gpio::Pins) + #[inline] + pub fn new(io : $crate::pac::IO_BANK0, pads: $crate::pac::PADS_BANK0, sio: $crate::sio::SioGpioBank0, reset : &mut $crate::pac::RESETS) -> Self { + let mut pins = $crate::gpio::Pins::new(io,pads,sio,reset); + Self { + $( + $( #[$id_cfg] )* + $name: pins.[<$Id:lower>], + )+ + } + } + } + $( + $( #[$id_cfg] )* + $crate::bsp_pins!(@aliases, $( $( $( #[$alias_cfg] )* $Id $Function $PullType $Alias )+ )? ); + )+ + } + }; + ( @aliases, $( $( $( #[$attr:meta] )* $Id:ident $Function:ident $PullType:ident $Alias:ident )+ )? ) => { + $crate::paste::paste! { + $( + $( + $( #[$attr] )* + /// Alias for a configured [`Pin`](rp2040_hal::gpio::Pin) + pub type $Alias = $crate::gpio::Pin< + $crate::gpio::bank0::$Id, + $crate::gpio::$Function, + $crate::gpio::$PullType + >; + )+ + )? + } + }; +} + +//============================================================================== +// InOutPin +//============================================================================== + +/// A wrapper [`AnyPin`]`` emulating open-drain function. +/// +/// This wrapper implements both InputPin and OutputPin, to simulate an open-drain pin as needed for +/// example by the wire protocol the DHT11 sensor speaks. +/// +/// +pub struct InOutPin { + inner: Pin, +} + +impl InOutPin { + /// Create a new wrapper + pub fn new(inner: T) -> InOutPin + where + T::Id: ValidFunction, + { + let mut inner = inner.into(); + inner.set_output_enable_override(OutputEnableOverride::Disable); + + // into Pin<_, FunctionSioOutput, _> + let inner = inner.into_push_pull_output_in_state(PinState::Low); + + Self { inner } + } +} + +impl InOutPin +where + T: AnyPin, + T::Id: ValidFunction, +{ + /// Releases the pin reverting to its previous function. + pub fn release(self) -> T { + // restore the previous typestate first + let mut inner = self.inner.reconfigure(); + // disable override + inner.set_output_enable_override(OutputEnableOverride::Normal); + // typelevel-return + T::from(inner) + } +} + +impl embedded_hal_0_2::digital::v2::InputPin for InOutPin { + type Error = Error; + fn is_high(&self) -> Result { + self.inner.is_high() + } + + fn is_low(&self) -> Result { + self.inner.is_low() + } +} + +impl embedded_hal_0_2::digital::v2::OutputPin for InOutPin { + type Error = Error; + fn set_low(&mut self) -> Result<(), Error> { + // The pin is already set to output low but this is inhibited by the override. + self.inner + .set_output_enable_override(OutputEnableOverride::Enable); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Error> { + // To set the open-drain pin to high, just disable the output driver by configuring the + // output override. That way, the DHT11 can still pull the data line down to send its response. + self.inner + .set_output_enable_override(OutputEnableOverride::Disable); + Ok(()) + } +} + +mod eh1 { + use embedded_hal::digital::{ErrorType, InputPin, OutputPin, StatefulOutputPin}; + + use super::{ + func, AnyPin, AsInputPin, Error, FunctionSio, InOutPin, OutputEnableOverride, Pin, PinId, + PullType, SioConfig, SioInput, SioOutput, + }; + + impl ErrorType for Pin, P> + where + I: PinId, + P: PullType, + S: SioConfig, + { + type Error = Error; + } + + impl OutputPin for Pin, P> + where + I: PinId, + P: PullType, + { + fn set_low(&mut self) -> Result<(), Self::Error> { + self._set_low(); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self._set_high(); + Ok(()) + } + } + + impl StatefulOutputPin for Pin, P> + where + I: PinId, + P: PullType, + { + fn is_set_high(&mut self) -> Result { + Ok(self._is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok(self._is_set_low()) + } + + fn toggle(&mut self) -> Result<(), Self::Error> { + self._toggle(); + Ok(()) + } + } + + impl InputPin for Pin, P> + where + I: PinId, + P: PullType, + { + fn is_high(&mut self) -> Result { + Ok(self._is_high()) + } + + fn is_low(&mut self) -> Result { + Ok(self._is_low()) + } + } + + impl ErrorType for AsInputPin<'_, I, F, P> + where + I: PinId, + F: func::Function, + P: PullType, + { + type Error = Error; + } + + impl InputPin for AsInputPin<'_, I, F, P> { + fn is_high(&mut self) -> Result { + Ok(self.0._is_high()) + } + + fn is_low(&mut self) -> Result { + Ok(self.0._is_low()) + } + } + + impl ErrorType for InOutPin + where + I: AnyPin, + { + type Error = Error; + } + + impl OutputPin for InOutPin + where + I: AnyPin, + { + fn set_low(&mut self) -> Result<(), Self::Error> { + // The pin is already set to output low but this is inhibited by the override. + self.inner + .set_output_enable_override(OutputEnableOverride::Enable); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + // To set the open-drain pin to high, just disable the output driver by configuring the + // output override. That way, the DHT11 can still pull the data line down to send its response. + self.inner + .set_output_enable_override(OutputEnableOverride::Disable); + Ok(()) + } + } + + impl InputPin for InOutPin + where + I: AnyPin, + { + fn is_high(&mut self) -> Result { + Ok(self.inner._is_high()) + } + + fn is_low(&mut self) -> Result { + Ok(self.inner._is_low()) + } + } +} diff --git a/rp-hal/rp2040-hal/src/gpio/pin.rs b/rp-hal/rp2040-hal/src/gpio/pin.rs new file mode 100644 index 0000000..bcdb898 --- /dev/null +++ b/rp-hal/rp2040-hal/src/gpio/pin.rs @@ -0,0 +1,175 @@ +//! ## Note 1 +//! +//! QSPI registers are ordered differently on pads, io & SIO: +//! - PADS: sclk, sd0, sd1, sd2, sd3, ss +//! - IO bank: sclk, ss, sd0, sd1, sd2, sd3 +//! - SIO: sclk, ss, sd0, sd1, sd2, sd3 +//! +//! This HAL will use the order shared by IO bank & SIO. The main reason for that being the bit +//! shift operation used in SIO and interrupt related registers. +//! +//! ## Note 2 +//! +//! The SWD and SWCLK pin only appear on the pad control and cannot be used as gpio. +//! They are therefore absent from this implementation. +//! +//! ## Note 3 +//! +//! Dues to limitations in svd2rust and svdtools (and their shared dependencies) it is not possible +//! to fully express the relations between the gpio registers in the different banks on the RP2040 +//! at the PAC level. +//! +//! These limitations are respectively: +//! - Inability to derive register with different reset values +//! - Inability to derive from path including clusters and/or arrays +//! +//! This modules bridges that gap by adding a trait definition per register type and implementing it +//! for each of the relevant registers. + +use crate::typelevel::Sealed; + +use super::{DynFunction, DynPullType}; + +pub(crate) mod pin_sealed; + +/// Value-level `enum` for the pin's bank. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum DynBankId { + /// GPIO Pins bank + Bank0, + /// QSPI Pins bank + Qspi, +} + +/// Type-level `enum` for the pin's bank ID. +pub trait BankId: Sealed {} + +/// Type-level `variant` of `BankId` +pub struct BankBank0; +impl Sealed for BankBank0 {} +impl BankId for BankBank0 {} + +/// Type-level `variant` of `BankId` +pub struct BankQspi; +impl Sealed for BankQspi {} +impl BankId for BankQspi {} + +/// Type-level `enum` for the pin Id (pin number + bank). +pub trait PinId: pin_sealed::PinIdOps { + /// This pin as a `DynPinId`. + fn as_dyn(&self) -> DynPinId; +} + +/// Value-level representation for the pin (bank + id). +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct DynPinId { + /// Pin bank. + pub bank: DynBankId, + /// Pin number. + pub num: u8, +} +impl PinId for DynPinId { + #[inline] + fn as_dyn(&self) -> DynPinId { + *self + } +} + +macro_rules! pin_ids { + ($bank:ident: $($id:expr;$name:ident),*) => { + pin_ids!($bank as $bank: $($id;$name),*); + }; + ($bank:ident as $prefix:ident: $($id:tt),*) => { + pin_ids!($bank as $prefix: $($id;$id),*); + }; + ($bank:ident as $prefix:ident: $($id:expr;$name:tt),*) => { + paste::paste!{ + $( + #[doc = "Type level variant for the pin `" $name "` in bank `" $prefix "`."] + #[derive(Debug)] + pub struct [<$prefix $name>] (pub(crate) ()); + impl crate::typelevel::Sealed for [<$prefix $name>] {} + impl PinId for [<$prefix $name>] { + #[inline] + fn as_dyn(&self) -> DynPinId { + DynPinId { + bank: DynBankId::$bank, + num: $id + } + } + } + impl pin_sealed::TypeLevelPinId for [<$prefix $name>] { + type Bank = []; + + const ID: DynPinId = DynPinId { + bank: DynBankId::$bank, + num: $id + }; + + fn new() -> Self { + Self(()) + } + } + )* + } + }; +} +/// Bank of all the GPIOs. +pub mod bank0 { + use super::{pin_sealed, BankBank0, DynBankId, DynPinId, PinId}; + pin_ids!(Bank0 as Gpio: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29); +} +/// Bank of the QSPI related pins. +pub mod qspi { + use super::{pin_sealed, BankQspi, DynBankId, DynPinId, PinId}; + pin_ids!(Qspi: 0;Sclk, 1;Ss, 2;Sd0, 3;Sd1, 4;Sd2, 5;Sd3); +} + +pub(crate) fn set_function(pin: &P, function: DynFunction) { + use crate::pac::io_bank0::gpio::gpio_ctrl::FUNCSEL_A; + let funcsel = match function { + DynFunction::Xip => FUNCSEL_A::JTAG, + DynFunction::Spi => FUNCSEL_A::SPI, + DynFunction::Uart => FUNCSEL_A::UART, + DynFunction::I2c => FUNCSEL_A::I2C, + DynFunction::Pwm => FUNCSEL_A::PWM, + DynFunction::Sio(sio) => { + let mask = pin.mask(); + match sio { + crate::gpio::DynSioConfig::Input => { + pin.sio_oe_clr().write(|w| unsafe { w.bits(mask) }); + } + crate::gpio::DynSioConfig::Output => { + pin.sio_oe_set().write(|w| unsafe { w.bits(mask) }); + } + } + + FUNCSEL_A::SIO + } + DynFunction::Pio0 => FUNCSEL_A::PIO0, + DynFunction::Pio1 => FUNCSEL_A::PIO1, + DynFunction::Clock => FUNCSEL_A::CLOCK, + DynFunction::Usb => FUNCSEL_A::USB, + DynFunction::Null => FUNCSEL_A::NULL, + }; + if funcsel != FUNCSEL_A::NULL { + pin.pad_ctrl().modify(|_, w| w.ie().set_bit()); + } else { + pin.pad_ctrl().modify(|_, w| w.ie().clear_bit()); + } + + pin.io_ctrl().modify(|_, w| w.funcsel().variant(funcsel)); +} +pub(crate) fn set_pull_type(pin: &P, pull_type: DynPullType) { + let (pue, pde) = match pull_type { + DynPullType::None => (false, false), + DynPullType::Up => (true, false), + DynPullType::Down => (false, true), + DynPullType::BusKeep => (true, true), + }; + + pin.pad_ctrl() + .modify(|_, w| w.pue().bit(pue).pde().bit(pde)); +} diff --git a/rp-hal/rp2040-hal/src/gpio/pin/pin_sealed.rs b/rp-hal/rp2040-hal/src/gpio/pin/pin_sealed.rs new file mode 100644 index 0000000..ce0109c --- /dev/null +++ b/rp-hal/rp2040-hal/src/gpio/pin/pin_sealed.rs @@ -0,0 +1,219 @@ +use super::{DynBankId, DynPinId}; +use crate::{pac, sio::CoreId}; + +pub trait TypeLevelPinId: super::PinId { + type Bank: super::BankId; + + const ID: DynPinId; + + fn new() -> Self; +} + +pub trait PinIdOps { + fn mask(&self) -> u32; + fn io_status(&self) -> &pac::io_bank0::gpio::GPIO_STATUS; + fn io_ctrl(&self) -> &pac::io_bank0::gpio::GPIO_CTRL; + fn pad_ctrl(&self) -> &pac::pads_bank0::GPIO; + + fn sio_in(&self) -> &pac::sio::GPIO_IN; + fn sio_out(&self) -> &pac::sio::GPIO_OUT; + fn sio_out_set(&self) -> &pac::sio::GPIO_OUT_SET; + fn sio_out_clr(&self) -> &pac::sio::GPIO_OUT_CLR; + fn sio_out_xor(&self) -> &pac::sio::GPIO_OUT_XOR; + fn sio_oe(&self) -> &pac::sio::GPIO_OE; + fn sio_oe_set(&self) -> &pac::sio::GPIO_OE_SET; + fn sio_oe_clr(&self) -> &pac::sio::GPIO_OE_CLR; + fn sio_oe_xor(&self) -> &pac::sio::GPIO_OE_XOR; + fn proc_in_by_pass(&self) -> &pac::syscfg::PROC_IN_SYNC_BYPASS; + + fn intr(&self) -> (&pac::io_bank0::INTR, usize); + fn proc_ints(&self, proc: CoreId) -> (&pac::io_bank0::PROC0_INTS, usize); + fn proc_inte(&self, proc: CoreId) -> (&pac::io_bank0::PROC0_INTE, usize); + fn proc_intf(&self, proc: CoreId) -> (&pac::io_bank0::PROC0_INTF, usize); + fn dormant_wake_ints(&self) -> (&pac::io_bank0::DORMANT_WAKE_INTS, usize); + fn dormant_wake_inte(&self) -> (&pac::io_bank0::DORMANT_WAKE_INTE, usize); + fn dormant_wake_intf(&self) -> (&pac::io_bank0::DORMANT_WAKE_INTF, usize); +} + +macro_rules! accessor_fns { + (sio $reg:ident) => { + paste::paste! { + fn [](&self) -> &$crate::pac::sio::[] { + let pin = self.as_dyn(); + unsafe { + let sio = &*$crate::pac::SIO::PTR; + match pin.bank { + DynBankId::Bank0 => sio.[](), + DynBankId::Qspi => core::mem::transmute::<&$crate::pac::sio::[],&$crate::pac::sio::[]>(sio.[]()), + } + } + } + } + }; + (io $reg:ident) => { + paste::paste! { + fn [](&self) -> &$crate::pac::io_bank0::gpio::[] { + let pin = self.as_dyn(); + match pin.bank { + DynBankId::Bank0 => { + let gpio = unsafe { &*$crate::pac::IO_BANK0::PTR }; + gpio.gpio(usize::from(pin.num)).[]() + } + DynBankId::Qspi => unsafe { + let qspi = &*$crate::pac::IO_QSPI::PTR; + core::mem::transmute::<&$crate::pac::io_qspi::gpio_qspi::[], &$crate::pac::io_bank0::gpio::[]>(qspi.gpio_qspi(usize::from(pin.num)).[]()) + }, + } + } + } + }; + (int $reg:ident) => { + paste::paste! { + fn [](&self, proc: CoreId) -> (&$crate::pac::io_bank0::[], usize) { + let pin = self.as_dyn(); + let (index, offset) = (pin.num / 8, pin.num % 8 * 4); + unsafe { + let reg = match pin.bank { + DynBankId::Bank0 => { + let bank = &*$crate::pac::IO_BANK0::PTR; + match proc { + CoreId::Core0 => bank.[](usize::from(index)), + CoreId::Core1 => core::mem::transmute::<&$crate::pac::io_bank0::[], &$crate::pac::io_bank0::[]>(bank.[](usize::from(index))), + } + } + DynBankId::Qspi => { + let bank = &*$crate::pac::IO_QSPI::PTR; + match proc { + CoreId::Core0 => core::mem::transmute::<&$crate::pac::io_qspi::[], &$crate::pac::io_bank0::[]>(bank.[]()), + CoreId::Core1 => core::mem::transmute::<&$crate::pac::io_qspi::[], &$crate::pac::io_bank0::[]>(bank.[]()), + } + } + }; + (reg, usize::from(offset)) + } + } + } + }; + (dormant $reg:ident) => { + paste::paste! { + fn [< dormant_wake_ $reg:lower>](&self) -> (&$crate::pac::io_bank0::[< DORMANT_WAKE_ $reg:upper >], usize) { + let pin = self.as_dyn(); + let (index, offset) = (pin.num / 8, pin.num % 8 * 4); + unsafe { + let reg = match pin.bank { + DynBankId::Bank0 => { + let bank = &*$crate::pac::IO_BANK0::PTR; + bank.[< dormant_wake_ $reg:lower>](usize::from(index)) + } + DynBankId::Qspi => { + let bank = &*$crate::pac::IO_QSPI::PTR; + core::mem::transmute::<&$crate::pac::io_qspi::[< DORMANT_WAKE_ $reg:upper >], &$crate::pac::io_bank0::[< DORMANT_WAKE_ $reg:upper >]>(bank.[< dormant_wake_ $reg:lower>]()) + } + }; + (reg, usize::from(offset)) + } + } + } + }; +} +impl PinIdOps for T +where + T: super::PinId, +{ + fn mask(&self) -> u32 { + 1 << self.as_dyn().num + } + fn pad_ctrl(&self) -> &pac::pads_bank0::GPIO { + let pin = self.as_dyn(); + match pin.bank { + DynBankId::Bank0 => { + let gpio = unsafe { &*pac::PADS_BANK0::PTR }; + gpio.gpio(usize::from(pin.num)) + } + DynBankId::Qspi => unsafe { + let qspi = &*pac::PADS_QSPI::PTR; + use rp2040_pac::{generic::Reg, pads_bank0, pads_qspi}; + match pin.num { + 0 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sclk()), + 1 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_ss()), + 2 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd0()), + 3 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd1()), + 4 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd2()), + 5 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd3()), + _ => unreachable!("Invalid QSPI bank pin number."), + } + }, + } + } + accessor_fns!(io ctrl); + accessor_fns!(io status); + + accessor_fns!(sio in); + accessor_fns!(sio out); + accessor_fns!(sio out_set); + accessor_fns!(sio out_clr); + accessor_fns!(sio out_xor); + accessor_fns!(sio oe); + accessor_fns!(sio oe_set); + accessor_fns!(sio oe_clr); + accessor_fns!(sio oe_xor); + + fn proc_in_by_pass(&self) -> &pac::syscfg::PROC_IN_SYNC_BYPASS { + let pin = self.as_dyn(); + unsafe { + let syscfg = &*pac::SYSCFG::PTR; + match pin.bank { + DynBankId::Bank0 => syscfg.proc_in_sync_bypass(), + DynBankId::Qspi => core::mem::transmute::< + &pac::syscfg::PROC_IN_SYNC_BYPASS_HI, + &pac::syscfg::PROC_IN_SYNC_BYPASS, + >(syscfg.proc_in_sync_bypass_hi()), + } + } + } + + fn intr(&self) -> (&pac::io_bank0::INTR, usize) { + let pin = self.as_dyn(); + let (index, offset) = (pin.num / 8, pin.num % 8 * 4); + unsafe { + let reg = match pin.bank { + DynBankId::Bank0 => { + let bank = &*pac::IO_BANK0::PTR; + bank.intr(usize::from(index)) + } + DynBankId::Qspi => { + let bank = &*pac::IO_QSPI::PTR; + core::mem::transmute::<&pac::io_qspi::INTR, &pac::io_bank0::INTR>(bank.intr()) + } + }; + + (reg, usize::from(offset)) + } + } + + accessor_fns!(int ints); + accessor_fns!(int inte); + accessor_fns!(int intf); + + accessor_fns!(dormant ints); + accessor_fns!(dormant inte); + accessor_fns!(dormant intf); +} diff --git a/rp-hal/rp2040-hal/src/gpio/pin_group.rs b/rp-hal/rp2040-hal/src/gpio/pin_group.rs new file mode 100644 index 0000000..88ff881 --- /dev/null +++ b/rp-hal/rp2040-hal/src/gpio/pin_group.rs @@ -0,0 +1,209 @@ +//! Pin Groups +//! +//! Lets you set multiple GPIOs simultaneously. + +use embedded_hal::digital::PinState; +use frunk::{hlist::Plucker, HCons, HNil}; + +use crate::typelevel::Sealed; + +use super::{ + pin::pin_sealed::TypeLevelPinId, AnyPin, FunctionSio, FunctionSioInput, FunctionSioOutput, Pin, + PinId, PullType, SioConfig, +}; + +/// Generate a read mask for a pin list. +pub trait ReadPinHList: Sealed { + /// Generate a mask for a pin list. + fn read_mask(&self) -> u32; +} +impl ReadPinHList for HNil { + fn read_mask(&self) -> u32 { + 0 + } +} +impl ReadPinHList for HCons { + fn read_mask(&self) -> u32 { + (1 << self.head.borrow().id().num) | self.tail.read_mask() + } +} + +/// Generate a write mask for a pin list. +pub trait WritePinHList: Sealed { + /// Generate a mask for a pin list. + fn write_mask(&self) -> u32; +} +impl WritePinHList for HNil { + fn write_mask(&self) -> u32 { + 0 + } +} +impl WritePinHList + for HCons, T> +{ + fn write_mask(&self) -> u32 { + // This is an input pin, so don't include it in write_mask + self.tail.write_mask() + } +} +impl WritePinHList + for HCons, T> +{ + fn write_mask(&self) -> u32 { + (1 << self.head.id().num) | self.tail.write_mask() + } +} + +/// A group of pins to be controlled together and guaranty single cycle control of several pins. +/// +/// ```no_run +/// # macro_rules! defmt { ($($a:tt)*) => {}} +/// use rp2040_hal::{pac, gpio::{bank0::Gpio12, Pin, Pins, PinState, PinGroup}, sio::Sio}; +/// +/// let mut peripherals = pac::Peripherals::take().unwrap(); +/// let sio = Sio::new(peripherals.SIO); +/// let pins = Pins::new(peripherals.IO_BANK0,peripherals.PADS_BANK0,sio.gpio_bank0, &mut peripherals.RESETS); +/// +/// let group = PinGroup::new(); +/// let group = group.add_pin(pins.gpio0.into_pull_up_input()); +/// let mut group = group.add_pin(pins.gpio4.into_push_pull_output_in_state(PinState::High)); +/// +/// defmt!("Group's state is: {}", group.read()); +/// group.toggle(); +/// defmt!("Group's state is: {}", group.read()); +/// ``` +pub struct PinGroup(T); +impl PinGroup { + /// Creates an empty pin group. + pub fn new() -> Self { + PinGroup(HNil) + } + + /// Add a pin to the group. + pub fn add_pin(self, pin: P) -> PinGroup> + where + C: SioConfig, + P: AnyPin>, + P::Id: TypeLevelPinId, + { + PinGroup(HCons { + head: pin, + tail: self.0, + }) + } +} +impl PinGroup> +where + H::Id: TypeLevelPinId, + H: AnyPin, +{ + /// Add a pin to the group. + pub fn add_pin(self, pin: P) -> PinGroup>> + where + C: SioConfig, + P: AnyPin>, + P::Id: TypeLevelPinId::Bank>, + { + PinGroup(HCons { + head: pin, + tail: self.0, + }) + } + + /// Pluck a pin from the group. + #[allow(clippy::type_complexity)] + pub fn remove_pin( + self, + ) -> (P, PinGroup< as Plucker>::Remainder>) + where + HCons: Plucker, + { + let (p, rest): (P, _) = self.0.pluck(); + (p, PinGroup(rest)) + } +} +impl PinGroup> +where + HCons: ReadPinHList + WritePinHList, + H: AnyPin, +{ + /// Read the whole group at once. + /// + /// The returned value is a bit field where each pin populates its own index. Therefore, there + /// might be "holes" in the value. Unoccupied bits will always read as 0. + /// + /// For example, if the group contains Gpio1 and Gpio3, a read may yield: + /// ```text + /// 0b0000_0000__0000_0000__0000_0000__0000_1010 + /// This is Gpio3 ↑↑↑ + /// Gpio2 is not used || + /// This is Gpio1 | + /// ``` + pub fn read(&self) -> u32 { + let mask = self.0.read_mask(); + crate::sio::Sio::read_bank0() & mask + } + + /// Write this set of pins all at the same time. + /// + /// This only affects output pins. Input pins in the + /// set are ignored. + pub fn set(&mut self, state: PinState) { + use super::pin::pin_sealed::PinIdOps; + let mask = self.0.write_mask(); + let head_id = self.0.head.borrow().id(); + if state == PinState::Low { + head_id.sio_out_clr().write(|w| unsafe { w.bits(mask) }); + } else { + head_id.sio_out_set().write(|w| unsafe { w.bits(mask) }); + } + } + + /// Set this set of pins to the state given in a single operation. + /// + /// The state passed in must be a mask where each bit corresponds to a gpio. + /// + /// For example, if the group contains Gpio1 and Gpio3, a read may yield: + /// ```text + /// 0b0000_0000__0000_0000__0000_0000__0000_1010 + /// This is Gpio3 ↑↑↑ + /// Gpio2 is not used || + /// This is Gpio1 | + /// ``` + /// + /// State corresponding to bins not in this group are ignored. + pub fn set_u32(&mut self, state: u32) { + use super::pin::pin_sealed::PinIdOps; + let mask = self.0.write_mask(); + let state_masked = mask & state; + let head_id = self.0.head.borrow().id(); + // UNSAFE: this register is 32bit wide and all bits are valid. + // The value set is masked + head_id.sio_out().modify(|r, w| unsafe { + // clear all bit part of this group + let cleared = r.bits() & !mask; + // set bits according to state + w.bits(cleared | state_masked) + }); + } + + /// Toggles this set of pins all at the same time. + /// + /// This only affects output pins. Input pins in the + /// set are ignored. + pub fn toggle(&mut self) { + use super::pin::pin_sealed::PinIdOps; + let mask = self.0.write_mask(); + self.0 + .head + .borrow() + .id() + .sio_out_xor() + .write(|w| unsafe { w.bits(mask) }); + } +} +impl Default for PinGroup { + fn default() -> Self { + Self::new() + } +} diff --git a/rp-hal/rp2040-hal/src/gpio/pull.rs b/rp-hal/rp2040-hal/src/gpio/pull.rs new file mode 100644 index 0000000..d98e0f8 --- /dev/null +++ b/rp-hal/rp2040-hal/src/gpio/pull.rs @@ -0,0 +1,64 @@ +use paste::paste; + +pub(crate) mod pull_sealed { + use super::DynPullType; + + pub trait PullType { + fn from(pm: DynPullType) -> Self; + fn as_dyn(&self) -> DynPullType; + } +} +/// Type-level `enum` for pull resistor types. +pub trait PullType: pull_sealed::PullType {} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +/// Value-level `enum` for pull resistor types. +pub enum DynPullType { + /// No pull enabled. + None, + /// Enable pull up. + Up, + /// Enable pull down. + Down, + /// This enables bus-keep mode. + /// + /// This is not documented in the datasheet but described in the + /// [c-sdk](https://github.com/raspberrypi/pico-sdk/blob/e7267f99febc70486923e17a8210088af058c915/src/rp2_common/hardware_gpio/gpio.c#L53) + /// as: + /// + /// > […] on RP2040, setting both pulls enables a "bus keep" function, + /// > i.e. weak pull to whatever is current high/low state of GPIO. + BusKeep, +} + +impl PullType for DynPullType {} +impl pull_sealed::PullType for DynPullType { + fn from(pm: DynPullType) -> Self { + pm + } + + fn as_dyn(&self) -> DynPullType { + *self + } +} + +macro_rules! pin_pull_type { + ($($pull_type:ident),*) => { + $(paste! { + /// Type-level `variant` of [`PullType`]. + #[derive(Debug)] + pub struct [](pub(super) ()); + impl PullType for [] {} + impl pull_sealed::PullType for [] { + fn from(_pm: DynPullType) -> Self { + Self(()) + } + fn as_dyn(&self) -> DynPullType { + DynPullType::[<$pull_type>] + } + } + })* + }; +} +pin_pull_type!(None, Up, Down, BusKeep); diff --git a/rp-hal/rp2040-hal/src/i2c.rs b/rp-hal/rp2040-hal/src/i2c.rs new file mode 100644 index 0000000..19c8232 --- /dev/null +++ b/rp-hal/rp2040-hal/src/i2c.rs @@ -0,0 +1,410 @@ +//! Inter-Integrated Circuit (I2C) bus +//! +//! See [Chapter 4 Section 3](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details +//! +//! ## Usage +//! ```no_run +//! use fugit::RateExtU32; +//! use rp2040_hal::{i2c::I2C, gpio::Pins, pac, Sio}; +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS); +//! +//! let mut i2c = I2C::i2c1( +//! peripherals.I2C1, +//! pins.gpio18.reconfigure(), // sda +//! pins.gpio19.reconfigure(), // scl +//! 400.kHz(), +//! &mut peripherals.RESETS, +//! 125_000_000.Hz(), +//! ); +//! +//! // Scan for devices on the bus by attempting to read from them +//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_Read; +//! for i in 0..=127u8 { +//! let mut readbuf: [u8; 1] = [0; 1]; +//! let result = i2c.read(i, &mut readbuf); +//! if let Ok(d) = result { +//! // Do whatever work you want to do with found devices +//! // writeln!(uart, "Device found at address{:?}", i).unwrap(); +//! } +//! } +//! +//! // Write some data to a device at 0x2c +//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_Write; +//! i2c.write(0x2Cu8, &[1, 2, 3]).unwrap(); +//! +//! // Write and then read from a device at 0x3a +//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_WriteRead; +//! let mut readbuf: [u8; 1] = [0; 1]; +//! i2c.write_read(0x2Cu8, &[1, 2, 3], &mut readbuf).unwrap(); +//! ``` +//! +//! See [examples/i2c.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal-examples/src/bin/i2c.rs) +//! for a complete example +//! +//! ## Async Usage +//! +//! See [examples/i2c_async.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal-examples/src/bin/i2c_async.rs) +//! and [examples/i2c_async_irq.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal-examples/src/bin/i2c_async_irq.rs) +//! for complete examples. + +use core::{marker::PhantomData, ops::Deref}; +use fugit::HertzU32; +use rp2040_pac::i2c0::ic_con::IC_10BITADDR_SLAVE_A; + +use crate::{ + gpio::{bank0::*, pin::pin_sealed::TypeLevelPinId, AnyPin, FunctionI2c, PullUp}, + pac::{ + i2c0::{ic_con::IC_10BITADDR_MASTER_A, RegisterBlock}, + I2C0, I2C1, RESETS, + }, + resets::SubsystemReset, + typelevel::Sealed, +}; + +mod controller; +pub mod peripheral; + +/// Pac I2C device +pub trait I2cDevice: Deref + SubsystemReset + Sealed { + /// Index of the peripheral. + const ID: usize; +} +impl Sealed for I2C0 {} +impl I2cDevice for I2C0 { + const ID: usize = 0; +} +impl Sealed for I2C1 {} +impl I2cDevice for I2C1 { + const ID: usize = 1; +} + +/// Marks valid/supported address types +pub trait ValidAddress: + Into + embedded_hal::i2c::AddressMode + embedded_hal_0_2::blocking::i2c::AddressMode + Copy +{ + /// Variant for the IC_CON.10bitaddr_master field + const BIT_ADDR_M: IC_10BITADDR_MASTER_A; + /// Variant for the IC_CON.10bitaddr_slave field + const BIT_ADDR_S: IC_10BITADDR_SLAVE_A; + + /// Validates the address against address ranges supported by the hardware. + fn is_valid(self) -> Result<(), Error>; +} +impl ValidAddress for u8 { + const BIT_ADDR_M: IC_10BITADDR_MASTER_A = IC_10BITADDR_MASTER_A::ADDR_7BITS; + const BIT_ADDR_S: IC_10BITADDR_SLAVE_A = IC_10BITADDR_SLAVE_A::ADDR_7BITS; + + fn is_valid(self) -> Result<(), Error> { + if self >= 0x80 { + Err(Error::AddressOutOfRange(self.into())) + } else { + Ok(()) + } + } +} +impl ValidAddress for u16 { + const BIT_ADDR_M: IC_10BITADDR_MASTER_A = IC_10BITADDR_MASTER_A::ADDR_10BITS; + const BIT_ADDR_S: IC_10BITADDR_SLAVE_A = IC_10BITADDR_SLAVE_A::ADDR_10BITS; + + fn is_valid(self) -> Result<(), Error> { + Ok(()) + } +} + +/// I2C error +#[non_exhaustive] +pub enum Error { + /// I2C abort with error + Abort(u32), + /// User passed in a read buffer that was 0 length + /// + /// This is a limitation of the RP2040 I2C peripheral. + /// If the slave ACKs its address, the I2C peripheral must read + /// at least one byte before sending the STOP condition. + InvalidReadBufferLength, + /// User passed in a write buffer that was 0 length + /// + /// This is a limitation of the RP2040 I2C peripheral. + /// If the slave ACKs its address, the I2C peripheral must write + /// at least one byte before sending the STOP condition. + InvalidWriteBufferLength, + /// Target i2c address is out of range + AddressOutOfRange(u16), + /// Target i2c address is reserved + AddressReserved(u16), +} + +impl core::fmt::Debug for Error { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use embedded_hal::i2c::Error as _; + match self { + Error::InvalidReadBufferLength => write!(fmt, "InvalidReadBufferLength"), + Error::InvalidWriteBufferLength => write!(fmt, "InvalidWriteBufferLength"), + Error::AddressOutOfRange(addr) => write!(fmt, "AddressOutOfRange({:x})", addr), + Error::AddressReserved(addr) => write!(fmt, "AddressReserved({:x})", addr), + Error::Abort(_) => { + write!(fmt, "{:?}", self.kind()) + } + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Error { + fn format(&self, fmt: defmt::Formatter) { + use embedded_hal::i2c::Error as _; + match self { + Error::InvalidReadBufferLength => defmt::write!(fmt, "InvalidReadBufferLength"), + Error::InvalidWriteBufferLength => defmt::write!(fmt, "InvalidWriteBufferLength"), + Error::AddressOutOfRange(addr) => defmt::write!(fmt, "AddressOutOfRange({:x})", addr), + Error::AddressReserved(addr) => defmt::write!(fmt, "AddressReserved({:x})", addr), + Error::Abort(_) => { + defmt::write!(fmt, "{}", defmt::Debug2Format(&self.kind())) + } + } + } +} + +impl embedded_hal::i2c::Error for Error { + fn kind(&self) -> embedded_hal::i2c::ErrorKind { + match &self { + Error::Abort(v) if v & 1<<12 != 0 // ARB_LOST + => embedded_hal::i2c::ErrorKind::ArbitrationLoss, + Error::Abort(v) if v & 1<<7 != 0 // ABRT_SBYTE_ACKDET + => embedded_hal::i2c::ErrorKind::Bus, + Error::Abort(v) if v & 1<<6 != 0 // ABRT_HS_ACKDET + => embedded_hal::i2c::ErrorKind::Bus, + Error::Abort(v) if v & 1<<4 != 0 // ABRT_GCALL_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + Error::Abort(v) if v & 1<<3 != 0 // ABRT_TXDATA_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Data), + Error::Abort(v) if v & 1<<2 != 0 // ABRT_10ADDR2_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + Error::Abort(v) if v & 1<<1 != 0 // ABRT_10ADDR1_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + Error::Abort(v) if v & 1<<0 != 0 // ABRT_7B_ADDR_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + _ => embedded_hal::i2c::ErrorKind::Other, + } + } +} + +macro_rules! pin_validation { + ($p:ident) => { + paste::paste!{ + #[doc = "Marker for PinId that can serve as " $p] + pub trait []: Sealed {} + + #[doc = "Valid " $p] + pub trait []: Sealed {} + + impl [] for T + where + T: AnyPin, + T::Id: [], + { + } + + #[doc = "A runtime validated " $p " pin for I2C."] + pub struct [](P, PhantomData); + impl Sealed for [] {} + impl [] for [] {} + impl [] + where + P: AnyPin, + S: I2cDevice, + { + /// Validate a pin's function on a i2c peripheral. + /// + #[doc = "Will err if the pin cannot be used as a " $p " pin for that I2C."] + pub fn validate(p: P, _u: &S) -> Result { + if [<$p:upper>].contains(&(p.borrow().id().num, S::ID)) && + p.borrow().id().bank == crate::gpio::DynBankId::Bank0 { + Ok(Self(p, PhantomData)) + } else { + Err(p) + } + } + } + } + }; + ($($p:ident),*) => { + $( + pin_validation!($p); + )* + }; +} + +pin_validation!(Scl, Sda); + +macro_rules! valid_pins { + ($($i2c:ident: { + sda: [$($sda:ident),*], + scl: [$($scl:ident),*] + }),*) => { + $( + $(impl ValidPinIdSda<$i2c> for $sda {})* + $(impl ValidPinIdScl<$i2c> for $scl {})* + )* + + const SDA: &[(u8, usize)] = &[$($(($sda::ID.num, $i2c::ID)),*),*]; + const SCL: &[(u8, usize)] = &[$($(($scl::ID.num, $i2c::ID)),*),*]; + }; +} +valid_pins! { + I2C0: { + sda: [Gpio0, Gpio4, Gpio8, Gpio12, Gpio16, Gpio20, Gpio24, Gpio28], + scl: [Gpio1, Gpio5, Gpio9, Gpio13, Gpio17, Gpio21, Gpio25, Gpio29] + }, + I2C1: { + sda: [Gpio2, Gpio6, Gpio10, Gpio14, Gpio18, Gpio22, Gpio26], + scl: [Gpio3, Gpio7, Gpio11, Gpio15, Gpio19, Gpio23, Gpio27] + } +} + +/// Operational mode of the I2C peripheral. +pub trait I2CMode: Sealed { + /// Indicates whether this mode is Controller or Peripheral. + const IS_CONTROLLER: bool; +} + +/// Marker for an I2C peripheral operating as a controller. +pub struct Controller {} +impl Sealed for Controller {} +impl I2CMode for Controller { + const IS_CONTROLLER: bool = true; +} + +/// Marker for an I2C block operating as a peripehral. +pub struct Peripheral { + state: peripheral::State, +} +impl Sealed for Peripheral {} +impl I2CMode for Peripheral { + const IS_CONTROLLER: bool = false; +} + +/// I2C peripheral +pub struct I2C { + i2c: I2C, + pins: Pins, + mode: Mode, +} + +impl I2C +where + Block: SubsystemReset + Deref, +{ + /// Releases the I2C peripheral and associated pins + #[allow(clippy::type_complexity)] + pub fn free(self, resets: &mut RESETS) -> (Block, (Sda, Scl)) { + self.i2c.reset_bring_down(resets); + + (self.i2c, self.pins) + } +} + +impl, PINS, Mode> I2C { + /// Depth of the TX FIFO. + pub const TX_FIFO_DEPTH: u8 = 16; + + /// Depth of the RX FIFO. + pub const RX_FIFO_DEPTH: u8 = 16; + + /// Number of bytes currently in the RX FIFO + #[inline] + pub fn rx_fifo_used(&self) -> u8 { + self.i2c.ic_rxflr().read().rxflr().bits() + } + + /// Remaining capacity in the RX FIFO + #[inline] + pub fn rx_fifo_available(&self) -> u8 { + Self::RX_FIFO_DEPTH - self.rx_fifo_used() + } + + /// RX FIFO is empty + #[inline] + pub fn rx_fifo_empty(&self) -> bool { + self.i2c.ic_status().read().rfne().bit_is_clear() + } + + /// Number of bytes currently in the TX FIFO + #[inline] + pub fn tx_fifo_used(&self) -> u8 { + self.i2c.ic_txflr().read().txflr().bits() + } + + /// Remaining capacity in the TX FIFO + #[inline] + pub fn tx_fifo_available(&self) -> u8 { + Self::TX_FIFO_DEPTH - self.tx_fifo_used() + } + + /// TX FIFO is at capacity + #[inline] + pub fn tx_fifo_full(&self) -> bool { + self.i2c.ic_status().read().tfnf().bit_is_clear() + } +} + +macro_rules! hal { + ($($I2CX:ident: ($i2cX:ident),)+) => { + $( + impl I2C<$I2CX, (Sda, Scl)> { + /// Configures the I2C peripheral to work in master mode + pub fn $i2cX( + i2c: $I2CX, + sda_pin: Sda, + scl_pin: Scl, + freq: F, + resets: &mut RESETS, + system_clock: SystemF) -> Self + where + F: Into, + Sda: ValidPinSda<$I2CX> + AnyPin, + Scl: ValidPinScl<$I2CX> + AnyPin, + SystemF: Into, + { + Self::new_controller(i2c, sda_pin, scl_pin, freq.into(), resets, system_clock.into()) + } + + $crate::paste::paste! { + /// Configures the I2C peripheral to work in master mode + /// + /// This function can be called without activating internal pull-ups on the I2C pins. + /// It should only be used if external pull-ups are provided. + pub fn [<$i2cX _with_external_pull_up>]( + i2c: $I2CX, + sda_pin: Sda, + scl_pin: Scl, + freq: F, + resets: &mut RESETS, + system_clock: SystemF) -> Self + where + F: Into, + Sda: ValidPinSda<$I2CX>, + Scl: ValidPinScl<$I2CX>, + SystemF: Into, + { + Self::new_controller(i2c, sda_pin, scl_pin, freq.into(), resets, system_clock.into()) + } + } + } + + impl $crate::async_utils::sealed::Wakeable for I2C<$I2CX, P, M> { + fn waker() -> &'static $crate::async_utils::sealed::IrqWaker { + static WAKER: $crate::async_utils::sealed::IrqWaker = + $crate::async_utils::sealed::IrqWaker::new(); + &WAKER + } + } + )+ + } +} +hal! { + I2C0: (i2c0), + I2C1: (i2c1), +} diff --git a/rp-hal/rp2040-hal/src/i2c/controller.rs b/rp-hal/rp2040-hal/src/i2c/controller.rs new file mode 100644 index 0000000..92653f6 --- /dev/null +++ b/rp-hal/rp2040-hal/src/i2c/controller.rs @@ -0,0 +1,499 @@ +use core::{ops::Deref, task::Poll}; +use embedded_hal_0_2::blocking::i2c::{Read, Write, WriteIter, WriteIterRead, WriteRead}; +use fugit::HertzU32; + +use embedded_hal::i2c as eh1; + +use crate::{ + i2c::{Controller, Error, ValidAddress, ValidPinScl, ValidPinSda, I2C}, + pac::{i2c0::RegisterBlock as Block, RESETS}, + resets::SubsystemReset, +}; + +pub(crate) mod non_blocking; + +impl I2C +where + T: SubsystemReset + Deref, + Sda: ValidPinSda, + Scl: ValidPinScl, +{ + /// Configures the I2C peripheral to work in controller mode + pub fn new_controller( + i2c: T, + sda_pin: Sda, + scl_pin: Scl, + freq: HertzU32, + resets: &mut RESETS, + system_clock: HertzU32, + ) -> Self { + let freq = freq.to_Hz(); + assert!(freq <= 1_000_000); + assert!(freq > 0); + + i2c.reset_bring_down(resets); + i2c.reset_bring_up(resets); + + i2c.ic_enable().write(|w| w.enable().disabled()); + + // select controller mode & speed + i2c.ic_con().modify(|_, w| { + w.speed().fast(); + w.master_mode().enabled(); + w.ic_slave_disable().slave_disabled(); + w.ic_restart_en().enabled(); + w.tx_empty_ctrl().enabled() + }); + + // Clear FIFO threshold + i2c.ic_tx_tl().write(|w| unsafe { w.tx_tl().bits(0) }); + i2c.ic_rx_tl().write(|w| unsafe { w.rx_tl().bits(0) }); + + let freq_in = system_clock.to_Hz(); + + // There are some subtleties to I2C timing which we are completely ignoring here + // See: https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_i2c/i2c.c#L69 + let period = (freq_in + freq / 2) / freq; + let lcnt = period * 3 / 5; // spend 3/5 (60%) of the period low + let hcnt = period - lcnt; // and 2/5 (40%) of the period high + + // Check for out-of-range divisors: + assert!(hcnt <= 0xffff); + assert!(lcnt <= 0xffff); + assert!(hcnt >= 8); + assert!(lcnt >= 8); + + // Per I2C-bus specification a device in standard or fast mode must + // internally provide a hold time of at least 300ns for the SDA signal to + // bridge the undefined region of the falling edge of SCL. A smaller hold + // time of 120ns is used for fast mode plus. + let sda_tx_hold_count = if freq < 1000000 { + // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns) + // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + ((freq_in * 3) / 10000000) + 1 + } else { + // fast mode plus requires a clk_in > 32MHz + assert!(freq_in >= 32_000_000); + + // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns) + // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + ((freq_in * 3) / 25000000) + 1 + }; + assert!(sda_tx_hold_count <= lcnt - 2); + + unsafe { + i2c.ic_fs_scl_hcnt() + .write(|w| w.ic_fs_scl_hcnt().bits(hcnt as u16)); + i2c.ic_fs_scl_lcnt() + .write(|w| w.ic_fs_scl_lcnt().bits(lcnt as u16)); + // spike filter duration + i2c.ic_fs_spklen().write(|w| { + let ticks = if lcnt < 16 { 1 } else { (lcnt / 16) as u8 }; + w.ic_fs_spklen().bits(ticks) + }); + // sda hold time + i2c.ic_sda_hold() + .modify(|_r, w| w.ic_sda_tx_hold().bits(sda_tx_hold_count as u16)); + + // make TX_EMPTY raise when the tx fifo is not full + i2c.ic_tx_tl() + .write(|w| w.tx_tl().bits(Self::TX_FIFO_DEPTH)); + // make RX_FULL raise when the rx fifo contains at least 1 byte + i2c.ic_rx_tl().write(|w| w.rx_tl().bits(0)); + // Enable clock stretching. + // Will hold clock when: + // - receiving and rx fifo is full + // - writing and tx fifo is empty + i2c.ic_con() + .modify(|_, w| w.rx_fifo_full_hld_ctrl().enabled()); + } + + // Enable I2C block + i2c.ic_enable().write(|w| w.enable().enabled()); + + Self { + i2c, + pins: (sda_pin, scl_pin), + mode: Controller {}, + } + } +} + +impl, PINS> I2C { + fn validate_buffer( + &mut self, + first: bool, + buf: &mut core::iter::Peekable, + err: Error, + ) -> Result<(), Error> + where + U: Iterator, + { + if buf.peek().is_some() { + Ok(()) + } else { + if !first { + self.abort(); + } + Err(err) + } + } + + fn setup(&mut self, addr: A) -> Result<(), Error> { + addr.is_valid()?; + + self.i2c.ic_enable().write(|w| w.enable().disabled()); + self.i2c + .ic_con() + .modify(|_, w| w.ic_10bitaddr_master().variant(A::BIT_ADDR_M)); + + let addr = addr.into(); + self.i2c + .ic_tar() + .write(|w| unsafe { w.ic_tar().bits(addr) }); + self.i2c.ic_enable().write(|w| w.enable().enabled()); + Ok(()) + } + + #[inline] + fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> { + let abort_reason = self.i2c.ic_tx_abrt_source().read().bits(); + if abort_reason != 0 { + // Note clearing the abort flag also clears the reason, and + // this instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + self.i2c.ic_clr_tx_abrt().read(); + Err(Error::Abort(abort_reason)) + } else { + Ok(()) + } + } + + #[inline] + fn poll_tx_not_full(&mut self) -> Poll> { + self.read_and_clear_abort_reason()?; + if !self.tx_fifo_full() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + + #[inline] + fn poll_tx_empty(&mut self) -> Poll<()> { + if self.i2c.ic_raw_intr_stat().read().tx_empty().is_inactive() { + Poll::Pending + } else { + Poll::Ready(()) + } + } + + #[inline] + fn poll_stop_detected(&mut self) -> Poll<()> { + if self.i2c.ic_raw_intr_stat().read().stop_det().is_inactive() { + Poll::Pending + } else { + Poll::Ready(()) + } + } + + #[inline] + fn abort(&mut self) { + self.i2c.ic_enable().modify(|_, w| w.abort().set_bit()); + while self.i2c.ic_enable().read().abort().bit_is_set() { + cortex_m::asm::nop() + } + while self.i2c.ic_raw_intr_stat().read().tx_abrt().bit_is_clear() { + cortex_m::asm::nop() + } + // clear tx_abort interrupt flags (might have already been clear by irq) + self.i2c.ic_clr_tx_abrt().read(); + // clear tx_abrt_source by reading it + self.i2c.ic_tx_abrt_source().read(); + } + + fn read_internal( + &mut self, + first_transaction: bool, + buffer: &mut [u8], + do_stop: bool, + ) -> Result<(), Error> { + self.validate_buffer( + first_transaction, + &mut buffer.iter().peekable(), + Error::InvalidReadBufferLength, + )?; + + let lastindex = buffer.len() - 1; + let mut first_byte = true; + for (i, byte) in buffer.iter_mut().enumerate() { + let last_byte = i == lastindex; + + // wait until there is space in the FIFO to write the next byte + while self.i2c.ic_status().read().tfnf().bit_is_clear() {} + + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + + w.stop().bit(do_stop && last_byte); + w.cmd().read() + }); + + while self.i2c.ic_rxflr().read().bits() == 0 { + self.read_and_clear_abort_reason()?; + } + + *byte = self.i2c.ic_data_cmd().read().dat().bits(); + } + + Ok(()) + } + + fn write_internal( + &mut self, + first_transaction: bool, + bytes: impl IntoIterator, + do_stop: bool, + ) -> Result<(), Error> { + let mut peekable = bytes.into_iter().peekable(); + self.validate_buffer( + first_transaction, + &mut peekable, + Error::InvalidWriteBufferLength, + )?; + + let mut abort_reason = Ok(()); + let mut first_byte = true; + 'outer: while let Some(byte) = peekable.next() { + if self.tx_fifo_full() { + // wait for more room in the fifo + loop { + match self.poll_tx_not_full() { + Poll::Pending => continue, + Poll::Ready(Ok(())) => break, + Poll::Ready(r) => { + abort_reason = r; + break 'outer; + } + } + } + } + + // else enqueue + let last = peekable.peek().is_none(); + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + w.stop().bit(do_stop && last); + unsafe { w.dat().bits(byte) } + }); + } + + if abort_reason.is_err() { + // Wait until the transmission of the address/data from the internal + // shift register has completed. + while self.poll_tx_empty().is_pending() {} + abort_reason = self.read_and_clear_abort_reason(); + } + + if abort_reason.is_err() || do_stop { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occurred. + while self.poll_stop_detected().is_pending() {} + self.i2c.ic_clr_stop_det().read().clr_stop_det(); + } + // Note: the hardware issues a STOP automatically on an abort condition. + // Note: the hardware also clears RX FIFO as well as TX on abort + + abort_reason + } +} + +impl, PINS> I2C { + /// Writes bytes to slave with address `address` + /// + /// # I2C Events (contract) + /// + /// Same as the `write` method + pub fn write_iter(&mut self, address: A, bytes: B) -> Result<(), Error> + where + B: IntoIterator, + { + self.setup(address)?; + self.write_internal(true, bytes, true) + } + + /// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a + /// single transaction* + /// + /// # I2C Events (contract) + /// + /// Same as the `write_read` method + pub fn write_iter_read( + &mut self, + address: A, + bytes: B, + buffer: &mut [u8], + ) -> Result<(), Error> + where + B: IntoIterator, + { + self.setup(address)?; + + self.write_internal(true, bytes, false)?; + self.read_internal(false, buffer, true) + } + + /// Execute the provided operations on the I2C bus (iterator version). + /// + /// Transaction contract: + /// - Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate. + /// - Data from adjacent operations of the same type are sent after each other without an SP or SR. + /// - Between adjacent operations of a different type an SR and SAD+R/W is sent. + /// - After executing the last operation an SP is sent automatically. + /// - If the last operation is a `Read` the master does not send an acknowledge for the last byte. + /// + /// - `ST` = start condition + /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing + /// - `SR` = repeated start condition + /// - `SP` = stop condition + fn transaction<'op: 'iter, 'iter, A: ValidAddress>( + &mut self, + address: A, + operations: impl IntoIterator>, + ) -> Result<(), Error> { + self.setup(address)?; + + let mut first = true; + let mut operations = operations.into_iter().peekable(); + while let Some(operation) = operations.next() { + let last = operations.peek().is_none(); + match operation { + eh1::Operation::Read(buf) => self.read_internal(first, buf, last)?, + eh1::Operation::Write(buf) => { + self.write_internal(first, buf.iter().cloned(), last)? + } + } + first = false; + } + Ok(()) + } + + #[cfg(feature = "i2c-write-iter")] + fn transaction_iter<'op, A, O, B>(&mut self, address: A, operations: O) -> Result<(), Error> + where + A: ValidAddress, + O: IntoIterator>, + B: IntoIterator, + { + use i2c_write_iter::Operation; + self.setup(address)?; + + let mut first = true; + let mut operations = operations.into_iter().peekable(); + while let Some(operation) = operations.next() { + let last = operations.peek().is_none(); + match operation { + Operation::Read(buf) => self.read_internal(first, buf, last)?, + Operation::WriteIter(buf) => self.write_internal(first, buf, last)?, + } + first = false; + } + Ok(()) + } +} + +impl, PINS> Read for I2C { + type Error = Error; + + fn read(&mut self, addr: A, buffer: &mut [u8]) -> Result<(), Error> { + self.setup(addr)?; + self.read_internal(true, buffer, true) + } +} +impl, PINS> WriteRead for I2C { + type Error = Error; + + fn write_read(&mut self, addr: A, tx: &[u8], rx: &mut [u8]) -> Result<(), Error> { + self.setup(addr)?; + + self.write_internal(true, tx.iter().cloned(), false)?; + self.read_internal(false, rx, true) + } +} + +impl, PINS> Write for I2C { + type Error = Error; + + fn write(&mut self, addr: A, tx: &[u8]) -> Result<(), Error> { + self.setup(addr)?; + self.write_internal(true, tx.iter().cloned(), true) + } +} + +impl, PINS> WriteIter for I2C { + type Error = Error; + + fn write(&mut self, address: A, bytes: B) -> Result<(), Self::Error> + where + B: IntoIterator, + { + self.write_iter(address, bytes) + } +} + +impl, PINS> WriteIterRead + for I2C +{ + type Error = Error; + + fn write_iter_read( + &mut self, + address: A, + bytes: B, + buffer: &mut [u8], + ) -> Result<(), Self::Error> + where + B: IntoIterator, + { + self.write_iter_read(address, bytes, buffer) + } +} + +impl, PINS> eh1::ErrorType for I2C { + type Error = Error; +} + +impl, PINS> eh1::I2c for I2C { + fn transaction( + &mut self, + address: A, + operations: &mut [eh1::Operation<'_>], + ) -> Result<(), Self::Error> { + self.transaction(address, operations.iter_mut()) + } +} + +#[cfg(feature = "i2c-write-iter")] +impl, PINS> + i2c_write_iter::I2cIter for I2C +{ + fn transaction_iter<'a, O, B>(&mut self, address: A, operations: O) -> Result<(), Self::Error> + where + O: IntoIterator>, + B: IntoIterator, + { + self.transaction_iter(address, operations) + } +} diff --git a/rp-hal/rp2040-hal/src/i2c/controller/non_blocking.rs b/rp-hal/rp2040-hal/src/i2c/controller/non_blocking.rs new file mode 100644 index 0000000..c9a7f3a --- /dev/null +++ b/rp-hal/rp2040-hal/src/i2c/controller/non_blocking.rs @@ -0,0 +1,347 @@ +use core::{ops::Deref, task::Poll}; +use embedded_hal_async::i2c::{AddressMode, Operation}; + +use crate::{ + async_utils::{sealed::Wakeable, AsyncPeripheral, CancellablePollFn as CPFn}, + i2c::{Controller, Error, ValidAddress, I2C}, + pac::i2c0::RegisterBlock, +}; + +macro_rules! impl_async_traits { + ($i2c:path) => { + impl

AsyncPeripheral for I2C<$i2c, P, Controller> + where + Self: $crate::async_utils::sealed::Wakeable, + { + fn on_interrupt() { + unsafe { + // This is equivalent to stealing from pac::Peripherals + let i2c = &*<$i2c>::ptr(); + + // Mask all interrupt flags. This does not clear the flags. + // Clearing is done by the driver after it wakes up. + i2c.ic_intr_mask().write_with_zero(|w| w); + } + // interrupts are now masked, we can wake the task and return from this handler. + Self::waker().wake(); + } + } + }; +} + +impl_async_traits!(rp2040_pac::I2C0); +impl_async_traits!(rp2040_pac::I2C1); + +enum TxEmptyConfig { + Empty, + NotFull, +} + +impl I2C +where + T: Deref, + Self: AsyncPeripheral, +{ + /// `tx_empty`: true to unmask tx_empty + #[inline] + fn unmask_intr(&mut self, tx_empty: bool) { + unsafe { + self.i2c.ic_intr_mask().write_with_zero(|w| { + w.m_tx_empty() + .bit(tx_empty) + .m_rx_full() + .disabled() + .m_tx_abrt() + .disabled() + .m_stop_det() + .disabled() + }); + } + } + #[inline] + fn configure_tx_empty(&mut self, cfg: TxEmptyConfig) { + self.i2c + .ic_tx_tl() + // SAFETY: we are within [0; TX_FIFO_DEPTH) + .write(|w| unsafe { + w.tx_tl().bits(match cfg { + TxEmptyConfig::Empty => 1, + TxEmptyConfig::NotFull => Self::TX_FIFO_DEPTH - 1, + }) + }); + } + + #[inline] + fn unmask_tx_empty(&mut self) { + self.configure_tx_empty(TxEmptyConfig::Empty); + self.unmask_intr(true) + } + + #[inline] + fn unmask_tx_not_full(&mut self) { + self.configure_tx_empty(TxEmptyConfig::NotFull); + self.unmask_intr(true) + } + + #[inline] + fn unmask_stop_det(&mut self) { + self.unmask_intr(false); + } + + #[inline] + fn poll_rx_not_empty_or_abrt(&mut self) -> Poll> { + self.read_and_clear_abort_reason()?; + if self.i2c.ic_raw_intr_stat().read().rx_full().bit_is_set() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + + #[inline] + fn cancel(&mut self) { + unsafe { + self.i2c.ic_intr_mask().write_with_zero(|w| w); + } + + self.abort(); + } + + async fn non_blocking_read_internal( + &mut self, + first_transaction: bool, + buffer: &mut [u8], + do_stop: bool, + ) -> Result<(), Error> { + self.validate_buffer( + first_transaction, + &mut buffer.iter().peekable(), + Error::InvalidReadBufferLength, + )?; + + let lastindex = buffer.len() - 1; + let mut first_byte = true; + for (i, byte) in buffer.iter_mut().enumerate() { + let last_byte = i == lastindex; + + // wait until there is space in the FIFO to write the next byte + // cannot abort during read, so ignore the result + let _ = CPFn::new( + self, + Self::poll_tx_not_full, + Self::unmask_tx_not_full, + Self::cancel, + ) + .await; + + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + + w.stop().bit(do_stop && last_byte); + w.cmd().read() + }); + + CPFn::new( + self, + Self::poll_rx_not_empty_or_abrt, + Self::unmask_tx_empty, + Self::cancel, + ) + .await?; + + *byte = self.i2c.ic_data_cmd().read().dat().bits(); + } + + Ok(()) + } + + async fn non_blocking_write_internal( + &mut self, + first_transaction: bool, + bytes: impl IntoIterator, + do_stop: bool, + ) -> Result<(), Error> { + let mut peekable = bytes.into_iter().peekable(); + self.validate_buffer( + first_transaction, + &mut peekable, + Error::InvalidWriteBufferLength, + )?; + + let mut abort_reason = Ok(()); + let mut first_byte = true; + while let Some(byte) = peekable.next() { + if self.tx_fifo_full() { + // wait for more room in the fifo + abort_reason = CPFn::new( + self, + Self::poll_tx_not_full, + Self::unmask_tx_not_full, + Self::cancel, + ) + .await; + if abort_reason.is_err() { + break; + } + } + + // else enqueue + let last = peekable.peek().is_none(); + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + w.stop().bit(do_stop && last); + unsafe { w.dat().bits(byte) } + }); + } + + if abort_reason.is_err() { + // Wait until the transmission of the address/data from the internal + // shift register has completed. + CPFn::new( + self, + Self::poll_tx_empty, + Self::unmask_tx_empty, + Self::cancel, + ) + .await; + abort_reason = self.read_and_clear_abort_reason(); + } + + if abort_reason.is_err() || do_stop { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occurred. + CPFn::new( + self, + Self::poll_stop_detected, + Self::unmask_stop_det, + Self::cancel, + ) + .await; + self.i2c.ic_clr_stop_det().read().clr_stop_det(); + } + // Note: the hardware issues a STOP automatically on an abort condition. + // Note: the hardware also clears RX FIFO as well as TX on abort + + abort_reason + } + + /// Writes to the i2c bus consuming bytes for the given iterator. + pub async fn write_iter_async(&mut self, address: A, bytes: U) -> Result<(), super::Error> + where + U: IntoIterator, + A: ValidAddress, + { + self.setup(address)?; + self.non_blocking_write_internal(true, bytes, true).await + } + + /// Writes to the i2c bus consuming bytes for the given iterator. + pub async fn write_iter_read_async( + &mut self, + address: A, + bytes: U, + read: &mut [u8], + ) -> Result<(), Error> + where + U: IntoIterator, + A: ValidAddress, + { + self.setup(address)?; + self.non_blocking_write_internal(true, bytes, false).await?; + self.non_blocking_read_internal(false, read, true).await + } + + /// Writes to the i2c bus taking operations from and iterator, writing from iterator of bytes, + /// reading to slices of bytes. + #[cfg(feature = "i2c-write-iter")] + pub async fn transaction_iter_async<'b, A, O, B>( + &mut self, + address: A, + operations: O, + ) -> Result<(), super::Error> + where + A: ValidAddress, + O: IntoIterator>, + B: IntoIterator, + { + self.setup(address)?; + + let mut first = true; + let mut operations = operations.into_iter().peekable(); + while let Some(operation) = operations.next() { + let last = operations.peek().is_none(); + match operation { + i2c_write_iter::Operation::Read(buf) => { + self.non_blocking_read_internal(first, buf, last).await? + } + i2c_write_iter::Operation::WriteIter(buf) => { + self.non_blocking_write_internal(first, buf, last).await? + } + } + first = false; + } + Ok(()) + } +} + +impl embedded_hal_async::i2c::I2c for I2C +where + Self: AsyncPeripheral, + A: ValidAddress + AddressMode, + T: Deref, +{ + async fn transaction( + &mut self, + addr: A, + operations: &mut [Operation<'_>], + ) -> Result<(), Error> { + self.setup(addr)?; + + let mut first = true; + let mut operations = operations.iter_mut().peekable(); + while let Some(op) = operations.next() { + let last = operations.peek().is_none(); + match op { + Operation::Read(buffer) => { + self.non_blocking_read_internal(first, buffer, last).await?; + } + Operation::Write(buffer) => { + self.non_blocking_write_internal(first, buffer.iter().cloned(), last) + .await?; + } + } + first = false; + } + Ok(()) + } +} + +#[cfg(feature = "i2c-write-iter")] +impl i2c_write_iter::non_blocking::I2cIter for I2C +where + Self: AsyncPeripheral, + A: 'static + ValidAddress + AddressMode, + T: Deref, +{ + async fn transaction_iter<'a, O, B>( + &mut self, + address: A, + operations: O, + ) -> Result<(), Self::Error> + where + O: IntoIterator>, + B: IntoIterator, + { + self.transaction_iter_async(address, operations).await + } +} diff --git a/rp-hal/rp2040-hal/src/i2c/peripheral.rs b/rp-hal/rp2040-hal/src/i2c/peripheral.rs new file mode 100644 index 0000000..3055786 --- /dev/null +++ b/rp-hal/rp2040-hal/src/i2c/peripheral.rs @@ -0,0 +1,320 @@ +//! # I2C Peripheral (slave) implementation +//! +//! The RP2040 I2C block can behave as a peripheral node on an I2C bus. +//! +//! In order to handle peripheral transactions this driver exposes an iterator streaming I2C event +//! that the usercode must handle to properly handle the I2C communitation. See [`Event`] for a +//! list of events to handle. +//! +//! Although [`Start`](Event::Start), [`Restart`](Event::Restart) and [`Stop`](Event::Stop) +//! events may not require any action on the device, [`TransferRead`](Event::TransferRead) and +//! [`TransferWrite`](Event::TransferWrite) require some action: +//! +//! - [`TransferRead`](Event::TransferRead): A controller is attempting to read from this peripheral. +//! The I2C block holds the SCL line low (clock stretching) until data is pushed to the transmission +//! FIFO by the user application using [`write`](I2C::write). +//! Data remaining in the FIFO when the bus constroller stops the transfer are ignored & the fifo +//! is flushed. +//! - [`TransferWrite`](Event::TransferWrite): A controller is sending data to this peripheral. +//! The I2C block holds the SCL line (clock stretching) until there is room for more data in the +//! Rx FIFO using [`read`](I2C::read). +//! Data are automatically acknowledged by the I2C block and it is not possible to NACK incoming +//! data coming to the rp2040. +//! +//! ## Warning +//! +//! `Start`, `Restart` and `Stop` events may not be reported before or after a write operations. +//! This is because several write operation may take place and complete before the core has time to +//! react. All the data sent will be stored in the peripheral FIFO but it will not be possible to +//! identify between which bytes should the start/restart/stop events precicely took place. +//! +//! Because a Read operation will always cause a pause waiting for the firmware's input, a `Start` +//! (or `Restart` if the peripheral is already active) will always be reported. However, this does +//! not mean no other event occurred in the mean time. +//! +//! For example, let's consider the following sequence: +//! +//! `Start, Write, Stop, Start, Write, Restart, Read, Stop.` +//! +//! Depending on the firmware's and bus' speed, this driver may only report: +//! - `Start, Write, Restart, Read, Stop` + +use core::{ops::Deref, task::Poll}; + +use embedded_hal::i2c::AddressMode; + +use super::{Peripheral, ValidAddress, ValidPinScl, ValidPinSda, I2C}; +use crate::{ + async_utils::{sealed::Wakeable, AsyncPeripheral, CancellablePollFn}, + pac::{i2c0::RegisterBlock, RESETS}, + resets::SubsystemReset, +}; + +/// I2C bus events +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Event { + /// Start condition has been detected. + Start, + /// Restart condition has been detected. + Restart, + /// The controller requests data. + TransferRead, + /// The controller sends data. + TransferWrite, + /// Stop condition detected. + Stop, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum State { + Idle, + Active, + Read, + Write, +} + +impl I2C +where + T: SubsystemReset + Deref, + Sda: ValidPinSda, + Scl: ValidPinScl, +{ + /// Configures the I2C peripheral to work in peripheral mode + /// + /// The bus *MUST* be idle when this method is called. + #[allow(clippy::type_complexity)] + pub fn new_peripheral_event_iterator( + i2c: T, + sda_pin: Sda, + scl_pin: Scl, + resets: &mut RESETS, + addr: A, + ) -> Self { + i2c.reset_bring_down(resets); + i2c.reset_bring_up(resets); + + i2c.ic_enable().write(|w| w.enable().disabled()); + + // TODO: Validate address? + let addr = addr.into(); + // SAFETY: Only address by this function. IC_SAR spec filters out bits 15:10. + // Any value is valid for the controller. They may not be for the bus itself though. + i2c.ic_sar().write(|w| unsafe { w.ic_sar().bits(addr) }); + // select peripheral mode & speed + i2c.ic_con().modify(|_, w| { + // run in fast mode + w.speed().fast(); + // setup slave mode + w.master_mode().disabled(); + w.ic_slave_disable().slave_enabled(); + // hold scl when fifo's full + w.rx_fifo_full_hld_ctrl().enabled(); + w.ic_restart_en().enabled(); + w.ic_10bitaddr_slave().variant(A::BIT_ADDR_S); + w + }); + + // Clear FIFO threshold + // SAFETY: Only address by this function. The field is 8bit long. 0 is a valid value. + i2c.ic_tx_tl().write(|w| unsafe { w.tx_tl().bits(0) }); + i2c.ic_rx_tl().write(|w| unsafe { w.rx_tl().bits(0) }); + + i2c.ic_clr_intr().read(); + + let mut me = Self { + i2c, + pins: (sda_pin, scl_pin), + mode: Peripheral { state: State::Idle }, + }; + me.unmask_intr(); + // Enable I2C block + me.i2c.ic_enable().write(|w| w.enable().enabled()); + + me + } +} + +fn unmask_intr(i2c: &RegisterBlock) { + // SAFETY: 0 is a valid value meaning all irq masked. + // This operation is atomic, `write_with_zero` only writes to the register. + unsafe { + i2c.ic_intr_mask().write_with_zero(|w| { + // Only keep these IRQ enabled. + w.m_start_det() + .disabled() + .m_rd_req() + .disabled() + .m_rx_full() + .disabled() + .m_stop_det() + .disabled() + }); + } +} + +/// SAFETY: Takes a non-mutable reference to RegisterBlock but mutates its `ic_intr_mask` register. +unsafe fn mask_intr(i2c: &RegisterBlock) { + // 0 is a valid value and means all flag masked. + unsafe { i2c.ic_intr_mask().write_with_zero(|w| w) } +} + +impl, PINS> I2C { + fn unmask_intr(&mut self) { + unmask_intr(&self.i2c) + } + fn mask_intr(&mut self) { + // SAFETY: We are the only owner of this register block. + unsafe { mask_intr(&self.i2c) } + } + + /// Push up to `usize::min(TX_FIFO_SIZE, buf.len())` bytes to the TX FIFO. + /// Returns the number of bytes pushed to the FIFO. Note this does *not* reflect how many bytes + /// are effectively received by the controller. + pub fn write(&mut self, buf: &[u8]) -> usize { + // just in case, clears previous tx abort. + self.i2c.ic_clr_tx_abrt().read(); + + let mut sent = 0; + for &b in buf.iter() { + if self.tx_fifo_full() { + break; + } + + // SAFETY: dat field is 8bits long. All values are valid. + self.i2c.ic_data_cmd().write(|w| unsafe { w.dat().bits(b) }); + sent += 1; + } + // serve a pending read request + self.i2c.ic_clr_rd_req().read(); + sent + } + + /// Pull up to `usize::min(RX_FIFO_SIZE, buf.len())` bytes from the RX FIFO. + pub fn read(&mut self, buf: &mut [u8]) -> usize { + buf.iter_mut().zip(self).map(|(b, r)| *b = r).count() + } +} + +/// This allows I2C to be used with `core::iter::Extend`. +impl, PINS> Iterator for I2C { + type Item = u8; + + fn next(&mut self) -> Option { + if self.rx_fifo_empty() { + None + } else { + Some(self.i2c.ic_data_cmd().read().dat().bits()) + } + } +} +impl, PINS> I2C { + /// Returns the next i2c event if any. + pub fn next_event(&mut self) -> Option { + let stat = self.i2c.ic_raw_intr_stat().read(); + + match self.mode.state { + State::Idle if stat.start_det().bit_is_set() => { + self.i2c.ic_clr_start_det().read(); + self.mode.state = State::Active; + Some(Event::Start) + } + State::Active if stat.rd_req().bit_is_set() => { + // Clearing `rd_req` is used by the hardware to detect when the I2C block can stop + // stretching the clock and start process the data pushed to the FIFO (if any). + // This is done in `Self::write`. + self.mode.state = State::Read; + // If stop_det is set at this point we know that it is related to a previous request, + // It cannot be due the end of the current request as SCL is held low while waiting + // for user input. + if stat.stop_det().bit_is_set() { + self.i2c.ic_clr_stop_det().read(); + } + Some(Event::TransferRead) + } + State::Active if !self.rx_fifo_empty() => { + self.mode.state = State::Write; + Some(Event::TransferWrite) + } + + State::Read if stat.rd_req().bit_is_set() => Some(Event::TransferRead), + State::Write if !self.rx_fifo_empty() => Some(Event::TransferWrite), + + State::Read | State::Write if stat.restart_det().bit_is_set() => { + self.i2c.ic_clr_restart_det().read(); + self.i2c.ic_clr_start_det().read(); + self.mode.state = State::Active; + Some(Event::Restart) + } + + _ if stat.stop_det().bit_is_set() => { + self.i2c.ic_clr_stop_det().read(); + self.i2c.ic_clr_tx_abrt().read(); + self.mode.state = State::Idle; + Some(Event::Stop) + } + + _ => None, + } + } +} + +macro_rules! impl_wakeable { + ($i2c:ty) => { + impl AsyncPeripheral for I2C<$i2c, PINS, Peripheral> + where + I2C<$i2c, PINS, Peripheral>: $crate::async_utils::sealed::Wakeable, + { + /// Wakes an async task (if any) & masks irqs + fn on_interrupt() { + unsafe { + // This is equivalent to stealing from pac::Peripherals + let i2c = &*<$i2c>::ptr(); + + mask_intr(i2c); + } + + // interrupts are now masked, we can wake the task and return from this handler. + Self::waker().wake(); + } + } + }; +} +impl_wakeable!(rp2040_pac::I2C0); +impl_wakeable!(rp2040_pac::I2C1); + +impl I2C +where + I2C: AsyncPeripheral, + T: Deref, +{ + /// Asynchronously waits for an Event. + pub async fn wait_next(&mut self) -> Event { + loop { + if let Some(evt) = self.next_event() { + return evt; + } + + CancellablePollFn::new( + self, + |me| { + let stat = me.i2c.ic_raw_intr_stat().read(); + if stat.start_det().bit_is_set() + || stat.restart_det().bit_is_set() + || stat.stop_det().bit_is_set() + || stat.rd_req().bit_is_set() + || stat.rx_full().bit_is_set() + { + Poll::Ready(()) + } else { + Poll::Pending + } + }, + Self::unmask_intr, + Self::mask_intr, + ) + .await; + } + } +} diff --git a/rp-hal/rp2040-hal/src/intrinsics.rs b/rp-hal/rp2040-hal/src/intrinsics.rs new file mode 100644 index 0000000..ca77da1 --- /dev/null +++ b/rp-hal/rp2040-hal/src/intrinsics.rs @@ -0,0 +1,274 @@ +/// Generate a series of aliases for an intrinsic function. +macro_rules! intrinsics_aliases { + ( + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + ) => {}; + ( + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + ) => {}; + + ( + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + $alias:ident + $($rest:ident)* + ) => { + #[cfg(all(target_arch = "arm", not(feature = "disable-intrinsics")))] + mod $alias { + #[no_mangle] + pub extern $abi fn $alias( $($argname: $ty),* ) -> $ret { + super::$name($($argname),*) + } + } + + intrinsics_aliases! { + extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($rest)* + } + }; + + ( + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + $alias:ident + $($rest:ident)* + ) => { + #[cfg(all(target_arch = "arm", not(feature = "disable-intrinsics")))] + mod $alias { + #[no_mangle] + unsafe extern $abi fn $alias( $($argname: $ty),* ) -> $ret { + super::$name($($argname),*) + } + } + + intrinsics_aliases! { + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($rest)* + } + }; +} + +/// The macro used to define overridden intrinsics. +/// +/// This is heavily inspired by the macro used by compiler-builtins. The idea +/// is to abstract anything special that needs to be done to override an +/// intrinsic function. Intrinsic generation is disabled for non-ARM targets +/// so things like CI and docs generation do not have problems. Additionally +/// they can be disabled with the crate feature `disable-intrinsics` for +/// testing or comparing performance. +/// +/// Like the compiler-builtins macro, it accepts a series of functions that +/// looks like normal Rust code: +/// +/// ```ignore +/// intrinsics! { +/// extern "C" fn foo(a: i32) -> u32 { +/// // ... +/// } +/// #[nonstandard_attribute] +/// extern "C" fn bar(a: i32) -> u32 { +/// // ... +/// } +/// } +/// ``` +/// +/// Each function can also be decorated with nonstandard attributes to control +/// additional behaviour: +/// +/// * `slower_than_default` - indicates that the override is slower than the +/// default implementation. Currently this just disables the override +/// entirely. +/// * `bootrom_v2` - indicates that the override is only available +/// on a V2 bootrom or higher. Only enabled when the feature +/// `rom-v2-intrinsics` is set. +/// * `alias` - accepts a list of names to alias the intrinsic to. +/// * `aeabi` - accepts a list of ARM EABI names to alias to. +/// +macro_rules! intrinsics { + () => {}; + + ( + #[slower_than_default] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + // Not exported, but defined so the actual implementation is + // considered used + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; + + ( + #[bootrom_v2] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(feature = "rom-v2-intrinsics"))] + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(feature = "rom-v2-intrinsics")] + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics!($($rest)*); + }; + + ( + #[alias = $($alias:ident),*] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + #[alias = $($alias:ident),*] + $(#[$($attr:tt)*])* + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + #[aeabi = $($alias:ident),*] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + extern "aapcs" fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + #[cfg(all(target_arch = "arm", not(feature = "disable-intrinsics")))] + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(all(target_arch = "arm", not(feature = "disable-intrinsics")))] + mod $name { + #[no_mangle] + $(#[$($attr)*])* + pub extern $abi fn $name( $($argname: $ty),* ) -> $ret { + super::$name($($argname),*) + } + } + + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(all(target_arch = "arm", not(feature = "disable-intrinsics"))))] + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; + + ( + $(#[$($attr:tt)*])* + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + #[cfg(all(target_arch = "arm", not(feature = "disable-intrinsics")))] + $(#[$($attr)*])* + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(all(target_arch = "arm", not(feature = "disable-intrinsics")))] + mod $name { + #[no_mangle] + $(#[$($attr)*])* + pub unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + super::$name($($argname),*) + } + } + + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(all(target_arch = "arm", not(feature = "disable-intrinsics"))))] + #[allow(dead_code)] + unsafe fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; +} diff --git a/rp-hal/rp2040-hal/src/lib.rs b/rp-hal/rp2040-hal/src/lib.rs new file mode 100644 index 0000000..2c16312 --- /dev/null +++ b/rp-hal/rp2040-hal/src/lib.rs @@ -0,0 +1,169 @@ +//! HAL for the RP2040 microcontroller +//! +//! This is an implementation of the [`embedded-hal`](https://crates.io/crates/embedded-hal) +//! traits for the RP2040 microcontroller +//! +//! NOTE This HAL is still under active development. This API will remain volatile until 1.0.0 +//! +//! # Crate features +//! +//! * **chrono** - +//! Add conversion functions between chrono types and the rp2040-hal specific DateTime type +//! * **critical-section-impl** - +//! critical section that is safe for multicore use +//! * **defmt** - +//! Implement `defmt::Format` for several types. +//! * **disable-intrinsics** - +//! Disable automatic mapping of language features (like floating point math) to ROM functions +//! * **embedded_hal_1** - +//! Support alpha release of embedded-hal +//! * **rom-func-cache** - +//! Memoize(cache) ROM function pointers on first use to improve performance +//! * **rt** - +//! Minimal startup / runtime for Cortex-M microcontrollers +//! * **rom-v2-intrinsics** - +//! This enables ROM functions for f64 math that were not present in the earliest RP2040s +//! * **rp2040-e5** - +//! This enables a fix for USB errata 5: USB device fails to exit RESET state on busy USB bus. +//! Only required for RP2040 B0 and RP2040 B1, but it also works for RP2040 B2 and above. +//! **Note that the workaround takes control of pin 15 (bank0) during usb reset so the bank needs +//! to be taken out of reset before calling `UsbBus::new`**. +//! Using `let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS);` +//! is enough to take the Bank 0 out of reset. +//! * **rtic-monotonic** - +//! Implement +//! `rtic_monotonic::Monotonic` based on the RP2040 timer peripheral +//! * **i2c-write-iter** - +//! Implement `i2c_write_iter` traits for `I2C<_, _, Controller>`. +//! * **binary-info** - +//! Include a `static` variable containing picotool compatible binary info. + +#![warn(missing_docs)] +#![no_std] + +#[doc(hidden)] +pub use paste; + +/// Re-export of the PAC +pub use rp2040_pac as pac; + +#[macro_use] +mod intrinsics; + +pub mod adc; +pub mod arch; +#[macro_use] +pub mod async_utils; +pub(crate) mod atomic_register_access; +pub use rp_binary_info as binary_info; +pub mod clocks; +#[cfg(feature = "critical-section-impl")] +mod critical_section_impl; +pub mod dma; +mod float; +pub mod gpio; +pub mod i2c; +pub mod multicore; +pub mod pio; +pub mod pll; +pub mod prelude; +pub mod pwm; +pub mod resets; +pub mod rom_data; +pub mod rosc; +pub mod rtc; +pub mod sio; +pub mod spi; +pub mod ssi; +pub mod timer; +pub mod typelevel; +pub mod uart; +pub mod usb; +pub mod vector_table; +pub mod vreg; +pub mod watchdog; +pub mod xosc; + +// Provide access to common datastructures to avoid repeating ourselves +pub use adc::Adc; +pub use clocks::Clock; +pub use i2c::I2C; +/// Attribute to declare the entry point of the program +/// +/// This is based on and can be used like the [entry attribute from +/// cortex-m-rt](https://docs.rs/cortex-m-rt/latest/cortex_m_rt/attr.entry.html). +/// +/// It extends that macro with code to unlock all spinlocks at the beginning +/// of `main`. As spinlocks are not automatically unlocked on software resets, +/// this can prevent unexpected deadlocks when running from a debugger. +pub use rp2040_hal_macros::entry; +use sio::CoreId; +pub use sio::Sio; +pub use spi::Spi; +pub use timer::Timer; +pub use watchdog::Watchdog; +// Re-export crates used in rp2040-hal's public API +pub extern crate fugit; + +/// Trigger full reset of the RP2040. +/// +/// Uses the watchdog and the power-on state machine (PSM) to reset all on-chip components. +pub fn reset() -> ! { + unsafe { + cortex_m::interrupt::disable(); + (*pac::PSM::PTR).wdsel().write(|w| w.bits(0x0001ffff)); + (*pac::WATCHDOG::PTR) + .ctrl() + .write(|w| w.trigger().set_bit()); + #[allow(clippy::empty_loop)] + loop {} + } +} + +/// Halt the RP2040. +/// +/// Disables the other core, and parks the current core in an +/// infinite loop with interrupts disabled. +/// +/// Doesn't stop other subsystems, like the DMA controller. +/// +/// When called from core1, core0 will be kept forced off, which +/// likely breaks debug connections. You may need to reboot with +/// BOOTSEL pressed to reboot into a debuggable state. +pub fn halt() -> ! { + unsafe { + cortex_m::interrupt::disable(); + // Stop other core + match crate::Sio::core() { + CoreId::Core0 => { + // Stop core 1. + (*pac::PSM::PTR) + .frce_off() + .modify(|_, w| w.proc1().set_bit()); + while !(*pac::PSM::PTR).frce_off().read().proc1().bit_is_set() { + cortex_m::asm::nop(); + } + // Restart core 1. Without this, most debuggers will fail connecting. + // It will loop indefinitely in BOOTROM, as nothing + // will trigger the wakeup sequence. + (*pac::PSM::PTR) + .frce_off() + .modify(|_, w| w.proc1().clear_bit()); + } + CoreId::Core1 => { + // Stop core 0. + (*pac::PSM::PTR) + .frce_off() + .modify(|_, w| w.proc0().set_bit()); + // We cannot restart core 0 here, as it would just boot into main. + // So the best we can do is to keep core 0 disabled, which may break + // further debug connections. + } + }; + + // Keep current core running, so debugging stays possible + loop { + cortex_m::asm::wfe() + } + } +} diff --git a/rp-hal/rp2040-hal/src/multicore.rs b/rp-hal/rp2040-hal/src/multicore.rs new file mode 100644 index 0000000..8d2fd16 --- /dev/null +++ b/rp-hal/rp2040-hal/src/multicore.rs @@ -0,0 +1,387 @@ +//! 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. +//! +//! # Usage +//! +//! ```no_run +//! use rp2040_hal::{pac, gpio::Pins, sio::Sio, multicore::{Multicore, Stack}}; +//! +//! static CORE1_STACK: Stack<4096> = Stack::new(); +//! +//! fn core1_task() { +//! loop {} +//! } +//! +//! fn main() -> ! { +//! let mut pac = pac::Peripherals::take().unwrap(); +//! let mut sio = Sio::new(pac.SIO); +//! // Other init code above this line +//! let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo); +//! let cores = mc.cores(); +//! let core1 = &mut cores[1]; +//! let _test = core1.spawn(CORE1_STACK.take().unwrap(), core1_task); +//! // The rest of your application below this line +//! # loop {} +//! } +//! +//! ``` +//! +//! For inter-processor communications, see [`crate::sio::SioFifo`] and [`crate::sio::Spinlock0`] +//! +//! For a detailed example, see [examples/multicore_fifo_blink.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal-examples/src/bin/multicore_fifo_blink.rs) + +use core::cell::Cell; +use core::cell::UnsafeCell; +use core::mem::ManuallyDrop; +use core::ops::Range; +use core::sync::atomic::compiler_fence; +use core::sync::atomic::Ordering; + +use crate::pac; +use crate::Sio; + +/// Errors for multicore operations. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Operation is invalid on this core. + InvalidCore, + /// Core was unresponsive to commands. + Unresponsive, +} + +#[inline(always)] +fn install_stack_guard(stack_limit: *mut usize) { + let core = unsafe { pac::CorePeripherals::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_limit 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 + const RBAR_VALID: u32 = 0x10; + core.MPU.rbar.write((addr & !0xff) | RBAR_VALID); + 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_limit: *mut usize) { + install_stack_guard(stack_limit); + // TODO: irq priorities +} + +/// Multicore execution management. +pub struct Multicore<'p> { + cores: [Core<'p>; 2], +} + +/// Data type for a properly aligned stack of N 32-bit (usize) words +#[repr(C, align(32))] +pub struct Stack { + /// Memory to be used for the stack + mem: UnsafeCell<[usize; SIZE]>, + taken: Cell, +} + +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} + +// Safety: Only one thread can `take` access to contents of the +// struct, guarded by a critical section. +unsafe impl Sync for Stack {} + +impl Stack { + /// Construct a stack of length SIZE, initialized to 0 + /// + /// The minimum allowed SIZE is 64 bytes, but most programs + /// will need a significantly larger stack. + pub const fn new() -> Stack { + const { assert!(SIZE >= 64, "Stack too small") }; + Stack { + mem: UnsafeCell::new([0; SIZE]), + taken: Cell::new(false), + } + } + + /// Take the StackAllocation out of this Stack. + /// + /// This returns None if the stack is already taken. + pub fn take(&self) -> Option { + let taken = critical_section::with(|_| self.taken.replace(true)); + if taken { + None + } else { + // Safety: We know the size of this allocation + unsafe { + let start = self.mem.get() as *mut usize; + let end = start.add(SIZE); + Some(StackAllocation::from_raw_parts(start, end)) + } + } + } + + /// Reset the taken flag of the stack area + /// + /// # Safety + /// + /// The caller must ensure that the stack is no longer in use, eg. because + /// the core that used it was reset. This method doesn't do any synchronization + /// so it must not be called from multiple threads concurrently. + pub unsafe fn reset(&self) { + self.taken.replace(false); + } +} + +/// This object represents a memory area which can be used for a stack. +/// +/// It is essentially a range of pointers with these additional guarantees: +/// The begin / end pointers must define a stack +/// with proper alignment (at least 8 bytes, preferably 32 bytes) +/// and sufficient size (64 bytes would be sound but much too little for +/// most real-world workloads). The underlying memory must +/// have a static lifetime and must be owned by the object exclusively. +/// No mutable references to that memory must exist. +/// Therefore, a function that gets passed such an object is free to write +/// to arbitrary memory locations in the range. +pub struct StackAllocation { + /// Start and end pointer of the StackAllocation as a Range + mem: Range<*mut usize>, +} + +impl StackAllocation { + fn get(&self) -> Range<*mut usize> { + self.mem.clone() + } + + /// Unsafely construct a stack allocation + /// + /// This is mainly useful to construct a stack allocation in some memory region + /// defined in a linker script, for example to place the stack in the SRAM4/5 regions. + /// + /// # Safety + /// + /// The caller must ensure that the guarantees that a StackAllocation provides + /// are upheld. + pub unsafe fn from_raw_parts(start: *mut usize, end: *mut usize) -> Self { + StackAllocation { mem: start..end } + } +} + +impl From<&Stack> for Option { + fn from(stack: &Stack) -> Self { + let taken = critical_section::with(|_| stack.taken.replace(true)); + if taken { + None + } else { + // Safety: We know the size of this allocation + unsafe { + let start = stack.mem.get() as *mut usize; + let end = start.add(SIZE); + Some(StackAllocation::from_raw_parts(start, end)) + } + } + } +} + +impl<'p> Multicore<'p> { + /// Create a new |Multicore| instance. + pub fn new( + psm: &'p mut pac::PSM, + ppb: &'p mut pac::PPB, + sio: &'p mut crate::sio::SioFifo, + ) -> Self { + Self { + cores: [ + Core { inner: None }, + Core { + inner: Some((psm, ppb, sio)), + }, + ], + } + } + + /// Get the available |Core| instances. + pub fn cores(&mut self) -> &'p mut [Core] { + &mut self.cores + } +} + +/// A handle for controlling a logical core. +pub struct Core<'p> { + inner: Option<( + &'p mut pac::PSM, + &'p mut pac::PPB, + &'p mut crate::sio::SioFifo, + )>, +} + +impl Core<'_> { + /// Get the id of this core. + pub fn id(&self) -> u8 { + match self.inner { + None => 0, + Some(..) => 1, + } + } + + /// Spawn a function on this core. + /// + /// The closure should not return. It is currently defined as `-> ()` because `-> !` is not yet + /// stable. + /// + /// Core 1 will be reset from core 0 in order to spawn another task. + /// + /// Resetting a single core of a running program can have undesired consequences. Deadlocks are + /// likely if the core being reset happens to be inside a critical section. + /// It may even break safety assumptions of some unsafe code. So, be careful when calling this method + /// more than once. + pub fn spawn(&mut self, stack: StackAllocation, entry: F) -> Result<(), Error> + where + F: FnOnce() + Send + 'static, + { + if let Some((psm, ppb, fifo)) = self.inner.as_mut() { + // 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( + _: u64, + _: u64, + entry: *mut ManuallyDrop, + stack_limit: *mut usize, + ) -> ! { + core1_setup(stack_limit); + + let entry = unsafe { ManuallyDrop::take(&mut *entry) }; + + // make sure the preceding read doesn't get reordered past the following fifo write + compiler_fence(Ordering::SeqCst); + + // Signal that it's safe for core 0 to get rid of the original value now. + // + // We don't have any way to get at core 1's SIO without using `Peripherals::steal` right now, + // since svd2rust doesn't really support multiple cores properly. + let peripherals = unsafe { pac::Peripherals::steal() }; + let mut sio = Sio::new(peripherals.SIO); + sio.fifo.write_blocking(1); + + entry(); + loop { + cortex_m::asm::wfe() + } + } + + // Reset the core + // TODO: resetting without prior check that the core is actually stowed is not great. + // But there does not seem to be any obvious way to check that. A marker flag could be + // set from this method and cleared for the wrapper after `entry` returned. But doing + // so wouldn't be zero cost. + psm.frce_off().modify(|_, w| w.proc1().set_bit()); + while !psm.frce_off().read().proc1().bit_is_set() { + cortex_m::asm::nop(); + } + psm.frce_off().modify(|_, w| w.proc1().clear_bit()); + + // Set up the stack + // AAPCS requires in 6.2.1.2 that the stack is 8bytes aligned., we may need to trim the + // array size to guaranty that the base of the stack (the end of the array) meets that requirement. + // The start of the array does not need to be aligned. + + let stack = stack.get(); + let mut stack_ptr = stack.end; + // on rp2040, usize are 4 bytes, so align_offset(8) on a *mut usize returns either 0 or 1. + let misalignment_offset = stack_ptr.align_offset(8); + + // 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 { + stack_ptr = stack_ptr.sub(misalignment_offset); + + // Push `stack_limit`. + stack_ptr = stack_ptr.sub(1); + stack_ptr.cast::<*mut usize>().write(stack.start); + + // 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 vector_table = ppb.vtor().read().bits(); + + // 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_blocking(cmd); + let response = fifo.read_blocking(); + 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, + // so we have to drop it ourselves. + drop(ManuallyDrop::into_inner(entry)); + return Err(Error::Unresponsive); + } + } + if seq >= cmd_seq.len() { + break; + } + } + + // Wait until the other core has copied `entry` before returning. + fifo.read_blocking(); + + Ok(()) + } else { + Err(Error::InvalidCore) + } + } +} diff --git a/rp-hal/rp2040-hal/src/pio.rs b/rp-hal/rp2040-hal/src/pio.rs new file mode 100644 index 0000000..a2900e6 --- /dev/null +++ b/rp-hal/rp2040-hal/src/pio.rs @@ -0,0 +1,2298 @@ +//! Programmable IO (PIO) +//! +//! See [Chapter 3 of the datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf#section_pio) for more details. + +use core::ops::Deref; +use pio::{Instruction, InstructionOperands, Program, SideSet, Wrap}; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + dma::{EndlessReadTarget, EndlessWriteTarget, ReadTarget, TransferSize, Word, WriteTarget}, + gpio::{Function, FunctionPio0, FunctionPio1}, + pac::{self, dma::ch::ch_ctrl_trig::TREQ_SEL_A, pio0::RegisterBlock, PIO0, PIO1}, + resets::SubsystemReset, + typelevel::Sealed, +}; + +const PIO_INSTRUCTION_COUNT: usize = 32; + +impl Sealed for PIO0 {} +impl Sealed for PIO1 {} + +/// PIO Instance +pub trait PIOExt: Deref + SubsystemReset + Sized + Send + Sealed { + /// Associated Pin Function. + type PinFunction: Function; + + /// Create a new PIO wrapper and split the state machines into individual objects. + #[allow(clippy::type_complexity)] // Required for symmetry with PIO::free(). + fn split( + self, + resets: &mut crate::pac::RESETS, + ) -> ( + PIO, + UninitStateMachine<(Self, SM0)>, + UninitStateMachine<(Self, SM1)>, + UninitStateMachine<(Self, SM2)>, + UninitStateMachine<(Self, SM3)>, + ) { + self.reset_bring_down(resets); + self.reset_bring_up(resets); + + let sm0 = UninitStateMachine { + block: self.deref(), + sm: self.sm(0), + _phantom: core::marker::PhantomData, + }; + let sm1 = UninitStateMachine { + block: self.deref(), + sm: self.sm(1), + _phantom: core::marker::PhantomData, + }; + let sm2 = UninitStateMachine { + block: self.deref(), + sm: self.sm(2), + _phantom: core::marker::PhantomData, + }; + let sm3 = UninitStateMachine { + block: self.deref(), + sm: self.sm(3), + _phantom: core::marker::PhantomData, + }; + ( + PIO { + used_instruction_space: 0, + pio: self, + }, + sm0, + sm1, + sm2, + sm3, + ) + } + + /// Number of this PIO (0..1). + fn id() -> usize; +} + +impl PIOExt for PIO0 { + type PinFunction = FunctionPio0; + fn id() -> usize { + 0 + } +} +impl PIOExt for PIO1 { + type PinFunction = FunctionPio1; + fn id() -> usize { + 1 + } +} + +#[allow(clippy::upper_case_acronyms)] +/// Programmable IO Block +pub struct PIO { + used_instruction_space: u32, // bit for each PIO_INSTRUCTION_COUNT + pio: P, +} + +impl core::fmt::Debug for PIO

{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("PIO") + .field("used_instruction_space", &self.used_instruction_space) + .field("pio", &"PIO { .. }") + .finish() + } +} + +// Safety: `PIO` only provides access to those registers which are not directly used by +// `StateMachine`. +unsafe impl Send for PIO

{} + +// Safety: `PIO` is marked Send so ensure all accesses remain atomic and no new concurrent accesses +// are added. +impl PIO

{ + /// Free this instance. + /// + /// All output pins are left in their current state. + pub fn free( + self, + _sm0: UninitStateMachine<(P, SM0)>, + _sm1: UninitStateMachine<(P, SM1)>, + _sm2: UninitStateMachine<(P, SM2)>, + _sm3: UninitStateMachine<(P, SM3)>, + ) -> P { + // All state machines have already been stopped. + self.pio + } + + /// This PIO's interrupt by index. + pub fn irq(&self) -> Interrupt<'_, P, IRQ> { + struct IRQSanity; + impl IRQSanity { + const CHECK: () = assert!(IRQ <= 1, "IRQ index must be either 0 or 1"); + } + + #[allow(clippy::let_unit_value)] + let _ = IRQSanity::::CHECK; + Interrupt { + block: self.pio.deref(), + _phantom: core::marker::PhantomData, + } + } + + /// This PIO's IRQ0 interrupt. + pub fn irq0(&self) -> Interrupt<'_, P, 0> { + self.irq() + } + + /// This PIO's IRQ1 interrupt. + pub fn irq1(&self) -> Interrupt<'_, P, 1> { + self.irq() + } + + /// Get raw irq flags. + /// + /// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of + /// the IRQ flags. + pub fn get_irq_raw(&self) -> u8 { + self.pio.irq().read().irq().bits() + } + + /// Clear PIO's IRQ flags indicated by the bits. + /// + /// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of + /// the IRQ flags. + // Safety: PIOExt provides exclusive access to the pio.irq register, this must be preserved to + // satisfy Send trait. + pub fn clear_irq(&self, flags: u8) { + self.pio.irq().write(|w| unsafe { w.irq().bits(flags) }); + } + + /// Force PIO's IRQ flags indicated by the bits. + /// + /// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of + /// the IRQ flags. + // Safety: PIOExt provides exclusive access to the pio.irq register, this must be preserved to + // satisfy Send trait. + pub fn force_irq(&self, flags: u8) { + self.pio + .irq_force() + .write(|w| unsafe { w.irq_force().bits(flags) }); + } + + /// Calculates a mask with the `len` right-most bits set. + fn instruction_mask(len: usize) -> u32 { + if len < 32 { + (1 << len) - 1 + } else { + 0xffffffff + } + } + + /// Tries to find an appropriate offset for the instructions, in range 0..=31. + fn find_offset_for_instructions(&self, i: &[u16], origin: Option) -> Option { + if i.len() > PIO_INSTRUCTION_COUNT || i.is_empty() { + None + } else { + let mask = Self::instruction_mask(i.len()); + if let Some(origin) = origin { + if origin as usize > PIO_INSTRUCTION_COUNT - i.len() + || self.used_instruction_space & (mask << origin) != 0 + { + None + } else { + Some(origin) + } + } else { + for i in (0..=32 - (i.len() as u8)).rev() { + if self.used_instruction_space & (mask << i) == 0 { + return Some(i); + } + } + None + } + } + } + + /// Allocates space in instruction memory and installs the program. + /// + /// The function returns a handle to the installed program that can be used to configure a + /// `StateMachine` via `PIOBuilder`. The program can be uninstalled to free instruction memory + /// via `uninstall()` once the state machine using the program has been uninitialized. + // Safety: PIOExt is marked send and should be the only object allowed to access pio.instr_mem + pub fn install( + &mut self, + p: &Program<{ pio::RP2040_MAX_PROGRAM_SIZE }>, + ) -> Result, InstallError> { + if let Some(offset) = self.find_offset_for_instructions(&p.code, p.origin) { + p.code + .iter() + .cloned() + .map(|instr| { + if instr & 0b1110_0000_0000_0000 == 0 { + // this is a JMP instruction -> add offset to address + let address = (instr & 0b11111) as u8; + let address = address + 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 { + // this is not a JMP instruction -> keep it unchanged + instr + } + }) + .enumerate() + .for_each(|(i, instr)| { + self.pio + .instr_mem(i + offset as usize) + .write(|w| unsafe { w.instr_mem0().bits(instr) }) + }); + self.used_instruction_space |= Self::instruction_mask(p.code.len()) << offset; + Ok(InstalledProgram { + offset, + length: p.code.len() as u8, + side_set: p.side_set, + wrap: p.wrap, + _phantom: core::marker::PhantomData, + }) + } else { + Err(InstallError::NoSpace) + } + } + + /// Removes the specified program from instruction memory, freeing the allocated space. + pub fn uninstall(&mut self, p: InstalledProgram

) { + let instr_mask = Self::instruction_mask(p.length as usize) << p.offset as u32; + self.used_instruction_space &= !instr_mask; + } +} + +/// Handle to a program that was placed in the PIO's instruction memory. +/// +/// Objects of this type can be reused for multiple state machines of the same PIO block to save +/// memory if multiple state machines are supposed to perform the same function (for example, if +/// one PIO block is used to implement multiple I2C busses). +/// +/// `PIO::uninstall(program)` can be used to free the space occupied by the program once it is no +/// longer used. +/// +/// # Examples +/// +/// ```no_run +/// use rp2040_hal::{pac, pio::PIOBuilder, pio::PIOExt}; +/// let mut peripherals = pac::Peripherals::take().unwrap(); +/// let (mut pio, sm0, _, _, _) = peripherals.PIO0.split(&mut peripherals.RESETS); +/// // Install a program in instruction memory. +/// let program = pio_proc::pio_asm!( +/// ".wrap_target", +/// "set pins, 1 [31]", +/// "set pins, 0 [31]", +/// ".wrap" +/// ).program; +/// let installed = pio.install(&program).unwrap(); +/// // Configure a state machine to use the program. +/// let (sm, rx, tx) = PIOBuilder::from_installed_program(installed).build(sm0); +/// // Uninitialize the state machine again, freeing the program. +/// let (sm, installed) = sm.uninit(rx, tx); +/// // Uninstall the program to free instruction memory. +/// pio.uninstall(installed); +/// ``` +/// +/// # Safety +/// +/// Objects of this type can outlive their `PIO` object. If the PIO block is reinitialized, the API +/// does not prevent the user from calling `uninstall()` when the PIO block does not actually hold +/// the program anymore. The user must therefore make sure that `uninstall()` is only called on the +/// PIO object which was used to install the program. +/// +/// ```ignore +/// let (mut pio, sm0, sm1, sm2, sm3) = pac.PIO0.split(&mut pac.RESETS); +/// // Install a program in instruction memory. +/// let installed = pio.install(&program).unwrap(); +/// // Reinitialize PIO. +/// let pio0 = pio.free(sm0, sm1, sm2, sm3); +/// let (mut pio, _, _, _, _) = pio0.split(&mut pac.RESETS); +/// // Do not do the following, the program is not in instruction memory anymore! +/// pio.uninstall(installed); +/// ``` +#[derive(Debug)] +pub struct InstalledProgram

{ + offset: u8, + length: u8, + side_set: SideSet, + wrap: Wrap, + _phantom: core::marker::PhantomData

, +} + +impl InstalledProgram

{ + /// Change the source and/or target for automatic program wrapping. + /// + /// This replaces the current wrap bounds with a new set. This can be useful if you are running + /// multiple state machines with the same program but using different wrap bounds. + /// + /// # Returns + /// + /// * [`Ok`] containing a new program with the provided wrap bounds + /// * [`Err`] containing the old program if the provided wrap was invalid (outside the bounds of + /// the program length) + pub fn set_wrap(self, wrap: Wrap) -> Result { + if wrap.source < self.length && wrap.target < self.length { + Ok(InstalledProgram { wrap, ..self }) + } else { + Err(self) + } + } + + /// Get the wrap target (entry point) of the installed program. + pub fn wrap_target(&self) -> u8 { + self.offset + self.wrap.target + } + + /// Get the offset the program is installed at. + pub fn offset(&self) -> u8 { + self.offset + } + + /// Clones this program handle so that it can be executed by two state machines at the same + /// time. + /// + /// # Safety + /// + /// This function is marked as unsafe because, once this function has been called, the + /// resulting handle can be used to call `PIO::uninstall()` while the program is still running. + /// + /// The user has to make sure to call `PIO::uninstall()` only once and only after all state + /// machines using the program have been uninitialized. + pub unsafe fn share(&self) -> InstalledProgram

{ + InstalledProgram { + offset: self.offset, + length: self.length, + side_set: self.side_set, + wrap: self.wrap, + _phantom: core::marker::PhantomData, + } + } +} + +/// State machine identifier (without a specified PIO block). +pub trait StateMachineIndex: Send + Sealed { + /// Numerical index of the state machine (0 to 3). + fn id() -> usize; +} + +/// First state machine. +pub struct SM0; +/// Second state machine. +pub struct SM1; +/// Third state machine. +pub struct SM2; +/// Fourth state machine. +pub struct SM3; + +impl StateMachineIndex for SM0 { + fn id() -> usize { + 0 + } +} + +impl Sealed for SM0 {} + +impl StateMachineIndex for SM1 { + fn id() -> usize { + 1 + } +} + +impl Sealed for SM1 {} + +impl StateMachineIndex for SM2 { + fn id() -> usize { + 2 + } +} + +impl Sealed for SM2 {} + +impl StateMachineIndex for SM3 { + fn id() -> usize { + 3 + } +} + +impl Sealed for SM3 {} + +/// Trait to identify a single state machine, as a generic type parameter to `UninitStateMachine`, +/// `InitStateMachine`, etc. +pub trait ValidStateMachine: Sealed { + /// The PIO block to which this state machine belongs. + type PIO: PIOExt; + + /// The index of this state machine (between 0 and 3). + fn id() -> usize; + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8; + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8; +} + +/// First state machine of the first PIO block. +pub type PIO0SM0 = (PIO0, SM0); +/// Second state machine of the first PIO block. +pub type PIO0SM1 = (PIO0, SM1); +/// Third state machine of the first PIO block. +pub type PIO0SM2 = (PIO0, SM2); +/// Fourth state machine of the first PIO block. +pub type PIO0SM3 = (PIO0, SM3); +/// First state machine of the second PIO block. +pub type PIO1SM0 = (PIO1, SM0); +/// Second state machine of the second PIO block. +pub type PIO1SM1 = (PIO1, SM1); +/// Third state machine of the second PIO block. +pub type PIO1SM2 = (PIO1, SM2); +/// Fourth state machine of the second PIO block. +pub type PIO1SM3 = (PIO1, SM3); + +impl ValidStateMachine for (P, SM) { + type PIO = P; + fn id() -> usize { + SM::id() + } + fn tx_dreq() -> u8 { + ((P::id() << 3) | SM::id()) as u8 + } + fn rx_dreq() -> u8 { + ((P::id() << 3) | SM::id() | 0x4) as u8 + } +} + +/// Pin State in the PIO +/// +/// Note the GPIO is able to override/invert that. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum PinState { + /// Pin in Low state. + High, + /// Pin in Low state. + Low, +} + +/// Pin direction in the PIO +/// +/// Note the GPIO is able to override/invert that. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum PinDir { + /// Pin set as an Input + Input, + /// Pin set as an Output. + Output, +} + +/// PIO State Machine (uninitialized, without a program). +#[derive(Debug)] +pub struct UninitStateMachine { + block: *const RegisterBlock, + sm: *const crate::pac::pio0::SM, + _phantom: core::marker::PhantomData, +} + +// Safety: `UninitStateMachine` only uses atomic accesses to shared registers. +unsafe impl Send for UninitStateMachine {} + +// Safety: `UninitStateMachine` is marked Send so ensure all accesses remain atomic and no new +// concurrent accesses are added. +impl UninitStateMachine { + /// Start and stop the state machine. + fn set_enabled(&mut self, enabled: bool) { + // Bits 3:0 are SM_ENABLE. + let mask = 1 << SM::id(); + if enabled { + self.set_ctrl_bits(mask); + } else { + self.clear_ctrl_bits(mask); + } + } + + fn restart(&mut self) { + // Bits 7:4 are SM_RESTART. + self.set_ctrl_bits(1 << (SM::id() + 4)); + } + + fn reset_clock(&mut self) { + // Bits 11:8 are CLKDIV_RESTART. + self.set_ctrl_bits(1 << (SM::id() + 8)); + } + + // Safety: All ctrl set access should go through this function to ensure atomic access. + fn set_ctrl_bits(&mut self, bits: u32) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_set((*self.block).ctrl().as_ptr(), bits); + } + } + + // Safety: All ctrl clear access should go through this function to ensure atomic access. + fn clear_ctrl_bits(&mut self, bits: u32) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_clear((*self.block).ctrl().as_ptr(), bits); + } + } + + // Safety: The Send trait assumes this is the only write to sm_clkdiv + fn set_clock_divisor(&self, int: u16, frac: u8) { + // Safety: This is the only write to this register + unsafe { + self.sm() + .sm_clkdiv() + .write(|w| w.int().bits(int).frac().bits(frac)); + } + } + + unsafe fn sm(&self) -> &crate::pac::pio0::SM { + &*self.sm + } + + unsafe fn pio(&self) -> &RegisterBlock { + &*self.block + } +} + +/// PIO State Machine with an associated program. +pub struct StateMachine { + sm: UninitStateMachine, + program: InstalledProgram, + _phantom: core::marker::PhantomData, +} + +/// Marker for an initialized, but stopped state machine. +pub struct Stopped; +/// Marker for an initialized and running state machine. +pub struct Running; + +/// Id for the PIO's IRQ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PioIRQ { + #[allow(missing_docs)] + Irq0, + #[allow(missing_docs)] + Irq1, +} +impl PioIRQ { + const fn to_index(self) -> usize { + match self { + PioIRQ::Irq0 => 0, + PioIRQ::Irq1 => 1, + } + } +} + +impl StateMachine { + /// Stops the state machine if it is still running and returns its program. + /// + /// The program can be uninstalled to free space once it is no longer used by any state + /// machine. + pub fn uninit( + mut self, + _rx: Rx, + _tx: Tx, + ) -> (UninitStateMachine, InstalledProgram) { + self.sm.set_enabled(false); + (self.sm, self.program) + } + + /// The address of the instruction currently being executed. + pub fn instruction_address(&self) -> u32 { + // Safety: Read only access without side effect + unsafe { self.sm.sm().sm_addr().read().bits() } + } + + #[deprecated(note = "Renamed to exec_instruction")] + /// Execute the instruction immediately. + pub fn set_instruction(&mut self, instruction: u16) { + let instruction = + Instruction::decode(instruction, self.program.side_set).expect("Invalid instruction"); + self.exec_instruction(instruction); + } + + /// Execute the instruction immediately. + /// + /// If an instruction written to INSTR stalls, it is stored in the same instruction latch used + /// by OUT EXEC and MOV EXEC, and will overwrite an in-progress instruction there. If EXEC + /// instructions are used, instructions written to INSTR must not stall. + pub fn exec_instruction(&mut self, instruction: Instruction) { + let instruction = instruction.encode(self.program.side_set); + + // Safety: all accesses to this register are controlled by this instance + unsafe { + self.sm + .sm() + .sm_instr() + .write(|w| w.sm0_instr().bits(instruction)) + } + } + + /// Check if the current instruction is stalled. + pub fn stalled(&self) -> bool { + // Safety: read only access without side effect + unsafe { self.sm.sm().sm_execctrl().read().exec_stalled().bit() } + } + + /// Clear both TX and RX FIFOs + pub fn clear_fifos(&mut self) { + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = &self.sm.sm(); + let sm_shiftctrl = &sm.sm_shiftctrl(); + let mut current = false; + // Toggling the FIFO join state clears the fifo + sm_shiftctrl.modify(|r, w| { + current = r.fjoin_rx().bit(); + w.fjoin_rx().bit(!current) + }); + sm_shiftctrl.modify(|_, w| w.fjoin_rx().bit(current)); + } + } + + /// Drain Tx fifo. + pub fn drain_tx_fifo(&mut self) { + // According to the datasheet 3.5.4.2 Page 358: + // + // When autopull is enabled, the behaviour of 'PULL' is altered: it becomes a no-op + // if the OSR is full. This is to avoid a race condition against the system + // DMA. It behaves as a fence: either an autopull has already taken place, in which case + // the 'PULL' has no effect, or the program will stall on the 'PULL' until data becomes + // available in the FIFO. + + // TODO: encode at compile time once pio 0.3.0 is out + const OUT: InstructionOperands = InstructionOperands::OUT { + destination: pio::OutDestination::NULL, + bit_count: 32, + }; + const PULL: InstructionOperands = InstructionOperands::PULL { + if_empty: false, + block: false, + }; + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = &self.sm.sm(); + let sm_pinctrl = &sm.sm_pinctrl(); + let sm_instr = &sm.sm_instr(); + let fstat = &self.sm.pio().fstat(); + + let operands = if sm.sm_shiftctrl().read().autopull().bit_is_set() { + OUT + } else { + PULL + } + .encode(); + + // Safety: sm0_instr may be accessed from SM::exec_instruction. + let mut saved_sideset_count = 0; + sm_pinctrl.modify(|r, w| { + saved_sideset_count = r.sideset_count().bits(); + w.sideset_count().bits(0) + }); + + let mask = 1 << SM::id(); + // white tx fifo is not empty + while (fstat.read().txempty().bits() & mask) == 0 { + sm_instr.write(|w| w.sm0_instr().bits(operands)) + } + + if saved_sideset_count != 0 { + sm_pinctrl.modify(|_, w| w.sideset_count().bits(saved_sideset_count)); + } + } + } + + /// Change the clock divider of a state machine. + /// + /// Changing the clock divider of a running state machine is allowed + /// and guaranteed to not cause any glitches, but the exact timing of + /// clock pulses during the change is not specified. + pub fn set_clock_divisor(&mut self, divisor: f32) { + // sm frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256) + let int = divisor as u16; + let frac = ((divisor - int as f32) * 256.0) as u8; + + self.sm.set_clock_divisor(int, frac); + } + + /// Change the clock divider of a state machine using a 16.8 fixed point value. + /// + /// Changing the clock divider of a running state machine is allowed + /// and guaranteed to not cause any glitches, but the exact timing of + /// clock pulses during the change is not specified. + pub fn clock_divisor_fixed_point(&mut self, int: u16, frac: u8) { + self.sm.set_clock_divisor(int, frac); + } +} + +// Safety: All shared register accesses are atomic. +unsafe impl Send for StateMachine {} + +// Safety: `StateMachine` is marked Send so ensure all accesses remain atomic and no new concurrent +// accesses are added. +impl StateMachine { + /// Starts execution of the selected program. + pub fn start(mut self) -> StateMachine { + // Enable SM + self.sm.set_enabled(true); + + StateMachine { + sm: self.sm, + program: self.program, + _phantom: core::marker::PhantomData, + } + } + + /// Sets the pin state for the specified pins. + /// + /// The user has to make sure that they do not select any pins that are in use by any + /// other state machines of the same PIO block. + /// + /// The iterator's item are pairs of `(pin_number, pin_state)`. + pub fn set_pins(&mut self, pins: impl IntoIterator) { + // TODO: turn those three into const once pio 0.3.0 is released + let set_high_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINS, + data: 1, + } + .encode(); + let set_low_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINS, + data: 0, + } + .encode(); + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_pinctrl = &sm.sm_pinctrl(); + let sm_execctrl = &sm.sm_execctrl(); + let sm_instr = &sm.sm_instr(); + + // sideset_count is implicitly set to 0 when the set_base/set_count are written (rather + // than modified) + let saved_pin_ctrl = sm_pinctrl.read().bits(); + let mut saved_execctrl = 0; + + sm_execctrl.modify(|r, w| { + saved_execctrl = r.bits(); + w.out_sticky().clear_bit() + }); + + for (pin_num, pin_state) in pins { + sm_pinctrl.write(|w| w.set_base().bits(pin_num).set_count().bits(1)); + let instruction = if pin_state == PinState::High { + set_high_instr + } else { + set_low_instr + }; + + sm_instr.write(|w| w.sm0_instr().bits(instruction)) + } + + sm_pinctrl.write(|w| w.bits(saved_pin_ctrl)); + sm_execctrl.write(|w| w.bits(saved_execctrl)); + } + } + + /// Set pin directions. + /// + /// The user has to make sure that they do not select any pins that are in use by any + /// other state machines of the same PIO block. + /// + /// The iterator's item are pairs of `(pin_number, pin_dir)`. + pub fn set_pindirs(&mut self, pindirs: impl IntoIterator) { + // TODO: turn those three into const once pio 0.3.0 is released + let set_output_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINDIRS, + data: 1, + } + .encode(); + let set_input_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINDIRS, + data: 0, + } + .encode(); + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_pinctrl = &sm.sm_pinctrl(); + let sm_execctrl = &sm.sm_execctrl(); + let sm_instr = &sm.sm_instr(); + + // sideset_count is implicitly set to 0 when the set_base/set_count are written (rather + // than modified) + let saved_pin_ctrl = sm_pinctrl.read().bits(); + let mut saved_execctrl = 0; + + sm_execctrl.modify(|r, w| { + saved_execctrl = r.bits(); + w.out_sticky().clear_bit() + }); + + for (pin_num, pin_dir) in pindirs { + sm_pinctrl.write(|w| w.set_base().bits(pin_num).set_count().bits(1)); + let instruction = if pin_dir == PinDir::Output { + set_output_instr + } else { + set_input_instr + }; + + sm_instr.write(|w| w.sm0_instr().bits(instruction)) + } + + sm_pinctrl.write(|w| w.bits(saved_pin_ctrl)); + sm_execctrl.write(|w| w.bits(saved_execctrl)); + } + } +} + +impl StateMachine<(P, SM), Stopped> { + /// Restarts the clock dividers for the specified state machines. + /// + /// As a result, the clock will be synchronous for the state machines, which is a precondition + /// for synchronous operation. + /// + /// The function returns an object that, once destructed, restarts the clock dividers. This + /// object allows further state machines to be added if more than two shall be synchronized. + /// + /// # Example + /// + /// ```ignore + /// sm0.synchronize_with(sm1).and_with(sm2); + /// ``` + pub fn synchronize_with<'sm, SM2: StateMachineIndex>( + &'sm mut self, + _other_sm: &'sm mut StateMachine<(P, SM2), Stopped>, + ) -> Synchronize<'sm, (P, SM)> { + let sm_mask = (1 << SM::id()) | (1 << SM2::id()); + Synchronize { sm: self, sm_mask } + } +} + +impl StateMachine<(P, SM), State> { + /// Create a group of state machines, which can be started/stopped synchronously + pub fn with( + self, + other_sm: StateMachine<(P, SM2), State>, + ) -> StateMachineGroup2 { + StateMachineGroup2 { + sm1: self, + sm2: other_sm, + } + } +} + +/// Group of 2 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup2< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, +} + +/// Group of 3 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup3< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, + sm3: StateMachine<(P, SM3Idx), State>, +} + +/// Group of 4 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup4< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, + sm3: StateMachine<(P, SM3Idx), State>, + sm4: StateMachine<(P, SM4Idx), State>, +} + +impl + StateMachineGroup2 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + ) { + (self.sm1, self.sm2) + } + + /// Add another state machine to the group + pub fn with( + self, + other_sm: StateMachine<(P, SM3Idx), State>, + ) -> StateMachineGroup3 { + StateMachineGroup3 { + sm1: self.sm1, + sm2: self.sm2, + sm3: other_sm, + } + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + State, + > StateMachineGroup3 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + StateMachine<(P, SM3Idx), State>, + ) { + (self.sm1, self.sm2, self.sm3) + } + + /// Add another state machine to the group + pub fn with( + self, + other_sm: StateMachine<(P, SM4Idx), State>, + ) -> StateMachineGroup4 { + StateMachineGroup4 { + sm1: self.sm1, + sm2: self.sm2, + sm3: self.sm3, + sm4: other_sm, + } + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) | (1 << SM3Idx::id()) + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + State, + > StateMachineGroup4 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + StateMachine<(P, SM3Idx), State>, + StateMachine<(P, SM4Idx), State>, + ) { + (self.sm1, self.sm2, self.sm3, self.sm4) + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) | (1 << SM3Idx::id()) | (1 << SM4Idx::id()) + } +} + +impl + StateMachineGroup2 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup2 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup2 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + > StateMachineGroup3 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup3 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup3 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + > StateMachineGroup4 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup4 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup4 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + sm4: StateMachine { + sm: self.sm4.sm, + program: self.sm4.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl + StateMachineGroup2 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup2 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup2 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + } + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + > StateMachineGroup3 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup3 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup3 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + } + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + > StateMachineGroup4 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup4 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup4 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + sm4: StateMachine { + sm: self.sm4.sm, + program: self.sm4.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +/// Type which, once destructed, restarts the clock dividers for all selected state machines, +/// effectively synchronizing them. +pub struct Synchronize<'sm, SM: ValidStateMachine> { + sm: &'sm mut StateMachine, + sm_mask: u32, +} + +impl<'sm, P: PIOExt, SM: StateMachineIndex> Synchronize<'sm, (P, SM)> { + /// Adds another state machine to be synchronized. + pub fn and_with( + mut self, + _other_sm: &'sm mut StateMachine<(P, SM2), Stopped>, + ) -> Self { + // Add another state machine index to the mask. + self.sm_mask |= 1 << SM2::id(); + self + } +} + +impl Drop for Synchronize<'_, SM> { + fn drop(&mut self) { + // Restart the clocks of all state machines specified by the mask. + // Bits 11:8 of CTRL contain CLKDIV_RESTART. + let sm_mask = self.sm_mask << 8; + self.sm.sm.set_ctrl_bits(sm_mask); + } +} + +impl StateMachine { + /// Stops execution of the selected program. + pub fn stop(mut self) -> StateMachine { + // Enable SM + self.sm.set_enabled(false); + + StateMachine { + sm: self.sm, + program: self.program, + _phantom: core::marker::PhantomData, + } + } + + /// Restarts the execution of the selected program from its wrap target. + pub fn restart(&mut self) { + // pause the state machine + self.sm.set_enabled(false); + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_pinctrl = &sm.sm_pinctrl(); + let sm_instr = &sm.sm_instr(); + + // save exec_ctrl & make side_set optional + let mut saved_sideset_count = 0; + sm_pinctrl.modify(|r, w| { + saved_sideset_count = r.sideset_count().bits(); + w.sideset_count().bits(0) + }); + + // revert it to its wrap target + let instruction = InstructionOperands::JMP { + condition: pio::JmpCondition::Always, + address: self.program.wrap_target(), + } + .encode(); + sm_instr.write(|w| w.sm0_instr().bits(instruction)); + + // restore exec_ctrl + if saved_sideset_count != 0 { + sm_pinctrl.modify(|_, w| w.sideset_count().bits(saved_sideset_count)); + } + + // clear osr/isr + self.sm.restart(); + } + + // unpause the state machine + self.sm.set_enabled(true); + } +} + +/// PIO RX FIFO handle. +pub struct Rx { + block: *const RegisterBlock, + _phantom: core::marker::PhantomData<(SM, RxSize)>, +} + +// Safety: All shared register accesses are atomic. +unsafe impl Send for Rx {} + +// Safety: `Rx` is marked Send so ensure all accesses remain atomic and no new concurrent accesses +// are added. +impl Rx { + unsafe fn block(&self) -> &pac::pio0::RegisterBlock { + &*self.block + } + + /// Gets the FIFO's address. + /// + /// This is useful if you want to DMA from this peripheral. + /// + /// NB: You are responsible for using the pointer correctly and not + /// underflowing the buffer. + pub fn fifo_address(&self) -> *const u32 { + // Safety: returning the address is safe as such. The user is responsible for any + // dereference ops at that address. + unsafe { self.block().rxf(SM::id()).as_ptr() } + } + + /// Gets the FIFO's `DREQ` value. + /// + /// This is a value between 0 and 39. Each FIFO on each state machine on + /// each PIO has a unique value. + pub fn dreq_value(&self) -> u8 { + if self.block as usize == 0x5020_0000usize { + TREQ_SEL_A::PIO0_RX0 as u8 + (SM::id() as u8) + } else { + TREQ_SEL_A::PIO1_RX0 as u8 + (SM::id() as u8) + } + } + + /// Get the next element from RX FIFO. + /// + /// Returns `None` if the FIFO is empty. + pub fn read(&mut self) -> Option { + if self.is_empty() { + return None; + } + + // Safety: The register is unique to this Rx instance. + Some(unsafe { core::ptr::read_volatile(self.fifo_address()) }) + } + + /// Enable/Disable the autopush feature of the state machine. + // Safety: This register is read by Rx, this is the only write. + pub fn enable_autopush(&mut self, enable: bool) { + // Safety: only instance reading/writing to autopush bit and no other write to this + // register + unsafe { + self.block() + .sm(SM::id()) + .sm_shiftctrl() + .modify(|_, w| w.autopush().bit(enable)) + } + } + + /// Indicate if the rx FIFO is empty + pub fn is_empty(&self) -> bool { + // Safety: Read only access without side effect + unsafe { self.block().fstat().read().rxempty().bits() & (1 << SM::id()) != 0 } + } + + /// Indicate if the rx FIFO is full + pub fn is_full(&self) -> bool { + // Safety: Read only access without side effect + unsafe { self.block().fstat().read().rxfull().bits() & (1 << SM::id()) != 0 } + } + + /// Enable RX FIFO not empty interrupt. + /// + /// This interrupt is raised when the RX FIFO is not empty, i.e. one could read more data from it. + pub fn enable_rx_not_empty_interrupt(&self, id: PioIRQ) { + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << SM::id(), + ); + } + } + + /// Disable RX FIFO not empty interrupt. + pub fn disable_rx_not_empty_interrupt(&self, id: PioIRQ) { + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << SM::id(), + ); + } + } + + /// Force RX FIFO not empty interrupt. + pub fn force_rx_not_empty_interrupt(&self, id: PioIRQ, state: bool) { + let action = if state { + write_bitmask_set + } else { + write_bitmask_clear + }; + // Safety: Atomic write to a single bit owned by this instance + unsafe { + action( + self.block().sm_irq(id.to_index()).irq_intf().as_ptr(), + 1 << SM::id(), + ); + } + } + + /// Set the transfer size used in DMA transfers. + pub fn transfer_size(self, size: RSZ) -> Rx { + let _ = size; + Rx { + block: self.block, + _phantom: core::marker::PhantomData, + } + } +} + +// Safety: This only reads from the state machine fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl ReadTarget for Rx { + type ReceivedWord = RxSize::Type; + + fn rx_treq() -> Option { + Some(SM::rx_dreq()) + } + + fn rx_address_count(&self) -> (u32, u32) { + ( + unsafe { &*self.block }.rxf(SM::id()).as_ptr() as u32, + u32::MAX, + ) + } + + fn rx_increment(&self) -> bool { + false + } +} + +impl EndlessReadTarget for Rx {} + +/// PIO TX FIFO handle. +pub struct Tx { + block: *const RegisterBlock, + _phantom: core::marker::PhantomData<(SM, TxSize)>, +} + +// Safety: All shared register accesses are atomic. +unsafe impl Send for Tx {} + +// Safety: `Tx` is marked Send so ensure all accesses remain atomic and no new concurrent accesses +// are added. +impl Tx { + unsafe fn block(&self) -> &pac::pio0::RegisterBlock { + &*self.block + } + + fn write_generic(&mut self, value: T) -> bool { + if !self.is_full() { + // Safety: Only accessed by this instance (unless DMA is used). + unsafe { + let reg_ptr = self.fifo_address() as *mut T; + reg_ptr.write_volatile(value); + } + true + } else { + false + } + } + + /// Gets the FIFO's address. + /// + /// This is useful if you want to DMA to this peripheral. + /// + /// NB: You are responsible for using the pointer correctly and not + /// overflowing the buffer. + pub fn fifo_address(&self) -> *const u32 { + // Safety: The only access to this register + unsafe { self.block().txf(SM::id()).as_ptr() } + } + + /// Gets the FIFO's `DREQ` value. + /// + /// This is a value between 0 and 39. Each FIFO on each state machine on + /// each PIO has a unique value. + pub fn dreq_value(&self) -> u8 { + if self.block as usize == 0x5020_0000usize { + TREQ_SEL_A::PIO0_TX0 as u8 + (SM::id() as u8) + } else { + TREQ_SEL_A::PIO1_TX0 as u8 + (SM::id() as u8) + } + } + + /// Write a u32 value to TX FIFO. + /// + /// Returns `true` if the value was written to FIFO, `false` otherwise. + pub fn write(&mut self, value: u32) -> bool { + self.write_generic(value) + } + + /// Write a replicated u8 value to TX FIFO. + /// + /// Memory mapped register writes that are smaller than 32bits will trigger + /// "Narrow IO Register Write" behaviour in RP2040 - the value written will + /// be replicated to the rest of the register as described in + /// [RP2040 Datasheet: 2.1.4. - Narrow IO Register Writes][section_2_1_4] + /// + /// + /// This 8bit write will set all 4 bytes of the FIFO to `value` + /// Eg: if you write `0xBA` the value written to the the FIFO will be + /// `0xBABABABA` + /// + /// If you wish to write an 8bit number without replication, + /// use `write(my_u8 as u32)` instead. + /// + /// Returns `true` if the value was written to FIFO, `false` otherwise. + /// + /// [section_2_1_4]: + pub fn write_u8_replicated(&mut self, value: u8) -> bool { + self.write_generic(value) + } + + /// Write a replicated 16bit value to TX FIFO. + /// + /// Memory mapped register writes that are smaller than 32bits will trigger + /// "Narrow IO Register Write" behaviour in RP2040 - the value written will + /// be replicated to the rest of the register as described in + /// [RP2040 Datasheet: 2.1.4. - Narrow IO Register Writes][section_2_1_4] + /// + /// This 16bit write will set both the upper and lower half of the FIFO entry to `value`. + /// + /// For example, if you write `0xC0DA` the value written to the FIFO will be + /// `0xC0DAC0DA` + /// + /// If you wish to write a 16bit number without replication, + /// use `write(my_u16 as u32)` instead. + /// + /// Returns `true` if the value was written to FIFO, `false` otherwise. + /// + /// [section_2_1_4]: + pub fn write_u16_replicated(&mut self, value: u16) -> bool { + self.write_generic(value) + } + + /// Checks if the state machine has stalled on empty TX FIFO during a blocking PULL, or an OUT + /// with autopull enabled. + /// + /// **Note this is a sticky flag and may not reflect the current state of the machine.** + pub fn has_stalled(&self) -> bool { + let mask = 1 << SM::id(); + // Safety: read-only access without side-effect + unsafe { self.block().fdebug().read().txstall().bits() & mask == mask } + } + + /// Clears the `tx_stalled` flag. + pub fn clear_stalled_flag(&self) { + let mask = 1 << SM::id(); + + // Safety: These bits are WC, only the one corresponding to this SM is set. + unsafe { + self.block().fdebug().write(|w| w.txstall().bits(mask)); + } + } + + /// Indicate if the tx FIFO is empty + pub fn is_empty(&self) -> bool { + // Safety: read-only access without side-effect + unsafe { self.block().fstat().read().txempty().bits() & (1 << SM::id()) != 0 } + } + + /// Indicate if the tx FIFO is full + pub fn is_full(&self) -> bool { + // Safety: read-only access without side-effect + unsafe { self.block().fstat().read().txfull().bits() & (1 << SM::id()) != 0 } + } + + /// Enable TX FIFO not full interrupt. + /// + /// This interrupt is raised when the TX FIFO is not full, i.e. one could push more data to it. + pub fn enable_tx_not_full_interrupt(&self, id: PioIRQ) { + // Safety: Atomic access to the register. Bit only modified by this Tx + unsafe { + write_bitmask_set( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << (SM::id() + 4), + ); + } + } + + /// Disable TX FIFO not full interrupt. + pub fn disable_tx_not_full_interrupt(&self, id: PioIRQ) { + // Safety: Atomic access to the register. Bit only modified by this Tx + unsafe { + write_bitmask_clear( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << (SM::id() + 4), + ); + } + } + + /// Force TX FIFO not full interrupt. + pub fn force_tx_not_full_interrupt(&self, id: PioIRQ) { + // Safety: Atomic access to the register. Bit only modified by this Tx + unsafe { + write_bitmask_set( + self.block().sm_irq(id.to_index()).irq_intf().as_ptr(), + 1 << (SM::id() + 4), + ); + } + } + + /// Set the transfer size used in DMA transfers. + pub fn transfer_size(self, size: RSZ) -> Tx { + let _ = size; + Tx { + block: self.block, + _phantom: core::marker::PhantomData, + } + } +} + +// Safety: This only writes to the state machine fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl WriteTarget for Tx { + type TransmittedWord = TxSize::Type; + + fn tx_treq() -> Option { + Some(SM::tx_dreq()) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + ( + unsafe { &*self.block }.txf(SM::id()).as_ptr() as u32, + u32::MAX, + ) + } + + fn tx_increment(&self) -> bool { + false + } +} + +impl EndlessWriteTarget for Tx {} + +/// PIO Interrupt controller. +#[derive(Debug)] +pub struct Interrupt<'a, P: PIOExt, const IRQ: usize> { + block: *const RegisterBlock, + _phantom: core::marker::PhantomData<&'a P>, +} + +// Safety: `Interrupt` provides exclusive access to interrupt registers. +unsafe impl Send for Interrupt<'_, P, IRQ> {} + +// Safety: `Interrupt` is marked Send so ensure all accesses remain atomic and no new concurrent +// accesses are added. +// `Interrupt` provides exclusive access to `irq_intf` to `irq_inte` for it's state machine, this +// must remain true to satisfy Send. +impl Interrupt<'_, P, IRQ> { + /// Enable interrupts raised by state machines. + /// + /// The PIO peripheral has 4 outside visible interrupts that can be raised by the state machines. Note that this + /// does not correspond with the state machine index; any state machine can raise any one of the four interrupts. + pub fn enable_sm_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_inte().as_ptr(), 1 << (id + 8)); + } + } + + /// Disable interrupts raised by state machines. + /// + /// See [`Self::enable_sm_interrupt`] for info about the index. + pub fn disable_sm_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear(self.irq().irq_inte().as_ptr(), 1 << (id + 8)); + } + } + + /// Force state machine interrupt. + /// + /// Note that this doesn't affect the state seen by the state machine. For that, see [`PIO::force_irq`]. + /// + /// + /// + /// See [`Self::enable_sm_interrupt`] for info about the index. + pub fn force_sm_interrupt(&self, id: u8, set: bool) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + if set { + write_bitmask_set(self.irq().irq_intf().as_ptr(), 1 << (id + 8)); + } else { + write_bitmask_clear(self.irq().irq_intf().as_ptr(), 1 << (id + 8)); + } + } + } + + /// Enable TX FIFO not full interrupt. + /// + /// Each of the 4 state machines have their own TX FIFO. This interrupt is raised when the TX FIFO is not full, i.e. + /// one could push more data to it. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn enable_tx_not_full_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_inte().as_ptr(), 1 << (id + 4)); + } + } + + /// Disable TX FIFO not full interrupt. + /// + /// See [`Self::enable_tx_not_full_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn disable_tx_not_full_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear(self.irq().irq_inte().as_ptr(), 1 << (id + 4)); + } + } + + /// Force TX FIFO not full interrupt. + /// + /// See [`Self::enable_tx_not_full_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn force_tx_not_full_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_intf().as_ptr(), 1 << (id + 4)); + } + } + + /// Enable RX FIFO not empty interrupt. + /// + /// Each of the 4 state machines have their own RX FIFO. This interrupt is raised when the RX FIFO is not empty, + /// i.e. one could read more data from it. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn enable_rx_not_empty_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_inte().as_ptr(), 1 << id); + } + } + + /// Disable RX FIFO not empty interrupt. + /// + /// See [`Self::enable_rx_not_empty_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn disable_rx_not_empty_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear(self.irq().irq_inte().as_ptr(), 1 << id); + } + } + + /// Force RX FIFO not empty interrupt. + /// + /// See [`Self::enable_rx_not_empty_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn force_rx_not_empty_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_intf().as_ptr(), 1 << id); + } + } + + /// Get the raw interrupt state. + /// + /// This is the state of the interrupts without interrupt masking and forcing. + pub fn raw(&self) -> InterruptState { + InterruptState( + // Safety: Read only access without side effect + unsafe { self.block().intr().read().bits() }, + ) + } + + /// Get the interrupt state. + /// + /// This is the state of the interrupts after interrupt masking and forcing. + pub fn state(&self) -> InterruptState { + InterruptState( + // Safety: Read only access without side effect + unsafe { self.irq().irq_ints().read().bits() }, + ) + } + + unsafe fn block(&self) -> &RegisterBlock { + &*self.block + } + + unsafe fn irq(&self) -> &crate::pac::pio0::SM_IRQ { + self.block().sm_irq(IRQ) + } +} + +/// Provides easy access for decoding PIO's interrupt state. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InterruptState(u32); + +macro_rules! raw_interrupt_accessor { + ($name:ident, $doc:literal, $idx:expr) => { + #[doc = concat!("Check whether interrupt ", $doc, " has been raised.")] + pub fn $name(self) -> bool { + self.0 & (1 << $idx) != 0 + } + }; +} +impl InterruptState { + raw_interrupt_accessor!(sm0_rx_not_empty, "SM0_RXNEMPTY", 0); + raw_interrupt_accessor!(sm1_rx_not_empty, "SM1_RXNEMPTY", 1); + raw_interrupt_accessor!(sm2_rx_not_empty, "SM2_RXNEMPTY", 2); + raw_interrupt_accessor!(sm3_rx_not_empty, "SM3_RXNEMPTY", 3); + + raw_interrupt_accessor!(sm0_tx_not_full, "SM0_TXNFULL", 4); + raw_interrupt_accessor!(sm1_tx_not_full, "SM1_TXNFULL", 5); + raw_interrupt_accessor!(sm2_tx_not_full, "SM2_TXNFULL", 6); + raw_interrupt_accessor!(sm3_tx_not_full, "SM3_TXNFULL", 7); + + raw_interrupt_accessor!(sm0, "SM0", 8); + raw_interrupt_accessor!(sm1, "SM1", 9); + raw_interrupt_accessor!(sm2, "SM2", 10); + raw_interrupt_accessor!(sm3, "SM3", 11); +} + +/// Comparison used for `mov x, status` instruction. +#[derive(Debug, Clone, Copy)] +pub enum MovStatusConfig { + /// The `mov x, status` instruction returns all ones if TX FIFO level is below the set status, otherwise all zeros. + Tx(u8), + /// The `mov x, status` instruction returns all ones if RX FIFO level is below the set status, otherwise all zeros. + Rx(u8), +} + +/// Shift direction for input and output shifting. +#[derive(Debug, Clone, Copy)] +pub enum ShiftDirection { + /// Shift register to left. + Left, + /// Shift register to right. + Right, +} + +impl ShiftDirection { + fn bit(self) -> bool { + match self { + Self::Left => false, + Self::Right => true, + } + } +} + +/// Builder to deploy a fully configured PIO program on one of the state +/// machines. +#[derive(Debug)] +pub struct PIOBuilder

{ + /// Clock divisor. + clock_divisor: (u16, u8), + + /// Program location and configuration. + program: InstalledProgram

, + /// GPIO pin used by `jmp pin` instruction. + jmp_pin: u8, + + /// Continuously assert the most recent OUT/SET to the pins. + out_sticky: bool, + /// Use a bit of OUT data as an auxiliary write enable. + /// + /// When [`out_sticky`](Self::out_sticky) is enabled, setting the bit to 0 deasserts for that instr. + inline_out: Option, + /// Config for `mov x, status` instruction. + mov_status: MovStatusConfig, + + /// Config for FIFO joining. + fifo_join: Buffers, + + /// Number of bits shifted out of `OSR` before autopull or conditional pull will take place. + pull_threshold: u8, + /// Number of bits shifted into `ISR` before autopush or conditional push will take place. + push_threshold: u8, + /// Shift direction for `OUT` instruction. + out_shiftdir: ShiftDirection, + /// Shift direction for `IN` instruction. + in_shiftdir: ShiftDirection, + /// Enable autopull. + autopull: bool, + /// Enable autopush. + autopush: bool, + + /// Number of pins asserted by a `SET`. + set_count: u8, + /// Number of pins asserted by an `OUT PINS`, `OUT PINDIRS` or `MOV PINS` instruction. + out_count: u8, + /// The first pin that is assigned in state machine's `IN` data bus. + in_base: u8, + /// The first pin that is affected by side-set operations. + side_set_base: u8, + /// The first pin that is affected by `SET PINS` or `SET PINDIRS` instructions. + set_base: u8, + /// The first pin that is affected by `OUT PINS`, `OUT PINDIRS` or `MOV PINS` instructions. + out_base: u8, +} + +/// Buffer sharing configuration. +#[derive(Debug, Clone, Copy)] +pub enum Buffers { + /// No sharing. + RxTx, + /// The memory of the RX FIFO is given to the TX FIFO to double its depth. + OnlyTx, + /// The memory of the TX FIFO is given to the RX FIFO to double its depth. + OnlyRx, +} + +/// Errors that occurred during `PIO::install`. +#[derive(Debug)] +pub enum InstallError { + /// There was not enough space for the instructions on the selected PIO. + NoSpace, +} + +impl PIOBuilder

{ + /// Set config settings based on information from the given [`InstalledProgram`]. + /// Additional configuration may be needed in addition to this. + /// + /// Note: This was formerly called `from_program`. The new function has + /// a different default shift direction, `ShiftDirection::Right`, matching + /// the hardware reset value. + pub fn from_installed_program(p: InstalledProgram

) -> Self { + PIOBuilder { + clock_divisor: (1, 0), + program: p, + jmp_pin: 0, + out_sticky: false, + inline_out: None, + mov_status: MovStatusConfig::Tx(0), + fifo_join: Buffers::RxTx, + pull_threshold: 0, + push_threshold: 0, + out_shiftdir: ShiftDirection::Right, + in_shiftdir: ShiftDirection::Right, + autopull: false, + autopush: false, + set_count: 5, + out_count: 0, + in_base: 0, + side_set_base: 0, + set_base: 0, + out_base: 0, + } + } + + /// Set config settings based on information from the given [`InstalledProgram`]. + /// Additional configuration may be needed in addition to this. + /// + /// Note: The shift direction for both input and output shift registers + /// defaults to `ShiftDirection::Left`, which is different from the + /// rp2040 reset value. The alternative [`Self::from_installed_program`], + /// fixes this. + #[deprecated( + note = "please use `from_installed_program` instead and update shift direction if necessary" + )] + pub fn from_program(p: InstalledProgram

) -> Self { + PIOBuilder { + clock_divisor: (1, 0), + program: p, + jmp_pin: 0, + out_sticky: false, + inline_out: None, + mov_status: MovStatusConfig::Tx(0), + fifo_join: Buffers::RxTx, + pull_threshold: 0, + push_threshold: 0, + out_shiftdir: ShiftDirection::Left, + in_shiftdir: ShiftDirection::Left, + autopull: false, + autopush: false, + set_count: 5, + out_count: 0, + in_base: 0, + side_set_base: 0, + set_base: 0, + out_base: 0, + } + } + + /// Set the config for when the status register is set to true. + /// + /// See `MovStatusConfig` for more info. + pub fn set_mov_status_config(mut self, mov_status: MovStatusConfig) -> Self { + self.mov_status = mov_status; + + self + } + + /// Set the pins asserted by `SET` instruction. + /// + /// The least-significant bit of `SET` instruction asserts the state of the pin indicated by `base`, the next bit + /// asserts the state of the next pin, and so on up to `count` pins. The pin numbers are considered modulo 32. + pub fn set_pins(mut self, base: u8, count: u8) -> Self { + assert!(count <= 5); + self.set_base = base; + self.set_count = count; + self + } + + /// Set the pins asserted by `OUT` instruction. + /// + /// The least-significant bit of `OUT` instruction asserts the state of the pin indicated by `base`, the next bit + /// asserts the state of the next pin, and so on up to `count` pins. The pin numbers are considered modulo 32. + pub fn out_pins(mut self, base: u8, count: u8) -> Self { + assert!(count <= 32); + self.out_base = base; + self.out_count = count; + self + } + + /// Set the pins used by `IN` instruction. + /// + /// The `IN` instruction reads the least significant bit from the pin indicated by `base`, the next bit from the + /// next pin, and so on. The pin numbers are considered modulo 32. + pub fn in_pin_base(mut self, base: u8) -> Self { + self.in_base = base; + self + } + + /// Set the pin used by `JMP PIN` instruction. + /// + /// When the pin set by this function is high, the jump is taken, otherwise not. + pub fn jmp_pin(mut self, pin: u8) -> Self { + self.jmp_pin = pin; + self + } + + /// Set the pins used by side-set instructions. + /// + /// The least-significant side-set bit asserts the state of the pin indicated by `base`, the next bit asserts the + /// state of the next pin, and so on up to [`pio::SideSet::bits()`] bits as configured in + /// [`pio::Program`]. + pub fn side_set_pin_base(mut self, base: u8) -> Self { + self.side_set_base = base; + self + } + // TODO: Update documentation above. + + /// Set buffer sharing. + /// + /// See [`Buffers`] for more information. + pub fn buffers(mut self, buffers: Buffers) -> Self { + self.fifo_join = buffers; + self + } + + /// Set the clock divisor. + /// + /// The is based on the sys_clk. Set 1 for full speed. A clock divisor of `n` will cause the state machine to run 1 + /// cycle every `n` clock cycles. For small values of `n`, a fractional divisor may introduce unacceptable jitter. + #[deprecated( + since = "0.7.0", + note = "Pulls in floating points. Use the fixed point alternative: clock_divisor_fixed_point" + )] + pub fn clock_divisor(mut self, divisor: f32) -> Self { + self.clock_divisor = (divisor as u16, (divisor * 256.0) as u8); + self + } + + /// The clock is based on the `sys_clk` and will execute an instruction every `int + (frac/256)` ticks. + /// + /// A clock divisor of `n` will cause the state machine to run 1 cycle every `n` clock cycles. If the integer part + /// is 0 then the fractional part must be 0. This is interpreted by the device as the integer 65536. + /// + /// For small values of `int`, a fractional divisor may introduce unacceptable jitter. + pub fn clock_divisor_fixed_point(mut self, int: u16, frac: u8) -> Self { + assert!(int != 0 || frac == 0); + self.clock_divisor = (int, frac); + self + } + + /// Set the output sticky state. + /// + /// When the output is set to be sticky, the PIO hardware continuously asserts the most recent `OUT`/`SET` to the + /// pins. + pub fn out_sticky(mut self, out_sticky: bool) -> Self { + self.out_sticky = out_sticky; + self + } + + /// Set the inline `OUT` enable bit. + /// + /// When set to value, the given bit of `OUT` instruction's data is used as an auxiliary write enable. When used + /// with [`Self::out_sticky`], writes with enable 0 will deassert the latest pin write. + pub fn inline_out(mut self, inline_out: Option) -> Self { + self.inline_out = inline_out; + self + } + + /// Set the autopush state. + /// + /// When autopush is enabled, the `IN` instruction automatically pushes the data once the number of bits reaches + /// threshold set by [`Self::push_threshold`]. + pub fn autopush(mut self, autopush: bool) -> Self { + self.autopush = autopush; + self + } + + /// Set the number of bits pushed into ISR before autopush or conditional push will take place. + pub fn push_threshold(mut self, threshold: u8) -> Self { + self.push_threshold = threshold; + self + } + + /// Set the autopull state. + /// + /// When autopull is enabled, the `OUT` instruction automatically pulls the data once the number of bits reaches + /// threshold set by [`Self::pull_threshold`]. + pub fn autopull(mut self, autopull: bool) -> Self { + self.autopull = autopull; + self + } + + /// Set the number of bits pulled from out of OSR before autopull or conditional pull will take place. + pub fn pull_threshold(mut self, threshold: u8) -> Self { + self.pull_threshold = threshold; + self + } + + /// Set the ISR shift direction for `IN` instruction. + /// + /// For example `ShiftDirection::Right` means that ISR is shifted to right, i.e. data enters from left. + pub fn in_shift_direction(mut self, direction: ShiftDirection) -> Self { + self.in_shiftdir = direction; + self + } + + /// Set the OSR shift direction for `OUT` instruction. + /// + /// For example `ShiftDirection::Right` means that OSR is shifted to right, i.e. data is taken from the right side. + pub fn out_shift_direction(mut self, direction: ShiftDirection) -> Self { + self.out_shiftdir = direction; + self + } + + /// Build the config and deploy it to a StateMachine. + #[allow(clippy::type_complexity)] // The return type cannot really be simplified. + pub fn build( + self, + mut sm: UninitStateMachine<(P, SM)>, + ) -> (StateMachine<(P, SM), Stopped>, Rx<(P, SM)>, Tx<(P, SM)>) { + let offset = self.program.offset; + + // Stop the SM + sm.set_enabled(false); + + // Write all configuration bits + sm.set_clock_divisor(self.clock_divisor.0, self.clock_divisor.1); + + // Safety: Only instance owning the SM + unsafe { + sm.sm().sm_execctrl().write(|w| { + w.side_en().bit(self.program.side_set.optional()); + w.side_pindir().bit(self.program.side_set.pindirs()); + + w.jmp_pin().bits(self.jmp_pin); + + if let Some(inline_out) = self.inline_out { + w.inline_out_en().bit(true); + w.out_en_sel().bits(inline_out); + } else { + w.inline_out_en().bit(false); + } + + w.out_sticky().bit(self.out_sticky); + + w.wrap_top().bits(offset + self.program.wrap.source); + w.wrap_bottom().bits(offset + self.program.wrap.target); + + let n = match self.mov_status { + MovStatusConfig::Tx(n) => { + w.status_sel().bit(false); + n + } + MovStatusConfig::Rx(n) => { + w.status_sel().bit(true); + n + } + }; + w.status_n().bits(n) + }); + + sm.sm().sm_shiftctrl().write(|w| { + let (fjoin_rx, fjoin_tx) = match self.fifo_join { + Buffers::RxTx => (false, false), + Buffers::OnlyTx => (false, true), + Buffers::OnlyRx => (true, false), + }; + w.fjoin_rx().bit(fjoin_rx); + w.fjoin_tx().bit(fjoin_tx); + + // TODO: Encode 32 as zero, and error on 0 + w.pull_thresh().bits(self.pull_threshold); + w.push_thresh().bits(self.push_threshold); + + w.out_shiftdir().bit(self.out_shiftdir.bit()); + w.in_shiftdir().bit(self.in_shiftdir.bit()); + + w.autopull().bit(self.autopull); + w.autopush().bit(self.autopush) + }); + + sm.sm().sm_pinctrl().write(|w| { + w.sideset_count().bits(self.program.side_set.bits()); + w.set_count().bits(self.set_count); + w.out_count().bits(self.out_count); + + w.in_base().bits(self.in_base); + w.sideset_base().bits(self.side_set_base); + w.set_base().bits(self.set_base); + w.out_base().bits(self.out_base) + }) + } + + // Restart SM and its clock + sm.restart(); + sm.reset_clock(); + + // Set starting location by forcing the state machine to execute a jmp + // to the beginning of the program we loaded in. + let instr = InstructionOperands::JMP { + condition: pio::JmpCondition::Always, + address: offset, + } + .encode(); + // Safety: Only instance owning the SM + unsafe { + sm.sm().sm_instr().write(|w| w.sm0_instr().bits(instr)); + } + + let rx = Rx { + block: sm.block, + _phantom: core::marker::PhantomData, + }; + let tx = Tx { + block: sm.block, + _phantom: core::marker::PhantomData, + }; + ( + StateMachine { + sm, + program: self.program, + _phantom: core::marker::PhantomData, + }, + rx, + tx, + ) + } +} diff --git a/rp-hal/rp2040-hal/src/pll.rs b/rp-hal/rp2040-hal/src/pll.rs new file mode 100644 index 0000000..786e2eb --- /dev/null +++ b/rp-hal/rp2040-hal/src/pll.rs @@ -0,0 +1,332 @@ +//! Phase-Locked Loops (PLL) +//! +//! See [Chapter 2 Section 18](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details. + +use core::{ + convert::Infallible, + marker::PhantomData, + ops::{Deref, Range, RangeInclusive}, +}; + +use fugit::{HertzU32, RateExtU32}; + +use nb::Error::WouldBlock; + +use crate::{clocks::ClocksManager, pac::RESETS, resets::SubsystemReset, typelevel::Sealed}; + +/// State of the PLL +pub trait State: Sealed {} + +/// PLL is disabled. +pub struct Disabled { + refdiv: u8, + fbdiv: u16, + post_div1: u8, + post_div2: u8, + frequency: HertzU32, +} + +/// PLL is configured, started and locking into its designated frequency. +pub struct Locking { + post_div1: u8, + post_div2: u8, + frequency: HertzU32, +} + +/// PLL is locked : it delivers a steady frequency. +pub struct Locked { + frequency: HertzU32, +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Locked {} +impl Sealed for Locked {} +impl State for Locking {} +impl Sealed for Locking {} + +/// Trait to handle both underlying devices from the PAC (PLL_SYS & PLL_USB) +pub trait PhaseLockedLoopDevice: + Deref + SubsystemReset + Sealed +{ +} + +impl PhaseLockedLoopDevice for crate::pac::PLL_SYS {} +impl Sealed for crate::pac::PLL_SYS {} +impl PhaseLockedLoopDevice for crate::pac::PLL_USB {} +impl Sealed for crate::pac::PLL_USB {} + +/// A PLL. +pub struct PhaseLockedLoop { + device: D, + state: S, +} + +impl PhaseLockedLoop { + fn transition(self, state: To) -> PhaseLockedLoop { + PhaseLockedLoop { + device: self.device, + state, + } + } + + /// Releases the underlying device. + pub fn free(self) -> D { + self.device + } +} + +/// Error type for the PLL module. +/// See Chapter 2, Section 18 §2 for details on constraints triggering these errors. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Proposed VCO frequency is out of range. + VcoFreqOutOfRange, + + /// Feedback Divider value is out of range. + FeedbackDivOutOfRange, + + /// Post Divider value is out of range. + PostDivOutOfRage, + + /// Reference Frequency is out of range. + RefFreqOutOfRange, + + /// Bad argument : overflows, bad conversion, ... + BadArgument, +} + +/// Parameters for a PLL. +pub struct PLLConfig { + /// Voltage Controlled Oscillator frequency. + pub vco_freq: HertzU32, + + /// Reference divider + pub refdiv: u8, + + /// Post Divider 1 + pub post_div1: u8, + + /// Post Divider 2 + pub post_div2: u8, +} + +/// Common configs for the two PLLs. Both assume the XOSC is cadenced at 12MHz ! +/// See Chapter 2, Section 18, §2 +pub mod common_configs { + use super::PLLConfig; + use fugit::HertzU32; + + /// Default, nominal configuration for PLL_SYS + pub const PLL_SYS_125MHZ: PLLConfig = PLLConfig { + vco_freq: HertzU32::MHz(1500), + refdiv: 1, + post_div1: 6, + post_div2: 2, + }; + + /// Default, nominal configuration for PLL_USB. + pub const PLL_USB_48MHZ: PLLConfig = PLLConfig { + vco_freq: HertzU32::MHz(1200), + refdiv: 1, + post_div1: 5, + post_div2: 5, + }; +} + +impl PhaseLockedLoop { + /// Instantiates a new Phase-Locked-Loop device. + pub fn new( + dev: D, + xosc_frequency: HertzU32, + config: PLLConfig, + ) -> Result, Error> { + const VCO_FREQ_RANGE: RangeInclusive = HertzU32::MHz(750)..=HertzU32::MHz(1_600); + const POSTDIV_RANGE: Range = 1..7; + const FBDIV_RANGE: Range = 16..320; + + let vco_freq = config.vco_freq; + + if !VCO_FREQ_RANGE.contains(&vco_freq) { + return Err(Error::VcoFreqOutOfRange); + } + + if !POSTDIV_RANGE.contains(&config.post_div1) || !POSTDIV_RANGE.contains(&config.post_div2) + { + return Err(Error::PostDivOutOfRage); + } + + let ref_freq_max_vco = (vco_freq.to_Hz() / 16).Hz(); + let ref_freq_range: Range = HertzU32::MHz(5)..ref_freq_max_vco; + + let ref_freq_hz: HertzU32 = xosc_frequency + .to_Hz() + .checked_div(u32::from(config.refdiv)) + .ok_or(Error::BadArgument)? + .Hz(); + + if !ref_freq_range.contains(&ref_freq_hz) { + return Err(Error::RefFreqOutOfRange); + } + + let fbdiv = vco_freq + .to_Hz() + .checked_div(ref_freq_hz.to_Hz()) + .ok_or(Error::BadArgument)?; + + let fbdiv: u16 = fbdiv.try_into().map_err(|_| Error::BadArgument)?; + + if !FBDIV_RANGE.contains(&fbdiv) { + return Err(Error::FeedbackDivOutOfRange); + } + + let refdiv = config.refdiv; + let post_div1 = config.post_div1; + let post_div2 = config.post_div2; + let frequency: HertzU32 = + (ref_freq_hz * u32::from(fbdiv)) / (u32::from(post_div1) * u32::from(post_div2)); + + Ok(PhaseLockedLoop { + state: Disabled { + refdiv, + fbdiv, + post_div1, + post_div2, + frequency, + }, + device: dev, + }) + } + + /// Configures and starts the PLL : it switches to Locking state. + pub fn initialize(self, resets: &mut crate::pac::RESETS) -> PhaseLockedLoop { + self.device.reset_bring_up(resets); + + // Turn off PLL in case it is already running + self.device.pwr().reset(); + self.device.fbdiv_int().reset(); + + self.device.cs().write(|w| unsafe { + w.refdiv().bits(self.state.refdiv); + w + }); + + self.device.fbdiv_int().write(|w| unsafe { + w.fbdiv_int().bits(self.state.fbdiv); + w + }); + + // Turn on PLL + self.device.pwr().modify(|_, w| { + w.pd().clear_bit(); + w.vcopd().clear_bit(); + w + }); + + let post_div1 = self.state.post_div1; + let post_div2 = self.state.post_div2; + let frequency = self.state.frequency; + + self.transition(Locking { + post_div1, + post_div2, + frequency, + }) + } +} + +/// A token that's given when the PLL is properly locked, so we can safely transition to the next state. +pub struct LockedPLLToken { + _private: PhantomData, +} + +impl PhaseLockedLoop { + /// Awaits locking of the PLL. + pub fn await_lock(&self) -> nb::Result, Infallible> { + if self.device.cs().read().lock().bit_is_clear() { + return Err(WouldBlock); + } + + Ok(LockedPLLToken { + _private: PhantomData, + }) + } + + /// Exchanges a token for a Locked PLL. + pub fn get_locked(self, _token: LockedPLLToken) -> PhaseLockedLoop { + // Set up post dividers + self.device.prim().write(|w| unsafe { + w.postdiv1().bits(self.state.post_div1); + w.postdiv2().bits(self.state.post_div2); + w + }); + + // Turn on post divider + self.device.pwr().modify(|_, w| { + w.postdivpd().clear_bit(); + w + }); + + let frequency = self.state.frequency; + + self.transition(Locked { frequency }) + } +} + +impl PhaseLockedLoop { + /// Get the operating frequency for the PLL + pub fn operating_frequency(&self) -> HertzU32 { + self.state.frequency + } + + /// Shut down the PLL. The returned PLL is configured the same as it was originally. + pub fn disable(self) -> PhaseLockedLoop { + let fbdiv = self.device.fbdiv_int().read().fbdiv_int().bits(); + let refdiv = self.device.cs().read().refdiv().bits(); + let prim = self.device.prim().read(); + let frequency = self.state.frequency; + + self.device.pwr().reset(); + self.device.fbdiv_int().reset(); + + self.transition(Disabled { + refdiv, + fbdiv, + post_div1: prim.postdiv1().bits(), + post_div2: prim.postdiv2().bits(), + frequency, + }) + } +} + +/// Blocking helper method to setup the PLL without going through all the steps. +pub fn setup_pll_blocking( + dev: D, + xosc_frequency: HertzU32, + config: PLLConfig, + clocks: &mut ClocksManager, + resets: &mut RESETS, +) -> Result, Error> { + // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. + nb::block!(clocks.system_clock.reset_source_await()).unwrap(); + + nb::block!(clocks.reference_clock.reset_source_await()).unwrap(); + + start_pll_blocking( + PhaseLockedLoop::new(dev, xosc_frequency.convert(), config)?, + resets, + ) +} + +/// Blocking helper method to (re)start a PLL. +pub fn start_pll_blocking( + disabled_pll: PhaseLockedLoop, + resets: &mut RESETS, +) -> Result, Error> { + let initialized_pll = disabled_pll.initialize(resets); + + let locked_pll_token = nb::block!(initialized_pll.await_lock()).unwrap(); + + Ok(initialized_pll.get_locked(locked_pll_token)) +} diff --git a/rp-hal/rp2040-hal/src/prelude.rs b/rp-hal/rp2040-hal/src/prelude.rs new file mode 100644 index 0000000..447c72b --- /dev/null +++ b/rp-hal/rp2040-hal/src/prelude.rs @@ -0,0 +1,3 @@ +//! Prelude +pub use crate::clocks::Clock as _rphal_clocks_Clock; +pub use crate::pio::PIOExt as _rphal_pio_PIOExt; diff --git a/rp-hal/rp2040-hal/src/pwm/dyn_slice.rs b/rp-hal/rp2040-hal/src/pwm/dyn_slice.rs new file mode 100644 index 0000000..5cf144e --- /dev/null +++ b/rp-hal/rp2040-hal/src/pwm/dyn_slice.rs @@ -0,0 +1,30 @@ +//! Semi-internal enums mostly used in typelevel magic + +/// Value-level `struct` representing slice IDs +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct DynSliceId { + /// Slice id + pub num: u8, +} + +/// Slice modes +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DynSliceMode { + /// Count continuously whenever the slice is enabled + FreeRunning, + /// Count continuously when a high level is detected on the B pin + InputHighRunning, + /// Count once with each rising edge detected on the B pin + CountRisingEdge, + /// Count once with each falling edge detected on the B pin + CountFallingEdge, +} + +/// Channel ids +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DynChannelId { + /// Channel A + A, + /// Channel B + B, +} diff --git a/rp-hal/rp2040-hal/src/pwm/mod.rs b/rp-hal/rp2040-hal/src/pwm/mod.rs new file mode 100644 index 0000000..0f71c56 --- /dev/null +++ b/rp-hal/rp2040-hal/src/pwm/mod.rs @@ -0,0 +1,1037 @@ +//! Pulse Width Modulation (PWM) +//! +//! First you must create a Slices struct which contains all the pwm slices. +//! +//! ```no_run +//! use rp2040_hal::{prelude::*, pwm::{InputHighRunning, Slices}}; +//! +//! +//! let mut pac = rp2040_pac::Peripherals::take().unwrap(); +//! +//! // Init PWMs +//! let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! +//! // Configure PWM4 +//! let mut pwm = pwm_slices.pwm4; +//! pwm.set_ph_correct(); +//! pwm.enable(); +//! +//! // Set to run when b channel is high +//! let pwm = pwm.into_mode::(); +//! ``` +//! +//! Once you have the PWM slice struct, you can add individual pins: +//! +//! ```no_run +//! # use rp2040_hal::{prelude::*, gpio::Pins, Sio, pwm::{InputHighRunning, Slices}}; +//! # let mut pac = rp2040_pac::Peripherals::take().unwrap(); +//! # let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! # let mut pwm = pwm_slices.pwm4.into_mode::(); +//! # let mut pac = rp2040_pac::Peripherals::take().unwrap(); +//! # +//! # let sio = Sio::new(pac.SIO); +//! # let pins = Pins::new( +//! # pac.IO_BANK0, +//! # pac.PADS_BANK0, +//! # sio.gpio_bank0, +//! # &mut pac.RESETS, +//! # ); +//! # +//! use embedded_hal::pwm::SetDutyCycle; +//! +//! // Use B channel (which inputs from GPIO 25) +//! let mut channel_b = pwm.channel_b; +//! let channel_pin_b = channel_b.input_from(pins.gpio25); +//! +//! // Use A channel (which outputs to GPIO 24) +//! let mut channel_a = pwm.channel_a; +//! let channel_pin_a = channel_a.output_to(pins.gpio24); +//! +//! // Set duty cycle +//! channel_a.set_duty_cycle(0x00ff); +//! let max_duty_cycle = channel_a.max_duty_cycle(); +//! channel_a.set_inverted(); // Invert the output +//! channel_a.clr_inverted(); // Don't invert the output +//! ``` +//! +//! The following configuration options are also available: +//! +//! ```no_run +//! # use rp2040_hal::{prelude::*, pwm::Slices}; +//! # let mut pac = rp2040_pac::Peripherals::take().unwrap(); +//! # let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! # let mut pwm = pwm_slices.pwm4; +//! pwm.set_ph_correct(); // Run in phase correct mode +//! pwm.clr_ph_correct(); // Don't run in phase correct mode +//! +//! pwm.set_div_int(1u8); // To set integer part of clock divider +//! pwm.set_div_frac(0u8); // To set fractional part of clock divider +//! +//! pwm.get_top(); // To get the TOP register +//! pwm.set_top(u16::MAX); // To set the TOP register +//! +//! ``` +//! +//! default_config() sets ph_correct to false, the clock divider to 1, does not invert the output, sets top to 65535, and resets the counter. +//! min_config() leaves those registers in the state they were before it was called (Careful, this can lead to unexpected behavior) +//! It's recommended to only call min_config() after calling default_config() on a pin that shares a PWM block. + +use core::convert::Infallible; +use core::marker::PhantomData; + +use embedded_dma::Word; +use embedded_hal::pwm::{ErrorType, SetDutyCycle}; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + dma::{EndlessWriteTarget, WriteTarget}, + gpio::{bank0::*, AnyPin, FunctionPwm, Pin, ValidFunction}, + pac::{self, dma::ch::ch_al1_ctrl::TREQ_SEL_A, PWM}, + resets::SubsystemReset, + typelevel::{Is, Sealed}, +}; + +pub mod dyn_slice; +pub use dyn_slice::*; + +mod reg; + +use reg::RegisterInterface; + +/// Used to pin traits to a specific channel (A or B) +pub trait ChannelId: Sealed { + /// Corresponding [`DynChannelId`] + const DYN: DynChannelId; +} + +/// Channel A +/// +/// These are attached to the even gpio pins and can only do PWM output +pub enum A {} + +/// Channel B +/// +/// These are attached to the odd gpio pins and can do PWM output and edge counting for input +pub enum B {} + +impl ChannelId for A { + const DYN: DynChannelId = DynChannelId::A; +} +impl ChannelId for B { + const DYN: DynChannelId = DynChannelId::B; +} +impl Sealed for A {} +impl Sealed for B {} + +/// Counter is free-running, and will count continuously whenever the slice is enabled +pub struct FreeRunning; +/// Count continuously when a high level is detected on the B pin +pub struct InputHighRunning; +/// Count once with each rising edge detected on the B pin +pub struct CountRisingEdge; +/// Count once with each falling edge detected on the B pin +pub struct CountFallingEdge; + +/// Type-level marker for tracking which slice modes are valid for which slices +pub trait ValidSliceMode: Sealed + SliceMode {} + +/// Type-level marker for tracking which slice modes are valid for which slices +pub trait ValidSliceInputMode: Sealed + ValidSliceMode {} + +/// Mode for slice +pub trait SliceMode: Sealed + Sized { + /// Corresponding [`DynSliceMode`] + const DYN: DynSliceMode; +} + +impl Sealed for FreeRunning {} +impl SliceMode for FreeRunning { + const DYN: DynSliceMode = DynSliceMode::FreeRunning; +} +impl Sealed for InputHighRunning {} +impl SliceMode for InputHighRunning { + const DYN: DynSliceMode = DynSliceMode::InputHighRunning; +} +impl Sealed for CountRisingEdge {} +impl SliceMode for CountRisingEdge { + const DYN: DynSliceMode = DynSliceMode::CountRisingEdge; +} +impl Sealed for CountFallingEdge {} +impl SliceMode for CountFallingEdge { + const DYN: DynSliceMode = DynSliceMode::CountFallingEdge; +} + +impl ValidSliceMode for FreeRunning {} +impl ValidSliceMode for InputHighRunning {} +impl ValidSliceMode for CountRisingEdge {} +impl ValidSliceMode for CountFallingEdge {} +impl ValidSliceInputMode for InputHighRunning {} +impl ValidSliceInputMode for CountRisingEdge {} +impl ValidSliceInputMode for CountFallingEdge {} + +//============================================================================== +// Slice IDs +//============================================================================== + +/// Type-level `enum` for slice IDs +pub trait SliceId: Sealed { + /// Corresponding [`DynSliceId`] + const DYN: DynSliceId; + /// [`SliceMode`] at reset + type Reset; + + /// Get DREQ number of PWM wrap. + const WRAP_DREQ: u8 = TREQ_SEL_A::PWM_WRAP0 as u8 + Self::DYN.num; +} + +macro_rules! slice_id { + ($Id:ident, $NUM:literal, $reset : ident) => { + $crate::paste::paste! { + #[doc = "Slice ID representing slice " $NUM] + pub enum $Id {} + impl Sealed for $Id {} + impl SliceId for $Id { + type Reset = $reset; + const DYN: DynSliceId = DynSliceId { num: $NUM }; + } + } + }; +} + +//============================================================================== +// AnySlice +//============================================================================== + +/// Type class for [`Slice`] types +/// +/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for +/// [`Slice`] types. See the `AnyKind` documentation for more details on the +/// pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +/// [type class]: crate::typelevel#type-classes +pub trait AnySlice +where + Self: Sealed, + Self: Is>, + ::Mode: ValidSliceMode<::Id>, +{ + /// [`SliceId`] of the corresponding [`Slice`] + type Id: SliceId; + /// [`SliceMode`] of the corresponding [`Slice`] + type Mode: SliceMode; +} + +impl Sealed for Slice +where + S: SliceId, + M: ValidSliceMode, +{ +} + +impl AnySlice for Slice +where + S: SliceId, + M: ValidSliceMode, +{ + type Id = S; + type Mode = M; +} + +/// Type alias to recover the specific [`Slice`] type from an implementation of +/// [`AnySlice`] +/// +/// See the [`AnyKind`] documentation for more details on the pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +type SpecificSlice = Slice<::Id, ::Mode>; + +//============================================================================== +// Registers +//============================================================================== + +/// Provide a safe register interface for [`Slice`]s +/// +/// This `struct` takes ownership of a [`SliceId`] and provides an API to +/// access the corresponding registers. +struct Registers { + id: PhantomData, +} + +// [`Registers`] takes ownership of the [`SliceId`], and [`Slice`] guarantees that +// each slice is a singleton, so this implementation is safe. +unsafe impl RegisterInterface for Registers { + #[inline] + fn id(&self) -> DynSliceId { + I::DYN + } +} + +impl Registers { + /// Create a new instance of [`Registers`] + /// + /// # Safety + /// + /// Users must never create two simultaneous instances of this `struct` with + /// the same [`SliceId`] + #[inline] + unsafe fn new() -> Self { + Registers { id: PhantomData } + } + + /// Provide a type-level equivalent for the + /// [`RegisterInterface::change_mode`] method. + #[inline] + fn change_mode>(&mut self) { + RegisterInterface::do_change_mode(self, M::DYN); + } +} + +/// Pwm slice +pub struct Slice +where + I: SliceId, + M: ValidSliceMode, +{ + regs: Registers, + mode: PhantomData, + /// Channel A (always output) + pub channel_a: Channel, + /// Channel B (input or output) + pub channel_b: Channel, +} + +impl Slice +where + I: SliceId, + M: ValidSliceMode, +{ + /// Create a new [`Slice`] + /// + /// # Safety + /// + /// Each [`Slice`] must be a singleton. For a given [`SliceId`], there must be + /// at most one corresponding [`Slice`] in existence at any given time. + /// Violating this requirement is `unsafe`. + #[inline] + pub(crate) unsafe fn new() -> Slice { + Slice { + regs: Registers::new(), + mode: PhantomData, + channel_a: Channel::new(0), + channel_b: Channel::new(0), + } + } + + /// Convert the slice to the requested [`SliceMode`] + #[inline] + pub fn into_mode>(mut self) -> Slice { + if N::DYN != M::DYN { + self.regs.change_mode::(); + } + // Safe because we drop the existing slice + unsafe { Slice::new() } + } + + /// Set a default config for the slice + pub fn default_config(&mut self) { + self.regs.write_ph_correct(false); + self.regs.write_div_int(1); // No divisor + self.regs.write_div_frac(0); // No divisor + self.regs.write_inv_a(false); //Don't invert the channel + self.regs.write_inv_b(false); //Don't invert the channel + self.regs.write_top(0xfffe); // Wrap at 0xfffe, so cc = 0xffff can indicate 100% duty cycle + self.regs.write_ctr(0x0000); //Reset the counter + self.regs.write_cc_a(0); //Default duty cycle of 0% + self.regs.write_cc_b(0); //Default duty cycle of 0% + } + + /// Advance the phase with one count + /// + /// Counter must be running at less than full speed (div_int + div_frac / 16 > 1) + #[inline] + pub fn advance_phase(&mut self) { + self.regs.advance_phase() + } + + /// Retard the phase with one count + /// + /// Counter must be running at less than full speed (div_int + div_frac / 16 > 1) + #[inline] + pub fn retard_phase(&mut self) { + self.regs.retard_phase() + } + + /// Enable phase correct mode + #[inline] + pub fn set_ph_correct(&mut self) { + self.regs.write_ph_correct(true) + } + + /// Disables phase correct mode + #[inline] + pub fn clr_ph_correct(&mut self) { + self.regs.write_ph_correct(false) + } + + /// Enable slice + #[inline] + pub fn enable(&mut self) { + self.regs.write_enable(true); + } + + /// Disable slice + #[inline] + pub fn disable(&mut self) { + self.regs.write_enable(false) + } + + /// Sets the integer part of the clock divider + #[inline] + pub fn set_div_int(&mut self, value: u8) { + self.regs.write_div_int(value) + } + + /// Sets the fractional part of the clock divider + #[inline] + pub fn set_div_frac(&mut self, value: u8) { + self.regs.write_div_frac(value) + } + + /// Get the counter register value + #[inline] + pub fn get_counter(&self) -> u16 { + self.regs.read_ctr() + } + + /// Set the counter register value + #[inline] + pub fn set_counter(&mut self, value: u16) { + self.regs.write_ctr(value) + } + + /// Get the top register value + #[inline] + pub fn get_top(&self) -> u16 { + self.regs.read_top() + } + + /// Sets the top register value + /// + /// Don't set this to 0xffff if you need true 100% duty cycle: + /// + /// The CC register, which is used to configure the duty cycle, + /// must be set to TOP + 1 for 100% duty cycle, but also is a + /// 16 bit register. + /// + /// In case you do set TOP to 0xffff, [`SetDutyCycle::set_duty_cycle`] + /// will slightly violate the trait's documentation, as + /// `SetDutyCycle::set_duty_cycle_fully_on` and other calls that + /// should lead to 100% duty cycle will only reach a duty cycle of + /// about 99.998%. + #[inline] + pub fn set_top(&mut self, value: u16) { + self.regs.write_top(value) + } + + /// Create the interrupt bitmask corresponding to this slice + #[inline] + fn bitmask(&self) -> u32 { + 1 << I::DYN.num + } + + /// Enable the PWM_IRQ_WRAP interrupt when this slice overflows. + #[inline] + pub fn enable_interrupt(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.inte().as_ptr(); + write_bitmask_set(reg, self.bitmask()); + } + } + + /// Disable the PWM_IRQ_WRAP interrupt for this slice. + #[inline] + pub fn disable_interrupt(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.inte().as_ptr(); + write_bitmask_clear(reg, self.bitmask()); + }; + } + + /// Did this slice trigger an overflow interrupt? + /// + /// This reports the raw interrupt flag, without considering masking or + /// forcing bits. It may return true even if the interrupt is disabled + /// or false even if the interrupt is forced. + #[inline] + pub fn has_overflown(&self) -> bool { + let mask = self.bitmask(); + unsafe { (*pac::PWM::ptr()).intr().read().bits() & mask == mask } + } + + /// Mark the interrupt handled for this slice. + #[inline] + pub fn clear_interrupt(&mut self) { + unsafe { (*pac::PWM::ptr()).intr().write(|w| w.bits(self.bitmask())) }; + } + + /// Force the interrupt. This bit is not cleared by hardware and must be manually cleared to + /// stop the interrupt from continuing to be asserted. + #[inline] + pub fn force_interrupt(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.intf().as_ptr(); + write_bitmask_set(reg, self.bitmask()); + } + } + + /// Clear force interrupt. This bit is not cleared by hardware and must be manually cleared to + /// stop the interrupt from continuing to be asserted. + #[inline] + pub fn clear_force_interrupt(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.intf().as_ptr(); + write_bitmask_clear(reg, self.bitmask()); + } + } +} + +macro_rules! pwm { + ($PWMX:ident, [ + $($SXi:ident: ($slice:literal, [$($pin_a:ident, $pin_b:ident),*], $i:expr)),+ + ]) => { + $( + slice_id!($SXi, $slice, FreeRunning); + + $( + impl ValidPwmOutputPin<$SXi, A> for $pin_a {} + impl ValidPwmOutputPin<$SXi, B> for $pin_b {} + impl ValidPwmInputPin<$SXi> for $pin_b {} + )* + )+ + + $crate::paste::paste!{ + + /// Collection of all the individual [`Slices`]s + pub struct Slices { + _pwm: $PWMX, + $( + #[doc = "Slice " $SXi] + pub [<$SXi:lower>] : Slice<$SXi,<$SXi as SliceId>::Reset>, + )+ + } + + impl Slices { + /// Take ownership of the PAC peripheral and split it into discrete [`Slice`]s + pub fn new(pwm: $PWMX, reset : &mut crate::pac::RESETS) -> Self { + pwm.reset_bring_up(reset); + unsafe { + Self { + _pwm: pwm, + $( + [<$SXi:lower>]: Slice::new(), + )+ + } + } + } + } + } + } +} + +pwm! { + PWM, [ + Pwm0: (0, [Gpio0, Gpio1, Gpio16, Gpio17], 0), + Pwm1: (1, [Gpio2, Gpio3, Gpio18, Gpio19], 1), + Pwm2: (2, [Gpio4, Gpio5, Gpio20, Gpio21], 2), + Pwm3: (3, [Gpio6, Gpio7, Gpio22, Gpio23], 3), + Pwm4: (4, [Gpio8, Gpio9, Gpio24, Gpio25], 4), + Pwm5: (5, [Gpio10, Gpio11, Gpio26, Gpio27], 5), + Pwm6: (6, [Gpio12, Gpio13, Gpio28, Gpio29], 6), + Pwm7: (7, [Gpio14, Gpio15], 7) + ] +} + +/// Marker trait for valid input pins (Channel B only) +pub trait ValidPwmInputPin: ValidFunction + Sealed {} +/// Marker trait for valid output pins +pub trait ValidPwmOutputPin: ValidFunction + Sealed {} + +impl Slices { + /// Free the pwm registers from the pwm hal struct while consuming it. + pub fn free(self) -> PWM { + self._pwm + } + + /// Enable multiple slices at the same time to make their counters sync up. + /// + /// You still need to call `slice` to get an actual slice + pub fn enable_simultaneous(&mut self, bits: u8) { + // Enable multiple slices at the same time + unsafe { + let reg = self._pwm.en().as_ptr(); + write_bitmask_set(reg, bits as u32); + } + } + + // /// Get pwm slice based on gpio pin + // pub fn borrow_mut_from_pin< + // S: SliceId, + // C: ChannelId, + // G: PinId + BankPinId + ValidPwmOutputPin, + // PM: PinMode + ValidPinMode, + // SM: ValidSliceMode, + // >(&mut self, _: &Pin) -> &mut Slice{ + // match S::DYN { + // DynSliceId{num} if num == 0 => &mut self.pwm0, + // DynSliceId{num} if num == 1 => &mut self.pwm1, + // DynSliceId{num} if num == 2 => &mut self.pwm2, + // DynSliceId{num} if num == 3 => &mut self.pwm3, + // DynSliceId{num} if num == 4 => &mut self.pwm4, + // DynSliceId{num} if num == 5 => &mut self.pwm5, + // DynSliceId{num} if num == 6 => &mut self.pwm6, + // DynSliceId{num} if num == 7 => &mut self.pwm7, + // _ => unreachable!() + // } + // } +} + +/// A Channel from the Pwm subsystem. +/// +/// Its attached to one of the eight slices and can be an A or B side channel +pub struct Channel { + regs: Registers, + slice_mode: PhantomData, + channel_id: PhantomData, + duty_cycle: u16, + enabled: bool, +} + +impl Channel { + pub(super) unsafe fn new(duty_cycle: u16) -> Self { + Channel { + regs: Registers::new(), + slice_mode: PhantomData, + channel_id: PhantomData, + duty_cycle, // stores the duty cycle while the channel is disabled + enabled: true, + } + } +} + +impl Sealed for Channel {} + +impl embedded_hal_0_2::PwmPin for Channel { + type Duty = u16; + + fn disable(&mut self) { + self.set_enabled(false); + } + + fn enable(&mut self) { + self.set_enabled(true); + } + + fn get_duty(&self) -> Self::Duty { + if self.enabled { + self.regs.read_cc_a() + } else { + self.duty_cycle + } + } + + fn get_max_duty(&self) -> Self::Duty { + SetDutyCycle::max_duty_cycle(self) + } + + fn set_duty(&mut self, duty: Self::Duty) { + let _ = SetDutyCycle::set_duty_cycle(self, duty); + } +} + +impl embedded_hal_0_2::PwmPin for Channel { + type Duty = u16; + + fn disable(&mut self) { + self.set_enabled(false); + } + + fn enable(&mut self) { + self.set_enabled(true); + } + + fn get_duty(&self) -> Self::Duty { + if self.enabled { + self.regs.read_cc_b() + } else { + self.duty_cycle + } + } + + fn get_max_duty(&self) -> Self::Duty { + SetDutyCycle::max_duty_cycle(self) + } + + fn set_duty(&mut self, duty: Self::Duty) { + let _ = SetDutyCycle::set_duty_cycle(self, duty); + } +} + +impl ErrorType for Channel { + type Error = Infallible; +} + +impl SetDutyCycle for Channel { + fn max_duty_cycle(&self) -> u16 { + self.regs.read_top().saturating_add(1) + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.duty_cycle = duty; + if self.enabled { + self.regs.write_cc_a(duty) + } + Ok(()) + } +} + +impl ErrorType for Channel { + type Error = Infallible; +} + +impl SetDutyCycle for Channel { + fn max_duty_cycle(&self) -> u16 { + self.regs.read_top().saturating_add(1) + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.duty_cycle = duty; + if self.enabled { + self.regs.write_cc_b(duty) + } + Ok(()) + } +} + +impl Channel { + /// Enable or disable the PWM channel + pub fn set_enabled(&mut self, enable: bool) { + if enable && !self.enabled { + // Restore the duty cycle. + self.regs.write_cc_a(self.duty_cycle); + self.enabled = true; + } else if !enable && self.enabled { + // We can't disable it without disturbing the other channel so this + // just sets the duty cycle to zero. + self.duty_cycle = self.regs.read_cc_a(); + self.regs.write_cc_a(0); + self.enabled = false; + } + } + + /// Capture a gpio pin and use it as pwm output for channel A + pub fn output_to(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmOutputPin, + { + pin.into().into_function() + } + + /// Invert channel output + #[inline] + pub fn set_inverted(&mut self) { + self.regs.write_inv_a(true) + } + + /// Stop inverting channel output + #[inline] + pub fn clr_inverted(&mut self) { + self.regs.write_inv_a(false) + } +} + +impl Channel { + /// Enable or disable the PWM channel + pub fn set_enabled(&mut self, enable: bool) { + if enable && !self.enabled { + // Restore the duty cycle. + self.regs.write_cc_b(self.duty_cycle); + self.enabled = true; + } else if !enable && self.enabled { + // We can't disable it without disturbing the other channel so this + // just sets the duty cycle to zero. + self.duty_cycle = self.regs.read_cc_b(); + self.regs.write_cc_b(0); + self.enabled = false; + } + } + + /// Capture a gpio pin and use it as pwm output for channel B + pub fn output_to(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmOutputPin, + { + pin.into().into_function() + } + + /// Invert channel output + #[inline] + pub fn set_inverted(&mut self) { + self.regs.write_inv_b(true) + } + + /// Stop inverting channel output + #[inline] + pub fn clr_inverted(&mut self) { + self.regs.write_inv_b(false) + } +} + +impl Channel +where + S::Mode: ValidSliceInputMode, +{ + /// Capture a gpio pin and use it as pwm input for channel B + pub fn input_from(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmInputPin, + { + pin.into().into_function() + } +} + +impl> Slice { + /// Capture a gpio pin and use it as pwm output + pub fn output_to(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmOutputPin, + { + pin.into().into_function() + } +} + +impl> Slice { + /// Capture a gpio pin and use it as pwm input for channel B + pub fn input_from(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmInputPin, + { + pin.into().into_function() + } +} + +/// Type representing DMA access to PWM cc register. +/// +/// Both channels are accessed together, because of narrow write replication. +/// +/// ```no_run +/// use cortex_m::singleton; +/// use rp2040_hal::dma::{double_buffer, DMAExt}; +/// use rp2040_hal::pwm::{CcFormat, SliceDmaWrite, Slices}; +/// +/// +/// let mut pac = rp2040_pac::Peripherals::take().unwrap(); +/// +/// // Init PWMs +/// let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +/// +/// // Configure PWM4 +/// let mut pwm = pwm_slices.pwm4; +/// pwm.enable(); +/// +/// let buf = singleton!(: [CcFormat; 4] = [CcFormat{a: 0x1000, b: 0x9000}; 4]).unwrap(); +/// let buf2 = singleton!(: [CcFormat; 4] = [CcFormat{a: 0xf000, b: 0x5000}; 4]).unwrap(); +/// +/// let dma = pac.DMA.split(&mut pac.RESETS); +/// +/// let dma_pwm = SliceDmaWrite::from(pwm); +/// +/// let dma_conf = double_buffer::Config::new((dma.ch0, dma.ch1), buf, dma_pwm.cc); +/// ``` +pub struct SliceDmaWriteCc> { + slice: PhantomData, + mode: PhantomData, +} + +/// Type representing DMA access to PWM top register. +/// +/// ```no_run +/// use cortex_m::{prelude::*, singleton}; +/// use rp2040_hal::dma::{double_buffer, DMAExt}; +/// use rp2040_hal::pwm::{SliceDmaWrite, Slices, TopFormat}; +/// +/// +/// let mut pac = rp2040_pac::Peripherals::take().unwrap(); +/// +/// // Init PWMs +/// let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +/// +/// // Configure PWM4 +/// let mut pwm = pwm_slices.pwm4; +/// pwm.enable(); +/// +/// // Just set to something mesurable. +/// pwm.channel_a.set_duty(0x1000); +/// pwm.channel_b.set_duty(0x1000); +/// +/// let buf = singleton!(: [TopFormat; 4] = [TopFormat::new(0x7fff); 4]).unwrap(); +/// let buf2 = singleton!(: [TopFormat; 4] = [TopFormat::new(0xfffe); 4]).unwrap(); +/// +/// let dma = pac.DMA.split(&mut pac.RESETS); +/// +/// // Reserve PWM slice for dma. +/// let dma_pwm = SliceDmaWrite::from(pwm); +/// +/// let dma_conf = double_buffer::Config::new((dma.ch0, dma.ch1), buf, dma_pwm.top); +/// ``` +pub struct SliceDmaWriteTop> { + slice: PhantomData, + mode: PhantomData, +} + +/// PWM slice while used for DMA writes. +/// ```no_run +/// use rp2040_hal::{prelude::*, pwm::{SliceDmaWrite, Slices}}; +/// +/// +/// let mut pac = rp2040_pac::Peripherals::take().unwrap(); +/// +/// // Init PWMs +/// let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +/// +/// // Configure PWM4 +/// let mut pwm = pwm_slices.pwm4; +/// pwm.enable(); +/// +/// // Use for DMA usage +/// let dma_pwm = SliceDmaWrite::from(pwm); +/// ``` +/// +pub struct SliceDmaWrite> { + /// Part for top writes. + pub top: SliceDmaWriteTop, + + /// Part for cc writes. + pub cc: SliceDmaWriteCc, + slice: Slice, +} + +impl> From> for SliceDmaWrite { + fn from(value: Slice) -> Self { + Self { + slice: value, + top: SliceDmaWriteTop { + slice: PhantomData, + mode: PhantomData, + }, + cc: SliceDmaWriteCc { + slice: PhantomData, + mode: PhantomData, + }, + } + } +} + +impl> From> for Slice { + fn from(value: SliceDmaWrite) -> Self { + value.slice + } +} + +/// Format for DMA transfers to PWM CC register. +#[derive(Clone, Copy, Eq, PartialEq)] +#[repr(C)] +#[repr(align(4))] +pub struct CcFormat { + /// CC register part for channel a. + pub a: u16, + /// CC register part for channel b. + pub b: u16, +} + +unsafe impl Word for CcFormat {} + +/// Format for DMA transfers to PWM TOP register. +/// +/// It is forbidden to use it as DMA write destination, +/// it is safe but it might not be compatible with a future use of reserved register fields. +#[derive(Clone, Copy, Eq)] +#[repr(C)] +#[repr(align(4))] +pub struct TopFormat { + /// Valid register part. + pub top: u16, + /// Reserved part. + /// Should always be zero + reserved: u16, +} + +impl PartialEq for TopFormat { + fn eq(&self, other: &TopFormat) -> bool { + self.top == other.top + } +} + +impl TopFormat { + /// Create a valid value. + pub fn new(top: u16) -> Self { + TopFormat { top, reserved: 0 } + } +} + +impl Default for TopFormat { + fn default() -> Self { + Self::new(u16::MAX) + } +} + +unsafe impl Word for TopFormat {} + +/// Safety: tx_address_count points to a register which is always a valid +/// write target. +unsafe impl> WriteTarget for SliceDmaWriteCc { + type TransmittedWord = CcFormat; + + fn tx_treq() -> Option { + Some(S::WRAP_DREQ) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + let regs = Registers { + id: PhantomData:: {}, + }; + (regs.ch().cc().as_ptr() as u32, u32::MAX) + } + + fn tx_increment(&self) -> bool { + false + } +} + +/// Safety: tx_address_count points to a register which is always a valid +/// write target. +unsafe impl> WriteTarget for SliceDmaWriteTop { + type TransmittedWord = TopFormat; + + fn tx_treq() -> Option { + Some(S::WRAP_DREQ) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + let regs = Registers { + id: PhantomData:: {}, + }; + (regs.ch().top().as_ptr() as u32, u32::MAX) + } + + fn tx_increment(&self) -> bool { + false + } +} + +impl> EndlessWriteTarget for SliceDmaWriteCc {} +impl> EndlessWriteTarget for SliceDmaWriteTop {} diff --git a/rp-hal/rp2040-hal/src/pwm/reg.rs b/rp-hal/rp2040-hal/src/pwm/reg.rs new file mode 100644 index 0000000..20c2700 --- /dev/null +++ b/rp-hal/rp2040-hal/src/pwm/reg.rs @@ -0,0 +1,111 @@ +use crate::{ + pac::{self, pwm::CH}, + pwm::dyn_slice::{DynSliceId, DynSliceMode}, +}; + +/// # Safety +/// +/// Users should only implement the [`id`] function. No default function +/// implementations should be overridden. The implementing type must also have +/// "control" over the corresponding slice ID, i.e. it must guarantee that each +/// slice ID is a singleton +pub(super) unsafe trait RegisterInterface { + /// Provide a [`DynSliceId`] identifying the set of registers controlled by + /// this type. + fn id(&self) -> DynSliceId; + + #[inline] + fn ch(&self) -> &CH { + let num = self.id().num as usize; + unsafe { (*pac::PWM::ptr()).ch(num) } + } + + #[inline] + fn advance_phase(&mut self) { + self.ch().csr().modify(|_, w| w.ph_adv().set_bit()) + } + + #[inline] + fn retard_phase(&mut self) { + self.ch().csr().modify(|_, w| w.ph_ret().set_bit()) + } + + #[inline] + fn do_change_mode(&mut self, mode: DynSliceMode) { + self.ch().csr().modify(|_, w| match mode { + DynSliceMode::FreeRunning => w.divmode().div(), + DynSliceMode::InputHighRunning => w.divmode().level(), + DynSliceMode::CountRisingEdge => w.divmode().rise(), + DynSliceMode::CountFallingEdge => w.divmode().fall(), + }) + } + + #[inline] + fn write_inv_a(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.a_inv().bit(value)); + } + + #[inline] + fn write_inv_b(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.b_inv().bit(value)); + } + + #[inline] + fn write_ph_correct(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.ph_correct().bit(value)); + } + + #[inline] + fn write_enable(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.en().bit(value)); + } + + #[inline] + fn write_div_int(&mut self, value: u8) { + self.ch() + .div() + .modify(|_, w| unsafe { w.int().bits(value) }); + } + #[inline] + fn write_div_frac(&mut self, value: u8) { + self.ch() + .div() + .modify(|_, w| unsafe { w.frac().bits(value) }); + } + + #[inline] + fn write_ctr(&mut self, value: u16) { + self.ch().ctr().write(|w| unsafe { w.ctr().bits(value) }); + } + + #[inline] + fn read_ctr(&self) -> u16 { + self.ch().ctr().read().ctr().bits() + } + #[inline] + fn write_cc_a(&mut self, value: u16) { + self.ch().cc().modify(|_, w| unsafe { w.a().bits(value) }); + } + #[inline] + fn read_cc_a(&self) -> u16 { + self.ch().cc().read().a().bits() + } + + #[inline] + fn write_cc_b(&mut self, value: u16) { + self.ch().cc().modify(|_, w| unsafe { w.b().bits(value) }); + } + + #[inline] + fn read_cc_b(&self) -> u16 { + self.ch().cc().read().b().bits() + } + #[inline] + fn write_top(&mut self, value: u16) { + self.ch().top().write(|w| unsafe { w.top().bits(value) }); + } + #[inline] + fn read_top(&self) -> u16 { + self.ch().top().read().top().bits() + } +} diff --git a/rp-hal/rp2040-hal/src/resets.rs b/rp-hal/rp2040-hal/src/resets.rs new file mode 100644 index 0000000..cb091d2 --- /dev/null +++ b/rp-hal/rp2040-hal/src/resets.rs @@ -0,0 +1,53 @@ +//! Subsystem Resets +//! +//! See [Chapter 2 Section 14](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details. + +mod private { + pub trait SubsystemReset { + fn reset_bring_up(&self, resets: &mut crate::pac::RESETS); + fn reset_bring_down(&self, resets: &mut crate::pac::RESETS); + } +} + +pub(crate) use private::SubsystemReset; + +macro_rules! generate_reset { + ($MODULE:ident, $module:ident) => { + impl SubsystemReset for $crate::pac::$MODULE { + fn reset_bring_up(&self, resets: &mut $crate::pac::RESETS) { + resets.reset().modify(|_, w| w.$module().clear_bit()); + while resets.reset_done().read().$module().bit_is_clear() {} + } + fn reset_bring_down(&self, resets: &mut $crate::pac::RESETS) { + resets.reset().modify(|_, w| w.$module().set_bit()); + } + } + }; +} + +// In datasheet order +generate_reset!(USBCTRL_REGS, usbctrl); +generate_reset!(UART1, uart1); +generate_reset!(UART0, uart0); +generate_reset!(TIMER, timer); +generate_reset!(TBMAN, tbman); +generate_reset!(SYSINFO, sysinfo); +generate_reset!(SYSCFG, syscfg); +generate_reset!(SPI1, spi1); +generate_reset!(SPI0, spi0); +generate_reset!(RTC, rtc); +generate_reset!(PWM, pwm); +generate_reset!(PLL_USB, pll_usb); +generate_reset!(PLL_SYS, pll_sys); +generate_reset!(PIO1, pio1); +generate_reset!(PIO0, pio0); +generate_reset!(PADS_QSPI, pads_qspi); +generate_reset!(PADS_BANK0, pads_bank0); +//generate_reset!(JTAG,jtag); // This doesn't seem to have an item in the pac +generate_reset!(IO_QSPI, io_qspi); +generate_reset!(IO_BANK0, io_bank0); +generate_reset!(I2C1, i2c1); +generate_reset!(I2C0, i2c0); +generate_reset!(DMA, dma); +generate_reset!(BUSCTRL, busctrl); +generate_reset!(ADC, adc); diff --git a/rp-hal/rp2040-hal/src/rom_data.rs b/rp-hal/rp2040-hal/src/rom_data.rs new file mode 100644 index 0000000..b4bb1d9 --- /dev/null +++ b/rp-hal/rp2040-hal/src/rom_data.rs @@ -0,0 +1,747 @@ +//! Functions and data from the RPI Bootrom. +//! +//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.3.1: +//! +//! > The Bootrom contains a number of public functions that provide useful +//! > RP2040 functionality that might be needed in the absence of any other code +//! > on the device, as well as highly optimized versions of certain key +//! > functionality that would otherwise have to take up space in most user +//! > binaries. + +#![allow(unknown_lints)] +#![allow(clippy::too_long_first_doc_paragraph)] + +/// A bootrom function table code. +pub type RomFnTableCode = [u8; 2]; + +/// This function searches for (table) +type RomTableLookupFn = unsafe extern "C" fn(*const u16, u32) -> T; + +/// The following addresses are described at `2.8.2. Bootrom Contents` +/// Pointer to the lookup table function supplied by the rom. +const ROM_TABLE_LOOKUP_PTR: *const u16 = 0x0000_0018 as _; + +/// Pointer to helper functions lookup table. +const FUNC_TABLE: *const u16 = 0x0000_0014 as _; + +/// Pointer to the public data lookup table. +const DATA_TABLE: *const u16 = 0x0000_0016 as _; + +/// Address of the version number of the ROM. +const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; + +/// Retrieve rom content from a table using a code. +fn rom_table_lookup(table: *const u16, tag: RomFnTableCode) -> T { + unsafe { + let rom_table_lookup_ptr: *const u32 = rom_hword_as_ptr(ROM_TABLE_LOOKUP_PTR); + let rom_table_lookup: RomTableLookupFn = core::mem::transmute(rom_table_lookup_ptr); + rom_table_lookup( + rom_hword_as_ptr(table) as *const u16, + u16::from_le_bytes(tag) as u32, + ) + } +} + +/// To save space, the ROM likes to store memory pointers (which are 32-bit on +/// the Cortex-M0+) using only the bottom 16-bits. The assumption is that the +/// values they point at live in the first 64 KiB of ROM, and the ROM is mapped +/// to address `0x0000_0000` and so 16-bits are always sufficient. +/// +/// This functions grabs a 16-bit value from ROM and expands it out to a full 32-bit pointer. +unsafe fn rom_hword_as_ptr(rom_address: *const u16) -> *const u32 { + let ptr: u16 = *rom_address; + ptr as *const u32 +} + +macro_rules! declare_rom_function { + ( + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: *const u32 = $lookup; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: *const u32 = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: *const u32 = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as *const u32, + }; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + pub extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; + + ( + $(#[$outer:meta])* + unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: *const u32 = $lookup; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: *const u32 = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: *const u32 = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as *const u32, + }; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + /// # Safety + /// + /// This is a low-level C function. It may be difficult to call safely from + /// Rust. If in doubt, check the RP2040 datasheet for details and do your own + /// safety evaluation. + pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; +} + +macro_rules! rom_functions { + () => {}; + + ( + $(#[$outer:meta])* + $c:literal fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty; + + $($rest:tt)* + ) => { + declare_rom_function! { + $(#[$outer])* + fn $name( $($argname: $ty),* ) -> $ret { + $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c) + } + } + + rom_functions!($($rest)*); + }; + + ( + $(#[$outer:meta])* + $c:literal unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty; + + $($rest:tt)* + ) => { + declare_rom_function! { + $(#[$outer])* + unsafe fn $name( $($argname: $ty),* ) -> $ret { + $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c) + } + } + + rom_functions!($($rest)*); + }; +} + +rom_functions! { + /// Return a count of the number of 1 bits in value. + b"P3" fn popcount32(value: u32) -> u32; + + /// Return the bits of value in the reverse order. + b"R3" fn reverse32(value: u32) -> u32; + + /// Return the number of consecutive high order 0 bits of value. If value is zero, returns 32. + b"L3" fn clz32(value: u32) -> u32; + + /// Return the number of consecutive low order 0 bits of value. If value is zero, returns 32. + b"T3" fn ctz32(value: u32) -> u32; + + /// Resets the RP2040 and uses the watchdog facility to re-start in BOOTSEL mode: + /// + /// * gpio_activity_pin_mask is provided to enable an 'activity light' via GPIO attached LED + /// for the USB Mass Storage Device: + /// * 0 No pins are used as per cold boot. + /// * Otherwise a single bit set indicating which GPIO pin should be set to output and + /// raised whenever there is mass storage activity from the host. + /// * disable_interface_mask may be used to control the exposed USB interfaces: + /// * 0 To enable both interfaces (as per cold boot). + /// * 1 To disable the USB Mass Storage Interface. + /// * 2 to Disable the USB PICOBOOT Interface. + b"UB" fn reset_to_usb_boot(gpio_activity_pin_mask: u32, disable_interface_mask: u32) -> (); + + /// Sets n bytes start at ptr to the value c and returns ptr + b"MS" unsafe fn memset(ptr: *mut u8, c: u8, n: u32) -> *mut u8; + + /// Sets n bytes start at ptr to the value c and returns ptr. + /// + /// Note this is a slightly more efficient variant of _memset that may only + /// be used if ptr is word aligned. + // Note the datasheet does not match the actual ROM for the code here, see + // https://github.com/raspberrypi/pico-feedback/issues/217 + b"S4" unsafe fn memset4(ptr: *mut u32, c: u8, n: u32) -> *mut u32; + + /// Copies n bytes starting at src to dest and returns dest. The results are undefined if the + /// regions overlap. + b"MC" unsafe fn memcpy(dest: *mut u8, src: *const u8, n: u32) -> *mut u8; + + /// Copies n bytes starting at src to dest and returns dest. The results are undefined if the + /// regions overlap. + /// + /// Note this is a slightly more efficient variant of _memcpy that may only be + /// used if dest and src are word aligned. + b"C4" unsafe fn memcpy44(dest: *mut u32, src: *const u32, n: u32) -> *mut u8; + + /// Restore all QSPI pad controls to their default state, and connect the SSI to the QSPI pads. + b"IF" unsafe fn connect_internal_flash() -> (); + + /// First set up the SSI for serial-mode operations, then issue the fixed XIP exit sequence. + /// + /// Note that the bootrom code uses the IO forcing logic to drive the CS pin, which must be + /// cleared before returning the SSI to XIP mode (e.g. by a call to _flash_flush_cache). This + /// function configures the SSI with a fixed SCK clock divisor of /6. + b"EX" unsafe fn flash_exit_xip() -> (); + + /// Erase a count bytes, starting at addr (offset from start of flash). + /// + /// Optionally, pass a block erase command e.g. D8h block erase, and the size of the block erased + /// by this command — this function will use the larger block erase where possible, for much higher + /// erase speed. addr must be aligned to a 4096-byte sector, and count must be a multiple of + /// 4096 bytes. + b"RE" unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> (); + + /// Program data to a range of flash addresses starting at `addr` (and + /// offset from the start of flash) and `count` bytes in size. + /// + /// The value `addr` must be aligned to a 256-byte boundary, and `count` must be a + /// multiple of 256. + b"RP" unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> (); + + /// Flush and enable the XIP cache. Also clears the IO forcing on QSPI CSn, so that the SSI can + /// drive the flashchip select as normal. + b"FC" unsafe fn flash_flush_cache() -> (); + + /// Configure the SSI to generate a standard 03h serial read command, with 24 address bits, + /// upon each XIP access. + /// + /// This is a very slow XIP configuration, but is very widely supported. + /// The debugger calls this function after performing a flash erase/programming operation, so + /// that the freshly-programmed code and data is visible to the debug host, without having to + /// know exactly what kind of flash device is connected. + b"CX" unsafe fn flash_enter_cmd_xip() -> (); + + /// This is the method that is entered by core 1 on reset to wait to be launched by core 0. + /// + /// There are few cases where you should call this method (resetting core 1 is much better). + /// This method does not return and should only ever be called on core 1. + b"WV" unsafe fn wait_for_vector() -> !; +} + +// Various C intrinsics in the ROM +intrinsics! { + #[alias = __popcountdi2] + extern "C" fn __popcountsi2(x: u32) -> u32 { + popcount32(x) + } + + #[alias = __clzdi2] + extern "C" fn __clzsi2(x: u32) -> u32 { + clz32(x) + } + + #[alias = __ctzdi2] + extern "C" fn __ctzsi2(x: u32) -> u32 { + ctz32(x) + } + + // __rbit is only unofficial, but it show up in the ARM documentation, + // so may as well hook it up. + #[alias = __rbitl] + extern "C" fn __rbit(x: u32) -> u32 { + reverse32(x) + } + + unsafe extern "aapcs" fn __aeabi_memset(dest: *mut u8, n: usize, c: i32) -> () { + // Different argument order + memset(dest, c as u8, n as u32); + } + + #[alias = __aeabi_memset8] + unsafe extern "aapcs" fn __aeabi_memset4(dest: *mut u8, n: usize, c: i32) -> () { + // Different argument order + memset4(dest as *mut u32, c as u8, n as u32); + } + + unsafe extern "aapcs" fn __aeabi_memclr(dest: *mut u8, n: usize) -> () { + memset(dest, 0, n as u32); + } + + #[alias = __aeabi_memclr8] + unsafe extern "aapcs" fn __aeabi_memclr4(dest: *mut u8, n: usize) -> () { + memset4(dest as *mut u32, 0, n as u32); + } + + unsafe extern "aapcs" fn __aeabi_memcpy(dest: *mut u8, src: *const u8, n: usize) -> () { + memcpy(dest, src, n as u32); + } + + #[alias = __aeabi_memcpy8] + unsafe extern "aapcs" fn __aeabi_memcpy4(dest: *mut u8, src: *const u8, n: usize) -> () { + memcpy44(dest as *mut u32, src as *const u32, n as u32); + } +} + +unsafe fn convert_str(s: *const u8) -> &'static str { + let mut end = s; + while *end != 0 { + end = end.add(1); + } + let s = core::slice::from_raw_parts(s, end.offset_from(s) as usize); + core::str::from_utf8_unchecked(s) +} + +/// The version number of the rom. +pub fn rom_version_number() -> u8 { + unsafe { *VERSION_NUMBER } +} + +/// The Raspberry Pi Trading Ltd copyright string. +pub fn copyright_string() -> &'static str { + let s: *const u8 = rom_table_lookup(DATA_TABLE, *b"CR"); + unsafe { convert_str(s) } +} + +/// The 8 most significant hex digits of the Bootrom git revision. +pub fn git_revision() -> u32 { + let s: *const u32 = rom_table_lookup(DATA_TABLE, *b"GR"); + unsafe { *s } +} + +/// The start address of the floating point library code and data. +/// +/// This and fplib_end along with the individual function pointers in +/// soft_float_table can be used to copy the floating point implementation into +/// RAM if desired. +pub fn fplib_start() -> *const u8 { + rom_table_lookup(DATA_TABLE, *b"FS") +} + +/// See Table 180 in the RP2040 datasheet for the contents of this table. +pub fn soft_float_table() -> *const usize { + rom_table_lookup(DATA_TABLE, *b"SF") +} + +/// The end address of the floating point library code and data. +pub fn fplib_end() -> *const u8 { + rom_table_lookup(DATA_TABLE, *b"FE") +} + +/// This entry is only present in the V2 bootrom. See Table 182 in the RP2040 datasheet for the contents of this table. +pub fn soft_double_table() -> *const usize { + if rom_version_number() < 2 { + panic!( + "Double precision operations require V2 bootrom (found: V{})", + rom_version_number() + ); + } + rom_table_lookup(DATA_TABLE, *b"SD") +} + +/// ROM functions using single-precision arithmetic (i.e. 'f32' in Rust terms) +pub mod float_funcs { + + macro_rules! make_functions { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + let table: *const usize = $crate::rom_data::soft_float_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + make_functions! { + /// Calculates `a + b` + 0x00 fadd(a: f32, b: f32) -> f32; + /// Calculates `a - b` + 0x04 fsub(a: f32, b: f32) -> f32; + /// Calculates `a * b` + 0x08 fmul(a: f32, b: f32) -> f32; + /// Calculates `a / b` + 0x0c fdiv(a: f32, b: f32) -> f32; + + // 0x10 and 0x14 are deprecated + + /// Calculates `sqrt(v)` (or return -Infinity if v is negative) + 0x18 fsqrt(v: f32) -> f32; + /// Converts an f32 to a signed integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `-0x80000000` to `0x7FFFFFFF` + 0x1c float_to_int(v: f32) -> i32; + /// Converts an f32 to an signed fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x20 float_to_fix(v: f32, n: i32) -> i32; + /// Converts an f32 to an unsigned integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `0x00000000` to `0xFFFFFFFF` + 0x24 float_to_uint(v: f32) -> u32; + /// Converts an f32 to an unsigned fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x28 float_to_ufix(v: f32, n: i32) -> u32; + /// Converts a signed integer to the nearest + /// f32 value, rounding to even on tie + 0x2c int_to_float(v: i32) -> f32; + /// Converts a signed fixed point integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x30 fix_to_float(v: i32, n: i32) -> f32; + /// Converts an unsigned integer to the nearest + /// f32 value, rounding to even on tie + 0x34 uint_to_float(v: u32) -> f32; + /// Converts an unsigned fixed point integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x38 ufix_to_float(v: u32, n: i32) -> f32; + /// Calculates the cosine of `angle`. The value + /// of `angle` is in radians, and must be in the range `-128` to `128` + 0x3c fcos(angle: f32) -> f32; + /// Calculates the sine of `angle`. The value of + /// `angle` is in radians, and must be in the range `-128` to `128` + 0x40 fsin(angle: f32) -> f32; + /// Calculates the tangent of `angle`. The value + /// of `angle` is in radians, and must be in the range `-128` to `128` + 0x44 ftan(angle: f32) -> f32; + + // 0x48 is deprecated + + /// Calculates the exponential value of `v`, + /// i.e. `e ** v` + 0x4c fexp(v: f32) -> f32; + /// Calculates the natural logarithm of `v`. If `v <= 0` return -Infinity + 0x50 fln(v: f32) -> f32; + } + + macro_rules! make_functions_v2 { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + if $crate::rom_data::rom_version_number() < 2 { + panic!( + "Floating point function requires V2 bootrom (found: V{})", + $crate::rom_data::rom_version_number() + ); + } + let table: *const usize = $crate::rom_data::soft_float_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + // These are only on BootROM v2 or higher + make_functions_v2! { + /// Compares two floating point numbers, returning: + /// • 0 if a == b + /// • -1 if a < b + /// • 1 if a > b + 0x54 fcmp(a: f32, b: f32) -> i32; + /// Computes the arc tangent of `y/x` using the + /// signs of arguments to determine the correct quadrant + 0x58 fatan2(y: f32, x: f32) -> f32; + /// Converts a signed 64-bit integer to the + /// nearest f32 value, rounding to even on tie + 0x5c int64_to_float(v: i64) -> f32; + /// Converts a signed fixed point 64-bit integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x60 fix64_to_float(v: i64, n: i32) -> f32; + /// Converts an unsigned 64-bit integer to the + /// nearest f32 value, rounding to even on tie + 0x64 uint64_to_float(v: u64) -> f32; + /// Converts an unsigned fixed point 64-bit + /// integer representation to the nearest f32 value, rounding to even on + /// tie. `n` specifies the position of the binary point in fixed point, so + /// `f = nearest(v/(2^n))` + 0x68 ufix64_to_float(v: u64, n: i32) -> f32; + /// Convert an f32 to a signed 64-bit integer, rounding towards -Infinity, + /// and clamping the result to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x6c float_to_int64(v: f32) -> i64; + /// Converts an f32 to a signed fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation - e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x70 float_to_fix64(v: f32, n: i32) -> i64; + /// Converts an f32 to an unsigned 64-bit + /// integer, rounding towards -Infinity, and clamping the result to lie + /// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF` + 0x74 float_to_uint64(v: f32) -> u64; + /// Converts an f32 to an unsigned fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation, e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `0x0000000000000000` to + /// `0xFFFFFFFFFFFFFFFF` + 0x78 float_to_ufix64(v: f32, n: i32) -> u64; + /// Converts an f32 to an f64. + 0x7c float_to_double(v: f32) -> f64; + } +} + +/// Functions using double-precision arithmetic (i.e. 'f64' in Rust terms) +pub mod double_funcs { + + macro_rules! make_double_funcs { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + let table: *const usize = $crate::rom_data::soft_double_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + make_double_funcs! { + /// Calculates `a + b` + 0x00 dadd(a: f64, b: f64) -> f64; + /// Calculates `a - b` + 0x04 dsub(a: f64, b: f64) -> f64; + /// Calculates `a * b` + 0x08 dmul(a: f64, b: f64) -> f64; + /// Calculates `a / b` + 0x0c ddiv(a: f64, b: f64) -> f64; + + // 0x10 and 0x14 are deprecated + + /// Calculates `sqrt(v)` (or return -Infinity if v is negative) + 0x18 dsqrt(v: f64) -> f64; + /// Converts an f64 to a signed integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `-0x80000000` to `0x7FFFFFFF` + 0x1c double_to_int(v: f64) -> i32; + /// Converts an f64 to an signed fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x20 double_to_fix(v: f64, n: i32) -> i32; + /// Converts an f64 to an unsigned integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `0x00000000` to `0xFFFFFFFF` + 0x24 double_to_uint(v: f64) -> u32; + /// Converts an f64 to an unsigned fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x28 double_to_ufix(v: f64, n: i32) -> u32; + /// Converts a signed integer to the nearest + /// double value, rounding to even on tie + 0x2c int_to_double(v: i32) -> f64; + /// Converts a signed fixed point integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x30 fix_to_double(v: i32, n: i32) -> f64; + /// Converts an unsigned integer to the nearest + /// double value, rounding to even on tie + 0x34 uint_to_double(v: u32) -> f64; + /// Converts an unsigned fixed point integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so f = + /// nearest(v/(2^n)) + 0x38 ufix_to_double(v: u32, n: i32) -> f64; + /// Calculates the cosine of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x3c dcos(angle: f64) -> f64; + /// Calculates the sine of `angle`. The value of + /// `angle` is in radians, and must be in the range `-1024` to `1024` + 0x40 dsin(angle: f64) -> f64; + /// Calculates the tangent of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x44 dtan(angle: f64) -> f64; + + // 0x48 is deprecated + + /// Calculates the exponential value of `v`, + /// i.e. `e ** v` + 0x4c dexp(v: f64) -> f64; + /// Calculates the natural logarithm of v. If v <= 0 return -Infinity + 0x50 dln(v: f64) -> f64; + + // These are only on BootROM v2 or higher + + /// Compares two floating point numbers, returning: + /// • 0 if a == b + /// • -1 if a < b + /// • 1 if a > b + 0x54 dcmp(a: f64, b: f64) -> i32; + /// Computes the arc tangent of `y/x` using the + /// signs of arguments to determine the correct quadrant + 0x58 datan2(y: f64, x: f64) -> f64; + /// Converts a signed 64-bit integer to the + /// nearest double value, rounding to even on tie + 0x5c int64_to_double(v: i64) -> f64; + /// Converts a signed fixed point 64-bit integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x60 fix64_to_double(v: i64, n: i32) -> f64; + /// Converts an unsigned 64-bit integer to the + /// nearest double value, rounding to even on tie + 0x64 uint64_to_double(v: u64) -> f64; + /// Converts an unsigned fixed point 64-bit + /// integer representation to the nearest double value, rounding to even on + /// tie. `n` specifies the position of the binary point in fixed point, so + /// `f = nearest(v/(2^n))` + 0x68 ufix64_to_double(v: u64, n: i32) -> f64; + /// Convert an f64 to a signed 64-bit integer, rounding towards -Infinity, + /// and clamping the result to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x6c double_to_int64(v: f64) -> i64; + /// Converts an f64 to a signed fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation - e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x70 double_to_fix64(v: f64, n: i32) -> i64; + /// Converts an f64 to an unsigned 64-bit + /// integer, rounding towards -Infinity, and clamping the result to lie + /// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF` + 0x74 double_to_uint64(v: f64) -> u64; + /// Converts an f64 to an unsigned fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation, e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `0x0000000000000000` to + /// `0xFFFFFFFFFFFFFFFF` + 0x78 double_to_ufix64(v: f64, n: i32) -> u64; + /// Converts an f64 to an f32 + 0x7c double_to_float(v: f64) -> f32; + } +} diff --git a/rp-hal/rp2040-hal/src/rosc.rs b/rp-hal/rp2040-hal/src/rosc.rs new file mode 100644 index 0000000..abad4f3 --- /dev/null +++ b/rp-hal/rp2040-hal/src/rosc.rs @@ -0,0 +1,148 @@ +//! Ring Oscillator (ROSC) +//! +//! See [Chapter 2 Section 17](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details +//! +//! In addition to its obvious role as a clock source, [`RingOscillator`] can also be used as a random number source +//! for the [`rand`] crate: +//! +//! ```no_run +//! # let mut pac = rp2040_pac::Peripherals::take().unwrap(); +//! use rp2040_hal::rosc::RingOscillator; +//! use rand::Rng; +//! let mut rnd = RingOscillator::new(pac.ROSC).initialize(); +//! let random_value: u32 = rnd.gen(); +//! ``` +//! [`rand`]: https://docs.rs/rand +use fugit::HertzU32; + +use crate::{pac::ROSC, typelevel::Sealed}; + +/// State of the Ring Oscillator (typestate trait) +pub trait State: Sealed {} + +/// ROSC is disabled (typestate) +pub struct Disabled; + +/// ROSC is initialized, ie we've given parameters (typestate) +pub struct Enabled { + freq_hz: HertzU32, +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Enabled {} +impl Sealed for Enabled {} + +/// A Ring Oscillator. +pub struct RingOscillator { + device: ROSC, + state: S, +} + +impl RingOscillator { + /// Transitions the oscillator to another state. + fn transition(self, state: To) -> RingOscillator { + RingOscillator { + device: self.device, + state, + } + } + + /// Releases the underlying device. + pub fn free(self) -> ROSC { + self.device + } +} + +impl RingOscillator { + /// Creates a new RingOscillator from the underlying device. + pub fn new(dev: ROSC) -> Self { + RingOscillator { + device: dev, + state: Disabled, + } + } + + /// Initializes the ROSC : frequency range is set, startup delay is calculated and set. + pub fn initialize(self) -> RingOscillator { + self.device.ctrl().write(|w| w.enable().enable()); + + use fugit::RateExtU32; + self.transition(Enabled { + freq_hz: 6_500_000u32.Hz(), + }) + } + + /// Initializes the ROSC with a known frequency. + /// See sections 2.17.3. "Modifying the frequency", and 2.15.6.2. "Using the frequency counter" + /// in the rp2040 datasheet for guidance on how to do this before initialising the ROSC. + /// Also see `rosc_as_system_clock` example for usage. + pub fn initialize_with_freq(self, known_freq: HertzU32) -> RingOscillator { + self.device.ctrl().write(|w| w.enable().enable()); + self.transition(Enabled { + freq_hz: known_freq, + }) + } +} + +impl RingOscillator { + /// Approx operating frequency of the ROSC in hertz + pub fn operating_frequency(&self) -> HertzU32 { + self.state.freq_hz + } + + /// Disables the ROSC + pub fn disable(self) -> RingOscillator { + self.device.ctrl().modify(|_r, w| w.enable().disable()); + + self.transition(Disabled) + } + + /// Generate random bit based on the Ring oscillator + /// This is not suited for security purposes + pub fn get_random_bit(&self) -> bool { + self.device.randombit().read().randombit().bit() + } + + /// Put the ROSC in DORMANT state. The method returns after the processor awakens. + /// + /// After waking up from the DORMANT state, ROSC restarts in approximately 1µs. + /// + /// # Safety + /// This method is marked unsafe because prior to switch the ROSC into DORMANT state, + /// PLLs must be stopped and IRQs have to be properly configured. + /// This method does not do any of that, it merely switches the ROSC to DORMANT state. + /// It should only be called if this oscillator is the clock source for the system clock. + /// See Chapter 2, Section 16, §5) for details. + pub unsafe fn dormant(&self) { + //taken from the C SDK + const ROSC_DORMANT_VALUE: u32 = 0x636f6d61; + + self.device.dormant().write(|w| w.bits(ROSC_DORMANT_VALUE)); + } +} + +impl rand_core::RngCore for RingOscillator { + 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]) { + for chunk in dest.iter_mut() { + *chunk = 0_u8; + for _ in 0..8 { + *chunk <<= 1; + *chunk ^= self.get_random_bit() as u8; + } + } + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } +} diff --git a/rp-hal/rp2040-hal/src/rtc/datetime.rs b/rp-hal/rp2040-hal/src/rtc/datetime.rs new file mode 100644 index 0000000..48f0780 --- /dev/null +++ b/rp-hal/rp2040-hal/src/rtc/datetime.rs @@ -0,0 +1,207 @@ +use crate::pac::rtc::{rtc_0, rtc_1, setup_0, setup_1}; + +/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs. +/// +/// [`DateTimeFilter`]: struct.DateTimeFilter.html +#[allow(clippy::enum_variant_names)] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The [DateTime] contains an invalid year value. Must be between `0..=4095`. + InvalidYear, + /// The [DateTime] contains an invalid month value. Must be between `1..=12`. + InvalidMonth, + /// The [DateTime] contains an invalid day value. Must be between `1..=31`. + InvalidDay, + /// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday. + InvalidDayOfWeek( + /// The value of the DayOfWeek that was given. + u8, + ), + /// The [DateTime] contains an invalid hour value. Must be between `0..=23`. + InvalidHour, + /// The [DateTime] contains an invalid minute value. Must be between `0..=59`. + InvalidMinute, + /// The [DateTime] contains an invalid second value. Must be between `0..=59`. + InvalidSecond, +} + +/// Structure containing date and time information +pub struct DateTime { + /// 0..4095 + pub year: u16, + /// 1..12, 1 is January + pub month: u8, + /// 1..28,29,30,31 depending on month + pub day: u8, + /// The day of week + pub day_of_week: DayOfWeek, + /// 0..23 + pub hour: u8, + /// 0..59 + pub minute: u8, + /// 0..59 + pub second: u8, +} + +/// A day of the week +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[allow(missing_docs)] +pub enum DayOfWeek { + Sunday = 0, + Monday = 1, + Tuesday = 2, + Wednesday = 3, + Thursday = 4, + Friday = 5, + Saturday = 6, +} + +fn day_of_week_from_u8(v: u8) -> Result { + Ok(match v { + 0 => DayOfWeek::Sunday, + 1 => DayOfWeek::Monday, + 2 => DayOfWeek::Tuesday, + 3 => DayOfWeek::Wednesday, + 4 => DayOfWeek::Thursday, + 5 => DayOfWeek::Friday, + 6 => DayOfWeek::Saturday, + x => return Err(Error::InvalidDayOfWeek(x)), + }) +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw as u8 +} + +pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year > 4095 { + Err(Error::InvalidYear) + } else if dt.month < 1 || dt.month > 12 { + Err(Error::InvalidMonth) + } else if dt.day < 1 || dt.day > 31 { + Err(Error::InvalidDay) + } else if dt.hour > 23 { + Err(Error::InvalidHour) + } else if dt.minute > 59 { + Err(Error::InvalidMinute) + } else if dt.second > 59 { + Err(Error::InvalidSecond) + } else { + Ok(()) + } +} + +pub(super) fn write_setup_0(dt: &DateTime, w: &mut setup_0::W) { + // Safety: the `.bits()` fields are marked `unsafe` but all bit values are valid + unsafe { + w.year().bits(dt.year); + w.month().bits(dt.month); + w.day().bits(dt.day); + } +} + +pub(super) fn write_setup_1(dt: &DateTime, w: &mut setup_1::W) { + // Safety: the `.bits()` fields are marked `unsafe` but all bit values are valid + unsafe { + w.dotw().bits(dt.day_of_week as u8); + w.hour().bits(dt.hour); + w.min().bits(dt.minute); + w.sec().bits(dt.second); + } +} + +pub(super) fn datetime_from_registers(rtc_0: rtc_0::R, rtc_1: rtc_1::R) -> Result { + let year = rtc_1.year().bits(); + let month = rtc_1.month().bits(); + let day = rtc_1.day().bits(); + + let day_of_week = rtc_0.dotw().bits(); + let hour = rtc_0.hour().bits(); + let minute = rtc_0.min().bits(); + let second = rtc_0.sec().bits(); + + let day_of_week = day_of_week_from_u8(day_of_week)?; + Ok(DateTime { + year, + month, + day, + day_of_week, + hour, + minute, + second, + }) +} + +#[cfg(feature = "chrono")] +pub mod chrono { + use super::{DateTime, DayOfWeek}; + use chrono::{NaiveDateTime, Weekday}; + + /// The RP2040's RTC can only hold years up to 4095. + pub struct YearOutOfRangeError; + + /// The given date/time is out of the range supported by `chrono::NaiveDateTime`. + pub enum OutOfRangeError { + YearMonthDay, + HourMinuteSecond, + } + + impl From for DayOfWeek { + fn from(wd: Weekday) -> DayOfWeek { + use DayOfWeek::*; + match wd { + Weekday::Mon => Monday, + Weekday::Tue => Tuesday, + Weekday::Wed => Wednesday, + Weekday::Thu => Thursday, + Weekday::Fri => Friday, + Weekday::Sat => Saturday, + Weekday::Sun => Sunday, + } + } + } + impl From for Weekday { + fn from(dow: DayOfWeek) -> Weekday { + use DayOfWeek::*; + match dow { + Monday => Weekday::Mon, + Tuesday => Weekday::Tue, + Wednesday => Weekday::Wed, + Thursday => Weekday::Thu, + Friday => Weekday::Fri, + Saturday => Weekday::Sat, + Sunday => Weekday::Sun, + } + } + } + impl TryFrom for DateTime { + type Error = YearOutOfRangeError; + fn try_from(dt: NaiveDateTime) -> Result { + if dt.year() > 4095 { + return Err(YearOutOfRangeError); + } + use chrono::Datelike; + use chrono::Timelike; + Ok(Self { + year: dt.year() as u16, + month: dt.month() as u8, + day: dt.day() as u8, + day_of_week: dt.weekday().into(), + hour: dt.hour() as u8, + minute: dt.minute() as u8, + second: dt.second() as u8, + }) + } + } + impl TryFrom for NaiveDateTime { + type Error = OutOfRangeError; + fn try_from(dt: DateTime) -> Result { + chrono::NaiveDate::from_ymd_opt(dt.year.into(), dt.month.into(), dt.day.into()) + .ok_or(OutOfRangeError::YearMonthDay)? + .and_hms_opt(dt.hour.into(), dt.minute.into(), dt.second.into()) + .ok_or(OutOfRangeError::HourMinuteSecond) + } + } +} diff --git a/rp-hal/rp2040-hal/src/rtc/datetime_chrono.rs b/rp-hal/rp2040-hal/src/rtc/datetime_chrono.rs new file mode 100644 index 0000000..5974d55 --- /dev/null +++ b/rp-hal/rp2040-hal/src/rtc/datetime_chrono.rs @@ -0,0 +1,68 @@ +use crate::pac::rtc::{rtc_0, rtc_1, setup_0, setup_1}; +use chrono::{Datelike, Timelike}; + +/// Alias for [`chrono::NaiveDateTime`] +pub type DateTime = chrono::NaiveDateTime; +/// Alias for [`chrono::Weekday`] +pub type DayOfWeek = chrono::Weekday; + +/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs. +/// +/// [`DateTimeFilter`]: struct.DateTimeFilter.html +#[derive(Clone, Debug, PartialEq, Eq)] +#[allow(clippy::enum_variant_names)] +pub enum Error { + /// The [DateTime] has an invalid year. The year must be between 0 and 4095. + InvalidYear, + /// The [DateTime] contains an invalid date. + InvalidDate, + /// The [DateTime] contains an invalid time. + InvalidTime, +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw.num_days_from_sunday() as u8 +} + +pub(crate) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year() < 0 || dt.year() > 4095 { + // rp2040 can't hold these years + Err(Error::InvalidYear) + } else { + // The rest of the chrono date is assumed to be valid + Ok(()) + } +} + +pub(super) fn write_setup_0(dt: &DateTime, w: &mut setup_0::W) { + // Safety: the `.bits()` fields are marked `unsafe` but all bit values are valid + unsafe { + w.year().bits(dt.year() as u16); + w.month().bits(dt.month() as u8); + w.day().bits(dt.day() as u8); + } +} + +pub(super) fn write_setup_1(dt: &DateTime, w: &mut setup_1::W) { + // Safety: the `.bits()` fields are marked `unsafe` but all bit values are valid + unsafe { + w.dotw().bits(dt.weekday().num_days_from_sunday() as u8); + w.hour().bits(dt.hour() as u8); + w.min().bits(dt.minute() as u8); + w.sec().bits(dt.second() as u8); + } +} + +pub(super) fn datetime_from_registers(rtc_0: rtc_0::R, rtc_1: rtc_1::R) -> Result { + let year = rtc_1.year().bits() as i32; + let month = rtc_1.month().bits() as u32; + let day = rtc_1.day().bits() as u32; + + let hour = rtc_0.hour().bits() as u32; + let minute = rtc_0.min().bits() as u32; + let second = rtc_0.sec().bits() as u32; + + let date = chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or(Error::InvalidDate)?; + let time = chrono::NaiveTime::from_hms_opt(hour, minute, second).ok_or(Error::InvalidTime)?; + Ok(DateTime::new(date, time)) +} diff --git a/rp-hal/rp2040-hal/src/rtc/filter.rs b/rp-hal/rp2040-hal/src/rtc/filter.rs new file mode 100644 index 0000000..7d0cfa2 --- /dev/null +++ b/rp-hal/rp2040-hal/src/rtc/filter.rs @@ -0,0 +1,120 @@ +use super::DayOfWeek; +use crate::pac::rtc::{irq_setup_0, irq_setup_1}; + +/// A filter used for [`RealTimeClock::schedule_alarm`]. +/// +/// [`RealTimeClock::schedule_alarm`]: struct.RealTimeClock.html#method.schedule_alarm +#[derive(Default)] +pub struct DateTimeFilter { + /// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value. + pub year: Option, + /// The month that this alarm should trigger on, `None` if the RTC alarm should not trigger on a month value. + pub month: Option, + /// The day that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day value. + pub day: Option, + /// The day of week that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day of week value. + pub day_of_week: Option, + /// The hour that this alarm should trigger on, `None` if the RTC alarm should not trigger on a hour value. + pub hour: Option, + /// The minute that this alarm should trigger on, `None` if the RTC alarm should not trigger on a minute value. + pub minute: Option, + /// The second that this alarm should trigger on, `None` if the RTC alarm should not trigger on a second value. + pub second: Option, +} + +impl DateTimeFilter { + /// Set a filter on the given year + pub fn year(mut self, year: u16) -> Self { + self.year = Some(year); + self + } + /// Set a filter on the given month + pub fn month(mut self, month: u8) -> Self { + self.month = Some(month); + self + } + /// Set a filter on the given day + pub fn day(mut self, day: u8) -> Self { + self.day = Some(day); + self + } + /// Set a filter on the given day of the week + pub fn day_of_week(mut self, day_of_week: DayOfWeek) -> Self { + self.day_of_week = Some(day_of_week); + self + } + /// Set a filter on the given hour + pub fn hour(mut self, hour: u8) -> Self { + self.hour = Some(hour); + self + } + /// Set a filter on the given minute + pub fn minute(mut self, minute: u8) -> Self { + self.minute = Some(minute); + self + } + /// Set a filter on the given second + pub fn second(mut self, second: u8) -> Self { + self.second = Some(second); + self + } +} + +// register helper functions +impl DateTimeFilter { + pub(super) fn write_setup_0(&self, w: &mut irq_setup_0::W) { + // Safety: setting .bits() is considered unsafe because + // svd2rust doesn't know what the valid values are. + // But all values in these bitmasks are safe + if let Some(year) = self.year { + w.year_ena().set_bit(); + + unsafe { + w.year().bits(year); + } + } + if let Some(month) = self.month { + w.month_ena().set_bit(); + unsafe { + w.month().bits(month); + } + } + if let Some(day) = self.day { + w.day_ena().set_bit(); + unsafe { + w.day().bits(day); + } + } + } + pub(super) fn write_setup_1(&self, w: &mut irq_setup_1::W) { + // Safety: setting .bits() is considered unsafe because + // svd2rust doesn't know what the valid values are. + // But all values in these bitmasks are safe + if let Some(day_of_week) = self.day_of_week { + w.dotw_ena().set_bit(); + let bits = super::datetime::day_of_week_to_u8(day_of_week); + + unsafe { + w.dotw().bits(bits); + } + } + if let Some(hour) = self.hour { + w.hour_ena().set_bit(); + unsafe { + w.hour().bits(hour); + } + } + if let Some(minute) = self.minute { + w.min_ena().set_bit(); + unsafe { + w.min().bits(minute); + } + } + if let Some(second) = self.second { + w.sec_ena().set_bit(); + unsafe { + w.sec().bits(second); + } + } + } +} diff --git a/rp-hal/rp2040-hal/src/rtc/mod.rs b/rp-hal/rp2040-hal/src/rtc/mod.rs new file mode 100644 index 0000000..35d34fd --- /dev/null +++ b/rp-hal/rp2040-hal/src/rtc/mod.rs @@ -0,0 +1,217 @@ +//! Real time clock functionality +//! +//! A [`RealTimeClock`] can be configured with an initial [`DateTime`]. Afterwards the clock will track time automatically. The current `DateTime` can be retrieved by [`RealTimeClock::now()`]. +//! +//! With the **chrono** feature enabled, the following types will be alias for chrono types: +//! - `DateTime`: `chrono::NaiveDateTime` +//! - `DayOfWeek`: `chrono::Weekday` +//! +//! # Notes +//! +//! There are some things to take into account. As per the datasheet: +//! +//! - **Day of week**: The RTC will not compute the correct day of the week; it will only increment the existing value. +//! - With the `chrono` feature, the day of week is calculated by chrono and should be correct. The value from the rp2040 itself is not used. +//! - **Leap year**: If the current year is evenly divisible by 4, a leap year is detected, then Feb 28th is followed by Feb 29th instead of March 1st. +//! - There are cases where this is incorrect, e.g. century years have no leap day, but the chip will still add a Feb 29th. +//! - To disable leap year checking and never have a Feb 29th, call `RealTimeClock::set_leap_year_check(false)`. +//! +//! Other limitations: +//! +//! - **Leap seconds**: The rp2040 will not take leap seconds into account +//! - With the `chrono` feature, leap seconds will be silently handled by `chrono`. This means there might be a slight difference between the value of [`RealTimeClock::now()`] and adding 2 times together in code. + +use crate::clocks::Clock; +use crate::clocks::RtcClock; +use crate::pac::{RESETS, RTC}; + +mod filter; + +pub use self::filter::DateTimeFilter; + +mod datetime; + +pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; + +/// A reference to the real time clock of the system +pub struct RealTimeClock { + rtc: RTC, + clock: RtcClock, +} + +impl RealTimeClock { + /// Create a new instance of the real time clock, with the given date as an initial value. + /// + /// Note that the [`ClocksManager`] should be enabled first. See the [`clocks`] module for more information. + /// + /// # Errors + /// + /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. + /// + /// [`ClocksManager`]: ../clocks/struct.ClocksManager.html + /// [`clocks`]: ../clocks/index.html + #[allow(unknown_lints)] + #[allow(clippy::needless_pass_by_ref_mut)] + pub fn new( + rtc: RTC, + clock: RtcClock, + resets: &mut RESETS, + initial_date: DateTime, + ) -> Result { + // Toggle the RTC reset + resets.reset().modify(|_, w| w.rtc().set_bit()); + resets.reset().modify(|_, w| w.rtc().clear_bit()); + while resets.reset_done().read().rtc().bit_is_clear() { + core::hint::spin_loop(); + } + + // Set the RTC divider + let freq = clock.freq().to_Hz() - 1; + rtc.clkdiv_m1().write(|w| unsafe { w.bits(freq) }); + + let mut result = Self { rtc, clock }; + result.set_leap_year_check(true); // should be on by default, make sure this is the case. + result.set_datetime(initial_date)?; + Ok(result) + } + + /// Enable or disable the leap year check. The rp2040 chip will always add a Feb 29th on every year that is divisible by 4, but this may be incorrect (e.g. on century years). This function allows you to disable this check. + /// + /// Leap year checking is enabled by default. + pub fn set_leap_year_check(&mut self, leap_year_check_enabled: bool) { + self.rtc + .ctrl() + .modify(|_, w| w.force_notleapyear().bit(!leap_year_check_enabled)); + } + + /// Checks to see if this RealTimeClock is running + pub fn is_running(&self) -> bool { + self.rtc.ctrl().read().rtc_active().bit_is_set() + } + + /// Set the datetime to a new value. + /// + /// # Errors + /// + /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. + pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> { + self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?; + + // disable RTC while we configure it + self.rtc.ctrl().modify(|_, w| w.rtc_enable().clear_bit()); + while self.rtc.ctrl().read().rtc_active().bit_is_set() { + core::hint::spin_loop(); + } + + self.rtc.setup_0().write(|w| { + self::datetime::write_setup_0(&t, w); + w + }); + self.rtc.setup_1().write(|w| { + self::datetime::write_setup_1(&t, w); + w + }); + + // Load the new datetime and re-enable RTC + self.rtc.ctrl().write(|w| w.load().set_bit()); + self.rtc.ctrl().write(|w| w.rtc_enable().set_bit()); + while self.rtc.ctrl().read().rtc_active().bit_is_clear() { + core::hint::spin_loop(); + } + + Ok(()) + } + + /// Return the current datetime. + /// + /// # Errors + /// + /// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`]. + pub fn now(&self) -> Result { + if !self.is_running() { + return Err(RtcError::NotRunning); + } + + let rtc_0 = self.rtc.rtc_0().read(); + let rtc_1 = self.rtc.rtc_1().read(); + + self::datetime::datetime_from_registers(rtc_0, rtc_1).map_err(RtcError::InvalidDateTime) + } + + fn set_match_ena(&mut self, ena: bool) { + // Set the enable bit and check if it is set + self.rtc.irq_setup_0().modify(|_, w| w.match_ena().bit(ena)); + while self.rtc.irq_setup_0().read().match_active().bit() != ena { + core::hint::spin_loop(); + } + } + + /// Disable the alarm that was scheduled with [`schedule_alarm`]. + /// + /// [`schedule_alarm`]: #method.schedule_alarm + pub fn disable_alarm(&mut self) { + self.set_match_ena(false) + } + + /// Schedule an alarm. The `filter` determines at which point in time this alarm is set. + /// + /// If not all fields are set, the alarm will repeat each time the RTC reaches these values. + /// For example, to fire every minute, set: + /// ```no_run + /// # use rp2040_hal::rtc::{RealTimeClock, DateTimeFilter}; + /// # let mut real_time_clock: RealTimeClock = unsafe { core::mem::zeroed() }; + /// real_time_clock.schedule_alarm(DateTimeFilter::default().second(0)); + /// ``` + /// + /// It is worth nothing that the alarm will not fire on schedule if the current time matches. + pub fn schedule_alarm(&mut self, filter: DateTimeFilter) { + self.set_match_ena(false); + + self.rtc.irq_setup_0().write(|w| { + filter.write_setup_0(w); + w + }); + self.rtc.irq_setup_1().write(|w| { + filter.write_setup_1(w); + w + }); + + self.set_match_ena(true); + } + + /// Enable the propagation of alarm to the NVIC. + pub fn enable_interrupt(&mut self) { + self.rtc.inte().modify(|_, w| w.rtc().set_bit()); + } + + /// Disable the propagation of the alarm to the NVIC. + pub fn disable_interrupt(&mut self) { + self.rtc.inte().modify(|_, w| w.rtc().clear_bit()); + } + + /// Clear the interrupt. + /// + /// This should be called every time the `RTC_IRQ` interrupt is triggered or the interrupt will + /// continually fire.. + pub fn clear_interrupt(&mut self) { + self.set_match_ena(false); + self.set_match_ena(true); + } + + /// Free the RTC peripheral and RTC clock + pub fn free(self, resets: &mut RESETS) -> (RTC, RtcClock) { + resets.reset().modify(|_, w| w.rtc().set_bit()); + (self.rtc, self.clock) + } +} + +/// Errors that can occur on methods on [RealTimeClock] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RtcError { + /// An invalid DateTime was given or stored on the hardware. + InvalidDateTime(DateTimeError), + + /// The RTC clock is not running + NotRunning, +} diff --git a/rp-hal/rp2040-hal/src/sio.rs b/rp-hal/rp2040-hal/src/sio.rs new file mode 100644 index 0000000..b817ffb --- /dev/null +++ b/rp-hal/rp2040-hal/src/sio.rs @@ -0,0 +1,846 @@ +//! Single Cycle Input and Output (SIO) +//! +//! To be able to partition parts of the SIO block to other modules: +//! +//! ```no_run +//! use rp2040_hal::{gpio::Pins, pac, sio::Sio}; +//! +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! ``` +//! +//! And then for example +//! +//! ```no_run +//! # use rp2040_hal::{gpio::Pins, pac, sio::Sio}; +//! # let mut peripherals = pac::Peripherals::take().unwrap(); +//! # let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS); +//! ``` + +use crate::typelevel::Sealed; + +use super::*; +use core::convert::Infallible; + +/// Id of the core. +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CoreId { + #[allow(missing_docs)] + Core0 = 0, + #[allow(missing_docs)] + Core1 = 1, +} + +/// Marker struct for ownership of SIO gpio bank0 +#[derive(Debug)] +pub struct SioGpioBank0 { + _private: (), +} + +/// Marker struct for ownership of SIO FIFO +#[derive(Debug)] +pub struct SioFifo { + _private: (), +} + +/// Marker struct for ownership of SIO gpio qspi +#[derive(Debug)] +pub struct SioGpioQspi { + _private: (), +} + +/// Marker struct for ownership of divide/modulo module +#[derive(Debug)] +pub struct HwDivider { + _private: (), +} + +/// Result of divide/modulo operation +#[derive(Debug)] +pub struct DivResult { + /// The quotient of divide/modulo operation + pub quotient: T, + /// The remainder of divide/modulo operation + pub remainder: T, +} + +/// Struct containing ownership markers for managing ownership of the SIO registers. +pub struct Sio { + _sio: pac::SIO, + /// GPIO Bank 0 registers + pub gpio_bank0: SioGpioBank0, + /// GPIO QSPI registers + pub gpio_qspi: SioGpioQspi, + /// 8-cycle hardware divide/modulo module + pub hwdivider: HwDivider, + /// Inter-core FIFO + pub fifo: SioFifo, + /// Interpolator 0 + pub interp0: Interp0, + /// Interpolator 1 + pub interp1: Interp1, +} + +impl Sio { + /// Create `Sio` from the PAC. + pub fn new(sio: pac::SIO) -> Self { + Self { + _sio: sio, + gpio_bank0: SioGpioBank0 { _private: () }, + gpio_qspi: SioGpioQspi { _private: () }, + fifo: SioFifo { _private: () }, + hwdivider: HwDivider { _private: () }, + interp0: Interp0 { + lane0: Interp0Lane0 { _private: () }, + lane1: Interp0Lane1 { _private: () }, + }, + interp1: Interp1 { + lane0: Interp1Lane0 { _private: () }, + lane1: Interp1Lane1 { _private: () }, + }, + } + } + + /// Reads the whole bank0 at once. + pub fn read_bank0() -> u32 { + unsafe { (*pac::SIO::PTR).gpio_in().read().bits() } + } + + /// Returns whether we are running on Core 0 (`0`) or Core 1 (`1`). + pub fn core() -> CoreId { + // Safety: it is always safe to read this read-only register + match unsafe { (*pac::SIO::ptr()).cpuid().read().bits() as u8 } { + 0 => CoreId::Core0, + 1 => CoreId::Core1, + _ => unreachable!("This MCU only has 2 cores."), + } + } +} + +impl SioFifo { + /// Check if the inter-core FIFO has valid data for reading. + /// + /// Returning `true` means there is valid data, `false` means it is empty + /// and you must not read from it. + pub fn is_read_ready(&mut self) -> bool { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_st().read().vld().bit_is_set() + } + + /// Check if the inter-core FIFO is ready to receive data. + /// + /// Returning `true` means there is room, `false` means it is full and you + /// must not write to it. + pub fn is_write_ready(&mut self) -> bool { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_st().read().rdy().bit_is_set() + } + + /// Return the FIFO status, as an integer. + pub fn status(&self) -> u32 { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_st().read().bits() + } + + /// Write to the inter-core FIFO. + /// + /// You must ensure the FIFO has space by calling `is_write_ready` + pub fn write(&mut self, value: u32) { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_wr().write(|w| unsafe { w.bits(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(); + } + + /// Read from the inter-core FIFO. + /// + /// Will return `Some(data)`, or `None` if the FIFO is empty. + pub fn read(&mut self) -> Option { + if self.is_read_ready() { + let sio = unsafe { &(*pac::SIO::ptr()) }; + Some(sio.fifo_rd().read().bits()) + } else { + None + } + } + + /// Read from the FIFO until it is empty, throwing the contents away. + pub fn drain(&mut self) { + while self.read().is_some() { + // Retry until FIFO empty + } + } + + /// Push to the FIFO, spinning if there's no space. + pub fn write_blocking(&mut self, value: u32) { + // We busy-wait for the FIFO to have some space + while !self.is_write_ready() { + cortex_m::asm::nop(); + } + + // Write the value to the FIFO - the other core will now be able to + // pop it off its end of the FIFO. + self.write(value); + + // Fire off an event to the other core + cortex_m::asm::sev(); + } + + /// Pop from the FIFO, spinning if there's currently no data. + pub fn read_blocking(&mut self) -> u32 { + // Keep trying until FIFO has data + loop { + // Have we got something? + if let Some(data) = self.read() { + // Yes, return it right away + return data; + } else { + // No, so sleep the CPU. We expect the sending core to `sev` + // on write. + cortex_m::asm::wfe(); + } + } + } +} + +#[cfg(target_arch = "arm")] +macro_rules! concatln { + ($(,)*) => { + "" + }; + ( $e:expr ) => { + $e + }; + ( $e:expr $(, $es:expr)+ $(,)*) => { + ::core::concat!( $e, "\n", concatln!($($es),+) ) + }; +} + +// This takes advantage of how AAPCS defines a 64-bit return on 32-bit registers +// by packing it into r0[0:31] and r1[32:63]. So all we need to do is put +// the remainder in the high order 32 bits of a 64 bit result. We can also +// alias the division operators to these for a similar reason r0 is the +// result either way and r1 a scratch register, so the caller can't assume it +// retains the argument value. + +#[cfg(target_arch = "arm")] +macro_rules! hwdivider_head { + () => { + concatln!( + "ldr r2, =(0xd0000000)", // SIO_BASE + // Check the DIRTY state of the divider by shifting it into the C + // status bit. + "ldr r3, [r2, #0x078]", // DIV_CSR + "lsrs r3, #2", // DIRTY = 1, so shift 2 down + // We only need to save the state when DIRTY, otherwise we can just do the + // division directly. + "bcs 2f", + "1:", + // Do the actual division now, we're either not DIRTY, or we've saved the + // state and branched back here so it's safe now. + ) + }; +} + +#[cfg(target_arch = "arm")] +macro_rules! hwdivider_tail { + () => { + concatln!( + // 8 cycle delay to wait for the result. Each branch takes two cycles + // and fits into a 2-byte Thumb instruction, so this is smaller than + // 8 NOPs. + "b 3f", + "3: b 3f", + "3: b 3f", + "3: b 3f", + "3:", + // Read the quotient last, since that's what clears the dirty flag. + "ldr r1, [r2, #0x074]", // DIV_REMAINDER + "ldr r0, [r2, #0x070]", // DIV_QUOTIENT + // Either return to the caller or back to the state restore. + "bx lr", + "2:", + // Since we can't save the signed-ness of the calculation, we have to make + // sure that there's at least an 8 cycle delay before we read the result. + // The push takes 5 cycles, and we've already spent at least 7 checking + // the DIRTY state to get here. + "push {{r4-r6, lr}}", + // Read the quotient last, since that's what clears the dirty flag. + "ldr r3, [r2, #0x060]", // DIV_UDIVIDEND + "ldr r4, [r2, #0x064]", // DIV_UDIVISOR + "ldr r5, [r2, #0x074]", // DIV_REMAINDER + "ldr r6, [r2, #0x070]", // DIV_QUOTIENT + // If we get interrupted here (before a write sets the DIRTY flag) it's + // fine, since we have the full state, so the interruptor doesn't have to + // restore it. Once the write happens and the DIRTY flag is set, the + // interruptor becomes responsible for restoring our state. + "bl 1b", + // If we are interrupted here, then the interruptor will start an incorrect + // calculation using a wrong divisor, but we'll restore the divisor and + // result ourselves correctly. This sets DIRTY, so any interruptor will + // save the state. + "str r3, [r2, #0x060]", // DIV_UDIVIDEND + // If we are interrupted here, the the interruptor may start the + // calculation using incorrectly signed inputs, but we'll restore the + // result ourselves. This sets DIRTY, so any interruptor will save the + // state. + "str r4, [r2, #0x064]", // DIV_UDIVISOR + // If we are interrupted here, the interruptor will have restored + // everything but the quotient may be wrongly signed. If the calculation + // started by the above writes is still ongoing it is stopped, so it won't + // replace the result we're restoring. DIRTY and READY set, but only + // DIRTY matters to make the interruptor save the state. + "str r5, [r2, #0x074]", // DIV_REMAINDER + // State fully restored after the quotient write. This sets both DIRTY + // and READY, so whatever we may have interrupted can read the result. + "str r6, [r2, #0x070]", // DIV_QUOTIENT + "pop {{r4-r6, pc}}", + ) + }; +} + +macro_rules! division_function { + ( + $name:ident $($intrinsic:ident)* ( $argty:ty ) { + $($begin:literal),+ + } + ) => { + #[cfg(all(target_arch = "arm", not(feature = "disable-intrinsics")))] + core::arch::global_asm!( + // Mangle the name slightly, since this is a global symbol. + concat!(".global _rphal_", stringify!($name)), + concat!(".type _rphal_", stringify!($name), ", %function"), + ".align 2", + concat!("_rphal_", stringify!($name), ":"), + $( + concat!(".global ", stringify!($intrinsic)), + concat!(".type ", stringify!($intrinsic), ", %function"), + concat!(stringify!($intrinsic), ":"), + )* + + hwdivider_head!(), + $($begin),+ , + hwdivider_tail!(), + ); + + #[cfg(all(target_arch = "arm", feature = "disable-intrinsics"))] + core::arch::global_asm!( + // Mangle the name slightly, since this is a global symbol. + concat!(".global _rphal_", stringify!($name)), + concat!(".type _rphal_", stringify!($name), ", %function"), + ".align 2", + concat!("_rphal_", stringify!($name), ":"), + + hwdivider_head!(), + $($begin),+ , + hwdivider_tail!(), + ); + + #[cfg(target_arch = "arm")] + extern "aapcs" { + // Connect a local name to global symbol above through FFI. + #[link_name = concat!("_rphal_", stringify!($name)) ] + fn $name(n: $argty, d: $argty) -> u64; + } + + #[cfg(not(target_arch = "arm"))] + #[allow(unused_variables)] + unsafe fn $name(n: $argty, d: $argty) -> u64 { 0 } + }; +} + +division_function! { + unsigned_divmod __aeabi_uidivmod __aeabi_uidiv ( u32 ) { + "str r0, [r2, #0x060]", // DIV_UDIVIDEND + "str r1, [r2, #0x064]" // DIV_UDIVISOR + } +} + +division_function! { + signed_divmod __aeabi_idivmod __aeabi_idiv ( i32 ) { + "str r0, [r2, #0x068]", // DIV_SDIVIDEND + "str r1, [r2, #0x06c]" // DIV_SDIVISOR + } +} + +fn divider_unsigned(n: u32, d: u32) -> DivResult { + let packed = unsafe { unsigned_divmod(n, d) }; + DivResult { + quotient: packed as u32, + remainder: (packed >> 32) as u32, + } +} + +fn divider_signed(n: i32, d: i32) -> DivResult { + let packed = unsafe { signed_divmod(n, d) }; + // Double casts to avoid sign extension + DivResult { + quotient: packed as u32 as i32, + remainder: (packed >> 32) as u32 as i32, + } +} + +impl HwDivider { + /// Perform hardware unsigned divide/modulo operation + pub fn unsigned(&self, dividend: u32, divisor: u32) -> DivResult { + divider_unsigned(dividend, divisor) + } + + /// Perform hardware signed divide/modulo operation + pub fn signed(&self, dividend: i32, divisor: i32) -> DivResult { + divider_signed(dividend, divisor) + } +} + +intrinsics! { + extern "C" fn __udivsi3(n: u32, d: u32) -> u32 { + divider_unsigned(n, d).quotient + } + + extern "C" fn __umodsi3(n: u32, d: u32) -> u32 { + divider_unsigned(n, d).remainder + } + + extern "C" fn __udivmodsi4(n: u32, d: u32, rem: Option<&mut u32>) -> u32 { + let quo_rem = divider_unsigned(n, d); + if let Some(rem) = rem { + *rem = quo_rem.remainder; + } + quo_rem.quotient + } + + extern "C" fn __divsi3(n: i32, d: i32) -> i32 { + divider_signed(n, d).quotient + } + + extern "C" fn __modsi3(n: i32, d: i32) -> i32 { + divider_signed(n, d).remainder + } + + extern "C" fn __divmodsi4(n: i32, d: i32, rem: &mut i32) -> i32 { + let quo_rem = divider_signed(n, d); + *rem = quo_rem.remainder; + quo_rem.quotient + } +} + +/// This type is just used to limit us to Spinlocks `0..=31` +pub trait SpinlockValid: Sealed {} + +/// Hardware based spinlock. +/// +/// You can claim this lock by calling either [`claim`], [`try_claim`] or +/// [`claim_async`]. These spin-locks are hardware backed, so if you lock +/// e.g. `Spinlock<6>`, then any other part of your application using +/// `Spinlock<6>` will contend for the same lock, without them needing to +/// share a reference or otherwise communicate with each other. +/// +/// When the obtained spinlock goes out of scope, it is automatically unlocked. +/// +/// +/// ```no_run +/// use rp2040_hal::sio::Spinlock0; +/// static mut SOME_GLOBAL_VAR: u32 = 0; +/// +/// /// This function is safe to call from two different cores, but is not safe +/// /// to call from an interrupt routine! +/// fn update_global_var() { +/// // Do not say `let _ = ` here - it will immediately unlock! +/// let _lock = Spinlock0::claim(); +/// // Do your thing here that Core 0 and Core 1 might want to do at the +/// // same time, like update this global variable: +/// unsafe { SOME_GLOBAL_VAR += 1 }; +/// // The lock is dropped here. +/// } +/// ``` +/// +/// **Warning**: These spinlocks are not re-entrant, meaning that the +/// following code will cause a deadlock: +/// +/// ```no_run +/// use rp2040_hal::sio::Spinlock0; +/// let lock_1 = Spinlock0::claim(); +/// let lock_2 = Spinlock0::claim(); // deadlock here +/// ``` +/// +/// **Note:** The `critical-section` implementation uses Spinlock 31. +/// +/// [`claim`]: #method.claim +/// [`try_claim`]: #method.try_claim +/// [`claim_async`]: #method.claim_asyncs +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 + let sio = unsafe { &*pac::SIO::ptr() }; + let lock = sio.spinlock(N).read().bits(); + if lock > 0 { + Some(Self(core::marker::PhantomData)) + } else { + None + } + } + + /// Claim the spinlock, will block the current thread until the lock is available. + /// + /// Note that calling this multiple times in a row will cause a deadlock + pub fn claim() -> Self { + loop { + if let Some(result) = Self::try_claim() { + break result; + } + } + } + + /// Try to claim the spinlock. Will return `WouldBlock` until the spinlock is available. + pub fn claim_async() -> nb::Result { + Self::try_claim().ok_or(nb::Error::WouldBlock) + } + + /// Clear a locked spin-lock. + /// + /// # Safety + /// + /// Only call this function if you hold the spin-lock. + pub unsafe fn release() { + let sio = &*pac::SIO::ptr(); + // Write (any value): release the lock + sio.spinlock(N).write_with_zero(|b| b.bits(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() } + } +} + +macro_rules! spinlock { + ($first:expr, $($rest:tt),+) => { + spinlock!($first); + spinlock!($($rest),+); + }; + ($id:expr) => { + $crate::paste::paste! { + /// Spinlock number $id + pub type [] = Spinlock<$id>; + impl SpinlockValid for Spinlock<$id> {} + impl Sealed for Spinlock<$id> {} + } + }; +} +spinlock!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30 +); + +/// Spinlock number 31 - used by critical section implementation +#[cfg(feature = "critical-section-impl")] +pub(crate) type Spinlock31 = Spinlock<31>; + +/// Spinlock number 31 - only public if critical-section-impl is not enabled +#[cfg(not(feature = "critical-section-impl"))] +pub type Spinlock31 = Spinlock<31>; + +impl SpinlockValid for Spinlock<31> {} +impl Sealed for Spinlock<31> {} + +/// Returns the current state of the spinlocks. Each index corresponds to the associated spinlock, e.g. if index `5` is set to `true`, it means that [`Spinlock5`] is currently locked. +/// +/// Note that spinlocks can be claimed or released at any point, so this function cannot guarantee the spinlock is actually available right after calling this function. This function is mainly intended for debugging. +pub fn spinlock_state() -> [bool; 32] { + // Safety: we're only reading from a register + let sio = unsafe { &*pac::SIO::ptr() }; + // A bitmap containing the state of all 32 spinlocks (1=locked). + let register = sio.spinlock_st().read().bits(); + let mut result = [false; 32]; + #[allow(clippy::needless_range_loop)] + for i in 0..32 { + result[i] = (register & (1 << i)) > 0; + } + result +} + +/// Free all spinlocks, regardless of their current status +/// +/// RP2040 does not release all spinlocks on reset. +/// The C SDK clears these all during entry, and so do we if you call hal::entry! +/// But if someone is using the default cortex-m entry they risk hitting deadlocks so provide *something* to help out +/// +/// # Safety +/// Where possible, you should use the hal::entry macro attribute on main instead of this. +/// You should call this as soon as possible after reset - preferably as the first entry in fn main(), before *ANY* use of spinlocks, atomics, or critical_section +pub unsafe fn spinlock_reset() { + // Using raw pointers to avoid taking peripherals accidently at startup + const SIO_BASE: u32 = 0xd0000000; + const SPINLOCK0_PTR: *mut u32 = (SIO_BASE + 0x100) as *mut u32; + const SPINLOCK_COUNT: usize = 32; + for i in 0..SPINLOCK_COUNT { + SPINLOCK0_PTR.wrapping_add(i).write_volatile(1); + } +} + +/// Configuration struct for one lane of the interpolator +pub struct LaneCtrl { + /// Bit 22 - Only present on INTERP1 on each core. If CLAMP mode is enabled: + /// - LANE0 result is shifted and masked ACCUM0, clamped by a lower bound of + /// BASE0 and an upper bound of BASE1. + /// - Signedness of these comparisons is determined by LANE0_CTRL_SIGNED + pub clamp: bool, + /// Bit 21 - Only present on INTERP0 on each core. If BLEND mode is enabled: + /// - LANE1 result is a linear interpolation between BASE0 and BASE1, controlled + /// by the 8 LSBs of lane 1 shift and mask value (a fractional number between + /// 0 and 255/256ths) + /// - LANE0 result does not have BASE0 added (yields only + /// the 8 LSBs of lane 1 shift+mask value) + /// - FULL result does not have lane 1 shift+mask value added (BASE2 + lane 0 shift+mask) + /// + /// LANE1 SIGNED flag controls whether the interpolation is signed or unsigned. + pub blend: bool, + /// Bits 19:20 - ORed into bits 29:28 of the lane result presented to the processor on the bus. + /// No effect on the internal 32-bit datapath. Handy for using a lane to generate sequence + /// of pointers into flash or SRAM. + pub force_msb: u8, + /// Bit 18 - If 1, mask + shift is bypassed for LANE0 result. This does not affect FULL result. + pub add_raw: bool, + /// Bit 17 - If 1, feed the opposite lane's result into this lane's accumulator on POP. + pub cross_result: bool, + /// Bit 16 - If 1, feed the opposite lane's accumulator into this lane's shift + mask hardware. + /// Takes effect even if ADD_RAW is set (the CROSS_INPUT mux is before the shift+mask bypass) + pub cross_input: bool, + /// Bit 15 - If SIGNED is set, the shifted and masked accumulator value is sign-extended to 32 bits + /// before adding to BASE0, and LANE0 PEEK/POP appear extended to 32 bits when read by processor. + pub signed: bool, + /// Bits 10:14 - The most-significant bit allowed to pass by the mask (inclusive) + /// Setting MSB < LSB may cause chip to turn inside-out + pub mask_msb: u8, + /// Bits 5:9 - The least-significant bit allowed to pass by the mask (inclusive) + pub mask_lsb: u8, + /// Bits 0:4 - Logical right-shift applied to accumulator before masking + pub shift: u8, +} + +impl Default for LaneCtrl { + fn default() -> Self { + Self::new() + } +} + +impl LaneCtrl { + /// Default configuration. Normal operation, unsigned, mask keeps all bits, no shift. + pub const fn new() -> Self { + Self { + clamp: false, + blend: false, + force_msb: 0, + add_raw: false, + cross_result: false, + cross_input: false, + signed: false, + mask_msb: 31, + mask_lsb: 0, + shift: 0, + } + } + + /// encode the configuration to be loaded in the ctrl register of one lane of an interpolator + pub const fn encode(&self) -> u32 { + assert!(!(self.blend && self.clamp)); + assert!(self.force_msb < 0b100); + assert!(self.mask_msb < 0b100000); + assert!(self.mask_lsb < 0b100000); + assert!(self.mask_msb >= self.mask_lsb); + assert!(self.shift < 0b100000); + ((self.clamp as u32) << 22) + | ((self.blend as u32) << 21) + | ((self.force_msb as u32) << 19) + | ((self.add_raw as u32) << 18) + | ((self.cross_result as u32) << 17) + | ((self.cross_input as u32) << 16) + | ((self.signed as u32) << 15) + | ((self.mask_msb as u32) << 10) + | ((self.mask_lsb as u32) << 5) + | (self.shift as u32) + } +} + +///Trait representing the functionality of a single lane of an interpolator. +pub trait Lane: Sealed { + ///Read the lane result, and simultaneously write lane results to both accumulators. + fn pop(&mut self) -> u32; + ///Read the lane result without altering any internal state + fn peek(&self) -> u32; + ///Write a value to the accumulator + fn set_accum(&mut self, v: u32); + ///Read the value from the accumulator + fn get_accum(&self) -> u32; + ///Write a value to the base register + fn set_base(&mut self, v: u32); + ///Read the value from the base register + fn get_base(&self) -> u32; + ///Write to the control register + fn set_ctrl(&mut self, v: u32); + ///Read from the control register + fn get_ctrl(&self) -> u32; + ///Add the value to the accumulator register + fn add_accum(&mut self, v: u32); + ///Read the raw shift and mask value (BASE register not added) + fn read_raw(&self) -> u32; +} + +///Trait representing the functionality of an interpolator. +/// ```no_run +/// use rp2040_hal::sio::{Sio,LaneCtrl,Lane}; +/// use rp2040_hal::pac; +/// let mut peripherals = pac::Peripherals::take().unwrap(); +/// let mut sio = Sio::new(peripherals.SIO); +/// +/// // by having the configuration const, the validity is checked during compilation. +/// const config: u32 = LaneCtrl { +/// mask_msb: 4, // Most significant bit of the mask is bit 4 +/// // By default the least significant bit is bit 0 +/// // this will keep only the 5 least significant bits. +/// // this is equivalent to %32 +/// ..LaneCtrl::new() +/// }.encode(); +/// sio.interp0.get_lane0().set_ctrl(config); +/// sio.interp0.get_lane0().set_accum(0); +/// sio.interp0.get_lane0().set_base(1); // will increment the value by 1 on each call to pop +/// +/// sio.interp0.get_lane0().peek(); // returns 1 +/// sio.interp0.get_lane0().pop(); // returns 1 +/// sio.interp0.get_lane0().pop(); // returns 2 +/// sio.interp0.get_lane0().pop(); // returns 3 +/// ``` +pub trait Interp: Sealed { + ///Read the interpolator result (Result 2 in the datasheet), and simultaneously write lane results to both accumulators. + fn pop(&mut self) -> u32; + ///Read the interpolator result (Result 2 in the datasheet) without altering any internal state + fn peek(&self) -> u32; + ///Write to the interpolator Base register (Base2 in the datasheet) + fn set_base(&mut self, v: u32); + ///Read the interpolator Base register (Base2 in the datasheet) + fn get_base(&self) -> u32; + ///Write the lower 16 bits to BASE0 and the upper bits to BASE1 simultaneously. Each half is sign-extended to 32 bits if that lane's SIGNED flag is set + fn set_base_1and0(&mut self, v: u32); +} + +macro_rules! interpolators { + ( + $($interp:ident : ( $( [ $lane:ident,$lane_id:expr ] ),+ ) ),+ + ) => { + $crate::paste::paste! { + + + $( + $( + #[doc = "The lane " $lane_id " of " $interp] + pub struct [<$interp $lane>]{ + _private: (), + } + impl Lane for [<$interp $lane>]{ + fn pop(&mut self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _pop_ $lane:lower>]().read().bits() + } + fn peek(&self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _peek_ $lane:lower>]().read().bits() + } + fn set_accum(&mut self,v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id>]().write(|w| unsafe { w.bits(v) }); + } + fn get_accum(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id>]().read().bits() + } + fn set_base(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base $lane_id>]().write(|w| unsafe { w.bits(v) }); + } + fn get_base(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base $lane_id>]().read().bits() + } + fn set_ctrl(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _ctrl_lane $lane_id>]().write(|w| unsafe { w.bits(v) }); + } + fn get_ctrl(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _ctrl_lane $lane_id>]().read().bits() + } + fn add_accum(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id _add>]().write(|w| unsafe { w.bits(v) }); + } + fn read_raw(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id _add>]().read().bits() + } + } + impl Sealed for [<$interp $lane>] {} + )+ + #[doc = "Interpolator " $interp] + pub struct $interp { + $( + [<$lane:lower>]: [<$interp $lane>], + )+ + } + impl $interp{ + $( + /// Lane accessor function + pub fn [](&mut self)->&mut [<$interp $lane>]{ + &mut self.[<$lane:lower>] + } + )+ + } + impl Interp for $interp{ + fn pop(&mut self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _pop_full>]().read().bits() + } + fn peek(&self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _peek_full>]().read().bits() + } + fn set_base(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base2>]().write(|w| unsafe { w.bits(v)}); + } + fn get_base(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base2>]().read().bits() + } + fn set_base_1and0(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base_1and0>]().write(|w| unsafe { w.bits(v)}); + } + } + impl Sealed for $interp {} + )+ + } + } + } + +interpolators!( + Interp0 : ([Lane0,0],[Lane1,1]), + Interp1 : ([Lane0,0],[Lane1,1]) +); diff --git a/rp-hal/rp2040-hal/src/spi.rs b/rp-hal/rp2040-hal/src/spi.rs new file mode 100644 index 0000000..58425ab --- /dev/null +++ b/rp-hal/rp2040-hal/src/spi.rs @@ -0,0 +1,562 @@ +//! Serial Peripheral Interface (SPI) +//! +//! [`Spi`] is the main struct exported by this module, representing a configured Spi bus. See its +//! docs for more information on its type parameters. +//! +//! See [Chapter 4 Section 4](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details +//! +//! ## Usage +//! +//! ```no_run +//! use embedded_hal::spi::MODE_0; +//! use fugit::RateExtU32; +//! use rp2040_hal::{spi::Spi, gpio::{Pins, FunctionSpi}, pac, Sio}; +//! +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS); +//! +//! let sclk = pins.gpio2.into_function::(); +//! let mosi = pins.gpio3.into_function::(); +//! +//! let spi_device = peripherals.SPI0; +//! let spi_pin_layout = (mosi, sclk); +//! +//! let spi = Spi::<_, _, _, 8>::new(spi_device, spi_pin_layout) +//! .init(&mut peripherals.RESETS, 125_000_000u32.Hz(), 16_000_000u32.Hz(), MODE_0); +//! ``` + +use core::{convert::Infallible, marker::PhantomData, ops::Deref}; + +use embedded_hal::spi::{self, Phase, Polarity}; +// Support Embedded HAL 0.2 for backwards-compatibility +use embedded_hal_0_2::{blocking::spi as blocking_spi02, spi as spi02}; +use embedded_hal_nb::spi::FullDuplex; +use fugit::{HertzU32, RateExtU32}; + +use crate::{ + dma::{EndlessReadTarget, EndlessWriteTarget, ReadTarget, WriteTarget}, + pac::{self, dma::ch::ch_ctrl_trig::TREQ_SEL_A, RESETS}, + resets::SubsystemReset, + typelevel::Sealed, +}; + +mod pins; +pub use pins::*; + +impl From for FrameFormat { + fn from(f: spi::Mode) -> Self { + Self::MotorolaSpi(f) + } +} + +impl From<&spi::Mode> for FrameFormat { + fn from(f: &spi::Mode) -> Self { + Self::MotorolaSpi(*f) + } +} + +/// SPI frame format +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum FrameFormat { + /// Motorola SPI format. See section 4.4.3.9 of RP2040 datasheet. + MotorolaSpi(spi::Mode), + /// Texas Instruments synchronous serial frame format. See section 4.4.3.8 of RP2040 datasheet. + TexasInstrumentsSynchronousSerial, + /// National Semiconductor Microwire frame format. See section 4.4.3.14 of RP2040 datasheet. + NationalSemiconductorMicrowire, +} + +impl From<&embedded_hal_0_2::spi::Mode> for FrameFormat { + fn from(f: &embedded_hal_0_2::spi::Mode) -> Self { + let embedded_hal_0_2::spi::Mode { polarity, phase } = f; + match (polarity, phase) { + (spi02::Polarity::IdleLow, spi02::Phase::CaptureOnFirstTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_0) + } + (spi02::Polarity::IdleLow, spi02::Phase::CaptureOnSecondTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_1) + } + (spi02::Polarity::IdleHigh, spi02::Phase::CaptureOnFirstTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_2) + } + (spi02::Polarity::IdleHigh, spi02::Phase::CaptureOnSecondTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_3) + } + } + } +} + +impl From for FrameFormat { + fn from(f: embedded_hal_0_2::spi::Mode) -> Self { + From::from(&f) + } +} + +/// State of the SPI +pub trait State: Sealed {} + +/// Spi is disabled +pub struct Disabled { + __private: (), +} + +/// Spi is enabled +pub struct Enabled { + __private: (), +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Enabled {} +impl Sealed for Enabled {} + +/// Pac SPI device +pub trait SpiDevice: Deref + SubsystemReset + Sealed { + /// Index of the peripheral. + const ID: usize; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8; + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8; +} + +impl Sealed for pac::SPI0 {} +impl SpiDevice for pac::SPI0 { + const ID: usize = 0; + fn tx_dreq() -> u8 { + TREQ_SEL_A::SPI0_TX.into() + } + fn rx_dreq() -> u8 { + TREQ_SEL_A::SPI0_RX.into() + } +} +impl Sealed for pac::SPI1 {} +impl SpiDevice for pac::SPI1 { + const ID: usize = 1; + fn tx_dreq() -> u8 { + TREQ_SEL_A::SPI1_TX.into() + } + fn rx_dreq() -> u8 { + TREQ_SEL_A::SPI1_RX.into() + } +} + +/// Data size used in spi +pub trait DataSize: Sealed {} + +impl DataSize for u8 {} +impl DataSize for u16 {} +impl Sealed for u8 {} +impl Sealed for u16 {} + +/// Configured Spi bus. +/// +/// This struct implements the `embedded-hal` Spi-related traits. It represents unique ownership +/// of the entire Spi bus of a single configured RP2040 Spi peripheral. +/// +/// `Spi` has four generic parameters: +/// - `S`: a typestate for whether the bus is [`Enabled`] or [`Disabled`]. Upon initial creation, +/// the bus is [`Disabled`]. You will then need to initialize it as either a main (master) or sub +/// (slave) device, providing the necessary configuration, at which point it will become [`Enabled`]. +/// - `D`: Which of the concrete Spi peripherals is being used, [`pac::SPI0`] or [`pac::SPI1`] +/// - `P`: Which pins are being used to configure the Spi peripheral `D`. A table of valid +/// pinouts for each Spi peripheral can be found in section 1.4.3 of the RP2040 datasheet. +/// The [`ValidSpiPinout`] trait is implemented for tuples of pin types that follow the layout: +/// - `(Tx, Sck)` (i.e. first the "Tx"/"MOSI" pin, then the "Sck"/"Clock" pin) +/// - `(Tx, Rx, Sck)` (i.e. first "Tx"/"MOSI", then "Rx"/"MISO", then "Sck"/"Clock" pin) +/// +/// If you select an invalid layout, you will get a compile error that `P` does not implement +/// [`ValidSpiPinout`] for your specified [`SpiDevice`] peripheral `D` +/// - `DS`: The "data size", i.e. the number of bits transferred per data frame. Defaults to 8. +/// +/// In most cases you won't have to specify these types manually and can let the compiler infer +/// them for you based on the values you pass in to `new`. If you want to select a different +/// data frame size, you'll need to do that by specifying the `DS` parameter manually. +/// +/// See [the module level docs][self] for an example. +pub struct Spi, const DS: u8 = 8u8> { + device: D, + pins: P, + state: PhantomData, +} + +impl, const DS: u8> Spi { + fn transition(self, _: To) -> Spi { + Spi { + device: self.device, + pins: self.pins, + state: PhantomData, + } + } + + /// Releases the underlying device and pins. + pub fn free(self) -> (D, P) { + (self.device, self.pins) + } + + /// Set device pre-scale and post-div properties to match the given baudrate as + /// closely as possible based on the given peripheral clock frequency. + /// + /// Typically the peripheral clock is set to 125_000_000 Hz. + /// + /// Returns the frequency that we were able to achieve, which may not be exactly + /// the requested baudrate. + pub fn set_baudrate, B: Into>( + &mut self, + peri_frequency: F, + baudrate: B, + ) -> HertzU32 { + let freq_in = peri_frequency.into().to_Hz(); + let baudrate = baudrate.into().to_Hz(); + let mut prescale: u8 = u8::MAX; + let mut postdiv: u8 = 0; + + // Find smallest prescale value which puts output frequency in range of + // post-divide. Prescale is an even number from 2 to 254 inclusive. + for prescale_option in (2u32..=254).step_by(2) { + // We need to use an saturating_mul here because with a high baudrate certain invalid prescale + // values might not fit in u32. However we can be sure those values exceed the max sys_clk frequency + // So clamping a u32::MAX is fine here... + if freq_in < ((prescale_option + 2) * 256).saturating_mul(baudrate) { + prescale = prescale_option as u8; + break; + } + } + + // We might not find a prescale value that lowers the clock freq enough, so we leave it at max + debug_assert_ne!(prescale, u8::MAX); + + // Find largest post-divide which makes output <= baudrate. Post-divide is + // an integer in the range 0 to 255 inclusive. + for postdiv_option in (1..=255u8).rev() { + if freq_in / (prescale as u32 * postdiv_option as u32) > baudrate { + postdiv = postdiv_option; + break; + } + } + + self.device + .sspcpsr() + .write(|w| unsafe { w.cpsdvsr().bits(prescale) }); + self.device + .sspcr0() + .modify(|_, w| unsafe { w.scr().bits(postdiv) }); + + // Return the frequency we were able to achieve + (freq_in / (prescale as u32 * (1 + postdiv as u32))).Hz() + } + + /// Set format + pub fn set_format(&mut self, frame_format: FrameFormat) { + self.device.sspcr0().modify(|_, w| unsafe { + w.dss().bits(DS - 1).frf().bits(match &frame_format { + FrameFormat::MotorolaSpi(_) => 0x00, + FrameFormat::TexasInstrumentsSynchronousSerial => 0x01, + FrameFormat::NationalSemiconductorMicrowire => 0x10, + }); + + /* + * Clock polarity (SPO) and clock phase (SPH) are only applicable to + * the Motorola SPI frame format. + */ + if let FrameFormat::MotorolaSpi(ref mode) = frame_format { + w.spo() + .bit(mode.polarity == Polarity::IdleHigh) + .sph() + .bit(mode.phase == Phase::CaptureOnSecondTransition); + } + w + }); + } +} + +impl, const DS: u8> Spi { + /// Create new not initialized Spi bus. Initialize it with [`.init`][Self::init] + /// or [`.init_slave`][Self::init_slave]. + /// + /// Valid pin sets are in the form of `(Tx, Sck)` or `(Tx, Rx, Sck)` + /// + /// If your pins are dynamically identified (`Pin`) they will first need to pass + /// validation using their corresponding [`ValidatedPinXX`](ValidatedPinTx). + pub fn new(device: D, pins: P) -> Spi { + Spi { + device, + pins, + state: PhantomData, + } + } + + /// Set master/slave + fn set_slave(&mut self, slave: bool) { + if slave { + self.device.sspcr1().modify(|_, w| w.ms().set_bit()); + } else { + self.device.sspcr1().modify(|_, w| w.ms().clear_bit()); + } + } + + fn init_spi, B: Into>( + mut self, + resets: &mut RESETS, + peri_frequency: F, + baudrate: B, + frame_format: FrameFormat, + slave: bool, + ) -> Spi { + self.device.reset_bring_down(resets); + self.device.reset_bring_up(resets); + + self.set_baudrate(peri_frequency, baudrate); + self.set_format(frame_format); + self.set_slave(slave); + // Always enable DREQ signals -- harmless if DMA is not listening + self.device + .sspdmacr() + .modify(|_, w| w.txdmae().set_bit().rxdmae().set_bit()); + + // Finally enable the SPI + self.device.sspcr1().modify(|_, w| w.sse().set_bit()); + + self.transition(Enabled { __private: () }) + } + + /// Initialize the SPI in master mode + pub fn init, B: Into, M: Into>( + self, + resets: &mut RESETS, + peri_frequency: F, + baudrate: B, + frame_format: M, + ) -> Spi { + self.init_spi(resets, peri_frequency, baudrate, frame_format.into(), false) + } + + /// Initialize the SPI in slave mode + pub fn init_slave>( + self, + resets: &mut RESETS, + frame_format: M, + ) -> Spi { + // Use dummy values for frequency and baudrate. + // With both values 0, set_baudrate will set prescale == u8::MAX, which will break if debug assertions are enabled. + // u8::MAX is outside the allowed range 2..=254 for CPSDVSR, which might interfere with proper operation in slave mode. + self.init_spi( + resets, + 1000u32.Hz(), + 1000u32.Hz(), + frame_format.into(), + true, + ) + } +} + +impl, const DS: u8> Spi { + fn is_writable(&self) -> bool { + self.device.sspsr().read().tnf().bit_is_set() + } + fn is_readable(&self) -> bool { + self.device.sspsr().read().rne().bit_is_set() + } + + /// Check if spi is busy transmitting and/or receiving + pub fn is_busy(&self) -> bool { + self.device.sspsr().read().bsy().bit_is_set() + } + + /// Disable the spi to reset its configuration. You'll then need to initialize it again to use + /// it. + pub fn disable(self) -> Spi { + self.device.sspcr1().modify(|_, w| w.sse().clear_bit()); + + self.transition(Disabled { __private: () }) + } +} + +macro_rules! impl_write { + ($type:ident, [$($nr:expr),+]) => { + + $( + impl> spi02::FullDuplex<$type> for Spi { + type Error = Infallible; + + fn read(&mut self) -> Result<$type, nb::Error> { + if !self.is_readable() { + return Err(nb::Error::WouldBlock); + } + + Ok(self.device.sspdr().read().data().bits() as $type) + } + fn send(&mut self, word: $type) -> Result<(), nb::Error> { + // Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX + // is full, PL022 inhibits RX pushes, and sets a sticky flag on + // push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set. + if !self.is_writable() { + return Err(nb::Error::WouldBlock); + } + + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(word as u16) }); + Ok(()) + } + } + + impl> blocking_spi02::write::Default<$type> for Spi {} + impl> blocking_spi02::transfer::Default<$type> for Spi {} + impl> blocking_spi02::write_iter::Default<$type> for Spi {} + + impl> spi::ErrorType for Spi { + type Error = Infallible; + } + + impl> spi::SpiBus<$type> for Spi { + fn read(&mut self, words: &mut [$type]) -> Result<(), Self::Error> { + for word in words.iter_mut() { + // write empty word + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(0) }); + + // read one word + while !self.is_readable() {} + *word = self.device.sspdr().read().data().bits() as $type; + } + Ok(()) + } + + fn write(&mut self, words: &[$type]) -> Result<(), Self::Error> { + for word in words.iter() { + // write one word + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(*word as u16) }); + + // drop read wordd + while !self.is_readable() {} + let _ = self.device.sspdr().read().data().bits(); + } + Ok(()) + } + + fn transfer(&mut self, read: &mut [$type], write: &[$type]) -> Result<(), Self::Error>{ + let len = read.len().max(write.len()); + for i in 0..len { + // write one word. Send empty word if buffer is empty. + let wb = write.get(i).copied().unwrap_or(0); + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(wb as u16) }); + + // read one word. Drop extra words if buffer is full. + while !self.is_readable() {} + let rb = self.device.sspdr().read().data().bits() as $type; + if let Some(r) = read.get_mut(i) { + *r = rb; + } + } + + Ok(()) + } + + fn transfer_in_place(&mut self, words: &mut [$type]) -> Result<(), Self::Error>{ + for word in words.iter_mut() { + // write one word + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(*word as u16) }); + + // read one word + while !self.is_readable() {} + *word = self.device.sspdr().read().data().bits() as $type; + } + + Ok(()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + while self.is_busy() {} + Ok(()) + } + } + + impl> FullDuplex<$type> for Spi { + fn read(&mut self) -> Result<$type, nb::Error> { + if !self.is_readable() { + return Err(nb::Error::WouldBlock); + } + + Ok(self.device.sspdr().read().data().bits() as $type) + } + fn write(&mut self, word: $type) -> Result<(), nb::Error> { + // Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX + // is full, PL022 inhibits RX pushes, and sets a sticky flag on + // push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set. + if !self.is_writable() { + return Err(nb::Error::WouldBlock); + } + + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(word as u16) }); + Ok(()) + } + } + + // Safety: This only reads from the RX fifo, so it doesn't + // interact with rust-managed memory. + unsafe impl> ReadTarget for Spi { + type ReceivedWord = $type; + + fn rx_treq() -> Option { + Some(D::rx_dreq()) + } + + fn rx_address_count(&self) -> (u32, u32) { + ( + self.device.sspdr().as_ptr() as u32, + u32::MAX, + ) + } + + fn rx_increment(&self) -> bool { + false + } + } + + impl> EndlessReadTarget for Spi {} + + // Safety: This only writes to the TX fifo, so it doesn't + // interact with rust-managed memory. + unsafe impl> WriteTarget for Spi { + type TransmittedWord = $type; + + fn tx_treq() -> Option { + Some(D::tx_dreq()) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + ( + self.device.sspdr().as_ptr() as u32, + u32::MAX, + ) + } + + fn tx_increment(&self) -> bool { + false + } + } + + impl> EndlessWriteTarget for Spi {} + )+ + + }; +} + +impl_write!(u8, [4, 5, 6, 7, 8]); +impl_write!(u16, [9, 10, 11, 12, 13, 14, 15, 16]); diff --git a/rp-hal/rp2040-hal/src/spi/pins.rs b/rp-hal/rp2040-hal/src/spi/pins.rs new file mode 100644 index 0000000..7eb96c1 --- /dev/null +++ b/rp-hal/rp2040-hal/src/spi/pins.rs @@ -0,0 +1,140 @@ +use core::marker::PhantomData; + +use crate::{ + gpio::{bank0::*, pin::pin_sealed::TypeLevelPinId, AnyPin, FunctionSpi}, + pac::{SPI0, SPI1}, + typelevel::{OptionT, OptionTNone, OptionTSome, Sealed}, +}; + +use super::SpiDevice; + +macro_rules! pin_validation { + ($p:ident) => { + paste::paste!{ + #[doc = "Indicates a valid " $p " pin for SPI0 or SPI1"] + pub trait []: Sealed {} + + #[doc = "Indicates a valid " $p " pin for SPI0 or SPI1"] + pub trait []: Sealed {} + + impl [] for T + where + T: AnyPin, + T::Id: [], + { + } + + #[doc = "A runtime validated " $p " pin for spi."] + pub struct [](P, PhantomData); + impl Sealed for [] {} + impl [] for [] {} + impl [] + where + P: AnyPin, + S: SpiDevice, + { + /// Validate a pin's function on a spi peripheral. + /// + #[doc = "Will err if the pin cannot be used as a " $p " pin for that Spi."] + pub fn validate(p: P, _u: &S) -> Result { + if [<$p:upper>].contains(&(p.borrow().id().num, S::ID)) && + p.borrow().id().bank == crate::gpio::DynBankId::Bank0 { + Ok(Self(p, PhantomData)) + } else { + Err(p) + } + } + } + + #[doc = "Indicates a valid optional " $p " pin for SPI0 or SPI1"] + pub trait []: OptionT {} + + impl [] for OptionTNone {} + impl [] for OptionTSome + where + U: SpiDevice, + T: [], + { + } + } + }; + ($($p:ident),*) => { + $( + pin_validation!($p); + )* + }; +} +pin_validation!(Tx, Rx, Sck, Cs); + +macro_rules! impl_valid_spi { + ($($spi:ident: { + rx: [$($rx:ident),*], + cs: [$($cs:ident),*], + sck: [$($sck:ident),*], + tx: [$($tx:ident),*], + }),*) => { + $( + $(impl ValidPinIdRx<$spi> for $rx {})* + $(impl ValidPinIdTx<$spi> for $tx {})* + $(impl ValidPinIdSck<$spi> for $sck {})* + $(impl ValidPinIdCs<$spi> for $cs {})* + )* + + const RX: &[(u8, usize)] = &[$($(($rx::ID.num, $spi::ID)),*),*]; + const TX: &[(u8, usize)] = &[$($(($tx::ID.num, $spi::ID)),*),*]; + const SCK: &[(u8, usize)] = &[$($(($sck::ID.num, $spi::ID)),*),*]; + const CS: &[(u8, usize)] = &[$($(($cs::ID.num, $spi::ID)),*),*]; + }; +} + +impl_valid_spi!( + SPI0: { + rx: [Gpio0, Gpio4, Gpio16, Gpio20], + cs: [Gpio1, Gpio5, Gpio17, Gpio21], + sck: [Gpio2, Gpio6, Gpio18, Gpio22], + tx: [Gpio3, Gpio7, Gpio19, Gpio23], + }, + SPI1: { + rx: [Gpio8, Gpio12, Gpio24, Gpio28], + cs: [Gpio9, Gpio13, Gpio25, Gpio29], + sck: [Gpio10, Gpio14, Gpio26], + tx: [Gpio11, Gpio15, Gpio27], + } +); + +/// Declares a valid SPI pinout. +pub trait ValidSpiPinout: Sealed { + #[allow(missing_docs)] + type Rx: ValidOptionRx; + #[allow(missing_docs)] + type Cs: ValidOptionCs; + #[allow(missing_docs)] + type Sck: ValidOptionSck; + #[allow(missing_docs)] + type Tx: ValidOptionTx; +} + +impl ValidSpiPinout for (Tx, Sck) +where + Spi: SpiDevice, + Tx: ValidPinTx, + Sck: ValidPinSck, +{ + type Rx = OptionTNone; + type Cs = OptionTNone; + type Sck = OptionTSome; + type Tx = OptionTSome; +} + +impl ValidSpiPinout for (Tx, Rx, Sck) +where + Spi: SpiDevice, + Tx: ValidPinTx, + Sck: ValidPinSck, + Rx: ValidPinRx, +{ + type Rx = OptionTSome; + type Cs = OptionTNone; + type Sck = OptionTSome; + type Tx = OptionTSome; +} diff --git a/rp-hal/rp2040-hal/src/ssi.rs b/rp-hal/rp2040-hal/src/ssi.rs new file mode 100644 index 0000000..f536cdb --- /dev/null +++ b/rp-hal/rp2040-hal/src/ssi.rs @@ -0,0 +1,5 @@ +//! Synchronous Serial Interface (SSI) +//! +//! See [Chapter 4 Section 10](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details. + +// TODO diff --git a/rp-hal/rp2040-hal/src/timer.rs b/rp-hal/rp2040-hal/src/timer.rs new file mode 100644 index 0000000..b6edbde --- /dev/null +++ b/rp-hal/rp2040-hal/src/timer.rs @@ -0,0 +1,544 @@ +//! Timer Peripheral +//! +//! The Timer peripheral on RP2040 consists of a 64-bit counter and 4 alarms. +//! The Counter is incremented once per microsecond. It obtains its clock source from the watchdog peripheral, you must enable the watchdog before using this peripheral. +//! Since it would take thousands of years for this counter to overflow you do not need to write logic for dealing with this if using get_counter. +//! +//! Each of the 4 alarms can match on the lower 32 bits of Counter and trigger an interrupt. +//! +//! See [Chapter 4 Section 6](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) of the datasheet for more details. + +use core::sync::atomic::{AtomicU8, Ordering}; +use fugit::{MicrosDurationU32, MicrosDurationU64, TimerInstantU64}; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + clocks::ClocksManager, + pac::{self, RESETS, TIMER}, + resets::SubsystemReset, + typelevel::Sealed, +}; + +/// Instant type used by the Timer & Alarm methods. +pub type Instant = TimerInstantU64<1_000_000>; + +static ALARMS: AtomicU8 = AtomicU8::new(0x0F); +fn take_alarm(mask: u8) -> bool { + critical_section::with(|_| { + let alarms = ALARMS.load(Ordering::Relaxed); + ALARMS.store(alarms & !mask, Ordering::Relaxed); + (alarms & mask) != 0 + }) +} +fn release_alarm(mask: u8) { + critical_section::with(|_| { + let alarms = ALARMS.load(Ordering::Relaxed); + ALARMS.store(alarms | mask, Ordering::Relaxed); + }); +} + +/// Timer peripheral +// +// This struct logically wraps a `pac::TIMER`, but doesn't actually store it: +// As after initialization all accesses are read-only anyways, the `pac::TIMER` can +// be summoned unsafely instead. This allows timer to be cloned. +// +// (Alarms do use write operations, but they are local to the respective alarm, and +// those are still owned singletons.) +// +// As the timer peripheral needs to be started first, this struct can only be +// constructed by calling `Timer::new(...)`. +#[derive(Clone, Copy)] +pub struct Timer { + _private: (), +} + +impl Timer { + /// Create a new [`Timer`] + /// + /// Make sure that clocks and watchdog are configured, so + /// that timer ticks happen at a frequency of 1MHz. + /// Otherwise, `Timer` won't work as expected. + pub fn new(timer: TIMER, resets: &mut RESETS, _clocks: &ClocksManager) -> Self { + timer.reset_bring_down(resets); + timer.reset_bring_up(resets); + Self { _private: () } + } + + /// Get the current counter value. + pub fn get_counter(&self) -> Instant { + // Safety: Only used for reading current timer value + let timer = unsafe { &*pac::TIMER::PTR }; + let mut hi0 = timer.timerawh().read().bits(); + let timestamp = loop { + let low = timer.timerawl().read().bits(); + let hi1 = timer.timerawh().read().bits(); + if hi0 == hi1 { + break (u64::from(hi0) << 32) | u64::from(low); + } + hi0 = hi1; + }; + TimerInstantU64::from_ticks(timestamp) + } + + /// Get the value of the least significant word of the counter. + pub fn get_counter_low(&self) -> u32 { + // Safety: Only used for reading current timer value + unsafe { &*pac::TIMER::PTR }.timerawl().read().bits() + } + + /// Initialized a Count Down instance without starting it. + pub fn count_down(&self) -> CountDown { + CountDown { + timer: *self, + period: MicrosDurationU64::nanos(0), + next_end: None, + } + } + /// Retrieve a reference to alarm 0. Will only return a value the first time this is called + pub fn alarm_0(&mut self) -> Option { + take_alarm(1 << 0).then_some(Alarm0(*self)) + } + + /// Retrieve a reference to alarm 1. Will only return a value the first time this is called + pub fn alarm_1(&mut self) -> Option { + take_alarm(1 << 1).then_some(Alarm1(*self)) + } + + /// Retrieve a reference to alarm 2. Will only return a value the first time this is called + pub fn alarm_2(&mut self) -> Option { + take_alarm(1 << 2).then_some(Alarm2(*self)) + } + + /// Retrieve a reference to alarm 3. Will only return a value the first time this is called + pub fn alarm_3(&mut self) -> Option { + take_alarm(1 << 3).then_some(Alarm3(*self)) + } + + /// Pauses execution for at minimum `us` microseconds. + fn delay_us_internal(&self, mut us: u32) { + let mut start = self.get_counter_low(); + // If we knew that the loop ran at least once per timer tick, + // this could be simplified to: + // ``` + // while timer.timelr.read().bits().wrapping_sub(start) <= us { + // cortex_m::asm::nop(); + // } + // ``` + // However, due to interrupts, for `us == u32::MAX`, we could + // miss the moment where the loop should terminate if the loop skips + // a timer tick. + loop { + let now = self.get_counter_low(); + let waited = now.wrapping_sub(start); + if waited >= us { + break; + } + start = now; + us -= waited; + } + } +} + +macro_rules! impl_delay_traits { + ($($t:ty),+) => { + $( + impl embedded_hal_0_2::blocking::delay::DelayUs<$t> for Timer { + fn delay_us(&mut self, us: $t) { + #![allow(unused_comparisons)] + assert!(us >= 0); // Only meaningful for i32 + self.delay_us_internal(us as u32) + } + } + impl embedded_hal_0_2::blocking::delay::DelayMs<$t> for Timer { + fn delay_ms(&mut self, ms: $t) { + #![allow(unused_comparisons)] + assert!(ms >= 0); // Only meaningful for i32 + for _ in 0..ms { + self.delay_us_internal(1000); + } + } + } + )* + } +} + +// The implementation for i32 is a workaround to allow `delay_ms(42)` construction without specifying a type. +impl_delay_traits!(u8, u16, u32, i32); + +impl embedded_hal::delay::DelayNs for Timer { + fn delay_ns(&mut self, ns: u32) { + // For now, just use microsecond delay, internally. Of course, this + // might cause a much longer delay than necessary. So a more advanced + // implementation would be desirable for sub-microsecond delays. + let us = ns.div_ceil(1000); + self.delay_us_internal(us) + } + + fn delay_us(&mut self, us: u32) { + self.delay_us_internal(us) + } + + fn delay_ms(&mut self, ms: u32) { + for _ in 0..ms { + self.delay_us_internal(1000); + } + } +} + +/// Implementation of the [`embedded_hal_0_2::timer`] traits using [`rp2040_hal::timer`](crate::timer) counter. +/// +/// There is no Embedded HAL 1.0 equivalent at this time. +/// +/// If all you need is a delay, [`Timer`] does implement [`embedded_hal::delay::DelayNs`]. +/// +/// ## Usage +/// ```no_run +/// use embedded_hal_0_2::timer::{CountDown, Cancel}; +/// use fugit::ExtU32; +/// use rp2040_hal; +/// let mut pac = rp2040_hal::pac::Peripherals::take().unwrap(); +/// // Make sure to initialize clocks, otherwise the timer wouldn't work +/// // properly. Omitted here for terseness. +/// let clocks: rp2040_hal::clocks::ClocksManager = todo!(); +/// // Configure the Timer peripheral in count-down mode +/// let timer = rp2040_hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); +/// let mut count_down = timer.count_down(); +/// // Create a count_down timer for 500 milliseconds +/// count_down.start(500.millis()); +/// // Block until timer has elapsed +/// let _ = nb::block!(count_down.wait()); +/// // Restart the count_down timer with a period of 100 milliseconds +/// count_down.start(100.millis()); +/// // Cancel it immediately +/// count_down.cancel(); +/// ``` +pub struct CountDown { + timer: Timer, + period: MicrosDurationU64, + next_end: Option, +} + +impl embedded_hal_0_2::timer::CountDown for CountDown { + type Time = MicrosDurationU64; + + fn start(&mut self, count: T) + where + T: Into, + { + self.period = count.into(); + self.next_end = Some( + self.timer + .get_counter() + .ticks() + .wrapping_add(self.period.to_micros()), + ); + } + + fn wait(&mut self) -> nb::Result<(), void::Void> { + if let Some(end) = self.next_end { + let ts = self.timer.get_counter().ticks(); + if ts >= end { + self.next_end = Some(end.wrapping_add(self.period.to_micros())); + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } else { + panic!("CountDown is not running!"); + } + } +} + +impl embedded_hal_0_2::timer::Periodic for CountDown {} + +impl embedded_hal_0_2::timer::Cancel for CountDown { + type Error = &'static str; + + fn cancel(&mut self) -> Result<(), Self::Error> { + if self.next_end.is_none() { + Err("CountDown is not running.") + } else { + self.next_end = None; + Ok(()) + } + } +} + +/// Alarm abstraction. +pub trait Alarm: Sealed { + /// Clear the interrupt flag. + /// + /// The interrupt is unable to trigger a 2nd time until this interrupt is cleared. + fn clear_interrupt(&mut self); + + /// Enable this alarm to trigger an interrupt. + /// + /// After this interrupt is triggered, make sure to clear the interrupt with [clear_interrupt]. + /// + /// [clear_interrupt]: #method.clear_interrupt + fn enable_interrupt(&mut self); + + /// Disable this alarm, preventing it from triggering an interrupt. + fn disable_interrupt(&mut self); + + /// Schedule the alarm to be finished after `countdown`. If [enable_interrupt] is called, + /// this will trigger interrupt whenever this time elapses. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule(&mut self, countdown: MicrosDurationU32) -> Result<(), ScheduleAlarmError>; + + /// Schedule the alarm to be finished at the given timestamp. If [enable_interrupt] is + /// called, this will trigger interrupt whenever this timestamp is reached. + /// + /// The RP2040 is unable to schedule an event taking place in more than + /// `u32::MAX` microseconds. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule_at(&mut self, timestamp: Instant) -> Result<(), ScheduleAlarmError>; + + /// Return true if this alarm is finished. The returned value is undefined if the alarm + /// has not been scheduled yet. + fn finished(&self) -> bool; + + /// Cancel an activated alarm. + fn cancel(&mut self) -> Result<(), ScheduleAlarmError>; +} + +macro_rules! impl_alarm { + ($name:ident { rb: $timer_alarm:ident, int: $int_alarm:ident, int_name: $int_name:tt, armed_bit_mask: $armed_bit_mask: expr }) => { + /// An alarm that can be used to schedule events in the future. Alarms can also be configured to trigger interrupts. + pub struct $name(Timer); + impl $name { + fn schedule_internal(&mut self, timestamp: Instant) -> Result<(), ScheduleAlarmError> { + let timestamp_low = (timestamp.ticks() & 0xFFFF_FFFF) as u32; + // Safety: Only used to access bits belonging exclusively to this alarm + let timer = unsafe { &*pac::TIMER::PTR }; + + // This lock is for time-criticality + cortex_m::interrupt::free(|_| { + let alarm = &timer.$timer_alarm(); + + // safety: This is the only code in the codebase that accesses memory address $timer_alarm + alarm.write(|w| unsafe { w.bits(timestamp_low) }); + + // If it is not set, it has already triggered. + let now = self.0.get_counter(); + if now > timestamp && (timer.armed().read().bits() & $armed_bit_mask) != 0 { + // timestamp was set to a value in the past + + // safety: TIMER.armed is a write-clear register, and there can only be + // 1 instance of AlarmN so we can safely atomically clear this bit. + unsafe { + timer.armed().write_with_zero(|w| w.bits($armed_bit_mask)); + crate::atomic_register_access::write_bitmask_set( + timer.intf().as_ptr(), + $armed_bit_mask, + ); + } + } + Ok(()) + }) + } + } + + impl Alarm for $name { + /// Clear the interrupt flag. This should be called after interrupt ` + #[doc = $int_name] + /// ` is called. + /// + /// The interrupt is unable to trigger a 2nd time until this interrupt is cleared. + fn clear_interrupt(&mut self) { + // safety: TIMER.intr is a write-clear register, so we can atomically clear our interrupt + // by writing its value to this field + // Only one instance of this alarm index can exist, and only this alarm interacts with this bit + // of the TIMER.inte register + unsafe { + let timer = &(*pac::TIMER::ptr()); + crate::atomic_register_access::write_bitmask_clear( + timer.intf().as_ptr(), + $armed_bit_mask, + ); + timer + .intr() + .write_with_zero(|w| w.$int_alarm().clear_bit_by_one()); + } + } + + /// Enable this alarm to trigger an interrupt. This alarm will trigger ` + #[doc = $int_name] + /// `. + /// + /// After this interrupt is triggered, make sure to clear the interrupt with [clear_interrupt]. + /// + /// [clear_interrupt]: #method.clear_interrupt + fn enable_interrupt(&mut self) { + // safety: using the atomic set alias means we can atomically set our interrupt enable bit. + // Only one instance of this alarm can exist, and only this alarm interacts with this bit + // of the TIMER.inte register + unsafe { + let timer = &(*pac::TIMER::ptr()); + let reg = (&timer.inte()).as_ptr(); + write_bitmask_set(reg, $armed_bit_mask); + } + } + + /// Disable this alarm, preventing it from triggering an interrupt. + fn disable_interrupt(&mut self) { + // safety: using the atomic set alias means we can atomically clear our interrupt enable bit. + // Only one instance of this alarm can exist, and only this alarm interacts with this bit + // of the TIMER.inte register + unsafe { + let timer = &(*pac::TIMER::ptr()); + let reg = (&timer.inte()).as_ptr(); + write_bitmask_clear(reg, $armed_bit_mask); + } + } + + /// Schedule the alarm to be finished after `countdown`. If [enable_interrupt] is called, + /// this will trigger interrupt ` + #[doc = $int_name] + /// ` whenever this time elapses. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule(&mut self, countdown: MicrosDurationU32) -> Result<(), ScheduleAlarmError> { + let timestamp = self.0.get_counter() + countdown; + self.schedule_internal(timestamp) + } + + /// Schedule the alarm to be finished at the given timestamp. If [enable_interrupt] is + /// called, this will trigger interrupt ` + #[doc = $int_name] + /// ` whenever this timestamp is reached. + /// + /// The RP2040 is unable to schedule an event taking place in more than + /// `u32::MAX` microseconds. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule_at(&mut self, timestamp: Instant) -> Result<(), ScheduleAlarmError> { + let now = self.0.get_counter(); + let duration = timestamp.ticks().saturating_sub(now.ticks()); + if duration > u32::MAX.into() { + return Err(ScheduleAlarmError::AlarmTooLate); + } + + self.schedule_internal(timestamp) + } + + /// Return true if this alarm is finished. The returned value is undefined if the alarm + /// has not been scheduled yet. + fn finished(&self) -> bool { + // safety: This is a read action and should not have any UB + let bits: u32 = unsafe { &*TIMER::ptr() }.armed().read().bits(); + (bits & $armed_bit_mask) == 0 + } + + /// Cancel an activated Alarm. No negative effects if it's already disabled. + /// Unlike `timer::cancel` trait, this only cancels the alarm and keeps the timer running + /// if it's already active. + fn cancel(&mut self) -> Result<(), ScheduleAlarmError> { + unsafe { + let timer = &*TIMER::ptr(); + timer.armed().write_with_zero(|w| w.bits($armed_bit_mask)); + crate::atomic_register_access::write_bitmask_clear( + timer.intf().as_ptr(), + $armed_bit_mask, + ); + } + + Ok(()) + } + } + + impl Sealed for $name {} + + impl Drop for $name { + fn drop(&mut self) { + self.disable_interrupt(); + release_alarm($armed_bit_mask) + } + } + }; +} + +/// Errors that can be returned from any of the `AlarmX::schedule` methods. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ScheduleAlarmError { + /// Alarm time is too high. Should not be more than `u32::MAX` in the future. + AlarmTooLate, +} + +impl_alarm!(Alarm0 { + rb: alarm0, + int: alarm_0, + int_name: "TIMER_IRQ_0", + armed_bit_mask: 0b0001 +}); + +impl_alarm!(Alarm1 { + rb: alarm1, + int: alarm_1, + int_name: "TIMER_IRQ_1", + armed_bit_mask: 0b0010 +}); + +impl_alarm!(Alarm2 { + rb: alarm2, + int: alarm_2, + int_name: "TIMER_IRQ_2", + armed_bit_mask: 0b0100 +}); + +impl_alarm!(Alarm3 { + rb: alarm3, + int: alarm_3, + int_name: "TIMER_IRQ_3", + armed_bit_mask: 0b1000 +}); + +/// Support for RTIC monotonic trait. +#[cfg(feature = "rtic-monotonic")] +pub mod monotonic { + use super::{Alarm, Instant, Timer}; + use fugit::ExtU32; + + /// RTIC Monotonic Implementation + pub struct Monotonic(pub Timer, A); + impl Monotonic { + /// Creates a new monotonic. + pub const fn new(timer: Timer, alarm: A) -> Self { + Self(timer, alarm) + } + } + impl rtic_monotonic::Monotonic for Monotonic { + type Instant = Instant; + type Duration = fugit::MicrosDurationU64; + + const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false; + + fn now(&mut self) -> Instant { + self.0.get_counter() + } + + fn set_compare(&mut self, instant: Instant) { + // The alarm can only trigger up to 2^32 - 1 ticks in the future. + // So, if `instant` is more than 2^32 - 2 in the future, we use `max_instant` instead. + let max_instant = self.0.get_counter() + 0xFFFF_FFFE.micros(); + let wake_at = core::cmp::min(instant, max_instant); + + // Cannot fail + let _ = self.1.schedule_at(wake_at); + self.1.enable_interrupt(); + } + + fn clear_compare_flag(&mut self) { + self.1.clear_interrupt(); + } + + fn zero() -> Self::Instant { + Instant::from_ticks(0) + } + + unsafe fn reset(&mut self) {} + } +} diff --git a/rp-hal/rp2040-hal/src/typelevel.rs b/rp-hal/rp2040-hal/src/typelevel.rs new file mode 100644 index 0000000..eb509d4 --- /dev/null +++ b/rp-hal/rp2040-hal/src/typelevel.rs @@ -0,0 +1,98 @@ +//! Module supporting type-level programming +//! +//! This is heavily inspired by the work in [`atsamd-rs`](https://github.com/atsamd-rs/atsamd). Please refer to the +//! [documentation](https://docs.rs/atsamd-hal/0.15.1/atsamd_hal/typelevel/index.html) +//! over there for more details. + +mod private { + /// Super trait used to mark traits with an exhaustive set of + /// implementations + pub trait Sealed {} +} + +use core::borrow::{Borrow, BorrowMut}; + +pub(crate) use private::Sealed; + +impl Sealed for (A, B) {} +impl Sealed for (A, B, C) {} +impl Sealed for (A, B, C, D) {} + +impl Sealed for frunk::HNil {} +impl Sealed for frunk::HCons {} + +/// Marker trait for type identity +/// +/// This trait is used as part of the [`AnyKind`] trait pattern. It represents +/// the concept of type identity, because all implementors have +/// `::Type == Self`. When used as a trait bound with a specific +/// type, it guarantees that the corresponding type parameter is exactly the +/// specific type. Stated differently, it guarantees that `T == Specific` in +/// the following example. +/// +/// ```text +/// where T: Is +/// ``` +/// +/// Moreover, the super traits guarantee that any instance of or reference to a +/// type `T` can be converted into the `Specific` type. +/// +/// ``` +/// # use rp2040_hal::typelevel::Is; +/// # struct Specific; +/// fn example(mut any: T) +/// where +/// T: Is, +/// { +/// let specific_mut: &mut Specific = any.borrow_mut(); +/// let specific_ref: &Specific = any.borrow(); +/// let specific: Specific = any.into(); +/// } +/// ``` +/// +/// [`AnyKind`]: https://docs.rs/atsamd-hal/0.15.1/atsamd_hal/typelevel/index.html#anykind-trait-pattern +pub trait Is +where + Self: Sealed, + Self: From>, + Self: Into>, + Self: Borrow>, + Self: BorrowMut>, +{ + #[allow(missing_docs)] + type Type; +} + +/// Type alias for [`Is::Type`] +pub type IsType = ::Type; + +impl Is for T +where + T: Sealed + Borrow + BorrowMut, +{ + type Type = T; +} + +// ===================== +// Type level option +// ===================== + +/// Type-level `enum` for Option. +pub trait OptionT: Sealed { + /// Is this Some or None ? + const IS_SOME: bool; +} + +/// Type-level variant for `OptionT` +pub struct OptionTNone; +impl Sealed for OptionTNone {} +impl OptionT for OptionTNone { + const IS_SOME: bool = false; +} + +/// Type-level variant for `OptionT` +pub struct OptionTSome(pub T); +impl Sealed for OptionTSome {} +impl OptionT for OptionTSome { + const IS_SOME: bool = true; +} diff --git a/rp-hal/rp2040-hal/src/uart/common_configs.rs b/rp-hal/rp2040-hal/src/uart/common_configs.rs new file mode 100644 index 0000000..b364454 --- /dev/null +++ b/rp-hal/rp2040-hal/src/uart/common_configs.rs @@ -0,0 +1,2 @@ +#[doc(inline)] +pub use rp_hal_common::uart::common_configs::*; diff --git a/rp-hal/rp2040-hal/src/uart/mod.rs b/rp-hal/rp2040-hal/src/uart/mod.rs new file mode 100644 index 0000000..6bdbe1a --- /dev/null +++ b/rp-hal/rp2040-hal/src/uart/mod.rs @@ -0,0 +1,49 @@ +//! Universal Asynchronous Receiver Transmitter (UART) +//! +//! See [Chapter 4 Section 2](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) of the datasheet for more details +//! +//! ## Usage +//! +//! See [examples/uart.rs](https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal-examples/src/bin/uart.rs) for a more complete example +//! ```no_run +//! use rp2040_hal::{Clock, clocks::init_clocks_and_plls, gpio::{Pins, FunctionUart}, pac, sio::Sio, uart::{self, DataBits, StopBits, UartConfig, UartPeripheral}, watchdog::Watchdog}; +//! use fugit::RateExtU32; +//! +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! let mut clocks = init_clocks_and_plls(XOSC_CRYSTAL_FREQ, peripherals.XOSC, peripherals.CLOCKS, peripherals.PLL_SYS, peripherals.PLL_USB, &mut peripherals.RESETS, &mut watchdog).ok().unwrap(); +//! +//! // Set up UART on GP0 and GP1 (Pico pins 1 and 2) +//! let pins = ( +//! pins.gpio0.into_function(), +//! pins.gpio1.into_function(), +//! ); +//! // Need to perform clock init before using UART or it will freeze. +//! let uart = UartPeripheral::new(peripherals.UART0, pins, &mut peripherals.RESETS) +//! .enable( +//! UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One), +//! clocks.peripheral_clock.freq(), +//! ).unwrap(); +//! +//! uart.write_full_blocking(b"Hello World!\r\n"); +//! ``` + +mod peripheral; +mod pins; +mod reader; +mod utils; +mod writer; + +pub use peripheral::UartPeripheral; +pub use pins::*; +pub use reader::{ReadError, ReadErrorType, Reader}; +pub use utils::*; +pub use writer::Writer; + +/// Common configurations for UART. +#[deprecated(note = "Use UartConfig::new(...) instead.")] +pub mod common_configs; diff --git a/rp-hal/rp2040-hal/src/uart/peripheral.rs b/rp-hal/rp2040-hal/src/uart/peripheral.rs new file mode 100644 index 0000000..6435229 --- /dev/null +++ b/rp-hal/rp2040-hal/src/uart/peripheral.rs @@ -0,0 +1,522 @@ +//! Universal Asynchronous Receiver Transmitter - Bi-directional Peripheral Code +//! +//! This module brings together `uart::reader` and `uart::writer` to give a +//! UartPeripheral object that can both read and write. + +use core::{convert::Infallible, fmt}; +use embedded_hal_0_2::serial as eh0; +use fugit::HertzU32; +use nb::Error::{Other, WouldBlock}; + +use crate::{ + pac::{self, uart0::uartlcr_h::W as UART_LCR_H_Writer, Peripherals, UART0, UART1}, + typelevel::OptionT, + uart::*, +}; + +use embedded_hal_nb::serial::{ErrorType, Read, Write}; + +/// An UART Peripheral based on an underlying UART device. +pub struct UartPeripheral> { + device: D, + _state: S, + pins: P, + read_error: Option, +} + +impl> UartPeripheral { + fn transition(self, state: To) -> UartPeripheral { + UartPeripheral { + device: self.device, + pins: self.pins, + _state: state, + read_error: None, + } + } + + /// Releases the underlying device and pins. + pub fn free(self) -> (D, P) { + (self.device, self.pins) + } +} + +impl> UartPeripheral { + /// Creates an UartPeripheral in Disabled state. + pub fn new(device: D, pins: P, resets: &mut pac::RESETS) -> UartPeripheral { + device.reset_bring_down(resets); + device.reset_bring_up(resets); + + UartPeripheral { + device, + _state: Disabled, + pins, + read_error: None, + } + } + + /// Enables the provided UART device with the given configuration. + pub fn enable( + self, + config: UartConfig, + frequency: HertzU32, + ) -> Result, Error> { + let (mut device, pins) = self.free(); + configure_baudrate(&mut device, config.baudrate, frequency)?; + + device.uartlcr_h().write(|w| { + // FIFOs are enabled + w.fen().set_bit(); // Leaved here for backward compatibility + set_format(w, &config.data_bits, &config.stop_bits, &config.parity); + w + }); + + // Enable the UART, and the TX,RC,CTS and RTS based on the pins + device.uartcr().write(|w| { + w.uarten().set_bit(); + w.txe().bit(P::Tx::IS_SOME); + w.rxe().bit(P::Rx::IS_SOME); + w.ctsen().bit(P::Cts::IS_SOME); + w.rtsen().bit(P::Rts::IS_SOME); + + w + }); + + device.uartdmacr().write(|w| { + w.txdmae().set_bit(); + w.rxdmae().set_bit(); + w + }); + + Ok(UartPeripheral { + device, + pins, + _state: Enabled, + read_error: None, + }) + } +} + +impl> UartPeripheral { + /// Disable this UART Peripheral, falling back to the Disabled state. + pub fn disable(self) -> UartPeripheral { + // Disable the UART, both TX and RX + self.device.uartcr().write(|w| { + w.uarten().clear_bit(); + w.txe().clear_bit(); + w.rxe().clear_bit(); + w.ctsen().clear_bit(); + w.rtsen().clear_bit(); + w + }); + + self.transition(Disabled) + } + + /// Enable/disable the rx/tx FIFO + /// + /// Unfortunately, it's not possible to enable/disable rx/tx + /// independently on this chip + /// Default is false + pub fn set_fifos(&mut self, enable: bool) { + super::reader::set_fifos(&self.device, enable) + } + + /// Set rx FIFO watermark + /// + /// See DS: Table 423 + pub fn set_rx_watermark(&mut self, watermark: FifoWatermark) { + super::reader::set_rx_watermark(&self.device, watermark) + } + + /// Set tx FIFO watermark + /// + /// See DS: Table 423 + pub fn set_tx_watermark(&mut self, watermark: FifoWatermark) { + super::writer::set_tx_watermark(&self.device, watermark) + } + + /// Enables the Receive Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is data in the receive register. + pub fn enable_rx_interrupt(&mut self) { + super::reader::enable_rx_interrupt(&self.device) + } + + /// Enables the Transmit Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is space in the transmit FIFO. + pub fn enable_tx_interrupt(&mut self) { + super::writer::enable_tx_interrupt(&self.device) + } + + /// Disables the Receive Interrupt. + pub fn disable_rx_interrupt(&mut self) { + super::reader::disable_rx_interrupt(&self.device) + } + + /// Disables the Transmit Interrupt. + pub fn disable_tx_interrupt(&mut self) { + super::writer::disable_tx_interrupt(&self.device) + } + + /// Is there space in the UART TX FIFO for new data to be written? + pub fn uart_is_writable(&self) -> bool { + super::writer::uart_is_writable(&self.device) + } + + /// Is the UART still busy transmitting data? + pub fn uart_is_busy(&self) -> bool { + super::writer::uart_is_busy(&self.device) + } + + /// Is there data in the UART RX FIFO ready to be read? + pub fn uart_is_readable(&self) -> bool { + super::reader::is_readable(&self.device) + } + + /// Writes bytes to the UART. + /// This function writes as long as it can. As soon that the FIFO is full, if : + /// - 0 bytes were written, a WouldBlock Error is returned + /// - some bytes were written, it is deemed to be a success + /// + /// Upon success, the remaining slice is returned. + pub fn write_raw<'d>(&self, data: &'d [u8]) -> nb::Result<&'d [u8], Infallible> { + super::writer::write_raw(&self.device, data) + } + + /// Reads bytes from the UART. + /// This function reads as long as it can. As soon that the FIFO is empty, if : + /// - 0 bytes were read, a WouldBlock Error is returned + /// - some bytes were read, it is deemed to be a success + /// + /// Upon success, it will return how many bytes were read. + pub fn read_raw<'b>(&self, buffer: &'b mut [u8]) -> nb::Result> { + super::reader::read_raw(&self.device, buffer) + } + + /// Writes bytes to the UART. + /// + /// This function blocks until the full buffer has been sent. + pub fn write_full_blocking(&self, data: &[u8]) { + super::writer::write_full_blocking(&self.device, data); + } + + /// Reads bytes from the UART. + /// + /// This function blocks until the full buffer has been received. + pub fn read_full_blocking(&self, buffer: &mut [u8]) -> Result<(), ReadErrorType> { + super::reader::read_full_blocking(&self.device, buffer) + } + + /// Initiates a break + /// + /// If transmitting, this takes effect immediately after the current byte has completed. + /// For proper execution of the break command, this must be held for at least 2 complete frames + /// worth of time. + /// + ///

+ /// + /// # Example + /// + /// ```no_run + /// # use rp2040_hal::uart::{Pins, ValidUartPinout, Enabled, UartPeripheral}; + /// # use rp2040_hal::pac::UART0; + /// # use rp2040_hal::timer::Timer; + /// # use rp2040_hal::typelevel::OptionTNone; + /// # use embedded_hal_0_2::blocking::delay::DelayUs; + /// # type PINS = Pins; + /// # fn example(mut serial: UartPeripheral, mut timer: Timer) { + /// serial.lowlevel_break_start(); + /// // at 115_200Bps on 8N1 configuration, 20bits takes (20*10⁶)/115200 = 173.611…μs. + /// timer.delay_us(175); + /// serial.lowlevel_break_stop(); + /// } + /// ``` + pub fn lowlevel_break_start(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().set_bit()); + } + + /// Terminates a break condition. + /// + /// See `lowlevel_break_start` for more details. + pub fn lowlevel_break_stop(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().clear_bit()); + } + + /// Join the reader and writer halves together back into the original Uart peripheral. + /// + /// A reader/writer pair can be obtained by calling [`split`]. + /// + /// [`split`]: #method.split + pub fn join(reader: Reader, writer: Writer) -> Self { + let _ = writer; + Self { + device: reader.device, + _state: Enabled, + pins: reader.pins, + read_error: reader.read_error, + } + } +} + +impl> UartPeripheral { + /// Split this peripheral into a separate reader and writer. + pub fn split(self) -> (Reader, Writer) { + let reader = Reader { + device: self.device, + pins: self.pins, + read_error: self.read_error, + }; + // Safety: reader and writer will never write to the same address + let device_copy = unsafe { Peripherals::steal().UART0 }; + let writer = Writer { + device: device_copy, + device_marker: core::marker::PhantomData, + pins: core::marker::PhantomData, + }; + (reader, writer) + } +} + +impl> UartPeripheral { + /// Split this peripheral into a separate reader and writer. + pub fn split(self) -> (Reader, Writer) { + let reader = Reader { + device: self.device, + pins: self.pins, + read_error: self.read_error, + }; + // Safety: reader and writer will never write to the same address + let device_copy = unsafe { Peripherals::steal().UART1 }; + let writer = Writer { + device: device_copy, + device_marker: core::marker::PhantomData, + pins: core::marker::PhantomData, + }; + (reader, writer) + } +} + +/// The PL011 (PrimeCell UART) supports a fractional baud rate divider +/// From the wanted baudrate, we calculate the divider's two parts: integer and fractional parts. +/// Code inspired from the C SDK. +fn calculate_baudrate_dividers( + wanted_baudrate: HertzU32, + frequency: HertzU32, +) -> Result<(u16, u16), Error> { + // See Chapter 4, Section 2 §7.1 from the datasheet for an explanation of how baudrate is + // calculated + let baudrate_div = frequency + .to_Hz() + .checked_mul(8) + .and_then(|r| r.checked_div(wanted_baudrate.to_Hz())) + .ok_or(Error::BadArgument)?; + + Ok(match (baudrate_div >> 7, ((baudrate_div & 0x7F) + 1) / 2) { + (0, _) => (1, 0), + + (int_part, _) if int_part >= 65535 => (65535, 0), + + (int_part, frac_part) => (int_part as u16, frac_part as u16), + }) +} + +/// Baudrate configuration. Code loosely inspired from the C SDK. +#[allow(unknown_lints)] +#[allow(clippy::needless_pass_by_ref_mut)] +fn configure_baudrate( + device: &mut U, + wanted_baudrate: HertzU32, + frequency: HertzU32, +) -> Result { + let (baud_div_int, baud_div_frac) = calculate_baudrate_dividers(wanted_baudrate, frequency)?; + + // First we load the integer part of the divider. + device.uartibrd().write(|w| unsafe { + w.baud_divint().bits(baud_div_int); + w + }); + + // Then we load the fractional part of the divider. + device.uartfbrd().write(|w| unsafe { + w.baud_divfrac().bits(baud_div_frac as u8); + w + }); + + // PL011 needs a (dummy) line control register write to latch in the + // divisors. We don't want to actually change LCR contents here. + device.uartlcr_h().modify(|_, w| w); + + Ok(HertzU32::from_raw( + (4 * frequency.to_Hz()) / (64 * baud_div_int as u32 + baud_div_frac as u32), + )) +} + +/// Format configuration. Code loosely inspired from the C SDK. +fn set_format<'w>( + w: &'w mut UART_LCR_H_Writer, + data_bits: &DataBits, + stop_bits: &StopBits, + parity: &Option, +) -> &'w mut UART_LCR_H_Writer { + match parity { + Some(p) => { + w.pen().set_bit(); + match p { + Parity::Odd => w.eps().clear_bit(), + Parity::Even => w.eps().set_bit(), + }; + } + None => { + w.pen().bit(false); + } + }; + + unsafe { + w.wlen().bits(match data_bits { + DataBits::Five => 0b00, + DataBits::Six => 0b01, + DataBits::Seven => 0b10, + DataBits::Eight => 0b11, + }) + }; + + match stop_bits { + StopBits::One => w.stp2().clear_bit(), + StopBits::Two => w.stp2().set_bit(), + }; + + w +} + +impl> eh0::Read for UartPeripheral { + type Error = ReadErrorType; + + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} + +impl> ErrorType for UartPeripheral { + type Error = ReadErrorType; +} + +impl> Read for UartPeripheral { + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} + +impl> eh0::Write for UartPeripheral { + type Error = Infallible; + + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + super::writer::transmit_flushed(&self.device) + } +} + +impl> Write for UartPeripheral { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + super::writer::transmit_flushed(&self.device).map_err(|e| match e { + WouldBlock => WouldBlock, + Other(v) => match v {}, + }) + } +} + +impl> fmt::Write for UartPeripheral { + fn write_str(&mut self, s: &str) -> fmt::Result { + s.bytes() + .try_for_each(|c| nb::block!(self.write(c))) + .map_err(|_| fmt::Error) + } +} + +impl embedded_io::Error for ReadErrorType { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} +impl> embedded_io::ErrorType + for UartPeripheral +{ + type Error = ReadErrorType; +} +impl> embedded_io::Read for UartPeripheral { + fn read(&mut self, buf: &mut [u8]) -> Result { + // If the last read stored an error, report it now + if let Some(err) = self.read_error.take() { + return Err(err); + } + match nb::block!(self.read_raw(buf)) { + Ok(bytes_read) => Ok(bytes_read), + Err(err) if !err.discarded.is_empty() => { + // If an error was reported but some bytes were already read, + // return the data now and store the error for the next + // invocation. + self.read_error = Some(err.err_type); + Ok(err.discarded.len()) + } + Err(err) => Err(err.err_type), + } + } +} + +impl> embedded_io::ReadReady + for UartPeripheral +{ + fn read_ready(&mut self) -> Result { + Ok(self.uart_is_readable() || self.read_error.is_some()) + } +} + +impl> embedded_io::Write for UartPeripheral { + fn write(&mut self, buf: &[u8]) -> Result { + self.write_full_blocking(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> Result<(), Self::Error> { + nb::block!(super::writer::transmit_flushed(&self.device)).unwrap(); // Infallible + Ok(()) + } +} + +impl> embedded_io::WriteReady + for UartPeripheral +{ + fn write_ready(&mut self) -> Result { + Ok(self.uart_is_writable()) + } +} diff --git a/rp-hal/rp2040-hal/src/uart/pins.rs b/rp-hal/rp2040-hal/src/uart/pins.rs new file mode 100644 index 0000000..3c742b1 --- /dev/null +++ b/rp-hal/rp2040-hal/src/uart/pins.rs @@ -0,0 +1,242 @@ +use core::marker::PhantomData; + +use crate::gpio::{bank0::*, pin::pin_sealed::TypeLevelPinId, AnyPin, FunctionUart}; +use crate::pac::{UART0, UART1}; +use crate::typelevel::{OptionT, OptionTNone, OptionTSome, Sealed}; + +use super::UartDevice; + +// All type level checked pins are inherently valid. +macro_rules! pin_validation { + ($p:ident) => { + paste::paste!{ + #[doc = "Indicates a valid " $p " pin for UART0 or UART1"] + pub trait []: Sealed {} + + #[doc = "Indicates a valid " $p " pin for UART0 or UART1"] + pub trait []: Sealed {} + + impl [] for T + where + T: AnyPin, + T::Id: [], + { + } + + #[doc = "A runtime validated " $p " pin for uart."] + pub struct [](P, PhantomData); + impl Sealed for [] {} + impl [] for [] {} + impl [] + where + P: AnyPin, + U: UartDevice, + { + /// Validate a pin's function on a uart peripheral. + /// + #[doc = "Will err if the pin cannot be used as a " $p " pin for that Uart."] + pub fn validate(p: P, _u: &U) -> Result { + if [<$p:upper>].contains(&(p.borrow().id().num, U::ID)) && + p.borrow().id().bank == crate::gpio::DynBankId::Bank0 { + Ok(Self(p, PhantomData)) + } else { + Err(p) + } + } + } + + #[doc = "Indicates a valid optional " $p " pin for UART0 or UART1"] + pub trait []: OptionT {} + + impl [] for OptionTNone {} + impl [] for OptionTSome + where + U: UartDevice, + T: [], + { + } + } + }; + ($($p:ident),*) => { + $( + pin_validation!($p); + )* + }; +} +pin_validation!(Tx, Rx, Cts, Rts); + +macro_rules! impl_valid_uart { + ($($uart:ident: { + tx: [$($tx:ident),*], + rx: [$($rx:ident),*], + cts: [$($cts:ident),*], + rts: [$($rts:ident),*], + }),*) => { + $( + $(impl ValidPinIdTx<$uart> for $tx {})* + $(impl ValidPinIdRx<$uart> for $rx {})* + $(impl ValidPinIdCts<$uart> for $cts {})* + $(impl ValidPinIdRts<$uart> for $rts {})* + )* + const RX: &[(u8, usize)] = &[$($(($rx::ID.num, $uart::ID)),*),*]; + const TX: &[(u8, usize)] = &[$($(($tx::ID.num, $uart::ID)),*),*]; + const CTS: &[(u8, usize)] = &[$($(($cts::ID.num, $uart::ID)),*),*]; + const RTS: &[(u8, usize)] = &[$($(($rts::ID.num, $uart::ID)),*),*]; + }; +} + +impl_valid_uart!( + UART0: { + tx: [Gpio0, Gpio12, Gpio16, Gpio28], + rx: [Gpio1, Gpio13, Gpio17, Gpio29], + cts: [Gpio2, Gpio14, Gpio18], + rts: [Gpio3, Gpio15, Gpio19], + }, + UART1: { + tx: [Gpio4, Gpio8, Gpio20, Gpio24], + rx: [Gpio5, Gpio9, Gpio21, Gpio25], + cts: [Gpio6, Gpio10, Gpio22, Gpio26], + rts: [Gpio7, Gpio11, Gpio23, Gpio27], + } +); + +/// Declares a valid UART pinout. +pub trait ValidUartPinout: Sealed { + #[allow(missing_docs)] + type Rx: ValidOptionRx; + #[allow(missing_docs)] + type Tx: ValidOptionTx; + #[allow(missing_docs)] + type Cts: ValidOptionCts; + #[allow(missing_docs)] + type Rts: ValidOptionRts; +} + +impl ValidUartPinout for (Tx, Rx) +where + Uart: UartDevice, + Tx: ValidPinTx, + Rx: ValidPinRx, +{ + type Tx = OptionTSome; + type Rx = OptionTSome; + type Cts = OptionTNone; + type Rts = OptionTNone; +} + +impl ValidUartPinout for (Tx, Rx, Cts, Rts) +where + Uart: UartDevice, + Tx: ValidPinTx, + Rx: ValidPinRx, + Cts: ValidPinCts, + Rts: ValidPinRts, +{ + type Rx = OptionTSome; + type Tx = OptionTSome; + type Cts = OptionTSome; + type Rts = OptionTSome; +} + +/// Customizable Uart pinout, allowing you to set the pins individually. +/// +/// The following pins are valid UART pins: +/// +/// |UART | TX | RX | CTS | RTS | +/// |-----|-------------|-------------|-------------|-------------| +/// |UART0|0, 12, 16, 28|1, 13, 17, 29|2, 14, 18 |3, 15, 19 | +/// |UART1|4, 8, 20, 24 |5, 9, 21, 25 |6, 10, 22, 26|7, 11, 23, 27| +/// +/// Every field can be set to [`OptionTNone`] to not configure them. +/// +/// Note that you can also use tuples `(RX, TX)` or `(RX, TX, CTS, RTS)` instead of this type. +/// +/// This struct can either be filled manually or with a builder pattern: +/// +/// ```no_run +/// # use rp2040_hal::uart::{Pins, ValidUartPinout}; +/// # use rp2040_hal::pac::UART0; +/// # let gpio_pins: rp2040_hal::gpio::Pins = unsafe { core::mem::zeroed() }; +/// let pins = Pins::default() +/// .tx(gpio_pins.gpio0.into_function()) +/// .rx(gpio_pins.gpio1.into_function()); +/// +/// fn assert_is_valid_uart0>(_: T) {} +/// +/// assert_is_valid_uart0(pins); +/// ``` +pub struct Pins { + #[allow(missing_docs)] + pub tx: Tx, + #[allow(missing_docs)] + pub rx: Rx, + #[allow(missing_docs)] + pub cts: Cts, + #[allow(missing_docs)] + pub rts: Rts, +} + +impl Default for Pins { + fn default() -> Self { + Self { + tx: OptionTNone, + rx: OptionTNone, + rts: OptionTNone, + cts: OptionTNone, + } + } +} + +impl Pins { + /// Set the TX pin + pub fn tx(self, tx: NewTx) -> Pins, Rx, Cts, Rts> { + Pins { + tx: OptionTSome(tx), + rx: self.rx, + rts: self.rts, + cts: self.cts, + } + } + /// Set the RX pin + pub fn rx(self, rx: NewRx) -> Pins, Cts, Rts> { + Pins { + tx: self.tx, + rx: OptionTSome(rx), + rts: self.rts, + cts: self.cts, + } + } + /// Set the CTS pin + pub fn cts(self, cts: NewCts) -> Pins, Rts> { + Pins { + tx: self.tx, + rx: self.rx, + rts: self.rts, + cts: OptionTSome(cts), + } + } + /// Set the RTS pin + pub fn rts(self, rts: NewRts) -> Pins> { + Pins { + tx: self.tx, + rx: self.rx, + rts: OptionTSome(rts), + cts: self.cts, + } + } +} + +impl Sealed for Pins {} +impl ValidUartPinout for Pins +where + Uart: UartDevice, + Tx: ValidOptionTx, + Rx: ValidOptionRx, + Cts: ValidOptionCts, + Rts: ValidOptionRts, +{ + type Rx = Rx; + type Tx = Tx; + type Cts = Cts; + type Rts = Rts; +} diff --git a/rp-hal/rp2040-hal/src/uart/reader.rs b/rp-hal/rp2040-hal/src/uart/reader.rs new file mode 100644 index 0000000..73acd4a --- /dev/null +++ b/rp-hal/rp2040-hal/src/uart/reader.rs @@ -0,0 +1,332 @@ +//! Universal Asynchronous Receiver Transmitter - Receiver Code +//! +//! This module is for receiving data with a UART. + +use super::{FifoWatermark, UartDevice, ValidUartPinout}; +use crate::dma::{EndlessReadTarget, ReadTarget}; +use crate::pac::uart0::RegisterBlock; +use embedded_hal_0_2::serial::Read as Read02; +use nb::Error::*; + +use embedded_hal_nb::serial::{Error, ErrorKind, ErrorType, Read}; + +/// When there's a read error. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug)] +pub struct ReadError<'err> { + /// The type of error + pub err_type: ReadErrorType, + + /// Reference to the data that was read but eventually discarded because of the error. + pub discarded: &'err [u8], +} + +/// Possible types of read errors. See Chapter 4, Section 2 §8 - Table 436: "UARTDR Register" +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug)] +pub enum ReadErrorType { + /// Triggered when the FIFO (or shift-register) is overflowed. + Overrun, + + /// Triggered when a break is received + Break, + + /// Triggered when there is a parity mismatch between what's received and our settings. + Parity, + + /// Triggered when the received character didn't have a valid stop bit. + Framing, +} + +impl Error for ReadErrorType { + fn kind(&self) -> ErrorKind { + match self { + ReadErrorType::Overrun => ErrorKind::Overrun, + ReadErrorType::Break => ErrorKind::Other, + ReadErrorType::Parity => ErrorKind::Parity, + ReadErrorType::Framing => ErrorKind::FrameFormat, + } + } +} + +pub(crate) fn is_readable(device: &D) -> bool { + device.uartfr().read().rxfe().bit_is_clear() +} + +/// Enable/disable the rx/tx FIFO +/// +/// Unfortunately, it's not possible to enable/disable rx/tx +/// independently on this chip +/// Default is false +pub fn set_fifos(rb: &RegisterBlock, enable: bool) { + if enable { + rb.uartlcr_h().modify(|_r, w| w.fen().set_bit()) + } else { + rb.uartlcr_h().modify(|_r, w| w.fen().clear_bit()) + } +} + +/// Set rx FIFO watermark +/// +/// See DS: Table 423 +pub fn set_rx_watermark(rb: &RegisterBlock, watermark: FifoWatermark) { + let wm = match watermark { + FifoWatermark::Bytes4 => 0, + FifoWatermark::Bytes8 => 1, + FifoWatermark::Bytes16 => 2, + FifoWatermark::Bytes24 => 3, + FifoWatermark::Bytes28 => 4, + }; + rb.uartifls() + .modify(|_r, w| unsafe { w.rxiflsel().bits(wm) }); +} + +/// Enables the Receive Interrupt. +/// +/// The relevant UARTx IRQ will fire when there is data in the receive register. +pub(crate) fn enable_rx_interrupt(rb: &RegisterBlock) { + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // high enables the interrupt. + + // We set the RX interrupt, and the RX Timeout interrupt. This means + // we will get an interrupt when the RX FIFO level is triggered, or + // when the RX FIFO is non-empty, but 32-bit periods have passed with + // no further data. This means we don't have to interrupt on every + // single byte, but can make use of the hardware FIFO. + rb.uartimsc().modify(|_r, w| { + w.rxim().set_bit(); + w.rtim().set_bit(); + w + }); +} + +/// Disables the Receive Interrupt. +pub(crate) fn disable_rx_interrupt(rb: &RegisterBlock) { + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // low disables the interrupt. + + rb.uartimsc().modify(|_r, w| { + w.rxim().clear_bit(); + w.rtim().clear_bit(); + w + }); +} + +pub(crate) fn read_raw<'b, D: UartDevice>( + device: &D, + buffer: &'b mut [u8], +) -> nb::Result> { + let mut bytes_read = 0; + + Ok(loop { + if !is_readable(device) { + if bytes_read == 0 { + // The overrun error (OE) bit is checked separately as it + // doesn't really correspond to a specific byte we've read. If + // we don't do this here, the overrun error is hidden until the + // next byte turns up - which may never happen. + if device.uartrsr().read().oe().bit_is_set() { + // We observed a FIFO overrun on an empty FIFO. Clear the + // error otherwise it sticks. + unsafe { + device.uartrsr().write_with_zero(|w| w); + } + // Now report the error. + // + // Note that you will also get an overrun error on the first + // byte that turns up after this error - we can't stop that + // as we have no mutable state to indicate that it's already + // been handled. But two overrun errors is better that none. + return Err(Other(ReadError { + err_type: ReadErrorType::Overrun, + discarded: &buffer[..bytes_read], + })); + } else { + return Err(WouldBlock); + } + } else { + break bytes_read; + } + } + + if bytes_read < buffer.len() { + let mut error: Option = None; + + let read = device.uartdr().read(); + + // If multiple status bits are set, report + // the most serious or most specific condition, + // in the following order of precedence: + // break > parity > framing + // + // overrun is last because the byte associated with it is still good. + if read.be().bit_is_set() { + error = Some(ReadErrorType::Break); + } else if read.pe().bit_is_set() { + error = Some(ReadErrorType::Parity); + } else if read.fe().bit_is_set() { + error = Some(ReadErrorType::Framing); + } else if read.oe().bit_is_set() { + error = Some(ReadErrorType::Overrun); + // if we get an overrun - there's still data there + buffer[bytes_read] = read.data().bits(); + bytes_read += 1; + } + + if let Some(err_type) = error { + return Err(Other(ReadError { + err_type, + discarded: &buffer[..bytes_read], + })); + } + + buffer[bytes_read] = read.data().bits(); + bytes_read += 1; + } else { + break bytes_read; + } + }) +} + +pub(crate) fn read_full_blocking( + device: &D, + buffer: &mut [u8], +) -> Result<(), ReadErrorType> { + let mut offset = 0; + + while offset != buffer.len() { + offset += match read_raw(device, &mut buffer[offset..]) { + Ok(bytes_read) => bytes_read, + Err(e) => match e { + Other(inner) => return Err(inner.err_type), + WouldBlock => continue, + }, + } + } + + Ok(()) +} + +/// Half of an [`UartPeripheral`] that is only capable of reading. Obtained by calling [`UartPeripheral::split()`] +/// +/// [`UartPeripheral`]: struct.UartPeripheral.html +/// [`UartPeripheral::split()`]: struct.UartPeripheral.html#method.split +pub struct Reader> { + pub(super) device: D, + pub(super) pins: P, + pub(super) read_error: Option, +} + +impl> Reader { + /// Reads bytes from the UART. + /// This function reads as long as it can. As soon that the FIFO is empty, if : + /// - 0 bytes were read, a WouldBlock Error is returned + /// - some bytes were read, it is deemed to be a success + /// + /// Upon success, it will return how many bytes were read. + pub fn read_raw<'b>(&self, buffer: &'b mut [u8]) -> nb::Result> { + read_raw(&self.device, buffer) + } + + /// Reads bytes from the UART. + /// This function blocks until the full buffer has been received. + pub fn read_full_blocking(&self, buffer: &mut [u8]) -> Result<(), ReadErrorType> { + read_full_blocking(&self.device, buffer) + } + + /// Enables the Receive Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is data in the receive register. + pub fn enable_rx_interrupt(&mut self) { + enable_rx_interrupt(&self.device) + } + + /// Disables the Receive Interrupt. + pub fn disable_rx_interrupt(&mut self) { + disable_rx_interrupt(&self.device) + } +} + +impl> embedded_io::ErrorType for Reader { + type Error = ReadErrorType; +} + +impl> embedded_io::Read for Reader { + fn read(&mut self, buf: &mut [u8]) -> Result { + // If the last read stored an error, report it now + if let Some(err) = self.read_error.take() { + return Err(err); + } + match nb::block!(self.read_raw(buf)) { + Ok(bytes_read) => Ok(bytes_read), + Err(err) if !err.discarded.is_empty() => { + // If an error was reported but some bytes were already read, + // return the data now and store the error for the next + // invocation. + self.read_error = Some(err.err_type); + Ok(err.discarded.len()) + } + Err(err) => Err(err.err_type), + } + } +} + +impl> embedded_io::ReadReady for Reader { + fn read_ready(&mut self) -> Result { + Ok(is_readable(&self.device)) + } +} + +impl> Read02 for Reader { + type Error = ReadErrorType; + + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} + +// Safety: This only reads from the RX fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl> ReadTarget for Reader { + type ReceivedWord = u8; + + fn rx_treq() -> Option { + Some(D::rx_dreq()) + } + + fn rx_address_count(&self) -> (u32, u32) { + (self.device.uartdr().as_ptr() as u32, u32::MAX) + } + + fn rx_increment(&self) -> bool { + false + } +} + +impl> EndlessReadTarget for Reader {} + +impl> ErrorType for Reader { + type Error = ReadErrorType; +} + +impl> Read for Reader { + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} diff --git a/rp-hal/rp2040-hal/src/uart/utils.rs b/rp-hal/rp2040-hal/src/uart/utils.rs new file mode 100644 index 0000000..74abc90 --- /dev/null +++ b/rp-hal/rp2040-hal/src/uart/utils.rs @@ -0,0 +1,93 @@ +use crate::pac::dma::ch::ch_ctrl_trig::TREQ_SEL_A; +use crate::pac::{uart0::RegisterBlock, UART0, UART1}; +use crate::resets::SubsystemReset; +use crate::typelevel::Sealed; +use core::ops::Deref; + +#[doc(inline)] +pub use rp_hal_common::uart::{DataBits, Parity, StopBits, UartConfig}; + +/// Error type for UART operations. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Bad argument : when things overflow, ... + BadArgument, +} +/// State of the UART Peripheral. +pub trait State: Sealed {} + +/// Trait to handle both underlying devices (UART0 & UART1) +pub trait UartDevice: Deref + SubsystemReset + Sealed + 'static { + /// Index of the Uart. + const ID: usize; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8 + where + Self: Sized; + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8 + where + Self: Sized; +} + +impl UartDevice for UART0 { + const ID: usize = 0; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8 { + TREQ_SEL_A::UART0_TX.into() + } + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8 { + TREQ_SEL_A::UART0_RX.into() + } +} +impl Sealed for UART0 {} +impl UartDevice for UART1 { + const ID: usize = 1; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8 { + TREQ_SEL_A::UART1_TX.into() + } + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8 { + TREQ_SEL_A::UART1_RX.into() + } +} +impl Sealed for UART1 {} + +/// UART is enabled. +pub struct Enabled; + +/// UART is disabled. +pub struct Disabled; + +impl State for Enabled {} +impl Sealed for Enabled {} +impl State for Disabled {} +impl Sealed for Disabled {} + +/// Rx/Tx FIFO Watermark +/// +/// Determine the FIFO level that trigger DMA/Interrupt +/// Default is Bytes16, see DS Table 423 and UARTIFLS Register +/// Example of use: +/// uart0.set_fifos(true); // Default is false +/// uart0.set_rx_watermark(hal::uart::FifoWatermark::Bytes8); +/// uart0.enable_rx_interrupt(); +/// +pub enum FifoWatermark { + /// Trigger when 4 bytes are (Rx: filled / Tx: available) + Bytes4, + /// Trigger when 8 bytes are (Rx: filled / Tx: available) + Bytes8, + /// Trigger when 16 bytes are (Rx: filled / Tx: available) + Bytes16, + /// Trigger when 24 bytes are (Rx: filled / Tx: available) + Bytes24, + /// Trigger when 28 bytes are (Rx: filled / Tx: available) + Bytes28, +} diff --git a/rp-hal/rp2040-hal/src/uart/writer.rs b/rp-hal/rp2040-hal/src/uart/writer.rs new file mode 100644 index 0000000..47bc5a0 --- /dev/null +++ b/rp-hal/rp2040-hal/src/uart/writer.rs @@ -0,0 +1,295 @@ +//! Universal Asynchronous Receiver Transmitter - Transmitter Code +//! +//! This module is for transmitting data with a UART. + +use super::{FifoWatermark, UartDevice, ValidUartPinout}; +use crate::dma::{EndlessWriteTarget, WriteTarget}; +use crate::pac::uart0::RegisterBlock; +use core::fmt; +use core::{convert::Infallible, marker::PhantomData}; +use embedded_hal_0_2::serial::Write as Write02; +use embedded_hal_nb::serial::{ErrorType, Write}; +use nb::Error::*; + +/// Set tx FIFO watermark +/// +/// See DS: Table 423 +pub fn set_tx_watermark(rb: &RegisterBlock, watermark: FifoWatermark) { + let wm = match watermark { + FifoWatermark::Bytes4 => 4, + FifoWatermark::Bytes8 => 3, + FifoWatermark::Bytes16 => 2, + FifoWatermark::Bytes24 => 1, + FifoWatermark::Bytes28 => 0, + }; + rb.uartifls() + .modify(|_r, w| unsafe { w.txiflsel().bits(wm) }); +} + +/// Returns `Err(WouldBlock)` if the UART is still busy transmitting data. +/// It returns Ok(()) when the TX fifo and the transmit shift register are empty +/// and the last stop bit is sent. +pub(crate) fn transmit_flushed(rb: &RegisterBlock) -> nb::Result<(), Infallible> { + if rb.uartfr().read().busy().bit_is_set() { + Err(WouldBlock) + } else { + Ok(()) + } +} + +/// Returns `true` if the TX FIFO has space, or false if it is full +pub(crate) fn uart_is_writable(rb: &RegisterBlock) -> bool { + rb.uartfr().read().txff().bit_is_clear() +} + +/// Returns `true` if the UART is busy transmitting data, `false` after all +/// bits (including stop bits) have been transmitted. +pub(crate) fn uart_is_busy(rb: &RegisterBlock) -> bool { + rb.uartfr().read().busy().bit_is_set() +} + +/// Writes bytes to the UART. +/// +/// This function writes as long as it can. As soon that the FIFO is full, +/// if: +/// - 0 bytes were written, a WouldBlock Error is returned +/// - some bytes were written, it is deemed to be a success +/// +/// Upon success, the remaining (unwritten) slice is returned. +pub(crate) fn write_raw<'d>( + rb: &RegisterBlock, + data: &'d [u8], +) -> nb::Result<&'d [u8], Infallible> { + let mut bytes_written = 0; + + for c in data { + if !uart_is_writable(rb) { + if bytes_written == 0 { + return Err(WouldBlock); + } else { + return Ok(&data[bytes_written..]); + } + } + + rb.uartdr().write(|w| unsafe { + w.data().bits(*c); + w + }); + + bytes_written += 1; + } + Ok(&data[bytes_written..]) +} + +/// Writes bytes to the UART. +/// +/// This function blocks until the full buffer has been sent. +pub(crate) fn write_full_blocking(rb: &RegisterBlock, data: &[u8]) { + let mut temp = data; + + while !temp.is_empty() { + temp = match write_raw(rb, temp) { + Ok(remaining) => remaining, + Err(WouldBlock) => continue, + Err(_) => unreachable!(), + } + } +} + +/// Enables the Transmit Interrupt. +/// +/// The relevant UARTx IRQ will fire when there is space in the transmit FIFO. +pub(crate) fn enable_tx_interrupt(rb: &RegisterBlock) { + // Access the UART FIFO Level Select. We set the TX FIFO trip level + // to be when it's half-empty.. + + // 2 means '<= 1/2 full'. + rb.uartifls() + .modify(|_r, w| unsafe { w.txiflsel().bits(2) }); + + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // high enables the interrupt. + + // We set the TX interrupt. This means we will get an interrupt when + // the TX FIFO level is triggered. This means we don't have to + // interrupt on every single byte, but can make use of the hardware + // FIFO. + rb.uartimsc().modify(|_r, w| { + w.txim().set_bit(); + w + }); +} + +/// Disables the Transmit Interrupt. +pub(crate) fn disable_tx_interrupt(rb: &RegisterBlock) { + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // low disables the interrupt. + + rb.uartimsc().modify(|_r, w| { + w.txim().clear_bit(); + w + }); +} + +/// Half of an [`UartPeripheral`] that is only capable of writing. Obtained by calling [`UartPeripheral::split()`] +/// +/// [`UartPeripheral`]: struct.UartPeripheral.html +/// [`UartPeripheral::split()`]: struct.UartPeripheral.html#method.split +pub struct Writer> { + pub(super) device: D, + pub(super) device_marker: PhantomData, + pub(super) pins: PhantomData

, +} + +impl> Writer { + /// Writes bytes to the UART. + /// + /// This function writes as long as it can. As soon that the FIFO is full, + /// if: + /// - 0 bytes were written, a WouldBlock Error is returned + /// - some bytes were written, it is deemed to be a success + /// + /// Upon success, the remaining (unwritten) slice is returned. + pub fn write_raw<'d>(&self, data: &'d [u8]) -> nb::Result<&'d [u8], Infallible> { + write_raw(&self.device, data) + } + + /// Writes bytes to the UART. + /// + /// This function blocks until the full buffer has been sent. + pub fn write_full_blocking(&self, data: &[u8]) { + write_full_blocking(&self.device, data); + } + + /// Enables the Transmit Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is space in the transmit FIFO. + pub fn enable_tx_interrupt(&mut self) { + enable_tx_interrupt(&self.device) + } + + /// Disables the Transmit Interrupt. + pub fn disable_tx_interrupt(&mut self) { + disable_tx_interrupt(&self.device) + } + + /// Initiates a break + /// + /// If transmitting, this takes effect immediately after the current byte has completed. + /// For proper execution of the break command, this must be held for at least 2 complete frames + /// worth of time. + /// + ///

The device won’t be able to send anything while breaking.
+ /// + /// # Example + /// + /// ```no_run + /// # use rp2040_hal::uart::{Pins, ValidUartPinout, Enabled, UartPeripheral}; + /// # use rp2040_hal::pac::UART0; + /// # use rp2040_hal::typelevel::OptionTNone; + /// # use embedded_hal_0_2::blocking::delay::DelayUs; + /// # type PINS = Pins; + /// # let mut serial: UartPeripheral = unsafe { core::mem::zeroed() }; + /// # let mut timer: rp2040_hal::Timer = unsafe { core::mem::zeroed() }; + /// serial.lowlevel_break_start(); + /// // at 115_200Bps on 8N1 configuration, 20bits takes (20*10⁶)/115200 = 173.611…μs. + /// timer.delay_us(175); + /// serial.lowlevel_break_stop(); + /// ``` + pub fn lowlevel_break_start(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().set_bit()); + } + + /// Terminates a break condition. + /// + /// See `lowlevel_break_start` for more details. + pub fn lowlevel_break_stop(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().clear_bit()); + } +} + +impl> embedded_io::ErrorType for Writer { + type Error = Infallible; +} + +impl> embedded_io::Write for Writer { + fn write(&mut self, buf: &[u8]) -> Result { + let remaining = nb::block!(write_raw(&self.device, buf)).unwrap(); // Infallible + Ok(buf.len() - remaining.len()) + } + fn flush(&mut self) -> Result<(), Self::Error> { + nb::block!(transmit_flushed(&self.device)).unwrap(); // Infallible + Ok(()) + } +} + +impl> embedded_io::WriteReady for Writer { + fn write_ready(&mut self) -> Result { + Ok(uart_is_writable(&self.device)) + } +} + +impl> Write02 for Writer { + type Error = Infallible; + + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + transmit_flushed(&self.device) + } +} + +// Safety: This only writes to the TX fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl> WriteTarget for Writer { + type TransmittedWord = u8; + + fn tx_treq() -> Option { + Some(D::tx_dreq()) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + (self.device.uartdr().as_ptr() as u32, u32::MAX) + } + + fn tx_increment(&self) -> bool { + false + } +} + +impl> EndlessWriteTarget for Writer {} + +impl> ErrorType for Writer { + type Error = Infallible; +} + +impl> Write for Writer { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + transmit_flushed(&self.device).map_err(|e| match e { + WouldBlock => WouldBlock, + Other(v) => match v {}, + }) + } +} + +impl> fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + s.bytes() + .try_for_each(|c| nb::block!(Write::write(self, c))) + .map_err(|_| fmt::Error) + } +} diff --git a/rp-hal/rp2040-hal/src/usb.rs b/rp-hal/rp2040-hal/src/usb.rs new file mode 100644 index 0000000..1e5e9dc --- /dev/null +++ b/rp-hal/rp2040-hal/src/usb.rs @@ -0,0 +1,749 @@ +//! Universal Serial Bus (USB) +//! +//! See [Chapter 4 Section 1](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details. +//! +//! ## Usage +//! +//! Initialize the Usb Bus forcing the VBUS detection. +//! ```no_run +//! use rp2040_hal::{clocks::init_clocks_and_plls, pac, Sio, usb::UsbBus, watchdog::Watchdog}; +//! use usb_device::class_prelude::UsbBusAllocator; +//! +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! +//! let mut pac = pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(pac.WATCHDOG); +//! let mut clocks = init_clocks_and_plls( +//! XOSC_CRYSTAL_FREQ, +//! pac.XOSC, +//! pac.CLOCKS, +//! pac.PLL_SYS, +//! pac.PLL_USB, +//! &mut pac.RESETS, +//! &mut watchdog +//! ).ok().unwrap(); +//! +//! let usb_bus = UsbBusAllocator::new(UsbBus::new( +//! pac.USBCTRL_REGS, +//! pac.USBCTRL_DPRAM, +//! clocks.usb_clock, +//! true, +//! &mut pac.RESETS, +//! )); +//! // Use the usb_bus as usual. +//! ``` +//! +//! See [pico_usb_serial.rs](https://github.com/rp-rs/rp-hal-boards/blob/main/boards/rp-pico/examples/pico_usb_serial.rs) for more complete examples +//! +//! +//! ## Enumeration issue with small EP0 max packet size +//! +//! During enumeration Windows hosts send a `StatusOut` after the `DataIn` packet of the first +//! `Get Descriptor` request even if the `DataIn` isn't completed (typically when the `max_packet_size_ep0` +//! is less than 18bytes). The next request is a `Set Address` that expect a `StatusIn`. +//! +//! The issue is that by the time the previous `DataIn` packet is acknowledged and the `StatusOut` +//! followed by `Setup` are received, the usb stack may have already prepared the next `DataIn` payload +//! in the EP0 IN mailbox resulting in the payload being transmitted to the host instead of the +//! `StatusIn` for the `Set Address` request as expected by the host. +//! +//! To avoid that issue, the EP0 In mailbox should be invalidated between the `Setup` packet and the +//! next `StatusIn` initiated by the host. The workaround implemented clears the available bit of the +//! EP0 In endpoint's buffer to stop the device from sending the data instead of the status packet. +//! This workaround has the caveat that the poll function must be called between those two which +//! are only separated by a few microseconds. +//! +//! If the required timing cannot be met, using an maximum packet size of the endpoint 0 above 18bytes +//! (e.g. `.max_packet_size_ep0(64)`) should avoid that issue. +//! +//! ## Issue on RP2040B0 and RP2040B1: USB device fails to exit RESET state on busy USB bus. +//! +//! The feature `rp2040-e5`implements the workaround described by [RP2040-E5](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#%5B%7B%22num%22%3A630%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C115%2C158.848%2Cnull%5D). +//! +//! The workaround requires the GPIO block to be released from its reset and has for side effect +//! that GPIO15 will be stolen for a few hundred microseconds each time a Reset is detected on the +//! USB bus. +//! +//! The pin will be temporarily put in "bus keep" mode, weakly pulling the output towards its current +//! logic level. In absence of external loads, the current logic level will be maintained. +//! A user will lose control of the pin's output and reading from it may not reflect the actual state +//! of the external pin. +//! +//! ```no_run +//! # use rp2040_hal::{clocks::init_clocks_and_plls, pac, usb::UsbBus, watchdog::Watchdog}; +//! # use usb_device::class_prelude::UsbBusAllocator; +//! use rp2040_hal::{gpio::Pins, Sio}; +//! +//! # const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! # +//! # let mut pac = pac::Peripherals::take().unwrap(); +//! # let mut watchdog = Watchdog::new(pac.WATCHDOG); +//! # let mut clocks = init_clocks_and_plls( +//! # XOSC_CRYSTAL_FREQ, +//! # pac.XOSC, +//! # pac.CLOCKS, +//! # pac.PLL_SYS, +//! # pac.PLL_USB, +//! # &mut pac.RESETS, +//! # &mut watchdog +//! # ).ok().unwrap(); +//! # +//! // required for the errata 5's workaround to function properly. +//! let sio = Sio::new(pac.SIO); +//! let _pins = Pins::new( +//! pac.IO_BANK0, +//! pac.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut pac.RESETS, +//! ); +//! # +//! # let usb_bus = UsbBusAllocator::new(UsbBus::new( +//! # pac.USBCTRL_REGS, +//! # pac.USBCTRL_DPRAM, +//! # clocks.usb_clock, +//! # true, +//! # &mut pac.RESETS, +//! # )); +//! # // Use the usb_bus as usual. +//! ``` + +use core::cell::RefCell; +use critical_section::Mutex; + +use usb_device::{ + bus::{PollResult, UsbBus as UsbBusTrait}, + endpoint::{EndpointAddress, EndpointType}, + Result as UsbResult, UsbDirection, UsbError, +}; + +use crate::{ + clocks::UsbClock, + pac::{RESETS, USBCTRL_DPRAM, USBCTRL_REGS}, + resets::SubsystemReset, +}; + +#[cfg(feature = "rp2040-e5")] +mod errata5; + +#[allow(clippy::bool_to_int_with_if)] +fn ep_addr_to_ep_buf_ctrl_idx(ep_addr: EndpointAddress) -> usize { + ep_addr.index() * 2 + (if ep_addr.is_in() { 0 } else { 1 }) +} +#[derive(Debug)] +struct Endpoint { + ep_type: EndpointType, + max_packet_size: u16, + buffer_offset: u16, +} +impl Endpoint { + unsafe fn get_buf_parts(&self) -> (*mut u8, usize) { + const DPRAM_BASE: *mut u8 = USBCTRL_DPRAM::ptr() as *mut u8; + if self.ep_type == EndpointType::Control { + (DPRAM_BASE.offset(0x100), self.max_packet_size as usize) + } else { + ( + DPRAM_BASE.offset(0x180 + (self.buffer_offset * 64) as isize), + self.max_packet_size as usize, + ) + } + } + + fn get_buf(&self) -> &[u8] { + // SAFETY: + // offset is checked by Inner::ep_allocate. + unsafe { + let (base, len) = self.get_buf_parts(); + core::slice::from_raw_parts(base as *const _, len) + } + } + + fn get_buf_mut(&mut self) -> &mut [u8] { + // SAFETY: + // offset is checked by Inner::ep_allocate. + unsafe { + let (base, len) = self.get_buf_parts(); + core::slice::from_raw_parts_mut(base, len) + } + } +} + +struct Inner { + ctrl_reg: USBCTRL_REGS, + ctrl_dpram: USBCTRL_DPRAM, + in_endpoints: [Option; 16], + out_endpoints: [Option; 16], + next_offset: u16, + read_setup: bool, + pll: UsbClock, + #[cfg(feature = "rp2040-e5")] + errata5_state: Option, +} +impl Inner { + fn new(ctrl_reg: USBCTRL_REGS, ctrl_dpram: USBCTRL_DPRAM, pll: UsbClock) -> Self { + Self { + ctrl_reg, + ctrl_dpram, + in_endpoints: Default::default(), + out_endpoints: Default::default(), + next_offset: 0, + read_setup: false, + pll, + #[cfg(feature = "rp2040-e5")] + errata5_state: None, + } + } + + fn ep_allocate( + &mut self, + ep_addr: Option, + ep_dir: UsbDirection, + ep_type: EndpointType, + max_packet_size: u16, + ) -> UsbResult { + let ep_addr = ep_addr + .or_else(|| { + let eps = if ep_dir == UsbDirection::In { + self.in_endpoints.iter() + } else { + self.out_endpoints.iter() + }; + // find free end point + let mut iter = eps.enumerate(); + // reserve ep0 for the control endpoint + if ep_type != EndpointType::Control { + iter.next(); + } + iter.find(|(_, ep)| ep.is_none()) + .map(|(index, _)| EndpointAddress::from_parts(index, ep_dir)) + }) + .ok_or(UsbError::EndpointOverflow)?; + + let is_ep0 = ep_addr.index() == 0; + let is_ctrl_ep = ep_type == EndpointType::Control; + if !(is_ep0 ^ !is_ctrl_ep) { + return Err(UsbError::Unsupported); + } + + let eps = if ep_addr.is_in() { + &mut self.in_endpoints + } else { + &mut self.out_endpoints + }; + let maybe_ep = eps + .get_mut(ep_addr.index()) + .ok_or(UsbError::EndpointOverflow)?; + if maybe_ep.is_some() { + return Err(UsbError::InvalidEndpoint); + } + + // Validate buffer size. From datasheet (4.1.2.5): + // Data Buffers are typically 64 bytes long as this is the max normal packet size for most FS packets. + // For Isochronous endpoints a maximum buffer size of 1023 bytes is supported. + // For other packet types the maximum size is 64 bytes per buffer. + if (!matches!(ep_type, EndpointType::Isochronous { .. }) && max_packet_size > 64) + || max_packet_size > 1023 + { + return Err(UsbError::Unsupported); + } + + if ep_addr.index() == 0 { + *maybe_ep = Some(Endpoint { + ep_type, + max_packet_size, + buffer_offset: 0, // not used on CTRL ep + }); + } else { + // size in 64bytes units. + // NOTE: the compiler is smart enough to recognize /64 as a 6bit right shift so let's + // keep the division here for the sake of clarity + let aligned_sized = (max_packet_size + 63) / 64; + if (self.next_offset + aligned_sized) > (4096 / 64) { + return Err(UsbError::EndpointMemoryOverflow); + } + + let buffer_offset = self.next_offset; + self.next_offset += aligned_sized; + + *maybe_ep = Some(Endpoint { + ep_type, + max_packet_size, + buffer_offset, + }); + } + Ok(ep_addr) + } + + fn ep_reset_all(&mut self) { + self.ctrl_reg + .sie_ctrl() + .modify(|_, w| w.ep0_int_1buf().set_bit()); + // expect ctrl ep to receive on DATA first + self.ctrl_dpram + .ep_buffer_control(0) + .write(|w| w.pid_0().set_bit()); + self.ctrl_dpram + .ep_buffer_control(1) + .write(|w| w.pid_0().set_bit()); + cortex_m::asm::delay(12); + self.ctrl_dpram + .ep_buffer_control(1) + .write(|w| w.available_0().set_bit()); + + for (index, ep) in itertools::interleave( + self.in_endpoints.iter().skip(1), // skip control endpoint + self.out_endpoints.iter().skip(1), // skip control endpoint + ) + .enumerate() + .filter_map(|(i, ep)| ep.as_ref().map(|ep| (i, ep))) + { + use crate::pac::usbctrl_dpram::ep_control::ENDPOINT_TYPE_A; + let ep_type = match ep.ep_type { + EndpointType::Bulk => ENDPOINT_TYPE_A::BULK, + EndpointType::Isochronous { .. } => ENDPOINT_TYPE_A::ISOCHRONOUS, + EndpointType::Control => ENDPOINT_TYPE_A::CONTROL, + EndpointType::Interrupt => ENDPOINT_TYPE_A::INTERRUPT, + }; + // configure + // ep 0 in&out are not part of index (skipped before enumeration) + self.ctrl_dpram.ep_control(index).modify(|_, w| unsafe { + w.endpoint_type().variant(ep_type); + w.interrupt_per_buff().set_bit(); + w.enable().set_bit(); + w.buffer_address().bits(0x180 + (ep.buffer_offset << 6)) + }); + // reset OUT ep and prepare IN ep to accept data + let buf_control = &self.ctrl_dpram.ep_buffer_control(index + 2); + if (index & 1) == 0 { + // first write occur on DATA0 so prepare the pid bit to be flipped + buf_control.write(|w| w.pid_0().set_bit()); + } else { + buf_control.write(|w| unsafe { + w.pid_0().clear_bit(); + w.length_0().bits(ep.max_packet_size) + }); + cortex_m::asm::delay(12); + buf_control.modify(|_, w| w.available_0().set_bit()); + } + } + } + + fn ep_write(&mut self, ep_addr: EndpointAddress, buf: &[u8]) -> UsbResult { + let index = ep_addr.index(); + let ep = self + .in_endpoints + .get_mut(index) + .and_then(Option::as_mut) + .ok_or(UsbError::InvalidEndpoint)?; + + let buf_control = &self.ctrl_dpram.ep_buffer_control(index * 2); + if buf_control.read().available_0().bit_is_set() { + return Err(UsbError::WouldBlock); + } + + let ep_buf = ep.get_buf_mut(); + if ep_buf.len() < buf.len() { + return Err(UsbError::BufferOverflow); + } + ep_buf[..buf.len()].copy_from_slice(buf); + + buf_control.modify(|r, w| unsafe { + w.length_0().bits(buf.len() as u16); + w.full_0().set_bit(); + w.pid_0().bit(!r.pid_0().bit()) + }); + cortex_m::asm::delay(12); + buf_control.modify(|_, w| w.available_0().set_bit()); + + Ok(buf.len()) + } + + fn ep_read(&mut self, ep_addr: EndpointAddress, buf: &mut [u8]) -> UsbResult { + let index = ep_addr.index(); + let ep = self + .out_endpoints + .get_mut(index) + .and_then(Option::as_mut) + .ok_or(UsbError::InvalidEndpoint)?; + + let buf_control = &self.ctrl_dpram.ep_buffer_control(index * 2 + 1); + let buf_control_val = buf_control.read(); + + let process_setup = index == 0 && self.read_setup; + if process_setup { + // assume we want to read the setup request + // + // the OUT packet will be either data or a status zlp + let len = 8; + let ep_buf = + unsafe { core::slice::from_raw_parts(USBCTRL_DPRAM::ptr() as *const u8, len) }; + if len > buf.len() { + return Err(UsbError::BufferOverflow); + } + + buf[..len].copy_from_slice(&ep_buf[..len]); + + // Next packet will be on DATA1 so clear pid_0 so it gets flipped by next buf config + self.ctrl_dpram + .ep_buffer_control(0) + .modify(|_, w| w.pid_0().clear_bit()); + // clear setup request flag + self.ctrl_reg + .sie_status() + .write(|w| w.setup_rec().clear_bit_by_one()); + + // clear any out standing out flag e.g. in case a zlp got discarded + self.ctrl_reg.buff_status().write(|w| unsafe { w.bits(2) }); + + let is_in_request = (buf[0] & 0x80) == 0x80; + let data_length = u16::from(buf[6]) | (u16::from(buf[7]) << 8); + let expect_data_or_zlp = is_in_request || data_length != 0; + + buf_control.modify(|_, w| unsafe { + w.length_0().bits(ep.max_packet_size); + w.full_0().clear_bit(); + w.pid_0().set_bit() + }); + // enable if and only if a dataphase is expected. + cortex_m::asm::delay(12); + buf_control.modify(|_, w| w.available_0().bit(expect_data_or_zlp)); + + self.read_setup = false; + Ok(len) + } else { + if buf_control_val.full_0().bit_is_clear() { + return Err(UsbError::WouldBlock); + } + let len = buf_control_val.length_0().bits().into(); + if len > buf.len() { + return Err(UsbError::BufferOverflow); + } + + buf[..len].copy_from_slice(&ep.get_buf()[..len]); + // Clear OUT flag once it is read. + self.ctrl_reg + .buff_status() + .write(|w| unsafe { w.bits(1 << (index * 2 + 1)) }); + + buf_control.modify(|r, w| unsafe { + w.length_0().bits(ep.max_packet_size); + w.full_0().clear_bit(); + w.pid_0().bit(!r.pid_0().bit()) + }); + if index != 0 || len == ep.max_packet_size.into() { + // only mark as available on the control endpoint if and only if the packet was + // max_packet_size + cortex_m::asm::delay(12); + buf_control.modify(|_, w| w.available_0().set_bit()); + } + Ok(len) + } + } +} + +/// Usb bus +pub struct UsbBus { + inner: Mutex>, +} + +impl UsbBus { + /// Create new usb bus struct and bring up usb as device. + pub fn new( + ctrl_reg: USBCTRL_REGS, + ctrl_dpram: USBCTRL_DPRAM, + pll: UsbClock, + force_vbus_detect_bit: bool, + resets: &mut RESETS, + ) -> Self { + #[cfg(feature = "rp2040-e5")] + errata5::Errata5State::check_bank0_reset(); + + ctrl_reg.reset_bring_down(resets); + ctrl_reg.reset_bring_up(resets); + + unsafe { + let raw_ctrl_reg = + core::slice::from_raw_parts_mut(USBCTRL_REGS::ptr() as *mut u32, 1 + 0x98 / 4); + raw_ctrl_reg.fill(0); + + let raw_ctrl_pdram = + core::slice::from_raw_parts_mut(USBCTRL_DPRAM::ptr() as *mut u32, 1 + 0xfc / 4); + raw_ctrl_pdram.fill(0); + } + + ctrl_reg.usb_muxing().modify(|_, w| { + w.to_phy().set_bit(); + w.softcon().set_bit() + }); + + if force_vbus_detect_bit { + ctrl_reg.usb_pwr().modify(|_, w| { + w.vbus_detect().set_bit(); + w.vbus_detect_override_en().set_bit() + }); + } + ctrl_reg.main_ctrl().modify(|_, w| { + w.sim_timing().clear_bit(); + w.host_ndevice().clear_bit(); + w.controller_en().set_bit() + }); + + Self { + inner: Mutex::new(RefCell::new(Inner::new(ctrl_reg, ctrl_dpram, pll))), + } + } + + /// Generates a resume request on the bus. + pub fn remote_wakeup(&self) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + inner + .ctrl_reg + .sie_ctrl() + .modify(|_, w| w.resume().set_bit()); + }); + } + + /// Stop and free the Usb resources + pub fn free(self, resets: &mut RESETS) -> (USBCTRL_REGS, USBCTRL_DPRAM, UsbClock) { + critical_section::with(|_cs| { + let inner = self.inner.into_inner().into_inner(); + + inner.ctrl_reg.reset_bring_down(resets); + + (inner.ctrl_reg, inner.ctrl_dpram, inner.pll) + }) + } +} + +impl UsbBusTrait for UsbBus { + fn alloc_ep( + &mut self, + ep_dir: UsbDirection, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + _interval: u8, + ) -> UsbResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + + inner.ep_allocate(ep_addr, ep_dir, ep_type, max_packet_size) + }) + } + + fn enable(&mut self) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + // at this stage ep's are expected to be in their reset state + // TODO: is it worth having a debug_assert for that here? + + // Enable interrupt generation when a buffer is done, when the bus is reset, + // and when a setup packet is received + // this should be sufficient for device mode, will need more for host. + inner.ctrl_reg.inte().modify(|_, w| { + w.buff_status() + .set_bit() + .bus_reset() + .set_bit() + .dev_resume_from_host() + .set_bit() + .dev_suspend() + .set_bit() + .setup_req() + .set_bit() + }); + + // enable pull up to let the host know we exist. + inner + .ctrl_reg + .sie_ctrl() + .modify(|_, w| w.pullup_en().set_bit()); + }) + } + fn reset(&self) { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + + // clear reset flag + inner + .ctrl_reg + .sie_status() + .write(|w| w.bus_reset().clear_bit_by_one()); + inner + .ctrl_reg + .buff_status() + .write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + + // reset all endpoints + inner.ep_reset_all(); + + // Reset address register + inner.ctrl_reg.addr_endp().reset(); + // TODO: RP2040-E5: work around implementation + // TODO: reset all endpoints & buffer statuses + }) + } + fn set_device_address(&self, addr: u8) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + inner + .ctrl_reg + .addr_endp() + .modify(|_, w| unsafe { w.address().bits(addr & 0x7F) }); + // reset ep0 + inner + .ctrl_dpram + .ep_buffer_control(0) + .modify(|_, w| w.pid_0().set_bit()); + inner + .ctrl_dpram + .ep_buffer_control(1) + .modify(|_, w| w.pid_0().set_bit()); + }) + } + fn write(&self, ep_addr: EndpointAddress, buf: &[u8]) -> UsbResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + inner.ep_write(ep_addr, buf) + }) + } + fn read(&self, ep_addr: EndpointAddress, buf: &mut [u8]) -> UsbResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + inner.ep_read(ep_addr, buf) + }) + } + fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + + if ep_addr.index() == 0 { + inner.ctrl_reg.ep_stall_arm().modify(|_, w| { + if ep_addr.is_in() { + w.ep0_in().bit(stalled) + } else { + w.ep0_out().bit(stalled) + } + }); + } + + let index = ep_addr_to_ep_buf_ctrl_idx(ep_addr); + inner + .ctrl_dpram + .ep_buffer_control(index) + .modify(|_, w| w.stall().bit(stalled)); + }) + } + fn is_stalled(&self, ep_addr: EndpointAddress) -> bool { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + let index = ep_addr_to_ep_buf_ctrl_idx(ep_addr); + inner + .ctrl_dpram + .ep_buffer_control(index) + .read() + .stall() + .bit_is_set() + }) + } + fn suspend(&self) {} + fn resume(&self) {} + fn poll(&self) -> PollResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + + #[cfg(feature = "rp2040-e5")] + if let Some(state) = inner.errata5_state.take() { + unsafe { + inner.errata5_state = state.update(); + } + return if inner.errata5_state.is_some() { + PollResult::None + } else { + PollResult::Reset + }; + } + + // check for bus reset and/or suspended states. + let ints = inner.ctrl_reg.ints().read(); + let mut buff_status = inner.ctrl_reg.buff_status().read().bits(); + + if ints.bus_reset().bit_is_set() { + #[cfg(feature = "rp2040-e5")] + if inner + .ctrl_reg + .sie_status() + .read() + .connected() + .bit_is_clear() + { + inner.errata5_state = Some(errata5::Errata5State::start()); + return PollResult::None; + } else { + return PollResult::Reset; + } + + #[cfg(not(feature = "rp2040-e5"))] + return PollResult::Reset; + } else if buff_status == 0 && ints.setup_req().bit_is_clear() { + if ints.dev_suspend().bit_is_set() { + inner + .ctrl_reg + .sie_status() + .write(|w| w.suspended().clear_bit_by_one()); + return PollResult::Suspend; + } else if ints.dev_resume_from_host().bit_is_set() { + inner + .ctrl_reg + .sie_status() + .write(|w| w.resume().clear_bit_by_one()); + return PollResult::Resume; + } + return PollResult::None; + } + + let (mut ep_out, mut ep_in_complete, mut ep_setup): (u16, u16, u16) = (0, 0, 0); + + // IN Complete shall only be reported once. + inner + .ctrl_reg + .buff_status() + .write(|w| unsafe { w.bits(0x5555_5555) }); + + for i in 0..32u32 { + if buff_status == 0 { + break; + } else if (buff_status & 1) == 1 { + let is_in = (i & 1) == 0; + let ep_idx = i / 2; + if is_in { + ep_in_complete |= 1 << ep_idx; + } else { + ep_out |= 1 << ep_idx; + } + } + buff_status >>= 1; + } + + // check for setup request + if ints.setup_req().bit_is_set() { + // Small max_packet_size_ep0 Work-Around + inner + .ctrl_dpram + .ep_buffer_control(0) + .modify(|_, w| w.available_0().clear_bit()); + + ep_setup |= 1; + inner.read_setup = true; + } + + PollResult::Data { + ep_out, + ep_in_complete, + ep_setup, + } + }) + } + + const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = false; +} diff --git a/rp-hal/rp2040-hal/src/usb/errata5.rs b/rp-hal/rp2040-hal/src/usb/errata5.rs new file mode 100644 index 0000000..5ba9b08 --- /dev/null +++ b/rp-hal/rp2040-hal/src/usb/errata5.rs @@ -0,0 +1,148 @@ +//! After coming out of reset, the hardware expects 800us of LS_J (linestate J) time +//! before it will move to the connected state. However on a hub that broadcasts packets +//! for other devices this isn't the case. The plan here is to wait for the end of the bus +//! reset, force an LS_J for 1ms and then switch control back to the USB phy. Unfortunately +//! this requires us to use GPIO15 as there is no other way to force the input path. +//! We only need to force DP as DM can be left at zero. It will be gated off by GPIO +//! logic if it isn't func selected. + +use crate::atomic_register_access::{write_bitmask_clear, write_bitmask_set}; +use crate::pac::Peripherals; + +pub struct ForceLineStateJ { + prev_pads: u32, + prev_io_ctrls: u32, +} +pub enum Errata5State { + WaitEndOfReset, + ForceLineStateJ(ForceLineStateJ), +} + +impl Errata5State { + pub fn start() -> Self { + Self::WaitEndOfReset + } + /// SAFETY: This method steals the peripherals. + /// It makes read only use of TIMER and SYSINFO and read/write access to USBCTRL_REGS. + /// Both peripherals must be initialized & running. + pub unsafe fn update(self) -> Option { + let pac = crate::pac::Peripherals::steal(); + match self { + Self::WaitEndOfReset => { + if pac.SYSINFO.chip_id().read().revision().bits() >= 2 { + None + } else if pac.USBCTRL_REGS.sie_status().read().line_state().is_se0() { + Some(self) + } else { + Some(Self::ForceLineStateJ(start_force_j(&pac))) + } + } + Self::ForceLineStateJ(ref state) => { + if pac + .USBCTRL_REGS + .sie_status() + .read() + .connected() + .bit_is_clear() + { + Some(self) + } else { + finish(&pac, state.prev_pads, state.prev_io_ctrls); + None + } + } + } + } + + /// Make sure bank0 is out of reset, which is necessary for the rp2040-e5 workaround. + /// If it is not, panic. + pub fn check_bank0_reset() { + // SAFETY: Only used for reading the reset state. + let pac = unsafe { crate::pac::Peripherals::steal() }; + let reset_state = pac.RESETS.reset().read(); + assert!( + reset_state.io_bank0().bit_is_clear() && reset_state.pads_bank0().bit_is_clear(), + "IO Bank 0 must be out of reset for this work around to function properly." + ); + } +} + +const DP_PULLUP_EN_FLAG: u32 = 0x0000_0002; +const DP_PULLUP_EN_OVERRIDE_FLAG: u32 = 0x0000_0004; + +fn start_force_j(pac: &Peripherals) -> ForceLineStateJ { + let pads = &pac.PADS_BANK0.gpio(15); + let io = &pac.IO_BANK0.gpio(15); + let usb_ctrl = &pac.USBCTRL_REGS; + + assert!(!usb_ctrl.sie_status().read().line_state().is_se0()); + assert!( + pac.IO_BANK0.gpio(16).gpio_ctrl().read().funcsel().bits() != 8, + "Not expecting DM to be function 8" + ); + + // backup io ctrl & pad ctrl + let prev_pads = pads.read().bits(); + let prev_io_ctrls = io.gpio_ctrl().read().bits(); + + // Enable bus keep and force pin to tristate, so USB DP muxing doesn't affect + // pin state + pads.modify(|_, w| w.pue().set_bit().pde().set_bit()); + io.gpio_ctrl().modify(|_, w| w.oeover().disable()); + + // Select function 8 (USB debug muxing) without disturbing other controls + io.gpio_ctrl().modify(|_, w| unsafe { w.funcsel().bits(8) }); + + // J state is a differential 1 for a full speed device so + // DP = 1 and DM = 0. Don't actually need to set DM low as it + // is already gated assuming it isn't funcseld. + io.gpio_ctrl().modify(|_, w| w.inover().high()); + + // Force PHY pull up to stay before switching away from the phy + unsafe { + let usbphy_direct = usb_ctrl.usbphy_direct().as_ptr(); + let usbphy_direct_override = usb_ctrl.usbphy_direct_override().as_ptr(); + write_bitmask_set(usbphy_direct, DP_PULLUP_EN_FLAG); + write_bitmask_set(usbphy_direct_override, DP_PULLUP_EN_OVERRIDE_FLAG); + } + + // Switch to GPIO phy with LS_J forced + unsafe { + usb_ctrl + .usb_muxing() + .write_with_zero(|w| w.to_digital_pad().set_bit().softcon().set_bit()); + } + + // LS_J is now forced, wait until the signal propagates through the usb logic. + loop { + let status = usb_ctrl.sie_status().read(); + if status.line_state().is_j() { + break; + } + } + + ForceLineStateJ { + prev_pads, + prev_io_ctrls, + } +} +fn finish(pac: &Peripherals, prev_pads: u32, prev_io_ctrls: u32) { + let pads = &pac.PADS_BANK0.gpio(15); + let io = &pac.IO_BANK0.gpio(15); + + // Switch back to USB phy + pac.USBCTRL_REGS + .usb_muxing() + .write(|w| w.to_phy().set_bit().softcon().set_bit()); + + // Get rid of DP pullup override + unsafe { + let usbphy_direct_override = pac.USBCTRL_REGS.usbphy_direct_override().as_ptr(); + write_bitmask_clear(usbphy_direct_override, DP_PULLUP_EN_OVERRIDE_FLAG); + + // Finally, restore the gpio ctrl value back to GPIO15 + io.gpio_ctrl().write(|w| w.bits(prev_io_ctrls)); + // Restore the pad ctrl value + pads.write(|w| w.bits(prev_pads)); + } +} diff --git a/rp-hal/rp2040-hal/src/vector_table.rs b/rp-hal/rp2040-hal/src/vector_table.rs new file mode 100644 index 0000000..432fea0 --- /dev/null +++ b/rp-hal/rp2040-hal/src/vector_table.rs @@ -0,0 +1,94 @@ +//! Interrupt vector table utilities +//! +//! Provide functionality to switch to another vector table using the +//! Vector Table Offset Register (VTOR) of the Cortex-M0+ +//! Also provides types and utilities for copying a vector table into RAM + +/// Entry for a Vector in the Interrupt Vector Table. +/// +/// Each entry in the Vector table is a union with usize to allow it to be 0 initialized via const initializer +/// +/// Implementation borrowed from https://docs.rs/cortex-m-rt/0.7.1/cortex_m_rt/index.html#__interrupts +#[derive(Clone, Copy)] +union Vector { + handler: extern "C" fn(), + reserved: usize, +} + +/// Data type for a properly aligned interrupt vector table +/// +/// The VTOR register can only point to a 256 byte offsets - see +/// [Cortex-M0+ Devices Generic User Guide](https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Processor/Exception-model/Vector-table) - +/// so that is our required alignment. +/// The vector table length depends on the number of interrupts the system supports. +/// The first 16 words are defined in the ARM Cortex-M spec. +/// The M0+ cores on RP2040 have 32 interrupts, of which only 26 are wired to external interrupt +/// signals - but the last 6 can be used for software interrupts so leave room for them +#[repr(C, align(256))] +pub struct VectorTable { + /// SP + Reset vector + 14 exceptions + 32 interrupts = 48 entries (192 bytes) in an RP2040 core's VectorTable + table: [Vector; 48], +} + +impl Default for VectorTable { + fn default() -> Self { + Self::new() + } +} + +impl VectorTable { + /// Create a new vector table. All entries will point to 0 - you must call init() + /// on this to copy the current vector table before setting it as active + pub const fn new() -> VectorTable { + VectorTable { + table: [Vector { reserved: 0 }; 48], + } + } + + /// Initialise our vector table by copying the current table on top of it + #[allow(unknown_lints)] + #[allow(clippy::needless_pass_by_ref_mut)] + pub fn init(&mut self, ppb: &mut crate::pac::PPB) { + let vector_table = ppb.vtor().read().bits(); + unsafe { + crate::rom_data::memcpy44( + &mut self.table as *mut _ as *mut u32, + vector_table as *const u32, + 192, + ) + }; + } + + /// Dynamically register a function as being an interrupt handler + pub fn register_handler(&mut self, interrupt_idx: usize, interrupt_fn: extern "C" fn()) { + self.table[16 + interrupt_idx].handler = interrupt_fn; + } + + /// Set the stack pointer address in a VectorTable. This will be used on Reset + /// + /// # Safety + /// There is no checking whether this is a valid stack pointer address + pub unsafe fn set_sp(&mut self, stack_pointer_address: usize) { + self.table[0].reserved = stack_pointer_address; + } + + /// Set the entry-point address in a VectorTable. This will be used on Reset + /// + /// # Safety + /// There is no checking whether this is a valid entry point + pub unsafe fn set_entry(&mut self, entry_address: usize) { + self.table[1].reserved = entry_address; + } + + /// Switch the current core to use this Interrupt Vector Table + /// + /// # Safety + /// Until the vector table has valid entries, activating it will cause an unhandled hardfault! + /// You must call init() first. + #[allow(unknown_lints)] + #[allow(clippy::needless_pass_by_ref_mut)] + pub unsafe fn activate(&mut self, ppb: &mut crate::pac::PPB) { + ppb.vtor() + .write(|w| w.bits(&mut self.table as *mut _ as *mut u32 as u32)); + } +} diff --git a/rp-hal/rp2040-hal/src/vreg.rs b/rp-hal/rp2040-hal/src/vreg.rs new file mode 100644 index 0000000..e28fab3 --- /dev/null +++ b/rp-hal/rp2040-hal/src/vreg.rs @@ -0,0 +1,37 @@ +//! On-chip voltage regulator (VREG) +//! +//! See [Chapter 2, Section 10](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf) of the datasheet for more details +//! +//! ## Usage +//! ```no_run +//! use rp2040_hal::pac::vreg_and_chip_reset::vreg::VSEL_A; +//! use rp2040_hal::{vreg::set_voltage, pac}; +//! let mut pac = pac::Peripherals::take().unwrap(); +//! // Set voltage to 1.20V +//! set_voltage(&mut pac.VREG_AND_CHIP_RESET, VSEL_A::VOLTAGE1_20); +//! ``` + +use crate::pac::vreg_and_chip_reset::vreg::VSEL_A; +use crate::pac::VREG_AND_CHIP_RESET; + +/// Set voltage to the on-chip voltage regulator. +/// +/// There is no guarantee that the device will operate at all of the available voltages. +/// Appropriate values should be selected in consideration of the system clock frequency and other factors to be set. +/// +/// # Arguments +/// +/// * `vreg_dev` - VREG peripheral +/// * `voltage` - Voltage to set +pub fn set_voltage(vreg_dev: &mut VREG_AND_CHIP_RESET, voltage: VSEL_A) { + vreg_dev.vreg().write(|w| w.vsel().variant(voltage)); +} + +/// Get voltage from the on-chip voltage regulator +/// +/// # Arguments +/// +/// * `vreg_dev` - VREG peripheral +pub fn get_voltage(vreg_dev: &VREG_AND_CHIP_RESET) -> Option { + vreg_dev.vreg().read().vsel().variant() +} diff --git a/rp-hal/rp2040-hal/src/watchdog.rs b/rp-hal/rp2040-hal/src/watchdog.rs new file mode 100644 index 0000000..67b467e --- /dev/null +++ b/rp-hal/rp2040-hal/src/watchdog.rs @@ -0,0 +1,226 @@ +//! 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. +//! +//! See [Chapter 4 Section 7](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) of the datasheet for more details +//! +//! ## Usage +//! ```no_run +//! use cortex_m::prelude::{_embedded_hal_watchdog_Watchdog, _embedded_hal_watchdog_WatchdogEnable}; +//! use fugit::ExtU32; +//! use rp2040_hal::{clocks::init_clocks_and_plls, pac, watchdog::Watchdog}; +//! let mut pac = pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(pac.WATCHDOG); +//! let _clocks = init_clocks_and_plls( +//! 12_000_000, +//! pac.XOSC, +//! pac.CLOCKS, +//! pac.PLL_SYS, +//! pac.PLL_USB, +//! &mut pac.RESETS, +//! &mut watchdog, +//! ).ok().unwrap(); +//! // Set to watchdog to reset if it's not reloaded within 1.05 seconds, and start it +//! watchdog.start(1_050_000.micros()); +//! // Feed the watchdog once per cycle to avoid reset +//! for _ in 1..=10000 { +//! cortex_m::asm::delay(100_000); +//! watchdog.feed(); +//! } +//! // Stop feeding, now we'll reset +//! loop {} +//! ``` +//! See [examples/watchdog.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal-examples/src/bin/watchdog.rs) for a more complete example + +// Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +use embedded_hal_0_2::watchdog; +use fugit::MicrosDurationU32; + +use crate::pac::{self, WATCHDOG}; + +/// Watchdog peripheral +pub struct Watchdog { + watchdog: WATCHDOG, + load_value: u32, // decremented by 2 per tick (µs) +} + +#[derive(Debug)] +#[allow(missing_docs)] +/// Scratch registers of the watchdog peripheral +pub enum ScratchRegister { + Scratch0, + Scratch1, + Scratch2, + Scratch3, + Scratch4, + Scratch5, + Scratch6, + Scratch7, +} + +impl Watchdog { + /// Create a new [`Watchdog`] + pub fn new(watchdog: WATCHDOG) -> Self { + Self { + watchdog, + load_value: 0, + } + } + + /// Starts 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) { + const WATCHDOG_TICK_ENABLE_BITS: u32 = 0x200; + + self.watchdog + .tick() + .write(|w| unsafe { w.bits(WATCHDOG_TICK_ENABLE_BITS | cycles as u32) }) + } + + /// Defines whether or not the watchdog timer should be paused when processor(s) are in debug mode + /// or when JTAG is accessing bus fabric + /// + /// # Arguments + /// + /// * `pause` - If true, watchdog timer will be paused + pub fn pause_on_debug(&mut self, pause: bool) { + self.watchdog.ctrl().write(|w| { + w.pause_dbg0() + .bit(pause) + .pause_dbg1() + .bit(pause) + .pause_jtag() + .bit(pause) + }) + } + + fn load_counter(&self, counter: u32) { + self.watchdog.load().write(|w| unsafe { w.bits(counter) }); + } + + fn enable(&self, bit: bool) { + self.watchdog.ctrl().write(|w| w.enable().bit(bit)) + } + + /// Read a scratch register + pub fn read_scratch(&self, reg: ScratchRegister) -> u32 { + match reg { + ScratchRegister::Scratch0 => self.watchdog.scratch0().read().bits(), + ScratchRegister::Scratch1 => self.watchdog.scratch1().read().bits(), + ScratchRegister::Scratch2 => self.watchdog.scratch2().read().bits(), + ScratchRegister::Scratch3 => self.watchdog.scratch3().read().bits(), + ScratchRegister::Scratch4 => self.watchdog.scratch4().read().bits(), + ScratchRegister::Scratch5 => self.watchdog.scratch5().read().bits(), + ScratchRegister::Scratch6 => self.watchdog.scratch6().read().bits(), + ScratchRegister::Scratch7 => self.watchdog.scratch7().read().bits(), + } + } + + /// Write a scratch register + pub fn write_scratch(&mut self, reg: ScratchRegister, value: u32) { + match reg { + ScratchRegister::Scratch0 => { + self.watchdog.scratch0().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch1 => { + self.watchdog.scratch1().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch2 => { + self.watchdog.scratch2().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch3 => { + self.watchdog.scratch3().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch4 => { + self.watchdog.scratch4().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch5 => { + self.watchdog.scratch5().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch6 => { + self.watchdog.scratch6().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch7 => { + self.watchdog.scratch7().write(|w| unsafe { w.bits(value) }) + } + } + } + + /// Configure which hardware will be reset by the watchdog + /// the default is everything except ROSC, XOSC + /// + /// Safety: ensure no other device is writing to psm.wdsel + /// This is easy at the moment, since nothing else uses PSM + unsafe fn configure_wdog_reset_triggers(&self) { + let psm = &*pac::PSM::ptr(); + psm.wdsel().write_with_zero(|w| { + w.bits(0x0001ffff); + w.xosc().clear_bit(); + w.rosc().clear_bit(); + w + }); + } + + /// Set the watchdog counter back to its load value, making sure + /// that the watchdog reboot will not be triggered for the configured + /// period. + pub fn feed(&self) { + self.load_counter(self.load_value) + } + + /// Start the watchdog. This enables a timer which will reboot the + /// rp2040 if [`Watchdog::feed()`] does not get called for the configured period. + pub fn start>(&mut self, period: T) { + const MAX_PERIOD: u32 = 0xFFFFFF; + + let delay_us = period.into().to_micros(); + 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); + } + + /// Disable the watchdog timer. + pub fn disable(&self) { + self.enable(false) + } +} + +impl watchdog::Watchdog for Watchdog { + fn feed(&mut self) { + (*self).feed() + } +} + +impl watchdog::WatchdogEnable for Watchdog { + type Time = MicrosDurationU32; + + fn start>(&mut self, period: T) { + self.start(period) + } +} + +impl watchdog::WatchdogDisable for Watchdog { + fn disable(&mut self) { + (*self).disable() + } +} diff --git a/rp-hal/rp2040-hal/src/xosc.rs b/rp-hal/rp2040-hal/src/xosc.rs new file mode 100644 index 0000000..8fb2447 --- /dev/null +++ b/rp-hal/rp2040-hal/src/xosc.rs @@ -0,0 +1,223 @@ +//! Crystal Oscillator (XOSC) +//! +//! See [Chapter 2 Section 16](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details. + +use core::{convert::Infallible, ops::RangeInclusive}; + +use fugit::HertzU32; +use nb::Error::WouldBlock; + +use crate::{pac::XOSC, typelevel::Sealed}; + +/// State of the Crystal Oscillator (typestate trait) +pub trait State: Sealed {} + +/// XOSC is disabled (typestate) +pub struct Disabled; + +/// XOSC is initialized but has not yet stabilized (typestate) +pub struct Unstable { + freq_hz: HertzU32, +} + +/// XOSC is stable (typestate) +pub struct Stable { + freq_hz: HertzU32, +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Unstable {} +impl Sealed for Unstable {} +impl State for Stable {} +impl Sealed for Stable {} + +/// Possible errors when initializing the CrystalOscillator +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Frequency is out of the 1-15MHz range (see datasheet) + FrequencyOutOfRange, + + /// Argument is bad : overflows, ... + BadArgument, +} + +/// Blocking helper method to setup the XOSC without going through all the steps. +/// +/// This uses a startup_delay_multiplier of 64, which is a rather conservative value +/// that should work even if the XOSC starts up slowly. In case you need a fast boot +/// sequence, and your XOSC starts up quickly enough, use [`setup_xosc_blocking_custom_delay`]. +pub fn setup_xosc_blocking( + xosc_dev: XOSC, + frequency: HertzU32, +) -> Result, Error> { + let initialized_xosc = CrystalOscillator::new(xosc_dev).initialize(frequency, 64)?; + + let stable_xosc_token = nb::block!(initialized_xosc.await_stabilization()).unwrap(); + + Ok(initialized_xosc.get_stable(stable_xosc_token)) +} + +/// Blocking helper method to setup the XOSC without going through all the steps. +/// +/// This function allows setting a startup_delay_multiplier to tune the amount of time +/// the chips waits for the XOSC to stabilize. +/// The default value in the C SDK is 1, which should work on the Raspberry Pico, and many +/// third-party boards. +/// [`setup_xosc_blocking`], uses a conservative value of 64, which is the value commonly +/// used on slower-starting oscillators. +pub fn setup_xosc_blocking_custom_delay( + xosc_dev: XOSC, + frequency: HertzU32, + startup_delay_multiplier: u32, +) -> Result, Error> { + let initialized_xosc = + CrystalOscillator::new(xosc_dev).initialize(frequency, startup_delay_multiplier)?; + + let stable_xosc_token = nb::block!(initialized_xosc.await_stabilization()).unwrap(); + + Ok(initialized_xosc.get_stable(stable_xosc_token)) +} + +/// A Crystal Oscillator. +pub struct CrystalOscillator { + device: XOSC, + state: S, +} + +impl CrystalOscillator { + /// Transitions the oscillator to another state. + fn transition(self, state: To) -> CrystalOscillator { + CrystalOscillator { + device: self.device, + state, + } + } + + /// Releases the underlying device. + pub fn free(self) -> XOSC { + self.device + } +} + +impl CrystalOscillator { + /// Creates a new CrystalOscillator from the underlying device. + pub fn new(dev: XOSC) -> Self { + CrystalOscillator { + device: dev, + state: Disabled, + } + } + + /// Initializes the XOSC : frequency range is set, startup delay is calculated and set. + /// Set startup_delay_multiplier to a value > 1 when using a slow-starting oscillator. + pub fn initialize( + self, + frequency: HertzU32, + startup_delay_multiplier: u32, + ) -> Result, Error> { + const ALLOWED_FREQUENCY_RANGE: RangeInclusive = + HertzU32::MHz(1)..=HertzU32::MHz(15); + //1 ms = 10e-3 sec and Freq = 1/T where T is in seconds so 1ms converts to 1000Hz + const STABLE_DELAY_AS_HZ: HertzU32 = HertzU32::Hz(1000); + const DIVIDER: u32 = 256; + + if !ALLOWED_FREQUENCY_RANGE.contains(&frequency) { + return Err(Error::FrequencyOutOfRange); + } + + if startup_delay_multiplier == 0 { + return Err(Error::BadArgument); + } + + self.device.ctrl().write(|w| { + w.freq_range()._1_15mhz(); + w + }); + + //startup_delay = ((freq_hz * STABLE_DELAY) / 256) = ((freq_hz / delay_to_hz) / 256) + // = freq_hz / (delay_to_hz * 256) + //See Chapter 2, Section 16, §3) + //We do the calculation first. + let startup_delay = frequency.to_Hz() / (STABLE_DELAY_AS_HZ.to_Hz() * DIVIDER); + let startup_delay = startup_delay.saturating_mul(startup_delay_multiplier); + + //Then we check if it fits into an u16. + let startup_delay: u16 = startup_delay.try_into().map_err(|_| Error::BadArgument)?; + + self.device.startup().write(|w| unsafe { + w.delay().bits(startup_delay); + w + }); + + self.device.ctrl().write(|w| { + w.enable().enable(); + w + }); + + Ok(self.transition(Unstable { freq_hz: frequency })) + } +} + +/// A token that's given when the oscillator is stabilized, and can be exchanged to proceed to the next stage. +pub struct StableOscillatorToken { + _private: (), +} + +impl CrystalOscillator { + /// One has to wait for the startup delay before using the oscillator, ie awaiting stabilization of the XOSC + pub fn await_stabilization(&self) -> nb::Result { + if self.device.status().read().stable().bit_is_clear() { + return Err(WouldBlock); + } + + Ok(StableOscillatorToken { _private: () }) + } + + /// Returns the stabilized oscillator + pub fn get_stable(self, _token: StableOscillatorToken) -> CrystalOscillator { + let freq_hz = self.state.freq_hz; + self.transition(Stable { freq_hz }) + } +} + +impl CrystalOscillator { + /// Operating frequency of the XOSC in hertz + pub fn operating_frequency(&self) -> HertzU32 { + self.state.freq_hz + } + + /// Disables the XOSC + pub fn disable(self) -> CrystalOscillator { + self.device.ctrl().modify(|_r, w| { + w.enable().disable(); + w + }); + + self.transition(Disabled) + } + + /// Put the XOSC in DORMANT state. The method returns after the processor awakens. + /// + /// After waking up from the DORMANT state, XOSC needs to re-stabilise. + /// + /// # Safety + /// This method is marked unsafe because prior to switch the XOSC into DORMANT state, + /// PLLs must be stopped and IRQs have to be properly configured. + /// This method does not do any of that, it merely switches the XOSC to DORMANT state. + /// It should only be called if this oscillator is the clock source for the system clock. + /// See Chapter 2, Section 16, §5) for details. + pub unsafe fn dormant(self) -> CrystalOscillator { + //taken from the C SDK + const XOSC_DORMANT_VALUE: u32 = 0x636f6d61; + + self.device.dormant().write(|w| { + w.bits(XOSC_DORMANT_VALUE); + w + }); + + let freq_hz = self.state.freq_hz; + self.transition(Unstable { freq_hz }) + } +} diff --git a/rp-hal/rp235x-hal-examples/.cargo/config.toml b/rp-hal/rp235x-hal-examples/.cargo/config.toml new file mode 100644 index 0000000..25535aa --- /dev/null +++ b/rp-hal/rp235x-hal-examples/.cargo/config.toml @@ -0,0 +1,96 @@ +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +# Add aliases for building and running for the ARM and RISC-V targets. +[alias] + +# Build arm or riscv +build-arm = "build --target=thumbv8m.main-none-eabihf" +build-riscv = "build --target=riscv32imac-unknown-none-elf" + +# Run arm or riscv +run-arm = "run --target=thumbv8m.main-none-eabihf" +run-riscv = "run --target=riscv32imac-unknown-none-elf" + +# Add other custom aliases here, `rrr-blinky` which +# runs in release mode a riscv version of blinky. +rrr-blinky = "run-riscv --release --bin=blinky" + +[build] +# Set the default target to match the Cortex-M33 in the RP2350 +target = "thumbv8m.main-none-eabihf" + +# This is the hard-float ABI for Arm mode. +# +# The FPU is enabled by default, and float function arguments use FPU +# registers. +[target.thumbv8m.main-none-eabihf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as a linker script. +# This is usually provided by the cortex-m-rt crate, and by default the +# version in that crate will include a file called `memory.x` which describes +# the particular memory layout for your specific chip. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "picotool load -u -v -x -t elf" + +# This is the soft-float ABI for Arm mode. +# +# The FPU is disabled by default, and float function arguments use integer +# registers. Only useful for making the `float_test` example give really bad +# results on the `f32` benchmark. +[target.thumbv8m.main-none-eabi] +# Pass some extra options to rustc. See above for descriptions. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "picotool load -u -v -x -t elf" + +# This is the soft-float ABI for RISC-V mode. +# +# Hazard 3 does not have an FPU and so float function arguments use integer +# registers. +[target.riscv32imac-unknown-none-elf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Trp235x_riscv.x also tells the linker to use +# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific +# things that the riscv-rt crate's `link.x` requires and then includes +# `link.x` automatically. This is the reverse of how we do it on Cortex-M. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp235x_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "picotool load -u -v -x -t elf" diff --git a/rp-hal/rp235x-hal-examples/.gitignore b/rp-hal/rp235x-hal-examples/.gitignore new file mode 100644 index 0000000..ff47c2d --- /dev/null +++ b/rp-hal/rp235x-hal-examples/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/rp-hal/rp235x-hal-examples/Cargo.toml b/rp-hal/rp235x-hal-examples/Cargo.toml new file mode 100644 index 0000000..9f8a1de --- /dev/null +++ b/rp-hal/rp235x-hal-examples/Cargo.toml @@ -0,0 +1,44 @@ +[package] +authors = ["The rp-rs Developers"] +categories = ["embedded", "hardware-support", "no-std", "no-std::no-alloc"] +description = "Examples for the rp235x-hal crate" +edition = "2021" +homepage = "https://github.com/rp-rs/rp-hal" +keywords = ["embedded", "hal", "raspberry-pi", "rp235x", "rp2350", "embedded-hal"] +license = "MIT OR Apache-2.0" +name = "rp235x-hal-examples" +repository = "https://github.com/rp-rs/rp-hal" +rust-version = "1.79" +version = "0.1.0" + +[dependencies] +cortex-m = "0.7.2" +cortex-m-rt = "0.7" +cortex-m-rtic = "1.1.4" +critical-section = {version = "1.2.0"} +defmt = "0.3" +defmt-rtt = "0.4.0" +dht-sensor = "0.2.1" +embedded-alloc = "0.5.1" +embedded-hal = "1.0.0" +embedded-hal-async = "1.0.0" +embedded-io = "0.6.1" +embedded_hal_0_2 = {package = "embedded-hal", version = "0.2.5", features = ["unproven"]} +fugit = "0.3.6" +futures = {version = "0.3.30", default-features = false, features = ["async-await"]} +hd44780-driver = "0.4.0" +heapless = "0.8.0" +nb = "1.0" +panic-halt = "0.2.0" +pio = "0.2.0" +pio-proc = "0.2.0" +rp235x-hal = {path = "../rp235x-hal", version = "0.2.0", features = ["binary-info", "critical-section-impl", "rt", "defmt"]} +usb-device = "0.3.2" +usbd-serial = "0.2.2" +static_cell = "2.1.0" + +[target.'cfg( target_arch = "arm" )'.dependencies] +embassy-executor = {version = "0.5", features = ["arch-cortex-m", "executor-thread"]} + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +embassy-executor = {version = "0.5", features = ["arch-riscv32", "executor-thread"]} diff --git a/rp-hal/rp235x-hal-examples/LICENSE-APACHE b/rp-hal/rp235x-hal-examples/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp-hal/rp235x-hal-examples/LICENSE-MIT b/rp-hal/rp235x-hal-examples/LICENSE-MIT new file mode 100644 index 0000000..6e052e3 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp-hal/rp235x-hal-examples/NOTICE b/rp-hal/rp235x-hal-examples/NOTICE new file mode 100644 index 0000000..790ecb1 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp-hal/rp235x-hal-examples/README.md b/rp-hal/rp235x-hal-examples/README.md new file mode 100644 index 0000000..8cbef2b --- /dev/null +++ b/rp-hal/rp235x-hal-examples/README.md @@ -0,0 +1,315 @@ + +
+

rp-hal

+ +

+ Rust Examples for the Raspberry Silicon RP235x family Microcontrollers +
+ View the examples » +
+
+ Explore the API docs + · + Report a Bug + · + Chat on Matrix +

+

+ + + + +
+

Table of Contents

+
    +
  1. Introduction
  2. +
  3. Getting Started
  4. +
  5. Roadmap
  6. +
  7. Contributing
  8. +
  9. License
  10. +
  11. Contact
  12. +
  13. Acknowledgements
  14. +
+
+ + +## Introduction + +The `rp235x-hal` package is a library crate of high-level Rust drivers for the +Raspberry Silicon RP235x family of microcontrollers. This folder contains a +collection of non-board specific example programs for you to study. + +We also provide a series of [*Board Support Package* (BSP) crates][BSPs], which +take the HAL crate and pre-configure the pins according to a specific PCB +design. If you are using one of the supported boards, you should use one of +those crates in preference, and return here to see documentation about specific +peripherals on the RP235x and how to use them. See the `boards` folder in +https://github.com/rp-rs/rp-hal-boards/ for more details. + +[BSPs]: https://github.com/rp-rs/rp-hal-boards/ + + +## Getting Started + +To build the examples, first grab a copy of the source code: + +```console +$ git clone https://github.com/rp-rs/rp-hal.git +``` + +Then use `rustup` to grab the Rust Standard Library for the appropriate targets. +RP2350 has two possible targets: `thumbv8m.main-none-eabihf` for the Arm mode, and +`riscv32imac-unknown-none-elf` for the RISC-V mode. + +```console +$ rustup target add thumbv8m.main-none-eabihf +$ rustup target add riscv32imac-unknown-none-elf +``` + +**Note: all examples assume the current directory is `/rp235x-hal-examples`.** +``` +cd rp235x-hal-examples +``` + +The most basic method is to use `cargo build` with the `--bin` flag to specify the example you want to +build. For example, to build the `blinky` example: + +```console +$ cargo build --bin blinky + Compiling proc-macro2 v1.0.89 + Compiling unicode-ident v1.0.13 + Compiling syn v1.0.109 +... + Compiling pio-parser v0.2.2 + Compiling rp235x-hal v0.2.0 + Compiling pio-proc v0.2.2 + Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.97s +``` + +This builds the default target, which is Arm mode and the ELF file +is located at `./target/thumbv8m.main-none-eabihf/debug/blinky`: + +```console +$ file ./target/thumbv8m.main-none-eabihf/debug/blinky +./target/thumbv8m.main-none-eabihf/debug/blinky: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped +``` + +If you want to build a binary that runs in RISC-V mode, then you must specify the RISC-V target to override the default: + +```console +$ cargo build --target=riscv32imac-unknown-none-elf --bin blinky + Compiling nb v1.1.0 + Compiling byteorder v1.5.0 + Compiling stable_deref_trait v1.2.0 +.. + Compiling futures v0.3.31 + Compiling frunk v0.4.3 + Compiling rp235x-hal v0.2.0 + Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.23s``` +``` + +And we see that the RISC-V mode ELF file is now present at `./target/riscv32imac-unknown-none-elf/debug/blinky`: + +```console +$ file ./target/riscv32imac-unknown-none-elf/debug/blinky +./target/riscv32imac-unknown-none-elf/debug/blinky: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped +``` + +You can also specify the Arm mode target directly by using +`--target thumbv8m.main-none-eabihf` instead of `--target riscv32imac-unknown-none-elf`. + +To build, flash and start the application on the RP235x +you use `cargo run` with one of the following commands. Note: be sure the RP235x is in BOOTSEL mode before using the `run` command because we use `picotool` to flash and run the binary: + +```console +$ cargo run --target thumbv8m.main-none-eabihf --bin blinky +$ cargo run --target riscv32imac-unknown-none-elf --bin blinky +``` + +For the release profile build pass `--release` to the `cargo build` +or `cargo run` commands. This will build the example with optimizations enabled: + +```console +$ cargo run --target thumbv8m.main-none-eabihf --release --bin blinky +$ cargo run --target riscv32imac-unknown-none-elf --release --bin blinky +``` + +For the Arm mode target all of the examples are built if no `--bin` is specified: + +```console +$ cargo clean + Removed 1488 files, 398.6MiB total +$ cargo build --target thumbv8m.main-none-eabihf + Compiling proc-macro2 v1.0.89 + Compiling unicode-ident v1.0.13 +.. + Compiling rp235x-hal v0.2.0 + Compiling pio-proc v0.2.2 + Finished `dev` profile [unoptimized + debuginfo] target(s) in 16.08s +$ find target/thumbv8m.main-none-eabihf/debug/ -maxdepth 1 -type f -executable | sort +target/thumbv8m.main-none-eabihf/debug/adc +target/thumbv8m.main-none-eabihf/debug/adc_fifo_dma +.. +target/thumbv8m.main-none-eabihf/debug/blinky +.. +target/thumbv8m.main-none-eabihf/debug/vector_table +target/thumbv8m.main-none-eabihf/debug/watchdog +``` + +For the RISC-V mode it is currently possible to build only *some* of the examples. See +[`riscv_examples.txt`](./riscv_examples.txt) for a list of known working examples. +The missing ones probably rely on interrupts, or some other thing we +haven't ported to work in RISC-V mode yet. + +Here is an example using the `blinky` example in RISC-V mode. We'll build and +run it first using the default dev profile, emulating the development cycle **Note:** be sure +the RP235x is in BOOTSEL mode before using the `run` command because we use `picotool` to flash and run the binary: + +```console +$ cargo build --bin blinky --target=riscv32imac-unknown-none-elf + Compiling rp235x-hal-examples v0.1.0 + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.15s +$ cargo run --bin blinky --target=riscv32imac-unknown-none-elf + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s + Running `picotool load -u -v -x -t elf target/riscv32imac-unknown-none-elf/debug/blinky` +Family id 'rp2350-riscv' can be downloaded in absolute space: + 00000000->02000000 +Loading into Flash: [==============================] 100% +Verifying Flash: [==============================] 100% + OK + +The device was rebooted to start the application. +``` + +At this point the development version is running on the RP2350 and the LED is blinking. +When we look at `blinky` using `file` we see we have generated a RISC-V mode ELF file: + +```console +$ file ./target/riscv32imac-unknown-none-elf/debug/blinky +./target/riscv32imac-unknown-none-elf/debug/blinky: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped +``` + +Next we'll build and run it using the release profile for "final" testing, +again be sure the RP235x is in BOOTSEL mode: + +```console +$ cargo run --bin blinky --target=riscv32imac-unknown-none-elf --release + Compiling proc-macro2 v1.0.89 + Compiling unicode-ident v1.0.13 +.. + Compiling pio-parser v0.2.2 + Compiling pio-proc v0.2.2 + Finished `release` profile [optimized] target(s) in 17.05s + Running `picotool load -u -v -x -t elf target/riscv32imac-unknown-none-elf/release/blinky` +Family id 'rp2350-riscv' can be downloaded in absolute space: + 00000000->02000000 +Loading into Flash: [==============================] 100% +Verifying Flash: [==============================] 100% + OK + +The device was rebooted to start the application. +``` + +The LED should be blinking as we are now running this binary: + +```console +$ file ./target/riscv32imac-unknown-none-elf/release/blinky +./target/riscv32imac-unknown-none-elf/release/blinky: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), statically linked, not stripped +``` + +The above commands work well, but the commands are somewhat verbose. +To make building and running commands more succinct an `[alias]` section +has been added to [.cargo/config.toml](../.cargo/config.toml) that define: +| Command Alias | Description | +|---|---| +| build-arm | build for ARM | +| build-riscv | build for RISC-V | +| run-arm | run on ARM | +| run-riscv | run on RISC-V | +| rrr-blinky | run release on RISC-V blinky | + +When using these aliases your build and run commands are much shorter. +Below we see the development cycle using `build-riscv` and `run-riscv`: + +```console +$ cargo build-riscv --bin blinky + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s +$ cargo run-riscv --bin blinky + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s + Running `picotool load -u -v -x -t elf target/riscv32imac-unknown-none-elf/debug/blinky` +Family id 'rp2350-riscv' can be downloaded in absolute space: + 00000000->02000000 +Loading into Flash: [==============================] 100% +Verifying Flash: [==============================] 100% + OK + +The device was rebooted to start the application. +``` + +And for the `run` command in `--release` profile and a RISC-V mode we added the `rrr-blinky` alias +as an example of customization. You might want to add others as you see fit: + +```console +$ cargo rrr-blinky + Finished `release` profile [optimized] target(s) in 0.05s + Running `picotool load -u -v -x -t elf target/riscv32imac-unknown-none-elf/release/blinky` +Family id 'rp2350-riscv' can be downloaded in absolute space: + 00000000->02000000 +Loading into Flash: [==============================] 100% +Verifying Flash: [==============================] 100% + OK + +The device was rebooted to start the application. +``` + + +## Roadmap + +NOTE The HAL is under active development, and so are these examples. As such, it +is likely to remain volatile until a 1.0.0 release. + +See the [open issues](https://github.com/rp-rs/rp-hal/issues) for a list of +proposed features (and known issues). + + +## Contributing + +Contributions are what make the open source community such an amazing place to +be learn, inspire, and create. Any contributions you make are **greatly +appreciated**. + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + + + +## License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + + +## Contact + +* Project Link: [https://github.com/rp-rs/rp-hal/issues](https://github.com/rp-rs/rp-hal/issues) +* Matrix: [#rp-rs:matrix.org](https://matrix.to/#/#rp-rs:matrix.org) + + +## Acknowledgements + +* [Othneil Drew's README template](https://github.com/othneildrew) diff --git a/rp-hal/rp235x-hal-examples/build.rs b/rp-hal/rp235x-hal-examples/build.rs new file mode 100644 index 0000000..5cc06c0 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/build.rs @@ -0,0 +1,27 @@ +//! Set up linker scripts for the rp235x-hal examples + +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put the linker script somewhere the linker can find it + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out.display()); + + // The file `memory.x` is loaded by cortex-m-rt's `link.x` script, which + // is what we specify in `.cargo/config.toml` for Arm builds + let memory_x = include_bytes!("memory.x"); + let mut f = File::create(out.join("memory.x")).unwrap(); + f.write_all(memory_x).unwrap(); + println!("cargo:rerun-if-changed=memory.x"); + + // The file `rp235x_riscv.x` is what we specify in `.cargo/config.toml` for + // RISC-V builds + let rp235x_riscv_x = include_bytes!("rp235x_riscv.x"); + let mut f = File::create(out.join("rp235x_riscv.x")).unwrap(); + f.write_all(rp235x_riscv_x).unwrap(); + println!("cargo:rerun-if-changed=rp235x_riscv.x"); + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/rp-hal/rp235x-hal-examples/memory.x b/rp-hal/rp235x-hal-examples/memory.x new file mode 100644 index 0000000..7d409ba --- /dev/null +++ b/rp-hal/rp235x-hal-examples/memory.x @@ -0,0 +1,77 @@ +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.start_block) + SIZEOF(.start_block); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; + +SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + +} INSERT AFTER .uninit; + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + diff --git a/rp-hal/rp235x-hal-examples/riscv_examples.txt b/rp-hal/rp235x-hal-examples/riscv_examples.txt new file mode 100644 index 0000000..0800802 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/riscv_examples.txt @@ -0,0 +1,30 @@ +adc +adc_fifo_dma +adc_fifo_poll +alloc +arch_flip +binary_info_demo +blinky +block_loop +dht11 +gpio_dyn_pin_array +gpio_in_out +i2c +lcd_display +mem_to_mem_dma +pio_blink +pio_dma +pio_proc_blink +pio_side_set +pio_synchronized +pwm_blink +pwm_blink_embedded_hal_1 +rom_funcs +rosc_as_system_clock +spi +spi_dma +uart +uart_dma +uart_loopback +usb +watchdog diff --git a/rp-hal/rp235x-hal-examples/rp235x_riscv.x b/rp-hal/rp235x-hal-examples/rp235x_riscv.x new file mode 100644 index 0000000..b97a0ad --- /dev/null +++ b/rp-hal/rp235x-hal-examples/rp235x_riscv.x @@ -0,0 +1,253 @@ +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `32`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > FLASH + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + __start_block_addr = .; + KEEP(*(.start_block)); + KEEP(*(.boot_info)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + . = ALIGN(4); + } > FLASH + + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > FLASH + + .data : ALIGN(32) + { + _sidata = LOADADDR(.data); + __sidata = LOADADDR(.data); + _sdata = .; + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(32); + _edata = .; + __edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : ALIGN(32) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(32); + _ebss = .; + } > RAM + + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 32 == 0, " +ERROR(riscv-rt): the start of the RAM must be 32-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, " +BUG(riscv-rt): .data is not 32-byte aligned"); + +ASSERT(_sidata % 32 == 0, " +BUG(riscv-rt): the LMA of .data is not 32-byte aligned"); + +ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, " +BUG(riscv-rt): .bss is not 32-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(riscv-rt): The .text section must be placed inside the FLASH region. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ + diff --git a/rp-hal/rp235x-hal-examples/src/bin/adc.rs b/rp-hal/rp235x-hal-examples/src/bin/adc.rs new file mode 100644 index 0000000..76aad66 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/adc.rs @@ -0,0 +1,131 @@ +//! # ADC Example +//! +//! This application demonstrates how to read ADC samples from the temperature +//! sensor and pin and output them to the UART on pins 1 and 2 at 115200 baud. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use core::fmt::Write; +// Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +use embedded_hal::delay::DelayNs; +use embedded_hal_0_2::adc::OneShot; +use hal::fugit::RateExtU32; +use hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then prints the temperature +/// in an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Write to the UART + uart.write_full_blocking(b"ADC example\r\n"); + + // Enable ADC + let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS); + + // Enable the temperature sense channel + let mut temperature_sensor = adc.take_temp_sensor().unwrap(); + + // Configure GPIO26 as an ADC input + let mut adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26).unwrap(); + loop { + // Read the raw ADC counts from the temperature sensor channel. + let temp_sens_adc_counts: u16 = adc.read(&mut temperature_sensor).unwrap(); + let pin_adc_counts: u16 = adc.read(&mut adc_pin_0).unwrap(); + writeln!( + uart, + "ADC readings: Temperature: {temp_sens_adc_counts:02} Pin: {pin_adc_counts:02}\r\n" + ) + .unwrap(); + delay.delay_ms(1000); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"ADC Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/adc_fifo_dma.rs b/rp-hal/rp235x-hal-examples/src/bin/adc_fifo_dma.rs new file mode 100644 index 0000000..74f1e0e --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/adc_fifo_dma.rs @@ -0,0 +1,193 @@ +//! # ADC FIFO DMA Example +//! +//! This application demonstrates how to read ADC samples in free-running mode, +//! and reading them from the FIFO by using a DMA transfer. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use hal::singleton; + +// Some things we need +use core::fmt::Write; +use embedded_hal::delay::DelayNs; +use fugit::RateExtU32; +use hal::dma::{single_buffer, DMAExt}; +use hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then prints the temperature +/// in an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer1(pac.TIMER1, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Write to the UART + uart.write_full_blocking(b"ADC FIFO DMA example\r\n"); + + // Initialize DMA + let dma = pac.DMA.split(&mut pac.RESETS); + + // Enable ADC + let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS); + + // Enable the temperature sense channel + let mut temperature_sensor = adc.take_temp_sensor().unwrap(); + + // Configure GPIO26 as an ADC input + let adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); + + // we'll capture 1000 samples in total (500 per channel) + // NOTE: when calling `shift_8bit` below, the type here must be changed from `u16` to `u8` + let buf_for_samples = singleton!(: [u16; 1000] = [0; 1000]).unwrap(); + + // Configure free-running mode: + let mut adc_fifo = adc + .build_fifo() + // Set clock divider to target a sample rate of 1000 samples per second (1ksps). + // The value was calculated by `(48MHz / 1ksps) - 1 = 47999.0`. + // Please check the `clock_divider` method documentation for details. + .clock_divider(47999, 0) + // sample the temperature sensor first + .set_channel(&mut temperature_sensor) + // then alternate between GPIO26 and the temperature sensor + .round_robin((&adc_pin_0, &temperature_sensor)) + // Uncomment this line to produce 8-bit samples, instead of 12 bit (lower bits are discarded) + //.shift_8bit() + // Enable DMA transfers for the FIFO + .enable_dma() + // Create the FIFO, but don't start it just yet + .start_paused(); + + // Start a DMA transfer (must happen before resuming the ADC FIFO) + let dma_transfer = + single_buffer::Config::new(dma.ch0, adc_fifo.dma_read_target(), buf_for_samples).start(); + + // Resume the FIFO to start capturing + adc_fifo.resume(); + + // initialize a timer, to measure the total sampling time (printed below) + let timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // NOTE: in a real-world program, instead of calling `wait` now, you would probably: + // 1. Enable one of the DMA interrupts for the channel (e.g. `dma.ch0.enable_irq0()`) + // 2. Set up a handler for the respective `DMA_IRQ_*` interrupt + // 3. Call `wait` only within that interrupt, which will be fired once the transfer is complete. + + // the DMA unit takes care of shuffling data from the FIFO into the buffer. + // We just sit here and wait... 😴 + let (_ch, _adc_read_target, buf_for_samples) = dma_transfer.wait(); + + // ^^^ the three results here (channel, adc::DmaReadTarget, write target) can be reused + // right away to start another transfer. + + let time_taken = timer.get_counter(); + + uart.write_full_blocking(b"Done sampling, printing results:\r\n"); + + // Stop free-running mode (the returned `adc` can be reused for future captures) + let _adc = adc_fifo.stop(); + + // Print the measured values + for i in 0..500 { + writeln!( + uart, + "Temp:\t{}\tPin\t{}\r", + buf_for_samples[i * 2], + buf_for_samples[i * 2 + 1] + ) + .unwrap(); + } + + writeln!(uart, "Sampling took: {}\r", time_taken).unwrap(); + + loop { + delay.delay_ms(1000); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"ADC FIFO DMA Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/adc_fifo_irq.rs b/rp-hal/rp235x-hal-examples/src/bin/adc_fifo_irq.rs new file mode 100644 index 0000000..5553a9c --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/adc_fifo_irq.rs @@ -0,0 +1,199 @@ +//! # ADC FIFO Interrupt Example +//! +//! This application demonstrates how to read ADC samples in free-running mode, +//! using the FIFO interrupt. +//! +//! It utilizes `rtic` (cortex-m-rtic crate) to safely share peripheral access between +//! initialization code and interrupt handlers. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +#[rtic::app(device = hal::pac)] +mod app { + use core::fmt::Write; + use fugit::RateExtU32; + use hal::Clock; + use rp235x_hal as hal; + + /// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. + /// Adjust if your board has a different frequency + const XTAL_FREQ_HZ: u32 = 12_000_000u32; + + // This example will capture 1000 samples to `shared.buf`. + // When it is done, it will stop the ADC, set `shared.done` to true, + // print the result and loop forever. + + const SAMPLE_COUNT: usize = 1000; + + type Uart = hal::uart::UartPeripheral< + hal::uart::Enabled, + hal::pac::UART0, + ( + hal::gpio::Pin, + hal::gpio::Pin, + ), + >; + + #[shared] + struct Shared { + done: bool, + buf: [u16; SAMPLE_COUNT], + uart: Uart, + } + + #[local] + struct Local { + adc_fifo: Option>, + } + + #[init(local = [adc: Option = None])] + fn init(c: init::Context) -> (Shared, Local, init::Monotonics) { + // Soft-reset does not release the hardware spinlocks + // Release them now to avoid a deadlock after debug or watchdog reset + unsafe { + hal::sio::spinlock_reset(); + } + + let mut resets = c.device.RESETS; + let mut watchdog = hal::Watchdog::new(c.device.WATCHDOG); + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + c.device.XOSC, + c.device.CLOCKS, + c.device.PLL_SYS, + c.device.PLL_USB, + &mut resets, + &mut watchdog, + ) + .unwrap(); + let sio = hal::Sio::new(c.device.SIO); + let pins = hal::gpio::Pins::new( + c.device.IO_BANK0, + c.device.PADS_BANK0, + sio.gpio_bank0, + &mut resets, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let uart = hal::uart::UartPeripheral::new(c.device.UART0, uart_pins, &mut resets) + .enable( + hal::uart::UartConfig::new( + 115200.Hz(), + hal::uart::DataBits::Eight, + None, + hal::uart::StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // the ADC is put into a local, to gain static lifetime + *c.local.adc = Some(hal::Adc::new(c.device.ADC, &mut resets)); + let adc = c.local.adc.as_mut().unwrap(); + + let mut adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); + + uart.write_full_blocking(b"ADC FIFO interrupt example\r\n"); + + let adc_fifo = adc + .build_fifo() + // Set clock divider to target a sample rate of 1000 samples per second (1ksps). + // The value was calculated by `(48MHz / 1ksps) - 1 = 47999.0`. + // Please check the `clock_divider` method documentation for details. + .clock_divider(47999, 0) + .set_channel(&mut adc_pin_0) + .enable_interrupt(1) + .start(); + + ( + Shared { + done: false, + buf: [0; SAMPLE_COUNT], + uart, + }, + Local { + adc_fifo: Some(adc_fifo), + }, + init::Monotonics(), + ) + } + + #[idle(shared = [done, buf, uart])] + fn idle(mut c: idle::Context) -> ! { + loop { + let finished = (&mut c.shared.done, &mut c.shared.buf, &mut c.shared.uart).lock( + |done, buf, uart| { + if *done { + for sample in buf { + writeln!(uart, "Sample: {}\r", sample).unwrap(); + } + writeln!(uart, "All done, going to sleep 😴\r").unwrap(); + true + } else { + false + } + }, + ); + + if finished { + break; + } + } + + #[allow(clippy::empty_loop)] + loop {} + } + + #[task( + binds = ADC_IRQ_FIFO, + priority = 1, + shared = [done, buf], + local = [adc_fifo, counter: usize = 0] + )] + fn adc_irq_fifo(mut c: adc_irq_fifo::Context) { + let sample = c.local.adc_fifo.as_mut().unwrap().read(); + let i = *c.local.counter; + c.shared.buf.lock(|buf| buf[i] = sample); + *c.local.counter += 1; + + if *c.local.counter == SAMPLE_COUNT { + c.local.adc_fifo.take().unwrap().stop(); + c.shared.done.lock(|done| *done = true); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"ADC FIFO IRQ Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/adc_fifo_poll.rs b/rp-hal/rp235x-hal-examples/src/bin/adc_fifo_poll.rs new file mode 100644 index 0000000..ad4ec5c --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/adc_fifo_poll.rs @@ -0,0 +1,198 @@ +//! # ADC FIFO Example +//! +//! This application demonstrates how to read ADC samples in free-running mode, +//! and reading them from the FIFO by polling the fifo's `len()`. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use core::fmt::Write; +use embedded_hal::delay::DelayNs; +use fugit::RateExtU32; +use hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then prints the temperature +/// in an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Write to the UART + uart.write_full_blocking(b"ADC FIFO poll example\r\n"); + + // Enable ADC + let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS); + + // Enable the temperature sense channel + let mut temperature_sensor = adc.take_temp_sensor().unwrap(); + + // Configure GPIO26 as an ADC input + let adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); + + // Configure free-running mode: + let mut adc_fifo = adc + .build_fifo() + // Set clock divider to target a sample rate of 1000 samples per second (1ksps). + // The value was calculated by `(48MHz / 1ksps) - 1 = 47999.0`. + // Please check the `clock_divider` method documentation for details. + .clock_divider(47999, 0) + // sample the temperature sensor first + .set_channel(&mut temperature_sensor) + // then alternate between GPIO26 and the temperature sensor + .round_robin((&adc_pin_0, &temperature_sensor)) + // Uncomment this line to produce 8-bit samples, instead of 12 bit (lower bits are discarded) + //.shift_8bit() + // start sampling + .start(); + + // we'll capture 1000 samples in total (500 per channel) + let mut temp_samples = [0; 500]; + let mut pin_samples = [0; 500]; + let mut i = 0; + + // initialize a timer, to measure the total sampling time (printed below) + let timer = hal::Timer::new_timer1(pac.TIMER1, &mut pac.RESETS, &clocks); + + loop { + // busy-wait until the FIFO contains at least two samples: + while adc_fifo.len() < 2 {} + + // fetch two values from the fifo + let temp_result = adc_fifo.read(); + let pin_result = adc_fifo.read(); + + // uncomment this line, to trigger an "underrun" condition + //let _extra_sample = adc_fifo.read(); + + if adc_fifo.is_over() { + // samples were pushed into the fifo faster they were read + uart.write_full_blocking(b"FIFO overrun!\r\n"); + } + if adc_fifo.is_under() { + // we tried to read samples more quickly than they were pushed into the fifo + uart.write_full_blocking(b"FIFO underrun!\r\n"); + } + + temp_samples[i] = temp_result; + pin_samples[i] = pin_result; + + i += 1; + + // uncomment this line to trigger an "overrun" condition + //delay.delay_ms(1000); + + if i == 500 { + break; + } + } + + let time_taken = timer.get_counter(); + + uart.write_full_blocking(b"Done sampling, printing results:\r\n"); + + // Stop free-running mode (the returned `adc` can be reused for future captures) + let _adc = adc_fifo.stop(); + + // Print the measured values + for i in 0..500 { + writeln!( + uart, + "Temp:\t{}\tPin\t{}\r", + temp_samples[i], pin_samples[i] + ) + .unwrap(); + } + + writeln!(uart, "Sampling took: {}\r", time_taken).unwrap(); + + loop { + delay.delay_ms(1000); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"ADC FIFO Poll Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/alloc.rs b/rp-hal/rp235x-hal-examples/src/bin/alloc.rs new file mode 100644 index 0000000..3dd6000 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/alloc.rs @@ -0,0 +1,124 @@ +//! # Alloc Example +//! +//! Uses alloc to create a Vec. +//! +//! This will blink an LED attached to GP25, which is the pin the Pico uses for +//! the on-board LED. It may need to be adapted to your particular board layout +//! and/or pin assignment. +//! +//! While blinking the LED, it will continuously push to a `Vec`, which will +//! eventually lead to a panic due to an out of memory condition. +//! +//! See the `Cargo.toml` file for Copyright and licence details. + +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::vec::Vec; +use embedded_alloc::Heap; + +#[global_allocator] +static ALLOCATOR: Heap = Heap::empty(); + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2350 peripherals, then blinks the LED in an +/// infinite loop where the duration indicates how many items were allocated. +#[hal::entry] +fn main() -> ! { + { + use core::mem::MaybeUninit; + const HEAP_SIZE: usize = 1024; + static mut HEAP: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { ALLOCATOR.init(core::ptr::addr_of_mut!(HEAP) as usize, HEAP_SIZE) } + } + + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + + let mut xs = Vec::new(); + xs.push(1); + + // Blink the LED at 1 Hz + loop { + led_pin.set_high().unwrap(); + let len = xs.len() as u32; + timer.delay_ms(100 * len); + xs.push(1); + led_pin.set_low().unwrap(); + timer.delay_ms(100 * len); + xs.push(1); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Memory Allocation Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/arch_flip.rs b/rp-hal/rp235x-hal-examples/src/bin/arch_flip.rs new file mode 100644 index 0000000..f79c6be --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/arch_flip.rs @@ -0,0 +1,110 @@ +//! # Architecture Flip example +//! +//! This application demonstrates running both Arm and RISC-V code on the same +//! chip, using partitions. +//! +//! You need a partition table with one partition for Arm (marked NO-BOOT for +//! RISC-V) and one partition for RISC-V (marked NO-BOOT for Arm). +//! +//! It will run for a few seconds, and then reboot into the other. You can tell +//! the difference because they have different blink rates. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + #[cfg(target_arch = "arm")] + let (delay_ms, arch) = (500, hal::reboot::RebootArch::Riscv); + + #[cfg(not(target_arch = "arm"))] + let (delay_ms, arch) = (250, hal::reboot::RebootArch::Arm); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + for _ in 0..10 { + led_pin.set_high().unwrap(); + timer.delay_ms(delay_ms); + led_pin.set_low().unwrap(); + timer.delay_ms(delay_ms); + } + + // Do an asynchronous reset into the bootloader, but flipping the architecture + hal::reboot::reboot(hal::reboot::RebootKind::Normal, arch); +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Architecture Flip Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/binary_info_demo.rs b/rp-hal/rp235x-hal-examples/src/bin/binary_info_demo.rs new file mode 100644 index 0000000..a0cb558 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/binary_info_demo.rs @@ -0,0 +1,102 @@ +//! # GPIO 'Blinky' Example, with Binary Info +//! +//! This application demonstrates how to control a GPIO pin on the RP2350, and +//! includes some picotool-compatible metadata. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use hal::binary_info; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + loop { + led_pin.set_high().unwrap(); + timer.delay_ms(500); + led_pin.set_low().unwrap(); + timer.delay_ms(500); + } +} + +/// This is a list of references to our table entries +/// +/// They must be in the `.bi_entries` section as we tell picotool the start and +/// end addresses of that section. +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 7] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"A GPIO blinky with extra metadata."), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), + hal::binary_info::rp_pico_board!(c"pico2"), + // An example with a non-Raspberry-Pi tag + hal::binary_info::int!(binary_info::make_tag(b"JP"), 0x0000_0001, 0x12345678), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/blinky.rs b/rp-hal/rp235x-hal-examples/src/bin/blinky.rs new file mode 100644 index 0000000..69472b9 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/blinky.rs @@ -0,0 +1,93 @@ +//! # GPIO 'Blinky' Example +//! +//! This application demonstrates how to control a GPIO pin on the rp235x. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + loop { + led_pin.set_high().unwrap(); + timer.delay_ms(500); + led_pin.set_low().unwrap(); + timer.delay_ms(500); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Blinky Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/block_loop.rs b/rp-hal/rp235x-hal-examples/src/bin/block_loop.rs new file mode 100644 index 0000000..cfd48a2 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/block_loop.rs @@ -0,0 +1,120 @@ +//! # An example application with two Blocks. +//! +//! Our first block is an *Image Definition* Block and it points at our second +//! Block which contains a placeholder item for the purposes of this example. +//! The placeholder isn't useful per se, but it allows us to show how to +//! construct two Blocks in a loop without having to use `picotool` to modify +//! the application after compilation and linking. +//! +//! See [Section +//! 5.1.5.2](https://rptl.io/rp2350-datasheet#bootrom-concept-block-loop) in the +//! RP2350 datasheet for more information about Blocks and Block Loops. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +// The linker script exports these symbols. +extern "C" { + /// This value is at an address equal to the difference between the start block and end block + static start_to_end: u32; + /// This value is at an address equal to the difference between the end block and start block + static end_to_start: u32; +} + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +#[allow(unused_unsafe)] // addr_of! is safe since rust 1.82.0 +pub static START_IMAGE_DEF: hal::block::ImageDef = + hal::block::ImageDef::secure_exe().with_offset(unsafe { core::ptr::addr_of!(start_to_end) }); + +/// A second Block, and the end of the program in flash +#[link_section = ".end_block"] +#[used] +#[allow(unused_unsafe)] // addr_of! is safe since rust 1.82.0 +pub static END_IMAGE_DEF: hal::block::Block<1> = + // Put a placeholder item in the block. + hal::block::Block::new([hal::block::item_ignored()]) + .with_offset(unsafe { core::ptr::addr_of!(end_to_start) }); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + loop { + led_pin.set_high().unwrap(); + timer.delay_ms(250); + led_pin.set_low().unwrap(); + timer.delay_ms(250); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!( + c"Blinks an LED, contains a Block Loop with two Blocks" + ), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/dht11.rs b/rp-hal/rp235x-hal-examples/src/bin/dht11.rs new file mode 100644 index 0000000..0a03387 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/dht11.rs @@ -0,0 +1,102 @@ +//! # DHT11 Example +//! +//! This application demonstrates how to read a DHT11 sensor on the rp235x. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! In this example, the DHT11 data pin should be connected to GPIO28. +//! +//! NOTE: The DHT11 driver only works reliably when compiled in release mode. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +use dht_sensor::{dht11, DhtReading}; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, assigns GPIO 28 to the +/// DHT11 driver, and takes a single measurement. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Use GPIO 28 as an InOutPin + let mut pin = hal::gpio::InOutPin::new(pins.gpio28); + let _ = pin.set_high(); + + // Perform a sensor reading + let _measurement = dht11::Reading::read(&mut delay, &mut pin); + + // In this case, we just ignore the result. A real application + // would do something with the measurement. + + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"DHT11 Sensor Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/float_test.rs b/rp-hal/rp235x-hal-examples/src/bin/float_test.rs new file mode 100644 index 0000000..3cf6547 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/float_test.rs @@ -0,0 +1,324 @@ +//! # DCP Example +//! +//! This example only works on Arm. +//! +//! This application demonstrates performing floating point operations +//! using the *Double Co-Processor*. Note that by default, DCP acceleration +//! is enabled. To see how slow software floating point operations are, +//! build this example with `--no-default-features`. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! Typical output (with default features): +//! +//! ```text +//! Floating Point Test +//! Testing software f64 addition +//! Running 1000 loops for f64 +//! Start: acc=0, arg=0.048638031340549406 +//! End : acc=48.63803134055, took 23.8 cycles per op +//! Testing DCP f64 addition +//! Running 1000 loops for f64 +//! Start: acc=0, arg=0.048638031340549406 +//! End : acc=48.63803134055, took 37.9 cycles per op +//! Testing FPU f32 addition +//! Running 1000 loops for f32 +//! Start: acc=0, arg=0.04863803 +//! End : acc=48.63764, took 3.5 cycles per op +//! Testing software f64 multiplication +//! Running 1000 loops for f64 +//! Start: acc=0.048638031340549406, arg=1.0001 +//! End : acc=0.053753069001926, took 40.8 cycles per op +//! Testing DCP f64 multiplication +//! Running 1000 loops for f64 +//! Start: acc=0.048638031340549406, arg=1.0001 +//! End : acc=0.053753069001926, took 54.7 cycles per op +//! Testing FPU f32 multiplication +//! Running 1000 loops for f32 +//! Start: acc=0.04863803, arg=1.0001 +//! End : acc=0.05375396, took 3.4 cycles per op +//! Rebooting now +//! PANIC: +//! PanicInfo { payload: Any { .. }, message: Some(Finished!), location: Location { file: "rp235x-hal/examples/float_test.rs", line: 166, col: 5 }, can_unwind: true, force_no_backtrace: false } +//! ``` +//! +//! Typical output (with no default features): +//! +//! ```text +//! Floating Point Test +//! Testing software f64 addition +//! Running 1000 loops for f64 +//! Start: acc=0, arg=0.8188217810460334 +//! End : acc=818.8217810460395, took 151.3 cycles per op +//! Testing DCP f64 addition +//! Running 1000 loops for f64 +//! Start: acc=0, arg=0.8188217810460334 +//! End : acc=818.8217810460395, took 38.0 cycles per op +//! Testing FPU f32 addition +//! Running 1000 loops for f32 +//! Start: acc=0, arg=0.8188218 +//! End : acc=818.82947, took 3.4 cycles per op +//! Testing software f64 multiplication +//! Running 1000 loops for f64 +//! Start: acc=0.8188217810460334, arg=1.0001 +//! End : acc=0.9049334951218081, took 123.0 cycles per op +//! Testing DCP f64 multiplication +//! Running 1000 loops for f64 +//! Start: acc=0.8188217810460334, arg=1.0001 +//! End : acc=0.9049334951218081, took 55.0 cycles per op +//! Testing FPU f32 multiplication +//! Running 1000 loops for f32 +//! Start: acc=0.8188218, arg=1.0001 +//! End : acc=0.9049483, took 3.4 cycles per op +//! Rebooting now +//! PANIC: +//! PanicInfo { payload: Any { .. }, message: Some(Finished!), location: Location { file: "rp235x-hal/examples/float_test.rs", line: 166, col: 5 }, can_unwind: true, force_no_backtrace: false } +//! ``` +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::{ + gpio, + uart::{DataBits, StopBits, UartConfig, UartPeripheral}, +}; + +use cortex_m_rt::exception; + +// Some things we need +use core::fmt::Write; +use hal::fugit::RateExtU32; +use hal::Clock; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +struct GlobalUart { + inner: critical_section::Mutex>>, +} + +type MyUart = UartPeripheral< + hal::uart::Enabled, + hal::pac::UART0, + ( + gpio::Pin, + gpio::Pin, + ), +>; + +static GLOBAL_UART: GlobalUart = GlobalUart { + inner: critical_section::Mutex::new(core::cell::RefCell::new(None)), +}; + +impl core::fmt::Write for &GlobalUart { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + critical_section::with(|cs| { + let mut cell = self.inner.borrow_ref_mut(cs); + let uart = cell.as_mut().unwrap(); + uart.write_str(s) + }) + } +} + +impl GlobalUart { + fn init(&self, uart: MyUart) { + critical_section::with(|cs| { + self.inner.borrow(cs).replace(Some(uart)); + }); + } +} + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + let mut cp = cortex_m::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from rp235x) on pin 1 (GPIO0) + pins.gpio0.into_function::(), + // UART RX (characters received by rp235x) on pin 2 (GPIO1) + pins.gpio1.into_function::(), + ); + let uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + GLOBAL_UART.init(uart); + + writeln!(&GLOBAL_UART, "\n\nFloating Point Test").unwrap(); + + // Optionally start a DCP operation, but not finish it, to see if the save/restore fires. + // + // If you uncomment this, you'll observe the DCP operations take ~46 cycles longer + // to do all the stacking/restoring of DCP state. + + // unsafe { + // let val0 = 0; + // let val1 = 0; + // core::arch::asm!( + // "mcrr p4,#1,{0},{1},c0 // WXUP {0},{1} - write {1}|{0} to xm and xe", + // in(reg) val0, + // in(reg) val1 + // ); + // } + + cp.DCB.enable_trace(); + cp.DWT.enable_cycle_counter(); + + let int_seed = get_random_seed(); + // make up a random number between 0 and 1 so the optimiser can't cheat and + // we are forced to do the maths + let seed = (int_seed & u128::from(u64::MAX)) as f64 / u64::MAX as f64; + + writeln!(&GLOBAL_UART, "Testing aeabi f64 addition").unwrap(); + benchmark(0.0, seed, |x, y| x + y, &cp.DWT); + writeln!(&GLOBAL_UART, "Testing DCP f64 addition").unwrap(); + benchmark(0.0, seed, hal::dcp::dadd, &cp.DWT); + writeln!(&GLOBAL_UART, "Testing aeabi f32 addition").unwrap(); + benchmark(0.0, seed as f32, |x, y| x + y, &cp.DWT); + + writeln!(&GLOBAL_UART, "Testing aeabi f64 multiplication").unwrap(); + benchmark(seed, 1.0001f64, |x, y| x * y, &cp.DWT); + writeln!(&GLOBAL_UART, "Testing DCP f64 multiplication").unwrap(); + benchmark(seed, 1.0001f64, hal::dcp::dmul, &cp.DWT); + writeln!(&GLOBAL_UART, "Testing aeabi f32 multiplication").unwrap(); + benchmark(seed as f32, 1.0001f32, |x, y| x * y, &cp.DWT); + + writeln!(&GLOBAL_UART, "Rebooting now").unwrap(); + + panic!("Finished!"); +} + +/// Use the SysInfo API to get a boot-time generated random number. +fn get_random_seed() -> u128 { + let mut buffer = [0u32; 5]; + let sys_info_mask = 0x0010; + unsafe { hal::rom_data::get_sys_info(buffer.as_mut_ptr(), buffer.len(), sys_info_mask) }; + let mut result: u128 = 0; + for word in &buffer[1..] { + result <<= 32; + result |= u128::from(*word); + } + result +} + +/// Run the given operation in a loop. +/// +/// Uses the DWT to calculate elapsed CPU cycles. +fn benchmark(acc: T, arg: T, mut f: F, dwt: &cortex_m::peripheral::DWT) +where + T: core::fmt::Display + core::marker::Copy, + F: FnMut(T, T) -> T, +{ + const LOOPS: u16 = 1000; + writeln!( + &GLOBAL_UART, + "Running {} loops for {}", + LOOPS, + core::any::type_name::() + ) + .unwrap(); + let mut acc: T = acc; + writeln!(&GLOBAL_UART, "Start: acc={}, arg={}", acc, arg).unwrap(); + + let start_count = dwt.cyccnt.read(); + for _ in 0..LOOPS { + acc = f(acc, arg); + } + let end_count = dwt.cyccnt.read(); + + let delta = end_count.wrapping_sub(start_count); + let cycles_per_op = delta as f32 / LOOPS as f32; + writeln!( + &GLOBAL_UART, + "End : acc={acc}, took {cycles_per_op:.1} cycles per op" + ) + .unwrap(); +} + +#[exception] +unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { + let _ = writeln!(&GLOBAL_UART, "HARD FAULT:\n{:#?}", ef); + + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(&GLOBAL_UART, "PANIC:\n{:?}", info); + + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Floating Point Test"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/gpio_dyn_pin_array.rs b/rp-hal/rp235x-hal-examples/src/bin/gpio_dyn_pin_array.rs new file mode 100644 index 0000000..0ca06f8 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/gpio_dyn_pin_array.rs @@ -0,0 +1,138 @@ +//! # GPIO Dynamic Pin Type Mode Example +//! +//! This application demonstrates how to put GPIO pins into their Dynamic Pin type on the rp235x. +//! +//! Usually, the type of each pin is different (which allows the type system to catch misuse). +//! But this stops you storing the pins in an array, or allowing a struct to take any pin. +//! This mode is also referred to as "Erased", "Downgraded", "Degraded", or "Dynamic". +//! +//! In order to see the result of this program, you will need to put LEDs and a current limiting +//! resistor on each of GPIO 2, 3, 4, 5. +//! The other side of the LED + resistor pair should be connected to GND. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use hal::gpio::{DynPinId, FunctionSioOutput, Pin, PullNone, PullUp}; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // We will use the rp235x timer peripheral as our delay source + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // To put pins into an array we have to convert them to Dynamically Typed pins. + // This means they'll carry their pin and bank numbers around with them at run time, + // rather than relying on the Type of the pin to track that. + let mut pinarray: [Pin; 4] = [ + pins.gpio2 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + pins.gpio3 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + pins.gpio4 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + pins.gpio5 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + ]; + + // Also set a pin as a dynamic input. We won't use this, it is just to demonstrate that + // pins can have other functions and still be Dynamically typed. + let _in_pin = pins.gpio23.into_floating_input().into_dyn_pin(); + + // You can also let the target type set the pin mode, using the type system to guide it. + // Once again, we're not going to use this array. The only reason it is here is to demonstrate a less verbose way to set pin modes + let mut _type_coerce: [Pin; 1] = + [pins.gpio22.reconfigure().into_dyn_pin()]; + + // Light one LED at a time. Start at GPIO2 and go through to GPIO5, then reverse. + loop { + for led in pinarray.iter_mut() { + led.set_high().unwrap(); + timer.delay_ms(50); + led.set_low().unwrap(); + } + for led in pinarray.iter_mut().rev() { + led.set_high().unwrap(); + timer.delay_ms(50); + led.set_low().unwrap(); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"GPIO Dynamic Pin Array Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/gpio_in_out.rs b/rp-hal/rp235x-hal-examples/src/bin/gpio_in_out.rs new file mode 100644 index 0000000..06ab24e --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/gpio_in_out.rs @@ -0,0 +1,97 @@ +//! # GPIO In/Out Example +//! +//! This application demonstrates how to control GPIO pins on the RP2040. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::digital::InputPin; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let _clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO 25 as an output + let mut out_pin = pins.gpio25.into_push_pull_output(); + + // Configure GPIO 23 as an input + let mut in_pin = pins.gpio23.into_pull_down_input(); + + // Output is the opposite of the input + loop { + if in_pin.is_low().unwrap() { + out_pin.set_high().unwrap(); + } else { + out_pin.set_low().unwrap(); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"GPIO Input/Output Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/gpio_irq_example.rs b/rp-hal/rp235x-hal-examples/src/bin/gpio_irq_example.rs new file mode 100644 index 0000000..04a4574 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/gpio_irq_example.rs @@ -0,0 +1,190 @@ +//! # GPIO IRQ Example +//! +//! This application demonstrates use of GPIO Interrupts. +//! It is also intended as a general introduction to interrupts with rp235x. +//! +//! Each GPIO can be triggered on the input being high (LevelHigh), being low (LevelLow) +//! starting high and then going low (EdgeLow) or starting low and becoming high (EdgeHigh) +//! +//! In this example, we trigger on EdgeLow. Our input pin configured to be pulled to the high logic-level +//! via an internal pullup resistor. This resistor is quite weak, so you can bring the logic level back to low +//! via an external jumper wire or switch. +//! Whenever we see the edge transition, we will toggle the output on GPIO25 - this is the LED pin on a Pico. +//! +//! Note that this demo does not perform any [software debouncing](https://en.wikipedia.org/wiki/Switch#Contact_bounce). +//! You can fix that through hardware, or you could disable the button interrupt in the interrupt and re-enable it +//! some time later using one of the Alarms of the Timer peripheral - this is left as an exercise for the reader. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::digital::StatefulOutputPin; + +// Our interrupt macro +use hal::pac::interrupt; + +// Some short-cuts to useful types +use core::cell::RefCell; +use critical_section::Mutex; +use hal::gpio; + +// The GPIO interrupt type we're going to generate +use gpio::Interrupt::EdgeLow; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +// Pin types quickly become very long! +// We'll create some type aliases using `type` to help with that + +/// This pin will be our output - it will drive an LED if you run this on a Pico +type LedPin = gpio::Pin; + +/// This pin will be our interrupt source. +/// It will trigger an interrupt if pulled to ground (via a switch or jumper wire) +type ButtonPin = gpio::Pin; + +/// Since we're always accessing these pins together we'll store them in a tuple. +/// Giving this tuple a type alias means we won't need to use () when putting them +/// inside an Option. That will be easier to read. +type LedAndButton = (LedPin, ButtonPin); + +/// This how we transfer our Led and Button pins into the Interrupt Handler. +/// We'll have the option hold both using the LedAndButton type. +/// This will make it a bit easier to unpack them later. +static GLOBAL_PINS: Mutex>> = Mutex::new(RefCell::new(None)); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let _clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO 25 as an output to drive our LED. + // we can use reconfigure() instead of into_pull_up_input() + // since the variable we're pushing it into has that type + let led = pins.gpio25.reconfigure(); + + // Set up the GPIO pin that will be our input + let in_pin = pins.gpio26.reconfigure(); + + // Trigger on the 'falling edge' of the input pin. + // This will happen as the button is being pressed + in_pin.set_interrupt_enabled(EdgeLow, true); + + // Give away our pins by moving them into the `GLOBAL_PINS` variable. + // We won't need to access them in the main thread again + critical_section::with(|cs| { + GLOBAL_PINS.borrow(cs).replace(Some((led, in_pin))); + }); + + // Unmask the IO_BANK0 IRQ so that the NVIC interrupt controller + // will jump to the interrupt function when the interrupt occurs. + // We do this last so that the interrupt can't go off while + // it is in the middle of being configured + unsafe { + cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::IO_IRQ_BANK0); + } + + loop { + // interrupts handle everything else in this example. + hal::arch::wfi(); + } +} + +#[allow(static_mut_refs)] // See https://github.com/rust-embedded/cortex-m/pull/561 +#[interrupt] +fn IO_IRQ_BANK0() { + // The `#[interrupt]` attribute covertly converts this to `&'static mut Option` + static mut LED_AND_BUTTON: Option = None; + + // This is one-time lazy initialisation. We steal the variables given to us + // via `GLOBAL_PINS`. + if LED_AND_BUTTON.is_none() { + critical_section::with(|cs| { + *LED_AND_BUTTON = GLOBAL_PINS.borrow(cs).take(); + }); + } + + // Need to check if our Option contains our pins + if let Some(gpios) = LED_AND_BUTTON { + // borrow led and button by *destructuring* the tuple + // these will be of type `&mut LedPin` and `&mut ButtonPin`, so we don't have + // to move them back into the static after we use them + let (led, button) = gpios; + // Check if the interrupt source is from the pushbutton going from high-to-low. + // Note: this will always be true in this example, as that is the only enabled GPIO interrupt source + if button.interrupt_status(EdgeLow) { + // toggle can't fail, but the embedded-hal traits always allow for it + // we can discard the return value by assigning it to an unnamed variable + let _ = led.toggle(); + + // Our interrupt doesn't clear itself. + // Do that now so we don't immediately jump back to this interrupt handler. + button.clear_interrupt(EdgeLow); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"GPIO IRQ Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/i2c.rs b/rp-hal/rp235x-hal-examples/src/bin/i2c.rs new file mode 100644 index 0000000..3b96278 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/i2c.rs @@ -0,0 +1,108 @@ +//! # I²C Example +//! +//! This application demonstrates how to talk to I²C devices with an rp235x. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal_0_2::blocking::i2c::Write; +use hal::fugit::RateExtU32; +use hal::gpio::{FunctionI2C, Pin}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then performs a single I²C +/// write to a fixed address. +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let sda_pin: Pin<_, FunctionI2C, _> = pins.gpio18.reconfigure(); + let scl_pin: Pin<_, FunctionI2C, _> = pins.gpio19.reconfigure(); + // let not_an_scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio20.reconfigure(); + + // Create the I²C drive, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let mut i2c = hal::I2C::i2c1( + pac.I2C1, + sda_pin, + scl_pin, // Try `not_an_scl_pin` here + 400.kHz(), + &mut pac.RESETS, + &clocks.system_clock, + ); + + // Write three bytes to the I²C device with 7-bit address 0x2C + i2c.write(0x2Cu8, &[1, 2, 3]).unwrap(); + + // Demo finish - just loop until reset + + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"I²C Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/i2c_async.rs b/rp-hal/rp235x-hal-examples/src/bin/i2c_async.rs new file mode 100644 index 0000000..291cc14 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/i2c_async.rs @@ -0,0 +1,134 @@ +//! # I²C Example +//! +//! This application demonstrates how to talk to I²C devices with an RP235x. +//! in an Async environment. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use hal::{ + fugit::RateExtU32, + gpio::bank0::{Gpio20, Gpio21}, + gpio::{FunctionI2C, Pin, PullUp}, + i2c::Controller, + pac::interrupt, + Clock, I2C, +}; + +// Import required types & traits. +use embassy_executor::Executor; +use embedded_hal_async::i2c::I2c; +use static_cell::StaticCell; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Bind the interrupt handler with the peripheral +#[interrupt] +unsafe fn I2C0_IRQ() { + use hal::async_utils::AsyncPeripheral; + I2C::::on_interrupt(); +} + +/// The function configures the RP235x peripherals, then performs a single I²C +/// write to a fixed address. +#[embassy_executor::task] +async fn demo() { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio20.reconfigure(); + let scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio21.reconfigure(); + + // Create the I²C drive, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let mut i2c = hal::I2C::new_controller( + pac.I2C0, + sda_pin, + scl_pin, + 400.kHz(), + &mut pac.RESETS, + clocks.system_clock.freq(), + ); + + // Unmask the interrupt in the NVIC to let the core wake up & enter the interrupt handler. + // Each core has its own NVIC so these needs to executed from the core where the IRQ are + // expected. + unsafe { + cortex_m::peripheral::NVIC::unpend(hal::pac::Interrupt::I2C0_IRQ); + cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::I2C0_IRQ); + } + + // Asynchronously write three bytes to the I²C device with 7-bit address 0x2C + i2c.write(0x76u8, &[1, 2, 3]).await.unwrap(); + + // Demo finish - just loop until reset + core::future::pending().await +} + +/// Entry point to our bare-metal application. +#[hal::entry] +fn main() -> ! { + static EXECUTOR: StaticCell = StaticCell::new(); + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| spawner.spawn(demo()).unwrap()); +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"I²C Async Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/i2c_async_cancelled.rs b/rp-hal/rp235x-hal-examples/src/bin/i2c_async_cancelled.rs new file mode 100644 index 0000000..f85240e --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/i2c_async_cancelled.rs @@ -0,0 +1,154 @@ +//! # I²C Example +//! +//! This application demonstrates how to talk to I²C devices with an RP235x. +//! in an Async environment. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +use core::task::Poll; +use embedded_hal_async::i2c::I2c; +use futures::FutureExt; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use hal::{ + fugit::RateExtU32, + gpio::bank0::{Gpio20, Gpio21}, + gpio::{FunctionI2C, Pin, PullUp}, + i2c::Controller, + pac::interrupt, + Clock, I2C, +}; + +use defmt_rtt as _; +use embassy_executor::Executor; +use static_cell::StaticCell; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[interrupt] +unsafe fn I2C0_IRQ() { + use hal::async_utils::AsyncPeripheral; + I2C::::on_interrupt(); +} + +/// The function configures the RP235x peripherals, then performs a single I²C +/// write to a fixed address. +#[embassy_executor::task] +async fn demo() { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio20.reconfigure(); + let scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio21.reconfigure(); + + // Create the I²C drive, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let mut i2c = hal::I2C::new_controller( + pac.I2C0, + sda_pin, + scl_pin, + 400.kHz(), + &mut pac.RESETS, + clocks.system_clock.freq(), + ); + + // Unmask the interrupt in the NVIC to let the core wake up & enter the interrupt handler. + unsafe { + cortex_m::peripheral::NVIC::unpend(hal::pac::Interrupt::I2C0_IRQ); + cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::I2C0_IRQ); + } + + let mut cnt = 0; + let timeout = core::future::poll_fn(|cx| { + cx.waker().wake_by_ref(); + if cnt == 1 { + Poll::Ready(()) + } else { + cnt += 1; + Poll::Pending + } + }); + + let mut v = [0; 32]; + v.iter_mut().enumerate().for_each(|(i, v)| *v = i as u8); + + // Asynchronously write three bytes to the I²C device with 7-bit address 0x2C + futures::select_biased! { + r = i2c.write(0x76u8, &v).fuse() => r.unwrap(), + _ = timeout.fuse() => { + defmt::info!("Timed out."); + } + } + i2c.write(0x76u8, &v).await.unwrap(); + + // Demo finish - just loop until reset + core::future::pending().await +} + +/// Entry point to our bare-metal application. +#[hal::entry] +fn main() -> ! { + static EXECUTOR: StaticCell = StaticCell::new(); + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| spawner.spawn(demo()).unwrap()); +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"I²C Async Cancellation Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/lcd_display.rs b/rp-hal/rp235x-hal-examples/src/bin/lcd_display.rs new file mode 100644 index 0000000..b94301e --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/lcd_display.rs @@ -0,0 +1,118 @@ +//! # LCD Display Example +//! +//! In this example, the rp235x is configured to drive a small two-line +//! alphanumeric LCD using the +//! [HD44780](https://crates.io/crates/hd44780-driver) driver. +//! +//! It drives the LCD by pushing data out of six GPIO pins. It may need to be +//! adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Our LCD driver +use hd44780_driver as hd44780; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, writes to the LCD, then goes +/// to sleep. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Create the LCD driver from some GPIO pins + let mut lcd = hd44780::HD44780::new_4bit( + pins.gpio16.into_push_pull_output(), // Register Select + pins.gpio17.into_push_pull_output(), // Enable + pins.gpio18.into_push_pull_output(), // d4 + pins.gpio19.into_push_pull_output(), // d5 + pins.gpio20.into_push_pull_output(), // d6 + pins.gpio21.into_push_pull_output(), // d7 + &mut delay, + ) + .unwrap(); + + // Clear the screen + lcd.reset(&mut delay).unwrap(); + lcd.clear(&mut delay).unwrap(); + + // Write to the top line + lcd.write_str("rp-hal on", &mut delay).unwrap(); + + // Move the cursor + lcd.set_cursor_pos(40, &mut delay).unwrap(); + + // Write more more text + lcd.write_str("HD44780!", &mut delay).unwrap(); + + // Do nothing - we're finished + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"LCD Display Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/mem_to_mem_dma.rs b/rp-hal/rp235x-hal-examples/src/bin/mem_to_mem_dma.rs new file mode 100644 index 0000000..8748289 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/mem_to_mem_dma.rs @@ -0,0 +1,95 @@ +//! # Memory to memory DMA transfer Example +//! +//! This application demonstrates how to use DMA to transfer data from memory to memory buffers. +//! +//! See the `Cargo.toml` file for Copyright and licence details. +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::singleton; + +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +use hal::dma::{single_buffer, DMAExt}; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Setup clocks and the watchdog. + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // Setup the pins. + let sio = hal::sio::Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Initialize DMA. + let dma = pac.DMA.split(&mut pac.RESETS); + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Use DMA to transfer some bytes (single buffering). + let tx_buf = singleton!(: [u8; 16] = [0x42; 16]).unwrap(); + let rx_buf = singleton!(: [u8; 16] = [0; 16]).unwrap(); + + // Use a single_buffer to read from tx_buf and write into rx_buf + let transfer = single_buffer::Config::new(dma.ch0, tx_buf, rx_buf).start(); + // Wait for both DMA channels to finish + let (_ch, tx_buf, rx_buf) = transfer.wait(); + + // Compare buffers to see if the data was transferred correctly + // Slow blink on success, fast on failure + let delay_ms = if tx_buf == rx_buf { 1000 } else { 100 }; + + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(delay_ms); + led_pin.set_low().unwrap(); + delay.delay_ms(delay_ms); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Memory-Memory DMA Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/multicore_fifo_blink.rs b/rp-hal/rp235x-hal-examples/src/bin/multicore_fifo_blink.rs new file mode 100644 index 0000000..2ea9968 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/multicore_fifo_blink.rs @@ -0,0 +1,178 @@ +//! # Multicore FIFO + GPIO 'Blinky' Example +//! +//! This application demonstrates FIFO communication between the CPU cores on the rp235x. +//! Core 0 will calculate and send a delay value to Core 1, which will then wait that long +//! before toggling the LED. +//! Core 0 will wait for Core 1 to complete this task and send an acknowledgement value. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::digital::StatefulOutputPin; +use hal::clocks::Clock; +use hal::multicore::{Multicore, Stack}; +use hal::sio::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Value to indicate that Core 1 has completed its task +const CORE1_TASK_COMPLETE: u32 = 0xEE; + +/// Stack for core 1 +/// +/// Core 0 gets its stack via the normal route - any memory not used by static values is +/// reserved for stack and initialised by cortex-m-rt. +/// To get the same for Core 1, we would need to compile everything separately and +/// modify the linker file for both programs, and that's quite annoying. +/// So instead, core1.spawn takes a [usize] which gets used for the stack. +/// NOTE: We use the `Stack` struct here to ensure that it has 32-byte alignment, which allows +/// the stack guard to take up the least amount of usable RAM. +static CORE1_STACK: Stack<4096> = Stack::new(); + +fn core1_task(sys_freq: u32) -> ! { + let mut pac = unsafe { hal::pac::Peripherals::steal() }; + let core = unsafe { cortex_m::Peripherals::steal() }; + + let mut sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let mut led_pin = pins.gpio25.into_push_pull_output(); + let mut delay = cortex_m::delay::Delay::new(core.SYST, sys_freq); + loop { + let input = sio.fifo.read(); + if let Some(word) = input { + delay.delay_ms(word); + led_pin.toggle().unwrap(); + sio.fifo.write_blocking(CORE1_TASK_COMPLETE); + }; + } +} + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let sys_freq = clocks.system_clock.freq().to_Hz(); + + // The single-cycle I/O block controls our GPIO pins + let mut sio = hal::sio::Sio::new(pac.SIO); + + let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo); + let cores = mc.cores(); + let core1 = &mut cores[1]; + let _test = core1.spawn(CORE1_STACK.take().unwrap(), move || core1_task(sys_freq)); + + /// How much we adjust the LED period every cycle + const LED_PERIOD_INCREMENT: i32 = 2; + + /// The minimum LED toggle interval we allow for. + const LED_PERIOD_MIN: i32 = 0; + + /// The maximum LED toggle interval period we allow for. Keep it reasonably short so it's easy to see. + const LED_PERIOD_MAX: i32 = 100; + + // Our current LED period. It starts at the shortest period, which is the highest blink frequency + let mut led_period: i32 = LED_PERIOD_MIN; + + // The direction we're incrementing our LED period. + // Since we start at the minimum value, start by counting up + let mut count_up = true; + + loop { + if count_up { + // Increment our period + led_period += LED_PERIOD_INCREMENT; + + // Change direction of increment if we hit the limit + if led_period > LED_PERIOD_MAX { + led_period = LED_PERIOD_MAX; + count_up = false; + } + } else { + // Decrement our period + led_period -= LED_PERIOD_INCREMENT; + + // Change direction of increment if we hit the limit + if led_period < LED_PERIOD_MIN { + led_period = LED_PERIOD_MIN; + count_up = true; + } + } + + // It should not be possible for led_period to go negative, but let's ensure that. + if led_period < 0 { + led_period = 0; + } + + // Send the new delay time to Core 1. We convert it + sio.fifo.write(led_period as u32); + + // Sleep until Core 1 sends a message to tell us it is done + let ack = sio.fifo.read_blocking(); + if ack != CORE1_TASK_COMPLETE { + // In a real application you might want to handle the case + // where the CPU sent the wrong message - we're going to + // ignore it here. + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Multicore FIFO Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/multicore_polyblink.rs b/rp-hal/rp235x-hal-examples/src/bin/multicore_polyblink.rs new file mode 100644 index 0000000..a297f71 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/multicore_polyblink.rs @@ -0,0 +1,136 @@ +//! # Multicore Blinking Example +//! +//! This application blinks two LEDs on GPIOs 2 and 3 at different rates (3Hz +//! and 4Hz respectively.) +//! +//! See the `Cargo.toml` file for Copyright and licence details. + +#![no_std] +#![no_main] + +use cortex_m::delay::Delay; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use hal::clocks::Clock; +use hal::gpio::Pins; +use hal::multicore::{Multicore, Stack}; +use hal::sio::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Some things we need +use embedded_hal::digital::StatefulOutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// The frequency at which core 0 will blink its LED (Hz). +const CORE0_FREQ: u32 = 3; +/// The frequency at which core 1 will blink its LED (Hz). +const CORE1_FREQ: u32 = 4; +/// The delay between each toggle of core 0's LED (us). +const CORE0_DELAY: u32 = 1_000_000 / CORE0_FREQ; +/// The delay between each toggle of core 1's LED (us). +const CORE1_DELAY: u32 = 1_000_000 / CORE1_FREQ; + +/// Stack for core 1 +/// +/// Core 0 gets its stack via the normal route - any memory not used by static +/// values is reserved for stack and initialised by cortex-m-rt. +/// To get the same for Core 1, we would need to compile everything seperately +/// and modify the linker file for both programs, and that's quite annoying. +/// So instead, core1.spawn takes a [usize] which gets used for the stack. +/// NOTE: We use the `Stack` struct here to ensure that it has 32-byte +/// alignment, which allows the stack guard to take up the least amount of +/// usable RAM. +static CORE1_STACK: Stack<4096> = Stack::new(); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + let core = cortex_m::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // Set up the GPIO pins + let mut sio = Sio::new(pac.SIO); + let pins = Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + let mut led1 = pins.gpio2.into_push_pull_output(); + let mut led2 = pins.gpio3.into_push_pull_output(); + + // Set up the delay for the first core. + let sys_freq = clocks.system_clock.freq().to_Hz(); + let mut delay = Delay::new(core.SYST, sys_freq); + + // Start up the second core to blink the second LED + let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo); + let cores = mc.cores(); + let core1 = &mut cores[1]; + core1 + .spawn(CORE1_STACK.take().unwrap(), move || { + // Get the second core's copy of the `CorePeripherals`, which are per-core. + // Unfortunately, `cortex-m` doesn't support this properly right now, + // so we have to use `steal`. + let core = unsafe { cortex_m::Peripherals::steal() }; + // Set up the delay for the second core. + let mut delay = Delay::new(core.SYST, sys_freq); + // Blink the second LED. + loop { + led2.toggle().unwrap(); + delay.delay_us(CORE1_DELAY) + } + }) + .unwrap(); + + // Blink the first LED. + loop { + led1.toggle().unwrap(); + delay.delay_us(CORE0_DELAY) + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Multicore Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/pio_blink.rs b/rp-hal/rp235x-hal-examples/src/bin/pio_blink.rs new file mode 100644 index 0000000..e814dd8 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/pio_blink.rs @@ -0,0 +1,92 @@ +//! This example toggles the GPIO25 pin, using a PIO program. +//! +//! If a LED is connected to that pin, like on a Pico board, the LED should blink. +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +use rp235x_hal as hal; + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pio::PIOExt; +use hal::Sio; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then blinks an LED using the PIO peripheral. +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // Define some simple PIO program. + const MAX_DELAY: u8 = 31; + let mut a = pio::Assembler::<32>::new(); + let mut wrap_target = a.label(); + let mut wrap_source = a.label(); + // Set pin as Out + a.set(pio::SetDestination::PINDIRS, 1); + // Define begin of program loop + a.bind(&mut wrap_target); + // Set pin low + a.set_with_delay(pio::SetDestination::PINS, 0, MAX_DELAY); + // Set pin high + a.set_with_delay(pio::SetDestination::PINS, 1, MAX_DELAY); + // Define end of program loop + a.bind(&mut wrap_source); + // The labels wrap_target and wrap_source, as set above, + // define a loop which is executed repeatedly by the PIO + // state machine. + let program = a.assemble_with_wrap(wrap_source, wrap_target); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program).unwrap(); + let (int, frac) = (0, 0); // as slow as possible (0 is interpreted as 65536) + let (sm, _, _) = hal::pio::PIOBuilder::from_installed_program(installed) + .set_pins(led_pin_id, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + sm.start(); + + // PIO runs in background, independently from CPU + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PIO Blinky Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/pio_dma.rs b/rp-hal/rp235x-hal-examples/src/bin/pio_dma.rs new file mode 100644 index 0000000..63add2e --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/pio_dma.rs @@ -0,0 +1,134 @@ +//! This example shows how to read from and write to PIO using DMA. +//! +//! If a LED is connected to that pin, like on a Pico board, it will continously output "HELLO +//! WORLD" in morse code. The example also tries to read the data back. If reading the data fails, +//! the message will only be shown once, and then the LED remains dark. +//! +//! See the `Cargo.toml` file for Copyright and licence details. +#![no_std] +#![no_main] + +use hal::singleton; + +use rp235x_hal as hal; + +use hal::dma::{double_buffer, single_buffer, DMAExt}; +use hal::gpio::{FunctionPio0, Pin}; +use hal::pio::PIOExt; +use hal::sio::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // HELLO WORLD in morse code: + // .... . .-.. .-.. --- / .-- --- .-. .-.. -.. + #[allow(clippy::unusual_byte_groupings)] + let message = [ + 0b10101010_00100010_11101010_00101110, + 0b10100011_10111011_10000000_10111011, + 0b10001110_11101110_00101110_10001011, + 0b10101000_11101010_00000000_00000000, + ]; + + // Define a PIO program which reads data from the TX FIFO bit by bit, configures the LED + // according to the data, and then writes the data back to the RX FIFO. + let program = pio_proc::pio_asm!( + ".wrap_target", + " out x, 1", + " mov pins, x", + " in x, 1 [13]", + ".wrap" + ); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program.program).unwrap(); + let (mut sm, rx, tx) = hal::pio::PIOBuilder::from_installed_program(installed) + .out_pins(led_pin_id, 1) + .clock_divisor_fixed_point(0, 0) // as slow as possible (0 is interpreted as 65536) + .autopull(true) + .autopush(true) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]); + sm.start(); + + let dma = pac.DMA.split(&mut pac.RESETS); + + // Transfer a single message via DMA. + let tx_buf = singleton!(: [u32; 4] = message).unwrap(); + let rx_buf = singleton!(: [u32; 4] = [0; 4]).unwrap(); + let tx_transfer = single_buffer::Config::new(dma.ch0, tx_buf, tx).start(); + let rx_transfer = single_buffer::Config::new(dma.ch1, rx, rx_buf).start(); + let (ch0, tx_buf, tx) = tx_transfer.wait(); + let (ch1, rx, rx_buf) = rx_transfer.wait(); + for i in 0..rx_buf.len() { + if rx_buf[i] != tx_buf[i] { + // The data did not match, abort. + #[allow(clippy::empty_loop)] + loop {} + } + } + + // Chain some buffers together for continuous transfers + let tx_buf2 = singleton!(: [u32; 4] = message).unwrap(); + let rx_buf2 = singleton!(: [u32; 4] = [0; 4]).unwrap(); + let tx_transfer = double_buffer::Config::new((ch0, ch1), tx_buf, tx).start(); + let mut tx_transfer = tx_transfer.read_next(tx_buf2); + let rx_transfer = double_buffer::Config::new((dma.ch2, dma.ch3), rx, rx_buf).start(); + let mut rx_transfer = rx_transfer.write_next(rx_buf2); + loop { + // When a transfer is done we immediately enqueue the buffers again. + if tx_transfer.is_done() { + let (tx_buf, next_tx_transfer) = tx_transfer.wait(); + tx_transfer = next_tx_transfer.read_next(tx_buf); + } + if rx_transfer.is_done() { + let (rx_buf, next_rx_transfer) = rx_transfer.wait(); + for i in 0..rx_buf.len() { + if rx_buf[i] != message[i] { + // The data did not match, abort. + #[allow(clippy::empty_loop)] + loop {} + } + } + rx_transfer = next_rx_transfer.write_next(rx_buf); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PIO Blinky with DMA Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/pio_proc_blink.rs b/rp-hal/rp235x-hal-examples/src/bin/pio_proc_blink.rs new file mode 100644 index 0000000..711701c --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/pio_proc_blink.rs @@ -0,0 +1,80 @@ +//! This example toggles the GPIO25 pin, using a PIO program compiled via pio_proc::pio!(). +//! +//! If a LED is connected to that pin, like on a Pico board, the LED should blink. +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pio::PIOExt; +use hal::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // Define some simple PIO program. + let program = pio_proc::pio_asm!( + ".wrap_target", + "set pins, 1 [31]", + "set pins, 0 [31]", + ".wrap" + ); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program.program).unwrap(); + let (int, frac) = (0, 0); // as slow as possible (0 is interpreted as 65536) + let (mut sm, _, _) = hal::pio::PIOBuilder::from_installed_program(installed) + .set_pins(led_pin_id, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]); + sm.start(); + + // PIO runs in background, independently from CPU + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PIO Proc-macro Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/pio_side_set.rs b/rp-hal/rp235x-hal-examples/src/bin/pio_side_set.rs new file mode 100644 index 0000000..ab10799 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/pio_side_set.rs @@ -0,0 +1,83 @@ +//! This example toggles the GPIO25 pin, using a PIO program compiled via pio_proc::pio!(). +//! +//! If a LED is connected to that pin, like on a Pico board, the LED should blink. +//! +//! This example makes use of side setting. +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pio::PIOExt; +use hal::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // Define some simple PIO program. + let program = pio_proc::pio_asm!( + ".side_set 1", // each instruction must set 1 bit + ".wrap_target", + " nop side 1 [15]", + " nop side 0 [15]", + ".wrap", + ); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program.program).unwrap(); + let (int, frac) = (0, 0); // as slow as possible (0 is interpreted as 65536) + let (mut sm, _, _) = hal::pio::PIOBuilder::from_installed_program(installed) + .side_set_pin_base(led_pin_id) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]); + sm.start(); + + // PIO runs in background, independently from CPU + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PIO Side-set Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/pio_synchronized.rs b/rp-hal/rp235x-hal-examples/src/bin/pio_synchronized.rs new file mode 100644 index 0000000..b64839f --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/pio_synchronized.rs @@ -0,0 +1,117 @@ +//! This example toggles the GPIO0 and GPIO1 pins, with each controlled from a +//! separate PIO state machine. +//! +//! Despite running in separate state machines, the clocks are synchronized at +//! the rise and fall times will be simultaneous. +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pio::PIOExt; +use hal::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure pins for Pio0. + let gp0: Pin<_, FunctionPio0, _> = pins.gpio0.into_function(); + let gp1: Pin<_, FunctionPio0, _> = pins.gpio1.into_function(); + + // PIN id for use inside of PIO + let pin0 = gp0.id().num; + let pin1 = gp1.id().num; + + // Define some simple PIO program. + let program = pio_proc::pio_asm!( + " +.wrap_target + set pins, 1 [31] + set pins, 0 [31] +.wrap + " + ); + + // Initialize and start PIO + let (mut pio, sm0, sm1, _, _) = pac.PIO0.split(&mut pac.RESETS); + // I'm "measuring" the phase offset between the two pins by connecting + // then through a LED. If there is a clock offset, there will be a + // short time with a voltage between the pins, so the LED will flash up. + // With a slow clock this is not visible, so use a reasonably fast clock. + let (int, frac) = (256, 0); + + let installed = pio.install(&program.program).unwrap(); + let (mut sm0, _, _) = hal::pio::PIOBuilder::from_installed_program( + // Safety: We won't uninstall the program, ever + unsafe { installed.share() }, + ) + .set_pins(pin0, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm0.set_pindirs([(pin0, hal::pio::PinDir::Output)]); + + let (mut sm1, _, _) = hal::pio::PIOBuilder::from_installed_program(installed) + .set_pins(pin1, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm1); + // The GPIO pin needs to be configured as an output. + sm1.set_pindirs([(pin1, hal::pio::PinDir::Output)]); + + // Start both SMs at the same time + let group = sm0.with(sm1).sync().start(); + hal::arch::delay(10_000_000); + + // Stop both SMs at the same time + let group = group.stop(); + hal::arch::delay(10_000_000); + + // Start them again and extract the individual state machines + let (sm1, sm2) = group.start().free(); + hal::arch::delay(10_000_000); + + // Stop the two state machines separately + let _sm1 = sm1.stop(); + hal::arch::delay(10_000_000); + let _sm2 = sm2.stop(); + + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PIO Synchronisation Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/powman_test.rs b/rp-hal/rp235x-hal-examples/src/bin/powman_test.rs new file mode 100644 index 0000000..80a517e --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/powman_test.rs @@ -0,0 +1,326 @@ +//! # POWMAN Example +//! +//! This application demonstrates talking to the POWMAN peripheral. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +use rp235x_hal::{ + self as hal, gpio, pac, + powman::{AotClockSource, FractionalFrequency, Powman}, + uart::{DataBits, StopBits, UartConfig, UartPeripheral}, +}; + +use cortex_m_rt::exception; +use pac::interrupt; + +// Some traits we need +use core::fmt::Write; +use hal::fugit::RateExtU32; +use hal::Clock; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +struct GlobalUart { + inner: critical_section::Mutex>>, +} + +type MyUart = UartPeripheral< + hal::uart::Enabled, + pac::UART0, + ( + gpio::Pin, + gpio::Pin, + ), +>; + +static GLOBAL_UART: GlobalUart = GlobalUart { + inner: critical_section::Mutex::new(core::cell::RefCell::new(None)), +}; + +impl core::fmt::Write for &GlobalUart { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + critical_section::with(|cs| { + let mut cell = self.inner.borrow_ref_mut(cs); + let uart = cell.as_mut().unwrap(); + uart.write_str(s) + }) + } +} + +impl GlobalUart { + fn init(&self, uart: MyUart) { + critical_section::with(|cs| { + self.inner.borrow(cs).replace(Some(uart)); + }); + } +} + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let mut cp = cortex_m::Peripherals::take().unwrap(); + + // Enable the cycle counter + cp.DCB.enable_trace(); + cp.DWT.enable_cycle_counter(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from rp235x) on pin 1 (GPIO0) + pins.gpio0.into_function::(), + // UART RX (characters received by rp235x) on pin 2 (GPIO1) + pins.gpio1.into_function::(), + ); + let uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + GLOBAL_UART.init(uart); + + let mut powman = Powman::new(pac.POWMAN, None); + + _ = writeln!(&GLOBAL_UART, "\n\nPOWMAN Test"); + + print_aot_status(&mut powman); + _ = writeln!(&GLOBAL_UART, "AOT time: 0x{:016x}", powman.aot_get_time()); + + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::POWMAN_IRQ_TIMER); + } + + _ = writeln!(&GLOBAL_UART, "Starting AOT..."); + powman.aot_start(); + // we don't know what oscillator we're on, so give it time to start whatever it is + cortex_m::asm::delay(150_000); + print_aot_status(&mut powman); + rollover_test(&mut powman); + loop_test(&mut powman); + alarm_test(&mut powman); + + let source = AotClockSource::Xosc(FractionalFrequency::from_hz(12_000_000)); + _ = writeln!(&GLOBAL_UART, "Switching AOT to {}", source); + powman.aot_set_clock(source).expect("selecting XOSC"); + print_aot_status(&mut powman); + loop_test(&mut powman); + + let source = AotClockSource::Lposc(FractionalFrequency::from_hz(32768)); + _ = writeln!(&GLOBAL_UART, "Switching AOT to {}", source); + powman.aot_set_clock(source).expect("selecting LPOSC"); + print_aot_status(&mut powman); + loop_test(&mut powman); + + _ = writeln!(&GLOBAL_UART, "Rebooting now"); + + panic!("Finished!"); +} + +fn print_aot_status(powman: &mut Powman) { + if powman.aot_is_running() { + _ = writeln!( + &GLOBAL_UART, + "AOT is running on {}", + powman.aot_get_clock().unwrap() + ); + } else { + _ = writeln!(&GLOBAL_UART, "AOT is not running."); + } +} + +/// Check we can roll-over a 32-bit boundary +fn rollover_test(powman: &mut Powman) { + let start_loop = 0x0000_0000_FFFF_FF00u64; + let end_loop = 0x0000_0001_0000_00FFu64; + _ = writeln!( + &GLOBAL_UART, + "Setting AOT to 0x{:016x} and waiting for rollover...", + start_loop + ); + powman.aot_stop(); + powman.aot_set_time(start_loop); + powman.aot_start(); + + let mut last = 0; + loop { + let now = powman.aot_get_time(); + if now == end_loop { + break; + } else if now < last || now > end_loop { + panic!("bad AOT read {}!", now); + } + last = now; + } + _ = writeln!(&GLOBAL_UART, "Rollover test complete - no glitches found",); + _ = writeln!(&GLOBAL_UART, "Clearing AOT..."); + powman.aot_clear(); + _ = writeln!(&GLOBAL_UART, "AOT time: 0x{:016x}", powman.aot_get_time()); +} + +/// In this function, we see how long it takes to pass a certain number of ticks. +fn loop_test(powman: &mut Powman) { + let start_loop = 0; + let end_loop = 2_000; // 2 seconds + _ = writeln!( + &GLOBAL_UART, + "Setting AOT to 0x{:016x} and benchmarking...", + start_loop + ); + powman.aot_stop(); + powman.aot_set_time(start_loop); + powman.aot_start(); + + let start_clocks = cortex_m::peripheral::DWT::cycle_count(); + loop { + let now = powman.aot_get_time(); + if now == end_loop { + break; + } + } + let end_clocks = cortex_m::peripheral::DWT::cycle_count(); + // Compare our AOT against our CPU clock speed + let delta_clocks = end_clocks.wrapping_sub(start_clocks) as u64; + let delta_ticks = end_loop - start_loop; + let cycles_per_tick = delta_clocks / delta_ticks; + // Assume we're running at 150 MHz + let ms_per_tick = (cycles_per_tick as f32 * 1000.0) / 150_000_000.0; + let percent = ((ms_per_tick - 1.0) / 1.0) * 100.0; + _ = writeln!( + &GLOBAL_UART, + "Loop complete ... {delta_ticks} ticks in {delta_clocks} CPU clock cycles = {cycles_per_tick} cycles/tick ~= {ms_per_tick} ms/tick ({percent:.3}%)", + ) + ; +} + +/// Test the alarm function +fn alarm_test(powman: &mut Powman) { + let start_time = 0x1_0000; + let alarm_time = start_time + 3000; // alarm is 3 seconds in the future + _ = writeln!( + &GLOBAL_UART, + "Setting AOT for {} ms in the future...", + alarm_time - start_time + ); + + powman.aot_stop(); + powman.aot_set_time(start_time); + powman.aot_alarm_clear(); + powman.aot_set_alarm(alarm_time); + powman.aot_alarm_interrupt_enable(); + powman.aot_alarm_enable(); + powman.aot_start(); + + _ = writeln!( + &GLOBAL_UART, + "Sleeping until alarm (* = wakeup, ! = POWMAN interrupt)...", + ); + while !powman.aot_alarm_ringing() { + cortex_m::asm::wfe(); + _ = write!(&GLOBAL_UART, "*",); + } + + _ = writeln!( + &GLOBAL_UART, + "\nAlarm fired at 0x{:016x}. Clearing alarm.", + powman.aot_get_time() + ); + + powman.aot_alarm_clear(); + + if powman.aot_alarm_ringing() { + panic!("Alarm did not clear!"); + } + + _ = writeln!(&GLOBAL_UART, "Alarm cleared OK"); +} + +#[interrupt] +#[allow(non_snake_case)] +fn POWMAN_IRQ_TIMER() { + Powman::static_aot_alarm_interrupt_disable(); + _ = write!(&GLOBAL_UART, "!"); + cortex_m::asm::sev(); +} + +#[exception] +unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { + let _ = writeln!(&GLOBAL_UART, "HARD FAULT:\n{:#?}", ef); + + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(&GLOBAL_UART, "PANIC:\n{:?}", info); + + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"POWMAN Test Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/pwm_blink.rs b/rp-hal/rp235x-hal-examples/src/bin/pwm_blink.rs new file mode 100644 index 0000000..34eae8d --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/pwm_blink.rs @@ -0,0 +1,124 @@ +//! # PWM Blink Example +//! +//! If you have an LED connected to pin 25, it will fade the LED using the PWM +//! peripheral. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::pwm::SetDutyCycle; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); +/// The minimum PWM value (i.e. LED brightness) we want +const LOW: u16 = 0; + +/// The maximum PWM value (i.e. LED brightness) we want +const HIGH: u16 = 25000; + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then fades the LED in an +/// infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Init PWMs + let mut pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM4 + let pwm = &mut pwm_slices.pwm4; + pwm.set_ph_correct(); + pwm.enable(); + + // Output channel B on PWM4 to GPIO 25 + let channel = &mut pwm.channel_b; + channel.output_to(pins.gpio25); + + // Infinite loop, fading LED up and down + loop { + // Ramp brightness up + for i in LOW..=HIGH { + delay.delay_us(8); + let _ = channel.set_duty_cycle(i); + } + + // Ramp brightness down + for i in (LOW..=HIGH).rev() { + delay.delay_us(8); + let _ = channel.set_duty_cycle(i); + } + + delay.delay_ms(500); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PWM Blinky Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/pwm_blink_embedded_hal_1.rs b/rp-hal/rp235x-hal-examples/src/bin/pwm_blink_embedded_hal_1.rs new file mode 100644 index 0000000..6695d87 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/pwm_blink_embedded_hal_1.rs @@ -0,0 +1,124 @@ +//! # PWM Blink Example +//! +//! If you have an LED connected to pin 25, it will fade the LED using the PWM +//! peripheral. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::pwm::SetDutyCycle; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// The minimum PWM value (i.e. LED brightness) we want +const LOW: u16 = 0; + +/// The maximum PWM value (i.e. LED brightness) we want +const HIGH: u16 = 25000; + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2350 peripherals, then fades the LED in an +/// infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Init PWMs + let mut pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM4 + let pwm = &mut pwm_slices.pwm4; + pwm.set_ph_correct(); + pwm.enable(); + + // Output channel B on PWM4 to GPIO 25 + let channel = &mut pwm.channel_b; + channel.output_to(pins.gpio25); + + // Infinite loop, fading LED up and down + loop { + // Ramp brightness up + for i in LOW..=HIGH { + delay.delay_us(8); + channel.set_duty_cycle(i).unwrap(); + } + + // Ramp brightness down + for i in (LOW..=HIGH).rev() { + delay.delay_us(8); + channel.set_duty_cycle(i).unwrap(); + } + + delay.delay_ms(500); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PWM Blinky Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/pwm_irq_input.rs b/rp-hal/rp235x-hal-examples/src/bin/pwm_irq_input.rs new file mode 100644 index 0000000..6a411b3 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/pwm_irq_input.rs @@ -0,0 +1,223 @@ +//! # PWM IRQ Input Example +//! +//! Read a 5V 50Hz PWM servo input signal from gpio pin 1 and turn the LED on when +//! the input signal is high ( > 1600 us duty pulse width ) and off when low ( < 1400 us ). +//! +//! This signal is commonly used with radio control model systems and small servos. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::digital::OutputPin; + +// Our interrupt macro +use hal::pac::interrupt; + +// Shorter alias for gpio and pwm modules +use hal::gpio; +use hal::pwm; + +// Some short-cuts to useful types for sharing data with the interrupt handlers +use core::cell::RefCell; +use critical_section::Mutex; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// 50 Hz PWM servo signals have a pulse width between 1000 us and 2000 us with +/// 1500 us as the centre point. us is the abbreviation for micro seconds. +/// +/// The PWM threshold value for turning off the LED in us +const LOW_US: u16 = 1475; + +/// The PWM threshold value for turning on the LED in us +const HIGH_US: u16 = 1525; + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Pin types quickly become very long! +/// We'll create some type aliases using `type` to help with that +/// +/// This pin will be our output - it will drive an LED if you run this on a Pico +type LedPin = gpio::Pin, gpio::PullNone>; + +/// This pin will be our input for a 50 Hz servo PWM signal +type InputPwmPin = gpio::Pin; + +/// This will be our PWM Slice - it will interpret the PWM signal from the pin +type PwmSlice = pwm::Slice; + +/// Since we're always accessing these pins together we'll store them in a tuple. +/// Giving this tuple a type alias means we won't need to use () when putting them +/// inside an Option. That will be easier to read. +type LedInputAndPwm = (LedPin, InputPwmPin, PwmSlice); + +/// This how we transfer our LED pin, input pin and PWM slice into the Interrupt Handler. +/// We'll have the option hold both using the LedAndInput type. +/// This will make it a bit easier to unpack them later. +static GLOBAL_PINS: Mutex>> = Mutex::new(RefCell::new(None)); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then fades the LED in an +/// infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Init PWMs + let pwm_slices = pwm::Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM0 slice + // The PWM slice clock should only run when the input is high (InputHighRunning) + let mut pwm: pwm::Slice<_, pwm::InputHighRunning> = pwm_slices.pwm0.into_mode(); + + // Divide the 125 MHz system clock by 125 to give a 1 MHz PWM slice clock (1 us per tick) + pwm.set_div_int(125); + pwm.enable(); + + // Connect to GPI O1 as the input to channel B on PWM0 + let input_pin = pins.gpio1.reconfigure(); + let channel = &mut pwm.channel_b; + channel.set_enabled(true); + + // Enable an interrupt whenever GPI O1 goes from high to low (the end of a pulse) + input_pin.set_interrupt_enabled(gpio::Interrupt::EdgeLow, true); + + // Configure GPIO 25 as an output to drive our LED. + // we can use reconfigure() instead of into_pull_up_input() + // since the variable we're pushing it into has that type + let led = pins.gpio25.reconfigure(); + + // Give away our pins by moving them into the `GLOBAL_PINS` variable. + // We won't need to access them in the main thread again + critical_section::with(|cs| { + GLOBAL_PINS.borrow(cs).replace(Some((led, input_pin, pwm))); + }); + + // Unmask the IO_BANK0 IRQ so that the NVIC interrupt controller + // will jump to the interrupt function when the interrupt occurs. + // We do this last so that the interrupt can't go off while + // it is in the middle of being configured + unsafe { + cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::IO_IRQ_BANK0); + } + + loop { + // interrupts handle everything else in this example. + hal::arch::wfi(); + } +} + +#[allow(static_mut_refs)] // See https://github.com/rust-embedded/cortex-m/pull/561 +#[interrupt] +fn IO_IRQ_BANK0() { + // The `#[interrupt]` attribute covertly converts this to `&'static mut Option` + static mut LED_INPUT_AND_PWM: Option = None; + + // This is one-time lazy initialisation. We steal the variables given to us + // via `GLOBAL_PINS`. + if LED_INPUT_AND_PWM.is_none() { + critical_section::with(|cs| { + *LED_INPUT_AND_PWM = GLOBAL_PINS.borrow(cs).take(); + }); + } + + // Need to check if our Option contains our pins and pwm slice + // borrow led, input and pwm by *destructuring* the tuple + // these will be of type `&mut LedPin`, `&mut InputPwmPin` and `&mut PwmSlice`, so we + // don't have to move them back into the static after we use them + if let Some((led, input, pwm)) = LED_INPUT_AND_PWM { + // Check if the interrupt source is from the input pin going from high-to-low. + // Note: this will always be true in this example, as that is the only enabled GPIO interrupt source + if input.interrupt_status(gpio::Interrupt::EdgeLow) { + // Read the width of the last pulse from the PWM Slice counter + let pulse_width_us = pwm.get_counter(); + + // if the PWM signal indicates low, turn off the LED + if pulse_width_us < LOW_US { + // set_low can't fail, but the embedded-hal traits always allow for it + // we can discard the Result + let _ = led.set_low(); + } + // if the PWM signal indicates high, turn on the LED + else if pulse_width_us > HIGH_US { + // set_high can't fail, but the embedded-hal traits always allow for it + // we can discard the Result + let _ = led.set_high(); + } + + // If the PWM signal was in the dead-zone between LOW and HIGH, don't change the LED's + // state. The dead-zone avoids the LED flickering rapidly when receiving a signal close + // to the mid-point, 1500 us in this case. + + // Reset the pwm counter back to 0, ready for the next pulse + pwm.set_counter(0); + + // Our interrupt doesn't clear itself. + // Do that now so we don't immediately jump back to this interrupt handler. + input.clear_interrupt(gpio::Interrupt::EdgeLow); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PWM IRQ Input Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/rom_funcs.rs b/rp-hal/rp235x-hal-examples/src/bin/rom_funcs.rs new file mode 100644 index 0000000..73a8d7a --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/rom_funcs.rs @@ -0,0 +1,456 @@ +//! # 'ROM Functions' Example +//! +//! This application demonstrates how to call functions in the rp235x's boot ROM. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +use rp235x_hal::rom_data::sys_info_api::{BootType, CpuInfo, FlashDevInfoSize, PartitionIndex}; +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use core::fmt::Write; +use hal::fugit::RateExtU32; +use hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from rp235x) on pin 1 (GPIO0) + pins.gpio0.into_function::(), + // UART RX (characters received by rp235x) on pin 2 (GPIO1) + pins.gpio1.into_function::(), + ); + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + _ = writeln!(uart); + _ = writeln!(uart, "rom_func example for RP2350"); + _ = writeln!(uart, "==========================="); + _ = writeln!(uart); + + _ = writeln!(uart, "CPU Secure Mode: {}", hal::rom_data::is_secure_mode()); + + _ = writeln!( + uart, + "ROM Git Revision: 0x{:x}", + hal::rom_data::git_revision() + ); + + _ = writeln!( + uart, + "Partition Table Info @ {:?}", + hal::rom_data::partition_table_pointer() + ); + + _ = writeln!( + uart, + "ROM Version: A{}", + hal::rom_data::rom_version_number() + ); + + get_otp_data(&mut uart, &mut pac.OTP_DATA); + get_otp_data_raw(&mut uart, &mut pac.OTP_DATA_RAW); + get_sys_info_chip_info(&mut uart); + get_sys_info_cpu_info(&mut uart); + get_sys_info_flash_dev_info(&mut uart); + get_sys_info_boot_random(&mut uart); + get_sys_info_start_block(&mut uart); + get_partition_table_info(&mut uart); + + const WORDS_PER_SCREEN_LINE: usize = 16; + _ = writeln!(uart, "Reading OTP:"); + _ = write!(uart, "Page Adr: "); + for col in 0..WORDS_PER_SCREEN_LINE { + _ = write!(uart, " {:04x}", col); + } + // These are the factory programmed pages + for page in [0, 1, 62, 63] { + for row in 0..hal::otp::NUM_ROWS_PER_PAGE { + let index = page * hal::otp::NUM_ROWS_PER_PAGE + row; + if row == 0 { + _ = write!(uart, "\nP{:02} {:04x}: ", page, index); + } else if (index % WORDS_PER_SCREEN_LINE) == 0 { + _ = write!(uart, "\n {:04x}: ", index); + } + match hal::otp::read_ecc_word(index) { + Ok(0) => { + _ = write!(uart, " ----"); + } + Ok(word) => { + _ = write!(uart, " {:04x}", word); + } + Err(hal::otp::Error::InvalidPermissions) => { + _ = write!(uart, " xxxx"); + } + Err(hal::otp::Error::InvalidIndex) => { + _ = write!(uart, " ????"); + } + } + } + } + _ = writeln!(uart); + + _ = writeln!(uart, "Reading raw OTP:"); + _ = write!(uart, "Page Adr: "); + for col in 0..WORDS_PER_SCREEN_LINE { + _ = write!(uart, " {:06x}", col); + } + // These are the factory programmed pages + for page in [0, 1, 62, 63] { + for row in 0..hal::otp::NUM_ROWS_PER_PAGE { + let index = page * hal::otp::NUM_ROWS_PER_PAGE + row; + if row == 0 { + _ = write!(uart, "\nP{:02} {:04x}: ", page, index); + } else if (index % WORDS_PER_SCREEN_LINE) == 0 { + _ = write!(uart, "\n {:04x}: ", index); + } + match hal::otp::read_raw_word(index) { + Ok(0) => { + _ = write!(uart, " ------"); + } + Ok(word) => { + _ = write!(uart, " {:06x}", word); + } + Err(hal::otp::Error::InvalidPermissions) => { + _ = write!(uart, " xxxxxx"); + } + Err(hal::otp::Error::InvalidIndex) => { + _ = write!(uart, " ??????"); + } + } + } + } + _ = writeln!(uart); + + // Do a reset into the bootloader. + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +/// Read OTP using the PAC +fn get_otp_data(uart: &mut T, otp_data: &mut hal::pac::OTP_DATA) +where + T: core::fmt::Write, +{ + _ = writeln!(uart, "Reading OTP_DATA"); + let package_id = (otp_data.chipid1().read().chipid1().bits() as u32) << 16 + | otp_data.chipid0().read().chipid0().bits() as u32; + let device_id = (otp_data.chipid3().read().chipid3().bits() as u32) << 16 + | otp_data.chipid2().read().chipid2().bits() as u32; + _ = writeln!(uart, "\tRP2350 Package ID: {:#010x}", package_id); + _ = writeln!(uart, "\tRP2350 Device ID : {:#010x}", device_id); +} + +/// Read OTP in raw mode using the PAC +/// +/// Currently this doesn't work due to SVD issues. +fn get_otp_data_raw(uart: &mut T, otp_data_raw: &mut hal::pac::OTP_DATA_RAW) +where + T: core::fmt::Write, +{ + _ = writeln!(uart, "Reading OTP_DATA_RAW"); + _ = writeln!( + uart, + "\tRP2350 Package ID: {:#010x} {:#010x}", + otp_data_raw.chipid0().read().bits(), + otp_data_raw.chipid1().read().bits() + ); + _ = writeln!( + uart, + "\tRP2350 Device ID : {:#010x} {:#010x}", + otp_data_raw.chipid2().read().bits(), + otp_data_raw.chipid3().read().bits() + ); +} + +/// Run get_sys_info with 0x0001 +fn get_sys_info_chip_info(uart: &mut T) +where + T: core::fmt::Write, +{ + let result = match hal::rom_data::sys_info_api::chip_info() { + Ok(Some(result)) => result, + Ok(None) => { + _ = writeln!(uart, "chip_info() not supported"); + return; + } + Err(e) => { + _ = writeln!(uart, "Failed to get chip info : {:?}", e); + return; + } + }; + + _ = writeln!(uart, "get_sys_info(CHIP_INFO/0x0001)"); + _ = writeln!(uart, "\tRP2350 Package ID: {:#010x}", result.package_sel); + _ = writeln!(uart, "\tRP2350 Device ID : {:#010x}", result.device_id); + _ = writeln!(uart, "\tRP2350 Wafer ID : {:#010x}", result.wafer_id); +} + +/// Run get_sys_info with 0x0004 +fn get_sys_info_cpu_info(uart: &mut T) +where + T: core::fmt::Write, +{ + let result = match hal::rom_data::sys_info_api::cpu_info() { + Ok(Some(result)) => result, + Ok(None) => { + _ = writeln!(uart, "cpu_info() not supported"); + return; + } + Err(e) => { + _ = writeln!(uart, "Failed to get cpu info: {:?}", e); + return; + } + }; + + _ = writeln!(uart, "get_sys_info(CPU_INFO/0x0004)"); + _ = writeln!( + uart, + "\tCPU Architecture: {}", + match result { + CpuInfo::Arm => "Arm", + CpuInfo::Risc => "RISC-V", + } + ); +} + +/// Run get_sys_info with 0x0008 +fn get_sys_info_flash_dev_info(uart: &mut T) +where + T: core::fmt::Write, +{ + let result = match hal::rom_data::sys_info_api::flash_dev_info() { + Ok(Some(result)) => result, + Ok(None) => { + _ = writeln!(uart, "flash_dev_info() not supported"); + return; + } + Err(e) => { + _ = writeln!(uart, "Failed to get flash device info: {:?}", e); + return; + } + }; + + _ = writeln!(uart, "get_sys_info(FLASH_DEV_INFO/0x0008)"); + let size_lookup = |value| match value { + FlashDevInfoSize::None => "None", + FlashDevInfoSize::K8 => "8K", + FlashDevInfoSize::K16 => "16K", + FlashDevInfoSize::K32 => "32K", + FlashDevInfoSize::K64 => "64K", + FlashDevInfoSize::K128 => "128K", + FlashDevInfoSize::K256 => "256K", + FlashDevInfoSize::K512 => "512K", + FlashDevInfoSize::M1 => "1M", + FlashDevInfoSize::M2 => "2M", + FlashDevInfoSize::M4 => "4M", + FlashDevInfoSize::M8 => "8M", + FlashDevInfoSize::M16 => "16M", + FlashDevInfoSize::Unknown => "Unknown", + }; + _ = writeln!(uart, "\tCS1 GPIO: {}", result.cs1_gpio()); + _ = writeln!( + uart, + "\tD8H Erase Supported: {}", + result.d8h_erase_supported() + ); + _ = writeln!(uart, "\tCS0 Size: {}", size_lookup(result.cs0_size())); + _ = writeln!(uart, "\tCS1 Size: {}", size_lookup(result.cs1_size())); +} + +/// Run get_sys_info with 0x0010 +fn get_sys_info_boot_random(uart: &mut T) +where + T: core::fmt::Write, +{ + let result = match hal::rom_data::sys_info_api::boot_random() { + Ok(Some(result)) => result, + Ok(None) => { + _ = writeln!(uart, "boot_random() not supported"); + return; + } + Err(e) => { + _ = writeln!(uart, "Failed to get random boot integer: {:?}", e); + return; + } + }; + + _ = writeln!(uart, "get_sys_info(BOOT_RANDOM/0x0010)"); + _ = writeln!(uart, "\tA random number: 0x{:32x}", result.0); +} + +/// Run get_sys_info with 0x0040; +fn get_sys_info_start_block(uart: &mut T) +where + T: core::fmt::Write, +{ + let result = match hal::rom_data::sys_info_api::boot_info() { + Ok(Some(result)) => result, + Ok(None) => { + _ = writeln!(uart, "boot_info() not supported"); + return; + } + Err(e) => { + _ = writeln!(uart, "Failed to get boot info: {:?}", e); + return; + } + }; + + _ = writeln!(uart, "get_sys_info(start_block/0x0040)"); + _ = writeln!( + uart, + "\tDiagnostic Partition: {}", + match result.diagnostic_partition { + PartitionIndex::Partition(_) => "Numbered partition", + PartitionIndex::None => "None", + PartitionIndex::Slot0 => "Slot 0", + PartitionIndex::Slot1 => "Slot 1", + PartitionIndex::Image => "Image", + PartitionIndex::Unknown => "Unknown", + } + ); + _ = writeln!( + uart, + "\tBoot Type: {}", + match result.boot_type { + BootType::Normal => "Normal", + BootType::BootSel => "bootsel", + BootType::RamImage => "RAM image", + BootType::FlashUpdate => "Flash update", + BootType::PcSp => "pc_sp", + BootType::Unknown => "Unknown", + } + ); + _ = writeln!(uart, "\tChained: {}", result.chained); + _ = writeln!(uart, "\tPartition: {}", result.partition); + _ = writeln!(uart, "\tTBYB Info: {:02x}", result.tbyb_update_info); + _ = writeln!(uart, "\tBoot Diagnostic: {:04x}", result.boot_diagnostic); + _ = writeln!( + uart, + "\tBoot Params: {:04x}, {:04x}", + result.boot_params[0], result.boot_params[1] + ); +} + +/// Run get_partition_table_info +fn get_partition_table_info(uart: &mut T) +where + T: core::fmt::Write, +{ + let mut buffer = [0u32; 16]; + let result = unsafe { + hal::rom_data::get_partition_table_info(buffer.as_mut_ptr(), buffer.len(), 0x0001) + }; + _ = writeln!( + uart, + "get_partition_table_info(PT_INFO/0x0001) -> {}", + result + ); + _ = writeln!(uart, "\tSupported Flags: {:#06x}", buffer[0]); + let partition_count = buffer[1] & 0xFF; + let has_partition_table = (buffer[1] & (1 << 8)) != 0; + let unpart = hal::block::UnpartitionedSpace::from_raw(buffer[2], buffer[3]); + _ = writeln!( + uart, + "\tNum Partitions: {} (Has Partition Table? {})", + partition_count, has_partition_table + ); + _ = writeln!(uart, "\tUnpartitioned Space: {}", unpart); + for part_idx in 0..partition_count { + let result = unsafe { + hal::rom_data::get_partition_table_info( + buffer.as_mut_ptr(), + buffer.len(), + (part_idx << 24) | 0x8010, + ) + }; + _ = writeln!( + uart, + "get_partition_table_info(PARTITION_LOCATION_AND_FLAGS|SINGLE_PARTITION/0x8010) -> {}", + result + ); + _ = writeln!(uart, "\tSupported Flags: {:#06x}", buffer[0]); + let part = hal::block::Partition::from_raw(buffer[1], buffer[2]); + _ = writeln!(uart, "\tPartition {}: {}", part_idx, part); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Tests ROM functions and reading OTP"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/rosc_as_system_clock.rs b/rp-hal/rp235x-hal-examples/src/bin/rosc_as_system_clock.rs new file mode 100644 index 0000000..96d54d9 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/rosc_as_system_clock.rs @@ -0,0 +1,337 @@ +//! # ROSC as system clock Example +//! +//! This application demonstrates how to use the ROSC as the system clock on the rp235x. +//! +//! It shows setting the frequency of the ROSC to a measured known frequency, and contains +//! helper functions to configure the ROSC drive strength to reach a desired target frequency. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use fugit::{HertzU32, RateExtU32}; +use hal::clocks::{Clock, ClockSource, ClocksManager, StoppableClock}; +use hal::pac::rosc::ctrl::FREQ_RANGE_A; +use hal::pac::{CLOCKS, ROSC}; +use hal::rosc::RingOscillator; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function attempts to find appropriate settings for the RingOscillator to reach a target +/// frequency, and then logs the actual attained frequency. +/// +/// The main reasons you'd want to use this is for power-saving in applications where precise +/// timings are not critical (you don't need to use USB peripherals for example). +/// Using the ROSC as the system clock allows under-clocking or over-clocking rp235x, and +/// it also can allow fast waking from a dormant state on the order of µs, which the XOSC cannot do. +/// +/// A motivating application for this was a flir lepton thermal camera module which +/// makes thermal images available via SPI at a rate of around 9Hz. Using the rp235xs ROSC, we +/// are able to clock out the thermal image via SPI and then enter dormant mode until the next vsync +/// interrupt wakes us again, saving some power. +#[hal::entry] +fn main() -> ! { + // Set target rosc frequency to 150Mhz + // Setting frequencies can be a matter of a bit of trial and error to see what + // actual frequencies you can easily hit. In practice, the lowest achieved with this method + // is around 32Mhz, and it seems to be able to ramp up to around 230Mhz + let desired_rosc_freq: HertzU32 = 150_000_000.Hz(); + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + led_pin.set_low().unwrap(); + + // Setup the crystal oscillator to do accurate measurements against + let xosc = hal::xosc::setup_xosc_blocking(pac.XOSC, XTAL_FREQ_HZ.Hz()).unwrap(); + + // Find appropriate settings for the desired ring oscillator frequency. + let measured_rosc_frequency = + find_target_rosc_frequency(&pac.ROSC, &pac.CLOCKS, desired_rosc_freq); + let rosc = RingOscillator::new(pac.ROSC); + + // Now initialise the ROSC with the reached frequency and set it as the system clock. + let rosc = rosc.initialize_with_freq(measured_rosc_frequency); + + let mut clocks = ClocksManager::new(pac.CLOCKS); + clocks + .system_clock + .configure_clock(&rosc, rosc.get_freq()) + .unwrap(); + + clocks + .peripheral_clock + .configure_clock(&clocks.system_clock, clocks.system_clock.freq()) + .unwrap(); + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Now we can disable the crystal oscillator and run off the ring oscillator, for power savings. + let _xosc_disabled = xosc.disable(); + // You may also wish to disable other clocks/peripherals that you don't need. + clocks.usb_clock.disable(); + clocks.gpio_output0_clock.disable(); + clocks.gpio_output1_clock.disable(); + clocks.gpio_output2_clock.disable(); + clocks.gpio_output3_clock.disable(); + clocks.adc_clock.disable(); + + // Check that desired frequency is close to the frequency speed. + // If it is, turn the LED on. If not, blink the LED. + let got_to_within_1_mhz_of_target = desired_rosc_freq + .to_kHz() + .abs_diff(measured_rosc_frequency.to_kHz()) + < 1000; + + if got_to_within_1_mhz_of_target { + // Now it's possible to easily take the ROSC dormant, to be woken by an external interrupt. + led_pin.set_high().unwrap(); + loop { + hal::arch::wfi(); + } + } else { + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(500); + led_pin.set_low().unwrap(); + delay.delay_ms(500); + } + } +} + +/// Measure the actual speed of the ROSC at the current freq_range and drive strength config +fn rosc_frequency_count_hz(clocks: &CLOCKS) -> HertzU32 { + // Wait for the frequency counter to be ready + while clocks.fc0_status().read().running().bit_is_set() { + hal::arch::nop(); + } + + // Set the speed of the reference clock in kHz. + clocks + .fc0_ref_khz() + .write(|w| unsafe { w.fc0_ref_khz().bits(XTAL_FREQ_HZ / 1000) }); + + // Corresponds to a 1ms test time, which seems to give good enough accuracy + clocks + .fc0_interval() + .write(|w| unsafe { w.fc0_interval().bits(10) }); + + // We don't really care about the min/max, so these are just set to min/max values. + clocks + .fc0_min_khz() + .write(|w| unsafe { w.fc0_min_khz().bits(0) }); + clocks + .fc0_max_khz() + .write(|w| unsafe { w.fc0_max_khz().bits(0xffffffff) }); + + // To measure rosc directly we use the value 0x03. + clocks + .fc0_src() + .write(|w| unsafe { w.fc0_src().bits(0x03) }); + + // Wait until the measurement is ready + while clocks.fc0_status().read().done().bit_is_clear() { + hal::arch::nop(); + } + + let speed_hz = clocks.fc0_result().read().khz().bits() * 1000; + speed_hz.Hz() +} + +/// Resets ROSC frequency range and stages drive strength, then increases the frequency range, +/// drive strength bits, and finally divider in order to try to come close to the desired target +/// frequency, returning the final measured ROSC frequency attained. +fn find_target_rosc_frequency( + rosc: &ROSC, + clocks: &CLOCKS, + target_frequency: HertzU32, +) -> HertzU32 { + reset_rosc_operating_frequency(rosc); + let mut div = 1; + let mut measured_rosc_frequency; + loop { + measured_rosc_frequency = rosc_frequency_count_hz(clocks); + // If it has overshot the target frequency, increase the divider and continue. + if measured_rosc_frequency > target_frequency { + div += 1; + set_rosc_div(rosc, div); + } else { + break; + } + } + loop { + measured_rosc_frequency = rosc_frequency_count_hz(clocks); + if measured_rosc_frequency > target_frequency { + // And probably want to step it down a notch? + break; + } + let can_increase = increase_drive_strength(rosc); + if !can_increase { + let can_increase_range = increase_freq_range(rosc); + if !can_increase_range { + break; + } + } + } + measured_rosc_frequency +} + +fn set_rosc_div(rosc: &ROSC, div: u32) { + assert!(div <= 32); + let div = if div == 32 { 0 } else { div }; + rosc.div().write(|w| unsafe { w.bits(0xaa0 + div) }); +} + +fn reset_rosc_operating_frequency(rosc: &ROSC) { + // Set divider to 1 + set_rosc_div(rosc, 1); + rosc.ctrl().write(|w| w.freq_range().low()); + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); +} + +fn read_freq_stage(rosc: &ROSC, stage: u8) -> u8 { + match stage { + 0 => rosc.freqa().read().ds0().bits(), + 1 => rosc.freqa().read().ds1().bits(), + 2 => rosc.freqa().read().ds2().bits(), + 3 => rosc.freqa().read().ds3().bits(), + 4 => rosc.freqb().read().ds4().bits(), + 5 => rosc.freqb().read().ds5().bits(), + 6 => rosc.freqb().read().ds6().bits(), + 7 => rosc.freqb().read().ds7().bits(), + _ => panic!("invalid frequency drive strength stage"), + } +} + +/// Increase the ROSC drive strength bits for the current freq_range +fn increase_drive_strength(rosc: &ROSC) -> bool { + const MAX_STAGE_DRIVE: u8 = 3; + // Assume div is 1, and freq_range is high + let mut stages: [u8; 8] = [0; 8]; + for (stage_index, stage) in stages.iter_mut().enumerate() { + *stage = read_freq_stage(rosc, stage_index as u8) + } + let num_stages_at_drive_level = match rosc.ctrl().read().freq_range().variant() { + Some(FREQ_RANGE_A::LOW) => 8, + Some(FREQ_RANGE_A::MEDIUM) => 6, + Some(FREQ_RANGE_A::HIGH) => 4, + Some(FREQ_RANGE_A::TOOHIGH) => panic!("Don't use TOOHIGH freq_range"), + None => { + // Start out at initial unset drive stage + return false; + } + }; + let mut next_i = 0; + for (index, x) in stages[0..num_stages_at_drive_level].windows(2).enumerate() { + if x[1] < x[0] { + next_i = index + 1; + break; + } + } + if stages[next_i] < MAX_STAGE_DRIVE { + stages[next_i] += 1; + let min = *stages[0..num_stages_at_drive_level] + .iter() + .min() + .unwrap_or(&0); + for stage in &mut stages[num_stages_at_drive_level..] { + *stage = min; + } + write_freq_stages(rosc, &stages); + true + } else { + false + } +} + +/// Sets the `freqa` and `freqb` ROSC drive strength stage registers. +fn write_freq_stages(rosc: &ROSC, stages: &[u8; 8]) { + let passwd: u32 = 0x9696 << 16; + let mut freq_a = passwd; + let mut freq_b = passwd; + for (stage_index, stage) in stages.iter().enumerate().take(4) { + freq_a |= ((*stage & 0x07) as u32) << (stage_index * 4); + } + for (stage_index, stage) in stages.iter().enumerate().skip(4) { + freq_b |= ((*stage & 0x07) as u32) << ((stage_index - 4) * 4); + } + rosc.freqa().write(|w| unsafe { w.bits(freq_a) }); + rosc.freqb().write(|w| unsafe { w.bits(freq_b) }); +} + +/// Increase the rosc frequency range up to the next step. +/// Returns a boolean to indicate whether the frequency was increased. +fn increase_freq_range(rosc: &ROSC) -> bool { + match rosc.ctrl().read().freq_range().variant() { + None => { + // Initial unset frequency range, move to LOW frequency range + rosc.ctrl().write(|w| w.freq_range().low()); + // Reset all the drive strength bits. + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); + true + } + Some(FREQ_RANGE_A::LOW) => { + // Transition from LOW to MEDIUM frequency range + rosc.ctrl().write(|w| w.freq_range().medium()); + // Reset all the drive strength bits. + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); + true + } + Some(FREQ_RANGE_A::MEDIUM) => { + // Transition from MEDIUM to HIGH frequency range + rosc.ctrl().write(|w| w.freq_range().high()); + // Reset all the drive strength bits. + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); + true + } + Some(FREQ_RANGE_A::HIGH) | Some(FREQ_RANGE_A::TOOHIGH) => { + // Already in the HIGH frequency range, and can't increase + false + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Ring Oscillator Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/spi.rs b/rp-hal/rp235x-hal-examples/src/bin/spi.rs new file mode 100644 index 0000000..04d5aae --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/spi.rs @@ -0,0 +1,131 @@ +//! # SPI Example +//! +//! This application demonstrates how to use the SPI Driver to talk to a remote +//! SPI device. +//! +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal_0_2::prelude::*; +use hal::clocks::Clock; +use hal::fugit::RateExtU32; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then performs some example +/// SPI transactions, then goes to sleep. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // These are implicitly used by the spi driver if they are in the correct mode + let spi_mosi = pins.gpio7.into_function::(); + let spi_miso = pins.gpio4.into_function::(); + let spi_sclk = pins.gpio6.into_function::(); + let spi_bus = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); + + // Exchange the uninitialised SPI driver for an initialised one + let mut spi_bus = spi_bus.init( + &mut pac.RESETS, + clocks.peripheral_clock.freq(), + 16.MHz(), + embedded_hal::spi::MODE_0, + ); + + // Write out 0, ignore return value + if spi_bus.write(&[0]).is_ok() { + // SPI write was successful + }; + + // write 50, then check the return + let send_success = spi_bus.send(50); + match send_success { + Ok(_) => { + // We succeeded, check the read value + if let Ok(_x) = spi_bus.read() { + // We got back `x` in exchange for the 0x50 we sent. + }; + } + Err(_) => todo!(), + } + + // Do a read+write at the same time. Data in `buffer` will be replaced with + // the data read from the SPI device. + let mut buffer: [u8; 4] = [1, 2, 3, 4]; + let transfer_success = spi_bus.transfer(&mut buffer); + #[allow(clippy::single_match)] + match transfer_success { + Ok(_) => {} // Handle success + Err(_) => {} // handle errors + }; + + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"SPI Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/spi_dma.rs b/rp-hal/rp235x-hal-examples/src/bin/spi_dma.rs new file mode 100644 index 0000000..d78704a --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/spi_dma.rs @@ -0,0 +1,123 @@ +//! # SPI DMA Example +//! +//! This application demonstrates how to use DMA for SPI transfers. +//! +//! The application expects the MISO and MOSI pins to be wired together so that it is able to check +//! whether the data was sent and received correctly. +//! +//! See the `Cargo.toml` file for Copyright and licence details. +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::singleton; + +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use hal::clocks::Clock; +use hal::dma::{bidirectional, DMAExt}; +use hal::fugit::RateExtU32; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Setup clocks and the watchdog. + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // Setup the pins. + let sio = hal::sio::Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Set up our SPI pins into the correct mode + let spi_mosi = pins.gpio7.into_function::(); + let spi_miso = pins.gpio4.into_function::(); + let spi_sclk = pins.gpio6.into_function::(); + let spi = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); + + // Exchange the uninitialised SPI driver for an initialised one + let spi = spi.init( + &mut pac.RESETS, + clocks.peripheral_clock.freq(), + 16_000_000u32.Hz(), + embedded_hal::spi::MODE_0, + ); + + // Initialize DMA. + let dma = pac.DMA.split(&mut pac.RESETS); + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Use DMA to transfer some bytes (single buffering). + let tx_buf = singleton!(: [u8; 16] = [0x42; 16]).unwrap(); + let rx_buf = singleton!(: [u8; 16] = [0; 16]).unwrap(); + + // Use BidirectionalConfig to simultaneously write to spi from tx_buf and read into rx_buf + let transfer = bidirectional::Config::new((dma.ch0, dma.ch1), tx_buf, spi, rx_buf).start(); + // Wait for both DMA channels to finish + let ((_ch0, _ch1), tx_buf, _spi, rx_buf) = transfer.wait(); + + // Compare buffers to see if the data was transferred correctly + for i in 0..rx_buf.len() { + if rx_buf[i] != tx_buf[i] { + // Fast blink on error + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(100); + led_pin.set_low().unwrap(); + delay.delay_ms(100); + } + } + } + + // Slow blink on success + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(500); + led_pin.set_low().unwrap(); + delay.delay_ms(500); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"SPI DMA Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/uart.rs b/rp-hal/rp235x-hal-examples/src/bin/uart.rs new file mode 100644 index 0000000..a5bfb41 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/uart.rs @@ -0,0 +1,163 @@ +//! # UART Example +//! +//! This application demonstrates how to use the UART Driver to talk to a serial +//! connection. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use hal::gpio; + +// Some things we need +use core::fmt::Write; +use embedded_hal::delay::DelayNs; +use hal::clocks::Clock; +use hal::fugit::RateExtU32; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig, ValidatedPinRx, ValidatedPinTx}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart0_pins = ( + // UART TX (characters sent from rp235x) on pin 4 (GPIO2) in Aux mode + pins.gpio2.into_function(), + // UART RX (characters received by rp235x) on pin 5 (GPIO3) in Aux mode + pins.gpio3.into_function(), + ); + let mut uart0 = hal::uart::UartPeripheral::new(pac.UART0, uart0_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Set UART1 up with some dynamic pins + + // UART TX (characters sent from rp235x) on pin 6 (GPIO4) + // + // We erase the type-state and make a dynamically typed pin. this is useful + // if you want to store it in a struct, or pass it as an argument to a + // library. + let mut uart1_tx = pins + .gpio4 + .reconfigure::() + .into_dyn_pin(); + // try and put it into UART mode (the type of the variable doesn't change) + if uart1_tx.try_set_function(gpio::DynFunction::Uart).is_err() { + panic!("Can't set pin as UART") + } + // wrap it, to prove to the UartPeripheral that it *is* in Uart mode + let Ok(uart1_tx) = ValidatedPinTx::validate(uart1_tx, &pac.UART1) else { + panic!("Can't use pin for UART 1 TX") + }; + + // UART RX (characters received by rp235x) on pin 7 (GPIO5) + // + // We erase the type-state and make a dynamically typed pin. this is useful + // if you want to store it in a struct, or pass it as an argument to a + // library. + let mut uart1_rx = pins + .gpio5 + .reconfigure::() + .into_dyn_pin(); + // try and put it into UART mode + if uart1_rx.try_set_function(gpio::DynFunction::Uart).is_err() { + panic!("Can't set pin as UART") + } + // wrap it, to prove to the UartPeripheral that it *is* in Uart mode + let Ok(uart1_rx) = ValidatedPinRx::validate(uart1_rx, &pac.UART1) else { + panic!("Can't use pin for UART 1 RX") + }; + // make a UART with our dynamic pin types + let uart1_pins = (uart1_tx, uart1_rx); + let mut uart1 = hal::uart::UartPeripheral::new(pac.UART1, uart1_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + uart0.write_full_blocking(b"UART example on UART0\r\n"); + uart1.write_full_blocking(b"UART example on UART1\r\n"); + + let mut value = 0u32; + loop { + writeln!(uart0, "UART0 says value: {value:02}\r").unwrap(); + writeln!(uart1, "UART1 says value: {value:02}\r").unwrap(); + delay.delay_ms(1000); + value += 1 + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"UART Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/uart_dma.rs b/rp-hal/rp235x-hal-examples/src/bin/uart_dma.rs new file mode 100644 index 0000000..e6b603c --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/uart_dma.rs @@ -0,0 +1,142 @@ +//! # UART DMA Example +//! +//! This application demonstrates how to use the UART peripheral with the +//! DMA controller. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +use hal::singleton; +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use hal::clocks::Clock; +use hal::dma::DMAExt; +use hal::fugit::RateExtU32; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from rp235x) on pin 1 (GPIO0) + pins.gpio0.into_function(), + // UART RX (characters received by rp235x) on pin 2 (GPIO1) + pins.gpio1.into_function(), + ); + let uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + // Initialize DMA. + let dma = pac.DMA.split(&mut pac.RESETS); + + uart.write_full_blocking(b"\r\n\r\nUART DMA echo example\r\n\r\n"); + + // In order to use DMA we need to split the UART into a RX (receive) and TX (transmit) pair + let (rx, tx) = uart.split(); + + // We can still write to the tx side of the UART after splitting + tx.write_full_blocking(b"Regular UART write\r\n"); + + // And we can DMA from a buffer into the UART + let teststring = b"DMA UART write\r\n"; + let tx_transfer = hal::dma::single_buffer::Config::new(dma.ch0, teststring, tx).start(); + + // Wait for the DMA transfer to finish so we can reuse the tx and the dma channel + let (ch0, _teststring, tx) = tx_transfer.wait(); + + // Let's test DMA RX into a buffer. + tx.write_full_blocking(b"Waiting for you to type 5 letters...\r\n"); + let rx_buf = singleton!(: [u8; 5] = [0; 5]).unwrap(); + let rx_transfer = hal::dma::single_buffer::Config::new(ch0, rx, rx_buf).start(); + let (ch0, rx, rx_buf) = rx_transfer.wait(); + + // Echo back the 5 characters the user typed + tx.write_full_blocking(b"You wrote \""); + tx.write_full_blocking(rx_buf); + tx.write_full_blocking(b"\"\r\n"); + + // Now just keep echoing anything that is received back out of TX + tx.write_full_blocking(b"Now echoing any character you write...\r\n"); + let _tx_transfer = hal::dma::single_buffer::Config::new(ch0, rx, tx).start(); + + loop { + // everything should be handled by DMA, nothing else to do + delay.delay_ms(1000); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"UART DMA Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/uart_loopback.rs b/rp-hal/rp235x-hal-examples/src/bin/uart_loopback.rs new file mode 100644 index 0000000..8e4a930 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/uart_loopback.rs @@ -0,0 +1,428 @@ +//! # UART Loopback Example +//! +//! This application tests handling UART errors. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. We assume you have connected GP0 to a TTL UART on your host +//! computer at 115200 baud. We assume that GP1 is connected to GP4, which is +//! our UART loopback connection. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use core::fmt::Write; +use embedded_hal::delay::DelayNs; +use hal::clocks::Clock; +use hal::fugit::RateExtU32; + +// UART related types +use hal::uart::{DataBits, Parity, StopBits, UartConfig}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Some sample text we can send. +/// +/// It's the same length as the UART FIFO, deliberately. +static SAMPLE32: [u8; 32] = *b"abcdefghijklmnopqrstuvwxyz012345"; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart0_pins = ( + // UART TX (characters sent from RP2350) + pins.gpio0.into_function(), + // UART RX (characters received by RP2350) + pins.gpio1.into_pull_up_input().into_function(), + ); + let mut uart0 = hal::uart::UartPeripheral::new(pac.UART0, uart0_pins, &mut pac.RESETS) + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Even), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart0.set_fifos(true); + + let uart1_pins = ( + // UART TX (characters sent from RP2350) + pins.gpio4.into_function(), + // UART RX (characters received by RP2350) + pins.gpio5.into_pull_up_input().into_function(), + ); + let mut uart1 = hal::uart::UartPeripheral::new(pac.UART1, uart1_pins, &mut pac.RESETS) + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Even), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart1.set_fifos(true); + + let mut buffer = [0u8; 128]; + + // ====================================================================== + // Single byte read/write + // ====================================================================== + + let sample = &SAMPLE32[0..1]; + _ = writeln!(uart0, "** Testing single byte write/read"); + uart1.write_full_blocking(sample); + delay.delay_ms(100); + uart0 + .read_full_blocking(&mut buffer[0..sample.len()]) + .unwrap(); + if &buffer[0..sample.len()] != sample { + _ = writeln!( + uart0, + "Failed:\n{:02x?} !=\n{:02x?}", + &buffer[0..sample.len()], + sample + ); + panic!("Test failed"); + } + _ = writeln!(uart0, "Tested OK"); + + // ====================================================================== + // FIFO backed read/write + // ====================================================================== + + let sample = &SAMPLE32[..]; + _ = writeln!(uart0, "** Testing FIFO write/read"); + uart1.write_full_blocking(sample); + delay.delay_ms(100); + uart0 + .read_full_blocking(&mut buffer[0..sample.len()]) + .unwrap(); + if &buffer[0..sample.len()] != sample { + _ = writeln!( + uart0, + "Failed:\n{:02x?} !=\n{:02x?}", + &buffer[0..sample.len()], + sample + ); + panic!("Test failed"); + } + _ = writeln!(uart0, "Tested OK"); + + // ====================================================================== + // FIFO overflow read/write + // + // Note: The Arm Primecell PL022 UART that Raspberry Pi uses has a 32-byte + // FIFO. We're about to overflow that FIFO. + // ====================================================================== + + let sample = &SAMPLE32[..]; + _ = writeln!(uart0, "** Testing FIFO overflow write/read"); + _ = writeln!(uart0, "Sending {} bytes...", sample.len() + 1); + // send 32 bytes to the receiving FIFO + uart1.write_full_blocking(sample); + // Now send one more byte to overflow the receiving FIFO. This byte is lost + // to the wind. + // + // NB: It fits into the TX FIFO because this is a 'blocking' call that + // waited for FIFO space. + uart1.write_full_blocking(&[0x00]); + // Let the TX FIFO drain. + delay.delay_ms(100); + // the first 32 bytes should read fine + uart0 + .read_full_blocking(&mut buffer[0..sample.len()]) + .unwrap(); + if &buffer[0..sample.len()] != sample { + _ = writeln!( + uart0, + "Failed:\n{:02x?} !=\n{:02x?}", + &buffer[0..sample.len()], + sample + ); + panic!("Test failed"); + } + _ = writeln!(uart0, "Got first 32 bytes OK..."); + + _ = writeln!( + uart0, + "I now want to see Overrun([]), WouldBlock, WouldBlock" + ); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + // Now send two more bytes - the first will also be flagged with an overrun error. + _ = writeln!(uart0, "Sending two more bytes..."); + uart1.write_full_blocking(&[0x01, 0x02]); + // let them transfer over + delay.delay_ms(100); + // annoyingly we see the overrun error again, then our data + _ = writeln!(uart0, "I want to see Overrun([1]), Ok(1), WouldBlock"); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + _ = writeln!(uart0, "RX: {:?}", uart0.read_raw(&mut buffer[..])); + + // ====================================================================== + // FIFO read/write with parity error + // ====================================================================== + + _ = writeln!(uart0, "** Testing FIFO read with parity errors"); + // Send three bytes with correct parity + uart1.write_full_blocking(&[0x00, 0x01, 0x02]); + delay.delay_ms(100); + // send one with bad settings + uart1 = uart1 + .disable() + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Odd), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart1.write_full_blocking(&[0x03]); + delay.delay_ms(100); + // send three more with good parity + uart1 = uart1 + .disable() + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Even), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart1.write_full_blocking(&[0x04, 0x05, 0x06]); + delay.delay_ms(100); + + _ = writeln!(uart0, "I want to see Parity error [0, 1, 2]"); + match uart0.read_raw(&mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + _ = writeln!(uart0, "I want to see RX: [4, 5, 6]"); + match uart0.read_raw(&mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + _ = writeln!(uart0, "I want to see WouldBlock"); + match uart0.read_raw(&mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + + // ====================================================================== + // FIFO backed read/write with embedded_io traits. + // ====================================================================== + + let sample = &SAMPLE32[..]; + _ = writeln!(uart0, "** Testing FIFO write/read with embedded-io"); + + embedded_io::Write::write_all(&mut uart1, sample).unwrap(); + delay.delay_ms(100); + + embedded_io::Read::read_exact(&mut uart0, &mut buffer[0..sample.len()]).unwrap(); + if &buffer[0..sample.len()] != sample { + _ = writeln!( + uart0, + "Failed:\n{:02x?} !=\n{:02x?}", + &buffer[0..sample.len()], + sample + ); + panic!("Test failed"); + } + _ = writeln!(uart0, "Tested OK"); + + // ====================================================================== + // FIFO read/write with parity error using embedded-io + // ====================================================================== + + _ = writeln!(uart0, "** Testing FIFO read with parity errors"); + // Send three bytes with correct parity + uart1.write_full_blocking(&[0x00, 0x01, 0x02]); + delay.delay_ms(100); + // send one with bad settings + uart1 = uart1 + .disable() + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Odd), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart1.write_full_blocking(&[0x03]); + delay.delay_ms(100); + // send three more with good parity + uart1 = uart1 + .disable() + .enable( + UartConfig::new( + 115200.Hz(), + DataBits::Eight, + Some(Parity::Even), + StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + uart1.write_full_blocking(&[0x04, 0x05, 0x06]); + delay.delay_ms(100); + + _ = writeln!(uart0, "I want to see RX: [0, 1, 2]"); + match embedded_io::Read::read(&mut uart0, &mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + _ = writeln!(uart0, "I want to see ParityError"); + match embedded_io::Read::read(&mut uart0, &mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + + _ = writeln!(uart0, "I want to see RX: [4, 5, 6]"); + match embedded_io::Read::read(&mut uart0, &mut buffer[..]) { + Ok(n) => { + _ = writeln!(uart0, "RX: {:?}", &buffer[0..n]); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + + _ = writeln!(uart0, "I want to see RX ready: false"); + match embedded_io::ReadReady::read_ready(&mut uart0) { + Ok(ready) => { + _ = writeln!(uart0, "RX ready: {}", ready); + } + Err(e) => { + _ = writeln!(uart0, "RXE: {:?}", e); + } + } + + // ====================================================================== + // Tests complete + // ====================================================================== + + _ = writeln!(uart0, "Tests complete. Review output for correctness."); + + // Do a reset into the bootloader. + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"UART Loopback Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + // wait about 1s for UART FIFOs to flush + for _ in 0..75_000_000 { + hal::arch::nop(); + } + // Do a reset into the bootloader. + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/usb.rs b/rp-hal/rp235x-hal-examples/src/bin/usb.rs new file mode 100644 index 0000000..51e93a4 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/usb.rs @@ -0,0 +1,153 @@ +//! # Pico USB Serial Example +//! +//! Creates a USB Serial device on a Pico board, with the USB driver running in +//! the main thread. +//! +//! This will create a USB Serial device echoing anything it receives. Incoming +//! ASCII characters are converted to upercase, so you can tell it is working +//! and not just local-echo! +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use core::fmt::Write; +use heapless::String; + +// USB Device support +use usb_device::{class_prelude::*, prelude::*}; + +// USB Communications Class Device support +use usbd_serial::SerialPort; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Set up the USB driver + let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new( + pac.USB, + pac.USB_DPRAM, + clocks.usb_clock, + true, + &mut pac.RESETS, + )); + + // Set up the USB Communications Class Device driver + let mut serial = SerialPort::new(&usb_bus); + + // Create a USB device with a fake VID and PID + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .strings(&[StringDescriptors::default() + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST")]) + .unwrap() + .device_class(2) // from: https://www.usb.org/defined-class-codes + .build(); + + let mut said_hello = false; + loop { + // A welcome message at the beginning + if !said_hello && timer.get_counter().ticks() >= 2_000_000 { + said_hello = true; + let _ = serial.write(b"Hello, World!\r\n"); + + let time = timer.get_counter().ticks(); + let mut text: String<64> = String::new(); + writeln!(&mut text, "Current timer ticks: {}", time).unwrap(); + + // This only works reliably because the number of bytes written to + // the serial port is smaller than the buffers available to the USB + // peripheral. In general, the return value should be handled, so that + // bytes not transferred yet don't get lost. + let _ = serial.write(text.as_bytes()); + } + + // Check for new data + if usb_dev.poll(&mut [&mut serial]) { + let mut buf = [0u8; 64]; + match serial.read(&mut buf) { + Err(_e) => { + // Do nothing + } + Ok(0) => { + // Do nothing + } + Ok(count) => { + // Convert to upper case + buf.iter_mut().take(count).for_each(|b| { + b.make_ascii_uppercase(); + }); + // Send back to the host + let mut wr_ptr = &buf[..count]; + while !wr_ptr.is_empty() { + match serial.write(wr_ptr) { + Ok(len) => wr_ptr = &wr_ptr[len..], + // On error, just drop unwritten data. + // One possible error is Err(WouldBlock), meaning the USB + // write buffer is full. + Err(_) => break, + }; + } + } + } + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"USB Serial Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/vector_table.rs b/rp-hal/rp235x-hal-examples/src/bin/vector_table.rs new file mode 100644 index 0000000..4149c56 --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/vector_table.rs @@ -0,0 +1,191 @@ +//! # RAM Vector Table example +//! +//! This application demonstrates how to create a new Interrupt Vector Table in RAM. +//! To demonstrate the extra utility of this, we also replace an entry in the Vector Table +//! with a new one. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use core::cell::RefCell; +use critical_section::Mutex; +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::StatefulOutputPin; +use hal::fugit::MicrosDurationU32; +use hal::timer::Alarm; + +use hal::pac::interrupt; +use hal::vector_table::VectorTable; +use static_cell::ConstStaticCell; + +// Memory that will hold our vector table in RAM +static RAM_VTABLE: ConstStaticCell = ConstStaticCell::new(VectorTable::new()); + +// Give our LED and Alarm a type alias to make it easier to refer to them +type LedAndAlarm = ( + hal::gpio::Pin, + hal::timer::Alarm0, +); + +// Place our LED and Alarm type in a static variable, so we can access it from interrupts +static LED_AND_ALARM: Mutex>> = Mutex::new(RefCell::new(None)); + +// Period that each of the alarms will be set for - 1 second and 300ms respectively +const SLOW_BLINK_INTERVAL_US: MicrosDurationU32 = MicrosDurationU32::secs(1); +const FAST_BLINK_INTERVAL_US: MicrosDurationU32 = MicrosDurationU32::millis(300); + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Need to make a reference to the Peripheral Base at this scope to avoid confusing the borrow checker + let ppb = &mut pac.PPB; + let ram_vtable = RAM_VTABLE.take(); + + // Copy the vector table that cortex_m_rt produced into the RAM vector table + ram_vtable.init(ppb); + // Replace the function that is called on Alarm0 interrupts with a new one + ram_vtable.register_handler( + hal::pac::Interrupt::TIMER0_IRQ_0 as usize, + timer_irq0_replacement, + ); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // Create simple delay + let mut delay = hal::Timer::new_timer1(pac.TIMER1, &mut pac.RESETS, &clocks); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let led_pin = pins.gpio25.into_push_pull_output(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + critical_section::with(|cs| { + let mut alarm = timer.alarm_0().unwrap(); + // Schedule an alarm in 1 second + let _ = alarm.schedule(SLOW_BLINK_INTERVAL_US); + // Enable generating an interrupt on alarm + alarm.enable_interrupt(); + // Move alarm into ALARM, so that it can be accessed from interrupts + LED_AND_ALARM.borrow(cs).replace(Some((led_pin, alarm))); + }); + // Unmask the timer0 IRQ so that it will generate an interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::TIMER0_IRQ_0); + } + + // After 5 seconds, switch to our modified vector rable + delay.delay_ms(5000); + unsafe { + critical_section::with(|_| { + ram_vtable.activate(ppb); + }); + } + + loop { + // Wait for an interrupt to fire before doing any more work + hal::arch::wfi(); + } +} + +// Regular interrupt handler for Alarm0. The `interrupt` macro will perform some transformations to ensure +// that this interrupt entry ends up in the vector table. +#[interrupt] +fn TIMER0_IRQ_0() { + critical_section::with(|cs| { + // Temporarily take our LED_AND_ALARM + let ledalarm = LED_AND_ALARM.borrow(cs).take(); + if let Some((mut led, mut alarm)) = ledalarm { + // Clear the alarm interrupt or this interrupt service routine will keep firing + alarm.clear_interrupt(); + // Schedule a new alarm after SLOW_BLINK_INTERVAL_US have passed (1 second) + let _ = alarm.schedule(SLOW_BLINK_INTERVAL_US); + // Blink the LED so we know we hit this interrupt + led.toggle().unwrap(); + // Return LED_AND_ALARM into our static variable + LED_AND_ALARM + .borrow(cs) + .replace_with(|_| Some((led, alarm))); + } + }); +} + +// This is the function we will use to replace TIMER_IRQ_0 in our RAM Vector Table +extern "C" fn timer_irq0_replacement() { + critical_section::with(|cs| { + let ledalarm = LED_AND_ALARM.borrow(cs).take(); + if let Some((mut led, mut alarm)) = ledalarm { + // Clear the alarm interrupt or this interrupt service routine will keep firing + alarm.clear_interrupt(); + // Schedule a new alarm after FAST_BLINK_INTERVAL_US have passed (300 milliseconds) + let _ = alarm.schedule(FAST_BLINK_INTERVAL_US); + led.toggle().unwrap(); + // Return LED_AND_ALARM into our static variable + LED_AND_ALARM + .borrow(cs) + .replace_with(|_| Some((led, alarm))); + } + }); +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Interrupt Vector Table Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-examples/src/bin/watchdog.rs b/rp-hal/rp235x-hal-examples/src/bin/watchdog.rs new file mode 100644 index 0000000..51e186b --- /dev/null +++ b/rp-hal/rp235x-hal-examples/src/bin/watchdog.rs @@ -0,0 +1,114 @@ +//! # Watchdog Example +//! +//! This application demonstrates how to use the rp235x Watchdog. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use hal::fugit::ExtU32; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. After a period of time, the watchdog will kick in to reset +/// the CPU. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure an LED so we can show the current state of the watchdog + let mut led_pin = pins.gpio25.into_push_pull_output(); + + // Set the LED high for 2 seconds so we know when we're about to start the watchdog + led_pin.set_high().unwrap(); + delay.delay_ms(2000); + + // Set to watchdog to reset if it's not reloaded within 1.05 seconds, and start it + watchdog.start(1_050.millis()); + + // Blink once a second for 5 seconds, refreshing the watchdog timer once a second to avoid a reset + for _ in 1..=5 { + led_pin.set_low().unwrap(); + delay.delay_ms(500); + led_pin.set_high().unwrap(); + delay.delay_ms(500); + watchdog.feed(); + } + + // Blink 5 times per second, not feeding the watchdog. + // The processor should reset in 1.05 seconds, or 5 blinks time + loop { + led_pin.set_low().unwrap(); + delay.delay_ms(100); + led_pin.set_high().unwrap(); + delay.delay_ms(100); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Watchdog Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp-hal/rp235x-hal-macros/.gitignore b/rp-hal/rp235x-hal-macros/.gitignore new file mode 100644 index 0000000..ff47c2d --- /dev/null +++ b/rp-hal/rp235x-hal-macros/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/rp-hal/rp235x-hal-macros/Cargo.toml b/rp-hal/rp235x-hal-macros/Cargo.toml new file mode 100644 index 0000000..6bef220 --- /dev/null +++ b/rp-hal/rp235x-hal-macros/Cargo.toml @@ -0,0 +1,20 @@ +[package] +description = "Macros used by rp235x-hal" +license = "MIT OR Apache-2.0" +name = "rp235x-hal-macros" +readme = "README.md" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/rp-rs/rp-hal" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +proc-macro2 = "1.0" + +[dependencies.syn] +features = ["full"] +version = "2.0" + diff --git a/rp-hal/rp235x-hal-macros/LICENSE-APACHE b/rp-hal/rp235x-hal-macros/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rp-hal/rp235x-hal-macros/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp-hal/rp235x-hal-macros/LICENSE-MIT b/rp-hal/rp235x-hal-macros/LICENSE-MIT new file mode 100644 index 0000000..6e052e3 --- /dev/null +++ b/rp-hal/rp235x-hal-macros/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp-hal/rp235x-hal-macros/NOTICE b/rp-hal/rp235x-hal-macros/NOTICE new file mode 100644 index 0000000..790ecb1 --- /dev/null +++ b/rp-hal/rp235x-hal-macros/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp-hal/rp235x-hal-macros/README.md b/rp-hal/rp235x-hal-macros/README.md new file mode 100644 index 0000000..e07be92 --- /dev/null +++ b/rp-hal/rp235x-hal-macros/README.md @@ -0,0 +1,22 @@ +# `rp235x-hal-macros` + +Macros used by rp235x-hal. + +## Entry macro + +Extension of the `cortex-m-rt` `#[entry]` with rp235x specific initialization code. + +Currently, it just unlocks all spinlocks before calling the entry function, and +sets up the Double Precision and GPIO Co-Procesors. + +# License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/rp-hal/rp235x-hal-macros/src/lib.rs b/rp-hal/rp235x-hal-macros/src/lib.rs new file mode 100644 index 0000000..ad2a255 --- /dev/null +++ b/rp-hal/rp235x-hal-macros/src/lib.rs @@ -0,0 +1,76 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::{parse, parse_macro_input, Item, ItemFn, Stmt}; + +#[proc_macro_attribute] +pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { + let mut f = parse_macro_input!(input as ItemFn); + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + + let clear_locks: TokenStream = quote!(unsafe { + const SIO_BASE: u32 = 0xd0000000; + const SPINLOCK0_PTR: *mut u32 = (SIO_BASE + 0x100) as *mut u32; + const SPINLOCK_COUNT: usize = 32; + for i in 0..SPINLOCK_COUNT { + SPINLOCK0_PTR.wrapping_add(i).write_volatile(1); + } + #[cfg(target_arch = "arm")] + { + // Enable the Double-Co-Pro and the GPIO Co-Pro in the CPACR register. + // We have to do this early, before there's a chance we might call + // any accelerated functions. + const SCB_CPACR_PTR: *mut u32 = 0xE000_ED88 as *mut u32; + const SCB_CPACR_FULL_ACCESS: u32 = 0b11; + // Do a R-M-W, because the FPU enable is here and that's already been enabled + let mut temp = SCB_CPACR_PTR.read_volatile(); + // DCP Co-Pro is 4, two-bits per entry + temp |= SCB_CPACR_FULL_ACCESS << (4 * 2); + // GPIO Co-Pro is 0, two-bits per entry + temp |= SCB_CPACR_FULL_ACCESS << (0 * 2); + SCB_CPACR_PTR.write_volatile(temp); + // Don't allow any DCP code to be moved before this fence. + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + } + }) + .into(); + let clear_locks = parse_macro_input!(clear_locks as Stmt); + + // statics must stay first so cortex_m_rt::entry still finds them + let stmts = insert_after_static(f.block.stmts, clear_locks); + f.block.stmts = stmts; + + quote!( + #[rp235x_hal::arch_entry] + #f + ) + .into() +} + +/// Insert new statements after initial block of statics +fn insert_after_static(stmts: impl IntoIterator, insert: Stmt) -> Vec { + let mut istmts = stmts.into_iter(); + let mut stmts = vec![]; + for stmt in istmts.by_ref() { + match stmt { + Stmt::Item(Item::Static(var)) => { + stmts.push(Stmt::Item(Item::Static(var))); + } + _ => { + stmts.push(insert); + stmts.push(stmt); + break; + } + } + } + stmts.extend(istmts); + + stmts +} diff --git a/rp-hal/rp235x-hal/.gitignore b/rp-hal/rp235x-hal/.gitignore new file mode 100644 index 0000000..ff47c2d --- /dev/null +++ b/rp-hal/rp235x-hal/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/rp-hal/rp235x-hal/CHANGELOG.md b/rp-hal/rp235x-hal/CHANGELOG.md new file mode 100644 index 0000000..65fab2d --- /dev/null +++ b/rp-hal/rp235x-hal/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Changed + +- First version + diff --git a/rp-hal/rp235x-hal/Cargo.toml b/rp-hal/rp235x-hal/Cargo.toml new file mode 100644 index 0000000..a9c6a39 --- /dev/null +++ b/rp-hal/rp235x-hal/Cargo.toml @@ -0,0 +1,101 @@ +[package] +authors = ["The rp-rs Developers"] +categories = ["embedded", "hardware-support", "no-std", "no-std::no-alloc"] +description = "A Rust Embeded-HAL impl for the RP2350 microcontroller" +edition = "2021" +homepage = "https://github.com/rp-rs/rp-hal" +keywords = ["embedded", "hal", "raspberry-pi", "rp2350", "embedded-hal"] +license = "MIT OR Apache-2.0" +name = "rp235x-hal" +repository = "https://github.com/rp-rs/rp-hal" +rust-version = "1.79" +version = "0.2.0" + +[package.metadata.docs.rs] +features = ["rt", "defmt", "rtic-monotonic"] +targets = ["thumbv8m.main-none-eabihf"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# Non-optional dependencies. Keep these sorted by name. +bitfield = "0.14.0" +critical-section = "1.2.0" +embedded-dma = "0.2.0" +embedded-hal = "1.0.0" +embedded-hal-async = "1.0.0" +embedded-hal-nb = "1.0.0" +embedded-io = "0.6.1" +embedded_hal_0_2 = {package = "embedded-hal", version = "0.2.5", features = ["unproven"]} +frunk = {version = "0.4.1", default-features = false} +fugit = "0.3.6" +gcd = ">=2.1,<3.0" +itertools = {version = "0.13.0", default-features = false} +nb = "1.0" +paste = "1.0" +pio = "0.2.0" +rand_core = "0.6.3" +rp-binary-info = {version = "0.1.0", path = "../rp-binary-info"} +rp-hal-common = {version = "0.1.0", path = "../rp-hal-common"} +rp235x-hal-macros = {version = "0.1.0", path = "../rp235x-hal-macros"} +rp235x-pac = {version = "0.1.0", features = ["critical-section", "rt"]} +sha2-const-stable = "0.1" +usb-device = "0.3.2" +vcell = "0.1" +void = {version = "1.0.2", default-features = false} + +# Optional dependencies. Keep these sorted by name. +defmt = {version = ">=0.2.0, <0.4", optional = true} +i2c-write-iter = {version = "1.0.0", features = ["async"], optional = true} +rtic-monotonic = {version = "1.0.0", optional = true} + +[target.'thumbv8m.main-none-eabihf'.dependencies] +cortex-m = "0.7.2" +cortex-m-rt = "0.7" + +[target.riscv32imac-unknown-none-elf.dependencies] +riscv = "0.11" +riscv-rt = "0.12" + +[dev-dependencies] +# Non-optional dependencies. Keep these sorted by name. +pio-proc = "0.2.0" +rand = {version = "0.8.5", default-features = false} + +# Optional dependencies. Keep these sorted by name. +# None + +[features] +# Enable the boot-up code from the arch runtime +rt = ["rp235x-pac/rt"] + +# Memoize(cache) ROM function pointers on first use to improve performance +rom-func-cache = [] + +# critical section that is safe for multicore use +critical-section-impl = ["critical-section/restore-state-u8"] + +# Implement `defmt::Format` for several types. +defmt = ["dep:defmt"] + +# Implement `rtic_monotonic::Monotonic` based on the RP235x timer peripheral +rtic-monotonic = ["dep:rtic-monotonic"] + +# Implement `i2c-write-iter` traits +i2c-write-iter = ["dep:i2c-write-iter"] + +# Use DCP to accelerate some (but not all) f64 operations. +# +# If you really want to save every last micro-amp, and know you aren't doing any +# f64 operations, you can disable this feature (which is on by default) and then +# manually disable the DCP by either clearing the bits we set for you in the +# CPACR register, or changing the #[entry] macro to not set those bits. +# +# Almost everyone will want this on, but we let the BSPs make that choice. +dcp-fast-f64 = [] + +# Add a binary-info header block containing picotool-compatible metadata. +# +# Requires 'rt' so that the vector table is correctly sized and therefore the +# header is within reach of picotool. +binary-info = ["rt", "rp-binary-info/binary-info"] diff --git a/rp-hal/rp235x-hal/README.md b/rp-hal/rp235x-hal/README.md new file mode 100644 index 0000000..1e3318f --- /dev/null +++ b/rp-hal/rp235x-hal/README.md @@ -0,0 +1,133 @@ + +
+

+ + Logo + + +

rp-hal

+ +

+ High-level Rust drivers for the Raspberry Silicon RP2350 Microcontroller +
+ Explore the API docs » +
+
+ View Demos + · + Report a Bug + · + Chat on Matrix +

+

+ + + + +
+

Table of Contents

+
    +
  1. Introduction
  2. +
  3. Getting Started
  4. +
  5. Roadmap
  6. +
  7. Contributing
  8. +
  9. License
  10. +
  11. Contact
  12. +
  13. Acknowledgements
  14. +
+
+ + +## Introduction + +This is the `rp235x-hal` package - a library crate of high-level Rust drivers +for the Raspberry Silicon RP2350 microcontroller, along with a collection of +non-board specific example programs for you to study. You should use this crate +in your application if you want to write code for the RP2350 microcontroller. +The *HAL* in the name standards for *Hardware Abstraction Layer*, and comes from +the fact that many of the drivers included implement the generic +hardware-abstraction interfaces defined in the Rust Embedded Working Group's +[embedded-hal](https://github.com/rust-embedded/embedded-hal) crate. + +We also provide a series of [*Board Support Package* (BSP) crates][BSPs], which take +this HAL crate and pre-configure the pins according to a specific PCB design. If +you are using one of the supported boards, you should use one of those crates in +preference, and return here to see documentation about specific peripherals on +the RP2350 and how to use them. See the `boards` folder in +https://github.com/rp-rs/rp-hal-boards/ for more details. + +[BSPs]: https://github.com/rp-rs/rp-hal-boards/ + +Some of the source code herein refers to the "RP2350 Datasheet". This can be +found at . + + +## Getting Started + +To include this crate in your project, amend your `Cargo.toml` file to include + +```toml +rp235x-hal = "*" +``` + +To obtain a copy of the source code (e.g. if you want to propose a bug-fix or +new feature, or simply to study the code), run: + +```console +$ git clone https://github.com/rp-rs/rp-hal.git +``` + +For details on how to program an RP2350 microcontroller, see the [top-level +rp-hal README](https://github.com/rp-rs/rp-hal/). + + +## Roadmap + +NOTE This HAL is under active development. As such, it is likely to remain +volatile until a 1.0.0 release. + +See the [open issues](https://github.com/rp-rs/rp-hal/issues) for a list of +proposed features (and known issues). + +### Implemented traits + +This crate aims to implement all traits from embedded-hal, both version +0.2 and 1.0. They can be used at the same time, so you can upgrade drivers +incrementally. + + +## Contributing + +Contributions are what make the open source community such an amazing place to +be learn, inspire, and create. Any contributions you make are **greatly +appreciated**. + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + + +## License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + + +## Contact + +* Project Link: [https://github.com/rp-rs/rp-hal/issues](https://github.com/rp-rs/rp-hal/issues) +* Matrix: [#rp-rs:matrix.org](https://matrix.to/#/#rp-rs:matrix.org) + + +## Acknowledgements + +* [Othneil Drew's README template](https://github.com/othneildrew) diff --git a/rp-hal/rp235x-hal/src/adc.rs b/rp-hal/rp235x-hal/src/adc.rs new file mode 100644 index 0000000..f88c684 --- /dev/null +++ b/rp-hal/rp235x-hal/src/adc.rs @@ -0,0 +1,950 @@ +//! Analog-Digital Converter (ADC) +//! +//! See [Section 12.4](https://rptl.io/rp2350-datasheet#section_adc) of the datasheet for more details +//! +//! ## Usage +//! +//! Capture ADC reading from a pin: + +//! ```no_run +//! // Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +//! use embedded_hal_0_2::adc::OneShot; +//! use rp235x_hal::{self as hal, adc::Adc, adc::AdcPin, gpio::Pins, Sio}; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Configure one of the pins as an ADC input +//! let mut adc_pin_0 = AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); +//! // Read the ADC counts from the ADC channel +//! let pin_adc_counts: u16 = adc.read(&mut adc_pin_0).unwrap(); +//! ``` +//! +//! Capture ADC reading from temperature sensor. Note that this needs conversion to be a real-world temperature. +//! +//! ```no_run +//! // Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +//! use embedded_hal_0_2::adc::OneShot; +//! use rp235x_hal::{self as hal, adc::Adc, gpio::Pins, Sio}; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Enable the temperature sensor +//! let mut temperature_sensor = adc.take_temp_sensor().unwrap(); +//! // Read the ADC counts from the ADC channel +//! let temperature_adc_counts: u16 = adc.read(&mut temperature_sensor).unwrap(); +//! ``` +//! +//! See [examples/adc.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/adc.rs) and +//! [pimoroni_pico_explorer_showcase.rs](https://github.com/rp-rs/rp-hal-boards/tree/main/boards/pimoroni-pico-explorer/examples/pimoroni_pico_explorer_showcase.rs) for more complete examples +//! +//! ### Free running mode with FIFO +//! +//! In free-running mode the ADC automatically captures samples in regular intervals. +//! The samples are written to a FIFO, from which they can be retrieved. +//! +//! ```no_run +//! # use rp235x_hal::{self as hal, adc::Adc, gpio::Pins, pac, Sio}; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Enable the temperature sensor +//! let mut temperature_sensor = adc.take_temp_sensor().unwrap(); +//! +//! // Configure & start capturing to the fifo: +//! let mut fifo = adc +//! .build_fifo() +//! .clock_divider(0, 0) // sample as fast as possible (500ksps. This is the default) +//! .set_channel(&mut temperature_sensor) +//! .start(); +//! +//! loop { +//! if fifo.len() > 0 { +//! // Read one captured ADC sample from the FIFO: +//! let temperature_adc_counts: u16 = fifo.read(); +//! } +//! } +//! ``` +//! See [examples/adc_fifo_poll.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/adc_fifo_poll.rs) for a more complete example. +//! +//! ### Using DMA +//! +//! When the ADC is in free-running mode, it's possible to use DMA to transfer data from the FIFO elsewhere, without having to read the FIFO manually. +//! +//! This requires a number of steps: +//! 1. Build an `AdcFifo`, with DMA enabled ([`AdcFifoBuilder::enable_dma`]) +//! 2. Use [`AdcFifoBuilder::prepare`] instead of [`AdcFifoBuilder::start`], so that the FIFO is created in `paused` state +//! 3. Start a DMA transfer ([`dma::single_buffer::Transfer`], [`dma::double_buffer::Transfer`], ...), using the [`AdcFifo::dma_read_target`] as the source (`from` parameter) +//! 4. Finally unpause the FIFO by calling [`AdcFifo::resume`], to start capturing +//! +//! Example: +//! ```no_run +//! use rp235x_hal::{self as hal, singleton, adc::Adc, gpio::Pins, pac, Sio, dma::{single_buffer, DMAExt}}; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS); +//! let dma = peripherals.DMA.split(&mut peripherals.RESETS); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Enable the temperature sensor +//! let mut temperature_sensor = adc.take_temp_sensor().unwrap(); +//! +//! // Configure & start capturing to the fifo: +//! let mut fifo = adc.build_fifo() +//! .clock_divider(0, 0) // sample as fast as possible (500ksps. This is the default) +//! .set_channel(&mut temperature_sensor) +//! .enable_dma() +//! .prepare(); +//! +//! // Set up a buffer, where the samples should be written: +//! let buf = singleton!(: [u16; 500] = [0; 500]).unwrap(); +//! +//! // Start DMA transfer +//! let transfer = single_buffer::Config::new(dma.ch0, fifo.dma_read_target(), buf).start(); +//! +//! // Resume the FIFO to start capturing +//! fifo.resume(); +//! +//! // Wait for the transfer to complete: +//! let (ch, adc_read_target, buf) = transfer.wait(); +//! +//! // do something with `buf` (it now contains 500 samples read from the ADC) +//! //... +//! ``` +//! //! See [examples/adc_fifo_dma.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/adc_fifo_dma.rs) for a more complete example. +//! +//! ### Free running mode without FIFO +//! +//! While free-running mode is usually used in combination with a FIFO, there are +//! use cases where it can be used without. For example, if you want to be able to +//! get the latest available sample at any point in time, and without waiting 96 ADC clock +//! cycles (2µs). +//! +//! In this case, you can just enable free-running mode on it's own. The ADC will +//! continuously do ADC conversions. The ones not read will just be discarded, but it's +//! always possible to read the latest value, without additional delay: +//! +//! ```no_run +//! use rp235x_hal::{self as hal, adc::Adc, adc::AdcPin, gpio::Pins, Sio}; +//! // Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +//! use embedded_hal_0_2::adc::OneShot; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Configure one of the pins as an ADC input +//! let mut adc_pin_0 = AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); +//! // Enable free-running mode +//! adc.free_running(&adc_pin_0); +//! // Read the ADC counts from the ADC channel whenever necessary +//! loop { +//! let pin_adc_counts: u16 = adc.read_single(); +//! // Do time critical stuff +//! } +//! ``` + +use core::convert::Infallible; +use core::marker::PhantomData; +// Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +use embedded_hal_0_2::adc::{Channel, OneShot}; + +use crate::{ + dma, + gpio::{ + bank0::{Gpio26, Gpio27, Gpio28, Gpio29}, + AnyPin, DynBankId, DynPinId, Function, OutputEnableOverride, Pin, PullType, ValidFunction, + }, + pac::{dma::ch::ch_ctrl_trig::TREQ_SEL_A, ADC, RESETS}, + resets::SubsystemReset, + typelevel::Sealed, +}; + +const TEMPERATURE_SENSOR_CHANNEL: u8 = 4; + +/// The pin was invalid for the requested operation +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InvalidPinError; + +/// A pin locked in use with the ADC. +pub struct AdcPin

+where + P: AnyPin, +{ + pin: P, + saved_output_disable: bool, + saved_input_enable: bool, +} + +impl

AdcPin

+where + P: AnyPin, +{ + /// Captures the pin to be used with an ADC and disables its digital circuitry. + pub fn new(pin: P) -> Result { + let pin_id = pin.borrow().id(); + if (26..=29).contains(&pin_id.num) && pin_id.bank == DynBankId::Bank0 { + let mut p = pin.into(); + let (od, ie) = (p.get_output_disable(), p.get_input_enable()); + p.set_output_enable_override(OutputEnableOverride::Disable); + p.set_input_enable(false); + Ok(Self { + pin: P::from(p), + saved_output_disable: od, + saved_input_enable: ie, + }) + } else { + Err(InvalidPinError) + } + } + + /// Release the pin and restore its digital circuitry's state. + pub fn release(self) -> P { + let mut p = self.pin.into(); + p.set_output_disable(self.saved_output_disable); + p.set_input_enable(self.saved_input_enable); + P::from(p) + } + + /// Returns the ADC channel of this AdcPin. + pub fn channel(&self) -> u8 { + let pin_id = self.pin.borrow().id(); + // Self::new() makes sure that this is a valid channel number + pin_id.num - 26 + } +} + +/// Trait for entities that can be used as ADC channels. +/// +/// This is implemented by [`AdcPin`] and by [`TempSense`]. +/// The trait is sealed and can't be implemented in other crates. +pub trait AdcChannel: Sealed { + /// Get the channel id used to configure the ADC peripheral. + fn channel(&self) -> u8; +} + +impl Sealed for AdcPin

{} +impl AdcChannel for AdcPin

{ + fn channel(&self) -> u8 { + self.channel() + } +} + +impl Sealed for TempSense {} +impl AdcChannel for TempSense { + fn channel(&self) -> u8 { + TEMPERATURE_SENSOR_CHANNEL + } +} + +macro_rules! channel { + ($pin:ident, $channel:expr) => { + impl Channel for AdcPin> + where + $pin: crate::gpio::ValidFunction, + { + type ID = u8; // ADC channels are identified numerically + + fn channel() -> u8 { + $channel + } + } + }; +} + +channel!(Gpio26, 0); +channel!(Gpio27, 1); +channel!(Gpio28, 2); +channel!(Gpio29, 3); + +impl Channel for AdcPin> +where + DynPinId: crate::gpio::ValidFunction, +{ + type ID = (); // ADC channels are identified at run time + fn channel() {} +} + +/// Internal temperature sensor type +pub struct TempSense { + __private: (), +} + +impl Channel for TempSense { + type ID = u8; // ADC channels are identified numerically + + fn channel() -> u8 { + TEMPERATURE_SENSOR_CHANNEL + } +} + +/// Analog to Digital Convertor (ADC). +/// +/// Represents an ADC within the RP2350. Each ADC has multiple channels, and each +/// channel is either associated with a specific GPIO pin or attached to the internal +/// temperature sensor. You should put the relevant pin into ADC mode by creating an +/// [`AdcPin`] object with it, or you can put the ADC into `Temperature Sensing Mode` +/// by calling [`Adc::take_temp_sensor()`]. Either way, the resulting objects can be +/// passed to the [`OneShot::read()`][a] trait method to actually do the read. +/// +/// [a]: embedded_hal_0_2::adc::OneShot::read +pub struct Adc { + device: ADC, +} + +impl Adc { + /// Create new adc struct and bring up adc + pub fn new(device: ADC, resets: &mut RESETS) -> Self { + device.reset_bring_down(resets); + device.reset_bring_up(resets); + + // Enable adc + device.cs().write(|w| w.en().set_bit()); + + // Wait for adc ready + while !device.cs().read().ready().bit_is_set() {} + + Self { device } + } + + /// Free underlying register block + pub fn free(self) -> ADC { + self.device + } + + /// Read the most recently sampled ADC value + /// + /// This function does not wait for the current conversion to finish. + /// If a conversion is still in progress, it returns the result of the + /// previous one. + /// + /// It also doesn't trigger a new conversion. + pub fn read_single(&self) -> u16 { + self.device.result().read().result().bits() + } + + /// Enable temperature sensor, returns a channel to use. + /// + /// This can only be done once before calling [`Adc::disable_temp_sensor()`]. If the sensor has already + /// been enabled, this method will panic. + #[deprecated( + note = "This method may panic, use `take_temp_sensor()` instead.", + since = "0.9.0" + )] + pub fn enable_temp_sensor(&mut self) -> TempSense { + self.take_temp_sensor() + .expect("Temp sensor is already enabled.") + } + + /// Enable temperature sensor, returns a channel to use + /// + /// If the sensor has already been enabled, this method returns `None`. + pub fn take_temp_sensor(&mut self) -> Option { + let mut disabled = false; + self.device.cs().modify(|r, w| { + disabled = r.ts_en().bit_is_clear(); + // if bit was already set, this is a nop + w.ts_en().set_bit() + }); + disabled.then_some(TempSense { __private: () }) + } + + /// Disable temperature sensor, consumes channel + pub fn disable_temp_sensor(&mut self, _: TempSense) { + self.device.cs().modify(|_, w| w.ts_en().clear_bit()); + } + + /// Start configuring free-running mode, and set up the FIFO + /// + /// The [`AdcFifoBuilder`] returned by this method can be used + /// to configure capture options, like sample rate, channels to + /// capture from etc. + /// + /// Capturing is started by calling [`AdcFifoBuilder::start`], which + /// returns an [`AdcFifo`] to read from. + pub fn build_fifo(&mut self) -> AdcFifoBuilder<'_, u16> { + AdcFifoBuilder { + adc: self, + marker: PhantomData, + } + } + + /// Enable free-running mode by setting the start_many flag. + pub fn free_running(&mut self, pin: &dyn AdcChannel) { + self.device.cs().modify(|_, w| { + unsafe { + w.ainsel().bits(pin.channel()); + } + w.start_many().set_bit(); + w + }); + } + + /// Disable free-running mode by unsetting the start_many flag. + pub fn stop(&mut self) { + self.device.cs().modify(|_, w| w.start_many().clear_bit()); + } + + fn inner_read(&mut self, chan: u8) -> u16 { + self.wait_ready(); + + self.device + .cs() + .modify(|_, w| unsafe { w.ainsel().bits(chan).start_once().set_bit() }); + + self.wait_ready(); + + self.read_single() + } + + /// Wait for the ADC to become ready. + /// + /// Also returns immediately if start_many is set, to avoid indefinite blocking. + pub fn wait_ready(&self) { + while !self.is_ready_or_free_running() { + core::hint::spin_loop(); + } + } + + fn is_ready_or_free_running(&self) -> bool { + let cs = self.device.cs().read(); + cs.ready().bit_is_set() || cs.start_many().bit_is_set() + } + + /// Returns true if the ADC is ready for the next conversion. + /// + /// This implies that any previous conversion has finished. + pub fn is_ready(&self) -> bool { + self.device.cs().read().ready().bit_is_set() + } +} + +// Implementation for TempSense and type-checked pins +impl OneShot for Adc +where + WORD: From, + SRC: Channel, +{ + type Error = Infallible; + + fn read(&mut self, _pin: &mut SRC) -> nb::Result { + let chan = SRC::channel(); + + Ok(self.inner_read(chan).into()) + } +} + +// Implementation for dyn-pins +impl OneShot>> for Adc +where + WORD: From, + F: Function, + M: PullType, + DynPinId: ValidFunction, + AdcPin>: Channel, +{ + type Error = Infallible; + + fn read(&mut self, pin: &mut AdcPin>) -> nb::Result { + Ok(self.inner_read(pin.channel()).into()) + } +} + +/// Used to configure & build an [`AdcFifo`] +/// +/// See [`Adc::build_fifo`] for details, as well as the `adc_fifo_*` [examples](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal/examples). +pub struct AdcFifoBuilder<'a, Word> { + adc: &'a mut Adc, + marker: PhantomData, +} + +impl<'a, Word> AdcFifoBuilder<'a, Word> { + /// Manually set clock divider to control sample rate + /// + /// The ADC is tied to the USB clock, normally running at 48MHz. ADC + /// conversion happens at 96 cycles per sample, so with the dividers both + /// set to 0 (the default) the sample rate will be `48MHz / 96 = 500ksps`. + /// + /// Setting the `int` and / or `frac` dividers will hold off between + /// samples, leading to an effective rate of: + /// + /// ```text + /// rate = 48MHz / (1 + int + (frac / 256)) + /// ``` + /// + /// To determine the required `int` and `frac` values for a given target + /// rate, use these equations: + /// + /// ```text + /// int = floor((48MHz / rate) - 1) + /// frac = round(256 * ((48MHz / rate) - 1 - int)) + /// ``` + /// + /// Some examples: + /// + /// | Target rate | `int` | `frac` | + /// |-------------|---------|--------| + /// | 1000sps | `47999` | `0` | + /// | 1024sps | `46874` | `0` | + /// | 1337sps | `35900` | `70` | + /// | 4096sps | `11717` | `192` | + /// | 96ksps | `499` | `0` | + /// + /// Since each conversion takes 96 cycles, setting `int` to anything below + /// 96 does not make a difference, and leads to the same result as setting + /// it to 0. + /// + /// The lowest possible rate is 732.41Hz, attainable by setting `int = + /// 0xFFFF, frac = 0xFF`. + /// + /// For more details, please refer to [Section + /// 12.4.3.2](https://rptl.io/rp2350-datasheet#section_adc) in the RP2350 + /// datasheet. + pub fn clock_divider(self, int: u16, frac: u8) -> Self { + self.adc + .device + .div() + .modify(|_, w| unsafe { w.int().bits(int).frac().bits(frac) }); + self + } + + /// Select ADC input channel to sample from + /// + /// If round-robin mode is used, this will only affect the first sample. + /// + /// The given `pin` can either be one of the ADC inputs (GPIO26-28) or the + /// internal temperature sensor (retrieved via [`Adc::take_temp_sensor`]). + pub fn set_channel(self, pin: &mut P) -> Self { + self.adc + .device + .cs() + .modify(|_, w| unsafe { w.ainsel().bits(pin.channel()) }); + self + } + + /// Set channels to use for round-robin mode + /// + /// Takes a tuple of channels, like `(&mut adc_pin, &mut temp_sense)`. + /// + /// **NOTE:** *The order in which the channels are specified has no effect! + /// Channels are always sampled in increasing order, by their channel number (Channel 0, Channel 1, ...).* + pub fn round_robin>(self, selected_channels: T) -> Self { + let RoundRobin(bits) = selected_channels.into(); + self.adc + .device + .cs() + .modify(|_, w| unsafe { w.rrobin().bits(bits as u16) }); + self + } + + /// Enable the FIFO interrupt ([`ADC_IRQ_FIFO`](crate::pac::Interrupt::ADC_IRQ_FIFO)) + /// + /// It will be triggered whenever there are at least `threshold` samples waiting in the FIFO. + pub fn enable_interrupt(self, threshold: u8) -> Self { + self.adc.device.inte().modify(|_, w| w.fifo().set_bit()); + self.adc + .device + .fcs() + .modify(|_, w| unsafe { w.thresh().bits(threshold) }); + self + } + + /// Shift values to produce 8 bit samples (discarding the lower 4 bits). + /// + /// Normally the ADC uses 12 bits of precision, packed into a u16. + /// Shifting the values loses some precision, but produces smaller samples. + /// + /// When this method has been called, the resulting fifo's `read` method returns u8. + pub fn shift_8bit(self) -> AdcFifoBuilder<'a, u8> { + self.adc.device.fcs().modify(|_, w| w.shift().set_bit()); + AdcFifoBuilder { + adc: self.adc, + marker: PhantomData, + } + } + + /// Enable DMA for the FIFO. + /// + /// This must be called to be able to transfer data from the ADC using a DMA transfer. + /// + /// **NOTE:** *this method sets the FIFO interrupt threshold to `1`, which is required for DMA transfers to work. + /// The threshold is the same one as set by [`AdcFifoBuilder::enable_interrupt`]. If you want to enable FIFO + /// interrupts, but also use DMA, the `threshold` parameter passed to `enable_interrupt` *must* be set to `1` as well.* + pub fn enable_dma(self) -> Self { + self.adc + .device + .fcs() + .modify(|_, w| unsafe { w.dreq_en().set_bit().thresh().bits(1) }); + self + } + + /// Enable ADC FIFO and start free-running conversion + /// + /// Use the returned [`AdcFifo`] instance to access the captured data. + /// + /// To stop capturing, call [`AdcFifo::stop`]. + /// + /// Note: if you plan to use the FIFO for DMA transfers, [`AdcFifoBuilder::prepare`] instead. + pub fn start(self) -> AdcFifo<'a, Word> { + self.adc.device.fcs().modify(|_, w| w.en().set_bit()); + self.adc.device.cs().modify(|_, w| w.start_many().set_bit()); + AdcFifo { + adc: self.adc, + marker: PhantomData, + } + } + + /// Enable ADC FIFO, but do not start conversion yet + /// + /// Same as [`AdcFifoBuilder::start`], except the FIFO is initially paused. + /// + /// Use [`AdcFifo::resume`] to start conversion. + pub fn start_paused(self) -> AdcFifo<'a, Word> { + self.adc.device.fcs().modify(|_, w| w.en().set_bit()); + self.adc + .device + .cs() + .modify(|_, w| w.start_many().clear_bit()); + AdcFifo { + adc: self.adc, + marker: PhantomData, + } + } + + /// Alias for [`AdcFifoBuilder::start_paused`]. + #[deprecated(note = "Use `start_paused()` instead.", since = "0.10.0")] + pub fn prepare(self) -> AdcFifo<'a, Word> { + self.start_paused() + } +} + +/// Represents the ADC fifo +/// +/// Constructed by [`AdcFifoBuilder::start`], which is accessible through [`Adc::build_fifo`]. +pub struct AdcFifo<'a, Word> { + adc: &'a mut Adc, + marker: PhantomData, +} + +impl<'a, Word> AdcFifo<'a, Word> { + #[allow(clippy::len_without_is_empty)] + /// Returns the number of elements currently in the fifo + pub fn len(&mut self) -> u8 { + self.adc.device.fcs().read().level().bits() + } + + /// Check if there was a fifo overrun + /// + /// An overrun happens when the fifo is filled up faster than `read` is called to consume it. + /// + /// This function also clears the `over` bit if it was set. + pub fn is_over(&mut self) -> bool { + let over = self.adc.device.fcs().read().over().bit(); + if over { + self.adc + .device + .fcs() + .modify(|_, w| w.over().clear_bit_by_one()); + } + over + } + + /// Check if there was a fifo underrun + /// + /// An underrun happens when `read` is called on an empty fifo (`len() == 0`). + /// + /// This function also clears the `under` bit if it was set. + pub fn is_under(&mut self) -> bool { + let under = self.adc.device.fcs().read().under().bit(); + if under { + self.adc + .device + .fcs() + .modify(|_, w| w.under().clear_bit_by_one()); + } + under + } + + /// Read the most recently sampled ADC value + /// + /// Returns the most recently sampled value, bypassing the FIFO. + /// + /// This can be used if you want to read samples occasionally, but don't + /// want to incur the 96 cycle delay of a one-off read. + /// + /// Example: + /// ```ignore + /// // start continuously sampling values: + /// let mut fifo = adc.build_fifo().set_channel(&mut adc_pin).start(); + /// + /// loop { + /// do_something_timing_critical(); + /// + /// // read the most recent value: + /// if fifo.read_single() > THRESHOLD { + /// led.set_high().unwrap(); + /// } else { + /// led.set_low().unwrap(); + /// } + /// } + /// + /// // stop sampling, when it's no longer needed + /// fifo.stop(); + /// ``` + /// + /// Note that when round-robin sampling is used, there is no way + /// to tell from which channel this sample came. + pub fn read_single(&mut self) -> u16 { + self.adc.read_single() + } + + /// Returns `true` if conversion is currently paused. + /// + /// While paused, no samples will be added to the FIFO. + /// + /// There may be existing samples in the FIFO though, or a conversion may still be in progress. + pub fn is_paused(&mut self) -> bool { + self.adc.device.cs().read().start_many().bit_is_clear() + } + + /// Temporarily pause conversion + /// + /// This method stops ADC conversion, but leaves everything else configured. + /// + /// No new samples are captured until [`AdcFifo::resume`] is called. + /// + /// Note that existing samples can still be read from the FIFO, and can possibly + /// cause interrupts and DMA transfer progress until the FIFO is emptied. + pub fn pause(&mut self) { + self.adc + .device + .cs() + .modify(|_, w| w.start_many().clear_bit()); + } + + /// Resume conversion after it was paused + /// + /// There are two situations when it makes sense to use this method: + /// - After having called [`AdcFifo::pause`] on an AdcFifo + /// - If the FIFO was initialized using [`AdcFifoBuilder::prepare`]. + /// + /// Calling this method when conversion is already running has no effect. + pub fn resume(&mut self) { + self.adc.device.cs().modify(|_, w| w.start_many().set_bit()); + } + + /// Clears the FIFO, removing all values + /// + /// Reads and discards values from the FIFO until it is empty. + /// + /// This only makes sense to use while the FIFO is paused (see [`AdcFifo::pause`]). + pub fn clear(&mut self) { + while self.len() > 0 { + self.read_from_fifo(); + } + } + + /// Stop capturing in free running mode. + /// + /// Resets all capture options that can be set via [`AdcFifoBuilder`] to + /// their defaults. + /// + /// Returns the underlying [`Adc`], to be reused. + pub fn stop(mut self) -> &'a mut Adc { + // stop capture and clear channel selection + self.adc + .device + .cs() + .modify(|_, w| unsafe { w.start_many().clear_bit().rrobin().bits(0).ainsel().bits(0) }); + // disable fifo interrupt + self.adc.device.inte().modify(|_, w| w.fifo().clear_bit()); + // Wait for one more conversion, then drain remaining values from fifo. + // This MUST happen *after* the interrupt is disabled, but + // *before* `thresh` is modified. Otherwise if `INTS.FIFO = 1`, + // the interrupt will be fired one more time. + // The only way to clear `INTS.FIFO` is for `FCS.LEVEL` to go + // below `FCS.THRESH`, which requires `FCS.THRESH` not to be 0. + while self.adc.device.cs().read().ready().bit_is_clear() {} + self.clear(); + // disable fifo, reset threshold to 0 and disable DMA + self.adc + .device + .fcs() + .modify(|_, w| unsafe { w.en().clear_bit().thresh().bits(0).dreq_en().clear_bit() }); + // reset clock divider + self.adc + .device + .div() + .modify(|_, w| unsafe { w.int().bits(0).frac().bits(0) }); + self.adc + } + + /// Block until a ADC_IRQ_FIFO interrupt occurs + /// + /// Interrupts must be enabled ([`AdcFifoBuilder::enable_interrupt`]), or else this methods blocks forever. + pub fn wait_for_interrupt(&mut self) { + while self.adc.device.intr().read().fifo().bit_is_clear() {} + } + + fn read_from_fifo(&mut self) -> u16 { + self.adc.device.fifo().read().val().bits() + } + + /// Returns a read-target for initiating DMA transfers + /// + /// The [`DmaReadTarget`] returned by this function can be used to initiate DMA transfers + /// reading from the ADC. + pub fn dma_read_target(&self) -> DmaReadTarget { + DmaReadTarget(self.adc.device.fifo().as_ptr() as u32, PhantomData) + } + + /// Trigger a single conversion + /// + /// Ignored when in [`Adc::free_running`] mode. + pub fn trigger(&mut self) { + self.adc.device.cs().modify(|_, w| w.start_once().set_bit()); + } + + /// Check if ADC is ready for the next conversion trigger + /// + /// Not useful when in [`Adc::free_running`] mode. + pub fn is_ready(&self) -> bool { + self.adc.device.cs().read().ready().bit_is_set() + } +} + +impl AdcFifo<'_, u16> { + /// Read a single value from the fifo (u16 version, not shifted) + pub fn read(&mut self) -> u16 { + self.read_from_fifo() + } +} + +impl AdcFifo<'_, u8> { + /// Read a single value from the fifo (u8 version, shifted) + /// + /// Also see [`AdcFifoBuilder::shift_8bit`]. + pub fn read(&mut self) -> u8 { + self.read_from_fifo() as u8 + } +} + +/// Represents a [`dma::ReadTarget`] for the [`AdcFifo`] +/// +/// If [`AdcFifoBuilder::shift_8bit`] was called when constructing the FIFO, +/// `Word` will be `u8`, otherwise it will be `u16`. +pub struct DmaReadTarget(u32, PhantomData); + +/// Safety: rx_address_count points to a register which is always a valid +/// read target. +unsafe impl dma::ReadTarget for DmaReadTarget { + type ReceivedWord = Word; + + fn rx_treq() -> Option { + Some(TREQ_SEL_A::ADC.into()) + } + + fn rx_address_count(&self) -> (u32, u32) { + (self.0, u32::MAX) + } + + fn rx_increment(&self) -> bool { + false + } +} + +impl dma::EndlessReadTarget for DmaReadTarget {} + +/// Internal struct representing values for the `CS.RROBIN` register. +/// +/// See [`AdcFifoBuilder::round_robin`], for usage example. +pub struct RoundRobin(u8); + +impl From<&PIN> for RoundRobin { + fn from(pin: &PIN) -> Self { + Self(1 << pin.channel()) + } +} + +impl From<(&A, &B)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, +{ + fn from(pins: (&A, &B)) -> Self { + Self(1 << pins.0.channel() | 1 << pins.1.channel()) + } +} + +impl From<(&A, &B, &C)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, + C: AdcChannel, +{ + fn from(pins: (&A, &B, &C)) -> Self { + Self(1 << pins.0.channel() | 1 << pins.1.channel() | 1 << pins.2.channel()) + } +} + +impl From<(&A, &B, &C, &D)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, + C: AdcChannel, + D: AdcChannel, +{ + fn from(pins: (&A, &B, &C, &D)) -> Self { + Self( + 1 << pins.0.channel() + | 1 << pins.1.channel() + | 1 << pins.2.channel() + | 1 << pins.3.channel(), + ) + } +} + +impl From<(&A, &B, &C, &D, &E)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, + C: AdcChannel, + D: AdcChannel, + E: AdcChannel, +{ + fn from(pins: (&A, &B, &C, &D, &E)) -> Self { + Self( + 1 << pins.0.channel() + | 1 << pins.1.channel() + | 1 << pins.2.channel() + | 1 << pins.3.channel() + | 1 << pins.4.channel(), + ) + } +} diff --git a/rp-hal/rp235x-hal/src/arch.rs b/rp-hal/rp235x-hal/src/arch.rs new file mode 100644 index 0000000..a87fe9b --- /dev/null +++ b/rp-hal/rp235x-hal/src/arch.rs @@ -0,0 +1,138 @@ +//! Portable in-line assembly +//! +//! Replaces `cortex_m::asm` with things that work on RISC-V and Arm. + +#[cfg(all(target_arch = "arm", target_os = "none"))] +mod inner { + pub use cortex_m::asm::{delay, dsb, nop, sev, wfe, wfi}; + pub use cortex_m::interrupt::{disable as interrupt_disable, enable as interrupt_enable}; + + /// Are interrupts current enabled? + pub fn interrupts_enabled() -> bool { + cortex_m::register::primask::read().is_active() + } + + /// Run the closure without interrupts + /// + /// No critical-section token because we haven't blocked the second core + pub fn interrupt_free(f: F) -> T + where + F: FnOnce() -> T, + { + let active = interrupts_enabled(); + if active { + interrupt_disable(); + } + let t = f(); + if active { + unsafe { + interrupt_enable(); + } + } + t + } +} + +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +mod inner { + pub use riscv::asm::{delay, nop, wfi}; + pub use riscv::interrupt::machine::{ + disable as interrupt_disable, enable as interrupt_enable, free as interrupt_free, + }; + + /// Send Event + #[inline(always)] + pub fn sev() { + unsafe { + // This is how h3.unblock is encoded. + core::arch::asm!("slt x0, x0, x1"); + } + } + + /// Wait for Event + /// + /// This is the interrupt-safe version of WFI. + pub fn wfe() { + let active = interrupts_enabled(); + if active { + interrupt_disable(); + } + wfi(); + if active { + unsafe { + interrupt_enable(); + } + } + } + + /// Data Synchronization Barrier + #[inline(always)] + pub fn dsb() { + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + unsafe { core::arch::asm!("fence", options(nostack, preserves_flags)) }; + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + } + + /// Are interrupts current enabled? + #[inline(always)] + pub fn interrupts_enabled() -> bool { + riscv::register::mstatus::read().mie() + } +} + +#[cfg(not(all(any(target_arch = "arm", target_arch = "riscv32"), target_os = "none")))] +mod inner { + /// Placeholder function to disable interrupts + pub fn interrupt_disable() {} + /// Placeholder function to enable interrupts + pub fn interrupt_enable() {} + /// Placeholder function to check if interrupts are enabled + pub fn interrupts_enabled() -> bool { + false + } + /// Placeholder function to wait for an event + pub fn wfe() {} + /// Placeholder function to do nothing + pub fn nop() {} + /// Placeholder function to emit a data synchronisation barrier + pub fn dsb() {} + /// Placeholder function to run a closure with interrupts disabled + pub fn interrupt_free(f: F) -> T + where + F: FnOnce() -> T, + { + f() + } + /// Placeholder function to wait for some clock cycles + pub fn delay(_: u32) {} + /// Placeholder function to emit an event + pub fn sev() {} +} + +pub use inner::*; + +/// Create a static variable which we can grab a mutable reference to exactly once. +#[macro_export] +macro_rules! singleton { + ($name:ident: $ty:ty = $expr:expr) => {{ + static mut $name: (::core::mem::MaybeUninit<$ty>, ::core::sync::atomic::AtomicBool) = + (::core::mem::MaybeUninit::uninit(), ::core::sync::atomic::AtomicBool::new(false)); + + #[allow(unsafe_code)] + if unsafe { $name.1.compare_exchange(false, true, ::core::sync::atomic::Ordering::SeqCst, ::core::sync::atomic::Ordering::SeqCst).is_ok() } { + // If we get here, the bool was false and we were the ones who set it to true. + // So we have exclusive access. + let expr = $expr; + #[allow(unsafe_code)] + unsafe { + $name.0 = ::core::mem::MaybeUninit::new(expr); + Some(&mut *$name.0.as_mut_ptr()) + } + } else { + None + } + }}; + (: $ty:ty = $expr:expr) => { + $crate::singleton!(VAR: $ty = $expr) + }; +} diff --git a/rp-hal/rp235x-hal/src/async_utils.rs b/rp-hal/rp235x-hal/src/async_utils.rs new file mode 100644 index 0000000..11f8276 --- /dev/null +++ b/rp-hal/rp235x-hal/src/async_utils.rs @@ -0,0 +1,143 @@ +//! Commonly used in async implementations. + +use core::{marker::PhantomData, task::Poll}; + +pub(crate) mod sealed { + use core::{cell::Cell, task::Waker}; + use critical_section::Mutex; + + pub trait Wakeable { + /// Returns the waker associated with driver instance. + fn waker() -> &'static IrqWaker; + } + + /// This type wraps a `Waker` in a `Mutex>`. + /// + /// While `critical_section::Mutex` intregrates nicely with RefCell, RefCell adds a borrow + /// counter that is not necessary for this usecase. + /// + /// This type is kept sealed to prevent user from mistakenly messing with the waker such as + /// clearing it while the driver is parked. + pub struct IrqWaker { + waker: Mutex>>, + } + + impl Default for IrqWaker { + fn default() -> Self { + Self::new() + } + } + + impl IrqWaker { + pub const fn new() -> Self { + Self { + waker: Mutex::new(Cell::new(None)), + } + } + pub fn wake(&self) { + critical_section::with(|cs| { + if let Some(waker) = self.waker.borrow(cs).take() { + Waker::wake(waker); + } + }); + } + pub fn register(&self, waker: &Waker) { + critical_section::with(|cs| { + self.waker.borrow(cs).replace(Some(waker.clone())); + }); + } + pub fn clear(&self) { + critical_section::with(|cs| { + self.waker.borrow(cs).take(); + }); + } + } +} + +/// Marks driver instances that can be bound to an interrupt to wake async tasks. +pub trait AsyncPeripheral: sealed::Wakeable { + /// Signals the driver of an interrupt. + fn on_interrupt(); +} + +#[must_use = "Future do nothing unless they are polled on."] +pub(crate) struct CancellablePollFn<'periph, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + CancelFn: FnMut(&mut Periph), +{ + periph: &'periph mut Periph, + poll: PFn, + enable_irq: EnIrqFn, + cancel: CancelFn, + done: bool, + // captures F's return type. + phantom: PhantomData, +} +impl<'p, Periph, PFn, EnIrqFn, CancelFn, OutputTy> + CancellablePollFn<'p, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + PFn: FnMut(&mut Periph) -> Poll, + EnIrqFn: FnMut(&mut Periph), + CancelFn: FnMut(&mut Periph), +{ + pub(crate) fn new( + periph: &'p mut Periph, + poll: PFn, + enable_irq: EnIrqFn, + cancel: CancelFn, + ) -> Self { + Self { + periph, + poll, + enable_irq, + cancel, + done: false, + phantom: PhantomData, + } + } +} + +impl core::future::Future + for CancellablePollFn<'_, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + PFn: FnMut(&mut Periph) -> Poll, + EnIrqFn: FnMut(&mut Periph), + CancelFn: FnMut(&mut Periph), +{ + type Output = OutputTy; + + fn poll(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll { + // SAFETY: We are not moving anything. + let Self { + ref mut periph, + poll: ref mut is_ready, + enable_irq: ref mut setup_flags, + ref mut done, + .. + } = unsafe { self.get_unchecked_mut() }; + let r = (is_ready)(periph); + if r.is_pending() { + Periph::waker().register(cx.waker()); + (setup_flags)(periph); + } else { + *done = true; + } + r + } +} +impl Drop + for CancellablePollFn<'_, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + CancelFn: FnMut(&mut Periph), +{ + fn drop(&mut self) { + if !self.done { + Periph::waker().clear(); + (self.cancel)(self.periph); + } + } +} diff --git a/rp-hal/rp235x-hal/src/atomic_register_access.rs b/rp-hal/rp235x-hal/src/atomic_register_access.rs new file mode 100644 index 0000000..b429bab --- /dev/null +++ b/rp-hal/rp235x-hal/src/atomic_register_access.rs @@ -0,0 +1,40 @@ +//! Provide atomic access to peripheral registers +//! +//! This feature is not available for all peripherals. +//! See [Section 2.1.3][section_2_1_3] of the RP2350 datasheet for details. +//! +//! [section_2_1_3]: https://rptl.io/rp2350-datasheet#atomic-rwtype + +use core::ptr::write_volatile; + +/// Perform atomic bitmask set operation on register +/// +/// See [Section 2.1.3][section_2_1_3] of the RP2350 datasheet for details. +/// +/// [section_2_1_3]: https://rptl.io/rp2350-datasheet#atomic-rwtype +/// +/// # Safety +/// +/// In addition to the requirements of [core::ptr::write_volatile], +/// `register` must point to a register providing atomic aliases. +#[inline] +pub(crate) unsafe fn write_bitmask_set(register: *mut u32, bits: u32) { + let alias = (register as usize + 0x2000) as *mut u32; + write_volatile(alias, bits); +} + +/// Perform atomic bitmask clear operation on register +/// +/// See [Section 2.1.3][section_2_1_3] of the RP2350 datasheet for details. +/// +/// [section_2_1_3]: https://rptl.io/rp2350-datasheet#atomic-rwtype +/// +/// # Safety +/// +/// In addition to the requirements of [core::ptr::write_volatile], +/// `register` must point to a register providing atomic aliases. +#[inline] +pub(crate) unsafe fn write_bitmask_clear(register: *mut u32, bits: u32) { + let alias = (register as usize + 0x3000) as *mut u32; + write_volatile(alias, bits); +} diff --git a/rp-hal/rp235x-hal/src/block.rs b/rp-hal/rp235x-hal/src/block.rs new file mode 100644 index 0000000..7421dd7 --- /dev/null +++ b/rp-hal/rp235x-hal/src/block.rs @@ -0,0 +1,1092 @@ +//! Support for the RP235x Boot ROM's "Block" structures +//! +//! Blocks contain pointers, to form Block Loops. +//! +//! The `IMAGE_DEF` Block (here the `ImageDef` type) tells the ROM how to boot a +//! firmware image. The `PARTITION_TABLE` Block (here the `PartitionTable` type) +//! tells the ROM how to divide the flash space up into partitions. + +// These all have a 1 byte size + +/// An item ID for encoding a Vector Table address +pub const ITEM_1BS_VECTOR_TABLE: u8 = 0x03; + +/// An item ID for encoding a Rolling Window Delta +pub const ITEM_1BS_ROLLING_WINDOW_DELTA: u8 = 0x05; + +/// An item ID for encoding a Signature +pub const ITEM_1BS_SIGNATURE: u8 = 0x09; + +/// An item ID for encoding a Salt +pub const ITEM_1BS_SALT: u8 = 0x0c; + +/// An item ID for encoding an Image Type +pub const ITEM_1BS_IMAGE_TYPE: u8 = 0x42; + +/// An item ID for encoding the image's Entry Point +pub const ITEM_1BS_ENTRY_POINT: u8 = 0x44; + +/// An item ID for encoding the definition of a Hash +pub const ITEM_2BS_HASH_DEF: u8 = 0x47; + +/// An item ID for encoding a Version +pub const ITEM_1BS_VERSION: u8 = 0x48; + +/// An item ID for encoding a Hash +pub const ITEM_1BS_HASH_VALUE: u8 = 0x4b; + +// These all have a 2-byte size + +/// An item ID for encoding a Load Map +pub const ITEM_2BS_LOAD_MAP: u8 = 0x06; + +/// An item ID for encoding a Partition Table +pub const ITEM_2BS_PARTITION_TABLE: u8 = 0x0a; + +/// An item ID for encoding a placeholder entry that is ignored +/// +/// Allows a Block to not be empty. +pub const ITEM_2BS_IGNORED: u8 = 0xfe; + +/// An item ID for encoding the special last item in a Block +/// +/// It records how long the Block is. +pub const ITEM_2BS_LAST: u8 = 0xff; + +// Options for ITEM_1BS_IMAGE_TYPE + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as invalid +pub const IMAGE_TYPE_INVALID: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as an executable +pub const IMAGE_TYPE_EXE: u16 = 0x0001; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as data +pub const IMAGE_TYPE_DATA: u16 = 0x0002; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as unspecified +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as Non Secure +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_NS: u16 = 0x0010; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as Non Secure +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_S: u16 = 0x0020; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU type as Arm +pub const IMAGE_TYPE_EXE_CPU_ARM: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU type as RISC-V +pub const IMAGE_TYPE_EXE_CPU_RISCV: u16 = 0x0100; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU as an RP2040 +pub const IMAGE_TYPE_EXE_CHIP_RP2040: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU as an RP2350 +pub const IMAGE_TYPE_EXE_CHIP_RP2350: u16 = 0x1000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the image as Try Before You Buy. +/// +/// This means the image must be marked as 'Bought' with the ROM before the +/// watchdog times out the trial period, otherwise it is erased and the previous +/// image will be booted. +pub const IMAGE_TYPE_TBYB: u16 = 0x8000; + +/// This is the magic Block Start value. +/// +/// The Pico-SDK calls it `PICOBIN_BLOCK_MARKER_START` +const BLOCK_MARKER_START: u32 = 0xffffded3; + +/// This is the magic Block END value. +/// +/// The Pico-SDK calls it `PICOBIN_BLOCK_MARKER_END` +const BLOCK_MARKER_END: u32 = 0xab123579; + +/// An Image Definition has one item in it - an [`ITEM_1BS_IMAGE_TYPE`] +pub type ImageDef = Block<1>; + +/// A Block as understood by the Boot ROM. +/// +/// This could be an Image Definition, or a Partition Table, or maybe some other +/// kind of block. +/// +/// It contains within the special start and end markers the Boot ROM is looking +/// for. +#[derive(Debug)] +#[repr(C)] +pub struct Block { + marker_start: u32, + items: [u32; N], + length: u32, + offset: *const u32, + marker_end: u32, +} + +unsafe impl Sync for Block {} + +impl Block { + /// Construct a new Binary Block, with the given items. + /// + /// The length, and the Start and End markers are added automatically. The + /// Block Loop pointer initially points to itself. + pub const fn new(items: [u32; N]) -> Block { + Block { + marker_start: BLOCK_MARKER_START, + items, + length: item_last(N as u16), + // offset from this block to next block in loop. By default + // we form a Block Loop with a single Block in it. + offset: core::ptr::null(), + marker_end: BLOCK_MARKER_END, + } + } + + /// Change the Block Loop offset value. + /// + /// This method isn't that useful because you can't evaluate the difference + /// between two pointers in a const context as the addresses aren't assigned + /// until long after the const evaluator has run. + /// + /// If you think you need this method, you might want to set a unique random + /// value here and swap it for the real offset as a post-processing step. + pub const fn with_offset(self, offset: *const u32) -> Block { + Block { offset, ..self } + } +} + +impl Block<0> { + /// Construct an empty block. + pub const fn empty() -> Block<0> { + Block::new([]) + } + + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<1> { + Block::new([word]) + } +} + +impl Block<1> { + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<2> { + Block::new([self.items[0], word]) + } +} + +impl Block<2> { + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<3> { + Block::new([self.items[0], self.items[1], word]) + } +} + +impl ImageDef { + /// Construct a new IMAGE_DEF Block, for an EXE with the given security and + /// architecture. + pub const fn arch_exe(security: Security, architecture: Architecture) -> Self { + Self::new([item_image_type_exe(security, architecture)]) + } + + /// Construct a new IMAGE_DEF Block, for an EXE with the given security. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn exe(security: Security) -> Self { + if cfg!(all(target_arch = "riscv32", target_os = "none")) { + Self::arch_exe(security, Architecture::Riscv) + } else { + Self::arch_exe(security, Architecture::Arm) + } + } + + /// Construct a new IMAGE_DEF Block, for a Non-Secure EXE. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn non_secure_exe() -> Self { + Self::exe(Security::NonSecure) + } + + /// Construct a new IMAGE_DEF Block, for a Secure EXE. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn secure_exe() -> Self { + Self::exe(Security::Secure) + } +} + +/// We make our partition table this fixed size. +pub const PARTITION_TABLE_MAX_ITEMS: usize = 128; + +/// Describes a unpartitioned space +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct UnpartitionedSpace { + permissions_and_location: u32, + permissions_and_flags: u32, +} + +impl UnpartitionedSpace { + /// Create a new unpartitioned space. + /// + /// It defaults to no permissions. + pub const fn new() -> Self { + Self { + permissions_and_location: 0, + permissions_and_flags: 0, + } + } + + /// Create a new unpartition space from run-time values. + /// + /// Get these from the ROM function `get_partition_table_info` with an argument of `PT_INFO`. + pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self { + Self { + permissions_and_location, + permissions_and_flags, + } + } + + /// Add a permission + pub const fn with_permission(self, permission: Permission) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | permission as u32, + permissions_and_location: self.permissions_and_location | permission as u32, + } + } + + /// Set a flag + pub const fn with_flag(self, flag: UnpartitionedFlag) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | flag as u32, + ..self + } + } + + /// Get the partition start and end + /// + /// The offsets are in 4 KiB sectors, inclusive. + pub fn get_first_last_sectors(&self) -> (u16, u16) { + ( + (self.permissions_and_location & 0x0000_1FFF) as u16, + ((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16, + ) + } + + /// Get the partition start and end + /// + /// The offsets are in bytes, inclusive. + pub fn get_first_last_bytes(&self) -> (u32, u32) { + let (first, last) = self.get_first_last_sectors(); + (u32::from(first) * 4096, (u32::from(last) * 4096) + 4095) + } + + /// Check if it has a permission + pub fn has_permission(&self, permission: Permission) -> bool { + let mask = permission as u32; + (self.permissions_and_flags & mask) != 0 + } + + /// Check if the partition has a flag set + pub fn has_flag(&self, flag: UnpartitionedFlag) -> bool { + let mask = flag as u32; + (self.permissions_and_flags & mask) != 0 + } +} + +impl core::fmt::Display for UnpartitionedSpace { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let (first, last) = self.get_first_last_bytes(); + write!( + f, + "{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}", + first, + last, + if self.has_permission(Permission::SecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::SecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::BootRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::BootWrite) { + 'W' + } else { + '_' + } + ) + } +} + +/// Describes a Partition +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Partition { + permissions_and_location: u32, + permissions_and_flags: u32, + id: Option, + extra_families: [u32; 4], + extra_families_len: usize, + name: [u8; 128], +} + +impl Partition { + const FLAGS_HAS_ID: u32 = 0b1; + const FLAGS_LINK_TYPE_A_PARTITION: u32 = 0b01 << 1; + const FLAGS_LINK_TYPE_OWNER: u32 = 0b10 << 1; + const FLAGS_LINK_MASK: u32 = 0b111111 << 1; + const FLAGS_HAS_NAME: u32 = 0b1 << 12; + const FLAGS_HAS_EXTRA_FAMILIES_SHIFT: u8 = 7; + const FLAGS_HAS_EXTRA_FAMILIES_MASK: u32 = 0b11 << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT; + + /// Create a new partition, with the given start and end sectors. + /// + /// It defaults to no permissions. + pub const fn new(first_sector: u16, last_sector: u16) -> Self { + // 0x2000 sectors of 4 KiB is 32 MiB, which is the total XIP area + assert!(first_sector < 0x2000); + assert!(last_sector < 0x2000); + assert!(first_sector <= last_sector); + Self { + permissions_and_location: (last_sector as u32) << 13 | first_sector as u32, + permissions_and_flags: 0, + id: None, + extra_families: [0; 4], + extra_families_len: 0, + name: [0; 128], + } + } + + /// Create a new partition from run-time values. + /// + /// Get these from the ROM function `get_partition_table_info` with an argument of `PARTITION_LOCATION_AND_FLAGS`. + pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self { + Self { + permissions_and_location, + permissions_and_flags, + id: None, + extra_families: [0; 4], + extra_families_len: 0, + name: [0; 128], + } + } + + /// Add a permission + pub const fn with_permission(self, permission: Permission) -> Self { + Self { + permissions_and_location: self.permissions_and_location | permission as u32, + permissions_and_flags: self.permissions_and_flags | permission as u32, + ..self + } + } + + /// Set the name of the partition + pub const fn with_name(self, name: &str) -> Self { + let mut new_name = [0u8; 128]; + let name = name.as_bytes(); + let mut idx = 0; + new_name[0] = name.len() as u8; + while idx < name.len() { + new_name[idx + 1] = name[idx]; + idx += 1; + } + Self { + name: new_name, + permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_NAME, + ..self + } + } + + /// Set the extra families for the partition. + /// + /// You can supply up to four. + pub const fn with_extra_families(self, extra_families: &[u32]) -> Self { + assert!(extra_families.len() <= 4); + let mut new_extra_families = [0u32; 4]; + let mut idx = 0; + while idx < extra_families.len() { + new_extra_families[idx] = extra_families[idx]; + idx += 1; + } + Self { + extra_families: new_extra_families, + extra_families_len: extra_families.len(), + permissions_and_flags: (self.permissions_and_flags + & !Self::FLAGS_HAS_EXTRA_FAMILIES_MASK) + | (extra_families.len() as u32) << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT, + ..self + } + } + + /// Set the ID + pub const fn with_id(self, id: u64) -> Self { + Self { + id: Some(id), + permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_ID, + ..self + } + } + + /// Add a link + pub const fn with_link(self, link: Link) -> Self { + let mut new_flags = self.permissions_and_flags & !Self::FLAGS_LINK_MASK; + match link { + Link::Nothing => {} + Link::ToA { partition_idx } => { + assert!(partition_idx < 16); + new_flags |= Self::FLAGS_LINK_TYPE_A_PARTITION; + new_flags |= (partition_idx as u32) << 3; + } + Link::ToOwner { partition_idx } => { + assert!(partition_idx < 16); + new_flags |= Self::FLAGS_LINK_TYPE_OWNER; + new_flags |= (partition_idx as u32) << 3; + } + } + Self { + permissions_and_flags: new_flags, + ..self + } + } + + /// Set a flag + pub const fn with_flag(self, flag: PartitionFlag) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | flag as u32, + ..self + } + } + + /// Get the partition start and end + /// + /// The offsets are in 4 KiB sectors, inclusive. + pub fn get_first_last_sectors(&self) -> (u16, u16) { + ( + (self.permissions_and_location & 0x0000_1FFF) as u16, + ((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16, + ) + } + + /// Get the partition start and end + /// + /// The offsets are in bytes, inclusive. + pub fn get_first_last_bytes(&self) -> (u32, u32) { + let (first, last) = self.get_first_last_sectors(); + (u32::from(first) * 4096, (u32::from(last) * 4096) + 4095) + } + + /// Check if it has a permission + pub fn has_permission(&self, permission: Permission) -> bool { + let mask = permission as u32; + (self.permissions_and_flags & mask) != 0 + } + + /// Get which extra families are allowed in this partition + pub fn get_extra_families(&self) -> &[u32] { + &self.extra_families[0..self.extra_families_len] + } + + /// Get the name of the partition + /// + /// Returns `None` if there's no name, or the name is not valid UTF-8. + pub fn get_name(&self) -> Option<&str> { + let len = self.name[0] as usize; + if len == 0 { + None + } else { + core::str::from_utf8(&self.name[1..=len]).ok() + } + } + + /// Get the ID + pub fn get_id(&self) -> Option { + self.id + } + + /// Check if this partition is linked + pub fn get_link(&self) -> Link { + if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_A_PARTITION) != 0 { + let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8; + Link::ToA { partition_idx } + } else if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_OWNER) != 0 { + let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8; + Link::ToOwner { partition_idx } + } else { + Link::Nothing + } + } + + /// Check if the partition has a flag set + pub fn has_flag(&self, flag: PartitionFlag) -> bool { + let mask = flag as u32; + (self.permissions_and_flags & mask) != 0 + } +} + +impl core::fmt::Display for Partition { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let (first, last) = self.get_first_last_bytes(); + write!( + f, + "{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}", + first, + last, + if self.has_permission(Permission::SecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::SecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::BootRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::BootWrite) { + 'W' + } else { + '_' + } + ) + } +} + +/// Describes a partition table. +/// +/// Don't store this as a static - make sure you convert it to a block. +#[derive(Clone)] +pub struct PartitionTableBlock { + /// This must look like a block, including the 1 word header and the 3 word footer. + contents: [u32; PARTITION_TABLE_MAX_ITEMS], + /// This value doesn't include the 1 word header or the 3 word footer + num_items: usize, +} + +impl PartitionTableBlock { + /// Create an empty Block, big enough for a partition table. + /// + /// At a minimum you need to call [`Self::add_partition_item`]. + pub const fn new() -> PartitionTableBlock { + let mut contents = [0; PARTITION_TABLE_MAX_ITEMS]; + contents[0] = BLOCK_MARKER_START; + contents[1] = item_last(0); + contents[2] = 0; + contents[3] = BLOCK_MARKER_END; + PartitionTableBlock { + contents, + num_items: 0, + } + } + + /// Add a partition to the partition table + pub const fn add_partition_item( + self, + unpartitioned: UnpartitionedSpace, + partitions: &[Partition], + ) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. add item header space (we fill this in later) + let header_idx = idx; + new_table.contents[idx] = 0; + idx += 1; + + // 2. unpartitioned space flags + // + // (the location of unpartition space is not recorded here - it is + // inferred because the unpartitioned space is where the partitions are + // not) + new_table.contents[idx] = unpartitioned.permissions_and_flags; + idx += 1; + + // 3. partition info + + let mut partition_no = 0; + while partition_no < partitions.len() { + // a. permissions_and_location (4K units) + new_table.contents[idx] = partitions[partition_no].permissions_and_location; + idx += 1; + + // b. permissions_and_flags + new_table.contents[idx] = partitions[partition_no].permissions_and_flags; + idx += 1; + + // c. ID + if let Some(id) = partitions[partition_no].id { + new_table.contents[idx] = id as u32; + new_table.contents[idx + 1] = (id >> 32) as u32; + idx += 2; + } + + // d. Extra Families + let mut extra_families_idx = 0; + while extra_families_idx < partitions[partition_no].extra_families_len { + new_table.contents[idx] = + partitions[partition_no].extra_families[extra_families_idx]; + idx += 1; + extra_families_idx += 1; + } + + // e. Name + let mut name_idx = 0; + while name_idx < partitions[partition_no].name[0] as usize { + let name_chunk = [ + partitions[partition_no].name[name_idx], + partitions[partition_no].name[name_idx + 1], + partitions[partition_no].name[name_idx + 2], + partitions[partition_no].name[name_idx + 3], + ]; + new_table.contents[idx] = u32::from_le_bytes(name_chunk); + name_idx += 4; + idx += 1; + } + + partition_no += 1; + } + + let len = idx - header_idx; + new_table.contents[header_idx] = + item_generic_2bs(partitions.len() as u8, len as u16, ITEM_2BS_PARTITION_TABLE); + + // 7. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } + + /// Add a version number to the partition table + pub const fn with_version(self, major: u16, minor: u16) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. add item + new_table.contents[idx] = item_generic_2bs(0, 2, ITEM_1BS_VERSION); + idx += 1; + new_table.contents[idx] = (major as u32) << 16 | minor as u32; + idx += 1; + + // 2. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } + + /// Add a a SHA256 hash of the Block + /// + /// Adds a `HASH_DEF` covering all the previous items in the Block, and a + /// `HASH_VALUE` with a SHA-256 hash of them. + pub const fn with_sha256(self) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. HASH_DEF says what is hashed + new_table.contents[idx] = item_generic_2bs(1, 2, ITEM_2BS_HASH_DEF); + idx += 1; + // we're hashing all the previous contents - including this line. + new_table.contents[idx] = (idx + 1) as u32; + idx += 1; + + // calculate hash over prior contents + let input = unsafe { + core::slice::from_raw_parts(new_table.contents.as_ptr() as *const u8, idx * 4) + }; + let hash: [u8; 32] = sha2_const_stable::Sha256::new().update(input).finalize(); + + // 2. HASH_VALUE contains the hash + new_table.contents[idx] = item_generic_2bs(0, 9, ITEM_1BS_HASH_VALUE); + idx += 1; + + let mut hash_idx = 0; + while hash_idx < hash.len() { + new_table.contents[idx] = u32::from_le_bytes([ + hash[hash_idx], + hash[hash_idx + 1], + hash[hash_idx + 2], + hash[hash_idx + 3], + ]); + idx += 1; + hash_idx += 4; + } + + // 3. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } +} + +impl Default for PartitionTableBlock { + fn default() -> Self { + Self::new() + } +} + +/// Flags that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum PartitionFlag { + NotBootableArm = 1 << 9, + NotBootableRiscv = 1 << 10, + Uf2DownloadAbNonBootableOwnerAffinity = 1 << 11, + Uf2DownloadNoReboot = 1 << 13, + AcceptsDefaultFamilyRp2040 = 1 << 14, + AcceptsDefaultFamilyData = 1 << 16, + AcceptsDefaultFamilyRp2350ArmS = 1 << 17, + AcceptsDefaultFamilyRp2350Riscv = 1 << 18, + AcceptsDefaultFamilyRp2350ArmNs = 1 << 19, +} + +/// Flags that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum UnpartitionedFlag { + Uf2DownloadNoReboot = 1 << 13, + AcceptsDefaultFamilyRp2040 = 1 << 14, + AcceptsDefaultFamilyAbsolute = 1 << 15, + AcceptsDefaultFamilyData = 1 << 16, + AcceptsDefaultFamilyRp2350ArmS = 1 << 17, + AcceptsDefaultFamilyRp2350Riscv = 1 << 18, + AcceptsDefaultFamilyRp2350ArmNs = 1 << 19, +} + +/// Kinds of linked partition +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Link { + /// Not linked to anything + Nothing, + /// This is a B partition - link to our A partition. + ToA { + /// The index of our matching A partition. + partition_idx: u8, + }, + /// Link to the partition that owns this one. + ToOwner { + /// The idx of our owner + partition_idx: u8, + }, +} + +/// Permissions that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum Permission { + /// Can be read in Secure Mode + /// + /// Corresponds to `PERMISSION_S_R_BITS` in the Pico SDK + SecureRead = 1 << 26, + /// Can be written in Secure Mode + /// + /// Corresponds to `PERMISSION_S_W_BITS` in the Pico SDK + SecureWrite = 1 << 27, + /// Can be read in Non-Secure Mode + /// + /// Corresponds to `PERMISSION_NS_R_BITS` in the Pico SDK + NonSecureRead = 1 << 28, + /// Can be written in Non-Secure Mode + /// + /// Corresponds to `PERMISSION_NS_W_BITS` in the Pico SDK + NonSecureWrite = 1 << 29, + /// Can be read in Non-Secure Bootloader mode + /// + /// Corresponds to `PERMISSION_NSBOOT_R_BITS` in the Pico SDK + BootRead = 1 << 30, + /// Can be written in Non-Secure Bootloader mode + /// + /// Corresponds to `PERMISSION_NSBOOT_W_BITS` in the Pico SDK + BootWrite = 1 << 31, +} + +impl Permission { + /// Is this permission bit set this in this bitmask? + pub const fn is_in(self, mask: u32) -> bool { + (mask & (self as u32)) != 0 + } +} + +/// The supported RP2350 CPU architectures +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Architecture { + /// Core is in Arm Cortex-M33 mode + Arm, + /// Core is in RISC-V / Hazard3 mode + Riscv, +} + +/// The kinds of Secure Boot we support +#[derive(Debug, Copy, Clone)] +pub enum Security { + /// Security mode not given + Unspecified, + /// Start in Non-Secure mode + NonSecure, + /// Start in Secure mode + Secure, +} + +/// Make an item containing a tag, 1 byte length and two extra bytes. +/// +/// The `command` arg should contain `1BS` +pub const fn item_generic_1bs(value: u16, length: u8, command: u8) -> u32 { + ((value as u32) << 16) | ((length as u32) << 8) | (command as u32) +} + +/// Make an item containing a tag, 2 byte length and one extra byte. +/// +/// The `command` arg should contain `2BS` +pub const fn item_generic_2bs(value: u8, length: u16, command: u8) -> u32 { + ((value as u32) << 24) | ((length as u32) << 8) | (command as u32) +} + +/// Create Image Type item, of type IGNORED. +pub const fn item_ignored() -> u32 { + item_generic_2bs(0, 1, ITEM_2BS_IGNORED) +} + +/// Create Image Type item, of type INVALID. +pub const fn item_image_type_invalid() -> u32 { + let value = IMAGE_TYPE_INVALID; + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create Image Type item, of type DATA. +pub const fn item_image_type_data() -> u32 { + let value = IMAGE_TYPE_DATA; + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create Image Type item, of type EXE. +pub const fn item_image_type_exe(security: Security, arch: Architecture) -> u32 { + let mut value = IMAGE_TYPE_EXE | IMAGE_TYPE_EXE_CHIP_RP2350; + + match arch { + Architecture::Arm => { + value |= IMAGE_TYPE_EXE_CPU_ARM; + } + Architecture::Riscv => { + value |= IMAGE_TYPE_EXE_CPU_RISCV; + } + } + + match security { + Security::Unspecified => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED, + Security::NonSecure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_NS, + Security::Secure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_S, + } + + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create a Block Last item. +pub const fn item_last(length: u16) -> u32 { + item_generic_2bs(0, length, ITEM_2BS_LAST) +} + +/// Create a Vector Table item. +/// +/// This is only allowed on Arm systems. +pub const fn item_vector_table(table_ptr: u32) -> [u32; 2] { + [item_generic_1bs(0, 2, ITEM_1BS_VECTOR_TABLE), table_ptr] +} + +/// Create an Entry Point item. +pub const fn item_entry_point(entry_point: u32, initial_sp: u32) -> [u32; 3] { + [ + item_generic_1bs(0, 3, ITEM_1BS_ENTRY_POINT), + entry_point, + initial_sp, + ] +} + +/// Create an Rolling Window item. +/// +/// The delta is the number of bytes into the image that 0x10000000 should +/// be mapped. +pub const fn item_rolling_window(delta: u32) -> [u32; 2] { + [item_generic_1bs(0, 3, ITEM_1BS_ROLLING_WINDOW_DELTA), delta] +} + +#[cfg(test)] +mod test { + use super::*; + + /// I used this JSON, with `picotool partition create`: + /// + /// ```json + /// { + /// "version": [1, 0], + /// "unpartitioned": { + /// "families": ["absolute"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// } + /// }, + /// "partitions": [ + /// { + /// "name": "A", + /// "id": 0, + /// "size": "2044K", + /// "families": ["rp2350-arm-s", "rp2350-riscv"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// } + /// }, + /// { + /// "name": "B", + /// "id": 1, + /// "size": "2044K", + /// "families": ["rp2350-arm-s", "rp2350-riscv"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// }, + /// "link": ["a", 0] + /// } + /// ] + /// } + /// ``` + #[test] + fn make_hashed_partition_table() { + let table = PartitionTableBlock::new() + .add_partition_item( + UnpartitionedSpace::new() + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_flag(UnpartitionedFlag::AcceptsDefaultFamilyAbsolute), + &[ + Partition::new(2, 512) + .with_id(0) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv) + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_name("A"), + Partition::new(513, 1023) + .with_id(1) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv) + .with_link(Link::ToA { partition_idx: 0 }) + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_name("B"), + ], + ) + .with_version(1, 0) + .with_sha256(); + let expected = &[ + 0xffffded3, // start + 0x02000c0a, // Item = PARTITION_TABLE + 0xfc008000, // Unpartitioned Space - permissions_and_flags + 0xfc400002, // Partition 0 - permissions_and_location (512 * 4096, 2 * 4096) + 0xfc061001, // permissions_and_flags HAS_ID | HAS_NAME | ARM-S | RISC-V + 0x00000000, // ID + 0x00000000, // ID + 0x00004101, // Name ("A") + 0xfc7fe201, // Partition 1 - permissions_and_location (1023 * 4096, 513 * 4096) + 0xfc061003, // permissions_and_flags LINKA(0) | HAS_ID | HAS_NAME | ARM-S | RISC-V + 0x00000001, // ID + 0x00000000, // ID + 0x00004201, // Name ("B") + 0x00000248, // Item = Version + 0x00010000, // 0, 1 + 0x01000247, // HASH_DEF with 2 words, and SHA256 hash + 0x00000011, // 17 words hashed + 0x0000094b, // HASH_VALUE with 9 words + 0x1945cdad, // Hash word 0 + 0x6b5f9773, // Hash word 1 + 0xe2bf39bd, // Hash word 2 + 0xb243e599, // Hash word 3 + 0xab2f0e9a, // Hash word 4 + 0x4d5d6d0b, // Hash word 5 + 0xf973050f, // Hash word 6 + 0x5ab6dadb, // Hash word 7 + 0x000019ff, // Last Item + 0x00000000, // Block Loop Next Offset + 0xab123579, // End + ]; + assert_eq!( + &table.contents[..29], + expected, + "{:#010x?}\n != \n{:#010x?}", + &table.contents[0..29], + expected, + ); + } +} diff --git a/rp-hal/rp235x-hal/src/clocks/clock_sources.rs b/rp-hal/rp235x-hal/src/clocks/clock_sources.rs new file mode 100644 index 0000000..ccf1398 --- /dev/null +++ b/rp-hal/rp235x-hal/src/clocks/clock_sources.rs @@ -0,0 +1,118 @@ +//! Available clocks + +use super::*; +use crate::{ + gpio::{ + bank0::{Gpio20, Gpio22}, + FunctionClock, Pin, PullNone, PullType, + }, + lposc::LowPowerOscillator, + pll::{Locked, PhaseLockedLoop}, + rosc::{Enabled as RingOscillatorEnabled, RingOscillator}, + typelevel::Sealed, + xosc::{CrystalOscillator, Stable}, +}; +use pac::{PLL_SYS, PLL_USB}; + +/// System PLL. +pub(crate) type PllSys = PhaseLockedLoop; +impl Sealed for PllSys {} +impl ClockSource for PllSys { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +/// USB PLL. +pub(crate) type PllUsb = PhaseLockedLoop; +impl Sealed for PllUsb {} +impl ClockSource for PllUsb { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +// The USB Clock Generator is a clock source +impl ClockSource for UsbClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The ADC Clock Generator is a clock source +impl ClockSource for AdcClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The HSTX Clock Generator is a clock source +impl ClockSource for HstxClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The System Clock Generator is a clock source +impl ClockSource for SystemClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The Reference Clock Generator is a clock source +impl ClockSource for ReferenceClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The Peripheral Clock Generator is a clock source +impl ClockSource for PeripheralClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The Low Power Oscillator is a clock source +pub(crate) type LpOsc = LowPowerOscillator; +impl ClockSource for LpOsc { + fn get_freq(&self) -> HertzU32 { + 32768.Hz() + } +} + +// The Crystal Oscillator +pub(crate) type Xosc = CrystalOscillator; +impl Sealed for Xosc {} +impl ClockSource for Xosc { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +// The Ring Oscillator +pub(crate) type Rosc = RingOscillator; +impl Sealed for Rosc {} +// We are assuming the second output is never phase shifted (see 2.17.4) +impl ClockSource for RingOscillator { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +// GPIN0 +pub(crate) type GPin0 = Pin; +impl ClockSource for GPin0 { + fn get_freq(&self) -> HertzU32 { + todo!() + } +} + +// GPIN1 +pub(crate) type GPin1 = Pin; +impl ClockSource for Pin { + fn get_freq(&self) -> HertzU32 { + todo!() + } +} diff --git a/rp-hal/rp235x-hal/src/clocks/macros.rs b/rp-hal/rp235x-hal/src/clocks/macros.rs new file mode 100644 index 0000000..954c624 --- /dev/null +++ b/rp-hal/rp235x-hal/src/clocks/macros.rs @@ -0,0 +1,431 @@ +macro_rules! clocks { + ( + $( + $(#[$attr:meta])* + struct $name:ident { + init_freq: $init_freq:expr, + reg: $reg:ident, + $(src: {$($src:ident: $src_variant:ident),*},)? + auxsrc: {$($auxsrc:ident: $aux_variant:ident),*} + $(, div: $div:tt)? + } + )* + + ) => { + + $crate::paste::paste!{ + /// Abstraction layer providing Clock Management. + pub struct ClocksManager { + clocks: CLOCKS, + $( + #[doc = "`" $name "` field"] + pub [<$name:snake>]: $name, + )* + } + + impl ClocksManager { + /// Exchanges CLOCKS block against Self. + pub fn new(mut clocks_block: CLOCKS) -> Self { + // Disable resus that may be enabled from previous software + unsafe { + clocks_block.clk_sys_resus_ctrl().write_with_zero(|w| w); + } + + let shared_clocks = ShareableClocks::new(&mut clocks_block); + ClocksManager { + clocks: clocks_block, + $( + [<$name:snake>]: $name { + shared_dev: shared_clocks, + frequency: $init_freq.Hz(), + }, + )* + } + } + } + } + + $( + clock!( + $(#[$attr])* + struct $name { + reg: $reg, + $(src: {$($src: $src_variant),*},)? + auxsrc: {$($auxsrc: $aux_variant),*} + $(, div: $div )? + } + ); + )* + }; +} + +macro_rules! clock { + { + $(#[$attr:meta])* + struct $name:ident { + reg: $reg:ident, + src: {$($src:ident: $src_variant:ident),*}, + auxsrc: {$($auxsrc:ident: $aux_variant:ident),*} + } + } => { + base_clock!{ + $(#[$attr])* + ($name, $reg, auxsrc={$($auxsrc: $aux_variant),*}) + } + + divisible_clock!($name, $reg); + + $crate::paste::paste!{ + $(impl ValidSrc<$name> for $src { + fn is_aux(&self) -> bool{ + false + } + fn variant(&self) -> [<$reg:camel SrcType>] { + [<$reg:camel SrcType>]::Src($crate::pac::clocks::[<$reg _ctrl>]::SRC_A::$src_variant) + } + })* + + impl GlitchlessClock for $name { + type Clock = Self; + + fn await_select(&self, clock_token: &ChangingClockToken) -> nb::Result<(), Infallible> { + let shared_dev = unsafe { self.shared_dev.get() }; + + let selected = shared_dev.[<$reg _selected>]().read().bits(); + if selected != 1 << clock_token.clock_nr { + return Err(nb::Error::WouldBlock); + } + + Ok(()) + } + } + + #[doc = "Holds register value for ClockSource for `" $name "`"] + pub enum [<$reg:camel SrcType>] { + #[doc = "Contains a valid clock source register value that is to be used to set a clock as glitchless source for `" $name "`"] + Src($crate::pac::clocks::[<$reg _ctrl>]::SRC_A), + #[doc = "Contains a valid clock source register value that is to be used to set a clock as aux source for `" $name "`"] + Aux($crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A) + } + + impl [<$reg:camel SrcType>] { + fn get_clock_id(&self) -> u8 { + match self { + Self::Src(v) => *v as u8, + Self::Aux(v) => *v as u8, + } + } + + fn unwrap_src(&self) -> $crate::pac::clocks::[<$reg _ctrl>]::SRC_A{ + match self { + Self::Src(v) => *v, + Self::Aux(_) => panic!(), + } + } + + fn unwrap_aux(&self) -> $crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A { + match self { + Self::Src(_) => panic!(), + Self::Aux(v) => *v + } + } + } + + impl $name { + /// Reset clock back to its reset source + pub fn reset_source_await(&mut self) -> nb::Result<(), Infallible> { + let shared_dev = unsafe { self.shared_dev.get() }; + + shared_dev.[<$reg _ctrl>]().modify(|_, w| { + w.src().variant(self.get_default_clock_source()) + }); + + use fugit::RateExtU32; + self.frequency = 12.MHz(); //TODO Get actual clock source.. Most likely 12 MHz though + + self.await_select(&ChangingClockToken{clock_nr:0, clock: PhantomData::}) + } + + fn set_src>(&mut self, src: &S)-> ChangingClockToken<$name> { + let shared_dev = unsafe { self.shared_dev.get() }; + + shared_dev.[<$reg _ctrl>]().modify(|_,w| { + w.src().variant(src.variant().unwrap_src()) + }); + + ChangingClockToken { + clock: PhantomData::<$name>, + clock_nr: src.variant().get_clock_id(), + } + } + + fn set_self_aux_src(&mut self) -> ChangingClockToken<$name> { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.src().variant(self.get_aux_source()) + }); + + ChangingClockToken{ + clock: PhantomData::<$name>, + clock_nr: $crate::pac::clocks::clk_ref_ctrl::SRC_A::CLKSRC_CLK_REF_AUX as u8, + } + } + } + + impl Clock for $name { + type Variant = [<$reg:camel SrcType>]; + + #[doc = "Get operating frequency for `" $name "`"] + fn freq(&self) -> HertzU32 { + self.frequency + } + + #[doc = "Configure `" $name "`"] + fn configure_clock>(&mut self, src: &S, freq: HertzU32) -> Result<(), ClockError>{ + let src_freq: HertzU32 = src.get_freq().into(); + + if freq.gt(&src_freq){ + return Err(ClockError::CantIncreaseFreq); + } + + let div = fractional_div(src_freq.to_Hz(), freq.to_Hz()).ok_or(ClockError::FrequencyTooLow)?; + + // If increasing divisor, set divisor before source. Otherwise set source + // before divisor. This avoids a momentary overspeed when e.g. switching + // to a faster source and increasing divisor to compensate. + if div > self.get_div() { + self.set_div(div); + } + + + // Set aux mux first, and then glitchless mux if this self has one + let token = if src.is_aux() { + // If switching a glitchless slice (ref or sys) to an aux source, switch + // away from aux *first* to avoid passing glitches when changing aux mux. + // Assume (!!!) glitchless source 0 is no faster than the aux source. + nb::block!(self.reset_source_await()).unwrap(); + + self.set_aux(src); + self.set_self_aux_src() + } else { + self.set_src(src) + }; + + nb::block!(self.await_select(&token)).unwrap(); + + + // Now that the source is configured, we can trust that the user-supplied + // divisor is a safe value. + self.set_div(div); + + // Store the configured frequency + use fugit::RateExtU32; + self.frequency = fractional_div(src_freq.to_Hz(), div).ok_or(ClockError::FrequencyTooHigh)?.Hz(); + + Ok(()) + } + } + } + }; + { + $( #[$attr:meta])* + struct $name:ident { + reg: $reg:ident, + auxsrc: {$($auxsrc:ident: $variant:ident),*}, + div: false + } + } => { + base_clock!{ + $(#[$attr])* + ($name, $reg, auxsrc={$($auxsrc: $variant),*}) + } + + // Just to match proper divisible clocks so we don't have to do something special in configure function + impl ClockDivision for $name { + fn set_div(&mut self, _: u32) {} + fn get_div(&self) -> u32 {1} + } + + stoppable_clock!($name, $reg); + }; + { + $( #[$attr:meta])* + struct $name:ident { + reg: $reg:ident, + auxsrc: {$($auxsrc:ident: $variant:ident),*} + } + } => { + base_clock!{ + $(#[$attr])* + ($name, $reg, auxsrc={$($auxsrc: $variant),*}) + } + + divisible_clock!($name, $reg); + stoppable_clock!($name, $reg); + }; +} + +macro_rules! divisible_clock { + ($name:ident, $reg:ident) => { + $crate::paste::paste! { + impl ClockDivision for $name { + fn set_div(&mut self, div: u32) { + unsafe { self.shared_dev.get() }.[<$reg _div>]().modify(|_, w| unsafe { + w.bits(div); + w + }); + } + fn get_div(&self) -> u32 { + unsafe { self.shared_dev.get() }.[<$reg _div>]().read().bits() + } + } + } + }; +} + +macro_rules! stoppable_clock { + ($name:ident, $reg:ident) => { + $crate::paste::paste!{ + #[doc = "Holds register value for ClockSource for `" $name "`"] + pub enum [<$reg:camel SrcType>] { + #[doc = "Contains a valid clock source register value that is to be used to set a clock as aux source for `" $name "`"] + Aux($crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A) + } + + impl [<$reg:camel SrcType>] { + fn unwrap_aux(&self) -> $crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A { + match self { + Self::Aux(v) => *v + } + } + } + + impl StoppableClock for $name { + /// Enable the clock + fn enable(&mut self) { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.enable().set_bit() + }); + } + + /// Disable the clock cleanly + fn disable(&mut self) { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.enable().clear_bit() + }); + } + + /// Disable the clock asynchronously + fn kill(&mut self) { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.kill().set_bit() + }); + } + } + + impl Clock for $name { + type Variant = [<$reg:camel SrcType>]; + + #[doc = "Get operating frequency for `" $name "`"] + fn freq(&self) -> HertzU32 { + self.frequency + } + + #[doc = "Configure `" $name "`"] + fn configure_clock>(&mut self, src: &S, freq: HertzU32) -> Result<(), ClockError>{ + let src_freq: HertzU32 = src.get_freq().into(); + + if freq.gt(&src_freq){ + return Err(ClockError::CantIncreaseFreq); + } + + let div = fractional_div(src_freq.to_Hz(), freq.to_Hz()).ok_or(ClockError::FrequencyTooLow)?; + + // If increasing divisor, set divisor before source. Otherwise set source + // before divisor. This avoids a momentary overspeed when e.g. switching + // to a faster source and increasing divisor to compensate. + if div > self.get_div() { + self.set_div(div); + } + + // If no glitchless mux, cleanly stop the clock to avoid glitches + // propagating when changing aux mux. Note it would be a really bad idea + // to do this on one of the glitchless clocks (clk_sys, clk_ref). + + // Disable clock. On clk_ref and clk_sys this does nothing, + // all other clocks have the ENABLE bit in the same position. + self.disable(); + if self.frequency > HertzU32::Hz(0) { + // Delay for 3 cycles of the target clock, for ENABLE propagation. + // Note XOSC_COUNT is not helpful here because XOSC is not + // necessarily running, nor is timer... so, 3 cycles per loop: + let sys_freq = 125_000_000; // TODO get actual sys_clk frequency + let delay_cyc = sys_freq / self.frequency.to_Hz() + 1u32; + crate::arch::delay(delay_cyc); + } + + // Set aux mux first, and then glitchless mux if this self has one + self.set_aux(src); + + // Enable clock. On clk_ref and clk_sys this does nothing, + // all other clocks have the ENABLE bit in the same posi + self.enable(); + + // Now that the source is configured, we can trust that the user-supplied + // divisor is a safe value. + self.set_div(div); + + // Store the configured frequency + use fugit::RateExtU32; + self.frequency = fractional_div(src_freq.to_Hz(), div).ok_or(ClockError::FrequencyTooHigh)?.Hz(); + + Ok(()) + } + } + } + }; +} + +macro_rules! base_clock { + { + $(#[$attr:meta])* + ($name:ident, $reg:ident, auxsrc={$($auxsrc:ident: $variant:ident),*}) + } => { + $crate::paste::paste!{ + + $(impl ValidSrc<$name> for $auxsrc { + + fn is_aux(&self) -> bool{ + true + } + fn variant(&self) -> [<$reg:camel SrcType>] { + [<$reg:camel SrcType>]::Aux($crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A::$variant) + } + })* + + $(#[$attr])* + pub struct $name { + shared_dev: ShareableClocks, + frequency: HertzU32, + } + + impl $name { + fn set_aux>(&mut self, src: &S) { + let shared_dev = unsafe { self.shared_dev.get() }; + + shared_dev.[<$reg _ctrl>]().modify(|_,w| { + w.auxsrc().variant(src.variant().unwrap_aux()) + }); + } + } + + impl Sealed for $name {} + + impl From<&$name> for HertzU32 + { + fn from(value: &$name) -> HertzU32 { + value.frequency + } + } + } + }; +} diff --git a/rp-hal/rp235x-hal/src/clocks/mod.rs b/rp-hal/rp235x-hal/src/clocks/mod.rs new file mode 100644 index 0000000..f9d0c65 --- /dev/null +++ b/rp-hal/rp235x-hal/src/clocks/mod.rs @@ -0,0 +1,746 @@ +//! Clocks (CLOCKS) +//! +//! +//! +//! ## Usage simple +//! ```no_run +//! use rp235x_hal::{self as hal, clocks::init_clocks_and_plls, watchdog::Watchdog}; +//! +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! let mut clocks = init_clocks_and_plls( +//! XOSC_CRYSTAL_FREQ, +//! peripherals.XOSC, +//! peripherals.CLOCKS, +//! peripherals.PLL_SYS, +//! peripherals.PLL_USB, +//! &mut peripherals.RESETS, +//! &mut watchdog, +//! ) +//! .ok() +//! .unwrap(); +//! ``` +//! +//! ## Usage extended +//! ```no_run +//! use fugit::RateExtU32; +//! use rp235x_hal::{clocks::{Clock, ClocksManager, ClockSource, InitError}, gpio::Pins, self as hal, pll::{common_configs::{PLL_SYS_150MHZ, PLL_USB_48MHZ}, setup_pll_blocking}, Sio, watchdog::Watchdog, xosc::setup_xosc_blocking}; +//! +//! # fn func() -> Result<(), InitError> { +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! +//! // Enable the xosc +//! let xosc = setup_xosc_blocking(peripherals.XOSC, XOSC_CRYSTAL_FREQ.Hz()).map_err(InitError::XoscErr)?; +//! +//! // Start tick in watchdog +//! watchdog.enable_tick_generation((XOSC_CRYSTAL_FREQ / 1_000_000) as u16); +//! +//! let mut clocks = ClocksManager::new(peripherals.CLOCKS); +//! +//! // Configure PLLs +//! // REF FBDIV VCO POSTDIV +//! // PLL SYS: 12 / 1 = 12MHz * 125 = 1500MHZ / 5 / 2 = 150MHz +//! // PLL USB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz +//! let pll_sys = setup_pll_blocking(peripherals.PLL_SYS, xosc.operating_frequency().into(), PLL_SYS_150MHZ, &mut clocks, &mut peripherals.RESETS).map_err(InitError::PllError)?; +//! let pll_usb = setup_pll_blocking(peripherals.PLL_USB, xosc.operating_frequency().into(), PLL_USB_48MHZ, &mut clocks, &mut peripherals.RESETS).map_err(InitError::PllError)?; +//! +//! // Configure clocks +//! // CLK_REF = XOSC (12MHz) / 1 = 12MHz +//! clocks.reference_clock.configure_clock(&xosc, xosc.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK SYS = PLL SYS (150MHz) / 1 = 150MHz +//! clocks.system_clock.configure_clock(&pll_sys, pll_sys.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK USB = PLL USB (48MHz) / 1 = 48MHz +//! clocks.usb_clock.configure_clock(&pll_usb, pll_usb.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz +//! clocks.adc_clock.configure_clock(&pll_usb, pll_usb.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK HSTX = PLL SYS (150MHz) / 1 = 150MHz +//! clocks.hstx_clock.configure_clock(&pll_sys, pll_sys.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable +//! // Normally choose clk_sys or clk_usb +//! clocks.peripheral_clock.configure_clock(&clocks.system_clock, clocks.system_clock.freq()).map_err(InitError::ClockError)?; +//! # Ok(()) +//! # } +//! ``` +//! +//! See [Chapter 8](https://rptl.io/rp2350-datasheet#section_clocks) for more details. + +use core::{convert::Infallible, marker::PhantomData}; +use fugit::{HertzU32, RateExtU32}; + +use crate::{ + pac::{self, CLOCKS, PLL_SYS, PLL_USB, RESETS, XOSC}, + pll::{ + common_configs::{PLL_SYS_150MHZ, PLL_USB_48MHZ}, + setup_pll_blocking, Error as PllError, Locked, PhaseLockedLoop, + }, + typelevel::Sealed, + watchdog::Watchdog, + xosc::{setup_xosc_blocking, CrystalOscillator, Error as XoscError, Stable}, +}; + +#[macro_use] +mod macros; +mod clock_sources; + +use clock_sources::PllSys; + +use self::clock_sources::{GPin0, GPin1, LpOsc, PllUsb, Rosc, Xosc}; + +bitfield::bitfield! { + /// Bit field mapping clock enable bits. + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Default)] + pub struct ClockGate(u64); + /// Clock gate to the clock controller. + pub sys_clock, set_sys_clock: 0; + /// Clock gate the hardware access control registers + pub sys_accessctrl, set_sys_accessctrl: 1; + /// Clock gate to the adc analog logic. + pub adc_adc, set_adc_adc: 2; + /// Clock gate to the adc peripheral. + pub sys_adc, set_sys_adc: 3; + /// Clock gate the Boot RAM + pub sys_bootram, set_sys_bootram: 4; + /// Clock gate the memory bus controller. + pub sys_busctrl, set_sys_busctrl: 5; + /// Clock gate the memory bus fabric. + pub sys_busfabric, set_sys_busfabric: 6; + /// Clock gate the dma controller. + pub sys_dma, set_sys_dma: 7; + /// Clock gate the Glitch Detector peripheral + pub sys_glitch_detector, set_sys_glitch_detector: 8; + /// Clock gate the High-Speed TX peripheral + pub hstx, set_hstx: 9; + /// Clock gate the High-Speed TX peripheral + pub sys_hstx, set_sys_hstx: 10; + /// Clock gate I2C0. + pub sys_i2c0, set_sys_i2c0: 11; + /// Clock gate I2C1. + pub sys_i2c1, set_sys_i2c1: 12; + /// Clock gate the IO controller. + pub sys_io, set_sys_io: 13; + /// Clock gate the JTAG peripheral. + pub sys_jtag, set_sys_jtag: 14; + /// Clock gate the OTP (one-time programmable memory) peripheral + pub ref_otp, set_ref_otp: 15; + /// Clock gate the OTP (one-time programmable memory) peripheral + pub sys_otp, set_sys_otp: 16; + /// Clock gate pad controller. + pub sys_pads, set_sys_pads: 17; + /// Clock gate PIO0 peripheral. + pub sys_pio0, set_sys_pio0: 18; + /// Clock gate PIO1 peripheral. + pub sys_pio1, set_sys_pio1: 19; + /// Clock gate PIO2 peripheral. + pub sys_pio2, set_sys_pio2: 20; + /// Clock gate the system PLL. + pub sys_pll_sys, set_sys_pll_sys: 21; + /// Clock gate the USB PLL. + pub sys_pll_usb, set_sys_pll_usb: 22; + /// Clock gate the Power Manager + pub ref_powman, set_ref_powman: 23; + /// Clock gate the Power Manager + pub sys_powman, set_sys_powman: 24; + /// Clock gate PWM peripheral. + pub sys_pwm, set_sys_pwm: 25; + /// Clock gate the reset controller. + pub sys_resets, set_sys_resets: 26; + /// Clock gate the ROM. + pub sys_rom, set_sys_rom: 27; + /// Clock gate the ROSC controller (not the rosc itself). + pub sys_rosc, set_sys_rosc: 28; + /// Clock gate the Power State Machine + pub sys_psm, set_sys_psm: 29; + /// Clock gate the SHA256 peripheral + pub sys_sha256, set_sys_sha256: 30; + /// Clock gate the SIO controller. + pub sys_sio, set_sys_sio: 31; + + /// Clock gate SPI0's baud generation. + pub peri_spi0, set_peri_spi0: 32; + /// Clock gate SPI0's controller.. + pub sys_spi0, set_sys_spi0: 33; + /// Clock gate SPI1's baud generation. + pub peri_spi1, set_peri_spi1: 34; + /// Clock gate SPI1's controller.. + pub sys_spi1, set_sys_spi1: 35; + /// Clock gate SRAM0. + pub sys_sram0, set_sys_sram0: 36; + /// Clock gate SRAM1. + pub sys_sram1, set_sys_sram1: 37; + /// Clock gate SRAM2. + pub sys_sram2, set_sys_sram2: 38; + /// Clock gate SRAM3. + pub sys_sram3, set_sys_sram3: 39; + /// Clock gate SRAM4. + pub sys_sram4, set_sys_sram4: 40; + /// Clock gate SRAM5. + pub sys_sram5, set_sys_sram5: 41; + /// Clock gate SRAM6 + pub sys_sram6, set_sys_sram6: 42; + /// Clock gate SRAM7 + pub sys_sram7, set_sys_sram7: 43; + /// Clock gate SRAM8 + pub sys_sram8, set_sys_sram8: 44; + /// Clock gate SRAM9 + pub sys_sram9, set_sys_sram9: 45; + /// Clock gate the system configuration controller. + pub sys_syscfg, set_sys_syscfg: 46; + /// Clock gate the system information peripheral. + pub sys_sysinfo, set_sys_sysinfo: 47; + /// Clock gate the test bench manager. + pub sys_tbman, set_sys_tbman: 48; + /// Clock gate the tick generator + pub ref_ticks, set_ref_ticks: 49; + /// Clock gate the tick generator + pub sys_ticks, set_sys_ticks: 50; + /// Clock gate the Timer 0 peripheral. + pub sys_timer0, set_sys_timer0: 51; + /// Clock gate the Timer 1 peripheral. + pub sys_timer1, set_sys_timer1: 52; + /// Clock gate the TrustZone Random Number Generator + pub sys_trng, set_sys_trng: 53; + /// Clock gate UART0's baud generation. + pub peri_uart0, set_peri_uart0: 54; + /// Clock gate UART0's controller. + pub sys_uart0, set_sys_uart0: 55; + /// Clock gate UART1's baud generation. + pub peri_uart1, set_peri_uart1: 56; + /// Clock gate UART1's controller. + pub sys_uart1, set_sys_uart1: 57; + /// Clock gate the USB controller. + pub sys_usbctrl, set_sys_usbctrl: 58; + /// Clock gate the USB logic. + pub usb, set_usb: 59; + /// Clock gate the Watchdog controller. + pub sys_watchdog, set_sys_watchdog: 60; + /// .Clock gate the XIP controller. + pub sys_xip, set_sys_xip: 61; + /// Clock gate the XOSC controller (not xosc itself). + pub sys_xosc, set_sys_xosc: 62; +} + +impl core::fmt::Debug for ClockGate { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ClockGate") + .field("sys_clock", &self.sys_clock()) + .field("sys_accessctrl", &self.sys_accessctrl()) + .field("adc_adc", &self.adc_adc()) + .field("sys_adc", &self.sys_adc()) + .field("sys_bootram", &self.sys_bootram()) + .field("sys_busctrl", &self.sys_busctrl()) + .field("sys_busfabric", &self.sys_busfabric()) + .field("sys_dma", &self.sys_dma()) + .field("sys_glitch_detector", &self.sys_glitch_detector()) + .field("hstx", &self.hstx()) + .field("sys_hstx", &self.sys_hstx()) + .field("sys_i2c0", &self.sys_i2c0()) + .field("sys_i2c1", &self.sys_i2c1()) + .field("sys_io", &self.sys_io()) + .field("sys_jtag", &self.sys_jtag()) + .field("ref_otp", &self.ref_otp()) + .field("sys_otp", &self.sys_otp()) + .field("sys_pads", &self.sys_pads()) + .field("sys_pio0", &self.sys_pio0()) + .field("sys_pio1", &self.sys_pio1()) + .field("sys_pio2", &self.sys_pio2()) + .field("sys_pll_sys", &self.sys_pll_sys()) + .field("sys_pll_usb", &self.sys_pll_usb()) + .field("ref_powman", &self.ref_powman()) + .field("sys_powman", &self.sys_powman()) + .field("sys_pwm", &self.sys_pwm()) + .field("sys_resets", &self.sys_resets()) + .field("sys_rom", &self.sys_rom()) + .field("sys_rosc", &self.sys_rosc()) + .field("sys_psm", &self.sys_psm()) + .field("sys_sha256", &self.sys_sha256()) + .field("sys_sio", &self.sys_sio()) + .field("peri_spi0", &self.peri_spi0()) + .field("sys_spi0", &self.sys_spi0()) + .field("peri_spi1", &self.peri_spi1()) + .field("sys_spi1", &self.sys_spi1()) + .field("sys_sram0", &self.sys_sram0()) + .field("sys_sram1", &self.sys_sram1()) + .field("sys_sram2", &self.sys_sram2()) + .field("sys_sram3", &self.sys_sram3()) + .field("sys_sram4", &self.sys_sram4()) + .field("sys_sram5", &self.sys_sram5()) + .field("sys_sram6", &self.sys_sram6()) + .field("sys_sram7", &self.sys_sram7()) + .field("sys_sram8", &self.sys_sram8()) + .field("sys_sram9", &self.sys_sram9()) + .field("sys_syscfg", &self.sys_syscfg()) + .field("sys_sysinfo", &self.sys_sysinfo()) + .field("sys_tbman", &self.sys_tbman()) + .field("ref_ticks", &self.ref_ticks()) + .field("sys_ticks", &self.sys_ticks()) + .field("sys_timer0", &self.sys_timer0()) + .field("sys_timer1", &self.sys_timer1()) + .field("sys_trng", &self.sys_trng()) + .field("peri_uart0", &self.peri_uart0()) + .field("sys_uart0", &self.sys_uart0()) + .field("peri_uart1", &self.peri_uart1()) + .field("sys_uart1", &self.sys_uart1()) + .field("sys_usbctrl", &self.sys_usbctrl()) + .field("usb", &self.usb()) + .field("sys_watchdog", &self.sys_watchdog()) + .field("sys_xip", &self.sys_xip()) + .field("sys_xosc", &self.sys_xosc()) + .finish() + } +} + +#[derive(Copy, Clone)] +/// Provides refs to the CLOCKS block. +struct ShareableClocks { + _internal: (), +} + +impl ShareableClocks { + fn new(_clocks: &mut CLOCKS) -> Self { + ShareableClocks { _internal: () } + } + + unsafe fn get(&self) -> &pac::clocks::RegisterBlock { + &*CLOCKS::ptr() + } +} + +/// Something when wrong setting up the clock +#[non_exhaustive] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClockError { + /// The frequency desired is higher than the source frequency + CantIncreaseFreq, + /// The desired frequency is to high (would overflow an u32) + FrequencyTooHigh, + /// The desired frequency is too low (divider can't reach the desired value) + FrequencyTooLow, +} + +/// For clocks +pub trait Clock: Sealed + Sized { + /// Enum with valid source clocks register values for `Clock` + type Variant; + + /// Get operating frequency + fn freq(&self) -> HertzU32; + + /// Configure this clock based on a clock source and desired frequency + fn configure_clock>( + &mut self, + src: &S, + freq: HertzU32, + ) -> Result<(), ClockError>; +} + +/// For clocks with a divider +trait ClockDivision { + /// Set integer divider value. + fn set_div(&mut self, div: u32); + /// Get integer diveder value. + fn get_div(&self) -> u32; +} + +/// Clock with glitchless source +trait GlitchlessClock { + /// Self type to hand to ChangingClockToken + type Clock: Clock; + + /// Await switching clock sources without glitches. Needs a token that is returned when setting + fn await_select( + &self, + clock_token: &ChangingClockToken, + ) -> nb::Result<(), Infallible>; +} + +/// Token which can be used to await the glitchless switch +pub struct ChangingClockToken { + clock_nr: u8, + clock: PhantomData, +} + +/// For clocks that can be disabled +pub trait StoppableClock: Sealed { + /// Enables the clock. + fn enable(&mut self); + + /// Disables the clock. + fn disable(&mut self); + + /// Kills the clock. + fn kill(&mut self); +} + +/// Trait for things that can be used as clock source +pub trait ClockSource: Sealed { + /// Get the operating frequency for this source + /// + /// Used to determine the divisor + fn get_freq(&self) -> HertzU32; +} + +/// Trait to contrain which ClockSource is valid for which Clock +pub trait ValidSrc: Sealed + ClockSource { + /// Is this a ClockSource for src or aux? + fn is_aux(&self) -> bool; + /// Get register value for this ClockSource + fn variant(&self) -> C::Variant; +} + +clocks! { + /// GPIO Output 0 Clock Generator + /// + /// Clock output to GPIO. Can be used to clock external devices or debug on + /// chip clocks with a logic analyser or oscilloscope. + struct GpioOutput0Clock { + init_freq: 0, + reg: clk_gpout0, + auxsrc: { + PllSys: CLKSRC_PLL_SYS, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1, + PllUsb: CLKSRC_PLL_USB, + /* CLKSRC_PLL_USB_PRIMARY_REF_OPCG */ + Rosc: ROSC_CLKSRC, + Xosc: XOSC_CLKSRC, + LpOsc: LPOSC_CLKSRC, + SystemClock: CLK_SYS, + UsbClock: CLK_USB, + AdcClock: CLK_ADC, + ReferenceClock: CLK_REF, + PeripheralClock: CLK_PERI, + HstxClock: CLK_HSTX + /* OTP_CLK2FC */ + } + } + /// GPIO Output 1 Clock Generator + /// + /// Clock output to GPIO. Can be used to clock external devices or debug on + /// chip clocks with a logic analyser or oscilloscope. + struct GpioOutput1Clock { + init_freq: 0, + reg: clk_gpout1, + auxsrc: { + PllSys: CLKSRC_PLL_SYS, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1, + PllUsb: CLKSRC_PLL_USB, + /* CLKSRC_PLL_USB_PRIMARY_REF_OPCG */ + Rosc: ROSC_CLKSRC, + Xosc: XOSC_CLKSRC, + LpOsc: LPOSC_CLKSRC, + SystemClock: CLK_SYS, + UsbClock: CLK_USB, + AdcClock: CLK_ADC, + ReferenceClock: CLK_REF, + PeripheralClock: CLK_PERI, + HstxClock: CLK_HSTX + /* OTP_CLK2FC */ + } + } + /// GPIO Output 2 Clock Generator + /// + /// Clock output to GPIO. Can be used to clock external devices or debug on + /// chip clocks with a logic analyser or oscilloscope. + struct GpioOutput2Clock { + init_freq: 0, + reg: clk_gpout2, + auxsrc: { + PllSys: CLKSRC_PLL_SYS, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1, + PllUsb: CLKSRC_PLL_USB, + /* CLKSRC_PLL_USB_PRIMARY_REF_OPCG */ + Rosc: ROSC_CLKSRC_PH, + Xosc: XOSC_CLKSRC, + LpOsc: LPOSC_CLKSRC, + SystemClock: CLK_SYS, + UsbClock: CLK_USB, + AdcClock: CLK_ADC, + ReferenceClock: CLK_REF, + PeripheralClock: CLK_PERI, + HstxClock: CLK_HSTX + /* OTP_CLK2FC */ + } + } + /// GPIO Output 3 Clock Generator + /// + /// Clock output to GPIO. Can be used to clock external devices or debug on + /// chip clocks with a logic analyser or oscilloscope. + struct GpioOutput3Clock { + init_freq: 0, + reg: clk_gpout3, + auxsrc: { + PllSys: CLKSRC_PLL_SYS, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1, + PllUsb: CLKSRC_PLL_USB, + /* CLKSRC_PLL_USB_PRIMARY_REF_OPCG */ + Rosc: ROSC_CLKSRC_PH, + Xosc: XOSC_CLKSRC, + LpOsc: LPOSC_CLKSRC, + SystemClock: CLK_SYS, + UsbClock: CLK_USB, + AdcClock: CLK_ADC, + ReferenceClock: CLK_REF, + PeripheralClock: CLK_PERI, + HstxClock: CLK_HSTX + /* OTP_CLK2FC */ + } + } + /// Reference clock that is always running 6 - 12MHz unless in DORMANT mode. + /// + /// Runs from Ring Oscillator (ROSC) at power-up but can be switched to + /// Crystal Oscillator (XOSC) for more accuracy. + struct ReferenceClock { + init_freq: 12_000_000, + // Starts from ROSC which actually varies with input voltage etc, + // but 12 MHz seems to be a good value + reg: clk_ref, + src: { + Rosc: ROSC_CLKSRC_PH, + /* Aux: CLKSRC_CLK_REF_AUX, */ + Xosc: XOSC_CLKSRC, + LpOsc: LPOSC_CLKSRC + }, + auxsrc: { + PllUsb:CLKSRC_PLL_USB, + GPin0:CLKSRC_GPIN0, + GPin1:CLKSRC_GPIN1 + /* CLKSRC_PLL_USB_PRIMARY_REF_OPCG */ + } + } + /// System clock that is always running unless in DORMANT mode. + /// + /// Runs from clk_ref at power-up but is typically switched to a PLL. + struct SystemClock { + init_freq: 12_000_000, // ref_clk is 12 MHz + reg: clk_sys, + src: { + ReferenceClock: CLK_REF, + SystemClock: CLKSRC_CLK_SYS_AUX + }, + auxsrc: { + PllSys: CLKSRC_PLL_SYS, + PllUsb: CLKSRC_PLL_USB, + Rosc: ROSC_CLKSRC, + Xosc: XOSC_CLKSRC, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1 + } + } + /// Peripheral clock. + /// + /// Typically runs from 12 - 150MHz clk_sys but allows peripherals to run at + /// a consistent speed if clk_sys is changed by software. + struct PeripheralClock { + init_freq: 12_000_000, // sys_clk is 12 MHz + reg: clk_peri, + auxsrc: { + SystemClock: CLK_SYS, + PllSys: CLKSRC_PLL_SYS, + PllUsb:CLKSRC_PLL_USB, + Rosc: ROSC_CLKSRC_PH, + Xosc: XOSC_CLKSRC, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1 + }, + div: false + } + /// HSTX (High-Speed Transmitter) Clock + struct HstxClock { + init_freq: 0, + reg: clk_hstx, + auxsrc: { + SystemClock: CLK_SYS, + PllUsb: CLKSRC_PLL_USB, + PllSys: CLKSRC_PLL_SYS, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1 + } + } + /// USB reference clock. + /// + /// Must be 48MHz. + struct UsbClock { + init_freq: 0, + reg: clk_usb, + auxsrc: { + PllUsb: CLKSRC_PLL_USB, + PllSys: CLKSRC_PLL_SYS, + Rosc: ROSC_CLKSRC_PH, + Xosc: XOSC_CLKSRC, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1 + } + } + /// ADC reference clock. + /// + /// Must be 48MHz. + struct AdcClock { + init_freq: 0, + reg: clk_adc, + auxsrc: { + PllUsb: CLKSRC_PLL_USB, + PllSys: CLKSRC_PLL_SYS, + Rosc: ROSC_CLKSRC_PH, + Xosc: XOSC_CLKSRC, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1 + } + } +} + +impl SystemClock { + fn get_default_clock_source(&self) -> pac::clocks::clk_sys_ctrl::SRC_A { + pac::clocks::clk_sys_ctrl::SRC_A::CLK_REF + } + + fn get_aux_source(&self) -> pac::clocks::clk_sys_ctrl::SRC_A { + pac::clocks::clk_sys_ctrl::SRC_A::CLKSRC_CLK_SYS_AUX + } +} + +impl ReferenceClock { + fn get_default_clock_source(&self) -> pac::clocks::clk_ref_ctrl::SRC_A { + pac::clocks::clk_ref_ctrl::SRC_A::ROSC_CLKSRC_PH + } + + fn get_aux_source(&self) -> pac::clocks::clk_ref_ctrl::SRC_A { + pac::clocks::clk_ref_ctrl::SRC_A::CLKSRC_CLK_REF_AUX + } +} + +impl ClocksManager { + /// Initialize the clocks to a sane default + pub fn init_default( + &mut self, + + xosc: &CrystalOscillator, + pll_sys: &PhaseLockedLoop, + pll_usb: &PhaseLockedLoop, + ) -> Result<(), ClockError> { + // Configure clocks + // CLK_REF = XOSC (12MHz) / 1 = 12MHz + self.reference_clock + .configure_clock(xosc, xosc.get_freq())?; + + // CLK SYS = PLL SYS (150MHz) / 1 = 150MHz + self.system_clock + .configure_clock(pll_sys, pll_sys.get_freq())?; + + // CLK USB = PLL USB (48MHz) / 1 = 48MHz + self.usb_clock + .configure_clock(pll_usb, pll_usb.get_freq())?; + + // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz + self.adc_clock + .configure_clock(pll_usb, pll_usb.get_freq())?; + + // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable + // Normally choose clk_sys or clk_usb + self.peripheral_clock + .configure_clock(&self.system_clock, self.system_clock.freq()) + } + + /// Configure the clocks staying ON during deep-sleep. + pub fn configure_sleep_enable(&mut self, clock_gate: ClockGate) { + self.clocks + .sleep_en0() + .write(|w| unsafe { w.bits(clock_gate.0 as u32) }); + self.clocks + .sleep_en1() + .write(|w| unsafe { w.bits((clock_gate.0 >> 32) as u32) }); + } + + /// Read the clock gate configuration while the device is in its (deep) sleep state. + pub fn sleep_enable(&self) -> ClockGate { + ClockGate( + (u64::from(self.clocks.sleep_en1().read().bits()) << 32) + | u64::from(self.clocks.sleep_en0().read().bits()), + ) + } + + /// Read the clock gate configuration while the device is in its wake state. + pub fn wake_enable(&self) -> ClockGate { + ClockGate( + (u64::from(self.clocks.wake_en1().read().bits()) << 32) + | u64::from(self.clocks.wake_en0().read().bits()), + ) + } + + /// Releases the CLOCKS block + pub fn free(self) -> CLOCKS { + self.clocks + } +} + +/// Possible init errors +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InitError { + /// Something went wrong setting up the Xosc + XoscErr(XoscError), + /// Something went wrong setting up the Pll + PllError(PllError), + /// Something went wrong setting up the Clocks + ClockError(ClockError), +} + +/// Initialize the clocks and plls according to the reference implementation +pub fn init_clocks_and_plls( + xosc_crystal_freq: u32, + xosc_dev: XOSC, + clocks_dev: CLOCKS, + pll_sys_dev: PLL_SYS, + pll_usb_dev: PLL_USB, + resets: &mut RESETS, + watchdog: &mut Watchdog, +) -> Result { + let xosc = setup_xosc_blocking(xosc_dev, xosc_crystal_freq.Hz()).map_err(InitError::XoscErr)?; + + // Configure watchdog tick generation to tick over every microsecond + watchdog.enable_tick_generation((xosc_crystal_freq / 1_000_000) as u16); + + let mut clocks = ClocksManager::new(clocks_dev); + + let pll_sys = setup_pll_blocking( + pll_sys_dev, + xosc.operating_frequency(), + PLL_SYS_150MHZ, + &mut clocks, + resets, + ) + .map_err(InitError::PllError)?; + let pll_usb = setup_pll_blocking( + pll_usb_dev, + xosc.operating_frequency(), + PLL_USB_48MHZ, + &mut clocks, + resets, + ) + .map_err(InitError::PllError)?; + + clocks + .init_default(&xosc, &pll_sys, &pll_usb) + .map_err(InitError::ClockError)?; + Ok(clocks) +} + +// Calculates (numerator<<16)/denominator. +fn fractional_div(numerator: u32, denominator: u32) -> Option { + let numerator: u64 = u64::from(numerator) * 65536; + let denominator: u64 = u64::from(denominator); + let result = numerator / denominator; + result.try_into().ok() +} diff --git a/rp-hal/rp235x-hal/src/critical_section_impl.rs b/rp-hal/rp235x-hal/src/critical_section_impl.rs new file mode 100644 index 0000000..abe0a8e --- /dev/null +++ b/rp-hal/rp235x-hal/src/critical_section_impl.rs @@ -0,0 +1,91 @@ +use core::sync::atomic::{AtomicU8, Ordering}; + +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 = crate::arch::interrupts_enabled(); + // 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 = crate::Sio::core() as u8 + 1_u8; + // 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 + crate::arch::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) = crate::sio::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 { + crate::arch::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 + crate::sio::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 { + crate::arch::interrupt_enable(); + } + } + } +} diff --git a/rp-hal/rp235x-hal/src/dcp.rs b/rp-hal/rp235x-hal/src/dcp.rs new file mode 100644 index 0000000..0abe04c --- /dev/null +++ b/rp-hal/rp235x-hal/src/dcp.rs @@ -0,0 +1,159 @@ +//! Double-Co-Pro Support +//! +//! The Double-Co-Pro is a very simple sort-of-FPU. It doesn't execute Arm FPU +//! instructions like the Cortex-M33F's built-in FPU does. But it can perform +//! some basic operations that can greatly speed up a pure-software FPU library. +//! We can: +//! +//! * copy 64-bit values in to the co-pro with an `mrrc` instruction +//! * copy 64-bit values out of the co-pro with an `mcrr` instruction +//! * trigger the co-pro to do a thing with a `cdp` instruction +//! +//! We provide some functions here which can do accelerated FPU operations. The +//! assembly is largely take from the Raspberry Pi Pico SDK. +//! +//! If you enable the `dcp-fast-f64` feature, we will replace the standard +//! `__aeabi_dadd` and `__aeabi_dmul` functions with versions that complete +//! around 3x to 6x faster. + +/// Perform a fast add of two f64 values, using the DCP. +pub fn dadd(x: f64, y: f64) -> f64 { + unsafe { _aapcs_dcp_add(x, y) } +} + +#[cfg(feature = "dcp-fast-f64")] +#[no_mangle] +/// Replace the built-in `__aeabi_dadd` function with one that uses the DCP. +extern "aapcs" fn __aeabi_dadd(x: f64, y: f64) -> f64 { + unsafe { _aapcs_dcp_add(x, y) } +} + +/// Perform a fast multiply of two f64 values, using the DCP. +pub fn dmul(x: f64, y: f64) -> f64 { + unsafe { _aapcs_dcp_mul(x, y) } +} + +#[cfg(feature = "dcp-fast-f64")] +#[no_mangle] +/// Replace the built-in `__aeabi_dmul` function with one that uses the DCP. +extern "aapcs" fn __aeabi_dmul(x: f64, y: f64) -> f64 { + unsafe { _aapcs_dcp_mul(x, y) } +} + +extern "aapcs" { + fn _aapcs_dcp_mul(x: f64, y: f64) -> f64; + fn _aapcs_dcp_add(x: f64, y: f64) -> f64; +} + +core::arch::global_asm!( + r#" + .syntax unified + .cpu cortex-m33 + .cfi_sections .debug_frame + .thumb + "#, + // Do a fast f64 multiply on the DCP. + // + // Assumes the DCP has been enabled in the CPACR register. + // + // * First f64 is in r0,r1 + // * Second f64 is in r2,r3 + // * Result is in r0,r1 + // + // This function is Copyright (c) 2024 Raspberry Pi (Trading) Ltd. + // SPDX-License-Identifier: BSD-3-Clause + r#" + .global _aapcs_dcp_mul + .type _aapcs_dcp_mul,%function + .cfi_startproc + .p2align 2 + 1: + push {{lr}} // Save LR + bl generic_save_state // Run the save/restore routine + b 1f // Jump forward to run the routine + _aapcs_dcp_mul: + mrc2 p4,#0,apsr_nzcv,c0,c0,#1 // PCMP apsr_nzcv - If DCP is running, set the Negative flag + bmi 1b // Branch back if minus (i.e. negative flag is set) + 1: // Usually we fall right through, which is fast + push {{r4,r14}} // Save the callee-save temp registers we are using + mcrr p4,#1,r0,r1,c0 // WXUP r0,r1 - write r1|r0 to xm and xe + mcrr p4,#1,r2,r3,c1 // WYUP r2,r3 - write r3|r2 to ym and ye + mrrc p4,#0,r0,r1,c4 // RXMS r0,r1,0 - read xm Q62-s + mrrc p4,#0,r2,r3,c5 // RYMS r2,r3,0 - read xy Q62-s + umull r4,r12,r0,r2 // Unsigned Long Multiply r12|r4 := r0*r2 + movs r14,#0 // r14 = 0 + umlal r12,r14,r0,r3 // Unsigned Long Multiply with Accumulate r14|r12 += r0*r3 + umlal r12,r14,r1,r2 // Unsigned Long Multiply with Accumulate r14|r12 += r1*r2 + mcrr p4,#2,r4,r12,c0 // WXMS r4,r12 - set xm bit 0 if r12|r4 is 0, or 1 if r12|r4 is non-zero + movs r4,#0 // r4 = 0 + umlal r14,r4,r1,r3 // Unsigned Long Multiply with Accumulate r4|r14 += r1*r3 + mcrr p4,#3,r14,r4,c0 // WXMO r14,r4 - write xm direct OR into b0, add exponents, XOR signs + cdp p4,#8,c0,c0,c0,#1 // NRDD - normalise and round double-precision + mrrc p4,#5,r0,r1,c0 // RDDM r0,r1 - read DMUL result packed from xm and xe + pop {{r4,lr}} // Restore the callee-save temp registers we are using + bx lr // Return to caller + .cfi_endproc + .size _aapcs_dcp_mul, . - _aapcs_dcp_mul + "#, + // Do a fast f64 add on the DCP. + // + // Assumes the DCP has been enabled in the CPACR register. + // + // * First f64 is in r0,r1 + // * Second f64 is in r2,r3 + // * Result is in r0,r1 + // + // This function is Copyright (c) 2024 Raspberry Pi (Trading) Ltd. + // SPDX-License-Identifier: BSD-3-Clause + r#" + .global _aapcs_dcp_add + .type _aapcs_dcp_add,%function + .cfi_startproc + .p2align 2 + 1: + push {{lr}} // Save LR + bl generic_save_state // Run the save/restore routine + b 1f // Jump forward to run the routine + _aapcs_dcp_add: + mrc2 p4,#0,apsr_nzcv,c0,c0,#1 // PCMP apsr_nzcv - If DCP is running, set the Negative flag + bmi 1b // Branch back if minus (i.e. negative flag is set) + 1: // Usually we fall right through, which is fast + mcrr p4,#1,r0,r1,c0 // WXUP r0,r1 - write r1|r0 to xm and xe + mcrr p4,#1,r2,r3,c1 // WYUP r2,r3 - write r3|r2 to ym and ye + cdp p4,#0,c0,c0,c1,#0 // ADD0 - compare X-Y, set status + cdp p4,#1,c0,c0,c1,#0 // ADD1 - xm := ±xm+±ym>>s or ±ym+±xm>>s + cdp p4,#8,c0,c0,c0,#1 // NRDD - normalise and round double-precision + mrrc p4,#1,r0,r1,c0 // RDDA r0,r1 - read DADD result packed from xm and xe + bx lr // Return to caller (or to state restore routine) + .cfi_endproc + .size _aapcs_dcp_add, . - _aapcs_dcp_add + "#, + // This code grabs the DCP state and pushes it to the stack. + // It then does a subroutine call back to where it came from + // and when that subroutine finishes (the original function we were + // trying to call), then we return here and restore the DCP state before + // actually returning to the caller. + r#" + .thumb_func + generic_save_state: + sub sp, #24 + push {{r0, r1}} + // save DCP state here + mrrc2 p4,#0,r0,r1,c8 // PXMD r0, r1 + strd r0, r1, [sp, #8 + 0] + mrrc2 p4,#0,r0,r1,c9 // PYMD r0, r1 + strd r0, r1, [sp, #8 + 8] + mrrc p4,#0,r0,r1,c10 // REFD r0, r1 + strd r0, r1, [sp, #8 + 16] + pop {{r0, r1}} + blx lr // briefly visit the function we're wrapping + // now restore the DCP state from the stack + pop {{r12, r14}} + mcrr p4,#0,r12,r14,c0 // WXMD r12, r14 + pop {{r12, r14}} + mcrr p4,#0,r12,r14,c1 // WYMD r12, r14 + pop {{r12, r14}} + mcrr p4,#0,r12,r14,c2 // WEFD r12, r14 + pop {{pc}} + "#, +); diff --git a/rp-hal/rp235x-hal/src/dma/bidirectional.rs b/rp-hal/rp235x-hal/src/dma/bidirectional.rs new file mode 100644 index 0000000..f6894d4 --- /dev/null +++ b/rp-hal/rp235x-hal/src/dma/bidirectional.rs @@ -0,0 +1,160 @@ +//! Bidirectional DMA transfers + +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::{ + single_channel::{ChannelConfig, SingleChannel}, + Pace, ReadTarget, WriteTarget, +}; + +/// DMA configuration for sending and receiving data simultaneously +pub struct Config +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + ch: (CH1, CH2), + from: FROM, + bidi: BIDI, + to: TO, + from_pace: Pace, + to_pace: Pace, + bswap: bool, +} + +impl Config +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + /// Create a DMA configuration for sending and receiving data simultaneously + pub fn new(ch: (CH1, CH2), from: FROM, bidi: BIDI, to: TO) -> Config { + Config { + ch, + from, + bidi, + to, + bswap: false, + from_pace: Pace::PreferSink, + to_pace: Pace::PreferSink, + } + } + + #[allow(clippy::wrong_self_convention)] + /// Set the transfer pacing for the DMA transfer from the source + pub fn from_pace(&mut self, pace: Pace) { + self.from_pace = pace; + } + + #[allow(clippy::wrong_self_convention)] + /// Set the transfer pacing for the DMA transfer to the target + pub fn to_pace(&mut self, pace: Pace) { + self.to_pace = pace; + } + + /// Enable/disable byteswapping for the DMA transfers, default value is false. + /// + /// For byte data, this has no effect. For halfword data, the two bytes of + /// each halfword are swapped. For word data, the four bytes of each word + /// are swapped to reverse order. + /// + /// This is a convenient way to change the (half-)words' byte endianness on the fly. + pub fn bswap(&mut self, bswap: bool) { + self.bswap = bswap; + } + + /// Start the DMA transfer + pub fn start(mut self) -> Transfer { + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the DMA channel and start it. + self.ch.0.config( + &self.from, + &mut self.bidi, + self.from_pace, + self.bswap, + None, + false, + ); + self.ch.1.config( + &self.bidi, + &mut self.to, + self.to_pace, + self.bswap, + None, + false, + ); + self.ch.0.start_both(&mut self.ch.1); + + Transfer { + ch: self.ch, + from: self.from, + bidi: self.bidi, + to: self.to, + } + } +} + +/// Instance of a bidirectional DMA transfer +pub struct Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + ch: (CH1, CH2), + from: FROM, + bidi: BIDI, + to: TO, +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + /// Check if an interrupt is pending for either channel and clear the corresponding pending bit + pub fn check_irq0(&mut self) -> bool { + let a = self.ch.0.check_irq0(); + let b = self.ch.1.check_irq0(); + a | b + } + + /// Check if an interrupt is pending for either channel and clear the corresponding pending bit + pub fn check_irq1(&mut self) -> bool { + let a = self.ch.0.check_irq1(); + let b = self.ch.1.check_irq1(); + a | b + } + + /// Check if the transfer is completed + pub fn is_done(&self) -> bool { + let a = self.ch.1.ch().ch_ctrl_trig().read().busy().bit_is_set(); + let b = self.ch.0.ch().ch_ctrl_trig().read().busy().bit_is_set(); + !(a | b) + } + + /// Block until transfer is complete + pub fn wait(self) -> ((CH1, CH2), FROM, BIDI, TO) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // TODO: Use a tuple type? + ((self.ch.0, self.ch.1), self.from, self.bidi, self.to) + } +} diff --git a/rp-hal/rp235x-hal/src/dma/double_buffer.rs b/rp-hal/rp235x-hal/src/dma/double_buffer.rs new file mode 100644 index 0000000..17f2933 --- /dev/null +++ b/rp-hal/rp235x-hal/src/dma/double_buffer.rs @@ -0,0 +1,316 @@ +//! Double-buffered DMA Transfers + +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::{ + single_channel::ChannelConfig, single_channel::SingleChannel, EndlessReadTarget, + EndlessWriteTarget, Pace, ReadTarget, WriteTarget, +}; + +/// Configuration for double-buffered DMA transfer +pub struct Config { + ch: (CH1, CH2), + from: FROM, + to: TO, + bswap: bool, + pace: Pace, +} + +impl Config +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Create a new configuration for double-buffered DMA transfer + pub fn new(ch: (CH1, CH2), from: FROM, to: TO) -> Config { + Config { + ch, + from, + to, + bswap: false, + pace: Pace::PreferSource, + } + } + + /// Sets the (preferred) pace for the DMA transfers. + /// + /// Usually, the code will automatically configure the correct pace, but + /// peripheral-to-peripheral transfers require the user to manually select whether the source + /// or the sink shall be queried for the pace signal. + pub fn pace(&mut self, pace: Pace) { + self.pace = pace; + } + + /// Enable/disable byteswapping for the DMA transfers, default value is false. + /// + /// For byte data, this has no effect. For halfword data, the two bytes of + /// each halfword are swapped. For word data, the four bytes of each word + /// are swapped to reverse order. + /// + /// This is a convenient way to change the (half-)words' byte endianness on the fly. + pub fn bswap(&mut self, bswap: bool) { + self.bswap = bswap; + } + + /// Start the DMA transfer + pub fn start(mut self) -> Transfer { + // TODO: Do we want to call any callbacks to configure source/sink? + + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the DMA channel and start it. + self.ch + .0 + .config(&self.from, &mut self.to, self.pace, self.bswap, None, true); + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: (), + second_ch: false, + } + } +} + +/// State for a double-buffered read +pub struct ReadNext(BUF); +/// State for a double-buffered write +pub struct WriteNext(BUF); + +/// Instance of a double-buffered DMA transfer +pub struct Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + ch: (CH1, CH2), + from: FROM, + to: TO, + pace: Pace, + bswap: bool, + state: STATE, + second_ch: bool, +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Check if an interrupt is pending for the active channel and clear the corresponding pending bit + pub fn check_irq0(&mut self) -> bool { + if self.second_ch { + self.ch.1.check_irq0() + } else { + self.ch.0.check_irq0() + } + } + + /// Check if an interrupt is pending for the active channel and clear the corresponding pending bit + pub fn check_irq1(&mut self) -> bool { + if self.second_ch { + self.ch.1.check_irq1() + } else { + self.ch.0.check_irq1() + } + } + + /// Check if the transfer is completed + pub fn is_done(&self) -> bool { + if self.second_ch { + !self.ch.1.ch().ch_ctrl_trig().read().busy().bit_is_set() + } else { + !self.ch.0.ch().ch_ctrl_trig().read().busy().bit_is_set() + } + } +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget + EndlessWriteTarget, +{ + /// Block until transfer completed + pub fn wait(self) -> (CH1, CH2, FROM, TO) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // TODO: Use a tuple type? + (self.ch.0, self.ch.1, self.from, self.to) + } +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget + EndlessWriteTarget, +{ + /// Perform the next read of a double-buffered sequence + pub fn read_next>( + mut self, + buf: BUF, + ) -> Transfer> { + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the _other_ DMA channel, but do not start it yet. + if self.second_ch { + self.ch + .0 + .config(&buf, &mut self.to, self.pace, self.bswap, None, false); + } else { + self.ch + .1 + .config(&buf, &mut self.to, self.pace, self.bswap, None, false); + } + + // Chain the first channel to the second. + if self.second_ch { + self.ch.1.set_chain_to_enabled(&mut self.ch.0); + } else { + self.ch.0.set_chain_to_enabled(&mut self.ch.1); + } + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: ReadNext(buf), + second_ch: self.second_ch, + } + } +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget + EndlessReadTarget, + TO: WriteTarget, +{ + /// Perform the next write of a double-buffered sequence + pub fn write_next>( + mut self, + mut buf: BUF, + ) -> Transfer> { + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the _other_ DMA channel, but do not start it yet. + if self.second_ch { + self.ch + .0 + .config(&self.from, &mut buf, self.pace, self.bswap, None, false); + } else { + self.ch + .1 + .config(&self.from, &mut buf, self.pace, self.bswap, None, false); + } + + // Chain the first channel to the second. + if self.second_ch { + self.ch.1.set_chain_to_enabled(&mut self.ch.0); + } else { + self.ch.0.set_chain_to_enabled(&mut self.ch.1); + } + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: WriteNext(buf), + second_ch: self.second_ch, + } + } +} + +impl Transfer> +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget + EndlessWriteTarget, + NEXT: ReadTarget, +{ + /// Block until the the transfer is complete + pub fn wait(self) -> (FROM, Transfer) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Invert second_ch as now the other channel is the "active" channel. + ( + self.from, + Transfer { + ch: self.ch, + from: self.state.0, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: (), + second_ch: !self.second_ch, + }, + ) + } +} + +impl Transfer> +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget + EndlessReadTarget, + TO: WriteTarget, + NEXT: WriteTarget, +{ + /// Block until transfer is complete + pub fn wait(self) -> (TO, Transfer) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Invert second_ch as now the other channel is the "active" channel. + ( + self.to, + Transfer { + ch: self.ch, + from: self.from, + to: self.state.0, + pace: self.pace, + bswap: self.bswap, + state: (), + second_ch: !self.second_ch, + }, + ) + } +} diff --git a/rp-hal/rp235x-hal/src/dma/mod.rs b/rp-hal/rp235x-hal/src/dma/mod.rs new file mode 100644 index 0000000..f879189 --- /dev/null +++ b/rp-hal/rp235x-hal/src/dma/mod.rs @@ -0,0 +1,327 @@ +//! Direct memory access (DMA). +//! +//! The DMA unit of the rp235x seems very simplistic at first when compared to other MCUs. For +//! example, the individual DMA channels do not support chaining multiple buffers. However, within +//! certain limits, the DMA engine supports a wide range of transfer and buffer types, often by +//! combining multiple DMA channels: +//! +//! * Simple RX/TX transfers filling a single buffer or transferring data from one peripheral to +//! another. +//! * RX/TX transfers that use multiple chained buffers: These transfers require two channels to +//! be combined, where the first DMA channel configures the second DMA channel. An example for +//! this transfer type can be found in the datasheet. +//! * Repeated transfers from/to a set of buffers: By allocating one channel per buffer and +//! chaining the channels together, continuous transfers to a set of ring buffers can be +//! achieved. Note, however, that the MCU manually needs to reconfigure the DMA units unless the +//! buffer addresses and sizes are aligned, in which case the ring buffer functionality of the +//! DMA engine can be used. Even then, however, at least two DMA channels are required as a +//! channel cannot be chained to itself. +//! +//! This API tries to provide three types of buffers: Single buffers, double-buffered transfers +//! where the user can specify the next buffer while the previous is being transferred, and +//! automatic continuous ring buffers consisting of two aligned buffers being read or written +//! alternatingly. + +use core::marker::PhantomData; +use embedded_dma::{ReadBuffer, WriteBuffer}; + +use crate::{ + pac::{self, DMA}, + resets::SubsystemReset, + typelevel::Sealed, +}; +// Export these types for easier use by external code +pub use crate::dma::single_channel::SingleChannel; + +// Bring in our submodules +pub mod bidirectional; +pub mod double_buffer; +pub mod single_buffer; +mod single_channel; + +/// DMA unit. +pub trait DMAExt: Sealed { + /// Splits the DMA unit into its individual channels. + fn split(self, resets: &mut pac::RESETS) -> Channels; + /// Splits the DMA unit into its individual channels with runtime ownership + fn dyn_split(self, resets: &mut pac::RESETS) -> DynChannels; +} + +/// DMA channel. +pub struct Channel { + _phantom: PhantomData, +} + +/// DMA channel identifier. +pub trait ChannelIndex: Sealed { + /// Numerical index of the DMA channel (0..11). + fn id() -> u8; +} + +macro_rules! channels { + ( + $($CHX:ident: ($chX:ident, $x:expr),)+ + ) => { + impl DMAExt for DMA { + fn split(self, resets: &mut pac::RESETS) -> Channels { + self.reset_bring_down(resets); + self.reset_bring_up(resets); + + Channels { + $( + $chX: Channel { + _phantom: PhantomData, + }, + )+ + } + } + + fn dyn_split(self, resets: &mut pac::RESETS) -> DynChannels{ + self.reset_bring_down(resets); + self.reset_bring_up(resets); + + DynChannels { + $( + $chX: Some(Channel { + _phantom: PhantomData, + }), + )+ + } + } + } + + impl Sealed for DMA {} + + /// Set of DMA channels. + pub struct Channels { + $( + /// DMA channel. + pub $chX: Channel<$CHX>, + )+ + } + $( + /// DMA channel identifier. + pub struct $CHX; + impl ChannelIndex for $CHX { + fn id() -> u8 { + $x + } + } + + impl Sealed for $CHX {} + )+ + + /// Set of DMA channels with runtime ownership. + pub struct DynChannels { + $( + /// DMA channel. + pub $chX: Option>, + )+ + } + } +} + +channels! { + CH0: (ch0, 0), + CH1: (ch1, 1), + CH2: (ch2, 2), + CH3: (ch3, 3), + CH4: (ch4, 4), + CH5: (ch5, 5), + CH6: (ch6, 6), + CH7: (ch7, 7), + CH8: (ch8, 8), + CH9: (ch9, 9), + CH10:(ch10, 10), + CH11:(ch11, 11), +} + +trait ChannelRegs { + unsafe fn ptr() -> *const pac::dma::CH; + fn regs(&self) -> &pac::dma::CH; +} + +impl ChannelRegs for Channel { + unsafe fn ptr() -> *const pac::dma::CH { + (*pac::DMA::ptr()).ch(CH::id() as usize) + } + + fn regs(&self) -> &pac::dma::CH { + unsafe { &*Self::ptr() } + } +} + +/// Trait which is implemented by anything that can be read via DMA. +/// +/// # Safety +/// +/// The implementing type must be safe to use for DMA reads. This means: +/// +/// - The range returned by rx_address_count must point to a valid address, +/// and if rx_increment is true, count must fit into the allocated buffer. +/// - As long as no `&mut self` method is called on the implementing object: +/// - `rx_address_count` must always return the same value, if called multiple +/// times. +/// - The memory specified by the pointer and size returned by `rx_address_count` +/// must not be freed during the transfer it is used in as long as `self` is not dropped. +pub unsafe trait ReadTarget { + /// Type which is transferred in a single DMA transfer. + type ReceivedWord; + + /// Returns the DREQ number for this data source (`None` for memory buffers). + fn rx_treq() -> Option; + + /// Returns the address and the maximum number of words that can be transferred from this data + /// source in a single DMA operation. + /// + /// For peripherals, the count should likely be u32::MAX. If a data source implements + /// EndlessReadTarget, it is suitable for infinite transfers from or to ring buffers. Note that + /// ring buffers designated for endless transfers, but with a finite buffer size, should return + /// the size of their individual buffers here. + /// + /// # Safety + /// + /// This function has the same safety guarantees as `ReadBuffer::read_buffer`. + fn rx_address_count(&self) -> (u32, u32); + + /// Returns whether the address shall be incremented after each transfer. + fn rx_increment(&self) -> bool; +} + +/// Marker which signals that `rx_address_count()` can be called multiple times. +/// +/// The DMA code will never call `rx_address_count()` to request more than two buffers to configure +/// two DMA channels. In the case of peripherals, the function can always return the same values. +pub trait EndlessReadTarget: ReadTarget {} + +/// Safety: ReadBuffer and ReadTarget have the same safety requirements. +unsafe impl ReadTarget for B { + type ReceivedWord = ::Word; + + fn rx_treq() -> Option { + None + } + + fn rx_address_count(&self) -> (u32, u32) { + let (ptr, len) = unsafe { self.read_buffer() }; + (ptr as u32, len as u32) + } + + fn rx_increment(&self) -> bool { + true + } +} + +/// Trait which is implemented by anything that can be written via DMA. +/// +/// # Safety +/// +/// The implementing type must be safe to use for DMA writes. This means: +/// +/// - The range returned by tx_address_count must point to a valid address, +/// and if tx_increment is true, count must fit into the allocated buffer. +/// - As long as no other `&mut self` method is called on the implementing object: +/// - `tx_address_count` must always return the same value, if called multiple +/// times. +/// - The memory specified by the pointer and size returned by `tx_address_count` +/// must not be freed during the transfer it is used in as long as `self` is not dropped. +pub unsafe trait WriteTarget { + /// Type which is transferred in a single DMA transfer. + type TransmittedWord; + + /// Returns the DREQ number for this data sink (`None` for memory buffers). + fn tx_treq() -> Option; + + /// Returns the address and the maximum number of words that can be transferred from this data + /// source in a single DMA operation. + /// + /// See `ReadTarget::rx_address_count` for a complete description of the semantics of this + /// function. + fn tx_address_count(&mut self) -> (u32, u32); + + /// Returns whether the address shall be incremented after each transfer. + fn tx_increment(&self) -> bool; +} + +/// Marker which signals that `tx_address_count()` can be called multiple times. +/// +/// The DMA code will never call `tx_address_count()` to request more than two buffers to configure +/// two DMA channels. In the case of peripherals, the function can always return the same values. +pub trait EndlessWriteTarget: WriteTarget {} + +/// Safety: WriteBuffer and WriteTarget have the same safety requirements. +unsafe impl WriteTarget for B { + type TransmittedWord = ::Word; + + fn tx_treq() -> Option { + None + } + + fn tx_address_count(&mut self) -> (u32, u32) { + let (ptr, len) = unsafe { self.write_buffer() }; + (ptr as u32, len as u32) + } + + fn tx_increment(&self) -> bool { + true + } +} + +/// Pacing for DMA transfers. +/// +/// Generally, while memory-to-memory DMA transfers can operate at maximum possible throughput, +/// transfers involving peripherals commonly have to wait for data to be available or for available +/// space in write queues. This type defines whether the sink or the source shall pace the transfer +/// for peripheral-to-peripheral transfers. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pace { + /// The DREQ signal from the source is used, if available. If not, the sink's DREQ signal is + /// used. + PreferSource, + /// The DREQ signal from the sink is used, if available. If not, the source's DREQ signal is + /// used. + PreferSink, + // TODO: Timers? +} + +/// Error during DMA configuration. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DMAError { + /// Buffers were not aligned to their size even though they needed to be. + Alignment, + /// An illegal configuration (i.e., buffer sizes not suitable for a memory-to-memory transfer) + /// was specified. + IllegalConfig, +} + +/// Constraint on transfer size types +pub trait TransferSize: Sealed { + /// Actual type of transfer + type Type; +} + +/// DMA transfer in bytes (u8) +#[derive(Debug, Copy, Clone)] +pub struct Byte; +/// DMA transfer in half words (u16) +#[derive(Debug, Copy, Clone)] +pub struct HalfWord; +/// DMA transfer in words (u32) +#[derive(Debug, Copy, Clone)] +pub struct Word; + +impl Sealed for Byte {} +impl Sealed for HalfWord {} +impl Sealed for Word {} + +impl TransferSize for Byte { + type Type = u8; +} +impl TransferSize for HalfWord { + type Type = u16; +} +impl TransferSize for Word { + type Type = u32; +} diff --git a/rp-hal/rp235x-hal/src/dma/single_buffer.rs b/rp-hal/rp235x-hal/src/dma/single_buffer.rs new file mode 100644 index 0000000..a4c657e --- /dev/null +++ b/rp-hal/rp235x-hal/src/dma/single_buffer.rs @@ -0,0 +1,149 @@ +//! Single-buffered or peripheral-peripheral DMA Transfers + +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::{ + single_channel::ChannelConfig, single_channel::SingleChannel, Pace, ReadTarget, WriteTarget, +}; + +/// Configuration for single-buffered DMA transfer +pub struct Config { + ch: CH, + from: FROM, + to: TO, + pace: Pace, + bswap: bool, +} + +impl Config +where + CH: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Create a new configuration for single-buffered DMA transfer + pub fn new(ch: CH, from: FROM, to: TO) -> Config { + Config { + ch, + from, + to, + pace: Pace::PreferSource, + bswap: false, + } + } + + /// Sets the (preferred) pace for the DMA transfers. + /// + /// Usually, the code will automatically configure the correct pace, but + /// peripheral-to-peripheral transfers require the user to manually select whether the source + /// or the sink shall be queried for the pace signal. + pub fn pace(&mut self, pace: Pace) { + self.pace = pace; + } + + /// Enable/disable byteswapping for the DMA transfers, default value is false. + /// + /// For byte data, this has no effect. For halfword data, the two bytes of + /// each halfword are swapped. For word data, the four bytes of each word + /// are swapped to reverse order. + /// + /// This is a convenient way to change the (half-)words' byte endianness on the fly. + pub fn bswap(&mut self, bswap: bool) { + self.bswap = bswap; + } + + /// Start the DMA transfer + pub fn start(mut self) -> Transfer { + // TODO: Do we want to call any callbacks to configure source/sink? + + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the DMA channel and start it. + self.ch + .config(&self.from, &mut self.to, self.pace, self.bswap, None, true); + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + } + } +} + +// TODO: Drop for most of these structs +/// Instance of a single-buffered DMA transfer +pub struct Transfer { + ch: CH, + from: FROM, + to: TO, +} + +impl Transfer +where + CH: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + pub fn check_irq0(&mut self) -> bool { + self.ch.check_irq0() + } + + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + pub fn check_irq1(&mut self) -> bool { + self.ch.check_irq1() + } + + /// Check if the transfer has completed. + pub fn is_done(&self) -> bool { + !self.ch.ch().ch_ctrl_trig().read().busy().bit_is_set() + } + + /// Block until the transfer is complete, returning the channel and targets + pub fn wait(self) -> (CH, FROM, TO) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + (self.ch, self.from, self.to) + } + + /// Aborts the current transfer, returning the channel and targets + pub fn abort(mut self) -> (CH, FROM, TO) { + let irq0_was_enabled = self.ch.is_enabled_irq0(); + let irq1_was_enabled = self.ch.is_enabled_irq1(); + self.ch.disable_irq0(); + self.ch.disable_irq1(); + + let chan_abort = unsafe { &*crate::pac::DMA::ptr() }.chan_abort(); + let abort_mask = (1 << self.ch.id()) as u16; + + chan_abort.write(|w| unsafe { w.chan_abort().bits(abort_mask) }); + + while chan_abort.read().bits() != 0 {} + + while !self.is_done() {} + + self.ch.check_irq0(); + self.ch.check_irq1(); + + if irq0_was_enabled { + self.ch.enable_irq0(); + } + + if irq1_was_enabled { + self.ch.enable_irq1(); + } + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + (self.ch, self.from, self.to) + } +} diff --git a/rp-hal/rp235x-hal/src/dma/single_channel.rs b/rp-hal/rp235x-hal/src/dma/single_channel.rs new file mode 100644 index 0000000..664fb19 --- /dev/null +++ b/rp-hal/rp235x-hal/src/dma/single_channel.rs @@ -0,0 +1,263 @@ +use crate::pac::DMA; + +use super::{Channel, ChannelIndex, Pace, ReadTarget, WriteTarget}; +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + dma::ChannelRegs, + typelevel::Sealed, +}; +use core::mem; + +/// Trait which implements low-level functionality for transfers using a single DMA channel. +pub trait SingleChannel: Sealed { + /// Returns the registers associated with this DMA channel. + /// + /// In the case of channel pairs, this returns the first channel. + fn ch(&self) -> &crate::pac::dma::CH; + /// Returns the index of the DMA channel. + fn id(&self) -> u8; + + #[deprecated(note = "Renamed to enable_irq0")] + /// Enables the DMA_IRQ_0 signal for this channel. + fn listen_irq0(&mut self) { + self.enable_irq0(); + } + + /// Enables the DMA_IRQ_0 signal for this channel. + fn enable_irq0(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_set((*DMA::ptr()).inte0().as_ptr(), 1 << self.id()); + } + } + + /// Check if the DMA_IRQ_0 signal for this channel is enabled. + fn is_enabled_irq0(&mut self) -> bool { + unsafe { ((*DMA::ptr()).inte0().read().bits() & (1 << self.id())) != 0 } + } + + #[deprecated(note = "Renamed to disable_irq0")] + /// Disables the DMA_IRQ_0 signal for this channel. + fn unlisten_irq0(&mut self) { + self.disable_irq0(); + } + + /// Disables the DMA_IRQ_0 signal for this channel. + fn disable_irq0(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_clear((*DMA::ptr()).inte0().as_ptr(), 1 << self.id()); + } + } + + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + fn check_irq0(&mut self) -> bool { + // Safety: The following is race-free as we only ever clear the bit for this channel. + // Nobody else modifies that bit. + unsafe { + let status = (*DMA::ptr()).ints0().read().bits(); + if (status & (1 << self.id())) != 0 { + // Clear the interrupt. + (*DMA::ptr()).ints0().write(|w| w.bits(1 << self.id())); + true + } else { + false + } + } + } + + #[deprecated(note = "Renamed to enable_irq1")] + /// Enables the DMA_IRQ_1 signal for this channel. + fn listen_irq1(&mut self) { + self.enable_irq1(); + } + + /// Enables the DMA_IRQ_1 signal for this channel. + fn enable_irq1(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_set((*DMA::ptr()).inte1().as_ptr(), 1 << self.id()); + } + } + + /// Check if the DMA_IRQ_1 signal for this channel is enabled. + fn is_enabled_irq1(&mut self) -> bool { + unsafe { ((*DMA::ptr()).inte1().read().bits() & (1 << self.id())) != 0 } + } + + #[deprecated(note = "Renamed to disable_irq1")] + /// Disables the DMA_IRQ_1 signal for this channel. + fn unlisten_irq1(&mut self) { + self.disable_irq1(); + } + + /// Disables the DMA_IRQ_1 signal for this channel. + fn disable_irq1(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_clear((*DMA::ptr()).inte1().as_ptr(), 1 << self.id()); + } + } + + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + fn check_irq1(&mut self) -> bool { + // Safety: The following is race-free as we only ever clear the bit for this channel. + // Nobody else modifies that bit. + unsafe { + let status = (*DMA::ptr()).ints1().read().bits(); + if (status & (1 << self.id())) != 0 { + // Clear the interrupt. + (*DMA::ptr()).ints1().write(|w| w.bits(1 << self.id())); + true + } else { + false + } + } + } +} + +impl SingleChannel for Channel { + fn ch(&self) -> &crate::pac::dma::CH { + self.regs() + } + + fn id(&self) -> u8 { + CH::id() + } +} + +impl Sealed for Channel {} + +impl SingleChannel for (Channel, Channel) { + fn ch(&self) -> &crate::pac::dma::CH { + self.0.regs() + } + + fn id(&self) -> u8 { + CH1::id() + } +} + +pub(crate) trait ChannelConfig { + fn config( + &mut self, + from: &FROM, + to: &mut TO, + pace: Pace, + bswap: bool, + chain_to: Option, + start: bool, + ) where + FROM: ReadTarget, + TO: WriteTarget; + + fn set_chain_to_enabled(&mut self, other: &mut CH); + fn start(&mut self); + fn start_both(&mut self, other: &mut CH); +} + +// rp235x's DMA engine only works with certain word sizes. Make sure that other +// word sizes will fail to compile. +struct IsValidWordSize { + w: core::marker::PhantomData, +} + +impl IsValidWordSize { + const OK: usize = { + match mem::size_of::() { + 1 | 2 | 4 => 0, // ok + _ => panic!("Unsupported DMA word size"), + } + }; +} + +impl ChannelConfig for CH { + fn config( + &mut self, + from: &FROM, + to: &mut TO, + pace: Pace, + bswap: bool, + chain_to: Option, + start: bool, + ) where + FROM: ReadTarget, + TO: WriteTarget, + { + // Configure the DMA channel. + let _ = IsValidWordSize::::OK; + + let (src, src_count) = from.rx_address_count(); + let src_incr = from.rx_increment(); + let (dest, dest_count) = to.tx_address_count(); + let dest_incr = to.tx_increment(); + const TREQ_UNPACED: u8 = 0x3f; + let treq = match pace { + Pace::PreferSource => FROM::rx_treq().or_else(TO::tx_treq).unwrap_or(TREQ_UNPACED), + Pace::PreferSink => TO::tx_treq().or_else(FROM::rx_treq).unwrap_or(TREQ_UNPACED), + }; + let len = u32::min(src_count, dest_count); + self.ch().ch_al1_ctrl().write(|w| unsafe { + w.data_size().bits(mem::size_of::() as u8 >> 1); + w.incr_read().bit(src_incr); + w.incr_write().bit(dest_incr); + w.treq_sel().bits(treq); + w.bswap().bit(bswap); + w.chain_to().bits(chain_to.unwrap_or_else(|| self.id())); + w.en().bit(true); + w + }); + self.ch().ch_read_addr().write(|w| unsafe { w.bits(src) }); + self.ch().ch_trans_count().write(|w| unsafe { + w.count().bits(len); + w.mode().normal(); + w + }); + if start { + self.ch() + .ch_al2_write_addr_trig() + .write(|w| unsafe { w.bits(dest) }); + } else { + self.ch().ch_write_addr().write(|w| unsafe { w.bits(dest) }); + } + } + + fn set_chain_to_enabled(&mut self, other: &mut CH2) { + // We temporarily pause the channel when setting CHAIN_TO, to prevent any race condition + // that could occur, as we need to check afterwards whether the channel was successfully + // chained to this channel or whether this channel was already completed. If we did not + // pause this channel, we could get into a situation where both channels completed in quick + // succession, yet we did not notice, as the situation is not distinguishable from one + // where the second channel was not started at all. + + self.ch().ch_al1_ctrl().modify(|_, w| unsafe { + w.chain_to().bits(other.id()); + w.en().clear_bit(); + w + }); + if self.ch().ch_al1_ctrl().read().busy().bit_is_set() { + // This channel is still active, so just continue. + self.ch().ch_al1_ctrl().modify(|_, w| w.en().set_bit()); + } else { + // This channel has already finished, so just start the other channel directly. + other.start(); + } + } + + fn start(&mut self) { + // Safety: The write does not interfere with any other writes, it only affects this + // channel. + unsafe { &*crate::pac::DMA::ptr() } + .multi_chan_trigger() + .write(|w| unsafe { w.bits(1 << self.id()) }); + } + + fn start_both(&mut self, other: &mut CH2) { + // Safety: The write does not interfere with any other writes, it only affects this + // channel and other (which we have an exclusive borrow of). + let channel_flags = 1 << self.id() | 1 << other.id(); + unsafe { &*crate::pac::DMA::ptr() } + .multi_chan_trigger() + .write(|w| unsafe { w.bits(channel_flags) }); + } +} diff --git a/rp-hal/rp235x-hal/src/gpio/func.rs b/rp-hal/rp235x-hal/src/gpio/func.rs new file mode 100644 index 0000000..fd807ad --- /dev/null +++ b/rp-hal/rp235x-hal/src/gpio/func.rs @@ -0,0 +1,234 @@ +use core::marker::PhantomData; + +use paste::paste; + +use super::pin::DynBankId; + +pub(crate) mod func_sealed { + use super::DynFunction; + + pub trait Function { + fn from(f: DynFunction) -> Self; + fn as_dyn(&self) -> DynFunction; + } +} + +/// Type-level `enum` for pin function. +pub trait Function: func_sealed::Function {} + +/// Describes the function currently assigned to a pin with a dynamic type. +/// +/// A 'pin' on the RP2350 can be connected to different parts of the chip +/// internally - for example, it could be configured as a GPIO pin and connected +/// to the SIO block, or it could be configured as a UART pin and connected to +/// the UART block. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DynFunction { + /// The 'XIP' (or Execute-in-place) function, which means talking to the QSPI Flash. + Xip, + /// The 'SPI' (or serial-peripheral-interface) function. + Spi, + /// The 'UART' (or serial-port) function. + Uart, + /// The 'I2C' (or inter-integrated circuit) function. This is sometimes also called TWI (for + /// two-wire-interface). + I2c, + /// The 'PWM' (or pulse-width-modulation) function. + Pwm, + /// The 'SIO' (or single-cycle input-output) function. This is the function to use for + /// 'manually' controlling the GPIO. + Sio(DynSioConfig), + /// The 'PIO' (or programmable-input-output) function for the PIO0 peripheral block. + Pio0, + /// The 'PIO' (or programmable-input-output) function for the PIO1 peripheral block. + Pio1, + /// The 'PIO' (or programmable-input-output) function for the PIO2 peripheral block. + Pio2, + /// The 'Clock' function. This can be used to input or output clock references to or from the + /// rp235x. + Clock, + /// The XIP CS1 function. + XipCs1, + /// The 'USB' function. Only VBUS detect, VBUS enable and overcurrent detect are configurable. + /// Other USB io have dedicated pins. + Usb, + /// The auxilliary UART function lets you use a UART CTS as UART TX and a + /// UART RTS pin as UART RX. + UartAux, + /// The 'Null' function for unused pins. + Null, +} + +/// Value-level `enum` for SIO configuration. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DynSioConfig { + /// Pin is configured as Input. + Input, + /// Pin is configured as Output. + Output, +} + +impl Function for DynFunction {} +impl func_sealed::Function for DynFunction { + #[inline] + fn from(f: DynFunction) -> Self { + f + } + + #[inline] + fn as_dyn(&self) -> DynFunction { + *self + } +} + +macro_rules! pin_func { + ($($fn:ident $(as $alias:ident)?),*) => { + $(paste! { + /// Type-level `variant` for pin [`Function`]. + #[derive(Debug)] + pub struct [](pub(super) ()); + impl Function for [] {} + impl func_sealed::Function for [] { + #[inline] + fn from(_f: DynFunction) -> Self { + Self(()) + } + #[inline] + fn as_dyn(&self) -> DynFunction { + DynFunction::[<$fn>] + } + } + $( + #[doc = "Alias to [`Function" $fn "`]."] + pub type [] = []; + )? + })* + }; +} +pin_func!(Xip, Spi, Uart, I2c as I2C, Pwm, Pio0, Pio1, Pio2, Clock, XipCs1, Usb, UartAux, Null); + +//============================================================================== +// SIO sub-types +//============================================================================== + +/// Type-level `variant` for pin [`Function`]. +#[derive(Debug)] +pub struct FunctionSio(PhantomData); +impl Function for FunctionSio {} +impl func_sealed::Function for FunctionSio { + fn from(_f: DynFunction) -> Self { + FunctionSio(PhantomData) + } + fn as_dyn(&self) -> DynFunction { + DynFunction::Sio(C::DYN) + } +} +/// Alias to [`FunctionSio`]. +pub type FunctionSioInput = FunctionSio; +/// Alias to [`FunctionSio`]. +pub type FunctionSioOutput = FunctionSio; + +/// Type-level `enum` for SIO configuration. +pub trait SioConfig { + #[allow(missing_docs)] + const DYN: DynSioConfig; +} + +/// Type-level `variant` for SIO configuration. +#[derive(Debug)] +pub enum SioInput {} +impl SioConfig for SioInput { + #[allow(missing_docs)] + const DYN: DynSioConfig = DynSioConfig::Input; +} +/// Type-level `variant` for SIO configuration. +#[derive(Debug)] +pub enum SioOutput {} +impl SioConfig for SioOutput { + #[allow(missing_docs)] + const DYN: DynSioConfig = DynSioConfig::Output; +} + +//============================================================================== +// Pin to function mapping +//============================================================================== + +/// Error type for invalid function conversion. +pub struct InvalidFunction; + +/// Marker of valid pin -> function combination. +/// +/// Where `impl ValidFunction for I` reads as `F is a valid function implemented for the pin I`. +pub trait ValidFunction: super::pin::PinId {} + +impl DynFunction { + pub(crate) fn is_valid(&self, id: &P) -> bool { + use DynBankId::*; + use DynFunction::*; + + let dyn_pin = id.as_dyn(); + match (self, dyn_pin.bank, dyn_pin.num) { + (Xip, Bank0, _) => false, + (XipCs1, Bank0, 0 | 8 | 19 | 47) => true, + (Clock, _, 0..=19 | 26..=29) => false, + (UartAux, _, n) if (n & 0x02) == 0 => false, + (_, Bank0, 0..=29) => true, + + (Xip | Sio(_), Qspi, 0..=5) => true, + (_, Qspi, 0..=5) => false, + + _ => unreachable!(), + } + } +} +impl ValidFunction for P {} +macro_rules! pin_valid_func { + ($bank:ident as $prefix:ident, [$head:ident $(, $func:ident)*], [$($name:tt),+]) => { + pin_valid_func!($bank as $prefix, [$($func),*], [$($name),+]); + paste::paste!{$( + impl ValidFunction<[]> for super::pin::[<$bank:lower>]::[<$prefix $name>] {} + )+} + }; + ($bank:ident as $prefix:ident, [], [$($name:tt),+]) => {}; +} +pin_valid_func!( + bank0 as Gpio, + [Spi, Uart, I2c, Pwm, Pio0, Pio1, Pio2, Usb, Null], + [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29 + ] +); + +pin_valid_func!( + bank0 as Gpio, + [UartAux], + [2, 3, 6, 7, 10, 11, 14, 15, 18, 19, 22, 23, 26, 27] +); +pin_valid_func!(bank0 as Gpio, [Clock], [20, 21, 22, 23, 24, 25]); +pin_valid_func!(bank0 as Gpio, [XipCs1], [0, 8, 19, 47]); +pin_valid_func!(qspi as Qspi, [Xip, Null], [Sclk, Sd0, Sd1, Sd2, Sd3, Ss]); + +macro_rules! pin_valid_func_sio { + ($bank:ident as $prefix:ident, [$($name:tt),+]) => { + paste::paste!{$( + impl ValidFunction> for super::pin::[<$bank:lower>]::[<$prefix $name>] {} + )+} + }; +} + +#[rustfmt::skip] +pin_valid_func_sio!( + bank0 as Gpio, + [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 + ] +); + +pin_valid_func_sio!(qspi as Qspi, [Sclk, Sd0, Sd1, Sd2, Sd3, Ss]); diff --git a/rp-hal/rp235x-hal/src/gpio/mod.rs b/rp-hal/rp235x-hal/src/gpio/mod.rs new file mode 100644 index 0000000..6861ace --- /dev/null +++ b/rp-hal/rp235x-hal/src/gpio/mod.rs @@ -0,0 +1,1646 @@ +//! General Purpose Input and Output (GPIO) +//! +//! ## Basic usage +//! ```no_run +//! use embedded_hal::digital::{InputPin, OutputPin}; +//! use rp235x_hal::{ +//! self as hal, clocks::init_clocks_and_plls, gpio::Pins, watchdog::Watchdog, Sio, +//! }; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! let mut clocks = init_clocks_and_plls( +//! XOSC_CRYSTAL_FREQ, +//! peripherals.XOSC, +//! peripherals.CLOCKS, +//! peripherals.PLL_SYS, +//! peripherals.PLL_USB, +//! &mut peripherals.RESETS, +//! &mut watchdog, +//! ) +//! .ok() +//! .unwrap(); +//! +//! let mut pac = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(pac.SIO); +//! let pins = rp235x_hal::gpio::Pins::new( +//! pac.IO_BANK0, +//! pac.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut pac.RESETS, +//! ); +//! // Set a pin to drive output +//! let mut output_pin = pins.gpio25.into_push_pull_output(); +//! // Drive output to 3.3V +//! output_pin.set_high().unwrap(); +//! // Drive output to 0V +//! output_pin.set_low().unwrap(); +//! // Set a pin to input +//! let mut input_pin = pins.gpio24.into_floating_input(); +//! // pinstate will be true if the pin is above 2V +//! let pinstate = input_pin.is_high().unwrap(); +//! // pinstate_low will be true if the pin is below 1.15V +//! let pinstate_low = input_pin.is_low().unwrap(); +//! // you'll want to pull-up or pull-down a switch if it's not done externally +//! let button_pin = pins.gpio23.into_pull_down_input(); +//! let button2_pin = pins.gpio22.into_pull_up_input(); +//! ``` +//! See [examples/gpio_in_out.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/gpio_in_out.rs) for a more practical example + +// Design Notes: +// +// - The user must not be able to instantiate by themselves nor obtain an instance of the Type-level +// structure. +// - non-typestated features (overrides, irq configuration, pads' output disable, pad's input +// enable, drive strength, schmitt, slew rate, sio's in sync bypass) are considered somewhat +// advanced usage of the pin (relative to reading/writing a gpio) and it is the responsibility of +// the user to make sure these are in a correct state when converting and passing the pin around. + +pub use embedded_hal::digital::PinState; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + pac, + sio::Sio, + typelevel::{self, Sealed}, +}; + +mod func; +pub(crate) mod pin; +mod pin_group; +mod pull; + +pub use func::*; +pub use pin::{DynBankId, DynPinId, PinId}; +pub use pin_group::PinGroup; +pub use pull::*; + +/// The amount of current that a pin can drive when used as an output. +#[allow(clippy::enum_variant_names)] +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum OutputDriveStrength { + /// 2 mA + TwoMilliAmps, + /// 4 mA + FourMilliAmps, + /// 8 mA + EightMilliAmps, + /// 12 mA + TwelveMilliAmps, +} + +/// The slew rate of a pin when used as an output. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum OutputSlewRate { + /// Slew slow + Slow, + /// Slew fast + Fast, +} + +/// Interrupt kind. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum Interrupt { + /// While low + LevelLow, + /// While high + LevelHigh, + /// On falling edge + EdgeLow, + /// On rising edge + EdgeHigh, +} +impl Interrupt { + fn mask(&self) -> u32 { + match self { + Interrupt::LevelLow => 0b0001, + Interrupt::LevelHigh => 0b0010, + Interrupt::EdgeLow => 0b0100, + Interrupt::EdgeHigh => 0b1000, + } + } +} + +/// Interrupt override state. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum InterruptOverride { + /// Don't invert the interrupt. + Normal = 0, + /// Invert the interrupt. + Invert = 1, + /// Drive interrupt low. + AlwaysLow = 2, + /// Drive interrupt high. + AlwaysHigh = 3, +} + +/// Input override state. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum InputOverride { + /// Don't invert the peripheral input. + Normal = 0, + /// Invert the peripheral input. + Invert = 1, + /// Drive peripheral input low. + AlwaysLow = 2, + /// Drive peripheral input high. + AlwaysHigh = 3, +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +/// Output enable override state. +pub enum OutputEnableOverride { + /// Use the original output enable signal from selected peripheral. + Normal = 0, + /// Invert the output enable signal from selected peripheral. + Invert = 1, + /// Disable output. + Disable = 2, + /// Enable output. + Enable = 3, +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +/// Output override state. +pub enum OutputOverride { + /// Use the original output signal from selected peripheral. + DontInvert = 0, + /// Invert the output signal from selected peripheral. + Invert = 1, + /// Drive output low. + AlwaysLow = 2, + /// Drive output high. + AlwaysHigh = 3, +} + +/// Represents a pin, with a given ID (e.g. Gpio3), a given function (e.g. FunctionUart) and a given pull type +/// (e.g. pull-down). +#[derive(Debug)] +pub struct Pin { + id: I, + function: F, + pull_type: P, +} + +/// Create a new pin instance. +/// +/// # Safety +/// The uniqueness of the pin is not verified. User must make sure no other instance of that specific +/// pin exists at the same time. +pub unsafe fn new_pin(id: DynPinId) -> Pin { + use pac::io_bank0::gpio::gpio_ctrl::FUNCSEL_A; + use pin::pin_sealed::PinIdOps; + + let funcsel = id + .io_ctrl() + .read() + .funcsel() + .variant() + .expect("Invalid funcsel read from register."); + let function = match funcsel { + FUNCSEL_A::JTAG => DynFunction::Xip, + FUNCSEL_A::SPI => DynFunction::Spi, + FUNCSEL_A::UART => DynFunction::Uart, + FUNCSEL_A::I2C => DynFunction::I2c, + FUNCSEL_A::PWM => DynFunction::Pwm, + FUNCSEL_A::SIO => { + let mask = id.mask(); + let cfg = if id.sio_oe().read().bits() & mask == mask { + DynSioConfig::Output + } else { + DynSioConfig::Input + }; + DynFunction::Sio(cfg) + } + FUNCSEL_A::PIO0 => DynFunction::Pio0, + FUNCSEL_A::PIO1 => DynFunction::Pio1, + FUNCSEL_A::PIO2 => DynFunction::Pio2, + FUNCSEL_A::GPCK => DynFunction::Clock, + FUNCSEL_A::USB => DynFunction::Usb, + FUNCSEL_A::UART_AUX => DynFunction::UartAux, + FUNCSEL_A::NULL => DynFunction::Null, + }; + let pad = id.pad_ctrl().read(); + let pull_type = match (pad.pue().bit_is_set(), pad.pde().bit_is_set()) { + (true, true) => DynPullType::BusKeep, + (true, false) => DynPullType::Up, + (false, true) => DynPullType::Down, + (false, false) => DynPullType::None, + }; + + Pin { + id, + function, + pull_type, + } +} + +impl Pin { + /// Pin ID. + pub fn id(&self) -> DynPinId { + self.id.as_dyn() + } + + /// # Safety + /// This method does not check if the pin is actually configured as the target function or pull + /// mode. This may lead to inconsistencies between the type-state and the actual state of the + /// pin's configuration. + pub unsafe fn into_unchecked(self) -> Pin { + Pin { + id: self.id, + function: F2::from(self.function.as_dyn()), + pull_type: P2::from(self.pull_type.as_dyn()), + } + } + + /// Convert the pin from one state to the other. + pub fn reconfigure(self) -> Pin + where + F2: func::Function, + P2: PullType, + I: func::ValidFunction, + { + self.into_function().into_pull_type() + } + + /// Convert the pin function. + #[deprecated( + note = "Misleading name `mode` when it changes the `function`. Please use `into_function` instead.", + since = "0.9.0" + )] + pub fn into_mode(self) -> Pin + where + F2: func::Function, + I: func::ValidFunction, + { + self.into_function() + } + + /// Convert the pin function. + pub fn into_function(self) -> Pin + where + F2: func::Function, + I: func::ValidFunction, + { + // Thanks to type-level validation, we know F2 is valid for I + let prev_function = self.function.as_dyn(); + let function = F2::from(prev_function); + let new_function = function.as_dyn(); + + if prev_function != new_function { + pin::set_function(&self.id, new_function); + } + + Pin { + function, + id: self.id, + pull_type: self.pull_type, + } + } + + /// Convert the pin pull type. + pub fn into_pull_type(self) -> Pin { + let prev_pull_type = self.pull_type.as_dyn(); + let pull_type = M2::from(prev_pull_type); + let new_pull_type = pull_type.as_dyn(); + + if prev_pull_type != new_pull_type { + pin::set_pull_type(&self.id, new_pull_type); + } + + Pin { + pull_type, + id: self.id, + function: self.function, + } + } + + /// Erase the Pin ID type check. + pub fn into_dyn_pin(self) -> Pin { + Pin { + id: self.id.as_dyn(), + function: self.function, + pull_type: self.pull_type, + } + } + + /// Get the pin's pull type. + pub fn pull_type(&self) -> DynPullType { + self.pull_type.as_dyn() + } + + //============================================================================== + // Typical pin conversions. + //============================================================================== + + /// Disable the pin and set it to float + #[inline] + pub fn into_floating_disabled(self) -> Pin + where + I: ValidFunction, + { + self.reconfigure() + } + + /// Disable the pin and set it to pull down + #[inline] + pub fn into_pull_down_disabled(self) -> Pin + where + I: ValidFunction, + { + self.reconfigure() + } + + /// Disable the pin and set it to pull up + #[inline] + pub fn into_pull_up_disabled(self) -> Pin + where + I: ValidFunction, + { + self.reconfigure() + } + + /// Configure the pin to operate as a floating input + #[inline] + pub fn into_floating_input(self) -> Pin, PullNone> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a pulled down input + #[inline] + pub fn into_pull_down_input(self) -> Pin, PullDown> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a pulled up input + #[inline] + pub fn into_pull_up_input(self) -> Pin, PullUp> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a bus keep input + #[inline] + pub fn into_bus_keep_input(self) -> Pin, PullBusKeep> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a push-pull output. + /// + /// If you want to specify the initial pin state, use [`Pin::into_push_pull_output_in_state`]. + #[inline] + pub fn into_push_pull_output(self) -> Pin, P> + where + I: ValidFunction>, + { + self.into_function() + } + + /// Configure the pin to operate as a push-pull output, specifying an initial + /// state which is applied immediately. + #[inline] + pub fn into_push_pull_output_in_state( + mut self, + state: PinState, + ) -> Pin, P> + where + I: ValidFunction>, + { + match state { + PinState::High => self._set_high(), + PinState::Low => self._set_low(), + } + self.into_push_pull_output() + } + + /// Configure the pin to operate as a readable push pull output. + /// + /// If you want to specify the initial pin state, use [`Pin::into_readable_output_in_state`]. + #[inline] + #[deprecated( + note = "All gpio are readable, use `.into_push_pull_output()` instead.", + since = "0.9.0" + )] + pub fn into_readable_output(self) -> Pin, P> + where + I: ValidFunction>, + { + self.into_function() + } + + /// Configure the pin to operate as a readable push pull output, specifying an initial + /// state which is applied immediately. + #[inline] + #[deprecated( + note = "All gpio are readable, use `.into_push_pull_output_in_state()` instead.", + since = "0.9.0" + )] + pub fn into_readable_output_in_state(self, state: PinState) -> Pin, P> + where + I: ValidFunction>, + { + self.into_push_pull_output_in_state(state) + } + + //============================================================================== + // methods available for all pins. + //============================================================================== + + // ======================= + // Pad related methods + + /// Get the current drive strength of the pin. + #[inline] + pub fn get_drive_strength(&self) -> OutputDriveStrength { + use pac::pads_bank0::gpio::DRIVE_A; + match self.id.pad_ctrl().read().drive().variant() { + DRIVE_A::_2M_A => OutputDriveStrength::TwoMilliAmps, + DRIVE_A::_4M_A => OutputDriveStrength::FourMilliAmps, + DRIVE_A::_8M_A => OutputDriveStrength::EightMilliAmps, + DRIVE_A::_12M_A => OutputDriveStrength::TwelveMilliAmps, + } + } + + /// Set the drive strength for the pin. + #[inline] + pub fn set_drive_strength(&mut self, strength: OutputDriveStrength) { + use pac::pads_bank0::gpio::DRIVE_A; + let variant = match strength { + OutputDriveStrength::TwoMilliAmps => DRIVE_A::_2M_A, + OutputDriveStrength::FourMilliAmps => DRIVE_A::_4M_A, + OutputDriveStrength::EightMilliAmps => DRIVE_A::_8M_A, + OutputDriveStrength::TwelveMilliAmps => DRIVE_A::_12M_A, + }; + self.id.pad_ctrl().modify(|_, w| w.drive().variant(variant)) + } + + /// Get the slew rate for the pin. + #[inline] + pub fn get_slew_rate(&self) -> OutputSlewRate { + if self.id.pad_ctrl().read().slewfast().bit_is_set() { + OutputSlewRate::Fast + } else { + OutputSlewRate::Slow + } + } + + /// Set the slew rate for the pin. + #[inline] + pub fn set_slew_rate(&mut self, rate: OutputSlewRate) { + self.id + .pad_ctrl() + .modify(|_, w| w.slewfast().bit(OutputSlewRate::Fast == rate)); + } + + /// Get whether the schmitt trigger (hysteresis) is enabled. + #[inline] + pub fn get_schmitt_enabled(&self) -> bool { + self.id.pad_ctrl().read().schmitt().bit_is_set() + } + + /// Enable/Disable the schmitt trigger. + #[inline] + pub fn set_schmitt_enabled(&self, enable: bool) { + self.id.pad_ctrl().modify(|_, w| w.schmitt().bit(enable)); + } + + /// Get the state of the digital output circuitry of the pad. + #[inline] + pub fn get_output_disable(&mut self) -> bool { + self.id.pad_ctrl().read().od().bit_is_set() + } + + /// Set the digital output circuitry of the pad. + #[inline] + pub fn set_output_disable(&mut self, disable: bool) { + self.id.pad_ctrl().modify(|_, w| w.od().bit(disable)); + } + + /// Get the state of the digital input circuitry of the pad. + #[inline] + pub fn get_input_enable(&mut self) -> bool { + self.id.pad_ctrl().read().ie().bit_is_set() + } + + /// Set the digital input circuitry of the pad. + #[inline] + pub fn set_input_enable(&mut self, enable: bool) { + self.id.pad_ctrl().modify(|_, w| w.ie().bit(enable)); + } + + // ======================= + // IO related methods + + /// Get the input override. + #[inline] + pub fn get_input_override(&self) -> InputOverride { + use pac::io_bank0::gpio::gpio_ctrl::INOVER_A; + match self.id.io_ctrl().read().inover().variant() { + INOVER_A::NORMAL => InputOverride::Normal, + INOVER_A::INVERT => InputOverride::Invert, + INOVER_A::LOW => InputOverride::AlwaysLow, + INOVER_A::HIGH => InputOverride::AlwaysHigh, + } + } + + /// Set the input override. + #[inline] + pub fn set_input_override(&mut self, override_value: InputOverride) { + use pac::io_bank0::gpio::gpio_ctrl::INOVER_A; + let variant = match override_value { + InputOverride::Normal => INOVER_A::NORMAL, + InputOverride::Invert => INOVER_A::INVERT, + InputOverride::AlwaysLow => INOVER_A::LOW, + InputOverride::AlwaysHigh => INOVER_A::HIGH, + }; + self.id.io_ctrl().modify(|_, w| w.inover().variant(variant)); + } + + /// Get the output enable override. + #[inline] + pub fn get_output_enable_override(&self) -> OutputEnableOverride { + use pac::io_bank0::gpio::gpio_ctrl::OEOVER_A; + match self.id.io_ctrl().read().oeover().variant() { + OEOVER_A::NORMAL => OutputEnableOverride::Normal, + OEOVER_A::INVERT => OutputEnableOverride::Invert, + OEOVER_A::DISABLE => OutputEnableOverride::Disable, + OEOVER_A::ENABLE => OutputEnableOverride::Enable, + } + } + + /// Set the output enable override. + #[inline] + pub fn set_output_enable_override(&mut self, override_value: OutputEnableOverride) { + use pac::io_bank0::gpio::gpio_ctrl::OEOVER_A; + let variant = match override_value { + OutputEnableOverride::Normal => OEOVER_A::NORMAL, + OutputEnableOverride::Invert => OEOVER_A::INVERT, + OutputEnableOverride::Disable => OEOVER_A::DISABLE, + OutputEnableOverride::Enable => OEOVER_A::ENABLE, + }; + self.id.io_ctrl().modify(|_, w| w.oeover().variant(variant)); + } + + /// Get the output override. + #[inline] + pub fn get_output_override(&self) -> OutputOverride { + use pac::io_bank0::gpio::gpio_ctrl::OUTOVER_A; + match self.id.io_ctrl().read().outover().variant() { + OUTOVER_A::NORMAL => OutputOverride::DontInvert, + OUTOVER_A::INVERT => OutputOverride::Invert, + OUTOVER_A::LOW => OutputOverride::AlwaysLow, + OUTOVER_A::HIGH => OutputOverride::AlwaysHigh, + } + } + + /// Set the output override. + #[inline] + pub fn set_output_override(&mut self, override_value: OutputOverride) { + use pac::io_bank0::gpio::gpio_ctrl::OUTOVER_A; + let variant = match override_value { + OutputOverride::DontInvert => OUTOVER_A::NORMAL, + OutputOverride::Invert => OUTOVER_A::INVERT, + OutputOverride::AlwaysLow => OUTOVER_A::LOW, + OutputOverride::AlwaysHigh => OUTOVER_A::HIGH, + }; + self.id + .io_ctrl() + .modify(|_, w| w.outover().variant(variant)); + } + + /// Get the interrupt override. + #[inline] + pub fn get_interrupt_override(&self) -> InterruptOverride { + use pac::io_bank0::gpio::gpio_ctrl::IRQOVER_A; + match self.id.io_ctrl().read().irqover().variant() { + IRQOVER_A::NORMAL => InterruptOverride::Normal, + IRQOVER_A::INVERT => InterruptOverride::Invert, + IRQOVER_A::LOW => InterruptOverride::AlwaysLow, + IRQOVER_A::HIGH => InterruptOverride::AlwaysHigh, + } + } + + /// Set the interrupt override. + #[inline] + pub fn set_interrupt_override(&mut self, override_value: InterruptOverride) { + use pac::io_bank0::gpio::gpio_ctrl::IRQOVER_A; + let variant = match override_value { + InterruptOverride::Normal => IRQOVER_A::NORMAL, + InterruptOverride::Invert => IRQOVER_A::INVERT, + InterruptOverride::AlwaysLow => IRQOVER_A::LOW, + InterruptOverride::AlwaysHigh => IRQOVER_A::HIGH, + }; + self.id + .io_ctrl() + .modify(|_, w| w.irqover().variant(variant)); + } + + // ======================= + // SIO related methods + + #[inline] + #[allow(clippy::bool_comparison)] // more explicit this way + pub(crate) fn _is_low(&self) -> bool { + let mask = self.id.mask(); + self.id.sio_in().read().bits() & mask == 0 + } + + #[inline] + #[allow(clippy::bool_comparison)] // more explicit this way + pub(crate) fn _is_high(&self) -> bool { + !self._is_low() + } + + #[inline] + pub(crate) fn _set_low(&mut self) { + let mask = self.id.mask(); + self.id.sio_out_clr().write(|w| unsafe { w.bits(mask) }); + } + + #[inline] + pub(crate) fn _set_high(&mut self) { + let mask = self.id.mask(); + self.id.sio_out_set().write(|w| unsafe { w.bits(mask) }); + } + + #[inline] + pub(crate) fn _toggle(&mut self) { + let mask = self.id.mask(); + self.id.sio_out_xor().write(|w| unsafe { w.bits(mask) }); + } + + #[inline] + pub(crate) fn _is_set_low(&self) -> bool { + let mask = self.id.mask(); + self.id.sio_out().read().bits() & mask == 0 + } + + #[inline] + pub(crate) fn _is_set_high(&self) -> bool { + !self._is_set_low() + } + + // ======================= + // Interrupt related methods + + /// Clear interrupt. + #[inline] + pub fn clear_interrupt(&mut self, interrupt: Interrupt) { + let (reg, offset) = self.id.intr(); + let mask = interrupt.mask(); + reg.write(|w| unsafe { w.bits(mask << offset) }); + } + + /// Interrupt status. + #[inline] + pub fn interrupt_status(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.proc_ints(Sio::core()); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Is interrupt enabled. + #[inline] + pub fn is_interrupt_enabled(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.proc_inte(Sio::core()); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Enable or disable interrupt. + #[inline] + pub fn set_interrupt_enabled(&self, interrupt: Interrupt, enabled: bool) { + let (reg, offset) = self.id.proc_inte(Sio::core()); + let mask = interrupt.mask(); + unsafe { + if enabled { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Is interrupt forced. + #[inline] + pub fn is_interrupt_forced(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.proc_intf(Sio::core()); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Force or release interrupt. + #[inline] + pub fn set_interrupt_forced(&self, interrupt: Interrupt, forced: bool) { + let (reg, offset) = self.id.proc_intf(Sio::core()); + let mask = interrupt.mask(); + unsafe { + if forced { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Dormant wake status. + #[inline] + pub fn dormant_wake_status(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.dormant_wake_ints(); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Is dormant wake enabled. + #[inline] + pub fn is_dormant_wake_enabled(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.dormant_wake_inte(); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Enable or disable dormant wake. + #[inline] + pub fn set_dormant_wake_enabled(&self, interrupt: Interrupt, enabled: bool) { + let (reg, offset) = self.id.dormant_wake_inte(); + let mask = interrupt.mask(); + unsafe { + if enabled { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Is dormant wake forced. + #[inline] + pub fn is_dormant_wake_forced(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.dormant_wake_intf(); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Force dormant wake. + #[inline] + pub fn set_dormant_wake_forced(&mut self, interrupt: Interrupt, forced: bool) { + let (reg, offset) = self.id.dormant_wake_intf(); + let mask = interrupt.mask(); + unsafe { + if forced { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Return a wrapper that implements InputPin. + /// + /// This allows to read from the pin independent of the selected function. + /// Depending on the pad configuration, reading from the pin may not return a + /// meaningful result. + /// + /// Calling this function does not set the pad's input enable bit. + pub fn as_input(&self) -> AsInputPin { + AsInputPin(self) + } +} +impl Pin, P> { + /// Is bypass enabled + #[inline] + pub fn is_sync_bypass(&self) -> bool { + let mask = self.id.mask(); + self.id.proc_in_by_pass().read().bits() & mask == mask + } + + /// Bypass the input sync stages. + /// + /// This saves two clock cycles in the input signal's path at the risks of introducing metastability. + #[inline] + pub fn set_sync_bypass(&mut self, bypass: bool) { + let mask = self.id.mask(); + let reg = self.id.proc_in_by_pass(); + unsafe { + if bypass { + write_bitmask_set(reg.as_ptr(), mask); + } else { + write_bitmask_clear(reg.as_ptr(), mask); + } + } + } +} +impl Pin { + /// Try to return to a type-checked pin id. + /// + /// This method may fail if the target pin id differs from the current dynamic one. + pub fn try_into_pin(self) -> Result, Self> { + if P2::ID == self.id { + Ok(Pin { + id: P2::new(), + function: self.function, + pull_type: self.pull_type, + }) + } else { + Err(self) + } + } + + /// Try to change the pin's function. + pub fn try_into_function(self) -> Result, Pin> + where + F2: func::Function, + { + // Thanks to type-level validation, we know F2 is valid for I + let prev_function = self.function.as_dyn(); + let function = F2::from(prev_function); + let function_as_dyn = function.as_dyn(); + + use func_sealed::Function; + if function_as_dyn.is_valid(&self.id) { + if function_as_dyn != prev_function.as_dyn() { + pin::set_function(&self.id, function_as_dyn); + } + Ok(Pin { + function, + id: self.id, + pull_type: self.pull_type, + }) + } else { + Err(self) + } + } +} +impl Pin { + /// Try to set the pin's function. + /// + /// This method may fail if the requested function is not supported by the pin, eg `FunctionXiP` + /// on a gpio from `Bank0`. + pub fn try_set_function(&mut self, function: DynFunction) -> Result<(), func::InvalidFunction> { + use func_sealed::Function; + if !function.is_valid(&self.id) { + return Err(func::InvalidFunction); + } else if function != self.function.as_dyn() { + pin::set_function(&self.id, function); + self.function = function; + } + Ok(()) + } + + /// Gets the pin's function. + pub fn function(&self) -> DynFunction { + use func_sealed::Function; + self.function.as_dyn() + } +} + +impl Pin { + /// Set the pin's pull type. + pub fn set_pull_type(&mut self, pull_type: DynPullType) { + if pull_type != self.pull_type { + pin::set_pull_type(&self.id, pull_type); + self.pull_type = pull_type; + } + } +} + +/// Wrapper providing input pin functions for GPIO pins independent of the configured mode. +pub struct AsInputPin<'a, I: PinId, F: func::Function, P: PullType>(&'a Pin); + +//============================================================================== +// Embedded-HAL +//============================================================================== + +/// GPIO error type. +pub type Error = core::convert::Infallible; + +impl embedded_hal_0_2::digital::v2::OutputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn set_low(&mut self) -> Result<(), Self::Error> { + self._set_low(); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self._set_high(); + Ok(()) + } +} + +/// Deprecated: Instead of implicitly implementing InputPin for function SioOutput, +/// use `pin.as_input()` to get access to input values independent of the selected function. +impl embedded_hal_0_2::digital::v2::InputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn is_high(&self) -> Result { + Ok(self._is_high()) + } + + fn is_low(&self) -> Result { + Ok(self._is_low()) + } +} + +impl embedded_hal_0_2::digital::v2::InputPin + for AsInputPin<'_, I, F, P> +{ + type Error = core::convert::Infallible; + + fn is_high(&self) -> Result { + Ok(self.0._is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.0._is_low()) + } +} + +impl embedded_hal_0_2::digital::v2::StatefulOutputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + fn is_set_high(&self) -> Result { + Ok(self._is_set_high()) + } + + fn is_set_low(&self) -> Result { + Ok(self._is_set_low()) + } +} + +impl embedded_hal_0_2::digital::v2::ToggleableOutputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn toggle(&mut self) -> Result<(), Self::Error> { + self._toggle(); + Ok(()) + } +} +impl embedded_hal_0_2::digital::v2::InputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn is_high(&self) -> Result { + Ok(self._is_high()) + } + + fn is_low(&self) -> Result { + Ok(self._is_low()) + } +} + +//============================================================================== +// Pins +//============================================================================== + +/// Default type state of a pin after reset of the pads, io and sio. +pub trait DefaultTypeState: crate::typelevel::Sealed { + /// Default function. + type Function: Function; + /// Default pull type. + type PullType: PullType; +} + +// Clear input enable for pins of bank0. +// Pins 26-29 are ADC pins. If the pins are connected to an analog input, +// the signal level may not be valid for a digital input. Therefore, input +// should be disabled by default. +// For the other GPIO pins, the same setting is applied for consistency. +macro_rules! reset_ie { + ( Bank0, $pads:ident ) => { + for id in (0..=29) { + $pads.gpio(id).modify(|_, w| w.ie().clear_bit()); + } + }; + ( Qspi, $pads:ident ) => {}; +} + +macro_rules! gpio { + ( $bank:ident:$prefix:ident, [ $(($id:expr, $pull_type:ident, $func:ident)),* ] ) => { + paste::paste!{ + #[doc = "Pin bank " [<$bank>] ] + pub mod [<$bank:snake>] { + use $crate::pac::{[],[]}; + use crate::sio::[]; + use super::{Pin, pin, pull, func}; + $(pub use super::pin::[<$bank:lower>]::[<$prefix $id>];)* + + $( + impl super::DefaultTypeState for [<$prefix $id>] { + type Function = super::[]; + type PullType = super::[]; + } + )* + gpio!(struct: $bank $prefix $([<$prefix $id>], $id, $func, $pull_type),*); + + impl Pins { + /// Take ownership of the PAC peripherals and SIO slice and split it into discrete [`Pin`]s + /// + /// This clears the input-enable flag for all Bank0 pads. + pub fn new(io : [], pads: [], sio: [], reset : &mut $crate::pac::RESETS) -> Self { + use $crate::resets::SubsystemReset; + pads.reset_bring_down(reset); + io.reset_bring_down(reset); + + { + use $crate::gpio::pin::DynBankId; + // SAFETY: this function owns the whole bank that will be affected. + let sio = unsafe { &*$crate::pac::SIO::PTR }; + if DynBankId::$bank == DynBankId::Bank0 { + unsafe { + // Put the low pins back to defaults + sio.gpio_oe().reset(); + sio.gpio_out().reset(); + // only reset the GPIOs, not the QSPI bank pins + sio.gpio_hi_oe_clr().write(|w| { + w.bits(0x0000_FFFF); + w + }); + sio.gpio_hi_out_clr().write(|w| { + w.bits(0x0000_FFFF); + w + }); + } + } else { + // only reset the QSPI bank pins not the GPIOs + unsafe { + sio.gpio_hi_oe_clr().write(|w| { + w.bits(0xFFF0_0000); + w + }); + sio.gpio_hi_out_clr().write(|w| { + w.bits(0xFFF0_0000); + w + }); + } + } + } + + io.reset_bring_up(reset); + pads.reset_bring_up(reset); + reset_ie!($bank, pads); + gpio!(members: io, pads, sio, $(([<$prefix $id>], $func, $pull_type)),+) + } + } + } + } + }; + (struct: $bank:ident $prefix:ident $($PXi:ident, $id:expr, $func:ident, $pull_type:ident),*) => { + paste::paste!{ + /// Collection of all the individual [`Pin`]s + #[derive(Debug)] + pub struct Pins { + _io: [], + _pads: [], + _sio: [], + $( + #[doc = "Pin " [<$PXi>] ] + pub [<$PXi:snake>]: Pin]::[<$prefix $id>] , func::[], pull::[]>, + )* + } + } + }; + (members: $io:ident, $pads:ident, $sio:ident, $(($PXi:ident, $func:ident, $pull_type:ident)),+) => { + paste::paste!{ + Self { + _io: $io, + _pads: $pads, + _sio: $sio, + $( + [<$PXi:snake>]: Pin { + id: [<$PXi>] (()), + function: func::[] (()), + pull_type: pull::[] (()) + }, + )+ + } + } + }; +} + +gpio!( + Bank0: Gpio, + [ + (0, Down, Null), + (1, Down, Null), + (2, Down, Null), + (3, Down, Null), + (4, Down, Null), + (5, Down, Null), + (6, Down, Null), + (7, Down, Null), + (8, Down, Null), + (9, Down, Null), + (10, Down, Null), + (11, Down, Null), + (12, Down, Null), + (13, Down, Null), + (14, Down, Null), + (15, Down, Null), + (16, Down, Null), + (17, Down, Null), + (18, Down, Null), + (19, Down, Null), + (20, Down, Null), + (21, Down, Null), + (22, Down, Null), + (23, Down, Null), + (24, Down, Null), + (25, Down, Null), + (26, Down, Null), + (27, Down, Null), + (28, Down, Null), + (29, Down, Null), + (30, Down, Null), + (31, Down, Null), + (32, Down, Null), + (33, Down, Null), + (34, Down, Null), + (35, Down, Null), + (36, Down, Null), + (37, Down, Null), + (38, Down, Null), + (39, Down, Null), + (40, Down, Null), + (41, Down, Null), + (42, Down, Null), + (43, Down, Null), + (44, Down, Null), + (45, Down, Null), + (46, Down, Null), + (47, Down, Null) + ] +); + +gpio!( + Qspi: Qspi, + [ + (UsbDp, None, Null), + (UsbDm, None, Null), + (Sclk, Down, Null), + (Ss, Up, Null), + (Sd0, None, Null), + (Sd1, None, Null), + (Sd2, None, Null), + (Sd3, None, Null) + ] +); + +pub use bank0::Pins; + +//============================================================================== +// AnyPin +//============================================================================== + +/// Type class for [`Pin`] types. +/// +/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for +/// [`Pin`] types. See the `AnyKind` documentation for more details on the +/// pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +/// [type class]: crate::typelevel#type-classes +pub trait AnyPin: Sealed +where + Self: typelevel::Sealed, + Self: typelevel::Is>, +{ + /// [`PinId`] of the corresponding [`Pin`] + type Id: PinId; + /// [`func::Function`] of the corresponding [`Pin`] + type Function: func::Function; + /// [`PullType`] of the corresponding [`Pin`] + type Pull: PullType; +} + +impl Sealed for Pin +where + I: PinId, + F: func::Function, + P: PullType, +{ +} + +impl AnyPin for Pin +where + I: PinId, + F: func::Function, + P: PullType, +{ + type Id = I; + type Function = F; + type Pull = P; +} + +/// Type alias to recover the specific [`Pin`] type from an implementation of [`AnyPin`]. +/// +/// See the [`AnyKind`] documentation for more details on the pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +pub type SpecificPin

= Pin<

::Id,

::Function,

::Pull>; + +//============================================================================== +// bsp_pins helper macro +//============================================================================== + +/// Helper macro to give meaningful names to GPIO pins +/// +/// The normal [`Pins`] struct names each [`Pin`] according to its [`PinId`]. +/// However, BSP authors would prefer to name each [`Pin`] according to its +/// function. This macro defines a new `Pins` struct with custom field names +/// for each [`Pin`]. +/// +/// # Example +/// Calling the macro like this: +/// ```rust +/// use rp235x_hal::bsp_pins; +/// bsp_pins! { +/// #[cfg(feature = "gpio")] +/// Gpio0 { +/// /// Doc gpio0 +/// name: gpio0, +/// aliases: { FunctionPio0, PullNone: PioPin } +/// }, +/// Gpio1 { +/// name: led, +/// aliases: { FunctionPwm, PullDown: LedPwm } +/// }, +/// } +/// ``` +/// +/// Is roughly equivalent to the following source code (excluding the docs strings below): +/// ``` +/// use ::rp235x_hal as hal; +/// use hal::gpio; +/// pub struct Pins { +/// /// Doc gpio0 +/// #[cfg(feature = "gpio")] +/// pub gpio0: gpio::Pin< +/// gpio::bank0::Gpio0, +/// ::Function, +/// ::PullType, +/// >, +/// pub led: gpio::Pin< +/// gpio::bank0::Gpio1, +/// ::Function, +/// ::PullType, +/// >, +/// } +/// impl Pins { +/// #[inline] +/// pub fn new( +/// io: hal::pac::IO_BANK0, +/// pads: hal::pac::PADS_BANK0, +/// sio: hal::sio::SioGpioBank0, +/// reset: &mut hal::pac::RESETS, +/// ) -> Self { +/// let mut pins = gpio::Pins::new(io, pads, sio, reset); +/// Self { +/// #[cfg(feature = "gpio")] +/// gpio0: pins.gpio0, +/// led: pins.gpio1, +/// } +/// } +/// } +/// pub type PioPin = gpio::Pin; +/// pub type LedPwm = gpio::Pin; +/// ``` +#[macro_export] +macro_rules! bsp_pins { + ( + $( + $( #[$id_cfg:meta] )* + $Id:ident { + $( #[$name_doc:meta] )* + name: $name:ident $(,)? + $( + aliases: { + $( + $( #[$alias_cfg:meta] )* + $Function:ty, $PullType:ident: $Alias:ident + ),+ + } + )? + } $(,)? + )+ + ) => { + $crate::paste::paste! { + + /// BSP replacement for the HAL + /// [`Pins`](rp235x_hal::gpio::Pins) type + /// + /// This type is intended to provide more meaningful names for the + /// given pins. + /// + /// To enable specific functions of the pins you can use the + /// [rp235x_hal::gpio::pin::Pin::into_function] function with + /// one of: + /// - [rp235x_hal::gpio::FunctionI2C] + /// - [rp235x_hal::gpio::FunctionPwm] + /// - [rp235x_hal::gpio::FunctionSpi] + /// - [rp235x_hal::gpio::FunctionXip] + /// - [rp235x_hal::gpio::FunctionPio0] + /// - [rp235x_hal::gpio::FunctionPio1] + /// - [rp235x_hal::gpio::FunctionPio2] + /// - [rp235x_hal::gpio::FunctionUart] + /// - [rp235x_hal::gpio::FunctionUartAux] + /// + /// like this: + ///```no_run + /// use rp235x_hal::{pac, gpio::{bank0::Gpio12, Pin, Pins}, sio::Sio}; + /// + /// let mut peripherals = hal::pac::Peripherals::take().unwrap(); + /// let sio = Sio::new(peripherals.SIO); + /// let pins = Pins::new(peripherals.IO_BANK0,peripherals.PADS_BANK0,sio.gpio_bank0, &mut peripherals.RESETS); + /// + /// let _spi_sclk = pins.gpio2.into_function::(); + /// let _spi_mosi = pins.gpio3.into_function::(); + /// let _spi_miso = pins.gpio4.into_function::(); + ///``` + /// + /// **See also [rp235x_hal::gpio] for more in depth information about this**! + pub struct Pins { + $( + $( #[$id_cfg] )* + $( #[$name_doc] )* + pub $name: $crate::gpio::Pin< + $crate::gpio::bank0::$Id, + <$crate::gpio::bank0::$Id as $crate::gpio::DefaultTypeState>::Function, + <$crate::gpio::bank0::$Id as $crate::gpio::DefaultTypeState>::PullType, + >, + )+ + } + + impl Pins { + /// Take ownership of the PAC [`PORT`] and split it into + /// discrete [`Pin`]s. + /// + /// This struct serves as a replacement for the HAL [`Pins`] + /// struct. It is intended to provide more meaningful names for + /// each [`Pin`] in a BSP. Any [`Pin`] not defined by the BSP is + /// dropped. + /// + /// [`Pin`](rp235x_hal::gpio::Pin) + /// [`Pins`](rp235x_hal::gpio::Pins) + #[inline] + pub fn new(io : $crate::pac::IO_BANK0, pads: $crate::pac::PADS_BANK0, sio: $crate::sio::SioGpioBank0, reset : &mut $crate::pac::RESETS) -> Self { + let mut pins = $crate::gpio::Pins::new(io,pads,sio,reset); + Self { + $( + $( #[$id_cfg] )* + $name: pins.[<$Id:lower>], + )+ + } + } + } + $( + $( #[$id_cfg] )* + $crate::bsp_pins!(@aliases, $( $( $( #[$alias_cfg] )* $Id $Function $PullType $Alias )+ )? ); + )+ + } + }; + ( @aliases, $( $( $( #[$attr:meta] )* $Id:ident $Function:ident $PullType:ident $Alias:ident )+ )? ) => { + $crate::paste::paste! { + $( + $( + $( #[$attr] )* + /// Alias for a configured [`Pin`](rp235x_hal::gpio::Pin) + pub type $Alias = $crate::gpio::Pin< + $crate::gpio::bank0::$Id, + $crate::gpio::$Function, + $crate::gpio::$PullType + >; + )+ + )? + } + }; +} + +//============================================================================== +// InOutPin +//============================================================================== + +/// A wrapper [`AnyPin`]`` emulating open-drain function. +/// +/// This wrapper implements both InputPin and OutputPin, to simulate an open-drain pin as needed for +/// example by the wire protocol the DHT11 sensor speaks. +/// +/// +pub struct InOutPin { + inner: Pin, +} + +impl InOutPin { + /// Create a new wrapper + pub fn new(inner: T) -> InOutPin + where + T::Id: ValidFunction, + { + let mut inner = inner.into(); + inner.set_output_enable_override(OutputEnableOverride::Disable); + + // into Pin<_, FunctionSioOutput, _> + let inner = inner.into_push_pull_output_in_state(PinState::Low); + + Self { inner } + } +} + +impl InOutPin +where + T: AnyPin, + T::Id: ValidFunction, +{ + /// Releases the pin reverting to its previous function. + pub fn release(self) -> T { + // restore the previous typestate first + let mut inner = self.inner.reconfigure(); + // disable override + inner.set_output_enable_override(OutputEnableOverride::Normal); + // typelevel-return + T::from(inner) + } +} + +impl embedded_hal_0_2::digital::v2::InputPin for InOutPin { + type Error = Error; + fn is_high(&self) -> Result { + self.inner.is_high() + } + + fn is_low(&self) -> Result { + self.inner.is_low() + } +} + +impl embedded_hal_0_2::digital::v2::OutputPin for InOutPin { + type Error = Error; + fn set_low(&mut self) -> Result<(), Error> { + // The pin is already set to output low but this is inhibited by the override. + self.inner + .set_output_enable_override(OutputEnableOverride::Enable); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Error> { + // To set the open-drain pin to high, just disable the output driver by configuring the + // output override. That way, the DHT11 can still pull the data line down to send its response. + self.inner + .set_output_enable_override(OutputEnableOverride::Disable); + Ok(()) + } +} + +mod eh1 { + use embedded_hal::digital::{ErrorType, InputPin, OutputPin, StatefulOutputPin}; + + use super::{ + func, AnyPin, AsInputPin, Error, FunctionSio, InOutPin, OutputEnableOverride, Pin, PinId, + PullType, SioConfig, SioInput, SioOutput, + }; + + impl ErrorType for Pin, P> + where + I: PinId, + P: PullType, + S: SioConfig, + { + type Error = Error; + } + + impl OutputPin for Pin, P> + where + I: PinId, + P: PullType, + { + fn set_low(&mut self) -> Result<(), Self::Error> { + self._set_low(); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self._set_high(); + Ok(()) + } + } + + impl StatefulOutputPin for Pin, P> + where + I: PinId, + P: PullType, + { + fn is_set_high(&mut self) -> Result { + Ok(self._is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok(self._is_set_low()) + } + + fn toggle(&mut self) -> Result<(), Self::Error> { + self._toggle(); + Ok(()) + } + } + + impl InputPin for Pin, P> + where + I: PinId, + P: PullType, + { + fn is_high(&mut self) -> Result { + Ok(self._is_high()) + } + + fn is_low(&mut self) -> Result { + Ok(self._is_low()) + } + } + + impl ErrorType for AsInputPin<'_, I, F, P> + where + I: PinId, + F: func::Function, + P: PullType, + { + type Error = Error; + } + + impl InputPin for AsInputPin<'_, I, F, P> { + fn is_high(&mut self) -> Result { + Ok(self.0._is_high()) + } + + fn is_low(&mut self) -> Result { + Ok(self.0._is_low()) + } + } + + impl ErrorType for InOutPin + where + I: AnyPin, + { + type Error = Error; + } + + impl OutputPin for InOutPin + where + I: AnyPin, + { + fn set_low(&mut self) -> Result<(), Self::Error> { + // The pin is already set to output low but this is inhibited by the override. + self.inner + .set_output_enable_override(OutputEnableOverride::Enable); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + // To set the open-drain pin to high, just disable the output driver by configuring the + // output override. That way, the DHT11 can still pull the data line down to send its response. + self.inner + .set_output_enable_override(OutputEnableOverride::Disable); + Ok(()) + } + } + + impl InputPin for InOutPin + where + I: AnyPin, + { + fn is_high(&mut self) -> Result { + Ok(self.inner._is_high()) + } + + fn is_low(&mut self) -> Result { + Ok(self.inner._is_low()) + } + } +} diff --git a/rp-hal/rp235x-hal/src/gpio/pin.rs b/rp-hal/rp235x-hal/src/gpio/pin.rs new file mode 100644 index 0000000..ef4aa6b --- /dev/null +++ b/rp-hal/rp235x-hal/src/gpio/pin.rs @@ -0,0 +1,216 @@ +//! ## Note 1 +//! +//! QSPI registers are ordered differently on pads, io & SIO: +//! - PADS: sclk, sd0, sd1, sd2, sd3, ss +//! - IO bank: sclk, ss, sd0, sd1, sd2, sd3 +//! - SIO: sclk, ss, sd0, sd1, sd2, sd3 +//! +//! This HAL will use the order shared by IO bank & SIO. The main reason for that being the bit +//! shift operation used in SIO and interrupt related registers. +//! +//! ## Note 2 +//! +//! The SWD and SWCLK pin only appear on the pad control and cannot be used as gpio. +//! They are therefore absent from this implementation. +//! +//! ## Note 3 +//! +//! Dues to limitations in svd2rust and svdtools (and their shared dependencies) it is not possible +//! to fully express the relations between the gpio registers in the different banks on the rp235x +//! at the PAC level. +//! +//! These limitations are respectively: +//! - Inability to derive register with different reset values +//! - Inability to derive from path including clusters and/or arrays +//! +//! This modules bridges that gap by adding a trait definition per register type and implementing it +//! for each of the relevant registers. + +use crate::typelevel::Sealed; + +use super::{DynFunction, DynPullType}; + +pub(crate) mod pin_sealed; + +/// Value-level `enum` for the pin's bank. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum DynBankId { + /// GPIO Pins bank + Bank0, + /// QSPI Pins bank + Qspi, +} + +/// Type-level `enum` for the pin's bank ID. +pub trait BankId: Sealed {} + +/// Type-level `variant` of `BankId` +pub struct BankBank0; +impl Sealed for BankBank0 {} +impl BankId for BankBank0 {} + +/// Type-level `variant` of `BankId` +pub struct BankQspi; +impl Sealed for BankQspi {} +impl BankId for BankQspi {} + +/// Type-level `enum` for the pin Id (pin number + bank). +pub trait PinId: pin_sealed::PinIdOps { + /// This pin as a `DynPinId`. + fn as_dyn(&self) -> DynPinId; +} + +/// Value-level representation for the pin (bank + id). +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct DynPinId { + /// Pin bank. + pub bank: DynBankId, + /// Pin number. + pub num: u8, +} +impl PinId for DynPinId { + #[inline] + fn as_dyn(&self) -> DynPinId { + *self + } +} + +macro_rules! pin_ids { + ($bank:ident: $($id:expr;$name:ident),*) => { + pin_ids!($bank as $bank: $($id;$name),*); + }; + ($bank:ident as $prefix:ident: $($id:tt),*) => { + pin_ids!($bank as $prefix: $($id;$id),*); + }; + ($bank:ident as $prefix:ident: $($id:expr;$name:tt),*) => { + paste::paste!{ + $( + #[doc = "Type level variant for the pin `" $name "` in bank `" $prefix "`."] + #[derive(Debug)] + pub struct [<$prefix $name>] (pub(crate) ()); + impl crate::typelevel::Sealed for [<$prefix $name>] {} + impl PinId for [<$prefix $name>] { + #[inline] + fn as_dyn(&self) -> DynPinId { + DynPinId { + bank: DynBankId::$bank, + num: $id + } + } + } + impl pin_sealed::TypeLevelPinId for [<$prefix $name>] { + type Bank = []; + + const ID: DynPinId = DynPinId { + bank: DynBankId::$bank, + num: $id + }; + + fn new() -> Self { + Self(()) + } + } + )* + } + }; +} + +/// The bank with all the GPIOs +/// +/// GPIOs 0 through 29 are available in all package variants. GPIOs 30 through +/// 47 are available only on the QFN-80 (RP2350B) package. +pub mod bank0 { + use super::{pin_sealed, BankBank0, DynBankId, DynPinId, PinId}; + pin_ids!( + Bank0 as Gpio: + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10,11,12,13,14,15,16,17,18,19, + 20,21,22,23,24,25,26,27,28,29, + 30,31,32,33,34,35,36,37,38,39, + 40,41,42,43,44,45,46,47 + ); +} + +/// The bank with the QSPI related pins. +/// +/// These should all have IDs over 32, because they are all controlled by the +/// hi registers. +pub mod qspi { + use super::{pin_sealed, BankQspi, DynBankId, DynPinId, PinId}; + pin_ids!(Qspi: 56;UsbDp, 57;UsbDm, 58;Sclk, 59;Ss, 60;Sd0, 61;Sd1, 62;Sd2, 63;Sd3); +} + +pub(crate) fn set_function(pin: &P, function: DynFunction) { + use crate::pac::io_bank0::gpio::gpio_ctrl::FUNCSEL_A; + let funcsel = match function { + // The XIP function has a value of 0, which on bank0 is called JTAG. + DynFunction::Xip => FUNCSEL_A::JTAG, + DynFunction::Spi => FUNCSEL_A::SPI, + DynFunction::Uart => FUNCSEL_A::UART, + DynFunction::I2c => FUNCSEL_A::I2C, + DynFunction::Pwm => FUNCSEL_A::PWM, + DynFunction::Sio(sio) => { + let mask = pin.mask(); + match sio { + crate::gpio::DynSioConfig::Input => { + pin.sio_oe_clr().write(|w| unsafe { w.bits(mask) }); + } + crate::gpio::DynSioConfig::Output => { + pin.sio_oe_set().write(|w| unsafe { w.bits(mask) }); + } + } + + FUNCSEL_A::SIO + } + DynFunction::Pio0 => FUNCSEL_A::PIO0, + DynFunction::Pio1 => FUNCSEL_A::PIO1, + DynFunction::Pio2 => FUNCSEL_A::PIO2, + DynFunction::Clock => FUNCSEL_A::GPCK, + DynFunction::XipCs1 => FUNCSEL_A::GPCK, + DynFunction::Usb => FUNCSEL_A::USB, + DynFunction::UartAux => FUNCSEL_A::UART_AUX, + DynFunction::Null => FUNCSEL_A::NULL, + }; + + if funcsel != FUNCSEL_A::NULL { + pin.pad_ctrl().modify(|_, w| { + // Set input enable on, output disable off + // RP2350: input enable defaults to off, so this is important! + w.ie().set_bit(); + w.od().clear_bit(); + // RP2350: remove pad isolation now a function is wired up + w.iso().clear_bit(); + w + }); + } else { + pin.pad_ctrl().modify(|_, w| { + // Set input enable off, output disable on + w.ie().clear_bit(); + w.od().set_bit(); + // RP2350: isolate pad + w.iso().set_bit(); + w + }); + } + + // Zero all fields apart from fsel; we want this IO to do what the peripheral tells it. + // This doesn't affect e.g. pullup/pulldown, as these are in pad controls. + unsafe { + pin.io_ctrl() + .write_with_zero(|w| w.funcsel().variant(funcsel)); + } +} + +pub(crate) fn set_pull_type(pin: &P, pull_type: DynPullType) { + let (pue, pde) = match pull_type { + DynPullType::None => (false, false), + DynPullType::Up => (true, false), + DynPullType::Down => (false, true), + DynPullType::BusKeep => (true, true), + }; + + pin.pad_ctrl() + .modify(|_, w| w.pue().bit(pue).pde().bit(pde)); +} diff --git a/rp-hal/rp235x-hal/src/gpio/pin/pin_sealed.rs b/rp-hal/rp235x-hal/src/gpio/pin/pin_sealed.rs new file mode 100644 index 0000000..12ec0c9 --- /dev/null +++ b/rp-hal/rp235x-hal/src/gpio/pin/pin_sealed.rs @@ -0,0 +1,279 @@ +use super::{DynBankId, DynPinId}; +use crate::{pac, sio::CoreId}; + +pub trait TypeLevelPinId: super::PinId { + type Bank: super::BankId; + + const ID: DynPinId; + + fn new() -> Self; +} + +pub trait PinIdOps { + /// Get the pin's 32-bit bit mask. + /// + /// Note there are more than 32 GPIOs so some have the same mask. + /// + /// The other methods in this trait will return a reference to the relevant + /// high or low register. + fn mask(&self) -> u32; + /// Is this GPIO in the high set (i.e. >= 32)? + fn is_hi(&self) -> bool; + /// Get the GPIO status register, either hi or low. + fn io_status(&self) -> &pac::io_bank0::gpio::GPIO_STATUS; + /// Get the GPIO control register, either hi or low. + fn io_ctrl(&self) -> &pac::io_bank0::gpio::GPIO_CTRL; + /// Get the Pad control register, either hi or low. + fn pad_ctrl(&self) -> &pac::pads_bank0::GPIO; + + /// Get the SIO Input register, either hi or low. + fn sio_in(&self) -> &pac::sio::GPIO_IN; + /// Get the SIO Output register, either hi or low. + fn sio_out(&self) -> &pac::sio::GPIO_OUT; + /// Get the SIO Output Set register, either hi or low. + fn sio_out_set(&self) -> &pac::sio::GPIO_OUT_SET; + /// Get the SIO Output Clear register, either hi or low. + fn sio_out_clr(&self) -> &pac::sio::GPIO_OUT_CLR; + /// Get the SIO Output XOR register, either hi or low. + fn sio_out_xor(&self) -> &pac::sio::GPIO_OUT_XOR; + /// Get the SIO Output Enable register, either hi or low. + fn sio_oe(&self) -> &pac::sio::GPIO_OE; + /// Get the SIO Output Enable Set register, either hi or low. + fn sio_oe_set(&self) -> &pac::sio::GPIO_OE_SET; + /// Get the SIO Output Enable Clear register, either hi or low. + fn sio_oe_clr(&self) -> &pac::sio::GPIO_OE_CLR; + /// Get the SIO Output Enable XOR register, either hi or low. + fn sio_oe_xor(&self) -> &pac::sio::GPIO_OE_XOR; + /// Get the SYSCFG Input Synchroniser Bypass enable register, either hi or lo + fn proc_in_by_pass(&self) -> &pac::syscfg::PROC_IN_SYNC_BYPASS; + + /// Raw Interrupt state. + /// + /// Get the raw interrupt register, and which bit in that register this GPIO + /// corresponds to. + fn intr(&self) -> (&pac::io_bank0::INTR, usize); + /// Interrupt Status for the given Core. + /// + /// Get the interrupt status register, and which bit in that register this + /// pin corresponds to. + fn proc_ints(&self, proc: CoreId) -> (&pac::io_bank0::PROC0_INTS, usize); + /// Interrupt Enable for the given Core. + /// + /// Get the interrupt enable register, and which bit in that register this + /// pin corresponds to. + fn proc_inte(&self, proc: CoreId) -> (&pac::io_bank0::PROC0_INTE, usize); + /// Interrupt Force for the given Core. + /// + /// Get the interrupt force register, and which bit in that register this + /// pin corresponds to. + fn proc_intf(&self, proc: CoreId) -> (&pac::io_bank0::PROC0_INTF, usize); + /// Interrupt Status for Dormant Wake. + /// + /// Get the interrupt status register, and which bit in that register this + /// pin corresponds to. + fn dormant_wake_ints(&self) -> (&pac::io_bank0::DORMANT_WAKE_INTS, usize); + /// Interrupt Enable for Dormant Wake. + /// + /// Get the interrupt enable register, and which bit in that register this + /// pin corresponds to. + fn dormant_wake_inte(&self) -> (&pac::io_bank0::DORMANT_WAKE_INTE, usize); + /// Interrupt Force for Dormant Wake. + /// + /// Get the interrupt force register, and which bit in that register this + /// pin corresponds to. + fn dormant_wake_intf(&self) -> (&pac::io_bank0::DORMANT_WAKE_INTF, usize); +} + +macro_rules! accessor_fns { + (sio $reg:ident) => { + paste::paste! { + fn [](&self) -> &$crate::pac::sio::[] { + let pin = self.as_dyn(); + unsafe { + let sio = &*$crate::pac::SIO::PTR; + match (self.is_hi(), pin.bank) { + // The first 32 GPIOs are in have registers called `gpio_xxx` + (false, DynBankId::Bank0) => sio.[](), + // The rest are controlled by `gpio_hi_xxx` + _ => core::mem::transmute::<&$crate::pac::sio::[],&$crate::pac::sio::[]>(sio.[]()), + } + } + } + } + }; + (io $reg:ident) => { + paste::paste! { + fn [](&self) -> &$crate::pac::io_bank0::gpio::[] { + let pin = self.as_dyn(); + match pin.bank { + DynBankId::Bank0 => { + let gpio = unsafe { &*$crate::pac::IO_BANK0::PTR }; + gpio.gpio(usize::from(pin.num)).[]() + } + DynBankId::Qspi => unsafe { + let qspi = &*$crate::pac::IO_QSPI::PTR; + core::mem::transmute::<&$crate::pac::io_qspi::gpio_qspi::[], &$crate::pac::io_bank0::gpio::[]>(qspi.gpio_qspi(usize::from(pin.num)).[]()) + }, + } + } + } + }; + (int $reg:ident) => { + paste::paste! { + fn [](&self, proc: CoreId) -> (&$crate::pac::io_bank0::[], usize) { + let pin = self.as_dyn(); + let (index, offset) = (pin.num / 8, pin.num % 8 * 4); + unsafe { + let reg = match pin.bank { + DynBankId::Bank0 => { + let bank = &*$crate::pac::IO_BANK0::PTR; + match proc { + CoreId::Core0 => bank.[](usize::from(index)), + CoreId::Core1 => core::mem::transmute::<&$crate::pac::io_bank0::[], &$crate::pac::io_bank0::[]>(bank.[](usize::from(index))), + } + } + DynBankId::Qspi => { + let bank = &*$crate::pac::IO_QSPI::PTR; + match proc { + CoreId::Core0 => core::mem::transmute::<&$crate::pac::io_qspi::[], &$crate::pac::io_bank0::[]>(bank.[]()), + CoreId::Core1 => core::mem::transmute::<&$crate::pac::io_qspi::[], &$crate::pac::io_bank0::[]>(bank.[]()), + } + } + }; + (reg, usize::from(offset)) + } + } + } + }; + (dormant $reg:ident) => { + paste::paste! { + fn [< dormant_wake_ $reg:lower>](&self) -> (&$crate::pac::io_bank0::[< DORMANT_WAKE_ $reg:upper >], usize) { + let pin = self.as_dyn(); + let (index, offset) = (pin.num / 8, pin.num % 8 * 4); + unsafe { + let reg = match pin.bank { + DynBankId::Bank0 => { + let bank = &*$crate::pac::IO_BANK0::PTR; + bank.[< dormant_wake_ $reg:lower>](usize::from(index)) + } + DynBankId::Qspi => { + let bank = &*$crate::pac::IO_QSPI::PTR; + core::mem::transmute::<&$crate::pac::io_qspi::[< DORMANT_WAKE_ $reg:upper >], &$crate::pac::io_bank0::[< DORMANT_WAKE_ $reg:upper >]>(bank.[< dormant_wake_ $reg:lower>]()) + } + }; + (reg, usize::from(offset)) + } + } + } + }; +} +impl PinIdOps for T +where + T: super::PinId, +{ + fn mask(&self) -> u32 { + let mask_bit = self.as_dyn().num % 32; + 1 << mask_bit + } + + fn is_hi(&self) -> bool { + self.as_dyn().num >= 32 + } + + fn pad_ctrl(&self) -> &pac::pads_bank0::GPIO { + let pin = self.as_dyn(); + match pin.bank { + DynBankId::Bank0 => { + let gpio = unsafe { &*pac::PADS_BANK0::PTR }; + gpio.gpio(usize::from(pin.num)) + } + DynBankId::Qspi => unsafe { + let qspi = &*pac::PADS_QSPI::PTR; + use rp235x_pac::{generic::Reg, pads_bank0, pads_qspi}; + match pin.num { + 0 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sclk()), + 1 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_ss()), + 2 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd0()), + 3 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd1()), + 4 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd2()), + 5 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd3()), + _ => unreachable!("Invalid QSPI bank pin number."), + } + }, + } + } + accessor_fns!(io ctrl); + accessor_fns!(io status); + + accessor_fns!(sio in); + accessor_fns!(sio out); + accessor_fns!(sio out_set); + accessor_fns!(sio out_clr); + accessor_fns!(sio out_xor); + accessor_fns!(sio oe); + accessor_fns!(sio oe_set); + accessor_fns!(sio oe_clr); + accessor_fns!(sio oe_xor); + + fn proc_in_by_pass(&self) -> &pac::syscfg::PROC_IN_SYNC_BYPASS { + let pin = self.as_dyn(); + unsafe { + let syscfg = &*pac::SYSCFG::PTR; + match (pin.is_hi(), pin.bank) { + (false, DynBankId::Bank0) => syscfg.proc_in_sync_bypass(), + _ => core::mem::transmute::< + &pac::syscfg::PROC_IN_SYNC_BYPASS_HI, + &pac::syscfg::PROC_IN_SYNC_BYPASS, + >(syscfg.proc_in_sync_bypass_hi()), + } + } + } + + fn intr(&self) -> (&pac::io_bank0::INTR, usize) { + let pin = self.as_dyn(); + let (index, offset) = (pin.num / 8, pin.num % 8 * 4); + unsafe { + let reg = match pin.bank { + DynBankId::Bank0 => { + let bank = &*pac::IO_BANK0::PTR; + bank.intr(usize::from(index)) + } + DynBankId::Qspi => { + // Here, index will always be 0 as there are only 6 GPIOs in + // this bank. Transmuting the io_qspi::INTR type is OK + // because it has the same layout as the io_bank0::INTR type. + let bank = &*pac::IO_QSPI::PTR; + core::mem::transmute::<&pac::io_qspi::INTR, &pac::io_bank0::INTR>(bank.intr()) + } + }; + + (reg, usize::from(offset)) + } + } + + accessor_fns!(int ints); + accessor_fns!(int inte); + accessor_fns!(int intf); + + accessor_fns!(dormant ints); + accessor_fns!(dormant inte); + accessor_fns!(dormant intf); +} diff --git a/rp-hal/rp235x-hal/src/gpio/pin_group.rs b/rp-hal/rp235x-hal/src/gpio/pin_group.rs new file mode 100644 index 0000000..5fe3a7d --- /dev/null +++ b/rp-hal/rp235x-hal/src/gpio/pin_group.rs @@ -0,0 +1,190 @@ +//! Pin Groups +//! +//! Lets you set multiple GPIOs simultaneously. + +use embedded_hal::digital::PinState; +use frunk::{hlist::Plucker, HCons, HNil}; + +use crate::typelevel::Sealed; + +use super::{ + pin::pin_sealed::TypeLevelPinId, AnyPin, FunctionSio, FunctionSioInput, FunctionSioOutput, Pin, + PinId, PullType, SioConfig, +}; + +/// Generate a read mask for a pin list. +pub trait ReadPinHList: Sealed { + /// Generate a mask for a pin list. + fn read_mask(&self) -> u32; +} +impl ReadPinHList for HNil { + fn read_mask(&self) -> u32 { + 0 + } +} +impl ReadPinHList for HCons { + fn read_mask(&self) -> u32 { + (1 << self.head.borrow().id().num) | self.tail.read_mask() + } +} + +/// Generate a write mask for a pin list. +pub trait WritePinHList: Sealed { + /// Generate a mask for a pin list. + fn write_mask(&self) -> u32; +} +impl WritePinHList for HNil { + fn write_mask(&self) -> u32 { + 0 + } +} +impl WritePinHList + for HCons, T> +{ + fn write_mask(&self) -> u32 { + // This is an input pin, so don't include it in write_mask + self.tail.write_mask() + } +} +impl WritePinHList + for HCons, T> +{ + fn write_mask(&self) -> u32 { + (1 << self.head.id().num) | self.tail.write_mask() + } +} + +/// A group of pins to be controlled together and guaranty single cycle control of several pins. +/// +/// ```no_run +/// # macro_rules! defmt { ($($a:tt)*) => {}} +/// use rp235x_hal::{ +/// self as hal, +/// gpio::{bank0::Gpio12, Pin, PinGroup, PinState, Pins}, +/// sio::Sio, +/// }; +/// +/// let mut peripherals = hal::pac::Peripherals::take().unwrap(); +/// let sio = Sio::new(peripherals.SIO); +/// let pins = Pins::new( +/// peripherals.IO_BANK0, +/// peripherals.PADS_BANK0, +/// sio.gpio_bank0, +/// &mut peripherals.RESETS, +/// ); +/// +/// let group = PinGroup::new(); +/// let group = group.add_pin(pins.gpio0.into_pull_up_input()); +/// let mut group = group.add_pin(pins.gpio4.into_push_pull_output_in_state(PinState::High)); +/// +/// defmt!("Group's state is: {}", group.read()); +/// group.toggle(); +/// defmt!("Group's state is: {}", group.read()); +/// ``` +pub struct PinGroup(T); +impl PinGroup { + /// Creates an empty pin group. + pub fn new() -> Self { + PinGroup(HNil) + } + + /// Add a pin to the group. + pub fn add_pin(self, pin: P) -> PinGroup> + where + C: SioConfig, + P: AnyPin>, + P::Id: TypeLevelPinId, + { + PinGroup(HCons { + head: pin, + tail: self.0, + }) + } +} +impl PinGroup> +where + H::Id: TypeLevelPinId, + H: AnyPin, +{ + /// Add a pin to the group. + pub fn add_pin(self, pin: P) -> PinGroup>> + where + C: SioConfig, + P: AnyPin>, + P::Id: TypeLevelPinId::Bank>, + { + PinGroup(HCons { + head: pin, + tail: self.0, + }) + } + + /// Pluck a pin from the group. + #[allow(clippy::type_complexity)] + pub fn remove_pin( + self, + ) -> (P, PinGroup< as Plucker>::Remainder>) + where + HCons: Plucker, + { + let (p, rest): (P, _) = self.0.pluck(); + (p, PinGroup(rest)) + } +} +impl PinGroup> +where + HCons: ReadPinHList + WritePinHList, + H: AnyPin, +{ + /// Read the whole group at once. + /// + /// The returned value is a bit field where each pin populates its own index. Therefore, there + /// might be "holes" in the value. Unoccupied bits will always read as 0. + /// + /// For example, if the group contains Gpio1 and Gpio3, a read may yield: + /// ```text + /// 0b0000_0000__0000_0000__0000_0000__0000_1010 + /// This is Gpio3 ↑↑↑ + /// Gpio2 is not used || + /// This is Gpio1 | + /// ``` + pub fn read(&self) -> u32 { + let mask = self.0.read_mask(); + crate::sio::Sio::read_bank0() & mask + } + + /// Write this set of pins all at the same time. + /// + /// This only affects output pins. Input pins in the + /// set are ignored. + pub fn set(&mut self, state: PinState) { + use super::pin::pin_sealed::PinIdOps; + let mask = self.0.write_mask(); + let head_id = self.0.head.borrow().id(); + if state == PinState::Low { + head_id.sio_out_clr().write(|w| unsafe { w.bits(mask) }); + } else { + head_id.sio_out_set().write(|w| unsafe { w.bits(mask) }); + } + } + + /// Toggles this set of pins all at the same time. + /// + /// This only affects output pins. Input pins in the + /// set are ignored. + pub fn toggle(&mut self) { + use super::pin::pin_sealed::PinIdOps; + let mask = self.0.write_mask(); + self.0 + .head + .borrow() + .id() + .sio_out_xor() + .write(|w| unsafe { w.bits(mask) }); + } +} +impl Default for PinGroup { + fn default() -> Self { + Self::new() + } +} diff --git a/rp-hal/rp235x-hal/src/gpio/pull.rs b/rp-hal/rp235x-hal/src/gpio/pull.rs new file mode 100644 index 0000000..ee00459 --- /dev/null +++ b/rp-hal/rp235x-hal/src/gpio/pull.rs @@ -0,0 +1,64 @@ +use paste::paste; + +pub(crate) mod pull_sealed { + use super::DynPullType; + + pub trait PullType { + fn from(pm: DynPullType) -> Self; + fn as_dyn(&self) -> DynPullType; + } +} +/// Type-level `enum` for pull resistor types. +pub trait PullType: pull_sealed::PullType {} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +/// Value-level `enum` for pull resistor types. +pub enum DynPullType { + /// No pull enabled. + None, + /// Enable pull up. + Up, + /// Enable pull down. + Down, + /// This enables bus-keep mode. + /// + /// This is not documented in the datasheet but described in the + /// [c-sdk](https://github.com/raspberrypi/pico-sdk/blob/e7267f99febc70486923e17a8210088af058c915/src/rp2_common/hardware_gpio/gpio.c#L53) + /// as: + /// + /// > […] on rp235x, setting both pulls enables a "bus keep" function, + /// > i.e. weak pull to whatever is current high/low state of GPIO. + BusKeep, +} + +impl PullType for DynPullType {} +impl pull_sealed::PullType for DynPullType { + fn from(pm: DynPullType) -> Self { + pm + } + + fn as_dyn(&self) -> DynPullType { + *self + } +} + +macro_rules! pin_pull_type { + ($($pull_type:ident),*) => { + $(paste! { + /// Type-level `variant` of [`PullType`]. + #[derive(Debug)] + pub struct [](pub(super) ()); + impl PullType for [] {} + impl pull_sealed::PullType for [] { + fn from(_pm: DynPullType) -> Self { + Self(()) + } + fn as_dyn(&self) -> DynPullType { + DynPullType::[<$pull_type>] + } + } + })* + }; +} +pin_pull_type!(None, Up, Down, BusKeep); diff --git a/rp-hal/rp235x-hal/src/i2c.rs b/rp-hal/rp235x-hal/src/i2c.rs new file mode 100644 index 0000000..4627247 --- /dev/null +++ b/rp-hal/rp235x-hal/src/i2c.rs @@ -0,0 +1,415 @@ +//! Inter-Integrated Circuit (I2C) bus +//! +//! See [Section 12.2](https://rptl.io/rp2350-datasheet#section_i2c) for more details +//! +//! ## Usage +//! ```no_run +//! use fugit::RateExtU32; +//! use rp235x_hal::{self as hal, gpio::Pins, i2c::I2C, Sio}; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! +//! let mut i2c = I2C::i2c1( +//! peripherals.I2C1, +//! pins.gpio18.reconfigure(), // sda +//! pins.gpio19.reconfigure(), // scl +//! 400.kHz(), +//! &mut peripherals.RESETS, +//! 125_000_000.Hz(), +//! ); +//! +//! // Scan for devices on the bus by attempting to read from them +//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_Read; +//! for i in 0..=127u8 { +//! let mut readbuf: [u8; 1] = [0; 1]; +//! let result = i2c.read(i, &mut readbuf); +//! if let Ok(d) = result { +//! // Do whatever work you want to do with found devices +//! // writeln!(uart, "Device found at address{:?}", i).unwrap(); +//! } +//! } +//! +//! // Write some data to a device at 0x2c +//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_Write; +//! i2c.write(0x2Cu8, &[1, 2, 3]).unwrap(); +//! +//! // Write and then read from a device at 0x3a +//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_WriteRead; +//! let mut readbuf: [u8; 1] = [0; 1]; +//! i2c.write_read(0x2Cu8, &[1, 2, 3], &mut readbuf).unwrap(); +//! ``` +//! +//! See [examples/i2c.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/i2c.rs) +//! for a complete example +//! +//! ## Async Usage +//! +//! See [examples/i2c_async.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/i2c_async.rs) +//! and [examples/i2c_async_irq.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/i2c_async_irq.rs) +//! for complete examples. + +use core::{marker::PhantomData, ops::Deref}; +use fugit::HertzU32; +use rp235x_pac::i2c0::ic_con::IC_10BITADDR_SLAVE_A; + +use crate::{ + gpio::{bank0::*, pin::pin_sealed::TypeLevelPinId, AnyPin, FunctionI2c, PullUp}, + pac::{ + i2c0::{ic_con::IC_10BITADDR_MASTER_A, RegisterBlock}, + I2C0, I2C1, RESETS, + }, + resets::SubsystemReset, + typelevel::Sealed, +}; + +mod controller; +pub mod peripheral; + +/// Pac I2C device +pub trait I2cDevice: Deref + SubsystemReset + Sealed { + /// Index of the peripheral. + const ID: usize; +} +impl Sealed for I2C0 {} +impl I2cDevice for I2C0 { + const ID: usize = 0; +} +impl Sealed for I2C1 {} +impl I2cDevice for I2C1 { + const ID: usize = 1; +} + +/// Marks valid/supported address types +pub trait ValidAddress: + Into + embedded_hal::i2c::AddressMode + embedded_hal_0_2::blocking::i2c::AddressMode + Copy +{ + /// Variant for the IC_CON.10bitaddr_master field + const BIT_ADDR_M: IC_10BITADDR_MASTER_A; + /// Variant for the IC_CON.10bitaddr_slave field + const BIT_ADDR_S: IC_10BITADDR_SLAVE_A; + + /// Validates the address against address ranges supported by the hardware. + fn is_valid(self) -> Result<(), Error>; +} +impl ValidAddress for u8 { + const BIT_ADDR_M: IC_10BITADDR_MASTER_A = IC_10BITADDR_MASTER_A::ADDR_7BITS; + const BIT_ADDR_S: IC_10BITADDR_SLAVE_A = IC_10BITADDR_SLAVE_A::ADDR_7BITS; + + fn is_valid(self) -> Result<(), Error> { + if self >= 0x80 { + Err(Error::AddressOutOfRange(self.into())) + } else { + Ok(()) + } + } +} +impl ValidAddress for u16 { + const BIT_ADDR_M: IC_10BITADDR_MASTER_A = IC_10BITADDR_MASTER_A::ADDR_10BITS; + const BIT_ADDR_S: IC_10BITADDR_SLAVE_A = IC_10BITADDR_SLAVE_A::ADDR_10BITS; + + fn is_valid(self) -> Result<(), Error> { + Ok(()) + } +} + +/// I2C error +#[non_exhaustive] +pub enum Error { + /// I2C abort with error + Abort(u32), + /// User passed in a read buffer that was 0 length + /// + /// This is a limitation of the rp235x I2C peripheral. + /// If the slave ACKs its address, the I2C peripheral must read + /// at least one byte before sending the STOP condition. + InvalidReadBufferLength, + /// User passed in a write buffer that was 0 length + /// + /// This is a limitation of the rp235x I2C peripheral. + /// If the slave ACKs its address, the I2C peripheral must write + /// at least one byte before sending the STOP condition. + InvalidWriteBufferLength, + /// Target i2c address is out of range + AddressOutOfRange(u16), + /// Target i2c address is reserved + AddressReserved(u16), +} + +impl core::fmt::Debug for Error { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use embedded_hal::i2c::Error as _; + match self { + Error::InvalidReadBufferLength => write!(fmt, "InvalidReadBufferLength"), + Error::InvalidWriteBufferLength => write!(fmt, "InvalidWriteBufferLength"), + Error::AddressOutOfRange(addr) => write!(fmt, "AddressOutOfRange({:x})", addr), + Error::AddressReserved(addr) => write!(fmt, "AddressReserved({:x})", addr), + Error::Abort(_) => { + write!(fmt, "{:?}", self.kind()) + } + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Error { + fn format(&self, fmt: defmt::Formatter) { + use embedded_hal::i2c::Error as _; + match self { + Error::InvalidReadBufferLength => defmt::write!(fmt, "InvalidReadBufferLength"), + Error::InvalidWriteBufferLength => defmt::write!(fmt, "InvalidWriteBufferLength"), + Error::AddressOutOfRange(addr) => defmt::write!(fmt, "AddressOutOfRange({:x})", addr), + Error::AddressReserved(addr) => defmt::write!(fmt, "AddressReserved({:x})", addr), + Error::Abort(_) => { + defmt::write!(fmt, "{}", defmt::Debug2Format(&self.kind())) + } + } + } +} + +impl embedded_hal::i2c::Error for Error { + fn kind(&self) -> embedded_hal::i2c::ErrorKind { + match &self { + Error::Abort(v) if v & 1<<12 != 0 // ARB_LOST + => embedded_hal::i2c::ErrorKind::ArbitrationLoss, + Error::Abort(v) if v & 1<<7 != 0 // ABRT_SBYTE_ACKDET + => embedded_hal::i2c::ErrorKind::Bus, + Error::Abort(v) if v & 1<<6 != 0 // ABRT_HS_ACKDET + => embedded_hal::i2c::ErrorKind::Bus, + Error::Abort(v) if v & 1<<4 != 0 // ABRT_GCALL_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + Error::Abort(v) if v & 1<<3 != 0 // ABRT_TXDATA_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Data), + Error::Abort(v) if v & 1<<2 != 0 // ABRT_10ADDR2_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + Error::Abort(v) if v & 1<<1 != 0 // ABRT_10ADDR1_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + Error::Abort(v) if v & 1<<0 != 0 // ABRT_7B_ADDR_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + _ => embedded_hal::i2c::ErrorKind::Other, + } + } +} + +macro_rules! pin_validation { + ($p:ident) => { + paste::paste!{ + #[doc = "Marker for PinId that can serve as " $p] + pub trait []: Sealed {} + + #[doc = "Valid " $p] + pub trait []: Sealed {} + + impl [] for T + where + T: AnyPin, + T::Id: [], + { + } + + #[doc = "A runtime validated " $p " pin for I2C."] + pub struct [](P, PhantomData); + impl Sealed for [] {} + impl [] for [] {} + impl [] + where + P: AnyPin, + S: I2cDevice, + { + /// Validate a pin's function on a i2c peripheral. + /// + #[doc = "Will err if the pin cannot be used as a " $p " pin for that I2C."] + pub fn validate(p: P, _u: &S) -> Result { + if [<$p:upper>].contains(&(p.borrow().id().num, S::ID)) && + p.borrow().id().bank == crate::gpio::DynBankId::Bank0 { + Ok(Self(p, PhantomData)) + } else { + Err(p) + } + } + } + } + }; + ($($p:ident),*) => { + $( + pin_validation!($p); + )* + }; +} + +pin_validation!(Scl, Sda); + +macro_rules! valid_pins { + ($($i2c:ident: { + sda: [$($sda:ident),*], + scl: [$($scl:ident),*] + }),*) => { + $( + $(impl ValidPinIdSda<$i2c> for $sda {})* + $(impl ValidPinIdScl<$i2c> for $scl {})* + )* + + const SDA: &[(u8, usize)] = &[$($(($sda::ID.num, $i2c::ID)),*),*]; + const SCL: &[(u8, usize)] = &[$($(($scl::ID.num, $i2c::ID)),*),*]; + }; +} +valid_pins! { + I2C0: { + sda: [Gpio0, Gpio4, Gpio8, Gpio12, Gpio16, Gpio20, Gpio24, Gpio28], + scl: [Gpio1, Gpio5, Gpio9, Gpio13, Gpio17, Gpio21, Gpio25, Gpio29] + }, + I2C1: { + sda: [Gpio2, Gpio6, Gpio10, Gpio14, Gpio18, Gpio22, Gpio26], + scl: [Gpio3, Gpio7, Gpio11, Gpio15, Gpio19, Gpio23, Gpio27] + } +} + +/// Operational mode of the I2C peripheral. +pub trait I2CMode: Sealed { + /// Indicates whether this mode is Controller or Peripheral. + const IS_CONTROLLER: bool; +} + +/// Marker for an I2C peripheral operating as a controller. +pub struct Controller {} +impl Sealed for Controller {} +impl I2CMode for Controller { + const IS_CONTROLLER: bool = true; +} + +/// Marker for an I2C block operating as a peripehral. +pub struct Peripheral { + state: peripheral::State, +} +impl Sealed for Peripheral {} +impl I2CMode for Peripheral { + const IS_CONTROLLER: bool = false; +} + +/// I2C peripheral +pub struct I2C { + i2c: I2C, + pins: Pins, + mode: Mode, +} + +impl I2C +where + Block: SubsystemReset + Deref, +{ + /// Releases the I2C peripheral and associated pins + #[allow(clippy::type_complexity)] + pub fn free(self, resets: &mut RESETS) -> (Block, (Sda, Scl)) { + self.i2c.reset_bring_down(resets); + + (self.i2c, self.pins) + } +} + +impl, PINS, Mode> I2C { + /// Depth of the TX FIFO. + pub const TX_FIFO_DEPTH: u8 = 16; + + /// Depth of the RX FIFO. + pub const RX_FIFO_DEPTH: u8 = 16; + + /// Number of bytes currently in the RX FIFO + #[inline] + pub fn rx_fifo_used(&self) -> u8 { + self.i2c.ic_rxflr().read().rxflr().bits() + } + + /// Remaining capacity in the RX FIFO + #[inline] + pub fn rx_fifo_available(&self) -> u8 { + Self::RX_FIFO_DEPTH - self.rx_fifo_used() + } + + /// RX FIFO is empty + #[inline] + pub fn rx_fifo_empty(&self) -> bool { + self.i2c.ic_status().read().rfne().bit_is_clear() + } + + /// Number of bytes currently in the TX FIFO + #[inline] + pub fn tx_fifo_used(&self) -> u8 { + self.i2c.ic_txflr().read().txflr().bits() + } + + /// Remaining capacity in the TX FIFO + #[inline] + pub fn tx_fifo_available(&self) -> u8 { + Self::TX_FIFO_DEPTH - self.tx_fifo_used() + } + + /// TX FIFO is at capacity + #[inline] + pub fn tx_fifo_full(&self) -> bool { + self.i2c.ic_status().read().tfnf().bit_is_clear() + } +} + +macro_rules! hal { + ($($I2CX:ident: ($i2cX:ident),)+) => { + $( + impl I2C<$I2CX, (Sda, Scl)> { + /// Configures the I2C peripheral to work in master mode + pub fn $i2cX( + i2c: $I2CX, + sda_pin: Sda, + scl_pin: Scl, + freq: F, + resets: &mut RESETS, + system_clock: SystemF) -> Self + where + F: Into, + Sda: ValidPinSda<$I2CX> + AnyPin, + Scl: ValidPinScl<$I2CX> + AnyPin, + SystemF: Into, + { + Self::new_controller(i2c, sda_pin, scl_pin, freq.into(), resets, system_clock.into()) + } + + $crate::paste::paste! { + /// Configures the I2C peripheral to work in master mode + /// + /// This function can be called without activating internal pull-ups on the I2C pins. + /// It should only be used if external pull-ups are provided. + pub fn [<$i2cX _with_external_pull_up>]( + i2c: $I2CX, + sda_pin: Sda, + scl_pin: Scl, + freq: F, + resets: &mut RESETS, + system_clock: SystemF) -> Self + where + F: Into, + Sda: ValidPinSda<$I2CX>, + Scl: ValidPinScl<$I2CX>, + SystemF: Into, + { + Self::new_controller(i2c, sda_pin, scl_pin, freq.into(), resets, system_clock.into()) + } + } + } + + impl $crate::async_utils::sealed::Wakeable for I2C<$I2CX, P, M> { + fn waker() -> &'static $crate::async_utils::sealed::IrqWaker { + static WAKER: $crate::async_utils::sealed::IrqWaker = + $crate::async_utils::sealed::IrqWaker::new(); + &WAKER + } + } + )+ + } +} +hal! { + I2C0: (i2c0), + I2C1: (i2c1), +} diff --git a/rp-hal/rp235x-hal/src/i2c/controller.rs b/rp-hal/rp235x-hal/src/i2c/controller.rs new file mode 100644 index 0000000..bc244ab --- /dev/null +++ b/rp-hal/rp235x-hal/src/i2c/controller.rs @@ -0,0 +1,499 @@ +use core::{ops::Deref, task::Poll}; +use embedded_hal_0_2::blocking::i2c::{Read, Write, WriteIter, WriteIterRead, WriteRead}; +use fugit::HertzU32; + +use embedded_hal::i2c as eh1; + +use crate::{ + i2c::{Controller, Error, ValidAddress, ValidPinScl, ValidPinSda, I2C}, + pac::{i2c0::RegisterBlock as Block, RESETS}, + resets::SubsystemReset, +}; + +pub(crate) mod non_blocking; + +impl I2C +where + T: SubsystemReset + Deref, + Sda: ValidPinSda, + Scl: ValidPinScl, +{ + /// Configures the I2C peripheral to work in controller mode + pub fn new_controller( + i2c: T, + sda_pin: Sda, + scl_pin: Scl, + freq: HertzU32, + resets: &mut RESETS, + system_clock: HertzU32, + ) -> Self { + let freq = freq.to_Hz(); + assert!(freq <= 1_000_000); + assert!(freq > 0); + + i2c.reset_bring_down(resets); + i2c.reset_bring_up(resets); + + i2c.ic_enable().write(|w| w.enable().disabled()); + + // select controller mode & speed + i2c.ic_con().modify(|_, w| { + w.speed().fast(); + w.master_mode().enabled(); + w.ic_slave_disable().slave_disabled(); + w.ic_restart_en().enabled(); + w.tx_empty_ctrl().enabled() + }); + + // Clear FIFO threshold + i2c.ic_tx_tl().write(|w| unsafe { w.tx_tl().bits(0) }); + i2c.ic_rx_tl().write(|w| unsafe { w.rx_tl().bits(0) }); + + let freq_in = system_clock.to_Hz(); + + // There are some subtleties to I2C timing which we are completely ignoring here + // See: https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_i2c/i2c.c#L69 + let period = (freq_in + freq / 2) / freq; + let lcnt = period * 3 / 5; // spend 3/5 (60%) of the period low + let hcnt = period - lcnt; // and 2/5 (40%) of the period high + + // Check for out-of-range divisors: + assert!(hcnt <= 0xffff); + assert!(lcnt <= 0xffff); + assert!(hcnt >= 8); + assert!(lcnt >= 8); + + // Per I2C-bus specification a device in standard or fast mode must + // internally provide a hold time of at least 300ns for the SDA signal to + // bridge the undefined region of the falling edge of SCL. A smaller hold + // time of 120ns is used for fast mode plus. + let sda_tx_hold_count = if freq < 1000000 { + // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns) + // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + ((freq_in * 3) / 10000000) + 1 + } else { + // fast mode plus requires a clk_in > 32MHz + assert!(freq_in >= 32_000_000); + + // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns) + // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + ((freq_in * 3) / 25000000) + 1 + }; + assert!(sda_tx_hold_count <= lcnt - 2); + + unsafe { + i2c.ic_fs_scl_hcnt() + .write(|w| w.ic_fs_scl_hcnt().bits(hcnt as u16)); + i2c.ic_fs_scl_lcnt() + .write(|w| w.ic_fs_scl_lcnt().bits(lcnt as u16)); + // spike filter duration + i2c.ic_fs_spklen().write(|w| { + let ticks = if lcnt < 16 { 1 } else { (lcnt / 16) as u8 }; + w.ic_fs_spklen().bits(ticks) + }); + // sda hold time + i2c.ic_sda_hold() + .modify(|_r, w| w.ic_sda_tx_hold().bits(sda_tx_hold_count as u16)); + + // make TX_EMPTY raise when the tx fifo is not full + i2c.ic_tx_tl() + .write(|w| w.tx_tl().bits(Self::TX_FIFO_DEPTH)); + // make RX_FULL raise when the rx fifo contains at least 1 byte + i2c.ic_rx_tl().write(|w| w.rx_tl().bits(0)); + // Enable clock stretching. + // Will hold clock when: + // - receiving and rx fifo is full + // - writing and tx fifo is empty + i2c.ic_con() + .modify(|_, w| w.rx_fifo_full_hld_ctrl().enabled()); + } + + // Enable I2C block + i2c.ic_enable().write(|w| w.enable().enabled()); + + Self { + i2c, + pins: (sda_pin, scl_pin), + mode: Controller {}, + } + } +} + +impl, PINS> I2C { + fn validate_buffer( + &mut self, + first: bool, + buf: &mut core::iter::Peekable, + err: Error, + ) -> Result<(), Error> + where + U: Iterator, + { + if buf.peek().is_some() { + Ok(()) + } else { + if !first { + self.abort(); + } + Err(err) + } + } + + fn setup(&mut self, addr: A) -> Result<(), Error> { + addr.is_valid()?; + + self.i2c.ic_enable().write(|w| w.enable().disabled()); + self.i2c + .ic_con() + .modify(|_, w| w.ic_10bitaddr_master().variant(A::BIT_ADDR_M)); + + let addr = addr.into(); + self.i2c + .ic_tar() + .write(|w| unsafe { w.ic_tar().bits(addr) }); + self.i2c.ic_enable().write(|w| w.enable().enabled()); + Ok(()) + } + + #[inline] + fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> { + let abort_reason = self.i2c.ic_tx_abrt_source().read().bits(); + if abort_reason != 0 { + // Note clearing the abort flag also clears the reason, and + // this instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + self.i2c.ic_clr_tx_abrt().read(); + Err(Error::Abort(abort_reason)) + } else { + Ok(()) + } + } + + #[inline] + fn poll_tx_not_full(&mut self) -> Poll> { + self.read_and_clear_abort_reason()?; + if !self.tx_fifo_full() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + + #[inline] + fn poll_tx_empty(&mut self) -> Poll<()> { + if self.i2c.ic_raw_intr_stat().read().tx_empty().is_inactive() { + Poll::Pending + } else { + Poll::Ready(()) + } + } + + #[inline] + fn poll_stop_detected(&mut self) -> Poll<()> { + if self.i2c.ic_raw_intr_stat().read().stop_det().is_inactive() { + Poll::Pending + } else { + Poll::Ready(()) + } + } + + #[inline] + fn abort(&mut self) { + self.i2c.ic_enable().modify(|_, w| w.abort().set_bit()); + while self.i2c.ic_enable().read().abort().bit_is_set() { + crate::arch::nop() + } + while self.i2c.ic_raw_intr_stat().read().tx_abrt().bit_is_clear() { + crate::arch::nop() + } + // clear tx_abort interrupt flags (might have already been clear by irq) + self.i2c.ic_clr_tx_abrt().read(); + // clear tx_abrt_source by reading it + self.i2c.ic_tx_abrt_source().read(); + } + + fn read_internal( + &mut self, + first_transaction: bool, + buffer: &mut [u8], + do_stop: bool, + ) -> Result<(), Error> { + self.validate_buffer( + first_transaction, + &mut buffer.iter().peekable(), + Error::InvalidReadBufferLength, + )?; + + let lastindex = buffer.len() - 1; + let mut first_byte = true; + for (i, byte) in buffer.iter_mut().enumerate() { + let last_byte = i == lastindex; + + // wait until there is space in the FIFO to write the next byte + while self.i2c.ic_status().read().tfnf().bit_is_clear() {} + + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + + w.stop().bit(do_stop && last_byte); + w.cmd().read() + }); + + while self.i2c.ic_rxflr().read().bits() == 0 { + self.read_and_clear_abort_reason()?; + } + + *byte = self.i2c.ic_data_cmd().read().dat().bits(); + } + + Ok(()) + } + + fn write_internal( + &mut self, + first_transaction: bool, + bytes: impl IntoIterator, + do_stop: bool, + ) -> Result<(), Error> { + let mut peekable = bytes.into_iter().peekable(); + self.validate_buffer( + first_transaction, + &mut peekable, + Error::InvalidWriteBufferLength, + )?; + + let mut abort_reason = Ok(()); + let mut first_byte = true; + 'outer: while let Some(byte) = peekable.next() { + if self.tx_fifo_full() { + // wait for more room in the fifo + loop { + match self.poll_tx_not_full() { + Poll::Pending => continue, + Poll::Ready(Ok(())) => break, + Poll::Ready(r) => { + abort_reason = r; + break 'outer; + } + } + } + } + + // else enqueue + let last = peekable.peek().is_none(); + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + w.stop().bit(do_stop && last); + unsafe { w.dat().bits(byte) } + }); + } + + if abort_reason.is_err() { + // Wait until the transmission of the address/data from the internal + // shift register has completed. + while self.poll_tx_empty().is_pending() {} + abort_reason = self.read_and_clear_abort_reason(); + } + + if abort_reason.is_err() || do_stop { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occurred. + while self.poll_stop_detected().is_pending() {} + self.i2c.ic_clr_stop_det().read().clr_stop_det(); + } + // Note: the hardware issues a STOP automatically on an abort condition. + // Note: the hardware also clears RX FIFO as well as TX on abort + + abort_reason + } +} + +impl, PINS> I2C { + /// Writes bytes to slave with address `address` + /// + /// # I2C Events (contract) + /// + /// Same as the `write` method + pub fn write_iter(&mut self, address: A, bytes: B) -> Result<(), Error> + where + B: IntoIterator, + { + self.setup(address)?; + self.write_internal(true, bytes, true) + } + + /// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a + /// single transaction* + /// + /// # I2C Events (contract) + /// + /// Same as the `write_read` method + pub fn write_iter_read( + &mut self, + address: A, + bytes: B, + buffer: &mut [u8], + ) -> Result<(), Error> + where + B: IntoIterator, + { + self.setup(address)?; + + self.write_internal(true, bytes, false)?; + self.read_internal(false, buffer, true) + } + + /// Execute the provided operations on the I2C bus (iterator version). + /// + /// Transaction contract: + /// - Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate. + /// - Data from adjacent operations of the same type are sent after each other without an SP or SR. + /// - Between adjacent operations of a different type an SR and SAD+R/W is sent. + /// - After executing the last operation an SP is sent automatically. + /// - If the last operation is a `Read` the master does not send an acknowledge for the last byte. + /// + /// - `ST` = start condition + /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing + /// - `SR` = repeated start condition + /// - `SP` = stop condition + fn transaction<'op: 'iter, 'iter, A: ValidAddress>( + &mut self, + address: A, + operations: impl IntoIterator>, + ) -> Result<(), Error> { + self.setup(address)?; + + let mut first = true; + let mut operations = operations.into_iter().peekable(); + while let Some(operation) = operations.next() { + let last = operations.peek().is_none(); + match operation { + eh1::Operation::Read(buf) => self.read_internal(first, buf, last)?, + eh1::Operation::Write(buf) => { + self.write_internal(first, buf.iter().cloned(), last)? + } + } + first = false; + } + Ok(()) + } + + #[cfg(feature = "i2c-write-iter")] + fn transaction_iter<'op, A, O, B>(&mut self, address: A, operations: O) -> Result<(), Error> + where + A: ValidAddress, + O: IntoIterator>, + B: IntoIterator, + { + use i2c_write_iter::Operation; + self.setup(address)?; + + let mut first = true; + let mut operations = operations.into_iter().peekable(); + while let Some(operation) = operations.next() { + let last = operations.peek().is_none(); + match operation { + Operation::Read(buf) => self.read_internal(first, buf, last)?, + Operation::WriteIter(buf) => self.write_internal(first, buf, last)?, + } + first = false; + } + Ok(()) + } +} + +impl, PINS> Read for I2C { + type Error = Error; + + fn read(&mut self, addr: A, buffer: &mut [u8]) -> Result<(), Error> { + self.setup(addr)?; + self.read_internal(true, buffer, true) + } +} +impl, PINS> WriteRead for I2C { + type Error = Error; + + fn write_read(&mut self, addr: A, tx: &[u8], rx: &mut [u8]) -> Result<(), Error> { + self.setup(addr)?; + + self.write_internal(true, tx.iter().cloned(), false)?; + self.read_internal(false, rx, true) + } +} + +impl, PINS> Write for I2C { + type Error = Error; + + fn write(&mut self, addr: A, tx: &[u8]) -> Result<(), Error> { + self.setup(addr)?; + self.write_internal(true, tx.iter().cloned(), true) + } +} + +impl, PINS> WriteIter for I2C { + type Error = Error; + + fn write(&mut self, address: A, bytes: B) -> Result<(), Self::Error> + where + B: IntoIterator, + { + self.write_iter(address, bytes) + } +} + +impl, PINS> WriteIterRead + for I2C +{ + type Error = Error; + + fn write_iter_read( + &mut self, + address: A, + bytes: B, + buffer: &mut [u8], + ) -> Result<(), Self::Error> + where + B: IntoIterator, + { + self.write_iter_read(address, bytes, buffer) + } +} + +impl, PINS> eh1::ErrorType for I2C { + type Error = Error; +} + +impl, PINS> eh1::I2c for I2C { + fn transaction( + &mut self, + address: A, + operations: &mut [eh1::Operation<'_>], + ) -> Result<(), Self::Error> { + self.transaction(address, operations.iter_mut()) + } +} + +#[cfg(feature = "i2c-write-iter")] +impl, PINS> + i2c_write_iter::I2cIter for I2C +{ + fn transaction_iter<'a, O, B>(&mut self, address: A, operations: O) -> Result<(), Self::Error> + where + O: IntoIterator>, + B: IntoIterator, + { + self.transaction_iter(address, operations) + } +} diff --git a/rp-hal/rp235x-hal/src/i2c/controller/non_blocking.rs b/rp-hal/rp235x-hal/src/i2c/controller/non_blocking.rs new file mode 100644 index 0000000..b3bd693 --- /dev/null +++ b/rp-hal/rp235x-hal/src/i2c/controller/non_blocking.rs @@ -0,0 +1,347 @@ +use core::{ops::Deref, task::Poll}; +use embedded_hal_async::i2c::{AddressMode, Operation}; + +use crate::{ + async_utils::{sealed::Wakeable, AsyncPeripheral, CancellablePollFn as CPFn}, + i2c::{Controller, Error, ValidAddress, I2C}, + pac::i2c0::RegisterBlock, +}; + +macro_rules! impl_async_traits { + ($i2c:path) => { + impl

AsyncPeripheral for I2C<$i2c, P, Controller> + where + Self: $crate::async_utils::sealed::Wakeable, + { + fn on_interrupt() { + unsafe { + // This is equivalent to stealing from pac::Peripherals + let i2c = &*<$i2c>::ptr(); + + // Mask all interrupt flags. This does not clear the flags. + // Clearing is done by the driver after it wakes up. + i2c.ic_intr_mask().write_with_zero(|w| w); + } + // interrupts are now masked, we can wake the task and return from this handler. + Self::waker().wake(); + } + } + }; +} + +impl_async_traits!(rp235x_pac::I2C0); +impl_async_traits!(rp235x_pac::I2C1); + +enum TxEmptyConfig { + Empty, + NotFull, +} + +impl I2C +where + T: Deref, + Self: AsyncPeripheral, +{ + /// `tx_empty`: true to unmask tx_empty + #[inline] + fn unmask_intr(&mut self, tx_empty: bool) { + unsafe { + self.i2c.ic_intr_mask().write_with_zero(|w| { + w.m_tx_empty() + .bit(tx_empty) + .m_rx_full() + .disabled() + .m_tx_abrt() + .disabled() + .m_stop_det() + .disabled() + }); + } + } + #[inline] + fn configure_tx_empty(&mut self, cfg: TxEmptyConfig) { + self.i2c + .ic_tx_tl() + // SAFETY: we are within [0; TX_FIFO_DEPTH) + .write(|w| unsafe { + w.tx_tl().bits(match cfg { + TxEmptyConfig::Empty => 1, + TxEmptyConfig::NotFull => Self::TX_FIFO_DEPTH - 1, + }) + }); + } + + #[inline] + fn unmask_tx_empty(&mut self) { + self.configure_tx_empty(TxEmptyConfig::Empty); + self.unmask_intr(true) + } + + #[inline] + fn unmask_tx_not_full(&mut self) { + self.configure_tx_empty(TxEmptyConfig::NotFull); + self.unmask_intr(true) + } + + #[inline] + fn unmask_stop_det(&mut self) { + self.unmask_intr(false); + } + + #[inline] + fn poll_rx_not_empty_or_abrt(&mut self) -> Poll> { + self.read_and_clear_abort_reason()?; + if self.i2c.ic_raw_intr_stat().read().rx_full().bit_is_set() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + + #[inline] + fn cancel(&mut self) { + unsafe { + self.i2c.ic_intr_mask().write_with_zero(|w| w); + } + + self.abort(); + } + + async fn non_blocking_read_internal( + &mut self, + first_transaction: bool, + buffer: &mut [u8], + do_stop: bool, + ) -> Result<(), Error> { + self.validate_buffer( + first_transaction, + &mut buffer.iter().peekable(), + Error::InvalidReadBufferLength, + )?; + + let lastindex = buffer.len() - 1; + let mut first_byte = true; + for (i, byte) in buffer.iter_mut().enumerate() { + let last_byte = i == lastindex; + + // wait until there is space in the FIFO to write the next byte + // cannot abort during read, so ignore the result + let _ = CPFn::new( + self, + Self::poll_tx_not_full, + Self::unmask_tx_not_full, + Self::cancel, + ) + .await; + + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + + w.stop().bit(do_stop && last_byte); + w.cmd().read() + }); + + CPFn::new( + self, + Self::poll_rx_not_empty_or_abrt, + Self::unmask_tx_empty, + Self::cancel, + ) + .await?; + + *byte = self.i2c.ic_data_cmd().read().dat().bits(); + } + + Ok(()) + } + + async fn non_blocking_write_internal( + &mut self, + first_transaction: bool, + bytes: impl IntoIterator, + do_stop: bool, + ) -> Result<(), Error> { + let mut peekable = bytes.into_iter().peekable(); + self.validate_buffer( + first_transaction, + &mut peekable, + Error::InvalidWriteBufferLength, + )?; + + let mut abort_reason = Ok(()); + let mut first_byte = true; + while let Some(byte) = peekable.next() { + if self.tx_fifo_full() { + // wait for more room in the fifo + abort_reason = CPFn::new( + self, + Self::poll_tx_not_full, + Self::unmask_tx_not_full, + Self::cancel, + ) + .await; + if abort_reason.is_err() { + break; + } + } + + // else enqueue + let last = peekable.peek().is_none(); + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + w.stop().bit(do_stop && last); + unsafe { w.dat().bits(byte) } + }); + } + + if abort_reason.is_err() { + // Wait until the transmission of the address/data from the internal + // shift register has completed. + CPFn::new( + self, + Self::poll_tx_empty, + Self::unmask_tx_empty, + Self::cancel, + ) + .await; + abort_reason = self.read_and_clear_abort_reason(); + } + + if abort_reason.is_err() || do_stop { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occurred. + CPFn::new( + self, + Self::poll_stop_detected, + Self::unmask_stop_det, + Self::cancel, + ) + .await; + self.i2c.ic_clr_stop_det().read().clr_stop_det(); + } + // Note: the hardware issues a STOP automatically on an abort condition. + // Note: the hardware also clears RX FIFO as well as TX on abort + + abort_reason + } + + /// Writes to the i2c bus consuming bytes for the given iterator. + pub async fn write_iter_async(&mut self, address: A, bytes: U) -> Result<(), super::Error> + where + U: IntoIterator, + A: ValidAddress, + { + self.setup(address)?; + self.non_blocking_write_internal(true, bytes, true).await + } + + /// Writes to the i2c bus consuming bytes for the given iterator. + pub async fn write_iter_read_async( + &mut self, + address: A, + bytes: U, + read: &mut [u8], + ) -> Result<(), Error> + where + U: IntoIterator, + A: ValidAddress, + { + self.setup(address)?; + self.non_blocking_write_internal(true, bytes, false).await?; + self.non_blocking_read_internal(false, read, true).await + } + + /// Writes to the i2c bus taking operations from and iterator, writing from iterator of bytes, + /// reading to slices of bytes. + #[cfg(feature = "i2c-write-iter")] + pub async fn transaction_iter_async<'b, A, O, B>( + &mut self, + address: A, + operations: O, + ) -> Result<(), super::Error> + where + A: ValidAddress, + O: IntoIterator>, + B: IntoIterator, + { + self.setup(address)?; + + let mut first = true; + let mut operations = operations.into_iter().peekable(); + while let Some(operation) = operations.next() { + let last = operations.peek().is_none(); + match operation { + i2c_write_iter::Operation::Read(buf) => { + self.non_blocking_read_internal(first, buf, last).await? + } + i2c_write_iter::Operation::WriteIter(buf) => { + self.non_blocking_write_internal(first, buf, last).await? + } + } + first = false; + } + Ok(()) + } +} + +impl embedded_hal_async::i2c::I2c for I2C +where + Self: AsyncPeripheral, + A: ValidAddress + AddressMode, + T: Deref, +{ + async fn transaction( + &mut self, + addr: A, + operations: &mut [Operation<'_>], + ) -> Result<(), Error> { + self.setup(addr)?; + + let mut first = true; + let mut operations = operations.iter_mut().peekable(); + while let Some(op) = operations.next() { + let last = operations.peek().is_none(); + match op { + Operation::Read(buffer) => { + self.non_blocking_read_internal(first, buffer, last).await?; + } + Operation::Write(buffer) => { + self.non_blocking_write_internal(first, buffer.iter().cloned(), last) + .await?; + } + } + first = false; + } + Ok(()) + } +} + +#[cfg(feature = "i2c-write-iter")] +impl i2c_write_iter::non_blocking::I2cIter for I2C +where + Self: AsyncPeripheral, + A: 'static + ValidAddress + AddressMode, + T: Deref, +{ + async fn transaction_iter<'a, O, B>( + &mut self, + address: A, + operations: O, + ) -> Result<(), Self::Error> + where + O: IntoIterator>, + B: IntoIterator, + { + self.transaction_iter_async(address, operations).await + } +} diff --git a/rp-hal/rp235x-hal/src/i2c/peripheral.rs b/rp-hal/rp235x-hal/src/i2c/peripheral.rs new file mode 100644 index 0000000..27c8450 --- /dev/null +++ b/rp-hal/rp235x-hal/src/i2c/peripheral.rs @@ -0,0 +1,320 @@ +//! # I2C Peripheral (slave) implementation +//! +//! The RP2350 I2C block can behave as a peripheral node on an I2C bus. +//! +//! In order to handle peripheral transactions this driver exposes an iterator streaming I2C event +//! that the usercode must handle to properly handle the I2C communitation. See [`Event`] for a +//! list of events to handle. +//! +//! Although [`Start`](Event::Start), [`Restart`](Event::Restart) and [`Stop`](Event::Stop) +//! events may not require any action on the device, [`TransferRead`](Event::TransferRead) and +//! [`TransferWrite`](Event::TransferWrite) require some action: +//! +//! - [`TransferRead`](Event::TransferRead): A controller is attempting to read from this peripheral. +//! The I2C block holds the SCL line low (clock stretching) until data is pushed to the transmission +//! FIFO by the user application using [`write`](I2C::write). +//! Data remaining in the FIFO when the bus constroller stops the transfer are ignored & the fifo +//! is flushed. +//! - [`TransferWrite`](Event::TransferWrite): A controller is sending data to this peripheral. +//! The I2C block holds the SCL line (clock stretching) until there is room for more data in the +//! Rx FIFO using [`read`](I2C::read). +//! Data are automatically acknowledged by the I2C block and it is not possible to NACK incoming +//! data coming to the RP2350. +//! +//! ## Warning +//! +//! `Start`, `Restart` and `Stop` events may not be reported before or after a write operations. +//! This is because several write operation may take place and complete before the core has time to +//! react. All the data sent will be stored in the peripheral FIFO but it will not be possible to +//! identify between which bytes should the start/restart/stop events precicely took place. +//! +//! Because a Read operation will always cause a pause waiting for the firmware's input, a `Start` +//! (or `Restart` if the peripheral is already active) will always be reported. However, this does +//! not mean no other event occurred in the mean time. +//! +//! For example, let's consider the following sequence: +//! +//! `Start, Write, Stop, Start, Write, Restart, Read, Stop.` +//! +//! Depending on the firmware's and bus' speed, this driver may only report: +//! - `Start, Write, Restart, Read, Stop` + +use core::{ops::Deref, task::Poll}; + +use embedded_hal::i2c::AddressMode; + +use super::{Peripheral, ValidAddress, ValidPinScl, ValidPinSda, I2C}; +use crate::{ + async_utils::{sealed::Wakeable, AsyncPeripheral, CancellablePollFn}, + pac::{i2c0::RegisterBlock, RESETS}, + resets::SubsystemReset, +}; + +/// I2C bus events +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Event { + /// Start condition has been detected. + Start, + /// Restart condition has been detected. + Restart, + /// The controller requests data. + TransferRead, + /// The controller sends data. + TransferWrite, + /// Stop condition detected. + Stop, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum State { + Idle, + Active, + Read, + Write, +} + +impl I2C +where + T: SubsystemReset + Deref, + Sda: ValidPinSda, + Scl: ValidPinScl, +{ + /// Configures the I2C peripheral to work in peripheral mode + /// + /// The bus *MUST* be idle when this method is called. + #[allow(clippy::type_complexity)] + pub fn new_peripheral_event_iterator( + i2c: T, + sda_pin: Sda, + scl_pin: Scl, + resets: &mut RESETS, + addr: A, + ) -> Self { + i2c.reset_bring_down(resets); + i2c.reset_bring_up(resets); + + i2c.ic_enable().write(|w| w.enable().disabled()); + + // TODO: Validate address? + let addr = addr.into(); + // SAFETY: Only address by this function. IC_SAR spec filters out bits 15:10. + // Any value is valid for the controller. They may not be for the bus itself though. + i2c.ic_sar().write(|w| unsafe { w.ic_sar().bits(addr) }); + // select peripheral mode & speed + i2c.ic_con().modify(|_, w| { + // run in fast mode + w.speed().fast(); + // setup slave mode + w.master_mode().disabled(); + w.ic_slave_disable().slave_enabled(); + // hold scl when fifo's full + w.rx_fifo_full_hld_ctrl().enabled(); + w.ic_restart_en().enabled(); + w.ic_10bitaddr_slave().variant(A::BIT_ADDR_S); + w + }); + + // Clear FIFO threshold + // SAFETY: Only address by this function. The field is 8bit long. 0 is a valid value. + i2c.ic_tx_tl().write(|w| unsafe { w.tx_tl().bits(0) }); + i2c.ic_rx_tl().write(|w| unsafe { w.rx_tl().bits(0) }); + + i2c.ic_clr_intr().read(); + + let mut me = Self { + i2c, + pins: (sda_pin, scl_pin), + mode: Peripheral { state: State::Idle }, + }; + me.unmask_intr(); + // Enable I2C block + me.i2c.ic_enable().write(|w| w.enable().enabled()); + + me + } +} + +fn unmask_intr(i2c: &RegisterBlock) { + // SAFETY: 0 is a valid value meaning all irq masked. + // This operation is atomic, `write_with_zero` only writes to the register. + unsafe { + i2c.ic_intr_mask().write_with_zero(|w| { + // Only keep these IRQ enabled. + w.m_start_det() + .disabled() + .m_rd_req() + .disabled() + .m_rx_full() + .disabled() + .m_stop_det() + .disabled() + }); + } +} + +/// SAFETY: Takes a non-mutable reference to RegisterBlock but mutates its `ic_intr_mask` register. +unsafe fn mask_intr(i2c: &RegisterBlock) { + // 0 is a valid value and means all flag masked. + unsafe { i2c.ic_intr_mask().write_with_zero(|w| w) } +} + +impl, PINS> I2C { + fn unmask_intr(&mut self) { + unmask_intr(&self.i2c) + } + fn mask_intr(&mut self) { + // SAFETY: We are the only owner of this register block. + unsafe { mask_intr(&self.i2c) } + } + + /// Push up to `usize::min(TX_FIFO_SIZE, buf.len())` bytes to the TX FIFO. + /// Returns the number of bytes pushed to the FIFO. Note this does *not* reflect how many bytes + /// are effectively received by the controller. + pub fn write(&mut self, buf: &[u8]) -> usize { + // just in case, clears previous tx abort. + self.i2c.ic_clr_tx_abrt().read(); + + let mut sent = 0; + for &b in buf.iter() { + if self.tx_fifo_full() { + break; + } + + // SAFETY: dat field is 8bits long. All values are valid. + self.i2c.ic_data_cmd().write(|w| unsafe { w.dat().bits(b) }); + sent += 1; + } + // serve a pending read request + self.i2c.ic_clr_rd_req().read(); + sent + } + + /// Pull up to `usize::min(RX_FIFO_SIZE, buf.len())` bytes from the RX FIFO. + pub fn read(&mut self, buf: &mut [u8]) -> usize { + buf.iter_mut().zip(self).map(|(b, r)| *b = r).count() + } +} + +/// This allows I2C to be used with `core::iter::Extend`. +impl, PINS> Iterator for I2C { + type Item = u8; + + fn next(&mut self) -> Option { + if self.rx_fifo_empty() { + None + } else { + Some(self.i2c.ic_data_cmd().read().dat().bits()) + } + } +} +impl, PINS> I2C { + /// Returns the next i2c event if any. + pub fn next_event(&mut self) -> Option { + let stat = self.i2c.ic_raw_intr_stat().read(); + + match self.mode.state { + State::Idle if stat.start_det().bit_is_set() => { + self.i2c.ic_clr_start_det().read(); + self.mode.state = State::Active; + Some(Event::Start) + } + State::Active if stat.rd_req().bit_is_set() => { + // Clearing `rd_req` is used by the hardware to detect when the I2C block can stop + // stretching the clock and start process the data pushed to the FIFO (if any). + // This is done in `Self::write`. + self.mode.state = State::Read; + // If stop_det is set at this point we know that it is related to a previous request, + // It cannot be due the end of the current request as SCL is held low while waiting + // for user input. + if stat.stop_det().bit_is_set() { + self.i2c.ic_clr_stop_det().read(); + } + Some(Event::TransferRead) + } + State::Active if !self.rx_fifo_empty() => { + self.mode.state = State::Write; + Some(Event::TransferWrite) + } + + State::Read if stat.rd_req().bit_is_set() => Some(Event::TransferRead), + State::Write if !self.rx_fifo_empty() => Some(Event::TransferWrite), + + State::Read | State::Write if stat.restart_det().bit_is_set() => { + self.i2c.ic_clr_restart_det().read(); + self.i2c.ic_clr_start_det().read(); + self.mode.state = State::Active; + Some(Event::Restart) + } + + _ if stat.stop_det().bit_is_set() => { + self.i2c.ic_clr_stop_det().read(); + self.i2c.ic_clr_tx_abrt().read(); + self.mode.state = State::Idle; + Some(Event::Stop) + } + + _ => None, + } + } +} + +macro_rules! impl_wakeable { + ($i2c:ty) => { + impl AsyncPeripheral for I2C<$i2c, PINS, Peripheral> + where + I2C<$i2c, PINS, Peripheral>: $crate::async_utils::sealed::Wakeable, + { + /// Wakes an async task (if any) & masks irqs + fn on_interrupt() { + unsafe { + // This is equivalent to stealing from pac::Peripherals + let i2c = &*<$i2c>::ptr(); + + mask_intr(i2c); + } + + // interrupts are now masked, we can wake the task and return from this handler. + Self::waker().wake(); + } + } + }; +} +impl_wakeable!(rp235x_pac::I2C0); +impl_wakeable!(rp235x_pac::I2C1); + +impl I2C +where + I2C: AsyncPeripheral, + T: Deref, +{ + /// Asynchronously waits for an Event. + pub async fn wait_next(&mut self) -> Event { + loop { + if let Some(evt) = self.next_event() { + return evt; + } + + CancellablePollFn::new( + self, + |me| { + let stat = me.i2c.ic_raw_intr_stat().read(); + if stat.start_det().bit_is_set() + || stat.restart_det().bit_is_set() + || stat.stop_det().bit_is_set() + || stat.rd_req().bit_is_set() + || stat.rx_full().bit_is_set() + { + Poll::Ready(()) + } else { + Poll::Pending + } + }, + Self::unmask_intr, + Self::mask_intr, + ) + .await; + } + } +} diff --git a/rp-hal/rp235x-hal/src/lib.rs b/rp-hal/rp235x-hal/src/lib.rs new file mode 100644 index 0000000..788741b --- /dev/null +++ b/rp-hal/rp235x-hal/src/lib.rs @@ -0,0 +1,169 @@ +//! HAL for the Raspberry Pi RP235x microcontrollers +//! +//! This is an implementation of the [`embedded-hal`](https://crates.io/crates/embedded-hal) +//! traits for the RP235x microcontrollers +//! +//! NOTE This HAL is still under active development. This API will remain volatile until 1.0.0 +//! +//! # Crate features +//! +//! * **critical-section-impl** - +//! critical section that is safe for multicore use +//! * **defmt** - +//! Implement `defmt::Format` for several types. +//! * **embedded_hal_1** - +//! Support alpha release of embedded-hal +//! * **rom-func-cache** - +//! Memoize(cache) ROM function pointers on first use to improve performance +//! * **rt** - +//! Minimal startup / runtime for Cortex-M microcontrollers +//! * **rtic-monotonic** - +//! Implement `rtic_monotonic::Monotonic` based on the RP2350 timer peripheral +//! * **i2c-write-iter** - +//! Implement `i2c_write_iter` traits for `I2C<_, _, Controller>`. +//! * **binary-info** - +//! Include a `static` variable containing picotool compatible binary info. + +#![recursion_limit = "256"] +#![warn(missing_docs)] +#![no_std] + +#[doc(hidden)] +pub use paste; + +/// Re-export of the PAC +pub use rp235x_pac as pac; + +pub mod adc; +pub mod arch; +#[macro_use] +pub mod async_utils; +pub(crate) mod atomic_register_access; +pub use rp_binary_info as binary_info; +pub mod block; +pub mod clocks; +#[cfg(feature = "critical-section-impl")] +mod critical_section_impl; +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub mod dcp; +pub mod dma; +pub mod gpio; +pub mod i2c; +pub mod lposc; +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub mod multicore; +pub mod otp; +pub mod pio; +pub mod pll; +pub mod powman; +pub mod prelude; +pub mod pwm; +pub mod reboot; +pub mod resets; +pub mod rom_data; +pub mod rosc; +pub mod sio; +pub mod spi; +pub mod timer; +pub mod typelevel; +pub mod uart; +pub mod usb; +pub mod vector_table; +pub mod watchdog; +pub mod xosc; + +// Provide access to common datastructures to avoid repeating ourselves +pub use adc::Adc; +pub use clocks::Clock; +pub use i2c::I2C; + +/// Attribute to declare the entry point of the program +/// +/// This is based on and can be used like the [entry attribute from +/// cortex-m-rt](https://docs.rs/cortex-m-rt/latest/cortex_m_rt/attr.entry.html). +/// +/// It extends that macro with code to unlock all spinlocks at the beginning of +/// `main`. As spinlocks are not automatically unlocked on software resets, this +/// can prevent unexpected deadlocks when running from a debugger. The macro +/// also enables the FPU (CP10) and the Double-Co-Processor (CP4) before we hit +/// main. +pub use rp235x_hal_macros::entry; + +/// Called by the rp235x-specific entry macro +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub use cortex_m_rt::entry as arch_entry; + +/// Called by the rp235x-specific entry macro +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +pub use riscv_rt::entry as arch_entry; + +use sio::CoreId; +pub use sio::Sio; +pub use spi::Spi; +pub use timer::Timer; +pub use watchdog::Watchdog; +// Re-export crates used in rp235x-hal's public API +pub extern crate fugit; + +/// Trigger full reset of the rp235x. +/// +/// Uses the watchdog and the power-on state machine (PSM) to reset all on-chip components. +pub fn reset() -> ! { + unsafe { + crate::arch::interrupt_disable(); + (*pac::PSM::PTR).wdsel().write(|w| w.bits(0x0001ffff)); + (*pac::WATCHDOG::PTR) + .ctrl() + .write(|w| w.trigger().set_bit()); + #[allow(clippy::empty_loop)] + loop {} + } +} + +/// Halt the rp235x. +/// +/// Disables the other core, and parks the current core in an +/// infinite loop with interrupts disabled. +/// +/// Doesn't stop other subsystems, like the DMA controller. +/// +/// When called from core1, core0 will be kept forced off, which +/// likely breaks debug connections. You may need to reboot with +/// BOOTSEL pressed to reboot into a debuggable state. +pub fn halt() -> ! { + unsafe { + crate::arch::interrupt_disable(); + // Stop other core + match crate::Sio::core() { + CoreId::Core0 => { + // Stop core 1. + (*pac::PSM::PTR) + .frce_off() + .modify(|_, w| w.proc1().set_bit()); + while !(*pac::PSM::PTR).frce_off().read().proc1().bit_is_set() { + crate::arch::nop(); + } + // Restart core 1. Without this, most debuggers will fail connecting. + // It will loop indefinitely in BOOTROM, as nothing + // will trigger the wakeup sequence. + (*pac::PSM::PTR) + .frce_off() + .modify(|_, w| w.proc1().clear_bit()); + } + CoreId::Core1 => { + // Stop core 0. + (*pac::PSM::PTR) + .frce_off() + .modify(|_, w| w.proc0().set_bit()); + // We cannot restart core 0 here, as it would just boot into main. + // So the best we can do is to keep core 0 disabled, which may break + // further debug connections. + } + }; + + // Keep current core running, so debugging stays possible + loop { + crate::arch::wfe() + } + } +} diff --git a/rp-hal/rp235x-hal/src/lposc.rs b/rp-hal/rp235x-hal/src/lposc.rs new file mode 100644 index 0000000..10f94cf --- /dev/null +++ b/rp-hal/rp235x-hal/src/lposc.rs @@ -0,0 +1,31 @@ +//! Low Power Oscillator (ROSC) +//! +//! See [Section +//! 8.4](https://rptl.io/rp2350-datasheet) for +//! more details +//! +//! The on-chip 32kHz Low Power Oscillator requires no external +//! components. It starts automatically when the always-on domain is powered and +//! provides a clock for the power manager and a tick for the Real-Time Clock +//! (RTC) when the switched-core power domain is powered off. + +use crate::{pac::powman::LPOSC, typelevel::Sealed}; + +/// A Low Power Oscillator. +pub struct LowPowerOscillator { + device: LPOSC, +} + +impl LowPowerOscillator { + /// Creates a new LowPowerOscillator from the underlying device. + pub fn new(dev: LPOSC) -> Self { + LowPowerOscillator { device: dev } + } + + /// Releases the underlying device. + pub fn free(self) -> LPOSC { + self.device + } +} + +impl Sealed for LowPowerOscillator {} diff --git a/rp-hal/rp235x-hal/src/multicore.rs b/rp-hal/rp235x-hal/src/multicore.rs new file mode 100644 index 0000000..cdf406f --- /dev/null +++ b/rp-hal/rp235x-hal/src/multicore.rs @@ -0,0 +1,371 @@ +//! Multicore support +//! +//! This module handles setup of the 2nd cpu core on the rp235x, 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. +//! +//! # Usage +//! +//! ```no_run +//! use rp235x_hal::{ +//! gpio::Pins, +//! multicore::{Multicore, Stack}, +//! pac, +//! sio::Sio, +//! }; +//! +//! static mut CORE1_STACK: Stack<4096> = Stack::new(); +//! +//! fn core1_task() { +//! loop {} +//! } +//! +//! fn main() -> ! { +//! let mut pac = hal::pac::Peripherals::take().unwrap(); +//! let mut sio = Sio::new(pac.SIO); +//! // Other init code above this line +//! let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo); +//! let cores = mc.cores(); +//! let core1 = &mut cores[1]; +//! let _test = core1.spawn(CORE1_STACK.take().unwrap(), core1_task); +//! // The rest of your application below this line +//! # loop {} +//! } +//! +//! ``` +//! +//! For inter-processor communications, see [`crate::sio::SioFifo`] and [`crate::sio::Spinlock0`] +//! +//! For a detailed example, see [examples/multicore_fifo_blink.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/multicore_fifo_blink.rs) + +use core::cell::Cell; +use core::cell::UnsafeCell; +use core::mem::ManuallyDrop; +use core::ops::Range; +use core::sync::atomic::compiler_fence; +use core::sync::atomic::Ordering; + +use crate::pac; +use crate::Sio; + +/// Errors for multicore operations. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Operation is invalid on this core. + InvalidCore, + /// Core was unresponsive to commands. + Unresponsive, +} + +#[inline(always)] +fn install_stack_guard(_stack_limit: *mut usize) { + // TBD Cortex-M33 MPU stack guard stuff. + // See the RP2040 code. +} + +#[inline(always)] +fn core1_setup(stack_limit: *mut usize) { + install_stack_guard(stack_limit); + // TODO: irq priorities +} + +/// Multicore execution management. +pub struct Multicore<'p> { + cores: [Core<'p>; 2], +} + +/// Data type for a properly aligned stack of N 32-bit (usize) words +#[repr(C, align(32))] +pub struct Stack { + /// Memory to be used for the stack + mem: UnsafeCell<[usize; SIZE]>, + taken: Cell, +} + +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} + +// Safety: Only one thread can `take` access to contents of the +// struct, guarded by a critical section. +unsafe impl Sync for Stack {} + +impl Stack { + /// Construct a stack of length SIZE, initialized to 0 + /// + /// The minimum allowed SIZE is 64 bytes, but most programs + /// will need a significantly larger stack. + pub const fn new() -> Stack { + const { assert!(SIZE >= 64, "Stack too small") }; + Stack { + mem: UnsafeCell::new([0; SIZE]), + taken: Cell::new(false), + } + } + + /// Take the StackAllocation out of this Stack. + /// + /// This returns None if the stack is already taken. + pub fn take(&self) -> Option { + let taken = critical_section::with(|_| self.taken.replace(true)); + if taken { + None + } else { + // Safety: We know the size of this allocation + unsafe { + let start = self.mem.get() as *mut usize; + let end = start.add(SIZE); + Some(StackAllocation::from_raw_parts(start, end)) + } + } + } + + /// Reset the taken flag of the stack area + /// + /// # Safety + /// + /// The caller must ensure that the stack is no longer in use, eg. because + /// the core that used it was reset. This method doesn't do any synchronization + /// so it must not be called from multiple threads concurrently. + pub unsafe fn reset(&self) { + self.taken.replace(false); + } +} + +/// This object represents a memory area which can be used for a stack. +/// +/// It is essentially a range of pointers with these additional guarantees: +/// The begin / end pointers must define a stack +/// with proper alignment (at least 8 bytes, preferably 32 bytes) +/// and sufficient size (64 bytes would be sound but much too little for +/// most real-world workloads). The underlying memory must +/// have a static lifetime and must be owned by the object exclusively. +/// No mutable references to that memory must exist. +/// Therefore, a function that gets passed such an object is free to write +/// to arbitrary memory locations in the range. +pub struct StackAllocation { + /// Start and end pointer of the StackAllocation as a Range + mem: Range<*mut usize>, +} + +impl StackAllocation { + fn get(&self) -> Range<*mut usize> { + self.mem.clone() + } + + /// Unsafely construct a stack allocation + /// + /// This is mainly useful to construct a stack allocation in some memory region + /// defined in a linker script, for example to place the stack in the SRAM4/5 regions. + /// + /// # Safety + /// + /// The caller must ensure that the guarantees that a StackAllocation provides + /// are upheld. + pub unsafe fn from_raw_parts(start: *mut usize, end: *mut usize) -> Self { + StackAllocation { mem: start..end } + } +} + +impl From<&Stack> for Option { + fn from(stack: &Stack) -> Self { + let taken = critical_section::with(|_| stack.taken.replace(true)); + if taken { + None + } else { + // Safety: We know the size of this allocation + unsafe { + let start = stack.mem.get() as *mut usize; + let end = start.add(SIZE); + Some(StackAllocation::from_raw_parts(start, end)) + } + } + } +} + +impl<'p> Multicore<'p> { + /// Create a new |Multicore| instance. + pub fn new( + psm: &'p mut pac::PSM, + ppb: &'p mut pac::PPB, + sio: &'p mut crate::sio::SioFifo, + ) -> Self { + Self { + cores: [ + Core { inner: None }, + Core { + inner: Some((psm, ppb, sio)), + }, + ], + } + } + + /// Get the available |Core| instances. + pub fn cores(&mut self) -> &'p mut [Core] { + &mut self.cores + } +} + +/// A handle for controlling a logical core. +pub struct Core<'p> { + inner: Option<( + &'p mut pac::PSM, + &'p mut pac::PPB, + &'p mut crate::sio::SioFifo, + )>, +} + +impl Core<'_> { + /// Get the id of this core. + pub fn id(&self) -> u8 { + match self.inner { + None => 0, + Some(..) => 1, + } + } + + /// Spawn a function on this core. + /// + /// The closure should not return. It is currently defined as `-> ()` because `-> !` is not yet + /// stable. + /// + /// Core 1 will be reset from core 0 in order to spawn another task. + /// + /// Resetting a single core of a running program can have undesired consequences. Deadlocks are + /// likely if the core being reset happens to be inside a critical section. + /// It may even break safety assumptions of some unsafe code. So, be careful when calling this method + /// more than once. + pub fn spawn(&mut self, stack: StackAllocation, entry: F) -> Result<(), Error> + where + F: FnOnce() + Send + 'static, + { + if let Some((psm, ppb, fifo)) = self.inner.as_mut() { + // 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( + _: u64, + _: u64, + entry: *mut ManuallyDrop, + stack_limit: *mut usize, + ) -> ! { + core1_setup(stack_limit); + + let entry = unsafe { ManuallyDrop::take(&mut *entry) }; + + // make sure the preceding read doesn't get reordered past the following fifo write + compiler_fence(Ordering::SeqCst); + + // Signal that it's safe for core 0 to get rid of the original value now. + // + // We don't have any way to get at core 1's SIO without using `Peripherals::steal` right now, + // since svd2rust doesn't really support multiple cores properly. + let peripherals = unsafe { pac::Peripherals::steal() }; + let mut sio = Sio::new(peripherals.SIO); + sio.fifo.write_blocking(1); + + entry(); + loop { + crate::arch::wfe() + } + } + + // Reset the core + // TODO: resetting without prior check that the core is actually stowed is not great. + // But there does not seem to be any obvious way to check that. A marker flag could be + // set from this method and cleared for the wrapper after `entry` returned. But doing + // so wouldn't be zero cost. + psm.frce_off().modify(|_, w| w.proc1().set_bit()); + while !psm.frce_off().read().proc1().bit_is_set() { + crate::arch::nop(); + } + psm.frce_off().modify(|_, w| w.proc1().clear_bit()); + + // Set up the stack + // AAPCS requires in 6.2.1.2 that the stack is 8bytes aligned., we may need to trim the + // array size to guaranty that the base of the stack (the end of the array) meets that requirement. + // The start of the array does not need to be aligned. + + let stack = stack.get(); + let mut stack_ptr = stack.end; + // on rp235x, usize are 4 bytes, so align_offset(8) on a *mut usize returns either 0 or 1. + let misalignment_offset = stack_ptr.align_offset(8); + + // 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 { + stack_ptr = stack_ptr.sub(misalignment_offset); + + // Push `stack_limit`. + stack_ptr = stack_ptr.sub(1); + stack_ptr.cast::<*mut usize>().write(stack.start); + + // 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 rp235x doesn't have + // memory caches, and writes happen in-order. + compiler_fence(Ordering::Release); + + let vector_table = ppb.vtor().read().bits(); + + // 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(); + crate::arch::sev(); + } + fifo.write_blocking(cmd); + let response = fifo.read_blocking(); + 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, + // so we have to drop it ourselves. + drop(ManuallyDrop::into_inner(entry)); + return Err(Error::Unresponsive); + } + } + if seq >= cmd_seq.len() { + break; + } + } + + // Wait until the other core has copied `entry` before returning. + fifo.read_blocking(); + + Ok(()) + } else { + Err(Error::InvalidCore) + } + } +} diff --git a/rp-hal/rp235x-hal/src/otp.rs b/rp-hal/rp235x-hal/src/otp.rs new file mode 100644 index 0000000..94d904d --- /dev/null +++ b/rp-hal/rp235x-hal/src/otp.rs @@ -0,0 +1,71 @@ +//! Interface to the RP2350's One Time Programmable Memory + +/// The ways in which we can fail to read OTP +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The user passed an invalid index to a function. + InvalidIndex, + /// The hardware refused to let us read this word, probably due to + /// read lock set earlier in the boot process. + InvalidPermissions, +} + +/// OTP read address, using automatic Error Correction. +/// +/// A 32-bit read returns the ECC-corrected data for two neighbouring rows, or +/// all-ones on permission failure. Only the first 8 KiB is populated. +pub const OTP_DATA_BASE: *const u32 = 0x4013_0000 as *const u32; + +/// OTP read address, without using any automatic Error Correction. +/// +/// A 32-bit read returns 24-bits of raw data from the OTP word. +pub const OTP_DATA_RAW_BASE: *const u32 = 0x4013_4000 as *const u32; + +/// How many pages in OTP (post error-correction) +pub const NUM_PAGES: usize = 64; + +/// How many rows in one page in OTP (post error-correction) +pub const NUM_ROWS_PER_PAGE: usize = 64; + +/// How many rows in OTP (post error-correction) +pub const NUM_ROWS: usize = NUM_PAGES * NUM_ROWS_PER_PAGE; + +/// Read one ECC protected word from the OTP +pub fn read_ecc_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // First do a raw read to check permissions + let _ = read_raw_word(row)?; + // One 32-bit read gets us two rows + let offset = row >> 1; + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_BASE.add(offset).read() }; + if (row & 1) == 0 { + Ok(value as u16) + } else { + Ok((value >> 16) as u16) + } +} + +/// Read one raw word from the OTP +/// +/// You get the 24-bit raw value in the lower part of the 32-bit result. +pub fn read_raw_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // One 32-bit read gets us one row + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_RAW_BASE.add(row).read() }; + if value == 0xFFFF_FFFF { + Err(Error::InvalidPermissions) + } else { + Ok(value) + } +} diff --git a/rp-hal/rp235x-hal/src/pio.rs b/rp-hal/rp235x-hal/src/pio.rs new file mode 100644 index 0000000..f81801f --- /dev/null +++ b/rp-hal/rp235x-hal/src/pio.rs @@ -0,0 +1,2331 @@ +//! Programmable IO (PIO) +//! +//! See [Chapter 11](https://rptl.io/rp2350-datasheet#section_pio) of the RP2350 +//! datasheet for more details. + +use core::ops::Deref; +use pio::{Instruction, InstructionOperands, Program, SideSet, Wrap}; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + dma::{EndlessReadTarget, EndlessWriteTarget, ReadTarget, TransferSize, Word, WriteTarget}, + gpio::{Function, FunctionPio0, FunctionPio1}, + pac::{self, dma::ch::ch_ctrl_trig::TREQ_SEL_A, pio0::RegisterBlock, PIO0, PIO1}, + resets::SubsystemReset, + typelevel::Sealed, +}; + +const PIO_INSTRUCTION_COUNT: usize = 32; + +impl Sealed for PIO0 {} +impl Sealed for PIO1 {} + +/// PIO Instance +pub trait PIOExt: Deref + SubsystemReset + Sized + Send + Sealed { + /// Associated Pin Function. + type PinFunction: Function; + + /// Create a new PIO wrapper and split the state machines into individual objects. + #[allow(clippy::type_complexity)] // Required for symmetry with PIO::free(). + fn split( + self, + resets: &mut crate::pac::RESETS, + ) -> ( + PIO, + UninitStateMachine<(Self, SM0)>, + UninitStateMachine<(Self, SM1)>, + UninitStateMachine<(Self, SM2)>, + UninitStateMachine<(Self, SM3)>, + ) { + self.reset_bring_down(resets); + self.reset_bring_up(resets); + + let sm0 = UninitStateMachine { + block: self.deref(), + sm: self.sm(0), + _phantom: core::marker::PhantomData, + }; + let sm1 = UninitStateMachine { + block: self.deref(), + sm: self.sm(1), + _phantom: core::marker::PhantomData, + }; + let sm2 = UninitStateMachine { + block: self.deref(), + sm: self.sm(2), + _phantom: core::marker::PhantomData, + }; + let sm3 = UninitStateMachine { + block: self.deref(), + sm: self.sm(3), + _phantom: core::marker::PhantomData, + }; + ( + PIO { + used_instruction_space: 0, + pio: self, + }, + sm0, + sm1, + sm2, + sm3, + ) + } + + /// Number of this PIO (0..1). + fn id() -> usize; +} + +impl PIOExt for PIO0 { + type PinFunction = FunctionPio0; + fn id() -> usize { + 0 + } +} +impl PIOExt for PIO1 { + type PinFunction = FunctionPio1; + fn id() -> usize { + 1 + } +} + +#[allow(clippy::upper_case_acronyms)] +/// Programmable IO Block +pub struct PIO { + used_instruction_space: u32, // bit for each PIO_INSTRUCTION_COUNT + pio: P, +} + +impl core::fmt::Debug for PIO

{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("PIO") + .field("used_instruction_space", &self.used_instruction_space) + .field("pio", &"PIO { .. }") + .finish() + } +} + +// Safety: `PIO` only provides access to those registers which are not directly used by +// `StateMachine`. +unsafe impl Send for PIO

{} + +// Safety: `PIO` is marked Send so ensure all accesses remain atomic and no new concurrent accesses +// are added. +impl PIO

{ + /// Free this instance. + /// + /// All output pins are left in their current state. + pub fn free( + self, + _sm0: UninitStateMachine<(P, SM0)>, + _sm1: UninitStateMachine<(P, SM1)>, + _sm2: UninitStateMachine<(P, SM2)>, + _sm3: UninitStateMachine<(P, SM3)>, + ) -> P { + // All state machines have already been stopped. + self.pio + } + + /// This PIO's interrupt by index. + pub fn irq(&self) -> Interrupt<'_, P, IRQ> { + struct IRQSanity; + impl IRQSanity { + const CHECK: () = assert!(IRQ <= 1, "IRQ index must be either 0 or 1"); + } + + #[allow(clippy::let_unit_value)] + let _ = IRQSanity::::CHECK; + Interrupt { + block: self.pio.deref(), + _phantom: core::marker::PhantomData, + } + } + + /// This PIO's IRQ0 interrupt. + pub fn irq0(&self) -> Interrupt<'_, P, 0> { + self.irq() + } + + /// This PIO's IRQ1 interrupt. + pub fn irq1(&self) -> Interrupt<'_, P, 1> { + self.irq() + } + + /// Get raw irq flags. + /// + /// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of + /// the IRQ flags. + pub fn get_irq_raw(&self) -> u8 { + self.pio.irq().read().irq().bits() + } + + /// Clear PIO's IRQ flags indicated by the bits. + /// + /// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of + /// the IRQ flags. + // Safety: PIOExt provides exclusive access to the pio.irq register, this must be preserved to + // satisfy Send trait. + pub fn clear_irq(&self, flags: u8) { + self.pio.irq().write(|w| unsafe { w.irq().bits(flags) }); + } + + /// Force PIO's IRQ flags indicated by the bits. + /// + /// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of + /// the IRQ flags. + // Safety: PIOExt provides exclusive access to the pio.irq register, this must be preserved to + // satisfy Send trait. + pub fn force_irq(&self, flags: u8) { + self.pio + .irq_force() + .write(|w| unsafe { w.irq_force().bits(flags) }); + } + + /// Calculates a mask with the `len` right-most bits set. + fn instruction_mask(len: usize) -> u32 { + if len < 32 { + (1 << len) - 1 + } else { + 0xffffffff + } + } + + /// Tries to find an appropriate offset for the instructions, in range 0..=31. + fn find_offset_for_instructions(&self, i: &[u16], origin: Option) -> Option { + if i.len() > PIO_INSTRUCTION_COUNT || i.is_empty() { + None + } else { + let mask = Self::instruction_mask(i.len()); + if let Some(origin) = origin { + if origin as usize > PIO_INSTRUCTION_COUNT - i.len() + || self.used_instruction_space & (mask << origin) != 0 + { + None + } else { + Some(origin) + } + } else { + for i in (0..=32 - (i.len() as u8)).rev() { + if self.used_instruction_space & (mask << i) == 0 { + return Some(i); + } + } + None + } + } + } + + /// Allocates space in instruction memory and installs the program. + /// + /// The function returns a handle to the installed program that can be used + /// to configure a `StateMachine` via `PIOBuilder`. The program can be + /// uninstalled to free instruction memory via `uninstall()` once the state + /// machine using the program has been uninitialized. + /// + /// Note: We use the RP2040 program size constant, but the RP2350 has the + /// same size instruction memory. + /// + /// # Safety + /// + /// `PIOExt` is marked send and should be the only object allowed to access + /// `pio.instr_mem` + pub fn install( + &mut self, + p: &Program<{ pio::RP2040_MAX_PROGRAM_SIZE }>, + ) -> Result, InstallError> { + if let Some(offset) = self.find_offset_for_instructions(&p.code, p.origin) { + p.code + .iter() + .cloned() + .map(|instr| { + if instr & 0b1110_0000_0000_0000 == 0 { + // this is a JMP instruction -> add offset to address + let address = (instr & 0b11111) as u8; + let address = address + 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 { + // this is not a JMP instruction -> keep it unchanged + instr + } + }) + .enumerate() + .for_each(|(i, instr)| { + self.pio + .instr_mem(i + offset as usize) + .write(|w| unsafe { w.instr_mem0().bits(instr) }) + }); + self.used_instruction_space |= Self::instruction_mask(p.code.len()) << offset; + Ok(InstalledProgram { + offset, + length: p.code.len() as u8, + side_set: p.side_set, + wrap: p.wrap, + _phantom: core::marker::PhantomData, + }) + } else { + Err(InstallError::NoSpace) + } + } + + /// Removes the specified program from instruction memory, freeing the allocated space. + pub fn uninstall(&mut self, p: InstalledProgram

) { + let instr_mask = Self::instruction_mask(p.length as usize) << p.offset as u32; + self.used_instruction_space &= !instr_mask; + } +} + +/// Handle to a program that was placed in the PIO's instruction memory. +/// +/// Objects of this type can be reused for multiple state machines of the same PIO block to save +/// memory if multiple state machines are supposed to perform the same function (for example, if +/// one PIO block is used to implement multiple I2C busses). +/// +/// `PIO::uninstall(program)` can be used to free the space occupied by the program once it is no +/// longer used. +/// +/// # Examples +/// +/// ```no_run +/// use rp235x_hal::{self as hal, pio::PIOBuilder, pio::PIOExt}; +/// let mut peripherals = hal::pac::Peripherals::take().unwrap(); +/// let (mut pio, sm0, _, _, _) = peripherals.PIO0.split(&mut peripherals.RESETS); +/// // Install a program in instruction memory. +/// let program = pio_proc::pio_asm!( +/// ".wrap_target", +/// "set pins, 1 [31]", +/// "set pins, 0 [31]", +/// ".wrap" +/// ) +/// .program; +/// let installed = pio.install(&program).unwrap(); +/// // Configure a state machine to use the program. +/// let (sm, rx, tx) = PIOBuilder::from_installed_program(installed).build(sm0); +/// // Uninitialize the state machine again, freeing the program. +/// let (sm, installed) = sm.uninit(rx, tx); +/// // Uninstall the program to free instruction memory. +/// pio.uninstall(installed); +/// ``` +/// +/// # Safety +/// +/// Objects of this type can outlive their `PIO` object. If the PIO block is reinitialized, the API +/// does not prevent the user from calling `uninstall()` when the PIO block does not actually hold +/// the program anymore. The user must therefore make sure that `uninstall()` is only called on the +/// PIO object which was used to install the program. +/// +/// ```ignore +/// let (mut pio, sm0, sm1, sm2, sm3) = pac.PIO0.split(&mut pac.RESETS); +/// // Install a program in instruction memory. +/// let installed = pio.install(&program).unwrap(); +/// // Reinitialize PIO. +/// let pio0 = pio.free(sm0, sm1, sm2, sm3); +/// let (mut pio, _, _, _, _) = pio0.split(&mut pac.RESETS); +/// // Do not do the following, the program is not in instruction memory anymore! +/// pio.uninstall(installed); +/// ``` +#[derive(Debug)] +pub struct InstalledProgram

{ + offset: u8, + length: u8, + side_set: SideSet, + wrap: Wrap, + _phantom: core::marker::PhantomData

, +} + +impl InstalledProgram

{ + /// Change the source and/or target for automatic program wrapping. + /// + /// This replaces the current wrap bounds with a new set. This can be useful if you are running + /// multiple state machines with the same program but using different wrap bounds. + /// + /// # Returns + /// + /// * [`Ok`] containing a new program with the provided wrap bounds + /// * [`Err`] containing the old program if the provided wrap was invalid (outside the bounds of + /// the program length) + pub fn set_wrap(self, wrap: Wrap) -> Result { + if wrap.source < self.length && wrap.target < self.length { + Ok(InstalledProgram { wrap, ..self }) + } else { + Err(self) + } + } + + /// Get the wrap target (entry point) of the installed program. + pub fn wrap_target(&self) -> u8 { + self.offset + self.wrap.target + } + + /// Get the offset the program is installed at. + pub fn offset(&self) -> u8 { + self.offset + } + + /// Clones this program handle so that it can be executed by two state machines at the same + /// time. + /// + /// # Safety + /// + /// This function is marked as unsafe because, once this function has been called, the + /// resulting handle can be used to call `PIO::uninstall()` while the program is still running. + /// + /// The user has to make sure to call `PIO::uninstall()` only once and only after all state + /// machines using the program have been uninitialized. + pub unsafe fn share(&self) -> InstalledProgram

{ + InstalledProgram { + offset: self.offset, + length: self.length, + side_set: self.side_set, + wrap: self.wrap, + _phantom: core::marker::PhantomData, + } + } +} + +/// State machine identifier (without a specified PIO block). +pub trait StateMachineIndex: Send + Sealed { + /// Numerical index of the state machine (0 to 3). + fn id() -> usize; +} + +/// First state machine. +pub struct SM0; +/// Second state machine. +pub struct SM1; +/// Third state machine. +pub struct SM2; +/// Fourth state machine. +pub struct SM3; + +impl StateMachineIndex for SM0 { + fn id() -> usize { + 0 + } +} + +impl Sealed for SM0 {} + +impl StateMachineIndex for SM1 { + fn id() -> usize { + 1 + } +} + +impl Sealed for SM1 {} + +impl StateMachineIndex for SM2 { + fn id() -> usize { + 2 + } +} + +impl Sealed for SM2 {} + +impl StateMachineIndex for SM3 { + fn id() -> usize { + 3 + } +} + +impl Sealed for SM3 {} + +/// Trait to identify a single state machine, as a generic type parameter to `UninitStateMachine`, +/// `InitStateMachine`, etc. +pub trait ValidStateMachine: Sealed { + /// The PIO block to which this state machine belongs. + type PIO: PIOExt; + + /// The index of this state machine (between 0 and 3). + fn id() -> usize; + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8; + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8; +} + +/// First state machine of the first PIO block. +pub type PIO0SM0 = (PIO0, SM0); +/// Second state machine of the first PIO block. +pub type PIO0SM1 = (PIO0, SM1); +/// Third state machine of the first PIO block. +pub type PIO0SM2 = (PIO0, SM2); +/// Fourth state machine of the first PIO block. +pub type PIO0SM3 = (PIO0, SM3); +/// First state machine of the second PIO block. +pub type PIO1SM0 = (PIO1, SM0); +/// Second state machine of the second PIO block. +pub type PIO1SM1 = (PIO1, SM1); +/// Third state machine of the second PIO block. +pub type PIO1SM2 = (PIO1, SM2); +/// Fourth state machine of the second PIO block. +pub type PIO1SM3 = (PIO1, SM3); + +impl ValidStateMachine for (P, SM) { + type PIO = P; + fn id() -> usize { + SM::id() + } + fn tx_dreq() -> u8 { + ((P::id() << 3) | SM::id()) as u8 + } + fn rx_dreq() -> u8 { + ((P::id() << 3) | SM::id() | 0x4) as u8 + } +} + +/// Pin State in the PIO +/// +/// Note the GPIO is able to override/invert that. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum PinState { + /// Pin in Low state. + High, + /// Pin in Low state. + Low, +} + +/// Pin direction in the PIO +/// +/// Note the GPIO is able to override/invert that. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum PinDir { + /// Pin set as an Input + Input, + /// Pin set as an Output. + Output, +} + +/// PIO State Machine (uninitialized, without a program). +#[derive(Debug)] +pub struct UninitStateMachine { + block: *const RegisterBlock, + sm: *const crate::pac::pio0::SM, + _phantom: core::marker::PhantomData, +} + +// Safety: `UninitStateMachine` only uses atomic accesses to shared registers. +unsafe impl Send for UninitStateMachine {} + +// Safety: `UninitStateMachine` is marked Send so ensure all accesses remain atomic and no new +// concurrent accesses are added. +impl UninitStateMachine { + /// Start and stop the state machine. + fn set_enabled(&mut self, enabled: bool) { + // Bits 3:0 are SM_ENABLE. + let mask = 1 << SM::id(); + if enabled { + self.set_ctrl_bits(mask); + } else { + self.clear_ctrl_bits(mask); + } + } + + fn restart(&mut self) { + // Bits 7:4 are SM_RESTART. + self.set_ctrl_bits(1 << (SM::id() + 4)); + } + + fn reset_clock(&mut self) { + // Bits 11:8 are CLKDIV_RESTART. + self.set_ctrl_bits(1 << (SM::id() + 8)); + } + + // Safety: All ctrl set access should go through this function to ensure atomic access. + fn set_ctrl_bits(&mut self, bits: u32) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_set((*self.block).ctrl().as_ptr(), bits); + } + } + + // Safety: All ctrl clear access should go through this function to ensure atomic access. + fn clear_ctrl_bits(&mut self, bits: u32) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_clear((*self.block).ctrl().as_ptr(), bits); + } + } + + // Safety: The Send trait assumes this is the only write to sm_clkdiv + fn set_clock_divisor(&self, int: u16, frac: u8) { + // Safety: This is the only write to this register + unsafe { + self.sm() + .sm_clkdiv() + .write(|w| w.int().bits(int).frac().bits(frac)); + } + } + + unsafe fn sm(&self) -> &crate::pac::pio0::SM { + &*self.sm + } + + unsafe fn pio(&self) -> &RegisterBlock { + &*self.block + } +} + +/// PIO State Machine with an associated program. +pub struct StateMachine { + sm: UninitStateMachine, + program: InstalledProgram, + _phantom: core::marker::PhantomData, +} + +/// Marker for an initialized, but stopped state machine. +pub struct Stopped; +/// Marker for an initialized and running state machine. +pub struct Running; + +/// Id for the PIO's IRQ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PioIRQ { + #[allow(missing_docs)] + Irq0, + #[allow(missing_docs)] + Irq1, +} +impl PioIRQ { + const fn to_index(self) -> usize { + match self { + PioIRQ::Irq0 => 0, + PioIRQ::Irq1 => 1, + } + } +} + +impl StateMachine { + /// Stops the state machine if it is still running and returns its program. + /// + /// The program can be uninstalled to free space once it is no longer used by any state + /// machine. + pub fn uninit( + mut self, + _rx: Rx, + _tx: Tx, + ) -> (UninitStateMachine, InstalledProgram) { + self.sm.set_enabled(false); + (self.sm, self.program) + } + + /// The address of the instruction currently being executed. + pub fn instruction_address(&self) -> u32 { + // Safety: Read only access without side effect + unsafe { self.sm.sm().sm_addr().read().bits() } + } + + #[deprecated(note = "Renamed to exec_instruction")] + /// Execute the instruction immediately. + pub fn set_instruction(&mut self, instruction: u16) { + let instruction = + Instruction::decode(instruction, self.program.side_set).expect("Invalid instruction"); + self.exec_instruction(instruction); + } + + /// Execute the instruction immediately. + /// + /// If an instruction written to INSTR stalls, it is stored in the same instruction latch used + /// by OUT EXEC and MOV EXEC, and will overwrite an in-progress instruction there. If EXEC + /// instructions are used, instructions written to INSTR must not stall. + pub fn exec_instruction(&mut self, instruction: Instruction) { + let instruction = instruction.encode(self.program.side_set); + + // Safety: all accesses to this register are controlled by this instance + unsafe { + self.sm + .sm() + .sm_instr() + .write(|w| w.sm0_instr().bits(instruction)) + } + } + + /// Check if the current instruction is stalled. + pub fn stalled(&self) -> bool { + // Safety: read only access without side effect + unsafe { self.sm.sm().sm_execctrl().read().exec_stalled().bit() } + } + + /// Clear both TX and RX FIFOs + pub fn clear_fifos(&mut self) { + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_shiftctrl = sm.sm_shiftctrl(); + let mut current = false; + // Toggling the FIFO join state clears the fifo + sm_shiftctrl.modify(|r, w| { + current = r.fjoin_rx().bit(); + w.fjoin_rx().bit(!current) + }); + sm_shiftctrl.modify(|_, w| w.fjoin_rx().bit(current)); + } + } + + /// Drain Tx fifo. + pub fn drain_tx_fifo(&mut self) { + // According to the datasheet 3.5.4.2 Page 358: + // + // When autopull is enabled, the behaviour of 'PULL' is altered: it becomes a no-op + // if the OSR is full. This is to avoid a race condition against the system + // DMA. It behaves as a fence: either an autopull has already taken place, in which case + // the 'PULL' has no effect, or the program will stall on the 'PULL' until data becomes + // available in the FIFO. + + // TODO: encode at compile time once pio 0.3.0 is out + const OUT: InstructionOperands = InstructionOperands::OUT { + destination: pio::OutDestination::NULL, + bit_count: 32, + }; + const PULL: InstructionOperands = InstructionOperands::PULL { + if_empty: false, + block: false, + }; + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_pinctrl = sm.sm_pinctrl(); + let sm_instr = sm.sm_instr(); + let fstat = self.sm.pio().fstat(); + + let operands = if sm.sm_shiftctrl().read().autopull().bit_is_set() { + OUT + } else { + PULL + } + .encode(); + + // Safety: sm0_instr may be accessed from SM::exec_instruction. + let mut saved_sideset_count = 0; + sm_pinctrl.modify(|r, w| { + saved_sideset_count = r.sideset_count().bits(); + w.sideset_count().bits(0) + }); + + let mask = 1 << SM::id(); + // white tx fifo is not empty + while (fstat.read().txempty().bits() & mask) == 0 { + sm_instr.write(|w| w.sm0_instr().bits(operands)) + } + + if saved_sideset_count != 0 { + sm_pinctrl.modify(|_, w| w.sideset_count().bits(saved_sideset_count)); + } + } + } + + /// Change the clock divider of a state machine. + /// + /// Changing the clock divider of a running state machine is allowed + /// and guaranteed to not cause any glitches, but the exact timing of + /// clock pulses during the change is not specified. + pub fn set_clock_divisor(&mut self, divisor: f32) { + // sm frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256) + let int = divisor as u16; + let frac = ((divisor - int as f32) * 256.0) as u8; + + self.sm.set_clock_divisor(int, frac); + } + + /// Change the clock divider of a state machine using a 16.8 fixed point value. + /// + /// Changing the clock divider of a running state machine is allowed + /// and guaranteed to not cause any glitches, but the exact timing of + /// clock pulses during the change is not specified. + pub fn clock_divisor_fixed_point(&mut self, int: u16, frac: u8) { + self.sm.set_clock_divisor(int, frac); + } +} + +// Safety: All shared register accesses are atomic. +unsafe impl Send for StateMachine {} + +// Safety: `StateMachine` is marked Send so ensure all accesses remain atomic and no new concurrent +// accesses are added. +impl StateMachine { + /// Starts execution of the selected program. + pub fn start(mut self) -> StateMachine { + // Enable SM + self.sm.set_enabled(true); + + StateMachine { + sm: self.sm, + program: self.program, + _phantom: core::marker::PhantomData, + } + } + + /// Sets the pin state for the specified pins. + /// + /// The user has to make sure that they do not select any pins that are in use by any + /// other state machines of the same PIO block. + /// + /// The iterator's item are pairs of `(pin_number, pin_state)`. + pub fn set_pins(&mut self, pins: impl IntoIterator) { + // TODO: turn those three into const once pio 0.3.0 is released + let set_high_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINS, + data: 1, + } + .encode(); + let set_low_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINS, + data: 0, + } + .encode(); + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_pinctrl = sm.sm_pinctrl(); + let sm_execctrl = sm.sm_execctrl(); + let sm_instr = sm.sm_instr(); + + // sideset_count is implicitly set to 0 when the set_base/set_count are written (rather + // than modified) + let saved_pin_ctrl = sm_pinctrl.read().bits(); + let mut saved_execctrl = 0; + + sm_execctrl.modify(|r, w| { + saved_execctrl = r.bits(); + w.out_sticky().clear_bit() + }); + + for (pin_num, pin_state) in pins { + sm_pinctrl.write(|w| w.set_base().bits(pin_num).set_count().bits(1)); + let instruction = if pin_state == PinState::High { + set_high_instr + } else { + set_low_instr + }; + + sm_instr.write(|w| w.sm0_instr().bits(instruction)) + } + + sm_pinctrl.write(|w| w.bits(saved_pin_ctrl)); + sm_execctrl.write(|w| w.bits(saved_execctrl)); + } + } + + /// Set pin directions. + /// + /// The user has to make sure that they do not select any pins that are in use by any + /// other state machines of the same PIO block. + /// + /// The iterator's item are pairs of `(pin_number, pin_dir)`. + pub fn set_pindirs(&mut self, pindirs: impl IntoIterator) { + // TODO: turn those three into const once pio 0.3.0 is released + let set_output_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINDIRS, + data: 1, + } + .encode(); + let set_input_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINDIRS, + data: 0, + } + .encode(); + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_pinctrl = &sm.sm_pinctrl(); + let sm_execctrl = &sm.sm_execctrl(); + let sm_instr = &sm.sm_instr(); + + // sideset_count is implicitly set to 0 when the set_base/set_count are written (rather + // than modified) + let saved_pin_ctrl = sm_pinctrl.read().bits(); + let mut saved_execctrl = 0; + + sm_execctrl.modify(|r, w| { + saved_execctrl = r.bits(); + w.out_sticky().clear_bit() + }); + + for (pin_num, pin_dir) in pindirs { + sm_pinctrl.write(|w| w.set_base().bits(pin_num).set_count().bits(1)); + let instruction = if pin_dir == PinDir::Output { + set_output_instr + } else { + set_input_instr + }; + + sm_instr.write(|w| w.sm0_instr().bits(instruction)) + } + + sm_pinctrl.write(|w| w.bits(saved_pin_ctrl)); + sm_execctrl.write(|w| w.bits(saved_execctrl)); + } + } +} + +impl StateMachine<(P, SM), Stopped> { + /// Restarts the clock dividers for the specified state machines. + /// + /// As a result, the clock will be synchronous for the state machines, which is a precondition + /// for synchronous operation. + /// + /// The function returns an object that, once destructed, restarts the clock dividers. This + /// object allows further state machines to be added if more than two shall be synchronized. + /// + /// # Example + /// + /// ```ignore + /// sm0.synchronize_with(sm1).and_with(sm2); + /// ``` + pub fn synchronize_with<'sm, SM2: StateMachineIndex>( + &'sm mut self, + _other_sm: &'sm mut StateMachine<(P, SM2), Stopped>, + ) -> Synchronize<'sm, (P, SM)> { + let sm_mask = (1 << SM::id()) | (1 << SM2::id()); + Synchronize { sm: self, sm_mask } + } +} + +impl StateMachine<(P, SM), State> { + /// Create a group of state machines, which can be started/stopped synchronously + pub fn with( + self, + other_sm: StateMachine<(P, SM2), State>, + ) -> StateMachineGroup2 { + StateMachineGroup2 { + sm1: self, + sm2: other_sm, + } + } +} + +/// Group of 2 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup2< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, +} + +/// Group of 3 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup3< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, + sm3: StateMachine<(P, SM3Idx), State>, +} + +/// Group of 4 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup4< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, + sm3: StateMachine<(P, SM3Idx), State>, + sm4: StateMachine<(P, SM4Idx), State>, +} + +impl + StateMachineGroup2 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + ) { + (self.sm1, self.sm2) + } + + /// Add another state machine to the group + pub fn with( + self, + other_sm: StateMachine<(P, SM3Idx), State>, + ) -> StateMachineGroup3 { + StateMachineGroup3 { + sm1: self.sm1, + sm2: self.sm2, + sm3: other_sm, + } + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + State, + > StateMachineGroup3 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + StateMachine<(P, SM3Idx), State>, + ) { + (self.sm1, self.sm2, self.sm3) + } + + /// Add another state machine to the group + pub fn with( + self, + other_sm: StateMachine<(P, SM4Idx), State>, + ) -> StateMachineGroup4 { + StateMachineGroup4 { + sm1: self.sm1, + sm2: self.sm2, + sm3: self.sm3, + sm4: other_sm, + } + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) | (1 << SM3Idx::id()) + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + State, + > StateMachineGroup4 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + StateMachine<(P, SM3Idx), State>, + StateMachine<(P, SM4Idx), State>, + ) { + (self.sm1, self.sm2, self.sm3, self.sm4) + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) | (1 << SM3Idx::id()) | (1 << SM4Idx::id()) + } +} + +impl + StateMachineGroup2 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup2 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup2 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + > StateMachineGroup3 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup3 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup3 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + > StateMachineGroup4 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup4 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup4 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + sm4: StateMachine { + sm: self.sm4.sm, + program: self.sm4.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl + StateMachineGroup2 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup2 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup2 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + } + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + > StateMachineGroup3 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup3 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup3 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + } + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + > StateMachineGroup4 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup4 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup4 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + sm4: StateMachine { + sm: self.sm4.sm, + program: self.sm4.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +/// Type which, once destructed, restarts the clock dividers for all selected state machines, +/// effectively synchronizing them. +pub struct Synchronize<'sm, SM: ValidStateMachine> { + sm: &'sm mut StateMachine, + sm_mask: u32, +} + +impl<'sm, P: PIOExt, SM: StateMachineIndex> Synchronize<'sm, (P, SM)> { + /// Adds another state machine to be synchronized. + pub fn and_with( + mut self, + _other_sm: &'sm mut StateMachine<(P, SM2), Stopped>, + ) -> Self { + // Add another state machine index to the mask. + self.sm_mask |= 1 << SM2::id(); + self + } +} + +impl Drop for Synchronize<'_, SM> { + fn drop(&mut self) { + // Restart the clocks of all state machines specified by the mask. + // Bits 11:8 of CTRL contain CLKDIV_RESTART. + let sm_mask = self.sm_mask << 8; + self.sm.sm.set_ctrl_bits(sm_mask); + } +} + +impl StateMachine { + /// Stops execution of the selected program. + pub fn stop(mut self) -> StateMachine { + // Enable SM + self.sm.set_enabled(false); + + StateMachine { + sm: self.sm, + program: self.program, + _phantom: core::marker::PhantomData, + } + } + + /// Restarts the execution of the selected program from its wrap target. + pub fn restart(&mut self) { + // pause the state machine + self.sm.set_enabled(false); + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_pinctrl = sm.sm_pinctrl(); + let sm_instr = sm.sm_instr(); + + // save exec_ctrl & make side_set optional + let mut saved_sideset_count = 0; + sm_pinctrl.modify(|r, w| { + saved_sideset_count = r.sideset_count().bits(); + w.sideset_count().bits(0) + }); + + // revert it to its wrap target + let instruction = InstructionOperands::JMP { + condition: pio::JmpCondition::Always, + address: self.program.wrap_target(), + } + .encode(); + sm_instr.write(|w| w.sm0_instr().bits(instruction)); + + // restore exec_ctrl + if saved_sideset_count != 0 { + sm_pinctrl.modify(|_, w| w.sideset_count().bits(saved_sideset_count)); + } + + // clear osr/isr + self.sm.restart(); + } + + // unpause the state machine + self.sm.set_enabled(true); + } +} + +/// PIO RX FIFO handle. +pub struct Rx { + block: *const RegisterBlock, + _phantom: core::marker::PhantomData<(SM, RxSize)>, +} + +// Safety: All shared register accesses are atomic. +unsafe impl Send for Rx {} + +// Safety: `Rx` is marked Send so ensure all accesses remain atomic and no new concurrent accesses +// are added. +impl Rx { + unsafe fn block(&self) -> &pac::pio0::RegisterBlock { + &*self.block + } + + /// Gets the FIFO's address. + /// + /// This is useful if you want to DMA from this peripheral. + /// + /// NB: You are responsible for using the pointer correctly and not + /// underflowing the buffer. + pub fn fifo_address(&self) -> *const u32 { + // Safety: returning the address is safe as such. The user is responsible for any + // dereference ops at that address. + unsafe { self.block().rxf(SM::id()).as_ptr() } + } + + /// Gets the FIFO's `DREQ` value. + /// + /// This is a value between 0 and 39. Each FIFO on each state machine on + /// each PIO has a unique value. + pub fn dreq_value(&self) -> u8 { + if self.block as usize == 0x5020_0000usize { + TREQ_SEL_A::PIO0_RX0 as u8 + (SM::id() as u8) + } else { + TREQ_SEL_A::PIO1_RX0 as u8 + (SM::id() as u8) + } + } + + /// Get the next element from RX FIFO. + /// + /// Returns `None` if the FIFO is empty. + pub fn read(&mut self) -> Option { + if self.is_empty() { + return None; + } + + // Safety: The register is unique to this Rx instance. + Some(unsafe { core::ptr::read_volatile(self.fifo_address()) }) + } + + /// Enable/Disable the autopush feature of the state machine. + // Safety: This register is read by Rx, this is the only write. + pub fn enable_autopush(&mut self, enable: bool) { + // Safety: only instance reading/writing to autopush bit and no other write to this + // register + unsafe { + self.block() + .sm(SM::id()) + .sm_shiftctrl() + .modify(|_, w| w.autopush().bit(enable)) + } + } + + /// Indicate if the rx FIFO is empty + pub fn is_empty(&self) -> bool { + // Safety: Read only access without side effect + unsafe { self.block().fstat().read().rxempty().bits() & (1 << SM::id()) != 0 } + } + + /// Indicate if the rx FIFO is full + pub fn is_full(&self) -> bool { + // Safety: Read only access without side effect + unsafe { self.block().fstat().read().rxfull().bits() & (1 << SM::id()) != 0 } + } + + /// Enable RX FIFO not empty interrupt. + /// + /// This interrupt is raised when the RX FIFO is not empty, i.e. one could read more data from it. + pub fn enable_rx_not_empty_interrupt(&self, id: PioIRQ) { + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << SM::id(), + ); + } + } + + /// Disable RX FIFO not empty interrupt. + pub fn disable_rx_not_empty_interrupt(&self, id: PioIRQ) { + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << SM::id(), + ); + } + } + + /// Force RX FIFO not empty interrupt. + pub fn force_rx_not_empty_interrupt(&self, id: PioIRQ, state: bool) { + let action = if state { + write_bitmask_set + } else { + write_bitmask_clear + }; + // Safety: Atomic write to a single bit owned by this instance + unsafe { + action( + self.block().sm_irq(id.to_index()).irq_intf().as_ptr(), + 1 << SM::id(), + ); + } + } + + /// Set the transfer size used in DMA transfers. + pub fn transfer_size(self, size: RSZ) -> Rx { + let _ = size; + Rx { + block: self.block, + _phantom: core::marker::PhantomData, + } + } +} + +// Safety: This only reads from the state machine fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl ReadTarget for Rx { + type ReceivedWord = RxSize::Type; + + fn rx_treq() -> Option { + Some(SM::rx_dreq()) + } + + fn rx_address_count(&self) -> (u32, u32) { + ( + unsafe { &*self.block }.rxf(SM::id()).as_ptr() as u32, + u32::MAX, + ) + } + + fn rx_increment(&self) -> bool { + false + } +} + +impl EndlessReadTarget for Rx {} + +/// PIO TX FIFO handle. +pub struct Tx { + block: *const RegisterBlock, + _phantom: core::marker::PhantomData<(SM, TxSize)>, +} + +// Safety: All shared register accesses are atomic. +unsafe impl Send for Tx {} + +// Safety: `Tx` is marked Send so ensure all accesses remain atomic and no new concurrent accesses +// are added. +impl Tx { + unsafe fn block(&self) -> &pac::pio0::RegisterBlock { + &*self.block + } + + fn write_generic(&mut self, value: T) -> bool { + if !self.is_full() { + // Safety: Only accessed by this instance (unless DMA is used). + unsafe { + let reg_ptr = self.fifo_address() as *mut T; + reg_ptr.write_volatile(value); + } + true + } else { + false + } + } + + /// Gets the FIFO's address. + /// + /// This is useful if you want to DMA to this peripheral. + /// + /// NB: You are responsible for using the pointer correctly and not + /// overflowing the buffer. + pub fn fifo_address(&self) -> *const u32 { + // Safety: The only access to this register + unsafe { self.block().txf(SM::id()).as_ptr() } + } + + /// Gets the FIFO's `DREQ` value. + /// + /// This is a value between 0 and 39. Each FIFO on each state machine on + /// each PIO has a unique value. + pub fn dreq_value(&self) -> u8 { + if self.block as usize == 0x5020_0000usize { + TREQ_SEL_A::PIO0_TX0 as u8 + (SM::id() as u8) + } else { + TREQ_SEL_A::PIO1_TX0 as u8 + (SM::id() as u8) + } + } + + /// Write a u32 value to TX FIFO. + /// + /// Returns `true` if the value was written to FIFO, `false` otherwise. + pub fn write(&mut self, value: u32) -> bool { + self.write_generic(value) + } + + /// Write a replicated u8 value to TX FIFO. + /// + /// Memory mapped register writes that are smaller than 32bits will trigger + /// "Narrow IO Register Write" behaviour in rp235x - the value written will + /// be replicated to the rest of the register as described in + /// [RP2350 Datasheet: 2.1.5. - Narrow IO Register Writes][section_2_1_5] + /// + /// + /// This 8bit write will set all 4 bytes of the FIFO to `value` + /// Eg: if you write `0xBA` the value written to the the FIFO will be + /// `0xBABABABA` + /// + /// If you wish to write an 8bit number without replication, + /// use `write(my_u8 as u32)` instead. + /// + /// Returns `true` if the value was written to FIFO, `false` otherwise. + /// + /// [section_2_1_5]: + pub fn write_u8_replicated(&mut self, value: u8) -> bool { + self.write_generic(value) + } + + /// Write a replicated 16bit value to TX FIFO. + /// + /// Memory mapped register writes that are smaller than 32bits will trigger + /// "Narrow IO Register Write" behaviour in rp235x - the value written will + /// be replicated to the rest of the register as described in + /// [RP2350 Datasheet: 2.1.5. - Narrow IO Register Writes][section_2_1_5] + /// + /// This 16bit write will set both the upper and lower half of the FIFO entry to `value`. + /// + /// For example, if you write `0xC0DA` the value written to the FIFO will be + /// `0xC0DAC0DA` + /// + /// If you wish to write a 16bit number without replication, + /// use `write(my_u16 as u32)` instead. + /// + /// Returns `true` if the value was written to FIFO, `false` otherwise. + /// + /// [section_2_1_5]: + pub fn write_u16_replicated(&mut self, value: u16) -> bool { + self.write_generic(value) + } + + /// Checks if the state machine has stalled on empty TX FIFO during a blocking PULL, or an OUT + /// with autopull enabled. + /// + /// **Note this is a sticky flag and may not reflect the current state of the machine.** + pub fn has_stalled(&self) -> bool { + let mask = 1 << SM::id(); + // Safety: read-only access without side-effect + unsafe { self.block().fdebug().read().txstall().bits() & mask == mask } + } + + /// Clears the `tx_stalled` flag. + pub fn clear_stalled_flag(&self) { + let mask = 1 << SM::id(); + + // Safety: These bits are WC, only the one corresponding to this SM is set. + unsafe { + self.block().fdebug().write(|w| w.txstall().bits(mask)); + } + } + + /// Indicate if the tx FIFO is empty + pub fn is_empty(&self) -> bool { + // Safety: read-only access without side-effect + unsafe { self.block().fstat().read().txempty().bits() & (1 << SM::id()) != 0 } + } + + /// Indicate if the tx FIFO is full + pub fn is_full(&self) -> bool { + // Safety: read-only access without side-effect + unsafe { self.block().fstat().read().txfull().bits() & (1 << SM::id()) != 0 } + } + + /// Enable TX FIFO not full interrupt. + /// + /// This interrupt is raised when the TX FIFO is not full, i.e. one could push more data to it. + pub fn enable_tx_not_full_interrupt(&self, id: PioIRQ) { + // Safety: Atomic access to the register. Bit only modified by this Tx + unsafe { + write_bitmask_set( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << (SM::id() + 4), + ); + } + } + + /// Disable TX FIFO not full interrupt. + pub fn disable_tx_not_full_interrupt(&self, id: PioIRQ) { + // Safety: Atomic access to the register. Bit only modified by this Tx + unsafe { + write_bitmask_clear( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << (SM::id() + 4), + ); + } + } + + /// Force TX FIFO not full interrupt. + pub fn force_tx_not_full_interrupt(&self, id: PioIRQ) { + // Safety: Atomic access to the register. Bit only modified by this Tx + unsafe { + write_bitmask_set( + self.block().sm_irq(id.to_index()).irq_intf().as_ptr(), + 1 << (SM::id() + 4), + ); + } + } + + /// Set the transfer size used in DMA transfers. + pub fn transfer_size(self, size: RSZ) -> Tx { + let _ = size; + Tx { + block: self.block, + _phantom: core::marker::PhantomData, + } + } +} + +// Safety: This only writes to the state machine fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl WriteTarget for Tx { + type TransmittedWord = TxSize::Type; + + fn tx_treq() -> Option { + Some(SM::tx_dreq()) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + ( + unsafe { &*self.block }.txf(SM::id()).as_ptr() as u32, + u32::MAX, + ) + } + + fn tx_increment(&self) -> bool { + false + } +} + +impl EndlessWriteTarget for Tx {} + +/// PIO Interrupt controller. +#[derive(Debug)] +pub struct Interrupt<'a, P: PIOExt, const IRQ: usize> { + block: *const RegisterBlock, + _phantom: core::marker::PhantomData<&'a P>, +} + +// Safety: `Interrupt` provides exclusive access to interrupt registers. +unsafe impl Send for Interrupt<'_, P, IRQ> {} + +// Safety: `Interrupt` is marked Send so ensure all accesses remain atomic and no new concurrent +// accesses are added. +// `Interrupt` provides exclusive access to `irq_intf` to `irq_inte` for it's state machine, this +// must remain true to satisfy Send. +impl Interrupt<'_, P, IRQ> { + /// Enable interrupts raised by state machines. + /// + /// The PIO peripheral has 4 outside visible interrupts that can be raised by the state machines. Note that this + /// does not correspond with the state machine index; any state machine can raise any one of the four interrupts. + pub fn enable_sm_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_inte().as_ptr(), 1 << (id + 8)); + } + } + + /// Disable interrupts raised by state machines. + /// + /// See [`Self::enable_sm_interrupt`] for info about the index. + pub fn disable_sm_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear(self.irq().irq_inte().as_ptr(), 1 << (id + 8)); + } + } + + /// Force state machine interrupt. + /// + /// Note that this doesn't affect the state seen by the state machine. For that, see [`PIO::force_irq`]. + /// + /// + /// + /// See [`Self::enable_sm_interrupt`] for info about the index. + pub fn force_sm_interrupt(&self, id: u8, set: bool) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + if set { + write_bitmask_set(self.irq().irq_intf().as_ptr(), 1 << (id + 8)); + } else { + write_bitmask_clear(self.irq().irq_intf().as_ptr(), 1 << (id + 8)); + } + } + } + + /// Enable TX FIFO not full interrupt. + /// + /// Each of the 4 state machines have their own TX FIFO. This interrupt is raised when the TX FIFO is not full, i.e. + /// one could push more data to it. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn enable_tx_not_full_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_inte().as_ptr(), 1 << (id + 4)); + } + } + + /// Disable TX FIFO not full interrupt. + /// + /// See [`Self::enable_tx_not_full_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn disable_tx_not_full_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear(self.irq().irq_inte().as_ptr(), 1 << (id + 4)); + } + } + + /// Force TX FIFO not full interrupt. + /// + /// See [`Self::enable_tx_not_full_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn force_tx_not_full_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_intf().as_ptr(), 1 << (id + 4)); + } + } + + /// Enable RX FIFO not empty interrupt. + /// + /// Each of the 4 state machines have their own RX FIFO. This interrupt is raised when the RX FIFO is not empty, + /// i.e. one could read more data from it. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn enable_rx_not_empty_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_inte().as_ptr(), 1 << id); + } + } + + /// Disable RX FIFO not empty interrupt. + /// + /// See [`Self::enable_rx_not_empty_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn disable_rx_not_empty_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear(self.irq().irq_inte().as_ptr(), 1 << id); + } + } + + /// Force RX FIFO not empty interrupt. + /// + /// See [`Self::enable_rx_not_empty_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn force_rx_not_empty_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_intf().as_ptr(), 1 << id); + } + } + + /// Get the raw interrupt state. + /// + /// This is the state of the interrupts without interrupt masking and forcing. + pub fn raw(&self) -> InterruptState { + InterruptState( + // Safety: Read only access without side effect + unsafe { self.block().intr().read().bits() }, + ) + } + + /// Get the interrupt state. + /// + /// This is the state of the interrupts after interrupt masking and forcing. + pub fn state(&self) -> InterruptState { + InterruptState( + // Safety: Read only access without side effect + unsafe { self.irq().irq_ints().read().bits() }, + ) + } + + unsafe fn block(&self) -> &RegisterBlock { + &*self.block + } + + unsafe fn irq(&self) -> &crate::pac::pio0::SM_IRQ { + self.block().sm_irq(IRQ) + } +} + +/// Provides easy access for decoding PIO's interrupt state. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InterruptState(u32); + +macro_rules! raw_interrupt_accessor { + ($name:ident, $doc:literal, $idx:expr) => { + #[doc = concat!("Check whether interrupt ", $doc, " has been raised.")] + pub fn $name(self) -> bool { + self.0 & (1 << $idx) != 0 + } + }; +} +impl InterruptState { + raw_interrupt_accessor!(sm0_rx_not_empty, "SM0_RXNEMPTY", 0); + raw_interrupt_accessor!(sm1_rx_not_empty, "SM1_RXNEMPTY", 1); + raw_interrupt_accessor!(sm2_rx_not_empty, "SM2_RXNEMPTY", 2); + raw_interrupt_accessor!(sm3_rx_not_empty, "SM3_RXNEMPTY", 3); + + raw_interrupt_accessor!(sm0_tx_not_full, "SM0_TXNFULL", 4); + raw_interrupt_accessor!(sm1_tx_not_full, "SM1_TXNFULL", 5); + raw_interrupt_accessor!(sm2_tx_not_full, "SM2_TXNFULL", 6); + raw_interrupt_accessor!(sm3_tx_not_full, "SM3_TXNFULL", 7); + + raw_interrupt_accessor!(sm0, "SM0", 8); + raw_interrupt_accessor!(sm1, "SM1", 9); + raw_interrupt_accessor!(sm2, "SM2", 10); + raw_interrupt_accessor!(sm3, "SM3", 11); +} + +/// Comparison used for `mov x, status` instruction. +#[derive(Debug, Clone, Copy)] +pub enum MovStatusConfig { + /// The `mov x, status` instruction returns all ones if TX FIFO level is below the set status, otherwise all zeros. + Tx(u8), + /// The `mov x, status` instruction returns all ones if RX FIFO level is below the set status, otherwise all zeros. + Rx(u8), + /// The `mov x, status` instruction returns all ones if the indexed IRQ flag is raised, otherwise all-zeroes + Irq(u8), +} + +/// Shift direction for input and output shifting. +#[derive(Debug, Clone, Copy)] +pub enum ShiftDirection { + /// Shift register to left. + Left, + /// Shift register to right. + Right, +} + +impl ShiftDirection { + fn bit(self) -> bool { + match self { + Self::Left => false, + Self::Right => true, + } + } +} + +/// Builder to deploy a fully configured PIO program on one of the state +/// machines. +#[derive(Debug)] +pub struct PIOBuilder

{ + /// Clock divisor. + clock_divisor: (u16, u8), + + /// Program location and configuration. + program: InstalledProgram

, + /// GPIO pin used by `jmp pin` instruction. + jmp_pin: u8, + + /// Continuously assert the most recent OUT/SET to the pins. + out_sticky: bool, + /// Use a bit of OUT data as an auxiliary write enable. + /// + /// When [`out_sticky`](Self::out_sticky) is enabled, setting the bit to 0 deasserts for that instr. + inline_out: Option, + /// Config for `mov x, status` instruction. + mov_status: MovStatusConfig, + + /// Config for FIFO joining. + fifo_join: Buffers, + + /// Number of bits shifted out of `OSR` before autopull or conditional pull will take place. + pull_threshold: u8, + /// Number of bits shifted into `ISR` before autopush or conditional push will take place. + push_threshold: u8, + /// Shift direction for `OUT` instruction. + out_shiftdir: ShiftDirection, + /// Shift direction for `IN` instruction. + in_shiftdir: ShiftDirection, + /// Enable autopull. + autopull: bool, + /// Enable autopush. + autopush: bool, + /// Number of pins which are not masked to 0 when read by an `IN PINS`, `WAIT PIN` or `MOV x, PINS` instruction. + in_count: u8, + + /// Number of pins asserted by a `SET`. + set_count: u8, + /// Number of pins asserted by an `OUT PINS`, `OUT PINDIRS` or `MOV PINS` instruction. + out_count: u8, + /// The first pin that is assigned in state machine's `IN` data bus. + in_base: u8, + /// The first pin that is affected by side-set operations. + side_set_base: u8, + /// The first pin that is affected by `SET PINS` or `SET PINDIRS` instructions. + set_base: u8, + /// The first pin that is affected by `OUT PINS`, `OUT PINDIRS` or `MOV PINS` instructions. + out_base: u8, +} + +/// Buffer sharing configuration. +#[derive(Debug, Clone, Copy)] +pub enum Buffers { + /// No sharing. + RxTx, + /// The memory of the RX FIFO is given to the TX FIFO to double its depth. + OnlyTx, + /// The memory of the TX FIFO is given to the RX FIFO to double its depth. + OnlyRx, +} + +/// Errors that occurred during `PIO::install`. +#[derive(Debug)] +pub enum InstallError { + /// There was not enough space for the instructions on the selected PIO. + NoSpace, +} + +impl PIOBuilder

{ + /// Set config settings based on information from the given [`InstalledProgram`]. + /// Additional configuration may be needed in addition to this. + /// + /// Note: This was formerly called `from_program`. The new function has + /// a different default shift direction, `ShiftDirection::Right`, matching + /// the hardware reset value. + pub fn from_installed_program(p: InstalledProgram

) -> Self { + PIOBuilder { + clock_divisor: (1, 0), + program: p, + jmp_pin: 0, + out_sticky: false, + inline_out: None, + mov_status: MovStatusConfig::Tx(0), + fifo_join: Buffers::RxTx, + pull_threshold: 0, + push_threshold: 0, + out_shiftdir: ShiftDirection::Right, + in_shiftdir: ShiftDirection::Right, + autopull: false, + autopush: false, + in_count: 0, + set_count: 5, + out_count: 0, + in_base: 0, + side_set_base: 0, + set_base: 0, + out_base: 0, + } + } + + /// Set config settings based on information from the given [`InstalledProgram`]. + /// Additional configuration may be needed in addition to this. + /// + /// Note: The shift direction for both input and output shift registers + /// defaults to `ShiftDirection::Left`, which is different from the + /// rp235x reset value. The alternative [`Self::from_installed_program`], + /// fixes this. + #[deprecated( + note = "please use `from_installed_program` instead and update shift direction if necessary" + )] + pub fn from_program(p: InstalledProgram

) -> Self { + PIOBuilder { + clock_divisor: (1, 0), + program: p, + jmp_pin: 0, + out_sticky: false, + inline_out: None, + mov_status: MovStatusConfig::Tx(0), + fifo_join: Buffers::RxTx, + pull_threshold: 0, + push_threshold: 0, + out_shiftdir: ShiftDirection::Left, + in_shiftdir: ShiftDirection::Left, + autopull: false, + autopush: false, + in_count: 0, + set_count: 5, + out_count: 0, + in_base: 0, + side_set_base: 0, + set_base: 0, + out_base: 0, + } + } + + /// Set the config for when the status register is set to true. + /// + /// See `MovStatusConfig` for more info. + pub fn set_mov_status_config(mut self, mov_status: MovStatusConfig) -> Self { + self.mov_status = mov_status; + + self + } + + /// Set the pins asserted by `SET` instruction. + /// + /// The least-significant bit of `SET` instruction asserts the state of the pin indicated by `base`, the next bit + /// asserts the state of the next pin, and so on up to `count` pins. The pin numbers are considered modulo 32. + pub fn set_pins(mut self, base: u8, count: u8) -> Self { + assert!(count <= 5); + self.set_base = base; + self.set_count = count; + self + } + + /// Set the pins asserted by `OUT` instruction. + /// + /// The least-significant bit of `OUT` instruction asserts the state of the pin indicated by `base`, the next bit + /// asserts the state of the next pin, and so on up to `count` pins. The pin numbers are considered modulo 32. + pub fn out_pins(mut self, base: u8, count: u8) -> Self { + assert!(count <= 32); + self.out_base = base; + self.out_count = count; + self + } + + /// Set the pins used by `IN` instruction. + /// + /// The `IN` instruction reads the least significant bit from the pin indicated by `base`, the next bit from the + /// next pin, and so on. The pin numbers are considered modulo 32. + pub fn in_pin_base(mut self, base: u8) -> Self { + self.in_base = base; + self + } + + /// Set the pin used by `JMP PIN` instruction. + /// + /// When the pin set by this function is high, the jump is taken, otherwise not. + pub fn jmp_pin(mut self, pin: u8) -> Self { + self.jmp_pin = pin; + self + } + + /// Set the pins used by side-set instructions. + /// + /// The least-significant side-set bit asserts the state of the pin indicated by `base`, the next bit asserts the + /// state of the next pin, and so on up to [`pio::SideSet::bits()`] bits as configured in + /// [`pio::Program`]. + pub fn side_set_pin_base(mut self, base: u8) -> Self { + self.side_set_base = base; + self + } + // TODO: Update documentation above. + + /// Set buffer sharing. + /// + /// See [`Buffers`] for more information. + pub fn buffers(mut self, buffers: Buffers) -> Self { + self.fifo_join = buffers; + self + } + + /// Set the clock divisor. + /// + /// The is based on the sys_clk. Set 1 for full speed. A clock divisor of `n` will cause the state machine to run 1 + /// cycle every `n` clock cycles. For small values of `n`, a fractional divisor may introduce unacceptable jitter. + #[deprecated( + since = "0.7.0", + note = "Pulls in floating points. Use the fixed point alternative: clock_divisor_fixed_point" + )] + pub fn clock_divisor(mut self, divisor: f32) -> Self { + self.clock_divisor = (divisor as u16, (divisor * 256.0) as u8); + self + } + + /// The clock is based on the `sys_clk` and will execute an instruction every `int + (frac/256)` ticks. + /// + /// A clock divisor of `n` will cause the state machine to run 1 cycle every `n` clock cycles. If the integer part + /// is 0 then the fractional part must be 0. This is interpreted by the device as the integer 65536. + /// + /// For small values of `int`, a fractional divisor may introduce unacceptable jitter. + pub fn clock_divisor_fixed_point(mut self, int: u16, frac: u8) -> Self { + assert!(int != 0 || frac == 0); + self.clock_divisor = (int, frac); + self + } + + /// Set the output sticky state. + /// + /// When the output is set to be sticky, the PIO hardware continuously asserts the most recent `OUT`/`SET` to the + /// pins. + pub fn out_sticky(mut self, out_sticky: bool) -> Self { + self.out_sticky = out_sticky; + self + } + + /// Set the inline `OUT` enable bit. + /// + /// When set to value, the given bit of `OUT` instruction's data is used as an auxiliary write enable. When used + /// with [`Self::out_sticky`], writes with enable 0 will deassert the latest pin write. + pub fn inline_out(mut self, inline_out: Option) -> Self { + self.inline_out = inline_out; + self + } + + /// Set the autopush state. + /// + /// When autopush is enabled, the `IN` instruction automatically pushes the data once the number of bits reaches + /// threshold set by [`Self::push_threshold`]. + pub fn autopush(mut self, autopush: bool) -> Self { + self.autopush = autopush; + self + } + + /// Set the number of pins which are not masked to 0 when read by an `IN PINS`, `WAIT PIN` or `MOV x, PINS` instruction. + /// + /// For example, an IN_COUNT of 5 means that the 5 LSBs of the IN pin group are + /// visible (bits 4:0), but the remaining 27 MSBs are masked to 0. A count of 32 is + /// encoded with a field value of 0, so the default behaviour is to not perform any + /// masking. + pub fn in_count(mut self, count: u8) -> Self { + self.in_count = count; + self + } + + /// Set the number of bits pushed into ISR before autopush or conditional push will take place. + pub fn push_threshold(mut self, threshold: u8) -> Self { + self.push_threshold = threshold; + self + } + + /// Set the autopull state. + /// + /// When autopull is enabled, the `OUT` instruction automatically pulls the data once the number of bits reaches + /// threshold set by [`Self::pull_threshold`]. + pub fn autopull(mut self, autopull: bool) -> Self { + self.autopull = autopull; + self + } + + /// Set the number of bits pulled from out of OSR before autopull or conditional pull will take place. + pub fn pull_threshold(mut self, threshold: u8) -> Self { + self.pull_threshold = threshold; + self + } + + /// Set the ISR shift direction for `IN` instruction. + /// + /// For example `ShiftDirection::Right` means that ISR is shifted to right, i.e. data enters from left. + pub fn in_shift_direction(mut self, direction: ShiftDirection) -> Self { + self.in_shiftdir = direction; + self + } + + /// Set the OSR shift direction for `OUT` instruction. + /// + /// For example `ShiftDirection::Right` means that OSR is shifted to right, i.e. data is taken from the right side. + pub fn out_shift_direction(mut self, direction: ShiftDirection) -> Self { + self.out_shiftdir = direction; + self + } + + /// Build the config and deploy it to a StateMachine. + #[allow(clippy::type_complexity)] // The return type cannot really be simplified. + pub fn build( + self, + mut sm: UninitStateMachine<(P, SM)>, + ) -> (StateMachine<(P, SM), Stopped>, Rx<(P, SM)>, Tx<(P, SM)>) { + let offset = self.program.offset; + + // Stop the SM + sm.set_enabled(false); + + // Write all configuration bits + sm.set_clock_divisor(self.clock_divisor.0, self.clock_divisor.1); + + // Safety: Only instance owning the SM + unsafe { + sm.sm().sm_execctrl().write(|w| { + w.side_en().bit(self.program.side_set.optional()); + w.side_pindir().bit(self.program.side_set.pindirs()); + + w.jmp_pin().bits(self.jmp_pin); + + if let Some(inline_out) = self.inline_out { + w.inline_out_en().bit(true); + w.out_en_sel().bits(inline_out); + } else { + w.inline_out_en().bit(false); + } + + w.out_sticky().bit(self.out_sticky); + + w.wrap_top().bits(offset + self.program.wrap.source); + w.wrap_bottom().bits(offset + self.program.wrap.target); + + let n = match self.mov_status { + MovStatusConfig::Tx(n) => { + w.status_sel().txlevel(); + n + } + MovStatusConfig::Rx(n) => { + w.status_sel().rxlevel(); + n + } + MovStatusConfig::Irq(n) => { + w.status_sel().irq(); + n + } + }; + w.status_n().bits(n) + }); + + sm.sm().sm_shiftctrl().write(|w| { + let (fjoin_rx, fjoin_tx) = match self.fifo_join { + Buffers::RxTx => (false, false), + Buffers::OnlyTx => (false, true), + Buffers::OnlyRx => (true, false), + }; + w.fjoin_rx().bit(fjoin_rx); + w.fjoin_tx().bit(fjoin_tx); + + // TODO: Encode 32 as zero, and error on 0 + w.pull_thresh().bits(self.pull_threshold); + w.push_thresh().bits(self.push_threshold); + + w.out_shiftdir().bit(self.out_shiftdir.bit()); + w.in_shiftdir().bit(self.in_shiftdir.bit()); + + w.autopull().bit(self.autopull); + w.autopush().bit(self.autopush); + + w.in_count().bits(self.in_count) + }); + + sm.sm().sm_pinctrl().write(|w| { + w.sideset_count().bits(self.program.side_set.bits()); + w.set_count().bits(self.set_count); + w.out_count().bits(self.out_count); + + w.in_base().bits(self.in_base); + w.sideset_base().bits(self.side_set_base); + w.set_base().bits(self.set_base); + w.out_base().bits(self.out_base) + }) + } + + // Restart SM and its clock + sm.restart(); + sm.reset_clock(); + + // Set starting location by forcing the state machine to execute a jmp + // to the beginning of the program we loaded in. + let instr = InstructionOperands::JMP { + condition: pio::JmpCondition::Always, + address: offset, + } + .encode(); + // Safety: Only instance owning the SM + unsafe { + sm.sm().sm_instr().write(|w| w.sm0_instr().bits(instr)); + } + + let rx = Rx { + block: sm.block, + _phantom: core::marker::PhantomData, + }; + let tx = Tx { + block: sm.block, + _phantom: core::marker::PhantomData, + }; + ( + StateMachine { + sm, + program: self.program, + _phantom: core::marker::PhantomData, + }, + rx, + tx, + ) + } +} diff --git a/rp-hal/rp235x-hal/src/pll.rs b/rp-hal/rp235x-hal/src/pll.rs new file mode 100644 index 0000000..70ec5d7 --- /dev/null +++ b/rp-hal/rp235x-hal/src/pll.rs @@ -0,0 +1,335 @@ +//! Phase-Locked Loops (PLL) +//! +//! See [Section 8.6](https://rptl.io/rp2350-datasheet#section_pll) for more details + +use core::{ + convert::{Infallible, TryInto}, + marker::PhantomData, + ops::{Deref, Range, RangeInclusive}, +}; + +use fugit::{HertzU32, RateExtU32}; + +use nb::Error::WouldBlock; + +use crate::{clocks::ClocksManager, pac::RESETS, resets::SubsystemReset, typelevel::Sealed}; + +/// State of the PLL +pub trait State: Sealed {} + +/// PLL is disabled. +pub struct Disabled { + refdiv: u8, + fbdiv: u16, + post_div1: u8, + post_div2: u8, + frequency: HertzU32, +} + +/// PLL is configured, started and locking into its designated frequency. +pub struct Locking { + post_div1: u8, + post_div2: u8, + frequency: HertzU32, +} + +/// PLL is locked : it delivers a steady frequency. +pub struct Locked { + frequency: HertzU32, +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Locked {} +impl Sealed for Locked {} +impl State for Locking {} +impl Sealed for Locking {} + +/// Trait to handle both underlying devices from the PAC (PLL_SYS & PLL_USB) +pub trait PhaseLockedLoopDevice: + Deref + SubsystemReset + Sealed +{ +} + +impl PhaseLockedLoopDevice for crate::pac::PLL_SYS {} +impl Sealed for crate::pac::PLL_SYS {} +impl PhaseLockedLoopDevice for crate::pac::PLL_USB {} +impl Sealed for crate::pac::PLL_USB {} + +/// A PLL. +pub struct PhaseLockedLoop { + device: D, + state: S, +} + +impl PhaseLockedLoop { + fn transition(self, state: To) -> PhaseLockedLoop { + PhaseLockedLoop { + device: self.device, + state, + } + } + + /// Releases the underlying device. + pub fn free(self) -> D { + self.device + } +} + +/// Error type for the PLL module. +/// +/// See [Section 8.6](https://rptl.io/rp2350-datasheet) for +/// details on constraints triggering these errors. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Proposed VCO frequency is out of range. + VcoFreqOutOfRange, + + /// Feedback Divider value is out of range. + FeedbackDivOutOfRange, + + /// Post Divider value is out of range. + PostDivOutOfRage, + + /// Reference Frequency is out of range. + RefFreqOutOfRange, + + /// Bad argument : overflows, bad conversion, ... + BadArgument, +} + +/// Parameters for a PLL. +pub struct PLLConfig { + /// Voltage Controlled Oscillator frequency. + pub vco_freq: HertzU32, + + /// Reference divider + pub refdiv: u8, + + /// Post Divider 1 + pub post_div1: u8, + + /// Post Divider 2 + pub post_div2: u8, +} + +/// Common configs for the two PLLs. Both assume the XOSC is cadenced at 12MHz ! +/// +/// See [Section 8.6.2](https://rptl.io/rp2350-datasheet) of the RP2350 datasheet. +pub mod common_configs { + use super::PLLConfig; + use fugit::HertzU32; + + /// Default, nominal configuration for PLL_SYS + pub const PLL_SYS_150MHZ: PLLConfig = PLLConfig { + vco_freq: HertzU32::MHz(1500), + refdiv: 1, + post_div1: 5, + post_div2: 2, + }; + + /// Default, nominal configuration for PLL_USB. + pub const PLL_USB_48MHZ: PLLConfig = PLLConfig { + vco_freq: HertzU32::MHz(1200), + refdiv: 1, + post_div1: 5, + post_div2: 5, + }; +} + +impl PhaseLockedLoop { + /// Instantiates a new Phase-Locked-Loop device. + pub fn new( + dev: D, + xosc_frequency: HertzU32, + config: PLLConfig, + ) -> Result, Error> { + const VCO_FREQ_RANGE: RangeInclusive = HertzU32::MHz(400)..=HertzU32::MHz(1_600); + const POSTDIV_RANGE: Range = 1..7; + const FBDIV_RANGE: Range = 16..320; + + let vco_freq = config.vco_freq; + + if !VCO_FREQ_RANGE.contains(&vco_freq) { + return Err(Error::VcoFreqOutOfRange); + } + + if !POSTDIV_RANGE.contains(&config.post_div1) || !POSTDIV_RANGE.contains(&config.post_div2) + { + return Err(Error::PostDivOutOfRage); + } + + let ref_freq_max_vco = (vco_freq.to_Hz() / 16).Hz(); + let ref_freq_range: Range = HertzU32::MHz(5)..ref_freq_max_vco; + + let ref_freq_hz: HertzU32 = xosc_frequency + .to_Hz() + .checked_div(u32::from(config.refdiv)) + .ok_or(Error::BadArgument)? + .Hz(); + + if !ref_freq_range.contains(&ref_freq_hz) { + return Err(Error::RefFreqOutOfRange); + } + + let fbdiv = vco_freq + .to_Hz() + .checked_div(ref_freq_hz.to_Hz()) + .ok_or(Error::BadArgument)?; + + let fbdiv: u16 = fbdiv.try_into().map_err(|_| Error::BadArgument)?; + + if !FBDIV_RANGE.contains(&fbdiv) { + return Err(Error::FeedbackDivOutOfRange); + } + + let refdiv = config.refdiv; + let post_div1 = config.post_div1; + let post_div2 = config.post_div2; + let frequency: HertzU32 = + (ref_freq_hz * u32::from(fbdiv)) / (u32::from(post_div1) * u32::from(post_div2)); + + Ok(PhaseLockedLoop { + state: Disabled { + refdiv, + fbdiv, + post_div1, + post_div2, + frequency, + }, + device: dev, + }) + } + + /// Configures and starts the PLL : it switches to Locking state. + pub fn initialize(self, resets: &mut crate::pac::RESETS) -> PhaseLockedLoop { + self.device.reset_bring_up(resets); + + // Turn off PLL in case it is already running + self.device.pwr().reset(); + self.device.fbdiv_int().reset(); + + self.device.cs().write(|w| unsafe { + w.refdiv().bits(self.state.refdiv); + w + }); + + self.device.fbdiv_int().write(|w| unsafe { + w.fbdiv_int().bits(self.state.fbdiv); + w + }); + + // Turn on PLL + self.device.pwr().modify(|_, w| { + w.pd().clear_bit(); + w.vcopd().clear_bit(); + w + }); + + let post_div1 = self.state.post_div1; + let post_div2 = self.state.post_div2; + let frequency = self.state.frequency; + + self.transition(Locking { + post_div1, + post_div2, + frequency, + }) + } +} + +/// A token that's given when the PLL is properly locked, so we can safely transition to the next state. +pub struct LockedPLLToken { + _private: PhantomData, +} + +impl PhaseLockedLoop { + /// Awaits locking of the PLL. + pub fn await_lock(&self) -> nb::Result, Infallible> { + if self.device.cs().read().lock().bit_is_clear() { + return Err(WouldBlock); + } + + Ok(LockedPLLToken { + _private: PhantomData, + }) + } + + /// Exchanges a token for a Locked PLL. + pub fn get_locked(self, _token: LockedPLLToken) -> PhaseLockedLoop { + // Set up post dividers + self.device.prim().write(|w| unsafe { + w.postdiv1().bits(self.state.post_div1); + w.postdiv2().bits(self.state.post_div2); + w + }); + + // Turn on post divider + self.device.pwr().modify(|_, w| { + w.postdivpd().clear_bit(); + w + }); + + let frequency = self.state.frequency; + + self.transition(Locked { frequency }) + } +} + +impl PhaseLockedLoop { + /// Get the operating frequency for the PLL + pub fn operating_frequency(&self) -> HertzU32 { + self.state.frequency + } + + /// Shut down the PLL. The returned PLL is configured the same as it was originally. + pub fn disable(self) -> PhaseLockedLoop { + let fbdiv = self.device.fbdiv_int().read().fbdiv_int().bits(); + let refdiv = self.device.cs().read().refdiv().bits(); + let prim = self.device.prim().read(); + let frequency = self.state.frequency; + + self.device.pwr().reset(); + self.device.fbdiv_int().reset(); + + self.transition(Disabled { + refdiv, + fbdiv, + post_div1: prim.postdiv1().bits(), + post_div2: prim.postdiv2().bits(), + frequency, + }) + } +} + +/// Blocking helper method to setup the PLL without going through all the steps. +pub fn setup_pll_blocking( + dev: D, + xosc_frequency: HertzU32, + config: PLLConfig, + clocks: &mut ClocksManager, + resets: &mut RESETS, +) -> Result, Error> { + // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. + nb::block!(clocks.system_clock.reset_source_await()).unwrap(); + + nb::block!(clocks.reference_clock.reset_source_await()).unwrap(); + + start_pll_blocking( + PhaseLockedLoop::new(dev, xosc_frequency.convert(), config)?, + resets, + ) +} + +/// Blocking helper method to (re)start a PLL. +pub fn start_pll_blocking( + disabled_pll: PhaseLockedLoop, + resets: &mut RESETS, +) -> Result, Error> { + let initialized_pll = disabled_pll.initialize(resets); + + let locked_pll_token = nb::block!(initialized_pll.await_lock()).unwrap(); + + Ok(initialized_pll.get_locked(locked_pll_token)) +} diff --git a/rp-hal/rp235x-hal/src/powman.rs b/rp-hal/rp235x-hal/src/powman.rs new file mode 100644 index 0000000..220127c --- /dev/null +++ b/rp-hal/rp235x-hal/src/powman.rs @@ -0,0 +1,662 @@ +//! POWMAN Support +//! +//! The POWMAN peripheral contains a mixture of functionality, including: +//! +//! * [x] An always-on timer (AOT) with alarm +//! * [ ] Voltage Regulator Control +//! * [ ] Brown-out detection +//! * [ ] Low-power oscillator control +//! * [ ] Using as GPIO as a time reference or wake-up signal +//! * [ ] The power-on statemachine, including last-power-on reason +//! +//! See [Section 6.5](https://rptl.io/rp2350-datasheet) of the RP2350 datasheet +//! for more details + +use crate::{ + gpio::{ + bank0::{Gpio12, Gpio14, Gpio20, Gpio22}, + DynPullType, FunctionSioInput, Pin, + }, + pac, +}; + +/// A frequency in kHz, represented as a fixed point 16.16 value +/// +/// The RP2350 needs the frequency in `(integer_kHz, frac_kHz)` where `frac_kHz` +/// goes from 0 to 65535 (and 32768 represents 0.5 kHz). This might seem odd, +/// but the AOT counts in milliseconds, so this is basically *integer number of +/// ticks per millisecond* and *fractional number of ticks per millisecond*. +/// +/// This type represents a frequency converted into that format. +/// +/// The easiest way to construct one is with [`FractionalFrequency::from_hz`]: +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct FractionalFrequency(u32); + +impl FractionalFrequency { + /// Create a new Fractional Frequency from any `fugit::Rate`. + pub const fn new( + rate: fugit::Rate, + ) -> FractionalFrequency { + // fugit has a `convert` method, but it has rounding problems. + // So we do it by hand. + let rd_times_ln: u64 = 65536u64 * NOM as u64; + let ld_times_rn: u64 = DENOM as u64 * 1000u64; + let divisor: u64 = gcd::binary_u64(ld_times_rn, rd_times_ln); + let rd_times_ln: u64 = rd_times_ln / divisor; + let ld_times_rn: u64 = ld_times_rn / divisor; + let raw = rate.raw() as u64 * rd_times_ln; + let raw = (raw + (ld_times_rn / 2)) / ld_times_rn; + FractionalFrequency(raw as u32) + } + + /// Create a new Fractional Frequency from an integer number in Hertz + /// + /// ```rust + /// # use rp235x_hal::powman::{AotClockSource, FractionalFrequency}; + /// let source = AotClockSource::Xosc(FractionalFrequency::from_hz(12_000_000)); + /// ``` + pub const fn from_hz(hz: u32) -> FractionalFrequency { + let hz: fugit::HertzU32 = fugit::HertzU32::from_raw(hz); + FractionalFrequency::new(hz) + } + + /// Convert to an integer value in Hz + /// + /// Note: this involves a loss of precision + pub const fn as_int_hz(self) -> u32 { + let (i, f) = self.to_registers(); + (i as u32 * 1000) + ((f as u32 * 1000) / 65536) + } + + /// Convert to an floating point value in Hz + pub fn as_float_hz(self) -> f32 { + let (i, f) = self.to_registers(); + (i as f32 + (f as f32) / 65536.0) * 1000.0 + } + + /// Construct a fractional frequency from the raw register contents + const fn from_registers(i: u16, f: u16) -> FractionalFrequency { + let raw = (i as u32) << 16 | (f as u32); + FractionalFrequency(raw) + } + + /// Convert to the raw register values + const fn to_registers(self) -> (u16, u16) { + let raw = self.0; + ((raw >> 16) as u16, raw as u16) + } +} + +impl core::fmt::Display for FractionalFrequency { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{} Hz", self.as_float_hz()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for FractionalFrequency { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{=u32} Hz", self.as_int_hz()) + } +} + +/// Specify which clock source the POWMAN Always-On-Timer is clocked from. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum AotClockSource { + /// On-Chip Crystal Oscillator (XOSC). + /// + /// When the chip core is powered, the tick source can be switched the + /// on-chip crystal oscillator for greater precision. + Xosc(FractionalFrequency), + /// On-Chip Low-Power Oscillator (LPOSC). + /// + /// The LPOSC frequency is not precise and may vary with voltage and + /// temperature + Lposc(FractionalFrequency), + /// External GPIO pin input to the Low-Power Oscillator (LPOSC). + /// + /// Uses the pin provided when the POWMAN driver was constructed. + GpioLpOsc(FractionalFrequency), + /// External 1 kHz GPIO pin. + /// + /// Uses the pin provided when the POWMAN driver was constructed. + Gpio1kHz, + /// External 1 Hz GPIO pin + /// + /// Uses the pin provided when the POWMAN driver was constructed. + Gpio1Hz, +} + +/// The ways in which frequency conversion can fail +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ClockSourceError { + /// Tried to construct an LPOSC frequency but it was too high + InvalidFrequency(FractionalFrequency), + /// Tried to pick an external clock source with no GPIO pin + MissingGpioPin, +} + +impl AotClockSource { + /// Create a clock source, using the XOSC running at the given frequency. + pub const fn new_xosc(freq: FractionalFrequency) -> AotClockSource { + AotClockSource::Xosc(freq) + } + + /// Create a clock source, using the LPOSC running at the given frequency. + /// + /// Gives an error if the frequency is above 256 kHz. + pub const fn new_lposc(freq: FractionalFrequency) -> Result { + if freq.0 >= 0x0100_0000 { + // LPOSC only goes up to 256 kHz + return Err(ClockSourceError::InvalidFrequency(freq)); + } + Ok(AotClockSource::Lposc(freq)) + } +} + +impl core::fmt::Display for AotClockSource { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + AotClockSource::Xosc(frac) => { + write!(f, "Crystal @ {}", frac) + } + AotClockSource::Lposc(frac) => { + write!(f, "On-Chip LowPower @ {}", frac) + } + AotClockSource::GpioLpOsc(frac) => { + write!(f, "GPIO LowPower @ {}", frac) + } + AotClockSource::Gpio1kHz => write!(f, "GPIO 1kHz"), + AotClockSource::Gpio1Hz => write!(f, "GPIO 1 Hz"), + } + } +} + +/// A bit in the POWMAN.TIMER register. +/// +/// We can't use the svd2rust API because we need to also set a key. +#[repr(u16)] +enum TimerBit { + Run = 1 << 1, + Clear = 1 << 2, + // 3 is reserved + AlarmEnable = 1 << 4, + // TIMER_POWERUP_ON_ALARM = 1 << 5, + AlarmRinging = 1 << 6, + // 7 is reserved + UseLposc = 1 << 8, + UseXosc = 1 << 9, + Use1khz = 1 << 10, + // 11:12 is reserved + Use1hz = 1 << 13, +} + +/// Describes a pin we can use as an AOT clock input +pub enum ClockPin { + /// Using GPIO 12 as clock input + Pin12(Pin), + /// Using GPIO 14 as clock input + Pin14(Pin), + /// Using GPIO 20 as clock input + Pin20(Pin), + /// Using GPIO 22 as clock input + Pin22(Pin), +} + +/// The Power Management device +pub struct Powman { + device: pac::POWMAN, + aot_clock_pin: Option, +} + +impl Powman { + const KEY_VALUE: u32 = 0x5AFE_0000; + const EXT_TIME_REF_DRIVE_LPCLK: u16 = 1 << 4; + const INT_TIMER: u32 = 1 << 1; + + /// Create a new AOT from the from the underlying POWMAN device. + pub fn new(device: pac::POWMAN, aot_clock_pin: Option) -> Powman { + // we set this once, and can ignore it throughout the rest of the driver + let ext_time_ref = device.ext_time_ref().as_ptr(); + match aot_clock_pin { + None => {} + Some(ClockPin::Pin12(_)) => { + unsafe { Self::powman_write(ext_time_ref, 0) }; + } + Some(ClockPin::Pin14(_)) => { + unsafe { Self::powman_write(ext_time_ref, 2) }; + } + Some(ClockPin::Pin20(_)) => { + unsafe { Self::powman_write(ext_time_ref, 1) }; + } + Some(ClockPin::Pin22(_)) => { + unsafe { Self::powman_write(ext_time_ref, 3) }; + } + } + + Powman { + device, + aot_clock_pin, + } + } + + /// Releases the underlying device. + pub fn free(self) -> (pac::POWMAN, Option) { + (self.device, self.aot_clock_pin) + } + + /// Start the Always-On-Timer + pub fn aot_start(&mut self) { + self.timer_set_bit(TimerBit::Run); + } + + /// Configure the clock source for the Always-On-Timer + /// + /// If the timer is running, it will be stopped and restarted. + pub fn aot_set_clock(&mut self, source: AotClockSource) -> Result<(), ClockSourceError> { + // only allowed to change the clock speed when the timer is stopped + let was_running = self.aot_is_running(); + if was_running { + self.aot_stop(); + } + match source { + AotClockSource::Xosc(freq) => { + let (int_portion, frac_portion) = freq.to_registers(); + unsafe { + Self::powman_write(self.device.xosc_freq_khz_int().as_ptr(), int_portion); + Self::powman_write(self.device.xosc_freq_khz_frac().as_ptr(), frac_portion); + self.timer_set_bit(TimerBit::UseXosc); + } + if was_running { + self.aot_start(); + // wait for change + while self.device.timer().read().using_xosc() != true { + core::hint::spin_loop(); + } + } + } + AotClockSource::Lposc(freq) => { + let (int_portion, frac_portion) = freq.to_registers(); + unsafe { + Self::powman_write(self.device.lposc_freq_khz_int().as_ptr(), int_portion); + Self::powman_write(self.device.lposc_freq_khz_frac().as_ptr(), frac_portion); + self.timer_set_bit(TimerBit::UseLposc); + Self::powman_clear_bits( + self.device.ext_time_ref().as_ptr(), + Self::EXT_TIME_REF_DRIVE_LPCLK, + ); + } + if was_running { + self.aot_start(); + // wait for change + while self.device.timer().read().using_lposc() != true { + core::hint::spin_loop(); + } + } + } + AotClockSource::GpioLpOsc(freq) => { + if self.aot_clock_pin.is_none() { + return Err(ClockSourceError::MissingGpioPin); + } + let (int_portion, frac_portion) = freq.to_registers(); + unsafe { + Self::powman_write(self.device.lposc_freq_khz_int().as_ptr(), int_portion); + Self::powman_write(self.device.lposc_freq_khz_frac().as_ptr(), frac_portion); + self.timer_set_bit(TimerBit::UseLposc); + // Use the selected GPIO to drive the 32kHz low power clock, in place of LPOSC. + Self::powman_set_bits( + self.device.ext_time_ref().as_ptr(), + Self::EXT_TIME_REF_DRIVE_LPCLK, + ); + } + if was_running { + self.aot_start(); + // wait for change + while self.device.timer().read().using_gpio_1khz() != true { + core::hint::spin_loop(); + } + } + } + AotClockSource::Gpio1kHz => { + if self.aot_clock_pin.is_none() { + return Err(ClockSourceError::MissingGpioPin); + } + self.timer_set_bit(TimerBit::Use1khz); + if was_running { + self.aot_start(); + // wait for change + while self.device.timer().read().using_gpio_1khz() != true { + core::hint::spin_loop(); + } + } + } + AotClockSource::Gpio1Hz => { + if self.aot_clock_pin.is_none() { + return Err(ClockSourceError::MissingGpioPin); + } + self.timer_set_bit(TimerBit::Use1hz); + if was_running { + self.aot_start(); + // wait for change + while self.device.timer().read().using_gpio_1hz() != true { + core::hint::spin_loop(); + } + } + } + } + Ok(()) + } + + /// Get the current Always-On-Timer clock source + /// + /// You may get `None` if the AOT is not running. + pub fn aot_get_clock(&mut self) -> Option { + let status = self.device.timer().read(); + if status.using_gpio_1hz().bit_is_set() { + Some(AotClockSource::Gpio1Hz) + } else if status.using_gpio_1khz().bit_is_set() { + Some(AotClockSource::Gpio1kHz) + } else if status.using_lposc().bit_is_set() { + let int_portion = self.device.lposc_freq_khz_int().read().bits() as u16; + let frac_portion = self.device.lposc_freq_khz_frac().read().bits() as u16; + Some(AotClockSource::Lposc(FractionalFrequency::from_registers( + int_portion, + frac_portion, + ))) + } else if status.using_xosc().bit_is_set() { + let int_portion = self.device.xosc_freq_khz_int().read().bits() as u16; + let frac_portion = self.device.xosc_freq_khz_frac().read().bits() as u16; + Some(AotClockSource::Xosc(FractionalFrequency::from_registers( + int_portion, + frac_portion, + ))) + } else { + None + } + } + + /// Stop the Always-On-Timer + pub fn aot_stop(&self) { + self.timer_clear_bit(TimerBit::Run); + } + + /// Is the Always-On-Timer running + pub fn aot_is_running(&self) -> bool { + let status = self.device.timer().read(); + status.run().bit_is_set() + } + + /// Get the time from the Always-On-Timer + pub fn aot_get_time(&self) -> u64 { + // keep reading until we do NOT cross a 32-bit boundary + loop { + let upper1 = self.device.read_time_upper().read().bits(); + let lower = self.device.read_time_lower().read().bits(); + let upper2 = self.device.read_time_upper().read().bits(); + if upper1 == upper2 { + // we did not cross a boundary + return u64::from(upper1) << 32 | u64::from(lower); + } + } + } + + /// Get the alarm time from the Always-On-Timer + pub fn aot_get_alarm(&self) -> u64 { + let alarm0 = self + .device + .alarm_time_15to0() + .read() + .alarm_time_15to0() + .bits() as u64; + let alarm1 = self + .device + .alarm_time_31to16() + .read() + .alarm_time_31to16() + .bits() as u64; + let alarm2 = self + .device + .alarm_time_47to32() + .read() + .alarm_time_47to32() + .bits() as u64; + let alarm3 = self + .device + .alarm_time_63to48() + .read() + .alarm_time_63to48() + .bits() as u64; + alarm3 << 48 | alarm2 << 32 | alarm1 << 16 | alarm0 + } + + /// Clear the Always-On-Timer + /// + /// If the timer is running, it will NOT be stopped. + pub fn aot_clear(&self) { + self.timer_set_bit(TimerBit::Clear); + } + + /// Enable the alarm + pub fn aot_alarm_enable(&self) { + self.timer_set_bit(TimerBit::AlarmEnable); + } + + /// Disable the alarm + /// + /// Note: this is not the same as clearing a ringing alarm. This just makes + /// sure it won't fire in the future. + pub fn aot_alarm_disable(&self) { + self.timer_clear_bit(TimerBit::AlarmEnable); + } + + /// Is the alarm currently ringing? + /// + /// In other words, has it fired since we last cleared it? + pub fn aot_alarm_ringing(&self) -> bool { + self.device.timer().read().alarm().bit_is_set() + } + + /// Clear a ringing alarm + /// + /// Also disables the alarm, otherwise the alarm is likely to go off again + /// right away. + pub fn aot_alarm_clear(&self) { + self.aot_alarm_disable(); + // Early datasheets said write a 1 bit to clear the ringing alarm, but + // they're lying. You have to write a zero. + self.timer_clear_bit(TimerBit::AlarmRinging); + } + + /// Enable the AOT Alarm interrupt + pub fn aot_alarm_interrupt_enable(&self) { + // Doesn't need a key to write + let inte = self.device.inte().as_ptr(); + unsafe { + crate::atomic_register_access::write_bitmask_set(inte, Self::INT_TIMER); + } + } + + /// Disable the AOT Alarm interrupt + pub fn aot_alarm_interrupt_disable(&self) { + // Doesn't need a key to write + let inte = self.device.inte().as_ptr(); + unsafe { + crate::atomic_register_access::write_bitmask_clear(inte, Self::INT_TIMER); + } + } + + /// Disable the AOT Alarm interrupt, from a free function + pub fn static_aot_alarm_interrupt_disable() { + unsafe { + let ptr = pac::POWMAN::PTR as *mut u32; + let inte = ptr.offset(0xe4 >> 2); + crate::atomic_register_access::write_bitmask_clear(inte, Self::INT_TIMER); + } + } + + /// Has the AOT Alarm interrupt fired? + pub fn aot_alarm_interrupt_status(&self) -> bool { + self.device.ints().read().timer().bit_is_set() + } + + /// Set the time in the Always-On-Timer + /// + /// If the timer is running, it will be stopped and restarted. + pub fn aot_set_time(&mut self, time: u64) { + // only allowed to change the time when the timer is stopped + let was_running = self.aot_is_running(); + if was_running { + self.aot_stop(); + } + self.device.set_time_15to0().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.set_time_15to0().bits(time as u16); + } + w + }); + self.device.set_time_31to16().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.set_time_31to16().bits((time >> 16) as u16); + } + w + }); + self.device.set_time_47to32().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.set_time_47to32().bits((time >> 32) as u16); + } + w + }); + self.device.set_time_63to48().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.set_time_63to48().bits((time >> 48) as u16); + } + w + }); + if was_running { + self.aot_start(); + } + } + + /// Set the alarm in the Always-On-Timer and start it. + pub fn aot_set_alarm(&mut self, time: u64) { + self.aot_alarm_disable(); + self.device.alarm_time_15to0().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.alarm_time_15to0().bits(time as u16); + } + w + }); + self.device.alarm_time_31to16().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.alarm_time_31to16().bits((time >> 16) as u16); + } + w + }); + self.device.alarm_time_47to32().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.alarm_time_47to32().bits((time >> 32) as u16); + } + w + }); + self.device.alarm_time_63to48().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.alarm_time_63to48().bits((time >> 48) as u16); + } + w + }); + } + + /// Clear the specified bits in the TIMER register. + /// + /// For every `1` bit in `bitmask`, that bit is set to `0` in `POWMAN.TIMER`. + fn timer_clear_bit(&self, bit: TimerBit) { + let reg = self.device.timer().as_ptr(); + // # Safety + // + // svd2rust generated the address for us, so we know it's right + // + // This is an atomic operation, so &self is fine + unsafe { + Self::powman_clear_bits(reg, bit as u16); + } + } + + /// Set the specified bits in the TIMER register. + /// + /// For every `1` bit in `bitmask`, that bit is set to `1` in `POWMAN.TIMER`. + fn timer_set_bit(&self, bit: TimerBit) { + let reg = self.device.timer().as_ptr(); + // # Safety + // + // svd2rust generated the address for us, so we know it's right + // + // This is an atomic operation, so &self is fine + unsafe { + Self::powman_set_bits(reg, bit as u16); + } + } + + /// Write to a POWMAN protected register, using the key + unsafe fn powman_write(addr: *mut u32, bits: u16) { + let bits_with_key: u32 = Self::KEY_VALUE | u32::from(bits); + addr.write_volatile(bits_with_key); + } + + /// Set bits in a POWMAN protected register, using the key + unsafe fn powman_set_bits(addr: *mut u32, bits: u16) { + let bits_with_key: u32 = Self::KEY_VALUE | u32::from(bits); + crate::atomic_register_access::write_bitmask_set(addr, bits_with_key); + } + + /// Clear bits in a POWMAN protected register, using the key + unsafe fn powman_clear_bits(addr: *mut u32, bits: u16) { + let bits_with_key: u32 = Self::KEY_VALUE | u32::from(bits); + crate::atomic_register_access::write_bitmask_clear(addr, bits_with_key); + } +} + +#[cfg(test)] +mod tests { + use crate::fugit::RateExtU32; + + use super::*; + + #[test] + fn test_split_join_12mhz() { + let clock_speed: fugit::HertzU32 = 12_000_000.Hz(); + let freq = FractionalFrequency::new(clock_speed); + // As given in the datasheet as the default values for 12 MHz XOSC + assert_eq!(freq.to_registers(), (0x2ee0, 0x0000)); + assert_eq!(freq.as_int_hz(), 12_000_000); + assert_eq!(freq.as_float_hz(), 12_000_000.0); + let freq2 = FractionalFrequency::from_registers(0x2ee0, 0x0000); + assert_eq!(freq, freq2); + let freq3 = FractionalFrequency::from_hz(12_000_000); + assert_eq!(freq, freq3); + } + + #[test] + fn test_split_32768() { + let clock_speed: fugit::HertzU32 = 32768.Hz(); + let freq = FractionalFrequency::new(clock_speed); + // As given in the datasheet as the default values for 32.768 kHz LPOSC + assert_eq!(freq.to_registers(), (0x20, 0xc49c)); + assert_eq!(freq.as_int_hz(), 32768); + let delta = freq.as_float_hz() - 32768.0; + assert!(delta < 0.01, "{} != 32768.0", freq.as_float_hz()); + let freq2 = FractionalFrequency::from_registers(0x20, 0xc49c); + assert_eq!(freq, freq2); + let freq3 = FractionalFrequency::from_hz(32768); + assert_eq!(freq, freq3); + } +} diff --git a/rp-hal/rp235x-hal/src/prelude.rs b/rp-hal/rp235x-hal/src/prelude.rs new file mode 100644 index 0000000..447c72b --- /dev/null +++ b/rp-hal/rp235x-hal/src/prelude.rs @@ -0,0 +1,3 @@ +//! Prelude +pub use crate::clocks::Clock as _rphal_clocks_Clock; +pub use crate::pio::PIOExt as _rphal_pio_PIOExt; diff --git a/rp-hal/rp235x-hal/src/pwm/dyn_slice.rs b/rp-hal/rp235x-hal/src/pwm/dyn_slice.rs new file mode 100644 index 0000000..5cf144e --- /dev/null +++ b/rp-hal/rp235x-hal/src/pwm/dyn_slice.rs @@ -0,0 +1,30 @@ +//! Semi-internal enums mostly used in typelevel magic + +/// Value-level `struct` representing slice IDs +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct DynSliceId { + /// Slice id + pub num: u8, +} + +/// Slice modes +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DynSliceMode { + /// Count continuously whenever the slice is enabled + FreeRunning, + /// Count continuously when a high level is detected on the B pin + InputHighRunning, + /// Count once with each rising edge detected on the B pin + CountRisingEdge, + /// Count once with each falling edge detected on the B pin + CountFallingEdge, +} + +/// Channel ids +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DynChannelId { + /// Channel A + A, + /// Channel B + B, +} diff --git a/rp-hal/rp235x-hal/src/pwm/mod.rs b/rp-hal/rp235x-hal/src/pwm/mod.rs new file mode 100644 index 0000000..998d220 --- /dev/null +++ b/rp-hal/rp235x-hal/src/pwm/mod.rs @@ -0,0 +1,1081 @@ +//! Pulse Width Modulation (PWM) +//! +//! First you must create a Slices struct which contains all the pwm slices. +//! +//! ```no_run +//! use rp235x_hal::{ +//! prelude::*, +//! pwm::{InputHighRunning, Slices}, +//! }; +//! +//! let mut pac = rp235x_pac::Peripherals::take().unwrap(); +//! +//! // Init PWMs +//! let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! +//! // Configure PWM4 +//! let mut pwm = pwm_slices.pwm4; +//! pwm.set_ph_correct(); +//! pwm.enable(); +//! +//! // Set to run when b channel is high +//! let pwm = pwm.into_mode::(); +//! ``` +//! +//! Once you have the PWM slice struct, you can add individual pins: +//! +//! ```no_run +//! # use rp235x_hal::{prelude::*, gpio::Pins, Sio, pwm::{InputHighRunning, Slices}}; +//! # let mut pac = rp235x_pac::Peripherals::take().unwrap(); +//! # let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! # let mut pwm = pwm_slices.pwm4.into_mode::(); +//! # let mut pac = rp235x_pac::Peripherals::take().unwrap(); +//! # +//! # let sio = Sio::new(pac.SIO); +//! # let pins = Pins::new( +//! # pac.IO_BANK0, +//! # pac.PADS_BANK0, +//! # sio.gpio_bank0, +//! # &mut pac.RESETS, +//! # ); +//! # +//! use embedded_hal::pwm::SetDutyCycle; +//! +//! // Use B channel (which inputs from GPIO 25) +//! let mut channel_b = pwm.channel_b; +//! let channel_pin_b = channel_b.input_from(pins.gpio25); +//! +//! // Use A channel (which outputs to GPIO 24) +//! let mut channel_a = pwm.channel_a; +//! let channel_pin_a = channel_a.output_to(pins.gpio24); +//! +//! // Set duty cycle +//! channel_a.set_duty_cycle(0x00ff); +//! let max_duty_cycle = channel_a.max_duty_cycle(); +//! channel_a.set_inverted(); // Invert the output +//! channel_a.clr_inverted(); // Don't invert the output +//! ``` +//! +//! The following configuration options are also available: +//! +//! ```no_run +//! # use rp235x_hal::{prelude::*, pwm::Slices}; +//! # let mut pac = rp235x_pac::Peripherals::take().unwrap(); +//! # let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! # let mut pwm = pwm_slices.pwm4; +//! pwm.set_ph_correct(); // Run in phase correct mode +//! pwm.clr_ph_correct(); // Don't run in phase correct mode +//! +//! pwm.set_div_int(1u8); // To set integer part of clock divider +//! pwm.set_div_frac(0u8); // To set fractional part of clock divider +//! +//! pwm.get_top(); // To get the TOP register +//! pwm.set_top(u16::MAX); // To set the TOP register +//! ``` +//! +//! default_config() sets ph_correct to false, the clock divider to 1, does not invert the output, sets top to 65535, and resets the counter. +//! min_config() leaves those registers in the state they were before it was called (Careful, this can lead to unexpected behavior) +//! It's recommended to only call min_config() after calling default_config() on a pin that shares a PWM block. + +use core::convert::Infallible; +use core::marker::PhantomData; + +use embedded_dma::Word; +use embedded_hal::pwm::{ErrorType, SetDutyCycle}; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + dma::{EndlessWriteTarget, WriteTarget}, + gpio::{bank0::*, AnyPin, FunctionPwm, Pin, ValidFunction}, + pac::{self, dma::ch::ch_al1_ctrl::TREQ_SEL_A, PWM}, + resets::SubsystemReset, + typelevel::{Is, Sealed}, +}; + +pub mod dyn_slice; +pub use dyn_slice::*; + +mod reg; + +use reg::RegisterInterface; + +/// Used to pin traits to a specific channel (A or B) +pub trait ChannelId: Sealed { + /// Corresponding [`DynChannelId`] + const DYN: DynChannelId; +} + +/// Channel A +/// +/// These are attached to the even gpio pins and can only do PWM output +pub enum A {} + +/// Channel B +/// +/// These are attached to the odd gpio pins and can do PWM output and edge counting for input +pub enum B {} + +impl ChannelId for A { + const DYN: DynChannelId = DynChannelId::A; +} +impl ChannelId for B { + const DYN: DynChannelId = DynChannelId::B; +} +impl Sealed for A {} +impl Sealed for B {} + +/// Counter is free-running, and will count continuously whenever the slice is enabled +pub struct FreeRunning; +/// Count continuously when a high level is detected on the B pin +pub struct InputHighRunning; +/// Count once with each rising edge detected on the B pin +pub struct CountRisingEdge; +/// Count once with each falling edge detected on the B pin +pub struct CountFallingEdge; + +/// Type-level marker for tracking which slice modes are valid for which slices +pub trait ValidSliceMode: Sealed + SliceMode {} + +/// Type-level marker for tracking which slice modes are valid for which slices +pub trait ValidSliceInputMode: Sealed + ValidSliceMode {} + +/// Mode for slice +pub trait SliceMode: Sealed + Sized { + /// Corresponding [`DynSliceMode`] + const DYN: DynSliceMode; +} + +impl Sealed for FreeRunning {} +impl SliceMode for FreeRunning { + const DYN: DynSliceMode = DynSliceMode::FreeRunning; +} +impl Sealed for InputHighRunning {} +impl SliceMode for InputHighRunning { + const DYN: DynSliceMode = DynSliceMode::InputHighRunning; +} +impl Sealed for CountRisingEdge {} +impl SliceMode for CountRisingEdge { + const DYN: DynSliceMode = DynSliceMode::CountRisingEdge; +} +impl Sealed for CountFallingEdge {} +impl SliceMode for CountFallingEdge { + const DYN: DynSliceMode = DynSliceMode::CountFallingEdge; +} + +impl ValidSliceMode for FreeRunning {} +impl ValidSliceMode for InputHighRunning {} +impl ValidSliceMode for CountRisingEdge {} +impl ValidSliceMode for CountFallingEdge {} +impl ValidSliceInputMode for InputHighRunning {} +impl ValidSliceInputMode for CountRisingEdge {} +impl ValidSliceInputMode for CountFallingEdge {} + +//============================================================================== +// Slice IDs +//============================================================================== + +/// Type-level `enum` for slice IDs +pub trait SliceId: Sealed { + /// Corresponding [`DynSliceId`] + const DYN: DynSliceId; + /// [`SliceMode`] at reset + type Reset; + + /// Get DREQ number of PWM wrap. + const WRAP_DREQ: u8 = TREQ_SEL_A::PWM_WRAP0 as u8 + Self::DYN.num; +} + +macro_rules! slice_id { + ($Id:ident, $NUM:literal, $reset : ident) => { + $crate::paste::paste! { + #[doc = "Slice ID representing slice " $NUM] + pub enum $Id {} + impl Sealed for $Id {} + impl SliceId for $Id { + type Reset = $reset; + const DYN: DynSliceId = DynSliceId { num: $NUM }; + } + } + }; +} + +//============================================================================== +// AnySlice +//============================================================================== + +/// Type class for [`Slice`] types +/// +/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for +/// [`Slice`] types. See the `AnyKind` documentation for more details on the +/// pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +/// [type class]: crate::typelevel#type-classes +pub trait AnySlice +where + Self: Sealed, + Self: Is>, + ::Mode: ValidSliceMode<::Id>, +{ + /// [`SliceId`] of the corresponding [`Slice`] + type Id: SliceId; + /// [`SliceMode`] of the corresponding [`Slice`] + type Mode: SliceMode; +} + +impl Sealed for Slice +where + S: SliceId, + M: ValidSliceMode, +{ +} + +impl AnySlice for Slice +where + S: SliceId, + M: ValidSliceMode, +{ + type Id = S; + type Mode = M; +} + +/// Type alias to recover the specific [`Slice`] type from an implementation of +/// [`AnySlice`] +/// +/// See the [`AnyKind`] documentation for more details on the pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +type SpecificSlice = Slice<::Id, ::Mode>; + +//============================================================================== +// Registers +//============================================================================== + +/// Provide a safe register interface for [`Slice`]s +/// +/// This `struct` takes ownership of a [`SliceId`] and provides an API to +/// access the corresponding registers. +struct Registers { + id: PhantomData, +} + +// [`Registers`] takes ownership of the [`SliceId`], and [`Slice`] guarantees that +// each slice is a singleton, so this implementation is safe. +unsafe impl RegisterInterface for Registers { + #[inline] + fn id(&self) -> DynSliceId { + I::DYN + } +} + +impl Registers { + /// Create a new instance of [`Registers`] + /// + /// # Safety + /// + /// Users must never create two simultaneous instances of this `struct` with + /// the same [`SliceId`] + #[inline] + unsafe fn new() -> Self { + Registers { id: PhantomData } + } + + /// Provide a type-level equivalent for the + /// [`RegisterInterface::change_mode`] method. + #[inline] + fn change_mode>(&mut self) { + RegisterInterface::do_change_mode(self, M::DYN); + } +} + +/// Pwm slice +pub struct Slice +where + I: SliceId, + M: ValidSliceMode, +{ + regs: Registers, + mode: PhantomData, + /// Channel A (always output) + pub channel_a: Channel, + /// Channel B (input or output) + pub channel_b: Channel, +} + +impl Slice +where + I: SliceId, + M: ValidSliceMode, +{ + /// Create a new [`Slice`] + /// + /// # Safety + /// + /// Each [`Slice`] must be a singleton. For a given [`SliceId`], there must be + /// at most one corresponding [`Slice`] in existence at any given time. + /// Violating this requirement is `unsafe`. + #[inline] + pub(crate) unsafe fn new() -> Slice { + Slice { + regs: Registers::new(), + mode: PhantomData, + channel_a: Channel::new(0), + channel_b: Channel::new(0), + } + } + + /// Convert the slice to the requested [`SliceMode`] + #[inline] + pub fn into_mode>(mut self) -> Slice { + if N::DYN != M::DYN { + self.regs.change_mode::(); + } + // Safe because we drop the existing slice + unsafe { Slice::new() } + } + + /// Set a default config for the slice + pub fn default_config(&mut self) { + self.regs.write_ph_correct(false); + self.regs.write_div_int(1); // No divisor + self.regs.write_div_frac(0); // No divisor + self.regs.write_inv_a(false); //Don't invert the channel + self.regs.write_inv_b(false); //Don't invert the channel + self.regs.write_top(0xfffe); // Wrap at 0xfffe, so cc = 0xffff can indicate 100% duty cycle + self.regs.write_ctr(0x0000); //Reset the counter + self.regs.write_cc_a(0); //Default duty cycle of 0% + self.regs.write_cc_b(0); //Default duty cycle of 0% + } + + /// Advance the phase with one count + /// + /// Counter must be running at less than full speed (div_int + div_frac / 16 > 1) + #[inline] + pub fn advance_phase(&mut self) { + self.regs.advance_phase() + } + + /// Retard the phase with one count + /// + /// Counter must be running at less than full speed (div_int + div_frac / 16 > 1) + #[inline] + pub fn retard_phase(&mut self) { + self.regs.retard_phase() + } + + /// Enable phase correct mode + #[inline] + pub fn set_ph_correct(&mut self) { + self.regs.write_ph_correct(true) + } + + /// Disables phase correct mode + #[inline] + pub fn clr_ph_correct(&mut self) { + self.regs.write_ph_correct(false) + } + + /// Enable slice + #[inline] + pub fn enable(&mut self) { + self.regs.write_enable(true); + } + + /// Disable slice + #[inline] + pub fn disable(&mut self) { + self.regs.write_enable(false) + } + + /// Sets the integer part of the clock divider + #[inline] + pub fn set_div_int(&mut self, value: u8) { + self.regs.write_div_int(value) + } + + /// Sets the fractional part of the clock divider + #[inline] + pub fn set_div_frac(&mut self, value: u8) { + self.regs.write_div_frac(value) + } + + /// Get the counter register value + #[inline] + pub fn get_counter(&self) -> u16 { + self.regs.read_ctr() + } + + /// Set the counter register value + #[inline] + pub fn set_counter(&mut self, value: u16) { + self.regs.write_ctr(value) + } + + /// Get the top register value + #[inline] + pub fn get_top(&self) -> u16 { + self.regs.read_top() + } + + /// Sets the top register value + /// + /// Don't set this to 0xffff if you need true 100% duty cycle: + /// + /// The CC register, which is used to configure the duty cycle, + /// must be set to TOP + 1 for 100% duty cycle, but also is a + /// 16 bit register. + /// + /// In case you do set TOP to 0xffff, [`SetDutyCycle::set_duty_cycle`] + /// will slightly violate the trait's documentation, as + /// `SetDutyCycle::set_duty_cycle_fully_on` and other calls that + /// should lead to 100% duty cycle will only reach a duty cycle of + /// about 99.998%. + #[inline] + pub fn set_top(&mut self, value: u16) { + self.regs.write_top(value) + } + + /// Create the interrupt bitmask corresponding to this slice + #[inline] + fn bitmask(&self) -> u32 { + 1 << I::DYN.num + } + + /// Enable the PWM_IRQ_WRAP interrupt when this slice overflows. + #[inline] + pub fn enable_interrupt0(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq0_inte().as_ptr(); + write_bitmask_set(reg, self.bitmask()); + } + } + + /// Disable the PWM_IRQ_WRAP interrupt for this slice. + #[inline] + pub fn disable_interrupt0(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq0_inte().as_ptr(); + write_bitmask_clear(reg, self.bitmask()); + }; + } + + /// Enable the second PWM_IRQ_WRAP interrupt when this slice overflows. + #[inline] + pub fn enable_interrupt1(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq1_inte().as_ptr(); + write_bitmask_set(reg, self.bitmask()); + } + } + + /// Disable the second PWM_IRQ_WRAP interrupt for this slice. + #[inline] + pub fn disable_interrupt1(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq1_inte().as_ptr(); + write_bitmask_clear(reg, self.bitmask()); + }; + } + + /// Did this slice trigger an overflow interrupt? + /// + /// This reports the raw interrupt flag, without considering masking or + /// forcing bits. It may return true even if the interrupt is disabled + /// or false even if the interrupt is forced. + #[inline] + pub fn has_overflown(&self) -> bool { + let mask = self.bitmask(); + unsafe { (*pac::PWM::ptr()).intr().read().bits() & mask == mask } + } + + /// Mark the interrupt handled for this slice. + #[inline] + pub fn clear_interrupt(&mut self) { + unsafe { (*pac::PWM::ptr()).intr().write(|w| w.bits(self.bitmask())) }; + } + + /// Force the interrupt. This bit is not cleared by hardware and must be manually cleared to + /// stop the interrupt from continuing to be asserted. + #[inline] + pub fn force_interrupt0(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq0_intf().as_ptr(); + write_bitmask_set(reg, self.bitmask()); + } + } + + /// Clear force interrupt. This bit is not cleared by hardware and must be manually cleared to + /// stop the interrupt from continuing to be asserted. + #[inline] + pub fn clear_force_interrupt0(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq0_intf().as_ptr(); + write_bitmask_clear(reg, self.bitmask()); + } + } + + /// Force the second interrupt. This bit is not cleared by hardware and must be manually cleared to + /// stop the interrupt from continuing to be asserted. + #[inline] + pub fn force_interrupt1(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq1_intf().as_ptr(); + write_bitmask_set(reg, self.bitmask()); + } + } + + /// Clear force second interrupt. This bit is not cleared by hardware and must be manually cleared to + /// stop the interrupt from continuing to be asserted. + #[inline] + pub fn clear_force_interrupt1(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq1_intf().as_ptr(); + write_bitmask_clear(reg, self.bitmask()); + } + } +} + +macro_rules! pwm { + ($PWMX:ident, [ + $($SXi:ident: ($slice:literal, [$($pin_a:ident, $pin_b:ident),*], $i:expr)),+ + ]) => { + $( + slice_id!($SXi, $slice, FreeRunning); + + $( + impl ValidPwmOutputPin<$SXi, A> for $pin_a {} + impl ValidPwmOutputPin<$SXi, B> for $pin_b {} + impl ValidPwmInputPin<$SXi> for $pin_b {} + )* + )+ + + $crate::paste::paste!{ + + /// Collection of all the individual [`Slices`]s + pub struct Slices { + _pwm: $PWMX, + $( + #[doc = "Slice " $SXi] + pub [<$SXi:lower>] : Slice<$SXi,<$SXi as SliceId>::Reset>, + )+ + } + + impl Slices { + /// Take ownership of the PAC peripheral and split it into discrete [`Slice`]s + pub fn new(pwm: $PWMX, reset : &mut crate::pac::RESETS) -> Self { + pwm.reset_bring_up(reset); + unsafe { + Self { + _pwm: pwm, + $( + [<$SXi:lower>]: Slice::new(), + )+ + } + } + } + } + } + } +} + +pwm! { + PWM, [ + Pwm0: (0, [Gpio0, Gpio1, Gpio16, Gpio17], 0), + Pwm1: (1, [Gpio2, Gpio3, Gpio18, Gpio19], 1), + Pwm2: (2, [Gpio4, Gpio5, Gpio20, Gpio21], 2), + Pwm3: (3, [Gpio6, Gpio7, Gpio22, Gpio23], 3), + Pwm4: (4, [Gpio8, Gpio9, Gpio24, Gpio25], 4), + Pwm5: (5, [Gpio10, Gpio11, Gpio26, Gpio27], 5), + Pwm6: (6, [Gpio12, Gpio13, Gpio28, Gpio29], 6), + Pwm7: (7, [Gpio14, Gpio15], 7) + ] +} + +/// Marker trait for valid input pins (Channel B only) +pub trait ValidPwmInputPin: ValidFunction + Sealed {} +/// Marker trait for valid output pins +pub trait ValidPwmOutputPin: ValidFunction + Sealed {} + +impl Slices { + /// Free the pwm registers from the pwm hal struct while consuming it. + pub fn free(self) -> PWM { + self._pwm + } + + /// Enable multiple slices at the same time to make their counters sync up. + /// + /// You still need to call `slice` to get an actual slice + pub fn enable_simultaneous(&mut self, bits: u8) { + // Enable multiple slices at the same time + unsafe { + let reg = self._pwm.en().as_ptr(); + write_bitmask_set(reg, bits as u32); + } + } + + // /// Get pwm slice based on gpio pin + // pub fn borrow_mut_from_pin< + // S: SliceId, + // C: ChannelId, + // G: PinId + BankPinId + ValidPwmOutputPin, + // PM: PinMode + ValidPinMode, + // SM: ValidSliceMode, + // >(&mut self, _: &Pin) -> &mut Slice{ + // match S::DYN { + // DynSliceId{num} if num == 0 => &mut self.pwm0, + // DynSliceId{num} if num == 1 => &mut self.pwm1, + // DynSliceId{num} if num == 2 => &mut self.pwm2, + // DynSliceId{num} if num == 3 => &mut self.pwm3, + // DynSliceId{num} if num == 4 => &mut self.pwm4, + // DynSliceId{num} if num == 5 => &mut self.pwm5, + // DynSliceId{num} if num == 6 => &mut self.pwm6, + // DynSliceId{num} if num == 7 => &mut self.pwm7, + // _ => unreachable!() + // } + // } +} + +/// A Channel from the Pwm subsystem. +/// +/// Its attached to one of the eight slices and can be an A or B side channel +pub struct Channel { + regs: Registers, + slice_mode: PhantomData, + channel_id: PhantomData, + duty_cycle: u16, + enabled: bool, +} + +impl Channel { + pub(super) unsafe fn new(duty_cycle: u16) -> Self { + Channel { + regs: Registers::new(), + slice_mode: PhantomData, + channel_id: PhantomData, + duty_cycle, // stores the duty cycle while the channel is disabled + enabled: true, + } + } +} + +impl Sealed for Channel {} + +impl embedded_hal_0_2::PwmPin for Channel { + type Duty = u16; + + fn disable(&mut self) { + self.set_enabled(false); + } + + fn enable(&mut self) { + self.set_enabled(true); + } + + fn get_duty(&self) -> Self::Duty { + if self.enabled { + self.regs.read_cc_a() + } else { + self.duty_cycle + } + } + + fn get_max_duty(&self) -> Self::Duty { + SetDutyCycle::max_duty_cycle(self) + } + + fn set_duty(&mut self, duty: Self::Duty) { + let _ = SetDutyCycle::set_duty_cycle(self, duty); + } +} + +impl embedded_hal_0_2::PwmPin for Channel { + type Duty = u16; + + fn disable(&mut self) { + self.set_enabled(false); + } + + fn enable(&mut self) { + self.set_enabled(true); + } + + fn get_duty(&self) -> Self::Duty { + if self.enabled { + self.regs.read_cc_b() + } else { + self.duty_cycle + } + } + + fn get_max_duty(&self) -> Self::Duty { + SetDutyCycle::max_duty_cycle(self) + } + + fn set_duty(&mut self, duty: Self::Duty) { + let _ = SetDutyCycle::set_duty_cycle(self, duty); + } +} + +impl ErrorType for Channel { + type Error = Infallible; +} + +impl SetDutyCycle for Channel { + fn max_duty_cycle(&self) -> u16 { + self.regs.read_top().saturating_add(1) + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.duty_cycle = duty; + if self.enabled { + self.regs.write_cc_a(duty) + } + Ok(()) + } +} + +impl ErrorType for Channel { + type Error = Infallible; +} + +impl SetDutyCycle for Channel { + fn max_duty_cycle(&self) -> u16 { + self.regs.read_top().saturating_add(1) + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.duty_cycle = duty; + if self.enabled { + self.regs.write_cc_b(duty) + } + Ok(()) + } +} + +impl Channel { + /// Enable or disable the PWM channel + pub fn set_enabled(&mut self, enable: bool) { + if enable && !self.enabled { + // Restore the duty cycle. + self.regs.write_cc_a(self.duty_cycle); + self.enabled = true; + } else if !enable && self.enabled { + // We can't disable it without disturbing the other channel so this + // just sets the duty cycle to zero. + self.duty_cycle = self.regs.read_cc_a(); + self.regs.write_cc_a(0); + self.enabled = false; + } + } + + /// Capture a gpio pin and use it as pwm output for channel A + pub fn output_to(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmOutputPin, + { + pin.into().into_function() + } + + /// Invert channel output + #[inline] + pub fn set_inverted(&mut self) { + self.regs.write_inv_a(true) + } + + /// Stop inverting channel output + #[inline] + pub fn clr_inverted(&mut self) { + self.regs.write_inv_a(false) + } +} + +impl Channel { + /// Enable or disable the PWM channel + pub fn set_enabled(&mut self, enable: bool) { + if enable && !self.enabled { + // Restore the duty cycle. + self.regs.write_cc_b(self.duty_cycle); + self.enabled = true; + } else if !enable && self.enabled { + // We can't disable it without disturbing the other channel so this + // just sets the duty cycle to zero. + self.duty_cycle = self.regs.read_cc_b(); + self.regs.write_cc_b(0); + self.enabled = false; + } + } + + /// Capture a gpio pin and use it as pwm output for channel B + pub fn output_to(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmOutputPin, + { + pin.into().into_function() + } + + /// Invert channel output + #[inline] + pub fn set_inverted(&mut self) { + self.regs.write_inv_b(true) + } + + /// Stop inverting channel output + #[inline] + pub fn clr_inverted(&mut self) { + self.regs.write_inv_b(false) + } +} + +impl Channel +where + S::Mode: ValidSliceInputMode, +{ + /// Capture a gpio pin and use it as pwm input for channel B + pub fn input_from(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmInputPin, + { + pin.into().into_function() + } +} + +impl> Slice { + /// Capture a gpio pin and use it as pwm output + pub fn output_to(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmOutputPin, + { + pin.into().into_function() + } +} + +impl> Slice { + /// Capture a gpio pin and use it as pwm input for channel B + pub fn input_from(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmInputPin, + { + pin.into().into_function() + } +} + +/// Type representing DMA access to PWM cc register. +/// +/// Both channels are accessed together, because of narrow write replication. +/// +/// ```no_run +/// use rp235x_hal::singleton; +/// use rp235x_hal::dma::{double_buffer, DMAExt}; +/// use rp235x_hal::pwm::{CcFormat, SliceDmaWrite, Slices}; +/// +/// let mut pac = rp235x_pac::Peripherals::take().unwrap(); +/// +/// // Init PWMs +/// let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +/// +/// // Configure PWM4 +/// let mut pwm = pwm_slices.pwm4; +/// pwm.enable(); +/// +/// let buf = singleton!(: [CcFormat; 4] = [CcFormat{a: 0x1000, b: 0x9000}; 4]).unwrap(); +/// let buf2 = singleton!(: [CcFormat; 4] = [CcFormat{a: 0xf000, b: 0x5000}; 4]).unwrap(); +/// +/// let dma = pac.DMA.split(&mut pac.RESETS); +/// +/// let dma_pwm = SliceDmaWrite::from(pwm); +/// +/// let dma_conf = double_buffer::Config::new((dma.ch0, dma.ch1), buf, dma_pwm.cc); +/// ``` +pub struct SliceDmaWriteCc> { + slice: PhantomData, + mode: PhantomData, +} + +/// Type representing DMA access to PWM top register. +/// +/// ```no_run +/// use embedded_hal_0_2::prelude::*; +/// use rp235x_hal::singleton; +/// use rp235x_hal::dma::{double_buffer, DMAExt}; +/// use rp235x_hal::pwm::{SliceDmaWrite, Slices, TopFormat}; +/// +/// +/// let mut pac = rp235x_pac::Peripherals::take().unwrap(); +/// +/// // Init PWMs +/// let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +/// +/// // Configure PWM4 +/// let mut pwm = pwm_slices.pwm4; +/// pwm.enable(); +/// +/// // Just set to something mesurable. +/// pwm.channel_a.set_duty(0x1000); +/// pwm.channel_b.set_duty(0x1000); +/// +/// let buf = singleton!(: [TopFormat; 4] = [TopFormat::new(0x7fff); 4]).unwrap(); +/// let buf2 = singleton!(: [TopFormat; 4] = [TopFormat::new(0xfffe); 4]).unwrap(); +/// +/// let dma = pac.DMA.split(&mut pac.RESETS); +/// +/// // Reserve PWM slice for dma. +/// let dma_pwm = SliceDmaWrite::from(pwm); +/// +/// let dma_conf = double_buffer::Config::new((dma.ch0, dma.ch1), buf, dma_pwm.top); +/// ``` +pub struct SliceDmaWriteTop> { + slice: PhantomData, + mode: PhantomData, +} + +/// PWM slice while used for DMA writes. +/// ```no_run +/// use rp235x_hal::{ +/// prelude::*, +/// pwm::{SliceDmaWrite, Slices}, +/// }; +/// +/// let mut pac = rp235x_pac::Peripherals::take().unwrap(); +/// +/// // Init PWMs +/// let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +/// +/// // Configure PWM4 +/// let mut pwm = pwm_slices.pwm4; +/// pwm.enable(); +/// +/// // Use for DMA usage +/// let dma_pwm = SliceDmaWrite::from(pwm); +/// ``` +pub struct SliceDmaWrite> { + /// Part for top writes. + pub top: SliceDmaWriteTop, + + /// Part for cc writes. + pub cc: SliceDmaWriteCc, + slice: Slice, +} + +impl> From> for SliceDmaWrite { + fn from(value: Slice) -> Self { + Self { + slice: value, + top: SliceDmaWriteTop { + slice: PhantomData, + mode: PhantomData, + }, + cc: SliceDmaWriteCc { + slice: PhantomData, + mode: PhantomData, + }, + } + } +} + +impl> From> for Slice { + fn from(value: SliceDmaWrite) -> Self { + value.slice + } +} + +/// Format for DMA transfers to PWM CC register. +#[derive(Clone, Copy, Eq, PartialEq)] +#[repr(C)] +#[repr(align(4))] +pub struct CcFormat { + /// CC register part for channel a. + pub a: u16, + /// CC register part for channel b. + pub b: u16, +} + +unsafe impl Word for CcFormat {} + +/// Format for DMA transfers to PWM TOP register. +/// +/// It is forbidden to use it as DMA write destination, +/// it is safe but it might not be compatible with a future use of reserved register fields. +#[derive(Clone, Copy, Eq)] +#[repr(C)] +#[repr(align(4))] +pub struct TopFormat { + /// Valid register part. + pub top: u16, + /// Reserved part. + /// Should always be zero + reserved: u16, +} + +impl PartialEq for TopFormat { + fn eq(&self, other: &TopFormat) -> bool { + self.top == other.top + } +} + +impl TopFormat { + /// Create a valid value. + pub fn new(top: u16) -> Self { + TopFormat { top, reserved: 0 } + } +} + +impl Default for TopFormat { + fn default() -> Self { + Self::new(u16::MAX) + } +} + +unsafe impl Word for TopFormat {} + +/// Safety: tx_address_count points to a register which is always a valid +/// write target. +unsafe impl> WriteTarget for SliceDmaWriteCc { + type TransmittedWord = CcFormat; + + fn tx_treq() -> Option { + Some(S::WRAP_DREQ) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + let regs = Registers { + id: PhantomData:: {}, + }; + (regs.ch().cc().as_ptr() as u32, u32::MAX) + } + + fn tx_increment(&self) -> bool { + false + } +} + +/// Safety: tx_address_count points to a register which is always a valid +/// write target. +unsafe impl> WriteTarget for SliceDmaWriteTop { + type TransmittedWord = TopFormat; + + fn tx_treq() -> Option { + Some(S::WRAP_DREQ) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + let regs = Registers { + id: PhantomData:: {}, + }; + (regs.ch().top().as_ptr() as u32, u32::MAX) + } + + fn tx_increment(&self) -> bool { + false + } +} + +impl> EndlessWriteTarget for SliceDmaWriteCc {} +impl> EndlessWriteTarget for SliceDmaWriteTop {} diff --git a/rp-hal/rp235x-hal/src/pwm/reg.rs b/rp-hal/rp235x-hal/src/pwm/reg.rs new file mode 100644 index 0000000..20c2700 --- /dev/null +++ b/rp-hal/rp235x-hal/src/pwm/reg.rs @@ -0,0 +1,111 @@ +use crate::{ + pac::{self, pwm::CH}, + pwm::dyn_slice::{DynSliceId, DynSliceMode}, +}; + +/// # Safety +/// +/// Users should only implement the [`id`] function. No default function +/// implementations should be overridden. The implementing type must also have +/// "control" over the corresponding slice ID, i.e. it must guarantee that each +/// slice ID is a singleton +pub(super) unsafe trait RegisterInterface { + /// Provide a [`DynSliceId`] identifying the set of registers controlled by + /// this type. + fn id(&self) -> DynSliceId; + + #[inline] + fn ch(&self) -> &CH { + let num = self.id().num as usize; + unsafe { (*pac::PWM::ptr()).ch(num) } + } + + #[inline] + fn advance_phase(&mut self) { + self.ch().csr().modify(|_, w| w.ph_adv().set_bit()) + } + + #[inline] + fn retard_phase(&mut self) { + self.ch().csr().modify(|_, w| w.ph_ret().set_bit()) + } + + #[inline] + fn do_change_mode(&mut self, mode: DynSliceMode) { + self.ch().csr().modify(|_, w| match mode { + DynSliceMode::FreeRunning => w.divmode().div(), + DynSliceMode::InputHighRunning => w.divmode().level(), + DynSliceMode::CountRisingEdge => w.divmode().rise(), + DynSliceMode::CountFallingEdge => w.divmode().fall(), + }) + } + + #[inline] + fn write_inv_a(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.a_inv().bit(value)); + } + + #[inline] + fn write_inv_b(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.b_inv().bit(value)); + } + + #[inline] + fn write_ph_correct(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.ph_correct().bit(value)); + } + + #[inline] + fn write_enable(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.en().bit(value)); + } + + #[inline] + fn write_div_int(&mut self, value: u8) { + self.ch() + .div() + .modify(|_, w| unsafe { w.int().bits(value) }); + } + #[inline] + fn write_div_frac(&mut self, value: u8) { + self.ch() + .div() + .modify(|_, w| unsafe { w.frac().bits(value) }); + } + + #[inline] + fn write_ctr(&mut self, value: u16) { + self.ch().ctr().write(|w| unsafe { w.ctr().bits(value) }); + } + + #[inline] + fn read_ctr(&self) -> u16 { + self.ch().ctr().read().ctr().bits() + } + #[inline] + fn write_cc_a(&mut self, value: u16) { + self.ch().cc().modify(|_, w| unsafe { w.a().bits(value) }); + } + #[inline] + fn read_cc_a(&self) -> u16 { + self.ch().cc().read().a().bits() + } + + #[inline] + fn write_cc_b(&mut self, value: u16) { + self.ch().cc().modify(|_, w| unsafe { w.b().bits(value) }); + } + + #[inline] + fn read_cc_b(&self) -> u16 { + self.ch().cc().read().b().bits() + } + #[inline] + fn write_top(&mut self, value: u16) { + self.ch().top().write(|w| unsafe { w.top().bits(value) }); + } + #[inline] + fn read_top(&self) -> u16 { + self.ch().top().read().top().bits() + } +} diff --git a/rp-hal/rp235x-hal/src/reboot.rs b/rp-hal/rp235x-hal/src/reboot.rs new file mode 100644 index 0000000..cb6e481 --- /dev/null +++ b/rp-hal/rp235x-hal/src/reboot.rs @@ -0,0 +1,87 @@ +//! Functions for rebooting the chip using the ROM. + +/// Types of reboot we support +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RebootKind { + /// A normal reboot + Normal, + /// Boot like BOOTSEL is held down + BootSel { + /// Disable the picoboot interface + picoboot_disabled: bool, + /// Disable the Mass Storage Device interface + msd_disabled: bool, + }, + /// Boot into RAM + Ram { + /// The start of the RAM area to boot from + start_addr: *const u32, + /// The size in bytes of that area + size: usize, + }, + /// Reboot but prefer the flash partition you just wrote + FlashUpdate { + /// The start of the flash area you want to boot from + start_addr: *const u32, + }, + /// Reboot into the given Program Counter and Stack Pointer + PcSp { + /// The new program counter + pc: fn() -> !, + /// The new stack pointer + sp: *const u32, + }, +} + +/// Which architecture should we reboot into +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum RebootArch { + /// No architecture preference + Normal, + /// Prefer to boot into ARM mode + Arm, + /// Prefer to boot into RISC-V mode + Riscv, +} + +/// Reboot the chip +pub fn reboot(kind: RebootKind, arch: RebootArch) -> ! { + let options = match arch { + RebootArch::Normal => 0x0000, + RebootArch::Arm => 0x0010, + RebootArch::Riscv => 0x0020, + }; + + match kind { + RebootKind::Normal => { + #[allow(clippy::identity_op)] + crate::rom_data::reboot(0x0000 | options, 500, 0, 0); + } + RebootKind::BootSel { + picoboot_disabled, + msd_disabled, + } => { + let mut flags = 0; + if picoboot_disabled { + flags |= 2; + } + if msd_disabled { + flags |= 1; + } + crate::rom_data::reboot(0x0002 | options, 500, 0, flags); + } + RebootKind::Ram { start_addr, size } => { + crate::rom_data::reboot(0x0003 | options, 500, start_addr as u32, size as u32); + } + RebootKind::FlashUpdate { start_addr } => { + crate::rom_data::reboot(0x0004 | options, 500, start_addr as u32, 0); + } + RebootKind::PcSp { pc, sp } => { + crate::rom_data::reboot(0x000d | options, 500, pc as usize as u32, sp as u32); + } + } + // Wait for the reboot (might take a few ms - it's asynchronous) + loop { + core::hint::spin_loop(); + } +} diff --git a/rp-hal/rp235x-hal/src/resets.rs b/rp-hal/rp235x-hal/src/resets.rs new file mode 100644 index 0000000..1607110 --- /dev/null +++ b/rp-hal/rp235x-hal/src/resets.rs @@ -0,0 +1,54 @@ +//! Subsystem Resets +//! +//! See [Chapter 7](https://rptl.io/rp2350-datasheet#section_resets) for more details. + +mod private { + pub trait SubsystemReset { + fn reset_bring_up(&self, resets: &mut crate::pac::RESETS); + fn reset_bring_down(&self, resets: &mut crate::pac::RESETS); + } +} + +pub(crate) use private::SubsystemReset; + +macro_rules! generate_reset { + ($MODULE:ident, $module:ident) => { + impl SubsystemReset for $crate::pac::$MODULE { + fn reset_bring_up(&self, resets: &mut $crate::pac::RESETS) { + resets.reset().modify(|_, w| w.$module().clear_bit()); + while resets.reset_done().read().$module().bit_is_clear() {} + } + fn reset_bring_down(&self, resets: &mut $crate::pac::RESETS) { + resets.reset().modify(|_, w| w.$module().set_bit()); + } + } + }; +} + +// In datasheet order +generate_reset!(USB, usbctrl); +generate_reset!(UART1, uart1); +generate_reset!(UART0, uart0); +generate_reset!(TIMER0, timer0); +generate_reset!(TIMER1, timer1); +generate_reset!(TBMAN, tbman); +generate_reset!(SYSINFO, sysinfo); +generate_reset!(SYSCFG, syscfg); +generate_reset!(SPI1, spi1); +generate_reset!(SPI0, spi0); +generate_reset!(HSTX_CTRL, hstx); +generate_reset!(PWM, pwm); +generate_reset!(PLL_USB, pll_usb); +generate_reset!(PLL_SYS, pll_sys); +generate_reset!(PIO1, pio1); +generate_reset!(PIO0, pio0); +generate_reset!(PADS_QSPI, pads_qspi); +generate_reset!(PADS_BANK0, pads_bank0); +//generate_reset!(JTAG,jtag); // This doesn't seem to have an item in the pac +generate_reset!(IO_QSPI, io_qspi); +generate_reset!(IO_BANK0, io_bank0); +generate_reset!(I2C1, i2c1); +generate_reset!(I2C0, i2c0); +generate_reset!(DMA, dma); +generate_reset!(BUSCTRL, busctrl); +generate_reset!(ADC, adc); diff --git a/rp-hal/rp235x-hal/src/rom_data.rs b/rp-hal/rp235x-hal/src/rom_data.rs new file mode 100644 index 0000000..dcb8d98 --- /dev/null +++ b/rp-hal/rp235x-hal/src/rom_data.rs @@ -0,0 +1,1349 @@ +//! Functions and data from the RPI Bootrom. +//! +//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +//! RP2350 datasheet: +//! +//! > Whilst some ROM space is dedicated to the implementation of the boot +//! > sequence and USB/UART boot interfaces, the bootrom also contains public +//! > functions that provide useful RP2350 functionality that may be useful for +//! > any code or runtime running on the device + +/// A bootrom function table code. +pub type RomFnTableCode = [u8; 2]; + +/// This function searches for the tag which matches the mask. +type RomTableLookupFn = unsafe extern "C" fn(code: u32, mask: u32) -> usize; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_0016 as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_0018 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A2: *const u16 = ROM_TABLE_LOOKUP_A2; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A1: *const u32 = ROM_TABLE_LOOKUP_A1; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_7DFA as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A2: *const u16 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A1: *const u32 = 0x0000_7DF4 as _; + +/// Address of the version number of the ROM. +const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; + +#[allow(unused)] +mod rt_flags { + pub const FUNC_RISCV: u32 = 0x0001; + pub const FUNC_RISCV_FAR: u32 = 0x0003; + pub const FUNC_ARM_SEC: u32 = 0x0004; + // reserved for 32-bit pointer: 0x0008 + pub const FUNC_ARM_NONSEC: u32 = 0x0010; + // reserved for 32-bit pointer: 0x0020 + pub const DATA: u32 = 0x0040; + // reserved for 32-bit pointer: 0x0080 + #[cfg(all(target_arch = "arm", target_os = "none"))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_ARM_SEC; + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_RISCV; +} + +/// Retrieve rom content from a table using a code. +pub fn rom_table_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_TABLE_LOOKUP_A1.read() as usize + } else { + ROM_TABLE_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +/// Retrieve rom data content from a table using a code. +pub fn rom_data_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_DATA_LOOKUP_A1.read() as usize + } else { + ROM_DATA_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +/// bootrom API function return codes as defined by section 5.4.3 in the rp2350 data sheet +/// See: https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf +#[repr(i32)] +#[derive(Debug)] +pub enum BootRomApiErrorCode { + /// The operation was disallowed by a security constraint + NotPermitted = -4, + /// One or more parameters passed to the function is outside the range of + /// supported values; [`BootRomApiErrorCode::InvalidAddress`] and + /// [`BootRomApiErrorCode::BadAlignment`] are more specific errors. + InvalidArg = -5, + /// An address argument was out-of-bounds or was determined to be an address + /// that the caller may not access + InvalidAddress = -10, + /// An address passed to the function was not correctly aligned + BadAlignment = -11, + /// Something happened or failed to happen in the past, and consequently the + /// request cannot currently be serviced. + InvalidState = -12, + /// A user-allocated buffer was too small to hold the result or working state + /// of the function + BufferTooSmall = -13, + /// The call failed because another bootrom function must be called first. + PreconditionNotMet = -14, + /// Cached data was determined to be inconsistent with the full version of + /// the data it was copied from + ModifiedData = -15, + /// The contents of a data structure are invalid + InvalidData = -16, + /// An attempt was made to access something that does not exist; or, a search failed + NotFound = -17, + /// Modification is impossible based on current state; e.g. attempted to clear + /// an OTP bit. + UnsupportedModification = -18, + /// A required lock is not owned. See Section 5.4.4. + LockRequired = -19, + /// An unknown error + Unknown = -1, +} + +impl From for BootRomApiErrorCode { + fn from(value: i32) -> Self { + match value { + -4 => Self::NotPermitted, + -5 => Self::InvalidArg, + -10 => Self::InvalidAddress, + -11 => Self::BadAlignment, + -12 => Self::InvalidState, + -13 => Self::BufferTooSmall, + -14 => Self::PreconditionNotMet, + -15 => Self::ModifiedData, + -16 => Self::InvalidData, + -17 => Self::NotFound, + -18 => Self::UnsupportedModification, + -19 => Self::LockRequired, + _ => Self::Unknown, + } + } +} + +/// This module defines a safe api to access the `get_sys_info` bootrom function +#[allow(unused)] +pub mod sys_info_api { + use super::BootRomApiErrorCode; + + /// Flags that the `get_sys_info`/ rom function can take + #[repr(u32)] + pub enum GetSysInfoFlag { + /// The flag used to get a chip's unique identifier + ChipInfo = 0x0001, + /// The flag used to get the critical register's value + Critical = 0x0002, + /// The flag used to get the current running CPU Architecture + CpuInfo = 0x0004, + /// The flag used to get flash device info + FlashDevInfo = 0x0008, + /// The flag used to get the random 128 bit integer generated on boot + BootRandom = 0x0010, + // Ignore nonce for now since it can't/shouldn't be called anyway? + // Nonce = 0x0020, + /// The flag used to get boot diagnostic info + BootInfo = 0x0040, + } + + impl GetSysInfoFlag { + /// Returns the length of the buffer needed to hold the data for the related operation returned + /// by [`super::get_sys_info()`]. This includes the initial segment to indicate which flags + /// were supported. The underlying enum represent a bitmask and these masks can be OR'd + /// together, however the safe API only uses one at a time so adding sizes is not a concern. + const fn buffer_length(&self) -> usize { + match self { + GetSysInfoFlag::ChipInfo => 4, + GetSysInfoFlag::Critical + | GetSysInfoFlag::CpuInfo + | GetSysInfoFlag::FlashDevInfo => 2, + GetSysInfoFlag::BootRandom | GetSysInfoFlag::BootInfo => 5, + } + } + } + + /// The unqiue identifier for each chip as reported by [`chip_info`] + pub struct ChipInfo { + /// The value of the `CHIP_INFO_PACKAGE_SEL` register + pub package_sel: u32, + /// The device's id + pub device_id: u32, + /// The wafer's id + pub wafer_id: u32, + } + + impl From<[u32; 3]> for ChipInfo { + fn from(value: [u32; 3]) -> Self { + ChipInfo { + package_sel: value[0], + device_id: value[1], + wafer_id: value[2], + } + } + } + + /// The value held within the critical register as reported by [`otp_critical_register`] + pub struct OtpCriticalReg(u32); + + impl OtpCriticalReg { + /// Check if secure boot is enabled + pub fn secure_boot_enabled(&self) -> bool { + (self.0 & 0x1) == 1 + } + + /// Check if secure debug is disabled + pub fn secure_debug_disabled(&self) -> bool { + (self.0 & 0x2) >> 1 == 1 + } + + /// Check if debug is disabled + pub fn debug_disabled(&self) -> bool { + (self.0 & 0x4) >> 2 == 1 + } + + /// Check the value of `DEFAULT_ARCHSEL` + pub fn default_arch_sel(&self) -> bool { + (self.0 & 0x8) >> 3 == 1 + } + + /// Check if the glitch detector is enabled + pub fn glitch_detector_enabled(&self) -> bool { + (self.0 & 0x10) >> 4 == 1 + } + + /// Value of `GLITCH_DETECTOR_SENS + pub fn glitch_detector_sens(&self) -> u8 { + ((self.0 & 0x60) >> 5) as _ + } + + /// Check if ARM is disabled + pub fn arm_disabled(&self) -> bool { + (self.0 & 0x10000) >> 16 == 1 + } + + /// Check if Risc-V is disabled + pub fn risc_disabled(&self) -> bool { + (self.0 & 0x20000) >> 17 == 1 + } + } + + impl From<[u32; 1]> for OtpCriticalReg { + fn from(value: [u32; 1]) -> OtpCriticalReg { + OtpCriticalReg(value[0]) + } + } + + #[repr(u32)] + /// CPU architectures that might be running as reported by [`cpu_info`] + pub enum CpuInfo { + /// Arm CPU + Arm, + /// Risc-V CPU + Risc, + } + + impl From<[u32; 1]> for CpuInfo { + fn from(value: [u32; 1]) -> CpuInfo { + if value[0] == 0 { + CpuInfo::Arm + } else { + CpuInfo::Risc + } + } + } + + /// Flash device information as reported by [`flash_dev_info`] + pub struct FlashDevInfo(u32); + + /// A struct to represent possible byte sizes that may be reported in [`FlashDevInfo`] + #[repr(u32)] + pub enum FlashDevInfoSize { + /// 0 bytes + None, + /// 8 KiB + K8, + /// 16 KiB + K16, + /// 32 KiB + K32, + /// 64 KiB + K64, + /// 128 KiB + K128, + /// 256 KiB + K256, + /// 512 KiB + K512, + /// 1 MiB + M1, + /// 2 MiB + M2, + /// 4 Mib + M4, + /// 8 MiB + M8, + /// 16 MiB + M16, + /// Unknown size + Unknown, + } + + impl From for FlashDevInfoSize { + fn from(value: u32) -> Self { + if value > 0xc { + return Self::Unknown; + } + + unsafe { core::mem::transmute::(value) } + } + } + + impl FlashDevInfo { + /// GPIO Number to be used for the secondary flash chip. See datasheet section 13.9 + pub fn cs1_gpio(&self) -> u8 { + (self.0 & 0x1f) as _ + } + + /// Check if all attached devices support a block erase command with a command prefix of + /// `D8h`` + pub fn d8h_erase_supported(&self) -> bool { + (self.0 & 0x80) != 0 + } + + /// Flash/PSRAM size on chip select 0 + pub fn cs0_size(&self) -> FlashDevInfoSize { + FlashDevInfoSize::from((self.0 & 0xf00) >> 8) + } + + /// Flash/PSRAM size on chip select 1 + pub fn cs1_size(&self) -> FlashDevInfoSize { + FlashDevInfoSize::from((self.0 & 0xf000) >> 12) + } + } + + impl From<[u32; 1]> for FlashDevInfo { + fn from(value: [u32; 1]) -> FlashDevInfo { + FlashDevInfo(value[0]) + } + } + + /// 128 bit random integer generated per boot as reported by [`boot_random`] + pub struct BootRandom(pub u128); + + impl From<[u32; 4]> for BootRandom { + fn from(value: [u32; 4]) -> BootRandom { + let mut result = 0; + for word in value { + result = (result << 32) | u128::from(word); + } + BootRandom(result) + } + } + + // based on https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_bootrom/include/pico/bootrom.h + /// Boot diagnostic info as described in 5.4 under the `get_sys_info` function + pub struct BootInfo { + /// Information about which partition is being diagnosed + pub diagnostic_partition: PartitionIndex, + /// Type of boot that occurred + pub boot_type: BootType, + /// Whether it was a chained boot + pub chained: bool, + /// What partition the boot came from + pub partition: i8, + // could probably make a nicer api for tbyb, but documentation is eh so im holding off for now + /// Try Before You Buy info + pub tbyb_update_info: u8, + /// boot diagnostic flags for section A and section B + pub boot_diagnostic: u32, + /// Boot parameters 0 and 1 + pub boot_params: [u32; 2], + } + + /// Recen boot diagnostic partition + pub enum PartitionIndex { + /// A partition along with its number + Partition(u8), + /// None + None, + /// Slot0 + Slot0, + /// Slot1 + Slot1, + /// Image + Image, + /// Unknown + Unknown, + } + + impl From for PartitionIndex { + fn from(value: i8) -> Self { + if !(-4..=15).contains(&value) { + return Self::Unknown; + } + + match value { + -1 => Self::None, + -2 => Self::Slot0, + -3 => Self::Slot1, + -4 => Self::Image, + _ => Self::Partition(value as u8), + } + } + } + + /// The type of boot that occurred + pub enum BootType { + /// Normal + Normal, + /// bootsel + BootSel, + /// Ram image + RamImage, + /// Flash update + FlashUpdate, + /// pc_sp + PcSp, + /// Unknown + Unknown, + } + + impl From for BootType { + fn from(value: u8) -> Self { + match value { + 0 => Self::Normal, + 2 => Self::BootSel, + 3 => Self::RamImage, + 4 => Self::FlashUpdate, + 8..=15 => Self::PcSp, + _ => Self::Unknown, + } + } + } + + #[repr(u16)] + /// Diagnostic flags reported by the upper and lower words in [`BootInfo::boot_diagnostic`] + pub enum BootDiagnosticFlags { + /// The region was searched for a block loop + RegionSearched = 0x0001, + /// A block loop was found but it was invalid + InvalidBlockLoop = 0x0002, + /// A valid block loop was found (Blocks from a loop wholly contained within the region, and + /// the blocks have the correct structure. Each block consists of items whose sizes sum to + /// the size of the block) + ValidBlockLoop = 0x0004, + /// A valid IMAGE_DEF was found in the region. A valid IMAGE_DEF must parse correctly and must + /// be executable + ValidImageDef = 0x0008, + /// Whether a partition table is present. This partition table must have a correct structure + /// formed if [`BootDiagnosticFlags::ValidBlockLoop`] is set. If the partition table turns + /// out to be invalid, then [`BootDiagnosticFlags::InvalidBlockLoop`] is set too (thus both + /// [`BootDiagnosticFlags::ValidBlockLoop`] and [`BootDiagnosticFlags::InvalidBlockLoop`] + /// will both be set) + HasPartitionTable = 0x0010, + /// There was a choice of partition/slot and this one was considered. The first slot/partition + /// is chosen based on a number of factors. If the first choice fails verification, then the + /// other choice will be considered. + /// + /// * the version of the PARTITION_TABLE/IMAGE_DEF present in the slot/partition respectively. + /// * whether the slot/partition is the "update region" as per a FLASH_UPDATE reboot. + /// * whether an IMAGE_DEF is marked as "explicit buy" + Considered = 0x0020, + /// This slot/partition was chosen (or was the only choice) + Chosen = 0x0040, + /// if a signature is required for the PARTITION_TABLE (via OTP setting), then whether the + /// PARTITION_TABLE is signed with a key matching one of the four stored in OTP + PartitionTableMatchingKeyForVerify = 0x0080, + /// set if a hash value check could be performed. In the case a signature is required, this + /// value is identical to [`BootDiagnosticFlags::PartitionTableMatchingKeyForVerify`] + PartitionTableHashForVerify = 0x0100, + /// whether the PARTITION_TABLE passed verification (signature/hash if present/required) + PartitionTableVerifiedOk = 0x0200, + /// if a signature is required for the IMAGE_DEF due to secure boot, then whether the + /// IMAGE_DEF is signed with a key matching one of the four stored in OTP + ImageDefMatchingKeyForVerify = 0x0400, + /// set if a hash value check could be performed. In the case a signature is required, this + /// value is identical to [`BootDiagnosticFlags::ImageDefMatchingKeyForVerify`] + ImageDefHashForVerify = 0x0800, + /// whether the PARTITION_TABLE passed verification (signature/hash if present/required) and + /// any LOAD_MAP is valid + ImageDefVerifiedOk = 0x1000, + /// whether any code was copied into RAM due to a LOAD_MAP + LoadMapEntriesLoaded = 0x2000, + /// whether an IMAGE_DEF from this region was launched + ImageLaunched = 0x4000, + /// whether the IMAGE_DEF failed final checks before launching; these checks include + /// + /// * verification failed (if it hasn’t been verified earlier in the CONSIDERED phase). + /// * a problem occurred setting up any rolling window. + /// * the rollback version could not be set in OTP (if required in Secure mode) + /// * the image was marked as Non-secure + /// * the image was marked as "explicit buy", and this was a flash boot, but then region was + /// not the "flash update" region + /// * the image has the wrong architecture, but architecture auto-switch is disabled (or the + /// correct architecture is disabled) + ImageConditionFailure = 0x8000, + } + + impl From<[u32; 4]> for BootInfo { + fn from(value: [u32; 4]) -> Self { + let word0 = value[0]; + + BootInfo { + diagnostic_partition: PartitionIndex::from((word0 & 0xFF) as i8), + boot_type: BootType::from((word0 >> 8) as u8), + chained: (word0 >> 8) & 0x80 > 0, + partition: (word0 >> 16) as _, + tbyb_update_info: (word0 >> 24) as _, + boot_diagnostic: value[1], + boot_params: [value[2], value[3]], + } + } + } + + impl BootInfo { + fn check_flag(diagnostics: u16, flag: BootDiagnosticFlags) -> bool { + (diagnostics & flag as u16) != 0 + } + + /// Check if the diagnostic flag in section A (the lower word) is set + pub fn check_section_a_flag(&self, flag: BootDiagnosticFlags) -> bool { + Self::check_flag(self.boot_diagnostic as u16, flag) + } + + /// Check if the diagnostic flag in section B (the upper word) is set + pub fn check_section_b_flag(&self, flag: BootDiagnosticFlags) -> bool { + Self::check_flag((self.boot_diagnostic >> 8) as u16, flag) + } + } + + #[macro_export] + /// Generates a function with the following signature: + /// + /// ```rs + /// pub fn $function_name() -> Result, BootRomApiErrorCode> + /// ``` + /// + /// Which safely calls [`get_sys_info`](super::get_sys_info()) using the flag provided via + /// the `flag` argument. `flag` is an expression that must resolve to a const variant of + /// [`GetSysInfoFlag`] + macro_rules! declare_get_sys_info_function { + ($(#[$meta:meta])* $function_name:ident, $ok_ret_type:ty, $flag:expr) => { + $(#[$meta])* + pub fn $function_name() -> Result, BootRomApiErrorCode> { + const FLAG: GetSysInfoFlag = $flag; + const BUFFER_LEN: usize = FLAG.buffer_length(); + let mut buffer = [0u32; FLAG.buffer_length()]; + let result = + unsafe { super::get_sys_info(buffer.as_mut_ptr(), buffer.len(), FLAG as u32) }; + + if result < 0 { + return Err(BootRomApiErrorCode::from(result)); + } else if buffer[0] == 0 { + // The operation returned successfully but the flag wasn't supported + // for one reason or another + return Ok(None); + } + + Ok(Some(<$ok_ret_type>::from( + TryInto::<[u32; BUFFER_LEN - 1]>::try_into(&buffer[1..]).unwrap(), + ))) + } + }; + } + + #[macro_export] + #[cfg(all(target_arch = "arm", target_os = "none"))] + /// Generates a function with the following signature: + /// + /// ```rs + /// pub fn $function_name() -> Result, BootRomApiErrorCode> + /// ``` + /// + /// Which safely calls [`get_sys_info_ns`](super::get_sys_info_ns()) using the flag provided via + /// the `flag` argument. `flag` is an expression that must resolve to a const variant of + /// [`GetSysInfoFlag`] + macro_rules! declare_get_sys_info_ns_function { + ($(#[$meta:meta])* $function_name:ident, $ok_ret_type:ty, $flag:expr) => { + $(#[$meta])* + pub fn $function_name() -> Result, BootRomApiErrorCode> { + const FLAG: GetSysInfoFlag = $flag; + const BUFFER_LEN: usize = FLAG.buffer_length(); + let mut buffer = [0u32; FLAG.buffer_length()]; + let result = + unsafe { super::get_sys_info_ns(buffer.as_mut_ptr(), buffer.len(), FLAG as u32) }; + + if result < 0 { + return Err(BootRomApiErrorCode::from(result)); + } else if buffer[0] == 0 { + // The operation returned successfully but the flag wasn't supported + // for one reason or another + return Ok(None); + } + + Ok(Some(<$ok_ret_type>::from( + TryInto::<[u32; BUFFER_LEN - 1]>::try_into(&buffer[1..]).unwrap(), + ))) + } + }; + } + + declare_get_sys_info_function!( + /// Get the unique identifier for the chip + chip_info, ChipInfo, GetSysInfoFlag::ChipInfo + ); + + declare_get_sys_info_function!( + /// Get the value of the OTP critical register + otp_critical_register, + OtpCriticalReg, + GetSysInfoFlag::Critical + ); + + declare_get_sys_info_function!( + /// Get the current running CPU's info + cpu_info, CpuInfo, GetSysInfoFlag::CpuInfo + ); + + declare_get_sys_info_function!( + /// Get flash device info in the format of OTP FLASH_DEVINFO + flash_dev_info, FlashDevInfo, GetSysInfoFlag::FlashDevInfo + ); + + declare_get_sys_info_function!( + /// Get a 128-bit random number generated on each boot + boot_random, BootRandom, GetSysInfoFlag::BootRandom + ); + + declare_get_sys_info_function!( + /// Get diagnostic boot info + boot_info, BootInfo, GetSysInfoFlag::BootInfo + ); + + #[cfg(all(target_arch = "arm", target_os = "none"))] + declare_get_sys_info_ns_function!( + /// Get the unique identifier for the chip + chip_info_ns, ChipInfo, GetSysInfoFlag::ChipInfo + ); + + #[cfg(all(target_arch = "arm", target_os = "none"))] + declare_get_sys_info_ns_function!( + /// Get the value of the OTP critical register + otp_critical_register_ns, + OtpCriticalReg, + GetSysInfoFlag::Critical + ); + + #[cfg(all(target_arch = "arm", target_os = "none"))] + declare_get_sys_info_ns_function!( + /// Get the current running CPU's info + cpu_info_ns, CpuInfo, GetSysInfoFlag::CpuInfo + ); + + #[cfg(all(target_arch = "arm", target_os = "none"))] + declare_get_sys_info_ns_function!( + /// Get flash device info in the format of OTP FLASH_DEVINFO + flash_dev_info_ns, FlashDevInfo, GetSysInfoFlag::FlashDevInfo + ); + + #[cfg(all(target_arch = "arm", target_os = "none"))] + declare_get_sys_info_ns_function!( + /// Get a 128-bit random number generated on each boot + boot_random_ns, BootRandom, GetSysInfoFlag::BootRandom + ); + + #[cfg(all(target_arch = "arm", target_os = "none"))] + declare_get_sys_info_ns_function!( + /// Get diagnostic boot info + boot_info_ns, BootInfo, GetSysInfoFlag::BootInfo + ); +} + +macro_rules! declare_rom_function { + ( + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + pub extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; + + ( + $(#[$outer:meta])* + unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + /// # Safety + /// + /// This is a low-level C function. It may be difficult to call safely from + /// Rust. If in doubt, check the rp235x datasheet for details and do your own + /// safety evaluation. + pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; +} + +// **************** 5.5.7 Low-level Flash Commands **************** + +declare_rom_function! { + /// Restore all QSPI pad controls to their default state, and connect the + /// QMI peripheral to the QSPI pads. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn connect_internal_flash() -> () { + crate::rom_data::rom_table_lookup(*b"IF", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Initialise the QMI for serial operations (direct mode) + /// + /// Also initialise a basic XIP mode, where the QMI will perform 03h serial + /// read commands at low speed (CLKDIV=12) in response to XIP reads. + /// + /// Then, issue a sequence to the QSPI device on chip select 0, designed to + /// return it from continuous read mode ("XIP mode") and/or QPI mode to a + /// state where it will accept serial commands. This is necessary after + /// system reset to restore the QSPI device to a known state, because + /// resetting RP2350 does not reset attached QSPI devices. It is also + /// necessary when user code, having already performed some + /// continuous-read-mode or QPI-mode accesses, wishes to return the QSPI + /// device to a state where it will accept the serial erase and programming + /// commands issued by the bootrom’s flash access functions. + /// + /// If a GPIO for the secondary chip select is configured via FLASH_DEVINFO, + /// then the XIP exit sequence is also issued to chip select 1. + /// + /// The QSPI device should be accessible for XIP reads after calling this + /// function; the name flash_exit_xip refers to returning the QSPI device + /// from its XIP state to a serial command state. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_exit_xip() -> () { + crate::rom_data::rom_table_lookup(*b"EX", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Erase count bytes, starting at addr (offset from start of flash). + /// + /// Optionally, pass a block erase command e.g. D8h block erase, and the + /// size of the block erased by this command — this function will use the + /// larger block erase where possible, for much higher erase speed. addr + /// must be aligned to a 4096-byte sector, and count must be a multiple of + /// 4096 bytes. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API, which can be achieved by calling connect_internal_flash() followed + /// by flash_exit_xip(). After the erase, the flash cache should be flushed + /// via flash_flush_cache() to ensure the modified flash data is visible to + /// cached XIP accesses. + /// + /// Finally, the original XIP mode should be restored by copying the saved + /// XIP setup function from bootram into SRAM, and executing it: the bootrom + /// provides a default function which restores the flash mode/clkdiv + /// discovered during flash scanning, and user programs can override this + /// with their own XIP setup function. + /// + /// For the duration of the erase operation, QMI is in direct mode (Section + /// 12.14.5) and attempting to access XIP from DMA, the debugger or the + /// other core will return a bus fault. XIP becomes accessible again once + /// the function returns. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> () { + crate::rom_data::rom_table_lookup(*b"RE", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Program data to a range of flash storage addresses starting at addr + /// (offset from the start of flash) and count bytes in size. + /// + /// `addr` must be aligned to a 256-byte boundary, and count must be a + /// multiple of 256. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API — see notes on flash_range_erase(). + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> () { + crate::rom_data::rom_table_lookup(*b"RP", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Flush the entire XIP cache, by issuing an invalidate by set/way + /// maintenance operation to every cache line (Section 4.4.1). + /// + /// This ensures that flash program/erase operations are visible to + /// subsequent cached XIP reads. + /// + /// Note that this unpins pinned cache lines, which may interfere with + /// cache-as-SRAM use of the XIP cache. + /// + /// No other operations are performed. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_flush_cache() -> () { + crate::rom_data::rom_table_lookup(*b"FC", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure the QMI to generate a standard 03h serial read command, with + /// 24 address bits, upon each XIP access. + /// + /// This is a slow XIP configuration, but is widely supported. CLKDIV is set + /// to 12. The debugger may call this function to ensure that flash is + /// readable following a program/erase operation. + /// + /// Note that the same setup is performed by flash_exit_xip(), and the + /// RP2350 flash program/erase functions do not leave XIP in an inaccessible + /// state, so calls to this function are largely redundant. It is provided + /// for compatibility with RP2040. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_enter_cmd_xip() -> () { + crate::rom_data::rom_table_lookup(*b"CX", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure QMI for one of a small menu of XIP read modes supported by the + /// bootrom. + /// + /// This mode is configured for both memory windows (both chip + /// selects), and the clock divisor is also applied to direct mode. + /// + /// The available modes are: + /// + /// * 0: `03h` serial read: serial address, serial data, no wait cycles + /// * 1: `0Bh` serial read: serial address, serial data, 8 wait cycles + /// * 2: `BBh` dual-IO read: dual address, dual data, 4 wait cycles + /// (including MODE bits, which are driven to 0) + /// * 3: `EBh` quad-IO read: quad address, quad data, 6 wait cycles + /// (including MODE bits, which are driven to 0) + /// + /// The XIP write command/format are not configured by this function. When + /// booting from flash, the bootrom tries each of these modes in turn, from + /// 3 down to 0. The first mode that is found to work is remembered, and a + /// default XIP setup function is written into bootram that calls this + /// function (flash_select_xip_read_mode) with the parameters discovered + /// during flash scanning. This can be called at any time to restore the + /// flash parameters discovered during flash boot. + /// + /// All XIP modes configured by the bootrom have an 8-bit serial command + /// prefix, so that the flash can remain in a serial command state, meaning + /// XIP accesses can be mixed more freely with program/erase serial + /// operations. This has a performance penalty, so users can perform their + /// own flash setup after flash boot using continuous read mode or QPI mode + /// to avoid or alleviate the command prefix cost. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_select_xip_read_mode(bootrom_xip_mode: u8, clkdiv: u8) -> () { + crate::rom_data::rom_table_lookup(*b"XM", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Restore the QMI address translation registers, ATRANS0 through ATRANS7, + /// to their reset state. + /// + /// This makes the runtime- to-storage address map an identity map, i.e. the + /// mapped and unmapped address are equal, and the entire space is fully mapped. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_reset_address_trans() -> () { + crate::rom_data::rom_table_lookup(*b"RA", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** High-level Flash Commands **************** + +declare_rom_function! { + /// Applies the address translation currently configured by QMI address + /// translation registers, ATRANS0 through ATRANS7. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Translating an address outside of the XIP runtime address window, or + /// beyond the bounds of an ATRANSx_SIZE field, returns + /// BOOTROM_ERROR_INVALID_ADDRESS, which is not a valid flash storage + /// address. Otherwise, return the storage address which QMI would access + /// when presented with the runtime address addr. This is effectively a + /// virtual-to-physical address translation for QMI. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_runtime_to_storage_addr(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_runtime_to_storage_addr()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_runtime_to_storage_addr_ns(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Perform a flash read, erase, or program operation. + /// + /// Erase operations must be sector-aligned (4096 bytes) and sector- + /// multiple-sized, and program operations must be page-aligned (256 bytes) + /// and page-multiple-sized; misaligned erase and program operations will + /// return BOOTROM_ERROR_BAD_ALIGNMENT. The operation — erase, read, program + /// — is selected by the CFLASH_OP_BITS bitfield of the flags argument. + /// + /// See datasheet section 5.5.8.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_op(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_op()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_op_ns(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Security Related Functions **************** + +declare_rom_function! { + /// Allow or disallow the specific NS API (note all NS APIs default to + /// disabled). + /// + /// See datasheet section 5.5.9.1 for more details. + /// + /// Supported architectures: ARM-S + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn set_ns_api_permission(ns_api_num: u32, allowed: u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"SP", crate::rom_data::rt_flags::FUNC_ARM_SEC) + } +} + +declare_rom_function! { + /// Utility method that can be used by secure ARM code to validate a buffer + /// passed to it from Non-secure code. + /// + /// See datasheet section 5.5.9.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn validate_ns_buffer() -> () { + crate::rom_data::rom_table_lookup(*b"VB", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Miscellaneous Functions **************** + +declare_rom_function! { + /// Resets the RP2350 and uses the watchdog facility to restart. + /// + /// See datasheet section 5.5.10.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + fn reboot(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [reboot()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + fn reboot_ns(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Resets internal bootrom state. + /// + /// See datasheet section 5.5.10.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn bootrom_state_reset(flags: u32) -> () { + crate::rom_data::rom_table_lookup(*b"SR", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Set a boot ROM callback. + /// + /// The only supported callback_number is 0 which sets the callback used for + /// the secure_call API. + /// + /// See datasheet section 5.5.10.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn set_rom_callback(callback_number: i32, callback_fn: *const ()) -> i32 { + crate::rom_data::rom_table_lookup(*b"RC", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** System Information Functions **************** + +declare_rom_function! { + /// Fills a buffer with various system information. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_sys_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_sys_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_sys_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Fills a buffer with information from the partition table. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_partition_table_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_partition_table_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_partition_table_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Loads the current partition table from flash, if present. + /// + /// See datasheet section 5.5.11.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn load_partition_table(workarea_base: *mut u8, workarea_size: usize, force_reload: bool) -> i32 { + crate::rom_data::rom_table_lookup(*b"LP", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Writes data from a buffer into OTP, or reads data from OTP into a buffer. + /// + /// See datasheet section 5.5.11.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn otp_access(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [otp_access()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn otp_access_ns(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Boot Related Functions **************** + +declare_rom_function! { + /// Determines which of the partitions has the "better" IMAGE_DEF. In the + /// case of executable images, this is the one that would be booted. + /// + /// See datasheet section 5.5.12.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn pick_ab_parition(workarea_base: *mut u8, workarea_size: usize, partition_a_num: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"AB", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Searches a memory region for a launchable image, and executes it if + /// possible. + /// + /// See datasheet section 5.5.12.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn chain_image(workarea_base: *mut u8, workarea_size: usize, region_base: i32, region_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"CI", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Perform an "explicit" buy of an executable launched via an IMAGE_DEF + /// which was "explicit buy" flagged. + /// + /// See datasheet section 5.5.12.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn explicit_buy(buffer: *mut u8, buffer_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"EB", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Not yet documented. + /// + /// See datasheet section 5.5.12.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_uf2_target_partition(workarea_base: *mut u8, workarea_size: usize, family_id: u32, partition_out: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GU", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Returns: The index of the B partition of partition A if a partition + /// table is present and loaded, and there is a partition A with a B + /// partition; otherwise returns BOOTROM_ERROR_NOT_FOUND. + /// + /// See datasheet section 5.5.12.5 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_b_partition(partition_a: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GB", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Non-secure-specific Functions **************** + +// NB: The "secure_call" function should be here, but it doesn't have a fixed +// function signature as it is designed to let you bounce into any secure +// function from non-secure mode. + +// **************** RISC-V Functions **************** + +declare_rom_function! { + /// Set stack for RISC-V bootrom functions to use. + /// + /// See datasheet section 5.5.14.1 for more details. + /// + /// Supported architectures: RISC-V + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn set_bootrom_stack(base_size: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"SS", crate::rom_data::rt_flags::FUNC_RISCV) + } +} + +/// The version number of the rom. +pub fn rom_version_number() -> u8 { + unsafe { *VERSION_NUMBER } +} + +/// The 8 most significant hex digits of the Bootrom git revision. +pub fn git_revision() -> u32 { + let ptr = rom_data_lookup(*b"GR", rt_flags::DATA) as *const u32; + unsafe { ptr.read() } +} + +/// A pointer to the resident partition table info. +/// +/// The resident partition table is the subset of the full partition table that +/// is kept in memory, and used for flash permissions. +pub fn partition_table_pointer() -> *const u32 { + let ptr = rom_data_lookup(*b"PT", rt_flags::DATA) as *const *const u32; + unsafe { ptr.read() } +} + +/// Determine if we are in secure mode +/// +/// Returns `true` if we are in secure mode and `false` if we are in non-secure +/// mode. +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub fn is_secure_mode() -> bool { + // Look at the start of ROM, which is always readable + #[allow(clippy::zero_ptr)] + let rom_base: *mut u32 = 0x0000_0000 as *mut u32; + // Use the 'tt' instruction to check the permissions for that address + let tt = cortex_m::asm::tt(rom_base); + // Is the secure bit set? => secure mode + (tt & (1 << 22)) != 0 +} + +/// Determine if we are in secure mode +/// +/// Always returns `false` on RISC-V as it is impossible to determine if +/// you are in Machine Mode or User Mode by design. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub fn is_secure_mode() -> bool { + false +} diff --git a/rp-hal/rp235x-hal/src/rosc.rs b/rp-hal/rp235x-hal/src/rosc.rs new file mode 100644 index 0000000..8f8db3f --- /dev/null +++ b/rp-hal/rp235x-hal/src/rosc.rs @@ -0,0 +1,152 @@ +//! Ring Oscillator (ROSC) +//! +//! See [Section 8.3](https://rptl.io/rp2350-datasheet#section_rosc) for more details. +//! +//! In addition to its obvious role as a clock source, [`RingOscillator`] can also be used as a random number source +//! for the [`rand`] crate: +//! +//! ```no_run +//! # let mut pac = rp235x_pac::Peripherals::take().unwrap(); +//! use rand::Rng; +//! use rp235x_hal::rosc::RingOscillator; +//! let mut rnd = RingOscillator::new(pac.ROSC).initialize(); +//! let random_value: u32 = rnd.gen(); +//! ``` +//! [`rand`]: https://docs.rs/rand +use fugit::HertzU32; + +use crate::{pac::ROSC, typelevel::Sealed}; + +/// State of the Ring Oscillator (typestate trait) +pub trait State: Sealed {} + +/// ROSC is disabled (typestate) +pub struct Disabled; + +/// ROSC is initialized, ie we've given parameters (typestate) +pub struct Enabled { + freq_hz: HertzU32, +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Enabled {} +impl Sealed for Enabled {} + +/// A Ring Oscillator. +pub struct RingOscillator { + device: ROSC, + state: S, +} + +impl RingOscillator { + /// Transitions the oscillator to another state. + fn transition(self, state: To) -> RingOscillator { + RingOscillator { + device: self.device, + state, + } + } + + /// Releases the underlying device. + pub fn free(self) -> ROSC { + self.device + } +} + +impl RingOscillator { + /// Creates a new RingOscillator from the underlying device. + pub fn new(dev: ROSC) -> Self { + RingOscillator { + device: dev, + state: Disabled, + } + } + + /// Initializes the ROSC : frequency range is set, startup delay is calculated and set. + pub fn initialize(self) -> RingOscillator { + self.device.ctrl().write(|w| w.enable().enable()); + + use fugit::RateExtU32; + self.transition(Enabled { + freq_hz: 6_500_000u32.Hz(), + }) + } + + /// Initializes the ROSC with a known frequency. + /// + /// See Sections 8.3.4 "Modifying the frequency", and 8.3.8 "Using the + /// frequency counter" in the [RP2350 datasheet](https://rptl.io/rp2350-datasheet) + /// for guidance on how to do this before initialising the ROSC. Also see + /// `rosc_as_system_clock` example for usage. + pub fn initialize_with_freq(self, known_freq: HertzU32) -> RingOscillator { + self.device.ctrl().write(|w| w.enable().enable()); + self.transition(Enabled { + freq_hz: known_freq, + }) + } +} + +impl RingOscillator { + /// Approx operating frequency of the ROSC in hertz + pub fn operating_frequency(&self) -> HertzU32 { + self.state.freq_hz + } + + /// Disables the ROSC + pub fn disable(self) -> RingOscillator { + self.device.ctrl().modify(|_r, w| w.enable().disable()); + + self.transition(Disabled) + } + + /// Generate random bit based on the Ring oscillator + /// This is not suited for security purposes + pub fn get_random_bit(&self) -> bool { + self.device.randombit().read().randombit().bit() + } + + /// Put the ROSC in DORMANT state. The method returns after the processor awakens. + /// + /// After waking up from the DORMANT state, ROSC restarts in approximately 1µs. + /// + /// # Safety + /// This method is marked unsafe because prior to switch the ROSC into DORMANT state, + /// PLLs must be stopped and IRQs have to be properly configured. + /// This method does not do any of that, it merely switches the ROSC to DORMANT state. + /// It should only be called if this oscillator is the clock source for the system clock. + /// + /// See [Section 6.5.3](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + pub unsafe fn dormant(&self) { + //taken from the C SDK + const ROSC_DORMANT_VALUE: u32 = 0x636f6d61; + + self.device.dormant().write(|w| w.bits(ROSC_DORMANT_VALUE)); + } +} + +impl rand_core::RngCore for RingOscillator { + 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]) { + for chunk in dest.iter_mut() { + *chunk = 0_u8; + for _ in 0..8 { + *chunk <<= 1; + *chunk ^= self.get_random_bit() as u8; + } + } + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } +} diff --git a/rp-hal/rp235x-hal/src/sio.rs b/rp-hal/rp235x-hal/src/sio.rs new file mode 100644 index 0000000..15bf245 --- /dev/null +++ b/rp-hal/rp235x-hal/src/sio.rs @@ -0,0 +1,616 @@ +//! Single Cycle Input and Output (SIO) +//! +//! To be able to partition parts of the SIO block to other modules: +//! +//! ```no_run +//! use rp235x_hal::{self as hal, gpio::Pins, sio::Sio}; +//! +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! ``` +//! +//! And then for example +//! +//! ```no_run +//! # use rp235x_hal::{self as hal, gpio::Pins, sio::Sio}; +//! # let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! # let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! ``` + +use crate::typelevel::Sealed; + +use super::*; +use core::convert::Infallible; + +/// Id of the core. +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CoreId { + #[allow(missing_docs)] + Core0 = 0, + #[allow(missing_docs)] + Core1 = 1, +} + +/// Marker struct for ownership of SIO gpio bank0 +#[derive(Debug)] +pub struct SioGpioBank0 { + _private: (), +} + +/// Marker struct for ownership of SIO FIFO +#[derive(Debug)] +pub struct SioFifo { + _private: (), +} + +/// Marker struct for ownership of SIO gpio qspi +#[derive(Debug)] +pub struct SioGpioQspi { + _private: (), +} + +/// Struct containing ownership markers for managing ownership of the SIO registers. +pub struct Sio { + _sio: pac::SIO, + /// GPIO Bank 0 registers + pub gpio_bank0: SioGpioBank0, + /// GPIO QSPI registers + pub gpio_qspi: SioGpioQspi, + /// Inter-core FIFO + pub fifo: SioFifo, + /// Interpolator 0 + pub interp0: Interp0, + /// Interpolator 1 + pub interp1: Interp1, +} + +impl Sio { + /// Create `Sio` from the PAC. + pub fn new(sio: pac::SIO) -> Self { + Self { + _sio: sio, + gpio_bank0: SioGpioBank0 { _private: () }, + gpio_qspi: SioGpioQspi { _private: () }, + fifo: SioFifo { _private: () }, + interp0: Interp0 { + lane0: Interp0Lane0 { _private: () }, + lane1: Interp0Lane1 { _private: () }, + }, + interp1: Interp1 { + lane0: Interp1Lane0 { _private: () }, + lane1: Interp1Lane1 { _private: () }, + }, + } + } + + /// Reads the whole bank0 at once. + pub fn read_bank0() -> u32 { + unsafe { (*pac::SIO::PTR).gpio_in().read().bits() } + } + + /// Returns whether we are running on Core 0 (`0`) or Core 1 (`1`). + pub fn core() -> CoreId { + // Safety: it is always safe to read this read-only register + match unsafe { (*pac::SIO::ptr()).cpuid().read().bits() as u8 } { + 0 => CoreId::Core0, + 1 => CoreId::Core1, + _ => unreachable!("This MCU only has 2 cores."), + } + } +} + +impl SioFifo { + /// Check if the inter-core FIFO has valid data for reading. + /// + /// Returning `true` means there is valid data, `false` means it is empty + /// and you must not read from it. + pub fn is_read_ready(&mut self) -> bool { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_st().read().vld().bit_is_set() + } + + /// Check if the inter-core FIFO is ready to receive data. + /// + /// Returning `true` means there is room, `false` means it is full and you + /// must not write to it. + pub fn is_write_ready(&mut self) -> bool { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_st().read().rdy().bit_is_set() + } + + /// Return the FIFO status, as an integer. + pub fn status(&self) -> u32 { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_st().read().bits() + } + + /// Write to the inter-core FIFO. + /// + /// You must ensure the FIFO has space by calling `is_write_ready` + pub fn write(&mut self, value: u32) { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_wr().write(|w| unsafe { w.bits(value) }); + // Fire off an event to the other core. + // This is required as the other core may be `wfe` (waiting for event) + crate::arch::sev(); + } + + /// Read from the inter-core FIFO. + /// + /// Will return `Some(data)`, or `None` if the FIFO is empty. + pub fn read(&mut self) -> Option { + if self.is_read_ready() { + let sio = unsafe { &(*pac::SIO::ptr()) }; + Some(sio.fifo_rd().read().bits()) + } else { + None + } + } + + /// Read from the FIFO until it is empty, throwing the contents away. + pub fn drain(&mut self) { + while self.read().is_some() { + // Retry until FIFO empty + } + } + + /// Push to the FIFO, spinning if there's no space. + pub fn write_blocking(&mut self, value: u32) { + // We busy-wait for the FIFO to have some space + while !self.is_write_ready() { + crate::arch::nop(); + } + + // Write the value to the FIFO - the other core will now be able to + // pop it off its end of the FIFO. + self.write(value); + + // Fire off an event to the other core + crate::arch::sev(); + } + + /// Pop from the FIFO, spinning if there's currently no data. + pub fn read_blocking(&mut self) -> u32 { + // Keep trying until FIFO has data + loop { + // Have we got something? + if let Some(data) = self.read() { + // Yes, return it right away + return data; + } else { + // No, so sleep the CPU. We expect the sending core to `sev` + // on write. + crate::arch::wfe(); + } + } + } +} + +/// This type is just used to limit us to Spinlocks `0..=31` +pub trait SpinlockValid: Sealed {} + +/// Hardware based spinlock. +/// +/// You can claim this lock by calling either [`claim`], [`try_claim`] or +/// [`claim_async`]. These spin-locks are hardware backed, so if you lock +/// e.g. `Spinlock<6>`, then any other part of your application using +/// `Spinlock<6>` will contend for the same lock, without them needing to +/// share a reference or otherwise communicate with each other. +/// +/// When the obtained spinlock goes out of scope, it is automatically unlocked. +/// +/// +/// ```no_run +/// use rp235x_hal::sio::Spinlock0; +/// static mut SOME_GLOBAL_VAR: u32 = 0; +/// +/// /// This function is safe to call from two different cores, but is not safe +/// /// to call from an interrupt routine! +/// fn update_global_var() { +/// // Do not say `let _ = ` here - it will immediately unlock! +/// let _lock = Spinlock0::claim(); +/// // Do your thing here that Core 0 and Core 1 might want to do at the +/// // same time, like update this global variable: +/// unsafe { SOME_GLOBAL_VAR += 1 }; +/// // The lock is dropped here. +/// } +/// ``` +/// +/// **Warning**: These spinlocks are not re-entrant, meaning that the +/// following code will cause a deadlock: +/// +/// ```no_run +/// use rp235x_hal::sio::Spinlock0; +/// let lock_1 = Spinlock0::claim(); +/// let lock_2 = Spinlock0::claim(); // deadlock here +/// ``` +/// +/// **Note:** The `critical-section` implementation uses Spinlock 31. +/// +/// [`claim`]: #method.claim +/// [`try_claim`]: #method.try_claim +/// [`claim_async`]: #method.claim_asyncs +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 + let sio = unsafe { &*pac::SIO::ptr() }; + let lock = sio.spinlock(N).read().bits(); + if lock > 0 { + Some(Self(core::marker::PhantomData)) + } else { + None + } + } + + /// Claim the spinlock, will block the current thread until the lock is available. + /// + /// Note that calling this multiple times in a row will cause a deadlock + pub fn claim() -> Self { + loop { + if let Some(result) = Self::try_claim() { + break result; + } + } + } + + /// Try to claim the spinlock. Will return `WouldBlock` until the spinlock is available. + pub fn claim_async() -> nb::Result { + Self::try_claim().ok_or(nb::Error::WouldBlock) + } + + /// Clear a locked spin-lock. + /// + /// # Safety + /// + /// Only call this function if you hold the spin-lock. + pub unsafe fn release() { + let sio = &*pac::SIO::ptr(); + // Write (any value): release the lock + sio.spinlock(N).write_with_zero(|b| b.bits(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() } + } +} + +macro_rules! spinlock { + ($first:expr, $($rest:tt),+) => { + spinlock!($first); + spinlock!($($rest),+); + }; + ($id:expr) => { + $crate::paste::paste! { + /// Spinlock number $id + pub type [] = Spinlock<$id>; + impl SpinlockValid for Spinlock<$id> {} + impl Sealed for Spinlock<$id> {} + } + }; +} +spinlock!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30 +); + +/// Spinlock number 31 - used by critical section implementation +#[cfg(feature = "critical-section-impl")] +pub(crate) type Spinlock31 = Spinlock<31>; + +/// Spinlock number 31 - only public if critical-section-impl is not enabled +#[cfg(not(feature = "critical-section-impl"))] +pub type Spinlock31 = Spinlock<31>; + +impl SpinlockValid for Spinlock<31> {} +impl Sealed for Spinlock<31> {} + +/// Returns the current state of the spinlocks. Each index corresponds to the associated spinlock, e.g. if index `5` is set to `true`, it means that [`Spinlock5`] is currently locked. +/// +/// Note that spinlocks can be claimed or released at any point, so this function cannot guarantee the spinlock is actually available right after calling this function. This function is mainly intended for debugging. +pub fn spinlock_state() -> [bool; 32] { + // Safety: we're only reading from a register + let sio = unsafe { &*pac::SIO::ptr() }; + // A bitmap containing the state of all 32 spinlocks (1=locked). + let register = sio.spinlock_st().read().bits(); + let mut result = [false; 32]; + #[allow(clippy::needless_range_loop)] + for i in 0..32 { + result[i] = (register & (1 << i)) > 0; + } + result +} + +/// Free all spinlocks, regardless of their current status +/// +/// rp235x does not release all spinlocks on reset. +/// The C SDK clears these all during entry, and so do we if you call hal::entry! +/// But if someone is using the default cortex-m entry they risk hitting deadlocks so provide *something* to help out +/// +/// # Safety +/// Where possible, you should use the hal::entry macro attribute on main instead of this. +/// You should call this as soon as possible after reset - preferably as the first entry in fn main(), before *ANY* use of spinlocks, atomics, or critical_section +pub unsafe fn spinlock_reset() { + // Using raw pointers to avoid taking peripherals accidently at startup + const SIO_BASE: u32 = 0xd0000000; + const SPINLOCK0_PTR: *mut u32 = (SIO_BASE + 0x100) as *mut u32; + const SPINLOCK_COUNT: usize = 32; + for i in 0..SPINLOCK_COUNT { + SPINLOCK0_PTR.wrapping_add(i).write_volatile(1); + } +} + +/// Configuration struct for one lane of the interpolator +pub struct LaneCtrl { + /// Bit 22 - Only present on INTERP1 on each core. If CLAMP mode is enabled: + /// - LANE0 result is shifted and masked ACCUM0, clamped by a lower bound of + /// BASE0 and an upper bound of BASE1. + /// - Signedness of these comparisons is determined by LANE0_CTRL_SIGNED + pub clamp: bool, + /// Bit 21 - Only present on INTERP0 on each core. If BLEND mode is enabled: + /// + /// - LANE1 result is a linear interpolation between BASE0 and BASE1, controlled + /// by the 8 LSBs of lane 1 shift and mask value (a fractional number between + /// 0 and 255/256ths) + /// - LANE0 result does not have BASE0 added (yields only + /// the 8 LSBs of lane 1 shift+mask value) + /// - FULL result does not have lane 1 shift+mask value added (BASE2 + lane 0 shift+mask) + /// + /// LANE1 SIGNED flag controls whether the interpolation is signed or unsigned. + pub blend: bool, + /// Bits 19:20 - ORed into bits 29:28 of the lane result presented to the processor on the bus. + /// No effect on the internal 32-bit datapath. Handy for using a lane to generate sequence + /// of pointers into flash or SRAM. + pub force_msb: u8, + /// Bit 18 - If 1, mask + shift is bypassed for LANE0 result. This does not affect FULL result. + pub add_raw: bool, + /// Bit 17 - If 1, feed the opposite lane's result into this lane's accumulator on POP. + pub cross_result: bool, + /// Bit 16 - If 1, feed the opposite lane's accumulator into this lane's shift + mask hardware. + /// Takes effect even if ADD_RAW is set (the CROSS_INPUT mux is before the shift+mask bypass) + pub cross_input: bool, + /// Bit 15 - If SIGNED is set, the shifted and masked accumulator value is sign-extended to 32 bits + /// before adding to BASE0, and LANE0 PEEK/POP appear extended to 32 bits when read by processor. + pub signed: bool, + /// Bits 10:14 - The most-significant bit allowed to pass by the mask (inclusive) + /// Setting MSB < LSB may cause chip to turn inside-out + pub mask_msb: u8, + /// Bits 5:9 - The least-significant bit allowed to pass by the mask (inclusive) + pub mask_lsb: u8, + /// Bits 0:4 - Logical right-shift applied to accumulator before masking + pub shift: u8, +} + +impl Default for LaneCtrl { + fn default() -> Self { + Self::new() + } +} + +impl LaneCtrl { + /// Default configuration. Normal operation, unsigned, mask keeps all bits, no shift. + pub const fn new() -> Self { + Self { + clamp: false, + blend: false, + force_msb: 0, + add_raw: false, + cross_result: false, + cross_input: false, + signed: false, + mask_msb: 31, + mask_lsb: 0, + shift: 0, + } + } + + /// encode the configuration to be loaded in the ctrl register of one lane of an interpolator + pub const fn encode(&self) -> u32 { + assert!(!(self.blend && self.clamp)); + assert!(self.force_msb < 0b100); + assert!(self.mask_msb < 0b100000); + assert!(self.mask_lsb < 0b100000); + assert!(self.mask_msb >= self.mask_lsb); + assert!(self.shift < 0b100000); + ((self.clamp as u32) << 22) + | ((self.blend as u32) << 21) + | ((self.force_msb as u32) << 19) + | ((self.add_raw as u32) << 18) + | ((self.cross_result as u32) << 17) + | ((self.cross_input as u32) << 16) + | ((self.signed as u32) << 15) + | ((self.mask_msb as u32) << 10) + | ((self.mask_lsb as u32) << 5) + | (self.shift as u32) + } +} + +///Trait representing the functionality of a single lane of an interpolator. +pub trait Lane: Sealed { + ///Read the lane result, and simultaneously write lane results to both accumulators. + fn pop(&mut self) -> u32; + ///Read the lane result without altering any internal state + fn peek(&self) -> u32; + ///Write a value to the accumulator + fn set_accum(&mut self, v: u32); + ///Read the value from the accumulator + fn get_accum(&self) -> u32; + ///Write a value to the base register + fn set_base(&mut self, v: u32); + ///Read the value from the base register + fn get_base(&self) -> u32; + ///Write to the control register + fn set_ctrl(&mut self, v: u32); + ///Read from the control register + fn get_ctrl(&self) -> u32; + ///Add the value to the accumulator register + fn add_accum(&mut self, v: u32); + ///Read the raw shift and mask value (BASE register not added) + fn read_raw(&self) -> u32; +} + +///Trait representing the functionality of an interpolator. +/// ```no_run +/// use rp235x_hal::{ +/// self as hal, +/// sio::{Lane, LaneCtrl, Sio}, +/// }; +/// let mut peripherals = hal::pac::Peripherals::take().unwrap(); +/// let mut sio = Sio::new(peripherals.SIO); +/// +/// // by having the configuration const, the validity is checked during compilation. +/// const config: u32 = LaneCtrl { +/// mask_msb: 4, // Most significant bit of the mask is bit 4 +/// // By default the least significant bit is bit 0 +/// // this will keep only the 5 least significant bits. +/// // this is equivalent to %32 +/// ..LaneCtrl::new() +/// } +/// .encode(); +/// sio.interp0.get_lane0().set_ctrl(config); +/// sio.interp0.get_lane0().set_accum(0); +/// sio.interp0.get_lane0().set_base(1); // will increment the value by 1 on each call to pop +/// +/// sio.interp0.get_lane0().peek(); // returns 1 +/// sio.interp0.get_lane0().pop(); // returns 1 +/// sio.interp0.get_lane0().pop(); // returns 2 +/// sio.interp0.get_lane0().pop(); // returns 3 +/// ``` +pub trait Interp: Sealed { + ///Read the interpolator result (Result 2 in the datasheet), and simultaneously write lane results to both accumulators. + fn pop(&mut self) -> u32; + ///Read the interpolator result (Result 2 in the datasheet) without altering any internal state + fn peek(&self) -> u32; + ///Write to the interpolator Base register (Base2 in the datasheet) + fn set_base(&mut self, v: u32); + ///Read the interpolator Base register (Base2 in the datasheet) + fn get_base(&self) -> u32; + ///Write the lower 16 bits to BASE0 and the upper bits to BASE1 simultaneously. Each half is sign-extended to 32 bits if that lane's SIGNED flag is set + fn set_base_1and0(&mut self, v: u32); +} + +macro_rules! interpolators { + ( + $($interp:ident : ( $( [ $lane:ident,$lane_id:expr ] ),+ ) ),+ + ) => { + $crate::paste::paste! { + + + $( + $( + #[doc = "The lane " $lane_id " of " $interp] + pub struct [<$interp $lane>]{ + _private: (), + } + impl Lane for [<$interp $lane>]{ + fn pop(&mut self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _pop_ $lane:lower>]().read().bits() + } + fn peek(&self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _peek_ $lane:lower>]().read().bits() + } + fn set_accum(&mut self,v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id>]().write(|w| unsafe { w.bits(v) }); + } + fn get_accum(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id>]().read().bits() + } + fn set_base(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base $lane_id>]().write(|w| unsafe { w.bits(v) }); + } + fn get_base(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base $lane_id>]().read().bits() + } + fn set_ctrl(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _ctrl_lane $lane_id>]().write(|w| unsafe { w.bits(v) }); + } + fn get_ctrl(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _ctrl_lane $lane_id>]().read().bits() + } + fn add_accum(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id _add>]().write(|w| unsafe { w.bits(v) }); + } + fn read_raw(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id _add>]().read().bits() + } + } + impl Sealed for [<$interp $lane>] {} + )+ + #[doc = "Interpolator " $interp] + pub struct $interp { + $( + [<$lane:lower>]: [<$interp $lane>], + )+ + } + impl $interp{ + $( + /// Lane accessor function + pub fn [](&mut self)->&mut [<$interp $lane>]{ + &mut self.[<$lane:lower>] + } + )+ + } + impl Interp for $interp{ + fn pop(&mut self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _pop_full>]().read().bits() + } + fn peek(&self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _peek_full>]().read().bits() + } + fn set_base(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base2>]().write(|w| unsafe { w.bits(v)}); + } + fn get_base(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base2>]().read().bits() + } + fn set_base_1and0(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base_1and0>]().write(|w| unsafe { w.bits(v)}); + } + } + impl Sealed for $interp {} + )+ + } + } + } + +interpolators!( + Interp0 : ([Lane0,0],[Lane1,1]), + Interp1 : ([Lane0,0],[Lane1,1]) +); diff --git a/rp-hal/rp235x-hal/src/spi.rs b/rp-hal/rp235x-hal/src/spi.rs new file mode 100644 index 0000000..6533e72 --- /dev/null +++ b/rp-hal/rp235x-hal/src/spi.rs @@ -0,0 +1,588 @@ +//! Serial Peripheral Interface (SPI) +//! +//! [`Spi`] is the main struct exported by this module, representing a configured Spi bus. See its +//! docs for more information on its type parameters. +//! +//! See [Section 12.3](https://rptl.io/rp2350-datasheet#section_spi) for more details. +//! +//! ## Usage +//! +//! ```no_run +//! use embedded_hal::spi::MODE_0; +//! use fugit::RateExtU32; +//! use rp235x_hal::{ +//! self as hal, +//! gpio::{FunctionSpi, Pins}, +//! spi::Spi, +//! Sio, +//! }; +//! +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! +//! let sclk = pins.gpio2.into_function::(); +//! let mosi = pins.gpio3.into_function::(); +//! +//! let spi_device = peripherals.SPI0; +//! let spi_pin_layout = (mosi, sclk); +//! +//! let spi = Spi::<_, _, _, 8>::new(spi_device, spi_pin_layout).init( +//! &mut peripherals.RESETS, +//! 125_000_000u32.Hz(), +//! 16_000_000u32.Hz(), +//! MODE_0, +//! ); +//! ``` + +use core::{convert::Infallible, marker::PhantomData, ops::Deref}; + +use embedded_hal::spi::{self, Phase, Polarity}; +// Support Embedded HAL 0.2 for backwards-compatibility +use embedded_hal_0_2::{blocking::spi as blocking_spi02, spi as spi02}; +use embedded_hal_nb::spi::FullDuplex; +use fugit::{HertzU32, RateExtU32}; + +use crate::{ + dma::{EndlessReadTarget, EndlessWriteTarget, ReadTarget, WriteTarget}, + pac::{self, dma::ch::ch_ctrl_trig::TREQ_SEL_A, RESETS}, + resets::SubsystemReset, + typelevel::Sealed, +}; + +mod pins; +pub use pins::*; + +impl From for FrameFormat { + fn from(f: spi::Mode) -> Self { + Self::MotorolaSpi(f) + } +} + +impl From<&spi::Mode> for FrameFormat { + fn from(f: &spi::Mode) -> Self { + Self::MotorolaSpi(*f) + } +} + +/// SPI frame format +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum FrameFormat { + /// Motorola SPI format. + /// + /// See [Section 12.3.4.9](https://rptl.io/rp2350-datasheet) of the RP2350 + /// datasheet. + MotorolaSpi(spi::Mode), + /// Texas Instruments synchronous serial frame format. + /// + /// See [Section 12.3.4.8](https://rptl.io/rp2350-datasheet) of the RP2350 + /// datasheet. + TexasInstrumentsSynchronousSerial, + /// National Semiconductor Microwire frame format. + /// + /// See [Section 12.3.4.14](https://rptl.io/rp2350-datasheet) of the RP2350 + /// datasheet. + NationalSemiconductorMicrowire, +} + +impl From<&embedded_hal_0_2::spi::Mode> for FrameFormat { + fn from(f: &embedded_hal_0_2::spi::Mode) -> Self { + let embedded_hal_0_2::spi::Mode { polarity, phase } = f; + match (polarity, phase) { + (spi02::Polarity::IdleLow, spi02::Phase::CaptureOnFirstTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_0) + } + (spi02::Polarity::IdleLow, spi02::Phase::CaptureOnSecondTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_1) + } + (spi02::Polarity::IdleHigh, spi02::Phase::CaptureOnFirstTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_2) + } + (spi02::Polarity::IdleHigh, spi02::Phase::CaptureOnSecondTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_3) + } + } + } +} + +impl From for FrameFormat { + fn from(f: embedded_hal_0_2::spi::Mode) -> Self { + From::from(&f) + } +} + +/// State of the SPI +pub trait State: Sealed {} + +/// Spi is disabled +pub struct Disabled { + __private: (), +} + +/// Spi is enabled +pub struct Enabled { + __private: (), +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Enabled {} +impl Sealed for Enabled {} + +/// Pac SPI device +pub trait SpiDevice: Deref + SubsystemReset + Sealed { + /// Index of the peripheral. + const ID: usize; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8; + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8; +} + +impl Sealed for pac::SPI0 {} +impl SpiDevice for pac::SPI0 { + const ID: usize = 0; + fn tx_dreq() -> u8 { + TREQ_SEL_A::SPI0_TX.into() + } + fn rx_dreq() -> u8 { + TREQ_SEL_A::SPI0_RX.into() + } +} +impl Sealed for pac::SPI1 {} +impl SpiDevice for pac::SPI1 { + const ID: usize = 1; + fn tx_dreq() -> u8 { + TREQ_SEL_A::SPI1_TX.into() + } + fn rx_dreq() -> u8 { + TREQ_SEL_A::SPI1_RX.into() + } +} + +/// Data size used in spi +pub trait DataSize: Sealed {} + +impl DataSize for u8 {} +impl DataSize for u16 {} +impl Sealed for u8 {} +impl Sealed for u16 {} + +/// Configured Spi bus. +/// +/// This struct implements the `embedded-hal` Spi-related traits. It represents unique ownership +/// of the entire Spi bus of a single configured rp235x Spi peripheral. +/// +/// `Spi` has four generic parameters: +/// +/// - `S`: a typestate for whether the bus is [`Enabled`] or [`Disabled`]. Upon initial creation, +/// the bus is [`Disabled`]. You will then need to initialize it as either a main (master) or sub +/// (slave) device, providing the necessary configuration, at which point it will become [`Enabled`]. +/// - `D`: Which of the concrete Spi peripherals is being used, [`pac::SPI0`] or [`pac::SPI1`] +/// - `P`: Which pins are being used to configure the Spi peripheral `D`. A table of valid +/// pinouts for each Spi peripheral can be found in section X.X.X of the rp235x datasheet. +/// +/// The [`ValidSpiPinout`] trait is implemented for tuples of pin types that follow the layout: +/// +/// - `(Tx, Sck)` (i.e. first the "Tx"/"MOSI" pin, then the "Sck"/"Clock" pin) +/// - `(Tx, Rx, Sck)` (i.e. first "Tx"/"MOSI", then "Rx"/"MISO", then "Sck"/"Clock" pin) +/// +/// If you select an invalid layout, you will get a compile error that `P` does not implement +/// [`ValidSpiPinout`] for your specified [`SpiDevice`] peripheral `D` +/// - `DS`: The "data size", i.e. the number of bits transferred per data frame. Defaults to 8. +/// +/// In most cases you won't have to specify these types manually and can let the compiler infer +/// them for you based on the values you pass in to `new`. If you want to select a different +/// data frame size, you'll need to do that by specifying the `DS` parameter manually. +/// +/// See [the module level docs][self] for an example. +pub struct Spi, const DS: u8 = 8u8> { + device: D, + pins: P, + state: PhantomData, +} + +impl, const DS: u8> Spi { + fn transition(self, _: To) -> Spi { + Spi { + device: self.device, + pins: self.pins, + state: PhantomData, + } + } + + /// Releases the underlying device and pins. + pub fn free(self) -> (D, P) { + (self.device, self.pins) + } + + /// Set device pre-scale and post-div properties to match the given baudrate as + /// closely as possible based on the given peripheral clock frequency. + /// + /// Typically the peripheral clock is set to 125_000_000 Hz. + /// + /// Returns the frequency that we were able to achieve, which may not be exactly + /// the requested baudrate. + pub fn set_baudrate, B: Into>( + &mut self, + peri_frequency: F, + baudrate: B, + ) -> HertzU32 { + let freq_in = peri_frequency.into().to_Hz(); + let baudrate = baudrate.into().to_Hz(); + let mut prescale: u8 = u8::MAX; + let mut postdiv: u8 = 0; + + // Find smallest prescale value which puts output frequency in range of + // post-divide. Prescale is an even number from 2 to 254 inclusive. + for prescale_option in (2u32..=254).step_by(2) { + // We need to use an saturating_mul here because with a high baudrate certain invalid prescale + // values might not fit in u32. However we can be sure those values exceed the max sys_clk frequency + // So clamping a u32::MAX is fine here... + if freq_in < ((prescale_option + 2) * 256).saturating_mul(baudrate) { + prescale = prescale_option as u8; + break; + } + } + + // We might not find a prescale value that lowers the clock freq enough, so we leave it at max + debug_assert_ne!(prescale, u8::MAX); + + // Find largest post-divide which makes output <= baudrate. Post-divide is + // an integer in the range 0 to 255 inclusive. + for postdiv_option in (1..=255u8).rev() { + if freq_in / (prescale as u32 * postdiv_option as u32) > baudrate { + postdiv = postdiv_option; + break; + } + } + + self.device + .sspcpsr() + .write(|w| unsafe { w.cpsdvsr().bits(prescale) }); + self.device + .sspcr0() + .modify(|_, w| unsafe { w.scr().bits(postdiv) }); + + // Return the frequency we were able to achieve + (freq_in / (prescale as u32 * (1 + postdiv as u32))).Hz() + } + + /// Set format + pub fn set_format(&mut self, frame_format: FrameFormat) { + self.device.sspcr0().modify(|_, w| unsafe { + w.dss().bits(DS - 1).frf().bits(match &frame_format { + FrameFormat::MotorolaSpi(_) => 0x00, + FrameFormat::TexasInstrumentsSynchronousSerial => 0x01, + FrameFormat::NationalSemiconductorMicrowire => 0x10, + }); + + /* + * Clock polarity (SPO) and clock phase (SPH) are only applicable to + * the Motorola SPI frame format. + */ + if let FrameFormat::MotorolaSpi(ref mode) = frame_format { + w.spo() + .bit(mode.polarity == Polarity::IdleHigh) + .sph() + .bit(mode.phase == Phase::CaptureOnSecondTransition); + } + w + }); + } +} + +impl, const DS: u8> Spi { + /// Create new not initialized Spi bus. Initialize it with [`.init`][Self::init] + /// or [`.init_slave`][Self::init_slave]. + /// + /// Valid pin sets are in the form of `(Tx, Sck)` or `(Tx, Rx, Sck)` + /// + /// If your pins are dynamically identified (`Pin`) they will first need to pass + /// validation using their corresponding [`ValidatedPinXX`](ValidatedPinTx). + pub fn new(device: D, pins: P) -> Spi { + Spi { + device, + pins, + state: PhantomData, + } + } + + /// Set master/slave + fn set_slave(&mut self, slave: bool) { + if slave { + self.device.sspcr1().modify(|_, w| w.ms().set_bit()); + } else { + self.device.sspcr1().modify(|_, w| w.ms().clear_bit()); + } + } + + fn init_spi, B: Into>( + mut self, + resets: &mut RESETS, + peri_frequency: F, + baudrate: B, + frame_format: FrameFormat, + slave: bool, + ) -> Spi { + self.device.reset_bring_down(resets); + self.device.reset_bring_up(resets); + + self.set_baudrate(peri_frequency, baudrate); + self.set_format(frame_format); + self.set_slave(slave); + // Always enable DREQ signals -- harmless if DMA is not listening + self.device + .sspdmacr() + .modify(|_, w| w.txdmae().set_bit().rxdmae().set_bit()); + + // Finally enable the SPI + self.device.sspcr1().modify(|_, w| w.sse().set_bit()); + + self.transition(Enabled { __private: () }) + } + + /// Initialize the SPI in master mode + pub fn init, B: Into, M: Into>( + self, + resets: &mut RESETS, + peri_frequency: F, + baudrate: B, + frame_format: M, + ) -> Spi { + self.init_spi(resets, peri_frequency, baudrate, frame_format.into(), false) + } + + /// Initialize the SPI in slave mode + pub fn init_slave>( + self, + resets: &mut RESETS, + frame_format: M, + ) -> Spi { + // Use dummy values for frequency and baudrate. + // With both values 0, set_baudrate will set prescale == u8::MAX, which will break if debug assertions are enabled. + // u8::MAX is outside the allowed range 2..=254 for CPSDVSR, which might interfere with proper operation in slave mode. + self.init_spi( + resets, + 1000u32.Hz(), + 1000u32.Hz(), + frame_format.into(), + true, + ) + } +} + +impl, const DS: u8> Spi { + fn is_writable(&self) -> bool { + self.device.sspsr().read().tnf().bit_is_set() + } + fn is_readable(&self) -> bool { + self.device.sspsr().read().rne().bit_is_set() + } + + /// Check if spi is busy transmitting and/or receiving + pub fn is_busy(&self) -> bool { + self.device.sspsr().read().bsy().bit_is_set() + } + + /// Disable the spi to reset its configuration. You'll then need to initialize it again to use + /// it. + pub fn disable(self) -> Spi { + self.device.sspcr1().modify(|_, w| w.sse().clear_bit()); + + self.transition(Disabled { __private: () }) + } +} + +macro_rules! impl_write { + ($type:ident, [$($nr:expr),+]) => { + + $( + impl> spi02::FullDuplex<$type> for Spi { + type Error = Infallible; + + fn read(&mut self) -> Result<$type, nb::Error> { + if !self.is_readable() { + return Err(nb::Error::WouldBlock); + } + + Ok(self.device.sspdr().read().data().bits() as $type) + } + fn send(&mut self, word: $type) -> Result<(), nb::Error> { + // Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX + // is full, PL022 inhibits RX pushes, and sets a sticky flag on + // push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set. + if !self.is_writable() { + return Err(nb::Error::WouldBlock); + } + + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(word as u16) }); + Ok(()) + } + } + + impl> blocking_spi02::write::Default<$type> for Spi {} + impl> blocking_spi02::transfer::Default<$type> for Spi {} + impl> blocking_spi02::write_iter::Default<$type> for Spi {} + + impl> spi::ErrorType for Spi { + type Error = Infallible; + } + + impl> spi::SpiBus<$type> for Spi { + fn read(&mut self, words: &mut [$type]) -> Result<(), Self::Error> { + for word in words.iter_mut() { + // write empty word + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(0) }); + + // read one word + while !self.is_readable() {} + *word = self.device.sspdr().read().data().bits() as $type; + } + Ok(()) + } + + fn write(&mut self, words: &[$type]) -> Result<(), Self::Error> { + for word in words.iter() { + // write one word + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(*word as u16) }); + + // drop read wordd + while !self.is_readable() {} + let _ = self.device.sspdr().read().data().bits(); + } + Ok(()) + } + + fn transfer(&mut self, read: &mut [$type], write: &[$type]) -> Result<(), Self::Error>{ + let len = read.len().max(write.len()); + for i in 0..len { + // write one word. Send empty word if buffer is empty. + let wb = write.get(i).copied().unwrap_or(0); + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(wb as u16) }); + + // read one word. Drop extra words if buffer is full. + while !self.is_readable() {} + let rb = self.device.sspdr().read().data().bits() as $type; + if let Some(r) = read.get_mut(i) { + *r = rb; + } + } + + Ok(()) + } + + fn transfer_in_place(&mut self, words: &mut [$type]) -> Result<(), Self::Error>{ + for word in words.iter_mut() { + // write one word + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(*word as u16) }); + + // read one word + while !self.is_readable() {} + *word = self.device.sspdr().read().data().bits() as $type; + } + + Ok(()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + while self.is_busy() {} + Ok(()) + } + } + + impl> FullDuplex<$type> for Spi { + fn read(&mut self) -> Result<$type, nb::Error> { + if !self.is_readable() { + return Err(nb::Error::WouldBlock); + } + + Ok(self.device.sspdr().read().data().bits() as $type) + } + fn write(&mut self, word: $type) -> Result<(), nb::Error> { + // Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX + // is full, PL022 inhibits RX pushes, and sets a sticky flag on + // push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set. + if !self.is_writable() { + return Err(nb::Error::WouldBlock); + } + + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(word as u16) }); + Ok(()) + } + } + + // Safety: This only reads from the RX fifo, so it doesn't + // interact with rust-managed memory. + unsafe impl> ReadTarget for Spi { + type ReceivedWord = $type; + + fn rx_treq() -> Option { + Some(D::rx_dreq()) + } + + fn rx_address_count(&self) -> (u32, u32) { + ( + self.device.sspdr().as_ptr() as u32, + u32::MAX, + ) + } + + fn rx_increment(&self) -> bool { + false + } + } + + impl> EndlessReadTarget for Spi {} + + // Safety: This only writes to the TX fifo, so it doesn't + // interact with rust-managed memory. + unsafe impl> WriteTarget for Spi { + type TransmittedWord = $type; + + fn tx_treq() -> Option { + Some(D::tx_dreq()) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + ( + self.device.sspdr().as_ptr() as u32, + u32::MAX, + ) + } + + fn tx_increment(&self) -> bool { + false + } + } + + impl> EndlessWriteTarget for Spi {} + )+ + + }; +} + +impl_write!(u8, [4, 5, 6, 7, 8]); +impl_write!(u16, [9, 10, 11, 12, 13, 14, 15, 16]); diff --git a/rp-hal/rp235x-hal/src/spi/pins.rs b/rp-hal/rp235x-hal/src/spi/pins.rs new file mode 100644 index 0000000..7eb96c1 --- /dev/null +++ b/rp-hal/rp235x-hal/src/spi/pins.rs @@ -0,0 +1,140 @@ +use core::marker::PhantomData; + +use crate::{ + gpio::{bank0::*, pin::pin_sealed::TypeLevelPinId, AnyPin, FunctionSpi}, + pac::{SPI0, SPI1}, + typelevel::{OptionT, OptionTNone, OptionTSome, Sealed}, +}; + +use super::SpiDevice; + +macro_rules! pin_validation { + ($p:ident) => { + paste::paste!{ + #[doc = "Indicates a valid " $p " pin for SPI0 or SPI1"] + pub trait []: Sealed {} + + #[doc = "Indicates a valid " $p " pin for SPI0 or SPI1"] + pub trait []: Sealed {} + + impl [] for T + where + T: AnyPin, + T::Id: [], + { + } + + #[doc = "A runtime validated " $p " pin for spi."] + pub struct [](P, PhantomData); + impl Sealed for [] {} + impl [] for [] {} + impl [] + where + P: AnyPin, + S: SpiDevice, + { + /// Validate a pin's function on a spi peripheral. + /// + #[doc = "Will err if the pin cannot be used as a " $p " pin for that Spi."] + pub fn validate(p: P, _u: &S) -> Result { + if [<$p:upper>].contains(&(p.borrow().id().num, S::ID)) && + p.borrow().id().bank == crate::gpio::DynBankId::Bank0 { + Ok(Self(p, PhantomData)) + } else { + Err(p) + } + } + } + + #[doc = "Indicates a valid optional " $p " pin for SPI0 or SPI1"] + pub trait []: OptionT {} + + impl [] for OptionTNone {} + impl [] for OptionTSome + where + U: SpiDevice, + T: [], + { + } + } + }; + ($($p:ident),*) => { + $( + pin_validation!($p); + )* + }; +} +pin_validation!(Tx, Rx, Sck, Cs); + +macro_rules! impl_valid_spi { + ($($spi:ident: { + rx: [$($rx:ident),*], + cs: [$($cs:ident),*], + sck: [$($sck:ident),*], + tx: [$($tx:ident),*], + }),*) => { + $( + $(impl ValidPinIdRx<$spi> for $rx {})* + $(impl ValidPinIdTx<$spi> for $tx {})* + $(impl ValidPinIdSck<$spi> for $sck {})* + $(impl ValidPinIdCs<$spi> for $cs {})* + )* + + const RX: &[(u8, usize)] = &[$($(($rx::ID.num, $spi::ID)),*),*]; + const TX: &[(u8, usize)] = &[$($(($tx::ID.num, $spi::ID)),*),*]; + const SCK: &[(u8, usize)] = &[$($(($sck::ID.num, $spi::ID)),*),*]; + const CS: &[(u8, usize)] = &[$($(($cs::ID.num, $spi::ID)),*),*]; + }; +} + +impl_valid_spi!( + SPI0: { + rx: [Gpio0, Gpio4, Gpio16, Gpio20], + cs: [Gpio1, Gpio5, Gpio17, Gpio21], + sck: [Gpio2, Gpio6, Gpio18, Gpio22], + tx: [Gpio3, Gpio7, Gpio19, Gpio23], + }, + SPI1: { + rx: [Gpio8, Gpio12, Gpio24, Gpio28], + cs: [Gpio9, Gpio13, Gpio25, Gpio29], + sck: [Gpio10, Gpio14, Gpio26], + tx: [Gpio11, Gpio15, Gpio27], + } +); + +/// Declares a valid SPI pinout. +pub trait ValidSpiPinout: Sealed { + #[allow(missing_docs)] + type Rx: ValidOptionRx; + #[allow(missing_docs)] + type Cs: ValidOptionCs; + #[allow(missing_docs)] + type Sck: ValidOptionSck; + #[allow(missing_docs)] + type Tx: ValidOptionTx; +} + +impl ValidSpiPinout for (Tx, Sck) +where + Spi: SpiDevice, + Tx: ValidPinTx, + Sck: ValidPinSck, +{ + type Rx = OptionTNone; + type Cs = OptionTNone; + type Sck = OptionTSome; + type Tx = OptionTSome; +} + +impl ValidSpiPinout for (Tx, Rx, Sck) +where + Spi: SpiDevice, + Tx: ValidPinTx, + Sck: ValidPinSck, + Rx: ValidPinRx, +{ + type Rx = OptionTSome; + type Cs = OptionTNone; + type Sck = OptionTSome; + type Tx = OptionTSome; +} diff --git a/rp-hal/rp235x-hal/src/timer.rs b/rp-hal/rp235x-hal/src/timer.rs new file mode 100644 index 0000000..0caf3ef --- /dev/null +++ b/rp-hal/rp235x-hal/src/timer.rs @@ -0,0 +1,639 @@ +//! Timer Peripheral +//! +//! The Timer peripheral on rp235x consists of a 64-bit counter and 4 alarms. +//! The Counter is incremented once per microsecond. It obtains its clock source from the watchdog peripheral, you must enable the watchdog before using this peripheral. +//! Since it would take thousands of years for this counter to overflow you do not need to write logic for dealing with this if using get_counter. +//! +//! Each of the 4 alarms can match on the lower 32 bits of Counter and trigger an interrupt. +//! +//! See [Section 12.8](https://rptl.io/rp2350-datasheet) of the datasheet for more details. + +use core::sync::atomic::{AtomicU8, Ordering}; +use fugit::{MicrosDurationU32, MicrosDurationU64, TimerInstantU64}; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + clocks::ClocksManager, + pac, + resets::SubsystemReset, + typelevel::Sealed, +}; + +/// Instant type used by the Timer & Alarm methods. +pub type Instant = TimerInstantU64<1_000_000>; + +static ALARMS: AtomicU8 = AtomicU8::new(0x0F); +fn take_alarm(mask: u8) -> bool { + critical_section::with(|_| { + let alarms = ALARMS.load(Ordering::Relaxed); + ALARMS.store(alarms & !mask, Ordering::Relaxed); + (alarms & mask) != 0 + }) +} +fn release_alarm(mask: u8) { + critical_section::with(|_| { + let alarms = ALARMS.load(Ordering::Relaxed); + ALARMS.store(alarms | mask, Ordering::Relaxed); + }); +} + +/// Represents Timer0 +/// +/// But unlike the PAC object, we can copy this one when we duplicate the timer. +#[derive(Clone, Copy)] +pub struct CopyableTimer0 { + _inner: (), +} + +/// Represents Timer1 +/// +/// But unlike the PAC object, we can copy this one when we duplicate the timer. +#[derive(Clone, Copy)] +pub struct CopyableTimer1 { + _inner: (), +} + +/// Trait to handle both underlying devices (TIMER0 and TIMER1) +pub trait TimerDevice: Sealed + Clone + Copy + 'static { + /// Index of the Timer. + const ID: usize; + + /// Get a timer registerblock, pointing at the appropriate timer + fn get_perif() -> &'static pac::timer0::RegisterBlock { + if Self::ID == 0 { + unsafe { &*pac::TIMER0::ptr() } + } else { + unsafe { &*pac::TIMER1::ptr() } + } + } +} + +impl TimerDevice for CopyableTimer0 { + const ID: usize = 0; +} +impl Sealed for CopyableTimer0 {} +impl TimerDevice for CopyableTimer1 { + const ID: usize = 1; +} +impl Sealed for CopyableTimer1 {} + +/// Timer peripheral +// +// This struct logically wraps a `pac::TIMERx`, but doesn't actually store it: +// As after initialization all accesses are read-only anyways, the `pac::TIMER` can +// be summoned unsafely instead. This allows timer to be cloned. +// +// (Alarms do use write operations, but they are local to the respective alarm, and +// those are still owned singletons.) +// +// As the timer peripheral needs to be started first, this struct can only be +// constructed by calling `Timer::new(...)`. +#[derive(Copy, Clone)] +pub struct Timer { + _device: core::marker::PhantomData, +} + +impl Timer { + /// Create a new [`Timer`] using `TIMER0` + /// + /// Make sure that clocks and watchdog are configured, so + /// that timer ticks happen at a frequency of 1MHz. + /// Otherwise, `Timer` won't work as expected. + pub fn new_timer0( + timer: pac::TIMER0, + resets: &mut pac::RESETS, + _clocks: &ClocksManager, + ) -> Self { + timer.reset_bring_down(resets); + timer.reset_bring_up(resets); + Self { + _device: core::marker::PhantomData, + } + } +} + +impl Timer { + /// Create a new [`Timer`] using `TIMER1` + /// + /// Make sure that clocks and watchdog are configured, so + /// that timer ticks happen at a frequency of 1MHz. + /// Otherwise, `Timer` won't work as expected. + pub fn new_timer1( + timer: pac::TIMER1, + resets: &mut pac::RESETS, + _clocks: &ClocksManager, + ) -> Self { + timer.reset_bring_down(resets); + timer.reset_bring_up(resets); + Self { + _device: core::marker::PhantomData, + } + } +} + +impl Timer +where + D: TimerDevice, +{ + /// Get the current counter value. + pub fn get_counter(&self) -> Instant { + // Safety: Only used for reading current timer value + let timer = D::get_perif(); + let mut hi0 = timer.timerawh().read().bits(); + let timestamp = loop { + let low = timer.timerawl().read().bits(); + let hi1 = timer.timerawh().read().bits(); + if hi0 == hi1 { + break (u64::from(hi0) << 32) | u64::from(low); + } + hi0 = hi1; + }; + TimerInstantU64::from_ticks(timestamp) + } + + /// Get the value of the least significant word of the counter. + pub fn get_counter_low(&self) -> u32 { + // Safety: Only used for reading current timer value + let timer = D::get_perif(); + timer.timerawl().read().bits() + } + + /// Initialized a Count Down instance without starting it. + pub fn count_down(&self) -> CountDown<'_, D> { + CountDown { + timer: self, + period: MicrosDurationU64::nanos(0), + next_end: None, + } + } + /// Retrieve a reference to alarm 0. Will only return a value the first time this is called + pub fn alarm_0(&mut self) -> Option> { + take_alarm(1 << 0).then_some(Alarm0(*self)) + } + + /// Retrieve a reference to alarm 1. Will only return a value the first time this is called + pub fn alarm_1(&mut self) -> Option> { + take_alarm(1 << 1).then_some(Alarm1(*self)) + } + + /// Retrieve a reference to alarm 2. Will only return a value the first time this is called + pub fn alarm_2(&mut self) -> Option> { + take_alarm(1 << 2).then_some(Alarm2(*self)) + } + + /// Retrieve a reference to alarm 3. Will only return a value the first time this is called + pub fn alarm_3(&mut self) -> Option> { + take_alarm(1 << 3).then_some(Alarm3(*self)) + } + + /// Pauses execution for at minimum `us` microseconds. + fn delay_us_internal(&self, mut us: u32) { + let mut start = self.get_counter_low(); + // If we knew that the loop ran at least once per timer tick, + // this could be simplified to: + // ``` + // while timer.timelr().read().bits().wrapping_sub(start) <= us { + // crate::arch::nop(); + // } + // ``` + // However, due to interrupts, for `us == u32::MAX`, we could + // miss the moment where the loop should terminate if the loop skips + // a timer tick. + loop { + let now = self.get_counter_low(); + let waited = now.wrapping_sub(start); + if waited >= us { + break; + } + start = now; + us -= waited; + } + } +} + +macro_rules! impl_delay_traits { + ($($t:ty),+) => { + $( + impl embedded_hal_0_2::blocking::delay::DelayUs<$t> for Timer where D: TimerDevice { + fn delay_us(&mut self, us: $t) { + #![allow(unused_comparisons)] + assert!(us >= 0); // Only meaningful for i32 + self.delay_us_internal(us as u32) + } + } + impl embedded_hal_0_2::blocking::delay::DelayMs<$t> for Timer where D: TimerDevice { + fn delay_ms(&mut self, ms: $t) { + #![allow(unused_comparisons)] + assert!(ms >= 0); // Only meaningful for i32 + for _ in 0..ms { + self.delay_us_internal(1000); + } + } + } + )* + } +} + +// The implementation for i32 is a workaround to allow `delay_ms(42)` construction without specifying a type. +impl_delay_traits!(u8, u16, u32, i32); + +impl embedded_hal::delay::DelayNs for Timer +where + D: TimerDevice, +{ + fn delay_ns(&mut self, ns: u32) { + // For now, just use microsecond delay, internally. Of course, this + // might cause a much longer delay than necessary. So a more advanced + // implementation would be desirable for sub-microsecond delays. + let us = ns.div_ceil(1000); + self.delay_us_internal(us) + } + + fn delay_us(&mut self, us: u32) { + self.delay_us_internal(us) + } + + fn delay_ms(&mut self, ms: u32) { + for _ in 0..ms { + self.delay_us_internal(1000); + } + } +} + +/// Implementation of the [`embedded_hal_0_2::timer`] traits using [`rp235x_hal::timer`](crate::timer) counter. +/// +/// There is no Embedded HAL 1.0 equivalent at this time. +/// +/// If all you need is a delay, [`Timer`] does implement [`embedded_hal::delay::DelayNs`]. +/// +/// ## Usage +/// ```no_run +/// use embedded_hal_0_2::timer::{Cancel, CountDown}; +/// use fugit::ExtU32; +/// use rp235x_hal; +/// let mut pac = rp235x_hal::pac::Peripherals::take().unwrap(); +/// // Make sure to initialize clocks, otherwise the timer wouldn't work +/// // properly. Omitted here for terseness. +/// let clocks: rp235x_hal::clocks::ClocksManager = todo!(); +/// // Configure the Timer peripheral in count-down mode +/// let timer = rp235x_hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); +/// let mut count_down = timer.count_down(); +/// // Create a count_down timer for 500 milliseconds +/// count_down.start(500.millis()); +/// // Block until timer has elapsed +/// let _ = nb::block!(count_down.wait()); +/// // Restart the count_down timer with a period of 100 milliseconds +/// count_down.start(100.millis()); +/// // Cancel it immediately +/// count_down.cancel(); +/// ``` +pub struct CountDown<'timer, D> +where + D: TimerDevice, +{ + timer: &'timer Timer, + period: MicrosDurationU64, + next_end: Option, +} + +impl embedded_hal_0_2::timer::CountDown for CountDown<'_, D> +where + D: TimerDevice, +{ + type Time = MicrosDurationU64; + + fn start(&mut self, count: T) + where + T: Into, + { + self.period = count.into(); + self.next_end = Some( + self.timer + .get_counter() + .ticks() + .wrapping_add(self.period.to_micros()), + ); + } + + fn wait(&mut self) -> nb::Result<(), void::Void> { + if let Some(end) = self.next_end { + let ts = self.timer.get_counter().ticks(); + if ts >= end { + self.next_end = Some(end.wrapping_add(self.period.to_micros())); + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } else { + panic!("CountDown is not running!"); + } + } +} + +impl embedded_hal_0_2::timer::Periodic for CountDown<'_, D> where D: TimerDevice {} + +impl embedded_hal_0_2::timer::Cancel for CountDown<'_, D> +where + D: TimerDevice, +{ + type Error = &'static str; + + fn cancel(&mut self) -> Result<(), Self::Error> { + if self.next_end.is_none() { + Err("CountDown is not running.") + } else { + self.next_end = None; + Ok(()) + } + } +} + +/// Alarm abstraction. +pub trait Alarm: Sealed { + /// Clear the interrupt flag. + /// + /// The interrupt is unable to trigger a 2nd time until this interrupt is cleared. + fn clear_interrupt(&mut self); + + /// Enable this alarm to trigger an interrupt. + /// + /// After this interrupt is triggered, make sure to clear the interrupt with [clear_interrupt]. + /// + /// [clear_interrupt]: #method.clear_interrupt + fn enable_interrupt(&mut self); + + /// Disable this alarm, preventing it from triggering an interrupt. + fn disable_interrupt(&mut self); + + /// Schedule the alarm to be finished after `countdown`. If [enable_interrupt] is called, + /// this will trigger interrupt whenever this time elapses. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule(&mut self, countdown: MicrosDurationU32) -> Result<(), ScheduleAlarmError>; + + /// Schedule the alarm to be finished at the given timestamp. If [enable_interrupt] is + /// called, this will trigger interrupt whenever this timestamp is reached. + /// + /// The rp235x is unable to schedule an event taking place in more than + /// `u32::MAX` microseconds. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule_at(&mut self, timestamp: Instant) -> Result<(), ScheduleAlarmError>; + + /// Return true if this alarm is finished. The returned value is undefined if the alarm + /// has not been scheduled yet. + fn finished(&self) -> bool; + + /// Cancel an activated alarm. + fn cancel(&mut self) -> Result<(), ScheduleAlarmError>; +} + +macro_rules! impl_alarm { + ($name:ident { rb: $timer_alarm:ident, int: $int_alarm:ident, int_name: $int_name:tt, armed_bit_mask: $armed_bit_mask: expr }) => { + /// An alarm that can be used to schedule events in the future. Alarms can also be configured to trigger interrupts. + pub struct $name(Timer) + where + D: TimerDevice; + impl $name + where + D: TimerDevice, + { + fn schedule_internal(&mut self, timestamp: Instant) -> Result<(), ScheduleAlarmError> { + let timestamp_low = (timestamp.ticks() & 0xFFFF_FFFF) as u32; + let timer = D::get_perif(); + + // This lock is for time-criticality + crate::arch::interrupt_free(|| { + let alarm = timer.$timer_alarm(); + + // safety: This is the only code in the codebase that accesses memory address $timer_alarm + alarm.write(|w| unsafe { w.bits(timestamp_low) }); + + // If it is not set, it has already triggered. + let now = self.0.get_counter(); + if now > timestamp && (timer.armed().read().bits() & $armed_bit_mask) != 0 { + // timestamp was set to a value in the past + + // safety: TIMER.armed is a write-clear register, and there can only be + // 1 instance of AlarmN so we can safely atomically clear this bit. + unsafe { + timer.armed().write_with_zero(|w| w.bits($armed_bit_mask)); + crate::atomic_register_access::write_bitmask_set( + timer.intf().as_ptr(), + $armed_bit_mask, + ); + } + } + Ok(()) + }) + } + } + + impl Alarm for $name + where + D: TimerDevice, + { + /// Clear the interrupt flag. This should be called after interrupt ` + #[doc = $int_name] + /// ` is called. + /// + /// The interrupt is unable to trigger a 2nd time until this interrupt is cleared. + fn clear_interrupt(&mut self) { + // safety: TIMER.intr is a write-clear register, so we can atomically clear our interrupt + // by writing its value to this field + // Only one instance of this alarm index can exist, and only this alarm interacts with this bit + // of the TIMER.inte register + unsafe { + let timer = D::get_perif(); + crate::atomic_register_access::write_bitmask_clear( + timer.intf().as_ptr(), + $armed_bit_mask, + ); + timer + .intr() + .write_with_zero(|w| w.$int_alarm().clear_bit_by_one()); + } + } + + /// Enable this alarm to trigger an interrupt. This alarm will trigger ` + #[doc = $int_name] + /// `. + /// + /// After this interrupt is triggered, make sure to clear the interrupt with [clear_interrupt]. + /// + /// [clear_interrupt]: #method.clear_interrupt + fn enable_interrupt(&mut self) { + // safety: using the atomic set alias means we can atomically set our interrupt enable bit. + // Only one instance of this alarm can exist, and only this alarm interacts with this bit + // of the TIMER.inte register + unsafe { + let timer = D::get_perif(); + let reg = timer.inte().as_ptr(); + write_bitmask_set(reg, $armed_bit_mask); + } + } + + /// Disable this alarm, preventing it from triggering an interrupt. + fn disable_interrupt(&mut self) { + // safety: using the atomic set alias means we can atomically clear our interrupt enable bit. + // Only one instance of this alarm can exist, and only this alarm interacts with this bit + // of the TIMER.inte register + unsafe { + let timer = D::get_perif(); + let reg = timer.inte().as_ptr(); + write_bitmask_clear(reg, $armed_bit_mask); + } + } + + /// Schedule the alarm to be finished after `countdown`. If [enable_interrupt] is called, + /// this will trigger interrupt ` + #[doc = $int_name] + /// ` whenever this time elapses. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule(&mut self, countdown: MicrosDurationU32) -> Result<(), ScheduleAlarmError> { + let timestamp = self.0.get_counter() + countdown; + self.schedule_internal(timestamp) + } + + /// Schedule the alarm to be finished at the given timestamp. If [enable_interrupt] is + /// called, this will trigger interrupt ` + #[doc = $int_name] + /// ` whenever this timestamp is reached. + /// + /// The rp235x is unable to schedule an event taking place in more than + /// `u32::MAX` microseconds. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule_at(&mut self, timestamp: Instant) -> Result<(), ScheduleAlarmError> { + let now = self.0.get_counter(); + let duration = timestamp.ticks().saturating_sub(now.ticks()); + if duration > u32::MAX.into() { + return Err(ScheduleAlarmError::AlarmTooLate); + } + + self.schedule_internal(timestamp) + } + + /// Return true if this alarm is finished. The returned value is undefined if the alarm + /// has not been scheduled yet. + fn finished(&self) -> bool { + // safety: This is a read action and should not have any UB + let timer = D::get_perif(); + let bits: u32 = timer.armed().read().bits(); + (bits & $armed_bit_mask) == 0 + } + + /// Cancel an activated Alarm. No negative effects if it's already disabled. + /// Unlike `timer::cancel` trait, this only cancels the alarm and keeps the timer running + /// if it's already active. + fn cancel(&mut self) -> Result<(), ScheduleAlarmError> { + unsafe { + let timer = D::get_perif(); + timer.armed().write_with_zero(|w| w.bits($armed_bit_mask)); + crate::atomic_register_access::write_bitmask_clear( + timer.intf().as_ptr(), + $armed_bit_mask, + ); + } + + Ok(()) + } + } + + impl Sealed for $name where D: TimerDevice {} + + impl Drop for $name + where + D: TimerDevice, + { + fn drop(&mut self) { + self.disable_interrupt(); + release_alarm($armed_bit_mask) + } + } + }; +} + +/// Errors that can be returned from any of the `AlarmX::schedule` methods. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ScheduleAlarmError { + /// Alarm time is too high. Should not be more than `u32::MAX` in the future. + AlarmTooLate, +} + +impl_alarm!(Alarm0 { + rb: alarm0, + int: alarm_0, + int_name: "TIMER_IRQ_0", + armed_bit_mask: 0b0001 +}); + +impl_alarm!(Alarm1 { + rb: alarm1, + int: alarm_1, + int_name: "TIMER_IRQ_1", + armed_bit_mask: 0b0010 +}); + +impl_alarm!(Alarm2 { + rb: alarm2, + int: alarm_2, + int_name: "TIMER_IRQ_2", + armed_bit_mask: 0b0100 +}); + +impl_alarm!(Alarm3 { + rb: alarm3, + int: alarm_3, + int_name: "TIMER_IRQ_3", + armed_bit_mask: 0b1000 +}); + +/// Support for RTIC monotonic trait. +#[cfg(feature = "rtic-monotonic")] +pub mod monotonic { + use super::{Alarm, Instant, Timer, TimerDevice}; + use fugit::ExtU32; + + /// RTIC Monotonic Implementation + pub struct Monotonic(pub Timer, A); + + impl Monotonic { + /// Creates a new monotonic. + pub const fn new(timer: Timer, alarm: A) -> Self { + Self(timer, alarm) + } + } + impl rtic_monotonic::Monotonic for Monotonic { + type Instant = Instant; + type Duration = fugit::MicrosDurationU64; + + const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false; + + fn now(&mut self) -> Instant { + self.0.get_counter() + } + + fn set_compare(&mut self, instant: Instant) { + // The alarm can only trigger up to 2^32 - 1 ticks in the future. + // So, if `instant` is more than 2^32 - 2 in the future, we use `max_instant` instead. + let max_instant = self.0.get_counter() + 0xFFFF_FFFE.micros(); + let wake_at = core::cmp::min(instant, max_instant); + + // Cannot fail + let _ = self.1.schedule_at(wake_at); + self.1.enable_interrupt(); + } + + fn clear_compare_flag(&mut self) { + self.1.clear_interrupt(); + } + + fn zero() -> Self::Instant { + Instant::from_ticks(0) + } + + unsafe fn reset(&mut self) {} + } +} diff --git a/rp-hal/rp235x-hal/src/typelevel.rs b/rp-hal/rp235x-hal/src/typelevel.rs new file mode 100644 index 0000000..24966fb --- /dev/null +++ b/rp-hal/rp235x-hal/src/typelevel.rs @@ -0,0 +1,98 @@ +//! Module supporting type-level programming +//! +//! This is heavily inspired by the work in [`atsamd-rs`](https://github.com/atsamd-rs/atsamd). Please refer to the +//! [documentation](https://docs.rs/atsamd-hal/0.15.1/atsamd_hal/typelevel/index.html) +//! over there for more details. + +mod private { + /// Super trait used to mark traits with an exhaustive set of + /// implementations + pub trait Sealed {} +} + +use core::borrow::{Borrow, BorrowMut}; + +pub(crate) use private::Sealed; + +impl Sealed for (A, B) {} +impl Sealed for (A, B, C) {} +impl Sealed for (A, B, C, D) {} + +impl Sealed for frunk::HNil {} +impl Sealed for frunk::HCons {} + +/// Marker trait for type identity +/// +/// This trait is used as part of the [`AnyKind`] trait pattern. It represents +/// the concept of type identity, because all implementors have +/// `::Type == Self`. When used as a trait bound with a specific +/// type, it guarantees that the corresponding type parameter is exactly the +/// specific type. Stated differently, it guarantees that `T == Specific` in +/// the following example. +/// +/// ```text +/// where T: Is +/// ``` +/// +/// Moreover, the super traits guarantee that any instance of or reference to a +/// type `T` can be converted into the `Specific` type. +/// +/// ``` +/// # use rp235x_hal::typelevel::Is; +/// # struct Specific; +/// fn example(mut any: T) +/// where +/// T: Is, +/// { +/// let specific_mut: &mut Specific = any.borrow_mut(); +/// let specific_ref: &Specific = any.borrow(); +/// let specific: Specific = any.into(); +/// } +/// ``` +/// +/// [`AnyKind`]: https://docs.rs/atsamd-hal/0.15.1/atsamd_hal/typelevel/index.html#anykind-trait-pattern +pub trait Is +where + Self: Sealed, + Self: From>, + Self: Into>, + Self: Borrow>, + Self: BorrowMut>, +{ + #[allow(missing_docs)] + type Type; +} + +/// Type alias for [`Is::Type`] +pub type IsType = ::Type; + +impl Is for T +where + T: Sealed + Borrow + BorrowMut, +{ + type Type = T; +} + +// ===================== +// Type level option +// ===================== + +/// Type-level `enum` for Option. +pub trait OptionT: Sealed { + /// Is this Some or None ? + const IS_SOME: bool; +} + +/// Type-level variant for `OptionT` +pub struct OptionTNone; +impl Sealed for OptionTNone {} +impl OptionT for OptionTNone { + const IS_SOME: bool = false; +} + +/// Type-level variant for `OptionT` +pub struct OptionTSome(pub T); +impl Sealed for OptionTSome {} +impl OptionT for OptionTSome { + const IS_SOME: bool = true; +} diff --git a/rp-hal/rp235x-hal/src/uart/common_configs.rs b/rp-hal/rp235x-hal/src/uart/common_configs.rs new file mode 100644 index 0000000..b364454 --- /dev/null +++ b/rp-hal/rp235x-hal/src/uart/common_configs.rs @@ -0,0 +1,2 @@ +#[doc(inline)] +pub use rp_hal_common::uart::common_configs::*; diff --git a/rp-hal/rp235x-hal/src/uart/mod.rs b/rp-hal/rp235x-hal/src/uart/mod.rs new file mode 100644 index 0000000..4f5efe9 --- /dev/null +++ b/rp-hal/rp235x-hal/src/uart/mod.rs @@ -0,0 +1,72 @@ +//! Universal Asynchronous Receiver Transmitter (UART) +//! +//! See [Section 12.1](https://rptl.io/rp2350-datasheet#section_uart) of the datasheet for more details. +//! +//! ## Usage +//! +//! See [examples/uart.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/uart.rs) for a more complete example. +//! +//! ```no_run +//! use fugit::RateExtU32; +//! use rp235x_hal::{ +//! self as hal, +//! clocks::init_clocks_and_plls, +//! gpio::{FunctionUart, Pins}, +//! pac, +//! sio::Sio, +//! uart::{self, DataBits, StopBits, UartConfig, UartPeripheral}, +//! watchdog::Watchdog, +//! Clock, +//! }; +//! +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! let mut clocks = init_clocks_and_plls( +//! XOSC_CRYSTAL_FREQ, +//! peripherals.XOSC, +//! peripherals.CLOCKS, +//! peripherals.PLL_SYS, +//! peripherals.PLL_USB, +//! &mut peripherals.RESETS, +//! &mut watchdog, +//! ) +//! .ok() +//! .unwrap(); +//! +//! // Set up UART on GP0 and GP1 (Pico pins 1 and 2) +//! let pins = (pins.gpio0.into_function(), pins.gpio1.into_function()); +//! // Need to perform clock init before using UART or it will freeze. +//! let uart = UartPeripheral::new(peripherals.UART0, pins, &mut peripherals.RESETS) +//! .enable( +//! UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One), +//! clocks.peripheral_clock.freq(), +//! ) +//! .unwrap(); +//! +//! uart.write_full_blocking(b"Hello World!\r\n"); +//! ``` + +mod peripheral; +mod pins; +mod reader; +mod utils; +mod writer; + +pub use peripheral::UartPeripheral; +pub use pins::*; +pub use reader::{ReadError, ReadErrorType, Reader}; +pub use utils::*; +pub use writer::Writer; + +/// Common configurations for UART. +#[deprecated(note = "Use UartConfig::new(...) instead.")] +pub mod common_configs; diff --git a/rp-hal/rp235x-hal/src/uart/peripheral.rs b/rp-hal/rp235x-hal/src/uart/peripheral.rs new file mode 100644 index 0000000..95421d3 --- /dev/null +++ b/rp-hal/rp235x-hal/src/uart/peripheral.rs @@ -0,0 +1,522 @@ +//! Universal Asynchronous Receiver Transmitter - Bi-directional Peripheral Code +//! +//! This module brings together `uart::reader` and `uart::writer` to give a +//! UartPeripheral object that can both read and write. + +use core::{convert::Infallible, fmt}; +use embedded_hal_0_2::serial as eh0; +use fugit::HertzU32; +use nb::Error::{Other, WouldBlock}; + +use crate::{ + pac::{self, uart0::uartlcr_h::W as UART_LCR_H_Writer, Peripherals, UART0, UART1}, + typelevel::OptionT, + uart::*, +}; + +use embedded_hal_nb::serial::{ErrorType, Read, Write}; + +/// An UART Peripheral based on an underlying UART device. +pub struct UartPeripheral> { + device: D, + _state: S, + pins: P, + read_error: Option, +} + +impl> UartPeripheral { + fn transition(self, state: To) -> UartPeripheral { + UartPeripheral { + device: self.device, + pins: self.pins, + _state: state, + read_error: None, + } + } + + /// Releases the underlying device and pins. + pub fn free(self) -> (D, P) { + (self.device, self.pins) + } +} + +impl> UartPeripheral { + /// Creates an UartPeripheral in Disabled state. + pub fn new(device: D, pins: P, resets: &mut pac::RESETS) -> UartPeripheral { + device.reset_bring_down(resets); + device.reset_bring_up(resets); + + UartPeripheral { + device, + _state: Disabled, + pins, + read_error: None, + } + } + + /// Enables the provided UART device with the given configuration. + pub fn enable( + self, + config: UartConfig, + frequency: HertzU32, + ) -> Result, Error> { + let (mut device, pins) = self.free(); + configure_baudrate(&mut device, config.baudrate, frequency)?; + + device.uartlcr_h().write(|w| { + // FIFOs are enabled + w.fen().set_bit(); // Leaved here for backward compatibility + set_format(w, &config.data_bits, &config.stop_bits, &config.parity); + w + }); + + // Enable the UART, and the TX,RC,CTS and RTS based on the pins + device.uartcr().write(|w| { + w.uarten().set_bit(); + w.txe().bit(P::Tx::IS_SOME); + w.rxe().bit(P::Rx::IS_SOME); + w.ctsen().bit(P::Cts::IS_SOME); + w.rtsen().bit(P::Rts::IS_SOME); + + w + }); + + device.uartdmacr().write(|w| { + w.txdmae().set_bit(); + w.rxdmae().set_bit(); + w + }); + + Ok(UartPeripheral { + device, + pins, + _state: Enabled, + read_error: None, + }) + } +} + +impl> UartPeripheral { + /// Disable this UART Peripheral, falling back to the Disabled state. + pub fn disable(self) -> UartPeripheral { + // Disable the UART, both TX and RX + self.device.uartcr().write(|w| { + w.uarten().clear_bit(); + w.txe().clear_bit(); + w.rxe().clear_bit(); + w.ctsen().clear_bit(); + w.rtsen().clear_bit(); + w + }); + + self.transition(Disabled) + } + + /// Enable/disable the rx/tx FIFO + /// + /// Unfortunately, it's not possible to enable/disable rx/tx + /// independently on this chip + /// Default is false + pub fn set_fifos(&mut self, enable: bool) { + super::reader::set_fifos(&self.device, enable) + } + + /// Set rx FIFO watermark + /// + /// See DS: Table 423 + pub fn set_rx_watermark(&mut self, watermark: FifoWatermark) { + super::reader::set_rx_watermark(&self.device, watermark) + } + + /// Set tx FIFO watermark + /// + /// See DS: Table 423 + pub fn set_tx_watermark(&mut self, watermark: FifoWatermark) { + super::writer::set_tx_watermark(&self.device, watermark) + } + + /// Enables the Receive Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is data in the receive register. + pub fn enable_rx_interrupt(&mut self) { + super::reader::enable_rx_interrupt(&self.device) + } + + /// Enables the Transmit Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is space in the transmit FIFO. + pub fn enable_tx_interrupt(&mut self) { + super::writer::enable_tx_interrupt(&self.device) + } + + /// Disables the Receive Interrupt. + pub fn disable_rx_interrupt(&mut self) { + super::reader::disable_rx_interrupt(&self.device) + } + + /// Disables the Transmit Interrupt. + pub fn disable_tx_interrupt(&mut self) { + super::writer::disable_tx_interrupt(&self.device) + } + + /// Is there space in the UART TX FIFO for new data to be written? + pub fn uart_is_writable(&self) -> bool { + super::writer::uart_is_writable(&self.device) + } + + /// Is the UART still busy transmitting data? + pub fn uart_is_busy(&self) -> bool { + super::writer::uart_is_busy(&self.device) + } + + /// Is there data in the UART RX FIFO ready to be read? + pub fn uart_is_readable(&self) -> bool { + super::reader::is_readable(&self.device) + } + + /// Writes bytes to the UART. + /// This function writes as long as it can. As soon that the FIFO is full, if : + /// - 0 bytes were written, a WouldBlock Error is returned + /// - some bytes were written, it is deemed to be a success + /// + /// Upon success, the remaining slice is returned. + pub fn write_raw<'d>(&self, data: &'d [u8]) -> nb::Result<&'d [u8], Infallible> { + super::writer::write_raw(&self.device, data) + } + + /// Reads bytes from the UART. + /// This function reads as long as it can. As soon that the FIFO is empty, if : + /// - 0 bytes were read, a WouldBlock Error is returned + /// - some bytes were read, it is deemed to be a success + /// + /// Upon success, it will return how many bytes were read. + pub fn read_raw<'b>(&self, buffer: &'b mut [u8]) -> nb::Result> { + super::reader::read_raw(&self.device, buffer) + } + + /// Writes bytes to the UART. + /// + /// This function blocks until the full buffer has been sent. + pub fn write_full_blocking(&self, data: &[u8]) { + super::writer::write_full_blocking(&self.device, data); + } + + /// Reads bytes from the UART. + /// + /// This function blocks until the full buffer has been received. + pub fn read_full_blocking(&self, buffer: &mut [u8]) -> Result<(), ReadErrorType> { + super::reader::read_full_blocking(&self.device, buffer) + } + + /// Initiates a break + /// + /// If transmitting, this takes effect immediately after the current byte has completed. + /// For proper execution of the break command, this must be held for at least 2 complete frames + /// worth of time. + /// + ///

+ /// + /// # Example + /// + /// ```no_run + /// # use rp235x_hal::uart::{Pins, ValidUartPinout, Enabled, UartPeripheral}; + /// # use rp235x_hal::pac::UART0; + /// # use rp235x_hal::timer::{Timer, CopyableTimer0}; + /// # use rp235x_hal::typelevel::OptionTNone; + /// # use embedded_hal_0_2::blocking::delay::DelayUs; + /// # type PINS = Pins; + /// # fn example(mut serial: UartPeripheral, mut timer: Timer) { + /// serial.lowlevel_break_start(); + /// // at 115_200Bps on 8N1 configuration, 20bits takes (20*10⁶)/115200 = 173.611…μs. + /// timer.delay_us(175); + /// serial.lowlevel_break_stop(); + /// } + /// ``` + pub fn lowlevel_break_start(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().set_bit()); + } + + /// Terminates a break condition. + /// + /// See `lowlevel_break_start` for more details. + pub fn lowlevel_break_stop(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().clear_bit()); + } + + /// Join the reader and writer halves together back into the original Uart peripheral. + /// + /// A reader/writer pair can be obtained by calling [`split`]. + /// + /// [`split`]: #method.split + pub fn join(reader: Reader, writer: Writer) -> Self { + let _ = writer; + Self { + device: reader.device, + _state: Enabled, + pins: reader.pins, + read_error: reader.read_error, + } + } +} + +impl> UartPeripheral { + /// Split this peripheral into a separate reader and writer. + pub fn split(self) -> (Reader, Writer) { + let reader = Reader { + device: self.device, + pins: self.pins, + read_error: self.read_error, + }; + // Safety: reader and writer will never write to the same address + let device_copy = unsafe { Peripherals::steal().UART0 }; + let writer = Writer { + device: device_copy, + device_marker: core::marker::PhantomData, + pins: core::marker::PhantomData, + }; + (reader, writer) + } +} + +impl> UartPeripheral { + /// Split this peripheral into a separate reader and writer. + pub fn split(self) -> (Reader, Writer) { + let reader = Reader { + device: self.device, + pins: self.pins, + read_error: self.read_error, + }; + // Safety: reader and writer will never write to the same address + let device_copy = unsafe { Peripherals::steal().UART1 }; + let writer = Writer { + device: device_copy, + device_marker: core::marker::PhantomData, + pins: core::marker::PhantomData, + }; + (reader, writer) + } +} + +/// The PL011 (PrimeCell UART) supports a fractional baud rate divider +/// From the wanted baudrate, we calculate the divider's two parts: integer and fractional parts. +/// Code inspired from the C SDK. +fn calculate_baudrate_dividers( + wanted_baudrate: HertzU32, + frequency: HertzU32, +) -> Result<(u16, u16), Error> { + // See [Section 12.1.7.1](https://rptl.io/rp2350-datasheet#section_uart) + // of the RP2350 datasheet for an explanation of how baudrate is calculated + let baudrate_div = frequency + .to_Hz() + .checked_mul(8) + .and_then(|r| r.checked_div(wanted_baudrate.to_Hz())) + .ok_or(Error::BadArgument)?; + + Ok(match (baudrate_div >> 7, ((baudrate_div & 0x7F) + 1) / 2) { + (0, _) => (1, 0), + + (int_part, _) if int_part >= 65535 => (65535, 0), + + (int_part, frac_part) => (int_part as u16, frac_part as u16), + }) +} + +/// Baudrate configuration. Code loosely inspired from the C SDK. +#[allow(unknown_lints)] +#[allow(clippy::needless_pass_by_ref_mut)] +fn configure_baudrate( + device: &mut U, + wanted_baudrate: HertzU32, + frequency: HertzU32, +) -> Result { + let (baud_div_int, baud_div_frac) = calculate_baudrate_dividers(wanted_baudrate, frequency)?; + + // First we load the integer part of the divider. + device.uartibrd().write(|w| unsafe { + w.baud_divint().bits(baud_div_int); + w + }); + + // Then we load the fractional part of the divider. + device.uartfbrd().write(|w| unsafe { + w.baud_divfrac().bits(baud_div_frac as u8); + w + }); + + // PL011 needs a (dummy) line control register write to latch in the + // divisors. We don't want to actually change LCR contents here. + device.uartlcr_h().modify(|_, w| w); + + Ok(HertzU32::from_raw( + (4 * frequency.to_Hz()) / (64 * baud_div_int as u32 + baud_div_frac as u32), + )) +} + +/// Format configuration. Code loosely inspired from the C SDK. +fn set_format<'w>( + w: &'w mut UART_LCR_H_Writer, + data_bits: &DataBits, + stop_bits: &StopBits, + parity: &Option, +) -> &'w mut UART_LCR_H_Writer { + match parity { + Some(p) => { + w.pen().set_bit(); + match p { + Parity::Odd => w.eps().clear_bit(), + Parity::Even => w.eps().set_bit(), + }; + } + None => { + w.pen().bit(false); + } + }; + + unsafe { + w.wlen().bits(match data_bits { + DataBits::Five => 0b00, + DataBits::Six => 0b01, + DataBits::Seven => 0b10, + DataBits::Eight => 0b11, + }) + }; + + match stop_bits { + StopBits::One => w.stp2().clear_bit(), + StopBits::Two => w.stp2().set_bit(), + }; + + w +} + +impl> eh0::Read for UartPeripheral { + type Error = ReadErrorType; + + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} + +impl> ErrorType for UartPeripheral { + type Error = ReadErrorType; +} + +impl> Read for UartPeripheral { + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} + +impl> eh0::Write for UartPeripheral { + type Error = Infallible; + + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + super::writer::transmit_flushed(&self.device) + } +} + +impl> Write for UartPeripheral { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + super::writer::transmit_flushed(&self.device).map_err(|e| match e { + WouldBlock => WouldBlock, + Other(v) => match v {}, + }) + } +} + +impl> fmt::Write for UartPeripheral { + fn write_str(&mut self, s: &str) -> fmt::Result { + s.bytes() + .try_for_each(|c| nb::block!(self.write(c))) + .map_err(|_| fmt::Error) + } +} + +impl embedded_io::Error for ReadErrorType { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} +impl> embedded_io::ErrorType + for UartPeripheral +{ + type Error = ReadErrorType; +} +impl> embedded_io::Read for UartPeripheral { + fn read(&mut self, buf: &mut [u8]) -> Result { + // If the last read stored an error, report it now + if let Some(err) = self.read_error.take() { + return Err(err); + } + match nb::block!(self.read_raw(buf)) { + Ok(bytes_read) => Ok(bytes_read), + Err(err) if !err.discarded.is_empty() => { + // If an error was reported but some bytes were already read, + // return the data now and store the error for the next + // invocation. + self.read_error = Some(err.err_type); + Ok(err.discarded.len()) + } + Err(err) => Err(err.err_type), + } + } +} + +impl> embedded_io::ReadReady + for UartPeripheral +{ + fn read_ready(&mut self) -> Result { + Ok(self.uart_is_readable() || self.read_error.is_some()) + } +} + +impl> embedded_io::Write for UartPeripheral { + fn write(&mut self, buf: &[u8]) -> Result { + self.write_full_blocking(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> Result<(), Self::Error> { + nb::block!(super::writer::transmit_flushed(&self.device)).unwrap(); // Infallible + Ok(()) + } +} + +impl> embedded_io::WriteReady + for UartPeripheral +{ + fn write_ready(&mut self) -> Result { + Ok(self.uart_is_writable()) + } +} diff --git a/rp-hal/rp235x-hal/src/uart/pins.rs b/rp-hal/rp235x-hal/src/uart/pins.rs new file mode 100644 index 0000000..ed1b40a --- /dev/null +++ b/rp-hal/rp235x-hal/src/uart/pins.rs @@ -0,0 +1,340 @@ +use core::marker::PhantomData; + +use crate::gpio::{ + bank0::*, pin::pin_sealed::TypeLevelPinId, AnyPin, DynFunction, FunctionUart, FunctionUartAux, + PullType, +}; +use crate::pac::{UART0, UART1}; +use crate::typelevel::{OptionT, OptionTNone, OptionTSome, Sealed}; + +use super::UartDevice; + +// All type level checked pins are inherently valid. +macro_rules! pin_validation { + ($p:ident) => { + paste::paste!{ + #[doc = "Indicates a valid " $p " pin the given UART"] + pub trait []: Sealed {} + + #[doc = "A runtime validated " $p " pin for the given UART."] + pub struct [](P, PhantomData); + + impl Sealed for [] {} + + impl [] + where + P: AnyPin, + U: UartDevice, + { + /// Validate a pin's function on a uart peripheral. + /// + #[doc = "Will err if the pin cannot be used as a " $p " pin for that Uart."] + pub fn validate(p: P, _u: &U) -> Result { + if [<$p:upper>].contains(&(p.borrow().id().num, U::ID, p.borrow().function())) && + p.borrow().id().bank == crate::gpio::DynBankId::Bank0 { + Ok(Self(p, PhantomData)) + } else { + Err(p) + } + } + } + + #[doc = "Indicates a valid optional " $p " pin for UART0 or UART1"] + pub trait []: OptionT {} + + impl [] for OptionTNone {} + impl [] for OptionTSome + where + U: UartDevice, + T: [], + { + } + } + }; + ($($p:ident),*) => { + $( + pin_validation!($p); + )* + }; +} +pin_validation!(Tx, Rx, Cts, Rts); + +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} + +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} + +impl ValidPinTx for ValidatedPinTx +where + P: AnyPin, + UART: UartDevice, +{ +} + +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} + +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} + +impl ValidPinRx for ValidatedPinRx +where + P: AnyPin, + UART: UartDevice, +{ +} + +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} + +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} + +impl ValidPinCts for ValidatedPinCts +where + P: AnyPin, + UART: UartDevice, +{ +} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} + +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} + +impl ValidPinRts for ValidatedPinRts +where + P: AnyPin, + UART: UartDevice, +{ +} + +const TX: &[(u8, usize, DynFunction)] = &[ + (Gpio0::ID.num, UART0::ID, DynFunction::Uart), + (Gpio12::ID.num, UART0::ID, DynFunction::Uart), + (Gpio16::ID.num, UART0::ID, DynFunction::Uart), + (Gpio28::ID.num, UART0::ID, DynFunction::Uart), + (Gpio4::ID.num, UART1::ID, DynFunction::Uart), + (Gpio8::ID.num, UART1::ID, DynFunction::Uart), + (Gpio20::ID.num, UART1::ID, DynFunction::Uart), + (Gpio24::ID.num, UART1::ID, DynFunction::Uart), + (Gpio2::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio14::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio18::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio6::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio10::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio22::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio26::ID.num, UART1::ID, DynFunction::UartAux), +]; + +const RX: &[(u8, usize, DynFunction)] = &[ + (Gpio1::ID.num, UART0::ID, DynFunction::Uart), + (Gpio13::ID.num, UART0::ID, DynFunction::Uart), + (Gpio17::ID.num, UART0::ID, DynFunction::Uart), + (Gpio29::ID.num, UART0::ID, DynFunction::Uart), + (Gpio5::ID.num, UART1::ID, DynFunction::Uart), + (Gpio9::ID.num, UART1::ID, DynFunction::Uart), + (Gpio21::ID.num, UART1::ID, DynFunction::Uart), + (Gpio25::ID.num, UART1::ID, DynFunction::Uart), + (Gpio3::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio15::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio19::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio7::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio11::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio23::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio27::ID.num, UART1::ID, DynFunction::UartAux), +]; + +const CTS: &[(u8, usize, DynFunction)] = &[ + (Gpio2::ID.num, UART0::ID, DynFunction::Uart), + (Gpio14::ID.num, UART0::ID, DynFunction::Uart), + (Gpio18::ID.num, UART0::ID, DynFunction::Uart), + (Gpio6::ID.num, UART1::ID, DynFunction::Uart), + (Gpio10::ID.num, UART1::ID, DynFunction::Uart), + (Gpio22::ID.num, UART1::ID, DynFunction::Uart), + (Gpio26::ID.num, UART1::ID, DynFunction::Uart), +]; + +const RTS: &[(u8, usize, DynFunction)] = &[ + (Gpio3::ID.num, UART0::ID, DynFunction::Uart), + (Gpio15::ID.num, UART0::ID, DynFunction::Uart), + (Gpio19::ID.num, UART0::ID, DynFunction::Uart), + (Gpio7::ID.num, UART1::ID, DynFunction::Uart), + (Gpio11::ID.num, UART1::ID, DynFunction::Uart), + (Gpio23::ID.num, UART1::ID, DynFunction::Uart), + (Gpio27::ID.num, UART1::ID, DynFunction::Uart), +]; + +/// Declares a valid UART pinout. +pub trait ValidUartPinout: Sealed { + #[allow(missing_docs)] + type Rx: ValidOptionRx; + #[allow(missing_docs)] + type Tx: ValidOptionTx; + #[allow(missing_docs)] + type Cts: ValidOptionCts; + #[allow(missing_docs)] + type Rts: ValidOptionRts; +} + +impl ValidUartPinout for (Tx, Rx) +where + Uart: UartDevice, + Tx: ValidPinTx, + Rx: ValidPinRx, +{ + type Tx = OptionTSome; + type Rx = OptionTSome; + type Cts = OptionTNone; + type Rts = OptionTNone; +} + +impl ValidUartPinout for (Tx, Rx, Cts, Rts) +where + Uart: UartDevice, + Tx: ValidPinTx, + Rx: ValidPinRx, + Cts: ValidPinCts, + Rts: ValidPinRts, +{ + type Rx = OptionTSome; + type Tx = OptionTSome; + type Cts = OptionTSome; + type Rts = OptionTSome; +} + +/// Customizable Uart pinout, allowing you to set the pins individually. +/// +/// The following pins are valid UART pins: +/// +/// |UART | TX | RX |CTS (or TX in Aux mode)|RTS (or RX in Aux mode)| +/// |-----|-------------|-------------|-----------------------|-----------------------| +/// |UART0|0, 12, 16, 28|1, 13, 17, 29|2, 14, 18 |3, 15, 19 | +/// |UART1|4, 8, 20, 24 |5, 9, 21, 25 |6, 10, 22, 26 |7, 11, 23, 27 | +/// +/// The RP235x allows you to use CTS pins as TX pins by using the +/// `FunctionUartAux` pin function (instead of `FunctionUart`). The same goes +/// for using RTS pins as RX pins. +/// +/// Every field can be set to [`OptionTNone`] to not configure them. +/// +/// Note that you can also use tuples `(RX, TX)` or `(RX, TX, CTS, RTS)` instead of this type. +/// +/// This struct can either be filled manually or with a builder pattern: +/// +/// ```no_run +/// # use rp235x_hal::uart::{Pins, ValidUartPinout}; +/// # use rp235x_hal::pac::UART0; +/// # let gpio_pins: rp235x_hal::gpio::Pins = unsafe { core::mem::zeroed() }; +/// let pins = Pins::default() +/// .tx(gpio_pins.gpio0.into_function()) +/// .rx(gpio_pins.gpio1.into_function()); +/// +/// fn assert_is_valid_uart0>(_: T) {} +/// +/// assert_is_valid_uart0(pins); +/// ``` +pub struct Pins { + #[allow(missing_docs)] + pub tx: Tx, + #[allow(missing_docs)] + pub rx: Rx, + #[allow(missing_docs)] + pub cts: Cts, + #[allow(missing_docs)] + pub rts: Rts, +} + +impl Default for Pins { + fn default() -> Self { + Self { + tx: OptionTNone, + rx: OptionTNone, + rts: OptionTNone, + cts: OptionTNone, + } + } +} + +impl Pins { + /// Set the TX pin + pub fn tx(self, tx: NewTx) -> Pins, Rx, Cts, Rts> { + Pins { + tx: OptionTSome(tx), + rx: self.rx, + rts: self.rts, + cts: self.cts, + } + } + /// Set the RX pin + pub fn rx(self, rx: NewRx) -> Pins, Cts, Rts> { + Pins { + tx: self.tx, + rx: OptionTSome(rx), + rts: self.rts, + cts: self.cts, + } + } + /// Set the CTS pin + pub fn cts(self, cts: NewCts) -> Pins, Rts> { + Pins { + tx: self.tx, + rx: self.rx, + rts: self.rts, + cts: OptionTSome(cts), + } + } + /// Set the RTS pin + pub fn rts(self, rts: NewRts) -> Pins> { + Pins { + tx: self.tx, + rx: self.rx, + rts: OptionTSome(rts), + cts: self.cts, + } + } +} + +impl Sealed for Pins {} +impl ValidUartPinout for Pins +where + Uart: UartDevice, + Tx: ValidOptionTx, + Rx: ValidOptionRx, + Cts: ValidOptionCts, + Rts: ValidOptionRts, +{ + type Rx = Rx; + type Tx = Tx; + type Cts = Cts; + type Rts = Rts; +} diff --git a/rp-hal/rp235x-hal/src/uart/reader.rs b/rp-hal/rp235x-hal/src/uart/reader.rs new file mode 100644 index 0000000..cdef686 --- /dev/null +++ b/rp-hal/rp235x-hal/src/uart/reader.rs @@ -0,0 +1,335 @@ +//! Universal Asynchronous Receiver Transmitter - Receiver Code +//! +//! This module is for receiving data with a UART. + +use super::{FifoWatermark, UartDevice, ValidUartPinout}; +use crate::dma::{EndlessReadTarget, ReadTarget}; +use crate::pac::uart0::RegisterBlock; +use embedded_hal_0_2::serial::Read as Read02; +use nb::Error::*; + +use embedded_hal_nb::serial::{Error, ErrorKind, ErrorType, Read}; + +/// When there's a read error. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug)] +pub struct ReadError<'err> { + /// The type of error + pub err_type: ReadErrorType, + + /// Reference to the data that was read but eventually discarded because of the error. + pub discarded: &'err [u8], +} + +/// Possible types of read errors. +/// +/// See [Section 12.1.8](https://rptl.io/rp2350-datasheet#section_uart) "UARTDR +/// Register" +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug)] +pub enum ReadErrorType { + /// Triggered when the FIFO (or shift-register) is overflowed. + Overrun, + + /// Triggered when a break is received + Break, + + /// Triggered when there is a parity mismatch between what's received and our settings. + Parity, + + /// Triggered when the received character didn't have a valid stop bit. + Framing, +} + +impl Error for ReadErrorType { + fn kind(&self) -> ErrorKind { + match self { + ReadErrorType::Overrun => ErrorKind::Overrun, + ReadErrorType::Break => ErrorKind::Other, + ReadErrorType::Parity => ErrorKind::Parity, + ReadErrorType::Framing => ErrorKind::FrameFormat, + } + } +} + +pub(crate) fn is_readable(device: &D) -> bool { + device.uartfr().read().rxfe().bit_is_clear() +} + +/// Enable/disable the rx/tx FIFO +/// +/// Unfortunately, it's not possible to enable/disable rx/tx +/// independently on this chip +/// Default is false +pub fn set_fifos(rb: &RegisterBlock, enable: bool) { + if enable { + rb.uartlcr_h().modify(|_r, w| w.fen().set_bit()) + } else { + rb.uartlcr_h().modify(|_r, w| w.fen().clear_bit()) + } +} + +/// Set rx FIFO watermark +/// +/// See DS: Table 423 +pub fn set_rx_watermark(rb: &RegisterBlock, watermark: FifoWatermark) { + let wm = match watermark { + FifoWatermark::Bytes4 => 0, + FifoWatermark::Bytes8 => 1, + FifoWatermark::Bytes16 => 2, + FifoWatermark::Bytes24 => 3, + FifoWatermark::Bytes28 => 4, + }; + rb.uartifls() + .modify(|_r, w| unsafe { w.rxiflsel().bits(wm) }); +} + +/// Enables the Receive Interrupt. +/// +/// The relevant UARTx IRQ will fire when there is data in the receive register. +pub(crate) fn enable_rx_interrupt(rb: &RegisterBlock) { + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // high enables the interrupt. + + // We set the RX interrupt, and the RX Timeout interrupt. This means + // we will get an interrupt when the RX FIFO level is triggered, or + // when the RX FIFO is non-empty, but 32-bit periods have passed with + // no further data. This means we don't have to interrupt on every + // single byte, but can make use of the hardware FIFO. + rb.uartimsc().modify(|_r, w| { + w.rxim().set_bit(); + w.rtim().set_bit(); + w + }); +} + +/// Disables the Receive Interrupt. +pub(crate) fn disable_rx_interrupt(rb: &RegisterBlock) { + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // low disables the interrupt. + + rb.uartimsc().modify(|_r, w| { + w.rxim().clear_bit(); + w.rtim().clear_bit(); + w + }); +} + +pub(crate) fn read_raw<'b, D: UartDevice>( + device: &D, + buffer: &'b mut [u8], +) -> nb::Result> { + let mut bytes_read = 0; + + Ok(loop { + if !is_readable(device) { + if bytes_read == 0 { + // The overrun error (OE) bit is checked separately as it + // doesn't really correspond to a specific byte we've read. If + // we don't do this here, the overrun error is hidden until the + // next byte turns up - which may never happen. + if device.uartrsr().read().oe().bit_is_set() { + // We observed a FIFO overrun on an empty FIFO. Clear the + // error otherwise it sticks. + unsafe { + device.uartrsr().write_with_zero(|w| w); + } + // Now report the error. + // + // Note that you will also get an overrun error on the first + // byte that turns up after this error - we can't stop that + // as we have no mutable state to indicate that it's already + // been handled. But two overrun errors is better that none. + return Err(Other(ReadError { + err_type: ReadErrorType::Overrun, + discarded: &buffer[..bytes_read], + })); + } else { + return Err(WouldBlock); + } + } else { + break bytes_read; + } + } + + if bytes_read < buffer.len() { + let mut error: Option = None; + + let read = device.uartdr().read(); + + // If multiple status bits are set, report + // the most serious or most specific condition, + // in the following order of precedence: + // break > parity > framing + // + // overrun is last because the byte associated with it is still good. + if read.be().bit_is_set() { + error = Some(ReadErrorType::Break); + } else if read.pe().bit_is_set() { + error = Some(ReadErrorType::Parity); + } else if read.fe().bit_is_set() { + error = Some(ReadErrorType::Framing); + } else if read.oe().bit_is_set() { + error = Some(ReadErrorType::Overrun); + // if we get an overrun - there's still data there + buffer[bytes_read] = read.data().bits(); + bytes_read += 1; + } + + if let Some(err_type) = error { + return Err(Other(ReadError { + err_type, + discarded: &buffer[..bytes_read], + })); + } + + buffer[bytes_read] = read.data().bits(); + bytes_read += 1; + } else { + break bytes_read; + } + }) +} + +pub(crate) fn read_full_blocking( + device: &D, + buffer: &mut [u8], +) -> Result<(), ReadErrorType> { + let mut offset = 0; + + while offset != buffer.len() { + offset += match read_raw(device, &mut buffer[offset..]) { + Ok(bytes_read) => bytes_read, + Err(e) => match e { + Other(inner) => return Err(inner.err_type), + WouldBlock => continue, + }, + } + } + + Ok(()) +} + +/// Half of an [`UartPeripheral`] that is only capable of reading. Obtained by calling [`UartPeripheral::split()`] +/// +/// [`UartPeripheral`]: struct.UartPeripheral.html +/// [`UartPeripheral::split()`]: struct.UartPeripheral.html#method.split +pub struct Reader> { + pub(super) device: D, + pub(super) pins: P, + pub(super) read_error: Option, +} + +impl> Reader { + /// Reads bytes from the UART. + /// This function reads as long as it can. As soon that the FIFO is empty, if : + /// - 0 bytes were read, a WouldBlock Error is returned + /// - some bytes were read, it is deemed to be a success + /// + /// Upon success, it will return how many bytes were read. + pub fn read_raw<'b>(&self, buffer: &'b mut [u8]) -> nb::Result> { + read_raw(&self.device, buffer) + } + + /// Reads bytes from the UART. + /// This function blocks until the full buffer has been received. + pub fn read_full_blocking(&self, buffer: &mut [u8]) -> Result<(), ReadErrorType> { + read_full_blocking(&self.device, buffer) + } + + /// Enables the Receive Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is data in the receive register. + pub fn enable_rx_interrupt(&mut self) { + enable_rx_interrupt(&self.device) + } + + /// Disables the Receive Interrupt. + pub fn disable_rx_interrupt(&mut self) { + disable_rx_interrupt(&self.device) + } +} + +impl> embedded_io::ErrorType for Reader { + type Error = ReadErrorType; +} + +impl> embedded_io::Read for Reader { + fn read(&mut self, buf: &mut [u8]) -> Result { + // If the last read stored an error, report it now + if let Some(err) = self.read_error.take() { + return Err(err); + } + match nb::block!(self.read_raw(buf)) { + Ok(bytes_read) => Ok(bytes_read), + Err(err) if !err.discarded.is_empty() => { + // If an error was reported but some bytes were already read, + // return the data now and store the error for the next + // invocation. + self.read_error = Some(err.err_type); + Ok(err.discarded.len()) + } + Err(err) => Err(err.err_type), + } + } +} + +impl> embedded_io::ReadReady for Reader { + fn read_ready(&mut self) -> Result { + Ok(is_readable(&self.device)) + } +} + +impl> Read02 for Reader { + type Error = ReadErrorType; + + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} + +// Safety: This only reads from the RX fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl> ReadTarget for Reader { + type ReceivedWord = u8; + + fn rx_treq() -> Option { + Some(D::rx_dreq()) + } + + fn rx_address_count(&self) -> (u32, u32) { + (self.device.uartdr().as_ptr() as u32, u32::MAX) + } + + fn rx_increment(&self) -> bool { + false + } +} + +impl> EndlessReadTarget for Reader {} + +impl> ErrorType for Reader { + type Error = ReadErrorType; +} + +impl> Read for Reader { + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} diff --git a/rp-hal/rp235x-hal/src/uart/utils.rs b/rp-hal/rp235x-hal/src/uart/utils.rs new file mode 100644 index 0000000..ce1bc2c --- /dev/null +++ b/rp-hal/rp235x-hal/src/uart/utils.rs @@ -0,0 +1,93 @@ +use crate::pac::dma::ch::ch_ctrl_trig::TREQ_SEL_A; +use crate::pac::{uart0::RegisterBlock, UART0, UART1}; +use crate::resets::SubsystemReset; +use crate::typelevel::Sealed; +use core::ops::Deref; + +#[doc(inline)] +pub use rp_hal_common::uart::{DataBits, Parity, StopBits, UartConfig}; + +/// Error type for UART operations. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Bad argument : when things overflow, ... + BadArgument, +} + +/// State of the UART Peripheral. +pub trait State: Sealed {} + +/// Trait to handle both underlying devices (UART0 & UART1) +pub trait UartDevice: Deref + SubsystemReset + Sealed + 'static { + /// Index of the Uart. + const ID: usize; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8 + where + Self: Sized; + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8 + where + Self: Sized; +} + +impl UartDevice for UART0 { + const ID: usize = 0; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8 { + TREQ_SEL_A::UART0_TX.into() + } + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8 { + TREQ_SEL_A::UART0_RX.into() + } +} +impl Sealed for UART0 {} +impl UartDevice for UART1 { + const ID: usize = 1; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8 { + TREQ_SEL_A::UART1_TX.into() + } + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8 { + TREQ_SEL_A::UART1_RX.into() + } +} +impl Sealed for UART1 {} + +/// UART is enabled. +pub struct Enabled; + +/// UART is disabled. +pub struct Disabled; + +impl State for Enabled {} +impl Sealed for Enabled {} +impl State for Disabled {} +impl Sealed for Disabled {} + +/// Rx/Tx FIFO Watermark +/// +/// Determine the FIFO level that trigger DMA/Interrupt +/// Default is Bytes16, see DS Table 423 and UARTIFLS Register +/// Example of use: +/// uart0.set_fifos(true); // Default is false +/// uart0.set_rx_watermark(hal::uart::FifoWatermark::Bytes8); +/// uart0.enable_rx_interrupt(); +pub enum FifoWatermark { + /// Trigger when 4 bytes are (Rx: filled / Tx: available) + Bytes4, + /// Trigger when 8 bytes are (Rx: filled / Tx: available) + Bytes8, + /// Trigger when 16 bytes are (Rx: filled / Tx: available) + Bytes16, + /// Trigger when 24 bytes are (Rx: filled / Tx: available) + Bytes24, + /// Trigger when 28 bytes are (Rx: filled / Tx: available) + Bytes28, +} diff --git a/rp-hal/rp235x-hal/src/uart/writer.rs b/rp-hal/rp235x-hal/src/uart/writer.rs new file mode 100644 index 0000000..ac9c477 --- /dev/null +++ b/rp-hal/rp235x-hal/src/uart/writer.rs @@ -0,0 +1,296 @@ +//! Universal Asynchronous Receiver Transmitter - Transmitter Code +//! +//! This module is for transmitting data with a UART. + +use super::{FifoWatermark, UartDevice, ValidUartPinout}; +use crate::dma::{EndlessWriteTarget, WriteTarget}; +use crate::pac::uart0::RegisterBlock; +use core::fmt; +use core::{convert::Infallible, marker::PhantomData}; +use embedded_hal_0_2::serial::Write as Write02; +use embedded_hal_nb::serial::{ErrorType, Write}; +use nb::Error::*; + +/// Set tx FIFO watermark +/// +/// See DS: Table 423 +pub fn set_tx_watermark(rb: &RegisterBlock, watermark: FifoWatermark) { + let wm = match watermark { + FifoWatermark::Bytes4 => 4, + FifoWatermark::Bytes8 => 3, + FifoWatermark::Bytes16 => 2, + FifoWatermark::Bytes24 => 1, + FifoWatermark::Bytes28 => 0, + }; + rb.uartifls() + .modify(|_r, w| unsafe { w.txiflsel().bits(wm) }); +} + +/// Returns `Err(WouldBlock)` if the UART is still busy transmitting data. +/// It returns Ok(()) when the TX fifo and the transmit shift register are empty +/// and the last stop bit is sent. +pub(crate) fn transmit_flushed(rb: &RegisterBlock) -> nb::Result<(), Infallible> { + if rb.uartfr().read().busy().bit_is_set() { + Err(WouldBlock) + } else { + Ok(()) + } +} + +/// Returns `true` if the TX FIFO has space, or false if it is full +pub(crate) fn uart_is_writable(rb: &RegisterBlock) -> bool { + rb.uartfr().read().txff().bit_is_clear() +} + +/// Returns `true` if the UART is busy transmitting data, `false` after all +/// bits (including stop bits) have been transmitted. +pub(crate) fn uart_is_busy(rb: &RegisterBlock) -> bool { + rb.uartfr().read().busy().bit_is_set() +} + +/// Writes bytes to the UART. +/// +/// This function writes as long as it can. As soon that the FIFO is full, +/// if: +/// - 0 bytes were written, a WouldBlock Error is returned +/// - some bytes were written, it is deemed to be a success +/// +/// Upon success, the remaining (unwritten) slice is returned. +pub(crate) fn write_raw<'d>( + rb: &RegisterBlock, + data: &'d [u8], +) -> nb::Result<&'d [u8], Infallible> { + let mut bytes_written = 0; + + for c in data { + if !uart_is_writable(rb) { + if bytes_written == 0 { + return Err(WouldBlock); + } else { + return Ok(&data[bytes_written..]); + } + } + + rb.uartdr().write(|w| unsafe { + w.data().bits(*c); + w + }); + + bytes_written += 1; + } + Ok(&data[bytes_written..]) +} + +/// Writes bytes to the UART. +/// +/// This function blocks until the full buffer has been sent. +pub(crate) fn write_full_blocking(rb: &RegisterBlock, data: &[u8]) { + let mut temp = data; + + while !temp.is_empty() { + temp = match write_raw(rb, temp) { + Ok(remaining) => remaining, + Err(WouldBlock) => continue, + Err(_) => unreachable!(), + } + } +} + +/// Enables the Transmit Interrupt. +/// +/// The relevant UARTx IRQ will fire when there is space in the transmit FIFO. +pub(crate) fn enable_tx_interrupt(rb: &RegisterBlock) { + // Access the UART FIFO Level Select. We set the TX FIFO trip level + // to be when it's half-empty.. + + // 2 means '<= 1/2 full'. + rb.uartifls() + .modify(|_r, w| unsafe { w.txiflsel().bits(2) }); + + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // high enables the interrupt. + + // We set the TX interrupt. This means we will get an interrupt when + // the TX FIFO level is triggered. This means we don't have to + // interrupt on every single byte, but can make use of the hardware + // FIFO. + rb.uartimsc().modify(|_r, w| { + w.txim().set_bit(); + w + }); +} + +/// Disables the Transmit Interrupt. +pub(crate) fn disable_tx_interrupt(rb: &RegisterBlock) { + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // low disables the interrupt. + + rb.uartimsc().modify(|_r, w| { + w.txim().clear_bit(); + w + }); +} + +/// Half of an [`UartPeripheral`] that is only capable of writing. Obtained by calling [`UartPeripheral::split()`] +/// +/// [`UartPeripheral`]: struct.UartPeripheral.html +/// [`UartPeripheral::split()`]: struct.UartPeripheral.html#method.split +pub struct Writer> { + pub(super) device: D, + pub(super) device_marker: PhantomData, + pub(super) pins: PhantomData

, +} + +impl> Writer { + /// Writes bytes to the UART. + /// + /// This function writes as long as it can. As soon that the FIFO is full, + /// if: + /// - 0 bytes were written, a WouldBlock Error is returned + /// - some bytes were written, it is deemed to be a success + /// + /// Upon success, the remaining (unwritten) slice is returned. + pub fn write_raw<'d>(&self, data: &'d [u8]) -> nb::Result<&'d [u8], Infallible> { + write_raw(&self.device, data) + } + + /// Writes bytes to the UART. + /// + /// This function blocks until the full buffer has been sent. + pub fn write_full_blocking(&self, data: &[u8]) { + write_full_blocking(&self.device, data); + } + + /// Enables the Transmit Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is space in the transmit FIFO. + pub fn enable_tx_interrupt(&mut self) { + enable_tx_interrupt(&self.device) + } + + /// Disables the Transmit Interrupt. + pub fn disable_tx_interrupt(&mut self) { + disable_tx_interrupt(&self.device) + } + + /// Initiates a break + /// + /// If transmitting, this takes effect immediately after the current byte has completed. + /// For proper execution of the break command, this must be held for at least 2 complete frames + /// worth of time. + /// + ///

The device won’t be able to send anything while breaking.
+ /// + /// # Example + /// + /// ```no_run + /// # use rp235x_hal::uart::{Pins, ValidUartPinout, Enabled, UartPeripheral}; + /// # use rp235x_hal::pac::UART0; + /// # use rp235x_hal::timer::{Timer, CopyableTimer0}; + /// # use rp235x_hal::typelevel::OptionTNone; + /// # use embedded_hal_0_2::blocking::delay::DelayUs; + /// # type PINS = Pins; + /// # fn example(mut serial: UartPeripheral, mut timer: Timer) { + /// serial.lowlevel_break_start(); + /// // at 115_200Bps on 8N1 configuration, 20bits takes (20*10⁶)/115200 = 173.611…μs. + /// timer.delay_us(175); + /// serial.lowlevel_break_stop(); + /// } + /// ``` + pub fn lowlevel_break_start(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().set_bit()); + } + + /// Terminates a break condition. + /// + /// See `lowlevel_break_start` for more details. + pub fn lowlevel_break_stop(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().clear_bit()); + } +} + +impl> embedded_io::ErrorType for Writer { + type Error = Infallible; +} + +impl> embedded_io::Write for Writer { + fn write(&mut self, buf: &[u8]) -> Result { + let remaining = nb::block!(write_raw(&self.device, buf)).unwrap(); // Infallible + Ok(buf.len() - remaining.len()) + } + fn flush(&mut self) -> Result<(), Self::Error> { + nb::block!(transmit_flushed(&self.device)).unwrap(); // Infallible + Ok(()) + } +} + +impl> embedded_io::WriteReady for Writer { + fn write_ready(&mut self) -> Result { + Ok(uart_is_writable(&self.device)) + } +} + +impl> Write02 for Writer { + type Error = Infallible; + + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + transmit_flushed(&self.device) + } +} + +// Safety: This only writes to the TX fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl> WriteTarget for Writer { + type TransmittedWord = u8; + + fn tx_treq() -> Option { + Some(D::tx_dreq()) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + (self.device.uartdr().as_ptr() as u32, u32::MAX) + } + + fn tx_increment(&self) -> bool { + false + } +} + +impl> EndlessWriteTarget for Writer {} + +impl> ErrorType for Writer { + type Error = Infallible; +} + +impl> Write for Writer { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + transmit_flushed(&self.device).map_err(|e| match e { + WouldBlock => WouldBlock, + Other(v) => match v {}, + }) + } +} + +impl> fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + s.bytes() + .try_for_each(|c| nb::block!(Write::write(self, c))) + .map_err(|_| fmt::Error) + } +} diff --git a/rp-hal/rp235x-hal/src/usb.rs b/rp-hal/rp235x-hal/src/usb.rs new file mode 100644 index 0000000..4440acc --- /dev/null +++ b/rp-hal/rp235x-hal/src/usb.rs @@ -0,0 +1,623 @@ +//! Universal Serial Bus (USB) +//! +//! See [pico_usb_serial.rs](https://github.com/rp-rs/rp-hal-boards/blob/main/boards/rp-pico/examples/pico_usb_serial.rs) for more complete examples +//! +//! +//! ## Enumeration issue with small EP0 max packet size +//! +//! During enumeration Windows hosts send a `StatusOut` after the `DataIn` packet of the first +//! `Get Descriptor` request even if the `DataIn` isn't completed (typically when the `max_packet_size_ep0` +//! is less than 18bytes). The next request is a `Set Address` that expect a `StatusIn`. +//! +//! The issue is that by the time the previous `DataIn` packet is acknowledged and the `StatusOut` +//! followed by `Setup` are received, the usb stack may have already prepared the next `DataIn` payload +//! in the EP0 IN mailbox resulting in the payload being transmitted to the host instead of the +//! `StatusIn` for the `Set Address` request as expected by the host. +//! +//! To avoid that issue, the EP0 In mailbox should be invalidated between the `Setup` packet and the +//! next `StatusIn` initiated by the host. The workaround implemented clears the available bit of the +//! EP0 In endpoint's buffer to stop the device from sending the data instead of the status packet. +//! This workaround has the caveat that the poll function must be called between those two which +//! are only separated by a few microseconds. +//! +//! If the required timing cannot be met, using an maximum packet size of the endpoint 0 above 18bytes +//! (e.g. `.max_packet_size_ep0(64)`) should avoid that issue. + +use core::cell::RefCell; +use critical_section::Mutex; + +use usb_device::{ + bus::{PollResult, UsbBus as UsbBusTrait}, + endpoint::{EndpointAddress, EndpointType}, + Result as UsbResult, UsbDirection, UsbError, +}; + +use crate::{clocks::UsbClock, pac, resets::SubsystemReset}; + +#[allow(clippy::bool_to_int_with_if)] +fn ep_addr_to_ep_buf_ctrl_idx(ep_addr: EndpointAddress) -> usize { + ep_addr.index() * 2 + (if ep_addr.is_in() { 0 } else { 1 }) +} +#[derive(Debug)] +struct Endpoint { + ep_type: EndpointType, + max_packet_size: u16, + buffer_offset: u16, +} +impl Endpoint { + unsafe fn get_buf_parts(&self) -> (*mut u8, usize) { + const DPRAM_BASE: *mut u8 = pac::USB_DPRAM::ptr() as *mut u8; + if self.ep_type == EndpointType::Control { + (DPRAM_BASE.offset(0x100), self.max_packet_size as usize) + } else { + ( + DPRAM_BASE.offset(0x180 + (self.buffer_offset * 64) as isize), + self.max_packet_size as usize, + ) + } + } + + fn get_buf(&self) -> &[u8] { + // SAFETY: + // offset is checked by Inner::ep_allocate. + unsafe { + let (base, len) = self.get_buf_parts(); + core::slice::from_raw_parts(base as *const _, len) + } + } + + fn get_buf_mut(&mut self) -> &mut [u8] { + // SAFETY: + // offset is checked by Inner::ep_allocate. + unsafe { + let (base, len) = self.get_buf_parts(); + core::slice::from_raw_parts_mut(base, len) + } + } +} + +struct Inner { + ctrl_reg: pac::USB, + ctrl_dpram: pac::USB_DPRAM, + in_endpoints: [Option; 16], + out_endpoints: [Option; 16], + next_offset: u16, + read_setup: bool, + pll: UsbClock, +} +impl Inner { + fn new(ctrl_reg: pac::USB, ctrl_dpram: pac::USB_DPRAM, pll: UsbClock) -> Self { + Self { + ctrl_reg, + ctrl_dpram, + in_endpoints: Default::default(), + out_endpoints: Default::default(), + next_offset: 0, + read_setup: false, + pll, + } + } + + fn ep_allocate( + &mut self, + ep_addr: Option, + ep_dir: UsbDirection, + ep_type: EndpointType, + max_packet_size: u16, + ) -> UsbResult { + let ep_addr = ep_addr + .or_else(|| { + let eps = if ep_dir == UsbDirection::In { + self.in_endpoints.iter() + } else { + self.out_endpoints.iter() + }; + // find free end point + let mut iter = eps.enumerate(); + // reserve ep0 for the control endpoint + if ep_type != EndpointType::Control { + iter.next(); + } + iter.find(|(_, ep)| ep.is_none()) + .map(|(index, _)| EndpointAddress::from_parts(index, ep_dir)) + }) + .ok_or(UsbError::EndpointOverflow)?; + + let is_ep0 = ep_addr.index() == 0; + let is_ctrl_ep = ep_type == EndpointType::Control; + if !(is_ep0 ^ !is_ctrl_ep) { + return Err(UsbError::Unsupported); + } + + let eps = if ep_addr.is_in() { + &mut self.in_endpoints + } else { + &mut self.out_endpoints + }; + let maybe_ep = eps + .get_mut(ep_addr.index()) + .ok_or(UsbError::EndpointOverflow)?; + if maybe_ep.is_some() { + return Err(UsbError::InvalidEndpoint); + } + + // Validate buffer size. From datasheet (4.1.2.5): + // Data Buffers are typically 64 bytes long as this is the max normal packet size for most FS packets. + // For Isochronous endpoints a maximum buffer size of 1023 bytes is supported. + // For other packet types the maximum size is 64 bytes per buffer. + if (!matches!(ep_type, EndpointType::Isochronous { .. }) && max_packet_size > 64) + || max_packet_size > 1023 + { + return Err(UsbError::Unsupported); + } + + if ep_addr.index() == 0 { + *maybe_ep = Some(Endpoint { + ep_type, + max_packet_size, + buffer_offset: 0, // not used on CTRL ep + }); + } else { + // size in 64bytes units. + // NOTE: the compiler is smart enough to recognize /64 as a 6bit right shift so let's + // keep the division here for the sake of clarity + let aligned_sized = (max_packet_size + 63) / 64; + if (self.next_offset + aligned_sized) > (4096 / 64) { + return Err(UsbError::EndpointMemoryOverflow); + } + + let buffer_offset = self.next_offset; + self.next_offset += aligned_sized; + + *maybe_ep = Some(Endpoint { + ep_type, + max_packet_size, + buffer_offset, + }); + } + Ok(ep_addr) + } + + fn ep_reset_all(&mut self) { + self.ctrl_reg + .sie_ctrl() + .modify(|_, w| w.ep0_int_1buf().set_bit()); + // expect ctrl ep to receive on DATA first + self.ctrl_dpram + .ep_buffer_control(0) + .write(|w| w.pid_0().set_bit()); + self.ctrl_dpram + .ep_buffer_control(1) + .write(|w| w.pid_0().set_bit()); + crate::arch::delay(12); + self.ctrl_dpram + .ep_buffer_control(1) + .write(|w| w.available_0().set_bit()); + + for (index, ep) in itertools::interleave( + self.in_endpoints.iter().skip(1), // skip control endpoint + self.out_endpoints.iter().skip(1), // skip control endpoint + ) + .enumerate() + .filter_map(|(i, ep)| ep.as_ref().map(|ep| (i, ep))) + { + use crate::pac::usb_dpram::ep_control::ENDPOINT_TYPE_A; + let ep_type = match ep.ep_type { + EndpointType::Bulk => ENDPOINT_TYPE_A::BULK, + EndpointType::Isochronous { .. } => ENDPOINT_TYPE_A::ISOCHRONOUS, + EndpointType::Control => ENDPOINT_TYPE_A::CONTROL, + EndpointType::Interrupt => ENDPOINT_TYPE_A::INTERRUPT, + }; + // configure + // ep 0 in&out are not part of index (skipped before enumeration) + self.ctrl_dpram.ep_control(index).modify(|_, w| unsafe { + w.endpoint_type().variant(ep_type); + w.interrupt_per_buff().set_bit(); + w.enable().set_bit(); + w.buffer_address().bits(0x180 + (ep.buffer_offset << 6)) + }); + // reset OUT ep and prepare IN ep to accept data + let buf_control = &self.ctrl_dpram.ep_buffer_control(index + 2); + if (index & 1) == 0 { + // first write occur on DATA0 so prepare the pid bit to be flipped + buf_control.write(|w| w.pid_0().set_bit()); + } else { + buf_control.write(|w| unsafe { + w.pid_0().clear_bit(); + w.length_0().bits(ep.max_packet_size) + }); + crate::arch::delay(12); + buf_control.modify(|_, w| w.available_0().set_bit()); + } + } + } + + fn ep_write(&mut self, ep_addr: EndpointAddress, buf: &[u8]) -> UsbResult { + let index = ep_addr.index(); + let ep = self + .in_endpoints + .get_mut(index) + .and_then(Option::as_mut) + .ok_or(UsbError::InvalidEndpoint)?; + + let buf_control = &self.ctrl_dpram.ep_buffer_control(index * 2); + if buf_control.read().available_0().bit_is_set() { + return Err(UsbError::WouldBlock); + } + + let ep_buf = ep.get_buf_mut(); + if ep_buf.len() < buf.len() { + return Err(UsbError::BufferOverflow); + } + ep_buf[..buf.len()].copy_from_slice(buf); + + buf_control.modify(|r, w| unsafe { + w.length_0().bits(buf.len() as u16); + w.full_0().set_bit(); + w.pid_0().bit(!r.pid_0().bit()) + }); + crate::arch::delay(12); + buf_control.modify(|_, w| w.available_0().set_bit()); + + Ok(buf.len()) + } + + fn ep_read(&mut self, ep_addr: EndpointAddress, buf: &mut [u8]) -> UsbResult { + let index = ep_addr.index(); + let ep = self + .out_endpoints + .get_mut(index) + .and_then(Option::as_mut) + .ok_or(UsbError::InvalidEndpoint)?; + + let buf_control = &self.ctrl_dpram.ep_buffer_control(index * 2 + 1); + let buf_control_val = buf_control.read(); + + let process_setup = index == 0 && self.read_setup; + if process_setup { + // assume we want to read the setup request + // + // the OUT packet will be either data or a status zlp + let len = 8; + let ep_buf = + unsafe { core::slice::from_raw_parts(pac::USB_DPRAM::ptr() as *const u8, len) }; + if len > buf.len() { + return Err(UsbError::BufferOverflow); + } + + buf[..len].copy_from_slice(&ep_buf[..len]); + + // Next packet will be on DATA1 so clear pid_0 so it gets flipped by next buf config + self.ctrl_dpram + .ep_buffer_control(0) + .modify(|_, w| w.pid_0().clear_bit()); + // clear setup request flag + self.ctrl_reg + .sie_status() + .write(|w| w.setup_rec().clear_bit_by_one()); + + // clear any out standing out flag e.g. in case a zlp got discarded + self.ctrl_reg.buff_status().write(|w| unsafe { w.bits(2) }); + + let is_in_request = (buf[0] & 0x80) == 0x80; + let data_length = u16::from(buf[6]) | (u16::from(buf[7]) << 8); + let expect_data_or_zlp = is_in_request || data_length != 0; + + buf_control.modify(|_, w| unsafe { + w.length_0().bits(ep.max_packet_size); + w.full_0().clear_bit(); + w.pid_0().set_bit() + }); + // enable if and only if a dataphase is expected. + crate::arch::delay(12); + buf_control.modify(|_, w| w.available_0().bit(expect_data_or_zlp)); + + self.read_setup = false; + Ok(len) + } else { + if buf_control_val.full_0().bit_is_clear() { + return Err(UsbError::WouldBlock); + } + let len = buf_control_val.length_0().bits().into(); + if len > buf.len() { + return Err(UsbError::BufferOverflow); + } + + buf[..len].copy_from_slice(&ep.get_buf()[..len]); + // Clear OUT flag once it is read. + self.ctrl_reg + .buff_status() + .write(|w| unsafe { w.bits(1 << (index * 2 + 1)) }); + + buf_control.modify(|r, w| unsafe { + w.length_0().bits(ep.max_packet_size); + w.full_0().clear_bit(); + w.pid_0().bit(!r.pid_0().bit()) + }); + if index != 0 || len == ep.max_packet_size.into() { + // only mark as available on the control endpoint if and only if the packet was + // max_packet_size + crate::arch::delay(12); + buf_control.modify(|_, w| w.available_0().set_bit()); + } + Ok(len) + } + } +} + +/// Usb bus +pub struct UsbBus { + inner: Mutex>, +} + +impl UsbBus { + /// Create new usb bus struct and bring up usb as device. + pub fn new( + ctrl_reg: pac::USB, + ctrl_dpram: pac::USB_DPRAM, + pll: UsbClock, + force_vbus_detect_bit: bool, + resets: &mut pac::RESETS, + ) -> Self { + ctrl_reg.reset_bring_down(resets); + ctrl_reg.reset_bring_up(resets); + + unsafe { + let raw_ctrl_reg = + core::slice::from_raw_parts_mut(pac::USB::ptr() as *mut u32, 1 + 0x98 / 4); + raw_ctrl_reg.fill(0); + + let raw_ctrl_pdram = + core::slice::from_raw_parts_mut(pac::USB_DPRAM::ptr() as *mut u32, 1 + 0xfc / 4); + raw_ctrl_pdram.fill(0); + } + + ctrl_reg.usb_muxing().modify(|_, w| { + w.to_phy().set_bit(); + w.softcon().set_bit() + }); + + if force_vbus_detect_bit { + ctrl_reg.usb_pwr().modify(|_, w| { + w.vbus_detect().set_bit(); + w.vbus_detect_override_en().set_bit() + }); + } + ctrl_reg.main_ctrl().modify(|_, w| { + w.sim_timing().clear_bit(); + w.host_ndevice().clear_bit(); + w.controller_en().set_bit() + }); + + Self { + inner: Mutex::new(RefCell::new(Inner::new(ctrl_reg, ctrl_dpram, pll))), + } + } + + /// Generates a resume request on the bus. + pub fn remote_wakeup(&self) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + inner + .ctrl_reg + .sie_ctrl() + .modify(|_, w| w.resume().set_bit()); + }); + } + + /// Stop and free the Usb resources + pub fn free(self, resets: &mut pac::RESETS) -> (pac::USB, pac::USB_DPRAM, UsbClock) { + critical_section::with(|_cs| { + let inner = self.inner.into_inner().into_inner(); + + inner.ctrl_reg.reset_bring_down(resets); + + (inner.ctrl_reg, inner.ctrl_dpram, inner.pll) + }) + } +} + +impl UsbBusTrait for UsbBus { + fn alloc_ep( + &mut self, + ep_dir: UsbDirection, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + _interval: u8, + ) -> UsbResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + + inner.ep_allocate(ep_addr, ep_dir, ep_type, max_packet_size) + }) + } + + fn enable(&mut self) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + // at this stage ep's are expected to be in their reset state + // TODO: is it worth having a debug_assert for that here? + + // Enable interrupt generation when a buffer is done, when the bus is reset, + // and when a setup packet is received + // this should be sufficient for device mode, will need more for host. + inner.ctrl_reg.inte().modify(|_, w| { + w.buff_status() + .set_bit() + .bus_reset() + .set_bit() + .dev_resume_from_host() + .set_bit() + .dev_suspend() + .set_bit() + .setup_req() + .set_bit() + }); + + // enable pull up to let the host know we exist. + inner + .ctrl_reg + .sie_ctrl() + .modify(|_, w| w.pullup_en().set_bit()); + }) + } + fn reset(&self) { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + + // clear reset flag + inner + .ctrl_reg + .sie_status() + .write(|w| w.bus_reset().clear_bit_by_one()); + inner + .ctrl_reg + .buff_status() + .write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + + // reset all endpoints + inner.ep_reset_all(); + + // Reset address register + inner.ctrl_reg.addr_endp().reset(); + // TODO: reset all endpoints & buffer statuses + }) + } + fn set_device_address(&self, addr: u8) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + inner + .ctrl_reg + .addr_endp() + .modify(|_, w| unsafe { w.address().bits(addr & 0x7F) }); + // reset ep0 + inner + .ctrl_dpram + .ep_buffer_control(0) + .modify(|_, w| w.pid_0().set_bit()); + inner + .ctrl_dpram + .ep_buffer_control(1) + .modify(|_, w| w.pid_0().set_bit()); + }) + } + fn write(&self, ep_addr: EndpointAddress, buf: &[u8]) -> UsbResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + inner.ep_write(ep_addr, buf) + }) + } + fn read(&self, ep_addr: EndpointAddress, buf: &mut [u8]) -> UsbResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + inner.ep_read(ep_addr, buf) + }) + } + fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + + if ep_addr.index() == 0 { + inner.ctrl_reg.ep_stall_arm().modify(|_, w| { + if ep_addr.is_in() { + w.ep0_in().bit(stalled) + } else { + w.ep0_out().bit(stalled) + } + }); + } + + let index = ep_addr_to_ep_buf_ctrl_idx(ep_addr); + inner + .ctrl_dpram + .ep_buffer_control(index) + .modify(|_, w| w.stall().bit(stalled)); + }) + } + fn is_stalled(&self, ep_addr: EndpointAddress) -> bool { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + let index = ep_addr_to_ep_buf_ctrl_idx(ep_addr); + inner + .ctrl_dpram + .ep_buffer_control(index) + .read() + .stall() + .bit_is_set() + }) + } + fn suspend(&self) {} + fn resume(&self) {} + fn poll(&self) -> PollResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + + // check for bus reset and/or suspended states. + let ints = inner.ctrl_reg.ints().read(); + let mut buff_status = inner.ctrl_reg.buff_status().read().bits(); + + if ints.bus_reset().bit_is_set() { + return PollResult::Reset; + } else if buff_status == 0 && ints.setup_req().bit_is_clear() { + if ints.dev_suspend().bit_is_set() { + inner + .ctrl_reg + .sie_status() + .write(|w| w.suspended().clear_bit_by_one()); + return PollResult::Suspend; + } else if ints.dev_resume_from_host().bit_is_set() { + inner + .ctrl_reg + .sie_status() + .write(|w| w.resume().clear_bit_by_one()); + return PollResult::Resume; + } + return PollResult::None; + } + + let (mut ep_out, mut ep_in_complete, mut ep_setup): (u16, u16, u16) = (0, 0, 0); + + // IN Complete shall only be reported once. + inner + .ctrl_reg + .buff_status() + .write(|w| unsafe { w.bits(0x5555_5555) }); + + for i in 0..32u32 { + if buff_status == 0 { + break; + } else if (buff_status & 1) == 1 { + let is_in = (i & 1) == 0; + let ep_idx = i / 2; + if is_in { + ep_in_complete |= 1 << ep_idx; + } else { + ep_out |= 1 << ep_idx; + } + } + buff_status >>= 1; + } + + // check for setup request + if ints.setup_req().bit_is_set() { + // Small max_packet_size_ep0 Work-Around + inner + .ctrl_dpram + .ep_buffer_control(0) + .modify(|_, w| w.available_0().clear_bit()); + + ep_setup |= 1; + inner.read_setup = true; + } + + PollResult::Data { + ep_out, + ep_in_complete, + ep_setup, + } + }) + } + + const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = false; +} diff --git a/rp-hal/rp235x-hal/src/vector_table.rs b/rp-hal/rp235x-hal/src/vector_table.rs new file mode 100644 index 0000000..2f56cc3 --- /dev/null +++ b/rp-hal/rp235x-hal/src/vector_table.rs @@ -0,0 +1,102 @@ +//! Interrupt vector table utilities +//! +//! Provide functionality to switch to another vector table using the +//! Vector Table Offset Register (VTOR) of the Cortex-33 +//! Also provides types and utilities for copying a vector table into RAM + +/// Entry for a Vector in the Interrupt Vector Table. +/// +/// Each entry in the Vector table is a union with usize to allow it to be 0 initialized via const initializer +/// +/// Implementation borrowed from https://docs.rs/cortex-m-rt/0.7.1/cortex_m_rt/index.html#__interrupts +#[derive(Clone, Copy)] +union Vector { + handler: extern "C" fn(), + reserved: usize, +} + +/// Data type for a properly aligned interrupt vector table +/// +/// The VTOR register can only point to a 128 byte offsets - see +/// [Cortex-33 Devices Generic User Guide](https://developer.arm.com/documentation/100235/0004/the-cortex-m33-peripherals/system-control-block/vector-table-offset-register?lang=en) - +/// so that is our required alignment. +/// The vector table length depends on the number of interrupts the system supports. +/// The first 16 words are defined in the ARM Cortex-M spec. +/// The Cortex-M33 cores on RP235x have 52 interrupts, of which only 47 are wired to external interrupt +/// signals - but the last 6 can be used for software interrupts so leave room for them +#[repr(C, align(128))] +pub struct VectorTable { + /// SP + Reset vector + 14 exceptions + 52 interrupts = 68 entries (272 bytes) in an rp235x core's VectorTable + table: [Vector; 68], +} + +impl Default for VectorTable { + fn default() -> Self { + Self::new() + } +} + +impl VectorTable { + /// Create a new vector table. All entries will point to 0 - you must call init() + /// on this to copy the current vector table before setting it as active + pub const fn new() -> VectorTable { + VectorTable { + table: [Vector { reserved: 0 }; 68], + } + } + + /// Initialise our vector table by copying the current table on top of it + #[allow(unknown_lints)] + #[allow(clippy::needless_pass_by_ref_mut)] + pub fn init(&mut self, ppb: &mut crate::pac::PPB) { + let mut vector_table = ppb.vtor().read().bits() as *const usize; + for entry in self.table.iter_mut() { + // Safety: + // + // This value must be valid because it's in the current vector table. + *entry = Vector { + reserved: unsafe { vector_table.read() }, + }; + // Safety: + // + // We are iterating through our copy of the vector table, which we + // know is the same size as the real vector table. + unsafe { + vector_table = vector_table.add(1); + } + } + } + + /// Dynamically register a function as being an interrupt handler + pub fn register_handler(&mut self, interrupt_idx: usize, interrupt_fn: extern "C" fn()) { + self.table[16 + interrupt_idx].handler = interrupt_fn; + } + + /// Set the stack pointer address in a VectorTable. This will be used on Reset + /// + /// # Safety + /// There is no checking whether this is a valid stack pointer address + pub unsafe fn set_sp(&mut self, stack_pointer_address: usize) { + self.table[0].reserved = stack_pointer_address; + } + + /// Set the entry-point address in a VectorTable. This will be used on Reset + /// + /// # Safety + /// There is no checking whether this is a valid entry point + pub unsafe fn set_entry(&mut self, entry_address: usize) { + self.table[1].reserved = entry_address; + } + + /// Switch the current core to use this Interrupt Vector Table + /// + /// # Safety + /// Until the vector table has valid entries, activating it will cause an unhandled hardfault! + /// You must call init() first. + #[allow(unknown_lints)] + #[allow(clippy::needless_pass_by_ref_mut)] + pub unsafe fn activate(&mut self, ppb: &mut crate::pac::PPB) { + ppb.vtor() + .write(|w| w.bits(&mut self.table as *mut _ as *mut u32 as u32)); + } +} diff --git a/rp-hal/rp235x-hal/src/watchdog.rs b/rp-hal/rp235x-hal/src/watchdog.rs new file mode 100644 index 0000000..5998dcc --- /dev/null +++ b/rp-hal/rp235x-hal/src/watchdog.rs @@ -0,0 +1,230 @@ +//! 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. +//! +//! See [Section 12.9](https://rptl.io/rp2350-datasheet) of the datasheet for more details +//! +//! ## Usage +//! ```no_run +//! use fugit::ExtU32; +//! use rp235x_hal::{self as hal, clocks::init_clocks_and_plls, watchdog::Watchdog}; +//! let mut pac = hal::pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(pac.WATCHDOG); +//! let _clocks = init_clocks_and_plls( +//! 12_000_000, +//! pac.XOSC, +//! pac.CLOCKS, +//! pac.PLL_SYS, +//! pac.PLL_USB, +//! &mut pac.RESETS, +//! &mut watchdog, +//! ) +//! .ok() +//! .unwrap(); +//! // Set to watchdog to reset if it's not reloaded within 1.05 seconds, and start it +//! watchdog.start(1_050_000.micros()); +//! // Feed the watchdog once per cycle to avoid reset +//! for _ in 1..=10000 { +//! hal::arch::delay(100_000); +//! watchdog.feed(); +//! } +//! // Stop feeding, now we'll reset +//! loop {} +//! ``` +//! See [examples/watchdog.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/watchdog.rs) for a more complete example + +// Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +use embedded_hal_0_2::watchdog; +use fugit::MicrosDurationU32; + +use crate::pac::{self, WATCHDOG}; + +/// Watchdog peripheral +pub struct Watchdog { + watchdog: WATCHDOG, + load_value: u32, // decremented by 2 per tick (µs) +} + +#[derive(Debug)] +#[allow(missing_docs)] +/// Scratch registers of the watchdog peripheral +pub enum ScratchRegister { + Scratch0, + Scratch1, + Scratch2, + Scratch3, + Scratch4, + Scratch5, + Scratch6, + Scratch7, +} + +impl Watchdog { + /// Create a new [`Watchdog`] + pub fn new(watchdog: WATCHDOG) -> Self { + Self { + watchdog, + load_value: 0, + } + } + + /// Starts tick generation on all the ticks. + /// + /// # 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: u16) { + // now we have separate ticks for every destination + // we own the watchdog, so no-one else can be writing to this register + let ticks = unsafe { &*pac::TICKS::ptr() }; + for ticker in ticks.tick_iter() { + // TODO: work out how to rename proc0_cycles to cycles in the SVD patch YAML + ticker + .cycles() + .write(|w| unsafe { w.proc0_cycles().bits(cycles) }); + ticker.ctrl().write(|w| w.enable().set_bit()); + } + } + + /// Defines whether or not the watchdog timer should be paused when processor(s) are in debug mode + /// or when JTAG is accessing bus fabric + /// + /// # Arguments + /// + /// * `pause` - If true, watchdog timer will be paused + pub fn pause_on_debug(&mut self, pause: bool) { + self.watchdog.ctrl().write(|w| { + w.pause_dbg0() + .bit(pause) + .pause_dbg1() + .bit(pause) + .pause_jtag() + .bit(pause) + }) + } + + fn load_counter(&self, counter: u32) { + self.watchdog.load().write(|w| unsafe { w.bits(counter) }); + } + + fn enable(&self, bit: bool) { + self.watchdog.ctrl().write(|w| w.enable().bit(bit)) + } + + /// Read a scratch register + pub fn read_scratch(&self, reg: ScratchRegister) -> u32 { + match reg { + ScratchRegister::Scratch0 => self.watchdog.scratch0().read().bits(), + ScratchRegister::Scratch1 => self.watchdog.scratch1().read().bits(), + ScratchRegister::Scratch2 => self.watchdog.scratch2().read().bits(), + ScratchRegister::Scratch3 => self.watchdog.scratch3().read().bits(), + ScratchRegister::Scratch4 => self.watchdog.scratch4().read().bits(), + ScratchRegister::Scratch5 => self.watchdog.scratch5().read().bits(), + ScratchRegister::Scratch6 => self.watchdog.scratch6().read().bits(), + ScratchRegister::Scratch7 => self.watchdog.scratch7().read().bits(), + } + } + + /// Write a scratch register + pub fn write_scratch(&mut self, reg: ScratchRegister, value: u32) { + match reg { + ScratchRegister::Scratch0 => { + self.watchdog.scratch0().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch1 => { + self.watchdog.scratch1().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch2 => { + self.watchdog.scratch2().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch3 => { + self.watchdog.scratch3().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch4 => { + self.watchdog.scratch4().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch5 => { + self.watchdog.scratch5().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch6 => { + self.watchdog.scratch6().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch7 => { + self.watchdog.scratch7().write(|w| unsafe { w.bits(value) }) + } + } + } + + /// Configure which hardware will be reset by the watchdog + /// the default is everything except ROSC, XOSC + /// + /// Safety: ensure no other device is writing to psm.wdsel + /// This is easy at the moment, since nothing else uses PSM + unsafe fn configure_wdog_reset_triggers(&self) { + let psm = &*pac::PSM::ptr(); + psm.wdsel().write_with_zero(|w| { + w.bits(0x0001ffff); + w.xosc().clear_bit(); + w.rosc().clear_bit(); + w + }); + } + + /// Set the watchdog counter back to its load value, making sure + /// that the watchdog reboot will not be triggered for the configured + /// period. + pub fn feed(&self) { + self.load_counter(self.load_value) + } + + /// Start the watchdog. This enables a timer which will reboot the + /// rp2350 if [`Watchdog::feed()`] does not get called for the configured period. + pub fn start>(&mut self, period: T) { + const MAX_PERIOD: u32 = 0xFFFFFF; + + let delay_us = period.into().to_micros(); + if delay_us > MAX_PERIOD / 2 { + panic!( + "Period cannot exceed maximum load value of {} ({} microseconds))", + MAX_PERIOD, + MAX_PERIOD / 2 + ); + } + self.load_value = delay_us; + + self.enable(false); + unsafe { + self.configure_wdog_reset_triggers(); + } + self.load_counter(self.load_value); + self.enable(true); + } + + /// Disable the watchdog timer. + pub fn disable(&self) { + self.enable(false) + } +} + +impl watchdog::Watchdog for Watchdog { + fn feed(&mut self) { + (*self).feed() + } +} + +impl watchdog::WatchdogEnable for Watchdog { + type Time = MicrosDurationU32; + + fn start>(&mut self, period: T) { + self.start(period) + } +} + +impl watchdog::WatchdogDisable for Watchdog { + fn disable(&mut self) { + (*self).disable() + } +} diff --git a/rp-hal/rp235x-hal/src/xosc.rs b/rp-hal/rp235x-hal/src/xosc.rs new file mode 100644 index 0000000..e88bd81 --- /dev/null +++ b/rp-hal/rp235x-hal/src/xosc.rs @@ -0,0 +1,227 @@ +//! Crystal Oscillator (XOSC) +//! +//! See [Section 8.2](https://rptl.io/rp2350-datasheet#section_xosc) for more details. + +use core::{convert::Infallible, ops::RangeInclusive}; + +use fugit::HertzU32; +use nb::Error::WouldBlock; + +use crate::{pac::XOSC, typelevel::Sealed}; + +/// State of the Crystal Oscillator (typestate trait) +pub trait State: Sealed {} + +/// XOSC is disabled (typestate) +pub struct Disabled; + +/// XOSC is initialized but has not yet stabilized (typestate) +pub struct Unstable { + freq_hz: HertzU32, +} + +/// XOSC is stable (typestate) +pub struct Stable { + freq_hz: HertzU32, +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Unstable {} +impl Sealed for Unstable {} +impl State for Stable {} +impl Sealed for Stable {} + +/// Possible errors when initializing the CrystalOscillator +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Frequency is out of the 1-15MHz range (see datasheet) + FrequencyOutOfRange, + + /// Argument is bad : overflows, ... + BadArgument, +} + +/// Blocking helper method to setup the XOSC without going through all the steps. +/// +/// This uses a startup_delay_multiplier of 64, which is a rather conservative value +/// that should work even if the XOSC starts up slowly. In case you need a fast boot +/// sequence, and your XOSC starts up quickly enough, use [`setup_xosc_blocking_custom_delay`]. +pub fn setup_xosc_blocking( + xosc_dev: XOSC, + frequency: HertzU32, +) -> Result, Error> { + let initialized_xosc = CrystalOscillator::new(xosc_dev).initialize(frequency, 64)?; + + let stable_xosc_token = nb::block!(initialized_xosc.await_stabilization()).unwrap(); + + Ok(initialized_xosc.get_stable(stable_xosc_token)) +} + +/// Blocking helper method to setup the XOSC without going through all the steps. +/// +/// This function allows setting a startup_delay_multiplier to tune the amount of time +/// the chips waits for the XOSC to stabilize. +/// The default value in the C SDK is 1, which should work on the Raspberry Pico, and many +/// third-party boards. +/// [`setup_xosc_blocking`], uses a conservative value of 64, which is the value commonly +/// used on slower-starting oscillators. +pub fn setup_xosc_blocking_custom_delay( + xosc_dev: XOSC, + frequency: HertzU32, + startup_delay_multiplier: u32, +) -> Result, Error> { + let initialized_xosc = + CrystalOscillator::new(xosc_dev).initialize(frequency, startup_delay_multiplier)?; + + let stable_xosc_token = nb::block!(initialized_xosc.await_stabilization()).unwrap(); + + Ok(initialized_xosc.get_stable(stable_xosc_token)) +} + +/// A Crystal Oscillator. +pub struct CrystalOscillator { + device: XOSC, + state: S, +} + +impl CrystalOscillator { + /// Transitions the oscillator to another state. + fn transition(self, state: To) -> CrystalOscillator { + CrystalOscillator { + device: self.device, + state, + } + } + + /// Releases the underlying device. + pub fn free(self) -> XOSC { + self.device + } +} + +impl CrystalOscillator { + /// Creates a new CrystalOscillator from the underlying device. + pub fn new(dev: XOSC) -> Self { + CrystalOscillator { + device: dev, + state: Disabled, + } + } + + /// Initializes the XOSC : frequency range is set, startup delay is calculated and set. + /// Set startup_delay_multiplier to a value > 1 when using a slow-starting oscillator. + pub fn initialize( + self, + frequency: HertzU32, + startup_delay_multiplier: u32, + ) -> Result, Error> { + const ALLOWED_FREQUENCY_RANGE: RangeInclusive = + HertzU32::MHz(1)..=HertzU32::MHz(15); + //1 ms = 10e-3 sec and Freq = 1/T where T is in seconds so 1ms converts to 1000Hz + const STABLE_DELAY_AS_HZ: HertzU32 = HertzU32::Hz(1000); + const DIVIDER: u32 = 256; + + if !ALLOWED_FREQUENCY_RANGE.contains(&frequency) { + return Err(Error::FrequencyOutOfRange); + } + + if startup_delay_multiplier == 0 { + return Err(Error::BadArgument); + } + + self.device.ctrl().write(|w| { + w.freq_range()._1_15mhz(); + w + }); + + //startup_delay = ((freq_hz * STABLE_DELAY) / 256) = ((freq_hz / + // delay_to_hz) / 256) = freq_hz / (delay_to_hz * 256) + // + // See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) + // of the RP2350 datasheet. + // + // We do the calculation first. + let startup_delay = frequency.to_Hz() / (STABLE_DELAY_AS_HZ.to_Hz() * DIVIDER); + let startup_delay = startup_delay.saturating_mul(startup_delay_multiplier); + + //Then we check if it fits into an u16. + let startup_delay: u16 = startup_delay.try_into().map_err(|_| Error::BadArgument)?; + + self.device.startup().write(|w| unsafe { + w.delay().bits(startup_delay); + w + }); + + self.device.ctrl().write(|w| { + w.enable().enable(); + w + }); + + Ok(self.transition(Unstable { freq_hz: frequency })) + } +} + +/// A token that's given when the oscillator is stabilized, and can be exchanged to proceed to the next stage. +pub struct StableOscillatorToken { + _private: (), +} + +impl CrystalOscillator { + /// One has to wait for the startup delay before using the oscillator, ie awaiting stabilization of the XOSC + pub fn await_stabilization(&self) -> nb::Result { + if self.device.status().read().stable().bit_is_clear() { + return Err(WouldBlock); + } + + Ok(StableOscillatorToken { _private: () }) + } + + /// Returns the stabilized oscillator + pub fn get_stable(self, _token: StableOscillatorToken) -> CrystalOscillator { + let freq_hz = self.state.freq_hz; + self.transition(Stable { freq_hz }) + } +} + +impl CrystalOscillator { + /// Operating frequency of the XOSC in hertz + pub fn operating_frequency(&self) -> HertzU32 { + self.state.freq_hz + } + + /// Disables the XOSC + pub fn disable(self) -> CrystalOscillator { + self.device.ctrl().modify(|_r, w| { + w.enable().disable(); + w + }); + + self.transition(Disabled) + } + + /// Put the XOSC in DORMANT state. The method returns after the processor awakens. + /// + /// After waking up from the DORMANT state, XOSC needs to re-stabilise. + /// + /// # Safety + /// This method is marked unsafe because prior to switch the XOSC into DORMANT state, + /// PLLs must be stopped and IRQs have to be properly configured. + /// This method does not do any of that, it merely switches the XOSC to DORMANT state. + /// It should only be called if this oscillator is the clock source for the system clock. + /// + /// See Sectiom 2.16, §5 for details. + pub unsafe fn dormant(self) -> CrystalOscillator { + //taken from the C SDK + const XOSC_DORMANT_VALUE: u32 = 0x636f6d61; + + self.device.dormant().write(|w| { + w.bits(XOSC_DORMANT_VALUE); + w + }); + + let freq_hz = self.state.freq_hz; + self.transition(Unstable { freq_hz }) + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..752bd37 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,12 @@ +[toolchain] +channel = "nightly" +components = [ "rust-src", "rustfmt", "llvm-tools" ] +targets = [ + "thumbv7em-none-eabi", + "thumbv7m-none-eabi", + "thumbv6m-none-eabi", + "thumbv7em-none-eabihf", + "thumbv8m.main-none-eabihf", + "riscv32imac-unknown-none-elf", + "wasm32-unknown-unknown", +] diff --git a/src/clock.rs b/src/clock.rs new file mode 100644 index 0000000..b0ac4a8 --- /dev/null +++ b/src/clock.rs @@ -0,0 +1,92 @@ +use fugit::TimerInstantU32; +use core::convert::Infallible; +use embedded_nal::nb; + +// use std::{ +// convert::Infallible, +// time::{SystemTime, UNIX_EPOCH}, +// }; +pub struct SysClock { + start_time: u32, + countdown_end: Option, +} + +impl SysClock { + pub fn new() -> Self { + Self { + start_time: Self::epoch(), + countdown_end: None, + } + } + + // pub fn epoch() -> u32 { + // SystemTime::now() + // .duration_since(UNIX_EPOCH) + // .expect("Time went backwards") + // .as_millis() as u32 + // } + pub fn epoch() -> u32 { + embassy_time::Instant::now().as_ticks() as u32 + } + + + pub fn now(&self) -> u32 { + Self::epoch() - self.start_time + } +} + +impl fugit_timer::Timer<1000> for SysClock { + type Error = Infallible; + + fn now(&mut self) -> fugit::TimerInstantU32<1000> { + TimerInstantU32::from_ticks(SysClock::now(self)) + } + + fn start(&mut self, duration: fugit::TimerDurationU32<1000>) -> Result<(), Self::Error> { + todo!() + } + + fn cancel(&mut self) -> Result<(), Self::Error> { + todo!() + } + + fn wait(&mut self) -> nb::Result<(), Self::Error> { + todo!() + } +} + + +// #[derive(Copy, Clone, Debug)] +// pub struct StandardClock { +// //start: std::time::Instant, +// start: embassy_time::Instant, +// } + +// impl Default for StandardClock { +// fn default() -> Self { +// Self { +// start: embassy_time::Instant::now(), +// } +// } +// } + +// impl embedded_time::Clock for StandardClock { +// /// With a 64-bit tick register, the clock can represent times up to approximately 594 years in +// /// duration, after which the clock will roll over. +// type T = u64; + +// /// Each tick of the clock is equivalent to 1 nanosecond. +// const SCALING_FACTOR: Fraction = Fraction::new(1, 1_000_000_000); + +// /// Get the current time from the clock. +// fn try_now(&self) -> Result, embedded_time::clock::Error> { +// let now = embassy_time::Instant::now(); + +// let elapsed = now.duration_since(self.start); + +// // Note: We are discarding the upper 64 bits of nanoseconds. However, even while doing so, +// // we can represent ~594 years of time, so this should not be relevant. +// Ok(Instant::new(elapsed.as_micros() as u64)) +// } +// } + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0c9ac1a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +#![no_std] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c6851d5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,361 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::udp::{PacketMetadata, UdpSocket}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; + +use embassy_time::Timer; + +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +//pub type RGB8 = RGB; + +use {defmt_rtt as _, panic_probe as _}; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = env!("SSID"); +const WIFI_PASSWORD: &str = env!("SSID_PASSWORD"); +const NUM_LEDS: usize = 100; + +//static mut LEDSTRIP: &mut [u8] = &mut [0u8; NUM_LEDS * 3]; + +static mut RGBARRAY: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + +// static CHANNEL: embassy_sync::channel::Channel = +// embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + let mut cur_message_count: u32 = 0; + + // outer loop + loop { + println!("Start of outer loop"); + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(100).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + // Then we can use it! + let mut rx_buffer = [0; 312]; + let mut tx_buffer = [0; 312]; + let mut rx_meta = [PacketMetadata::EMPTY; 16]; + let mut tx_meta = [PacketMetadata::EMPTY; 16]; + let mut buf = [0; 312]; + let mut socket = UdpSocket::new( + stack, + &mut rx_meta, + &mut rx_buffer, + &mut tx_meta, + &mut tx_buffer, + ); + + socket.bind(1234).unwrap(); + + let mut err_count = 0; + + loop { + println!("Start loop"); + match socket.recv_from(&mut buf).await { + Ok((n, ep)) => { + println!("Start cat"); + + match bincode::decode_from_slice::( + &buf, + bincode::config::standard(), + ) { + Ok(decoded) => { + let ledcontainer = decoded.0; + if ledcontainer.message_count > cur_message_count { + ledcontainer.ledstrip.to_array(); + cur_message_count = ledcontainer.message_count; + } + + info!("[RECEIVED] Got message!"); + } + Err(_e) => { + info!("DOG ERROR"); + } + } + } + Err(_) => { + err_count += 1; + warn!("[RECEIVER] Could not get message with error: {}", buf); + + // if we receive 10 error messages in a row with errors, we reconnect to wifi and restart all! + if err_count > 10 { + break; + } + } + } + } + + if !stack.is_link_up() { + println!("Wifi not up. Reconnecting..."); + } + + control.leave().await; + + println!("End of outer loop"); + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + //let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + //let rc = CHANNEL.receive().await; + // if current_ledstrip != rc { + // info!("OCTOPUS got a new led strip"); + + // for i in 0..rc.leds.len() { + // rgb_arr[i].r = rc.leds[i].r; + // rgb_arr[i].g = rc.leds[i].g; + // rgb_arr[i].b = rc.leds[i].b; + // } + + // current_ledstrip = rc.clone(); + + // info!("writing led!"); + unsafe { + np.write(RGBARRAY.iter().cloned()).await.ok(); + } + + // } + + Timer::after(embassy_time::Duration::from_millis(4)).await; + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } + pub fn to_array(&self) { + for i in 0..NUM_LEDS { + unsafe { RGBARRAY[i] = RGB8::new(self.leds[i].g, self.leds[i].r, self.leds[i].b) } + } + } +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedContainer { + message_count: u32, + ledstrip: LedStrip, +} diff --git a/src/main_final_working.rs b/src/main_final_working.rs new file mode 100644 index 0000000..db6c7ff --- /dev/null +++ b/src/main_final_working.rs @@ -0,0 +1,424 @@ +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use bincode::config::Configuration; +use bincode::Decode; +use cyw43::JoinOptions; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor, Spawner}; +use embassy_net::tcp::client::{TcpClient, TcpClientState}; +use embassy_net::{Config, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::spi::{Phase, Polarity, Spi}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::Timer; +use embedded_nal_async::TcpConnect; +//use embedded_nal_async::TcpConnect; +//use embedded_nal_async::stack::tcp::TcpConnect; +//use embedded_nal_async::TcpConnect; +use rand::RngCore; +use smart_leds::RGB8; +use static_cell::StaticCell; +use ws2812_async::{ColorOrder, Ws2812}; +use {defmt_rtt as _, panic_probe as _}; + +use rust_mqtt::{ + client::{client::MqttClient, client_config::ClientConfig}, + utils::rng_generator::CountingRng, +}; +//pub type RGB8 = RGB; + +// after observing somewhat jumpy behavior of the neopixel task, I decided to set the scheduler and orhestrator to high priority +// hight priority runs on interrupt +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +// low priority runs in thread-mode +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[global_allocator] +static ALLOCATOR: emballoc::Allocator<4096> = emballoc::Allocator::new(); +extern crate alloc; + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = env!("SSID"); +const WIFI_PASSWORD: &str = env!("SSID_PASSWORD"); +const CLIENT_ID: &str = env!("MQTT_CLIENT_ID"); +const TOPIC: &str = env!("MQTT_TOPIC"); +const NUM_LEDS: usize = 100; + +static CHANNEL: embassy_sync::channel::Channel = + embassy_sync::channel::Channel::new(); + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} + +assign_resources! { + led_peripheral: LedPeripheral { + inner_spi: SPI1, + clk_pin: PIN_14, // this is just a dummy pin, the neopixel uses only the mosi pin + mosi_pin: PIN_15, + tx_dma_ch: DMA_CH1, + }, + wifi: WifiResources { + pwr_pin: PIN_23, + cs_pin: PIN_25, + pio_sm: PIO0, + dio_pin: PIN_24, + clk_pin: PIN_29, + dma_ch: DMA_CH0, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Main thread!"); + + let p = embassy_rp::init(Default::default()); + let r = split_resources!(p); + + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + + let executor = EXECUTOR_LOW.init(Executor::new()); + + executor.run(|spawner| { + // update the RTC + spawner.spawn(run_mqtt(spawner, r.wifi)).unwrap(); + + static mut CORE1_STACK: Stack<4096> = Stack::new(); + static EXECUTOR1: StaticCell = StaticCell::new(); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1 + .run(|spawner| unwrap!(spawner.spawn(led_ctrl_core1_task(r.led_peripheral)))); + }, + ); + }); +} + +#[embassy_executor::task] +pub async fn run_mqtt(spawner: Spawner, r: WifiResources) { + let mut rng = RoscRng; + + let fw = include_bytes!("../embassy/cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../embassy/cyw43-firmware/43439A0_clm.bin"); + + info!("init wifi"); + let pwr = Output::new(r.pwr_pin, Level::Low); + let cs = Output::new(r.cs_pin, Level::High); + let mut pio = Pio::new(r.pio_sm, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + r.dio_pin, + r.clk_pin, + r.dma_ch, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + // Use static IP configuration instead of DHCP + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + net_device, + config, + RESOURCES.init(StackResources::new()), + seed, + ); + + unwrap!(spawner.spawn(net_task(runner))); + + // outer loop + loop { + println!("Start of outer loop"); + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + info!("waiting for link up..."); + while !stack.is_link_up() { + Timer::after_millis(500).await; + } + info!("Link is up!"); + + info!("waiting for stack to be up..."); + stack.wait_config_up().await; + info!("Stack is up!"); + + let tcp_state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let mut tcp_client = TcpClient::new(stack, &tcp_state); + tcp_client.set_timeout(Some(embassy_time::Duration::from_secs(360))); + + let ip_addr = env!("MQTT_BROKER_ADDR"); + + let addr = core::net::SocketAddr::new( + ip_addr.parse::().unwrap().into(), + 1883, + ); + + // let connection = embedded_nal::TcpStream::connect(addr) + // .await + // .map_err(|_| ReasonCode::NetworkError) + // .unwrap(); + //let connection = FromTokio::::new(connection); + let mut config = ClientConfig::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(20000), + ); + + config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS0); + config.add_client_id(CLIENT_ID); + config.keep_alive = u16::MAX; + + // config.add_username(USERNAME); + // config.add_password(PASSWORD); + config.max_packet_size = 400; + let mut recv_buffer = [0; 400]; + let mut write_buffer = [0; 400]; + + //let r = tcp_client.connect(addr).await; + + let r; + + loop { + match tcp_client.connect(addr).await { + Ok(rp) => { + info!("Attempting to connect to mqtt broker"); + r = rp; + break; + } + Err(err) => { + info!("Failed to join broker: {}. Trying again!", err); + Timer::after_millis(100).await; + } + }; + } + + let mut client: MqttClient< + '_, + embassy_net::tcp::client::TcpConnection<'_, 1, 1024, 1024>, + 5, + CountingRng, + > = MqttClient::<_, 5, _>::new(r, &mut write_buffer, 512, &mut recv_buffer, 512, config); + + loop { + match client.connect_to_broker().await { + Ok(_) => { + info!("CAT Connected to broker"); + break; + } + Err(err) => { + info!("DOG connect_to_broker failed with status={}", err); + } + } + } + + loop { + match client.subscribe_to_topic(TOPIC).await { + Ok(_) => { + info!("CAT subscribed to topic"); + break; + } + Err(err) => { + info!("DOG subscribe_to_topic failed with status={}", err); + } + } + } + + let mut old_msg = LedStrip::new(0); + + let mut err_count = 0; + loop { + info!("[RECEIVER] Waiting for new message"); + let msg = client.receive_message().await; + + info!("[RECEIVER] Received msg: {}", msg); + + if msg.is_ok() { + err_count = 0; + + let message = msg.unwrap(); + + info!("[RECEIVER] message has size: {}", message.1.len()); + + match bincode::decode_from_slice::( + message.1, + bincode::config::standard(), + ) { + Ok(decoded) => { + if old_msg != decoded.0 { + CHANNEL.send(decoded.0.clone()).await; + old_msg = decoded.0; + info!("[RECEIVER] Sent message"); + } + } + Err(_e) => { + info!("DOG"); + } + } + } else { + err_count += 1; + warn!("[RECEIVER] Could not get message with error: {}", msg.err()); + + // if we receive 10 error messages in a row with errors, we reconnect to wifi and restart all: + if err_count > 10 { + break; + } + } + //Timer::after(Duration::from_secs(2)).await; + let _ = client.send_ping().await; + } + + if !stack.is_link_up() { + println!("Wifi not up. Reconnecting..."); + } + + control.leave().await; + + println!("End of outer loop"); + } +} + +#[embassy_executor::task] +async fn led_ctrl_core1_task(led_peripheral: LedPeripheral) { + info!("Hello from core 1"); + + let mut current_ledstrip = LedStrip::new(0); + + // Spi configuration for the neopixel + let mut spi_config = embassy_rp::spi::Config::default(); + spi_config.frequency = 3_800_000; + spi_config.phase = Phase::CaptureOnFirstTransition; + spi_config.polarity = Polarity::IdleLow; + // let spi = embassy_rp::spi::Spi::new_txonly(led_peripheral.inner_spi, led_peripheral.clk_pin, led_peripheral.mosi_pin, led_peripheral.tx_dma_ch, spi_config); + let spi = Spi::new_txonly( + led_peripheral.inner_spi, + led_peripheral.clk_pin, + led_peripheral.mosi_pin, + led_peripheral.tx_dma_ch, + spi_config, + ); + let mut np: Ws2812<_, { 12 * NUM_LEDS }> = Ws2812::new(spi); + np.set_color_order(ColorOrder::GRB); + + let mut data = [RGB8::default(); NUM_LEDS]; + + for led in data.iter_mut().step_by(1) { + led.r = 250; // blue + led.g = 150; // red + led.b = 0; // green + } + + //np.write(empty.iter().cloned()).await.ok(); + + //np.write(data.iter().cloned()).await.ok(); + let mut rgb_arr: [RGB8; NUM_LEDS] = [RGB8::new(0, 0, 0); NUM_LEDS]; + + loop { + let rc = CHANNEL.receive().await; + info!("OCTOPUS yyyy"); + + if current_ledstrip != rc { + info!("OCTOPUS got a new led strip"); + + for i in 0..rc.leds.len() { + rgb_arr[i].r = rc.leds[i].r; + rgb_arr[i].g = rc.leds[i].g; + rgb_arr[i].b = rc.leds[i].b; + } + + current_ledstrip = rc.clone(); + + info!("writing led!"); + np.write(rgb_arr.iter().cloned()).await.ok(); + + // Timer::after(Duration::from_millis(0)).await; + } + } +} + +#[derive(Debug, Decode, Clone, Copy, PartialEq)] +struct Led { + r: u8, + g: u8, + b: u8, +} + +#[derive(Clone, Debug, Decode, PartialEq)] +pub struct LedStrip { + leds: [Led; NUM_LEDS], +} + +impl LedStrip { + // naive inital start with uniform colour on entire strip + pub fn new(_r: u8) -> Self { + let arr: [Led; NUM_LEDS] = [Led { r: 0, g: 0, b: 0 }; NUM_LEDS]; + + LedStrip { leds: arr } + } +} diff --git a/src/resources.rs b/src/resources.rs new file mode 100644 index 0000000..e69de29

+ + Logo + + +

` in +/// the HAL's public API (such as driver constructors), calling `.into_ref()` to obtain a `PeripheralRef`, +/// and storing that in the driver struct. +/// +/// `.into_ref()` on an owned `T` yields a `PeripheralRef<'static, T>`. +/// `.into_ref()` on an `&'a mut T` yields a `PeripheralRef<'a, T>`. +pub trait Peripheral: Sized { + /// Peripheral singleton type + type P; + + /// Unsafely clone (duplicate) a peripheral singleton. + /// + /// # Safety + /// + /// This returns an owned clone of the peripheral. You must manually ensure + /// only one copy of the peripheral is in use at a time. For example, don't + /// create two SPI drivers on `SPI1`, because they will "fight" each other. + /// + /// You should strongly prefer using `into_ref()` instead. It returns a + /// `PeripheralRef`, which allows the borrow checker to enforce this at compile time. + unsafe fn clone_unchecked(&self) -> Self::P; + + /// Convert a value into a `PeripheralRef`. + /// + /// When called on an owned `T`, yields a `PeripheralRef<'static, T>`. + /// When called on an `&'a mut T`, yields a `PeripheralRef<'a, T>`. + #[inline] + fn into_ref<'a>(self) -> PeripheralRef<'a, Self::P> + where + Self: 'a, + { + PeripheralRef::new(unsafe { self.clone_unchecked() }) + } +} + +impl<'b, T: DerefMut> Peripheral for T +where + T::Target: Peripheral, +{ + type P = ::P; + + #[inline] + unsafe fn clone_unchecked(&self) -> Self::P { + T::Target::clone_unchecked(self) + } +} + +impl<'b, T: Peripheral> Peripheral for PeripheralRef<'_, T> { + type P = T::P; + + #[inline] + unsafe fn clone_unchecked(&self) -> Self::P { + T::clone_unchecked(self) + } +} diff --git a/embassy/embassy-hal-internal/src/ratio.rs b/embassy/embassy-hal-internal/src/ratio.rs new file mode 100644 index 0000000..91dcfd4 --- /dev/null +++ b/embassy/embassy-hal-internal/src/ratio.rs @@ -0,0 +1,130 @@ +//! Types for dealing with rational numbers. +use core::ops::{Add, Div, Mul}; + +use num_traits::{CheckedAdd, CheckedDiv, CheckedMul}; + +/// Represents the ratio between two numbers. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Ratio { + /// Numerator. + numer: T, + /// Denominator. + denom: T, +} + +impl Ratio { + /// Creates a new `Ratio`. + #[inline(always)] + pub const fn new_raw(numer: T, denom: T) -> Ratio { + Ratio { numer, denom } + } + + /// Gets an immutable reference to the numerator. + #[inline(always)] + pub const fn numer(&self) -> &T { + &self.numer + } + + /// Gets an immutable reference to the denominator. + #[inline(always)] + pub const fn denom(&self) -> &T { + &self.denom + } +} + +impl Ratio { + /// Converts to an integer, rounding towards zero. + #[inline(always)] + pub fn to_integer(&self) -> T { + unwrap!(self.numer().checked_div(self.denom())) + } +} + +impl Div for Ratio { + type Output = Self; + + #[inline(always)] + fn div(mut self, rhs: T) -> Self::Output { + self.denom = unwrap!(self.denom().checked_mul(&rhs)); + self + } +} + +impl Mul for Ratio { + type Output = Self; + + #[inline(always)] + fn mul(mut self, rhs: T) -> Self::Output { + self.numer = unwrap!(self.numer().checked_mul(&rhs)); + self + } +} + +impl Add for Ratio { + type Output = Self; + + #[inline(always)] + fn add(mut self, rhs: T) -> Self::Output { + self.numer = unwrap!(unwrap!(self.denom().checked_mul(&rhs)).checked_add(self.numer())); + self + } +} + +macro_rules! impl_from_for_float { + ($from:ident) => { + impl From> for f32 { + #[inline(always)] + fn from(r: Ratio<$from>) -> Self { + (r.numer as f32) / (r.denom as f32) + } + } + + impl From> for f64 { + #[inline(always)] + fn from(r: Ratio<$from>) -> Self { + (r.numer as f64) / (r.denom as f64) + } + } + }; +} + +impl_from_for_float!(u8); +impl_from_for_float!(u16); +impl_from_for_float!(u32); +impl_from_for_float!(u64); +impl_from_for_float!(u128); +impl_from_for_float!(i8); +impl_from_for_float!(i16); +impl_from_for_float!(i32); +impl_from_for_float!(i64); +impl_from_for_float!(i128); + +impl core::fmt::Display for Ratio { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::write!(f, "{} / {}", self.numer(), self.denom()) + } +} + +#[cfg(test)] +mod tests { + use super::Ratio; + + #[test] + fn basics() { + let mut r = Ratio::new_raw(1, 2) + 2; + assert_eq!(*r.numer(), 5); + assert_eq!(*r.denom(), 2); + assert_eq!(r.to_integer(), 2); + + r = r * 2; + assert_eq!(*r.numer(), 10); + assert_eq!(*r.denom(), 2); + assert_eq!(r.to_integer(), 5); + + r = r / 2; + assert_eq!(*r.numer(), 10); + assert_eq!(*r.denom(), 4); + assert_eq!(r.to_integer(), 2); + } +} diff --git a/embassy/embassy-net-adin1110/Cargo.toml b/embassy/embassy-net-adin1110/Cargo.toml new file mode 100644 index 0000000..0b2a674 --- /dev/null +++ b/embassy/embassy-net-adin1110/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "embassy-net-adin1110" +version = "0.2.0" +description = "embassy-net driver for the ADIN1110 ethernet chip" +keywords = ["embedded", "ADIN1110", "embassy-net", "embedded-hal-async", "ethernet"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +edition = "2021" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-adin1110" + +[dependencies] +heapless = "0.8" +defmt = { version = "0.3", optional = true } +log = { version = "0.4", default-features = false, optional = true } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +bitfield = "0.14.0" + +[dev-dependencies] +embedded-hal-mock = { version = "0.10.0", features = ["embedded-hal-async", "eh1"] } +crc = "3.0.1" +env_logger = "0.10" +critical-section = { version = "1.1.2", features = ["std"] } +futures-test = "0.3.28" + +[features] +defmt = [ "dep:defmt", "embedded-hal-1/defmt-03" ] +log = ["dep:log"] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-adin1110-v$VERSION/embassy-net-adin1110/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-adin1110/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] diff --git a/embassy/embassy-net-adin1110/README.md b/embassy/embassy-net-adin1110/README.md new file mode 100644 index 0000000..0514274 --- /dev/null +++ b/embassy/embassy-net-adin1110/README.md @@ -0,0 +1,78 @@ +# SPE ADIN1110 `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for the `Analog ADIN1110` SPI SPE ethernet chips. + +## What is SPE or Single Pair Ethernet / 10 BASE-T1L + +SPE stands for Single Pair Ethernet. As the names implies, SPE uses differential signalling with 2 wires (a twisted-pair) in a cable as the physical medium. +SPE is full-duplex - it can transmit and receive ethernet packets at the same time. SPE is still ethernet, only the physical layer is different. + +SPE also supports [`PoDL (Power over Data Line)`](https://www.ti.com/lit/an/snla395/snla395.pdf), power delivery from 0.5 up to 50 Watts, similar to [`PoE`](https://en.wikipedia.org/wiki/Power_over_Ethernet), but an additional hardware and handshake protocol are needed. + +SPE has many link speeds but only `10 BASE-T1L` is able to reach cable lengths up to 1000 meters in `2.4 Vpp` transmit amplitude. +Currently in 2023, none of the standards are compatible with each other. +Thus `10 BASE-T1L` won't work with a `10 BASE-T1S`, `100 BASE-T1` or any standard `x BASE-T`. + +In the industry SPE is also called [`APL (Advanced Physical Layer)`](https://www.ethernet-apl.org), and is based on the `10 BASE-T1L` standard. + +APL can be used in [`intrinsic safety applications/explosion hazardous areas`](https://en.wikipedia.org/wiki/Electrical_equipment_in_hazardous_areas) which has its own name and standard called [`2-WISE (2-wire intrinsically safe ethernet) IEC TS 60079-47:2021`](https://webstore.iec.ch/publication/64292). + +`10 BASE-T1L` and `ADIN1110` are designed to support intrinsic safety applications. The power supply energy is fixed and PDoL is not supported. + +## Supported SPI modes + +`ADIN1110` supports two SPI modes. `Generic` and [`OPEN Alliance 10BASE-T1x MAC-PHY serial interface`](https://opensig.org/wp-content/uploads/2023/12/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf) + +Both modes support with and without additional CRC. +Currently only `Generic` SPI with or without CRC is supported. + +*NOTE:* SPI Mode is selected by the hardware pins `SPI_CFG0` and `SPI_CFG1`. Software can't detect nor change the mode. + +## Hardware + +- Tested on [`Analog Devices EVAL-ADIN1110EBZ`](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adin1110.html) with an `STM32L4S5QII3P`, see [`spe_adin1110_http_server`](../examples/stm32l4/src/bin/spe_adin1110_http_server.rs) for an example. +- [`SparkFun MicroMod Single Pair Ethernet Function Board`](https://www.sparkfun.com/products/19038) or [`SparkFun MicroMod Single Pair Ethernet Kit (End Of Life)`](https://www.sparkfun.com/products/19628), supporting multiple microcontrollers. **Make sure to check if it's a microcontroller that is supported by Embassy!** + +## Other SPE chips + +* [`Analog ADIN2111`](https://www.analog.com/en/products/adin2111.html) 2 Port SPI version. Can work with this driver. +* [`Analog ADIN1100`](https://www.analog.com/en/products/adin1100.html) RGMII version. + +## Testing + +ADIN1110 library can tested on the host with a mock SPI driver. + +$ `cargo test --target x86_64-unknown-linux-gnu` + +## Benchmark + +- Benchmarked on [`Analog Devices EVAL-ADIN1110EBZ`](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adin1110.html), with [`spe_adin1110_http_server`](../examples/stm32l4/src/bin/spe_adin1110_http_server.rs) example. + +Basic `ping` benchmark +```rust,ignore +# ping -c 60 + +60 packets transmitted, 60 received, 0% packet loss, time 59066ms +rtt min/avg/max/mdev = 1.089/1.161/1.237/0.018 ms + +# ping -s 1472 -M do -c 60 + +60 packets transmitted, 60 received, 0% packet loss, time 59066ms +rtt min/avg/max/mdev = 5.122/5.162/6.177/0.133 ms +``` + +HTTP load generator benchmark with [`oha`](https://github.com/hatoo/oha) +```rust,ignore +# oha -c 1 http:// -z 60s +Summary: + Success rate: 50.00% + Total: 60.0005 secs + Slowest: 0.0055 secs + Fastest: 0.0033 secs + Average: 0.0034 secs + Requests/sec: 362.1971 + + Total data: 2.99 MiB + Size/request: 289 B + Size/sec: 51.11 KiB +``` diff --git a/embassy/embassy-net-adin1110/src/crc32.rs b/embassy/embassy-net-adin1110/src/crc32.rs new file mode 100644 index 0000000..4b3c69f --- /dev/null +++ b/embassy/embassy-net-adin1110/src/crc32.rs @@ -0,0 +1,365 @@ +/// CRC32 lookup table. +pub const CRC32R_LOOKUP_TABLE: [u32; 256] = [ + 0x0000_0000, + 0x7707_3096, + 0xEE0E_612C, + 0x9909_51BA, + 0x076D_C419, + 0x706A_F48F, + 0xE963_A535, + 0x9E64_95A3, + 0x0EDB_8832, + 0x79DC_B8A4, + 0xE0D5_E91E, + 0x97D2_D988, + 0x09B6_4C2B, + 0x7EB1_7CBD, + 0xE7B8_2D07, + 0x90BF_1D91, + 0x1DB7_1064, + 0x6AB0_20F2, + 0xF3B9_7148, + 0x84BE_41DE, + 0x1ADA_D47D, + 0x6DDD_E4EB, + 0xF4D4_B551, + 0x83D3_85C7, + 0x136C_9856, + 0x646B_A8C0, + 0xFD62_F97A, + 0x8A65_C9EC, + 0x1401_5C4F, + 0x6306_6CD9, + 0xFA0F_3D63, + 0x8D08_0DF5, + 0x3B6E_20C8, + 0x4C69_105E, + 0xD560_41E4, + 0xA267_7172, + 0x3C03_E4D1, + 0x4B04_D447, + 0xD20D_85FD, + 0xA50A_B56B, + 0x35B5_A8FA, + 0x42B2_986C, + 0xDBBB_C9D6, + 0xACBC_F940, + 0x32D8_6CE3, + 0x45DF_5C75, + 0xDCD6_0DCF, + 0xABD1_3D59, + 0x26D9_30AC, + 0x51DE_003A, + 0xC8D7_5180, + 0xBFD0_6116, + 0x21B4_F4B5, + 0x56B3_C423, + 0xCFBA_9599, + 0xB8BD_A50F, + 0x2802_B89E, + 0x5F05_8808, + 0xC60C_D9B2, + 0xB10B_E924, + 0x2F6F_7C87, + 0x5868_4C11, + 0xC161_1DAB, + 0xB666_2D3D, + 0x76DC_4190, + 0x01DB_7106, + 0x98D2_20BC, + 0xEFD5_102A, + 0x71B1_8589, + 0x06B6_B51F, + 0x9FBF_E4A5, + 0xE8B8_D433, + 0x7807_C9A2, + 0x0F00_F934, + 0x9609_A88E, + 0xE10E_9818, + 0x7F6A_0DBB, + 0x086D_3D2D, + 0x9164_6C97, + 0xE663_5C01, + 0x6B6B_51F4, + 0x1C6C_6162, + 0x8565_30D8, + 0xF262_004E, + 0x6C06_95ED, + 0x1B01_A57B, + 0x8208_F4C1, + 0xF50F_C457, + 0x65B0_D9C6, + 0x12B7_E950, + 0x8BBE_B8EA, + 0xFCB9_887C, + 0x62DD_1DDF, + 0x15DA_2D49, + 0x8CD3_7CF3, + 0xFBD4_4C65, + 0x4DB2_6158, + 0x3AB5_51CE, + 0xA3BC_0074, + 0xD4BB_30E2, + 0x4ADF_A541, + 0x3DD8_95D7, + 0xA4D1_C46D, + 0xD3D6_F4FB, + 0x4369_E96A, + 0x346E_D9FC, + 0xAD67_8846, + 0xDA60_B8D0, + 0x4404_2D73, + 0x3303_1DE5, + 0xAA0A_4C5F, + 0xDD0D_7CC9, + 0x5005_713C, + 0x2702_41AA, + 0xBE0B_1010, + 0xC90C_2086, + 0x5768_B525, + 0x206F_85B3, + 0xB966_D409, + 0xCE61_E49F, + 0x5EDE_F90E, + 0x29D9_C998, + 0xB0D0_9822, + 0xC7D7_A8B4, + 0x59B3_3D17, + 0x2EB4_0D81, + 0xB7BD_5C3B, + 0xC0BA_6CAD, + 0xEDB8_8320, + 0x9ABF_B3B6, + 0x03B6_E20C, + 0x74B1_D29A, + 0xEAD5_4739, + 0x9DD2_77AF, + 0x04DB_2615, + 0x73DC_1683, + 0xE363_0B12, + 0x9464_3B84, + 0x0D6D_6A3E, + 0x7A6A_5AA8, + 0xE40E_CF0B, + 0x9309_FF9D, + 0x0A00_AE27, + 0x7D07_9EB1, + 0xF00F_9344, + 0x8708_A3D2, + 0x1E01_F268, + 0x6906_C2FE, + 0xF762_575D, + 0x8065_67CB, + 0x196C_3671, + 0x6E6B_06E7, + 0xFED4_1B76, + 0x89D3_2BE0, + 0x10DA_7A5A, + 0x67DD_4ACC, + 0xF9B9_DF6F, + 0x8EBE_EFF9, + 0x17B7_BE43, + 0x60B0_8ED5, + 0xD6D6_A3E8, + 0xA1D1_937E, + 0x38D8_C2C4, + 0x4FDF_F252, + 0xD1BB_67F1, + 0xA6BC_5767, + 0x3FB5_06DD, + 0x48B2_364B, + 0xD80D_2BDA, + 0xAF0A_1B4C, + 0x3603_4AF6, + 0x4104_7A60, + 0xDF60_EFC3, + 0xA867_DF55, + 0x316E_8EEF, + 0x4669_BE79, + 0xCB61_B38C, + 0xBC66_831A, + 0x256F_D2A0, + 0x5268_E236, + 0xCC0C_7795, + 0xBB0B_4703, + 0x2202_16B9, + 0x5505_262F, + 0xC5BA_3BBE, + 0xB2BD_0B28, + 0x2BB4_5A92, + 0x5CB3_6A04, + 0xC2D7_FFA7, + 0xB5D0_CF31, + 0x2CD9_9E8B, + 0x5BDE_AE1D, + 0x9B64_C2B0, + 0xEC63_F226, + 0x756A_A39C, + 0x026D_930A, + 0x9C09_06A9, + 0xEB0E_363F, + 0x7207_6785, + 0x0500_5713, + 0x95BF_4A82, + 0xE2B8_7A14, + 0x7BB1_2BAE, + 0x0CB6_1B38, + 0x92D2_8E9B, + 0xE5D5_BE0D, + 0x7CDC_EFB7, + 0x0BDB_DF21, + 0x86D3_D2D4, + 0xF1D4_E242, + 0x68DD_B3F8, + 0x1FDA_836E, + 0x81BE_16CD, + 0xF6B9_265B, + 0x6FB0_77E1, + 0x18B7_4777, + 0x8808_5AE6, + 0xFF0F_6A70, + 0x6606_3BCA, + 0x1101_0B5C, + 0x8F65_9EFF, + 0xF862_AE69, + 0x616B_FFD3, + 0x166C_CF45, + 0xA00A_E278, + 0xD70D_D2EE, + 0x4E04_8354, + 0x3903_B3C2, + 0xA767_2661, + 0xD060_16F7, + 0x4969_474D, + 0x3E6E_77DB, + 0xAED1_6A4A, + 0xD9D6_5ADC, + 0x40DF_0B66, + 0x37D8_3BF0, + 0xA9BC_AE53, + 0xDEBB_9EC5, + 0x47B2_CF7F, + 0x30B5_FFE9, + 0xBDBD_F21C, + 0xCABA_C28A, + 0x53B3_9330, + 0x24B4_A3A6, + 0xBAD0_3605, + 0xCDD7_0693, + 0x54DE_5729, + 0x23D9_67BF, + 0xB366_7A2E, + 0xC461_4AB8, + 0x5D68_1B02, + 0x2A6F_2B94, + 0xB40B_BE37, + 0xC30C_8EA1, + 0x5A05_DF1B, + 0x2D02_EF8D, +]; + +/// Generate Ethernet Frame Check Sequence +#[allow(non_camel_case_types)] +#[derive(Debug)] +pub struct ETH_FCS(pub u32); + +impl ETH_FCS { + const CRC32_OK: u32 = 0x2144_df1c; + + /// Create a new frame check sequence from `data`. + #[must_use] + pub fn new(data: &[u8]) -> Self { + let fcs = data.iter().fold(u32::MAX, |crc, byte| { + let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte; + CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8) + }) ^ u32::MAX; + Self(fcs) + } + + /// Update the frame check sequence with `data`. + #[must_use] + pub fn update(self, data: &[u8]) -> Self { + let fcs = data.iter().fold(self.0 ^ u32::MAX, |crc, byte| { + let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte; + CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8) + }) ^ u32::MAX; + Self(fcs) + } + + /// Check if the frame check sequence is correct. + #[must_use] + pub fn crc_ok(&self) -> bool { + self.0 == Self::CRC32_OK + } + + /// Switch byte order. + #[must_use] + pub fn hton_bytes(&self) -> [u8; 4] { + self.0.to_le_bytes() + } + + /// Switch byte order as a u32. + #[must_use] + pub fn hton(&self) -> u32 { + self.0.to_le() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn crc32_ethernet_frame() { + let packet_a = &[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xff, 0x06, 0x00, 0x01, 0x08, 0x00, + 0x06, 0x04, 0x00, 0x01, 0x00, 0xe0, 0x4c, 0x68, 0x0e, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc0, 0xa8, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x65, 0x90, 0x3d, + ]; + + let packet_b = &[ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xdd, 0x06, 0x00, 0x01, 0x08, 0x00, + 0x06, 0x04, 0x00, 0x02, 0x00, 0xe0, 0x4c, 0x68, 0x09, 0xde, 0xc0, 0xa8, 0x01, 0x02, 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xc0, 0xa8, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3d, 0x67, 0x7c, + ]; + + // Packet A + let own_crc = ETH_FCS::new(&packet_a[0..60]); + let crc_bytes = own_crc.hton_bytes(); + println!("{:08x} {:02x?}", own_crc.0, crc_bytes); + assert_eq!(&crc_bytes, &packet_a[60..64]); + + let own_crc = ETH_FCS::new(packet_a); + println!("{:08x}", own_crc.0); + assert_eq!(own_crc.0, ETH_FCS::CRC32_OK); + + // Packet B + let own_crc = ETH_FCS::new(&packet_b[0..60]); + let crc_bytes = own_crc.hton_bytes(); + println!("{:08x} {:02x?}", own_crc.0, crc_bytes); + assert_eq!(&crc_bytes, &packet_b[60..64]); + + let own_crc = ETH_FCS::new(packet_b); + println!("{:08x}", own_crc.0); + assert_eq!(own_crc.0, ETH_FCS::CRC32_OK); + } + + #[test] + fn crc32_update() { + let full_data = &[ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xdd, 0x06, 0x00, 0x01, 0x08, 0x00, + 0x06, 0x04, 0x00, 0x02, 0x00, 0xe0, 0x4c, 0x68, 0x09, 0xde, 0xc0, 0xa8, 0x01, 0x02, 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xc0, 0xa8, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3d, 0x67, 0x7c, + ]; + + let (part_a, part_b) = full_data.split_at(16); + let crc_partially = ETH_FCS::new(part_a).update(part_b); + + let crc_full = ETH_FCS::new(full_data); + + assert_eq!(crc_full.0, crc_partially.0); + } +} diff --git a/embassy/embassy-net-adin1110/src/crc8.rs b/embassy/embassy-net-adin1110/src/crc8.rs new file mode 100644 index 0000000..321983e --- /dev/null +++ b/embassy/embassy-net-adin1110/src/crc8.rs @@ -0,0 +1,53 @@ +/// CRC-8/ITU +const CRC8X_TABLE: [u8; 256] = [ + 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, + 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, + 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, + 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, + 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, + 0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, + 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80, 0x95, + 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, + 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f, + 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, + 0x33, 0x34, 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 0x3e, + 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7, + 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, + 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3, +]; + +/// Calculate the crc of a piece of data. +pub fn crc8(data: &[u8]) -> u8 { + data.iter().fold(0, |crc, &byte| CRC8X_TABLE[usize::from(byte ^ crc)]) +} + +#[cfg(test)] +mod tests { + use ::crc::{Crc, CRC_8_SMBUS}; + + use super::crc8; + + #[test] + fn spi_header_crc8() { + let data = &[0x80, 0x00]; + + let c = Crc::::new(&CRC_8_SMBUS); + let mut dig = c.digest(); + dig.update(data); + let sw_crc = dig.finalize(); + + let own_crc = crc8(data); + + assert_eq!(own_crc, sw_crc); + assert_eq!(own_crc, 182); + + let data = &[0x80, 0x01]; + let mut dig = c.digest(); + dig.update(data); + let sw_crc = dig.finalize(); + let own_crc = crc8(data); + + assert_eq!(own_crc, sw_crc); + assert_eq!(own_crc, 177); + } +} diff --git a/embassy/embassy-net-adin1110/src/fmt.rs b/embassy/embassy-net-adin1110/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-net-adin1110/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-net-adin1110/src/lib.rs b/embassy/embassy-net-adin1110/src/lib.rs new file mode 100644 index 0000000..7f1c772 --- /dev/null +++ b/embassy/embassy-net-adin1110/src/lib.rs @@ -0,0 +1,1330 @@ +#![cfg_attr(not(test), no_std)] +#![deny(clippy::pedantic)] +#![allow(async_fn_in_trait)] +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::missing_panics_doc)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// must go first! +mod fmt; + +mod crc32; +mod crc8; +mod mdio; +mod phy; +mod regs; + +use ch::driver::LinkState; +pub use crc32::ETH_FCS; +use crc8::crc8; +use embassy_futures::select::{select, Either}; +use embassy_net_driver_channel as ch; +use embassy_time::Timer; +use embedded_hal_1::digital::OutputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::{Error, Operation, SpiDevice}; +use heapless::Vec; +pub use mdio::MdioBus; +pub use phy::Phy10BaseT1x; +use phy::{RegsC22, RegsC45}; +use regs::{Config0, Config2, SpiRegisters as sr, Status0, Status1}; + +use crate::fmt::Bytes; +use crate::regs::{LedCntrl, LedFunc, LedPol, LedPolarity, SpiHeader}; + +/// ADIN1110 intern PHY ID +pub const PHYID: u32 = 0x0283_BC91; + +/// Error values ADIN1110 +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(non_camel_case_types)] +pub enum AdinError { + /// SPI-BUS Error + Spi(E), + /// Ethernet FCS error + FCS, + /// SPI Header CRC error + SPI_CRC, + /// Received or sended ethernet packet is too big + PACKET_TOO_BIG, + /// Received or sended ethernet packet is too small + PACKET_TOO_SMALL, + /// MDIO transaction timeout + MDIO_ACC_TIMEOUT, +} + +/// Type alias `Result` type with `AdinError` as error type. +pub type AEResult = core::result::Result>; + +/// Internet PHY address +pub const MDIO_PHY_ADDR: u8 = 0x01; + +/// Maximum Transmission Unit +pub const MTU: usize = 1514; + +/// Max SPI/Frame buffer size +pub const MAX_BUFF: usize = 2048; + +const DONT_CARE_BYTE: u8 = 0x00; +const TURN_AROUND_BYTE: u8 = 0x00; + +/// Packet minimal frame/packet length +const ETH_MIN_LEN: usize = 64; +/// Ethernet `Frame Check Sequence` length +const FCS_LEN: usize = 4; +/// Packet minimal frame/packet length without `Frame Check Sequence` length +const ETH_MIN_WITHOUT_FCS_LEN: usize = ETH_MIN_LEN - FCS_LEN; + +/// SPI Header, contains SPI action and register id. +const SPI_HEADER_LEN: usize = 2; +/// SPI Header CRC length +const SPI_HEADER_CRC_LEN: usize = 1; +/// SPI Header Turn Around length +const SPI_HEADER_TA_LEN: usize = 1; +/// Frame Header length +const FRAME_HEADER_LEN: usize = 2; +/// Space for last bytes to create multipule 4 bytes on the end of a FIFO read/write. +const SPI_SPACE_MULTIPULE: usize = 3; + +/// P1 = 0x00, P2 = 0x01 +const PORT_ID_BYTE: u8 = 0x00; + +/// Type alias for the embassy-net driver for ADIN1110 +pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; + +/// Internal state for the embassy-net integration. +pub struct State { + ch_state: ch::State, +} +impl State { + /// Create a new `State`. + #[must_use] + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// ADIN1110 embassy-net driver +#[derive(Debug)] +pub struct ADIN1110 { + /// SPI bus + spi: SPI, + /// Enable CRC on SPI transfer. + /// This must match with the hardware pin `SPI_CFG0` were low = CRC enable, high = CRC disabled. + spi_crc: bool, + /// Append FCS by the application of transmit packet, false = FCS is appended by the MAC, true = FCS appended by the application. + append_fcs_on_tx: bool, +} + +impl ADIN1110 { + /// Create a new ADIN1110 instance. + pub fn new(spi: SPI, spi_crc: bool, append_fcs_on_tx: bool) -> Self { + Self { + spi, + spi_crc, + append_fcs_on_tx, + } + } + + /// Read a SPI register + pub async fn read_reg(&mut self, reg: sr) -> AEResult { + let mut tx_buf = Vec::::new(); + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_addr(reg); + let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); + + if self.spi_crc { + // Add CRC for header data + let _ = tx_buf.push(crc8(&tx_buf)); + } + + // Turn around byte, give the chip the time to access/setup the answer data. + let _ = tx_buf.push(TURN_AROUND_BYTE); + + let mut rx_buf = [0; 5]; + + let spi_read_len = if self.spi_crc { rx_buf.len() } else { rx_buf.len() - 1 }; + + let mut spi_op = [Operation::Write(&tx_buf), Operation::Read(&mut rx_buf[0..spi_read_len])]; + + self.spi.transaction(&mut spi_op).await.map_err(AdinError::Spi)?; + + if self.spi_crc { + let crc = crc8(&rx_buf[0..4]); + if crc != rx_buf[4] { + return Err(AdinError::SPI_CRC); + } + } + + let value = u32::from_be_bytes(rx_buf[0..4].try_into().unwrap()); + + trace!("REG Read {} = {:08x} SPI {}", reg, value, Bytes(&tx_buf)); + + Ok(value) + } + + /// Write a SPI register + pub async fn write_reg(&mut self, reg: sr, value: u32) -> AEResult<(), SPI::Error> { + let mut tx_buf = Vec::::new(); + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_write(true); + spi_hdr.set_addr(reg); + let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); + + if self.spi_crc { + // Add CRC for header data + let _ = tx_buf.push(crc8(&tx_buf)); + } + + let val = value.to_be_bytes(); + let _ = tx_buf.extend_from_slice(val.as_slice()); + + if self.spi_crc { + // Add CRC for header data + let _ = tx_buf.push(crc8(val.as_slice())); + } + + trace!("REG Write {} = {:08x} SPI {}", reg, value, Bytes(&tx_buf)); + + self.spi.write(&tx_buf).await.map_err(AdinError::Spi) + } + + /// helper function for write to `MDIO_ACC` register and wait for ready! + async fn write_mdio_acc_reg(&mut self, mdio_acc_val: u32) -> AEResult { + self.write_reg(sr::MDIO_ACC, mdio_acc_val).await?; + + // TODO: Add proper timeout! + for _ in 0..100_000 { + let val = self.read_reg(sr::MDIO_ACC).await?; + if val & 0x8000_0000 != 0 { + return Ok(val); + } + } + + Err(AdinError::MDIO_ACC_TIMEOUT) + } + + /// Read out fifo ethernet packet memory received via the wire. + pub async fn read_fifo(&mut self, frame: &mut [u8]) -> AEResult { + const HEAD_LEN: usize = SPI_HEADER_LEN + SPI_HEADER_CRC_LEN + SPI_HEADER_TA_LEN; + const TAIL_LEN: usize = FCS_LEN + SPI_SPACE_MULTIPULE; + + let mut tx_buf = Vec::::new(); + + // Size of the frame, also includes the `frame header` and `FCS`. + let fifo_frame_size = self.read_reg(sr::RX_FSIZE).await? as usize; + + if fifo_frame_size < ETH_MIN_LEN + FRAME_HEADER_LEN { + return Err(AdinError::PACKET_TOO_SMALL); + } + + let packet_size = fifo_frame_size - FRAME_HEADER_LEN - FCS_LEN; + + if packet_size > frame.len() { + trace!("MAX: {} WANT: {}", frame.len(), packet_size); + return Err(AdinError::PACKET_TOO_BIG); + } + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_addr(sr::RX); + let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); + + if self.spi_crc { + // Add CRC for header data + let _ = tx_buf.push(crc8(&tx_buf)); + } + + // Turn around byte, TODO: Unknown that this is. + let _ = tx_buf.push(TURN_AROUND_BYTE); + + let mut frame_header = [0, 0]; + let mut fcs_and_extra = [0; TAIL_LEN]; + + // Packet read of write to the MAC packet buffer must be a multipul of 4! + let tail_size = (fifo_frame_size & 0x03) + FCS_LEN; + + let mut spi_op = [ + Operation::Write(&tx_buf), + Operation::Read(&mut frame_header), + Operation::Read(&mut frame[0..packet_size]), + Operation::Read(&mut fcs_and_extra[0..tail_size]), + ]; + + self.spi.transaction(&mut spi_op).await.map_err(AdinError::Spi)?; + + // According to register `CONFIG2`, bit 5 `CRC_APPEND` discription: + // "Similarly, on receive, the CRC32 is forwarded with the frame to the host where the host must verify it is correct." + // The application must allways check the FCS. It seems that the MAC/PHY has no option to handle this. + let fcs_calc = ETH_FCS::new(&frame[0..packet_size]); + + if fcs_calc.hton_bytes() == fcs_and_extra[0..4] { + Ok(packet_size) + } else { + Err(AdinError::FCS) + } + } + + /// Write to fifo ethernet packet memory send over the wire. + pub async fn write_fifo(&mut self, frame: &[u8]) -> AEResult<(), SPI::Error> { + const HEAD_LEN: usize = SPI_HEADER_LEN + SPI_HEADER_CRC_LEN + FRAME_HEADER_LEN; + const TAIL_LEN: usize = ETH_MIN_LEN - FCS_LEN + FCS_LEN + SPI_SPACE_MULTIPULE; + + if frame.len() < (6 + 6 + 2) { + return Err(AdinError::PACKET_TOO_SMALL); + } + if frame.len() > (MAX_BUFF - FRAME_HEADER_LEN) { + return Err(AdinError::PACKET_TOO_BIG); + } + + // SPI HEADER + [OPTIONAL SPI CRC] + FRAME HEADER + let mut head_data = Vec::::new(); + // [OPTIONAL PAD DATA] + FCS + [OPTINAL BYTES MAKE SPI FRAME EVEN] + let mut tail_data = Vec::::new(); + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_write(true); + spi_hdr.set_addr(sr::TX); + + head_data + .extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()) + .map_err(|_e| AdinError::PACKET_TOO_BIG)?; + + if self.spi_crc { + // Add CRC for header data + head_data + .push(crc8(&head_data[0..2])) + .map_err(|_| AdinError::PACKET_TOO_BIG)?; + } + + // Add port number, ADIN1110 its fixed to zero/P1, but for ADIN2111 has two ports. + head_data + .extend_from_slice(u16::from(PORT_ID_BYTE).to_be_bytes().as_slice()) + .map_err(|_e| AdinError::PACKET_TOO_BIG)?; + + // ADIN1110 MAC and PHY don´t accept ethernet packet smaller than 64 bytes. + // So padded the data minus the FCS, FCS is automatilly added to by the MAC. + if frame.len() < ETH_MIN_WITHOUT_FCS_LEN { + let _ = tail_data.resize(ETH_MIN_WITHOUT_FCS_LEN - frame.len(), 0x00); + } + + // Append FCS by the application + if self.append_fcs_on_tx { + let mut frame_fcs = ETH_FCS::new(frame); + + if !tail_data.is_empty() { + frame_fcs = frame_fcs.update(&tail_data); + } + + let _ = tail_data.extend_from_slice(frame_fcs.hton_bytes().as_slice()); + } + + // len = frame_size + optional padding + 2 bytes Frame header + let send_len_orig = frame.len() + tail_data.len() + FRAME_HEADER_LEN; + + let send_len = u32::try_from(send_len_orig).map_err(|_| AdinError::PACKET_TOO_BIG)?; + + // Packet read of write to the MAC packet buffer must be a multipul of 4 bytes! + let pad_len = send_len_orig & 0x03; + if pad_len != 0 { + let spi_pad_len = 4 - pad_len + tail_data.len(); + let _ = tail_data.resize(spi_pad_len, DONT_CARE_BYTE); + } + + self.write_reg(sr::TX_FSIZE, send_len).await?; + + trace!( + "TX: hdr {} [{}] {}-{}-{} SIZE: {}", + head_data.len(), + frame.len(), + Bytes(head_data.as_slice()), + Bytes(frame), + Bytes(tail_data.as_slice()), + send_len, + ); + + let mut transaction = [ + Operation::Write(head_data.as_slice()), + Operation::Write(frame), + Operation::Write(tail_data.as_slice()), + ]; + + self.spi.transaction(&mut transaction).await.map_err(AdinError::Spi) + } + + /// Programs the mac address in the mac filters. + /// Also set the boardcast address. + /// The chip supports 2 priority queues but current code doesn't support this mode. + pub async fn set_mac_addr(&mut self, mac: &[u8; 6]) -> AEResult<(), SPI::Error> { + let mac_high_part = u16::from_be_bytes(mac[0..2].try_into().unwrap()); + let mac_low_part = u32::from_be_bytes(mac[2..6].try_into().unwrap()); + + // program our mac address in the mac address filter + self.write_reg(sr::ADDR_FILT_UPR0, (1 << 16) | (1 << 30) | u32::from(mac_high_part)) + .await?; + self.write_reg(sr::ADDR_FILT_LWR0, mac_low_part).await?; + + self.write_reg(sr::ADDR_MSK_UPR0, u32::from(mac_high_part)).await?; + self.write_reg(sr::ADDR_MSK_LWR0, mac_low_part).await?; + + // Also program broadcast address in the mac address filter + self.write_reg(sr::ADDR_FILT_UPR1, (1 << 16) | (1 << 30) | 0xFFFF) + .await?; + self.write_reg(sr::ADDR_FILT_LWR1, 0xFFFF_FFFF).await?; + self.write_reg(sr::ADDR_MSK_UPR1, 0xFFFF).await?; + self.write_reg(sr::ADDR_MSK_LWR1, 0xFFFF_FFFF).await?; + + Ok(()) + } +} + +impl mdio::MdioBus for ADIN1110 { + type Error = AdinError; + + /// Read from the PHY Registers as Clause 22. + async fn read_cl22(&mut self, phy_id: u8, reg: u8) -> Result { + let mdio_acc_val: u32 = + (0x1 << 28) | u32::from(phy_id & 0x1F) << 21 | u32::from(reg & 0x1F) << 16 | (0x3 << 26); + + // Result is in the lower half of the answer. + #[allow(clippy::cast_possible_truncation)] + self.write_mdio_acc_reg(mdio_acc_val).await.map(|val| val as u16) + } + + /// Read from the PHY Registers as Clause 45. + async fn read_cl45(&mut self, phy_id: u8, regc45: (u8, u16)) -> Result { + let mdio_acc_val = u32::from(phy_id & 0x1F) << 21 | u32::from(regc45.0 & 0x1F) << 16 | u32::from(regc45.1); + + self.write_mdio_acc_reg(mdio_acc_val).await?; + + let mdio_acc_val = u32::from(phy_id & 0x1F) << 21 | u32::from(regc45.0 & 0x1F) << 16 | (0x03 << 26); + + // Result is in the lower half of the answer. + #[allow(clippy::cast_possible_truncation)] + self.write_mdio_acc_reg(mdio_acc_val).await.map(|val| val as u16) + } + + /// Write to the PHY Registers as Clause 22. + async fn write_cl22(&mut self, phy_id: u8, reg: u8, val: u16) -> Result<(), Self::Error> { + let mdio_acc_val: u32 = + (0x1 << 28) | u32::from(phy_id & 0x1F) << 21 | u32::from(reg & 0x1F) << 16 | (0x1 << 26) | u32::from(val); + + self.write_mdio_acc_reg(mdio_acc_val).await.map(|_| ()) + } + + /// Write to the PHY Registers as Clause 45. + async fn write_cl45(&mut self, phy_id: u8, regc45: (u8, u16), value: u16) -> AEResult<(), SPI::Error> { + let phy_id = u32::from(phy_id & 0x1F) << 21; + let dev_addr = u32::from(regc45.0 & 0x1F) << 16; + let reg = u32::from(regc45.1); + + let mdio_acc_val: u32 = phy_id | dev_addr | reg; + self.write_mdio_acc_reg(mdio_acc_val).await?; + + let mdio_acc_val: u32 = phy_id | dev_addr | (0x01 << 26) | u32::from(value); + self.write_mdio_acc_reg(mdio_acc_val).await.map(|_| ()) + } +} + +/// Background runner for the ADIN1110. +/// +/// You must call `.run()` in a background task for the ADIN1110 to operate. +pub struct Runner<'d, SPI, INT, RST> { + mac: ADIN1110, + ch: ch::Runner<'d, MTU>, + int: INT, + is_link_up: bool, + _reset: RST, +} + +impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> { + /// Run the driver. + #[allow(clippy::too_many_lines)] + pub async fn run(mut self) -> ! { + loop { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + + loop { + debug!("Waiting for interrupts"); + match select(self.int.wait_for_low(), tx_chan.tx_buf()).await { + Either::First(_) => { + let mut status1_clr = Status1(0); + let mut status1 = Status1(self.mac.read_reg(sr::STATUS1).await.unwrap()); + + while status1.p1_rx_rdy() { + debug!("alloc RX packet buffer"); + match select(rx_chan.rx_buf(), tx_chan.tx_buf()).await { + // Handle frames that needs to transmit from the wire. + // Note: rx_chan.rx_buf() channel don´t accept new request + // when the tx_chan is full. So these will be handled + // automaticly. + Either::First(frame) => match self.mac.read_fifo(frame).await { + Ok(n) => { + rx_chan.rx_done(n); + } + Err(e) => match e { + AdinError::PACKET_TOO_BIG => { + error!("RX Packet too big, DROP"); + self.mac.write_reg(sr::FIFO_CLR, 1).await.unwrap(); + } + AdinError::PACKET_TOO_SMALL => { + error!("RX Packet too small, DROP"); + self.mac.write_reg(sr::FIFO_CLR, 1).await.unwrap(); + } + AdinError::Spi(e) => { + error!("RX Spi error {}", e.kind()); + } + e => { + error!("RX Error {:?}", e); + } + }, + }, + Either::Second(frame) => { + // Handle frames that needs to transmit to the wire. + self.mac.write_fifo(frame).await.unwrap(); + tx_chan.tx_done(); + } + } + status1 = Status1(self.mac.read_reg(sr::STATUS1).await.unwrap()); + } + + let status0 = Status0(self.mac.read_reg(sr::STATUS0).await.unwrap()); + if status1.0 & !0x1b != 0 { + error!("SPE CHIP STATUS 0:{:08x} 1:{:08x}", status0.0, status1.0); + } + + if status1.tx_rdy() { + status1_clr.set_tx_rdy(true); + trace!("TX_DONE"); + } + + if status1.link_change() { + let link = status1.p1_link_status(); + self.is_link_up = link; + + if link { + let link_status = self + .mac + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA7::AN_STATUS_EXTRA.into()) + .await + .unwrap(); + + let volt = if link_status & (0b11 << 5) == (0b11 << 5) { + "2.4" + } else { + "1.0" + }; + + let mse = self + .mac + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1::MSE_VAL.into()) + .await + .unwrap(); + + info!("LINK Changed: Link Up, Volt: {} V p-p, MSE: {:0004}", volt, mse); + } else { + info!("LINK Changed: Link Down"); + } + + state_chan.set_link_state(if link { LinkState::Up } else { LinkState::Down }); + status1_clr.set_link_change(true); + } + + if status1.tx_ecc_err() { + error!("SPI TX_ECC_ERR error, CLEAR TX FIFO"); + self.mac.write_reg(sr::FIFO_CLR, 2).await.unwrap(); + status1_clr.set_tx_ecc_err(true); + } + + if status1.rx_ecc_err() { + error!("SPI RX_ECC_ERR error"); + status1_clr.set_rx_ecc_err(true); + } + + if status1.spi_err() { + error!("SPI SPI_ERR CRC error"); + status1_clr.set_spi_err(true); + } + + if status0.phyint() { + let crsm_irq_st = self + .mac + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::CRSM_IRQ_STATUS.into()) + .await + .unwrap(); + + let phy_irq_st = self + .mac + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1F::PHY_SYBSYS_IRQ_STATUS.into()) + .await + .unwrap(); + + warn!( + "SPE CHIP PHY CRSM_IRQ_STATUS {:04x} PHY_SUBSYS_IRQ_STATUS {:04x}", + crsm_irq_st, phy_irq_st + ); + } + + if status0.txfcse() { + error!("Ethernet Frame FCS and calc FCS don't match!"); + } + + // Clear status0 + self.mac.write_reg(sr::STATUS0, 0xFFF).await.unwrap(); + self.mac.write_reg(sr::STATUS1, status1_clr.0).await.unwrap(); + } + Either::Second(packet) => { + // Handle frames that needs to transmit to the wire. + self.mac.write_fifo(packet).await.unwrap(); + tx_chan.tx_done(); + } + } + } + } + } +} + +/// Obtain a driver for using the ADIN1110 with [`embassy-net`](crates.io/crates/embassy-net). +pub async fn new( + mac_addr: [u8; 6], + state: &'_ mut State, + spi_dev: SPI, + int: INT, + mut reset: RST, + spi_crc: bool, + append_fcs_on_tx: bool, +) -> (Device<'_>, Runner<'_, SPI, INT, RST>) { + use crate::regs::{IMask0, IMask1}; + + info!("INIT ADIN1110"); + + // Reset sequence + reset.set_low().unwrap(); + + // Wait t1: 20-43mS + Timer::after_millis(30).await; + + reset.set_high().unwrap(); + + // Wait t3: 50mS + Timer::after_millis(50).await; + + // Create device + let mut mac = ADIN1110::new(spi_dev, spi_crc, append_fcs_on_tx); + + // Check PHYID + let id = mac.read_reg(sr::PHYID).await.unwrap(); + assert_eq!(id, PHYID); + + debug!("SPE: CHIP MAC/ID: {:08x}", id); + + #[cfg(any(feature = "defmt", feature = "log"))] + { + let adin_phy = Phy10BaseT1x::default(); + let phy_id = adin_phy.get_id(&mut mac).await.unwrap(); + debug!("SPE: CHIP: PHY ID: {:08x}", phy_id); + } + + let mi_control = mac.read_cl22(MDIO_PHY_ADDR, RegsC22::CONTROL as u8).await.unwrap(); + debug!("SPE CHIP PHY MI_CONTROL {:04x}", mi_control); + if mi_control & 0x0800 != 0 { + let val = mi_control & !0x0800; + debug!("SPE CHIP PHY MI_CONTROL Disable PowerDown"); + mac.write_cl22(MDIO_PHY_ADDR, RegsC22::CONTROL as u8, val) + .await + .unwrap(); + } + + // Config0 + let mut config0 = Config0(0x0000_0006); + config0.set_txfcsve(mac.append_fcs_on_tx); + mac.write_reg(sr::CONFIG0, config0.0).await.unwrap(); + + // Config2 + let mut config2 = Config2(0x0000_0800); + // crc_append must be disable if tx_fcs_validation_enable is true! + config2.set_crc_append(!mac.append_fcs_on_tx); + mac.write_reg(sr::CONFIG2, config2.0).await.unwrap(); + + // Pin Mux Config 1 + let led_val = (0b11 << 6) | (0b11 << 4); // | (0b00 << 1); + mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::DIGIO_PINMUX.into(), led_val) + .await + .unwrap(); + + let mut led_pol = LedPolarity(0); + led_pol.set_led1_polarity(LedPol::ActiveLow); + led_pol.set_led0_polarity(LedPol::ActiveLow); + + // Led Polarity Regisgere Active Low + mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::LED_POLARITY.into(), led_pol.0) + .await + .unwrap(); + + // Led Both On + let mut led_cntr = LedCntrl(0x0); + + // LED1: Yellow + led_cntr.set_led1_en(true); + led_cntr.set_led1_function(LedFunc::TxLevel2P4); + // LED0: Green + led_cntr.set_led0_en(true); + led_cntr.set_led0_function(LedFunc::LinkupTxRxActicity); + + mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::LED_CNTRL.into(), led_cntr.0) + .await + .unwrap(); + + // Set ADIN1110 Interrupts, RX_READY and LINK_CHANGE + // Enable interrupts LINK_CHANGE, TX_RDY, RX_RDY(P1), SPI_ERR + // Have to clear the mask the enable it. + let mut imask0_val = IMask0(0x0000_1FBF); + imask0_val.set_txfcsem(false); + imask0_val.set_phyintm(false); + imask0_val.set_txboem(false); + imask0_val.set_rxboem(false); + imask0_val.set_txpem(false); + + mac.write_reg(sr::IMASK0, imask0_val.0).await.unwrap(); + + // Set ADIN1110 Interrupts, RX_READY and LINK_CHANGE + // Enable interrupts LINK_CHANGE, TX_RDY, RX_RDY(P1), SPI_ERR + // Have to clear the mask the enable it. + let mut imask1_val = IMask1(0x43FA_1F1A); + imask1_val.set_link_change_mask(false); + imask1_val.set_p1_rx_rdy_mask(false); + imask1_val.set_spi_err_mask(false); + imask1_val.set_tx_ecc_err_mask(false); + imask1_val.set_rx_ecc_err_mask(false); + + mac.write_reg(sr::IMASK1, imask1_val.0).await.unwrap(); + + // Program mac address but also sets mac filters. + mac.set_mac_addr(&mac_addr).await.unwrap(); + + let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr)); + ( + device, + Runner { + ch: runner, + mac, + int, + is_link_up: false, + _reset: reset, + }, + ) +} + +#[allow(clippy::similar_names)] +#[cfg(test)] +mod tests { + use core::convert::Infallible; + + use embedded_hal_1::digital::{ErrorType, OutputPin}; + use embedded_hal_async::delay::DelayNs; + use embedded_hal_bus::spi::ExclusiveDevice; + use embedded_hal_mock::common::Generic; + use embedded_hal_mock::eh1::spi::{Mock as SpiMock, Transaction as SpiTransaction}; + + #[derive(Debug, Default)] + struct CsPinMock { + pub high: u32, + pub low: u32, + } + impl OutputPin for CsPinMock { + fn set_low(&mut self) -> Result<(), Self::Error> { + self.low += 1; + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.high += 1; + Ok(()) + } + } + impl ErrorType for CsPinMock { + type Error = Infallible; + } + + use super::*; + + // TODO: This is currently a workaround unit `ExclusiveDevice` is moved to `embedded-hal-bus` + // see https://github.com/rust-embedded/embedded-hal/pull/462#issuecomment-1560014426 + struct MockDelay {} + + impl DelayNs for MockDelay { + async fn delay_ns(&mut self, _ns: u32) { + todo!() + } + + async fn delay_us(&mut self, _us: u32) { + todo!() + } + + async fn delay_ms(&mut self, _ms: u32) { + todo!() + } + } + + struct TestHarnass { + spe: ADIN1110>, CsPinMock, MockDelay>>, + spi: Generic>, + } + + impl TestHarnass { + pub fn new(expectations: &[SpiTransaction], spi_crc: bool, append_fcs_on_tx: bool) -> Self { + let cs = CsPinMock::default(); + let delay = MockDelay {}; + let spi = SpiMock::new(expectations); + let spi_dev: ExclusiveDevice>, CsPinMock, MockDelay> = + ExclusiveDevice::new(spi.clone(), cs, delay); + let spe: ADIN1110< + ExclusiveDevice>, CsPinMock, MockDelay>, + > = ADIN1110::new(spi_dev, spi_crc, append_fcs_on_tx); + + Self { spe, spi } + } + + pub fn done(&mut self) { + self.spi.done(); + } + } + + #[futures_test::test] + async fn mac_read_registers_without_crc() { + // Configure expectations + let expectations = [ + // 1st + SpiTransaction::write_vec(vec![0x80, 0x01, TURN_AROUND_BYTE]), + SpiTransaction::read_vec(vec![0x02, 0x83, 0xBC, 0x91]), + SpiTransaction::flush(), + // 2nd + SpiTransaction::write_vec(vec![0x80, 0x02, TURN_AROUND_BYTE]), + SpiTransaction::read_vec(vec![0x00, 0x00, 0x06, 0xC3]), + SpiTransaction::flush(), + ]; + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, false, true); + + // Read PHIID + let val = th.spe.read_reg(sr::PHYID).await.expect("Error"); + assert_eq!(val, 0x0283_BC91); + + // Read CAPAVILITY + let val = th.spe.read_reg(sr::CAPABILITY).await.expect("Error"); + assert_eq!(val, 0x0000_06C3); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn mac_read_registers_with_crc() { + // Configure expectations + let expectations = [ + // 1st + SpiTransaction::write_vec(vec![0x80, 0x01, 177, TURN_AROUND_BYTE]), + SpiTransaction::read_vec(vec![0x02, 0x83, 0xBC, 0x91, 215]), + SpiTransaction::flush(), + // 2nd + SpiTransaction::write_vec(vec![0x80, 0x02, 184, TURN_AROUND_BYTE]), + SpiTransaction::read_vec(vec![0x00, 0x00, 0x06, 0xC3, 57]), + SpiTransaction::flush(), + ]; + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, true, true); + + assert_eq!(crc8(0x0283_BC91_u32.to_be_bytes().as_slice()), 215); + assert_eq!(crc8(0x0000_06C3_u32.to_be_bytes().as_slice()), 57); + + // Read PHIID + let val = th.spe.read_reg(sr::PHYID).await.expect("Error"); + assert_eq!(val, 0x0283_BC91); + + // Read CAPAVILITY + let val = th.spe.read_reg(sr::CAPABILITY).await.expect("Error"); + assert_eq!(val, 0x0000_06C3); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn mac_write_registers_without_crc() { + // Configure expectations + let expectations = [ + SpiTransaction::write_vec(vec![0xA0, 0x09, 0x12, 0x34, 0x56, 0x78]), + SpiTransaction::flush(), + ]; + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, false, true); + + // Write reg: 0x1FFF + assert!(th.spe.write_reg(sr::STATUS1, 0x1234_5678).await.is_ok()); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn mac_write_registers_with_crc() { + // Configure expectations + let expectations = [ + SpiTransaction::write_vec(vec![0xA0, 0x09, 39, 0x12, 0x34, 0x56, 0x78, 28]), + SpiTransaction::flush(), + ]; + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, true, true); + + // Write reg: 0x1FFF + assert!(th.spe.write_reg(sr::STATUS1, 0x1234_5678).await.is_ok()); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn write_packet_to_fifo_minimal_with_crc() { + // Configure expectations + let mut expectations = vec![]; + + // Write TX_SIZE reg + expectations.push(SpiTransaction::write_vec(vec![160, 48, 136, 0, 0, 0, 66, 201])); + expectations.push(SpiTransaction::flush()); + + // Write TX reg. + // SPI Header + optional CRC + Frame Header + expectations.push(SpiTransaction::write_vec(vec![160, 49, 143, 0, 0])); + // Packet data + let packet = [0xFF_u8; 60]; + expectations.push(SpiTransaction::write_vec(packet.to_vec())); + + let mut tail = std::vec::Vec::::with_capacity(100); + // Padding + if let Some(padding_len) = (ETH_MIN_LEN - FCS_LEN).checked_sub(packet.len()) { + tail.resize(padding_len, 0x00); + } + // Packet FCS + optinal padding + tail.extend_from_slice(&[77, 241, 140, 244, DONT_CARE_BYTE, DONT_CARE_BYTE]); + + expectations.push(SpiTransaction::write_vec(tail)); + expectations.push(SpiTransaction::flush()); + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, true, true); + + assert!(th.spe.write_fifo(&packet).await.is_ok()); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn write_packet_to_fifo_minimal_with_crc_without_fcs() { + // Configure expectations + let mut expectations = vec![]; + + // Write TX_SIZE reg + expectations.push(SpiTransaction::write_vec(vec![160, 48, 136, 0, 0, 0, 62, 186])); + expectations.push(SpiTransaction::flush()); + + // Write TX reg. + // SPI Header + optional CRC + Frame Header + expectations.push(SpiTransaction::write_vec(vec![160, 49, 143, 0, 0])); + // Packet data + let packet = [0xFF_u8; 60]; + expectations.push(SpiTransaction::write_vec(packet.to_vec())); + + let mut tail = std::vec::Vec::::with_capacity(100); + // Padding + if let Some(padding_len) = (ETH_MIN_LEN - FCS_LEN).checked_sub(packet.len()) { + tail.resize(padding_len, 0x00); + } + // Packet FCS + optinal padding + tail.extend_from_slice(&[DONT_CARE_BYTE, DONT_CARE_BYTE]); + + expectations.push(SpiTransaction::write_vec(tail)); + expectations.push(SpiTransaction::flush()); + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, true, false); + + assert!(th.spe.write_fifo(&packet).await.is_ok()); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn write_packet_to_fifo_max_mtu_with_crc() { + assert_eq!(MTU, 1514); + // Configure expectations + let mut expectations = vec![]; + + // Write TX_SIZE reg + expectations.push(SpiTransaction::write_vec(vec![160, 48, 136, 0, 0, 5, 240, 159])); + expectations.push(SpiTransaction::flush()); + + // Write TX reg. + // SPI Header + optional CRC + Frame Header + expectations.push(SpiTransaction::write_vec(vec![160, 49, 143, 0, 0])); + // Packet data + let packet = [0xAA_u8; MTU]; + expectations.push(SpiTransaction::write_vec(packet.to_vec())); + + let mut tail = std::vec::Vec::::with_capacity(100); + // Padding + if let Some(padding_len) = (ETH_MIN_LEN - FCS_LEN).checked_sub(packet.len()) { + tail.resize(padding_len, 0x00); + } + // Packet FCS + optinal padding + tail.extend_from_slice(&[49, 196, 205, 160]); + + expectations.push(SpiTransaction::write_vec(tail)); + expectations.push(SpiTransaction::flush()); + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, true, true); + + assert!(th.spe.write_fifo(&packet).await.is_ok()); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn write_packet_to_fifo_invalid_lengths() { + assert_eq!(MTU, 1514); + + // Configure expectations + let expectations = vec![]; + + // Max packet size = MAX_BUFF - FRAME_HEADER_LEN + let packet = [0xAA_u8; MAX_BUFF - FRAME_HEADER_LEN + 1]; + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, true, true); + + // minimal + assert!(matches!( + th.spe.write_fifo(&packet[0..(6 + 6 + 2 - 1)]).await, + Err(AdinError::PACKET_TOO_SMALL) + )); + + // max + 1 + assert!(matches!( + th.spe.write_fifo(&packet).await, + Err(AdinError::PACKET_TOO_BIG) + )); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn write_packet_to_fifo_arp_46bytes_with_crc() { + // Configure expectations + let mut expectations = vec![]; + + // Write TX_SIZE reg + expectations.push(SpiTransaction::write_vec(vec![160, 48, 136, 0, 0, 0, 66, 201])); + expectations.push(SpiTransaction::flush()); + + // Write TX reg. + // Header + expectations.push(SpiTransaction::write_vec(vec![160, 49, 143, 0, 0])); + // Packet data + let packet = [ + 34, 51, 68, 85, 102, 119, 18, 52, 86, 120, 154, 188, 8, 6, 0, 1, 8, 0, 6, 4, 0, 2, 18, 52, 86, 120, 154, + 188, 192, 168, 16, 4, 34, 51, 68, 85, 102, 119, 192, 168, 16, 1, + ]; + expectations.push(SpiTransaction::write_vec(packet.to_vec())); + + let mut tail = std::vec::Vec::::with_capacity(100); + // Padding + if let Some(padding_len) = (ETH_MIN_LEN - FCS_LEN).checked_sub(packet.len()) { + tail.resize(padding_len, 0x00); + } + // Packet FCS + optinal padding + tail.extend_from_slice(&[147, 149, 213, 68, DONT_CARE_BYTE, DONT_CARE_BYTE]); + + expectations.push(SpiTransaction::write_vec(tail)); + expectations.push(SpiTransaction::flush()); + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, true, true); + + assert!(th.spe.write_fifo(&packet).await.is_ok()); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn write_packet_to_fifo_arp_46bytes_without_crc() { + // Configure expectations + let mut expectations = vec![]; + + // Write TX_SIZE reg + expectations.push(SpiTransaction::write_vec(vec![160, 48, 0, 0, 0, 66])); + expectations.push(SpiTransaction::flush()); + + // Write TX reg. + // SPI Header + Frame Header + expectations.push(SpiTransaction::write_vec(vec![160, 49, 0, 0])); + // Packet data + let packet = [ + 34, 51, 68, 85, 102, 119, 18, 52, 86, 120, 154, 188, 8, 6, 0, 1, 8, 0, 6, 4, 0, 2, 18, 52, 86, 120, 154, + 188, 192, 168, 16, 4, 34, 51, 68, 85, 102, 119, 192, 168, 16, 1, + ]; + expectations.push(SpiTransaction::write_vec(packet.to_vec())); + + let mut tail = std::vec::Vec::::with_capacity(100); + // Padding + if let Some(padding_len) = (ETH_MIN_LEN - FCS_LEN).checked_sub(packet.len()) { + tail.resize(padding_len, 0x00); + } + // Packet FCS + optinal padding + tail.extend_from_slice(&[147, 149, 213, 68, DONT_CARE_BYTE, DONT_CARE_BYTE]); + + expectations.push(SpiTransaction::write_vec(tail)); + expectations.push(SpiTransaction::flush()); + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, false, true); + + assert!(th.spe.write_fifo(&packet).await.is_ok()); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn read_packet_from_fifo_packet_too_big_for_frame_buffer() { + // Configure expectations + let mut expectations = vec![]; + + // Read RX_SIZE reg + let rx_size: u32 = u32::try_from(ETH_MIN_LEN + FRAME_HEADER_LEN + FCS_LEN).unwrap(); + let mut rx_size_vec = rx_size.to_be_bytes().to_vec(); + rx_size_vec.push(crc8(&rx_size_vec)); + + expectations.push(SpiTransaction::write_vec(vec![128, 144, 79, TURN_AROUND_BYTE])); + expectations.push(SpiTransaction::read_vec(rx_size_vec)); + expectations.push(SpiTransaction::flush()); + + let mut frame = [0; MTU]; + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, true, true); + + let ret = th.spe.read_fifo(&mut frame[0..ETH_MIN_LEN - 1]).await; + assert!(matches!(dbg!(ret), Err(AdinError::PACKET_TOO_BIG))); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn read_packet_from_fifo_packet_too_small() { + // Configure expectations + let mut expectations = vec![]; + + // This value is importen for this test! + assert_eq!(ETH_MIN_LEN, 64); + + // Packet data, size = `ETH_MIN_LEN` - `FCS_LEN` - 1 + let packet = [0; 64 - FCS_LEN - 1]; + + // Read RX_SIZE reg + let rx_size: u32 = u32::try_from(packet.len() + FRAME_HEADER_LEN + FCS_LEN).unwrap(); + let mut rx_size_vec = rx_size.to_be_bytes().to_vec(); + rx_size_vec.push(crc8(&rx_size_vec)); + + expectations.push(SpiTransaction::write_vec(vec![128, 144, 79, TURN_AROUND_BYTE])); + expectations.push(SpiTransaction::read_vec(rx_size_vec)); + expectations.push(SpiTransaction::flush()); + + let mut frame = [0; MTU]; + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, true, true); + + let ret = th.spe.read_fifo(&mut frame).await; + assert!(matches!(dbg!(ret), Err(AdinError::PACKET_TOO_SMALL))); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn read_packet_from_fifo_packet_corrupted_fcs() { + let mut frame = [0; MTU]; + // Configure expectations + let mut expectations = vec![]; + + let packet = [0xDE; 60]; + let crc_en = true; + + // Read RX_SIZE reg + let rx_size: u32 = u32::try_from(packet.len() + FRAME_HEADER_LEN + FCS_LEN).unwrap(); + let mut rx_size_vec = rx_size.to_be_bytes().to_vec(); + if crc_en { + rx_size_vec.push(crc8(&rx_size_vec)); + } + + // SPI Header with CRC + let mut rx_fsize = vec![128, 144, 79, TURN_AROUND_BYTE]; + if !crc_en { + // remove the CRC on idx 2 + rx_fsize.swap_remove(2); + } + expectations.push(SpiTransaction::write_vec(rx_fsize)); + expectations.push(SpiTransaction::read_vec(rx_size_vec)); + expectations.push(SpiTransaction::flush()); + + // Read RX reg, SPI Header with CRC + let mut rx_reg = vec![128, 145, 72, TURN_AROUND_BYTE]; + if !crc_en { + // remove the CRC on idx 2 + rx_reg.swap_remove(2); + } + expectations.push(SpiTransaction::write_vec(rx_reg)); + // Frame Header + expectations.push(SpiTransaction::read_vec(vec![0, 0])); + // Packet data + expectations.push(SpiTransaction::read_vec(packet.to_vec())); + + let packet_crc = ETH_FCS::new(&packet); + + let mut tail = std::vec::Vec::::with_capacity(100); + + tail.extend_from_slice(&packet_crc.hton_bytes()); + // increase last byte with 1. + if let Some(crc) = tail.last_mut() { + *crc = crc.wrapping_add(1); + } + + // Need extra bytes? + let pad = (packet.len() + FCS_LEN + FRAME_HEADER_LEN) & 0x03; + if pad != 0 { + // Packet FCS + optinal padding + tail.resize(tail.len() + pad, DONT_CARE_BYTE); + } + + expectations.push(SpiTransaction::read_vec(tail)); + expectations.push(SpiTransaction::flush()); + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, crc_en, false); + + let ret = th.spe.read_fifo(&mut frame).await.expect_err("Error!"); + assert!(matches!(ret, AdinError::FCS)); + + // Mark end of the SPI test. + th.done(); + } + + #[futures_test::test] + async fn read_packet_to_fifo_check_spi_read_multipule_of_u32_valid_lengths() { + let packet_buffer = [0; MTU]; + let mut frame = [0; MTU]; + let mut expectations = std::vec::Vec::with_capacity(16); + + // Packet data, size = `ETH_MIN_LEN` - `FCS_LEN` + for packet_size in [60, 61, 62, 63, 64, MTU - 4, MTU - 3, MTU - 2, MTU - 1, MTU] { + for crc_en in [false, true] { + expectations.clear(); + + let packet = &packet_buffer[0..packet_size]; + + // Read RX_SIZE reg + let rx_size: u32 = u32::try_from(packet.len() + FRAME_HEADER_LEN + FCS_LEN).unwrap(); + let mut rx_size_vec = rx_size.to_be_bytes().to_vec(); + if crc_en { + rx_size_vec.push(crc8(&rx_size_vec)); + } + + // SPI Header with CRC + let mut rx_fsize = vec![128, 144, 79, TURN_AROUND_BYTE]; + if !crc_en { + // remove the CRC on idx 2 + rx_fsize.swap_remove(2); + } + expectations.push(SpiTransaction::write_vec(rx_fsize)); + expectations.push(SpiTransaction::read_vec(rx_size_vec)); + expectations.push(SpiTransaction::flush()); + + // Read RX reg, SPI Header with CRC + let mut rx_reg = vec![128, 145, 72, TURN_AROUND_BYTE]; + if !crc_en { + // remove the CRC on idx 2 + rx_reg.swap_remove(2); + } + expectations.push(SpiTransaction::write_vec(rx_reg)); + // Frame Header + expectations.push(SpiTransaction::read_vec(vec![0, 0])); + // Packet data + expectations.push(SpiTransaction::read_vec(packet.to_vec())); + + let packet_crc = ETH_FCS::new(packet); + + let mut tail = std::vec::Vec::::with_capacity(100); + + tail.extend_from_slice(&packet_crc.hton_bytes()); + + // Need extra bytes? + let pad = (packet_size + FCS_LEN + FRAME_HEADER_LEN) & 0x03; + if pad != 0 { + // Packet FCS + optinal padding + tail.resize(tail.len() + pad, DONT_CARE_BYTE); + } + + expectations.push(SpiTransaction::read_vec(tail)); + expectations.push(SpiTransaction::flush()); + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, crc_en, false); + + let ret = th.spe.read_fifo(&mut frame).await.expect("Error!"); + assert_eq!(ret, packet_size); + + // Mark end of the SPI test. + th.done(); + } + } + } + + #[futures_test::test] + async fn spi_crc_error() { + // Configure expectations + let expectations = vec![ + SpiTransaction::write_vec(vec![128, 144, 79, TURN_AROUND_BYTE]), + SpiTransaction::read_vec(vec![0x00, 0x00, 0x00, 0x00, 0xDD]), + SpiTransaction::flush(), + ]; + + // Create TestHarnass + let mut th = TestHarnass::new(&expectations, true, false); + + let ret = th.spe.read_reg(sr::RX_FSIZE).await; + assert!(matches!(dbg!(ret), Err(AdinError::SPI_CRC))); + + // Mark end of the SPI test. + th.done(); + } +} diff --git a/embassy/embassy-net-adin1110/src/mdio.rs b/embassy/embassy-net-adin1110/src/mdio.rs new file mode 100644 index 0000000..6fea937 --- /dev/null +++ b/embassy/embassy-net-adin1110/src/mdio.rs @@ -0,0 +1,177 @@ +/// PHY Address: (0..=0x1F), 5-bits long. +#[allow(dead_code)] +type PhyAddr = u8; + +/// PHY Register: (0..=0x1F), 5-bits long. +#[allow(dead_code)] +type RegC22 = u8; + +/// PHY Register Clause 45. +#[allow(dead_code)] +type RegC45 = u16; + +/// PHY Register Value +#[allow(dead_code)] +type RegVal = u16; + +#[allow(dead_code)] +const REG13: RegC22 = 13; +#[allow(dead_code)] +const REG14: RegC22 = 14; + +#[allow(dead_code)] +const PHYADDR_MASK: u8 = 0x1f; +#[allow(dead_code)] +const DEV_MASK: u8 = 0x1f; + +#[allow(dead_code)] +#[repr(u16)] +enum Reg13Op { + Addr = 0b00 << 14, + Write = 0b01 << 14, + PostReadIncAddr = 0b10 << 14, + Read = 0b11 << 14, +} + +/// `MdioBus` trait +/// Driver needs to implement the Clause 22 +/// Optional Clause 45 is the device supports this. +/// +/// Clause 45 methodes are bases on +pub trait MdioBus { + /// Error type. + type Error; + + /// Read, Clause 22 + async fn read_cl22(&mut self, phy_id: PhyAddr, reg: RegC22) -> Result; + + /// Write, Clause 22 + async fn write_cl22(&mut self, phy_id: PhyAddr, reg: RegC22, reg_val: RegVal) -> Result<(), Self::Error>; + + /// Read, Clause 45 + /// This is the default implementation. + /// Many hardware these days support direct Clause 45 operations. + /// Implement this function when your hardware supports it. + async fn read_cl45(&mut self, phy_id: PhyAddr, regc45: (u8, RegC45)) -> Result { + // Write FN + let val = (Reg13Op::Addr as RegVal) | RegVal::from(regc45.0 & DEV_MASK); + + self.write_cl22(phy_id, REG13, val).await?; + // Write Addr + self.write_cl22(phy_id, REG14, regc45.1).await?; + + // Write FN + let val = (Reg13Op::Read as RegVal) | RegVal::from(regc45.0 & DEV_MASK); + self.write_cl22(phy_id, REG13, val).await?; + // Write Addr + self.read_cl22(phy_id, REG14).await + } + + /// Write, Clause 45 + /// This is the default implementation. + /// Many hardware these days support direct Clause 45 operations. + /// Implement this function when your hardware supports it. + async fn write_cl45(&mut self, phy_id: PhyAddr, regc45: (u8, RegC45), reg_val: RegVal) -> Result<(), Self::Error> { + let dev_addr = RegVal::from(regc45.0 & DEV_MASK); + let reg = regc45.1; + + // Write FN + let val = (Reg13Op::Addr as RegVal) | dev_addr; + self.write_cl22(phy_id, REG13, val).await?; + // Write Addr + self.write_cl22(phy_id, REG14, reg).await?; + + // Write FN + let val = (Reg13Op::Write as RegVal) | dev_addr; + self.write_cl22(phy_id, REG13, val).await?; + // Write Addr + self.write_cl22(phy_id, REG14, reg_val).await + } +} + +#[cfg(test)] +mod tests { + use core::convert::Infallible; + + use super::{MdioBus, PhyAddr, RegC22, RegVal}; + + #[derive(Debug, PartialEq, Eq)] + enum A { + Read(PhyAddr, RegC22), + Write(PhyAddr, RegC22, RegVal), + } + + struct MockMdioBus(Vec); + + impl MockMdioBus { + pub fn clear(&mut self) { + self.0.clear(); + } + } + + impl MdioBus for MockMdioBus { + type Error = Infallible; + + async fn write_cl22( + &mut self, + phy_id: super::PhyAddr, + reg: super::RegC22, + reg_val: super::RegVal, + ) -> Result<(), Self::Error> { + self.0.push(A::Write(phy_id, reg, reg_val)); + Ok(()) + } + + async fn read_cl22( + &mut self, + phy_id: super::PhyAddr, + reg: super::RegC22, + ) -> Result { + self.0.push(A::Read(phy_id, reg)); + Ok(0) + } + } + + #[futures_test::test] + async fn read_test() { + let mut mdiobus = MockMdioBus(Vec::with_capacity(20)); + + mdiobus.clear(); + mdiobus.read_cl22(0x01, 0x00).await.unwrap(); + assert_eq!(mdiobus.0, vec![A::Read(0x01, 0x00)]); + + mdiobus.clear(); + mdiobus.read_cl45(0x01, (0xBB, 0x1234)).await.unwrap(); + assert_eq!( + mdiobus.0, + vec![ + #[allow(clippy::identity_op)] + A::Write(0x01, 13, (0b00 << 14) | 27), + A::Write(0x01, 14, 0x1234), + A::Write(0x01, 13, (0b11 << 14) | 27), + A::Read(0x01, 14) + ] + ); + } + + #[futures_test::test] + async fn write_test() { + let mut mdiobus = MockMdioBus(Vec::with_capacity(20)); + + mdiobus.clear(); + mdiobus.write_cl22(0x01, 0x00, 0xABCD).await.unwrap(); + assert_eq!(mdiobus.0, vec![A::Write(0x01, 0x00, 0xABCD)]); + + mdiobus.clear(); + mdiobus.write_cl45(0x01, (0xBB, 0x1234), 0xABCD).await.unwrap(); + assert_eq!( + mdiobus.0, + vec![ + A::Write(0x01, 13, 27), + A::Write(0x01, 14, 0x1234), + A::Write(0x01, 13, (0b01 << 14) | 27), + A::Write(0x01, 14, 0xABCD) + ] + ); + } +} diff --git a/embassy/embassy-net-adin1110/src/phy.rs b/embassy/embassy-net-adin1110/src/phy.rs new file mode 100644 index 0000000..d54d843 --- /dev/null +++ b/embassy/embassy-net-adin1110/src/phy.rs @@ -0,0 +1,143 @@ +use crate::mdio::MdioBus; + +#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)] +#[repr(u8)] +/// Clause 22 Registers +pub enum RegsC22 { + /// MII Control Register + CONTROL = 0x00, + /// MII Status Register + STATUS = 0x01, + /// PHY Identifier 1 Register + PHY_ID1 = 0x02, + /// PHY Identifier 2 Register. + PHY_ID2 = 0x03, +} + +/// Clause 45 Registers +#[allow(non_snake_case, dead_code)] +pub mod RegsC45 { + /// Device Address: 0x01 + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[repr(u16)] + pub enum DA1 { + /// PMA/PMD Control 1 Register + PMA_PMD_CNTRL1 = 0x0000, + /// PMA/PMD Status 1 Register + PMA_PMD_STAT1 = 0x0001, + /// MSE Value Register + MSE_VAL = 0x830B, + } + + impl DA1 { + #[must_use] + pub fn into(self) -> (u8, u16) { + (0x01, self as u16) + } + } + + /// Device Address: 0x03 + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[repr(u16)] + pub enum DA3 { + /// PCS Control 1 Register + PCS_CNTRL1 = 0x0000, + /// PCS Status 1 Register + PCS_STAT1 = 0x0001, + /// PCS Status 2 Register + PCS_STAT2 = 0x0008, + } + + impl DA3 { + #[must_use] + pub fn into(self) -> (u8, u16) { + (0x03, self as u16) + } + } + + /// Device Address: 0x07 + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[repr(u16)] + pub enum DA7 { + /// Extra Autonegotiation Status Register + AN_STATUS_EXTRA = 0x8001, + } + + impl DA7 { + #[must_use] + pub fn into(self) -> (u8, u16) { + (0x07, self as u16) + } + } + + /// Device Address: 0x1E + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[repr(u16)] + pub enum DA1E { + /// System Interrupt Status Register + CRSM_IRQ_STATUS = 0x0010, + /// System Interrupt Mask Register + CRSM_IRQ_MASK = 0x0020, + /// Pin Mux Configuration 1 Register + DIGIO_PINMUX = 0x8c56, + /// LED Control Register. + LED_CNTRL = 0x8C82, + /// LED Polarity Register + LED_POLARITY = 0x8C83, + } + + impl DA1E { + #[must_use] + pub fn into(self) -> (u8, u16) { + (0x1e, self as u16) + } + } + + /// Device Address: 0x1F + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[repr(u16)] + pub enum DA1F { + /// PHY Subsystem Interrupt Status Register + PHY_SYBSYS_IRQ_STATUS = 0x0011, + /// PHY Subsystem Interrupt Mask Register + PHY_SYBSYS_IRQ_MASK = 0x0021, + } + + impl DA1F { + #[must_use] + pub fn into(self) -> (u8, u16) { + (0x1f, self as u16) + } + } +} + +/// 10-BASE-T1x PHY functions. +pub struct Phy10BaseT1x(u8); + +impl Default for Phy10BaseT1x { + fn default() -> Self { + Self(0x01) + } +} + +impl Phy10BaseT1x { + /// Get the both parts of the PHYID. + pub async fn get_id(&self, mdiobus: &mut MDIOBUS) -> Result + where + MDIOBUS: MdioBus, + MDE: core::fmt::Debug, + { + let mut phyid = u32::from(mdiobus.read_cl22(self.0, RegsC22::PHY_ID1 as u8).await?) << 16; + phyid |= u32::from(mdiobus.read_cl22(self.0, RegsC22::PHY_ID2 as u8).await?); + Ok(phyid) + } + + /// Get the Mean Squared Error Value. + pub async fn get_sqi(&self, mdiobus: &mut MDIOBUS) -> Result + where + MDIOBUS: MdioBus, + MDE: core::fmt::Debug, + { + mdiobus.read_cl45(self.0, RegsC45::DA1::MSE_VAL.into()).await + } +} diff --git a/embassy/embassy-net-adin1110/src/regs.rs b/embassy/embassy-net-adin1110/src/regs.rs new file mode 100644 index 0000000..8780c2b --- /dev/null +++ b/embassy/embassy-net-adin1110/src/regs.rs @@ -0,0 +1,417 @@ +use core::fmt::{Debug, Display}; + +use bitfield::{bitfield, bitfield_bitrange, bitfield_fields}; + +#[allow(missing_docs)] +#[allow(non_camel_case_types)] +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u16)] +/// SPI REGISTER DETAILS +/// Table 38. +pub enum SpiRegisters { + IDVER = 0x00, + PHYID = 0x01, + CAPABILITY = 0x02, + RESET = 0x03, + CONFIG0 = 0x04, + CONFIG2 = 0x06, + STATUS0 = 0x08, + STATUS1 = 0x09, + IMASK0 = 0x0C, + IMASK1 = 0x0D, + MDIO_ACC = 0x20, + TX_FSIZE = 0x30, + TX = 0x31, + TX_SPACE = 0x32, + FIFO_CLR = 0x36, + ADDR_FILT_UPR0 = 0x50, + ADDR_FILT_LWR0 = 0x51, + ADDR_FILT_UPR1 = 0x52, + ADDR_FILT_LWR1 = 0x53, + ADDR_MSK_LWR0 = 0x70, + ADDR_MSK_UPR0 = 0x71, + ADDR_MSK_LWR1 = 0x72, + ADDR_MSK_UPR1 = 0x73, + RX_FSIZE = 0x90, + RX = 0x91, +} + +impl Display for SpiRegisters { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{self:?}") + } +} + +impl From for u16 { + fn from(val: SpiRegisters) -> Self { + val as u16 + } +} + +impl From for SpiRegisters { + fn from(value: u16) -> Self { + match value { + 0x00 => Self::IDVER, + 0x01 => Self::PHYID, + 0x02 => Self::CAPABILITY, + 0x03 => Self::RESET, + 0x04 => Self::CONFIG0, + 0x06 => Self::CONFIG2, + 0x08 => Self::STATUS0, + 0x09 => Self::STATUS1, + 0x0C => Self::IMASK0, + 0x0D => Self::IMASK1, + 0x20 => Self::MDIO_ACC, + 0x30 => Self::TX_FSIZE, + 0x31 => Self::TX, + 0x32 => Self::TX_SPACE, + 0x36 => Self::FIFO_CLR, + 0x50 => Self::ADDR_FILT_UPR0, + 0x51 => Self::ADDR_FILT_LWR0, + 0x52 => Self::ADDR_FILT_UPR1, + 0x53 => Self::ADDR_FILT_LWR1, + 0x70 => Self::ADDR_MSK_LWR0, + 0x71 => Self::ADDR_MSK_UPR0, + 0x72 => Self::ADDR_MSK_LWR1, + 0x73 => Self::ADDR_MSK_UPR1, + 0x90 => Self::RX_FSIZE, + 0x91 => Self::RX, + e => panic!("Unknown value {}", e), + } + } +} + +// Register definitions +bitfield! { + /// Status0 Register bits + pub struct Status0(u32); + impl Debug; + u32; + /// Control Data Protection Error + pub cdpe, _ : 12; + /// Transmit Frame Check Squence Error + pub txfcse, _: 11; + /// Transmit Time Stamp Capture Available C + pub ttscac, _ : 10; + /// Transmit Time Stamp Capture Available B + pub ttscab, _ : 9; + /// Transmit Time Stamp Capture Available A + pub ttscaa, _ : 8; + /// PHY Interrupt for Port 1 + pub phyint, _ : 7; + /// Reset Complete + pub resetc, _ : 6; + /// Header error + pub hdre, _ : 5; + /// Loss of Frame Error + pub lofe, _ : 4; + /// Receiver Buffer Overflow Error + pub rxboe, _ : 3; + /// Host Tx FIFO Under Run Error + pub txbue, _ : 2; + /// Host Tx FIFO Overflow + pub txboe, _ : 1; + /// Transmit Protocol Error + pub txpe, _ : 0; +} + +bitfield! { + /// Status1 Register bits + pub struct Status1(u32); + impl Debug; + u32; + /// ECC Error on Reading the Frame Size from a Tx FIFO + pub tx_ecc_err, set_tx_ecc_err: 12; + /// ECC Error on Reading the Frame Size from an Rx FIFO + pub rx_ecc_err, set_rx_ecc_err : 11; + /// Detected an Error on an SPI Transaction + pub spi_err, set_spi_err: 10; + /// Rx MAC Interframe Gap Error + pub p1_rx_ifg_err, set_p1_rx_ifg_err : 8; + /// Port1 Rx Ready High Priority + pub p1_rx_rdy_hi, set_p1_rx_rdy_hi : 5; + /// Port 1 Rx FIFO Contains Data + pub p1_rx_rdy, set_p1_rx_rdy : 4; + /// Tx Ready + pub tx_rdy, set_tx_rdy : 3; + /// Link Status Changed + pub link_change, set_link_change : 1; + /// Port 1 Link Status + pub p1_link_status, _ : 0; +} + +bitfield! { + /// Config0 Register bits + pub struct Config0(u32); + impl Debug; + u32; + /// Configuration Synchronization + pub sync, set_sync : 15; + /// Transmit Frame Check Sequence Validation Enable + pub txfcsve, set_txfcsve : 14; + /// !CS Align Receive Frame Enable + pub csarfe, set_csarfe : 13; + /// Zero Align Receive Frame Enable + pub zarfe, set_zarfe : 12; + /// Transmit Credit Threshold + pub tcxthresh, set_tcxthresh : 11, 10; + /// Transmit Cut Through Enable + pub txcte, set_txcte : 9; + /// Receive Cut Through Enable + pub rxcte, set_rxcte : 8; + /// Frame Time Stamp Enable + pub ftse, set_ftse : 7; + /// Receive Frame Time Stamp Select + pub ftss, set_ftss : 6; + /// Enable Control Data Read Write Protection + pub prote, set_prote : 5; + /// Enable TX Data Chunk Sequence and Retry + pub seqe, set_seqe : 4; + /// Chunk Payload Selector (N). + pub cps, set_cps : 2, 0; +} + +bitfield! { + /// Config2 Register bits + pub struct Config2(u32); + impl Debug; + u32; + /// Assert TX_RDY When the Tx FIFO is Empty + pub tx_rdy_on_empty, set_tx_rdy_on_empty : 8; + /// Determines If the SFD is Detected in the PHY or MAC + pub sdf_detect_src, set_sdf_detect_src : 7; + /// Statistics Clear on Reading + pub stats_clr_on_rd, set_stats_clr_on_rd : 6; + /// Enable SPI CRC + pub crc_append, set_crc_append : 5; + /// Admit Frames with IFG Errors on Port 1 (P1) + pub p1_rcv_ifg_err_frm, set_p1_rcv_ifg_err_frm : 4; + /// Forward Frames Not Matching Any MAC Address to the Host + pub p1_fwd_unk2host, set_p1_fwd_unk2host : 2; + /// SPI to MDIO Bridge MDC Clock Speed + pub mspeed, set_mspeed : 0; +} + +bitfield! { + /// IMASK0 Register bits + pub struct IMask0(u32); + impl Debug; + u32; + /// Control Data Protection Error Mask + pub cppem, set_cppem : 12; + /// Transmit Frame Check Sequence Error Mask + pub txfcsem, set_txfcsem : 11; + /// Transmit Time Stamp Capture Available C Mask + pub ttscacm, set_ttscacm : 10; + /// Transmit Time Stamp Capture Available B Mask + pub ttscabm, set_ttscabm : 9; + /// Transmit Time Stamp Capture Available A Mask + pub ttscaam, set_ttscaam : 8; + /// Physical Layer Interrupt Mask + pub phyintm, set_phyintm : 7; + /// RESET Complete Mask + pub resetcm, set_resetcm : 6; + /// Header Error Mask + pub hdrem, set_hdrem : 5; + /// Loss of Frame Error Mask + pub lofem, set_lofem : 4; + /// Receive Buffer Overflow Error Mask + pub rxboem, set_rxboem : 3; + /// Transmit Buffer Underflow Error Mask + pub txbuem, set_txbuem : 2; + /// Transmit Buffer Overflow Error Mask + pub txboem, set_txboem : 1; + /// Transmit Protocol Error Mask + pub txpem, set_txpem : 0; +} + +bitfield! { + /// IMASK1 Register bits + pub struct IMask1(u32); + impl Debug; + u32; + /// Mask Bit for TXF_ECC_ERR + pub tx_ecc_err_mask, set_tx_ecc_err_mask : 12; + /// Mask Bit for RXF_ECC_ERR + pub rx_ecc_err_mask, set_rx_ecc_err_mask : 11; + /// Mask Bit for SPI_ERR + /// This field is only used with the generic SPI protocol + pub spi_err_mask, set_spi_err_mask : 10; + /// Mask Bit for RX_IFG_ERR + pub p1_rx_ifg_err_mask, set_p1_rx_ifg_err_mask : 8; + /// Mask Bit for P1_RX_RDY + /// This field is only used with the generic SPI protocol + pub p1_rx_rdy_mask, set_p1_rx_rdy_mask : 4; + /// Mask Bit for TX_FRM_DONE + /// This field is only used with the generic SPI protocol + pub tx_rdy_mask, set_tx_rdy_mask : 3; + /// Mask Bit for LINK_CHANGE + pub link_change_mask, set_link_change_mask : 1; +} + +/// LED Functions +#[repr(u8)] +pub enum LedFunc { + LinkupTxRxActicity = 0, + LinkupTxActicity, + LinkupRxActicity, + LinkupOnly, + TxRxActivity, + TxActivity, + RxActivity, + LinkupRxEr, + LinkupRxTxEr, + RxEr, + RxTxEr, + TxSop, + RxSop, + On, + Off, + Blink, + TxLevel2P4, + TxLevel1P0, + Master, + Slave, + IncompatiableLinkCfg, + AnLinkGood, + AnComplete, + TsTimer, + LocRcvrStatus, + RemRcvrStatus, + Clk25Ref, + TxTCLK, + Clk120MHz, +} + +impl From for u8 { + fn from(val: LedFunc) -> Self { + val as u8 + } +} + +impl From for LedFunc { + fn from(value: u8) -> Self { + match value { + 0 => LedFunc::LinkupTxRxActicity, + 1 => LedFunc::LinkupTxActicity, + 2 => LedFunc::LinkupRxActicity, + 3 => LedFunc::LinkupOnly, + 4 => LedFunc::TxRxActivity, + 5 => LedFunc::TxActivity, + 6 => LedFunc::RxActivity, + 7 => LedFunc::LinkupRxEr, + 8 => LedFunc::LinkupRxTxEr, + 9 => LedFunc::RxEr, + 10 => LedFunc::RxTxEr, + 11 => LedFunc::TxSop, + 12 => LedFunc::RxSop, + 13 => LedFunc::On, + 14 => LedFunc::Off, + 15 => LedFunc::Blink, + 16 => LedFunc::TxLevel2P4, + 17 => LedFunc::TxLevel1P0, + 18 => LedFunc::Master, + 19 => LedFunc::Slave, + 20 => LedFunc::IncompatiableLinkCfg, + 21 => LedFunc::AnLinkGood, + 22 => LedFunc::AnComplete, + 23 => LedFunc::TsTimer, + 24 => LedFunc::LocRcvrStatus, + 25 => LedFunc::RemRcvrStatus, + 26 => LedFunc::Clk25Ref, + 27 => LedFunc::TxTCLK, + 28 => LedFunc::Clk120MHz, + e => panic!("Invalid value {}", e), + } + } +} + +/// LED Control Register +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct LedCntrl(pub u16); +bitfield_bitrange! {struct LedCntrl(u16)} + +impl LedCntrl { + bitfield_fields! { + u8; + /// LED 0 Pin Function + pub from into LedFunc, led0_function, set_led0_function: 4, 0; + /// LED 0 Mode Selection + pub led0_mode, set_led0_mode: 5; + /// Qualify Certain LED 0 Options with Link Status. + pub led0_link_st_qualify, set_led0_link_st_qualify: 6; + /// LED 0 Enable + pub led0_en, set_led0_en: 7; + /// LED 1 Pin Function + pub from into LedFunc, led1_function, set_led1_function: 12, 8; + /// /// LED 1 Mode Selection + pub led1_mode, set_led1_mode: 13; + /// Qualify Certain LED 1 Options with Link Status. + pub led1_link_st_qualify, set_led1_link_st_qualify: 14; + /// LED 1 Enable + pub led1_en, set_led1_en: 15; + } + + pub fn new() -> Self { + LedCntrl(0) + } +} + +// LED Polarity +#[repr(u8)] +pub enum LedPol { + AutoSense = 0, + ActiveHigh, + ActiveLow, +} + +impl From for u8 { + fn from(val: LedPol) -> Self { + val as u8 + } +} + +impl From for LedPol { + fn from(value: u8) -> Self { + match value { + 0 => LedPol::AutoSense, + 1 => LedPol::ActiveHigh, + 2 => LedPol::ActiveLow, + e => panic!("Invalid value {}", e), + } + } +} + +/// LED Control Register +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct LedPolarity(pub u16); +bitfield_bitrange! {struct LedPolarity(u16)} + +impl LedPolarity { + bitfield_fields! { + u8; + /// LED 1 Polarity + pub from into LedPol, led1_polarity, set_led1_polarity: 3, 2; + /// LED 0 Polarity + pub from into LedPol, led0_polarity, set_led0_polarity: 1, 0; + } +} + +/// SPI Header +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct SpiHeader(pub u16); +bitfield_bitrange! {struct SpiHeader(u16)} + +impl SpiHeader { + bitfield_fields! { + u16; + /// Mask Bit for TXF_ECC_ERR + pub control, set_control : 15; + pub full_duplex, set_full_duplex : 14; + /// Read or Write to register + pub write, set_write : 13; + /// Registers ID/addr + pub from into SpiRegisters, addr, set_addr: 11, 0; + } +} diff --git a/embassy/embassy-net-driver-channel/CHANGELOG.md b/embassy/embassy-net-driver-channel/CHANGELOG.md new file mode 100644 index 0000000..d7af7e5 --- /dev/null +++ b/embassy/embassy-net-driver-channel/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.3.0 - 2024-08-05 + +- Add collapse_debuginfo to fmt.rs macros. +- Update embassy-sync version + +## 0.2.0 - 2023-10-18 + +- Update `embassy-net-driver` to v0.2 +- `Runner::new` now takes an `embassy_net_driver::HardwareAddress` parameter. +- `Runner::set_ethernet_address` is now `set_hardware_address`. + +## 0.1.0 - 2023-06-29 + +- First release diff --git a/embassy/embassy-net-driver-channel/Cargo.toml b/embassy/embassy-net-driver-channel/Cargo.toml new file mode 100644 index 0000000..cabc0c3 --- /dev/null +++ b/embassy/embassy-net-driver-channel/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "embassy-net-driver-channel" +version = "0.3.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "High-level channel-based driver for the `embassy-net` async TCP/IP network stack." +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-driver-channel" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-channel-v$VERSION/embassy-net-driver-channel/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver-channel/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } diff --git a/embassy/embassy-net-driver-channel/README.md b/embassy/embassy-net-driver-channel/README.md new file mode 100644 index 0000000..1955624 --- /dev/null +++ b/embassy/embassy-net-driver-channel/README.md @@ -0,0 +1,86 @@ +# embassy-net-driver-channel + +This crate provides a toolkit for implementing [`embassy-net`](https://crates.io/crates/embassy-net) drivers in a +higher level way than implementing the [`embassy-net-driver`](https://crates.io/crates/embassy-net-driver) trait directly. + +The `embassy-net-driver` trait is polling-based. To implement it, you must write the packet receive/transmit state machines by +hand, and hook up the `Waker`s provided by `embassy-net` to the right interrupt handlers so that `embassy-net` +knows when to poll your driver again to make more progress. + +With `embassy-net-driver-channel` you get a "channel-like" interface instead, where you can send/receive packets +to/from embassy-net. The intended usage is to spawn a "driver task" in the background that does this, passing +packets between the hardware and the channel. + +## A note about deadlocks + +When implementing a driver using this crate, it might be tempting to write it in the most straightforward way: + +```rust,ignore +loop { + // Wait for either.. + match select( + // ... the chip signaling an interrupt, indicating a packet is available to receive, or + irq_pin.wait_for_low(), + // ... a TX buffer becoming available, i.e. embassy-net wants to send a packet + tx_chan.tx_buf(), + ).await { + Either::First(_) => { + // a packet is ready to be received! + let buf = rx_chan.rx_buf().await; // allocate a rx buf from the packet queue + let n = receive_packet_over_spi(buf).await; + rx_chan.rx_done(n); + } + Either::Second(buf) => { + // a packet is ready to be sent! + send_packet_over_spi(buf).await; + tx_chan.tx_done(); + } + } +} +``` + +However, this code has a latent deadlock bug. The symptom is it can hang at `rx_chan.rx_buf().await` under load. + +The reason is that, under load, both the TX and RX queues can get full at the same time. When this happens, the `embassy-net` task stalls trying to send because the TX queue is full, therefore it stops processing packets in the RX queue. Your driver task also stalls because the RX queue is full, therefore it stops processing packets in the TX queue. + +The fix is to make sure to always service the TX queue while you're waiting for space to become available in the RX queue. For example, select on either "tx_chan.tx_buf() available" or "INT is low AND rx_chan.rx_buf() available": + +```rust,ignore +loop { + // Wait for either.. + match select( + async { + // ... the chip signaling an interrupt, indicating a packet is available to receive + irq_pin.wait_for_low().await; + // *AND* the buffer is ready... + rx_chan.rx_buf().await + }, + // ... or a TX buffer becoming available, i.e. embassy-net wants to send a packet + tx_chan.tx_buf(), + ).await { + Either::First(buf) => { + // a packet is ready to be received! + let n = receive_packet_over_spi(buf).await; + rx_chan.rx_done(n); + } + Either::Second(buf) => { + // a packet is ready to be sent! + send_packet_over_spi(buf).await; + tx_chan.tx_done(); + } + } +} +``` + +## Examples + +These `embassy-net` drivers are implemented using this crate. You can look at them for inspiration. + +- [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W +- [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support. +- [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips. +- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy/embassy-net-driver-channel/src/fmt.rs b/embassy/embassy-net-driver-channel/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-net-driver-channel/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-net-driver-channel/src/lib.rs b/embassy/embassy-net-driver-channel/src/lib.rs new file mode 100644 index 0000000..600efd9 --- /dev/null +++ b/embassy/embassy-net-driver-channel/src/lib.rs @@ -0,0 +1,410 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// 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}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::waitqueue::WakerRegistration; +use embassy_sync::zerocopy_channel; + +/// Channel state. +/// +/// Holds a buffer of packets with size MTU, for both TX and RX. +pub struct State { + rx: [PacketBuf; N_RX], + tx: [PacketBuf; N_TX], + inner: MaybeUninit>, +} + +impl State { + /// Create a new channel state. + pub const fn new() -> Self { + Self { + rx: [const { PacketBuf::new() }; N_RX], + tx: [const { PacketBuf::new() }; 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>, +} + +struct Shared { + link_state: LinkState, + waker: WakerRegistration, + hardware_address: driver::HardwareAddress, +} + +/// Channel runner. +/// +/// Holds the shared state and the lower end of channels for inbound and outbound packets. +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>, +} + +/// State runner. +/// +/// Holds the shared state of the channel such as link state. +#[derive(Clone, Copy)] +pub struct StateRunner<'d> { + shared: &'d Mutex>, +} + +/// RX runner. +/// +/// Holds the lower end of the channel for passing inbound packets up the stack. +pub struct RxRunner<'d, const MTU: usize> { + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, +} + +/// TX runner. +/// +/// Holds the lower end of the channel for passing outbound packets down the stack. +pub struct TxRunner<'d, const MTU: usize> { + tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, +} + +impl<'d, const MTU: usize> Runner<'d, MTU> { + /// Split the runner into separate runners for controlling state, rx and tx. + 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 }, + ) + } + + /// Split the runner into separate runners for controlling state, rx and tx borrowing the underlying state. + pub fn borrow_split(&mut self) -> (StateRunner<'_>, RxRunner<'_, MTU>, TxRunner<'_, MTU>) { + ( + StateRunner { shared: self.shared }, + RxRunner { + rx_chan: self.rx_chan.borrow(), + }, + TxRunner { + tx_chan: self.tx_chan.borrow(), + }, + ) + } + + /// Create a state runner sharing the state channel. + pub fn state_runner(&self) -> StateRunner<'d> { + StateRunner { shared: self.shared } + } + + /// Set the link state. + 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(); + }); + } + + /// Set the hardware address. + pub fn set_hardware_address(&mut self, address: driver::HardwareAddress) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.hardware_address = address; + s.waker.wake(); + }); + } + + /// Wait until there is space for more inbound packets and return a slice they can be copied into. + pub async fn rx_buf(&mut self) -> &mut [u8] { + let p = self.rx_chan.send().await; + &mut p.buf + } + + /// Check if there is space for more inbound packets right now. + pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.rx_chan.try_send()?; + Some(&mut p.buf) + } + + /// Polling the inbound channel if there is space for packets. + 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, + } + } + + /// Mark packet of len bytes as pushed to the inbound channel. + pub fn rx_done(&mut self, len: usize) { + let p = self.rx_chan.try_send().unwrap(); + p.len = len; + self.rx_chan.send_done(); + } + + /// Wait until there is space for more outbound packets and return a slice they can be copied into. + pub async fn tx_buf(&mut self) -> &mut [u8] { + let p = self.tx_chan.receive().await; + &mut p.buf[..p.len] + } + + /// Check if there is space for more outbound packets right now. + pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.tx_chan.try_receive()?; + Some(&mut p.buf[..p.len]) + } + + /// Polling the outbound channel if there is space for packets. + pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.tx_chan.poll_receive(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), + Poll::Pending => Poll::Pending, + } + } + + /// Mark outbound packet as copied. + pub fn tx_done(&mut self) { + self.tx_chan.receive_done(); + } +} + +impl<'d> StateRunner<'d> { + /// Set link state. + 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(); + }); + } + + /// Set the hardware address. + pub fn set_hardware_address(&self, address: driver::HardwareAddress) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.hardware_address = address; + s.waker.wake(); + }); + } +} + +impl<'d, const MTU: usize> RxRunner<'d, MTU> { + /// Wait until there is space for more inbound packets and return a slice they can be copied into. + pub async fn rx_buf(&mut self) -> &mut [u8] { + let p = self.rx_chan.send().await; + &mut p.buf + } + + /// Check if there is space for more inbound packets right now. + pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.rx_chan.try_send()?; + Some(&mut p.buf) + } + + /// Polling the inbound channel if there is space for packets. + 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, + } + } + + /// Mark packet of len bytes as pushed to the inbound channel. + 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> { + /// Wait until there is space for more outbound packets and return a slice they can be copied into. + pub async fn tx_buf(&mut self) -> &mut [u8] { + let p = self.tx_chan.receive().await; + &mut p.buf[..p.len] + } + + /// Check if there is space for more outbound packets right now. + pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.tx_chan.try_receive()?; + Some(&mut p.buf[..p.len]) + } + + /// Polling the outbound channel if there is space for packets. + pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.tx_chan.poll_receive(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), + Poll::Pending => Poll::Pending, + } + } + + /// Mark outbound packet as copied. + pub fn tx_done(&mut self) { + self.tx_chan.receive_done(); + } +} + +/// Create a channel. +/// +/// Returns a pair of handles for interfacing with the peripheral and the networking stack. +/// +/// The runner is interfacing with the peripheral at the lower part of the stack. +/// The device is interfacing with the networking stack on the layer above. +pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>( + state: &'d mut State, + hardware_address: driver::HardwareAddress, +) -> (Runner<'d, MTU>, Device<'d, MTU>) { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = MTU; + + // 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, + hardware_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, + }, + ) +} + +/// Represents a packet of size MTU. +pub struct PacketBuf { + len: usize, + buf: [u8; MTU], +} + +impl PacketBuf { + /// Create a new packet buffer. + pub const fn new() -> Self { + Self { len: 0, buf: [0; MTU] } + } +} + +/// Channel device. +/// +/// Holds the shared state and upper end of channels for inbound and outbound packets. +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_receive(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 hardware_address(&self) -> driver::HardwareAddress { + self.shared.lock(|s| s.borrow().hardware_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 + }) + } +} + +/// A rx token. +/// +/// Holds inbound receive channel and interfaces with embassy-net-driver. +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_receive()); + let r = f(&mut pkt.buf[..pkt.len]); + self.rx.receive_done(); + r + } +} + +/// A tx token. +/// +/// Holds outbound transmit channel and interfaces with embassy-net-driver. +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 + } +} diff --git a/embassy/embassy-net-driver/CHANGELOG.md b/embassy/embassy-net-driver/CHANGELOG.md new file mode 100644 index 0000000..165461e --- /dev/null +++ b/embassy/embassy-net-driver/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.2.0 - 2023-10-18 + +- Added support for IEEE 802.15.4 mediums. +- Added `Driver::hardware_address()`, `HardwareAddress`. +- Removed `Medium` enum. The medium is deduced out of the hardware address. +- Removed `Driver::ethernet_address()`. Replacement is `hardware_address()`. + +## 0.1.0 - 2023-06-29 + +- First release diff --git a/embassy/embassy-net-driver/Cargo.toml b/embassy/embassy-net-driver/Cargo.toml new file mode 100644 index 0000000..97e8a0d --- /dev/null +++ b/embassy/embassy-net-driver/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "embassy-net-driver" +version = "0.2.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Driver trait for the `embassy-net` async TCP/IP network stack." +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-driver" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[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" + +[package.metadata.docs.rs] +features = ["defmt"] + +[dependencies] +defmt = { version = "0.3", optional = true } diff --git a/embassy/embassy-net-driver/README.md b/embassy/embassy-net-driver/README.md new file mode 100644 index 0000000..24fcaaf --- /dev/null +++ b/embassy/embassy-net-driver/README.md @@ -0,0 +1,18 @@ +# embassy-net-driver + +This crate contains the driver trait necessary for adding [`embassy-net`](https://crates.io/crates/embassy-net) support +for a new hardware platform. + +If you want to *use* `embassy-net` with already made drivers, you should depend on the main `embassy-net` crate, not on this crate. + +If you are writing a driver, you should depend only on this crate, not on the main `embassy-net` crate. +This will allow your driver to continue working for newer `embassy-net` major versions, without needing an update, +if the driver trait has not had breaking changes. + +See also [`embassy-net-driver-channel`](https://crates.io/crates/embassy-net-driver-channel), which provides a higer-level API +to construct a driver that processes packets in its own background task and communicates with the `embassy-net` task via +packet queues for RX and TX. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy/embassy-net-driver/src/lib.rs b/embassy/embassy-net-driver/src/lib.rs new file mode 100644 index 0000000..4c84771 --- /dev/null +++ b/embassy/embassy-net-driver/src/lib.rs @@ -0,0 +1,219 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +use core::task::Context; + +/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum HardwareAddress { + /// Ethernet medium, with a A six-octet Ethernet address. + /// + /// 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([u8; 6]), + /// 6LoWPAN over IEEE802.15.4, with an eight-octet address. + Ieee802154([u8; 8]), + /// Indicates that a Driver is IP-native, and has no hardware address. + /// + /// 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, +} + +/// Main `embassy-net` driver API. +/// +/// This is essentially an interface for sending and receiving raw network frames. +/// +/// The interface is based on _tokens_, which are types that allow to receive/transmit a +/// single packet. The `receive` and `transmit` functions only construct such tokens, the +/// real sending/receiving operation are performed when the tokens are consumed. +pub trait Driver { + /// A token to receive a single network packet. + type RxToken<'a>: RxToken + where + Self: 'a; + + /// A token to transmit a single network packet. + type TxToken<'a>: TxToken + where + Self: 'a; + + /// Construct a token pair consisting of one receive token and one transmit token. + /// + /// If there is a packet ready to be received, this function must return `Some`. + /// If there isn't, it must return `None`, and wake `cx.waker()` when a packet is ready. + /// + /// The additional transmit token makes it possible to generate a reply packet based + /// on the contents of the received packet. For example, this makes it possible to + /// handle arbitrarily large ICMP echo ("ping") requests, where the all received bytes + /// need to be sent back, without heap allocation. + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>; + + /// Construct a transmit token. + /// + /// If there is free space in the transmit buffer to transmit a packet, this function must return `Some`. + /// If there isn't, it must return `None`, and wake `cx.waker()` when space becomes available. + /// + /// Note that [`TxToken::consume`] is infallible, so it is not allowed to return a token + /// if there is no free space and fail later. + fn transmit(&mut self, cx: &mut Context) -> Option>; + + /// Get the link state. + /// + /// This function must return the current link state of the device, and wake `cx.waker()` when + /// the link state changes. + fn link_state(&mut self, cx: &mut Context) -> LinkState; + + /// Get a description of device capabilities. + fn capabilities(&self) -> Capabilities; + + /// Get the device's hardware address. + /// + /// The returned hardware address also determines the "medium" of this driver. This indicates + /// what kind of packet the sent/received bytes are, and determines some behaviors of + /// the interface. For example, ARP/NDISC address resolution is only done for Ethernet mediums. + fn hardware_address(&self) -> HardwareAddress; +} + +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 hardware_address(&self) -> HardwareAddress { + T::hardware_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 { + /// 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, +} + +/// 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 { + /// Checksum behavior for IPv4. + pub ipv4: Checksum, + /// Checksum behavior for UDP. + pub udp: Checksum, + /// Checksum behavior for TCP. + pub tcp: Checksum, + /// Checksum behavior for ICMPv4. + pub icmpv4: Checksum, + /// Checksum behavior for ICMPv6. + 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 + } +} + +/// The link state of a network device. +#[derive(PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LinkState { + /// The link is down. + Down, + /// The link is up. + Up, +} diff --git a/embassy/embassy-net-enc28j60/Cargo.toml b/embassy/embassy-net-enc28j60/Cargo.toml new file mode 100644 index 0000000..cafced4 --- /dev/null +++ b/embassy/embassy-net-enc28j60/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "embassy-net-enc28j60" +version = "0.1.0" +description = "embassy-net driver for the ENC28J60 ethernet chip" +keywords = ["embedded", "enc28j60", "embassy-net", "embedded-hal-async", "ethernet"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +edition = "2021" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-enc28j60" + +[dependencies] +embedded-hal = { version = "1.0" } +embedded-hal-async = { version = "1.0" } +embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-enc28j60-v$VERSION/embassy-net-enc28j60/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-enc28j60/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] diff --git a/embassy/embassy-net-enc28j60/README.md b/embassy/embassy-net-enc28j60/README.md new file mode 100644 index 0000000..5c663b5 --- /dev/null +++ b/embassy/embassy-net-enc28j60/README.md @@ -0,0 +1,9 @@ +# `embassy-net-enc28j60` + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for the Microchip ENC28J60 Ethernet chip. + +Based on [@japaric](https://github.com/japaric)'s [`enc28j60`](https://github.com/japaric/enc28j60) crate. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy/embassy-net-enc28j60/src/bank0.rs b/embassy/embassy-net-enc28j60/src/bank0.rs new file mode 100644 index 0000000..1c1b3a7 --- /dev/null +++ b/embassy/embassy-net-enc28j60/src/bank0.rs @@ -0,0 +1,69 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + ERDPTL = 0x00, + ERDPTH = 0x01, + EWRPTL = 0x02, + EWRPTH = 0x03, + ETXSTL = 0x04, + ETXSTH = 0x05, + ETXNDL = 0x06, + ETXNDH = 0x07, + ERXSTL = 0x08, + ERXSTH = 0x09, + ERXNDL = 0x0a, + ERXNDH = 0x0b, + ERXRDPTL = 0x0c, + ERXRDPTH = 0x0d, + ERXWRPTL = 0x0e, + ERXWRPTH = 0x0f, + EDMASTL = 0x10, + EDMASTH = 0x11, + EDMANDL = 0x12, + EDMANDH = 0x13, + EDMADSTL = 0x14, + EDMADSTH = 0x15, + EDMACSL = 0x16, + EDMACSH = 0x17, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } + + pub(crate) fn is_eth_register(&self) -> bool { + match *self { + Register::ERDPTL => true, + Register::ERDPTH => true, + Register::EWRPTL => true, + Register::EWRPTH => true, + Register::ETXSTL => true, + Register::ETXSTH => true, + Register::ETXNDL => true, + Register::ETXNDH => true, + Register::ERXSTL => true, + Register::ERXSTH => true, + Register::ERXNDL => true, + Register::ERXNDH => true, + Register::ERXRDPTL => true, + Register::ERXRDPTH => true, + Register::ERXWRPTL => true, + Register::ERXWRPTH => true, + Register::EDMASTL => true, + Register::EDMASTH => true, + Register::EDMANDL => true, + Register::EDMANDH => true, + Register::EDMADSTL => true, + Register::EDMADSTH => true, + Register::EDMACSL => true, + Register::EDMACSH => true, + } + } +} + +impl Into for Register { + fn into(self) -> super::Register { + super::Register::Bank0(self) + } +} diff --git a/embassy/embassy-net-enc28j60/src/bank1.rs b/embassy/embassy-net-enc28j60/src/bank1.rs new file mode 100644 index 0000000..30560ed --- /dev/null +++ b/embassy/embassy-net-enc28j60/src/bank1.rs @@ -0,0 +1,84 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + EHT0 = 0x00, + EHT1 = 0x01, + EHT2 = 0x02, + EHT3 = 0x03, + EHT4 = 0x04, + EHT5 = 0x05, + EHT6 = 0x06, + EHT7 = 0x07, + EPMM0 = 0x08, + EPMM1 = 0x09, + EPMM2 = 0x0a, + EPMM3 = 0x0b, + EPMM4 = 0x0c, + EPMM5 = 0x0d, + EPMM6 = 0x0e, + EPMM7 = 0x0f, + EPMCSL = 0x10, + EPMCSH = 0x11, + EPMOL = 0x14, + EPMOH = 0x15, + ERXFCON = 0x18, + EPKTCNT = 0x19, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } + + pub(crate) fn is_eth_register(&self) -> bool { + match *self { + Register::EHT0 => true, + Register::EHT1 => true, + Register::EHT2 => true, + Register::EHT3 => true, + Register::EHT4 => true, + Register::EHT5 => true, + Register::EHT6 => true, + Register::EHT7 => true, + Register::EPMM0 => true, + Register::EPMM1 => true, + Register::EPMM2 => true, + Register::EPMM3 => true, + Register::EPMM4 => true, + Register::EPMM5 => true, + Register::EPMM6 => true, + Register::EPMM7 => true, + Register::EPMCSL => true, + Register::EPMCSH => true, + Register::EPMOL => true, + Register::EPMOH => true, + Register::ERXFCON => true, + Register::EPKTCNT => true, + } + } +} + +impl Into for Register { + fn into(self) -> super::Register { + super::Register::Bank1(self) + } +} + +register!(ERXFCON, 0b1010_0001, u8, { + #[doc = "Broadcast Filter Enable bit"] + bcen @ 0, + #[doc = "Multicast Filter Enable bit"] + mcen @ 1, + #[doc = "Hash Table Filter Enable bit"] + hten @ 2, + #[doc = "Magic Packet™ Filter Enable bit"] + mpen @ 3, + #[doc = "Pattern Match Filter Enable bit"] + pmen @ 4, + #[doc = "Post-Filter CRC Check Enable bit"] + crcen @ 5, + #[doc = "AND/OR Filter Select bit"] + andor @ 6, + #[doc = "Unicast Filter Enable bit"] + ucen @ 7, +}); diff --git a/embassy/embassy-net-enc28j60/src/bank2.rs b/embassy/embassy-net-enc28j60/src/bank2.rs new file mode 100644 index 0000000..74a1d24 --- /dev/null +++ b/embassy/embassy-net-enc28j60/src/bank2.rs @@ -0,0 +1,86 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + MACON1 = 0x00, + MACON3 = 0x02, + MACON4 = 0x03, + MABBIPG = 0x04, + MAIPGL = 0x06, + MAIPGH = 0x07, + MACLCON1 = 0x08, + MACLCON2 = 0x09, + MAMXFLL = 0x0a, + MAMXFLH = 0x0b, + MICMD = 0x12, + MIREGADR = 0x14, + MIWRL = 0x16, + MIWRH = 0x17, + MIRDL = 0x18, + MIRDH = 0x19, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } + + pub(crate) fn is_eth_register(&self) -> bool { + match *self { + Register::MACON1 => false, + Register::MACON3 => false, + Register::MACON4 => false, + Register::MABBIPG => false, + Register::MAIPGL => false, + Register::MAIPGH => false, + Register::MACLCON1 => false, + Register::MACLCON2 => false, + Register::MAMXFLL => false, + Register::MAMXFLH => false, + Register::MICMD => false, + Register::MIREGADR => false, + Register::MIWRL => false, + Register::MIWRH => false, + Register::MIRDL => false, + Register::MIRDH => false, + } + } +} + +impl Into for Register { + fn into(self) -> super::Register { + super::Register::Bank2(self) + } +} + +register!(MACON1, 0, u8, { + #[doc = "Enable packets to be received by the MAC"] + marxen @ 0, + #[doc = "Control frames will be discarded after being processed by the MAC"] + passall @ 1, + #[doc = "Inhibit transmissions when pause control frames are received"] + rxpaus @ 2, + #[doc = "Allow the MAC to transmit pause control frames"] + txpaus @ 3, +}); + +register!(MACON3, 0, u8, { + #[doc = "MAC will operate in Full-Duplex mode"] + fuldpx @ 0, + #[doc = "The type/length field of transmitted and received frames will be checked"] + frmlnen @ 1, + #[doc = "Frames bigger than MAMXFL will be aborted when transmitted or received"] + hfrmen @ 2, + #[doc = "No proprietary header is present"] + phdren @ 3, + #[doc = "MAC will append a valid CRC to all frames transmitted regardless of PADCFG bit"] + txcrcen @ 4, + #[doc = "All short frames will be zero-padded to 64 bytes and a valid CRC will then be appended"] + padcfg @ 5..7, +}); + +register!(MICMD, 0, u8, { + #[doc = "MII Read Enable bit"] + miird @ 0, + #[doc = "MII Scan Enable bit"] + miiscan @ 1, +}); diff --git a/embassy/embassy-net-enc28j60/src/bank3.rs b/embassy/embassy-net-enc28j60/src/bank3.rs new file mode 100644 index 0000000..4f7eb94 --- /dev/null +++ b/embassy/embassy-net-enc28j60/src/bank3.rs @@ -0,0 +1,53 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + MAADR5 = 0x00, + MAADR6 = 0x01, + MAADR3 = 0x02, + MAADR4 = 0x03, + MAADR1 = 0x04, + MAADR2 = 0x05, + EBSTSD = 0x06, + EBSTCON = 0x07, + EBSTCSL = 0x08, + EBSTCSH = 0x09, + MISTAT = 0x0a, + EREVID = 0x12, + ECOCON = 0x15, + EFLOCON = 0x17, + EPAUSL = 0x18, + EPAUSH = 0x19, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } + + pub(crate) fn is_eth_register(&self) -> bool { + match *self { + Register::MAADR5 => false, + Register::MAADR6 => false, + Register::MAADR3 => false, + Register::MAADR4 => false, + Register::MAADR1 => false, + Register::MAADR2 => false, + Register::EBSTSD => true, + Register::EBSTCON => true, + Register::EBSTCSL => true, + Register::EBSTCSH => true, + Register::MISTAT => false, + Register::EREVID => true, + Register::ECOCON => true, + Register::EFLOCON => true, + Register::EPAUSL => true, + Register::EPAUSH => true, + } + } +} + +impl Into for Register { + fn into(self) -> super::Register { + super::Register::Bank3(self) + } +} diff --git a/embassy/embassy-net-enc28j60/src/common.rs b/embassy/embassy-net-enc28j60/src/common.rs new file mode 100644 index 0000000..ef339dd --- /dev/null +++ b/embassy/embassy-net-enc28j60/src/common.rs @@ -0,0 +1,106 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + ECON1 = 0x1f, + ECON2 = 0x1e, + EIE = 0x1b, + EIR = 0x1c, + ESTAT = 0x1d, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } + + pub(crate) fn is_eth_register(&self) -> bool { + match *self { + Register::ECON1 => true, + Register::ECON2 => true, + Register::EIE => true, + Register::EIR => true, + Register::ESTAT => true, + } + } +} + +impl Into for Register { + fn into(self) -> super::Register { + super::Register::Common(self) + } +} + +register!(EIE, 0, u8, { + #[doc = "Receive Error Interrupt Enable bit"] + rxerie @ 0, + #[doc = "Transmit Error Interrupt Enable bit"] + txerie @ 1, + #[doc = "Transmit Enable bit"] + txie @ 3, + #[doc = "Link Status Change Interrupt Enable bit"] + linkie @ 4, + #[doc = "DMA Interrupt Enable bit"] + dmaie @ 5, + #[doc = "Receive Packet Pending Interrupt Enable bit"] + pktie @ 6, + #[doc = "Global INT Interrupt Enable bit"] + intie @ 7, +}); + +register!(EIR, 0, u8, { + #[doc = "Receive Error Interrupt Flag bit"] + rxerif @ 0, + #[doc = "Transmit Error Interrupt Flag bit"] + txerif @ 1, + #[doc = "Transmit Interrupt Flag bit"] + txif @ 3, + #[doc = "Link Change Interrupt Flag bit"] + linkif @ 4, + #[doc = "DMA Interrupt Flag bit"] + dmaif @ 5, + #[doc = "Receive Packet Pending Interrupt Flag bit"] + pktif @ 6, +}); + +register!(ESTAT, 0, u8, { + #[doc = "Clock Ready bit"] + clkrdy @ 0, + #[doc = "Transmit Abort Error bit"] + txabrt @ 1, + #[doc = "Receive Busy bit"] + rxbusy @ 2, + #[doc = "Late Collision Error bit"] + latecol @ 4, + #[doc = "Ethernet Buffer Error Status bit"] + bufer @ 6, + #[doc = "INT Interrupt Flag bit"] + int @ 7, +}); + +register!(ECON2, 0b1000_0000, u8, { + #[doc = "Voltage Regulator Power Save Enable bit"] + vrps @ 3, + #[doc = "Power Save Enable bit"] + pwrsv @ 5, + #[doc = "Packet Decrement bit"] + pktdec @ 6, + #[doc = "Automatic Buffer Pointer Increment Enable bit"] + autoinc @ 7, +}); + +register!(ECON1, 0, u8, { + #[doc = "Bank Select bits"] + bsel @ 0..1, + #[doc = "Receive Enable bi"] + rxen @ 2, + #[doc = "Transmit Request to Send bit"] + txrts @ 3, + #[doc = "DMA Checksum Enable bit"] + csumen @ 4, + #[doc = "DMA Start and Busy Status bit"] + dmast @ 5, + #[doc = "Receive Logic Reset bit"] + rxrst @ 6, + #[doc = "Transmit Logic Reset bit"] + txrst @ 7, +}); diff --git a/embassy/embassy-net-enc28j60/src/fmt.rs b/embassy/embassy-net-enc28j60/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-net-enc28j60/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-net-enc28j60/src/header.rs b/embassy/embassy-net-enc28j60/src/header.rs new file mode 100644 index 0000000..c2d4e46 --- /dev/null +++ b/embassy/embassy-net-enc28j60/src/header.rs @@ -0,0 +1,30 @@ +register!(RxStatus, 0, u32, { + #[doc = "Indicates length of the received frame"] + byte_count @ 0..15, + #[doc = "Indicates a packet over 50,000 bit times occurred or that a packet was dropped since the last receive"] + long_event @ 16, + #[doc = "Indicates that at some time since the last receive, a carrier event was detected"] + carrier_event @ 18, + #[doc = "Indicates that frame CRC field value does not match the CRC calculated by the MAC"] + crc_error @ 20, + #[doc = "Indicates that frame length field value in the packet does not match the actual data byte length and specifies a valid length"] + length_check_error @ 21, + #[doc = "Indicates that frame type/length field was larger than 1500 bytes (type field)"] + length_out_of_range @ 22, + #[doc = "Indicates that at the packet had a valid CRC and no symbol errors"] + received_ok @ 23, + #[doc = "Indicates packet received had a valid Multicast address"] + multicast @ 24, + #[doc = "Indicates packet received had a valid Broadcast address."] + broadcast @ 25, + #[doc = "Indicates that after the end of this packet, an additional 1 to 7 bits were received"] + dribble_nibble @ 26, + #[doc = "Current frame was recognized as a control frame for having a valid type/length designating it as a control frame"] + receive_control_frame @ 27, + #[doc = "Current frame was recognized as a control frame containing a valid pause frame opcode and a valid destination address"] + receive_pause_control_frame @ 28, + #[doc = "Current frame was recognized as a control frame but it contained an unknown opcode"] + receive_unknown_opcode @ 29, + #[doc = "Current frame was recognized as a VLAN tagged frame"] + receive_vlan_type_detected @ 30, +}); diff --git a/embassy/embassy-net-enc28j60/src/lib.rs b/embassy/embassy-net-enc28j60/src/lib.rs new file mode 100644 index 0000000..c1f3271 --- /dev/null +++ b/embassy/embassy-net-enc28j60/src/lib.rs @@ -0,0 +1,721 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// must go first. +mod fmt; + +#[macro_use] +mod macros; +mod bank0; +mod bank1; +mod bank2; +mod bank3; +mod common; +mod header; +mod phy; +mod traits; + +use core::cmp; + +use embassy_net_driver::{Capabilities, HardwareAddress, LinkState}; +use embassy_time::Duration; +use embedded_hal::digital::OutputPin; +use embedded_hal::spi::{Operation, SpiDevice}; +use traits::U16Ext; + +// Total buffer size (see section 3.2) +const BUF_SZ: u16 = 8 * 1024; + +// Maximum frame length +const MAX_FRAME_LENGTH: u16 = 1518; // value recommended in the data sheet + +// Size of the Frame check sequence (32-bit CRC) +const CRC_SZ: u16 = 4; + +// define the boundaries of the TX and RX buffers +// to workaround errata #5 we do the opposite of what section 6.1 of the data sheet +// says: we place the RX buffer at address 0 and the TX buffer after it +const RXST: u16 = 0x0000; +const RXND: u16 = 0x19ff; +const TXST: u16 = 0x1a00; +const _TXND: u16 = 0x1fff; + +const MTU: usize = 1514; // 1500 IP + 14 ethernet header + +/// ENC28J60 embassy-net driver +pub struct Enc28j60 { + mac_addr: [u8; 6], + + spi: S, + rst: Option, + + bank: Bank, + + // address of the next packet in buffer memory + next_packet: u16, +} + +impl Enc28j60 +where + S: SpiDevice, + O: OutputPin, +{ + /// Create a new ENC28J60 driver instance. + /// + /// The RST pin is optional. If None, reset will be done with a SPI + /// soft reset command, instead of via the RST pin. + pub fn new(spi: S, rst: Option, mac_addr: [u8; 6]) -> Self { + let mut res = Self { + mac_addr, + spi, + rst, + + bank: Bank::Bank0, + next_packet: RXST, + }; + res.init(); + res + } + + fn init(&mut self) { + if let Some(rst) = &mut self.rst { + rst.set_low().unwrap(); + embassy_time::block_for(Duration::from_millis(5)); + rst.set_high().unwrap(); + embassy_time::block_for(Duration::from_millis(5)); + } else { + embassy_time::block_for(Duration::from_millis(5)); + self.soft_reset(); + embassy_time::block_for(Duration::from_millis(5)); + } + + debug!( + "enc28j60: erevid {=u8:x}", + self.read_control_register(bank3::Register::EREVID) + ); + debug!("enc28j60: waiting for clk"); + while common::ESTAT(self.read_control_register(common::Register::ESTAT)).clkrdy() == 0 {} + debug!("enc28j60: clk ok"); + + if self.read_control_register(bank3::Register::EREVID) == 0 { + panic!("ErevidIsZero"); + } + + // disable CLKOUT output + self.write_control_register(bank3::Register::ECOCON, 0); + + self.init_rx(); + + // TX start + // "It is recommended that an even address be used for ETXST" + debug_assert_eq!(TXST % 2, 0); + self.write_control_register(bank0::Register::ETXSTL, TXST.low()); + self.write_control_register(bank0::Register::ETXSTH, TXST.high()); + + // TX end is set in `transmit` + + // MAC initialization (see section 6.5) + // 1. Set the MARXEN bit in MACON1 to enable the MAC to receive frames. + self.write_control_register( + bank2::Register::MACON1, + bank2::MACON1::default().marxen(1).passall(0).rxpaus(1).txpaus(1).bits(), + ); + + // 2. Configure the PADCFG, TXCRCEN and FULDPX bits of MACON3. + self.write_control_register( + bank2::Register::MACON3, + bank2::MACON3::default().frmlnen(1).txcrcen(1).padcfg(0b001).bits(), + ); + + // 4. Program the MAMXFL registers with the maximum frame length to be permitted to be + // received or transmitted + self.write_control_register(bank2::Register::MAMXFLL, MAX_FRAME_LENGTH.low()); + self.write_control_register(bank2::Register::MAMXFLH, MAX_FRAME_LENGTH.high()); + + // 5. Configure the Back-to-Back Inter-Packet Gap register, MABBIPG. + // Use recommended value of 0x12 + self.write_control_register(bank2::Register::MABBIPG, 0x12); + + // 6. Configure the Non-Back-to-Back Inter-Packet Gap register low byte, MAIPGL. + // Use recommended value of 0x12 + self.write_control_register(bank2::Register::MAIPGL, 0x12); + self.write_control_register(bank2::Register::MAIPGH, 0x0c); + + // 9. Program the local MAC address into the MAADR1:MAADR6 registers + self.write_control_register(bank3::Register::MAADR1, self.mac_addr[0]); + self.write_control_register(bank3::Register::MAADR2, self.mac_addr[1]); + self.write_control_register(bank3::Register::MAADR3, self.mac_addr[2]); + self.write_control_register(bank3::Register::MAADR4, self.mac_addr[3]); + self.write_control_register(bank3::Register::MAADR5, self.mac_addr[4]); + self.write_control_register(bank3::Register::MAADR6, self.mac_addr[5]); + + // Set the PHCON2.HDLDIS bit to prevent automatic loopback of the data which is transmitted + self.write_phy_register(phy::Register::PHCON2, phy::PHCON2::default().hdldis(1).bits()); + + // Globally enable interrupts + //self.bit_field_set(common::Register::EIE, common::EIE::mask().intie()); + + // Set the per packet control byte; we'll always use the value 0 + self.write_buffer_memory(Some(TXST), &mut [0]); + + // Enable reception + self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxen()); + } + + fn init_rx(&mut self) { + // RX start + // "It is recommended that the ERXST Pointer be programmed with an even address" + self.write_control_register(bank0::Register::ERXSTL, RXST.low()); + self.write_control_register(bank0::Register::ERXSTH, RXST.high()); + + // RX read pointer + // NOTE Errata #14 so we are using an *odd* address here instead of ERXST + self.write_control_register(bank0::Register::ERXRDPTL, RXND.low()); + self.write_control_register(bank0::Register::ERXRDPTH, RXND.high()); + + // RX end + self.write_control_register(bank0::Register::ERXNDL, RXND.low()); + self.write_control_register(bank0::Register::ERXNDH, RXND.high()); + + // decrease the packet count to 0 + while self.read_control_register(bank1::Register::EPKTCNT) != 0 { + self.bit_field_set(common::Register::ECON2, common::ECON2::mask().pktdec()); + } + + self.next_packet = RXST; + } + + fn reset_rx(&mut self) { + self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxrst()); + self.bit_field_clear(common::Register::ECON1, common::ECON1::mask().rxrst()); + self.init_rx(); + self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxen()); + } + + /// Returns the device's MAC address + pub fn address(&self) -> [u8; 6] { + self.mac_addr + } + + /// Flushes the transmit buffer, ensuring all pending transmissions have completed + /// NOTE: The returned packet *must* be `read` or `ignore`-d, otherwise this method will always + /// return `None` on subsequent invocations + pub fn receive(&mut self, buf: &mut [u8]) -> Option { + if self.pending_packets() == 0 { + // Errata #6: we can't rely on PKTIF so we check PKTCNT + return None; + } + + let curr_packet = self.next_packet; + + // read out the first 6 bytes + let mut temp_buf = [0; 6]; + self.read_buffer_memory(Some(curr_packet), &mut temp_buf); + + // next packet pointer + let next_packet = u16::from_parts(temp_buf[0], temp_buf[1]); + // status vector + let status = header::RxStatus(u32::from_le_bytes(temp_buf[2..].try_into().unwrap())); + let len_with_crc = status.byte_count() as u16; + + if len_with_crc < CRC_SZ || len_with_crc > 1600 || next_packet > RXND { + warn!("RX buffer corrupted, resetting RX logic to recover..."); + self.reset_rx(); + return None; + } + + let len = len_with_crc - CRC_SZ; + self.read_buffer_memory(None, &mut buf[..len as usize]); + + // update ERXRDPT + // due to Errata #14 we must write an odd address to ERXRDPT + // we know that ERXST = 0, that ERXND is odd and that next_packet is even + let rxrdpt = if self.next_packet < 1 || self.next_packet > RXND + 1 { + RXND + } else { + self.next_packet - 1 + }; + // "To move ERXRDPT, the host controller must write to ERXRDPTL first." + self.write_control_register(bank0::Register::ERXRDPTL, rxrdpt.low()); + self.write_control_register(bank0::Register::ERXRDPTH, rxrdpt.high()); + + // decrease the packet count + self.bit_field_set(common::Register::ECON2, common::ECON2::mask().pktdec()); + + self.next_packet = next_packet; + + Some(len as usize) + } + + fn wait_tx_ready(&mut self) { + for _ in 0u32..10000 { + if common::ECON1(self.read_control_register(common::Register::ECON1)).txrts() == 0 { + return; + } + } + + // work around errata #12 by resetting the transmit logic before every new + // transmission + self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrst()); + self.bit_field_clear(common::Register::ECON1, common::ECON1::mask().txrst()); + //self.bit_field_clear(common::Register::EIR, { + // let mask = common::EIR::mask(); + // mask.txerif() | mask.txif() + //}); + } + + /// Starts the transmission of `bytes` + /// + /// It's up to the caller to ensure that `bytes` is a valid Ethernet frame. The interface will + /// take care of appending a (4 byte) CRC to the frame and of padding the frame to the minimum + /// size allowed by the Ethernet specification (64 bytes, or 46 bytes of payload). + /// + /// NOTE This method will flush any previous transmission that's in progress + /// + /// # Panics + /// + /// If `bytes` length is greater than 1514, the maximum frame length allowed by the interface, + /// or greater than the transmit buffer + pub fn transmit(&mut self, bytes: &[u8]) { + assert!(bytes.len() <= self.mtu() as usize); + + self.wait_tx_ready(); + + // NOTE the plus one is to not overwrite the per packet control byte + let wrpt = TXST + 1; + + // 1. ETXST was set during initialization + + // 2. write the frame to the IC memory + self.write_buffer_memory(Some(wrpt), bytes); + + let txnd = wrpt + bytes.len() as u16 - 1; + + // 3. Set the end address of the transmit buffer + self.write_control_register(bank0::Register::ETXNDL, txnd.low()); + self.write_control_register(bank0::Register::ETXNDH, txnd.high()); + + // 4. reset interrupt flag + //self.bit_field_clear(common::Register::EIR, common::EIR::mask().txif()); + + // 5. start transmission + self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrts()); + + // Wait until transmission finishes + //while common::ECON1(self.read_control_register(common::Register::ECON1)).txrts() == 1 {} + + /* + // read the transmit status vector + let mut tx_stat = [0; 7]; + self.read_buffer_memory(None, &mut tx_stat); + + let stat = common::ESTAT(self.read_control_register(common::Register::ESTAT)); + + if stat.txabrt() == 1 { + // work around errata #12 by reading the transmit status vector + if stat.latecol() == 1 || (tx_stat[2] & (1 << 5)) != 0 { + panic!("LateCollision") + } else { + panic!("TransmitAbort") + } + }*/ + } + + /// Get whether the link is up + pub fn is_link_up(&mut self) -> bool { + let bits = self.read_phy_register(phy::Register::PHSTAT2); + phy::PHSTAT2(bits).lstat() == 1 + } + + /// Returns the interface Maximum Transmission Unit (MTU) + /// + /// The value returned by this function will never exceed 1514 bytes. The actual value depends + /// on the memory assigned to the transmission buffer when initializing the device + pub fn mtu(&self) -> u16 { + cmp::min(BUF_SZ - RXND - 1, MAX_FRAME_LENGTH - CRC_SZ) + } + + /* Miscellaneous */ + /// Returns the number of packets that have been received but have not been processed yet + pub fn pending_packets(&mut self) -> u8 { + self.read_control_register(bank1::Register::EPKTCNT) + } + + /// Adjusts the receive filter to *accept* these packet types + pub fn accept(&mut self, packets: &[Packet]) { + let mask = bank1::ERXFCON::mask(); + let mut val = 0; + for packet in packets { + match packet { + Packet::Broadcast => val |= mask.bcen(), + Packet::Multicast => val |= mask.mcen(), + Packet::Unicast => val |= mask.ucen(), + } + } + + self.bit_field_set(bank1::Register::ERXFCON, val) + } + + /// Adjusts the receive filter to *ignore* these packet types + pub fn ignore(&mut self, packets: &[Packet]) { + let mask = bank1::ERXFCON::mask(); + let mut val = 0; + for packet in packets { + match packet { + Packet::Broadcast => val |= mask.bcen(), + Packet::Multicast => val |= mask.mcen(), + Packet::Unicast => val |= mask.ucen(), + } + } + + self.bit_field_clear(bank1::Register::ERXFCON, val) + } + + /* Private */ + /* Read */ + fn read_control_register(&mut self, register: R) -> u8 + where + R: Into, + { + self._read_control_register(register.into()) + } + + fn _read_control_register(&mut self, register: Register) -> u8 { + self.change_bank(register); + + if register.is_eth_register() { + let mut buffer = [Instruction::RCR.opcode() | register.addr(), 0]; + self.spi.transfer_in_place(&mut buffer).unwrap(); + buffer[1] + } else { + // MAC, MII regs need a dummy byte. + let mut buffer = [Instruction::RCR.opcode() | register.addr(), 0, 0]; + self.spi.transfer_in_place(&mut buffer).unwrap(); + buffer[2] + } + } + + fn read_phy_register(&mut self, register: phy::Register) -> u16 { + // set PHY register address + self.write_control_register(bank2::Register::MIREGADR, register.addr()); + + // start read operation + self.write_control_register(bank2::Register::MICMD, bank2::MICMD::default().miird(1).bits()); + + // wait until the read operation finishes + while self.read_control_register(bank3::Register::MISTAT) & 0b1 != 0 {} + + self.write_control_register(bank2::Register::MICMD, bank2::MICMD::default().miird(0).bits()); + + let l = self.read_control_register(bank2::Register::MIRDL); + let h = self.read_control_register(bank2::Register::MIRDH); + (l as u16) | (h as u16) << 8 + } + + /* Write */ + fn _write_control_register(&mut self, register: Register, value: u8) { + self.change_bank(register); + + let buffer = [Instruction::WCR.opcode() | register.addr(), value]; + self.spi.write(&buffer).unwrap(); + } + + fn write_control_register(&mut self, register: R, value: u8) + where + R: Into, + { + self._write_control_register(register.into(), value) + } + + fn write_phy_register(&mut self, register: phy::Register, value: u16) { + // set PHY register address + self.write_control_register(bank2::Register::MIREGADR, register.addr()); + + self.write_control_register(bank2::Register::MIWRL, (value & 0xff) as u8); + // this starts the write operation + self.write_control_register(bank2::Register::MIWRH, (value >> 8) as u8); + + // wait until the write operation finishes + while self.read_control_register(bank3::Register::MISTAT) & 0b1 != 0 {} + } + + /* RMW */ + fn modify_control_register(&mut self, register: R, f: F) + where + F: FnOnce(u8) -> u8, + R: Into, + { + self._modify_control_register(register.into(), f) + } + + fn _modify_control_register(&mut self, register: Register, f: F) + where + F: FnOnce(u8) -> u8, + { + let r = self._read_control_register(register); + self._write_control_register(register, f(r)) + } + + /* Auxiliary */ + fn change_bank(&mut self, register: Register) { + let bank = register.bank(); + + if let Some(bank) = bank { + if self.bank == bank { + // already on the register bank + return; + } + + // change bank + self.bank = bank; + match bank { + Bank::Bank0 => self.bit_field_clear(common::Register::ECON1, 0b11), + Bank::Bank1 => self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b01), + Bank::Bank2 => self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b10), + Bank::Bank3 => self.bit_field_set(common::Register::ECON1, 0b11), + } + } else { + // common register + } + } + + /* Primitive operations */ + fn bit_field_clear(&mut self, register: R, mask: u8) + where + R: Into, + { + self._bit_field_clear(register.into(), mask) + } + + fn _bit_field_clear(&mut self, register: Register, mask: u8) { + debug_assert!(register.is_eth_register()); + + self.change_bank(register); + + self.spi + .write(&[Instruction::BFC.opcode() | register.addr(), mask]) + .unwrap(); + } + + fn bit_field_set(&mut self, register: R, mask: u8) + where + R: Into, + { + self._bit_field_set(register.into(), mask) + } + + fn _bit_field_set(&mut self, register: Register, mask: u8) { + debug_assert!(register.is_eth_register()); + + self.change_bank(register); + + self.spi + .write(&[Instruction::BFS.opcode() | register.addr(), mask]) + .unwrap(); + } + + fn read_buffer_memory(&mut self, addr: Option, buf: &mut [u8]) { + if let Some(addr) = addr { + self.write_control_register(bank0::Register::ERDPTL, addr.low()); + self.write_control_register(bank0::Register::ERDPTH, addr.high()); + } + + self.spi + .transaction(&mut [Operation::Write(&[Instruction::RBM.opcode()]), Operation::Read(buf)]) + .unwrap(); + } + + fn soft_reset(&mut self) { + self.spi.write(&[Instruction::SRC.opcode()]).unwrap(); + } + + fn write_buffer_memory(&mut self, addr: Option, buffer: &[u8]) { + if let Some(addr) = addr { + self.write_control_register(bank0::Register::EWRPTL, addr.low()); + self.write_control_register(bank0::Register::EWRPTH, addr.high()); + } + + self.spi + .transaction(&mut [Operation::Write(&[Instruction::WBM.opcode()]), Operation::Write(buffer)]) + .unwrap(); + } +} + +#[derive(Clone, Copy, PartialEq)] +enum Bank { + Bank0, + Bank1, + Bank2, + Bank3, +} + +#[derive(Clone, Copy)] +enum Instruction { + /// Read Control Register + RCR = 0b000_00000, + /// Read Buffer Memory + RBM = 0b001_11010, + /// Write Control Register + WCR = 0b010_00000, + /// Write Buffer Memory + WBM = 0b011_11010, + /// Bit Field Set + BFS = 0b100_00000, + /// Bit Field Clear + BFC = 0b101_00000, + /// System Reset Command + SRC = 0b111_11111, +} + +impl Instruction { + fn opcode(&self) -> u8 { + *self as u8 + } +} + +#[derive(Clone, Copy)] +enum Register { + Bank0(bank0::Register), + Bank1(bank1::Register), + Bank2(bank2::Register), + Bank3(bank3::Register), + Common(common::Register), +} + +impl Register { + fn addr(&self) -> u8 { + match *self { + Register::Bank0(r) => r.addr(), + Register::Bank1(r) => r.addr(), + Register::Bank2(r) => r.addr(), + Register::Bank3(r) => r.addr(), + Register::Common(r) => r.addr(), + } + } + + fn bank(&self) -> Option { + Some(match *self { + Register::Bank0(_) => Bank::Bank0, + Register::Bank1(_) => Bank::Bank1, + Register::Bank2(_) => Bank::Bank2, + Register::Bank3(_) => Bank::Bank3, + Register::Common(_) => return None, + }) + } + + fn is_eth_register(&self) -> bool { + match *self { + Register::Bank0(r) => r.is_eth_register(), + Register::Bank1(r) => r.is_eth_register(), + Register::Bank2(r) => r.is_eth_register(), + Register::Bank3(r) => r.is_eth_register(), + Register::Common(r) => r.is_eth_register(), + } + } +} + +/// Packet type, used to configure receive filters +#[non_exhaustive] +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum Packet { + /// Broadcast packets + Broadcast, + /// Multicast packets + Multicast, + /// Unicast packets + Unicast, +} + +static mut TX_BUF: [u8; MTU] = [0; MTU]; +static mut RX_BUF: [u8; MTU] = [0; MTU]; + +impl embassy_net_driver::Driver for Enc28j60 +where + S: SpiDevice, + O: OutputPin, +{ + type RxToken<'a> + = RxToken<'a> + where + Self: 'a; + + type TxToken<'a> + = TxToken<'a, S, O> + where + Self: 'a; + + fn receive(&mut self, cx: &mut core::task::Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let rx_buf = unsafe { &mut *core::ptr::addr_of_mut!(RX_BUF) }; + let tx_buf = unsafe { &mut *core::ptr::addr_of_mut!(TX_BUF) }; + if let Some(n) = self.receive(rx_buf) { + Some((RxToken { buf: &mut rx_buf[..n] }, TxToken { buf: tx_buf, eth: self })) + } else { + cx.waker().wake_by_ref(); + None + } + } + + fn transmit(&mut self, _cx: &mut core::task::Context) -> Option> { + let tx_buf = unsafe { &mut *core::ptr::addr_of_mut!(TX_BUF) }; + Some(TxToken { buf: tx_buf, eth: self }) + } + + fn link_state(&mut self, cx: &mut core::task::Context) -> LinkState { + cx.waker().wake_by_ref(); + match self.is_link_up() { + true => LinkState::Up, + false => LinkState::Down, + } + } + + fn capabilities(&self) -> Capabilities { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = MTU; + caps + } + + fn hardware_address(&self) -> HardwareAddress { + HardwareAddress::Ethernet(self.mac_addr) + } +} + +/// embassy-net RX token. +pub struct RxToken<'a> { + buf: &'a mut [u8], +} + +impl<'a> embassy_net_driver::RxToken for RxToken<'a> { + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + f(self.buf) + } +} + +/// embassy-net TX token. +pub struct TxToken<'a, S, O> +where + S: SpiDevice, + O: OutputPin, +{ + eth: &'a mut Enc28j60, + buf: &'a mut [u8], +} + +impl<'a, S, O> embassy_net_driver::TxToken for TxToken<'a, S, O> +where + S: SpiDevice, + O: OutputPin, +{ + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + assert!(len <= self.buf.len()); + let r = f(&mut self.buf[..len]); + self.eth.transmit(&self.buf[..len]); + r + } +} diff --git a/embassy/embassy-net-enc28j60/src/macros.rs b/embassy/embassy-net-enc28j60/src/macros.rs new file mode 100644 index 0000000..8d06495 --- /dev/null +++ b/embassy/embassy-net-enc28j60/src/macros.rs @@ -0,0 +1,89 @@ +macro_rules! register { + ($REGISTER:ident, $reset_value:expr, $uxx:ty, { + $(#[$($attr:tt)*] $bitfield:ident @ $range:expr,)+ + }) => { + #[derive(Clone, Copy)] + pub(crate) struct $REGISTER { + bits: $uxx, + _mode: ::core::marker::PhantomData, + } + + impl $REGISTER { + #[allow(dead_code)] + pub(crate) fn mask() -> $REGISTER { + $REGISTER { bits: 0, _mode: ::core::marker::PhantomData } + } + + $( + #[allow(dead_code)] + pub(crate) fn $bitfield(&self) -> $uxx { + use super::traits::OffsetSize; + + let size = $range.size(); + let offset = $range.offset(); + ((1 << size) - 1) << offset + } + )+ + } + + impl ::core::default::Default for $REGISTER { + fn default() -> Self { + $REGISTER { bits: $reset_value, _mode: ::core::marker::PhantomData } + } + } + + #[allow(non_snake_case)] + #[allow(dead_code)] + pub(crate) fn $REGISTER(bits: $uxx) -> $REGISTER { + $REGISTER { bits, _mode: ::core::marker::PhantomData } + } + + impl $REGISTER { + #[allow(dead_code)] + pub(crate) fn modify(self) -> $REGISTER { + $REGISTER { bits: self.bits, _mode: ::core::marker::PhantomData } + } + + $( + #[$($attr)*] + #[allow(dead_code)] + pub(crate) fn $bitfield(&self) -> $uxx { + use super::traits::OffsetSize; + + let offset = $range.offset(); + let size = $range.size(); + let mask = (1 << size) - 1; + + (self.bits >> offset) & mask + } + )+ + } + + impl $REGISTER { + #[allow(dead_code)] + pub(crate) fn bits(self) -> $uxx { + self.bits + } + + $( + #[$($attr)*] + #[allow(dead_code)] + pub(crate) fn $bitfield(&mut self, mut bits: $uxx) -> &mut Self { + use super::traits::OffsetSize; + + let offset = $range.offset(); + let size = $range.size(); + let mask = (1 << size) - 1; + + debug_assert!(bits <= mask); + bits &= mask; + + self.bits &= !(mask << offset); + self.bits |= bits << offset; + + self + } + )+ + } + } +} diff --git a/embassy/embassy-net-enc28j60/src/phy.rs b/embassy/embassy-net-enc28j60/src/phy.rs new file mode 100644 index 0000000..89144ad --- /dev/null +++ b/embassy/embassy-net-enc28j60/src/phy.rs @@ -0,0 +1,35 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + PHCON1 = 0x00, + PHSTAT1 = 0x01, + PHID1 = 0x02, + PHID2 = 0x03, + PHCON2 = 0x10, + PHSTAT2 = 0x11, + PHIE = 0x12, + PHIR = 0x13, + PHLCON = 0x14, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } +} + +register!(PHCON2, 0, u16, { + #[doc = "PHY Half-Duplex Loopback Disable bit"] + hdldis @ 8, + #[doc = "Jabber Correction Disable bit"] + jabber @ 10, + #[doc = "Twisted-Pair Transmitter Disable bit"] + txdis @ 13, + #[doc = "PHY Force Linkup bit"] + frclnk @ 14, +}); + +register!(PHSTAT2, 0, u16, { + #[doc = "Link Status bit"] + lstat @ 10, +}); diff --git a/embassy/embassy-net-enc28j60/src/traits.rs b/embassy/embassy-net-enc28j60/src/traits.rs new file mode 100644 index 0000000..08f9404 --- /dev/null +++ b/embassy/embassy-net-enc28j60/src/traits.rs @@ -0,0 +1,57 @@ +use core::ops::Range; + +pub(crate) trait OffsetSize { + fn offset(self) -> u8; + fn size(self) -> u8; +} + +impl OffsetSize for u8 { + fn offset(self) -> u8 { + self + } + + fn size(self) -> u8 { + 1 + } +} + +impl OffsetSize for Range { + fn offset(self) -> u8 { + self.start + } + + fn size(self) -> u8 { + self.end - self.start + } +} + +pub(crate) trait U16Ext { + fn from_parts(low: u8, high: u8) -> Self; + + fn low(self) -> u8; + + fn high(self) -> u8; +} + +impl U16Ext for u16 { + fn from_parts(low: u8, high: u8) -> u16 { + ((high as u16) << 8) + low as u16 + } + + fn low(self) -> u8 { + (self & 0xff) as u8 + } + + fn high(self) -> u8 { + (self >> 8) as u8 + } +} + +#[derive(Clone, Copy)] +pub struct Mask; + +#[derive(Clone, Copy)] +pub struct R; + +#[derive(Clone, Copy)] +pub struct W; diff --git a/embassy/embassy-net-esp-hosted/Cargo.toml b/embassy/embassy-net-esp-hosted/Cargo.toml new file mode 100644 index 0000000..d22ccb1 --- /dev/null +++ b/embassy/embassy-net-esp-hosted/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "embassy-net-esp-hosted" +version = "0.1.0" +edition = "2021" +description = "embassy-net driver for ESP-Hosted" +keywords = ["embedded", "esp-hosted", "embassy-net", "embedded-hal-async", "wifi"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-esp-hosted" + +[features] +defmt = [ "dep:defmt", "heapless/defmt-03" ] +log = [ "dep:log" ] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-sync = { version = "0.6.1", path = "../embassy-sync"} +embassy-futures = { version = "0.1.0", path = "../embassy-futures"} +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"} + +embedded-hal = { version = "1.0" } +embedded-hal-async = { version = "1.0" } + +noproto = "0.1.0" +heapless = "0.8" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-esp-hosted-v$VERSION/embassy-net-esp-hosted/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-esp-hosted/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] diff --git a/embassy/embassy-net-esp-hosted/README.md b/embassy/embassy-net-esp-hosted/README.md new file mode 100644 index 0000000..524231e --- /dev/null +++ b/embassy/embassy-net-esp-hosted/README.md @@ -0,0 +1,11 @@ +# ESP-Hosted `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for Espressif SoCs running the the [ESP-Hosted](https://github.com/espressif/esp-hosted) stack. + +See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf52840) directory for usage examples with the nRF52840. + +## Interoperability + +This crate can run on any executor. + +It supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async). diff --git a/embassy/embassy-net-esp-hosted/src/control.rs b/embassy/embassy-net-esp-hosted/src/control.rs new file mode 100644 index 0000000..b1838a4 --- /dev/null +++ b/embassy/embassy-net-esp-hosted/src/control.rs @@ -0,0 +1,230 @@ +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::{HardwareAddress, LinkState}; +use heapless::String; + +use crate::ioctl::Shared; +use crate::proto::{self, CtrlMsg}; + +/// Errors reported by control. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The operation failed with the given error code. + Failed(u32), + /// The operation timed out. + Timeout, + /// Internal error. + Internal, +} + +/// Handle for managing the network and WiFI state. +pub struct Control<'a> { + state_ch: ch::StateRunner<'a>, + shared: &'a Shared, +} + +/// WiFi mode. +#[allow(unused)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum WifiMode { + /// No mode. + None = 0, + /// Client station. + Sta = 1, + /// Access point mode. + Ap = 2, + /// Repeater mode. + ApSta = 3, +} + +pub use proto::CtrlWifiSecProt as Security; + +/// WiFi status. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Status { + /// Service Set Identifier. + pub ssid: String<32>, + /// Basic Service Set Identifier. + pub bssid: [u8; 6], + /// Received Signal Strength Indicator. + pub rssi: i32, + /// WiFi channel. + pub channel: u32, + /// Security mode. + pub security: Security, +} + +macro_rules! ioctl { + ($self:ident, $req_variant:ident, $resp_variant:ident, $req:ident, $resp:ident) => { + let mut msg = proto::CtrlMsg { + msg_id: proto::CtrlMsgId::$req_variant as _, + msg_type: proto::CtrlMsgType::Req as _, + payload: Some(proto::CtrlMsgPayload::$req_variant($req)), + }; + $self.ioctl(&mut msg).await?; + #[allow(unused_mut)] + let Some(proto::CtrlMsgPayload::$resp_variant(mut $resp)) = msg.payload + else { + warn!("unexpected response variant"); + return Err(Error::Internal); + }; + if $resp.resp != 0 { + return Err(Error::Failed($resp.resp)); + } + }; +} + +impl<'a> Control<'a> { + pub(crate) fn new(state_ch: ch::StateRunner<'a>, shared: &'a Shared) -> Self { + Self { state_ch, shared } + } + + /// Initialize device. + pub async fn init(&mut self) -> Result<(), Error> { + debug!("wait for init event..."); + self.shared.init_wait().await; + + debug!("set heartbeat"); + self.set_heartbeat(10).await?; + + debug!("set wifi mode"); + self.set_wifi_mode(WifiMode::Sta as _).await?; + + let mac_addr = self.get_mac_addr().await?; + debug!("mac addr: {:02x}", mac_addr); + self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr)); + + Ok(()) + } + + /// Get the current status. + pub async fn get_status(&mut self) -> Result { + let req = proto::CtrlMsgReqGetApConfig {}; + ioctl!(self, ReqGetApConfig, RespGetApConfig, req, resp); + trim_nulls(&mut resp.ssid); + Ok(Status { + ssid: resp.ssid, + bssid: parse_mac(&resp.bssid)?, + rssi: resp.rssi as _, + channel: resp.chnl, + security: resp.sec_prot, + }) + } + + /// Connect to the network identified by ssid using the provided password. + pub async fn connect(&mut self, ssid: &str, password: &str) -> Result<(), Error> { + let req = proto::CtrlMsgReqConnectAp { + ssid: unwrap!(String::try_from(ssid)), + pwd: unwrap!(String::try_from(password)), + bssid: String::new(), + listen_interval: 3, + is_wpa3_supported: true, + }; + ioctl!(self, ReqConnectAp, RespConnectAp, req, resp); + self.state_ch.set_link_state(LinkState::Up); + Ok(()) + } + + /// Disconnect from any currently connected network. + pub async fn disconnect(&mut self) -> Result<(), Error> { + let req = proto::CtrlMsgReqGetStatus {}; + ioctl!(self, ReqDisconnectAp, RespDisconnectAp, req, resp); + self.state_ch.set_link_state(LinkState::Down); + Ok(()) + } + + /// duration in seconds, clamped to [10, 3600] + async fn set_heartbeat(&mut self, duration: u32) -> Result<(), Error> { + let req = proto::CtrlMsgReqConfigHeartbeat { enable: true, duration }; + ioctl!(self, ReqConfigHeartbeat, RespConfigHeartbeat, req, resp); + Ok(()) + } + + async fn get_mac_addr(&mut self) -> Result<[u8; 6], Error> { + let req = proto::CtrlMsgReqGetMacAddress { + mode: WifiMode::Sta as _, + }; + ioctl!(self, ReqGetMacAddress, RespGetMacAddress, req, resp); + parse_mac(&resp.mac) + } + + async fn set_wifi_mode(&mut self, mode: u32) -> Result<(), Error> { + let req = proto::CtrlMsgReqSetMode { mode }; + ioctl!(self, ReqSetWifiMode, RespSetWifiMode, req, resp); + + Ok(()) + } + + async fn ioctl(&mut self, msg: &mut CtrlMsg) -> Result<(), Error> { + debug!("ioctl req: {:?}", &msg); + + let mut buf = [0u8; 128]; + + let req_len = noproto::write(msg, &mut buf).map_err(|_| { + warn!("failed to serialize control request"); + Error::Internal + })?; + + struct CancelOnDrop<'a>(&'a Shared); + + impl CancelOnDrop<'_> { + fn defuse(self) { + core::mem::forget(self); + } + } + + impl Drop for CancelOnDrop<'_> { + fn drop(&mut self) { + self.0.ioctl_cancel(); + } + } + + let ioctl = CancelOnDrop(self.shared); + + let resp_len = ioctl.0.ioctl(&mut buf, req_len).await; + + ioctl.defuse(); + + *msg = noproto::read(&buf[..resp_len]).map_err(|_| { + warn!("failed to serialize control request"); + Error::Internal + })?; + debug!("ioctl resp: {:?}", msg); + + Ok(()) + } +} + +// WHY IS THIS A STRING? WHYYYY +fn parse_mac(mac: &str) -> Result<[u8; 6], Error> { + fn nibble_from_hex(b: u8) -> Result { + match b { + b'0'..=b'9' => Ok(b - b'0'), + b'a'..=b'f' => Ok(b + 0xa - b'a'), + b'A'..=b'F' => Ok(b + 0xa - b'A'), + _ => { + warn!("invalid hex digit {}", b); + Err(Error::Internal) + } + } + } + + let mac = mac.as_bytes(); + let mut res = [0; 6]; + if mac.len() != 17 { + warn!("unexpected MAC length"); + return Err(Error::Internal); + } + for (i, b) in res.iter_mut().enumerate() { + *b = (nibble_from_hex(mac[i * 3])? << 4) | nibble_from_hex(mac[i * 3 + 1])? + } + Ok(res) +} + +fn trim_nulls(s: &mut String) { + while s.chars().rev().next() == Some(0 as char) { + s.pop(); + } +} diff --git a/embassy/embassy-net-esp-hosted/src/esp_hosted_config.proto b/embassy/embassy-net-esp-hosted/src/esp_hosted_config.proto new file mode 100644 index 0000000..aa1bfde --- /dev/null +++ b/embassy/embassy-net-esp-hosted/src/esp_hosted_config.proto @@ -0,0 +1,432 @@ +syntax = "proto3"; + +/* Enums similar to ESP IDF */ +enum Ctrl_VendorIEType { + Beacon = 0; + Probe_req = 1; + Probe_resp = 2; + Assoc_req = 3; + Assoc_resp = 4; +} + +enum Ctrl_VendorIEID { + ID_0 = 0; + ID_1 = 1; +} + +enum Ctrl_WifiMode { + NONE = 0; + STA = 1; + AP = 2; + APSTA = 3; +} + +enum Ctrl_WifiBw { + BW_Invalid = 0; + HT20 = 1; + HT40 = 2; +} + +enum Ctrl_WifiPowerSave { + PS_Invalid = 0; + MIN_MODEM = 1; + MAX_MODEM = 2; +} + +enum Ctrl_WifiSecProt { + Open = 0; + WEP = 1; + WPA_PSK = 2; + WPA2_PSK = 3; + WPA_WPA2_PSK = 4; + WPA2_ENTERPRISE = 5; + WPA3_PSK = 6; + WPA2_WPA3_PSK = 7; +} + +/* enums for Control path */ +enum Ctrl_Status { + Connected = 0; + Not_Connected = 1; + No_AP_Found = 2; + Connection_Fail = 3; + Invalid_Argument = 4; + Out_Of_Range = 5; +} + + +enum CtrlMsgType { + MsgType_Invalid = 0; + Req = 1; + Resp = 2; + Event = 3; + MsgType_Max = 4; +} + +enum CtrlMsgId { + MsgId_Invalid = 0; + + /** Request Msgs **/ + Req_Base = 100; + + Req_GetMACAddress = 101; + Req_SetMacAddress = 102; + Req_GetWifiMode = 103; + Req_SetWifiMode = 104; + + Req_GetAPScanList = 105; + Req_GetAPConfig = 106; + Req_ConnectAP = 107; + Req_DisconnectAP = 108; + + Req_GetSoftAPConfig = 109; + Req_SetSoftAPVendorSpecificIE = 110; + Req_StartSoftAP = 111; + Req_GetSoftAPConnectedSTAList = 112; + Req_StopSoftAP = 113; + + Req_SetPowerSaveMode = 114; + Req_GetPowerSaveMode = 115; + + Req_OTABegin = 116; + Req_OTAWrite = 117; + Req_OTAEnd = 118; + + Req_SetWifiMaxTxPower = 119; + Req_GetWifiCurrTxPower = 120; + + Req_ConfigHeartbeat = 121; + /* Add new control path command response before Req_Max + * and update Req_Max */ + Req_Max = 122; + + /** Response Msgs **/ + Resp_Base = 200; + + Resp_GetMACAddress = 201; + Resp_SetMacAddress = 202; + Resp_GetWifiMode = 203; + Resp_SetWifiMode = 204; + + Resp_GetAPScanList = 205; + Resp_GetAPConfig = 206; + Resp_ConnectAP = 207; + Resp_DisconnectAP = 208; + + Resp_GetSoftAPConfig = 209; + Resp_SetSoftAPVendorSpecificIE = 210; + Resp_StartSoftAP = 211; + Resp_GetSoftAPConnectedSTAList = 212; + Resp_StopSoftAP = 213; + + Resp_SetPowerSaveMode = 214; + Resp_GetPowerSaveMode = 215; + + Resp_OTABegin = 216; + Resp_OTAWrite = 217; + Resp_OTAEnd = 218; + + Resp_SetWifiMaxTxPower = 219; + Resp_GetWifiCurrTxPower = 220; + + Resp_ConfigHeartbeat = 221; + /* Add new control path command response before Resp_Max + * and update Resp_Max */ + Resp_Max = 222; + + /** Event Msgs **/ + Event_Base = 300; + Event_ESPInit = 301; + Event_Heartbeat = 302; + Event_StationDisconnectFromAP = 303; + Event_StationDisconnectFromESPSoftAP = 304; + /* Add new control path command notification before Event_Max + * and update Event_Max */ + Event_Max = 305; +} + +/* internal supporting structures for CtrlMsg */ +message ScanResult { + bytes ssid = 1; + uint32 chnl = 2; + int32 rssi = 3; + bytes bssid = 4; + Ctrl_WifiSecProt sec_prot = 5; +} + +message ConnectedSTAList { + bytes mac = 1; + int32 rssi = 2; +} + + +/* Control path structures */ +/** Req/Resp structure **/ +message CtrlMsg_Req_GetMacAddress { + int32 mode = 1; +} + +message CtrlMsg_Resp_GetMacAddress { + bytes mac = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_GetMode { +} + +message CtrlMsg_Resp_GetMode { + int32 mode = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_SetMode { + int32 mode = 1; +} + +message CtrlMsg_Resp_SetMode { + int32 resp = 1; +} + +message CtrlMsg_Req_GetStatus { +} + +message CtrlMsg_Resp_GetStatus { + int32 resp = 1; +} + +message CtrlMsg_Req_SetMacAddress { + bytes mac = 1; + int32 mode = 2; +} + +message CtrlMsg_Resp_SetMacAddress { + int32 resp = 1; +} + +message CtrlMsg_Req_GetAPConfig { +} + +message CtrlMsg_Resp_GetAPConfig { + bytes ssid = 1; + bytes bssid = 2; + int32 rssi = 3; + int32 chnl = 4; + Ctrl_WifiSecProt sec_prot = 5; + int32 resp = 6; +} + +message CtrlMsg_Req_ConnectAP { + string ssid = 1; + string pwd = 2; + string bssid = 3; + bool is_wpa3_supported = 4; + int32 listen_interval = 5; +} + +message CtrlMsg_Resp_ConnectAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg_Req_GetSoftAPConfig { +} + +message CtrlMsg_Resp_GetSoftAPConfig { + bytes ssid = 1; + bytes pwd = 2; + int32 chnl = 3; + Ctrl_WifiSecProt sec_prot = 4; + int32 max_conn = 5; + bool ssid_hidden = 6; + int32 bw = 7; + int32 resp = 8; +} + +message CtrlMsg_Req_StartSoftAP { + string ssid = 1; + string pwd = 2; + int32 chnl = 3; + Ctrl_WifiSecProt sec_prot = 4; + int32 max_conn = 5; + bool ssid_hidden = 6; + int32 bw = 7; +} + +message CtrlMsg_Resp_StartSoftAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg_Req_ScanResult { +} + +message CtrlMsg_Resp_ScanResult { + uint32 count = 1; + repeated ScanResult entries = 2; + int32 resp = 3; +} + +message CtrlMsg_Req_SoftAPConnectedSTA { +} + +message CtrlMsg_Resp_SoftAPConnectedSTA { + uint32 num = 1; + repeated ConnectedSTAList stations = 2; + int32 resp = 3; +} + +message CtrlMsg_Req_OTABegin { +} + +message CtrlMsg_Resp_OTABegin { + int32 resp = 1; +} + +message CtrlMsg_Req_OTAWrite { + bytes ota_data = 1; +} + +message CtrlMsg_Resp_OTAWrite { + int32 resp = 1; +} + +message CtrlMsg_Req_OTAEnd { +} + +message CtrlMsg_Resp_OTAEnd { + int32 resp = 1; +} + +message CtrlMsg_Req_VendorIEData { + int32 element_id = 1; + int32 length = 2; + bytes vendor_oui = 3; + int32 vendor_oui_type = 4; + bytes payload = 5; +} + +message CtrlMsg_Req_SetSoftAPVendorSpecificIE { + bool enable = 1; + Ctrl_VendorIEType type = 2; + Ctrl_VendorIEID idx = 3; + CtrlMsg_Req_VendorIEData vendor_ie_data = 4; +} + +message CtrlMsg_Resp_SetSoftAPVendorSpecificIE { + int32 resp = 1; +} + +message CtrlMsg_Req_SetWifiMaxTxPower { + int32 wifi_max_tx_power = 1; +} + +message CtrlMsg_Resp_SetWifiMaxTxPower { + int32 resp = 1; +} + +message CtrlMsg_Req_GetWifiCurrTxPower { +} + +message CtrlMsg_Resp_GetWifiCurrTxPower { + int32 wifi_curr_tx_power = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_ConfigHeartbeat { + bool enable = 1; + int32 duration = 2; +} + +message CtrlMsg_Resp_ConfigHeartbeat { + int32 resp = 1; +} + +/** Event structure **/ +message CtrlMsg_Event_ESPInit { + bytes init_data = 1; +} + +message CtrlMsg_Event_Heartbeat { + int32 hb_num = 1; +} + +message CtrlMsg_Event_StationDisconnectFromAP { + int32 resp = 1; +} + +message CtrlMsg_Event_StationDisconnectFromESPSoftAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg { + /* msg_type could be req, resp or Event */ + CtrlMsgType msg_type = 1; + + /* msg id */ + CtrlMsgId msg_id = 2; + + /* union of all msg ids */ + oneof payload { + /** Requests **/ + CtrlMsg_Req_GetMacAddress req_get_mac_address = 101; + CtrlMsg_Req_SetMacAddress req_set_mac_address = 102; + CtrlMsg_Req_GetMode req_get_wifi_mode = 103; + CtrlMsg_Req_SetMode req_set_wifi_mode = 104; + + CtrlMsg_Req_ScanResult req_scan_ap_list = 105; + CtrlMsg_Req_GetAPConfig req_get_ap_config = 106; + CtrlMsg_Req_ConnectAP req_connect_ap = 107; + CtrlMsg_Req_GetStatus req_disconnect_ap = 108; + + CtrlMsg_Req_GetSoftAPConfig req_get_softap_config = 109; + CtrlMsg_Req_SetSoftAPVendorSpecificIE req_set_softap_vendor_specific_ie = 110; + CtrlMsg_Req_StartSoftAP req_start_softap = 111; + CtrlMsg_Req_SoftAPConnectedSTA req_softap_connected_stas_list = 112; + CtrlMsg_Req_GetStatus req_stop_softap = 113; + + CtrlMsg_Req_SetMode req_set_power_save_mode = 114; + CtrlMsg_Req_GetMode req_get_power_save_mode = 115; + + CtrlMsg_Req_OTABegin req_ota_begin = 116; + CtrlMsg_Req_OTAWrite req_ota_write = 117; + CtrlMsg_Req_OTAEnd req_ota_end = 118; + + CtrlMsg_Req_SetWifiMaxTxPower req_set_wifi_max_tx_power = 119; + CtrlMsg_Req_GetWifiCurrTxPower req_get_wifi_curr_tx_power = 120; + CtrlMsg_Req_ConfigHeartbeat req_config_heartbeat = 121; + + /** Responses **/ + CtrlMsg_Resp_GetMacAddress resp_get_mac_address = 201; + CtrlMsg_Resp_SetMacAddress resp_set_mac_address = 202; + CtrlMsg_Resp_GetMode resp_get_wifi_mode = 203; + CtrlMsg_Resp_SetMode resp_set_wifi_mode = 204; + + CtrlMsg_Resp_ScanResult resp_scan_ap_list = 205; + CtrlMsg_Resp_GetAPConfig resp_get_ap_config = 206; + CtrlMsg_Resp_ConnectAP resp_connect_ap = 207; + CtrlMsg_Resp_GetStatus resp_disconnect_ap = 208; + + CtrlMsg_Resp_GetSoftAPConfig resp_get_softap_config = 209; + CtrlMsg_Resp_SetSoftAPVendorSpecificIE resp_set_softap_vendor_specific_ie = 210; + CtrlMsg_Resp_StartSoftAP resp_start_softap = 211; + CtrlMsg_Resp_SoftAPConnectedSTA resp_softap_connected_stas_list = 212; + CtrlMsg_Resp_GetStatus resp_stop_softap = 213; + + CtrlMsg_Resp_SetMode resp_set_power_save_mode = 214; + CtrlMsg_Resp_GetMode resp_get_power_save_mode = 215; + + CtrlMsg_Resp_OTABegin resp_ota_begin = 216; + CtrlMsg_Resp_OTAWrite resp_ota_write = 217; + CtrlMsg_Resp_OTAEnd resp_ota_end = 218; + CtrlMsg_Resp_SetWifiMaxTxPower resp_set_wifi_max_tx_power = 219; + CtrlMsg_Resp_GetWifiCurrTxPower resp_get_wifi_curr_tx_power = 220; + CtrlMsg_Resp_ConfigHeartbeat resp_config_heartbeat = 221; + + /** Notifications **/ + CtrlMsg_Event_ESPInit event_esp_init = 301; + CtrlMsg_Event_Heartbeat event_heartbeat = 302; + CtrlMsg_Event_StationDisconnectFromAP event_station_disconnect_from_AP = 303; + CtrlMsg_Event_StationDisconnectFromESPSoftAP event_station_disconnect_from_ESP_SoftAP = 304; + } +} diff --git a/embassy/embassy-net-esp-hosted/src/fmt.rs b/embassy/embassy-net-esp-hosted/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-net-esp-hosted/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-net-esp-hosted/src/ioctl.rs b/embassy/embassy-net-esp-hosted/src/ioctl.rs new file mode 100644 index 0000000..e2a6815 --- /dev/null +++ b/embassy/embassy-net-esp-hosted/src/ioctl.rs @@ -0,0 +1,123 @@ +use core::cell::RefCell; +use core::future::poll_fn; +use core::task::Poll; + +use embassy_sync::waitqueue::WakerRegistration; + +use crate::fmt::Bytes; + +#[derive(Clone, Copy)] +pub struct PendingIoctl { + pub buf: *mut [u8], + pub req_len: usize, +} + +#[derive(Clone, Copy)] +enum IoctlState { + Pending(PendingIoctl), + Sent { buf: *mut [u8] }, + Done { resp_len: usize }, +} + +pub struct Shared(RefCell); + +struct SharedInner { + ioctl: IoctlState, + is_init: bool, + control_waker: WakerRegistration, + runner_waker: WakerRegistration, +} + +impl Shared { + pub fn new() -> Self { + Self(RefCell::new(SharedInner { + ioctl: IoctlState::Done { resp_len: 0 }, + is_init: false, + control_waker: WakerRegistration::new(), + runner_waker: WakerRegistration::new(), + })) + } + + pub async fn ioctl_wait_complete(&self) -> usize { + poll_fn(|cx| { + let mut this = self.0.borrow_mut(); + if let IoctlState::Done { resp_len } = this.ioctl { + Poll::Ready(resp_len) + } else { + this.control_waker.register(cx.waker()); + Poll::Pending + } + }) + .await + } + + pub async fn ioctl_wait_pending(&self) -> PendingIoctl { + let pending = poll_fn(|cx| { + let mut this = self.0.borrow_mut(); + if let IoctlState::Pending(pending) = this.ioctl { + Poll::Ready(pending) + } else { + this.runner_waker.register(cx.waker()); + Poll::Pending + } + }) + .await; + + self.0.borrow_mut().ioctl = IoctlState::Sent { buf: pending.buf }; + pending + } + + pub fn ioctl_cancel(&self) { + self.0.borrow_mut().ioctl = IoctlState::Done { resp_len: 0 }; + } + + pub async fn ioctl(&self, buf: &mut [u8], req_len: usize) -> usize { + trace!("ioctl req bytes: {:02x}", Bytes(&buf[..req_len])); + + { + let mut this = self.0.borrow_mut(); + this.ioctl = IoctlState::Pending(PendingIoctl { buf, req_len }); + this.runner_waker.wake(); + } + + self.ioctl_wait_complete().await + } + + pub fn ioctl_done(&self, response: &[u8]) { + let mut this = self.0.borrow_mut(); + if let IoctlState::Sent { buf } = this.ioctl { + trace!("ioctl resp bytes: {:02x}", Bytes(response)); + + // TODO fix this + (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); + + this.ioctl = IoctlState::Done { + resp_len: response.len(), + }; + this.control_waker.wake(); + } else { + warn!("IOCTL Response but no pending Ioctl"); + } + } + + // // // // // // // // // // // // // // // // // // // // + + pub fn init_done(&self) { + let mut this = self.0.borrow_mut(); + this.is_init = true; + this.control_waker.wake(); + } + + pub async fn init_wait(&self) { + poll_fn(|cx| { + let mut this = self.0.borrow_mut(); + if this.is_init { + Poll::Ready(()) + } else { + this.control_waker.register(cx.waker()); + Poll::Pending + } + }) + .await + } +} diff --git a/embassy/embassy-net-esp-hosted/src/lib.rs b/embassy/embassy-net-esp-hosted/src/lib.rs new file mode 100644 index 0000000..f05e2a7 --- /dev/null +++ b/embassy/embassy-net-esp-hosted/src/lib.rs @@ -0,0 +1,359 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +use embassy_futures::select::{select4, Either4}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embassy_time::{Duration, Instant, Timer}; +use embedded_hal::digital::{InputPin, OutputPin}; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiDevice; + +use crate::ioctl::{PendingIoctl, Shared}; +use crate::proto::{CtrlMsg, CtrlMsgPayload}; + +mod proto; + +// must be first +mod fmt; + +mod control; +mod ioctl; + +pub use control::*; + +const MTU: usize = 1514; + +macro_rules! impl_bytes { + ($t:ident) => { + impl $t { + pub const SIZE: usize = core::mem::size_of::(); + + #[allow(unused)] + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + unsafe { core::mem::transmute(*self) } + } + + #[allow(unused)] + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + unsafe { core::mem::transmute(bytes) } + } + + #[allow(unused)] + pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + + unsafe { core::mem::transmute(bytes) } + } + } + }; +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +struct PayloadHeader { + /// InterfaceType on lower 4 bits, number on higher 4 bits. + if_type_and_num: u8, + + /// Flags. + /// + /// bit 0: more fragments. + flags: u8, + + len: u16, + offset: u16, + checksum: u16, + seq_num: u16, + reserved2: u8, + + /// Packet type for HCI or PRIV interface, reserved otherwise + hci_priv_packet_type: u8, +} +impl_bytes!(PayloadHeader); + +#[allow(unused)] +#[repr(u8)] +enum InterfaceType { + Sta = 0, + Ap = 1, + Serial = 2, + Hci = 3, + Priv = 4, + Test = 5, +} + +const MAX_SPI_BUFFER_SIZE: usize = 1600; +const HEARTBEAT_MAX_GAP: Duration = Duration::from_secs(20); + +/// State for the esp-hosted driver. +pub struct State { + shared: Shared, + ch: ch::State, +} + +impl State { + /// Create a new state. + pub fn new() -> Self { + Self { + shared: Shared::new(), + ch: ch::State::new(), + } + } +} + +/// Type alias for network driver. +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +/// Create a new esp-hosted driver using the provided state, SPI peripheral and pins. +/// +/// Returns a device handle for interfacing with embassy-net, a control handle for +/// interacting with the driver, and a runner for communicating with the WiFi device. +pub async fn new<'a, SPI, IN, OUT>( + state: &'a mut State, + spi: SPI, + handshake: IN, + ready: IN, + reset: OUT, +) -> (NetDriver<'a>, Control<'a>, Runner<'a, SPI, IN, OUT>) +where + SPI: SpiDevice, + IN: InputPin + Wait, + OUT: OutputPin, +{ + let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); + let state_ch = ch_runner.state_runner(); + + let runner = Runner { + ch: ch_runner, + state_ch, + shared: &state.shared, + next_seq: 1, + handshake, + ready, + reset, + spi, + heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP, + }; + + (device, Control::new(state_ch, &state.shared), runner) +} + +/// Runner for communicating with the WiFi device. +pub struct Runner<'a, SPI, IN, OUT> { + ch: ch::Runner<'a, MTU>, + state_ch: ch::StateRunner<'a>, + shared: &'a Shared, + + next_seq: u16, + heartbeat_deadline: Instant, + + spi: SPI, + handshake: IN, + ready: IN, + reset: OUT, +} + +impl<'a, SPI, IN, OUT> Runner<'a, SPI, IN, OUT> +where + SPI: SpiDevice, + IN: InputPin + Wait, + OUT: OutputPin, +{ + /// Run the packet processing. + pub async fn run(mut self) -> ! { + debug!("resetting..."); + self.reset.set_low().unwrap(); + Timer::after_millis(100).await; + self.reset.set_high().unwrap(); + Timer::after_millis(1000).await; + + let mut tx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; + let mut rx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; + + loop { + self.handshake.wait_for_high().await.unwrap(); + + let ioctl = self.shared.ioctl_wait_pending(); + let tx = self.ch.tx_buf(); + let ev = async { self.ready.wait_for_high().await.unwrap() }; + let hb = Timer::at(self.heartbeat_deadline); + + match select4(ioctl, tx, ev, hb).await { + Either4::First(PendingIoctl { buf, req_len }) => { + tx_buf[12..24].copy_from_slice(b"\x01\x08\x00ctrlResp\x02"); + tx_buf[24..26].copy_from_slice(&(req_len as u16).to_le_bytes()); + tx_buf[26..][..req_len].copy_from_slice(&unsafe { &*buf }[..req_len]); + + let mut header = PayloadHeader { + if_type_and_num: InterfaceType::Serial as _, + len: (req_len + 14) as _, + offset: PayloadHeader::SIZE as _, + seq_num: self.next_seq, + ..Default::default() + }; + self.next_seq = self.next_seq.wrapping_add(1); + + // Calculate checksum + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + header.checksum = checksum(&tx_buf[..26 + req_len]); + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + } + Either4::Second(packet) => { + tx_buf[12..][..packet.len()].copy_from_slice(packet); + + let mut header = PayloadHeader { + if_type_and_num: InterfaceType::Sta as _, + len: packet.len() as _, + offset: PayloadHeader::SIZE as _, + seq_num: self.next_seq, + ..Default::default() + }; + self.next_seq = self.next_seq.wrapping_add(1); + + // Calculate checksum + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + header.checksum = checksum(&tx_buf[..12 + packet.len()]); + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + + self.ch.tx_done(); + } + Either4::Third(()) => { + tx_buf[..PayloadHeader::SIZE].fill(0); + } + Either4::Fourth(()) => { + panic!("heartbeat from esp32 stopped") + } + } + + if tx_buf[0] != 0 { + trace!("tx: {:02x}", &tx_buf[..40]); + } + + self.spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + + // The esp-hosted firmware deasserts the HANSHAKE pin a few us AFTER ending the SPI transfer + // If we check it again too fast, we'll see it's high from the previous transfer, and if we send it + // data it will get lost. + // Make sure we check it after 100us at minimum. + let delay_until = Instant::now() + Duration::from_micros(100); + self.handle_rx(&mut rx_buf); + Timer::at(delay_until).await; + } + } + + fn handle_rx(&mut self, buf: &mut [u8]) { + trace!("rx: {:02x}", &buf[..40]); + + let buf_len = buf.len(); + let h = PayloadHeader::from_bytes_mut((&mut buf[..PayloadHeader::SIZE]).try_into().unwrap()); + + if h.len == 0 || h.offset as usize != PayloadHeader::SIZE { + return; + } + + let payload_len = h.len as usize; + if buf_len < PayloadHeader::SIZE + payload_len { + warn!("rx: len too big"); + return; + } + + let if_type_and_num = h.if_type_and_num; + let want_checksum = h.checksum; + h.checksum = 0; + let got_checksum = checksum(&buf[..PayloadHeader::SIZE + payload_len]); + if want_checksum != got_checksum { + warn!("rx: bad checksum. Got {:04x}, want {:04x}", got_checksum, want_checksum); + return; + } + + let payload = &mut buf[PayloadHeader::SIZE..][..payload_len]; + + match if_type_and_num & 0x0f { + // STA + 0 => match self.ch.try_rx_buf() { + Some(buf) => { + buf[..payload.len()].copy_from_slice(payload); + self.ch.rx_done(payload.len()) + } + None => warn!("failed to push rxd packet to the channel."), + }, + // serial + 2 => { + trace!("serial rx: {:02x}", payload); + if payload.len() < 14 { + warn!("serial rx: too short"); + return; + } + + let is_event = match &payload[..12] { + b"\x01\x08\x00ctrlResp\x02" => false, + b"\x01\x08\x00ctrlEvnt\x02" => true, + _ => { + warn!("serial rx: bad tlv"); + return; + } + }; + + let len = u16::from_le_bytes(payload[12..14].try_into().unwrap()) as usize; + if payload.len() < 14 + len { + warn!("serial rx: too short 2"); + return; + } + let data = &payload[14..][..len]; + + if is_event { + self.handle_event(data); + } else { + self.shared.ioctl_done(data); + } + } + _ => warn!("unknown iftype {}", if_type_and_num), + } + } + + fn handle_event(&mut self, data: &[u8]) { + let Ok(event) = noproto::read::(data) else { + warn!("failed to parse event"); + return; + }; + + debug!("event: {:?}", &event); + + let Some(payload) = &event.payload else { + warn!("event without payload?"); + return; + }; + + match payload { + CtrlMsgPayload::EventEspInit(_) => self.shared.init_done(), + CtrlMsgPayload::EventHeartbeat(_) => self.heartbeat_deadline = Instant::now() + HEARTBEAT_MAX_GAP, + CtrlMsgPayload::EventStationDisconnectFromAp(e) => { + info!("disconnected, code {}", e.resp); + self.state_ch.set_link_state(LinkState::Down); + } + _ => {} + } + } +} + +fn checksum(buf: &[u8]) -> u16 { + let mut res = 0u16; + for &b in buf { + res = res.wrapping_add(b as _); + } + res +} diff --git a/embassy/embassy-net-esp-hosted/src/proto.rs b/embassy/embassy-net-esp-hosted/src/proto.rs new file mode 100644 index 0000000..089ded6 --- /dev/null +++ b/embassy/embassy-net-esp-hosted/src/proto.rs @@ -0,0 +1,656 @@ +#![allow(unused)] + +use heapless::{String, Vec}; + +/// internal supporting structures for CtrlMsg + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct ScanResult { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub chnl: u32, + #[noproto(tag = "3")] + pub rssi: u32, + #[noproto(tag = "4")] + pub bssid: String<32>, + #[noproto(tag = "5")] + pub sec_prot: CtrlWifiSecProt, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct ConnectedStaList { + #[noproto(tag = "1")] + pub mac: String<32>, + #[noproto(tag = "2")] + pub rssi: u32, +} +/// * Req/Resp structure * + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqGetMacAddress { + #[noproto(tag = "1")] + pub mode: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespGetMacAddress { + #[noproto(tag = "1")] + pub mac: String<32>, + #[noproto(tag = "2")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqGetMode {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespGetMode { + #[noproto(tag = "1")] + pub mode: u32, + #[noproto(tag = "2")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqSetMode { + #[noproto(tag = "1")] + pub mode: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespSetMode { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqGetStatus {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespGetStatus { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqSetMacAddress { + #[noproto(tag = "1")] + pub mac: String<32>, + #[noproto(tag = "2")] + pub mode: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespSetMacAddress { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqGetApConfig {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespGetApConfig { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub bssid: String<32>, + #[noproto(tag = "3")] + pub rssi: u32, + #[noproto(tag = "4")] + pub chnl: u32, + #[noproto(tag = "5")] + pub sec_prot: CtrlWifiSecProt, + #[noproto(tag = "6")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqConnectAp { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub pwd: String<32>, + #[noproto(tag = "3")] + pub bssid: String<32>, + #[noproto(tag = "4")] + pub is_wpa3_supported: bool, + #[noproto(tag = "5")] + pub listen_interval: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespConnectAp { + #[noproto(tag = "1")] + pub resp: u32, + #[noproto(tag = "2")] + pub mac: String<32>, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqGetSoftApConfig {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespGetSoftApConfig { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub pwd: String<32>, + #[noproto(tag = "3")] + pub chnl: u32, + #[noproto(tag = "4")] + pub sec_prot: CtrlWifiSecProt, + #[noproto(tag = "5")] + pub max_conn: u32, + #[noproto(tag = "6")] + pub ssid_hidden: bool, + #[noproto(tag = "7")] + pub bw: u32, + #[noproto(tag = "8")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqStartSoftAp { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub pwd: String<32>, + #[noproto(tag = "3")] + pub chnl: u32, + #[noproto(tag = "4")] + pub sec_prot: CtrlWifiSecProt, + #[noproto(tag = "5")] + pub max_conn: u32, + #[noproto(tag = "6")] + pub ssid_hidden: bool, + #[noproto(tag = "7")] + pub bw: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespStartSoftAp { + #[noproto(tag = "1")] + pub resp: u32, + #[noproto(tag = "2")] + pub mac: String<32>, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqScanResult {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespScanResult { + #[noproto(tag = "1")] + pub count: u32, + #[noproto(repeated, tag = "2")] + pub entries: Vec, + #[noproto(tag = "3")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqSoftApConnectedSta {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespSoftApConnectedSta { + #[noproto(tag = "1")] + pub num: u32, + #[noproto(repeated, tag = "2")] + pub stations: Vec, + #[noproto(tag = "3")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqOtaBegin {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespOtaBegin { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqOtaWrite { + #[noproto(tag = "1")] + pub ota_data: Vec, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespOtaWrite { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqOtaEnd {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespOtaEnd { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqVendorIeData { + #[noproto(tag = "1")] + pub element_id: u32, + #[noproto(tag = "2")] + pub length: u32, + #[noproto(tag = "3")] + pub vendor_oui: Vec, + #[noproto(tag = "4")] + pub vendor_oui_type: u32, + #[noproto(tag = "5")] + pub payload: Vec, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqSetSoftApVendorSpecificIe { + #[noproto(tag = "1")] + pub enable: bool, + #[noproto(tag = "2")] + pub r#type: CtrlVendorIeType, + #[noproto(tag = "3")] + pub idx: CtrlVendorIeid, + #[noproto(optional, tag = "4")] + pub vendor_ie_data: Option, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespSetSoftApVendorSpecificIe { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqSetWifiMaxTxPower { + #[noproto(tag = "1")] + pub wifi_max_tx_power: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespSetWifiMaxTxPower { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqGetWifiCurrTxPower {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespGetWifiCurrTxPower { + #[noproto(tag = "1")] + pub wifi_curr_tx_power: u32, + #[noproto(tag = "2")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgReqConfigHeartbeat { + #[noproto(tag = "1")] + pub enable: bool, + #[noproto(tag = "2")] + pub duration: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgRespConfigHeartbeat { + #[noproto(tag = "1")] + pub resp: u32, +} +/// * Event structure * + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgEventEspInit { + #[noproto(tag = "1")] + pub init_data: Vec, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgEventHeartbeat { + #[noproto(tag = "1")] + pub hb_num: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgEventStationDisconnectFromAp { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsgEventStationDisconnectFromEspSoftAp { + #[noproto(tag = "1")] + pub resp: u32, + #[noproto(tag = "2")] + pub mac: String<32>, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct CtrlMsg { + /// msg_type could be req, resp or Event + #[noproto(tag = "1")] + pub msg_type: CtrlMsgType, + /// msg id + #[noproto(tag = "2")] + pub msg_id: CtrlMsgId, + /// union of all msg ids + #[noproto( + oneof, + tags = "101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 301, 302, 303, 304" + )] + pub payload: Option, +} + +/// union of all msg ids +#[derive(Debug, Clone, Eq, PartialEq, noproto::Oneof)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum CtrlMsgPayload { + /// * Requests * + #[noproto(tag = "101")] + ReqGetMacAddress(CtrlMsgReqGetMacAddress), + #[noproto(tag = "102")] + ReqSetMacAddress(CtrlMsgReqSetMacAddress), + #[noproto(tag = "103")] + ReqGetWifiMode(CtrlMsgReqGetMode), + #[noproto(tag = "104")] + ReqSetWifiMode(CtrlMsgReqSetMode), + #[noproto(tag = "105")] + ReqScanApList(CtrlMsgReqScanResult), + #[noproto(tag = "106")] + ReqGetApConfig(CtrlMsgReqGetApConfig), + #[noproto(tag = "107")] + ReqConnectAp(CtrlMsgReqConnectAp), + #[noproto(tag = "108")] + ReqDisconnectAp(CtrlMsgReqGetStatus), + #[noproto(tag = "109")] + ReqGetSoftapConfig(CtrlMsgReqGetSoftApConfig), + #[noproto(tag = "110")] + ReqSetSoftapVendorSpecificIe(CtrlMsgReqSetSoftApVendorSpecificIe), + #[noproto(tag = "111")] + ReqStartSoftap(CtrlMsgReqStartSoftAp), + #[noproto(tag = "112")] + ReqSoftapConnectedStasList(CtrlMsgReqSoftApConnectedSta), + #[noproto(tag = "113")] + ReqStopSoftap(CtrlMsgReqGetStatus), + #[noproto(tag = "114")] + ReqSetPowerSaveMode(CtrlMsgReqSetMode), + #[noproto(tag = "115")] + ReqGetPowerSaveMode(CtrlMsgReqGetMode), + #[noproto(tag = "116")] + ReqOtaBegin(CtrlMsgReqOtaBegin), + #[noproto(tag = "117")] + ReqOtaWrite(CtrlMsgReqOtaWrite), + #[noproto(tag = "118")] + ReqOtaEnd(CtrlMsgReqOtaEnd), + #[noproto(tag = "119")] + ReqSetWifiMaxTxPower(CtrlMsgReqSetWifiMaxTxPower), + #[noproto(tag = "120")] + ReqGetWifiCurrTxPower(CtrlMsgReqGetWifiCurrTxPower), + #[noproto(tag = "121")] + ReqConfigHeartbeat(CtrlMsgReqConfigHeartbeat), + /// * Responses * + #[noproto(tag = "201")] + RespGetMacAddress(CtrlMsgRespGetMacAddress), + #[noproto(tag = "202")] + RespSetMacAddress(CtrlMsgRespSetMacAddress), + #[noproto(tag = "203")] + RespGetWifiMode(CtrlMsgRespGetMode), + #[noproto(tag = "204")] + RespSetWifiMode(CtrlMsgRespSetMode), + #[noproto(tag = "205")] + RespScanApList(CtrlMsgRespScanResult), + #[noproto(tag = "206")] + RespGetApConfig(CtrlMsgRespGetApConfig), + #[noproto(tag = "207")] + RespConnectAp(CtrlMsgRespConnectAp), + #[noproto(tag = "208")] + RespDisconnectAp(CtrlMsgRespGetStatus), + #[noproto(tag = "209")] + RespGetSoftapConfig(CtrlMsgRespGetSoftApConfig), + #[noproto(tag = "210")] + RespSetSoftapVendorSpecificIe(CtrlMsgRespSetSoftApVendorSpecificIe), + #[noproto(tag = "211")] + RespStartSoftap(CtrlMsgRespStartSoftAp), + #[noproto(tag = "212")] + RespSoftapConnectedStasList(CtrlMsgRespSoftApConnectedSta), + #[noproto(tag = "213")] + RespStopSoftap(CtrlMsgRespGetStatus), + #[noproto(tag = "214")] + RespSetPowerSaveMode(CtrlMsgRespSetMode), + #[noproto(tag = "215")] + RespGetPowerSaveMode(CtrlMsgRespGetMode), + #[noproto(tag = "216")] + RespOtaBegin(CtrlMsgRespOtaBegin), + #[noproto(tag = "217")] + RespOtaWrite(CtrlMsgRespOtaWrite), + #[noproto(tag = "218")] + RespOtaEnd(CtrlMsgRespOtaEnd), + #[noproto(tag = "219")] + RespSetWifiMaxTxPower(CtrlMsgRespSetWifiMaxTxPower), + #[noproto(tag = "220")] + RespGetWifiCurrTxPower(CtrlMsgRespGetWifiCurrTxPower), + #[noproto(tag = "221")] + RespConfigHeartbeat(CtrlMsgRespConfigHeartbeat), + /// * Notifications * + #[noproto(tag = "301")] + EventEspInit(CtrlMsgEventEspInit), + #[noproto(tag = "302")] + EventHeartbeat(CtrlMsgEventHeartbeat), + #[noproto(tag = "303")] + EventStationDisconnectFromAp(CtrlMsgEventStationDisconnectFromAp), + #[noproto(tag = "304")] + EventStationDisconnectFromEspSoftAp(CtrlMsgEventStationDisconnectFromEspSoftAp), +} + +/// Enums similar to ESP IDF +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum CtrlVendorIeType { + #[default] + Beacon = 0, + ProbeReq = 1, + ProbeResp = 2, + AssocReq = 3, + AssocResp = 4, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum CtrlVendorIeid { + #[default] + Id0 = 0, + Id1 = 1, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum CtrlWifiMode { + #[default] + None = 0, + Sta = 1, + Ap = 2, + Apsta = 3, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum CtrlWifiBw { + #[default] + BwInvalid = 0, + Ht20 = 1, + Ht40 = 2, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum CtrlWifiPowerSave { + #[default] + PsInvalid = 0, + MinModem = 1, + MaxModem = 2, +} + +/// Wifi Security Settings +#[allow(missing_docs)] +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlWifiSecProt { + #[default] + Open = 0, + Wep = 1, + WpaPsk = 2, + Wpa2Psk = 3, + WpaWpa2Psk = 4, + Wpa2Enterprise = 5, + Wpa3Psk = 6, + Wpa2Wpa3Psk = 7, +} + +/// enums for Control path +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum CtrlStatus { + #[default] + Connected = 0, + NotConnected = 1, + NoApFound = 2, + ConnectionFail = 3, + InvalidArgument = 4, + OutOfRange = 5, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum CtrlMsgType { + #[default] + MsgTypeInvalid = 0, + Req = 1, + Resp = 2, + Event = 3, + MsgTypeMax = 4, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum CtrlMsgId { + #[default] + MsgIdInvalid = 0, + /// * Request Msgs * + ReqBase = 100, + ReqGetMacAddress = 101, + ReqSetMacAddress = 102, + ReqGetWifiMode = 103, + ReqSetWifiMode = 104, + ReqGetApScanList = 105, + ReqGetApConfig = 106, + ReqConnectAp = 107, + ReqDisconnectAp = 108, + ReqGetSoftApConfig = 109, + ReqSetSoftApVendorSpecificIe = 110, + ReqStartSoftAp = 111, + ReqGetSoftApConnectedStaList = 112, + ReqStopSoftAp = 113, + ReqSetPowerSaveMode = 114, + ReqGetPowerSaveMode = 115, + ReqOtaBegin = 116, + ReqOtaWrite = 117, + ReqOtaEnd = 118, + ReqSetWifiMaxTxPower = 119, + ReqGetWifiCurrTxPower = 120, + ReqConfigHeartbeat = 121, + /// Add new control path command response before Req_Max + /// and update Req_Max + ReqMax = 122, + /// * Response Msgs * + RespBase = 200, + RespGetMacAddress = 201, + RespSetMacAddress = 202, + RespGetWifiMode = 203, + RespSetWifiMode = 204, + RespGetApScanList = 205, + RespGetApConfig = 206, + RespConnectAp = 207, + RespDisconnectAp = 208, + RespGetSoftApConfig = 209, + RespSetSoftApVendorSpecificIe = 210, + RespStartSoftAp = 211, + RespGetSoftApConnectedStaList = 212, + RespStopSoftAp = 213, + RespSetPowerSaveMode = 214, + RespGetPowerSaveMode = 215, + RespOtaBegin = 216, + RespOtaWrite = 217, + RespOtaEnd = 218, + RespSetWifiMaxTxPower = 219, + RespGetWifiCurrTxPower = 220, + RespConfigHeartbeat = 221, + /// Add new control path command response before Resp_Max + /// and update Resp_Max + RespMax = 222, + /// * Event Msgs * + EventBase = 300, + EventEspInit = 301, + EventHeartbeat = 302, + EventStationDisconnectFromAp = 303, + EventStationDisconnectFromEspSoftAp = 304, + /// Add new control path command notification before Event_Max + /// and update Event_Max + EventMax = 305, +} diff --git a/embassy/embassy-net-nrf91/Cargo.toml b/embassy/embassy-net-nrf91/Cargo.toml new file mode 100644 index 0000000..f9e55c0 --- /dev/null +++ b/embassy/embassy-net-nrf91/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "embassy-net-nrf91" +version = "0.1.0" +edition = "2021" +description = "embassy-net driver for Nordic nRF91-series cellular modems" +keywords = ["embedded", "nrf91", "embassy-net", "cellular"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-nrf91" + +[features] +defmt = [ "dep:defmt", "heapless/defmt-03" ] +log = [ "dep:log" ] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +nrf-pac = { git = "https://github.com/embassy-rs/nrf-pac", rev = "52e3a757f06035c94291bfc42b0c03f71e4d677e" } +cortex-m = "0.7.7" + +embassy-time = { version = "0.3.1", path = "../embassy-time" } +embassy-sync = { version = "0.6.1", path = "../embassy-sync"} +embassy-futures = { version = "0.1.0", path = "../embassy-futures"} +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"} + +heapless = "0.8" +embedded-io = "0.6.1" +at-commands = "0.5.4" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-nrf91/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] diff --git a/embassy/embassy-net-nrf91/README.md b/embassy/embassy-net-nrf91/README.md new file mode 100644 index 0000000..30da717 --- /dev/null +++ b/embassy/embassy-net-nrf91/README.md @@ -0,0 +1,9 @@ +# nRF91 `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) driver for Nordic nRF91-series cellular modems. + +See the [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf9160) directory for usage examples with the nRF9160. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy/embassy-net-nrf91/src/context.rs b/embassy/embassy-net-nrf91/src/context.rs new file mode 100644 index 0000000..2dda615 --- /dev/null +++ b/embassy/embassy-net-nrf91/src/context.rs @@ -0,0 +1,362 @@ +//! Helper utility to configure a specific modem context. +use core::net::IpAddr; +use core::str::FromStr; + +use at_commands::builder::CommandBuilder; +use at_commands::parser::CommandParser; +use embassy_time::{Duration, Timer}; +use heapless::Vec; + +/// Provides a higher level API for controlling a given context. +pub struct Control<'a> { + control: crate::Control<'a>, + cid: u8, +} + +/// Configuration for a given context +pub struct Config<'a> { + /// Desired APN address. + pub apn: &'a [u8], + /// Desired authentication protocol. + pub auth_prot: AuthProt, + /// Credentials. + pub auth: Option<(&'a [u8], &'a [u8])>, + /// SIM pin + pub pin: Option<&'a [u8]>, +} + +/// Authentication protocol. +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum AuthProt { + /// No authentication. + None = 0, + /// PAP authentication. + Pap = 1, + /// CHAP authentication. + Chap = 2, +} + +/// Error returned by control. +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Not enough space for command. + BufferTooSmall, + /// Error parsing response from modem. + AtParseError, + /// Error parsing IP addresses. + AddrParseError, +} + +impl From for Error { + fn from(_: at_commands::parser::ParseError) -> Self { + Self::AtParseError + } +} + +/// Status of a given context. +#[derive(PartialEq, Debug)] +pub struct Status { + /// Attached to APN or not. + pub attached: bool, + /// IP if assigned. + pub ip: Option, + /// Gateway if assigned. + pub gateway: Option, + /// DNS servers if assigned. + pub dns: Vec, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Status { + fn format(&self, f: defmt::Formatter<'_>) { + defmt::write!(f, "attached: {}", self.attached); + if let Some(ip) = &self.ip { + defmt::write!(f, ", ip: {}", defmt::Debug2Format(&ip)); + } + } +} + +impl<'a> Control<'a> { + /// Create a new instance of a control handle for a given context. + /// + /// Will wait for the modem to be initialized if not. + pub async fn new(control: crate::Control<'a>, cid: u8) -> Self { + control.wait_init().await; + Self { control, cid } + } + + /// Perform a raw AT command + pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { + self.control.at_command(req, resp).await + } + + /// Configures the modem with the provided config. + /// + /// NOTE: This will disconnect the modem from any current APN and should not + /// be called if the configuration has not been changed. + /// + /// After configuring, invoke [`enable()`] to activate the configuration. + pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGDCONT") + .with_int_parameter(self.cid) + .with_string_parameter("IP") + .with_string_parameter(config.apn) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + // info!("RES1: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + let mut op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGAUTH") + .with_int_parameter(self.cid) + .with_int_parameter(config.auth_prot as u8); + if let Some((username, password)) = config.auth { + op = op.with_string_parameter(username).with_string_parameter(password); + } + let op = op.finish().map_err(|_| Error::BufferTooSmall)?; + + let n = self.control.at_command(op, &mut buf).await; + // info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + if let Some(pin) = config.pin { + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CPIN") + .with_string_parameter(pin) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let _ = self.control.at_command(op, &mut buf).await; + // Ignore ERROR which means no pin required + } + + Ok(()) + } + + /// Attach to the PDN + pub async fn attach(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGATT") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + /// Read current connectivity status for modem. + pub async fn detach(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGATT") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + async fn attached(&self) -> Result { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_query(&mut cmd, true) + .named("+CGATT") + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (res,) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGATT: ") + .expect_int_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + Ok(res == 1) + } + + /// Read current connectivity status for modem. + pub async fn status(&self) -> Result { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_query(&mut cmd, true) + .named("+CGATT") + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (res,) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGATT: ") + .expect_int_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + let attached = res == 1; + if !attached { + return Ok(Status { + attached, + ip: None, + gateway: None, + dns: Vec::new(), + }); + } + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGPADDR") + .with_int_parameter(self.cid) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (_, ip1, _ip2) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGPADDR: ") + .expect_int_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + + let ip = if let Some(ip) = ip1 { + let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?; + Some(ip) + } else { + None + }; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGCONTRDP") + .with_int_parameter(self.cid) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, _mtu) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGCONTRDP: ") + .expect_int_parameter() + .expect_optional_int_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + + let gateway = if let Some(ip) = gateway { + if ip.is_empty() { + None + } else { + Some(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) + } + } else { + None + }; + + let mut dns = Vec::new(); + if let Some(ip) = dns1 { + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) + .unwrap(); + } + + if let Some(ip) = dns2 { + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) + .unwrap(); + } + + Ok(Status { + attached, + ip, + gateway, + dns, + }) + } + + async fn wait_attached(&self) -> Result { + while !self.attached().await? { + Timer::after(Duration::from_secs(1)).await; + } + let status = self.status().await?; + Ok(status) + } + + /// Disable modem + pub async fn disable(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + Ok(()) + } + + /// Enable modem + pub async fn enable(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + // Make modem survive PDN detaches + let op = CommandBuilder::create_set(&mut cmd, true) + .named("%XPDNCFG") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + /// Run a control loop for this context, ensuring that reaattach is handled. + pub async fn run(&self, reattach: F) -> Result<(), Error> { + self.enable().await?; + let status = self.wait_attached().await?; + let mut fd = self.control.open_raw_socket().await; + reattach(&status); + + loop { + if !self.attached().await? { + trace!("detached"); + + self.control.close_raw_socket(fd).await; + let status = self.wait_attached().await?; + trace!("attached"); + fd = self.control.open_raw_socket().await; + reattach(&status); + } + Timer::after(Duration::from_secs(10)).await; + } + } +} diff --git a/embassy/embassy-net-nrf91/src/fmt.rs b/embassy/embassy-net-nrf91/src/fmt.rs new file mode 100644 index 0000000..35b929f --- /dev/null +++ b/embassy/embassy-net-nrf91/src/fmt.rs @@ -0,0 +1,274 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-net-nrf91/src/lib.rs b/embassy/embassy-net-nrf91/src/lib.rs new file mode 100644 index 0000000..3abe2c7 --- /dev/null +++ b/embassy/embassy-net-nrf91/src/lib.rs @@ -0,0 +1,1070 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] +#![deny(unused_must_use)] + +// must be first +mod fmt; + +pub mod context; + +use core::cell::RefCell; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::mem::{self, MaybeUninit}; +use core::ptr::{self, addr_of, addr_of_mut, copy_nonoverlapping}; +use core::slice; +use core::sync::atomic::{compiler_fence, fence, Ordering}; +use core::task::{Poll, Waker}; + +use cortex_m::peripheral::NVIC; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pipe; +use embassy_sync::waitqueue::{AtomicWaker, WakerRegistration}; +use heapless::Vec; +use {embassy_net_driver_channel as ch, nrf_pac as pac}; + +const RX_SIZE: usize = 8 * 1024; +const TRACE_SIZE: usize = 16 * 1024; +const TRACE_BUF: usize = 1024; +const MTU: usize = 1500; + +/// Network driver. +/// +/// This is the type you have to pass to `embassy-net` when creating the network stack. +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// Call this function on IPC IRQ +pub fn on_ipc_irq() { + trace!("irq"); + + pac::IPC_NS.inten().write(|_| ()); + WAKER.wake(); +} + +struct Allocator<'a> { + start: *mut u8, + end: *mut u8, + _phantom: PhantomData<&'a mut u8>, +} + +impl<'a> Allocator<'a> { + fn alloc_bytes(&mut self, size: usize) -> &'a mut [MaybeUninit] { + // safety: both pointers come from the same allocation. + let available_size = unsafe { self.end.offset_from(self.start) } as usize; + if size > available_size { + panic!("out of memory") + } + + // safety: we've checked above this doesn't go out of bounds. + let p = self.start; + self.start = unsafe { p.add(size) }; + + // safety: we've checked the pointer is in-bounds. + unsafe { slice::from_raw_parts_mut(p as *mut _, size) } + } + + fn alloc(&mut self) -> &'a mut MaybeUninit { + let align = mem::align_of::(); + let size = mem::size_of::(); + + let align_size = match (self.start as usize) % align { + 0 => 0, + n => align - n, + }; + + // safety: both pointers come from the same allocation. + let available_size = unsafe { self.end.offset_from(self.start) } as usize; + if align_size + size > available_size { + panic!("out of memory") + } + + // safety: we've checked above this doesn't go out of bounds. + let p = unsafe { self.start.add(align_size) }; + self.start = unsafe { p.add(size) }; + + // safety: we've checked the pointer is aligned and in-bounds. + unsafe { &mut *(p as *mut _) } + } +} + +/// Create a new nRF91 embassy-net driver. +pub async fn new<'a>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], +) -> (NetDriver<'a>, Control<'a>, Runner<'a>) { + let (n, c, r, _) = new_internal(state, shmem, None).await; + (n, c, r) +} + +/// Create a new nRF91 embassy-net driver with trace. +pub async fn new_with_trace<'a>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], + trace_buffer: &'a mut TraceBuffer, +) -> (NetDriver<'a>, Control<'a>, Runner<'a>, TraceReader<'a>) { + let (n, c, r, t) = new_internal(state, shmem, Some(trace_buffer)).await; + (n, c, r, t.unwrap()) +} + +/// Create a new nRF91 embassy-net driver. +async fn new_internal<'a>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], + trace_buffer: Option<&'a mut TraceBuffer>, +) -> (NetDriver<'a>, Control<'a>, Runner<'a>, Option>) { + let shmem_len = shmem.len(); + let shmem_ptr = shmem.as_mut_ptr() as *mut u8; + + const SPU_REGION_SIZE: usize = 8192; // 8kb + assert!(shmem_len != 0); + assert!( + shmem_len % SPU_REGION_SIZE == 0, + "shmem length must be a multiple of 8kb" + ); + assert!( + (shmem_ptr as usize) % SPU_REGION_SIZE == 0, + "shmem length must be a multiple of 8kb" + ); + assert!( + (shmem_ptr as usize + shmem_len) < 0x2002_0000, + "shmem must be in the lower 128kb of RAM" + ); + + let spu = pac::SPU_S; + debug!("Setting IPC RAM as nonsecure..."); + let region_start = (shmem_ptr as usize - 0x2000_0000) / SPU_REGION_SIZE; + let region_end = region_start + shmem_len / SPU_REGION_SIZE; + for i in region_start..region_end { + spu.ramregion(i).perm().write(|w| { + w.set_execute(true); + w.set_write(true); + w.set_read(true); + w.set_secattr(false); + w.set_lock(false); + }) + } + + spu.periphid(42).perm().write(|w| w.set_secattr(false)); + + let mut alloc = Allocator { + start: shmem_ptr, + end: unsafe { shmem_ptr.add(shmem_len) }, + _phantom: PhantomData, + }; + + let ipc = pac::IPC_NS; + let power = pac::POWER_S; + + let cb: &mut ControlBlock = alloc.alloc().write(unsafe { mem::zeroed() }); + let rx = alloc.alloc_bytes(RX_SIZE); + let trace = alloc.alloc_bytes(TRACE_SIZE); + + cb.version = 0x00010000; + cb.rx_base = rx.as_mut_ptr() as _; + cb.rx_size = RX_SIZE; + cb.control_list_ptr = &mut cb.lists[0]; + cb.data_list_ptr = &mut cb.lists[1]; + cb.modem_info_ptr = &mut cb.modem_info; + cb.trace_ptr = &mut cb.trace; + cb.lists[0].len = LIST_LEN; + cb.lists[1].len = LIST_LEN; + cb.trace.base = trace.as_mut_ptr() as _; + cb.trace.size = TRACE_SIZE; + + ipc.gpmem(0).write_value(cb as *mut _ as u32); + ipc.gpmem(1).write_value(0); + + // connect task/event i to channel i + for i in 0..8 { + ipc.send_cnf(i).write(|w| w.0 = 1 << i); + ipc.receive_cnf(i).write(|w| w.0 = 1 << i); + } + + compiler_fence(Ordering::SeqCst); + + // POWER.LTEMODEM.STARTN = 0 + // The reg is missing in the PAC?? + let startn = unsafe { (power.as_ptr() as *mut u32).add(0x610 / 4) }; + unsafe { startn.write_volatile(0) } + + unsafe { NVIC::unmask(pac::Interrupt::IPC) }; + + let state_inner = &*state.inner.write(RefCell::new(StateInner { + init: false, + init_waker: WakerRegistration::new(), + cb, + requests: [const { None }; REQ_COUNT], + next_req_serial: 0x12345678, + net_fd: None, + + rx_control_list: ptr::null_mut(), + rx_data_list: ptr::null_mut(), + rx_seq_no: 0, + rx_check: PointerChecker { + start: rx.as_mut_ptr() as *mut u8, + end: (rx.as_mut_ptr() as *mut u8).wrapping_add(RX_SIZE), + }, + + tx_seq_no: 0, + tx_buf_used: [false; TX_BUF_COUNT], + tx_waker: WakerRegistration::new(), + + trace_chans: Vec::new(), + trace_check: PointerChecker { + start: trace.as_mut_ptr() as *mut u8, + end: (trace.as_mut_ptr() as *mut u8).wrapping_add(TRACE_SIZE), + }, + })); + + let control = Control { state: state_inner }; + + let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ip); + let state_ch = ch_runner.state_runner(); + state_ch.set_link_state(ch::driver::LinkState::Up); + + let (trace_reader, trace_writer) = if let Some(trace) = trace_buffer { + let (r, w) = trace.trace.split(); + (Some(r), Some(w)) + } else { + (None, None) + }; + + let runner = Runner { + ch: ch_runner, + state: state_inner, + trace_writer, + }; + + (device, control, runner, trace_reader) +} + +/// State holding modem traces. +pub struct TraceBuffer { + trace: pipe::Pipe, +} + +/// Represents writer half of the trace buffer. +pub type TraceWriter<'a> = pipe::Writer<'a, NoopRawMutex, TRACE_BUF>; + +/// Represents the reader half of the trace buffer. +pub type TraceReader<'a> = pipe::Reader<'a, NoopRawMutex, TRACE_BUF>; + +impl TraceBuffer { + /// Create a new TraceBuffer. + pub const fn new() -> Self { + Self { + trace: pipe::Pipe::new(), + } + } +} + +/// Shared state for the driver. +pub struct State { + ch: ch::State, + inner: MaybeUninit>, +} + +impl State { + /// Create a new State. + pub const fn new() -> Self { + Self { + ch: ch::State::new(), + inner: MaybeUninit::uninit(), + } + } +} + +const TX_BUF_COUNT: usize = 4; +const TX_BUF_SIZE: usize = 1500; + +struct TraceChannelInfo { + ptr: *mut TraceChannel, + start: *mut u8, + end: *mut u8, +} + +const REQ_COUNT: usize = 4; + +struct PendingRequest { + req_serial: u32, + resp_msg: *mut Message, + waker: Waker, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct NoFreeBufs; + +struct StateInner { + init: bool, + init_waker: WakerRegistration, + + cb: *mut ControlBlock, + requests: [Option; REQ_COUNT], + next_req_serial: u32, + + net_fd: Option, + + rx_control_list: *mut List, + rx_data_list: *mut List, + rx_seq_no: u16, + rx_check: PointerChecker, + + tx_seq_no: u16, + tx_buf_used: [bool; TX_BUF_COUNT], + tx_waker: WakerRegistration, + + trace_chans: Vec, + trace_check: PointerChecker, +} + +impl StateInner { + fn poll(&mut self, trace_writer: &mut Option>, ch: &mut ch::Runner) { + trace!("poll!"); + let ipc = pac::IPC_NS; + + if ipc.events_receive(0).read() != 0 { + ipc.events_receive(0).write_value(0); + trace!("ipc 0"); + } + + if ipc.events_receive(2).read() != 0 { + ipc.events_receive(2).write_value(0); + trace!("ipc 2"); + + if !self.init { + let desc = unsafe { addr_of!((*self.cb).modem_info).read_volatile() }; + assert_eq!(desc.version, 1); + + self.rx_check.check_mut(desc.control_list_ptr); + self.rx_check.check_mut(desc.data_list_ptr); + + self.rx_control_list = desc.control_list_ptr; + self.rx_data_list = desc.data_list_ptr; + let rx_control_len = unsafe { addr_of!((*self.rx_control_list).len).read_volatile() }; + let rx_data_len = unsafe { addr_of!((*self.rx_data_list).len).read_volatile() }; + assert_eq!(rx_control_len, LIST_LEN); + assert_eq!(rx_data_len, LIST_LEN); + self.init = true; + + debug!("IPC initialized OK!"); + self.init_waker.wake(); + } + } + + if ipc.events_receive(4).read() != 0 { + ipc.events_receive(4).write_value(0); + trace!("ipc 4"); + + loop { + let list = unsafe { &mut *self.rx_control_list }; + let control_work = self.process(list, true, ch); + let list = unsafe { &mut *self.rx_data_list }; + let data_work = self.process(list, false, ch); + if !control_work && !data_work { + break; + } + } + } + + if ipc.events_receive(6).read() != 0 { + ipc.events_receive(6).write_value(0); + trace!("ipc 6"); + } + + if ipc.events_receive(7).read() != 0 { + ipc.events_receive(7).write_value(0); + trace!("ipc 7: trace"); + + let msg = unsafe { addr_of!((*self.cb).trace.rx_state).read_volatile() }; + if msg != 0 { + trace!("trace msg {}", msg); + match msg { + 0 => unreachable!(), + 1 => { + let ctx = unsafe { addr_of!((*self.cb).trace.rx_ptr).read_volatile() } as *mut TraceContext; + debug!("trace init: {:?}", ctx); + self.trace_check.check(ctx); + let chans = unsafe { addr_of!((*ctx).chans).read_volatile() }; + for chan_ptr in chans { + let chan = self.trace_check.check_read(chan_ptr); + self.trace_check.check(chan.start); + self.trace_check.check(chan.end); + assert!(chan.start < chan.end); + self.trace_chans + .push(TraceChannelInfo { + ptr: chan_ptr, + start: chan.start, + end: chan.end, + }) + .map_err(|_| ()) + .unwrap() + } + } + 2 => { + for chan_info in &self.trace_chans { + let read_ptr = unsafe { addr_of!((*chan_info.ptr).read_ptr).read_volatile() }; + let write_ptr = unsafe { addr_of!((*chan_info.ptr).write_ptr).read_volatile() }; + assert!(read_ptr >= chan_info.start && read_ptr <= chan_info.end); + assert!(write_ptr >= chan_info.start && write_ptr <= chan_info.end); + if read_ptr != write_ptr { + let id = unsafe { addr_of!((*chan_info.ptr).id).read_volatile() }; + fence(Ordering::SeqCst); // synchronize volatile accesses with the slice access. + if read_ptr < write_ptr { + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts(read_ptr, write_ptr.offset_from(read_ptr) as _) + }); + } else { + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts(read_ptr, chan_info.end.offset_from(read_ptr) as _) + }); + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts( + chan_info.start, + write_ptr.offset_from(chan_info.start) as _, + ) + }); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the slice access. + unsafe { addr_of_mut!((*chan_info.ptr).read_ptr).write_volatile(write_ptr) }; + } + } + } + _ => warn!("unknown trace msg {}", msg), + } + unsafe { addr_of_mut!((*self.cb).trace.rx_state).write_volatile(0) }; + } + } + + ipc.intenset().write(|w| { + w.set_receive0(true); + w.set_receive2(true); + w.set_receive4(true); + w.set_receive6(true); + w.set_receive7(true); + }); + } + + fn handle_trace(writer: &mut Option>, id: u8, data: &[u8]) { + if let Some(writer) = writer { + trace!("trace: {} {}", id, data.len()); + let mut header = [0u8; 5]; + header[0] = 0xEF; + header[1] = 0xBE; + header[2..4].copy_from_slice(&(data.len() as u16).to_le_bytes()); + header[4] = id; + writer.try_write(&header).ok(); + writer.try_write(data).ok(); + } + } + + fn process(&mut self, list: *mut List, is_control: bool, ch: &mut ch::Runner) -> bool { + let mut did_work = false; + for i in 0..LIST_LEN { + let item_ptr = unsafe { addr_of_mut!((*list).items[i]) }; + let preamble = unsafe { addr_of!((*item_ptr).state).read_volatile() }; + if preamble & 0xFF == 0x01 && preamble >> 16 == self.rx_seq_no as u32 { + let msg_ptr = unsafe { addr_of!((*item_ptr).message).read_volatile() }; + let msg = self.rx_check.check_read(msg_ptr); + + debug!("rx seq {} msg: {:?}", preamble >> 16, msg); + + if is_control { + self.handle_control(&msg); + } else { + self.handle_data(&msg, ch); + } + + unsafe { addr_of_mut!((*item_ptr).state).write_volatile(0x03) }; + self.rx_seq_no = self.rx_seq_no.wrapping_add(1); + + did_work = true; + } + } + did_work + } + + fn find_free_message(&mut self, ch: usize) -> Option { + for i in 0..LIST_LEN { + let preamble = unsafe { addr_of!((*self.cb).lists[ch].items[i].state).read_volatile() }; + if matches!(preamble & 0xFF, 0 | 3) { + trace!("using tx msg idx {}", i); + return Some(i); + } + } + return None; + } + + fn find_free_tx_buf(&mut self) -> Option { + for i in 0..TX_BUF_COUNT { + if !self.tx_buf_used[i] { + trace!("using tx buf idx {}", i); + return Some(i); + } + } + return None; + } + + fn send_message(&mut self, msg: &mut Message, data: &[u8]) -> Result<(), NoFreeBufs> { + if data.is_empty() { + msg.data = ptr::null_mut(); + msg.data_len = 0; + self.send_message_raw(msg) + } else { + assert!(data.len() <= TX_BUF_SIZE); + let buf_idx = self.find_free_tx_buf().ok_or(NoFreeBufs)?; + let buf = unsafe { addr_of_mut!((*self.cb).tx_bufs[buf_idx]) } as *mut u8; + unsafe { copy_nonoverlapping(data.as_ptr(), buf, data.len()) } + msg.data = buf; + msg.data_len = data.len(); + self.tx_buf_used[buf_idx] = true; + + fence(Ordering::SeqCst); // synchronize copy_nonoverlapping (non-volatile) with volatile writes below. + if let Err(e) = self.send_message_raw(msg) { + msg.data = ptr::null_mut(); + msg.data_len = 0; + self.tx_buf_used[buf_idx] = false; + self.tx_waker.wake(); + Err(e) + } else { + Ok(()) + } + } + } + + fn send_message_raw(&mut self, msg: &Message) -> Result<(), NoFreeBufs> { + let (ch, ipc_ch) = match msg.channel { + 1 => (0, 1), // control + 2 => (1, 3), // data + _ => unreachable!(), + }; + + // allocate a msg. + let idx = self.find_free_message(ch).ok_or(NoFreeBufs)?; + + debug!("tx seq {} msg: {:?}", self.tx_seq_no, msg); + + let msg_slot = unsafe { addr_of_mut!((*self.cb).msgs[ch][idx]) }; + unsafe { msg_slot.write_volatile(*msg) } + let list_item = unsafe { addr_of_mut!((*self.cb).lists[ch].items[idx]) }; + unsafe { addr_of_mut!((*list_item).message).write_volatile(msg_slot) } + unsafe { addr_of_mut!((*list_item).state).write_volatile((self.tx_seq_no as u32) << 16 | 0x01) } + self.tx_seq_no = self.tx_seq_no.wrapping_add(1); + + let ipc = pac::IPC_NS; + ipc.tasks_send(ipc_ch).write_value(1); + Ok(()) + } + + fn handle_control(&mut self, msg: &Message) { + match msg.id >> 16 { + 1 => debug!("control msg: modem ready"), + 2 => self.handle_control_free(msg.data), + _ => warn!("unknown control message id {:08x}", msg.id), + } + } + + fn handle_control_free(&mut self, ptr: *mut u8) { + let base = unsafe { addr_of!((*self.cb).tx_bufs) } as usize; + let ptr = ptr as usize; + + if ptr < base { + warn!("control free bad pointer {:08x}", ptr); + return; + } + + let diff = ptr - base; + let idx = diff / TX_BUF_SIZE; + + if idx >= TX_BUF_COUNT || idx * TX_BUF_SIZE != diff { + warn!("control free bad pointer {:08x}", ptr); + return; + } + + trace!("control free pointer {:08x} idx {}", ptr, idx); + if !self.tx_buf_used[idx] { + warn!( + "control free pointer {:08x} idx {}: buffer was already free??", + ptr, idx + ); + } + self.tx_buf_used[idx] = false; + self.tx_waker.wake(); + } + + fn handle_data(&mut self, msg: &Message, ch: &mut ch::Runner) { + if !msg.data.is_null() { + self.rx_check.check_length(msg.data, msg.data_len); + } + + let freed = match msg.id & 0xFFFF { + // AT + 3 => { + match msg.id >> 16 { + // AT request ack + 2 => false, + // AT response + 3 => self.handle_resp(msg), + // AT notification + 4 => false, + x => { + warn!("received unknown AT kind {}", x); + false + } + } + } + // IP + 4 => { + match msg.id >> 28 { + // IP response + 8 => self.handle_resp(msg), + // IP notification + 9 => match (msg.id >> 16) & 0xFFF { + // IP receive notification + 1 => { + if let Some(buf) = ch.try_rx_buf() { + let mut len = msg.data_len; + if len > buf.len() { + warn!("truncating rx'd packet from {} to {} bytes", len, buf.len()); + len = buf.len(); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + unsafe { ptr::copy_nonoverlapping(msg.data, buf.as_mut_ptr(), len) } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + ch.rx_done(len); + } + false + } + _ => false, + }, + x => { + warn!("received unknown IP kind {}", x); + false + } + } + } + x => { + warn!("received unknown kind {}", x); + false + } + }; + + if !freed { + self.send_free(msg); + } + } + + fn handle_resp(&mut self, msg: &Message) -> bool { + let req_serial = u32::from_le_bytes(msg.param[0..4].try_into().unwrap()); + if req_serial == 0 { + return false; + } + + for optr in &mut self.requests { + if let Some(r) = optr { + if r.req_serial == req_serial { + let r = optr.take().unwrap(); + unsafe { r.resp_msg.write(*msg) } + r.waker.wake(); + *optr = None; + return true; + } + } + } + + warn!( + "resp with id {} serial {} doesn't match any pending req", + msg.id, req_serial + ); + false + } + + fn send_free(&mut self, msg: &Message) { + if msg.data.is_null() { + return; + } + + let mut free_msg: Message = unsafe { mem::zeroed() }; + free_msg.channel = 1; // control + free_msg.id = 0x20001; // free + free_msg.data = msg.data; + free_msg.data_len = msg.data_len; + + unwrap!(self.send_message_raw(&free_msg)); + } +} + +struct PointerChecker { + start: *mut u8, + end: *mut u8, +} + +impl PointerChecker { + // check the pointer is in bounds in the arena, panic otherwise. + fn check_length(&self, ptr: *const u8, len: usize) { + assert!(ptr as usize >= self.start as usize); + let end_ptr = (ptr as usize).checked_add(len).unwrap(); + assert!(end_ptr <= self.end as usize); + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check(&self, ptr: *const T) { + assert!(ptr.is_aligned()); + self.check_length(ptr as *const u8, mem::size_of::()); + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check_read(&self, ptr: *const T) -> T { + self.check(ptr); + unsafe { ptr.read_volatile() } + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check_mut(&self, ptr: *mut T) { + self.check(ptr as *const T) + } +} + +/// Control handle for the driver. +/// +/// You can use this object to control the modem at runtime, such as running AT commands. +pub struct Control<'a> { + state: &'a RefCell, +} + +impl<'a> Control<'a> { + /// Wait for modem IPC to be initialized. + pub async fn wait_init(&self) { + poll_fn(|cx| { + let mut state = self.state.borrow_mut(); + if state.init { + return Poll::Ready(()); + } + state.init_waker.register(cx.waker()); + Poll::Pending + }) + .await + } + + async fn request(&self, msg: &mut Message, req_data: &[u8], resp_data: &mut [u8]) -> usize { + // get waker + let waker = poll_fn(|cx| Poll::Ready(cx.waker().clone())).await; + + // Send request + let mut state = self.state.borrow_mut(); + let mut req_serial = state.next_req_serial; + if msg.id & 0xFFFF == 3 { + // AT response seems to keep only the lower 8 bits. Others do keep the full 32 bits..?? + req_serial &= 0xFF; + } + + // increment next_req_serial, skip zero because we use it as an "ignore" value. + // We have to skip when the *lowest byte* is zero because AT responses. + state.next_req_serial = state.next_req_serial.wrapping_add(1); + if state.next_req_serial & 0xFF == 0 { + state.next_req_serial = state.next_req_serial.wrapping_add(1); + } + + drop(state); // don't borrow state across awaits. + + msg.param[0..4].copy_from_slice(&req_serial.to_le_bytes()); + + poll_fn(|cx| { + let mut state = self.state.borrow_mut(); + state.tx_waker.register(cx.waker()); + match state.send_message(msg, req_data) { + Ok(_) => Poll::Ready(()), + Err(NoFreeBufs) => Poll::Pending, + } + }) + .await; + + // Setup the pending request state. + let mut state = self.state.borrow_mut(); + let (req_slot_idx, req_slot) = state + .requests + .iter_mut() + .enumerate() + .find(|(_, x)| x.is_none()) + .unwrap(); + msg.id = 0; // zero out id, so when it becomes nonzero we know the req is done. + let msg_ptr: *mut Message = msg; + *req_slot = Some(PendingRequest { + req_serial, + resp_msg: msg_ptr, + waker, + }); + + drop(state); // don't borrow state across awaits. + + // On cancel, unregister the request slot. + let _drop = OnDrop::new(|| { + // Remove request slot. + let mut state = self.state.borrow_mut(); + let slot = &mut state.requests[req_slot_idx]; + if let Some(s) = slot { + if s.req_serial == req_serial { + *slot = None; + } + } + + // If cancelation raced with actually receiving the response, + // we own the data, so we have to free it. + let msg = unsafe { &mut *msg_ptr }; + if msg.id != 0 { + state.send_free(msg); + } + }); + // Wait for response. + poll_fn(|_| { + // we have to use the raw pointer and not the original reference `msg` + // because that'd invalidate the raw ptr that's still stored in `req_slot`. + if unsafe { (*msg_ptr).id } != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + _drop.defuse(); + + if msg.data.is_null() { + // no response data. + return 0; + } + + // Copy response data out, if any. + // Pointer was validated in StateInner::handle_data(). + let mut len = msg.data_len; + if len > resp_data.len() { + warn!("truncating response data from {} to {}", len, resp_data.len()); + len = resp_data.len(); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + unsafe { ptr::copy_nonoverlapping(msg.data, resp_data.as_mut_ptr(), len) } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + self.state.borrow_mut().send_free(msg); + len + } + + /// Run an AT command. + /// + /// The response is written in `resp` and its length returned. + pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x0001_0003; // AT command + msg.param_len = 4; + + self.request(&mut msg, req, resp).await + } + + /// Open the raw socket used for sending/receiving IP packets. + /// + /// This must be done after `AT+CFUN=1` (?) + async fn open_raw_socket(&self) -> u32 { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7001_0004; // open socket + msg.param_len = 20; + + let param = [ + 0xFF, 0xFF, 0xFF, 0xFF, // req_serial + 0xFF, 0xFF, 0xFF, 0xFF, // ??? + 0x05, 0x00, 0x00, 0x00, // family + 0x03, 0x00, 0x00, 0x00, // type + 0x00, 0x00, 0x00, 0x00, // protocol + ]; + msg.param[..param.len()].copy_from_slice(¶m); + + self.request(&mut msg, &[], &mut []).await; + + assert_eq!(msg.id, 0x80010004); + assert!(msg.param_len >= 12); + let status = u32::from_le_bytes(msg.param[8..12].try_into().unwrap()); + assert_eq!(status, 0); + assert_eq!(msg.param_len, 16); + let fd = u32::from_le_bytes(msg.param[12..16].try_into().unwrap()); + self.state.borrow_mut().net_fd.replace(fd); + + trace!("got FD: {}", fd); + fd + } + + async fn close_raw_socket(&self, fd: u32) { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7009_0004; // close socket + msg.param_len = 8; + msg.param[4..8].copy_from_slice(&fd.to_le_bytes()); + + self.request(&mut msg, &[], &mut []).await; + + assert_eq!(msg.id, 0x80090004); + assert!(msg.param_len >= 12); + let status = u32::from_le_bytes(msg.param[8..12].try_into().unwrap()); + assert_eq!(status, 0); + } +} + +/// Background runner for the driver. +pub struct Runner<'a> { + ch: ch::Runner<'a, MTU>, + state: &'a RefCell, + trace_writer: Option>, +} + +impl<'a> Runner<'a> { + /// Run the driver operation in the background. + /// + /// You must run this in a background task, concurrently with all network operations. + pub async fn run(mut self) -> ! { + poll_fn(|cx| { + WAKER.register(cx.waker()); + + let mut state = self.state.borrow_mut(); + state.poll(&mut self.trace_writer, &mut self.ch); + + if let Poll::Ready(buf) = self.ch.poll_tx_buf(cx) { + if let Some(fd) = state.net_fd { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7006_0004; // IP send + msg.param_len = 12; + msg.param[4..8].copy_from_slice(&fd.to_le_bytes()); + if let Err(e) = state.send_message(&mut msg, buf) { + warn!("tx failed: {:?}", e); + } + self.ch.tx_done(); + } + } + + Poll::Pending + }) + .await + } +} + +const LIST_LEN: usize = 16; + +#[repr(C)] +struct ControlBlock { + version: u32, + rx_base: *mut u8, + rx_size: usize, + control_list_ptr: *mut List, + data_list_ptr: *mut List, + modem_info_ptr: *mut ModemInfo, + trace_ptr: *mut Trace, + unk: u32, + + modem_info: ModemInfo, + trace: Trace, + + // 0 = control, 1 = data + lists: [List; 2], + msgs: [[Message; LIST_LEN]; 2], + + tx_bufs: [[u8; TX_BUF_SIZE]; TX_BUF_COUNT], +} + +#[repr(C)] +struct ModemInfo { + version: u32, + control_list_ptr: *mut List, + data_list_ptr: *mut List, + padding: [u32; 5], +} + +#[repr(C)] +struct Trace { + size: usize, + base: *mut u8, + tx_state: u32, + tx_ptr: *mut u8, + rx_state: u32, + rx_ptr: *mut u8, + unk1: u32, + unk2: u32, +} + +const TRACE_CHANNEL_COUNT: usize = 3; + +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct TraceContext { + unk1: u32, + unk2: u32, + len: u32, + chans: [*mut TraceChannel; TRACE_CHANNEL_COUNT], +} + +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct TraceChannel { + id: u8, + unk1: u8, + unk2: u8, + unk3: u8, + write_ptr: *mut u8, + read_ptr: *mut u8, + start: *mut u8, + end: *mut u8, +} + +#[repr(C)] +struct List { + len: usize, + items: [ListItem; LIST_LEN], +} + +#[repr(C)] +struct ListItem { + /// top 16 bits: seqno + /// bottom 8 bits: + /// 0x01: sent + /// 0x02: held + /// 0x03: freed + state: u32, + message: *mut Message, +} + +#[repr(C)] +#[derive(defmt::Format, Clone, Copy)] +struct Message { + id: u32, + + /// 1 = control, 2 = data + channel: u8, + unk1: u8, + unk2: u8, + unk3: u8, + + data: *mut u8, + data_len: usize, + param_len: usize, + param: [u8; 44], +} + +struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + pub fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } + + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} diff --git a/embassy/embassy-net-ppp/Cargo.toml b/embassy/embassy-net-ppp/Cargo.toml new file mode 100644 index 0000000..9c214d5 --- /dev/null +++ b/embassy/embassy-net-ppp/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "embassy-net-ppp" +version = "0.1.0" +description = "embassy-net driver for PPP over Serial" +keywords = ["embedded", "ppp", "embassy-net", "embedded-hal-async", "async"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +edition = "2021" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-ppp" + +[features] +defmt = ["dep:defmt", "ppproto/defmt"] +log = ["dep:log", "ppproto/log"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +embedded-io-async = { version = "0.6.1" } +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +ppproto = { version = "0.2.0"} +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-ppp-v$VERSION/embassy-net-ppp/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-ppp/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] diff --git a/embassy/embassy-net-ppp/README.md b/embassy/embassy-net-ppp/README.md new file mode 100644 index 0000000..0eb7cee --- /dev/null +++ b/embassy/embassy-net-ppp/README.md @@ -0,0 +1,9 @@ +# `embassy-net-ppp` + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for PPP over Serial. + +## Interoperability + +This crate can run on any executor. + +It supports any serial port implementing [`embedded-io-async`](https://crates.io/crates/embedded-io-async). diff --git a/embassy/embassy-net-ppp/src/fmt.rs b/embassy/embassy-net-ppp/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-net-ppp/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-net-ppp/src/lib.rs b/embassy/embassy-net-ppp/src/lib.rs new file mode 100644 index 0000000..54a98c9 --- /dev/null +++ b/embassy/embassy-net-ppp/src/lib.rs @@ -0,0 +1,174 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +// must be first +mod fmt; + +use core::convert::Infallible; +use core::mem::MaybeUninit; + +use embassy_futures::select::{select, Either}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embedded_io_async::{BufRead, Write}; +use ppproto::pppos::{BufferFullError, PPPoS, PPPoSAction}; +pub use ppproto::{Config, Ipv4Status}; + +const MTU: usize = 1500; + +/// Type alias for the embassy-net driver. +pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; + +/// 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 driver. +/// +/// You must call `.run()` in a background task for the driver to operate. +pub struct Runner<'d> { + ch: ch::Runner<'d, MTU>, +} + +/// Error returned by [`Runner::run`]. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RunError { + /// Reading from the serial port failed. + Read(E), + /// Writing to the serial port failed. + Write(E), + /// Writing to the serial got EOF. + Eof, + /// PPP protocol was terminated by the peer + Terminated, +} + +impl<'d> Runner<'d> { + /// You must call this in a background task for the driver to operate. + /// + /// If reading/writing to the underlying serial port fails, the link state + /// is set to Down and the error is returned. + /// + /// It is allowed to cancel this function's future (i.e. drop it). This will terminate + /// the PPP connection and set the link state to Down. + /// + /// After this function returns or is canceled, you can call it again to establish + /// a new PPP connection. + pub async fn run( + &mut self, + mut rw: RW, + config: ppproto::Config<'_>, + mut on_ipv4_up: impl FnMut(Ipv4Status), + ) -> Result> { + let mut ppp = PPPoS::new(config); + ppp.open().unwrap(); + + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.borrow_split(); + state_chan.set_link_state(LinkState::Down); + let _ondrop = OnDrop::new(|| state_chan.set_link_state(LinkState::Down)); + + let mut rx_buf = [0; 2048]; + let mut tx_buf = [0; 2048]; + + let mut needs_poll = true; + let mut was_up = false; + + loop { + let rx_fut = async { + let buf = rx_chan.rx_buf().await; + let rx_data = match needs_poll { + true => &[][..], + false => match rw.fill_buf().await { + Ok(rx_data) if rx_data.len() == 0 => return Err(RunError::Eof), + Ok(rx_data) => rx_data, + Err(e) => return Err(RunError::Read(e)), + }, + }; + Ok((buf, rx_data)) + }; + let tx_fut = tx_chan.tx_buf(); + match select(rx_fut, tx_fut).await { + Either::First(r) => { + needs_poll = false; + + let (buf, rx_data) = r?; + let n = ppp.consume(rx_data, &mut rx_buf); + rw.consume(n); + + match ppp.poll(&mut tx_buf, &mut rx_buf) { + PPPoSAction::None => {} + PPPoSAction::Received(rg) => { + let pkt = &rx_buf[rg]; + buf[..pkt.len()].copy_from_slice(pkt); + rx_chan.rx_done(pkt.len()); + } + PPPoSAction::Transmit(n) => rw.write_all(&tx_buf[..n]).await.map_err(RunError::Write)?, + } + + let status = ppp.status(); + match status.phase { + ppproto::Phase::Dead => { + return Err(RunError::Terminated); + } + ppproto::Phase::Open => { + if !was_up { + on_ipv4_up(status.ipv4.unwrap()); + } + was_up = true; + state_chan.set_link_state(LinkState::Up); + } + _ => { + was_up = false; + state_chan.set_link_state(LinkState::Down); + } + } + } + Either::Second(pkt) => { + match ppp.send(pkt, &mut tx_buf) { + Ok(n) => rw.write_all(&tx_buf[..n]).await.map_err(RunError::Write)?, + Err(BufferFullError) => unreachable!(), + } + tx_chan.tx_done(); + } + } + } + } +} + +/// Create a PPP embassy-net driver instance. +/// +/// This returns two structs: +/// - a `Device` that you must pass to the `embassy-net` stack. +/// - a `Runner`. You must call `.run()` on it in a background task. +pub fn new<'a, const N_RX: usize, const N_TX: usize>(state: &'a mut State) -> (Device<'a>, Runner<'a>) { + let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ip); + (device, Runner { ch: runner }) +} + +struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} diff --git a/embassy/embassy-net-tuntap/Cargo.toml b/embassy/embassy-net-tuntap/Cargo.toml new file mode 100644 index 0000000..2cd0c0f --- /dev/null +++ b/embassy/embassy-net-tuntap/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "embassy-net-tuntap" +version = "0.1.0" +description = "embassy-net driver for Linux TUN/TAP interfaces." +keywords = ["embedded", "tuntap", "embassy-net", "ethernet", "async"] +categories = ["embedded", "hardware-support", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +edition = "2021" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-tuntap" + +[dependencies] +embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } +async-io = "1.6.0" +log = "0.4.14" +libc = "0.2.101" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-tuntap-v$VERSION/embassy-net-tuntap/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-tuntap/src/" +target = "x86_64-unknown-linux-gnu" diff --git a/embassy/embassy-net-tuntap/README.md b/embassy/embassy-net-tuntap/README.md new file mode 100644 index 0000000..60a4a1b --- /dev/null +++ b/embassy/embassy-net-tuntap/README.md @@ -0,0 +1,7 @@ +# `embassy-net` integration for Linux TUN/TAP interfaces. + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for for Linux TUN (IP medium) and TAP (Ethernet medium) interfaces. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy/embassy-net-tuntap/src/lib.rs b/embassy/embassy-net-tuntap/src/lib.rs new file mode 100644 index 0000000..2ff23f4 --- /dev/null +++ b/embassy/embassy-net-tuntap/src/lib.rs @@ -0,0 +1,244 @@ +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +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::{Capabilities, Driver, HardwareAddress, LinkState}; +use log::*; + +/// Get the MTU of the given interface. +pub const SIOCGIFMTU: libc::c_ulong = 0x8921; +/// Get the index of the given interface. +pub const _SIOCGIFINDEX: libc::c_ulong = 0x8933; +/// Capture all packages. +pub const _ETH_P_ALL: libc::c_short = 0x0003; +/// Set the interface flags. +pub const TUNSETIFF: libc::c_ulong = 0x400454CA; +/// TUN device. +pub const _IFF_TUN: libc::c_int = 0x0001; +/// TAP device. +pub const IFF_TAP: libc::c_int = 0x0002; +/// No packet information. +pub const IFF_NO_PI: libc::c_int = 0x1000; + +const ETHERNET_HEADER_LEN: usize = 14; + +#[repr(C)] +#[derive(Debug)] +#[allow(non_camel_case_types)] +struct ifreq { + ifr_name: [libc::c_char; libc::IF_NAMESIZE], + ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */ +} + +fn ifreq_for(name: &str) -> ifreq { + let mut ifreq = ifreq { + ifr_name: [0; libc::IF_NAMESIZE], + ifr_data: 0, + }; + for (i, byte) in name.as_bytes().iter().enumerate() { + ifreq.ifr_name[i] = *byte as libc::c_char + } + ifreq +} + +fn ifreq_ioctl(lower: libc::c_int, ifreq: &mut ifreq, cmd: libc::c_ulong) -> io::Result { + unsafe { + let res = libc::ioctl(lower, cmd as _, ifreq as *mut ifreq); + if res == -1 { + return Err(io::Error::last_os_error()); + } + } + + Ok(ifreq.ifr_data) +} + +/// A TUN/TAP device. +#[derive(Debug)] +pub struct TunTap { + fd: libc::c_int, + mtu: usize, +} + +impl AsRawFd for TunTap { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl TunTap { + /// Create a new TUN/TAP device. + pub fn new(name: &str) -> io::Result { + unsafe { + let fd = libc::open( + "/dev/net/tun\0".as_ptr() as *const libc::c_char, + libc::O_RDWR | libc::O_NONBLOCK, + ); + if fd == -1 { + return Err(io::Error::last_os_error()); + } + + let mut ifreq = ifreq_for(name); + ifreq.ifr_data = IFF_TAP | IFF_NO_PI; + ifreq_ioctl(fd, &mut ifreq, TUNSETIFF)?; + + let socket = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP); + if socket == -1 { + return Err(io::Error::last_os_error()); + } + + let ip_mtu = ifreq_ioctl(socket, &mut ifreq, SIOCGIFMTU); + libc::close(socket); + let ip_mtu = ip_mtu? as usize; + + // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.) + // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it. + let mtu = ip_mtu + ETHERNET_HEADER_LEN; + + Ok(TunTap { fd, mtu }) + } + } +} + +impl Drop for TunTap { + fn drop(&mut self) { + unsafe { + libc::close(self.fd); + } + } +} + +impl io::Read for TunTap { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) }; + if len == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(len as usize) + } + } +} + +impl io::Write for TunTap { + fn write(&mut self, buf: &[u8]) -> io::Result { + let len = unsafe { libc::write(self.fd, buf.as_ptr() as *mut libc::c_void, buf.len()) }; + if len == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(len as usize) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// A TUN/TAP device, wrapped in an async interface. +pub struct TunTapDevice { + device: Async, +} + +impl TunTapDevice { + /// Create a new TUN/TAP device. + pub fn new(name: &str) -> io::Result { + Ok(Self { + device: Async::new(TunTap::new(name)?)?, + }) + } +} + +impl Driver for TunTapDevice { + type RxToken<'a> + = RxToken + where + Self: 'a; + type TxToken<'a> + = TxToken<'a> + where + Self: 'a; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let mut buf = vec![0; self.device.get_ref().mtu]; + loop { + match self.device.get_mut().read(&mut buf) { + Ok(n) => { + buf.truncate(n); + return Some(( + RxToken { buffer: buf }, + TxToken { + device: &mut self.device, + }, + )); + } + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + if !self.device.poll_readable(cx).is_ready() { + return None; + } + } + Err(e) => panic!("read error: {:?}", e), + } + } + } + + fn transmit(&mut self, _cx: &mut Context) -> Option> { + Some(TxToken { + device: &mut self.device, + }) + } + + fn capabilities(&self) -> Capabilities { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = self.device.get_ref().mtu; + caps + } + + fn link_state(&mut self, _cx: &mut Context) -> LinkState { + LinkState::Up + } + + fn hardware_address(&self) -> HardwareAddress { + HardwareAddress::Ethernet([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/embassy/embassy-net-wiznet/Cargo.toml b/embassy/embassy-net-wiznet/Cargo.toml new file mode 100644 index 0000000..e7fb3f4 --- /dev/null +++ b/embassy/embassy-net-wiznet/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "embassy-net-wiznet" +version = "0.1.0" +description = "embassy-net driver for WIZnet SPI Ethernet chips" +keywords = ["embedded", "embassy-net", "embedded-hal-async", "ethernet", "async"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +edition = "2021" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-wiznet" + +[dependencies] +embedded-hal = { version = "1.0" } +embedded-hal-async = { version = "1.0" } +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +defmt = { version = "0.3", optional = true } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-wiznet-v$VERSION/embassy-net-wiznet/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-wiznet/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] diff --git a/embassy/embassy-net-wiznet/README.md b/embassy/embassy-net-wiznet/README.md new file mode 100644 index 0000000..b666198 --- /dev/null +++ b/embassy/embassy-net-wiznet/README.md @@ -0,0 +1,16 @@ +# WIZnet `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet SPI ethernet chips, operating in MACRAW mode. + +See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/rp) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico)) module. + +## Supported chips + +- W5500 +- W5100S + +## Interoperability + +This crate can run on any executor. + +It supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async). diff --git a/embassy/embassy-net-wiznet/src/chip/mod.rs b/embassy/embassy-net-wiznet/src/chip/mod.rs new file mode 100644 index 0000000..2e7a9ed --- /dev/null +++ b/embassy/embassy-net-wiznet/src/chip/mod.rs @@ -0,0 +1,48 @@ +//! Wiznet W5100s and W5500 family driver. +mod w5500; +pub use w5500::W5500; +mod w5100s; +use embedded_hal_async::spi::SpiDevice; +pub use w5100s::W5100S; + +pub(crate) trait SealedChip { + type Address; + + /// The version of the chip as reported by the VERSIONR register. + /// This is used to verify that the chip is supported by the driver, + /// and that SPI communication is working. + const CHIP_VERSION: u8; + + const COMMON_MODE: Self::Address; + const COMMON_MAC: Self::Address; + const COMMON_SOCKET_INTR: Self::Address; + const COMMON_PHY_CFG: Self::Address; + const COMMON_VERSION: Self::Address; + + const SOCKET_MODE: Self::Address; + const SOCKET_COMMAND: Self::Address; + const SOCKET_RXBUF_SIZE: Self::Address; + const SOCKET_TXBUF_SIZE: Self::Address; + const SOCKET_TX_FREE_SIZE: Self::Address; + const SOCKET_TX_DATA_WRITE_PTR: Self::Address; + const SOCKET_RECVD_SIZE: Self::Address; + const SOCKET_RX_DATA_READ_PTR: Self::Address; + const SOCKET_INTR_MASK: Self::Address; + const SOCKET_INTR: Self::Address; + + const SOCKET_MODE_VALUE: u8; + + const BUF_SIZE: u16; + const AUTO_WRAP: bool; + + fn rx_addr(addr: u16) -> Self::Address; + fn tx_addr(addr: u16) -> Self::Address; + + async fn bus_read(spi: &mut SPI, address: Self::Address, data: &mut [u8]) + -> Result<(), SPI::Error>; + async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error>; +} + +/// Trait for Wiznet chips. +#[allow(private_bounds)] +pub trait Chip: SealedChip {} diff --git a/embassy/embassy-net-wiznet/src/chip/w5100s.rs b/embassy/embassy-net-wiznet/src/chip/w5100s.rs new file mode 100644 index 0000000..4c4b7ab --- /dev/null +++ b/embassy/embassy-net-wiznet/src/chip/w5100s.rs @@ -0,0 +1,65 @@ +use embedded_hal_async::spi::{Operation, SpiDevice}; + +const SOCKET_BASE: u16 = 0x400; +const TX_BASE: u16 = 0x4000; +const RX_BASE: u16 = 0x6000; + +/// Wizard W5100S chip. +pub enum W5100S {} + +impl super::Chip for W5100S {} +impl super::SealedChip for W5100S { + type Address = u16; + + const CHIP_VERSION: u8 = 0x51; + + const COMMON_MODE: Self::Address = 0x00; + const COMMON_MAC: Self::Address = 0x09; + const COMMON_SOCKET_INTR: Self::Address = 0x16; + const COMMON_PHY_CFG: Self::Address = 0x3c; + const COMMON_VERSION: Self::Address = 0x80; + + const SOCKET_MODE: Self::Address = SOCKET_BASE + 0x00; + const SOCKET_COMMAND: Self::Address = SOCKET_BASE + 0x01; + const SOCKET_RXBUF_SIZE: Self::Address = SOCKET_BASE + 0x1E; + const SOCKET_TXBUF_SIZE: Self::Address = SOCKET_BASE + 0x1F; + const SOCKET_TX_FREE_SIZE: Self::Address = SOCKET_BASE + 0x20; + const SOCKET_TX_DATA_WRITE_PTR: Self::Address = SOCKET_BASE + 0x24; + const SOCKET_RECVD_SIZE: Self::Address = SOCKET_BASE + 0x26; + const SOCKET_RX_DATA_READ_PTR: Self::Address = SOCKET_BASE + 0x28; + const SOCKET_INTR_MASK: Self::Address = SOCKET_BASE + 0x2C; + const SOCKET_INTR: Self::Address = SOCKET_BASE + 0x02; + + const SOCKET_MODE_VALUE: u8 = (1 << 2) | (1 << 6); + + const BUF_SIZE: u16 = 0x2000; + const AUTO_WRAP: bool = false; + + fn rx_addr(addr: u16) -> Self::Address { + RX_BASE + addr + } + + fn tx_addr(addr: u16) -> Self::Address { + TX_BASE + addr + } + + async fn bus_read( + spi: &mut SPI, + address: Self::Address, + data: &mut [u8], + ) -> Result<(), SPI::Error> { + spi.transaction(&mut [ + Operation::Write(&[0x0F, (address >> 8) as u8, address as u8]), + Operation::Read(data), + ]) + .await + } + + async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> { + spi.transaction(&mut [ + Operation::Write(&[0xF0, (address >> 8) as u8, address as u8]), + Operation::Write(data), + ]) + .await + } +} diff --git a/embassy/embassy-net-wiznet/src/chip/w5500.rs b/embassy/embassy-net-wiznet/src/chip/w5500.rs new file mode 100644 index 0000000..5cfcb94 --- /dev/null +++ b/embassy/embassy-net-wiznet/src/chip/w5500.rs @@ -0,0 +1,76 @@ +use embedded_hal_async::spi::{Operation, SpiDevice}; + +#[repr(u8)] +pub enum RegisterBlock { + Common = 0x00, + Socket0 = 0x01, + TxBuf = 0x02, + RxBuf = 0x03, +} + +/// Wiznet W5500 chip. +pub enum W5500 {} + +impl super::Chip for W5500 {} +impl super::SealedChip for W5500 { + type Address = (RegisterBlock, u16); + + const CHIP_VERSION: u8 = 0x04; + + const COMMON_MODE: Self::Address = (RegisterBlock::Common, 0x00); + const COMMON_MAC: Self::Address = (RegisterBlock::Common, 0x09); + const COMMON_SOCKET_INTR: Self::Address = (RegisterBlock::Common, 0x18); + const COMMON_PHY_CFG: Self::Address = (RegisterBlock::Common, 0x2E); + const COMMON_VERSION: Self::Address = (RegisterBlock::Common, 0x39); + + const SOCKET_MODE: Self::Address = (RegisterBlock::Socket0, 0x00); + const SOCKET_COMMAND: Self::Address = (RegisterBlock::Socket0, 0x01); + const SOCKET_RXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x1E); + const SOCKET_TXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x1F); + const SOCKET_TX_FREE_SIZE: Self::Address = (RegisterBlock::Socket0, 0x20); + const SOCKET_TX_DATA_WRITE_PTR: Self::Address = (RegisterBlock::Socket0, 0x24); + const SOCKET_RECVD_SIZE: Self::Address = (RegisterBlock::Socket0, 0x26); + const SOCKET_RX_DATA_READ_PTR: Self::Address = (RegisterBlock::Socket0, 0x28); + const SOCKET_INTR_MASK: Self::Address = (RegisterBlock::Socket0, 0x2C); + const SOCKET_INTR: Self::Address = (RegisterBlock::Socket0, 0x02); + + const SOCKET_MODE_VALUE: u8 = (1 << 2) | (1 << 7); + + const BUF_SIZE: u16 = 0x4000; + const AUTO_WRAP: bool = true; + + fn rx_addr(addr: u16) -> Self::Address { + (RegisterBlock::RxBuf, addr) + } + + fn tx_addr(addr: u16) -> Self::Address { + (RegisterBlock::TxBuf, addr) + } + + async fn bus_read( + spi: &mut SPI, + address: Self::Address, + data: &mut [u8], + ) -> Result<(), SPI::Error> { + let address_phase = address.1.to_be_bytes(); + let control_phase = [(address.0 as u8) << 3]; + let operations = &mut [ + Operation::Write(&address_phase), + Operation::Write(&control_phase), + Operation::TransferInPlace(data), + ]; + spi.transaction(operations).await + } + + async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> { + let address_phase = address.1.to_be_bytes(); + let control_phase = [(address.0 as u8) << 3 | 0b0000_0100]; + let data_phase = data; + let operations = &mut [ + Operation::Write(&address_phase[..]), + Operation::Write(&control_phase), + Operation::Write(&data_phase), + ]; + spi.transaction(operations).await + } +} diff --git a/embassy/embassy-net-wiznet/src/device.rs b/embassy/embassy-net-wiznet/src/device.rs new file mode 100644 index 0000000..d2b6bb0 --- /dev/null +++ b/embassy/embassy-net-wiznet/src/device.rs @@ -0,0 +1,255 @@ +use core::marker::PhantomData; + +use embedded_hal_async::spi::SpiDevice; + +use crate::chip::Chip; + +#[repr(u8)] +enum Command { + Open = 0x01, + Send = 0x20, + Receive = 0x40, +} + +#[repr(u8)] +enum Interrupt { + Receive = 0b00100_u8, +} + +/// Wiznet chip in MACRAW mode +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct WiznetDevice { + spi: SPI, + _phantom: PhantomData, +} + +/// Error type when initializing a new Wiznet device +pub enum InitError { + /// Error occurred when sending or receiving SPI data + SpiError(SE), + /// The chip returned a version that isn't expected or supported + InvalidChipVersion { + /// The version that is supported + expected: u8, + /// The version that was returned by the chip + actual: u8, + }, +} + +impl From for InitError { + fn from(e: SE) -> Self { + InitError::SpiError(e) + } +} + +impl core::fmt::Debug for InitError +where + SE: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + InitError::SpiError(e) => write!(f, "SpiError({:?})", e), + InitError::InvalidChipVersion { expected, actual } => { + write!(f, "InvalidChipVersion {{ expected: {}, actual: {} }}", expected, actual) + } + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for InitError +where + SE: defmt::Format, +{ + fn format(&self, f: defmt::Formatter) { + match self { + InitError::SpiError(e) => defmt::write!(f, "SpiError({})", e), + InitError::InvalidChipVersion { expected, actual } => { + defmt::write!(f, "InvalidChipVersion {{ expected: {}, actual: {} }}", expected, actual) + } + } + } +} + +impl WiznetDevice { + /// Create and initialize the driver + pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result> { + let mut this = Self { + spi, + _phantom: PhantomData, + }; + + // Reset device + this.bus_write(C::COMMON_MODE, &[0x80]).await?; + + // Check the version of the chip + let mut version = [0]; + this.bus_read(C::COMMON_VERSION, &mut version).await?; + if version[0] != C::CHIP_VERSION { + #[cfg(feature = "defmt")] + defmt::error!("invalid chip version: {} (expected {})", version[0], C::CHIP_VERSION); + return Err(InitError::InvalidChipVersion { + actual: version[0], + expected: C::CHIP_VERSION, + }); + } + + // Enable interrupt pin + this.bus_write(C::COMMON_SOCKET_INTR, &[0x01]).await?; + // Enable receive interrupt + this.bus_write(C::SOCKET_INTR_MASK, &[Interrupt::Receive as u8]).await?; + + // Set MAC address + this.bus_write(C::COMMON_MAC, &mac_addr).await?; + + // Set the raw socket RX/TX buffer sizes. + let buf_kbs = (C::BUF_SIZE / 1024) as u8; + this.bus_write(C::SOCKET_TXBUF_SIZE, &[buf_kbs]).await?; + this.bus_write(C::SOCKET_RXBUF_SIZE, &[buf_kbs]).await?; + + // MACRAW mode with MAC filtering. + this.bus_write(C::SOCKET_MODE, &[C::SOCKET_MODE_VALUE]).await?; + this.command(Command::Open).await?; + + Ok(this) + } + + async fn bus_read(&mut self, address: C::Address, data: &mut [u8]) -> Result<(), SPI::Error> { + C::bus_read(&mut self.spi, address, data).await + } + + async fn bus_write(&mut self, address: C::Address, data: &[u8]) -> Result<(), SPI::Error> { + C::bus_write(&mut self.spi, address, data).await + } + + async fn reset_interrupt(&mut self, code: Interrupt) -> Result<(), SPI::Error> { + let data = [code as u8]; + self.bus_write(C::SOCKET_INTR, &data).await + } + + async fn get_tx_write_ptr(&mut self) -> Result { + let mut data = [0u8; 2]; + self.bus_read(C::SOCKET_TX_DATA_WRITE_PTR, &mut data).await?; + Ok(u16::from_be_bytes(data)) + } + + async fn set_tx_write_ptr(&mut self, ptr: u16) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + self.bus_write(C::SOCKET_TX_DATA_WRITE_PTR, &data).await + } + + async fn get_rx_read_ptr(&mut self) -> Result { + let mut data = [0u8; 2]; + self.bus_read(C::SOCKET_RX_DATA_READ_PTR, &mut data).await?; + Ok(u16::from_be_bytes(data)) + } + + async fn set_rx_read_ptr(&mut self, ptr: u16) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + self.bus_write(C::SOCKET_RX_DATA_READ_PTR, &data).await + } + + async fn command(&mut self, command: Command) -> Result<(), SPI::Error> { + let data = [command as u8]; + self.bus_write(C::SOCKET_COMMAND, &data).await + } + + async fn get_rx_size(&mut self) -> Result { + loop { + // Wait until two sequential reads are equal + let mut res0 = [0u8; 2]; + self.bus_read(C::SOCKET_RECVD_SIZE, &mut res0).await?; + let mut res1 = [0u8; 2]; + self.bus_read(C::SOCKET_RECVD_SIZE, &mut res1).await?; + if res0 == res1 { + break Ok(u16::from_be_bytes(res0)); + } + } + } + + async fn get_tx_free_size(&mut self) -> Result { + let mut data = [0; 2]; + self.bus_read(C::SOCKET_TX_FREE_SIZE, &mut data).await?; + Ok(u16::from_be_bytes(data)) + } + + /// Read bytes from the RX buffer. + async fn read_bytes(&mut self, read_ptr: &mut u16, buffer: &mut [u8]) -> Result<(), SPI::Error> { + if C::AUTO_WRAP { + self.bus_read(C::rx_addr(*read_ptr), buffer).await?; + } else { + let addr = *read_ptr % C::BUF_SIZE; + if addr as usize + buffer.len() <= C::BUF_SIZE as usize { + self.bus_read(C::rx_addr(addr), buffer).await?; + } else { + let n = C::BUF_SIZE - addr; + self.bus_read(C::rx_addr(addr), &mut buffer[..n as usize]).await?; + self.bus_read(C::rx_addr(0), &mut buffer[n as usize..]).await?; + } + } + + *read_ptr = (*read_ptr).wrapping_add(buffer.len() as u16); + + Ok(()) + } + + /// Read an ethernet frame from the device. Returns the number of bytes read. + pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result { + let rx_size = self.get_rx_size().await? as usize; + if rx_size == 0 { + return Ok(0); + } + + self.reset_interrupt(Interrupt::Receive).await?; + + let mut read_ptr = self.get_rx_read_ptr().await?; + + // First two bytes gives the size of the received ethernet frame + let expected_frame_size: usize = { + let mut frame_bytes = [0u8; 2]; + self.read_bytes(&mut read_ptr, &mut frame_bytes).await?; + u16::from_be_bytes(frame_bytes) as usize - 2 + }; + + // Read the ethernet frame + self.read_bytes(&mut read_ptr, &mut frame[..expected_frame_size]) + .await?; + + // Register RX as completed + self.set_rx_read_ptr(read_ptr).await?; + self.command(Command::Receive).await?; + + Ok(expected_frame_size) + } + + /// Write an ethernet frame to the device. Returns number of bytes written + pub async fn write_frame(&mut self, frame: &[u8]) -> Result { + while self.get_tx_free_size().await? < frame.len() as u16 {} + let write_ptr = self.get_tx_write_ptr().await?; + + if C::AUTO_WRAP { + self.bus_write(C::tx_addr(write_ptr), frame).await?; + } else { + let addr = write_ptr % C::BUF_SIZE; + if addr as usize + frame.len() <= C::BUF_SIZE as usize { + self.bus_write(C::tx_addr(addr), frame).await?; + } else { + let n = C::BUF_SIZE - addr; + self.bus_write(C::tx_addr(addr), &frame[..n as usize]).await?; + self.bus_write(C::tx_addr(0), &frame[n as usize..]).await?; + } + } + + self.set_tx_write_ptr(write_ptr.wrapping_add(frame.len() as u16)) + .await?; + self.command(Command::Send).await?; + Ok(frame.len()) + } + + pub async fn is_link_up(&mut self) -> bool { + let mut link = [0]; + self.bus_read(C::COMMON_PHY_CFG, &mut link).await.ok(); + link[0] & 1 == 1 + } +} diff --git a/embassy/embassy-net-wiznet/src/lib.rs b/embassy/embassy-net-wiznet/src/lib.rs new file mode 100644 index 0000000..3fbd4c7 --- /dev/null +++ b/embassy/embassy-net-wiznet/src/lib.rs @@ -0,0 +1,133 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +pub mod chip; +mod device; + +use embassy_futures::select::{select3, Either3}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embassy_time::{Duration, Ticker, Timer}; +use embedded_hal::digital::OutputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiDevice; + +use crate::chip::Chip; +pub use crate::device::InitError; +use crate::device::WiznetDevice; + +// If you change this update the docs of State +const MTU: usize = 1514; + +/// Type alias for the embassy-net driver. +pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; + +/// Internal state for the embassy-net integration. +/// +/// The two generic arguments `N_RX` and `N_TX` set the size of the receive and +/// send packet queue. With a the ethernet MTU of _1514_ this takes up `N_RX + +/// NTX * 1514` bytes. While setting these both to 1 is the minimum this might +/// hurt performance as a packet can not be received while processing another. +/// +/// # Warning +/// On devices with a small amount of ram (think ~64k) watch out with the size +/// of there parameters. They will quickly use too much RAM. +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 driver. +/// +/// You must call `.run()` in a background task for the driver to operate. +pub struct Runner<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> { + mac: WiznetDevice, + ch: ch::Runner<'d, MTU>, + int: INT, + _reset: RST, +} + +/// You must call this in a background task for the driver to operate. +impl<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, C, SPI, INT, RST> { + /// Run the driver. + pub async fn run(mut self) -> ! { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + let mut tick = Ticker::every(Duration::from_millis(500)); + loop { + match select3( + async { + self.int.wait_for_low().await.ok(); + rx_chan.rx_buf().await + }, + tx_chan.tx_buf(), + tick.next(), + ) + .await + { + Either3::First(p) => { + if let Ok(n) = self.mac.read_frame(p).await { + rx_chan.rx_done(n); + } + } + Either3::Second(p) => { + self.mac.write_frame(p).await.ok(); + tx_chan.tx_done(); + } + Either3::Third(()) => { + if self.mac.is_link_up().await { + state_chan.set_link_state(LinkState::Up); + } else { + state_chan.set_link_state(LinkState::Down); + } + } + } + } + } +} + +/// Create a Wiznet ethernet chip driver for [`embassy-net`](https://crates.io/crates/embassy-net). +/// +/// This returns two structs: +/// - a `Device` that you must pass to the `embassy-net` stack. +/// - a `Runner`. You must call `.run()` on it in a background task. +pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin>( + mac_addr: [u8; 6], + state: &'a mut State, + spi_dev: SPI, + int: INT, + mut reset: RST, +) -> Result<(Device<'a>, Runner<'a, C, SPI, INT, RST>), InitError> { + // Reset the chip. + reset.set_low().ok(); + // Ensure the reset is registered. + Timer::after_millis(1).await; + reset.set_high().ok(); + + // Wait for PLL lock. Some chips are slower than others. + // Slowest is w5100s which is 100ms, so let's just wait that. + Timer::after_millis(100).await; + + let mac = WiznetDevice::new(spi_dev, mac_addr).await?; + + let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr)); + + Ok(( + device, + Runner { + ch: runner, + mac, + int, + _reset: reset, + }, + )) +} diff --git a/embassy/embassy-net/CHANGELOG.md b/embassy/embassy-net/CHANGELOG.md new file mode 100644 index 0000000..c4ccff2 --- /dev/null +++ b/embassy/embassy-net/CHANGELOG.md @@ -0,0 +1,76 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +No unreleased changes yet... Quick, go send a PR! + +## 0.5 - 2024-11-28 + +- Refactor the API structure, simplifying lifetimes and generics. + - Stack is now a thin handle that implements `Copy+Clone`. Instead of passing `&Stack` around, you can now pass `Stack`. + - `Stack` and `DnsSocket` no longer need a generic parameter for the device driver. + - The `run()` method has been moved to a new `Runner` struct. + - Sockets are covariant wrt their lifetime. + - An implication of the refactor is now you need only one `StaticCell` instead of two if you need to share the network stack between tasks. +- Use standard `core::net` IP types instead of custom ones from smoltcp. +- Update to `smoltcp` v0.12. +- Add `mdns` Cargo feature. +- dns: properly handle `AddrType::Either` in `get_host_by_name()` +- dns: truncate instead of panic if the DHCP server gives us more DNS servers than the configured maximum. +- stack: add `wait_link_up()`, `wait_link_down()`, `wait_config_down()`. +- tcp: Add `recv_queue()`, `send_queue()`. +- tcp: Add `wait_read_ready()`, `wait_write_ready()`. +- tcp: allow setting timeout through `embedded-nal` client. +- tcp: fix `flush()` hanging forever if socket is closed with pending data. +- tcp: fix `flush()` not waiting for ACK of FIN. +- tcp: implement `ReadReady`, `WriteReady` traits from `embedded-io`. +- udp, raw: Add `wait_send_ready()`, `wait_recv_ready()`, `flush()`. +- udp: add `recv_from_with()`, `send_to_with()` methods, allowing for IO with one less copy. +- udp: send/recv now takes/returns full `UdpMetadata` instead of just the remote `IpEndpoint`. +- raw: add raw sockets. + + +## 0.4 - 2024-01-11 + +- Update to `embassy-time` v0.3. + +## 0.3 - 2024-01-04 + +- Added `ReadReady` and `WriteReady` impls on `TcpSocket`. +- Avoid never resolving `TcpIo::read` when the output buffer is empty. +- Update to `smoltcp` v0.11. +- Forward constants from `smoltcp` in DNS query results so changing DNS result size in `smoltcp` properly propagates. +- Removed the nightly feature. + +## 0.2.1 - 2023-10-31 + +- Re-add impl_trait_projections +- Fix: Reset DHCP socket when the link up is detected + +## 0.2.0 - 2023-10-18 + +- Re-export `smoltcp::wire::IpEndpoint` +- Add poll functions on UdpSocket +- Make dual-stack work in embassy-net +- Fix multicast support +- Allow ethernet and 802.15.4 to coexist +- Add IEEE802.15.4 address to embassy net Stack +- Use HardwareAddress in Driver +- Add async versions of smoltcp's `send` and `recv` closure based API +- add error translation to tcp errors +- Forward TCP/UDP socket capacity impls +- allow changing IP config at runtime +- allow non-'static drivers +- Remove impl_trait_projections +- update embedded-io, embedded-nal-async +- add support for dhcp hostname option +- Wake stack's task after queueing a DNS query + +## 0.1.0 - 2023-06-29 + +- First release diff --git a/embassy/embassy-net/Cargo.toml b/embassy/embassy-net/Cargo.toml new file mode 100644 index 0000000..1d79a2a --- /dev/null +++ b/embassy/embassy-net/Cargo.toml @@ -0,0 +1,84 @@ +[package] +name = "embassy-net" +version = "0.5.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Async TCP/IP network stack for embedded systems" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net" +categories = [ + "embedded", + "no-std", + "asynchronous", + "network-programming", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/src/" +features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] + +[features] +default = [] +std = [] + +## Enable defmt +defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "heapless/defmt-03", "defmt?/ip_in_core"] + +## Trace all raw received and transmitted packets using defmt or log. +packet-trace = [] + +#! Many of the following feature flags are re-exports of smoltcp feature flags. See +#! the [smoltcp feature flag documentation](https://github.com/smoltcp-rs/smoltcp#feature-flags) +#! for more details + +## Enable UDP support +udp = ["smoltcp/socket-udp"] +## Enable Raw support +raw = ["smoltcp/socket-raw"] +## Enable TCP support +tcp = ["smoltcp/socket-tcp"] +## Enable DNS support +dns = ["smoltcp/socket-dns", "smoltcp/proto-dns"] +## Enable mDNS support +mdns = ["dns", "smoltcp/socket-mdns"] +## Enable DHCPv4 support +dhcpv4 = ["proto-ipv4", "medium-ethernet", "smoltcp/socket-dhcpv4"] +## Enable DHCPv4 support with hostname +dhcpv4-hostname = ["dhcpv4"] +## Enable IPv4 support +proto-ipv4 = ["smoltcp/proto-ipv4"] +## Enable IPv6 support +proto-ipv6 = ["smoltcp/proto-ipv6"] +## Enable the Ethernet medium +medium-ethernet = ["smoltcp/medium-ethernet"] +## Enable the IP medium +medium-ip = ["smoltcp/medium-ip"] +## Enable the IEEE 802.15.4 medium +medium-ieee802154 = ["smoltcp/medium-ieee802154"] +## Enable multicast support (for both ipv4 and/or ipv6 if enabled) +multicast = ["smoltcp/multicast"] + +[dependencies] + +defmt = { version = "0.3.8", optional = true } +log = { version = "0.4.14", optional = true } + +smoltcp = { version = "0.12.0", default-features = false, features = [ + "socket", + "async", +] } + +embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embedded-io-async = { version = "0.6.1" } + +managed = { version = "0.8.0", default-features = false, features = [ "map" ] } +heapless = { version = "0.8", default-features = false } +embedded-nal-async = "0.8.0" +document-features = "0.2.7" diff --git a/embassy/embassy-net/README.md b/embassy/embassy-net/README.md new file mode 100644 index 0000000..1722ffc --- /dev/null +++ b/embassy/embassy-net/README.md @@ -0,0 +1,54 @@ +# embassy-net + +`embassy-net` is a no-std no-alloc async network stack, designed for embedded systems. + +It builds on [`smoltcp`](https://github.com/smoltcp-rs/smoltcp). It provides a higher-level and more opinionated +API. It glues together the components provided by `smoltcp`, handling the low-level details with defaults and +memory management designed to work well for embedded systems, aiming for a more "Just Works" experience. + +## Features + +- IPv4, IPv6 +- Ethernet and bare-IP mediums. +- TCP, UDP, DNS, DHCPv4 +- TCP sockets implement the `embedded-io` async traits. +- Multicast + +See the [`smoltcp`](https://github.com/smoltcp-rs/smoltcp) README for a detailed list of implemented and +unimplemented features of the network protocols. + +## Hardware support + +- [`esp-wifi`](https://github.com/esp-rs/esp-wifi) for WiFi support on bare-metal ESP32 chips. Maintained by Espressif. +- [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W +- [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support. +- [`embassy-stm32`](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32) for the builtin Ethernet MAC in all STM32 chips (STM32F1, STM32F2, STM32F4, STM32F7, STM32H7, STM32H5). +- [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips (W5100S, W5500) +- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. + +## Examples + +- For usage with Embassy HALs and network chip drivers, search [here](https://github.com/embassy-rs/embassy/tree/main/examples) for `eth` or `wifi`. +- The [`esp-wifi` repo](https://github.com/esp-rs/esp-wifi) has examples for use on bare-metal ESP32 chips. +- For usage on `std` platforms, see [the `std` examples](https://github.com/embassy-rs/embassy/tree/main/examples/std/src/bin) + +## Adding support for new hardware + +To add `embassy-net` support for new hardware (i.e. a new Ethernet or WiFi chip, or +an Ethernet/WiFi MCU peripheral), you have to implement the [`embassy-net-driver`](https://crates.io/crates/embassy-net-driver) +traits. + +Alternatively, [`embassy-net-driver-channel`](https://crates.io/crates/embassy-net-driver-channel) provides a higher-level API +to construct a driver that processes packets in its own background task and communicates with the `embassy-net` task via +packet queues for RX and TX. + +Drivers should depend only on `embassy-net-driver` or `embassy-net-driver-channel`. Never on the main `embassy-net` crate. +This allows existing drivers to continue working for newer `embassy-net` major versions, without needing an update, if the driver +trait has not had breaking changes. + +## Interoperability + +This crate can run on any executor. + +[`embassy-time`](https://crates.io/crates/embassy-time) is used for timekeeping and timeouts. You must +link an `embassy-time` driver in your project to use this crate. diff --git a/embassy/embassy-net/src/dns.rs b/embassy/embassy-net/src/dns.rs new file mode 100644 index 0000000..dbe7377 --- /dev/null +++ b/embassy/embassy-net/src/dns.rs @@ -0,0 +1,120 @@ +//! DNS client compatible with the `embedded-nal-async` traits. +//! +//! This exists only for compatibility with crates that use `embedded-nal-async`. +//! Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're +//! not using `embedded-nal-async`. + +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::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, + } + } +} + +/// DNS client compatible with the `embedded-nal-async` traits. +/// +/// This exists only for compatibility with crates that use `embedded-nal-async`. +/// Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're +/// not using `embedded-nal-async`. +pub struct DnsSocket<'a> { + stack: Stack<'a>, +} + +impl<'a> DnsSocket<'a> { + /// Create a new DNS socket using the provided stack. + /// + /// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated. + pub fn new(stack: Stack<'a>) -> 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 + } +} + +impl<'a> embedded_nal_async::Dns for DnsSocket<'a> { + type Error = Error; + + async fn get_host_by_name( + &self, + host: &str, + addr_type: embedded_nal_async::AddrType, + ) -> Result { + use core::net::IpAddr; + + use embedded_nal_async::AddrType; + + let (qtype, secondary_qtype) = match addr_type { + AddrType::IPv4 => (DnsQueryType::A, None), + AddrType::IPv6 => (DnsQueryType::Aaaa, None), + AddrType::Either => { + #[cfg(not(feature = "proto-ipv6"))] + let v6_first = false; + #[cfg(feature = "proto-ipv6")] + let v6_first = self.stack.config_v6().is_some(); + match v6_first { + true => (DnsQueryType::Aaaa, Some(DnsQueryType::A)), + false => (DnsQueryType::A, Some(DnsQueryType::Aaaa)), + } + } + }; + let mut addrs = self.query(host, qtype).await?; + if addrs.is_empty() { + if let Some(qtype) = secondary_qtype { + addrs = self.query(host, qtype).await? + } + } + if let Some(first) = addrs.get(0) { + Ok(match first { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(addr) => IpAddr::V4(*addr), + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(addr) => IpAddr::V6(*addr), + }) + } else { + Err(Error::Failed) + } + } + + async fn get_host_by_address(&self, _addr: core::net::IpAddr, _result: &mut [u8]) -> Result { + todo!() + } +} + +fn _assert_covariant<'a, 'b: 'a>(x: DnsSocket<'b>) -> DnsSocket<'a> { + x +} diff --git a/embassy/embassy-net/src/driver_util.rs b/embassy/embassy-net/src/driver_util.rs new file mode 100644 index 0000000..536f4c3 --- /dev/null +++ b/embassy/embassy-net/src/driver_util.rs @@ -0,0 +1,112 @@ +use core::task::Context; + +use embassy_net_driver::{Capabilities, Checksum, Driver, RxToken, TxToken}; +use smoltcp::phy::{self, Medium}; +use smoltcp::time::Instant; + +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, + pub medium: Medium, +} + +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 receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + self.inner + .receive(unwrap!(self.cx.as_deref_mut())) + .map(|(rx, tx)| (RxTokenAdapter(rx), TxTokenAdapter(tx))) + } + + /// Construct a transmit token. + fn transmit(&mut self, _timestamp: Instant) -> Option> { + self.inner.transmit(unwrap!(self.cx.as_deref_mut())).map(TxTokenAdapter) + } + + /// Get a description of device capabilities. + 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 = self.medium; + smolcaps.checksum.ipv4 = convert(caps.checksum.ipv4); + smolcaps.checksum.tcp = convert(caps.checksum.tcp); + smolcaps.checksum.udp = convert(caps.checksum.udp); + #[cfg(feature = "proto-ipv4")] + { + smolcaps.checksum.icmpv4 = convert(caps.checksum.icmpv4); + } + #[cfg(feature = "proto-ipv6")] + { + smolcaps.checksum.icmpv6 = convert(caps.checksum.icmpv6); + } + + smolcaps + } +} + +pub(crate) struct RxTokenAdapter(T) +where + T: RxToken; + +impl phy::RxToken for RxTokenAdapter +where + T: RxToken, +{ + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + self.0.consume(|buf| { + #[cfg(feature = "packet-trace")] + trace!("embassy device rx: {:02x}", buf); + f(buf) + }) + } +} + +pub(crate) struct TxTokenAdapter(T) +where + T: TxToken; + +impl phy::TxToken for TxTokenAdapter +where + T: TxToken, +{ + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + self.0.consume(len, |buf| { + let r = f(buf); + #[cfg(feature = "packet-trace")] + trace!("embassy device tx: {:02x}", buf); + r + }) + } +} diff --git a/embassy/embassy-net/src/fmt.rs b/embassy/embassy-net/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy/embassy-net/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy/embassy-net/src/lib.rs b/embassy/embassy-net/src/lib.rs new file mode 100644 index 0000000..831e01d --- /dev/null +++ b/embassy/embassy-net/src/lib.rs @@ -0,0 +1,890 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(async_fn_in_trait)] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +#[cfg(not(any(feature = "proto-ipv4", feature = "proto-ipv6")))] +compile_error!("You must enable at least one of the following features: proto-ipv4, proto-ipv6"); + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +#[cfg(feature = "dns")] +pub mod dns; +mod driver_util; +#[cfg(feature = "raw")] +pub mod raw; +#[cfg(feature = "tcp")] +pub mod tcp; +mod time; +#[cfg(feature = "udp")] +pub mod udp; + +use core::cell::RefCell; +use core::future::{poll_fn, Future}; +use core::mem::MaybeUninit; +use core::pin::pin; +use core::task::{Context, Poll}; + +pub use embassy_net_driver as driver; +use embassy_net_driver::{Driver, LinkState}; +use embassy_sync::waitqueue::WakerRegistration; +use embassy_time::{Instant, Timer}; +use heapless::Vec; +#[cfg(feature = "dns")] +pub use smoltcp::config::DNS_MAX_SERVER_COUNT; +#[cfg(feature = "multicast")] +pub use smoltcp::iface::MulticastError; +#[cfg(any(feature = "dns", feature = "dhcpv4"))] +use smoltcp::iface::SocketHandle; +use smoltcp::iface::{Interface, SocketSet, SocketStorage}; +use smoltcp::phy::Medium; +#[cfg(feature = "dhcpv4")] +use smoltcp::socket::dhcpv4::{self, RetryConfig}; +#[cfg(feature = "medium-ethernet")] +pub use smoltcp::wire::EthernetAddress; +#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154", feature = "medium-ip"))] +pub use smoltcp::wire::HardwareAddress; +#[cfg(any(feature = "udp", feature = "tcp"))] +pub use smoltcp::wire::IpListenEndpoint; +#[cfg(feature = "medium-ieee802154")] +pub use smoltcp::wire::{Ieee802154Address, Ieee802154Frame}; +pub use smoltcp::wire::{IpAddress, IpCidr, IpEndpoint}; +#[cfg(feature = "proto-ipv4")] +pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr}; +#[cfg(feature = "proto-ipv6")] +pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; + +use crate::driver_util::DriverAdapter; +use crate::time::{instant_from_smoltcp, instant_to_smoltcp}; + +const LOCAL_PORT_MIN: u16 = 1025; +const LOCAL_PORT_MAX: u16 = 65535; +#[cfg(feature = "dns")] +const MAX_QUERIES: usize = 4; +#[cfg(feature = "dhcpv4-hostname")] +const MAX_HOSTNAME_LEN: usize = 32; + +/// Memory resources needed for a network stack. +pub struct StackResources { + sockets: MaybeUninit<[SocketStorage<'static>; SOCK]>, + inner: MaybeUninit>, + #[cfg(feature = "dns")] + queries: MaybeUninit<[Option; MAX_QUERIES]>, + #[cfg(feature = "dhcpv4-hostname")] + hostname: HostnameResources, +} + +#[cfg(feature = "dhcpv4-hostname")] +struct HostnameResources { + option: MaybeUninit>, + data: MaybeUninit<[u8; MAX_HOSTNAME_LEN]>, +} + +impl StackResources { + /// Create a new set of stack resources. + pub const fn new() -> Self { + Self { + sockets: MaybeUninit::uninit(), + inner: MaybeUninit::uninit(), + #[cfg(feature = "dns")] + queries: MaybeUninit::uninit(), + #[cfg(feature = "dhcpv4-hostname")] + hostname: HostnameResources { + option: MaybeUninit::uninit(), + data: MaybeUninit::uninit(), + }, + } + } +} + +/// Static IP address configuration. +#[cfg(feature = "proto-ipv4")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticConfigV4 { + /// IP address and subnet mask. + pub address: Ipv4Cidr, + /// Default gateway. + pub gateway: Option, + /// DNS servers. + pub dns_servers: Vec, +} + +/// Static IPv6 address configuration +#[cfg(feature = "proto-ipv6")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticConfigV6 { + /// IP address and subnet mask. + pub address: Ipv6Cidr, + /// Default gateway. + pub gateway: Option, + /// DNS servers. + pub dns_servers: Vec, +} + +/// DHCP configuration. +#[cfg(feature = "dhcpv4")] +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct DhcpConfig { + /// Maximum lease duration. + /// + /// If not set, the lease duration specified by the server will be used. + /// If set, the lease duration will be capped at this value. + pub max_lease_duration: Option, + /// Retry configuration. + pub retry_config: RetryConfig, + /// Ignore NAKs from DHCP servers. + /// + /// This is not compliant with the DHCP RFCs, since theoretically we must stop using the assigned IP when receiving a NAK. This can increase reliability on broken networks with buggy routers or rogue DHCP servers, however. + pub ignore_naks: bool, + /// Server port. This is almost always 67. Do not change unless you know what you're doing. + pub server_port: u16, + /// Client port. This is almost always 68. Do not change unless you know what you're doing. + pub client_port: u16, + /// Our hostname. This will be sent to the DHCP server as Option 12. + #[cfg(feature = "dhcpv4-hostname")] + pub hostname: Option>, +} + +#[cfg(feature = "dhcpv4")] +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, + #[cfg(feature = "dhcpv4-hostname")] + hostname: None, + } + } +} + +/// Network stack configuration. +#[derive(Debug, Clone, Default)] +#[non_exhaustive] +pub struct Config { + /// IPv4 configuration + #[cfg(feature = "proto-ipv4")] + pub ipv4: ConfigV4, + /// IPv6 configuration + #[cfg(feature = "proto-ipv6")] + pub ipv6: ConfigV6, +} + +impl Config { + /// IPv4 configuration with static addressing. + #[cfg(feature = "proto-ipv4")] + pub const fn ipv4_static(config: StaticConfigV4) -> Self { + Self { + ipv4: ConfigV4::Static(config), + #[cfg(feature = "proto-ipv6")] + ipv6: ConfigV6::None, + } + } + + /// IPv6 configuration with static addressing. + #[cfg(feature = "proto-ipv6")] + pub const fn ipv6_static(config: StaticConfigV6) -> Self { + Self { + #[cfg(feature = "proto-ipv4")] + ipv4: ConfigV4::None, + ipv6: ConfigV6::Static(config), + } + } + + /// IPv4 configuration with dynamic addressing. + /// + /// # Example + /// ```rust + /// # use embassy_net::Config; + /// let _cfg = Config::dhcpv4(Default::default()); + /// ``` + #[cfg(feature = "dhcpv4")] + pub const fn dhcpv4(config: DhcpConfig) -> Self { + Self { + ipv4: ConfigV4::Dhcp(config), + #[cfg(feature = "proto-ipv6")] + ipv6: ConfigV6::None, + } + } +} + +/// Network stack IPv4 configuration. +#[cfg(feature = "proto-ipv4")] +#[derive(Debug, Clone, Default)] +pub enum ConfigV4 { + /// Do not configure IPv4. + #[default] + None, + /// Use a static IPv4 address configuration. + Static(StaticConfigV4), + /// Use DHCP to obtain an IP address configuration. + #[cfg(feature = "dhcpv4")] + Dhcp(DhcpConfig), +} + +/// Network stack IPv6 configuration. +#[cfg(feature = "proto-ipv6")] +#[derive(Debug, Clone, Default)] +pub enum ConfigV6 { + /// Do not configure IPv6. + #[default] + None, + /// Use a static IPv6 address configuration. + Static(StaticConfigV6), +} + +/// Network stack runner. +/// +/// You must call [`Runner::run()`] in a background task for the network stack to work. +pub struct Runner<'d, D: Driver> { + driver: D, + stack: Stack<'d>, +} + +/// Network stack handle +/// +/// Use this to create sockets. It's `Copy`, so you can pass +/// it by value instead of by reference. +#[derive(Copy, Clone)] +pub struct Stack<'d> { + inner: &'d RefCell, +} + +pub(crate) struct Inner { + pub(crate) sockets: SocketSet<'static>, // Lifetime type-erased. + pub(crate) iface: Interface, + /// Waker used for triggering polls. + pub(crate) waker: WakerRegistration, + /// Waker used for waiting for link up or config up. + state_waker: WakerRegistration, + hardware_address: HardwareAddress, + next_local_port: u16, + link_up: bool, + #[cfg(feature = "proto-ipv4")] + static_v4: Option, + #[cfg(feature = "proto-ipv6")] + static_v6: Option, + #[cfg(feature = "dhcpv4")] + dhcp_socket: Option, + #[cfg(feature = "dns")] + dns_socket: SocketHandle, + #[cfg(feature = "dns")] + dns_waker: WakerRegistration, + #[cfg(feature = "dhcpv4-hostname")] + hostname: *mut HostnameResources, +} + +fn _assert_covariant<'a, 'b: 'a>(x: Stack<'b>) -> Stack<'a> { + x +} + +/// Create a new network stack. +pub fn new<'d, D: Driver, const SOCK: usize>( + mut driver: D, + config: Config, + resources: &'d mut StackResources, + random_seed: u64, +) -> (Stack<'d>, Runner<'d, D>) { + let (hardware_address, medium) = to_smoltcp_hardware_address(driver.hardware_address()); + let mut iface_cfg = smoltcp::iface::Config::new(hardware_address); + iface_cfg.random_seed = random_seed; + + let iface = Interface::new( + iface_cfg, + &mut DriverAdapter { + inner: &mut driver, + cx: None, + medium, + }, + instant_to_smoltcp(Instant::now()), + ); + + unsafe fn transmute_slice(x: &mut [T]) -> &'static mut [T] { + core::mem::transmute(x) + } + + let sockets = resources.sockets.write([SocketStorage::EMPTY; SOCK]); + #[allow(unused_mut)] + let mut sockets: SocketSet<'static> = SocketSet::new(unsafe { transmute_slice(sockets) }); + + let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; + + #[cfg(feature = "dns")] + let dns_socket = sockets.add(dns::Socket::new( + &[], + managed::ManagedSlice::Borrowed(unsafe { + transmute_slice(resources.queries.write([const { None }; MAX_QUERIES])) + }), + )); + + let mut inner = Inner { + sockets, + iface, + waker: WakerRegistration::new(), + state_waker: WakerRegistration::new(), + next_local_port, + hardware_address, + link_up: false, + #[cfg(feature = "proto-ipv4")] + static_v4: None, + #[cfg(feature = "proto-ipv6")] + static_v6: None, + #[cfg(feature = "dhcpv4")] + dhcp_socket: None, + #[cfg(feature = "dns")] + dns_socket, + #[cfg(feature = "dns")] + dns_waker: WakerRegistration::new(), + #[cfg(feature = "dhcpv4-hostname")] + hostname: &mut resources.hostname, + }; + + #[cfg(feature = "proto-ipv4")] + inner.set_config_v4(config.ipv4); + #[cfg(feature = "proto-ipv6")] + inner.set_config_v6(config.ipv6); + inner.apply_static_config(); + + let inner = &*resources.inner.write(RefCell::new(inner)); + let stack = Stack { inner }; + (stack, Runner { driver, stack }) +} + +fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) { + match addr { + #[cfg(feature = "medium-ethernet")] + driver::HardwareAddress::Ethernet(eth) => (HardwareAddress::Ethernet(EthernetAddress(eth)), Medium::Ethernet), + #[cfg(feature = "medium-ieee802154")] + driver::HardwareAddress::Ieee802154(ieee) => ( + HardwareAddress::Ieee802154(Ieee802154Address::Extended(ieee)), + Medium::Ieee802154, + ), + #[cfg(feature = "medium-ip")] + driver::HardwareAddress::Ip => (HardwareAddress::Ip, Medium::Ip), + + #[allow(unreachable_patterns)] + _ => panic!( + "Unsupported medium {:?}. Make sure to enable the right medium feature in embassy-net's Cargo features.", + addr + ), + } +} + +impl<'d> Stack<'d> { + fn with(&self, f: impl FnOnce(&Inner) -> R) -> R { + f(&self.inner.borrow()) + } + + fn with_mut(&self, f: impl FnOnce(&mut Inner) -> R) -> R { + f(&mut self.inner.borrow_mut()) + } + + /// Get the hardware address of the network interface. + pub fn hardware_address(&self) -> HardwareAddress { + self.with(|i| i.hardware_address) + } + + /// Check whether the link is up. + pub fn is_link_up(&self) -> bool { + self.with(|i| i.link_up) + } + + /// Check whether the network stack has a valid IP configuration. + /// This is true if the network stack has a static IP configuration or if DHCP has completed + pub fn is_config_up(&self) -> bool { + let v4_up; + let v6_up; + + #[cfg(feature = "proto-ipv4")] + { + v4_up = self.config_v4().is_some(); + } + #[cfg(not(feature = "proto-ipv4"))] + { + v4_up = false; + } + + #[cfg(feature = "proto-ipv6")] + { + v6_up = self.config_v6().is_some(); + } + #[cfg(not(feature = "proto-ipv6"))] + { + v6_up = false; + } + + v4_up || v6_up + } + + /// Wait for the network device to obtain a link signal. + pub async fn wait_link_up(&self) { + self.wait(|| self.is_link_up()).await + } + + /// Wait for the network device to lose link signal. + pub async fn wait_link_down(&self) { + self.wait(|| !self.is_link_up()).await + } + + /// Wait for the network stack to obtain a valid IP configuration. + /// + /// ## Notes: + /// - Ensure [`Runner::run`] has been started before using this function. + /// + /// - This function may never return (e.g. if no configuration is obtained through DHCP). + /// The caller is supposed to handle a timeout for this case. + /// + /// ## Example + /// ```ignore + /// let config = embassy_net::Config::dhcpv4(Default::default()); + /// // Init network stack + /// // NOTE: DHCP and DNS need one socket slot if enabled. This is why we're + /// // provisioning space for 3 sockets here: one for DHCP, one for DNS, and one for your code (e.g. TCP). + /// // If you use more sockets you must increase this. If you don't enable DHCP or DNS you can decrease it. + /// static RESOURCES: StaticCell> = StaticCell::new(); + /// let (stack, runner) = embassy_net::new( + /// driver, + /// config, + /// RESOURCES.init(embassy_net::StackResources::new()), + /// seed + /// ); + /// // Launch network task that runs `runner.run().await` + /// spawner.spawn(net_task(runner)).unwrap(); + /// // Wait for DHCP config + /// stack.wait_config_up().await; + /// // use the network stack + /// // ... + /// ``` + pub async fn wait_config_up(&self) { + self.wait(|| self.is_config_up()).await + } + + /// Wait for the network stack to lose a valid IP configuration. + pub async fn wait_config_down(&self) { + self.wait(|| !self.is_config_up()).await + } + + fn wait<'a>(&'a self, mut predicate: impl FnMut() -> bool + 'a) -> impl Future + 'a { + poll_fn(move |cx| { + if predicate() { + Poll::Ready(()) + } else { + // If the config is not up, we register a waker that is woken up + // when a config is applied (static or DHCP). + trace!("Waiting for config up"); + + self.with_mut(|i| { + i.state_waker.register(cx.waker()); + }); + + Poll::Pending + } + }) + } + + /// Get the current IPv4 configuration. + /// + /// If using DHCP, this will be None if DHCP hasn't been able to + /// acquire an IP address, or Some if it has. + #[cfg(feature = "proto-ipv4")] + pub fn config_v4(&self) -> Option { + self.with(|i| i.static_v4.clone()) + } + + /// Get the current IPv6 configuration. + #[cfg(feature = "proto-ipv6")] + pub fn config_v6(&self) -> Option { + self.with(|i| i.static_v6.clone()) + } + + /// Set the IPv4 configuration. + #[cfg(feature = "proto-ipv4")] + pub fn set_config_v4(&self, config: ConfigV4) { + self.with_mut(|i| { + i.set_config_v4(config); + i.apply_static_config(); + }) + } + + /// Set the IPv6 configuration. + #[cfg(feature = "proto-ipv6")] + pub fn set_config_v6(&self, config: ConfigV6) { + self.with_mut(|i| { + i.set_config_v6(config); + i.apply_static_config(); + }) + } + + /// 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> { + // For A and AAAA queries we try detect whether `name` is just an IP address + match qtype { + #[cfg(feature = "proto-ipv4")] + dns::DnsQueryType::A => { + if let Ok(ip) = name.parse().map(IpAddress::Ipv4) { + return Ok([ip].into_iter().collect()); + } + } + #[cfg(feature = "proto-ipv6")] + dns::DnsQueryType::Aaaa => { + if let Ok(ip) = name.parse().map(IpAddress::Ipv6) { + return Ok([ip].into_iter().collect()); + } + } + _ => {} + } + + let query = poll_fn(|cx| { + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); + match socket.start_query(i.iface.context(), name, qtype) { + Ok(handle) => { + i.waker.wake(); + Poll::Ready(Ok(handle)) + } + Err(dns::StartQueryError::NoFreeSlot) => { + i.dns_waker.register(cx.waker()); + Poll::Pending + } + Err(e) => Poll::Ready(Err(e)), + } + }) + }) + .await?; + + #[must_use = "to delay the drop handler invocation to the end of the scope"] + struct OnDrop { + f: core::mem::MaybeUninit, + } + + impl OnDrop { + fn new(f: F) -> Self { + Self { + f: core::mem::MaybeUninit::new(f), + } + } + + fn defuse(self) { + core::mem::forget(self) + } + } + + impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } + } + + let drop = OnDrop::new(|| { + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); + socket.cancel_query(query); + i.waker.wake(); + i.dns_waker.wake(); + }) + }); + + let res = poll_fn(|cx| { + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); + match socket.get_query_result(query) { + Ok(addrs) => { + i.dns_waker.wake(); + 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 + } +} + +#[cfg(feature = "multicast")] +impl<'d> Stack<'d> { + /// Join a multicast group. + pub fn join_multicast_group(&self, addr: impl Into) -> Result<(), MulticastError> { + self.with_mut(|i| i.iface.join_multicast_group(addr)) + } + + /// Leave a multicast group. + pub fn leave_multicast_group(&self, addr: impl Into) -> Result<(), MulticastError> { + self.with_mut(|i| i.iface.leave_multicast_group(addr)) + } + + /// Get whether the network stack has joined the given multicast group. + pub fn has_multicast_group(&self, addr: impl Into) -> bool { + self.with(|i| i.iface.has_multicast_group(addr)) + } +} + +impl Inner { + #[allow(clippy::absurd_extreme_comparisons)] + pub fn get_local_port(&mut self) -> u16 { + let res = self.next_local_port; + self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 }; + res + } + + #[cfg(feature = "proto-ipv4")] + pub fn set_config_v4(&mut self, config: ConfigV4) { + // Handle static config. + self.static_v4 = match config.clone() { + ConfigV4::None => None, + #[cfg(feature = "dhcpv4")] + ConfigV4::Dhcp(_) => None, + ConfigV4::Static(c) => Some(c), + }; + + // Handle DHCP config. + #[cfg(feature = "dhcpv4")] + match config { + ConfigV4::Dhcp(c) => { + // Create the socket if it doesn't exist. + if self.dhcp_socket.is_none() { + let socket = smoltcp::socket::dhcpv4::Socket::new(); + let handle = self.sockets.add(socket); + self.dhcp_socket = Some(handle); + } + + // Configure it + let socket = self.sockets.get_mut::(unwrap!(self.dhcp_socket)); + socket.set_ignore_naks(c.ignore_naks); + socket.set_max_lease_duration(c.max_lease_duration.map(crate::time::duration_to_smoltcp)); + socket.set_ports(c.server_port, c.client_port); + socket.set_retry_config(c.retry_config); + + socket.set_outgoing_options(&[]); + #[cfg(feature = "dhcpv4-hostname")] + if let Some(h) = c.hostname { + // safety: + // - we just did set_outgoing_options([]) so we know the socket is no longer holding a reference. + // - we know this pointer lives for as long as the stack exists, because `new()` borrows + // the resources for `'d`. Therefore it's OK to pass a reference to this to smoltcp. + let hostname = unsafe { &mut *self.hostname }; + + // create data + let data = hostname.data.write([0; MAX_HOSTNAME_LEN]); + data[..h.len()].copy_from_slice(h.as_bytes()); + let data: &[u8] = &data[..h.len()]; + + // set the option. + let option = hostname.option.write(smoltcp::wire::DhcpOption { data, kind: 12 }); + socket.set_outgoing_options(core::slice::from_ref(option)); + } + + socket.reset(); + } + _ => { + // Remove DHCP socket if any. + if let Some(socket) = self.dhcp_socket { + self.sockets.remove(socket); + self.dhcp_socket = None; + } + } + } + } + + #[cfg(feature = "proto-ipv6")] + pub fn set_config_v6(&mut self, config: ConfigV6) { + self.static_v6 = match config { + ConfigV6::None => None, + ConfigV6::Static(c) => Some(c), + }; + } + + fn apply_static_config(&mut self) { + let mut addrs = Vec::new(); + #[cfg(feature = "dns")] + let mut dns_servers: Vec<_, 6> = Vec::new(); + #[cfg(feature = "proto-ipv4")] + let mut gateway_v4 = None; + #[cfg(feature = "proto-ipv6")] + let mut gateway_v6 = None; + + #[cfg(feature = "proto-ipv4")] + if let Some(config) = &self.static_v4 { + debug!("IPv4: UP"); + debug!(" IP address: {:?}", config.address); + debug!(" Default gateway: {:?}", config.gateway); + + unwrap!(addrs.push(IpCidr::Ipv4(config.address)).ok()); + gateway_v4 = config.gateway; + #[cfg(feature = "dns")] + for s in &config.dns_servers { + debug!(" DNS server: {:?}", s); + unwrap!(dns_servers.push(s.clone().into()).ok()); + } + } else { + info!("IPv4: DOWN"); + } + + #[cfg(feature = "proto-ipv6")] + if let Some(config) = &self.static_v6 { + debug!("IPv6: UP"); + debug!(" IP address: {:?}", config.address); + debug!(" Default gateway: {:?}", config.gateway); + + unwrap!(addrs.push(IpCidr::Ipv6(config.address)).ok()); + gateway_v6 = config.gateway.into(); + #[cfg(feature = "dns")] + for s in &config.dns_servers { + debug!(" DNS server: {:?}", s); + unwrap!(dns_servers.push(s.clone().into()).ok()); + } + } else { + info!("IPv6: DOWN"); + } + + // Apply addresses + self.iface.update_ip_addrs(|a| *a = addrs); + + // Apply gateways + #[cfg(feature = "proto-ipv4")] + if let Some(gateway) = gateway_v4 { + unwrap!(self.iface.routes_mut().add_default_ipv4_route(gateway)); + } else { + self.iface.routes_mut().remove_default_ipv4_route(); + } + #[cfg(feature = "proto-ipv6")] + if let Some(gateway) = gateway_v6 { + unwrap!(self.iface.routes_mut().add_default_ipv6_route(gateway)); + } else { + self.iface.routes_mut().remove_default_ipv6_route(); + } + + // Apply DNS servers + #[cfg(feature = "dns")] + if !dns_servers.is_empty() { + let count = if dns_servers.len() > DNS_MAX_SERVER_COUNT { + warn!("Number of DNS servers exceeds DNS_MAX_SERVER_COUNT, truncating list."); + DNS_MAX_SERVER_COUNT + } else { + dns_servers.len() + }; + self.sockets + .get_mut::(self.dns_socket) + .update_servers(&dns_servers[..count]); + } + + self.state_waker.wake(); + } + + fn poll(&mut self, cx: &mut Context<'_>, driver: &mut D) { + self.waker.register(cx.waker()); + + let (_hardware_addr, medium) = to_smoltcp_hardware_address(driver.hardware_address()); + + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + { + let do_set = match medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => true, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => true, + #[allow(unreachable_patterns)] + _ => false, + }; + if do_set { + self.iface.set_hardware_addr(_hardware_addr); + } + } + + let timestamp = instant_to_smoltcp(Instant::now()); + let mut smoldev = DriverAdapter { + cx: Some(cx), + inner: driver, + medium, + }; + self.iface.poll(timestamp, &mut smoldev, &mut self.sockets); + + // Update link up + let old_link_up = self.link_up; + self.link_up = driver.link_state(cx) == LinkState::Up; + + // Print when changed + if old_link_up != self.link_up { + info!("link_up = {:?}", self.link_up); + self.state_waker.wake(); + } + + #[cfg(feature = "dhcpv4")] + if let Some(dhcp_handle) = self.dhcp_socket { + let socket = self.sockets.get_mut::(dhcp_handle); + + let configure = if self.link_up { + if old_link_up != self.link_up { + socket.reset(); + } + match socket.poll() { + None => false, + Some(dhcpv4::Event::Deconfigured) => { + self.static_v4 = None; + true + } + Some(dhcpv4::Event::Configured(config)) => { + self.static_v4 = Some(StaticConfigV4 { + address: config.address, + gateway: config.router, + dns_servers: config.dns_servers, + }); + true + } + } + } else if old_link_up { + socket.reset(); + self.static_v4 = None; + true + } else { + false + }; + if configure { + self.apply_static_config() + } + } + + if let Some(poll_at) = self.iface.poll_at(timestamp, &mut self.sockets) { + let t = pin!(Timer::at(instant_from_smoltcp(poll_at))); + if t.poll(cx).is_ready() { + cx.waker().wake_by_ref(); + } + } + } +} + +impl<'d, D: Driver> Runner<'d, D> { + /// Run the network stack. + /// + /// You must call this in a background task, to process network events. + pub async fn run(&mut self) -> ! { + poll_fn(|cx| { + self.stack.with_mut(|i| i.poll(cx, &mut self.driver)); + Poll::<()>::Pending + }) + .await; + unreachable!() + } +} diff --git a/embassy/embassy-net/src/raw.rs b/embassy/embassy-net/src/raw.rs new file mode 100644 index 0000000..a88bcc4 --- /dev/null +++ b/embassy/embassy-net/src/raw.rs @@ -0,0 +1,191 @@ +//! Raw sockets. + +use core::future::poll_fn; +use core::mem; +use core::task::{Context, Poll}; + +use embassy_net_driver::Driver; +use smoltcp::iface::{Interface, SocketHandle}; +use smoltcp::socket::raw; +pub use smoltcp::socket::raw::PacketMetadata; +pub use smoltcp::wire::{IpProtocol, IpVersion}; + +use crate::Stack; + +/// Error returned by [`RawSocket::recv`] and [`RawSocket::send`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RecvError { + /// Provided buffer was smaller than the received packet. + Truncated, +} + +/// An Raw socket. +pub struct RawSocket<'a> { + stack: Stack<'a>, + handle: SocketHandle, +} + +impl<'a> RawSocket<'a> { + /// Create a new Raw socket using the provided stack and buffers. + pub fn new( + stack: Stack<'a>, + ip_version: IpVersion, + ip_protocol: IpProtocol, + rx_meta: &'a mut [PacketMetadata], + rx_buffer: &'a mut [u8], + tx_meta: &'a mut [PacketMetadata], + tx_buffer: &'a mut [u8], + ) -> Self { + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(raw::Socket::new( + ip_version, + ip_protocol, + raw::PacketBuffer::new(rx_meta, rx_buffer), + raw::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); + + Self { stack, handle } + } + + fn with_mut(&self, f: impl FnOnce(&mut raw::Socket, &mut Interface) -> R) -> R { + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) + } + + /// Wait until the socket becomes readable. + /// + /// A socket is readable when a packet has been received, or when there are queued packets in + /// the buffer. + pub async fn wait_recv_ready(&self) { + poll_fn(move |cx| self.poll_recv_ready(cx)).await + } + + /// Receive a datagram. + /// + /// This method will wait until a datagram is received. + pub async fn recv(&self, buf: &mut [u8]) -> Result { + poll_fn(move |cx| self.poll_recv(buf, cx)).await + } + + /// Wait until a datagram can be read. + /// + /// When no datagram is readable, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready`. + pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Receive a datagram. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + pub fn poll_recv(&self, buf: &mut [u8], cx: &mut Context<'_>) -> Poll> { + self.with_mut(|s, _| match s.recv_slice(buf) { + Ok(n) => Poll::Ready(Ok(n)), + // No data ready + Err(raw::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)), + Err(raw::RecvError::Exhausted) => { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when there is space in the buffer, from initial memory or after + /// dispatching datagrams on a full buffer. + pub async fn wait_send_ready(&self) { + poll_fn(move |cx| self.poll_send_ready(cx)).await + } + + /// Wait until a datagram can be sent. + /// + /// When no datagram can be sent (i.e. the buffer is full), this method will return + /// `Poll::Pending` and register the current task to be notified when + /// space is freed in the buffer after a datagram has been dispatched. + /// + /// When a datagram can be sent, this method will return `Poll::Ready`. + pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + // socket buffer is full wait until a datagram has been dispatched + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Send a datagram. + /// + /// This method will wait until the datagram has been sent.` + pub async fn send(&self, buf: &[u8]) { + poll_fn(move |cx| self.poll_send(buf, cx)).await + } + + /// Send a datagram. + /// + /// When the datagram has been sent, this method will return `Poll::Ready(Ok())`. + /// + /// When the socket's send buffer is full, this method will return `Poll::Pending` + /// and register the current task to be notified when the buffer has space available. + pub fn poll_send(&self, buf: &[u8], cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| match s.send_slice(buf) { + // Entire datagram has been sent + Ok(()) => Poll::Ready(()), + Err(raw::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Flush the socket. + /// + /// This method will wait until the socket is flushed. + pub async fn flush(&mut self) { + poll_fn(move |cx| { + self.with_mut(|s, _| { + if s.send_queue() == 0 { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + }) + .await + } +} + +impl Drop for RawSocket<'_> { + fn drop(&mut self) { + self.stack.with_mut(|i| i.sockets.remove(self.handle)); + } +} + +fn _assert_covariant<'a, 'b: 'a>(x: RawSocket<'b>) -> RawSocket<'a> { + x +} diff --git a/embassy/embassy-net/src/tcp.rs b/embassy/embassy-net/src/tcp.rs new file mode 100644 index 0000000..32d3740 --- /dev/null +++ b/embassy/embassy-net/src/tcp.rs @@ -0,0 +1,923 @@ +//! TCP sockets. +//! +//! # Listening +//! +//! `embassy-net` does not have a `TcpListener`. Instead, individual `TcpSocket`s can be put into +//! listening mode by calling [`TcpSocket::accept`]. +//! +//! Incoming connections when no socket is listening are rejected. To accept many incoming +//! connections, create many sockets and put them all into listening mode. + +use core::future::poll_fn; +use core::mem; +use core::task::{Context, Poll}; + +use embassy_time::Duration; +use smoltcp::iface::{Interface, SocketHandle}; +use smoltcp::socket::tcp; +pub use smoltcp::socket::tcp::State; +use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; + +use crate::time::duration_to_smoltcp; +use crate::Stack; + +/// Error returned by TcpSocket read/write functions. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The connection was reset. + /// + /// This can happen on receiving a RST packet, or on timeout. + ConnectionReset, +} + +/// Error returned by [`TcpSocket::connect`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConnectError { + /// The socket is already connected or listening. + InvalidState, + /// The remote host rejected the connection with a RST packet. + ConnectionReset, + /// Connect timed out. + TimedOut, + /// No route to host. + NoRoute, +} + +/// Error returned by [`TcpSocket::accept`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AcceptError { + /// The socket is already connected or listening. + InvalidState, + /// Invalid listen port + InvalidPort, + /// The remote host rejected the connection with a RST packet. + ConnectionReset, +} + +/// A TCP socket. +pub struct TcpSocket<'a> { + io: TcpIo<'a>, +} + +/// The reader half of a TCP socket. +pub struct TcpReader<'a> { + io: TcpIo<'a>, +} + +/// The writer half of a TCP socket. +pub struct TcpWriter<'a> { + io: TcpIo<'a>, +} + +impl<'a> TcpReader<'a> { + /// Wait until the socket becomes readable. + /// + /// A socket becomes readable when the receive half of the full-duplex connection is open + /// (see [`may_recv()`](TcpSocket::may_recv)), and there is some pending data in the receive buffer. + /// + /// This is the equivalent of [read](#method.read), without buffering any data. + pub async fn wait_read_ready(&self) { + poll_fn(move |cx| self.io.poll_read_ready(cx)).await + } + + /// Read data from the socket. + /// + /// Returns how many bytes were read, or an error. If no data is available, it waits + /// until there is at least one byte available. + /// + /// # Note + /// A return value of Ok(0) means that we have read all data and the remote + /// side has closed our receive half of the socket. The remote can no longer + /// send bytes. + /// + /// The send half of the socket is still open. If you want to reconnect using + /// the socket you split this reader off the send half needs to be closed using + /// [`abort()`](TcpSocket::abort). + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + + /// Call `f` with the largest contiguous slice of octets in the receive buffer, + /// and dequeue the amount of elements returned by `f`. + /// + /// If no data is available, it waits until there is at least one byte available. + pub async fn read_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.read_with(f).await + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn recv_capacity(&self) -> usize { + self.io.recv_capacity() + } + + /// Return the amount of octets queued in the receive buffer. This value can be larger than + /// the slice read by the next `recv` or `peek` call because it includes all queued octets, + /// and not only the octets that may be returned as a contiguous slice. + pub fn recv_queue(&self) -> usize { + self.io.recv_queue() + } +} + +impl<'a> TcpWriter<'a> { + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when the transmit half of the full-duplex connection is open + /// (see [`may_send()`](TcpSocket::may_send)), and the transmit buffer is not full. + /// + /// This is the equivalent of [write](#method.write), without sending any data. + pub async fn wait_write_ready(&self) { + poll_fn(move |cx| self.io.poll_write_ready(cx)).await + } + + /// Write data to the socket. + /// + /// Returns how many bytes were written, or an error. If the socket is not ready to + /// accept data, it waits until it is. + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + /// Flushes the written data to the socket. + /// + /// This waits until all data has been sent, and ACKed by the remote host. For a connection + /// closed with [`abort()`](TcpSocket::abort) it will wait for the TCP RST packet to be sent. + pub async fn flush(&mut self) -> Result<(), Error> { + self.io.flush().await + } + + /// Call `f` with the largest contiguous slice of octets in the transmit buffer, + /// and enqueue the amount of elements returned by `f`. + /// + /// If the socket is not ready to accept data, it waits until it is. + pub async fn write_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.write_with(f).await + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn send_capacity(&self) -> usize { + self.io.send_capacity() + } + + /// Return the amount of octets queued in the transmit buffer. + pub fn send_queue(&self) -> usize { + self.io.send_queue() + } +} + +impl<'a> TcpSocket<'a> { + /// Create a new TCP socket on the given stack, with the given buffers. + pub fn new(stack: Stack<'a>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { + let handle = stack.with_mut(|i| { + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(tcp::Socket::new( + tcp::SocketBuffer::new(rx_buffer), + tcp::SocketBuffer::new(tx_buffer), + )) + }); + + Self { + io: TcpIo { stack, handle }, + } + } + + /// Return the maximum number of bytes inside the recv buffer. + pub fn recv_capacity(&self) -> usize { + self.io.recv_capacity() + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn send_capacity(&self) -> usize { + self.io.send_capacity() + } + + /// Return the amount of octets queued in the transmit buffer. + pub fn send_queue(&self) -> usize { + self.io.send_queue() + } + + /// Return the amount of octets queued in the receive buffer. This value can be larger than + /// the slice read by the next `recv` or `peek` call because it includes all queued octets, + /// and not only the octets that may be returned as a contiguous slice. + pub fn recv_queue(&self) -> usize { + self.io.recv_queue() + } + + /// Call `f` with the largest contiguous slice of octets in the transmit buffer, + /// and enqueue the amount of elements returned by `f`. + /// + /// If the socket is not ready to accept data, it waits until it is. + pub async fn write_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.write_with(f).await + } + + /// Call `f` with the largest contiguous slice of octets in the receive buffer, + /// and dequeue the amount of elements returned by `f`. + /// + /// If no data is available, it waits until there is at least one byte available. + pub async fn read_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.read_with(f).await + } + + /// Split the socket into reader and a writer halves. + pub fn split(&mut self) -> (TcpReader<'_>, TcpWriter<'_>) { + (TcpReader { io: self.io }, TcpWriter { io: self.io }) + } + + /// Connect to a remote host. + pub async fn connect(&mut self, remote_endpoint: T) -> Result<(), ConnectError> + where + T: Into, + { + let local_port = self.io.stack.with_mut(|i| i.get_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| { + self.io.with_mut(|s, _| match s.state() { + tcp::State::Closed | tcp::State::TimeWait => Poll::Ready(Err(ConnectError::ConnectionReset)), + tcp::State::Listen => unreachable!(), + tcp::State::SynSent | tcp::State::SynReceived => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + _ => Poll::Ready(Ok(())), + }) + }) + .await + } + + /// Accept a connection from a remote host. + /// + /// This function puts the socket in listening mode, and waits until a connection is received. + pub async fn accept(&mut self, local_endpoint: T) -> Result<(), AcceptError> + where + T: Into, + { + 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| { + self.io.with_mut(|s, _| match s.state() { + tcp::State::Listen | tcp::State::SynSent | tcp::State::SynReceived => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + _ => Poll::Ready(Ok(())), + }) + }) + .await + } + + /// Wait until the socket becomes readable. + /// + /// A socket becomes readable when the receive half of the full-duplex connection is open + /// (see [may_recv](#method.may_recv)), and there is some pending data in the receive buffer. + /// + /// This is the equivalent of [read](#method.read), without buffering any data. + pub async fn wait_read_ready(&self) { + poll_fn(move |cx| self.io.poll_read_ready(cx)).await + } + + /// Read data from the socket. + /// + /// Returns how many bytes were read, or an error. If no data is available, it waits + /// until there is at least one byte available. + /// + /// A return value of Ok(0) means that the socket was closed and is longer + /// able to receive any data. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when the transmit half of the full-duplex connection is open + /// (see [may_send](#method.may_send)), and the transmit buffer is not full. + /// + /// This is the equivalent of [write](#method.write), without sending any data. + pub async fn wait_write_ready(&self) { + poll_fn(move |cx| self.io.poll_write_ready(cx)).await + } + + /// Write data to the socket. + /// + /// Returns how many bytes were written, or an error. If the socket is not ready to + /// accept data, it waits until it is. + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + /// Flushes the written data to the socket. + /// + /// This waits until all data has been sent, and ACKed by the remote host. For a connection + /// closed with [`abort()`](TcpSocket::abort) it will wait for the TCP RST packet to be sent. + pub async fn flush(&mut self) -> Result<(), Error> { + self.io.flush().await + } + + /// Set the timeout for the socket. + /// + /// If the timeout is set, the socket will be closed if no data is received for the + /// specified duration. + /// + /// # Note: + /// Set a keep alive interval ([`set_keep_alive`] to prevent timeouts when + /// the remote could still respond. + pub fn set_timeout(&mut self, duration: Option) { + self.io + .with_mut(|s, _| s.set_timeout(duration.map(duration_to_smoltcp))) + } + + /// Set the keep-alive interval for the socket. + /// + /// If the keep-alive interval is set, the socket will send keep-alive packets after + /// the specified duration of inactivity. + /// + /// If not set, the socket will not send keep-alive packets. + /// + /// By setting a [`timeout`](Self::timeout) larger then the keep alive you + /// can detect a remote endpoint that no longer answers. + pub fn set_keep_alive(&mut self, interval: Option) { + self.io + .with_mut(|s, _| s.set_keep_alive(interval.map(duration_to_smoltcp))) + } + + /// Set the hop limit field in the IP header of sent packets. + pub fn set_hop_limit(&mut self, hop_limit: Option) { + self.io.with_mut(|s, _| s.set_hop_limit(hop_limit)) + } + + /// Get the local endpoint of the socket. + /// + /// Returns `None` if the socket is not bound (listening) or not connected. + pub fn local_endpoint(&self) -> Option { + self.io.with(|s, _| s.local_endpoint()) + } + + /// Get the remote endpoint of the socket. + /// + /// Returns `None` if the socket is not connected. + pub fn remote_endpoint(&self) -> Option { + self.io.with(|s, _| s.remote_endpoint()) + } + + /// Get the state of the socket. + pub fn state(&self) -> State { + self.io.with(|s, _| s.state()) + } + + /// Close the write half of the socket. + /// + /// This closes only the write half of the socket. The read half side remains open, the + /// socket can still receive data. + /// + /// Data that has been written to the socket and not yet sent (or not yet ACKed) will still + /// still sent. The last segment of the pending to send data is sent with the FIN flag set. + pub fn close(&mut self) { + self.io.with_mut(|s, _| s.close()) + } + + /// Forcibly close the socket. + /// + /// This instantly closes both the read and write halves of the socket. Any pending data + /// that has not been sent will be lost. + /// + /// Note that the TCP RST packet is not sent immediately - if the `TcpSocket` is dropped too soon + /// the remote host may not know the connection has been closed. + /// `abort()` callers should wait for a [`flush()`](TcpSocket::flush) call to complete before + /// dropping or reusing the socket. + pub fn abort(&mut self) { + self.io.with_mut(|s, _| s.abort()) + } + + /// Return whether the transmit half of the full-duplex connection is open. + /// + /// This function returns true if it's possible to send data and have it arrive + /// to the remote endpoint. However, it does not make any guarantees about the state + /// of the transmit buffer, and even if it returns true, [write](#method.write) may + /// not be able to enqueue any octets. + /// + /// In terms of the TCP state machine, the socket must be in the `ESTABLISHED` or + /// `CLOSE-WAIT` state. + pub fn may_send(&self) -> bool { + self.io.with(|s, _| s.may_send()) + } + + /// Check whether the transmit half of the full-duplex connection is open + /// (see [may_send](#method.may_send)), and the transmit buffer is not full. + pub fn can_send(&self) -> bool { + self.io.with(|s, _| s.can_send()) + } + + /// return whether the receive half of the full-duplex connection is open. + /// This function returns true if it’s possible to receive data from the remote endpoint. + /// It will return true while there is data in the receive buffer, and if there isn’t, + /// as long as the remote endpoint has not closed the connection. + pub fn may_recv(&self) -> bool { + self.io.with(|s, _| s.may_recv()) + } + + /// Get whether the socket is ready to receive data, i.e. whether there is some pending data in the receive buffer. + pub fn can_recv(&self) -> bool { + self.io.with(|s, _| s.can_recv()) + } +} + +impl<'a> Drop for TcpSocket<'a> { + fn drop(&mut self) { + self.io.stack.with_mut(|i| i.sockets.remove(self.io.handle)); + } +} + +fn _assert_covariant<'a, 'b: 'a>(x: TcpSocket<'b>) -> TcpSocket<'a> { + x +} +fn _assert_covariant_reader<'a, 'b: 'a>(x: TcpReader<'b>) -> TcpReader<'a> { + x +} +fn _assert_covariant_writer<'a, 'b: 'a>(x: TcpWriter<'b>) -> TcpWriter<'a> { + x +} + +// ======================= + +#[derive(Copy, Clone)] +struct TcpIo<'a> { + stack: Stack<'a>, + handle: SocketHandle, +} + +impl<'d> TcpIo<'d> { + fn with(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R { + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) + } + + fn with_mut(&self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) + } + + fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + async fn read(&mut self, buf: &mut [u8]) -> Result { + 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) { + // Reading into empty buffer + Ok(0) if buf.is_empty() => { + // embedded_io_async::Read's contract is to not block if buf is empty. While + // this function is not a direct implementor of the trait method, we still don't + // want our future to never resolve. + Poll::Ready(Ok(0)) + } + // No data ready + Ok(0) => { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + // Data ready! + Ok(n) => Poll::Ready(Ok(n)), + // EOF + Err(tcp::RecvError::Finished) => Poll::Ready(Ok(0)), + // Connection reset. TODO: this can also be timeouts etc, investigate. + Err(tcp::RecvError::InvalidState) => Poll::Ready(Err(Error::ConnectionReset)), + }) + }) + .await + } + + fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + + async fn write(&mut self, buf: &[u8]) -> Result { + 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) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + // Some data sent + Ok(n) => Poll::Ready(Ok(n)), + // Connection reset. TODO: this can also be timeouts etc, investigate. + Err(tcp::SendError::InvalidState) => Poll::Ready(Err(Error::ConnectionReset)), + }) + }) + .await + } + + async fn write_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + if !s.can_send() { + if s.may_send() { + // socket buffer is full wait until it has atleast one byte free + s.register_send_waker(cx.waker()); + Poll::Pending + } else { + // if we can't transmit because the transmit half of the duplex connection is closed then return an error + Poll::Ready(Err(Error::ConnectionReset)) + } + } else { + Poll::Ready(match s.send(unwrap!(f.take())) { + // Connection reset. TODO: this can also be timeouts etc, investigate. + Err(tcp::SendError::InvalidState) => Err(Error::ConnectionReset), + Ok(r) => Ok(r), + }) + } + }) + }) + .await + } + + async fn read_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + if !s.can_recv() { + if s.may_recv() { + // socket buffer is empty wait until it has atleast one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } else { + // if we can't receive because the receive half of the duplex connection is closed then return an error + Poll::Ready(Err(Error::ConnectionReset)) + } + } else { + Poll::Ready(match s.recv(unwrap!(f.take())) { + // Connection reset. TODO: this can also be timeouts etc, investigate. + Err(tcp::RecvError::Finished) | Err(tcp::RecvError::InvalidState) => { + Err(Error::ConnectionReset) + } + Ok(r) => Ok(r), + }) + } + }) + }) + .await + } + + async fn flush(&mut self) -> Result<(), Error> { + poll_fn(move |cx| { + self.with_mut(|s, _| { + let data_pending = (s.send_queue() > 0) && s.state() != tcp::State::Closed; + let fin_pending = matches!( + s.state(), + tcp::State::FinWait1 | tcp::State::Closing | tcp::State::LastAck + ); + let rst_pending = s.state() == tcp::State::Closed && s.remote_endpoint().is_some(); + + // If there are outstanding send operations, register for wake up and wait + // smoltcp issues wake-ups when octets are dequeued from the send buffer + if data_pending || fin_pending || rst_pending { + s.register_send_waker(cx.waker()); + Poll::Pending + // No outstanding sends, socket is flushed + } else { + Poll::Ready(Ok(())) + } + }) + }) + .await + } + + fn recv_capacity(&self) -> usize { + self.with(|s, _| s.recv_capacity()) + } + + fn send_capacity(&self) -> usize { + self.with(|s, _| s.send_capacity()) + } + + fn send_queue(&self) -> usize { + self.with(|s, _| s.send_queue()) + } + + fn recv_queue(&self) -> usize { + self.with(|s, _| s.recv_queue()) + } +} + +mod embedded_io_impls { + use super::*; + + impl embedded_io_async::Error for ConnectError { + fn kind(&self) -> embedded_io_async::ErrorKind { + match self { + ConnectError::ConnectionReset => embedded_io_async::ErrorKind::ConnectionReset, + ConnectError::TimedOut => embedded_io_async::ErrorKind::TimedOut, + ConnectError::NoRoute => embedded_io_async::ErrorKind::NotConnected, + ConnectError::InvalidState => embedded_io_async::ErrorKind::Other, + } + } + } + + impl embedded_io_async::Error for Error { + fn kind(&self) -> embedded_io_async::ErrorKind { + match self { + Error::ConnectionReset => embedded_io_async::ErrorKind::ConnectionReset, + } + } + } + + impl<'d> embedded_io_async::ErrorType for TcpSocket<'d> { + type Error = Error; + } + + impl<'d> embedded_io_async::Read for TcpSocket<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + } + + impl<'d> embedded_io_async::ReadReady for TcpSocket<'d> { + fn read_ready(&mut self) -> Result { + Ok(self.io.with(|s, _| s.can_recv() || !s.may_recv())) + } + } + + impl<'d> embedded_io_async::Write for TcpSocket<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.io.flush().await + } + } + + impl<'d> embedded_io_async::WriteReady for TcpSocket<'d> { + fn write_ready(&mut self) -> Result { + Ok(self.io.with(|s, _| s.can_send())) + } + } + + impl<'d> embedded_io_async::ErrorType for TcpReader<'d> { + type Error = Error; + } + + impl<'d> embedded_io_async::Read for TcpReader<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + } + + impl<'d> embedded_io_async::ReadReady for TcpReader<'d> { + fn read_ready(&mut self) -> Result { + Ok(self.io.with(|s, _| s.can_recv() || !s.may_recv())) + } + } + + impl<'d> embedded_io_async::ErrorType for TcpWriter<'d> { + type Error = Error; + } + + impl<'d> embedded_io_async::Write for TcpWriter<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.io.flush().await + } + } + + impl<'d> embedded_io_async::WriteReady for TcpWriter<'d> { + fn write_ready(&mut self) -> Result { + Ok(self.io.with(|s, _| s.can_send())) + } + } +} + +/// TCP client compatible with `embedded-nal-async` traits. +pub mod client { + use core::cell::{Cell, UnsafeCell}; + use core::mem::MaybeUninit; + use core::net::IpAddr; + use core::ptr::NonNull; + + use super::*; + + /// TCP client connection pool compatible with `embedded-nal-async` traits. + /// + /// The pool is capable of managing up to N concurrent connections with tx and rx buffers according to TX_SZ and RX_SZ. + pub struct TcpClient<'d, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { + stack: Stack<'d>, + state: &'d TcpClientState, + socket_timeout: Option, + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, N, TX_SZ, RX_SZ> { + /// Create a new `TcpClient`. + pub fn new(stack: Stack<'d>, state: &'d TcpClientState) -> Self { + Self { + stack, + state, + socket_timeout: None, + } + } + + /// Set the timeout for each socket created by this `TcpClient`. + /// + /// If the timeout is set, the socket will be closed if no data is received for the + /// specified duration. + pub fn set_timeout(&mut self, timeout: Option) { + self.socket_timeout = timeout; + } + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect + for TcpClient<'d, N, TX_SZ, RX_SZ> + { + type Error = Error; + type Connection<'m> + = TcpConnection<'m, N, TX_SZ, RX_SZ> + where + Self: 'm; + + async fn connect<'a>(&'a self, remote: core::net::SocketAddr) -> Result, Self::Error> { + let addr: crate::IpAddress = match remote.ip() { + #[cfg(feature = "proto-ipv4")] + IpAddr::V4(addr) => crate::IpAddress::Ipv4(addr), + #[cfg(not(feature = "proto-ipv4"))] + IpAddr::V4(_) => panic!("ipv4 support not enabled"), + #[cfg(feature = "proto-ipv6")] + IpAddr::V6(addr) => crate::IpAddress::Ipv6(addr), + #[cfg(not(feature = "proto-ipv6"))] + IpAddr::V6(_) => panic!("ipv6 support not enabled"), + }; + let remote_endpoint = (addr, remote.port()); + let mut socket = TcpConnection::new(self.stack, self.state)?; + socket.socket.set_timeout(self.socket_timeout); + socket + .socket + .connect(remote_endpoint) + .await + .map_err(|_| Error::ConnectionReset)?; + Ok(socket) + } + } + + /// Opened TCP connection in a [`TcpClient`]. + pub struct TcpConnection<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> { + socket: TcpSocket<'d>, + state: &'d TcpClientState, + bufs: NonNull<([u8; TX_SZ], [u8; RX_SZ])>, + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpConnection<'d, N, TX_SZ, RX_SZ> { + fn new(stack: Stack<'d>, state: &'d TcpClientState) -> Result { + let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?; + Ok(Self { + socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) }, + state, + bufs, + }) + } + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> Drop for TcpConnection<'d, N, TX_SZ, RX_SZ> { + fn drop(&mut self) { + unsafe { + self.socket.close(); + self.state.pool.free(self.bufs); + } + } + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::ErrorType + for TcpConnection<'d, N, TX_SZ, RX_SZ> + { + type Error = Error; + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::Read + for TcpConnection<'d, N, TX_SZ, RX_SZ> + { + 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_async::Write + for TcpConnection<'d, N, TX_SZ, RX_SZ> + { + async fn write(&mut self, buf: &[u8]) -> Result { + self.socket.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.socket.flush().await + } + } + + /// State for TcpClient + pub struct TcpClientState { + pool: Pool<([u8; TX_SZ], [u8; RX_SZ]), N>, + } + + impl TcpClientState { + /// Create a new `TcpClientState`. + pub const fn new() -> Self { + Self { pool: Pool::new() } + } + } + + struct Pool { + used: [Cell; N], + data: [UnsafeCell>; N], + } + + impl Pool { + const VALUE: Cell = Cell::new(false); + const UNINIT: UnsafeCell> = UnsafeCell::new(MaybeUninit::uninit()); + + const fn new() -> Self { + Self { + used: [Self::VALUE; N], + data: [Self::UNINIT; N], + } + } + } + + impl Pool { + fn alloc(&self) -> Option> { + for n in 0..N { + // this can't race because Pool is not Sync. + if !self.used[n].get() { + self.used[n].set(true); + let p = self.data[n].get() as *mut T; + return Some(unsafe { NonNull::new_unchecked(p) }); + } + } + None + } + + /// safety: p must be a pointer obtained from self.alloc that hasn't been freed yet. + unsafe fn free(&self, p: NonNull) { + let origin = self.data.as_ptr() as *mut T; + let n = p.as_ptr().offset_from(origin); + assert!(n >= 0); + assert!((n as usize) < N); + self.used[n as usize].set(false); + } + } +} diff --git a/embassy/embassy-net/src/time.rs b/embassy/embassy-net/src/time.rs new file mode 100644 index 0000000..b98d40f --- /dev/null +++ b/embassy/embassy-net/src/time.rs @@ -0,0 +1,20 @@ +#![allow(unused)] + +use embassy_time::{Duration, Instant}; +use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; + +pub(crate) fn instant_to_smoltcp(instant: Instant) -> SmolInstant { + SmolInstant::from_micros(instant.as_micros() as i64) +} + +pub(crate) fn instant_from_smoltcp(instant: SmolInstant) -> Instant { + Instant::from_micros(instant.total_micros() as u64) +} + +pub(crate) fn duration_to_smoltcp(duration: Duration) -> SmolDuration { + SmolDuration::from_micros(duration.as_micros()) +} + +pub(crate) fn duration_from_smoltcp(duration: SmolDuration) -> Duration { + Duration::from_micros(duration.total_micros()) +} diff --git a/embassy/embassy-net/src/udp.rs b/embassy/embassy-net/src/udp.rs new file mode 100644 index 0000000..76602ed --- /dev/null +++ b/embassy/embassy-net/src/udp.rs @@ -0,0 +1,374 @@ +//! UDP sockets. + +use core::future::poll_fn; +use core::mem; +use core::task::{Context, Poll}; + +use smoltcp::iface::{Interface, SocketHandle}; +use smoltcp::socket::udp; +pub use smoltcp::socket::udp::{PacketMetadata, UdpMetadata}; +use smoltcp::wire::IpListenEndpoint; + +use crate::Stack; + +/// Error returned by [`UdpSocket::bind`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BindError { + /// The socket was already open. + InvalidState, + /// No route to host. + NoRoute, +} + +/// Error returned by [`UdpSocket::recv_from`] and [`UdpSocket::send_to`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SendError { + /// No route to host. + NoRoute, + /// Socket not bound to an outgoing port. + SocketNotBound, +} + +/// Error returned by [`UdpSocket::recv_from`] and [`UdpSocket::send_to`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RecvError { + /// Provided buffer was smaller than the received packet. + Truncated, +} + +/// An UDP socket. +pub struct UdpSocket<'a> { + stack: Stack<'a>, + handle: SocketHandle, +} + +impl<'a> UdpSocket<'a> { + /// Create a new UDP socket using the provided stack and buffers. + pub fn new( + stack: Stack<'a>, + rx_meta: &'a mut [PacketMetadata], + rx_buffer: &'a mut [u8], + tx_meta: &'a mut [PacketMetadata], + tx_buffer: &'a mut [u8], + ) -> Self { + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(udp::Socket::new( + udp::PacketBuffer::new(rx_meta, rx_buffer), + udp::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); + + Self { stack, handle } + } + + /// Bind the socket to a local endpoint. + pub fn bind(&mut self, endpoint: T) -> Result<(), BindError> + where + T: Into, + { + let mut endpoint = endpoint.into(); + + if endpoint.port == 0 { + // If user didn't specify port allocate a dynamic port. + endpoint.port = self.stack.with_mut(|i| i.get_local_port()); + } + + match self.with_mut(|s, _| s.bind(endpoint)) { + Ok(()) => Ok(()), + Err(udp::BindError::InvalidState) => Err(BindError::InvalidState), + Err(udp::BindError::Unaddressable) => Err(BindError::NoRoute), + } + } + + fn with(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R { + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) + } + + fn with_mut(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R { + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) + } + + /// Wait until the socket becomes readable. + /// + /// A socket is readable when a packet has been received, or when there are queued packets in + /// the buffer. + pub async fn wait_recv_ready(&self) { + poll_fn(move |cx| self.poll_recv_ready(cx)).await + } + + /// Wait until a datagram can be read. + /// + /// When no datagram is readable, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready`. + pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Receive a datagram. + /// + /// This method will wait until a datagram is received. + /// + /// Returns the number of bytes received and the remote endpoint. + pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, UdpMetadata), RecvError> { + poll_fn(move |cx| self.poll_recv_from(buf, cx)).await + } + + /// Receive a datagram. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready` with the + /// number of bytes received and the remote endpoint. + pub fn poll_recv_from( + &self, + buf: &mut [u8], + cx: &mut Context<'_>, + ) -> Poll> { + self.with_mut(|s, _| match s.recv_slice(buf) { + Ok((n, meta)) => Poll::Ready(Ok((n, meta))), + // No data ready + Err(udp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)), + Err(udp::RecvError::Exhausted) => { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Receive a datagram with a zero-copy function. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will call the provided function + /// with the number of bytes received and the remote endpoint and return + /// `Poll::Ready` with the function's returned value. + pub async fn recv_from_with(&mut self, f: F) -> R + where + F: FnOnce(&[u8], UdpMetadata) -> R, + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + match s.recv() { + Ok((buffer, endpoint)) => Poll::Ready(unwrap!(f.take())(buffer, endpoint)), + Err(udp::RecvError::Truncated) => unreachable!(), + Err(udp::RecvError::Exhausted) => { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + } + }) + }) + .await + } + + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when there is space in the buffer, from initial memory or after + /// dispatching datagrams on a full buffer. + pub async fn wait_send_ready(&self) { + poll_fn(move |cx| self.poll_send_ready(cx)).await + } + + /// Wait until a datagram can be sent. + /// + /// When no datagram can be sent (i.e. the buffer is full), this method will return + /// `Poll::Pending` and register the current task to be notified when + /// space is freed in the buffer after a datagram has been dispatched. + /// + /// When a datagram can be sent, this method will return `Poll::Ready`. + pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + // socket buffer is full wait until a datagram has been dispatched + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Send a datagram to the specified remote endpoint. + /// + /// This method will wait until the datagram has been sent. + /// + /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)` + pub async fn send_to(&self, buf: &[u8], remote_endpoint: T) -> Result<(), SendError> + where + T: Into, + { + let remote_endpoint: UdpMetadata = remote_endpoint.into(); + poll_fn(move |cx| self.poll_send_to(buf, remote_endpoint, cx)).await + } + + /// Send a datagram to the specified remote endpoint. + /// + /// When the datagram has been sent, this method will return `Poll::Ready(Ok())`. + /// + /// When the socket's send buffer is full, this method will return `Poll::Pending` + /// and register the current task to be notified when the buffer has space available. + /// + /// When the remote endpoint is not reachable, this method will return `Poll::Ready(Err(Error::NoRoute))`. + pub fn poll_send_to(&self, buf: &[u8], remote_endpoint: T, cx: &mut Context<'_>) -> Poll> + where + T: Into, + { + self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) { + // Entire datagram has been sent + Ok(()) => Poll::Ready(Ok(())), + Err(udp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(udp::SendError::Unaddressable) => { + // If no sender/outgoing port is specified, there is not really "no route" + if s.endpoint().port == 0 { + Poll::Ready(Err(SendError::SocketNotBound)) + } else { + Poll::Ready(Err(SendError::NoRoute)) + } + } + }) + } + + /// Send a datagram to the specified remote endpoint with a zero-copy function. + /// + /// This method will wait until the buffer can fit the requested size before + /// calling the function to fill its contents. + /// + /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)` + pub async fn send_to_with(&mut self, size: usize, remote_endpoint: T, f: F) -> Result + where + T: Into + Copy, + F: FnOnce(&mut [u8]) -> R, + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + match s.send(size, remote_endpoint) { + Ok(buffer) => Poll::Ready(Ok(unwrap!(f.take())(buffer))), + Err(udp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(udp::SendError::Unaddressable) => { + // If no sender/outgoing port is specified, there is not really "no route" + if s.endpoint().port == 0 { + Poll::Ready(Err(SendError::SocketNotBound)) + } else { + Poll::Ready(Err(SendError::NoRoute)) + } + } + } + }) + }) + .await + } + + /// Flush the socket. + /// + /// This method will wait until the socket is flushed. + pub async fn flush(&mut self) { + poll_fn(move |cx| { + self.with_mut(|s, _| { + if s.send_queue() == 0 { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + }) + .await + } + + /// Returns the local endpoint of the socket. + pub fn endpoint(&self) -> IpListenEndpoint { + self.with(|s, _| s.endpoint()) + } + + /// Returns whether the socket is open. + + pub fn is_open(&self) -> bool { + self.with(|s, _| s.is_open()) + } + + /// Close the socket. + pub fn close(&mut self) { + self.with_mut(|s, _| s.close()) + } + + /// Returns whether the socket is ready to send data, i.e. it has enough buffer space to hold a packet. + pub fn may_send(&self) -> bool { + self.with(|s, _| s.can_send()) + } + + /// Returns whether the socket is ready to receive data, i.e. it has received a packet that's now in the buffer. + pub fn may_recv(&self) -> bool { + self.with(|s, _| s.can_recv()) + } + + /// Return the maximum number packets the socket can receive. + pub fn packet_recv_capacity(&self) -> usize { + self.with(|s, _| s.packet_recv_capacity()) + } + + /// Return the maximum number packets the socket can receive. + pub fn packet_send_capacity(&self) -> usize { + self.with(|s, _| s.packet_send_capacity()) + } + + /// Return the maximum number of bytes inside the recv buffer. + pub fn payload_recv_capacity(&self) -> usize { + self.with(|s, _| s.payload_recv_capacity()) + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn payload_send_capacity(&self) -> usize { + self.with(|s, _| s.payload_send_capacity()) + } + + /// Set the hop limit field in the IP header of sent packets. + pub fn set_hop_limit(&mut self, hop_limit: Option) { + self.with_mut(|s, _| s.set_hop_limit(hop_limit)) + } +} + +impl Drop for UdpSocket<'_> { + fn drop(&mut self) { + self.stack.with_mut(|i| i.sockets.remove(self.handle)); + } +} + +fn _assert_covariant<'a, 'b: 'a>(x: UdpSocket<'b>) -> UdpSocket<'a> { + x +} diff --git a/embassy/embassy-nrf/CHANGELOG.md b/embassy/embassy-nrf/CHANGELOG.md new file mode 100644 index 0000000..f8d6ab7 --- /dev/null +++ b/embassy/embassy-nrf/CHANGELOG.md @@ -0,0 +1,53 @@ +# Changelog for embassy-nrf + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-08-05 + +- Support for NRF chips: + - nrf51 + - nrf9151 +- Support for new peripherals: + - EGU + - radio - low-level support for IEEE 802.15.4 and BLE via radio peripheral +- Peripheral changes: + - gpio: Drop GPIO Pin generics (API break) + - pdm: Fix gain register value derivation + - pwm: + - Expose `duty` method + - Expose `pwm::PWM_CLK_HZ` and add `is_enabled` method + - Allow specifying OutputDrive for PWM channels + - Fix infinite loop + - spim: + - Reduce trace-level messages ("Copying SPIM tx buffer..") + - Support configuring bit order for bus + - Allow specifying OutputDrive for SPI pins + - Add bounds checks for EasyDMA buffer size + - Implement chunked DMA transfers + - uart: + - Add support for rx- or tx-only BufferedUart + - Implement splitting Rx/Tx + - Add support for handling RX errors +- Miscellaneous changes: + - Add `collapse_debuginfo` to fmt.rs macros. + - Drop `sealed` mod + - nrf52840: Add dcdc voltage parameter to configure REG0 regulator + +## 0.1.0 - 2024-01-12 + +- First release with support for following NRF chips: + - nrf52805 + - nrf52810 + - nrf52811 + - nrf52820 + - nrf52832 + - nrf52833 + - nrf52840 + - nrf5340 + - nrf9160 + diff --git a/embassy/embassy-nrf/Cargo.toml b/embassy/embassy-nrf/Cargo.toml new file mode 100644 index 0000000..48f80bb --- /dev/null +++ b/embassy/embassy-nrf/Cargo.toml @@ -0,0 +1,164 @@ +[package] +name = "embassy-nrf" +version = "0.2.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Embassy Hardware Abstraction Layer (HAL) for nRF series microcontrollers" +keywords = ["embedded", "async", "nordic", "nrf", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-nrf" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-nrf-v$VERSION/embassy-nrf/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-nrf/src/" + +features = ["time", "defmt", "unstable-pac", "gpiote", "time-driver-rtc1"] +flavors = [ + { regex_feature = "nrf51", target = "thumbv6m-none-eabi" }, + { regex_feature = "nrf52.*", target = "thumbv7em-none-eabihf" }, + { regex_feature = "nrf53.*", target = "thumbv8m.main-none-eabihf" }, + { regex_feature = "nrf54.*", target = "thumbv8m.main-none-eabihf" }, + { regex_feature = "nrf91.*", target = "thumbv8m.main-none-eabihf" }, +] + +[package.metadata.docs.rs] +features = ["nrf52840", "time", "defmt", "unstable-pac", "gpiote", "time-driver-rtc1"] +rustdoc-args = ["--cfg", "docsrs"] + +[features] +default = ["rt"] +## Cortex-M runtime (enabled by default) +rt = ["nrf-pac/rt"] + +## Enable features requiring `embassy-time` +time = ["dep:embassy-time"] + +## Enable defmt +defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "embassy-usb-driver/defmt", "embassy-embedded-hal/defmt"] + +## Reexport the PAC for the currently enabled chip at `embassy_nrf::pac` (unstable) +unstable-pac = [] +# This is unstable because semver-minor (non-breaking) releases of embassy-nrf may major-bump (breaking) the PAC version. +# If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC. +# There are no plans to make this stable. + +## Enable GPIO tasks and events +gpiote = [] + +## Use RTC1 as the time driver for `embassy-time`, with a tick rate of 32.768khz +time-driver-rtc1 = ["_time-driver"] + +## Allow using the NFC pins as regular GPIO pins (P0_09/P0_10 on nRF52, P0_02/P0_03 on nRF53) +nfc-pins-as-gpio = [] + +## Allow using the RST pin as a regular GPIO pin. +## * nRF52805, nRF52810, nRF52811, nRF52832: P0_21 +## * nRF52820, nRF52833, nRF52840: P0_18 +reset-pin-as-gpio = [] + +## Implements the MultiwriteNorFlash trait for QSPI. Should only be enabled if your external +## flash supports the semantics described [here](https://docs.rs/embedded-storage/0.3.1/embedded_storage/nor_flash/trait.MultiwriteNorFlash.html) +qspi-multiwrite-flash = [] + +#! ### Chip selection features +## nRF51 +nrf51 = ["nrf-pac/nrf51", "_nrf51"] +## nRF52805 +nrf52805 = ["nrf-pac/nrf52805", "_nrf52"] +## nRF52810 +nrf52810 = ["nrf-pac/nrf52810", "_nrf52"] +## nRF52811 +nrf52811 = ["nrf-pac/nrf52811", "_nrf52"] +## nRF52820 +nrf52820 = ["nrf-pac/nrf52820", "_nrf52"] +## nRF52832 +nrf52832 = ["nrf-pac/nrf52832", "_nrf52", "_nrf52832_anomaly_109"] +## nRF52833 +nrf52833 = ["nrf-pac/nrf52833", "_nrf52", "_gpio-p1"] +## nRF52840 +nrf52840 = ["nrf-pac/nrf52840", "_nrf52", "_gpio-p1"] +## nRF5340 application core in Secure mode +nrf5340-app-s = ["_nrf5340-app", "_s"] +## nRF5340 application core in Non-Secure mode +nrf5340-app-ns = ["_nrf5340-app", "_ns"] +## nRF5340 network core +nrf5340-net = ["_nrf5340-net"] +## nRF54L15 application core in Secure mode +nrf54l15-app-s = ["_nrf54l15-app", "_s"] +## nRF54L15 application core in Non-Secure mode +nrf54l15-app-ns = ["_nrf54l15-app", "_ns"] + +## nRF9160 in Secure mode +nrf9160-s = ["_nrf9160", "_s", "_nrf91"] +## nRF9160 in Non-Secure mode +nrf9160-ns = ["_nrf9160", "_ns", "_nrf91"] +## The nRF9120 is the internal part number for the nRF9161 and nRF9151. +## nRF9120 in Secure mode +nrf9120-s = ["_nrf9120", "_s", "_nrf91"] +nrf9151-s = ["nrf9120-s"] +nrf9161-s = ["nrf9120-s"] +## nRF9120 in Non-Secure mode +nrf9120-ns = ["_nrf9120", "_ns", "_nrf91"] +nrf9151-ns = ["nrf9120-ns"] +nrf9161-ns = ["nrf9120-ns"] + +# Features starting with `_` are for internal use only. They're not intended +# to be enabled by other crates, and are not covered by semver guarantees. + +_nrf5340-app = ["_nrf5340", "nrf-pac/nrf5340-app"] +_nrf5340-net = ["_nrf5340", "nrf-pac/nrf5340-net"] +_nrf5340 = ["_gpio-p1", "_dppi"] +_nrf54l15-app = ["_nrf54l15", "nrf-pac/nrf54l15-app"] +_nrf54l15 = ["_nrf54l", "_gpio-p1", "_gpio-p2"] +_nrf54l = ["_dppi"] + +_nrf9160 = ["nrf-pac/nrf9160", "_dppi"] +_nrf9120 = ["nrf-pac/nrf9120", "_dppi"] +_nrf52 = ["_ppi"] +_nrf51 = ["_ppi"] +_nrf91 = [] + +_time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768", "dep:embassy-time-queue-driver"] + +# trustzone state. +_s = [] +_ns = [] + +_ppi = [] +_dppi = [] +_gpio-p1 = [] +_gpio-p2 = [] + +# Errata workarounds +_nrf52832_anomaly_109 = [] + +[dependencies] +embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } +embassy-time-queue-driver = { version = "0.1", path = "../embassy-time-queue-driver", optional = true } +embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } +embassy-sync = { version = "0.6.1", path = "../embassy-sync" } +embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } +embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" } +embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } + +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-io = { version = "0.6.0" } +embedded-io-async = { version = "0.6.1" } + +nrf-pac = { git = "https://github.com/embassy-rs/nrf-pac", rev = "52e3a757f06035c94291bfc42b0c03f71e4d677e" } + +defmt = { version = "0.3", optional = true } +bitflags = "2.4.2" +log = { version = "0.4.14", optional = true } +cortex-m-rt = ">=0.6.15,<0.8" +cortex-m = "0.7.6" +critical-section = "1.1" +rand_core = "0.6.3" +fixed = "1.10.0" +embedded-storage = "0.3.1" +embedded-storage-async = "0.4.1" +cfg-if = "1.0.0" +document-features = "0.2.7" diff --git a/embassy/embassy-nrf/README.md b/embassy/embassy-nrf/README.md new file mode 100644 index 0000000..3df5f1f --- /dev/null +++ b/embassy/embassy-nrf/README.md @@ -0,0 +1,76 @@ +# 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 mode and handling interrupts, so that applications can focus on more important matters. + +NOTE: The Embassy HALs can be used both for non-async and async operations. For async, you can choose which runtime you want to use. + +For a complete list of available peripherals and features, see the [embassy-nrf documentation](https://docs.embassy.dev/embassy-nrf). + +## Hardware support + +The `embassy-nrf` HAL supports most variants of the nRF family: + +* nRF51 ([examples](https://github.com/embassy-rs/embassy/tree/main/examples/nrf51)) +* nRF52 ([examples](https://github.com/embassy-rs/embassy/tree/main/examples/nrf52840)) +* nRF53 ([examples](https://github.com/embassy-rs/embassy/tree/main/examples/nrf5340)) +* nRF91 ([examples](https://github.com/embassy-rs/embassy/tree/main/examples/nrf9160)) + +Most peripherals are supported, but can vary between chip families. To check what's available, make sure to pick the MCU you're targeting in the top menu in the [documentation](https://docs.embassy.dev/embassy-nrf). + +For MCUs with TrustZone support, both Secure (S) and Non-Secure (NS) modes are supported. Running in Secure mode +allows running Rust code without a SPM or TF-M binary, saving flash space and simplifying development. + +## Time driver + +If the `time-driver-rtc1` feature is enabled, the HAL uses the RTC peripheral as a global time driver for [embassy-time](https://crates.io/crates/embassy-time), with a tick rate of 32768 Hz. + +## Embedded-hal + +The `embassy-nrf` HAL implements the traits from [embedded-hal](https://crates.io/crates/embedded-hal) (v0.2 and 1.0) and [embedded-hal-async](https://crates.io/crates/embedded-hal-async), as well as [embedded-io](https://crates.io/crates/embedded-io) and [embedded-io-async](https://crates.io/crates/embedded-io-async). + +## Interoperability + +This crate can run on any executor. + +Optionally, some features requiring [`embassy-time`](https://crates.io/crates/embassy-time) can be activated with the `time` feature. If you enable it, +you must link an `embassy-time` driver in your project. + +## 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: + +```rust,ignore +// 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. diff --git a/embassy/embassy-nrf/src/buffered_uarte.rs b/embassy/embassy-nrf/src/buffered_uarte.rs new file mode 100644 index 0000000..b55e70a --- /dev/null +++ b/embassy/embassy-nrf/src/buffered_uarte.rs @@ -0,0 +1,905 @@ +//! Async buffered UART driver. +//! +//! Note that discarding a future from a read or write operation may lead to losing +//! data. For example, when using `futures_util::future::select` and completion occurs +//! on the "other" future, you should capture the incomplete future and continue to use +//! it for the next read or write. This pattern is a consideration for all IO, and not +//! just serial communications. +//! +//! Please also see [crate::uarte] to understand when [BufferedUarte] should be used. + +use core::cmp::min; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::slice; +use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU8, AtomicUsize, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::atomic_ring_buffer::RingBuffer; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use pac::uarte::vals; +// Re-export SVD variants to allow user to directly set values +pub use pac::uarte::vals::{Baudrate, ConfigParity as Parity}; + +use crate::gpio::{AnyPin, Pin as GpioPin}; +use crate::interrupt::typelevel::Interrupt; +use crate::ppi::{ + self, AnyConfigurableChannel, AnyGroup, Channel, ConfigurableChannel, Event, Group, Ppi, PpiGroup, Task, +}; +use crate::timer::{Instance as TimerInstance, Timer}; +use crate::uarte::{configure, configure_rx_pins, configure_tx_pins, drop_tx_rx, Config, Instance as UarteInstance}; +use crate::{interrupt, pac, Peripheral, EASY_DMA_SIZE}; + +pub(crate) struct State { + tx_buf: RingBuffer, + tx_count: AtomicUsize, + + rx_buf: RingBuffer, + rx_started: AtomicBool, + rx_started_count: AtomicU8, + rx_ended_count: AtomicU8, + rx_ppi_ch: AtomicU8, +} + +/// UART error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + // No errors for now +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + tx_buf: RingBuffer::new(), + tx_count: AtomicUsize::new(0), + + rx_buf: RingBuffer::new(), + rx_started: AtomicBool::new(false), + rx_started_count: AtomicU8::new(0), + rx_ended_count: AtomicU8::new(0), + rx_ppi_ch: AtomicU8::new(0), + } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + //trace!("irq: start"); + let r = U::regs(); + let ss = U::state(); + let s = U::buffered_state(); + + if let Some(mut rx) = unsafe { s.rx_buf.try_writer() } { + let buf_len = s.rx_buf.len(); + let half_len = buf_len / 2; + + if r.events_error().read() != 0 { + r.events_error().write_value(0); + let errs = r.errorsrc().read(); + r.errorsrc().write_value(errs); + + if errs.overrun() { + panic!("BufferedUarte overrun"); + } + } + + // Received some bytes, wake task. + if r.inten().read().rxdrdy() && r.events_rxdrdy().read() != 0 { + r.intenclr().write(|w| w.set_rxdrdy(true)); + r.events_rxdrdy().write_value(0); + ss.rx_waker.wake(); + } + + if r.events_endrx().read() != 0 { + //trace!(" irq_rx: endrx"); + r.events_endrx().write_value(0); + + let val = s.rx_ended_count.load(Ordering::Relaxed); + s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed); + } + + if r.events_rxstarted().read() != 0 || !s.rx_started.load(Ordering::Relaxed) { + //trace!(" irq_rx: rxstarted"); + let (ptr, len) = rx.push_buf(); + if len >= half_len { + r.events_rxstarted().write_value(0); + + //trace!(" irq_rx: starting second {:?}", half_len); + + // Set up the DMA read + r.rxd().ptr().write_value(ptr as u32); + r.rxd().maxcnt().write(|w| w.set_maxcnt(half_len as _)); + + let chn = s.rx_ppi_ch.load(Ordering::Relaxed); + + // Enable endrx -> startrx PPI channel. + // From this point on, if endrx happens, startrx is automatically fired. + ppi::regs().chenset().write(|w| w.0 = 1 << chn); + + // It is possible that endrx happened BEFORE enabling the PPI. In this case + // the PPI channel doesn't trigger, and we'd hang. We have to detect this + // and manually start. + + // check again in case endrx has happened between the last check and now. + if r.events_endrx().read() != 0 { + //trace!(" irq_rx: endrx"); + r.events_endrx().write_value(0); + + let val = s.rx_ended_count.load(Ordering::Relaxed); + s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed); + } + + let rx_ended = s.rx_ended_count.load(Ordering::Relaxed); + let rx_started = s.rx_started_count.load(Ordering::Relaxed); + + // If we started the same amount of transfers as ended, the last rxend has + // already occured. + let rxend_happened = rx_started == rx_ended; + + // Check if the PPI channel is still enabled. The PPI channel disables itself + // when it fires, so if it's still enabled it hasn't fired. + let ppi_ch_enabled = ppi::regs().chen().read().ch(chn as _); + + // if rxend happened, and the ppi channel hasn't fired yet, the rxend got missed. + // this condition also naturally matches if `!started`, needed to kickstart the DMA. + if rxend_happened && ppi_ch_enabled { + //trace!("manually starting."); + + // disable the ppi ch, it's of no use anymore. + ppi::regs().chenclr().write(|w| w.set_ch(chn as _, true)); + + // manually start + r.tasks_startrx().write_value(1); + } + + rx.push_done(half_len); + + s.rx_started_count.store(rx_started.wrapping_add(1), Ordering::Relaxed); + s.rx_started.store(true, Ordering::Relaxed); + } else { + //trace!(" irq_rx: rxstarted no buf"); + r.intenclr().write(|w| w.set_rxstarted(true)); + } + } + } + + // ============================= + + if let Some(mut tx) = unsafe { s.tx_buf.try_reader() } { + // TX end + if r.events_endtx().read() != 0 { + r.events_endtx().write_value(0); + + let n = s.tx_count.load(Ordering::Relaxed); + //trace!(" irq_tx: endtx {:?}", n); + tx.pop_done(n); + ss.tx_waker.wake(); + s.tx_count.store(0, Ordering::Relaxed); + } + + // If not TXing, start. + if s.tx_count.load(Ordering::Relaxed) == 0 { + let (ptr, len) = tx.pop_buf(); + let len = len.min(EASY_DMA_SIZE); + if len != 0 { + //trace!(" irq_tx: starting {:?}", len); + s.tx_count.store(len, Ordering::Relaxed); + + // Set up the DMA write + r.txd().ptr().write_value(ptr as u32); + r.txd().maxcnt().write(|w| w.set_maxcnt(len as _)); + + // Start UARTE Transmit transaction + r.tasks_starttx().write_value(1); + } + } + } + + //trace!("irq: end"); + } +} + +/// Buffered UARTE driver. +pub struct BufferedUarte<'d, U: UarteInstance, T: TimerInstance> { + tx: BufferedUarteTx<'d, U>, + rx: BufferedUarteRx<'d, U, T>, +} + +impl<'d, U: UarteInstance, T: TimerInstance> Unpin for BufferedUarte<'d, U, T> {} + +impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { + /// Create a new BufferedUarte without hardware flow control. + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] + pub fn new( + uarte: impl Peripheral